hm-doc-tool 0.3.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.
Files changed (40) hide show
  1. package/README.md +230 -0
  2. package/dist/api-client.d.ts +44 -0
  3. package/dist/api-client.js +137 -0
  4. package/dist/common/config.d.ts +11 -0
  5. package/dist/common/config.js +70 -0
  6. package/dist/common/path-generator.d.ts +39 -0
  7. package/dist/common/path-generator.js +192 -0
  8. package/dist/common/tree-formatter.d.ts +19 -0
  9. package/dist/common/tree-formatter.js +129 -0
  10. package/dist/common/tree-processor.d.ts +35 -0
  11. package/dist/common/tree-processor.js +145 -0
  12. package/dist/common/types.d.ts +38 -0
  13. package/dist/common/types.js +5 -0
  14. package/dist/download/downloader.d.ts +46 -0
  15. package/dist/download/downloader.js +251 -0
  16. package/dist/download/index.d.ts +16 -0
  17. package/dist/download/index.js +168 -0
  18. package/dist/download/link-localizer.d.ts +13 -0
  19. package/dist/download/link-localizer.js +116 -0
  20. package/dist/download/markdown-converter.d.ts +1 -0
  21. package/dist/download/markdown-converter.js +96 -0
  22. package/dist/download/summary-generator.d.ts +46 -0
  23. package/dist/download/summary-generator.js +188 -0
  24. package/dist/download/turndown-rules.d.ts +2 -0
  25. package/dist/download/turndown-rules.js +394 -0
  26. package/dist/index.d.ts +2 -0
  27. package/dist/index.js +65 -0
  28. package/dist/tree/index.d.ts +11 -0
  29. package/dist/tree/index.js +91 -0
  30. package/dist/wiki/index.d.ts +7 -0
  31. package/dist/wiki/index.js +22 -0
  32. package/dist/wiki/wiki-generator.d.ts +3 -0
  33. package/dist/wiki/wiki-generator.js +357 -0
  34. package/dist/wiki/wiki-types.d.ts +61 -0
  35. package/dist/wiki/wiki-types.js +3 -0
  36. package/dist/wiki/wiki-utils.d.ts +33 -0
  37. package/dist/wiki/wiki-utils.js +180 -0
  38. package/docs_catalog.json +28 -0
  39. package/package.json +29 -0
  40. package/wiki_config.json +198 -0
