skip to Main Content

I have a string received from an editor, which includes HTML tags, like this:

const htmlString = "<div>Account <span contenteditable="false">{{account}}</span></div>. 
<div>Hello <span contenteditable="false">{{hello}}</span></div>"

In this content, there are two variables with the format {{account}} and {{hello}}.

In my database, I store variable data in the format { key: string, value: string, isDefault: boolean }:

[
   { "key" : "account", "value" : "", "isDefault" : true, },
   { "key" : "hello", "value" : "Hello everyone", "isDefault" : false }
]

First, I use a function to remove HTML tags:

const blockTags = /<(div|h1|h2|h3|h4|h5|h6|p|ul|ol|li|br)[^>]*>/gi;
const inlineTags = /</?(span|a|strong|em|b|i|u)[^>]*>/gi;

let content = htmlString.replace(blockTags, 'n').replace(/</(div|h1|h2|h3|h4|h5|h6|p)>/gi, 'n');
content = content.replace(inlineTags, '');
content = content.replace(/<[^>]+>/g, '');
content = content.replace(/ns*n/g, 'n').trim();

Then, I extract the variables:

const variables = (content.match(/{{(.*?)}}/gi) || []).map((item) => item.replace(/{{|}}/g, ''));

Finally, I use a function to replace all variables with their corresponding values from the database, if variable is default (isDefault = true), i replace by dynamic value based on config by my system rule:

const objVariables = variables.reduce((acc, { key, value, isDefault }) => {
    acc[key] = { value, isDefault };
    return acc;
  }, {});

const result = content.replace(/{{(.*?)}}/g, (match, variable) => {
   const variableData = objVariables[variable];
   if (variableData && variableData.isDefault) {
      if (variable === "account") {
         return "ACCOUNT_NAME";
      }
    }
   return variableData ? variableData.value : match;
});

I want to replace all variables in the HTML string with the values stored in the database, but I think my code isn’t the best solution and may be slow. I’m looking for an optimized solution or any suggestions.

2

