Home > other >  Extract variables from string based on template
Extract variables from string based on template

Time:03-15

I have a template string which has merge fields:

Example:

let templateStr = '[!field]: value entered is too large. (max length=[!limit])';

I have an error message string which follows the above mentioned template format, here the field name and max length can vary according to the field that user is trying to save.

Example:

let errorMessage = 'Country: value entered is too large. (max length=80)';

Here I am trying to extract the variables (field, limit) referenced in the templateStr based on the errorMessage.

extractTemplateVar(errorMessage, templateStr) // should return field='Country' and limit='80'

if errorMessage = 'Email: value entered is too large. (max length=100); then,

extractTemplateVar(errorMessage, templateStr) // should return field='Email' and limit='100'

I have tried using regex, to match and extract variables from string based on template. its not working as intended

let pattern = '(. ): data value too large: .*(max length=(. ))';
new RegExp(pattern ).exec(errorMessage);

Guys, please help me resolve this issue. Thanks in advance guys.

CodePudding user response:

This is a bit gross. There might be a better way.

Part 1, extract the variable names from the template.

Part 2, split the template by those names to get the "rest of the template"

Part 3, convert "rest of template" fragments to regex compatible format. I probably missed a few things that need to be escaped

Part 4, join the template fragments back to a regex-compatible string, separated by (.*?) - which will capture the actual values of the variables

Part 5, run the constructed regex against the message, to extract variable values

Part 6, generate a return structure from the variable names and values

let templateStr = '[!field]: value entered is too large. (max length=[!limit])';
let errorMessage = 'Country: value entered is too large. (max length=80)';

const extractTemplateVars = (template, message) => {
  let regex = /\[\!(.*?)\]/g;
  let splitRegex = /\[\!.*?\]/g;

  let match, matches = [];
  // Part 1
  while (match = regex.exec(template)) {
    matches.push(match[1]);
  }

  // Part 2
  let arr = template.split(splitRegex);
  // Part 3
  arr = arr.map(s => 
    s.replaceAll("\\", "\\\\")
     .replaceAll("(", "\\(")
     .replaceAll(")", "\\)")
     .replaceAll(".", "\\.")
  );
  // Part 4
  let reStr = arr.join("(.*?)");
  let re = new RegExp(reStr, "g");
  
  // Part 5
  let m = re.exec(message);
  let [garbage, ...values] = m;
  
  // Part 6
  let output = matches.map((match, i) => ({[match]: values[i]}));
  
  console.log(output);
};

extractTemplateVars(templateStr, errorMessage);

CodePudding user response:

Based on your example strings, as a pattern you could use 2 capture groups:

([^:\n] ):.*?\(max length=([^\s=()] )\)

The pattern matches:

  • ([^:] ) Capture group 1, match 1 times any character except for a : or newline
  • :.*? Match : and the rest of the line no greedy
  • \(max length= Match (max length=
    • ([^\s=()] ) Capture group 2, match 1 times any character except for = ( )
  • \) Match )

Regex demo

const regex = /([^:\n] ):.*?\(max length=([^\s=()] )\)/;
const errorMessage = 'Email: value entered is too large. (max length=100)';
const m = errorMessage.match(regex);

if (m) {
  console.log(`field='${m[1]}' and limit='${m[2]}'`);
}

If you want to match only digits for the limit, you can also use \d

([^:\n] ):.*?\(max length=(\d )\)

CodePudding user response:

This is a novel task indeed. Here's the steps I'd take to tackle this:

  1. Extract templates ([!TEMPLATE_NAME]) using regex
  2. In the template string, get all the text before and after the template, later to be used in regex lookarounds
  3. Create a regex with dynamic lookarounds. Note that we escaped the regular text to avoid strange behavior in our regex
  4. Match the text inside the given string and use it
  5. $$$ Profit! $$$

And here's your code:

let templateStr = '[!field]: value entered is too large. (max length=[!limit])';
let errorMessage = 'Country: value entered is too large. (max length=80)';

function extractTemplateVar(str, template) {
  const match = /\[!. ?\]/g,
        data = [],
        result = {},
        escapeRegex = str => str.replace(/[-\/\\^$* ?.()|[\]{}]/g, "\\$&");
  
  let matching;
  while(matching = match.exec(template)) {
    const index = matching.index;
    const str = matching[0];
    const text = str.match(/(?<=\[!). ?(?=\])/)[0];
    const endIndex = str.length   index;
    const front = escapeRegex(template.substr(0, index).replace(/\[!. ?\]/g, "::TMP::"));
    const back = escapeRegex(template.substr(endIndex).replace(/\[!. ?\]/g, "::TMP::"));
    data.push({ text, back, front });
  }
  
  data.forEach(n => {
    const re = new RegExp(`(?<=${n.front.replaceAll("::TMP::", ".*?")}). ?(?=${n.back.replaceAll("::TMP::", ".*?")})`);
    const findText = str.match(re)[0];
    result[n.text] = findText;
  });
  
  return result;
}

console.log(extractTemplateVar(errorMessage, templateStr));

  • Related