如何阻止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的语义,又能得到合适的输出,可以用这两种方案:
直接使用
str(soup)输出(最简单)
正如你代码里注释的那样,直接将BeautifulSoup对象转为字符串,不会对任何有效空白做修改:print(str(soup))这种方式的好处是完全保留原始语义,缺点是输出没有缩进,可读性稍弱,但对于需要严格保留空白的场景来说是最稳妥的。
自定义格式化函数(兼顾可读性和语义)
如果你既想要缩进美化的输出,又要严格尊重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
相关产品推荐
相关产品推荐

