【问题标题】:How to prettify HTML so tag attributes will remain in one single line?如何美化 HTML 以使标签属性保留在一行中?
【发布时间】:2018-06-01 10:13:42
【问题描述】:

我得到了这段小代码:

text = """<html><head></head><body>
    <h1 style="
    text-align: center;
">Main site</h1>
    <div>
        <p style="
    color: blue;
    text-align: center;
">text1
        </p>
        <p style="
    color: blueviolet;
    text-align: center;
">text2
        </p>
    </div>
    <div>
        <p style="text-align:center">
            <img src="./foo/test.jpg" alt="Testing static images" style="
">
        </p>
    </div>
</body></html>
"""

import sys
import re
import bs4


def prettify(soup, indent_width=4):
    r = re.compile(r'^(\s*)', re.MULTILINE)
    return r.sub(r'\1' * indent_width, soup.prettify())

soup = bs4.BeautifulSoup(text, "html.parser")
print(prettify(soup))

上面sn-p现在的输出是:

<html>
    <head>
    </head>
    <body>
        <h1 style="
                text-align: center;
">
            Main site
        </h1>
        <div>
            <p style="
                color: blue;
                text-align: center;
">
                text1
            </p>
            <p style="
                color: blueviolet;
                text-align: center;
">
                text2
            </p>
        </div>
        <div>
            <p style="text-align:center">
                <img alt="Testing static images" src="./foo/test.jpg" style="
"/>
            </p>
        </div>
    </body>
</html>

我想弄清楚如何格式化输出,让它变成这样:

<html>
    <head>
    </head>
    <body>
        <h1 style="text-align: center;">
            Main site
        </h1>
        <div>
            <p style="color: blue;text-align: center;">
                text1
            </p>
            <p style="color: blueviolet;text-align: center;">
                text2
            </p>
        </div>
        <div>
            <p style="text-align:center">
                <img alt="Testing static images" src="./foo/test.jpg" style=""/>
            </p>
        </div>
    </body>
</html>

另外,如果可能的话,我想将诸如&lt;tag attrib1=value1 attrib2=value2 ... attribn=valuen&gt; 之类的html 语句保留在一行中。当我说“如果可能”时,我的意思是不要搞砸属性本身的值(value1、value2、...、valuen)。

这可以用beautifulsoup4 实现吗?据我在文档中阅读,您似乎可以使用自定义 formatter,但我不知道如何拥有自定义格式化程序,以便它可以满足所描述的要求。

编辑:

@alecxe 的解决方案非常简单,不幸的是在一些更复杂的情况下失败了,比如下面的一个,即:

test1 = """
<div id="dialer-capmaign-console" class="fill-vertically" style="flex: 1 1 auto;">
    <div id="sessionsGrid" data-columns="[
        { field: 'dialerSession.startTime', format:'{0:G}', title:'Start time', width:122 },
        { field: 'dialerSession.endTime', format:'{0:G}', title:'End time', width:122, attributes: {class:'tooltip-column'}},
        { field: 'conversationStartTime', template: cty.ui.gct.duration_dialerSession_conversationStartTime_endTime, title:'Duration', width:80},
        { field: 'dialerSession.caller.lastName',template: cty.ui.gct.person_dialerSession_caller_link, title:'Caller', width:160 },
        { field: 'noteType',template:cty.ui.gct.nameDescription_noteType, title:'Note type', width:150, attributes: {class:'tooltip-column'}},
        { field: 'note', title:'Note'}
        ]">
</div>
</div>
"""

from bs4 import BeautifulSoup
import re


def prettify(soup, indent_width=4, single_lines=True):
    if single_lines:
        for tag in soup():
            for attr in tag.attrs:
                print(tag.attrs[attr], tag.attrs[attr].__class__)
                tag.attrs[attr] = " ".join(
                    tag.attrs[attr].replace("\n", " ").split())

    r = re.compile(r'^(\s*)', re.MULTILINE)
    return r.sub(r'\1' * indent_width, soup.prettify())


def html_beautify(text):
    soup = BeautifulSoup(text, "html.parser")
    return prettify(soup)

print(html_beautify(test1))

追溯:

dialer-capmaign-console <class 'str'>
['fill-vertically'] <class 'list'>
Traceback (most recent call last):
  File "d:\mcve\x.py", line 35, in <module>
    print(html_beautify(test1))
  File "d:\mcve\x.py", line 33, in html_beautify
    return prettify(soup)
  File "d:\mcve\x.py", line 25, in prettify
    tag.attrs[attr].replace("\n", " ").split())
