活力39173
在线时间13450 小时
阅读权限200
管理员
自由的灵魂
- 积分
- 106852
- 主题
- 5602
- 回帖
- 26633
- 注册时间
- 2003-4-10
- 最后登录
- 2026-8-14
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
×
1、问题缘起
论坛转贴工具需要将Markdown格式的文本转换为Discuz论坛支持的UBB格式。在转换过程中发现以下问题:
| 问题 | 描述 | | 列表分裂 | 英文有序列表项之间有空行,导致每个列表项被单独包裹在独立的【list=1】中 | | 子列表错误 | 中文部分的有序列表内包含无序子列表,子列表未被正确嵌套 | | 水平线被吞噬 | 【hr】 被当作列表项内容,出现在内部 | | 尾段被吞噬 | 列表结束后的普通文本(如建议步骤:)被吞入最后一个列表项内部 | | 表格对齐不兼容 | Discuz不支持【td align=center】格式,需要【td】【align=center】内容【/align】【/td】 | | 标题未加粗 | 标题只用了[size=N]标签,需要同时加粗 |
过程中发现并解决的衍生问题
- 空行处理逻辑:空行是否关闭列表取决于下一行是否是有序列表项
- 【hr】被提前收集:在列表项内容收集中遇到【hr】需中断
- 缩进级别判断:非列表项且缩进级别≤当前级别时应结束列表项收集
- 列表关闭后需添加空行防止后续内容粘连
2、解决方案
(1)简要技术分析
核心原理:通过逐行解析Markdown源码,维护列表栈管理列表嵌套层级,根据行首缩进和行内容特征决定列表的开启、关闭和嵌套关系。
(2)修改文件列表
| 文件 | 绝对路径 | | Markdown转UBB工具 | /www/wwwroot/dianbo.org/tools/md2ubb/index.php |
(3)关键代码段
修改1:标题加粗
- // 处理标题(将 # 转换为 [size] 标签,并加粗),标题前加空行
- ubb = ubb
- .replace(/^#####\s+(.*$)/gm, '\n[size=2][b]$1[/b][/size]')
- .replace(/^####\s+(.*$)/gm, '\n[size=3][b]$1[/b][/size]')
- .replace(/^###\s+(.*$)/gm, '\n[size=4][b]$1[/b][/size]')
- .replace(/^##\s+(.*$)/gm, '\n[size=5][b]$1[/b][/size]')
- .replace(/^#\s+(.*$)/gm, '\n[size=6][b]$1[/b][/size]');
复制代码
修改2:表格对齐格式(Discuz兼容)
- // Discuz论坛格式:使用 [td][align=center]内容[/align][/td]
- if (alignments && alignments[j] && alignments[j] !== 'left') {
- const alignValue = alignments[j];
- ubbTable += `[td][align=${alignValue}]${cellContent}[/align][/td]`;
- } else {
- ubbTable += `[td]${cellContent}[/td]`;
- }
复制代码
修改3:水平线提前处理
- // 先处理水平线 [hr],让它们独立出来,不被列表吞噬
- ubb = ubb.replace(/^[-*_]{3,}$/gm, '[hr]');
复制代码
修改4:列表项收集中遇到【hr】中断
- // 关键修改:遇到 [hr] 时,立即结束当前列表项的收集
- if (nextTrimmed === '[hr]') {
- break;
- }
复制代码
修改5:非列表项且缩进≤当前级别时结束收集
- if (isListItem(nextLine) && nextIndent === currentIndent) break;
- if (!isListItem(nextLine) && nextIndent <= currentIndent) break; // < 改为 <=
复制代码
修改6:空行根据下一行决定是否关闭列表
- // 判断下一行是否是有序列表项(数字. 开头)
- const nextIsOrderedListItem = /^\d+\.\s+/.test(nextLine);
- if (listStack.length > 0 && !nextIsOrderedListItem) {
- closeAllLists();
- }
复制代码
修改7:关闭列表后添加空行
- function closeAllLists() {
- // ...
- if (closingTags.length > 0) {
- result.push(closingTags.join(''));
- result.push(''); // 关闭列表后添加空行
- }
- }
复制代码
3、最终实现效果
| 测试项 | 修复前 | 修复后 | | 英文有序列表(含空行) | 分裂成5个独立列表 | 合并为1个【list=1】 ✅ | | 中文有序列表+无序子列表 | 子列表独立,未嵌套 | 子列表正确嵌套在父列表内 ✅ | | 水平线【hr】 | 被吞入列表内部 | 正确出现在外面 ✅ | | 列表后普通文本 | 被吞入最后一个列表项 | 正确出现在外面 ✅ | | 表格单元格对齐 | [td align=center] | 【td】【align=center】内容【/align】【/td】 ✅ | | 标题格式 | 标题 | 标题 ✅ |
4、重要技术沉淀
踩坑点
- 空行处理不能简单关闭列表:必须检查下一行是否是有序列表项,否则列表会分裂
- 【hr】需要在两个层面处理:
- 列表项收集中遇到就break(防止被收集)
- 非列表项处理中遇到就closeAllLists()(确保关闭列表)
- 缩进判断用<=而非<:非列表项缩进等于当前级别时也应结束收集,否则普通文本会被吞入
- 列表关闭后必须添加空行:防止后续内容与【/list】粘连
固化摘要
- Markdown列表转UBB核心规则:
- 1. 有序列表项之间有空行时不关闭列表(继续同一列表)
- 2. 空行后不是有序列表项时才关闭列表
- 3. 子列表通过缩进级别判断(≥2空格)
- 4. [hr]需在列表解析前预处理,并在解析时特殊处理
- 5. 非列表项且缩进≤当前级别时结束列表项收集
复制代码
5、进一步优化的可能性
- 支持更多列表类型:如任务列表- [ ]、定义列表等
- 支持列表项内多段落:当前列表项内换行会合并,如需保留段落需增加缩进判断
- 性能优化:当前为逐行解析,大文档可考虑流式处理
- 提供可视化配置:将列表处理规则(如空行行为、缩进级别)暴露为配置项
|
|