小能豆

如何将文本段数组转换为 DOM 树对象?

javascript

让我们在 Figma 中编辑一个文本块,如图所示:

在此输入图像描述

segmentsFigma 插件 API为该文本块提供以下内容:

const segments = [
  { "characters": "Lorem ", "fontWeight": 400, "listOptions": { "type": "NONE" }, "indentation": 0, "hyperlink": null },
  { "characters": "Ipsum", "fontWeight": 700, "listOptions": { "type": "NONE" }, "indentation": 0, "hyperlink": null },
  { "characters": " is \nsimply dummy text of \n", "fontWeight": 400, "listOptions": { "type": "NONE" }, "indentation": 0, "hyperlink": null },
  { "characters": "the printing and \n", "fontWeight": 400, "listOptions": { "type": "UNORDERED" }, "indentation": 1, "hyperlink": null },
  { "characters": "typesetting \n", "fontWeight": 400, "listOptions": { "type": "UNORDERED" }, "indentation": 2, "hyperlink": null },
  { "characters": "industry. \n", "fontWeight": 400, "listOptions": { "type": "UNORDERED" }, "indentation": 1, "hyperlink": null },
  { "characters": "Lorem Ipsum has been the ", "fontWeight": 400, "listOptions": { "type": "NONE" }, "indentation": 0, "hyperlink": null },
  { "characters": "industry's standard", "fontWeight": 400, "listOptions": { "type": "NONE" }, "indentation": 0, "hyperlink": { "type": "URL", "value": "http://example.com" } },
  { "characters": " dummy text ever since the 1500s, \n", "fontWeight": 400, "listOptions": { "type": "NONE" }, "indentation": 0, "hyperlink": null },
  { "characters": "when an unknown \n", "fontWeight": 400, "listOptions": { "type": "ORDERED" }, "indentation": 1, "hyperlink": null },
  { "characters": "printer took \na galley of \ntype and \n", "fontWeight": 400, "listOptions": { "type": "UNORDERED" }, "indentation": 2, "hyperlink": null },
  { "characters": "scrambled it\n", "fontWeight": 400, "listOptions": { "type": "ORDERED" }, "indentation": 1, "hyperlink": null },
  { "characters": "\nto make a type\n\n", "fontWeight": 400, "listOptions": { "type": "NONE" }, "indentation": 0, "hyperlink": null },
  { "characters": "specimen book.\n", "fontWeight": 400, "listOptions": { "type": "UNORDERED" }, "indentation": 1, "hyperlink": null },
  { "characters": "It has survived\n", "fontWeight": 400, "listOptions": { "type": "ORDERED" }, "indentation": 3, "hyperlink": null },
  { "characters": "not only\nfive centuries,", "fontWeight": 400, "listOptions": { "type": "ORDERED" }, "indentation": 2, "hyperlink": null }
]

由于列表很长,让我们稍微简化一下:

const segments = [
  { ind: 0, list: null, chars: "Lorem ", bold: false, link: null },
  { ind: 0, list: null, chars: "Ipsum", bold: true, link: null },
  { ind: 0, list: null, chars: " is \nsimply dummy text of \n", bold: false, link: null },
  { ind: 1, list: "UL", chars: "the printing and \n", bold: false, link: null },
  { ind: 2, list: "UL", chars: "typesetting \n", bold: false, link: null },
  { ind: 1, list: "UL", chars: "industry. \n", bold: false, link: null },
  { ind: 0, list: null, chars: "Lorem Ipsum has been the ", bold: false, link: null },
  { ind: 0, list: null, chars: "industry's standard", bold: false, link: "http://example.com" },
  { ind: 0, list: null, chars: " dummy text ever since the 1500s, \n", bold: false, link: null },
  { ind: 1, list: "OL", chars: "when an unknown \n", bold: false, link: null },
  { ind: 2, list: "UL", chars: "printer took \na galley of \ntype and \n", bold: false, link: null },
  { ind: 1, list: "OL", chars: "scrambled it\n", bold: false, link: null },
  { ind: 0, list: null, chars: "\nto make a type\n\n", bold: false, link: null },
  { ind: 1, list: "UL", chars: "specimen book.\n", bold: false, link: null },
  { ind: 3, list: "OL", chars: "It has survived\n", bold: false, link: null },
  { ind: 2, list: "OL", chars: " not only\nfive centuries,", bold: false, link: null }
]

我正在尝试获取此段数据并使用 Javascript 将其转换为 HTML 树。输出应如下所示:

<span>Lorem </span>
<strong>Ipsum</strong> 
<span> is <br>simply dummy text of </span>
<ul>
  <li>
    <span>the printing and </span>
  </li>
  <ul>
    <li><span>typesettting </span></li>
  </ul>
  <li><span>industry. </span></li>