AttributeError: 'list' object has no attribute 'replace'

【问题讨论】:

    标签: python html beautifulsoup code-formatting


    【解决方案1】:

    BeautifulSoup 尝试保留输入 HTML 的属性值中的换行符和多个空格。

    这里的一种解决方法是在美化之前迭代元素属性并清理它们 - 删除换行符并用单个空格替换多个连续空格:

    for tag in soup():
        for attr in tag.attrs:
            tag.attrs[attr] = " ".join(tag.attrs[attr].replace("\n", " ").split())
    
    print(soup.prettify())
    

    打印:

    <html>
     <head>
     </head>
     <body>
      <h1 style="text-align: center;">
       Main site
      </h1>
      <div>
       <p style="color: blue; text-align: center;">
        text1
       </p>
       <p style="color: blueviolet; text-align: center;">
        text2
       </p>
      </div>
      <div>
       <p style="text-align:center">
        <img alt="Testing static images" src="./foo/test.jpg" style=""/>
       </p>
      </div>
     </body>
    </html>
    

    更新(解决多值属性,如class):

    您只需要稍加修改,为属性为list 类型的情况添加特殊处理:

    for tag in soup():
        tag.attrs = {
            attr: [" ".join(attr_value.replace("\n", " ").split()) for attr_value in value] 
                  if isinstance(value, list)
                  else " ".join(value.replace("\n", " ").split())
            for attr, value in tag.attrs.items()
        }
    

    【讨论】:

    • 在这里接受和给予赏金的原因如下:1)问题是指 bs4,这个问题符合要求 2)更多人的支持和土地的第一个回答 3)@carlo chen 答案不是开箱即用,即:tidylib 不是一个自包含的包,它需要一些外部 dll。
    【解决方案2】:

    虽然 BeautifulSoup 更常用,但如果您正在处理怪癖并有更具体的要求,HTML Tidy 可能是更好的选择。

    安装 Python 库 (pip install pytidylib) 后,尝试以下代码:

    from tidylib import Tidy
    tidy = Tidy()
    # assign string to text
    config = {
        "doctype": "omit",
        # "show-body-only": True
    }
    print tidy.tidy_document(text, options=config)[0]
    

    tidy.tidy_document 返回一个包含 HTML 和任何可能发生的错误的元组。此代码将输出

    <html>
      <head>
        <title></title>
      </head>
      <body>
        <h1 style="text-align: center;">
          Main site
        </h1>
        <div>
          <p style="color: blue; text-align: center;">
            text1
          </p>
          <p style="color: blueviolet; text-align: center;">
            text2
          </p>
        </div>
        <div>
          <p style="text-align:center">
            <img src="./foo/test.jpg" alt="Testing static images" style="">
          </p>
        </div>
      </body>
    </html>
    

    取消注释第二个示例的 "show-body-only": True

    <div id="dialer-capmaign-console" class="fill-vertically" style="flex: 1 1 auto;">
      <div id="sessionsGrid" data-columns="[ { field: 'dialerSession.startTime', format:'{0:G}', title:'Start time', width:122 }, { field: 'dialerSession.endTime', format:'{0:G}', title:'End time', width:122, attributes: {class:'tooltip-column'}}, { field: 'conversationStartTime', template: cty.ui.gct.duration_dialerSession_conversationStartTime_endTime, title:'Duration', width:80}, { field: 'dialerSession.caller.lastName',template: cty.ui.gct.person_dialerSession_caller_link, title:'Caller', width:160 }, { field: 'noteType',template:cty.ui.gct.nameDescription_noteType, title:'Note type', width:150, attributes: {class:'tooltip-column'}}, { field: 'note', title:'Note'} ]"></div>
    </div>
    

    有关更多选项和自定义,请参阅more configuration。有一些特定于属性的包装选项可能会有所帮助。可以看到,空元素只占一行,html-tidy 会自动尝试添加DOCTYPEheadtitle 标签。

    【讨论】:

      猜你喜欢
      • 2021-04-16
      • 2015-06-16
      • 1970-01-01
      • 1970-01-01
      • 2012-02-06
      • 2021-04-04
      • 2021-08-30
      • 2021-10-24
      • 1970-01-01
      相关资源
      最近更新 更多