Answers


  1. I think you should just iterate over your variables and replace if they’re found in the string… There’s no need to strip tags.

    EDIT:
    Note that if you want to save your result with HTML, then this is the best option because it can also parse the variables in the HTML attributes like <div id="account-{{account}}">. Also, regardless of how the string is obtained, the code below works over a string, not the HTML itself.

    const myVariables =
    [
      { "key" : "account",  "value" : "",               "isDefault" : true  },
      { "key" : "hello",    "value" : "Hello everyone", "isDefault" : false },
    ];
    
    let myString = myContent.innerHTML;
    
    myVariables.forEach
    (
      ( { key, value, isDefault } ) => myString = myString.replaceAll
      (
        `{{${key}}}`,
        (
          ( isDefault && ( key === 'account' ) )
          ? 'ACCOUNT_NAME'
          : value ?? key
        )
      )
    );
    
    // You can keep the parsed string. This is just for show:
    myContent.innerHTML = myString;
    console.log('New String (with HTML):', myString);
    <div id="myContent">
      <div id="account-{{account}}">Account <span contenteditable="false">{{account}}</span>.</div>
      <div>Hello <span contenteditable="false">{{hello}}</span></div>
    </div>

    Note that in the example above the html was not even touched.

    EDIT: Updated Answer

    If you want only the text fragments (stripping all HTML tags), you can achieve almost the same way as the above function. See below:

    const myVariables =
    [
      { "key" : "account",  "value" : "",               "isDefault" : true  },
      { "key" : "hello",    "value" : "Hello everyone", "isDefault" : false },
    ];
    
    // This string is just an example. Don't bother with the structure I used.
    let myString = // No line-breaks.
      '<div>Account: <span contenteditable="false">{{account}}</span></div>'+
      '<div>Greeting: <span contenteditable="false">{{hello}}</span></div>';
    
    // Let the browser strip the HTML for you...
    let virtualElement = document.createElement('body');
    {
      virtualElement.innerHTML = myString
        // You can uncomment the code line below,
        // but you'll see double line-breaks often.
        //.replaceAll(/<(div|h[1-6]|p|br)[^>]*>/gi, 'n')
        .replaceAll(/</(div|h[1-6]|p)>/gi, 'n');
      
      // The browser strips all other html tags...
      myString = virtualElement.textContent;
    }
    virtualElement = null;
    
    // Remains the same as the previous example...
    myVariables.forEach
    (
      ( { key, value, isDefault } ) => myString = myString.replaceAll
      (
        `{{${key}}}`,
        (
          ( isDefault && ( key === 'account' ) )
          ? 'ACCOUNT_NAME'
          : value ?? key
        )
      )
    );
    
    // Output
    console.log(myString);

    But, if you want to retain the HTML (untouched) and just change the text fragments, you can do as below:

    const myVariables =
    [
      { "key" : "account",  "value" : "",               "isDefault" : true  },
      { "key" : "hello",    "value" : "Hello everyone", "isDefault" : false },
    ];
    
    // This string is just an example. Don't bother with the structure I used.
    let myString =
      '<div>Account: <span contenteditable="false">{{account}}</span></div>'+"n"+
      '<div>Greeting: <span contenteditable="false">{{hello}}</span></div>'+"n";
    
    // TEST:
    const newString = evaluateString(myString);
    
    // Inserting with HTML in the document:
    document.body.innerHTML = newString;
    
    // Showing in the console:
    console.log(newString);
    
    // Remains almost the same as the previous example...
    // But wrapped in a function.
    function parseString ( myString )
    {
      myVariables.forEach
      (
        ( { key, value, isDefault } ) => myString = myString.replaceAll
        (
          `{{${key}}}`,
          (
            ( isDefault && ( key === 'account' ) )
            ? 'ACCOUNT_NAME'
            : value ?? key
          )
        )
      );
      
      return myString;
    }
    
    // New function
    function evaluateString ( myString )
    {
      const virtualElement = document.createElement('body');
      virtualElement.innerHTML = myString;
      
      return evaluateTexts(virtualElement).innerHTML;
      
    }
    
    // New function
    function evaluateTexts ( myElement )
    {
      for ( const childNode of myElement.childNodes )
      {
        if ( childNode.nodeName == '#text' )
        {
          childNode.textContent = parseString(childNode.textContent);
        }
        
        else
        {
          evaluateTexts(childNode);
        }
      }
      return myElement;
    }
    Login or Signup to reply.
  2. const htmlString = `<div>Account <span contenteditable="false">{{account}}</span></div>.
    <div>Hello <span contenteditable="false">{{hello}}</span></div>`;
    
    const databaseVariables = [
        { key: "account", value: "", isDefault: true },
        { key: "hello", value: "Hello everyone", isDefault: false },
    ];
    
    // Step 1: Convert database array into a Map for faster lookups
    const variableMap = new Map(databaseVariables.map(item => [item.key, item]));
    
    // Step 2: Function to strip HTML tags while keeping content structure
    function stripHtml(html) {
        // Replace block tags with newline and inline tags with nothing
        return html
            .replace(/<(div|h[1-6]|p|br)[^>]*>/gi, 'n') // Block-level elements
            .replace(/</(div|h[1-6]|p)>/gi, 'n')        // Block-level end tags
            .replace(/</?(span|a|strong|em|b|i|u)[^>]*>/gi, '') // Inline tags
            .replace(/ns*n/g, 'n')                    // Remove excess newlines
            .replace(/<[^>]+>/g, '')                      // Remove remaining tags
            .trim();
    }
    
    // Step 3: Function to replace variables in content
    function replaceVariables(content) {
        return content.replace(/{{(.*?)}}/g, (match, variable) => {
            const variableData = variableMap.get(variable);
            
            if (variableData) {
                if (variableData.isDefault && variable === "account") {
                    return "ACCOUNT_NAME";
                }
                return variableData.value || match;
            }
            return match;
        });
    }
    
    const strippedContent = stripHtml(htmlString);
    const finalResult = replaceVariables(strippedContent);
    
    console.log(finalResult);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search