You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用jq/yq/xq处理XML内嵌HTML并实现XML-YAML往返转换

问题描述

我有一个基于xdxf词典格式的XML词典文件,希望实现XML与YAML的往返转换。该格式(含DTD)中,<deftext>标签包裹的释义文本内可能嵌套<kref>(交叉引用)标签,或存在<sub>下标标签。使用go或python版本的yq进行XML转YAML转换后,<kref>和<sub>标签无法正确包裹对应文本,转回XML时结构也会出错。请问这是格式本身的限制,还是有办法适配(或保留XML标签)?

简化的sample.xml(来自xdxf仓库)

<lexicon>
    <ar>
        <k id="fb982hk">Society</k>
        <def>
            <deftext>Plural form of word <kref>index</kref>.
            </deftext>
        </def>
    </ar>
    <ar>
        <k>CO
            <sub>2</sub>
        </k>
        <def>
            <deftext>Carbon dioxide (CO<sub>2</sub>) - a heavy odorless gas formed during respiration.
            </deftext>
        </def>
    </ar>
  </lexicon>

使用go版yq转换为YAML的结果

yq -p=xml -o=yaml < sample.xml 
lexicon:
  ar:
    - k:
        +content: Society
        +@id: fb982hk
      def:
        deftext:
          +content:
            - Plural form of word
            - .
          kref: index
    - k:
        +content: CO
        sub: "2"
      def:
        deftext:
          +content:
            - Carbon dioxide (CO
            - ) - a heavy odorless gas formed during respiration.
          sub: "2"

使用python版yq转换为YAML的结果

xq < sample.xml | yq -y 
lexicon:
  ar:
    - k:
        '@id': fb982hk
        '#text': Society
      def:
        deftext:
          kref: index
          '#text': Plural form of word .
    - k:
        sub: '2'
        '#text': CO
      def:
        deftext:
          sub: '2'
          '#text': Carbon dioxide (CO) - a heavy odorless gas formed during respiration.
解决方案

这不是XML或YAML格式本身的限制,而是yq工具默认的XML序列化逻辑导致的问题:XML的混合内容(文本与嵌套标签共存)在转YAML时,yq会把文本和标签拆分为独立的顶级字段,完全丢失了原有的顺序和嵌套依赖关系,因此转回XML时必然结构错乱。

要实现正确的往返转换,有以下两种可行方案:

1. 自定义XML-YAML转换逻辑(推荐)

用Python编写自定义解析/序列化逻辑,完整保留XML节点的层级、文本与标签的顺序。示例代码如下:

XML转YAML

import xml.etree.ElementTree as ET
import yaml

def xml_node_to_dict(node):
    result = {"tag": node.tag}
    # 保留属性
    if node.attrib:
        result["attrs"] = node.attrib
    # 保留内容(文本+子节点,按原顺序)
    content = []
    # 处理节点内的前置文本
    if node.text and node.text.strip():
        content.append({"type": "text", "value": node.text.strip()})
    # 处理子节点
    for child in node:
        content.append({"type": "node", "value": xml_node_to_dict(child)})
    # 处理子节点后的文本
    if node.tail and node.tail.strip():
        content.append({"type": "text", "value": node.tail.strip()})
    if content:
        result["content"] = content
    return result

# 解析XML
tree = ET.parse("sample.xml")
root = tree.getroot()
xml_dict = xml_node_to_dict(root)

# 序列化为YAML
with open("output.yaml", "w", encoding="utf-8") as f:
    yaml.dump(xml_dict, f, default_flow_style=False, allow_unicode=True)

YAML转XML

import yaml
import xml.etree.ElementTree as ET

def dict_to_xml_node(data):
    node = ET.Element(data["tag"], data.get("attrs", {}))
    content = data.get("content", [])
    for item in content:
        if item["type"] == "text":
            # 追加文本,注意处理节点已有内容的情况
            if node.text is None:
                node.text = item["value"]
            else:
                node.text += item["value"]
        elif item["type"] == "node":
            child_node = dict_to_xml_node(item["value"])
            node.append(child_node)
            # 处理子节点后的文本
            if "tail" in item:
                child_node.tail = item["tail"]
    return node

# 解析YAML
with open("output.yaml", "r", encoding="utf-8") as f:
    yaml_data = yaml.safe_load(f)

# 生成XML
root_node = dict_to_xml_node(yaml_data)
tree = ET.ElementTree(root_node)
tree.write("restored.xml", encoding="utf-8", xml_declaration=True)

这种方式能100%保留XML的原始结构,确保往返转换后完全一致。

2. 调整yq参数或使用自定义转换脚本

如果一定要用yq工具,可以尝试以下方式:

  • 对于Go版yq,查看官方文档是否有保留混合内容顺序的参数,但目前版本可能无此功能;
  • 使用yq的自定义表达式,手动重组YAML结构,将分散的+content/#text和标签字段合并为有序列表,但这种方式需要针对xdxf格式编写复杂的匹配规则,通用性较差。
总结

核心问题是yq默认无法正确处理XML的混合内容结构,自定义转换逻辑是最可靠的解决方案,能完美实现XML与YAML的无损往返转换。

内容的提问来源于stack exchange,提问作者Boyd

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 13:30:42