Skip to main content
Glama
converter.ts4.4 kB
const INDENT_PREFIX = "\t"; export class ContentConverter { xmlToMarkdown(xml: string): string { if (!xml) { return ""; } let md = xml.replace(/\r\n/g, "\n"); // 任务列表 md = md.replace(/<input type="checkbox" checked="true" \/>/g, "- [x] "); md = md.replace(/<input type="checkbox" \/>/g, "- [ ] "); // 无序列表 md = md.replace(/<bullet indent="1" \/>/g, "- "); md = md.replace(/<bullet indent="2" \/>/g, `${INDENT_PREFIX}- `); // 粗体 md = md.replace(/<b>(.*?)<\/b>/gs, "**$1**"); // 图片 md = md.replace(/<img fileid="(.*?)" imgshow=".*?" imgdes=".*?" \/>/g, "![图片](minote://image/$1)"); // 音频 md = md.replace(/<sound fileid="(.*?)" \/>/g, "[🔊 音频: $1]"); // 普通文本 md = md.replace(/<text indent="1">(.*?)<\/text>/gs, (_match, text) => sanitizeMarkdownText(text)); md = md.replace(/<text indent="2">(.*?)<\/text>/gs, (_match, text) => `${INDENT_PREFIX}${sanitizeMarkdownText(text)}`); // 异常缩进直接去掉 md = md.replace(/<text indent="NaN">.*?<\/text>/gs, ""); // 移除多余 XML 标签 md = md.replace(/<0\/>/g, ""); md = md.replace(/<new-format\/>/g, ""); // 清理任何残留尖括号标签 md = md.replace(/<\/?[a-zA-Z0-9-]+[^>]*>/g, ""); return md .split("\n") .map((line) => line.trimEnd()) .join("\n") .trim(); } markdownToXml(markdown: string, uploadedImages: Map<string, string> = new Map()): string { if (!markdown) { return ""; } const xmlLines: string[] = []; const lines = markdown.replace(/\r\n/g, "\n").split("\n"); for (const rawLine of lines) { const line = rawLine.trimEnd(); // 图片:支持 minote 协议和从上传映射获取 const imageMatch = line.match(/!\[[^\]]*\]\(([^)]+)\)/); if (imageMatch) { const [, target] = imageMatch; if (target) { const fileId = resolveImageFileId(target, uploadedImages); if (fileId) { xmlLines.push(`<img fileid="${fileId}" imgshow="0" imgdes="" />`); continue; } } } if (/^- \[x\] /.test(line)) { const text = line.replace(/^- \[x\] /, ""); xmlLines.push(`<input type="checkbox" checked="true" />${escapeXmlText(text)}`); continue; } if (/^- \[ \] /.test(line)) { const text = line.replace(/^- \[ \] /, ""); xmlLines.push(`<input type="checkbox" />${escapeXmlText(text)}`); continue; } if (/^\t- /.test(line)) { const text = line.replace(/^\t- /, ""); xmlLines.push(`<bullet indent="2" />${escapeXmlText(text)}`); continue; } if (/^- /.test(line)) { const text = line.replace(/^- /, ""); xmlLines.push(`<bullet indent="1" />${escapeXmlText(text)}`); continue; } const indent = line.startsWith("\t") ? "2" : "1"; const normalized = line.replace(/^\t/, ""); const processed = normalized.replace(/\*\*(.*?)\*\*/g, (_match, boldText) => `<b>${escapeXmlText(boldText)}</b>`); xmlLines.push(`<text indent="${indent}">${escapeXmlText(processed, true)}</text>`); } return xmlLines.join("\n"); } } function sanitizeMarkdownText(text: string | undefined): string { return decodeHtmlEntities(text ?? "").replace(/\s+$/g, ""); } function resolveImageFileId(target: string, uploadedImages: Map<string, string>): string | undefined { if (target.startsWith("minote://image/")) { return target.substring("minote://image/".length); } return uploadedImages.get(target); } function escapeXmlText(text: string, allowTags = false): string { if (!text) { return ""; } const escaped = text .replace(/&/g, "&amp;") .replace(/</g, "&lt;") .replace(/>/g, "&gt;"); if (allowTags) { // allowTags = true 表示文本内可能已包含格式化标签(如 <b>),这里恢复尖括号 return escaped.replace(/&lt;(\/?)b&gt;/g, "<$1b>"); } return escaped; } const htmlEntityMap: Record<string, string> = { amp: "&", lt: "<", gt: ">", quot: '"', apos: "'", }; function decodeHtmlEntities(value: string): string { return value.replace(/&([a-z]+);/gi, (_match, entity: string) => { const decoded = htmlEntityMap[entity.toLowerCase()]; return decoded ?? `&${entity};`; }); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/LaelLuo/mi_note_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server