diff --git a/fake/xml.fake.ts b/fake/xml.fake.ts
index 75ef5e2..209a987 100644
--- a/fake/xml.fake.ts
+++ b/fake/xml.fake.ts
@@ -1,21 +1,49 @@
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 解析
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 = `
- George
- John
- Reminder
- Don't forget the meeting at 3 PM today.
+ ${esc(to)}
+ ${esc(from)}
+ ${esc(heading)}
+ ${esc(body)}
- 42
- 2025-09-02T10:00:00Z
+ ${esc(id)}
+ ${esc(createdAt)}
`;
@@ -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 = `
+
+ ${esc(to)}
+ ${esc(from)}
+ ${esc(heading)}
+ ${esc(body)}
+
+ ${esc(id)}
+ ${esc(createdAt)}
+
+`;
+
+ res.writeHead(200, { 'Content-Type': 'application/xml; charset=UTF-8' });
+ res.end(xml);
+ });
+ },
+ },
]);
diff --git a/src/pages/Page/API.page.vue b/src/pages/Page/API.page.vue
index f0c7e15..b92c669 100644
--- a/src/pages/Page/API.page.vue
+++ b/src/pages/Page/API.page.vue
@@ -15,6 +15,10 @@ onMounted(() => {
// 选择此方式的原因:可控性更强,便于处理编码、容错与字段抽取策略
let xmlText = $ref(null);
let xmlParsed = $ref | null>(null);
+let xmlPostText = $ref(null);
+let xmlPostParsed = $ref | null>(null);
+let xmlPostXmlText = $ref(null);
+let xmlPostXmlParsed = $ref | null>(null);
onMounted(async () => {
try {
@@ -22,6 +26,14 @@ onMounted(async () => {
responseType: 'text',
headers: { Accept: 'application/xml' },
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;
@@ -45,6 +57,78 @@ onMounted(async () => {
}
});
+// 演示 POST 提交 JSON,由服务端返回 XML,再解析
+onMounted(async () => {
+ try {
+ const res = await axios.post(
+ `${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-Client
+ Browser
+ XMLSubmit
+ Send by XML body
+
+ 777
+ ${new Date().toISOString()}
+
+`;
+
+ const res = await axios.post(`${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')
.then((response) => response.json())
.then((json) => console.log(json)); */
@@ -63,6 +147,18 @@ onMounted(async () => {
解析后的结果
{{ xmlParsed }}
+
+ POST 返回的 XML 字符串
+ {{ xmlPostText }}
+
+ POST 解析后的结果
+ {{ xmlPostParsed }}
+
+ POST(XML body) 返回的 XML 字符串
+ {{ xmlPostXmlText }}
+
+ POST(XML body) 解析后的结果
+ {{ xmlPostXmlParsed }}