如何优雅解析和展示结构化文本内容

发布于:2025-09-02 ⋅ 阅读:(18) ⋅ 点赞:(0)

前言

最近接到一个小的需求:根据后端新返回的数据结构,处理后展示文本内容。

返回的数据结构如下:包含了文本内容和对应的样式信息

{
  "result": {
    "answer": "[{\"text\":\"\\n\"},{\"style\":\"font-weight: bold;color: #51B0E8\",\"text\":\"【查询用户信息】请说\"},{\"style\":\"color: #51B0E8\",\"text\":\":\"},{\"text\":\"您有带身份证吗?我先看看您现在的套餐。\\n\"},{\"style\":\"font-weight: bold;color: #51B0E8\",\"text\":\"【介绍副卡】请说\"},{\"style\":\"color: #51B0E8\",\"text\":\":\"},{\"text\":\"副卡10块钱一个月,副卡的共享要到\"},{\"style\":\"font-weight: bold;color: #9FC524\",\"text\":\"次月才生效\"},{\"text\":\",如果现在用要额外收费。超出打电话要\"},{\"style\":\"font-weight: bold;color: #9FC524\",\"text\":\"1毛1分钟\"},{\"text\":\",流量要5块1G。\\n\"},{\"style\":\"font-weight: bold;color: #51B0E8\",\"text\":\"【了解使用场景】请说\"},{\"style\":\"color: #51B0E8\",\"text\":\":\"},{\"text\":\"您是打算自己用,还是家人用?\"}]"
  }
}

这种结构将文本内容拆分为多个片段,每个片段可以有自己的样式定义。

如果简单地将其作为字符串处理,会丢失所有样式信息和格式。

一、传统的处理方式

一般可能会尝试直接处理这个 JSON 字符串,比如下面这种方式:

if (item.result && item.result.answer) {
  const recommendDiv = document.createElement("div");
  recommendDiv.style.marginTop = "10px";
  recommendDiv.innerHTML = `<div><span style="font-weight:bold;">建议:</span>“${item.result.answer.replace(/\n/g, "<br>")}”</div>`;
  div.appendChild(recommendDiv);
}

这种方式存在明显问题:

  • 无法解析和应用每个文本片段的样式
  • 简单替换换行符的方式不够优雅
  • 直接将 JSON 字符串展示出来,用户体验差

二、优雅的解决方案

正确的处理方式应该是先解析 JSON 数据,然后逐个处理每个文本片段,应用对应的样式。

下面是一个完整的页面实现,展示了如何正确解析和展示结构化文本内容:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>结构化文本展示示例</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        primary: '#51B0E8',
                        secondary: '#9FC524',
                        neutral: '#f0f2f5',
                    },
                    fontFamily: {
                        sans: ['Inter', 'system-ui', 'sans-serif'],
                    },
                }
            }
        }
    </script>
    <style type="text/tailwindcss">
        @layer utilities {
            .content-card {
                @apply bg-white rounded-lg shadow-md p-6 mb-6 transition-all duration-300 hover:shadow-lg;
            }
            .section-title {
                @apply text-xl font-bold text-gray-800 mb-4 pb-2 border-b border-gray-200;
            }
        }
    </style>
