Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

记一次 HTML 富文本特殊字符转义 #332

Open
toFrankie opened this issue Mar 16, 2024 · 0 comments
Open

记一次 HTML 富文本特殊字符转义 #332

toFrankie opened this issue Mar 16, 2024 · 0 comments
Labels
2024 2024 年撰写 HTML 与 HTML(包括 WXSS 等)相关的文章 前端 与 JavaScript、ECMAScript、Web 前端相关的文章

Comments

@toFrankie
Copy link
Owner

toFrankie commented Mar 16, 2024

配图源自 Freepik

背景

此前有个项目里面一个功能是:运营后台进行富文本配置,然后在浏览器、微信小程序各端做展示。

富文本编辑器本身是可以设置字体的,当预设的字体名称包含空格(比如 Microsoft Yahei)时,出现问题了,在小程序端渲染时字体不生效。

当时临时方案是将字体名称用连字符替代空格,因为第二天要上线。

这不是一个好的解决方案,比如针对 Microsoft YaheiPingFang SCHelvetica Neue 这类系统内置的字体,不应写成 Microsoft-Yahei,这是不合理的。

下面开始找找根本原因。

开始之前

通常来说,我们在 CSS 中设置 font-family 时,字体名称是可以包含空格的,但有空格时,应该要用引号括起来。

MDN

The name of a font family. For example, "Times" and "Helvetica" are font families. Font family names containing whitespace should be quoted. For example: "Comic Sans MS".

比如:

.app {
  font-family: "Helvetica Neue", "Segoe UI", Arial, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", sans-serif;
}

鉴于浏览器的包容性很强,其实不写引号,也能正常识别。

寻找原因

经排查发现,前面遇到的问题,其实是就是因为对 HTML 实体转义后,导致 HTML 结构不正确导致的。

后台的富文本编辑器用的是 Braft Editor,它设置字体的方式如下:

const fontFamilies = [
  {
    name: 'ST Song',
    family: '"ST Song"', // "'ST Song'" 在 BraftEditor 内不生效
  },
]
<BraftEditor fontFamilies={fontFamilies} />

是的,在 Braft Editor 里字体名称只能用「双引号」包裹,单引号不生效。

假设富文本编辑器输出的 HTML 如下(Hello World):

<p><span style="font-family:&quot;ST Song&quot;">&quot;Hello World&quot;</span></p>

在浏览器中,这段富文本内容直接通过 Element.innerHTML 去修改 DOM,是可以得到预期效果的。但在小程序里,需要对类似 &quot;(双引号)等 HTML Entity 进行转换,才能正常显示,否则它会将 &quot; 当作五个普通字符,而不是一个双引号。

之前是通过 entities 来做转义的。

import { decodeHTML } from 'entities'

const html = `<p><span style="font-family:&quot;ST Song&quot;">&quot;Hello World&quot;</span></p>`

const transformedHtml = decodeHTML(html)

console.log(transformedHtml)

得到的转义结果是:

<p><span style="font-family:"ST Song"">"Hello World"</span></p>

其实这就看出问题了,style 属性包裹了两个双引号,自然解析不到预期结果。

解决问题

我们知道,CSS 字符串类型的属性值,可以用单引号或双引号。我们先把 style 里可能出现的引号,全部转为单引号,这样的话就能正确解析了。

CSS 属性值使用到引号的(只想起了这几个):

  • font-family
  • content
  • url()
  • ...

Related Link: https://www.w3.org/TR/2011/REC-CSS2-20110607/syndata.html#values

这样的话,用表达式做匹配出 style 的属性值,然后将里面的引号替换掉,方法如下:

function transformHtmlInlineStyle(html) {
  return html.replace(/(\s+style="[^"]*")/gi, match => {
    return match.replace(/&quot;|&apos;|&#34;|&#39;|&#x22;|&#x27;/gi, "'")
  })
}

因此,上面的流程只要加多一步就行:

  import { decodeHTML } from 'entities'

  const html = `<p><span style="font-family:&quot;ST Song&quot;">&quot;Hello World&quot;</span></p>`

+ let transformedHtml = transformHtmlInlineStyle(html)

  transformedHtml = decodeHTML(html)

  console.log(transformedHtml)

得到的结果为:

<p><span style="font-family:'ST Song'">"Hello World"</span></p>

示例:CodeSandbox

The end.

@toFrankie toFrankie added 2024 2024 年撰写 HTML 与 HTML(包括 WXSS 等)相关的文章 前端 与 JavaScript、ECMAScript、Web 前端相关的文章 labels Mar 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2024 2024 年撰写 HTML 与 HTML(包括 WXSS 等)相关的文章 前端 与 JavaScript、ECMAScript、Web 前端相关的文章
Projects
None yet
Development

No branches or pull requests

1 participant