Пользовательская ширина отступа для BeautifulSoup .prettify()

Есть ли способ определить пользовательскую ширину отступа для функции .prettify()? Из того, что я могу получить из его источника -

def prettify(self, encoding=None, formatter="minimal"):
    if encoding is None:
        return self.decode(True, formatter=formatter)
    else:
        return self.encode(encoding, True, formatter=formatter)

Нет возможности указать ширину отступа. Я думаю, это из-за этой строки в функции decode_contents() -

s.append(" " * (indent_level - 1))

Который имеет фиксированную длину 1 пробел! (ПОЧЕМУ!!) Я попытался указать indent_level=4, это просто приводит к этому -

    <section>
     <article>
      <h1>
      </h1>
      <p>
      </p>
     </article>
    </section>

Что выглядит просто глупо. :|

Теперь я могу взломать это, но я просто хочу убедиться, что я что-то упустил. Потому что это должно быть основной функцией. :-/

Если у вас есть способ улучшить HTML-коды, дайте мне знать.


person Bibhas Debnath    schedule 19.03.2013    source источник
comment
Отвечая на ваш побочный вопрос (ПОЧЕМУ!): HTML и XML имеют тенденцию быть очень, очень глубоко вложенными, и я предполагаю, что ребятам из Крамми нравятся окна с 80 столбцами. Но вы можете опубликовать в списке рассылки/группе и/или сообщить об ошибке, запрашивая эту функцию (и, поскольку патч довольно прост — и ramabodhi уже в значительной степени написал его для вас — вы должны включить его в свое электронное письмо/сообщение об ошибке). отчет).   -  person abarnert    schedule 20.03.2013
comment
Похоже, что кто-то отправил аналогичный патч против 3.2 в список рассылки пару лет назад. См. здесь.   -  person abarnert    schedule 20.03.2013
comment
Отступ в 1 пробел выглядит просто глупо. :| - Спасибо. Это именно то, о чем я думал, когда искал эту проблему.   -  person Brandin    schedule 24.08.2015


Ответы (4)


На самом деле я справился с этим сам, самым хакерским способом: постобработкой результата.

r = re.compile(r'^(\s*)', re.MULTILINE)
def prettify_2space(s, encoding=None, formatter="minimal"):
    return r.sub(r'\1\1', s.prettify(encoding, formatter))

На самом деле, я исправил prettify_2space вместо prettify в классе. Это не обязательно для решения, но давайте все равно сделаем это и сделаем ширину отступа параметром, а не жестко запрограммируем его равным 2:

orig_prettify = bs4.BeautifulSoup.prettify
r = re.compile(r'^(\s*)', re.MULTILINE)
def prettify(self, encoding=None, formatter="minimal", indent_width=4):
    return r.sub(r'\1' * indent_width, orig_prettify(self, encoding, formatter))
bs4.BeautifulSoup.prettify = prettify

So:

x = '''<section><article><h1></h1><p></p></article></section>'''
soup = bs4.BeautifulSoup(x)
print(soup.prettify(indent_width=3))

… дает:

<html>
   <body>
      <section>
         <article>
            <h1>
            </h1>
            <p>
            </p>
         </article>
      </section>
   </body>
</html>

Очевидно, что если вы хотите пропатчить Tag.prettify так же, как и BeautifulSoup.prettify, вы должны сделать там то же самое. (Возможно, вы захотите создать общую оболочку, которую вы можете применить к обоим, вместо того, чтобы повторять себя.) И если есть какие-либо другие методы prettify, то же самое.

person abarnert    schedule 20.03.2013

Насколько я могу судить, эта функция не встроена, так как существует несколько решений этой проблемы.

Предполагая, что вы используете BeautifulSoup 4, вот решения, которые я придумал

Жестко закодируйте его. Это требует минимальных изменений, это нормально, если вам не нужно, чтобы отступ был разным в разных обстоятельствах:

myTab = 4 # add this
if pretty_print:
   # space = (' ' * (indent_level - 1))
    space = (' ' * (indent_level - myTab))
    #indent_contents = indent_level + 1
    indent_contents = indent_level + myTab 

Еще одна проблема с предыдущим решением заключается в том, что текстовое содержимое не будет иметь отступ полностью последовательно, но все равно будет привлекательно. Если вам нужно более гибкое/согласованное решение, вы можете просто изменить класс.

