DOMParser API:在JavaScript中解析HTML和XML

2023-03-15 前端

在前端开发中,我们经常需要处理 HTML 字符串或 XML 数据。直接使用 innerHTML 存在安全和性能隐患,而 DOMParser API 提供了更安全、更可控的解析方式。本文将从基础用法到实战案例,全面讲解 DOMParser API。

什么是 DOMParser?

DOMParser 是一个浏览器内置的 API,可以将 HTML 或 XML 字符串解析为 DOM 文档(Document 对象)。解析后的文档可以通过标准的 DOM API(如 querySelector、getElementById)进行操作。

// 基本用法
const parser = new DOMParser();
const doc = parser.parseFromString('<h1>Hello World</h1>', 'text/html');
console.log(doc.querySelector('h1').textContent); // "Hello World"

vs innerHTML

大多数开发者习惯用 innerHTML 来解析 HTML:

const container = document.createElement('div');
container.innerHTML = '<p>Hello World</p>';

但这有几个问题:

  1. 副作用:innerHTML 会修改当前文档的 DOM
  2. 性能:频繁操作真实 DOM 开销大
  3. 安全:插入的内容可能包含脚本执行

DOMParser 则解析到一个全新的、独立的文档中,不会影响当前页面。

特性 innerHTML DOMParser
解析成独立文档 ❌ 需创建临时元素 ✅ 返回完整 Document
影响页面 ✅ 直接插入当前 DOM ❌ 不影响当前页面
解析 XML ❌ 不支持 ✅ 支持
SVG 解析 ⚠️ 部分支持 ✅ image/svg+xml
错误处理 ❌ 静默失败 ✅ parseError 元素
性能 一般(操作真实 DOM) 🟢 更好(虚拟文档)

支持的 MIME 类型

DOMParser.parseFromString() 的第二个参数指定 MIME 类型,决定了如何解析输入的字符串:

MIME 类型 解析方式 返回文档类型 使用场景
text/html HTML 解析器 HTMLDocument 解析 HTML 片段或完整页面
text/xml XML 解析器 XMLDocument 解析 XML 数据
application/xml XML 解析器 XMLDocument 与 text/xml 相同
application/xhtml+xml XHTML 解析器 XMLDocument 解析 XHTML
image/svg+xml XML 解析器 XMLDocument 解析 SVG
const parser = new DOMParser();

// 解析 HTML
const htmlDoc = parser.parseFromString('<p>Hello</p>', 'text/html');

// 解析 XML
const xmlDoc = parser.parseFromString('<root><item id="1"/></root>', 'text/xml');

// 解析 SVG
const svgDoc = parser.parseFromString(
  '<svg xmlns="http://www.w3.org/2000/svg"><circle r="50"/></svg>',
  'image/svg+xml'
);

核心用法

1. 解析 HTML 片段

const parser = new DOMParser();
const htmlString = `
  <div class="card">
    <img src="photo.jpg" alt="照片">
    <h2 class="title">标题</h2>
    <p class="desc">描述文字</p>
  </div>
`;

const doc = parser.parseFromString(htmlString, 'text/html');
// 注意:HTML 文档会自动包含 <html><head><body> 等元素

// 直接从 body 中取内容
const card = doc.body.firstElementChild;
console.log(card.querySelector('.title').textContent); // "标题"

2. 解析完整的 HTML 文档

const parser = new DOMParser();
const fullHtml = `
<!DOCTYPE html>
<html>
<head>
  <title>测试页面</title>
  <meta charset="UTF-8">
</head>
<body>
  <main>
    <h1>页面标题</h1>
    <p>页面内容</p>
  </main>
</body>
</html>
`;

const doc = parser.parseFromString(fullHtml, 'text/html');
console.log(doc.title);                 // "测试页面"
console.log(doc.querySelector('h1').textContent); // "页面标题"

3. 解析 XML 数据

const parser = new DOMParser();
const xmlString = `
<?xml version="1.0" encoding="UTF-8"?>
<bookstore>
  <book category="编程">
    <title lang="zh">JavaScript 高级程序设计</title>
    <author>张三</author>
    <price>59.00</price>
  </book>
  <book category="前端">
    <title lang="zh">CSS 权威指南</title>
    <author>李四</author>
    <price>49.00</price>
  </book>
</bookstore>
`;

const xmlDoc = parser.parseFromString(xmlString, 'text/xml');

// 使用 DOM API 访问 XML 数据
const books = xmlDoc.querySelectorAll('book');
books.forEach(book => {
  const title = book.querySelector('title').textContent;
  const author = book.querySelector('author').textContent;
  console.log(`《${title}》- ${author}`);
});
// 《JavaScript 高级程序设计》- 张三
// 《CSS 权威指南》- 李四

4. 错误处理

const parser = new DOMParser();

// 故意传入格式错误的 XML
const badXml = '<root><item>broken</roo';
const doc = parser.parseFromString(badXml, 'text/xml');

// 检查是否解析成功
const parseError = doc.querySelector('parsererror');
if (parseError) {
  console.error('XML 解析失败:', parseError.textContent);
  // 包含错误信息和位置
} else {
  console.log('解析成功');
}

// 对于 HTML,解析器非常宽容,几乎不会失败
const htmlDoc = parser.parseFromString('<p>unclosed', 'text/html');
// HTML 解析器会自动补全标签,结果是一个完整的文档

