getmdfromleetcode 1.1.0 → 1.1.2

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.
package/README.md CHANGED
@@ -12,6 +12,7 @@
12
12
  - 提取官方题解内容(包括解题思路和代码实现)
13
13
  - 支持多种编程语言的代码展示
14
14
  - 数学公式自动转换为 LaTeX 格式
15
+ - 支持将生成内容复制到系统剪贴板
15
16
 
16
17
  ## 安装
17
18
 
@@ -23,6 +24,18 @@
23
24
  yarn install
24
25
  ```
25
26
 
27
+ 或者全局安装:
28
+
29
+ ```bash
30
+ yarn global add getmdfromleetcode
31
+ ```
32
+
33
+ 或者使用 npm:
34
+
35
+ ```bash
36
+ npm install -g getmdfromleetcode
37
+ ```
38
+
26
39
  ## 使用方法
27
40
 
28
41
  ### 作为命令行工具使用
@@ -37,12 +50,10 @@ node index.js -u <leetcode-problem-url>
37
50
  node index.js -u https://leetcode.cn/problems/two-sum/
38
51
  ```
39
52
 
40
- ### 使用 yarn 脚本
41
-
42
- 你也可以使用 yarn 脚本运行:
53
+ 安装后也可以直接使用命令:
43
54
 
44
55
  ```bash
45
- yarn start -u https://leetcode.cn/problems/two-sum/
56
+ getmdfromleetcode -u <leetcode-problem-url>
46
57
  ```
47
58
 
48
59
  ### 参数说明
@@ -50,17 +61,24 @@ yarn start -u https://leetcode.cn/problems/two-sum/
50
61
  - `-u, --url`: 指定 LeetCode 题目 URL(必填)
51
62
  - `-e, --english`: 切换到英文内容显示
52
63
  - `-r, --raw`: 输出原始 HTML 格式内容
64
+ - `-c, --clipboard`: 将输出内容复制到系统剪贴板
53
65
 
54
66
  示例:
55
67
  ```bash
56
68
  # 获取中文题目内容
57
- yarn start -u https://leetcode.cn/problems/two-sum/
69
+ getmdfromleetcode -u https://leetcode.cn/problems/two-sum/
58
70
 
59
71
  # 获取英文题目内容
60
- yarn start -u https://leetcode.cn/problems/two-sum/ -e
72
+ getmdfromleetcode -u https://leetcode.cn/problems/two-sum/ -e
61
73
 
62
74
  # 获取原始 HTML 格式内容
63
- yarn start -u https://leetcode.cn/problems/two-sum/ -r
75
+ getmdfromleetcode -u https://leetcode.cn/problems/two-sum/ -r
76
+
77
+ # 获取题目内容并复制到剪贴板
78
+ getmdfromleetcode -u https://leetcode.cn/problems/two-sum/ -c
79
+
80
+ # 组合使用多个参数
81
+ getmdfromleetcode -u https://leetcode.cn/problems/two-sum/ -e -c
64
82
  ```
65
83
 
66
84
  ## 输出内容
package/index.js CHANGED
@@ -6,29 +6,49 @@
6
6
  * 从LeetCode题目页面生成Markdown格式的题目描述
7
7
  */
8
8
 
9
- import yargs from 'yargs/yargs';
9
+ import yargs from 'yargs';
10
10
  import { hideBin } from 'yargs/helpers';
11
11
  import { fetchProblemDataViaGraphQL, fetchProblemDataFromPage } from './lib/problemDataFetcher.js';
12
12
  import { formatAsMarkdown } from './lib/markdownFormatter.js';
13
+ import { execSync } from 'child_process';
14
+ import { writeFileSync } from 'fs';
13
15
 
14
16
  // 解析命令行参数
15
17
  const argv = yargs(hideBin(process.argv))
16
18
  .option('url', {
17
19
  alias: 'u',
18
20
  type: 'string',
19
- description: 'LeetCode题目URL',
21
+ description: 'LeetCode problem URL',
20
22
  demandOption: true
21
23
  })
22
24
  .option('english', {
23
25
  alias: 'e',
24
26
  type: 'boolean',
25
- description: '使用英文内容',
27
+ description: 'Use English content',
26
28
  default: false
27
29
  })
