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

如何阻止BeautifulSoup的prettify()方法修改XML的有效空白以避免改变文件语义

如何阻止BeautifulSoup的prettify()方法修改XML的有效空白以避免改变文件语义

我完全懂你遇到的这个闹心问题——当你用BeautifulSoup处理带有xml:space="preserve"属性的XML(比如你示例里的SVG文件)时,prettify()方法根本不把这个属性当回事,直接修改那些关键的有效空白,把原本的文本排版全搞乱了。

带有有效空白的示例XML文件

<svg viewBox="0 0 160 50" xmlns="http://www.w3.org/2000/svg">
  <text y="20" xml:space="default">    Default    spacing</text>
  <text y="40" xml:space="preserve">    <tspan>reserved    spacing</tspan></text>
</svg>

复现问题的测试代码

from bs4 import BeautifulSoup

xml_string_with_significant_whitespace ='''
<svg viewBox="0 0 160 50" xmlns="http://www.w3.org/2000/svg">
  <text y="20" xml:space="default">    Default    spacing</text>
  <text y="40" xml:space="preserve">    <tspan>reserved    spacing</tspan></text>
</svg>
'''
soup = BeautifulSoup(xml_string_with_significant_whitespace, "xml")

# no modifications made

print(soup.prettify())  # modifies significant whitespace
# print(str(soup)) # doesn't modify significant whitespace

prettify()的输出(空白已被修改)

<svg viewBox="0 0 160 50" xmlns="http://www.w3.org/2000/svg">
 <text xml:space="default" y="20">
  Default    spacing
 </text>
 <text xml:space="preserve" y="40">
  <tspan>
   reserved    spacing
  </tspan>
 </text>
</svg>

你能明显看到,prettify()不仅给所有标签加了统一缩进,还把<text>和<tspan>内部的原始空白重新调整了——放到SVG里渲染的话,文本的位置和间距完全不符合预期。


解决办法

prettify()的设计初衷是为了统一美化输出的可读性,它本身就会忽略xml:space这类语义化的空白控制属性。如果你想既保留XML的语义,又能得到合适的输出,可以用这两种方案:

  1. 直接使用str(soup)输出(最简单)
    正如你代码里注释的那样,直接将BeautifulSoup对象转为字符串,不会对任何有效空白做修改:

    print(str(soup))
    

    这种方式的好处是完全保留原始语义,缺点是输出没有缩进,可读性稍弱,但对于需要严格保留空白的场景来说是最稳妥的。

  2. 自定义格式化函数(兼顾可读性和语义)
    如果你既想要缩进美化的输出,又要严格尊重xml:space="preserve"的规则,可以自己写一个遍历处理函数,只对不需要保留空白的元素做缩进:

    def prettify_respect_xml_space(soup, indent="  ", level=0):
        output = []
        current_indent = indent * level
        for content in soup.contents:
            if isinstance(content, str):
                # 直接保留文本节点的原始空白
                output.append(content)
                continue
            # 处理标签元素
            tag_start = f"{current_indent}<{content.name}" if level != 0 else f"<{content.name}"
            # 处理标签属性
            if content.attrs:
                attrs_str = " ".join([f'{key}="{value}"' for key, value in content.attrs.items()])
                tag_start += f" {attrs_str}>"
            else:
                tag_start += ">"
            output.append(tag_start)
            
            # 判断是否需要保留内部空白
            preserve_space = content.get("xml:space") == "preserve"
            if content.contents:
                if preserve_space:
                    # 保留内部所有原始内容,不做缩进
                    output.append("".join(str(c) for c in content.contents))
                else:
                    # 递归处理子元素,增加缩进层级
                    output.append("\n")
                    output.append(prettify_respect_xml_space(content, indent, level + 1))
                    output.append(f"\n{current_indent}")
            
            # 闭合标签
            output.append(f"</{content.name}>")
            # 标签间换行
            if content.next_sibling is not None:
                output.append("\n")
        return "".join(output).strip()
    
    
使用自定义函数输出

print(prettify_respect_xml_space(soup))

这个函数会检查每个元素的`xml:space`属性,对标记为`preserve`的元素直接保留内部原始空白,其他元素则正常添加缩进,完美兼顾了可读性和语义准确性。

备注:内容来源于stack exchange,提问作者elechris
相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.14 13:38:05