DOMParser vs innerHTML 安全性对比

XSS 风险对比

const malicious = '<img src=x onerror=alert("hacked")>';

// ❌ innerHTML —— 高危!会执行 onerror 中的脚本
const div = document.createElement('div');
div.innerHTML = malicious;
// 脚本被执行!alert("hacked")

// ✅ DOMParser —— 安全!不会执行脚本
const parser = new DOMParser();
const doc = parser.parseFromString(malicious, 'text/html');
// 解析成功,但脚本不会执行

// 但当把解析结果插入到 DOM 时,要特别注意
document.body.appendChild(doc.body.firstElementChild);
// ⚠️ 如果使用 append 或 appendChild,还是会触发脚本!

如何安全地使用解析结果?

最佳实践:只提取需要的内容

function extractTextFromHtml(html) {
  const parser = new DOMParser();
  const doc = parser.parseFromString(html, 'text/html');
  return doc.body.textContent || '';
}

function extractLinks(html) {
  const parser = new DOMParser();
  const doc = parser.parseFromString(html, 'text/html');
  return Array.from(doc.querySelectorAll('a[href]')).map(a => ({
    text: a.textContent,
    href: a.getAttribute('href')
  }));
}

实战:RSS 解析器

下面实现一个完整的 RSS 解析器:

DOMParser 的其他应用场景

场景 1:模板渲染

function renderTemplate(template, data) {
  const parser = new DOMParser();
  let html = template;

  // 替换模板变量
  for (const [key, value] of Object.entries(data)) {
    html = html.replace(new RegExp(`\\$\\{${key}\\}`, 'g'), value);
  }

  const doc = parser.parseFromString(html, 'text/html');
  return doc.body.children;
}

const template = '<p>你好,${name}!你的订单 ${orderId} 已确认。</p>';
const result = renderTemplate(template, {
  name: '张三',
  orderId: 'ORD-2023-001'
});
// <p>你好,张三!你的订单 ORD-2023-001 已确认。</p>

场景 2:提取页面元数据

function extractMetaData(htmlString) {
  const parser = new DOMParser();
  const doc = parser.parseFromString(htmlString, 'text/html');

  return {
    title: doc.title,
    description: doc.querySelector('meta[name="description"]')?.content || '',
    keywords: doc.querySelector('meta[name="keywords"]')?.content?.split(',').map(s => s.trim()) || [],
    ogImage: doc.querySelector('meta[property="og:image"]')?.content || '',
    links: Array.from(doc.querySelectorAll('a[href]')).map(a => a.href),
    images: Array.from(doc.querySelectorAll('img[src]')).map(img => img.src)
  };
}

场景 3:SVG 动态生成

function createSVGIcon(name, size = 24) {
  const parser = new DOMParser();
  const svgString = `
    <svg xmlns="http://www.w3.org/2000/svg"
         width="${size}" height="${size}" viewBox="0 0 24 24">
      <circle cx="12" cy="12" r="10" fill="#4A90D9"/>
      <text x="12" y="16" text-anchor="middle"
            fill="white" font-size="12">${name[0]}</text>
    </svg>
  `;

  const doc = parser.parseFromString(svgString, 'image/svg+xml');
  return doc.documentElement; // 返回 SVG 元素
}

// 使用
const svgIcon = createSVGIcon('A', 32);
document.body.appendChild(svgIcon);

场景 4:Markdown 转 HTML(纯前端)

// 简单 Markdown 转 HTML(使用 DOMParser 安全解析结果)
function simpleMarkdownToHTML(md) {
  let html = md
    .replace(/^### (.*$)/gm, '<h3>$1</h3>')
    .replace(/^## (.*$)/gm, '<h2>$1</h2>')
    .replace(/^# (.*$)/gm, '<h1>$1</h1>')
    .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
    .replace(/\*(.*?)\*/g, '<em>$1</em>')
    .replace(/!\[(.*?)\]\((.*?)\)/g, '<img alt="$1" src="$2">')
    .replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2">$1</a>')
    .replace(/\n\n/g, '</p><p>')
    .replace(/\n/g, '<br>');

  html = '<p>' + html + '</p>';

  // 用 DOMParser 验证和清理
  const parser = new DOMParser();
  const doc = parser.parseFromString(html, 'text/html');
  return doc.body.innerHTML;
}

浏览器兼容性

所有现代浏览器都支持 DOMParser,包括 IE10+。

// 简易 polyfill(非 IE 时代已不太需要)
if (!window.DOMParser) {
  throw new Error('当前浏览器不支持 DOMParser API');
}

总结

DOMParser API 是浏览器中解析 HTML/XML 的强大工具:

  1. 独立解析:在虚拟文档中解析,不影响当前页面
  2. 多格式支持:HTML、XML、SVG、XHTML
  3. 安全性:比 innerHTML 更安全(不会执行脚本)
  4. 错误处理:XML 解析失败时有明确的错误信息
  5. 标准 DOM API:解析结果可以通过标准方法操作

使用场景包括但不限于:

  • HTML 片段解析与清理
  • RSS/XML 数据解析
  • SVG 动态生成
  • 模板渲染
  • 元数据提取

扩展阅读