返回列表 发新帖

【研究报告】MT论坛bbcode与html互转思路及坑

[复制链接]

55

主题

3624

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
883
好评
190
信誉
181

MT论坛新人MT论坛最佳新人MT论坛帅哥考神MT论坛活跃会员挂机大佬

发表于 2026-5-7 18:09:46 来自手机  | 显示全部楼层 | 阅读模式  来自 陕西
什么是bbcode?
详细见:【论坛教程】mt论坛语法总汇(bbcode)
简而言之:bbcode是discuz论坛帖子和评论实际存储的形式,你看到的哪些精美帖子,眼前一亮的评论,都是由bbcode代码构成。
html与bbcode互转的意义
从代码上,bbcode更简洁,存储效率高。特别是如果你想写一个原生app,如何将html解析为清晰的其他结构,掌握了怎么转为bbcode,转其他结构也更容易。
转换思路
这我熟,【论坛神器】一键发出几乎和大佬一样的帖子(copy.js)通过让ai参考WhiteSevs大佬“MT 论坛优化”油猴插件,通过正则表达式成功解析出大部分bbcode代码。
正则示例
  1. // 处理颜色和背景色
  2. bbcode = bbcode.replace(
  3.   /<font[^>][i]color="([^"]+)"[^>][/i]>([\s\S]*?)<\/font>/gi,
  4.   "[color=$1]$2[/color]",
  5. );
复制代码

但是使用正则匹配,不可避免的遇到一些问题:
转换时遇到的问题
html与结果无法区分
由于使用正则把html当做字符串,此时内容和html标签难以区分,例如
  1. <font color="1195D1"><strong><app</strong></font>
复制代码

上述代码对应的bbcode为
  1. [color=1195D1][b]<app[/b][/color]
复制代码

按照正则匹配,可以正确解析。但是正则解析脚本并不认识html标签,解析脚本处理后仍会残留html标签,需要删除
  1. [color=1195D1][b]<app[/b][/color]<br>
复制代码

常见html标签
  1. <h1><p><a><img><div><span><ul><ol><li><table><tr><td><th><form><input><button><br><hr><strong><em>……
复制代码

如果你觉得,它们特征是由<>包裹的。你使用正则把<与>之间的内容当做html标签,此时
  1. <app[/b][/color]<br>
复制代码

也被认为是一个标签,故删除,格外丢失了“<app”(内容)和一些bbcode,解析结果不完整
解决办法
方案1:你可以穷举所有html标签,将其全部替换为空,但是这不现实。即使你已经很努力的穷举,终会有遗漏情况,还是可能遇到上述误删情况。
方案2:也是我当时使用的方案,但也无法避免误删
  1. // 清理非代码块部分的HTML标签(只匹配真正的HTML标签)
  2. bbcode = bbcode.replace(/<[a-zA-Z][^>]*>/gi, '')
  3.                          .replace(/<\/[a-zA-Z][^>]*>/gi, ''):
复制代码

难以处理嵌套标签
html天然支持嵌套,但是如果将其转化为text,并使用正则进行解析,难度倍增。
正则天生不适合嵌套标签,例如:
  1. [size=5]5555[size=2]222[/size]55[/size]
复制代码

你能使用正则匹配吗?它极易错误的匹配了
  1. [size=5]5555[size=2]222[/size]
复制代码

第一个size的开头第二个size的结尾匹配,这很明显不符合预期
解决办法
  1. // 修复后(支持嵌套,循环匹配直到无标签)
  2. while (/\[size=([\s\S][i]?)\]([\s\S][/i]?)\[\/size\]/g.test(processedText)) {
  3.         processedText = processedText.replace(/\[size=([\s\S][i]?)\]([\s\S][/i]?)\[\/size\]/g, '<font size="$1">$2</font>'); }
复制代码

具体原理问ai,简而言之:每次循环都先匹配最内标签,逐步向外
但是,引入while循环,性能变差,可读性也不好
终极解决办法
不破不立,经过一天与ai的和谐交流,最终编写出核心js代码,它们分别是
BBCode2Html
预览框带完整环境,任意页面均可调用
  1. // 使用示例

  2. // 带预览窗,已自动注入css
  3. BBCode2Html.show(`[size=5][align=center]教程预览[doge][#大拇指][/align][/size]`);

  4. // bbcode2html,需要自行配置css
  5. console.log(BBCode2Html.replaceText("[b]加粗[/b] [color=red]红色[/color]"))
复制代码