</head>
<body class="bg-gray-50 font-sans text-gray-800">
    <div class="container mx-auto px-4 py-8 max-w-3xl">
        <header class="mb-8 text-center">
            <h1 class="text-3xl font-bold text-gray-800 mb-2">结构化文本展示示例</h1>
            <p class="text-gray-600">演示如何解析并显示包含样式和换行的结构化文本内容</p>
        </header>

        <div class="content-card">
            <h2 class="section-title">
                <i class="fa fa-file-text-o mr-2 text-primary"></i>后端数据展示
            </h2>
            <div id="raw-data" class="bg-gray-50 p-4 rounded text-sm font-mono overflow-x-auto text-gray-700"></div>
        </div>

        <div class="content-card">
            <h2 class="section-title">
                <i class="fa fa-eye mr-2 text-primary"></i>解析后的展示效果
            </h2>
            <div id="parsed-content" class="min-h-[200px]"></div>
        </div>

        <div class="content-card">
            <h2 class="section-title">
                <i class="fa fa-info-circle mr-2 text-primary"></i>说明
            </h2>
            <ul class="list-disc pl-5 space-y-2 text-gray-700">
                <li>蓝色粗体文本为操作提示</li>
                <li>绿色粗体文本为重要说明</li>
                <li>自动保留原始数据中的换行效果</li>
                <li>所有样式均按照原始数据中的定义进行展示</li>
            </ul>
        </div>
    </div>

    <script>
        // 模拟后端返回的数据
        const mockData = {
            result: {
                answer: "[{\"text\":\"\\n\"},{\"style\":\"font-weight: bold;color: #51B0E8\",\"text\":\"【查询用户信息】请说\"},{\"style\":\"color: #51B0E8\",\"text\":\":\"},{\"text\":\"您有带身份证吗?我先看看您现在的套餐。\\n\"},{\"style\":\"font-weight: bold;color: #51B0E8\",\"text\":\"【介绍副卡】请说\"},{\"style\":\"color: #51B0E8\",\"text\":\":\"},{\"text\":\"副卡10块钱一个月,副卡的共享要到\"},{\"style\":\"font-weight: bold;color: #9FC524\",\"text\":\"次月才生效\"},{\"text\":\",如果现在用要额外收费。超出打电话要\"},{\"style\":\"font-weight: bold;color: #9FC524\",\"text\":\"1毛1分钟\"},{\"text\":\",流量要5块1G。\\n\"},{\"style\":\"font-weight: bold;color: #51B0E8\",\"text\":\"【了解使用场景】请说\"},{\"style\":\"color: #51B0E8\",\"text\":\":\"},{\"text\":\"您是打算自己用,还是家人用?\"}]"
            }
        };

        // 显示原始数据
        document.getElementById('raw-data').textContent = JSON.stringify(mockData, null, 2);

        // 解析并展示结构化内容
        function renderStructuredContent(data) {
            const container = document.getElementById('parsed-content');
            
            if (data.result && data.result.answer) {
                const recommendDiv = document.createElement("div");
                recommendDiv.className = "p-4 bg-neutral rounded-lg";
                
                // 解析JSON字符串为对象数组
                const answerParts = JSON.parse(data.result.answer);
                
                // 创建容器并添加"建议:"标签
                const contentContainer = document.createElement("div");
                const labelSpan = document.createElement("span");
                labelSpan.className = "font-bold text-gray-800";
                labelSpan.textContent = "建议:";
                contentContainer.appendChild(labelSpan);
                
                // 遍历每个部分并应用样式
                answerParts.forEach(part => {
                    const span = document.createElement("span");
                    
                    // 应用样式(如果有的话)
                    if (part.style) {
                        const stylePairs = part.style.split(';');
                        stylePairs.forEach(pair => {
                            if (pair.trim()) {
                                const [key, value] = pair.split(':');
                                // 将CSS属性名转换为驼峰式(如font-weight -> fontWeight)
                                const styleKey = key.trim().replace(/-([a-z])/g, (match, letter) => letter.toUpperCase());
                                span.style[styleKey] = value.trim();
                            }
                        });
                    }
                    
                    // 设置文本内容(保留换行效果)
                    span.textContent = part.text;
                    contentContainer.appendChild(span);
                });
                
                recommendDiv.appendChild(contentContainer);
                container.appendChild(recommendDiv);
            } else {
                container.innerHTML = '<div class="text-gray-500 italic">没有可显示的内容</div>';
            }
        }

        // 页面加载完成后渲染内容
        document.addEventListener('DOMContentLoaded', () => {
            renderStructuredContent(mockData);
        });
    </script>
</body>
</html>

效果如下:

三、核心解析逻辑

解析和展示结构化文本的核心逻辑在renderStructuredContent函数中,主要包含以下步骤:

1、解析 JSON 数据

使用JSON.parse()将字符串转换为对象数组

const answerParts = JSON.parse(data.result.answer);
2、创建容器元素

为整个内容和 "建议:" 标签创建容器

const contentContainer = document.createElement("div");
const labelSpan = document.createElement("span");
labelSpan.className = "font-bold text-gray-800";
labelSpan.textContent = "建议:";
contentContainer.appendChild(labelSpan);
3、遍历文本片段

为每个文本片段创建 span 元素并应用样式

answerParts.forEach(part => {
    const span = document.createElement("span");
    
    // 应用样式
    if (part.style) {
        const stylePairs = part.style.split(';');
        stylePairs.forEach(pair => {
            if (pair.trim()) {
                const [key, value] = pair.split(':');
                // CSS属性名驼峰式转换
                const styleKey = key.trim().replace(/-([a-z])/g, (match, letter) => letter.toUpperCase());
                span.style[styleKey] = value.trim();
            }
        });
    }
    
    // 设置文本内容
    span.textContent = part.text;
    contentContainer.appendChild(span);
});
4、实现要点解析
  • CSS 属性名转换:由于 CSS 中使用连字符(如font-weight),而 JavaScript 中需要使用驼峰式(如fontWeight),因此需要进行转换:
const styleKey = key.trim().replace(/-([a-z])/g, (match, letter) => letter.toUpperCase());
  • 使用 textContent:使用textContent而非innerHTML设置文本内容,这样可以:
  • 自动保留换行符的效果
  • 避免 XSS 安全问题
  • 确保文本内容被正确转义
  • 样式应用:逐个解析样式字符串中的键值对,并应用到对应的元素上,确保每个文本片段都能展示其应有的样式。

四、总结

通过这种方式处理结构化文本内容,我们能够准确地还原后端数据中包含的所有样式信息和格式要求。

关键:对于结构化数据,应该先解析再处理,而不是简单地当作字符串来处理。这种思路可以应用于许多类似的场景,帮助我们写出更优雅、更健壮的代码。


网站公告

今日签到

点亮在社区的每一天
去签到