feat(xml): 增加 XML 解析和处理功能

- 添加了处理 XML 请求和响应的工具函数
- 实现了 GET 和 POST 请求的 XML 解析和返回
- 更新了 API 页面,增加了 XML POST 请求和解析的示例
- 优化了 XML 生成和转义逻辑,防止二次转义
This commit is contained in:
严浩
2025-09-02 16:19:31 +08:00
parent b9e6a139b7
commit cc0e5679a9
2 changed files with 198 additions and 8 deletions

View File

@@ -1,21 +1,49 @@
import { defineFakeRoute } from 'vite-plugin-fake-server/client';
// 简单的实体转义,避免在 XML 中出现非法字符;先替换 & 再替换其他符号,防止二次转义
function esc(s: string) {
return s
.replaceAll('&', '&')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&apos;');
}
// 从原始 XML 文本中提取某个标签的文本内容(弱解析,仅用于演示)
function pickTag(xml: string, tag: string) {
// 说明使用非贪婪匹配并允许标签上带属性N.B. 未处理命名空间与嵌套同名标签的复杂情形
const re = new RegExp(`<${tag}(?:\n|\r|\s|>|/)[^>]*>([\s\S]*?)<\/${tag}>`, 'i');
const m = xml.match(re);
return m?.[1]?.trim() ?? '';
}
// 通过 rawResponse 返回 XML 文本,便于在浏览器端演示 XML 解析
export default defineFakeRoute([
{
method: 'GET',
url: '/xml/sample',
rawResponse(_req, res) {
// 这里模拟一个简单的 XML 文档
rawResponse(req, res) {
// 通过查询参数自定义返回的 XML 字段
// 支持的参数to, from, heading, body, id, createdAt
const url = new URL(req.url!, 'http://localhost');
const q = url.searchParams;
const to = q.get('to') ?? 'George';
const from = q.get('from') ?? 'John';
const heading = q.get('heading') ?? 'Reminder';
const body = q.get('body') ?? "Don't forget the meeting at 3 PM today.";
const id = q.get('id') ?? '42';
const createdAt = q.get('createdAt') ?? '2025-09-02T10:00:00Z';
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>George</to>
<from>John</from>
<heading>Reminder</heading>
<body>Don't forget the meeting at 3 PM today.</body>
<to>${esc(to)}</to>
<from>${esc(from)}</from>
<heading>${esc(heading)}</heading>
<body>${esc(body)}</body>
<meta>
<id>42</id>
<createdAt>2025-09-02T10:00:00Z</createdAt>
<id>${esc(id)}</id>
<createdAt>${esc(createdAt)}</createdAt>
</meta>
</note>`;
@@ -23,4 +51,70 @@ export default defineFakeRoute([
res.end(xml);
},
},
{
method: 'POST',
url: '/xml/submit',
rawResponse(req, res) {
const chunks: Buffer[] = [];
req.on('data', (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
req.on('end', () => {
const contentType = String(req.headers['content-type'] || '').toLowerCase();
const raw = Buffer.concat(chunks).toString('utf8');
// 默认值,与 GET 一致
let to = 'George';
let from = 'John';
let heading = 'Reminder';
let body = "Don't forget the meeting at 3 PM today.";
let id = '42';
let createdAt = new Date().toISOString();
try {
if (contentType.includes('application/json')) {
const data = JSON.parse(raw || '{}');
to = data.to ?? to;
from = data.from ?? from;
heading = data.heading ?? heading;
body = data.body ?? body;
id = data.id ?? id;
createdAt = data.createdAt ?? createdAt;
} else if (contentType.includes('application/xml') || contentType.includes('text/xml')) {
// 解析 XML 请求体中常用字段(演示用,未处理命名空间/CDATA 等复杂场景)
to = pickTag(raw, 'to') || to;
from = pickTag(raw, 'from') || from;
heading = pickTag(raw, 'heading') || heading;
body = pickTag(raw, 'body') || body;
id = pickTag(raw, 'id') || id;
createdAt = pickTag(raw, 'createdAt') || createdAt;
} else if (contentType.includes('application/x-www-form-urlencoded')) {
// 简单解析表单key=value&…;此处演示用途,未处理复杂编码场景
const params = new URLSearchParams(raw);
to = params.get('to') ?? to;
from = params.get('from') ?? from;
heading = params.get('heading') ?? heading;
body = params.get('body') ?? body;
id = params.get('id') ?? id;
createdAt = params.get('createdAt') ?? createdAt;
}
} catch {
// 忽略解析错误,保持默认值
}
const xml = `<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>${esc(to)}</to>
<from>${esc(from)}</from>
<heading>${esc(heading)}</heading>
<body>${esc(body)}</body>
<meta>
<id>${esc(id)}</id>
<createdAt>${esc(createdAt)}</createdAt>
</meta>
</note>`;
res.writeHead(200, { 'Content-Type': 'application/xml; charset=UTF-8' });
res.end(xml);
});
},
},
]);