@yinyoudexing/xml2word 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/normalizeDocumentXml.ts","../src/lib/validateXml.ts","../src/lib/createDocxZip.ts","../src/lib/htmlToWordBodyXml.ts","../src/index.ts"],"sourcesContent":["import type { XmlToDocxInputKind } from \"../index.js\";\n\nconst WORD_MAIN_NS = \"http://schemas.openxmlformats.org/wordprocessingml/2006/main\";\n\nfunction hasWordDocumentRoot(xml: string): boolean {\n return /<w:document[\\s>]/.test(xml);\n}\n\nfunction ensureXmlDeclaration(xml: string): string {\n if (/^\\s*<\\?xml\\b/.test(xml)) return xml;\n return `<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\\n${xml}`;\n}\n\nfunction wrapBodyXml(bodyXml: string): string {\n const xml = `<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<w:document\n xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\"\n xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\"\n>\n <w:body>\n${bodyXml}\n <w:sectPr>\n <w:pgSz w:w=\"12240\" w:h=\"15840\"/>\n <w:pgMar w:top=\"1440\" w:right=\"1440\" w:bottom=\"1440\" w:left=\"1440\" w:header=\"708\" w:footer=\"708\" w:gutter=\"0\"/>\n <w:cols w:space=\"708\"/>\n <w:docGrid w:linePitch=\"360\"/>\n </w:sectPr>\n </w:body>\n</w:document>\n`;\n return xml;\n}\n\nfunction ensureWordNamespace(xml: string): string {\n const hasWNamespace =\n /xmlns:w\\s*=\\s*[\"']http:\\/\\/schemas\\.openxmlformats\\.org\\/wordprocessingml\\/2006\\/main[\"']/.test(\n xml,\n );\n if (hasWNamespace) return xml;\n\n return xml.replace(\n /<w:document\\b/,\n `<w:document xmlns:w=\"${WORD_MAIN_NS}\"`,\n );\n}\n\nexport function normalizeDocumentXml(xml: string, inputKind: XmlToDocxInputKind): string {\n const trimmed = xml.trim();\n if (!trimmed) {\n throw new Error(\"XML is empty.\");\n }\n\n if (inputKind === \"document\") {\n const withDecl = ensureXmlDeclaration(trimmed);\n const withNs = ensureWordNamespace(withDecl);\n if (!hasWordDocumentRoot(withNs)) {\n throw new Error('inputKind=\"document\" requires a <w:document> root.');\n }\n return withNs;\n }\n\n if (inputKind === \"body\") {\n return wrapBodyXml(trimmed);\n }\n\n if (hasWordDocumentRoot(trimmed)) {\n const withDecl = ensureXmlDeclaration(trimmed);\n return ensureWordNamespace(withDecl);\n }\n\n return wrapBodyXml(trimmed);\n}\n\n","import { XMLValidator } from \"fast-xml-parser\";\n\nexport function validateXmlIfNeeded(xml: string, validateXml: boolean): void {\n if (!validateXml) return;\n\n const result = XMLValidator.validate(xml);\n if (result === true) return;\n\n const err = (result as { err?: { msg?: string; line?: number; col?: number } }).err;\n const msg = err?.msg ?? \"Invalid XML.\";\n const line = err?.line;\n const col = err?.col;\n\n const location =\n typeof line === \"number\" && typeof col === \"number\" ? ` (line ${line}, col ${col})` : \"\";\n throw new Error(`${msg}${location}`);\n}\n\n","import JSZip from \"jszip\";\nimport type { XmlToDocxOptions } from \"../index.js\";\nimport { normalizeDocumentXml } from \"./normalizeDocumentXml.js\";\nimport { validateXmlIfNeeded } from \"./validateXml.js\";\n\nconst CONTENT_TYPES_XML = `<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Types xmlns=\"http://schemas.openxmlformats.org/package/2006/content-types\">\n <Default Extension=\"rels\" ContentType=\"application/vnd.openxmlformats-package.relationships+xml\"/>\n <Default Extension=\"xml\" ContentType=\"application/xml\"/>\n <Override PartName=\"/word/document.xml\" ContentType=\"application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml\"/>\n</Types>\n`;\n\nconst ROOT_RELS_XML = `<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">\n <Relationship Id=\"rId1\"\n Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument\"\n Target=\"word/document.xml\"/>\n</Relationships>\n`;\n\nexport async function createDocxZipUint8Array(\n xml: string,\n options: XmlToDocxOptions = {},\n): Promise<Uint8Array> {\n const documentXml = normalizeDocumentXml(xml, options.inputKind ?? \"auto\");\n validateXmlIfNeeded(documentXml, options.validateXml ?? true);\n\n const zip = new JSZip();\n zip.file(\"[Content_Types].xml\", CONTENT_TYPES_XML);\n\n const relsFolder = zip.folder(\"_rels\");\n relsFolder?.file(\".rels\", ROOT_RELS_XML);\n\n const wordFolder = zip.folder(\"word\");\n wordFolder?.file(\"document.xml\", documentXml);\n\n return zip.generateAsync({ type: \"uint8array\" });\n}\n\n","import { parseDocument } from \"htmlparser2\";\n\ntype HtmlNode = {\n type?: string;\n name?: string;\n data?: string;\n attribs?: Record<string, string | undefined>;\n children?: HtmlNode[];\n};\n\ntype TextStyle = {\n bold?: boolean;\n italic?: boolean;\n underline?: boolean;\n colorHex?: string;\n fontFamily?: string;\n fontSizeHalfPoints?: number;\n};\n\nfunction escapeXmlText(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/'/g, \"&apos;\");\n}\n\nfunction shouldPreserveSpace(text: string): boolean {\n if (!text) return false;\n return /^\\s/.test(text) || /\\s$/.test(text) || /\\s{2,}/.test(text);\n}\n\nfunction parseStyleAttribute(style: string | undefined): Record<string, string> {\n if (!style) return {};\n const normalized = style.replace(/\\r/g, \"\\n\");\n const parts = normalized.split(\";\");\n const entries: [string, string][] = [];\n for (const part of parts) {\n const idx = part.indexOf(\":\");\n if (idx <= 0) continue;\n const key = part.slice(0, idx).trim().toLowerCase();\n const val = part.slice(idx + 1).trim();\n if (!key || !val) continue;\n entries.push([key, val]);\n }\n return Object.fromEntries(entries);\n}\n\nfunction parseRgbToHex(value: string): string | undefined {\n const m = value\n .trim()\n .toLowerCase()\n .match(/^rgb\\(\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*,\\s*(\\d{1,3})\\s*\\)$/);\n if (!m) return undefined;\n const nums = [Number(m[1]), Number(m[2]), Number(m[3])];\n if (nums.some((n) => Number.isNaN(n) || n < 0 || n > 255)) return undefined;\n return nums.map((n) => n.toString(16).padStart(2, \"0\")).join(\"\").toUpperCase();\n}\n\nfunction parseCssColorToHex(value: string | undefined): string | undefined {\n if (!value) return undefined;\n const v = value.trim();\n const hex = v.match(/^#([0-9a-fA-F]{6})$/)?.[1];\n if (hex) return hex.toUpperCase();\n return parseRgbToHex(v);\n}\n\nfunction parseFontSizeToHalfPoints(value: string | undefined): number | undefined {\n if (!value) return undefined;\n const v = value.trim().toLowerCase();\n const pt = v.match(/^(\\d+(?:\\.\\d+)?)pt$/);\n if (pt) return Math.max(1, Math.round(Number(pt[1]) * 2));\n const px = v.match(/^(\\d+(?:\\.\\d+)?)px$/);\n if (px) {\n const ptValue = (Number(px[1]) * 72) / 96;\n return Math.max(1, Math.round(ptValue * 2));\n }\n return undefined;\n}\n\nfunction normalizeFontFamily(value: string | undefined): string | undefined {\n if (!value) return undefined;\n const first = value.split(\",\")[0]?.trim();\n if (!first) return undefined;\n return first.replace(/^[\"']|[\"']$/g, \"\");\n}\n\nfunction mergeTextStyle(base: TextStyle, patch: TextStyle): TextStyle {\n return {\n bold: patch.bold ?? base.bold,\n italic: patch.italic ?? base.italic,\n underline: patch.underline ?? base.underline,\n colorHex: patch.colorHex ?? base.colorHex,\n fontFamily: patch.fontFamily ?? base.fontFamily,\n fontSizeHalfPoints: patch.fontSizeHalfPoints ?? base.fontSizeHalfPoints,\n };\n}\n\nfunction styleFromElement(node: HtmlNode): TextStyle {\n const tag = node.name?.toLowerCase();\n const styleAttr = node.attribs?.style;\n const css = parseStyleAttribute(styleAttr);\n\n const boldFromCss = (() => {\n const v = css[\"font-weight\"]?.trim().toLowerCase();\n if (!v) return undefined;\n if (v === \"bold\" || v === \"bolder\") return true;\n const n = Number(v);\n if (!Number.isNaN(n)) return n >= 600;\n return undefined;\n })();\n\n const italicFromCss = (() => {\n const v = css[\"font-style\"]?.trim().toLowerCase();\n if (!v) return undefined;\n if (v === \"italic\" || v === \"oblique\") return true;\n return undefined;\n })();\n\n const underlineFromCss = (() => {\n const v = css[\"text-decoration\"]?.trim().toLowerCase();\n if (!v) return undefined;\n return v.includes(\"underline\");\n })();\n\n const tagBold = tag === \"b\" || tag === \"strong\" ? true : undefined;\n const tagItalic = tag === \"i\" || tag === \"em\" ? true : undefined;\n const tagUnderline = tag === \"u\" ? true : undefined;\n\n return {\n bold: tagBold ?? boldFromCss,\n italic: tagItalic ?? italicFromCss,\n underline: tagUnderline ?? underlineFromCss,\n colorHex: parseCssColorToHex(css.color),\n fontFamily: normalizeFontFamily(css[\"font-family\"]),\n fontSizeHalfPoints: parseFontSizeToHalfPoints(css[\"font-size\"]),\n };\n}\n\nfunction getTextContent(node: HtmlNode): string {\n if (node.type === \"text\") return node.data ?? \"\";\n let out = \"\";\n const children = node.children ?? [];\n for (const c of children) out += getTextContent(c);\n return out;\n}\n\ntype RunToken =\n | { kind: \"text\"; text: string; style: TextStyle }\n | { kind: \"br\" };\n\nfunction collectInlineRuns(node: HtmlNode, inherited: TextStyle, out: RunToken[]): void {\n if (node.type === \"text\") {\n const text = node.data ?? \"\";\n if (text) out.push({ kind: \"text\", text, style: inherited });\n return;\n }\n\n if (node.type === \"tag\") {\n const tag = node.name?.toLowerCase();\n if (tag === \"br\") {\n out.push({ kind: \"br\" });\n return;\n }\n const next = mergeTextStyle(inherited, styleFromElement(node));\n const children = node.children ?? [];\n for (const c of children) collectInlineRuns(c, next, out);\n return;\n }\n\n const children = node.children ?? [];\n for (const c of children) collectInlineRuns(c, inherited, out);\n}\n\nfunction buildRunXml(style: TextStyle, text: string): string {\n const rPrParts: string[] = [];\n if (style.bold) rPrParts.push(\"<w:b/>\");\n if (style.italic) rPrParts.push(\"<w:i/>\");\n if (style.underline) rPrParts.push('<w:u w:val=\"single\"/>');\n if (style.colorHex) rPrParts.push(`<w:color w:val=\"${style.colorHex}\"/>`);\n if (style.fontFamily) {\n const ff = escapeXmlText(style.fontFamily);\n rPrParts.push(`<w:rFonts w:ascii=\"${ff}\" w:hAnsi=\"${ff}\" w:eastAsia=\"${ff}\"/>`);\n }\n if (typeof style.fontSizeHalfPoints === \"number\") {\n const sz = style.fontSizeHalfPoints;\n rPrParts.push(`<w:sz w:val=\"${sz}\"/><w:szCs w:val=\"${sz}\"/>`);\n }\n\n const rPrXml = rPrParts.length ? `<w:rPr>${rPrParts.join(\"\")}</w:rPr>` : \"\";\n const escaped = escapeXmlText(text);\n const preserve = shouldPreserveSpace(text) ? ' xml:space=\"preserve\"' : \"\";\n return `<w:r>${rPrXml}<w:t${preserve}>${escaped}</w:t></w:r>`;\n}\n\nfunction hasClass(node: HtmlNode, className: string): boolean {\n const cls = node.attribs?.class;\n if (!cls) return false;\n return cls.split(/\\s+/).includes(className);\n}\n\nfunction isSkippableSubtree(node: HtmlNode): boolean {\n if (node.type !== \"tag\") return false;\n const tag = node.name?.toLowerCase();\n if (tag === \"button\" || tag === \"canvas\") return true;\n if (tag === \"img\" && hasClass(node, \"ProseMirror-separator\")) return true;\n if (node.attribs?.id === \"pages\") return true;\n if (hasClass(node, \"ProseMirror-widget\")) return true;\n return false;\n}\n\nfunction parseCssLengthToTwips(\n value: string | undefined,\n baseFontHalfPoints: number,\n): number | undefined {\n if (!value) return undefined;\n const v = value.trim().toLowerCase();\n if (!v) return undefined;\n\n const pt = v.match(/^(-?\\d+(?:\\.\\d+)?)pt$/);\n if (pt) return Math.round(Number(pt[1]) * 20);\n\n const px = v.match(/^(-?\\d+(?:\\.\\d+)?)px$/);\n if (px) return Math.round((Number(px[1]) * 72 * 20) / 96);\n\n const em = v.match(/^(-?\\d+(?:\\.\\d+)?)em$/);\n if (em) {\n const basePt = baseFontHalfPoints / 2;\n return Math.round(Number(em[1]) * basePt * 20);\n }\n\n const num = v.match(/^(-?\\d+(?:\\.\\d+)?)$/);\n if (num) return Math.round(Number(num[1]));\n\n return undefined;\n}\n\nfunction inferFirstFontSizeHalfPoints(node: HtmlNode): number | undefined {\n const stack: HtmlNode[] = [node];\n while (stack.length) {\n const cur = stack.pop() as HtmlNode;\n if (cur.type === \"tag\") {\n const css = parseStyleAttribute(cur.attribs?.style);\n const sz = parseFontSizeToHalfPoints(css[\"font-size\"]);\n if (typeof sz === \"number\") return sz;\n }\n const children = cur.children ?? [];\n for (let i = children.length - 1; i >= 0; i--) {\n stack.push(children[i] as HtmlNode);\n }\n }\n return undefined;\n}\n\nfunction buildParagraphPrXml(\n node: HtmlNode,\n baseFontHalfPoints: number,\n extraInd?: { leftTwips?: number; hangingTwips?: number },\n): string {\n const css = parseStyleAttribute(node.attribs?.style);\n const parts: string[] = [];\n\n const align = css[\"text-align\"]?.trim().toLowerCase();\n const jcVal =\n align === \"center\"\n ? \"center\"\n : align === \"right\"\n ? \"right\"\n : align === \"justify\"\n ? \"both\"\n : undefined;\n if (jcVal) parts.push(`<w:jc w:val=\"${jcVal}\"/>`);\n\n const left = (() => {\n const marginLeft = parseCssLengthToTwips(css[\"margin-left\"], baseFontHalfPoints);\n const paddingLeft = parseCssLengthToTwips(css[\"padding-left\"], baseFontHalfPoints);\n const sum = (marginLeft ?? 0) + (paddingLeft ?? 0);\n if (!sum) return undefined;\n return Math.max(0, sum);\n })();\n\n const firstLine = (() => {\n const textIndent = parseCssLengthToTwips(css[\"text-indent\"], baseFontHalfPoints);\n if (typeof textIndent !== \"number\" || !textIndent) return undefined;\n return Math.max(0, textIndent);\n })();\n\n const indAttrs: string[] = [];\n const leftTwips = extraInd?.leftTwips ?? left;\n if (typeof leftTwips === \"number\") indAttrs.push(`w:left=\"${leftTwips}\"`);\n const hangingTwips = extraInd?.hangingTwips;\n if (typeof hangingTwips === \"number\") indAttrs.push(`w:hanging=\"${hangingTwips}\"`);\n if (typeof firstLine === \"number\") indAttrs.push(`w:firstLine=\"${firstLine}\"`);\n if (indAttrs.length) parts.push(`<w:ind ${indAttrs.join(\" \")}/>`);\n\n const before = parseCssLengthToTwips(css[\"margin-top\"], baseFontHalfPoints);\n const after = parseCssLengthToTwips(css[\"margin-bottom\"], baseFontHalfPoints);\n const lineHeight = (() => {\n const lh = css[\"line-height\"]?.trim().toLowerCase();\n if (!lh || lh === \"normal\") return undefined;\n\n const unitless = lh.match(/^(\\d+(?:\\.\\d+)?)$/);\n if (unitless) {\n const multiplier = Number(unitless[1]);\n if (!Number.isFinite(multiplier) || multiplier <= 0) return undefined;\n const basePt = baseFontHalfPoints / 2;\n return Math.round(basePt * multiplier * 20);\n }\n\n const twips = parseCssLengthToTwips(lh, baseFontHalfPoints);\n if (typeof twips !== \"number\") return undefined;\n return Math.max(1, twips);\n })();\n\n if (\n typeof before === \"number\" ||\n typeof after === \"number\" ||\n typeof lineHeight === \"number\"\n ) {\n const attrs: string[] = [];\n if (typeof before === \"number\") attrs.push(`w:before=\"${Math.max(0, before)}\"`);\n if (typeof after === \"number\") attrs.push(`w:after=\"${Math.max(0, after)}\"`);\n if (typeof lineHeight === \"number\") {\n attrs.push(`w:line=\"${lineHeight}\"`, 'w:lineRule=\"exact\"');\n }\n parts.push(`<w:spacing ${attrs.join(\" \")}/>`);\n }\n\n if (!parts.length) return \"\";\n return `<w:pPr>${parts.join(\"\")}</w:pPr>`;\n}\n\nfunction buildParagraphXmlFromContainer(\n node: HtmlNode,\n baseStyle: TextStyle,\n extraInd?: { leftTwips?: number; hangingTwips?: number },\n): string {\n const baseFontHalfPoints = baseStyle.fontSizeHalfPoints ?? inferFirstFontSizeHalfPoints(node) ?? 28;\n const pPrXml = buildParagraphPrXml(node, baseFontHalfPoints, extraInd);\n\n const runs: RunToken[] = [];\n for (const c of node.children ?? []) collectInlineRuns(c, baseStyle, runs);\n\n const rXml: string[] = [];\n for (const token of runs) {\n if (token.kind === \"br\") {\n rXml.push(\"<w:r><w:br/></w:r>\");\n continue;\n }\n const text = token.text;\n if (!text) continue;\n if (!text.trim()) continue;\n rXml.push(buildRunXml(token.style, text));\n }\n\n if (!rXml.length) return \"\";\n return `<w:p>${pPrXml}${rXml.join(\"\")}</w:p>`;\n}\n\nconst PAGE_BREAK_XML = '<w:p><w:r><w:br w:type=\"page\"/></w:r></w:p>';\n\nfunction isExplicitPageBreak(node: HtmlNode): boolean {\n if (node.type !== \"tag\") return false;\n const tag = node.name?.toLowerCase();\n const css = parseStyleAttribute(node.attribs?.style);\n const cls = node.attribs?.class ?? \"\";\n const classList = cls ? cls.split(/\\s+/) : [];\n\n if (tag === \"hr\" && classList.includes(\"page-break\")) return true;\n if (classList.includes(\"page-break\")) return true;\n if (node.attribs?.[\"data-page-break\"] === \"true\") return true;\n\n const after = css[\"page-break-after\"]?.toLowerCase() ?? css[\"break-after\"]?.toLowerCase();\n const before = css[\"page-break-before\"]?.toLowerCase() ?? css[\"break-before\"]?.toLowerCase();\n if (after?.includes(\"always\") || before?.includes(\"always\")) return true;\n\n return false;\n}\n\nfunction buildHeadingBaseStyle(level: number): TextStyle {\n const size = level === 1 ? 44 : level === 2 ? 32 : level === 3 ? 28 : level === 4 ? 24 : 22;\n return { bold: true, fontSizeHalfPoints: size };\n}\n\nfunction buildListBlocks(listNode: HtmlNode, ordered: boolean): string[] {\n const items: HtmlNode[] = [];\n const stack: HtmlNode[] = [...(listNode.children ?? [])];\n while (stack.length) {\n const n = stack.shift() as HtmlNode;\n if (n.type === \"tag\" && n.name?.toLowerCase() === \"li\") items.push(n);\n }\n\n const out: string[] = [];\n for (let i = 0; i < items.length; i++) {\n const prefix = ordered ? `${i + 1}. ` : \"• \";\n const li = items[i] as HtmlNode;\n\n const baseStyle: TextStyle = {};\n const runs: RunToken[] = [];\n runs.push({ kind: \"text\", text: prefix, style: baseStyle });\n for (const c of li.children ?? []) collectInlineRuns(c, baseStyle, runs);\n\n const rXml: string[] = [];\n for (const token of runs) {\n if (token.kind === \"br\") {\n rXml.push(\"<w:r><w:br/></w:r>\");\n continue;\n }\n const text = token.text;\n if (!text) continue;\n if (!text.trim()) continue;\n rXml.push(buildRunXml(token.style, text));\n }\n if (!rXml.length) continue;\n\n const pPrXml = buildParagraphPrXml(li, inferFirstFontSizeHalfPoints(li) ?? 28, {\n leftTwips: 720,\n hangingTwips: 360,\n });\n out.push(`<w:p>${pPrXml}${rXml.join(\"\")}</w:p>`);\n }\n\n return out;\n}\n\nfunction buildTableXml(tableNode: HtmlNode): string {\n const rows: HtmlNode[] = [];\n const stack: HtmlNode[] = [...(tableNode.children ?? [])];\n while (stack.length) {\n const n = stack.shift() as HtmlNode;\n if (n.type === \"tag\" && n.name?.toLowerCase() === \"tr\") rows.push(n);\n if (n.children?.length) stack.unshift(...n.children);\n }\n\n const rowXml: string[] = [];\n for (const tr of rows) {\n const cells = (tr.children ?? []).filter(\n (c) => c.type === \"tag\" && (c.name === \"td\" || c.name === \"th\"),\n );\n\n const cellXml: string[] = [];\n for (const cell of cells) {\n const isHeader = cell.name === \"th\";\n const baseStyle: TextStyle = isHeader ? { bold: true } : {};\n const pXml = buildParagraphXmlFromContainer(cell, baseStyle);\n const paragraphs = pXml ? pXml : \"<w:p/>\";\n cellXml.push(\n `<w:tc><w:tcPr><w:tcW w:w=\"0\" w:type=\"auto\"/></w:tcPr>${paragraphs}</w:tc>`,\n );\n }\n if (cellXml.length) rowXml.push(`<w:tr>${cellXml.join(\"\")}</w:tr>`);\n }\n\n const tblPr = `<w:tblPr><w:tblW w:w=\"0\" w:type=\"auto\"/><w:tblBorders><w:top w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"D9D9D9\"/><w:left w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"D9D9D9\"/><w:bottom w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"D9D9D9\"/><w:right w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"D9D9D9\"/><w:insideH w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"D9D9D9\"/><w:insideV w:val=\"single\" w:sz=\"4\" w:space=\"0\" w:color=\"D9D9D9\"/></w:tblBorders></w:tblPr>`;\n const tblGrid = `<w:tblGrid/>`;\n return `<w:tbl>${tblPr}${tblGrid}${rowXml.join(\"\")}</w:tbl>`;\n}\n\nfunction collectBodyBlocks(node: HtmlNode, out: string[]): void {\n if (isSkippableSubtree(node)) return;\n\n if (node.type === \"tag\") {\n const tag = node.name?.toLowerCase();\n\n if (isExplicitPageBreak(node)) {\n out.push(PAGE_BREAK_XML);\n return;\n }\n\n if (tag === \"p\") {\n const pXml = buildParagraphXmlFromContainer(node, {});\n if (pXml) out.push(pXml);\n return;\n }\n\n if (tag && /^h[1-6]$/.test(tag)) {\n const level = Number(tag.slice(1));\n const hXml = buildParagraphXmlFromContainer(node, buildHeadingBaseStyle(level));\n if (hXml) out.push(hXml);\n return;\n }\n\n if (tag === \"table\") {\n const tblXml = buildTableXml(node);\n if (tblXml) out.push(tblXml);\n return;\n }\n\n if (tag === \"ul\" || tag === \"ol\") {\n out.push(...buildListBlocks(node, tag === \"ol\"));\n return;\n }\n }\n\n for (const c of node.children ?? []) collectBodyBlocks(c, out);\n}\n\nexport function textToWordBodyXml(text: string): string {\n const normalized = text.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n if (!normalized.trim()) {\n throw new Error(\"Text is empty.\");\n }\n\n const lines = normalized.split(\"\\n\");\n const out: string[] = [];\n for (const line of lines) {\n if (!line) {\n out.push(\"<w:p/>\");\n continue;\n }\n out.push(`<w:p>${buildRunXml({}, line)}</w:p>`);\n }\n return out.join(\"\");\n}\n\n/**\n * 把 HTML 字符串转换成 WordprocessingML 的 body 片段(由 <w:p> / <w:tbl> 等组成)。\n * 说明:\n * - 这个函数只生成 body 内容,不生成完整的 <w:document> 包装\n * - 支持:p/span/strong/br、h1~h6、ul/ol/li、table/tr/td/th、基础分页标记\n * - 不支持:canvas 图表、复杂 CSS、HTML 图片(需要额外提供图片二进制)\n */\nexport function htmlToWordBodyXml(html: string): string {\n const normalized = html.replace(/\\r\\n/g, \"\\n\").replace(/\\r/g, \"\\n\");\n const doc = parseDocument(normalized, {\n lowerCaseAttributeNames: true,\n lowerCaseTags: true,\n recognizeSelfClosing: true,\n }) as unknown as HtmlNode;\n\n const out: string[] = [];\n collectBodyBlocks(doc, out);\n\n if (!out.length) {\n const text = getTextContent(doc);\n return textToWordBodyXml(text);\n }\n\n return out.join(\"\");\n}\n","export type XmlToDocxInputKind = \"auto\" | \"document\" | \"body\";\nexport type HtmlToDocxInputFormat = \"auto\" | \"html\" | \"text\";\n\n/**\n * XML→DOCX 输入类型。\n * - auto: 自动判断是否为完整的 <w:document>;否则按 body 片段处理\n * - document: 你传入的是完整的 WordprocessingML document.xml(必须以 <w:document> 开头)\n * - body: 你传入的是 <w:p> 等正文片段(库会自动包一层 <w:document><w:body>)\n */\nexport interface XmlToDocxOptions {\n inputKind?: XmlToDocxInputKind;\n /**\n * 是否校验 XML 合法性。\n * - true: 发现 XML 不合法会直接抛错(推荐生产开启)\n * - false: 跳过校验(更快,但错误会在 Word 打开时才暴露)\n */\n validateXml?: boolean;\n}\n\n/**\n * HTML→DOCX 输入格式。\n * - auto: 自动判断(像 HTML 就按 HTML 解析,否则当纯文本)\n * - html: 强制当 HTML 解析\n * - text: 强制当纯文本处理(按换行拆段)\n */\nexport interface HtmlToDocxOptions {\n inputFormat?: HtmlToDocxInputFormat;\n /**\n * 是否校验最终生成的 document.xml 是否为合法 XML。\n */\n validateXml?: boolean;\n}\n\nfunction looksLikeHtml(input: string): boolean {\n const s = input.trim();\n if (!s) return false;\n if (!s.includes(\"<\") || !s.includes(\">\")) return false;\n return /<\\/?[a-zA-Z][\\s\\S]*?>/.test(s);\n}\n\n/**\n * 把 WordprocessingML XML 转成 docx(二进制 Uint8Array)。\n * 适合 Node/浏览器通用场景。\n */\nexport async function xmlToDocxUint8Array(\n xml: string,\n options: XmlToDocxOptions = {},\n): Promise<Uint8Array> {\n const { createDocxZipUint8Array } = await import(\"./lib/createDocxZip.js\");\n return createDocxZipUint8Array(xml, options);\n}\n\n/**\n * 把 WordprocessingML XML 转成 docx(Blob)。\n * 适合浏览器下载:new File([blob], 'xx.docx') 或 URL.createObjectURL(blob)。\n */\nexport async function xmlToDocxBlob(\n xml: string,\n options: XmlToDocxOptions = {},\n): Promise<Blob> {\n const docx = await xmlToDocxUint8Array(xml, options);\n const bytes = new Uint8Array(docx);\n return new Blob([bytes], {\n type: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n });\n}\n\n/**\n * 把 WordprocessingML XML 转成 docx(Buffer)。\n * 仅在 Node 环境可用,适合直接 fs.writeFileSync('xx.docx', buffer)。\n */\nexport async function xmlToDocxBuffer(\n xml: string,\n options: XmlToDocxOptions = {},\n): Promise<Buffer> {\n const docx = await xmlToDocxUint8Array(xml, options);\n const BufferCtor = (globalThis as unknown as { Buffer?: typeof Buffer }).Buffer;\n if (!BufferCtor) {\n throw new Error(\"Buffer is not available. Use xmlToDocxUint8Array or xmlToDocxBlob instead.\");\n }\n return BufferCtor.from(docx);\n}\n\n/**\n * 把 HTML/纯文本字符串转成 docx(二进制 Uint8Array)。\n * - 适合前端直接传入“很长的字符串”(HTML 或纯文本)\n * - inputFormat 默认 auto:像 HTML 就按 HTML 解析,否则按纯文本解析\n */\nexport async function htmlToDocxUint8Array(\n html: string,\n options: HtmlToDocxOptions = {},\n): Promise<Uint8Array> {\n const { htmlToWordBodyXml, textToWordBodyXml } = await import(\"./lib/htmlToWordBodyXml.js\");\n const format = options.inputFormat ?? \"auto\";\n const bodyXml =\n format === \"html\"\n ? htmlToWordBodyXml(html)\n : format === \"text\"\n ? textToWordBodyXml(html)\n : looksLikeHtml(html)\n ? htmlToWordBodyXml(html)\n : textToWordBodyXml(html);\n return xmlToDocxUint8Array(bodyXml, {\n inputKind: \"body\",\n validateXml: options.validateXml,\n });\n}\n\n/**\n * 把 HTML/纯文本字符串转成 docx(Blob)。\n * 适合浏览器下载。\n */\nexport async function htmlToDocxBlob(\n html: string,\n options: HtmlToDocxOptions = {},\n): Promise<Blob> {\n const docx = await htmlToDocxUint8Array(html, options);\n const bytes = new Uint8Array(docx);\n return new Blob([bytes], {\n type: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n });\n}\n\n/**\n * 把 HTML/纯文本字符串转成 docx(Buffer)。\n * 仅在 Node 环境可用。\n */\nexport async function htmlToDocxBuffer(\n html: string,\n options: HtmlToDocxOptions = {},\n): Promise<Buffer> {\n const docx = await htmlToDocxUint8Array(html, options);\n const BufferCtor = (globalThis as unknown as { Buffer?: typeof Buffer }).Buffer;\n if (!BufferCtor) {\n throw new Error(\"Buffer is not available. Use htmlToDocxUint8Array or htmlToDocxBlob instead.\");\n }\n return BufferCtor.from(docx);\n}\n"],"mappings":"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","names":["JSZip","children","createDocxZipUint8Array","htmlToWordBodyXml","textToWordBodyXml"]}
@@ -0,0 +1,63 @@
1
+ type XmlToDocxInputKind = "auto" | "document" | "body";
2
+ type HtmlToDocxInputFormat = "auto" | "html" | "text";
3
+ /**
4
+ * XML→DOCX 输入类型。
5
+ * - auto: 自动判断是否为完整的 <w:document>;否则按 body 片段处理
6
+ * - document: 你传入的是完整的 WordprocessingML document.xml(必须以 <w:document> 开头)
7
+ * - body: 你传入的是 <w:p> 等正文片段(库会自动包一层 <w:document><w:body>)
8
+ */
9
+ interface XmlToDocxOptions {
10
+ inputKind?: XmlToDocxInputKind;
11
+ /**
12
+ * 是否校验 XML 合法性。
13
+ * - true: 发现 XML 不合法会直接抛错(推荐生产开启)
14
+ * - false: 跳过校验(更快,但错误会在 Word 打开时才暴露)
15
+ */
16
+ validateXml?: boolean;
17
+ }
18
+ /**
19
+ * HTML→DOCX 输入格式。
20
+ * - auto: 自动判断(像 HTML 就按 HTML 解析,否则当纯文本)
21
+ * - html: 强制当 HTML 解析
22
+ * - text: 强制当纯文本处理(按换行拆段)
23
+ */
24
+ interface HtmlToDocxOptions {
25
+ inputFormat?: HtmlToDocxInputFormat;
26
+ /**
27
+ * 是否校验最终生成的 document.xml 是否为合法 XML。
28
+ */
29
+ validateXml?: boolean;
30
+ }
31
+ /**
32
+ * 把 WordprocessingML XML 转成 docx(二进制 Uint8Array)。
33
+ * 适合 Node/浏览器通用场景。
34
+ */
35
+ declare function xmlToDocxUint8Array(xml: string, options?: XmlToDocxOptions): Promise<Uint8Array>;
36
+ /**
37
+ * 把 WordprocessingML XML 转成 docx(Blob)。
38
+ * 适合浏览器下载:new File([blob], 'xx.docx') 或 URL.createObjectURL(blob)。
39
+ */
40
+ declare function xmlToDocxBlob(xml: string, options?: XmlToDocxOptions): Promise<Blob>;
41
+ /**
42
+ * 把 WordprocessingML XML 转成 docx(Buffer)。
43
+ * 仅在 Node 环境可用,适合直接 fs.writeFileSync('xx.docx', buffer)。
44
+ */
45
+ declare function xmlToDocxBuffer(xml: string, options?: XmlToDocxOptions): Promise<Buffer>;
46
+ /**
47
+ * 把 HTML/纯文本字符串转成 docx(二进制 Uint8Array)。
48
+ * - 适合前端直接传入“很长的字符串”(HTML 或纯文本)
49
+ * - inputFormat 默认 auto:像 HTML 就按 HTML 解析,否则按纯文本解析
50
+ */
51
+ declare function htmlToDocxUint8Array(html: string, options?: HtmlToDocxOptions): Promise<Uint8Array>;
52
+ /**
53
+ * 把 HTML/纯文本字符串转成 docx(Blob)。
54
+ * 适合浏览器下载。
55
+ */
56
+ declare function htmlToDocxBlob(html: string, options?: HtmlToDocxOptions): Promise<Blob>;
57
+ /**
58
+ * 把 HTML/纯文本字符串转成 docx(Buffer)。
59
+ * 仅在 Node 环境可用。
60
+ */
61
+ declare function htmlToDocxBuffer(html: string, options?: HtmlToDocxOptions): Promise<Buffer>;
62
+
63
+ export { type HtmlToDocxInputFormat, type HtmlToDocxOptions, type XmlToDocxInputKind, type XmlToDocxOptions, htmlToDocxBlob, htmlToDocxBuffer, htmlToDocxUint8Array, xmlToDocxBlob, xmlToDocxBuffer, xmlToDocxUint8Array };
@@ -0,0 +1,63 @@
1
+ type XmlToDocxInputKind = "auto" | "document" | "body";
2
+ type HtmlToDocxInputFormat = "auto" | "html" | "text";
3
+ /**
4
+ * XML→DOCX 输入类型。
5
+ * - auto: 自动判断是否为完整的 <w:document>;否则按 body 片段处理
6
+ * - document: 你传入的是完整的 WordprocessingML document.xml(必须以 <w:document> 开头)
7
+ * - body: 你传入的是 <w:p> 等正文片段(库会自动包一层 <w:document><w:body>)
8
+ */
9
+ interface XmlToDocxOptions {
10
+ inputKind?: XmlToDocxInputKind;
11
+ /**
12
+ * 是否校验 XML 合法性。
13
+ * - true: 发现 XML 不合法会直接抛错(推荐生产开启)
14
+ * - false: 跳过校验(更快,但错误会在 Word 打开时才暴露)
15
+ */
16
+ validateXml?: boolean;
17
+ }
18
+ /**
19
+ * HTML→DOCX 输入格式。
20
+ * - auto: 自动判断(像 HTML 就按 HTML 解析,否则当纯文本)
21
+ * - html: 强制当 HTML 解析
22
+ * - text: 强制当纯文本处理(按换行拆段)
23
+ */
24
+ interface HtmlToDocxOptions {
25
+ inputFormat?: HtmlToDocxInputFormat;
26
+ /**
27
+ * 是否校验最终生成的 document.xml 是否为合法 XML。
28
+ */
29
+ validateXml?: boolean;
30
+ }
31
+ /**
32
+ * 把 WordprocessingML XML 转成 docx(二进制 Uint8Array)。
33
+ * 适合 Node/浏览器通用场景。
34
+ */
35
+ declare function xmlToDocxUint8Array(xml: string, options?: XmlToDocxOptions): Promise<Uint8Array>;
36
+ /**
37
+ * 把 WordprocessingML XML 转成 docx(Blob)。
38
+ * 适合浏览器下载:new File([blob], 'xx.docx') 或 URL.createObjectURL(blob)。
39
+ */
40
+ declare function xmlToDocxBlob(xml: string, options?: XmlToDocxOptions): Promise<Blob>;
41
+ /**
42
+ * 把 WordprocessingML XML 转成 docx(Buffer)。
43
+ * 仅在 Node 环境可用,适合直接 fs.writeFileSync('xx.docx', buffer)。
44
+ */
45
+ declare function xmlToDocxBuffer(xml: string, options?: XmlToDocxOptions): Promise<Buffer>;
46
+ /**
47
+ * 把 HTML/纯文本字符串转成 docx(二进制 Uint8Array)。
48
+ * - 适合前端直接传入“很长的字符串”(HTML 或纯文本)
49
+ * - inputFormat 默认 auto:像 HTML 就按 HTML 解析,否则按纯文本解析
50
+ */
51
+ declare function htmlToDocxUint8Array(html: string, options?: HtmlToDocxOptions): Promise<Uint8Array>;
52
+ /**
53
+ * 把 HTML/纯文本字符串转成 docx(Blob)。
54
+ * 适合浏览器下载。
55
+ */
56
+ declare function htmlToDocxBlob(html: string, options?: HtmlToDocxOptions): Promise<Blob>;
57
+ /**
58
+ * 把 HTML/纯文本字符串转成 docx(Buffer)。
59
+ * 仅在 Node 环境可用。
60
+ */
61
+ declare function htmlToDocxBuffer(html: string, options?: HtmlToDocxOptions): Promise<Buffer>;
62
+
63
+ export { type HtmlToDocxInputFormat, type HtmlToDocxOptions, type XmlToDocxInputKind, type XmlToDocxOptions, htmlToDocxBlob, htmlToDocxBuffer, htmlToDocxUint8Array, xmlToDocxBlob, xmlToDocxBuffer, xmlToDocxUint8Array };
package/dist/index.js ADDED
@@ -0,0 +1,59 @@
1
+ // src/index.ts
2
+ function looksLikeHtml(input) {
3
+ const s = input.trim();
4
+ if (!s) return false;
5
+ if (!s.includes("<") || !s.includes(">")) return false;
6
+ return /<\/?[a-zA-Z][\s\S]*?>/.test(s);
7
+ }
8
+ async function xmlToDocxUint8Array(xml, options = {}) {
9
+ const { createDocxZipUint8Array } = await import("./createDocxZip-WVDRDYZT.js");
10
+ return createDocxZipUint8Array(xml, options);
11
+ }
12
+ async function xmlToDocxBlob(xml, options = {}) {
13
+ const docx = await xmlToDocxUint8Array(xml, options);
14
+ const bytes = new Uint8Array(docx);
15
+ return new Blob([bytes], {
16
+ type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
17
+ });
18
+ }
19
+ async function xmlToDocxBuffer(xml, options = {}) {
20
+ const docx = await xmlToDocxUint8Array(xml, options);
21
+ const BufferCtor = globalThis.Buffer;
22
+ if (!BufferCtor) {
23
+ throw new Error("Buffer is not available. Use xmlToDocxUint8Array or xmlToDocxBlob instead.");
24
+ }
25
+ return BufferCtor.from(docx);
26
+ }
27
+ async function htmlToDocxUint8Array(html, options = {}) {
28
+ const { htmlToWordBodyXml, textToWordBodyXml } = await import("./htmlToWordBodyXml-RFBPSL2Q.js");
29
+ const format = options.inputFormat ?? "auto";
30
+ const bodyXml = format === "html" ? htmlToWordBodyXml(html) : format === "text" ? textToWordBodyXml(html) : looksLikeHtml(html) ? htmlToWordBodyXml(html) : textToWordBodyXml(html);
31
+ return xmlToDocxUint8Array(bodyXml, {
32
+ inputKind: "body",
33
+ validateXml: options.validateXml
34
+ });
35
+ }
36
+ async function htmlToDocxBlob(html, options = {}) {
37
+ const docx = await htmlToDocxUint8Array(html, options);
38
+ const bytes = new Uint8Array(docx);
39
+ return new Blob([bytes], {
40
+ type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
41
+ });
42
+ }
43
+ async function htmlToDocxBuffer(html, options = {}) {
44
+ const docx = await htmlToDocxUint8Array(html, options);
45
+ const BufferCtor = globalThis.Buffer;
46
+ if (!BufferCtor) {
47
+ throw new Error("Buffer is not available. Use htmlToDocxUint8Array or htmlToDocxBlob instead.");
48
+ }
49
+ return BufferCtor.from(docx);
50
+ }
51
+ export {
52
+ htmlToDocxBlob,
53
+ htmlToDocxBuffer,
54
+ htmlToDocxUint8Array,
55
+ xmlToDocxBlob,
56
+ xmlToDocxBuffer,
57
+ xmlToDocxUint8Array
58
+ };
59
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export type XmlToDocxInputKind = \"auto\" | \"document\" | \"body\";\nexport type HtmlToDocxInputFormat = \"auto\" | \"html\" | \"text\";\n\n/**\n * XML→DOCX 输入类型。\n * - auto: 自动判断是否为完整的 <w:document>;否则按 body 片段处理\n * - document: 你传入的是完整的 WordprocessingML document.xml(必须以 <w:document> 开头)\n * - body: 你传入的是 <w:p> 等正文片段(库会自动包一层 <w:document><w:body>)\n */\nexport interface XmlToDocxOptions {\n inputKind?: XmlToDocxInputKind;\n /**\n * 是否校验 XML 合法性。\n * - true: 发现 XML 不合法会直接抛错(推荐生产开启)\n * - false: 跳过校验(更快,但错误会在 Word 打开时才暴露)\n */\n validateXml?: boolean;\n}\n\n/**\n * HTML→DOCX 输入格式。\n * - auto: 自动判断(像 HTML 就按 HTML 解析,否则当纯文本)\n * - html: 强制当 HTML 解析\n * - text: 强制当纯文本处理(按换行拆段)\n */\nexport interface HtmlToDocxOptions {\n inputFormat?: HtmlToDocxInputFormat;\n /**\n * 是否校验最终生成的 document.xml 是否为合法 XML。\n */\n validateXml?: boolean;\n}\n\nfunction looksLikeHtml(input: string): boolean {\n const s = input.trim();\n if (!s) return false;\n if (!s.includes(\"<\") || !s.includes(\">\")) return false;\n return /<\\/?[a-zA-Z][\\s\\S]*?>/.test(s);\n}\n\n/**\n * 把 WordprocessingML XML 转成 docx(二进制 Uint8Array)。\n * 适合 Node/浏览器通用场景。\n */\nexport async function xmlToDocxUint8Array(\n xml: string,\n options: XmlToDocxOptions = {},\n): Promise<Uint8Array> {\n const { createDocxZipUint8Array } = await import(\"./lib/createDocxZip.js\");\n return createDocxZipUint8Array(xml, options);\n}\n\n/**\n * 把 WordprocessingML XML 转成 docx(Blob)。\n * 适合浏览器下载:new File([blob], 'xx.docx') 或 URL.createObjectURL(blob)。\n */\nexport async function xmlToDocxBlob(\n xml: string,\n options: XmlToDocxOptions = {},\n): Promise<Blob> {\n const docx = await xmlToDocxUint8Array(xml, options);\n const bytes = new Uint8Array(docx);\n return new Blob([bytes], {\n type: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n });\n}\n\n/**\n * 把 WordprocessingML XML 转成 docx(Buffer)。\n * 仅在 Node 环境可用,适合直接 fs.writeFileSync('xx.docx', buffer)。\n */\nexport async function xmlToDocxBuffer(\n xml: string,\n options: XmlToDocxOptions = {},\n): Promise<Buffer> {\n const docx = await xmlToDocxUint8Array(xml, options);\n const BufferCtor = (globalThis as unknown as { Buffer?: typeof Buffer }).Buffer;\n if (!BufferCtor) {\n throw new Error(\"Buffer is not available. Use xmlToDocxUint8Array or xmlToDocxBlob instead.\");\n }\n return BufferCtor.from(docx);\n}\n\n/**\n * 把 HTML/纯文本字符串转成 docx(二进制 Uint8Array)。\n * - 适合前端直接传入“很长的字符串”(HTML 或纯文本)\n * - inputFormat 默认 auto:像 HTML 就按 HTML 解析,否则按纯文本解析\n */\nexport async function htmlToDocxUint8Array(\n html: string,\n options: HtmlToDocxOptions = {},\n): Promise<Uint8Array> {\n const { htmlToWordBodyXml, textToWordBodyXml } = await import(\"./lib/htmlToWordBodyXml.js\");\n const format = options.inputFormat ?? \"auto\";\n const bodyXml =\n format === \"html\"\n ? htmlToWordBodyXml(html)\n : format === \"text\"\n ? textToWordBodyXml(html)\n : looksLikeHtml(html)\n ? htmlToWordBodyXml(html)\n : textToWordBodyXml(html);\n return xmlToDocxUint8Array(bodyXml, {\n inputKind: \"body\",\n validateXml: options.validateXml,\n });\n}\n\n/**\n * 把 HTML/纯文本字符串转成 docx(Blob)。\n * 适合浏览器下载。\n */\nexport async function htmlToDocxBlob(\n html: string,\n options: HtmlToDocxOptions = {},\n): Promise<Blob> {\n const docx = await htmlToDocxUint8Array(html, options);\n const bytes = new Uint8Array(docx);\n return new Blob([bytes], {\n type: \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\",\n });\n}\n\n/**\n * 把 HTML/纯文本字符串转成 docx(Buffer)。\n * 仅在 Node 环境可用。\n */\nexport async function htmlToDocxBuffer(\n html: string,\n options: HtmlToDocxOptions = {},\n): Promise<Buffer> {\n const docx = await htmlToDocxUint8Array(html, options);\n const BufferCtor = (globalThis as unknown as { Buffer?: typeof Buffer }).Buffer;\n if (!BufferCtor) {\n throw new Error(\"Buffer is not available. Use htmlToDocxUint8Array or htmlToDocxBlob instead.\");\n }\n return BufferCtor.from(docx);\n}\n"],"mappings":";AAiCA,SAAS,cAAc,OAAwB;AAC7C,QAAM,IAAI,MAAM,KAAK;AACrB,MAAI,CAAC,EAAG,QAAO;AACf,MAAI,CAAC,EAAE,SAAS,GAAG,KAAK,CAAC,EAAE,SAAS,GAAG,EAAG,QAAO;AACjD,SAAO,wBAAwB,KAAK,CAAC;AACvC;AAMA,eAAsB,oBACpB,KACA,UAA4B,CAAC,GACR;AACrB,QAAM,EAAE,wBAAwB,IAAI,MAAM,OAAO,6BAAwB;AACzE,SAAO,wBAAwB,KAAK,OAAO;AAC7C;AAMA,eAAsB,cACpB,KACA,UAA4B,CAAC,GACd;AACf,QAAM,OAAO,MAAM,oBAAoB,KAAK,OAAO;AACnD,QAAM,QAAQ,IAAI,WAAW,IAAI;AACjC,SAAO,IAAI,KAAK,CAAC,KAAK,GAAG;AAAA,IACvB,MAAM;AAAA,EACR,CAAC;AACH;AAMA,eAAsB,gBACpB,KACA,UAA4B,CAAC,GACZ;AACjB,QAAM,OAAO,MAAM,oBAAoB,KAAK,OAAO;AACnD,QAAM,aAAc,WAAqD;AACzE,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,MAAM,4EAA4E;AAAA,EAC9F;AACA,SAAO,WAAW,KAAK,IAAI;AAC7B;AAOA,eAAsB,qBACpB,MACA,UAA6B,CAAC,GACT;AACrB,QAAM,EAAE,mBAAmB,kBAAkB,IAAI,MAAM,OAAO,iCAA4B;AAC1F,QAAM,SAAS,QAAQ,eAAe;AACtC,QAAM,UACJ,WAAW,SACP,kBAAkB,IAAI,IACtB,WAAW,SACT,kBAAkB,IAAI,IACtB,cAAc,IAAI,IAChB,kBAAkB,IAAI,IACtB,kBAAkB,IAAI;AAChC,SAAO,oBAAoB,SAAS;AAAA,IAClC,WAAW;AAAA,IACX,aAAa,QAAQ;AAAA,EACvB,CAAC;AACH;AAMA,eAAsB,eACpB,MACA,UAA6B,CAAC,GACf;AACf,QAAM,OAAO,MAAM,qBAAqB,MAAM,OAAO;AACrD,QAAM,QAAQ,IAAI,WAAW,IAAI;AACjC,SAAO,IAAI,KAAK,CAAC,KAAK,GAAG;AAAA,IACvB,MAAM;AAAA,EACR,CAAC;AACH;AAMA,eAAsB,iBACpB,MACA,UAA6B,CAAC,GACb;AACjB,QAAM,OAAO,MAAM,qBAAqB,MAAM,OAAO;AACrD,QAAM,aAAc,WAAqD;AACzE,MAAI,CAAC,YAAY;AACf,UAAM,IAAI,MAAM,8EAA8E;AAAA,EAChG;AACA,SAAO,WAAW,KAAK,IAAI;AAC7B;","names":[]}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@yinyoudexing/xml2word",
3
+ "version": "0.1.0",
4
+ "description": "Convert WordprocessingML XML / HTML / text into a .docx file",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "sideEffects": false,
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "keywords": [
24
+ "docx",
25
+ "word",
26
+ "wordprocessingml",
27
+ "xml",
28
+ "html",
29
+ "export"
30
+ ],
31
+ "scripts": {
32
+ "build": "tsup src/index.ts --format esm,cjs --dts --sourcemap --clean",
33
+ "typecheck": "tsc -p tsconfig.json --noEmit",
34
+ "test": "npm run build && node --test",
35
+ "prepublishOnly": "npm run test && npm run typecheck"
36
+ },
37
+ "dependencies": {
38
+ "fast-xml-parser": "^4.5.1",
39
+ "htmlparser2": "^10.0.0",
40
+ "jszip": "^3.10.1"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^22.10.2",
44
+ "tsup": "^8.3.5",
45
+ "typescript": "^5.7.3"
46
+ }
47
+ }