|
|
什么是bbcode?
详细见:【论坛教程】mt论坛语法总汇(bbcode)
简而言之:bbcode是discuz论坛帖子和评论实际存储的形式,你看到的哪些精美帖子,眼前一亮的评论,都是由bbcode代码构成。
html与bbcode互转的意义
从代码上,bbcode更简洁,存储效率高。特别是如果你想写一个原生app,如何将html解析为清晰的其他结构,掌握了怎么转为bbcode,转其他结构也更容易。
转换思路
这我熟,【论坛神器】一键发出几乎和大佬一样的帖子(copy.js)通过让ai参考WhiteSevs大佬“MT 论坛优化”油猴插件,通过正则表达式成功解析出大部分bbcode代码。
正则示例
- // 处理颜色和背景色
- bbcode = bbcode.replace(
- /<font[^>][i]color="([^"]+)"[^>][/i]>([\s\S]*?)<\/font>/gi,
- "[color=$1]$2[/color]",
- );
复制代码
但是使用正则匹配,不可避免的遇到一些问题:
转换时遇到的问题
html与结果无法区分
由于使用正则把html当做字符串,此时内容和html标签难以区分,例如
- <font color="1195D1"><strong><app</strong></font>
复制代码
上述代码对应的bbcode为
- [color=1195D1][b]<app[/b][/color]
复制代码
按照正则匹配,可以正确解析。但是正则解析脚本并不认识html标签,解析脚本处理后仍会残留html标签,需要删除
- [color=1195D1][b]<app[/b][/color]<br>
复制代码
常见html标签
- <h1><p><a><img><div><span><ul><ol><li><table><tr><td><th><form><input><button><br><hr><strong><em>……
复制代码
如果你觉得,它们特征是由<>包裹的。你使用正则把<与>之间的内容当做html标签,此时
也被认为是一个标签,故删除,格外丢失了“<app”(内容)和一些bbcode,解析结果不完整
解决办法
方案1:你可以穷举所有html标签,将其全部替换为空,但是这不现实。即使你已经很努力的穷举,终会有遗漏情况,还是可能遇到上述误删情况。
方案2:也是我当时使用的方案,但也无法避免误删
- // 清理非代码块部分的HTML标签(只匹配真正的HTML标签)
- bbcode = bbcode.replace(/<[a-zA-Z][^>]*>/gi, '')
- .replace(/<\/[a-zA-Z][^>]*>/gi, ''):
复制代码
难以处理嵌套标签
html天然支持嵌套,但是如果将其转化为text,并使用正则进行解析,难度倍增。
正则天生不适合嵌套标签,例如:
- [size=5]5555[size=2]222[/size]55[/size]
复制代码
你能使用正则匹配吗?它极易错误的匹配了
- [size=5]5555[size=2]222[/size]
复制代码
即第一个size的开头与第二个size的结尾匹配,这很明显不符合预期
解决办法
- // 修复后(支持嵌套,循环匹配直到无标签)
- while (/\[size=([\s\S][i]?)\]([\s\S][/i]?)\[\/size\]/g.test(processedText)) {
- processedText = processedText.replace(/\[size=([\s\S][i]?)\]([\s\S][/i]?)\[\/size\]/g, '<font size="$1">$2</font>'); }
复制代码
具体原理问ai,简而言之:每次循环都先匹配最内标签,逐步向外
但是,引入while循环,性能变差,可读性也不好
终极解决办法
不破不立,经过一天与ai的和谐交流,最终编写出核心js代码,它们分别是
BBCode2Html
预览框带完整环境,任意页面均可调用
- // 使用示例
- // 带预览窗,已自动注入css
- BBCode2Html.show(`[size=5][align=center]教程预览[doge][#大拇指][/align][/size]`);
- // bbcode2html,需要自行配置css
- console.log(BBCode2Html.replaceText("[b]加粗[/b] [color=red]红色[/color]"))
复制代码
Html2BBCode
- // 使用方法
- // 方式1:传入 DOM
- console.log(Html2BBCode.convert(document.querySelector('.comiis_messages')))
- // 方式2:传入 HTML 字符串(自动转DOM)
- const html = '<div class="comiis[i]quote bg[/i]h f_c">...</div>';
- console.log(Html2BBCode.convert(html));
复制代码
我已将两个核心转换代码抽离为单独的js文件,大家可参考使用
qcxs/mtbbs CDN by jsDelivr
使用示例
消息预览
消息预览可以在消息页面预览消息详情的脚步,初版预览过弱,只能查看text,现在对其进行改进,让它预览时显示bbcode,点击显示html预览。
使用@require引入上述js,
- // ==UserScript==
- // [url=home.php?mod=space&uid=121064]@name[/url] [MT论坛]消息提醒预览
- // @namespace https://github.com/qcxs/mtbbs
- // @version 2026-05-06
- // @description 显示消息预览,短消息快速查看,避免“不看担心错过消息,看了发现是水回复”
- // @author 青春向上
- // @match https://bbs.binmt.cc/home.php?mod=space&do=notice&view=mypost*
- // @icon https://bbs.binmt.cc/favicon.ico
- // @grant none
- // @require https://cdn.jsdelivr.net/gh/qcxs/mtbbs@master/require/BBCode2Html.js
- // @require https://cdn.jsdelivr.net/gh/qcxs/mtbbs@master/require/Html2BBCode.js
- // @run-at document-idle
- // ==/UserScript==
复制代码
使用innerHtml获取html,调用HTML2BBCode,把评论转为bbocde
- const content = contentElement?.innerHTML || '';
- if (!content) throw new Error('内容为空');
- if (Html2BBCode == null) {
- alert('核心Html2BBCode未加载,请查看require链接是否正确')
- return;
复制代码
给预览文字添加点击事件
- span.onclick = () => {
- if (BBCode2Html == null) {
- alert('核心BBCode2Html未加载,请查看require链接是否正确')
- return;
- }
- BBCode2Html.show(`${content}`);
- };
复制代码
这样一个消息预览就升级成功了,效果图,bbocde
预览图,和查看评论简直一模一样
代码解析
Html2BBCode
具体原理可将代码发送给ai,提示词
- 解释为什么上述代码可以将html解析为BBCode,并且天然支持嵌套解析,写出一个嵌套示例解析流程
复制代码
大致思路
采用DOM 遍历 + 责任链匹配 + 递归解析,且子节点优先解析,天然支持区分html与结果、处理嵌套标签,即无视上述问题。
责任链
定义了三种责任链
- class → 标签属性 → 正则 → textContent
复制代码
使用class匹配code标签内容
- if (cls.includes('comiis_blockcode')) {
- const text = el.textContent.trim();
- return `\n[code]${text}
复制代码 \n`;
}[/code]
使用标签名匹配b标签
- if (tag === 'strong' || tag === 'b') return `[b]${this.parseChildren(el)}[/b]`;
复制代码
使用正则匹配hide标签(我没有抛弃正则,只是它不在是主力军)
- // 3.1 本帖隐藏 [hide]
- if (cls.includes('comiis[i]quote') && cls.includes('bg[/i]h') && cls.includes('f_c')) {
- const h2 = el.querySelector('h2');
- if (h2?.textContent.includes('本帖隐藏的内容')) {
- const content = this.parseChildren(el).replace(/^本帖隐藏的内容:/, '').trim();
- return `\n[hide]${content}[/hide]\n`;
- }
- }
复制代码
其实是人为的将html标签解析分为三种类型。有的标签简单,很容易匹配;有的复杂,需要多一些代码,分类后代码更清晰,你也可以只使用一个解析函数。
举例讲解代码流程
- <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
- <div class="comiis_messages ...">
复制代码
解析子节点,成功匹配
- <font size="2">回复</font>
- <font color="#999999">喵喵猫…</font>
- <br>
- <font color="#999999">不错…</font>
复制代码
转为
- [size=2]回复[/size]
- [color=#999999]喵喵猫...[/color]
- (换行符,不可见)
- [color=#999999]不错...[/color]
复制代码
解析到包裹子节点的父节点,(子节点优先,父节点匹配在子节点之后)
- <div class="comiis[i]quote bg[/i]h b[i]dashed f[/i]c">
复制代码
子节点为父节点的文本,被父节点quote包裹
- [quote][size=2]回复[/size][color=#999999]喵喵猫 发表于 2025-8-4 16:08[/color] [color=#999999]不错,最近一直很给力的[/color][/quote]
复制代码
遇到尴尬的事,回复2字不是在quote包裹的开头(前面有size=2),所以去除回复2字失败
- // 1. 引用 [quote]
- if (cls.includes('comiis[i]quote') && cls.includes('bg[/i]h') && cls.includes('b[i]dashed') && cls.includes('f[/i]c')) {
- const font = el.querySelector('font');
- const bq = el.querySelector('blockquote');
- if (font?.textContent.startsWith('回复') && bq) {
- const content = this.parseChildren(bq).replace(/^回复/, '').trim();
- return `\n[quote]${content}[/quote]\n`;
- }
- }
复制代码
但是我早有准备,所有解析完毕后,还有收尾工作,在收尾时把它删除即可,或者把'回复'改为'回复'
- formatBBCode(text) {
- return text
- .replace(/\n{2,}/g, '\n')
- .replace(/[ \t]+/g, ' ')
- .trim()
- .replace(/\[size=2\]回复\[\/size\] ?/g, ''); // quote的回复2字特殊处理
- }
复制代码
其他解析流程同理,故省略,最终解析结果为
- [quote][color=#999999]喵喵猫 发表于 2025-8-4 16:08[/color] [color=#999999]不错,最近一直很给力的[/color][/quote] 感谢夸奖。[doge调皮]
复制代码
BBCode2Html
坑
自以为bbcode转html很简单,毕竟网上随便一搜就能找到bbcode在线预览网页
是不是看起来挺不错?遇到嵌套标签立马就废了
要知道,论坛是支持嵌套标签的,不信你去看【简单逆向】unity游戏通用去除google广告思路 - MT论坛,示例就从这个帖子来的
- [size=5][align=center]系列教程[size=2](点击跳转)[/size][/align][/size]
复制代码
从html源码中,能看出来论坛对嵌套标签支持的很好
改进版的copy和预览,也同样完美支持
在上面难以处理嵌套标签的解决办法,我已经提供了一种思路,现在讲解另一个思路
bbcode转html的另一个思路
怎么解决的呢?答案是:抄!,我累死累活的和ai激烈交流,才想到上面那个解决办法,要不是最后觉得性能太差了,我都不想改
当然抄也有技巧,如果是抄论坛网页,会发现,服务器返回的就是html代码,根本看不出来怎么生成的。如果是抄上面那个bbcode浏览器,它也不支持。辛亏,discuz论坛是开源的!
一切的问题,都来源于不知道源代码
打开vs code,看图搜索,经过浏览,找到论坛解析原理
试着让ai分析一下,提示词(后接上述php源码)
- 发现使用 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方案。
原理,标签拆分处理
开标签 / 关标签 分开替换!
- 开头 [size=xxx] → 替换成 <font ...> ,结尾 [/size] → 统一替换成 </font>
复制代码
示例bbcode处理后结果
- <font size=5> A <font size=3> B </font> C </font>
复制代码
它根本就没有管什么标签嵌套!浏览器天然支持 HTML 标签嵌套,不需要正则去 “找对应结束标签”!
原来我大方向就是错的,难怪代码越ai越离谱,discuz这串代码瞬间点醒了我,于是BBCode2Html最后一块短板也补上了。
表情支持
论坛支持表情,表情本质上是img标签,只不过url以论坛cdn开头,例如
- [色] ---> <img src="https://cdn.binmt.cc/static/image/smiley/qq/qq014.gif" smilieid="1229" border="0" alt="">
复制代码
同理你也可以自定义表情库,只需将表情上传至MT论坛专用图床,再按照下述链接填写
- 不就是精华帖嘛,我也有:[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,使得预览框显示和论坛一模一样。
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <link rel="stylesheet" href="https://cdn.binmt.cc/template/comiis_app/comiis/css/comiis.css?LOt" type="text/css" media="all">
- <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"/>
- </head>
- <body>
- <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>
- </body>
- </html>
复制代码
使用js修改iframe显示
- // 后续只替换内容区域,不重建DOM/CSS
- const contentEl = doc.getElementById('preview-content');
- if (contentEl) {
- contentEl.innerHTML = this.replaceText(bbcode);
- }
复制代码
网页如果没有smiliesarray,将无法正确显示表情。故增加了初始化smiliesarray
- const script = document.createElement('script');
- script.src = 'https://cdn.binmt.cc/data/cache/common[i]smilies[/i]var.js?LOt';
- script.charset = 'utf-8';
- // 脚本加载完成后更新表情数据
- await new Promise((resolve, reject) => {
- script.onload = resolve;
- script.onerror = reject;
- document.head.appendChild(script);
- });
- this.smilies[i]array = window.smilies[/i]array || [];
- this.smilies[i]type = window.smilies[/i]type || {};
复制代码
结尾
核心代码和坑讲解完毕,我也顺便更新了脚本,可以去qcxs - 用户主页 | ScriptCat安装最新版脚本。
PS:两个脚本相结合,理论上可以写出移动端可视化编辑器,更多可能由你们创造,欢迎(注明原作者)二改。
脚本功能不只于此,更多可能由你创造,以下是我让ai随便编写的网页
|
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|