使用 hightlight.js 格式化 xml 文本

1,106 阅读3分钟

昨日产品新加了一个需求,要求对某个展示 xml 文本的功能优化,输出美观且易阅读的文本。然后不知道从哪个网站搬来了一个截图,让我参照这个截图优化系统的功能。 image.png

我们系统原先展示的 xml 文本是未经格式的,并且带有乱码的内容,

image.png

不忍吐槽,这种未经格式化的文本,也不知道他们之前怎么用下去的,估计压根就很少点击过这个功能!

想要完成这个需求,首先得借助能格式化这种 xml 文本的工具。

想起之前自己捣鼓项目时用到了 hightlight.js 来格式化 js 代码,正好能够胜任我这个业务场景。

highlight.js 是一个广泛使用的语法高亮库,能够自动检测并高亮显示超过 190 种编程语言的代码。

在 Components 目录新建一个 xml-formatter 的 Vue 组件,组件内部代码实现如下:

<script setup lang="ts">
import hljs from "highlight.js";
import xmlLanguage from "highlight.js/lib/languages/xml";
import { isString } from "lodash-es";
import { computed } from "vue";

const props = defineProps({
    raw: {
        type: String,
    },
});

hljs.registerLanguage("javascript", xmlLanguage);

const xmlContent = computed(() => {
    if (isString(props.raw)) {
        const uint8Array = Uint8Array.from(atob(props.raw), (c) => c.charCodeAt(0));
        const text = new TextDecoder("utf-8").decode(uint8Array);
        return hljs.highlight(text, { language: "xml" }).value;
    }
    return null;
});
</script>

查看之前的源码发现输出的 xml 文本会有中文乱码,是因为之前将后端接口返回的 Base64 编码的文本直接通过 window.atob 来解码的。

由于 window.atob 处理的 Base64 编码返回的是 ASCII 字符串。对于中文这种非 ASCII 码,解码时必定会乱码,所以只能换种思路。

TextDecoderTextEncoder 来处理 Base64 编码的字符串。这些 API 可以正确处理 Unicode 字符。

乱码的问题解决后,接下来就需要将格式化后的 xml 代码输出到页面了。

在 Vue.js 我们能使用 v-html 指令在模板中动态地渲染 HTML 结构的内容。在 React 中也可以使用 dangerouslySetInnerHTML 渲染 HTML 内容。

接着还需要用到 <pre><code> 这两个 HTML 标签作为内容的输出。

<pre> 标签能够保留文本中的空白、换行和缩进,使文本在浏览器中呈现的样式与其在 HTML 源代码中的格式一。而 <code> 标签能够增加 HTML 文档的语义性。

模板代码如下:

<template>
    <pre><code class="hljs" v-html="xmlContent"></code></pre>
</template>

实现完成的效果如下图,现在可以看到格式化后的 XML 代码了。

image.png

但是格式化后的样式还和截图的有很大区别,所以还需要编写 xml 代码样式。

我们打开 Devtools 审查元素 DOM 结构能看到使用 highlightjs 格式化的代码不同的属性都有对应的类名。

image.png

那么只需要为这些类名编写对应的 CSS 样式即可实现最后一个小功能点。

CSS 代码如下:

<style lang="scss">
pre code {
    white-space: pre-wrap;
    word-break: break-all;
}

.hljs {
    display: block;
    overflow-x: auto;
    padding: 0.5em;
    color: #abb2bf;
    background: #282c34;

    .hljs-comment,
    .hljs-quote {
        color: #5c6370;
        font-style: italic;
    }

    .hljs-doctag,
    .hljs-formula,
    .hljs-keyword {
        color: #c678dd;
    }

    .hljs-deletion,
    .hljs-name,
    .hljs-section,
    .hljs-selector-tag,
    .hljs-subst {
        color: #e06c75;
    }

    .hljs-literal {
        color: #56b6c2;
    }

    .hljs-addition,
    .hljs-attribute,
    .hljs-meta-string,
    .hljs-regexp,
    .hljs-string {
        color: #98c379;
    }

    .hljs-built_in,
    .hljs-class .hljs-title {
        color: #e6c07b;
    }

    .hljs-attr,
    .hljs-number,
    .hljs-selector-attr,
    .hljs-selector-class,
    .hljs-selector-pseudo,
    .hljs-template-variable,
    .hljs-type,
    .hljs-variable {
        color: #d19a66;
    }

    .hljs-emphasis {
        font-style: italic;
    }

    .hljs-strong {
        font-weight: 700;
    }

    .hljs-link {
        text-decoration: underline;
    }
}
</style>

最终效果如下图所示:

image.png

对比之前产品的截图样例,完美的复刻了。又可以畅快的搬砖(摸鱼)了~