Html2BBCode
  1. // 使用方法

  2. // 方式1:传入 DOM
  3. console.log(Html2BBCode.convert(document.querySelector('.comiis_messages')))

  4. // 方式2:传入 HTML 字符串(自动转DOM)
  5. const html = '<div class="comiis[i]quote bg[/i]h f_c">...</div>';
  6. console.log(Html2BBCode.convert(html));
复制代码

我已将两个核心转换代码抽离为单独的js文件,大家可参考使用
qcxs/mtbbs CDN by jsDelivr
使用示例
消息预览
消息预览可以在消息页面预览消息详情的脚步,初版预览过弱,只能查看text,现在对其进行改进,让它预览时显示bbcode,点击显示html预览。

使用@require引入上述js,
  1. // ==UserScript==
  2. // [url=home.php?mod=space&uid=121064]@name[/url]         [MT论坛]消息提醒预览
  3. // @namespace    https://github.com/qcxs/mtbbs
  4. // @version      2026-05-06
  5. // @description  显示消息预览,短消息快速查看,避免“不看担心错过消息,看了发现是水回复”
  6. // @author       青春向上
  7. // @match        https://bbs.binmt.cc/home.php?mod=space&do=notice&view=mypost*
  8. // @icon         https://bbs.binmt.cc/favicon.ico
  9. // @grant        none
  10. // @require      https://cdn.jsdelivr.net/gh/qcxs/mtbbs@master/require/BBCode2Html.js
  11. // @require      https://cdn.jsdelivr.net/gh/qcxs/mtbbs@master/require/Html2BBCode.js
  12. // @run-at       document-idle
  13. // ==/UserScript==
复制代码