28
30
  .option('raw', {
29
31
  alias: 'r',
30
32
  type: 'boolean',
31
- description: '输出原始HTML格式',
33
+ description: 'Output raw HTML content',
34
+ default: false
35
+ })
36
+ .option('clipboard', {
37
+ alias: 'c',
38
+ type: 'boolean',
39
+ description: 'Copy output to clipboard',
40
+ default: false
41
+ })
42
+ .option('username', {
43
+ alias: 'n',
44
+ type: 'string',
45
+ description: 'Username for specific solution author',
46
+ default: 'endlesscheng' // 设置默认用户名为endlesscheng
47
+ })
48
+ .option('solutionOnly', {
49
+ alias: 's',
50
+ type: 'boolean',
51
+ description: 'Output solution only',
32
52
  default: false
33
53
  })
34
54
  .help()
@@ -37,35 +57,70 @@ const argv = yargs(hideBin(process.argv))
37
57
 
38
58
  // 主函数
39
59
  async function main() {
60
+ const { url, english, raw, clipboard, username, solutionOnly } = argv;
61
+ const language = english ? 'english' : 'chinese';
62
+ const rawOutput = raw;
63
+
40
64
  try {
41
- const url = argv.url;
42
- const useEnglish = argv.english;
43
- const rawOutput = argv.raw;
44
- const language = useEnglish ? 'english' : 'chinese';
45
-
46
65
  let questionData;
47
66
 
67
+ // 首先尝试通过GraphQL API获取数据
48
68
  try {
49
- // 首先尝试通过GraphQL API获取数据
50
- questionData = await fetchProblemDataViaGraphQL(url, language);
51
- } catch (graphqlError) {
52
- console.error(`GraphQL API request failed: ${graphqlError.message}`);
53
- try {
54
- // 如果GraphQL失败,尝试从页面的__NEXT_DATA__中提取
55
- questionData = await fetchProblemDataFromPage(url, language);
56
- } catch (pageError) {
57
- console.error(`Page data extraction failed: ${pageError.message}`);
58
- throw new Error(`Failed to fetch problem data via both methods: ${graphqlError.message}; ${pageError.message}`);
59
- }
69
+ questionData = await fetchProblemDataViaGraphQL(url, language, username);
70
+ } catch (apiError) {
71
+ console.error('Failed to fetch data via GraphQL API, trying to fetch from page...', apiError.message);
72
+ // 如果API获取失败,则尝试从页面获取数据
73
+ questionData = await fetchProblemDataFromPage(url, language, username);
60
74
  }
61
75
 
62
- // 格式化为Markdown
63
- const markdown = formatAsMarkdown(questionData, rawOutput);
76
+ // 添加语言信息到 questionData
77
+ questionData.isEnglish = english;
78
+
79
+ // 根据solutionOnly参数决定输出内容
80
+ let outputContent;
81
+ if (solutionOnly) {
82
+ // 只输出题解
83
+ if (questionData.solution) {
84
+ outputContent = formatAsMarkdown(questionData, rawOutput, true); // 传递solutionOnly标志
85
+ } else {
86
+ console.log('No solution found for the given problem.');
87
+ return;
88
+ }
89
+ } else {
90
+ // 正常输出完整内容
91
+ outputContent = formatAsMarkdown(questionData, rawOutput);
92
+ }
64
93
 
65
- // 输出结果
66
- console.log(markdown);
94
+ // 如果用户指定了复制到剪贴板的选项,则复制内容
95
+ if (clipboard) {
96
+ try {
97
+ // 将内容写入临时文件
98
+ writeFileSync('/tmp/leetcode_content.txt', outputContent);
99
+
100
+ // 根据操作系统使用不同的命令复制到剪贴板
101
+ if (process.platform === 'darwin') {
102
+ // macOS
103
+ execSync('pbcopy < /tmp/leetcode_content.txt');
104
+ } else if (process.platform === 'win32') {
105
+ // Windows
106
+ execSync('clip < /tmp/leetcode_content.txt');
107
+ } else {
108
+ // Linux
109
+ execSync('xclip -selection clipboard < /tmp/leetcode_content.txt');
110
+ }
111
+
112
+ console.log('Content copied to clipboard successfully!');
113
+ } catch (clipboardError) {
114
+ console.error('Failed to copy content to clipboard:', clipboardError.message);
115
+ // 即使复制到剪贴板失败,仍然输出内容
116
+ console.log(outputContent);
117
+ }
118
+ } else {
119
+ // 正常输出内容
120
+ console.log(outputContent);
121
+ }
67
122
  } catch (error) {
68
- console.error('Error:', error.message);
123
+ console.error('Error fetching or processing problem data:', error.message);
69
124
  process.exit(1);
70
125
  }
71
126
  }
