feat(xml): 增加 XML 解析和处理功能
- 添加了处理 XML 请求和响应的工具函数 - 实现了 GET 和 POST 请求的 XML 解析和返回 - 更新了 API 页面,增加了 XML POST 请求和解析的示例 - 优化了 XML 生成和转义逻辑,防止二次转义
This commit is contained in:
110
fake/xml.fake.ts
110
fake/xml.fake.ts
@@ -1,21 +1,49 @@
|
|||||||
import { defineFakeRoute } from 'vite-plugin-fake-server/client';
|
import { defineFakeRoute } from 'vite-plugin-fake-server/client';
|
||||||
|
|
||||||
|
// 简单的实体转义,避免在 XML 中出现非法字符;先替换 & 再替换其他符号,防止二次转义
|
||||||
|
function esc(s: string) {
|
||||||
|
return s
|
||||||
|
.replaceAll('&', '&')
|
||||||
|
.replaceAll('<', '<')
|
||||||
|
.replaceAll('>', '>')
|
||||||
|
.replaceAll('"', '"')
|
||||||
|
.replaceAll("'", ''');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从原始 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 解析
|
// 通过 rawResponse 返回 XML 文本,便于在浏览器端演示 XML 解析
|
||||||
export default defineFakeRoute([
|
export default defineFakeRoute([
|
||||||
{
|
{
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
url: '/xml/sample',
|
url: '/xml/sample',
|
||||||
rawResponse(_req, res) {
|
rawResponse(req, res) {
|
||||||
// 这里模拟一个简单的 XML 文档
|
// 通过查询参数自定义返回的 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"?>
|
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<note>
|
<note>
|
||||||
<to>George</to>
|
<to>${esc(to)}</to>
|
||||||
<from>John</from>
|
<from>${esc(from)}</from>
|
||||||
<heading>Reminder</heading>
|
<heading>${esc(heading)}</heading>
|
||||||
<body>Don't forget the meeting at 3 PM today.</body>
|
<body>${esc(body)}</body>
|
||||||
<meta>
|
<meta>
|
||||||
<id>42</id>
|
<id>${esc(id)}</id>
|
||||||
<createdAt>2025-09-02T10:00:00Z</createdAt>
|
<createdAt>${esc(createdAt)}</createdAt>
|
||||||
</meta>
|
</meta>
|
||||||
</note>`;
|
</note>`;
|
||||||
|
|
||||||
@@ -23,4 +51,70 @@ export default defineFakeRoute([
|
|||||||
res.end(xml);
|
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);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
]);
|
]);
|
||||||
|
@@ -15,6 +15,10 @@ onMounted(() => {
|
|||||||
// 选择此方式的原因:可控性更强,便于处理编码、容错与字段抽取策略
|
// 选择此方式的原因:可控性更强,便于处理编码、容错与字段抽取策略
|
||||||
let xmlText = $ref<string | null>(null);
|
let xmlText = $ref<string | null>(null);
|
||||||
let xmlParsed = $ref<Record<string, string> | null>(null);
|
let xmlParsed = $ref<Record<string, string> | null>(null);
|
||||||
|
let xmlPostText = $ref<string | null>(null);
|
||||||
|
let xmlPostParsed = $ref<Record<string, string> | null>(null);
|
||||||
|
let xmlPostXmlText = $ref<string | null>(null);
|
||||||
|
let xmlPostXmlParsed = $ref<Record<string, string> | null>(null);
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
try {
|
try {
|
||||||
@@ -22,6 +26,14 @@ onMounted(async () => {
|
|||||||
responseType: 'text',
|
responseType: 'text',
|
||||||
headers: { Accept: 'application/xml' },
|
headers: { Accept: 'application/xml' },
|
||||||
transformResponse: [(data: string) => data],
|
transformResponse: [(data: string) => data],
|
||||||
|
params: {
|
||||||
|
to: 'Alice',
|
||||||
|
from: 'Bob',
|
||||||
|
heading: 'Greeting',
|
||||||
|
body: 'Hello from API.page.vue',
|
||||||
|
id: '1001',
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
xmlText = res.data;
|
xmlText = res.data;
|
||||||
|
|
||||||
@@ -45,6 +57,78 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 演示 POST 提交 JSON,由服务端返回 XML,再解析
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const res = await axios.post<string>(
|
||||||
|
`${baseURL}/xml/submit`,
|
||||||
|
{
|
||||||
|
to: 'Tom',
|
||||||
|
from: 'Jerry',
|
||||||
|
heading: 'PostXML',
|
||||||
|
body: 'This is a POST body to XML service',
|
||||||
|
id: '9001',
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: { 'Content-Type': 'application/json', Accept: 'application/xml' },
|
||||||
|
responseType: 'text',
|
||||||
|
transformResponse: [(data: string) => data],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
xmlPostText = res.data;
|
||||||
|
const doc = new DOMParser().parseFromString(res.data, 'application/xml');
|
||||||
|
const pick = (selector: string) => doc.querySelector(selector)?.textContent?.trim() ?? '';
|
||||||
|
xmlPostParsed = {
|
||||||
|
to: pick('to'),
|
||||||
|
from: pick('from'),
|
||||||
|
heading: pick('heading'),
|
||||||
|
body: pick('body'),
|
||||||
|
id: pick('meta > id'),
|
||||||
|
createdAt: pick('meta > createdAt'),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('XML POST 请求失败: ', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 演示 POST 以 XML 请求体提交,服务端解析 XML 并返回规范化的 XML
|
||||||
|
onMounted(async () => {
|
||||||
|
try {
|
||||||
|
const payload = `<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<note>
|
||||||
|
<to>XML-Client</to>
|
||||||
|
<from>Browser</from>
|
||||||
|
<heading>XMLSubmit</heading>
|
||||||
|
<body>Send by XML body</body>
|
||||||
|
<meta>
|
||||||
|
<id>777</id>
|
||||||
|
<createdAt>${new Date().toISOString()}</createdAt>
|
||||||
|
</meta>
|
||||||
|
</note>`;
|
||||||
|
|
||||||
|
const res = await axios.post<string>(`${baseURL}/xml/submit`, payload, {
|
||||||
|
headers: { 'Content-Type': 'application/xml', Accept: 'application/xml' },
|
||||||
|
responseType: 'text',
|
||||||
|
transformResponse: [(data: string) => data],
|
||||||
|
});
|
||||||
|
xmlPostXmlText = res.data;
|
||||||
|
|
||||||
|
const doc = new DOMParser().parseFromString(res.data, 'application/xml');
|
||||||
|
const pick = (selector: string) => doc.querySelector(selector)?.textContent?.trim() ?? '';
|
||||||
|
xmlPostXmlParsed = {
|
||||||
|
to: pick('to'),
|
||||||
|
from: pick('from'),
|
||||||
|
heading: pick('heading'),
|
||||||
|
body: pick('body'),
|
||||||
|
id: pick('meta > id'),
|
||||||
|
createdAt: pick('meta > createdAt'),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
console.error('XML POST(XML body) 请求失败: ', error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/* fetch('https://jsonplaceholder.typicode.com/posts/1')
|
/* fetch('https://jsonplaceholder.typicode.com/posts/1')
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((json) => console.log(json)); */
|
.then((json) => console.log(json)); */
|
||||||
@@ -63,6 +147,18 @@ onMounted(async () => {
|
|||||||
|
|
||||||
<h3>解析后的结果</h3>
|
<h3>解析后的结果</h3>
|
||||||
<pre>{{ xmlParsed }}</pre>
|
<pre>{{ xmlParsed }}</pre>
|
||||||
|
|
||||||
|
<h3>POST 返回的 XML 字符串</h3>
|
||||||
|
<pre class="break-all whitespace-pre-wrap">{{ xmlPostText }}</pre>
|
||||||
|
|
||||||
|
<h3>POST 解析后的结果</h3>
|
||||||
|
<pre>{{ xmlPostParsed }}</pre>
|
||||||
|
|
||||||
|
<h3>POST(XML body) 返回的 XML 字符串</h3>
|
||||||
|
<pre class="break-all whitespace-pre-wrap">{{ xmlPostXmlText }}</pre>
|
||||||
|
|
||||||
|
<h3>POST(XML body) 解析后的结果</h3>
|
||||||
|
<pre>{{ xmlPostXmlParsed }}</pre>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped></style>
|
<style scoped></style>
|
||||||
|
Reference in New Issue
Block a user