提问人:cdauth 提问时间:10/27/2021 最后编辑:cdauth 更新时间:3/21/2023 访问量:942
如何在Node.js中解析和修改XHTML(支持HTML实体和CDATA部分)?
How to parse and modify XHTML in Node.js (supporting HTML entities and CDATA sections)?
问:
我正在开发一个接收XHTML片段(Confluence存储格式)的Node.js应用程序,应该对其进行一些修改,然后发回修改后的XHTML。XHTML 可以包含 HTML 实体(如 )和 CDATA 部分(如 )。ö
<![CDATA[test]]>
我遇到的挑战是,使用我尝试过的解析器,当我在 HTML 模式下解析代码段时,CDATA 部分会中断,但是当我在 XML 模式下解析它时,HTML 实体无法正确解释。
下面是一个示例,我如何在浏览器中使其工作,但是我如何使用jsdom和cheerio无法使其工作。有没有其他库可以用来实现这一点,或者使用 jsdom 或 cheerio 的任何不同方法?
在浏览器中
在浏览器中,我可以在 XML 模式下使用 DOMParser
。使用测试片段,我可以将其包装在XHTML正文中:<span>ö<![CDATA[ä]]></span>
const doc = new DOMParser().parseFromString(`<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><body><span>ö<![CDATA[ä]]></span></body></html>`, 'application/xml');
doc.querySelector('body').innerHTML; // <span>ö<![CDATA[ä]]></span>
doc.querySelector('body').textContent; // öä
XML MIME 类型可确保正确解释 CDATA 部分,而 XHTML DOCTYPE 可确保实体受支持。
JSDOM的
为了在 Node.js 中实现相同的目标,我尝试使用 jsdom。问题是,当我在 HTML 模式下解析代码时,CDATA 部分会转换为注释,但是当我在 XML 模式下解析它时,由于 HTML 实体而引发异常:
import { JSDOM } from 'jsdom';
const xhtml = `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><body><span>ö<![CDATA[ä]]></span></body></html>`;
new JSDOM(xhtml).window.document.body.innerHTML; // <span>ö<!--[CDATA[ä]]--></span>
new JSDOM(xhtml).window.document.body.textContent; // ö
new JSDOM(xhtml, { contentType: 'application/xml' }); // Uncaught DOMException [SyntaxError]: about:blank:1:186: undefined entity.
更新:我已将问题报告给jsdom。
再见
我更喜欢在后端进行 DOM 修改的方法是 cheerio。在 HTML 模式下使用 cheerio,CDATA 部分将转换为注释。在 XML 模式下,实体不会被解释,而是被双重转义为 .在不解码实体的 XML 模式下,XHTML 被正确保留,但实体未正确解释,这在获取文本内容时可以看出。&ouml;
import cheerio from 'cheerio';
const xhtml = `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html><body><span>ö<![CDATA[ä]]></span></body></html>`;
cheerio.load(xhtml).root().find('body').html(); // <span>ö<!--[CDATA[ä]]--></span>
cheerio.load(xhtml).root().find('body').text(); // ö
cheerio.load(xhtml, { xmlMode: true }).root().find('body').html(); // <span>&ouml;<![CDATA[ä]]></span>
cheerio.load(xhtml, { xmlMode: true }).root().find('body').html(); // öä
cheerio.load(xhtml, { xmlMode: true, decodeEntities: false }).root().find('body').html(); // <span>ö<![CDATA[ä]]></span>
cheerio.load(xhtml, { xmlMode: true, decodeEntities: false }).root().find('body').text(); // öä
更新:我已将问题报告给cheerio。
答:
有人指出了 cheerio 中该问题的解决方法:
cheerio.load(xhtml, { xml: { xmlMode: false, recognizeCDATA: true, recognizeSelfClosing: true } });
使用这些选项,我可以在 Node.js 环境中成功解析 XHTML。
除了这个解决方案之外,我还注意到在浏览器中使用 的缺点是浏览器之间存在不一致。特别是,当将查询选择器与 XML 命名空间结合使用时,我有时必须在查询中包含命名空间,有时则不包含。由于这些不一致,jquery也正式不支持XML命名空间。为了在浏览器之间以及前端、前端测试和后端之间实现一致的行为,我决定使用 cheerio 甚至在浏览器中解析 XHTML。DOMParser
评论
application/xhtml+xml