lxml ElementTree 内の特定の html 要素 (el) のコンテンツが、レンダリングされた HTML ページの行の先頭のコンテンツであるかどうかを判断する関数に取り組んでいます。これを行うために、el の左にある最も右のブロック レベル要素を見つけて、これら 2 つの間にコンテンツがあるかどうかを判断しようとしています。
これは、el から始まる逆方向のトラバーサルを使用して、DFS の逆の順序でトラバーサルを行うことで発生する可能性があると考えています。しかし、これを行うためにlxmlまたはxpathを使用してより簡単な方法が存在するかどうかも調べようとしています。これまでのところ、特定の要素の祖先または左の兄弟である要素をいくつかの基準で見つける方法を見つけましたが、特定のノードの右側 (または左側) のツリー全体で機能するものは見つけていません。
lxml または xpath を使用してこの検索を行う簡単な方法を知っている人はいますか?
例
<html>
<body class="first">
root
<!-- A span that does not have its own content, but does have several levels of children-->
<span>
<a>
<b>
<h1 class="first">
A block level that is the decendant of several non block levels
</h1>
</b>
</a>
<span class="first" id="tricky">
A non-block level that has no block levels among its ancestors, but a block level element among its left cousins
</span>
<span>
A non-block level that has no block levels among its ancestors, and content between itself and its nearest left-cousin block level
</span>
</span>
<div class="first">
a block level
</div>
<div>
<span class="first">first content in a non block level in a block level</span>
<span>following content in a non block level in a block level</span>
</div>
<div>
<i> </i><bclass="first">a non block level that contains the first content within a block level, but follows an empty non-block level</b>
</div>
</body>
</html>
上記では、レンダリング時に行の先頭のコンテンツを表示するように見える要素に「最初の」クラスを追加しました。特に興味深いのは、ID が "tricky" の要素です。その要素は、祖先も兄弟もブロック レベルの要素ではありませんが、行の最初のコンテンツを表示するためです。「トリッキー」は、その兄弟の 1 つ (h1) の子孫がブロック レベルであり、その h1 の後に他のコンテンツがないため、新しい行になります。
フォローアップ この時点で、一種の後方トラバーサルを行う関数を Python で作成しました。少し複雑ですが、うまくいくようです:
block_level = {'blockquote','br','dd','div','dl','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','td','ul'}
# Returns true if the content of the provided element is the leading content of a line
# This function runs on HTML elements before any translation occurs
# Here 'content' refers to non-whiespace characters
def is_first_in_line_html(self, el):
# This element contains no content, so it can't be the leading content of a line.
if el.text is None or el.text.strip() == '': return False
# This element has content and is a block level, so its content is the leading content of a line.
if el.tag in block_level: return True
# This element has content, is not a block level, and is the body element. Definitely leading content of a line.
if el.tag == 'body': return True
# Final case - is there content between the present element and the nearest block level element to the left of the present
# element.
def traverse_children(element, bound_text):
children = element.iterchildren(reversed=True)
for child in children:
if child.tail is not None: bound_text = child.tail + bound_text
if bound_text.strip() != '': return False
if child.tag in block_level: return bound_text.strip() == ''
rst_children = traverse_children(child, bound_text)
if rst_children is not None: return rst_children
if child.text is not None: bound_text = child.text + bound_text
if bound_text.strip() != '': return False
return None
def traverse_left_sibs_and_ancestors(element, bound_text):
left_sibs = element.itersiblings(preceding=True)
for sib in left_sibs:
if sib.tail is not None: bound_text = sib.tail + bound_text
if bound_text.strip() != '': return False
if sib.tag in block_level: return bound_text.strip() == ''
rst_children = traverse_children(sib, bound_text)
if rst_children is not None: return rst_children
if sib.text is not None: bound_text = sib.text + bound_text
if bound_text.strip() != '': return False
parent = element.getparent()
if parent.tail is not None: bound_text = parent.tail + bound_text
if parent.tag == 'body': return bound_text.strip() == ''
if parent.tag in block_level: return bound_text.strip() == ''
return traverse_left_sibs_and_ancestors(parent)
return traverse_left_sibs_and_ancestors(el, '')