@courtifyai/docx-render 1.0.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,3 @@
1
+ export { DocumentParser } from './document-parser'
2
+ export { XmlParser, xmlParser, parseXmlString, LengthUsage } from './xml-parser'
3
+ export type { ILengthUsage } from './xml-parser'
@@ -0,0 +1,152 @@
1
+ import { XML_NS } from '../types'
2
+
3
+ /**
4
+ * XML 解析器
5
+ * 提供 XML 元素遍历和属性读取的工具方法
6
+ */
7
+ export class XmlParser {
8
+ /**
9
+ * 获取元素的所有子元素
10
+ */
11
+ elements(elem: Element, localName?: string): Element[] {
12
+ const result: Element[] = []
13
+ for (let i = 0; i < elem.childNodes.length; i++) {
14
+ const child = elem.childNodes[i]
15
+ if (child.nodeType === Node.ELEMENT_NODE) {
16
+ if (!localName || (child as Element).localName === localName) {
17
+ result.push(child as Element)
18
+ }
19
+ }
20
+ }
21
+ return result
22
+ }
23
+
24
+ /**
25
+ * 获取第一个匹配的子元素
26
+ */
27
+ element(elem: Element, localName: string): Element | null {
28
+ for (let i = 0; i < elem.childNodes.length; i++) {
29
+ const child = elem.childNodes[i]
30
+ if (child.nodeType === Node.ELEMENT_NODE && (child as Element).localName === localName) {
31
+ return child as Element
32
+ }
33
+ }
34
+ return null
35
+ }
36
+
37
+ /**
38
+ * 获取元素属性值
39
+ */
40
+ attr(elem: Element | null | undefined, localName: string): string | undefined {
41
+ if (!elem) return undefined
42
+
43
+ // 尝试带命名空间的属性
44
+ for (const ns of Object.values(XML_NS)) {
45
+ const value = elem.getAttributeNS(ns, localName)
46
+ if (value) return value
47
+ }
48
+ // 尝试带前缀的属性
49
+ const prefixedValue = elem.getAttribute(`w:${localName}`)
50
+ if (prefixedValue) return prefixedValue
51
+ // 尝试无前缀属性
52
+ return elem.getAttribute(localName) || undefined
53
+ }
54
+
55
+ /**
56
+ * 获取布尔属性
57
+ */
58
+ boolAttr(elem: Element | null | undefined, localName: string, defaultValue = false): boolean {
59
+ if (!elem) return defaultValue
60
+ const val = this.attr(elem, localName)
61
+ if (val === undefined) return defaultValue
62
+ return val === '1' || val === 'true' || val === 'on'
63
+ }
64
+
65
+ /**
66
+ * 获取整数属性
67
+ */
68
+ intAttr(elem: Element | null | undefined, localName: string): number | undefined {
69
+ if (!elem) return undefined
70
+ const val = this.attr(elem, localName)
71
+ if (!val) return undefined
72
+ return parseInt(val, 10)
73
+ }
74
+
75
+ /**
76
+ * 获取长度属性并转换单位
77
+ */
78
+ lengthAttr(elem: Element | null | undefined, localName: string, usage: ILengthUsage = LengthUsage.Dxa): string | undefined {
79
+ if (!elem) return undefined
80
+ const val = this.attr(elem, localName)
81
+ return this.convertLength(val, usage)
82
+ }
83
+
84
+ /**
85
+ * 转换长度单位
86
+ */
87
+ convertLength(val: string | undefined, usage: ILengthUsage = LengthUsage.Dxa): string | undefined {
88
+ if (!val) return undefined
89
+ // 如果已经有单位,直接返回
90
+ if (/[a-z%]+$/i.test(val)) return val
91
+
92
+ const num = parseFloat(val) * usage.mul
93
+ return `${num.toFixed(2)}${usage.unit}`
94
+ }
95
+
96
+ /**
97
+ * 获取元素的文本内容
98
+ */
99
+ textContent(elem: Element | null | undefined): string {
100
+ if (!elem) return ''
101
+ return elem.textContent || ''
102
+ }
103
+ }
104
+
105
+ /**
106
+ * 长度单位转换配置
107
+ */
108
+ export interface ILengthUsage {
109
+ mul: number
110
+ unit: string
111
+ }
112
+
113
+ export const LengthUsage = {
114
+ /** 1/20 点 (twip) */
115
+ Dxa: { mul: 0.05, unit: 'pt' },
116
+ /** EMU (English Metric Unit) */
117
+ Emu: { mul: 1 / 12700, unit: 'pt' },
118
+ /** 半点 */
119
+ FontSize: { mul: 0.5, unit: 'pt' },
120
+ /** 1/8 点 */
121
+ Border: { mul: 0.125, unit: 'pt' },
122
+ /** 点 */
123
+ Point: { mul: 1, unit: 'pt' },
124
+ /** 百分比 (1/50) */
125
+ Percent: { mul: 0.02, unit: '%' },
126
+ } as const
127
+
128
+ /**
129
+ * 解析 XML 字符串
130
+ */
131
+ export function parseXmlString(xmlString: string): Document {
132
+ // 移除 BOM
133
+ if (xmlString.charCodeAt(0) === 0xFEFF) {
134
+ xmlString = xmlString.substring(1)
135
+ }
136
+ // 移除 XML 声明
137
+ xmlString = xmlString.replace(/<\?xml[^?]*\?>/g, '')
138
+
139
+ const parser = new DOMParser()
140
+ const doc = parser.parseFromString(xmlString, 'application/xml')
141
+
142
+ // 检查解析错误
143
+ const parseError = doc.getElementsByTagName('parsererror')[0]
144
+ if (parseError) {
145
+ throw new Error(`XML 解析错误: ${parseError.textContent}`)
146
+ }
147
+
148
+ return doc
149
+ }
150
+
151
+ // 全局 XML 解析器实例
152
+ export const xmlParser = new XmlParser()