block by curran 8f65dc5fbdd07fb0271a2a66479e4be9

Indenter

Full Screen

I was searching for a way to indent a generated HTML fragment string, and was frustrated that all the packages I could find for doing this seem overly complex for the task at hand:

So I thought to myself, how hard could it be to write a function that indents HTML? It should be about a page of code, and would be a fun little puzzle. This is the result from the experiment, a small function that indents an HTML or XML fragment string.

If you find this useful and would like it released as a proper package, please let me know.

Built with blockbuilder.org

web counter

index.html

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
</head>

<body>
  <pre id="indented-pre" style="font-size: 1.78em"></pre>
  <script>

// Indents an HTML or XML fragment string.
function indent(str, numSpaces){
  
  // The number of spaces to indent by (integer, defaults to 2).
  if(typeof numSpaces === "undefined"){
    numSpaces = 2;
  }
  
  var spaces = "";
  for(var i = 0; i < numSpaces; i++){
    spaces += " ";
  }

  // The current indentation level (integer).
  var indentation = 0;

  // The child count context stack.
  var stack = [];

  // The output string buffer (array of strings).
  var output = [];

  // For each character...
  for (var i = 0; i < str.length; i++){

    // Detect opening or closing tags.
    if(str[i] === "<"){

      // Detect a closing tag.
      if(i < str.length - 1 && str[i + 1] === "/"){

        // If there are any children, decrease indent.
        if(stack.pop() !== 0){
          indentation--;
        } else {

          // If there are no childten,
          // place closing tags on the same line as their opening tag.
          output.push("<");
          continue;
        }

      // Detect an opening tag.
      } else {

        // Increase indentation only if this is the first child.
        if(stack[stack.length - 1] === 0){
          indentation++;
        }

        // Increment the child count for the current context.
        stack[stack.length - 1]++;

        // Push a new child count context onto the stack.
        stack.push(0);
      }

      // Output a newline.
      if(i !== 0){
        output.push("\n");
      }
      
      // Output indentation spaces.
      for(var j = 0; j < indentation; j++){
        output.push(spaces);
      }
    }

    // Append the current character to the output buffer.
    output.push(str[i]);
  }
  
  // Concatenate the array of strings in the output buffer.
  return output.join("");
}
    
// A generated HTML fragment string that we want indented.
var str = '<svg height="500" width="960"><g class="reactive-vis-margin-g" transform="translate(50,50)"><g class="reactive-vis-scatter-layer"><g class="reactive-vis-scatter-mark" transform="translate(0,0)" height="10" width="10"><circle class="reactive-vis-circle"></circle></g><g class="reactive-vis-scatter-mark" transform="translate(66.15384615384647,400)" height="16.690459207925603" width="16.690459207925603"><circle class="reactive-vis-circle"></circle></g><g class="reactive-vis-scatter-mark" transform="translate(793.8461538461535,50.00000000000006)" height="20" width="20"><circle class="reactive-vis-circle"></circle></g><g class="reactive-vis-scatter-mark" transform="translate(860,200)" height="19.820624179302296" width="19.820624179302296"><circle class="reactive-vis-circle"></circle></g></g></g></svg>';

// Compute the indented string.
var indented = indent(str);

// Display the indented string in the <pre> tag.
var pre = document.getElementById("indented-pre");
pre.textContent = indented;

// Vary the indentation to demonstrate the functionality clearly.
setInterval(function (){
  var numSpaces = Math.floor((Math.sin(Date.now() / 1000) + 1) * 6);
  pre.textContent = indent(str, numSpaces);
}, 200);

  </script>
</body>