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 +25 -7
- package/index.js +80 -25
- package/lib/markdownFormatter.js +108 -152
- package/lib/problemDataFetcher.js +50 -12
- package/package.json +14 -6
- package/check_solution.js +0 -20
- package/clean_next_data.json +0 -1
- package/index.js.backup +0 -355
- package/next_data.json +0 -1
- package/problem_page.html +0 -93
- package/solution.html +0 -89
- package/solution_data.json +0 -2604
- package/solution_page.html +0 -1
- package/solution_page_full.html +0 -1
- package/specific_solution.html +0 -89
- package/test_format.js +0 -20
- package/test_subscript.js +0 -52
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
|
-
|
|
41
|
-
|
|
42
|
-
你也可以使用 yarn 脚本运行:
|
|
53
|
+
安装后也可以直接使用命令:
|
|
43
54
|
|
|
44
55
|
```bash
|
|
45
|
-
|
|
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
|
-
|
|
69
|
+
getmdfromleetcode -u https://leetcode.cn/problems/two-sum/
|
|
58
70
|
|
|
59
71
|
# 获取英文题目内容
|
|
60
|
-
|
|
72
|
+
getmdfromleetcode -u https://leetcode.cn/problems/two-sum/ -e
|
|
61
73
|
|
|
62
74
|
# 获取原始 HTML 格式内容
|
|
63
|
-
|
|
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
|
|
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
|
|
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: '
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
//
|
|
63
|
-
|
|
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
|
-
|
|
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
|
}
|
package/lib/markdownFormatter.js
CHANGED
|
@@ -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 +=
|
|
58
|
+
text += ` **${$child.text()}** `;
|
|
57
59
|
} else if (child.tagName === 'em') {
|
|
58
|
-
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 +=
|
|
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('
|
|
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('
|
|
78
|
-
|
|
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
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
190
|
+
|
|
191
|
+
// 根据语言显示不同的标题
|
|
192
|
+
const descriptionTitle = isEnglish ? "Description" : "题目描述";
|
|
193
|
+
markdown += `## ${descriptionTitle}\n\n${formattedDescription}\n`;
|
|
175
194
|
} else {
|
|
176
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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(/</g, '<').replace(/>/g, '>');
|
|
213
|
+
|
|
214
|
+
// solutionContent = solutionContent.replace(/</g, '<').replace(/>/g, '>');
|
|
215
|
+
|
|
216
|
+
markdown += solutionContent + '\n';
|
|
261
217
|
} else {
|
|
262
218
|
markdown += '\n## 题解\n\n';
|
|
263
219
|
markdown += '没有找到题解\n\n';
|