Python3: Как преобразовать обычный html во вложенный словарь на основе уровня тегов `h`? - PullRequest
1 голос
/ 27 января 2020

У меня есть html, который выглядит следующим образом:

<h1>Sanctuary Verses</h1>
    <h2>Purpose and Importance of the Sanctuary</h2>
       <p>Ps 73:17\nUntil I went into the sanctuary of God; [then] understood I their end.</p>
       <p>...</p>
    <h2>Some other title</h2>
        <p>...</p>
         <h3>sub-sub-title</h3>
             <p>sub-sub-content</p>
    <h2>Some different title</h2>
        <p>...</p>...

Нет тегов div или section, которые группируют теги p. Это хорошо работает для целей отображения. Я хочу извлечь данные так, чтобы получить желаемый вывод.

Желаемый вывод:

  1. Теги h должны отображаться как заголовки и вкладываться в соответствии с их уровнями
  2. Теги p должны быть добавлены к содержимому под указанным заголовком c, указанным тегом h

Желаемый результат:

{
  "title": "Sanctuary Verses"
  "contents": [
    {"title": "Purpose and Importance of the Sanctuary"
     "contents":["Ps 73:17\nUntil I went into the sanctuary of God; [then] understood I their end.",
                 "...."
                ]
    },
    {"title": "Some other title"
     "contents": ["...",
                 {"title": "sub-sub-title"
                  "content": ["sub-sub-content"]
                 }
                 ]
    },
    {"title": "Some different title"
     "content": ["...","..."]
    }
}

Я написал некоторый обходной код, который помог мне получить желаемый результат. Мне интересно, какой самый простой способ получить желаемый результат

Ответы [ 2 ]

1 голос
/ 27 января 2020

Вы можете использовать рекурсию с itertools.groupby:

import itertools as it, re
def to_tree(d):
  v, r = [list(b) for _, b in it.groupby(d, key=lambda x:not x[0])], []
  for i in v:
    if r and isinstance(r[-1], dict) and not r[-1]['content']:
      r[-1]['content'] = to_tree([(j[4:], k) for j, k in i])
    else:
      for _, k in i:
        r.append(re.sub('</*\w+\>', '', k) if not re.findall('^\<h', k) else {'title':re.sub('</*\w+\>', '', k), 'content':[]})
  return r

import json
result = to_tree([((lambda x:'' if not x else x[0])(re.findall('^\s+', i)), re.sub('^\s+', '', i)) for i in filter(None, html.split('\n'))])
print(json.dumps(result[0], indent=4))

Выход:

{
   "title": "Sanctuary Verses",
   "content": [
    {
        "title": "Purpose and Importance of the Sanctuary",
        "content": [
            "Ps 73:17 Until I went into the sanctuary of God; [then] understood I their end.",
            "..."
        ]
    },
    {
        "title": "Some other title",
        "content": [
            "...",
            {
                "title": "sub-sub-title",
                "content": [
                    "sub-sub-content"
                ]
            }
        ]
    },
      {
         "title": "Some different title",
         "content": [
            "..."
         ]
      }
   ] 
} 
1 голос
/ 27 января 2020

Это своего рода проблема стека / проблема графа. Давайте назовем это деревом. (или документ или что-то в этом роде.)

Я думаю, ваш первоначальный кортеж может быть улучшен. (текст, глубина, тип)

stack = []
depth = 0
broken_value = -1
current = {"title":"root", "contents":[]}
for item in list_of_tuples:
    if item[1]>depth:
         #deeper
         next = { "title":item[0], "contents":[]  }
         current["contents"].append(next)
         stack.append(current)
         current=next
         depth = item[1]
    elif item[1]<depth:
         #shallower closes current gets previous level
         while depth>item[1]:
             prev = stack.pop()
             depth = depth-1
         current = {"title":item[0], "content":[]}
         stack[-1].append(current)
         depth=item[1]
    else:
         #same depth 
         if item[2]==broken_value:
             #<p> element gets added to current level.
             current['contents'].append(item[0])
         else:
             #<h> element gets added to parent of current.
             current = {"title":item[0], "content":[]}
             stack[-1]["contents"].append(current)
    broken_value = item[2]

Это создаст произвольный график глубины, который предполагает, что глубина увеличивается на 1, но может уменьшаться на произвольное число.

Вероятно, было бы лучше отслеживать глубину в словаре, чтобы вы могли перемещать более одной глубины за раз. Вместо просто «заголовок» и «контент» может быть «заголовок», «глубина» и «контент»

Пояснение
Стек отслеживает открытые элементы и наш текущий Элемент - это элемент, который мы строим.

Если мы находим глубину>, чем наша текущая глубина, то мы помещаем текущий элемент в стек (он все еще открыт) и начинаем работать со следующим элементом уровня.

Если глубина меньше текущего элемента, мы закроем текущий элемент и родительские элементы на одну глубину.

Наконец, если это та же самая глубина, мы решаем, является ли это только что добавленным элементом 'p', или другим 'h', который закрывает ток и начинает новый ток.

...