找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 28|回复: 0

[维护日志] [2026-06-07] [功能] [Markdown转UBB列表解析] - Markdown多级列表与UBB转换功能完善

[复制链接]
  • 打卡等级:本地老炮

9336

威望

6912

金钱

1万

贡献

管理员

自由的灵魂

积分
106852
主题
5602
回帖
26633
注册时间
2003-4-10
最后登录
2026-8-14
发表于 2026-6-11 11:16:40 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
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:标题加粗
  1. // 处理标题(将 # 转换为 [size] 标签,并加粗),标题前加空行
  2. ubb = ubb
  3.     .replace(/^#####\s+(.*$)/gm, '\n[size=2][b]$1[/b][/size]')
  4.     .replace(/^####\s+(.*$)/gm, '\n[size=3][b]$1[/b][/size]')
  5.     .replace(/^###\s+(.*$)/gm, '\n[size=4][b]$1[/b][/size]')
  6.     .replace(/^##\s+(.*$)/gm, '\n[size=5][b]$1[/b][/size]')
  7.     .replace(/^#\s+(.*$)/gm, '\n[size=6][b]$1[/b][/size]');
复制代码


修改2:表格对齐格式(Discuz兼容)
  1. // Discuz论坛格式:使用 [td][align=center]内容[/align][/td]
  2. if (alignments && alignments[j] && alignments[j] !== 'left') {
  3.     const alignValue = alignments[j];
  4.     ubbTable += `[td][align=${alignValue}]${cellContent}[/align][/td]`;
  5. } else {
  6.     ubbTable += `[td]${cellContent}[/td]`;
  7. }
复制代码


修改3:水平线提前处理
  1. // 先处理水平线 [hr],让它们独立出来,不被列表吞噬
  2. ubb = ubb.replace(/^[-*_]{3,}$/gm, '[hr]');
复制代码


修改4:列表项收集中遇到【hr】中断
  1. // 关键修改:遇到 [hr] 时,立即结束当前列表项的收集
  2. if (nextTrimmed === '[hr]') {
  3.     break;
  4. }
复制代码


修改5:非列表项且缩进≤当前级别时结束收集
  1. if (isListItem(nextLine) && nextIndent === currentIndent) break;
  2. if (!isListItem(nextLine) && nextIndent <= currentIndent) break;  // < 改为 <=
复制代码


修改6:空行根据下一行决定是否关闭列表
  1. // 判断下一行是否是有序列表项(数字. 开头)
  2. const nextIsOrderedListItem = /^\d+\.\s+/.test(nextLine);

  3. if (listStack.length > 0 && !nextIsOrderedListItem) {
  4.     closeAllLists();
  5. }
复制代码


修改7:关闭列表后添加空行
  1. function closeAllLists() {
  2.     // ...
  3.     if (closingTags.length > 0) {
  4.         result.push(closingTags.join(''));
  5.         result.push('');  // 关闭列表后添加空行
  6.     }
  7. }
复制代码





3、最终实现效果

测试项修复前修复后
英文有序列表(含空行)分裂成5个独立列表合并为1个【list=1】
中文有序列表+无序子列表子列表独立,未嵌套子列表正确嵌套在父列表内 ✅
水平线【hr】被吞入列表内部正确出现在外面 ✅
列表后普通文本被吞入最后一个列表项正确出现在外面 ✅
表格单元格对齐[td align=center]【td】【align=center】内容【/align】【/td】
标题格式标题标题





4、重要技术沉淀

踩坑点

  • 空行处理不能简单关闭列表:必须检查下一行是否是有序列表项,否则列表会分裂
  • 【hr】需要在两个层面处理
    • 列表项收集中遇到就break(防止被收集)
    • 非列表项处理中遇到就closeAllLists()(确保关闭列表)
  • 缩进判断用<=而非<:非列表项缩进等于当前级别时也应结束收集,否则普通文本会被吞入
  • 列表关闭后必须添加空行:防止后续内容与【/list】粘连


固化摘要

  1. Markdown列表转UBB核心规则:
  2. 1. 有序列表项之间有空行时不关闭列表(继续同一列表)
  3. 2. 空行后不是有序列表项时才关闭列表
  4. 3. 子列表通过缩进级别判断(≥2空格)
  5. 4. [hr]需在列表解析前预处理,并在解析时特殊处理
  6. 5. 非列表项且缩进≤当前级别时结束列表项收集
复制代码





5、进一步优化的可能性

  • 支持更多列表类型:如任务列表- [ ]、定义列表等
  • 支持列表项内多段落:当前列表项内换行会合并,如需保留段落需增加缩进判断
  • 性能优化:当前为逐行解析,大文档可考虑流式处理
  • 提供可视化配置:将列表处理规则(如空行行为、缩进级别)暴露为配置项

不懂就搜!点此搜点拨论坛。如果本坛没有,请尝试点此问AI,或跟帖提问。
发帖前注意看置顶帖
不定期借助AI对点拨论坛陈年老帖进行挖坟回复,打扰勿怪!
发现掉链,请直接回帖报告。
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|点拨论坛 |网站地图|网站地图

GMT+8, 2026-8-14 21:43 , Processed in 0.061437 second(s), 9 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表