</ul>
<span>Lorem Ipsum has been the </span>
<a href="http://example.com">industry's standard</a>
<span> dummy text ever since the 1500s, </span>
<ol>
  <li><span>when an unknown </span></li>
  <ul>
    <li><span>printer took </span></li>
    <li><span>a galley of </span></li>
    <li><span>type and </span></li>
  </ul>
  <li><span>scrambled it</span></li>
</ol>
<span>to make a type</span>
<ul>
  <li>
    <span>specimen book.</span>
  </li>
  <ol>
    <ol>
      <li><span>It has survived</span></li>
    </ol>
    <li><span>not only</span></li>
    <li><span>five countries,</span></li>
  </ol>
</ul>

我试过:

function getPureSegment(chars: string) {
  if (chars.endsWith("\n")) chars = chars.slice(0, -1)
  return ["<span>", chars.replaceAll(/\n/g, "<br>"), "</span>"]
}

function getOpeningListTag(segment) {
  const type = segment.listOptions.type
  if (type === "ORDERED") return "<ol>"
  if (type === "UNORDERED") return "<ul>"
}

function getClosingListTag(segment) {
  const type = segment.listOptions.type
  if (type === "ORDERED") return "</ol>"
  if (type === "UNORDERED") return "</ul>"
}

function getHtml(segments) {
  let prevSegment = { indentation: 0 }
  return segments.flatMap((segment, idx) => {
    const pure = getPureSegment(segment.characters)
    let line
    const endsBreakLine = segment.characters.endsWith("\n")
    const isLastSegment = idx === segments.length - 1
    if (segment.indentation == 0) {
      if (segment.indentation < prevSegment.indentation) {
        line = [getClosingListTag(prevSegment), ...pure]
      } else {
        line = pure
      }
    } else if (segment.indentation > 0) {
      if (segment.indentation > prevSegment.indentation) {
        line = [getOpeningListTag(segment), "<li>", ...pure, (isLastSegment || segments[idx + 1].indentation < segment.indentation) && "</li>"].filter(Boolean)
      } else if (segment.indentation == prevSegment.indentation) {
        line = [segments[idx - 1].characters.endsWith("\n") && "<li>", ...pure, endsBreakLine && "</li>"].filter(Boolean)
      } else {
        line = [getClosingListTag(segment), "<li>", ...pure, endsBreakLine && "</li>"].filter(Boolean)
      }
      if (isLastSegment) line.push("</ul>".repeat(segment.indentation))
    }
    prevSegment = segment
    return line
  }).join("\n")
}

阅读 104

收藏
2024-02-28

共1个答案

小能豆

您的代码看起来几乎正确,但是有一些地方需要调整。下面是我对您的函数的一些修改和补充:

function getHtml(segments) {
  let prevSegment = { indentation: 0 }
  let html = "";

  segments.forEach((segment, idx) => {
    const pure = getPureSegment(segment.chars);
    let line = "";
    const endsBreakLine = segment.chars.endsWith("\n");
    const isLastSegment = idx === segments.length - 1;

    if (segment.ind === 0) {
      if (segment.ind < prevSegment.ind) {
        line = getClosingListTag(prevSegment) + pure.join("");
      } else {
        line = pure.join("");
      }
    } else if (segment.ind > 0) {
      if (segment.ind > prevSegment.ind) {
        line = getOpeningListTag(segment) + "<li>" + pure.join("") + (isLastSegment || segments[idx + 1].ind < segment.ind ? "</li>" : "");
      } else if (segment.ind === prevSegment.ind) {
        line = (segments[idx - 1].chars.endsWith("\n") ? "<li>" : "") + pure.join("") + (endsBreakLine ? "</li>" : "");
      } else {
        line = getClosingListTag(segment) + "<li>" + pure.join("") + (endsBreakLine ? "</li>" : "");
      }

      if (isLastSegment) {
        for (let i = segment.ind; i > 0; i--) {
          line += "</ul>";
        }
      }
    }

    prevSegment = segment;
    html += line + "\n";
  });

  return html;
}

在这个修改后的函数中,我做了以下几个修改和调整:

  1. getHtml函数中,我使用了forEach而不是flatMap。这是因为我们需要逐行构建HTML字符串,而不是返回一个扁平的数组。
  2. 我将segment.characters更改为segment.chars,以匹配您提供的简化段落数据。
  3. 我将pure数组中的元素连接起来以形成一个字符串,并将其添加到line变量中。
  4. 在处理列表标签时,我用if-else语句替换了filter(Boolean)。这样可以更清晰地表达条件。
  5. 在迭代结束时,我将每个段落的HTML行添加到html变量中,并返回最终的HTML字符串。

我希望这些修改能够解决您的问题,并产生您期望的HTML输出。

2024-02-28