5

在 markdown 中使用中文符号 - rxliuli blog

 7 months ago
source link: https://blog.rxliuli.com/p/2029b35ae4094a48a3073f998f10af9c/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

在 markdown 中使用中文符号

在 markd_
2024年2月16日 凌晨

1k 字

9 分钟

本文最后更新于:2024年2月16日 上午

最近再次碰到 markdown 对中文的支持问题,由于这个问题已经长期存在,所以想谈一下现状和解决方法。当同时使用中文符号和粗体/斜体时,就会出现问题。

**真没想到我这么快就要死了。**她有些自暴自弃地想着。

会被渲染为

**真没想到我这么快就要死了。**她有些自暴自弃地想着。

实际上,这种情况还有很多,几乎所有中文符号都无法被正常渲染。

示例 渲染
**真,**她 **真,**她
**真。**她 **真。**她
**真、**她 **真、**她
**真;**她 **真;**她
**真:**她 **真:**她
**真?**她 **真?**她
**真!**她 **真!**她
**真“**她 **真“**她
**真”**她 **真”**她
**真‘**她 **真‘**她
**真’**她 **真’**她
**真(**她 **真(**她
**真)**她 **真)**她
**真【**她 **真【**她
**真】**她 **真】**她
**真《**她 **真《**她
**真》**她 **真》**她
**真—**她 **真—**她
**真~**她 **真~**她
**真…**她 **真…**她
**真·**她 **真·**她
**真〃**她 **真〃**她
**真-**她 **真-**她
**真々**她 真々

这种情况有多常见呢?

根据吾辈之前维护同人小说的经验,在一本 80w 字的小说中出现了 2700 次以上,可以在《魔法少女小圆 飞向星空》的项目中搜索 /\*\*.*?[,。、;:?!“”‘’()【】《》—~…·〃-々]\*\* / 找到真实的用例。

那么,如何解决呢?现在可以通过加空格、零宽度字符或者将符号移动到粗体外面。

示例 渲染
**真,** 她 真,
**真,**​她 **真,**​她
**真**,她 ,她

第一种方法会呈现出非预期的渲染,第二种方法输入很困难,最后一种方法则是必须改变输入,和第一种有类似的问题。

例如 docusaurus 就推荐了第一种方法,并结合 markdown 解析插件来自动清理粗体后面的多余空格。原本 **真,** 她 会渲染为 <p><strong>真,</strong> 她</p>,但通过插件处理后会变成 <p><strong>真,</strong>她</p>

吾辈也为 mdast(remark 底层库) 实现过一个类似的插件,参考

import { Transform } from 'mdast-util-from-markdown'

export function clearStrongAfterSpace(): Transform {
return (root) => {
visit(root, (it) => {
if (it.type === 'paragraph') {
const children = (it as Paragraph).children
children.forEach((it, i) => {
if (it.type === 'strong') {
const next = children[i + 1]
const s = (it.children[0] as Text).value
if (s) {
const last = s.slice(s.length - 1)
if (
next &&
next.type === 'text' &&
',。、;:?!“”‘’()【】《》—~…·〃-々'
.split('')
.includes(last) &&
next.value.startsWith(' ')
) {
next.value = next.value.trim()
}
}
}
})
}
})
return root
}
}
import { toHast } from 'mdast-util-to-hast'
import { toHtml } from 'hast-util-to-html'
import { fromMarkdown } from 'mdast-util-from-markdown'
import { clearStrongAfterSpace } from '../cjk'

const render = (s: string) =>
toHtml(
toHast(
fromMarkdown(s, {
mdastExtensions: [
{
transforms: [clearStrongAfterSpace()],
},
],
}),
)!,
)

console.log(render('**真,** 她')) // `<p><strong>真,</strong>她</p>`

吾辈也为 markdown-it 实现了这个插件,参考:https://github.com/mark-magic/mark-magic/blob/77f9b1571a7d96847fc39e0aa8504ed994a64b71/packages/plugin-docs/src/assets/config.ts#L13-L42

尽管上面的解决方法还算不错,但对于使用 markdown 编写大量内容的人而言,它仍然不够直观。尤其是不检查渲染结果就不可能知道是否忘记添加了额外的空格,这在上面提到的小说中非常明显,当内容增加到一定程度时,这种检查就变得非常烦人。尤其是从外部来源(转换)得到一个 markdown 时,这可能会非常常见。

commonmark 官方目前又开始在推进这个问题了,距离问题最初提出已经过去了 4 年,想要关注进展可以关注 issue commonmark/commonmark-spec#650

尽管官方规范如果定义,那么问题将会极大的解决。但考虑到规范的定义和实现是漫长的,所以目前吾辈也在尝试编写 mdast 插件自行处理解析部分,以处理上面提到的那个中文符号示例列表。相关代码参考: https://github.com/rxliuli/liuli-tools/blob/82d81eeb0d661ad1338d04d1a75f173764dea9bc/packages/markdown-util/src/cjk.ts#L46-L77

尽管 markdown 对 cjk 的支持有诸多不顺,但 markdown 作为一种开放的文本格式仍然很棒,所有文本数据采用 markdown 并根据需要分发为不同的格式似乎是最好的。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK