如何为我在 JavaScript 中编写的 HTML 生成器添加适当的缩进

How can I add proper indentation to this HTML generator that I wrote in JavaScript

我编写了一个函数,它接受一个表示 DOM 树的对象并输出 HTML 字符串。我使用了 DFS 方法来遍历对象。

const tree = {
  children: [
    { children: [{ children: ['bar'], tag: 'span' }], tag: 'div' },
    { children: ['baz'], tag: 'div' },
  ],
  tag: 'body',
}

function fromTreeToString(root) {
  if (!root.children) {
    return root
  }
  return (
    `<${root.tag}>` + '\n' +
    `${root.children.map((node) => fromTreeToString(node)).join('\n')}` + '\n' +
    `</${root.tag}>`
  )
}

fromTreeToString(tree)

输出为

<body> 
<div> 
<span> 
bar 
</span> 
</div> 
<div> 
baz 
</div> 
</body> 

我想知道如何修改此函数以使输出具有正确的缩进。理想情况下,输出应该类似于

<body> 
  <div> 
    <span> 
      bar 
    </span> 
  </div> 
  <div> 
    baz 
  </div> 
</body> 

理想情况下,此 API 的用户可以自定义缩进,例如选项卡与 space

您可以将适当的缩进传递给您的函数:

function fromTreeToString(root, indent) {
  indent = indent || '';

  if (!root.children) {
    return indent + root
  }
  return (
    `${indent}<${root.tag}>` + '\n' +
    `${root.children.map((node) => fromTreeToString(node, indent + '. ')).join('\n')}` + '\n' +
    `${indent}</${root.tag}>`
  )
}

这里,每次递归都会增加缩进'. '。模板字符串中的中间行没有直接缩进。它从递归调用中的 indent + '. ' 中获取缩进。

在此版本中,您传递缩进字符串并返回一个函数,该函数采用树结构和 returns 格式化字符串:

const fromTreeToString = (indent = '  ', depth = 0) => (node) =>
  `${indent.repeat(depth)}${node.tag ? `<${node .tag
  }>\n${(node.children || []).map(c => fromTreeToString(indent, depth + 1)(c)) .join ('\n')
  }\n${indent.repeat(depth)}</${node .tag}>` : node}`


const tree = {children: [{children: [{children: ['bar'], tag: 'span'}], tag: 'div'}, {children: ['baz'], tag: 'div'}, ], tag: 'body'}

const twoSpace = fromTreeToString('  ')
const fourSpace = fromTreeToString('    ')
const oneTab = fromTreeToString('\t')

console .log (twoSpace(tree))
console .log (fourSpace(tree))
console .log (oneTab(tree))
.as-console-wrapper {max-height: 100% !important; top: 0}

注意您的标题具有误导性,因为您不是解析HTML而是格式化它。

更新

这里是没有higher-order功能的版本。您可以传递任何您喜欢的缩进字符串。不通过就加两个空格。

const fromTreeToString = (node, indent = '  ', depth = 0) =>
  `${indent.repeat(depth)}${node.tag ? `<${node .tag
  }>\n${(node.children || []).map(c => fromTreeToString(c, indent, depth + 1)) .join ('\n')
  }\n${indent.repeat(depth)}</${node .tag}>` : node}`


const tree = {children: [{children: [{children: ['bar'], tag: 'span'}], tag: 'div'}, {children: ['baz'], tag: 'div'}, ], tag: 'body'}

console .log (fromTreeToString(tree, '  '))
console .log (fromTreeToString(tree, '    '))
console .log (fromTreeToString(tree, '\t'))

您也可以首先使用递归到 return 一个 数组 行,并且只在末尾连接它们:

function fromTreeToString(root, indent='\t') {
    const recur = root => root.children ? [
            `<${root.tag}>`, 
            ...root.children.flatMap(recur).map(s => indent + s),
            `</${root.tag}>`
        ] : root;
    return recur(root).join('\n');
}

const tree = {children: [{ children: [{ children: ['bar'], tag: 'span' }], tag: 'div' },{ children: ['baz'], tag: 'div' },],tag: 'body'}

console.log(fromTreeToString(tree, '. '));

这是使用 object-scan 的迭代答案。可能不是您 应该 使用的东西,但您可以。老实说,我只是很好奇它会如何发挥作用

// const objectScan = require('object-scan');

const tree = { children: [{ children: [{ children: ['bar'], tag: 'span' }], tag: 'div' }, { children: ['baz'], tag: 'div' }], tag: 'body' };

const fn = (down) => ({ key, value, context }) => {
  if (value.constructor === Object) {
    context.push(`${' '.repeat(key.length / 2)}<${down ? '' : '/'}${value.tag}>`);
  } else if (down && typeof value === 'string') {
    context.push(`${' '.repeat(key.length / 2)}${value}`);
  }
};

const r = objectScan(['', '**(^children$)'], {
  reverse: false,
  useArraySelector: false,
  breakFn: fn(true),
  filterFn: fn(false)
})(tree, []).join('\n');

console.log(r);
/* =>
<body>
  <div>
    <span>
      bar
    </span>
  </div>
  <div>
    baz
  </div>
</body>
*/
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="https://bundle.run/object-scan@14.3.1"></script>

免责声明:我是object-scan

的作者