Найдите функцию prettify и измените ее как таковую (она находится в классе Tag в element.py):

#Add the myTab keyword to the functions parameters (or whatever you want to call it), set it to your preferred default.
def prettify(self, encoding=None, formatter="minimal", myTab=2): 
    Tag.myTab= myTab # add a reference to it in the Tag class
    if encoding is None:
        return self.decode(True, formatter=formatter)
    else:
        return self.encode(encoding, True, formatter=formatter)

А затем прокрутите вверх до метода декодирования в классе Tag и внесите следующие изменения:

if pretty_print:
    #space = (' ' * (indent_level - 1))
    space = (' ' * (indent_level - Tag.myTab))
    #indent_contents = indent_level + Tag.myTab 
    indent_contents = indent_level + Tag.myTab

Затем перейдите к методу decode_contents в классе Tag и внесите следующие изменения:

#s.append(" " * (indent_level - 1))
s.append(" " * (indent_level - Tag.myTab))

Теперь BeautifulSoup('‹root›‹child›‹desc›Text‹/desc›‹/child›‹/root›').prettify(myTab=4) вернет:

<root>
    <child>
        <desc>
            Text
        </desc>
    </child>
</root>

** Нет необходимости исправлять класс BeautifulSoup, поскольку он наследует класс Tag. Класса Patching Tag достаточно для достижения цели.

person tryexceptcontinue    schedule 20.03.2013
comment
Это должно быть очень легко преобразовать в патч для исходного дерева bs4, что удобно. ОП может просто создать свой собственный форк дерева bzr и исправить его, отправить патч вверх по течению и т. д. - person abarnert; 20.03.2013
comment
Спасибо, парни. Я просто не мог поверить, что только у одного человека была проблема с этим за эти годы, и он предложил патч, и он до сих пор не слит. Я уже изменил функцию, чтобы она принимала переменную длину (поскольку я ненавижу жесткое кодирование). Это в значительной степени делает то, что вы предложили. Но дело в том, что вам нужно что-то предоставить для indent_level из-за этой строки pretty_print = (indent_level is not None) И, как я вижу, значение по умолчанию для indent_level равно None, и нет никакого динамического способа изменить его. ‹_‹ - person Bibhas Debnath; 20.03.2013

Вот способ увеличить отступ без вмешательства в исходные функции и т. д. Создайте следующую функцию:

# Increase indentation of 'text' by 'n' spaces
def add_indent(text,n):
  sp = " "*n
  lsep = chr(10) if text.find(chr(13)) == -1 else chr(13)+chr(10)
  lines = text.split(lsep)
  for i in range(len(lines)):
    spacediff = len(lines[i]) - len(lines[i].lstrip())
    if spacediff: lines[i] = sp*spacediff + lines[i] 
  return lsep.join(lines)

Затем преобразуйте полученный текст с помощью вышеуказанной функции:

x = '''<section><article><h1></h1><p></p></article></section>'''
soup = bs4.BeautifulSoup(x, 'html.parser')  # I don't know if you need 'html.parser'
text = soup.prettify()                      # I do, otherwise I get a warning
text = add_indent(text,1) # Increase indentation by 1 space 
print(text)
'''
Output:
<html>
  <body>
    <section>
      <article>
        <h1>
        </h1>
        <p>
        </p>
      </article>
    </section>
  </body>
</html>
'''
person Apostolos    schedule 27.07.2020

Если вы используете pycharm, вы можете автоматически переформатировать html-файл, нажав:

Ctrl + Alt + L

при открытии предварительно подготовленного html-файла в pycharm.

Это изменит отступ с одного пробела на 4 или любой другой, который вы установили для html в Настройки>Редактор>Стиль кода>HTML (по умолчанию = 4).

https://www.jetbrains.com/pycharm/guide/tips/reformat-code/

Вам нужно будет сделать это для каждого html-файла, для которого вы запускаете prettify().

Извините, если я оживляю старую тему, но у меня была такая же проблема, и я нашел простое решение.

person Jnat    schedule 17.05.2020
comment
Это не ответ на заданный вопрос; OP ищет программный способ форматирования произвольного вывода BS .prettify(), а не ручной способ на основе IDE для форматирования одного конкретного файла. - person smci; 14.07.2020