如何用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
相关产品推荐
相关产品推荐

