实现过程
一、准备工作
- 安装好最新[DevEco Studio]开发工具,创建一个新的空项目。
二、整体思路
主要有以下几个步骤
- 正则处理自闭合标签例如img 、input 等,方便后续处理。
- 递归解析标签,并且判断处理特殊标签给他们加上默认样式 例如 h1~h6 以及 strong 、b、big、a、s、等标签。
- 解析标签上的 style样式、以及其他属性样式等。
- 利用[@Builder装饰器]自定义构建函数 递归构造解析生成对应的ArkUI。
- 利用[@Extend装饰器]定义扩展组件样式,方便一些样式的集成共用。
大致的流程图如下:
三、解析富文本内容–转换为JSON
- 处理自闭合标签。 在ets/pages 目录下新增文件
parseHtmlToJson.ts
export interface VNode {
type: string;
level?: number;
props: {}; // 属性可以是字符串或对象(如样式)
text?: string; // 标签内的文本内容
children?: VNode[]; // 子节点列表
}
export class parseHTML{
selfClosingTagRegex = /<([a-zA-Z0-9-]+)([^>]*)/>/g; //匹配自闭合标签
constructor() {
}
parseHTMLtoJSON(htmlString:string){
// 使用正则表达式的替换功能来转换自闭合标签
const result = htmlString.replace(this.selfClosingTagRegex, (match, tagName, attributes) => {
// 构造结束标签
return `<${tagName}${attributes}></${tagName}>`;
});
console.log("result",result)
}
}
修改ets/pages/Index.ets文件。
import {parseHTML} from "./parseHtmlToJson"
@Entry
@Component
struct Index {
@State htmlStr:string =`
<h1>h1标签</h1>
<h6>h6标签</h6>
<div>
<a href="http://www.baidu.com">a标签</a>
<span>span标签</span>
<strong>strong标签</strong>
<img src="https://img1.baidu.com/it/u=728576857,3157099301&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=313" />
<input style="color:red" placeholder="请输入..." type="number" maxlength="2" value="我是input标签"/>
</div>
<p style="margin: 10px;border: 5px solid #000;">带边框样式的</p>
`;
parseHTML = new parseHTML();
aboutToAppear(){
const result = this.parseHTML.parseHTMLtoJSON(this.htmlStr);
console.log('result',JSON.stringify(result))
}
build() {
Column(){
}
}
}
可以看到打印结果给自闭合标签添加了尾部
- 将html转换为JSON树,给特殊添加标签默认样式,解析标签上的属性。
修改一下 parseHtmlToJson.ts
文件,完整代码如下
interface NestedObject {
[key: string]: string | number| object
}
export interface VNode {
type: string;
props: {
[key: string]: string | number| object
style?:NestedObject
}; // 属性可以是字符串或对象(如样式)
text?: string; // 标签内的文本内容
children?: VNode[]; // 子节点列表
}
export class parseHTML{
selfClosingTagRegex = /<([a-zA-Z0-9-]+)([^>]*)/>/g; //匹配自闭合标签
baseFontColor: string = '#000'; //基础颜色
baseFontSize: string | number = '16';//默认字体大小
themeFontColor: string = 'blue'; //默认主题颜色 用于处理a 标签等
inlineElements = this.makeMap('text,a,abbr,acronym,applet,b,basefont,bdo,big,button,cite,del,dfn,em,font,i,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,tt,u,var')
constructor() {
}
// 解析标签属性
parseAttributes(attrString: string): Record<string, string | Record<string, string | number>> {
const attrs: Record<string, string | Record<string, string | number>> = {};
const attrRegex = /(\w+)="(.*?)"/g;
let match: RegExpExecArray | null;
while ((match = attrRegex.exec(attrString)) !== null) {
const [, name, value] = match;
if (name === 'style') {
// 如果是 style 属性,将其解析为对象
const styleObject: Record<string, string | number> = {};
value.split(';').forEach((style) => {
let [property, val] = style.split(':').map(s => s.trim());
if (property && val) {
console.log('valval', val)
if (val.includes('px')) {
val = this.removePxUnits(val); // 去掉 'px'
}
styleObject[this.toCamelCase(property)] = val;
// 拆分 border 属性
if (property === 'border') {
const borderParts = val.split(' ');
if (borderParts.length === 3) {
styleObject['borderWidth'] = borderParts[0];
styleObject['borderStyle'] = borderParts[1];
styleObject['borderColor'] = borderParts[2];
}
}
// 拆分 margin 属性
if (property === 'margin') {
const marginParts = val.split(' ');
switch (marginParts.length) {
case 1:
styleObject['