4

次のような HTML ソースから一連の空の DOM 要素をクリーンアップする方法を見つけようとしています。

<div class="empty">
    <div>&nbsp;</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>

ただし、有効な要素や改行を傷つけたくありません。したがって、結果は次のようになります。

<a href="http://example.com">good</a>
<br>
<img src="http://example.com/logo.png" />

これまでのところ、次のような XPath をいくつか試しました。

$xpath = new DOMXPath($dom);

//$x = '//*[not(*) and not(normalize-space(.))]';
//$x = '//*[not(text() or node() or self::br)]';
//$x = 'not(normalize-space(.) or self::br)';
$x = '//*[not(text() or node() or self::br)]';

while(($nodeList = $xpath->query($x)) && $nodeList->length > 0) {
    foreach ($nodeList as $node) {
        $node->parentNode->removeChild($node);
    }
}

空の場合は何の役にも立たない空の DOM ノードを削除するための正しい XPath を教えてもらえますか? (img、br、および input は空であっても目的を果たします)

現在の出力:

<div>
    <div>&nbsp;</div>

</div>
<a href="http://example.com">good</a>
<div>

</div>
<br>

アップデート

明確にするために、次のいずれかの XPath クエリを探しています。

  • すべてが見つかるまで空のノードのマッチングを再帰的に行う (空のノードの親を含む)
  • 各クリーンアップ後に複数回正常に実行できます (私の例に示すように)
4

4 に答える 4

7

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(., '&#xA0;', '')))
  and
    not(descendant-or-self::*
          [self::img or self::input or self::br])]"/>
</xsl:stylesheet>

提供された XML に適用すると (整形式の XML ドキュメントになるように修正されます):

<html>
    <div class="empty">
        <div>&#xA0;</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>

説明:

  1. アイデンティティ ルールは、実行対象として選択されたすべてのノードを「そのまま」コピーします。

  2. img任意の要素 ( 、inputおよび を除く) の ID テンプレートをオーバーライドする 1 つのテンプレートがありbr、その文字列値からいずれか&nbsp;が削除されたものは空の文字列です。このテンプレートの本体は空で、一致した要素を事実上「削除」します。一致した要素は出力にコピーされません。


Ⅱ.更新

OP は、次のような 1 つ以上の XPath 式が必要であることを明確にしています。

"各クリーンアップ後に複数回正常に実行できます。 "

興味深いことに、削除する必要があるすべてのノードを正確に選択する単一の XPath 式が存在します。したがって、「複数のクリーンアップ」は完全に回避されます。

//*[not(normalize-space((translate(., '&#xA0;', ''))))
  and
    not(descendant-or-self::*[self::img or self::input or self::br])
    ]
     [not(ancestor::*
             [count(.| //*[not(normalize-space((translate(., '&#xA0;', ''))))
                         and
                           not(descendant-or-self::*
                                  [self::img or self::input or self::br])
                          ]
                    )
             =
              count(//*[not(normalize-space((translate(., '&#xA0;', ''))))
                      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(., '&#xA0;', ''))))
      and
        not(descendant-or-self::*[self::img or self::input or self::br])
       ]
        [not(ancestor::*
               [count(.| //*[not(normalize-space((translate(., '&#xA0;', ''))))
                           and
                             not(descendant-or-self::*
                                    [self::img or self::input or self::br])
                             ]
                      )
               =
                count(//*[not(normalize-space((translate(., '&#xA0;', ''))))
                        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(., '&#xA0;', ''))))
     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(., '&#xA0;', ''))))
  and
    not(descendant-or-self::*[self::img or self::input or self::br])
    ]
     [not(ancestor::*
             [count(.| //*[not(normalize-space((translate(., '&#xA0;', ''))))
                         and
                           not(descendant-or-self::*
                                  [self::img or self::input or self::br])
                          ]
                    )
             =
              count(//*[not(normalize-space((translate(., '&#xA0;', ''))))
                      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(., '&#xA0;', ''))))
         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(.,'&#xA0;',''))]
    ]
     /ancestor-or-self::node()

次に、削除するノードは次のとおりです。

  //node()
     [not(count(.
              |
                //node() 
                   [self::input or self::img or self::br
                  or
                    self::text()[normalize-space(translate(.,'&#xA0;',''))]
                   ]
                    /ancestor-or-self::node()
                )
        =
         count(//node()
                  [self::input or self::img or self::br
                 or
                   self::text()[normalize-space(translate(.,'&#xA0;',''))]
                  ]
                   /ancestor-or-self::node()
               )
         )
     ]

ただし、これらはすべて削除するノードであり、「削除するトップ ノード」だけではないことに注意してください。「削除する上位ノード」だけを表現することもできますが、結果の表現はかなり複雑になります。all-nodes-to-delete を削除しようとすると、「削除する上位ノード」の子孫がドキュメント順に続くため、エラーが発生します。

于 2012-08-03T03:45:54.277 に答える
2

では、テキスト ノード、 、 、<br>および<img>それらの先祖が必要ですか?

//brandですべての br と img を取得できます//img

//text()ですべてのテキスト ノードを取得し、 で空でないすべてのテキスト ノードを取得できます//text()[normalize-space()]。(ただし、xml パーサーがまだそれを行っていない場合//text()[normalize-space(translate(., '&nbsp;', ''))]は、テキスト ノードをフィルター処理する必要があるかもしれません)&nbsp;

そして、 ですべての親を取得できますancestor-or-self::*

したがって、結果の式は

//br/ancestor-or-self::* | //img/ancestor-or-self::* | //text()[normalize-space()]/ancestor-or-self::*

XPath 2 ではさらに短くなります:

(//br | //img | //text()[normalize-space()])/ancestor-or-self::*
于 2012-08-03T17:43:26.953 に答える
1

これに似た XPath を試しましたか?

*[not(*) and not(text()[normalize-space()])]

  • not(*)= 子要素なし
  • text()[normalize-space()]= 空白テキスト以外のノードを含める (これを逆にしない)
于 2012-08-02T17:26:31.190 に答える
1

必要な結果を得る最も簡単な方法は、テキストで正規表現を使用することです。備考: この式を数回使用する必要があります。貪欲ではないため、最も低い空の子ノードのみを削除するため、すべての空のノードを削除するには、正規表現を数回呼び出す必要があります。

解決策は次のとおりです。

<?
$text = '<div class="empty">
    <div>&nbsp;</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>';

// recursive function
function recreplace($text)
{
    $restext = preg_replace("/<div(.*)?>((\s|&nbsp;)*|(\s|&nbsp;)*<p>(\s|&nbsp;)*<\/p>(\s|&nbsp;)*)*<\/div>/U", '', $text);
    if ($text != $restext) 
    {
        recreplace($restext);
    }
    else
    {
        return $restext;
    }
}

print recreplace($text);
?>

このコードは、目的の結果を出力します。正規表現を編集できる必要がある場合は、空としてカウントする必要がある他のタグを ( as として<p> </p>) 追加することができます。

与えられた例では、この関数は自分自身を結果的に 2 回呼び出し、3 回目は置換を行わずに呼び出します - そしてそれが結果になります。

于 2012-08-03T06:36:55.717 に答える