@@ -0,0 +1,46 @@
1
+ import type { FlatNode, OutputTask } from "../common/types.js";
2
+ /** Summary 节点结构 */
3
+ export interface SummaryNode {
4
+ nodeId: string;
5
+ nodeName: string;
6
+ relateDocument: string | null;
7
+ parent: string | null;
8
+ summary: string;
9
+ }
10
+ /**
11
+ * 从 Markdown 内容中提取第一行正文
12
+ *
13
+ * 排除:
14
+ * - yaml frontmatter(--- 包裹的部分)
15
+ * - 标题(# 开头)
16
+ * - 代码块(``` 包裹的部分)
17
+ * - 空行
18
+ * - 链接(只保留链接文本)
19
+ */
20
+ export declare function extractFirstLine(content: string): string;
21
+ /**
22
+ * 为下载任务生成 summary(只处理叶子节点)
23
+ *
24
+ * @param nodes - 所有平铺节点
25
+ * @param tasks - 下载任务列表(包含正确的文件路径)
26
+ */
27
+ export declare function generateSummary(nodes: FlatNode[], tasks: OutputTask[]): Promise<SummaryNode[]>;
28
+ /**
29
+ * 保存 summary JSON 文件
30
+ *
31
+ * @param summaryNodes - summary 节点列表
32
+ * @param outputPath - 输出文件路径
33
+ */
34
+ export declare function saveSummaryJson(summaryNodes: SummaryNode[], outputPath: string): Promise<void>;
35
+ /**
36
+ * 生成 summary JSON 文件(主入口)
37
+ *
38
+ * 直接使用下载流程返回的任务列表和节点数据,避免重复计算路径
39
+ *
40
+ * @param catalogName - catalog 名称
41
+ * @param outputDir - 输出目录(下载时使用的 actualOutputDir)
42
+ * @param tasks - 下载任务列表
43
+ * @param nodes - 平铺节点列表
44
+ * @param targetNodeName - 目标节点名称(用于文件名)
45
+ */
46
+ export declare function generateSummaryFromTasks(catalogName: string, outputDir: string, tasks: OutputTask[], nodes: FlatNode[], targetNodeName?: string): Promise<void>;
@@ -0,0 +1,188 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.extractFirstLine = extractFirstLine;
37
+ exports.generateSummary = generateSummary;
38
+ exports.saveSummaryJson = saveSummaryJson;
39
+ exports.generateSummaryFromTasks = generateSummaryFromTasks;
40
+ /**
41
+ * Summary 生成器
42
+ *
43
+ * 职责:
44
+ * 1. 从下载的 Markdown 文件中提取第一行正文作为摘要
45
+ * 2. 生成 summary JSON 文件
46
+ */
47
+ const fs = __importStar(require("fs/promises"));
48
+ const path = __importStar(require("path"));
49
+ /**
50
+ * 从 Markdown 内容中提取第一行正文
51
+ *
52
+ * 排除:
53
+ * - yaml frontmatter(--- 包裹的部分)
54
+ * - 标题(# 开头)
55
+ * - 代码块(``` 包裹的部分)
56
+ * - 空行
57
+ * - 链接(只保留链接文本)
58
+ */
59
+ function extractFirstLine(content) {
60
+ const lines = content.split("\n");
61
+ let inFrontmatter = false;
62
+ let inCodeBlock = false;
63
+ let frontmatterEnded = false;
64
+ for (const line of lines) {
65
+ const trimmed = line.trim();
66
+ // 处理 yaml frontmatter
67
+ if (trimmed === "---") {
68
+ if (!inFrontmatter && !frontmatterEnded) {
69
+ // 第一个 --- 开始 frontmatter
70
+ inFrontmatter = true;
71
+ continue;
72
+ }
73
+ else if (inFrontmatter) {
74
+ // 第二个 --- 结束 frontmatter
75
+ inFrontmatter = false;
76
+ frontmatterEnded = true;
77
+ continue;
78
+ }
79
+ }
80
+ if (inFrontmatter)
81
+ continue;
82
+ // 处理代码块
83
+ if (trimmed.startsWith("```")) {
84
+ inCodeBlock = !inCodeBlock;
85
+ continue;
86
+ }
87
+ if (inCodeBlock)
88
+ continue;
89
+ // 跳过空行、标题、表格
90
+ if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("|"))
91
+ continue;
92
+ // 提取正文,去掉链接标记
93
+ let text = trimmed;
94
+ // 去掉列表符号
95
+ text = text.replace(/^-\s+/, "");
96
+ // 处理图片链接: ![alt](url) -> alt(必须在链接之前,否则 ![alt](url) 会被链接正则误匹配)
97
+ text = text.replace(/!\[([^\]]*)\]\([^()]*(?:\([^)]*\)[^()]*)*\)/g, "$1");
98
+ // 处理 markdown 链接: [text](url) -> text
99
+ // url 中可能含括号(如文件名),用 [^()]*(?:\([^)]*\)[^()]*)* 匹配一层嵌套
100
+ text = text.replace(/\[([^\]]+)\]\([^()]*(?:\([^)]*\)[^()]*)*\)/g, "$1");
101
+ // 处理 html 链接: <a href="url">text</a> -> text
102
+ text = text.replace(/<a\s+[^>]*>([^<]+)<\/a>/gi, "$1");
103
+ // 去掉加粗符号
104
+ text = text.replace(/\*\*([^*]+)\*\*/g, "$1");
105
+ // 如果处理后还有内容,且不是"说明"或"注意",返回
106
+ const cleaned = text.trim();
107
+ if (cleaned && cleaned !== "说明" && cleaned !== "注意") {
108
+ // 跳过 API 版本说明文本
109
+ if (/API version \d+开始支持.*后续版本.*起始版本/.test(cleaned)) {
110
+ continue;
111
+ }
112
+ return cleaned;
113
+ }
114
+ }
115
+ return "";
116
+ }
117
+ /**
118
+ * 为下载任务生成 summary(只处理叶子节点)
119
+ *
120
+ * @param nodes - 所有平铺节点
121
+ * @param tasks - 下载任务列表(包含正确的文件路径)
122
+ */
123
+ async function generateSummary(nodes, tasks) {
124
+ const summaryNodes = [];
125
+ // 创建 nodeId 到节点的映射
126
+ const nodeMap = new Map(nodes.map((n) => [n.nodeId, n]));
127
+ // 只处理叶子节点的下载任务
128
+ const leafTasks = tasks.filter((t) => t.isLeaf);
129
+ for (const task of leafTasks) {
130
+ const node = nodeMap.get(task.nodeId);
131
+ if (!node)
132
+ continue;
133
+ let summary = "";
134
+ const filePath = path.join(task.outputDir, task.fileName);
135
+ try {
136
+ const content = await fs.readFile(filePath, "utf-8");
137
+ summary = extractFirstLine(content);
138
+ }
139
+ catch {
140
+ // 文件不存在或读取失败,summary 为空
141
+ }
142
+ // 如果没有提取到摘要,使用 nodeName
143
+ if (!summary) {
144
+ summary = node.nodeName;
145
+ }
146
+ summaryNodes.push({
147
+ nodeId: node.nodeId,
148
+ nodeName: node.nodeName,
149
+ relateDocument: node.relateDocument,
150
+ parent: node.parent,
151
+ summary,
152
+ });
153
+ }
154
+ return summaryNodes;
155
+ }
156
+ /**
157
+ * 保存 summary JSON 文件
158
+ *
159
+ * @param summaryNodes - summary 节点列表
160
+ * @param outputPath - 输出文件路径
161
+ */
162
+ async function saveSummaryJson(summaryNodes, outputPath) {
163
+ const json = JSON.stringify(summaryNodes, null, 2);
164
+ await fs.writeFile(outputPath, json, "utf-8");
165
+ }
166
+ /**
167
+ * 生成 summary JSON 文件(主入口)
168
+ *
169
+ * 直接使用下载流程返回的任务列表和节点数据,避免重复计算路径
170
+ *
171
+ * @param catalogName - catalog 名称
172
+ * @param outputDir - 输出目录(下载时使用的 actualOutputDir)
173
+ * @param tasks - 下载任务列表
174
+ * @param nodes - 平铺节点列表
175
+ * @param targetNodeName - 目标节点名称(用于文件名)
176
+ */
177
+ async function generateSummaryFromTasks(catalogName, outputDir, tasks, nodes, targetNodeName) {
178
+ console.log("\n正在生成 summary ...");
179
+ const summaryNodes = await generateSummary(nodes, tasks);
180
+ // 构建文件名:catalog-nodename_summary.json 或 catalog_summary.json
181
+ let summaryFileName = `${catalogName}_summary.json`;
182
+ if (targetNodeName) {
183
+ summaryFileName = `${catalogName}-${targetNodeName}_summary.json`;
184
+ }
185
+ const summaryPath = path.join(outputDir, summaryFileName);
186
+ await saveSummaryJson(summaryNodes, summaryPath);
187
+ console.log(`已生成 summary (${summaryNodes.length} 个节点) -> ${summaryPath}`);
188
+ }
@@ -0,0 +1,2 @@
1
+ import TurndownService from "turndown";
2
+ export declare function addCustomRules(td: TurndownService): void;
@@ -0,0 +1,394 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.addCustomRules = addCustomRules;
4
+ // UI text to strip from code blocks and content
5
+ const UI_TEXT = new Set([
6
+ "展开", "收起", "复制", "深色代码主题", "自动换行", "深色",
7
+ "代码主题", "收起代码", "Copy", "Collapse",
8
+ ]);
9
+ function isUnwantedText(text) {
10
+ return UI_TEXT.has(text.trim());
11
+ }
12
+ // --- Cleanup rules ---
13
+ const removeCodeUIContainers = {
14
+ filter(node) {
15
+ if (node.nodeType !== 1)
16
+ return false;
17
+ const cls = node.className?.toLowerCase() ?? "";
18
+ return [
19
+ "code-operate", "code-tools", "code-action", "code-copy",
20
+ "code-fold", "code-theme", "code-line-num",
21
+ "table-expand", "table-collapse", "expand-btn", "collapse-btn",
22
+ ].some((c) => cls.includes(c));
23
+ },
24
+ replacement() {
25
+ return "";
26
+ },
27
+ };
28
+ const removeUIParagraphs = {
29
+ filter(node) {
30
+ if (node.nodeType !== 1)
31
+ return false;
32
+ if (!["P", "DIV", "SPAN", "TD", "TH"].includes(node.nodeName))
33
+ return false;
34
+ const text = (node.textContent ?? "").trim();
35
+ return text.length <= 10 && isUnwantedText(text);
36
+ },
37
+ replacement() {
38
+ return "";
39
+ },
40
+ };
41
+ // Navigation short text that should be removed when appearing as exact content
42
+ const NAV_SHORT_TEXT = new Set([
43
+ "简体中文", "English", "Рус中文", "下载 App",
44
+ "探索", "设计", "开发", "分发", "推广与变现", "生态合作", "支持", "更多",
45
+ "立即登录", "输入关键字搜索",
46
+ "Hello,", "欢迎来到开发者联盟",
47
+ "CTRL+K", "Created with Pixso",
48
+ // FAQ 特定导航文本
49
+ "版本说明", "指南", "API参考", "最佳实践", "FAQ", "变更预告",
50
+ "多设备场景", "手机", "Pura X常见问题",
51
+ "应用质量", "技术质量",
52
+ "开发者能力认证", "我的", "管理中心", "个人中心",
53
+ "我的学堂", "我的收藏", "我的活动", "我的工单",
54
+ // FAQ 侧边栏标签
55
+ "Stack", "HarmonyOS 6", "animation", "module.json5",
56
+ "layoutWeight", "Navigation", "bindPopup", "bindsheet",
57
+ "RelativeContainer", "CustomDialogController", "animateTo",
58
+ ]);
59
+ const removeNavigation = {
60
+ filter(node) {
61
+ if (node.nodeType !== 1)
62
+ return false;
63
+ const text = (node.textContent ?? "").trim();
64
+ // Remove elements whose text exactly matches navigation short text
65
+ if (text.length <= 20 && NAV_SHORT_TEXT.has(text)) {
66
+ return true;
67
+ }
68
+ // Remove empty paragraphs (but keep those with images/tables/code)
69
+ if (node.nodeName === "P" && text.length === 0) {
70
+ const hasContent = node.querySelector("img, code, pre, table");
71
+ if (!hasContent)
72
+ return true;
73
+ }
74
+ const cls = node.className?.toLowerCase() ?? "";
75
+ const id = node.id?.toLowerCase() ?? "";
76
+ const unwantedClasses = [
77
+ "top-bar", "menu-bar", "sidebar", "search-bar", "breadcrumb", "toolbar", "footer-nav", "main-nav",
78
+ "side-nav", "nav-menu", "header-nav", "page-header",
79
+ "doc-nav", "related-links", "more-links",
80
+ "social-share", "social-links", "social-media",
81
+ "community-section", "feedback-section", "qrcode-section",
82
+ ];
83
+ const classList = cls.split(/\s+/);
84
+ const matchesClass = classList.some((c) => unwantedClasses.some((u) => c === u || c.startsWith(u + "-") || c.endsWith("-" + u)));
85
+ const matchesId = unwantedClasses.some((u) => id === u || id.startsWith(u + "-") || id.endsWith("-" + u));
86
+ return matchesClass || matchesId;
87
+ },
88
+ replacement() {
89
+ return "";
90
+ },
91
+ };
92
+ const removeCodeUI = {
93
+ filter(node) {
94
+ if (node.nodeType === 3) {
95
+ return isUnwantedText(node.textContent ?? "");
96
+ }
97
+ if (node.nodeType === 1 && ["P", "DIV", "SPAN"].includes(node.nodeName)) {
98
+ const text = (node.textContent ?? "").trim();
99
+ return isUnwantedText(text);
100
+ }
101
+ return false;
102
+ },
103
+ replacement() {
104
+ return "";
105
+ },
106
+ };
107
+ const removePlatformBadge = {
108
+ filter(node) {
109
+ return node.nodeType === 3 && node.textContent?.trim() === "PhonePC/2in1TabletTVWearable";
110
+ },
111
+ replacement() {
112
+ return "";
113
+ },
114
+ };
115
+ // --- Code block rules ---
116
+ const huaweiCodeBlock = {
117
+ filter(node) {
118
+ return node.nodeName === "PRE" && !!node.querySelector("ol.linenums");
119
+ },
120
+ replacement(_content, node) {
121
+ const langMatch = (node.className ?? "").match(/language-(\w+)/);
122
+ const language = langMatch?.[1] ?? "";
123
+ const lines = [];
124
+ const ol = node.querySelector("ol.linenums");
125
+ if (ol) {
126
+ const lis = ol.querySelectorAll("li");
127
+ for (let i = 0; i < lis.length; i++) {
128
+ const li = lis[i];
129
+ const text = (li.textContent ?? "").replace(/\n+$/, "").trimEnd();
130
+ if (text && !isUnwantedText(text)) {
131
+ lines.push(text);
132
+ }
133
+ }
134
+ }
135
+ return "\n```" + language + "\n" + lines.join("\n") + "\n```\n";
136
+ },
137
+ };
138
+ function inferLanguage(codeText) {
139
+ if (codeText.includes("import ") || codeText.includes("export ") || codeText.includes("interface ")) {
140
+ return "typescript";
141
+ }
142
+ if (codeText.includes("#include") || codeText.includes("int main")) {
143
+ return "cpp";
144
+ }
145
+ if (codeText.includes("public class")) {
146
+ return "java";
147
+ }
148
+ return "";
149
+ }
150
+ const genericPre = {
151
+ filter(node) {
152
+ return node.nodeName === "PRE" && !node.querySelector("ol.linenums");
153
+ },
154
+ replacement(content, node) {
155
+ const codeElem = node.querySelector("code");
156
+ let codeText = codeElem?.textContent ?? node.textContent ?? "";
157
+ if (content && content.trim().length > codeText.trim().length) {
158
+ codeText = content;
159
+ }
160
+ // Detect language from class
161
+ let language = "";
162
+ const clsMatch = (node.className ?? "").match(/language-(\w+)/);
163
+ if (clsMatch)
164
+ language = clsMatch[1];
165
+ if (!language && codeElem) {
166
+ const codeMatch = (codeElem.className ?? "").match(/language-(\w+)/);
167
+ if (codeMatch)
168
+ language = codeMatch[1];
169
+ }
170
+ // Infer language from content if not detected
171
+ if (!language && codeText) {
172
+ language = inferLanguage(codeText);
173
+ }
174
+ const cleanLines = codeText.split("\n").filter((line) => {
175
+ const trimmed = line.trim();
176
+ return !trimmed || !isUnwantedText(trimmed);
177
+ });
178
+ return "\n```" + language + "\n" + cleanLines.join("\n") + "\n```\n";
179
+ },
180
+ };
181
+ const standardCodeBlock = {
182
+ filter(node) {
183
+ return node.nodeName === "PRE"
184
+ && !!node.firstChild
185
+ && node.firstChild.nodeName === "CODE";
186
+ },
187
+ replacement(content, node) {
188
+ const codeNode = node.firstChild;
189
+ const lang = codeNode.className || codeNode.getAttribute("class") || "";
190
+ const langMatch = lang.match(/language-(\w+)|hljs language-(\w+)/);
191
+ const language = langMatch ? (langMatch[1] || langMatch[2]) : "";
192
+ const codeContent = codeNode.textContent || content;
193
+ const cleanContent = codeContent
194
+ .split("\n")
195
+ .filter((line) => {
196
+ const trimmed = line.trim();
197
+ return !isUnwantedText(trimmed);
198
+ })
199
+ .join("\n");
200
+ return "\n```" + language + "\n" + cleanContent + "\n```\n";
201
+ },
202
+ };
203
+ // --- Table rules ---
204
+ /**
205
+ * 递归提取节点的富文本内容,保留链接、代码、图片等内联格式
206
+ */
207
+ function richTextContent(node) {
208
+ let result = "";
209
+ for (const child of Array.from(node.childNodes)) {
210
+ if (child.nodeType === 3) {
211
+ result += child.textContent ?? "";
212
+ }
213
+ else if (child.nodeType === 1) {
214
+ const el = child;
215
+ if (el.nodeName === "A" && el.getAttribute("href")) {
216
+ const href = el.getAttribute("href");
217
+ const full = href.startsWith("#") ? href : href.startsWith("http") ? href : `https://developer.huawei.com${href}`;
218
+ result += `[${el.textContent?.trim() ?? ""}](${full})`;
219
+ }
220
+ else if (el.nodeName === "CODE") {
221
+ result += `\`${el.textContent?.trim() ?? ""}\``;
222
+ }
223
+ else if (el.nodeName === "SUP") {
224
+ result += `<sup>${el.textContent ?? ""}</sup>`;
225
+ }
226
+ else if (el.nodeName === "IMG") {
227
+ const src = el.getAttribute("src") ?? "";
228
+ const alt = el.getAttribute("alt") ?? "";
229
+ result += `![${alt}](${src})`;
230
+ }
231
+ else if (el.nodeName === "P" || el.nodeName === "DIV" || el.nodeName === "SPAN" || el.nodeName === "STRONG" || el.nodeName === "EM") {
232
+ // 递归处理容器元素,保留内部格式
233
+ const inner = richTextContent(el);
234
+ if (el.nodeName === "STRONG")
235
+ result += `**${inner}**`;
236
+ else if (el.nodeName === "EM")
237
+ result += `*${inner}*`;
238
+ else
239
+ result += inner;
240
+ }
241
+ else {
242
+ result += richTextContent(el);
243
+ }
244
+ }
245
+ }
246
+ return result;
247
+ }
248
+ /**
249
+ * 提取单元格富文本内容,保留链接、代码、图片等内联格式
250
+ */
251
+ function cellRichText(cell) {
252
+ return cleanCellText(richTextContent(cell));
253
+ }
254
+ function cleanCellText(text) {
255
+ let cleaned = text.replace(/\s+/g, " ");
256
+ for (const t of UI_TEXT) {
257
+ cleaned = cleaned.replaceAll(t, "");
258
+ }
259
+ return cleaned.trim();
260
+ }
261
+ const harmonyTable = {
262
+ filter(node) {
263
+ return node.nodeName === "TABLE";
264
+ },
265
+ replacement(_content, node) {
266
+ const rows = Array.from(node.querySelectorAll("tr"));
267
+ if (rows.length === 0)
268
+ return "";
269
+ let md = "\n";
270
+ rows.forEach((row, rowIndex) => {
271
+ const cells = Array.from(row.children)
272
+ .filter((c) => c.nodeName === "TH" || c.nodeName === "TD")
273
+ .map((cell) => {
274
+ return cellRichText(cell);
275
+ });
276
+ if (cells.length === 0)
277
+ return;
278
+ md += "| " + cells.join(" | ") + " |\n";
279
+ const isHeader = rowIndex === 0 &&
280
+ (Array.from(row.children).every((_, i) => row.children[i]?.nodeName === "TH") ||
281
+ row.parentElement?.nodeName === "THEAD");
282
+ if (isHeader) {
283
+ md += "| " + cells.map(() => "---").join(" | ") + " |\n";
284
+ }
285
+ });
286
+ return md + "\n";
287
+ },
288
+ };
289
+ const divTable = {
290
+ filter(node) {
291
+ if (node.nodeType !== 1 || node.nodeName !== "DIV")
292
+ return false;
293
+ const cls = (node.className ?? "").toLowerCase();
294
+ const tableClasses = ["table", "tbl", "data-table", "table-container", "table-wrap"];
295
+ if (!tableClasses.some((c) => cls.includes(c)))
296
+ return false;
297
+ const rowDivs = Array.from(node.children).filter((child) => {
298
+ const childClass = (child.className ?? "").toLowerCase();
299
+ return childClass.includes("tr") || childClass.includes("row") || childClass.includes("table-row");
300
+ });
301
+ return rowDivs.length > 0;
302
+ },
303
+ replacement(content, node) {
304
+ const rows = Array.from(node.children).filter((child) => {
305
+ const childClass = (child.className ?? "").toLowerCase();
306
+ return childClass.includes("tr") || childClass.includes("row") || childClass.includes("table-row");
307
+ });
308
+ if (rows.length === 0)
309
+ return content;
310
+ let md = "\n";
311
+ rows.forEach((row, rowIndex) => {
312
+ const cells = Array.from(row.children)
313
+ .filter((child) => {
314
+ const childClass = (child.className ?? "").toLowerCase();
315
+ return childClass.includes("td") || childClass.includes("th") || childClass.includes("cell");
316
+ })
317
+ .map((cell) => {
318
+ return cellRichText(cell);
319
+ });
320
+ if (cells.length === 0)
321
+ return;
322
+ md += "| " + cells.join(" | ") + " |\n";
323
+ if (rowIndex === 0) {
324
+ md += "| " + cells.map(() => "---").join(" | ") + " |\n";
325
+ }
326
+ });
327
+ return md + "\n";
328
+ },
329
+ };
330
+ // --- Superscript rule ---
331
+ const processSuperscript = {
332
+ filter(node) {
333
+ return node.nodeName === "SUP";
334
+ },
335
+ replacement(content) {
336
+ return ` <sup>${content}</sup>`;
337
+ },
338
+ };
339
+ // --- Image rules ---
340
+ const processImages = {
341
+ filter(node) {
342
+ return node.nodeName === "IMG";
343
+ },
344
+ replacement(content, node) {
345
+ const src = node.getAttribute("src") ?? "";
346
+ const alt = node.getAttribute("alt") ?? "";
347
+ // 检查是否是特殊图标
348
+ if (src.includes("note_3.0-zh-cn.png")) {
349
+ return "**说明**";
350
+ }
351
+ if (src.includes("notice_3.0-zh-cn.png") || src.includes("caution_3.0-zh-cn.png")) {
352
+ return "**注意**";
353
+ }
354
+ // 普通图片,保持原有格式
355
+ return `![${alt}](${src})`;
356
+ },
357
+ };
358
+ // --- Link rule ---
359
+ const processLinks = {
360
+ filter(node) {
361
+ return node.nodeName === "A" && !!node.getAttribute("href");
362
+ },
363
+ replacement(content, node) {
364
+ const href = node.getAttribute("href");
365
+ if (!href || href === "#" || href === "javascript:void(0)")
366
+ return content ?? "";
367
+ // 锚点链接保持原样,不拼域名
368
+ if (href.startsWith("#"))
369
+ return `[${content}](${href})`;
370
+ const full = href.startsWith("http") ? href : `https://developer.huawei.com${href}`;
371
+ return `[${content}](${full})`;
372
+ },
373
+ };
374
+ // --- Register all ---
375
+ function addCustomRules(td) {
376
+ const rules = [
377
+ ["removeCodeUIContainers", removeCodeUIContainers],
378
+ ["removeUIParagraphs", removeUIParagraphs],
379
+ ["removeNavigation", removeNavigation],
380
+ ["removeCodeUI", removeCodeUI],
381
+ ["removePlatformBadge", removePlatformBadge],
382
+ ["huaweiCodeBlock", huaweiCodeBlock],
383
+ ["genericPre", genericPre],
384
+ ["standardCodeBlock", standardCodeBlock],
385
+ ["harmonyTable", harmonyTable],
386
+ ["divTable", divTable],
387
+ ["processSuperscript", processSuperscript],
388
+ ["processImages", processImages],
389
+ ["processLinks", processLinks],
390
+ ];
391
+ for (const [name, rule] of rules) {
392
+ td.addRule(name, rule);
393
+ }
394
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};