@@ -1,17 +1,19 @@
1
1
  /**
2
- * Markdown格式化模块
2
+ * Markdown format化模块
3
3
  */
4
4
 
5
5
  import * as cheerio from 'cheerio';
6
6
 
7
7
  // 将题目内容格式化为Markdown
8
- function formatAsMarkdown(question, rawOutput = false) {
8
+ function formatAsMarkdown(question, rawOutput = false, solutionOnly = false) {
9
9
  const title = question.displayTitle;
10
10
  const difficulty = question.difficulty;
11
11
  const content = question.displayContent;
12
12
  const topicTags = question.topicTags || [];
13
13
  const solution = question.solution;
14
-
14
+ const isEnglish = question.isEnglish || false;
15
+ const url = question.url || ''; // 获取题目URL
16
+ //console.log(question);
15
17
  // 如果需要原始输出,则返回包含标题和难度的原始HTML内容
16
18
  if (rawOutput) {
17
19
  let rawResult = '';
@@ -22,7 +24,7 @@ function formatAsMarkdown(question, rawOutput = false) {
22
24
  rawResult += `<p><strong>Difficulty:</strong> ${difficulty}</p>\n`;
23
25
  }
24
26
  rawResult += content;
25
-
27
+
26
28
  // 添加题解内容(如果存在)
27
29
  if (solution && solution.content) {
28
30
  rawResult += `\n\n<h2>题解</h2>\n`;
@@ -31,20 +33,20 @@ function formatAsMarkdown(question, rawOutput = false) {
31
33
  rawResult += `\n\n<h2>题解</h2>\n`;
32
34
  rawResult += `<p>没有找到题解</p>\n`;
33
35
  }
34
-
36
+
35
37
  return rawResult;
36
38
  }
37
-
39
+
38
40
  // 使用cheerio处理HTML格式的描述
39
- const $ = cheerio.load(content, {decodeEntities: false});
40
-
41
+ const $ = cheerio.load(content, { decodeEntities: false });
42
+
41
43
  // 提取文本内容并格式化
42
44
  let description = '';
43
-
45
+
44
46
  // 按照HTML中的顺序处理所有元素
45
47
  $('body').children().each((i, elem) => {
46
48
  const $elem = $(elem);
47
-
49
+
48
50
  if (elem.tagName === 'p') {
49
51
  // 处理段落中的内联元素
50
52
  let text = '';
@@ -53,14 +55,14 @@ function formatAsMarkdown(question, rawOutput = false) {
53
55
  if (child.type === 'text') {
54
56
  text += child.data;
55
57
  } else if (child.tagName === 'strong') {
56
- text += `**${$child.text()}**`;
58
+ text += ` **${$child.text()}** `;
57
59
  } else if (child.tagName === 'em') {
58
- text += `*${$child.text()}*`;
60
+ text += ` *${$child.text()}* `;
59
61
  } else if (child.tagName === 'code') {
60
62
  text += `\`${$child.text()}\``;
61
63
  } else if (child.tagName === 'sup') {
62
64
  // 处理上标(数学公式中的幂)
63
- text += `^${$child.text()}`;
65
+ text += `^{${$child.text()}}`;
64
66
  } else if (child.tagName === 'sub') {
65
67
  // 处理下标
66
68
  text += `_${$child.text()}`;
@@ -72,10 +74,18 @@ function formatAsMarkdown(question, rawOutput = false) {
72
74
  text = text.trim();
73
75
  if (text) {
74
76
  // 特殊处理示例标题
75
- if (text.startsWith('示例') || text.startsWith('Example')) {
77
+ if (text.startsWith('**示例') || text.startsWith('**Example')) {
78
+ // 去掉前后的**
79
+ text = text.replace(/^\*\*(.*)\*\*$/, '$1');
76
80
  description += `\n\n## ${text}\n\n`;
77
- } else if (text.startsWith('提示') || text.startsWith('Hint')) {
78
- description += `\n\n## 提示:\n`;
81
+ } else if (text.startsWith('**提示') || text.startsWith('**Hint')) {
82
+ // 去掉前后的**
83
+ text = text.replace(/^\*\*(.*)\*\*$/, '$1');
84
+ description += `\n\n## ${text}\n`;
85
+ } else if (text.startsWith('**进阶**')) {
86
+ // 去掉前后的**
87
+ text = text.replace(/^\*\*(.*)\*\*$/, '$1');
88
+ description += `\n\n## ${text}\n`;
79
89
  } else {
80
90
  description += text + '\n\n';
81
91
  }
@@ -87,177 +97,123 @@ function formatAsMarkdown(question, rawOutput = false) {
87
97
  }
88
98
  } else if (elem.tagName === 'ul' || elem.tagName === 'ol') {
89
99
  $elem.children('li').each((j, li) => {
90
- const text = $(li).text().trim();
91
- if (text) {
92
- description += `- ${text}\n`;
100
+ let text = '';
101
+ // 保留li元素中的所有内容,包括strong标签
102
+ $(li).contents().each((k, child) => {
103
+ const $child = $(child);
104
+ if (child.type === 'text') {
105
+ text += child.data;
106
+ } else if (child.tagName === 'strong') {
107
+ text += ` **${$child.text()}** `;
108
+ } else if (child.tagName === 'code') {
109
+ // 特殊处理code标签中的sup和sub标签
110
+ let codeText = '';
111
+ $child.contents().each((l, codeChild) => {
112
+ if (codeChild.type === 'text') {
113
+ codeText += codeChild.data;
114
+ } else if (codeChild.tagName === 'sup') {
115
+ // 处理上标(数学公式中的幂)
116
+ console.log('处理code中的sup标签'); // 调试信息
117
+ codeText += `^${$(codeChild).text()}`;
118
+ } else if (codeChild.tagName === 'sub') {
119
+ // 处理下标
120
+ console.log('处理code中的sub标签'); // 调试信息
121
+ codeText += `_${$(codeChild).text()}`;
122
+ } else {
123
+ codeText += $(codeChild).text();
124
+ }
125
+ });
126
+ text += `\`${codeText}\``;
127
+ } else if (child.tagName === 'sup') {
128
+ // 处理上标(数学公式中的幂)
129
+ text += `^${$child.text()}`;
130
+ } else if (child.tagName === 'sub') {
131
+ // 处理下标
132
+ text += `_${$child.text()}`;
133
+ } else {
134
+ // 其他标签直接获取文本
135
+ text += $child.text();
136
+ }
137
+ });
138
+ if (text.trim()) {
139
+ // 处理列表项中的数学表达式
140
+ description += `- ${text.trim()}\n`;
93
141
  }
94
142
  });
95
143
  description += '\n';
96
144
  }
97
145
  });
98
-
146
+
147
+ // 如果只输出题解
148
+ if (solutionOnly) {
149
+ if (solution && solution.content) {
150
+ // 直接返回题解内容
151
+ let solutionContent = solution.content || '';
152
+ return solutionContent;
153
+ } else {
154
+ return '没有找到题解';
155
+ }
156
+ }
157
+
99
158
  // 构建Markdown输出
100
159
  let markdown = '';
101
160
  if (title) {
102
161
  markdown += `# ${title}\n\n`;
103
162
  }
104
-
163
+
105
164
  if (difficulty) {
106
165
  markdown += `**Difficulty:** ${difficulty}\n\n`;
107
166
  } else {
108
167
  markdown += `**Difficulty:** 未找到\n\n`;
109
168
  }
110
-
169
+
111
170
  // 添加题目标签
112
171
  if (topicTags.length > 0) {
113
- const tags = topicTags.map(tag =>
172
+ const tags = topicTags.map(tag =>
114
173
  tag.translatedName || tag.name
115
174
  ).join(', ');
116
175
  markdown += `**Tags:** ${tags}\n\n`;
117
176
  }
118
-
177
+
119
178
  if (description) {
120
179
  // 格式化描述内容
121
180
  let formattedDescription = description;
122
-
123
- // 处理示例部分
124
- formattedDescription = formattedDescription.replace(/示例\s*(\d+)\s*:/g, '\n\n## 示例 $1:\n\n');
125
- formattedDescription = formattedDescription.replace(/示例\s*(\d+)\s*:/g, '\n\n## 示例 $1:\n\n');
126
- formattedDescription = formattedDescription.replace(/Example\s*(\d+)\s*:/g, '\n\n## Example $1:\n\n');
127
-
181
+
182
+ // 仅保留解释部分的处理
128
183
  // 处理解释部分
129
184
  formattedDescription = formattedDescription.replace(/解释:/g, '\n**解释:** ');
130
185
  formattedDescription = formattedDescription.replace(/Explanation:/g, '\n**Explanation:** ');
131
-
132
- // 处理提示部分
133
- formattedDescription = formattedDescription.replace(/提示:/g, '\n\n## 提示:\n');
134
- formattedDescription = formattedDescription.replace(/进阶:/g, '\n\n**进阶:**\n');
135
- formattedDescription = formattedDescription.replace(/Follow up:/g, '\n\n**Follow up:**\n');
136
-
137
- // 处理列表项
138
- formattedDescription = formattedDescription.replace(/\n \* /g, '\n- ');
139
-
140
- // 修复特殊字符问题
141
- formattedDescription = formattedDescription.replace(/(\d+)\\u003csup\\u003e(\d+)\\u003c\/sup\\u003e/g, '$1^$2');
142
- formattedDescription = formattedDescription.replace(/(\d+)\\u003csub\\u003e(\d+)\\u003c\/sub\\u003e/g, '$1_$2');
143
-
144
- // 特殊处理常见的数学范围表达式,先处理特定的表达式
145
- formattedDescription = formattedDescription.replace(/10\s*\^\s*4/g, '$10^4$');
146
- formattedDescription = formattedDescription.replace(/10\s*\^\s*5/g, '$10^5$');
147
- formattedDescription = formattedDescription.replace(/10\s*\^\s*9/g, '$10^9$');
148
-
149
- // 将其他数字^数字的表达式用$包裹,符合LaTeX规范(排除特定已处理的表达式)
150
- formattedDescription = formattedDescription.replace(/(\d+)\s*\^\s*(\d+)/g, function(match, p1, p2) {
151
- // 如果不是已经特殊处理的表达式,则添加LaTeX标记
152
- if (!(p1 === '10' && (p2 === '4' || p2 === '5' || p2 === '9'))) {
153
- return `$${p1}^${p2}$`;
154
- }
155
- return match;
156
- });
157
-
158
- // 处理下标表达式(例如 H_2 -> $H_2$ 或 H_{2} -> $H_{2}$)
159
- formattedDescription = formattedDescription.replace(/([A-Za-z])_({?[0-9]+}?)/g, function(match, p1, p2) {
160
- // 如果已经是{数字}格式,则直接使用,否则添加大括号
161
- if (!p2.startsWith('{')) {
162
- p2 = '{' + p2 + '}';
163
- }
164
- return `$${p1}_${p2}$`;
165
- });
166
-
167
- // 处理负数的上标情况
168
- formattedDescription = formattedDescription.replace(/-\s*(\d+)\^(\d+)/g, '-\$$1^$2\$');
169
-
186
+
170
187
  // 清理多余的空白字符
171
188
  formattedDescription = formattedDescription.replace(/\n\s*\n\s*\n/g, '\n\n');
172
189
  formattedDescription = formattedDescription.replace(/^ +/gm, '');
173
-
174
- markdown += `## Description\n\n${formattedDescription}\n`;
190
+
191
+ // 根据语言显示不同的标题
192
+ const descriptionTitle = isEnglish ? "Description" : "题目描述";
193
+ markdown += `## ${descriptionTitle}\n\n${formattedDescription}\n`;
175
194
  } else {
176
- markdown += '## Description\n\n未能提取到题目描述\n\n';
195
+ // 根据语言显示不同的标题
196
+ const descriptionTitle = isEnglish ? "Description" : "题目描述";
197
+ markdown += `## ${descriptionTitle}\n\n未能提取到题目描述\n\n`;
198
+ }
199
+ // 添加题目来源
200
+ if (url) {
201
+ markdown += `\n\n## SOURCE\n\n[${title}](${url})\n\n`;
177
202
  }
178
-
179
203
  // 添加题解内容(如果存在)
180
204
  if (solution && solution.content) {
181
205
  markdown += '\n## 题解\n\n';
182
-
183
- // 处理题解内容
184
- const solution$ = cheerio.load(solution.content || '', {decodeEntities: false});
185
-
186
- // 提取题解文本内容并格式化
187
- let solutionText = '';
188
-
189
- // 更简单直接的方法:直接提取所有文本内容并按段落处理
190
- const allText = solution$('body').text();
191
- // 修复:不使用trim,保留原始段落格式
192
- const paragraphs = allText.split('\n');
193
-
194
- paragraphs.forEach(paragraph => {
195
- // 修复:只对纯文本段落进行处理,保留空行和缩进
196
- if (paragraph.trim()) {
197
- // 检查是否是标题
198
- if (paragraph.trim().startsWith('####')) {
199
- solutionText += `\n\n${paragraph.trim()}\n\n`;
200
- } else if (paragraph.trim().startsWith('###')) {
201
- solutionText += `\n\n${paragraph.trim()}\n\n`;
202
- } else if (paragraph.trim().startsWith('##')) {
203
- solutionText += `\n\n${paragraph.trim()}\n\n`;
204
- } else if (paragraph.trim().startsWith('#')) {
205
- solutionText += `\n\n${paragraph.trim()}\n\n`;
206
- } else {
207
- // 普通段落,保留原始格式
208
- solutionText += `${paragraph}\n`;
209
- }
210
- } else {
211
- // 空行直接添加
212
- solutionText += '\n';
213
- }
214
- });
215
-
216
- // 特殊处理代码块部分,保留原始缩进
217
- const codeBlocks = solution$('pre');
218
- codeBlocks.each((i, elem) => {
219
- const $elem = solution$(elem);
220
- const classAttr = $elem.attr('class') || '';
221
- let lang = '';
222
- // 尝试从class中提取语言信息
223
- const langMatch = classAttr.match(/\[([^\]]+)\]/);
224
- if (langMatch) {
225
- lang = langMatch[1].split('-')[0];
226
- }
227
-
228
- const codeText = $elem.text();
229
- if (codeText) {
230
- // 修复:保留原始代码的缩进
231
- solutionText += '```' + lang + '\n' + codeText + '```\n\n';
232
- }
233
- });
234
-
235
- // 应用与题目描述相同的数学公式处理
236
- solutionText = solutionText.replace(/(\d+)\\u003csup\\u003e(\d+)\\u003c\/sup\\u003e/g, '$1^$2');
237
- solutionText = solutionText.replace(/(\d+)\\u003csub\\u003e(\d+)\\u003c\/sub\\u003e/g, '$1_$2');
238
- solutionText = solutionText.replace(/10\s*\^\s*4/g, '$10^4$');
239
- solutionText = solutionText.replace(/10\s*\^\s*5/g, '$10^5$');
240
- solutionText = solutionText.replace(/10\s*\^\s*9/g, '$10^9$');
241
- solutionText = solutionText.replace(/(\d+)\s*\^\s*(\d+)/g, function(match, p1, p2) {
242
- if (!(p1 === '10' && (p2 === '4' || p2 === '5' || p2 === '9'))) {
243
- return `$${p1}^${p2}$`;
244
- }
245
- return match;
246
- });
247
- // 处理下标表达式(例如 H_2 -> $H_2$ 或 H_{2} -> $H_{2}$)
248
- solutionText = solutionText.replace(/([A-Za-z])_({?[0-9]+}?)/g, function(match, p1, p2) {
249
- // 如果已经是{数字}格式,则直接使用,否则添加大括号
250
- if (!p2.startsWith('{')) {
251
- p2 = '{' + p2 + '}';
252
- }
253
- return `$${p1}_${p2}$`;
254
- });
255
- solutionText = solutionText.replace(/-\s*(\d+)\^(\d+)/g, '-\$$1^$2\$');
256
-
257
- // 清理多余的空白字符,但保留必要的换行
258
- solutionText = solutionText.replace(/\n\s*\n\s*\n/g, '\n\n');
259
-
260
- markdown += solutionText;
206
+
207
+ // 直接使用题解的原始内容,不进行Markdown格式转换
208
+ // 保留HTML格式并处理特殊字符
209
+ let solutionContent = solution.content || '';
210
+
211
+ // 处理HTML实体,但保留HTML标签结构
212
+ // solutionContent = solutionContent.replace(/&lt;/g, '<').replace(/&gt;/g, '>');
213
+
214
+ // solutionContent = solutionContent.replace(/&lt;/g, '<').replace(/&gt;/g, '>');
215
+
216
+ markdown += solutionContent + '\n';
261
217
  } else {
262
218
  markdown += '\n## 题解\n\n';
263
219
  markdown += '没有找到题解\n\n';