# 一条空列表的 debug 之旅:我修了 4 次才搞定微信公众号排版
## 事情是这样的
我在做一个自动把博客文章推送到微信公众号草稿箱的功能。后端拿到 Markdown,转成 HTML,再调微信 API 推进去。
文章内容是小米 MiMo 的活动介绍,里面有一段列表:
``
这个邮箱会用来:
- 接收评估结果通知
- 注册/登录 MiMo 开放平台
`
很简单对吧?两行列表。
结果推到微信草稿箱一看——
中间多了一个空的圆点。
就是:• 接收评估结果通知
,然后一个光秃秃的 •
,然后 • 注册/登录 MiMo 开放平台
。
三行 bullet,中间那个啥也没有。
## 第一次修:正则匹配出了问题
我第一反应是:源 markdown 有问题?可能有空行?
查了数据库,源内容干干净净,没有多余空行。
那就看 Markdown → HTML 的转换逻辑。原来的代码是用正则做的:
`
java
// 把 "- item" 转成 <li>
html = html.replaceAll("(?m)^- (.+)$", "<li>$1</li>");
// 把连续的 <li> 包进 <ul>
html = html.replaceAll("((?:<li[^>]>.</li>[\n\r]*)+)", "<ul>$1</ul>");
`
然后还有有序列表的处理:
`
java
// 把 "1. item" 转成 <li>
html = html.replaceAll("(?m)^\d+\.\s+(.+)$", "<li>$1</li>");
// 把连续的 <li> 包进 <ol>
html = orderedGroup.matcher(html).replaceAll(mr -> {
String m = mr.group();
if (m.contains("<ul")) return m; // 已经在 <ul> 里的就跳过
return "<ol>" + m + "</ol>";
});
`
问题 1: 嵌套在 里。
有序列表的分组正则会匹配所有 标签——包括已经在 里的。m.contains(" 这个检查没用,因为匹配到的只是 标签本身,不包含外层的 标签。
结果:,双重嵌套。
修复: 给无序列表的 加 type="disc",有序列表加 type="1",正则只匹配对应类型。
## 第二次修:
被 吃了
修完嵌套问题,发现
被拆到了不同的段落块里。
原因是:正则 [
]* 在匹配最后一个 后面的换行时,把列表后面的空行也吞进去了:
`
<li>item1</li>
<li>item2</li>
← 空行也被匹配了
</ul>
`
替换后变成:
`
<ul><li>item1</li>
<li>item2</li>
</ul>
`
在
前面,而段落分割是按
切的。一切,
就跑到了另一个 block 里,被 包住了。
修复: 加了清理步骤,把
移到
。
## 第三次修:Java 正则少了个括号
修完上面两个,编译报错了:
`
java.util.regex.PatternSyntaxException: Unclosed group near index 59
`
查了半天,发现是这一行:
`java
trimmed.matches("(?s)^(?!<)(.+?)\n(<(ul|ol|pre|blockquote|table|h[1-4]|hr).*")
`
h[1-4] 后面少了一个 )。正则本身是对的,但 Java 字符串里少写了闭合括号。
修复: 加上 )。
## 第四次(最终):微信编辑器不认
以上三个都修了,逻辑上完全正确。Python 模拟跑出来的 HTML 干干净净:
`html
<p>这个邮箱会用来:</p>
<ul style="padding-left:24px;margin:8px 0;">
<li style="margin:4px 0;">接收评估结果通知</li>
<li style="margin:4px 0;">注册/登录 MiMo 开放平台</li>
</ul>
`
但微信草稿箱里还是多了一个空 bullet。
我怀疑是 type="disc" 属性的问题——这是 HTML4 的遗留属性,微信编辑器可能不认识,处理的时候出了幺蛾子。
最终修复: 彻底重写列表处理。放弃正则方案,改成逐行解析:
`java
for (String line : lines) {
String trimmedLine = line.trim();
boolean isUlItem = trimmedLine.matches("^- .+$");
if (isUlItem) {
if (!inUl) { inUl = true; listBuf.append("<ul>"); }
listBuf.append("<li>" + trimmedLine.substring(2) + "</li>");
} else {
if (inUl) { listBuf.append("</ul>
"); inUl = false; }
listBuf.append(line + "
");
}
}
`
不再用 type="disc",不再用复杂的正则嵌套。纯 ,干干净净。
这次终于好了。
## 踩坑总结
| 问题 | 原因 | 解法 |
|------|------|------|
| 嵌套在 里 | 有序列表正则匹配了所有 | 用 type 属性区分(后来弃用) |
|
被 包裹 | [
]* 吞了空行 | 清理
|
| Java 编译报错 | 正则少了个 ) | 加上 |
| 微信编辑器空 bullet | type 属性不兼容 | 逐行解析,不用正则 |
## 教训
1. 微信公众号是个"特殊"的 HTML 环境。
你以为标准 HTML 能用,其实微信编辑器对很多属性的处理和浏览器不一样。type="disc"` 在 Chrome 没问题,到微信就炸。
2. 用正则做结构化解析,迟早要出事。
列表是有嵌套结构的,用正则平铺处理,处理一个层级还行,两个层级就开始互相干扰。最后改成逐行解析(本质上是个简单状态机),代码更长但逻辑清晰得多。
3. 排版问题要实际验证。
我用 Python 模拟跑出来"完美"的 HTML,到微信草稿箱还是有问题。有些坑只有实际部署才能发现。
---
凌晨两点,一个空圆点,四次修复。做公众号的第三天。