使用innerHtml获取html,调用HTML2BBCode,把评论转为bbocde
  1. const content = contentElement?.innerHTML || '';
  2. if (!content) throw new Error('内容为空');
  3. if (Html2BBCode == null) {
  4.         alert('核心Html2BBCode未加载,请查看require链接是否正确')
  5. return;
复制代码

给预览文字添加点击事件
  1. span.onclick = () => {
  2.     if (BBCode2Html == null) {
  3.         alert('核心BBCode2Html未加载,请查看require链接是否正确')
  4.         return;
  5.     }
  6.     BBCode2Html.show(`${content}`);
  7. };
复制代码

这样一个消息预览就升级成功了,效果图,bbocde

预览图,和查看评论简直一模一样

代码解析
Html2BBCode
具体原理可将代码发送给ai,提示词
  1. 解释为什么上述代码可以将html解析为BBCode,并且天然支持嵌套解析,写出一个嵌套示例解析流程
复制代码

大致思路
采用DOM 遍历 + 责任链匹配 + 递归解析,且子节点优先解析,天然支持区分html与结果处理嵌套标签,即无视上述问题。
责任链
定义了三种责任链
  1. class → 标签属性 → 正则 → textContent
复制代码

使用class匹配code标签内容
  1. if (cls.includes('comiis_blockcode')) {
  2.         const text = el.textContent.trim();
  3.         return `\n[code]${text}
复制代码
\n`;
}[/code]
使用标签名匹配b标签
  1. if (tag === 'strong' || tag === 'b') return `[b]${this.parseChildren(el)}[/b]`;
复制代码

使用正则匹配hide标签(我没有抛弃正则,只是它不在是主力军)
  1. // 3.1 本帖隐藏 [hide]
  2. if (cls.includes('comiis[i]quote') && cls.includes('bg[/i]h') && cls.includes('f_c')) {
  3.         const h2 = el.querySelector('h2');
  4.         if (h2?.textContent.includes('本帖隐藏的内容')) {
  5.                 const content = this.parseChildren(el).replace(/^本帖隐藏的内容:/, '').trim();
  6.                 return `\n[hide]${content}[/hide]\n`;
  7.         }
  8. }
复制代码

其实是人为的将html标签解析分为三种类型。有的标签简单,很容易匹配;有的复杂,需要多一些代码,分类后代码更清晰,你也可以只使用一个解析函数。
举例讲解代码流程
  1. <div class="comiis[i]messages comiis[/i]aimg[i]show cl"> <div class="comiis[/i]a comiis[i]message[/i]table cl"> <div class="comiis[i]quote bg[/i]h b[i]dashed f[/i]c"> <blockquote> <font size="2">回复</font> <font color="#999999">喵喵猫 发表于 2025-8-4 16:08</font><br> <font color="#999999">不错,最近一直很给力的</font> </blockquote> </div> <br> 感谢夸奖。<img src="https://cdn.binmt.cc/static/image/smiley/doge/6.png" smilieid="1362" border="0" alt=""> </div> </div>
复制代码

解析根节点,无任何责任链匹配,执行兜底:parseChildren
  1. <div class="comiis_messages ...">
复制代码

解析子节点,成功匹配
  1. <font size="2">回复</font>
  2. <font color="#999999">喵喵猫…</font>
  3. <br>
  4. <font color="#999999">不错…</font>
复制代码

转为
  1. [size=2]回复[/size]
  2. [color=#999999]喵喵猫...[/color]
  3. (换行符,不可见)
  4. [color=#999999]不错...[/color]
复制代码

解析到包裹子节点的父节点,(子节点优先,父节点匹配在子节点之后)
  1. <div class="comiis[i]quote bg[/i]h b[i]dashed f[/i]c">
复制代码

子节点为父节点的文本,被父节点quote包裹
  1. [quote][size=2]回复[/size][color=#999999]喵喵猫 发表于 2025-8-4 16:08[/color] [color=#999999]不错,最近一直很给力的[/color][/quote]
复制代码

遇到尴尬的事,回复2字不是在quote包裹的开头(前面有size=2),所以去除回复2字失败
  1. // 1. 引用 [quote]
  2. if (cls.includes('comiis[i]quote') && cls.includes('bg[/i]h') && cls.includes('b[i]dashed') && cls.includes('f[/i]c')) {
  3.         const font = el.querySelector('font');
  4.         const bq = el.querySelector('blockquote');
  5.         if (font?.textContent.startsWith('回复') && bq) {
  6.                 const content = this.parseChildren(bq).replace(/^回复/, '').trim();
  7.                 return `\n[quote]${content}[/quote]\n`;
  8.         }
  9. }
复制代码

但是我早有准备,所有解析完毕后,还有收尾工作,在收尾时把它删除即可,或者把'回复'改为'回复'
  1. formatBBCode(text) {
  2.      return text
  3.              .replace(/\n{2,}/g, '\n')
  4.              .replace(/[ \t]+/g, ' ')
  5.              .trim()
  6.              .replace(/\[size=2\]回复\[\/size\] ?/g, ''); // quote的回复2字特殊处理
  7.   }
复制代码

其他解析流程同理,故省略,最终解析结果为
  1. [quote][color=#999999]喵喵猫 发表于 2025-8-4 16:08[/color] [color=#999999]不错,最近一直很给力的[/color][/quote] 感谢夸奖。[doge调皮]
复制代码

BBCode2Html

自以为bbcode转html很简单,毕竟网上随便一搜就能找到bbcode在线预览网页

是不是看起来挺不错?遇到嵌套标签立马就废了

要知道,论坛是支持嵌套标签的,不信你去看【简单逆向】unity游戏通用去除google广告思路 - MT论坛,示例就从这个帖子来的
  1. [size=5][align=center]系列教程[size=2](点击跳转)[/size][/align][/size]
复制代码

从html源码中,能看出来论坛对嵌套标签支持的很好

改进版的copy和预览,也同样完美支持

在上面难以处理嵌套标签解决办法,我已经提供了一种思路,现在讲解另一个思路
bbcode转html的另一个思路
怎么解决的呢?答案是:抄!,我累死累活的和ai激烈交流,才想到上面那个解决办法,要不是最后觉得性能太差了,我都不想改
当然抄也有技巧,如果是抄论坛网页,会发现,服务器返回的就是html代码,根本看不出来怎么生成的。如果是抄上面那个bbcode浏览器,它也不支持。辛亏,discuz论坛是开源的!
一切的问题,都来源于不知道源代码
打开vs code,看图搜索,经过浏览,找到论坛解析原理

试着让ai分析一下,提示词(后接上述php源码)
  1. 发现使用 while (/\[size=([\s\S][i]?)]([\s\S][/i]?)\[\/size]/g.test(html)) html = html.replace(/\[size=([\s\S][i]?)]([\s\S][/i]?)\[\/size]/g, '<font size="$1">$2</font>');性能不好,使用while是因为匹配[size=5]11111[size=3]3333[/size]1111[/size]结构,如果直接使用正则匹配,可能导致第一个size与第二个size结束标识匹配成功,造成解析错误。以下是discuz论坛对size标签的解析方案,分析为什么它可以避免上述问题?
复制代码

精妙的代码

Discuz! 没有用while循环暴力匹配,而是通过 非贪婪匹配 + 精准正则约束 + 一次性批量替换完美解决了 BBCode 嵌套标签的贪婪匹配错误问题,性能和正确性都远while方案。
原理,标签拆分处理
开标签 / 关标签 分开替换!
  1. 开头 [size=xxx] → 替换成 <font ...> ,结尾 [/size] → 统一替换成 </font>
复制代码

示例bbcode处理后结果
  1. <font size=5> A <font size=3> B </font> C </font>
复制代码

它根本就没有管什么标签嵌套!浏览器天然支持 HTML 标签嵌套,不需要正则去 “找对应结束标签”!
原来我大方向就是错的,难怪代码越ai越离谱,discuz这串代码瞬间点醒了我,于是BBCode2Html最后一块短板也补上了。
表情支持
论坛支持表情,表情本质上是img标签,只不过url以论坛cdn开头,例如
  1. [色]  --->  <img src="https://cdn.binmt.cc/static/image/smiley/qq/qq014.gif" smilieid="1229" border="0" alt="">
复制代码

同理你也可以自定义表情库,只需将表情上传至MT论坛专用图床,再按照下述链接填写
  1. 不就是精华帖嘛,我也有:[url=https://bbs.binmt.cc/thread-154148-1-1.html]【论坛神器】一键发出几乎和大佬一样的帖子(copy.js)[/url][img=30,40]https://cdn.binmt.cc/static/image/stamp/001.gif[/img][呲牙]
复制代码

效果图,使img指定精华标识宽30,可以看见和表情差不多大小

脚本通过获取smilies_array的值,动态获取出论坛所有表情,才将表情的bbcode和html对应起来。
但是为了BBCode2Html的预览可以脱离原网页使用,我又将整个预览框写在iframe里,和原网页隔绝,并且引入论坛css,使得预览框显示和论坛一模一样。
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <link rel="stylesheet" href="https://cdn.binmt.cc/template/comiis_app/comiis/css/comiis.css?LOt" type="text/css" media="all">
  6. <link rel="stylesheet" href="https://bbs.binmt.cc/source/plugin/comiis[i]app/cache/comiis[/i]1[i]style.css?LOt" type="text/css" media="all" id="comiis[/i]app_addclass"/>
  7. </head>
  8. <body>
  9. <div class="comiis[i]messages comiis[/i]aimg[i]show cl"><div class="comiis[/i]a comiis[i]message[/i]table cl" id="preview-content"></div></div>
  10. </body>
  11. </html>
复制代码

使用js修改iframe显示
  1. // 后续只替换内容区域,不重建DOM/CSS
  2. const contentEl = doc.getElementById('preview-content');
  3. if (contentEl) {
  4.         contentEl.innerHTML = this.replaceText(bbcode);
  5. }
复制代码

网页如果没有smiliesarray,将无法正确显示表情。故增加了初始化smiliesarray
  1. const script = document.createElement('script');
  2. script.src = 'https://cdn.binmt.cc/data/cache/common[i]smilies[/i]var.js?LOt';
  3. script.charset = 'utf-8';

  4. // 脚本加载完成后更新表情数据
  5. await new Promise((resolve, reject) => {
  6.         script.onload = resolve;
  7.         script.onerror = reject;
  8.         document.head.appendChild(script);
  9. });
  10. this.smilies[i]array = window.smilies[/i]array || [];
  11. this.smilies[i]type = window.smilies[/i]type || {};
复制代码

结尾
核心代码和坑讲解完毕,我也顺便更新了脚本,可以去qcxs - 用户主页 | ScriptCat安装最新版脚本。
PS:两个脚本相结合,理论上可以写出移动端可视化编辑器,更多可能由你们创造,欢迎(注明原作者)二改。
脚本功能不只于此,更多可能由你创造,以下是我让ai随便编写的网页

本帖子中包含更多资源

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

x
回复

使用道具 举报

55

主题

3624

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
883
好评
190
信誉
181

MT论坛新人MT论坛最佳新人MT论坛帅哥考神MT论坛活跃会员挂机大佬

发表于 2026-5-7 20:42:33 来自手机  | 显示全部楼层  来自 陕西
本帖最后由 青春向上 于 2026-5-19 16:20 编辑

官方省流:
MT 论坛 BBCode 与 HTML 互转,正则方案易出错、难处理嵌套;最终采用DOM 遍历 + 责任链 + 递归实现 HTML 转 BBCode,借鉴 Discuz开 / 关标签分开替换实现 BBCode 转 HTML,完美支持嵌套、表情与论坛样式,已封装为可直接调用的 JS 工具,用于论坛预览等场景。


再分享除油猴脚本使用外的其他示例:
https://share.feijipan.com/s/YZ4LwkPv
包含[内嵌网页示例]markdown2bbcode.zip,使用教程:
在线体验地址:https://qcxs.github.io/mtbbs/mt-convert/
纯静态网页,解压后用浏览器打开,也可上传至托管网站
使用cdn引入css、js,保证版本最新

核心转换函数
https://cdn.jsdelivr.net/gh/qcxs/mtbbs@master/require/BBCode2Html.js

表情支持
https://cdn.binmt.cc/data/cache/common_smilies_var.js?LOt

美化预览,与MT论坛一致
https://cdn.binmt.cc/template/comiis_app/comiis/css/comiis.css?LOt
https://bbs.binmt.cc/source/plugin/comiis_app/cache/comiis_1_style.css?LOt

仅作为BBCode2Html.js内嵌网页示例,实际使用时可能存在bug。


[移植python示例]html2bbcode.zip,使用教程:
1,安装python
2,执行python test.py
3,输出html对应的bbcode

Html2BBCode.py由Html2BBCode.js根据ai自动生成,只是展示js的代码迁移能力,实际以Html2BBCode.js为准。

Html2BBCode.js最新版获取链接:
https://cdn.jsdelivr.net/gh/qcxs/mtbbs@master/require/Html2BBCode.js
帖子有一处小错误,如下:
  1. const content = contentElement?.innerHTML || ''; 
  2. if (!content) throw new Error('内容为空'); 
  3. f (Html2BBCode == null) { 
  4.        alert('核心Html2BBCode未加载,请查看require链接是否正确') 
  5.      return; 

  6. return Html2BBCode.convert(content);
复制代码

少了使用Html2BBCode.convert(content);的示例。


消息预览脚本又又更新了,去除了require,想参考教程提到的代码,请查看历史版本:2026-05-06
回复

使用道具 举报

0

主题

3199

回帖

7290

积分

硕士生

Rank: 6Rank: 6

金币
3521
好评
0
信誉
100

MT论坛新人考神MT论坛帅哥

发表于 2026-5-8 06:25:17 来自手机  | 显示全部楼层  来自 浙江
感谢分享
回复

使用道具 举报

334

主题

4120

回帖

1万

积分

博士生

一介独特的草民

Rank: 7Rank: 7Rank: 7

金币
3373
好评
155
信誉
224

考神MT论坛活跃会员MT论坛侠客MT论坛最佳新人MT论坛新人MT论坛灌水老大

发表于 2026-5-8 17:31:27 来自手机  | 显示全部楼层  来自 重庆
大佬辛苦,我自己开发了一个mt插件目前也可以实现简易的bbcode与html和markdown的互转,正在迁移v3接口并继续完善中,欢迎大家体验和反馈!

本帖子中包含更多资源

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

x
回复

使用道具 举报

55

主题

3624

回帖

1万

积分

博士生

Rank: 7Rank: 7Rank: 7

金币
883
好评
190
信誉
181

MT论坛新人MT论坛最佳新人MT论坛帅哥考神MT论坛活跃会员挂机大佬

发表于 2026-5-8 18:23:43 来自手机  | 显示全部楼层  来自 陕西
是果宝呐 发表于 2026-5-8 17:31
大佬辛苦,我自己开发了一个mt插件目前也可以实现简易的bbcode与html和markdown的互转,正在迁移v3接口并继 ...
大佬可以试试我分享的脚本,只需两行代码,就能使用bbcode预览框。(我记得mt插件支持打开网页,这脚本天然适合网页使用)
  1. 在网页中引入js
  2.     <script src="https://cdn.jsdelivr.net/gh/qcxs/mtbbs@master/require/BBCode2Html.js"></script>
  3. 使用
  4. BBCode2Html.show(`[align=center]教程预览[doge][#大拇指][/align]`); 
复制代码

写这个脚本和教程花费一些时间,等我把作业补完,也写一个mt插件给大家玩玩。
回复

使用道具 举报

1

主题

1万

回帖

3万

积分

博士后

Rank: 8Rank: 8

金币
9068
好评
4
信誉
1332

考神MT论坛帅哥MT论坛新人

发表于 2026-5-25 07:01:19 来自手机  | 显示全部楼层  来自 广西
看看
回复

使用道具 举报

0

主题

209

回帖

493

积分

初中生

Rank: 3Rank: 3

金币
352
好评
0
信誉
100
发表于 2026-6-21 13:53:52 来自手机  | 显示全部楼层  来自 山东
大佬大佬大佬
回复

使用道具 举报

0

主题

197

回帖

329

积分

初中生

Rank: 3Rank: 3

金币
76
好评
0
信誉
100
发表于 2026-7-1 10:02:36 | 显示全部楼层  来自 湖北
支持一下
回复

使用道具 举报

1

主题

299

回帖

792

积分

初中生

Rank: 3Rank: 3

金币
427
好评
0
信誉
100
发表于 2026-7-1 16:19:24 来自手机  | 显示全部楼层  来自 江苏
11111
回复

使用道具 举报

发表回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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