I. 初期ソリューション:
XPath は、XML ドキュメントのクエリ言語です。そのため、XPath 式の評価では、XML ドキュメントからノードを選択するか、ノード以外の情報を抽出するだけで、XML ドキュメントを変更することはありません。したがって、XPath 式を評価しても、ノードが削除または挿入されることはありません。XML ドキュメントは同じままです。
あなたが望むのは、「HTML ソースから空の DOM 要素の束をクリーンアップする」ことであり、XPath だけでは実行できません。
これは、XPath に関する最も信頼できる唯一の公式 (規範的と言う) の情報源 -- W3C XPath 1.0 勧告によって確認されています。
" XPath の主な目的は、XML [XML] ドキュメントの一部に対処することです。この主な目的をサポートするために、文字列、数値、およびブール値を操作するための基本的な機能も提供します。XPath はコンパクトな非 XML 構文を使用して、 URI および XML 属性値内での XPath の使用. XPath は、XML ドキュメントの表面的な構文ではなく、抽象的で論理的な構造で動作します. XPath の名前は、XML ドキュメントの階層構造をナビゲートするための URL のように、パス表記法を使用することから付けられます。 XML ドキュメントです。」
したがって、require 機能を実装するには、XPath と組み合わせて追加の言語を使用する必要があります。
XSLT は、XML 変換用に特別に設計された言語です。
以下は、XSLT ベースの例です。要求されたクリーンアップを実行する短くて単純な XSLT 変換です。
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"*[not(string(translate(., ' ', '')))
and
not(descendant-or-self::*
[self::img or self::input or self::br])]"/>
</xsl:stylesheet>
提供された XML に適用すると (整形式の XML ドキュメントになるように修正されます):
<html>
<div class="empty">
<div> </div>
<div></div>
</div>
<a href="http://example.com">good</a>
<div>
<p></p>
</div>
<br />
<img src="http://example.com/logo.png" />
<div></div>
</html>
必要な正しい結果が生成されます。
<html>
<a href="http://example.com">good</a>
<br/>
<img src="http://example.com/logo.png"/>
</html>
説明:
アイデンティティ ルールは、実行対象として選択されたすべてのノードを「そのまま」コピーします。
img
任意の要素 ( 、input
および を除く) の ID テンプレートをオーバーライドする 1 つのテンプレートがありbr
、その文字列値からいずれか
が削除されたものは空の文字列です。このテンプレートの本体は空で、一致した要素を事実上「削除」します。一致した要素は出力にコピーされません。
Ⅱ.更新:
OP は、次のような 1 つ以上の XPath 式が必要であることを明確にしています。
"各クリーンアップ後に複数回正常に実行できます。 "
興味深いことに、削除する必要があるすべてのノードを正確に選択する単一の XPath 式が存在します。したがって、「複数のクリーンアップ」は完全に回避されます。
//*[not(normalize-space((translate(., ' ', ''))))
and
not(descendant-or-self::*[self::img or self::input or self::br])
]
[not(ancestor::*
[count(.| //*[not(normalize-space((translate(., ' ', ''))))
and
not(descendant-or-self::*
[self::img or self::input or self::br])
]
)
=
count(//*[not(normalize-space((translate(., ' ', ''))))
and
not(descendant-or-self::*
[self::img or self::input or self::br])
]
)
]
)
]
XSLT ベースの検証:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"//*[not(normalize-space((translate(., ' ', ''))))
and
not(descendant-or-self::*[self::img or self::input or self::br])
]
[not(ancestor::*
[count(.| //*[not(normalize-space((translate(., ' ', ''))))
and
not(descendant-or-self::*
[self::img or self::input or self::br])
]
)
=
count(//*[not(normalize-space((translate(., ' ', ''))))
and
not(descendant-or-self::*
[self::img or self::input or self::br])
]
)
]
)
]
"/>
</xsl:stylesheet>
この変換が、提供された (整形式の) XML ドキュメント (上記) に適用されると、XPath 式によって選択されたノードを除いて、すべてのノードが「そのまま」コピーされます。
<html>
<a href="http://example.com">good</a>
<br/>
<img src="http://example.com/logo.png"/>
</html>
説明:
$vAllEmpty
質問の「空」の定義に従って、「空」であるすべてのノードで示しましょう。
$vAllEmpty
次の XPath 式で表されます。
//*[not(normalize-space((translate(., ' ', ''))))
and
not(descendant-or-self::*
[self::img or self::input or self::br])
]
これらすべてを削除するには、「トップ ノード」だけを削除する必要があります。$vAllEmpty
そのようなすべての「トップノード」のセットを次のように示しましょう$vTopEmpty
。
$vTopEmpty
$vAllEmpty
次の XPath 2.0 式を使用して表現できます。
$vAllEmpty[not(ancestor::* intersect $vAllEmpty)]
$vAllEmpty
これは、 にもある祖先要素を持たないノードを選択し$vAllEmpty
ます。
最後の XPath 式には、同等の XPath 1.0 式があります。
$vAllEmpty[not(ancestor::*[count(.|$vAllEmpty) = count($vAllEmpty)])]
ここで、最後の式$vAllEmpty
を上記で定義した展開された XPath 式に置き換えます。これが、「削除するトップ ノード」のみを選択する最終的な式を取得する方法です。
//*[not(normalize-space((translate(., ' ', ''))))
and
not(descendant-or-self::*[self::img or self::input or self::br])
]
[not(ancestor::*
[count(.| //*[not(normalize-space((translate(., ' ', ''))))
and
not(descendant-or-self::*
[self::img or self::input or self::br])
]
)
=
count(//*[not(normalize-space((translate(., ' ', ''))))
and
not(descendant-or-self::*
[self::img or self::input or self::br])
]
)
]
)
]
変数を使用した短い XSLT-2.0 ベースの検証:
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:variable name="vAllEmpty" select=
"//*[not(normalize-space((translate(., ' ', ''))))
and
not(descendant-or-self::*
[self::img or self::input or self::br])
]"/>
<xsl:variable name="vTopEmpty" select=
"$vAllEmpty[not(ancestor::* intersect $vAllEmpty)]"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="*[. intersect $vTopEmpty]"/>
</xsl:stylesheet>
この変換により、 に属するノードを除いて、すべてのノードが「そのまま」コピーされます$vTopEmpty
。結果は正しく、期待されるものです。
<html>
<a href="http://example.com">good</a>
<br/>
<img src="http://example.com/logo.png"/>
</html>
III. 代替ソリューション(「複数のクリーンアップ」が必要になる場合があります):
別のアプローチは、削除するノードを指定しようとするのではなく、保持するノードを指定することです。この場合、削除するノードは、すべてのノードと保持するノードの間の設定差になります。
保持するノードは、次の XPath 式によって選択されます。
//node()
[self::input or self::img or self::br
or
self::text()[normalize-space(translate(.,' ',''))]
]
/ancestor-or-self::node()
次に、削除するノードは次のとおりです。
//node()
[not(count(.
|
//node()
[self::input or self::img or self::br
or
self::text()[normalize-space(translate(.,' ',''))]
]
/ancestor-or-self::node()
)
=
count(//node()
[self::input or self::img or self::br
or
self::text()[normalize-space(translate(.,' ',''))]
]
/ancestor-or-self::node()
)
)
]
ただし、これらはすべて削除するノードであり、「削除するトップ ノード」だけではないことに注意してください。「削除する上位ノード」だけを表現することもできますが、結果の表現はかなり複雑になります。all-nodes-to-delete を削除しようとすると、「削除する上位ノード」の子孫がドキュメント順に続くため、エラーが発生します。