簡體   English   中英

使用LibXML在Perl中用String替換XML節點

[英]Replace XML node with String in Perl using LibXML

我目前正在使用帶有LibXML的perl腳本來處理給定的XML文件。 這很好,但是如果我有一個既有子節點又有自由文本的節點,我就會開始掙扎。 輸入示例為:

<Errors>
    <Error>
        this node works fine
    </Error>
    <Error>
        some text <testTag>with a node</testTag> in between
    </Error>
</Errors>

預期產量:

<Errors>
    <Error>
        this node works fine
    </Error>
    <Error>
        some text HELLOwith a nodeHELLO in between
    </Error>
</Errors>

我嘗試了replaceChild(“ HELLO”,$ testTagNode); 用字符串替換節點,然后可以(如果需要)通過簡單的搜索替換進一步處理,但是我只遇到“ not a blessed reference”錯誤。 (我覺得如果真的那樣的話,那會很臟的。)

如果我嘗試像這樣直接在父節點上運行簡單的搜索替換

$error=~s/\</HELLO/g;

它根本不會觸發(無論是否逃避<),因為LibXML似乎忽略了我沒有特別要求的每個標簽; 如果我嘗試打印出第二個錯誤,它也會給我

some text with a node in between

對於文件的其余部分,這實際上是一個非常好的功能,但在這種情況下不是。

但是我可以

$error->removeChild($testTagNode);

這表明我確實找到了它,但並沒有進一步幫助我。 從理論上講,我可以刪除節點,保存內容,然后將內容重新插入到父節點中。 問題在於它需要位於之前的確切位置。 我唯一可能做的就是將整個文件作為字符串讀取,在將其輸入LibXML之前先對其進行基本的搜索替換,但這可能會產生很大的開銷,而且並不是一個很好的解決方案。

我覺得我正在忽略一些實質性的事情,因為這看起來像是一件很基本的任務,但是我似乎找不到任何東西。 也許我只是看錯了方向,並且有一種完全不同的方法可用。 任何幫助表示贊賞。

XML :: XSH2(它只是XML :: LibXML的包裝)中,以下內容似乎起作用:

for //testTag/text() {
    insert text 'HELLO' prepend . ;
    insert text 'HELLO' append . ;
    move . replace .. ;
}

轉換回XML :: LibXML留給讀者練習。

首先-我認為您要執行的操作不一定特別有用。 但是,我要注意-在處理節點時-如果像第二個示例中那樣有嵌套節點,則實際上會得到3個“節點”,但其中兩個指定為#PCDATA

因此,您可以執行以下操作:

#!/usr/bin/env perl
use strict;
use warnings;
use XML::Twig;
use Data::Dumper;

my $twig = XML::Twig->new( pretty_print => 'indented_a' )->parse( \*DATA );
foreach my $error ( $twig->get_xpath('//Error') ) {
    my $replace_text;
    foreach my $child ( $error->children ) {
        my $tag = $child->tag;
        print "Child: $tag ", $child->trimmed_text, "\n";
        $tag = '' if $tag eq "#PCDATA";
        $replace_text .= $tag . $child->trimmed_text . $tag;
    }

    $error->set_text($replace_text);
    print $error ->trimmed_text, "\n";
}
print $twig->sprint;

__DATA__
<Errors>
    <Error>
        this node works fine
    </Error>
    <Error>
        some text <testTag>with a node</testTag> in between
    </Error>
</Errors>

這變成了:

<Errors>
  <Error>this node works fine</Error>
  <Error>some texttestTagwith a nodetestTagin between</Error>
</Errors>

顯然,您可以將testTag重命名為testTag

(和我一起負擔-我將看看如何在LibXML中做到這一點-不幸的是,它無法輕松安裝在Windows機器上)。

OK,所以使用XML::LibXML

#!/usr/bin/env perl
use strict;
use warnings;
use XML::LibXML;


my $xml = XML::LibXML->load_xml( IO => \*DATA );
foreach my $error ( $xml -> findnodes ( '//Error' ) ) {
   my $replace_text; 
   foreach my $child ( $error -> childNodes ) {
      my $tag = $child -> nodeName;
      $tag = '' if $tag eq '#text';
      $replace_text .= $tag . $child -> textContent . $tag; 
      $err -> removeChild($child);
   } 
   $err -> appendTextNode($replace); 
}

print $xml -> toString;

__DATA__
<Errors>
    <Error>
        this node works fine
    </Error>
    <Error>
        some text <testTag>with a node</testTag> in between
    </Error>
</Errors>

卸下testTag元素將取消其所有的孩子也一樣,所以我們必須在每個子女移動testTag元素插入的父testTag刪除之前元素testTag元素。 在XML :: LibXML中,此操作如下:(已測試)

for my $node ($doc->findnodes('/Errors/Error//testTag')) {
   my $parent = $node->parentNode();

   for my $child_node (
      XML::LibXML::Text->new("HELLO"),
      $node->childNodes(),
      XML::LibXML::Text->new("HELLO"),
   ) {
      $parent->insertBefore($child_node, $node);
   }

   $node->unbindNode();
}

筆記:

  • 處理具有任意數量的text和element子元素的testTag元素。
  • 處理不是Error元素的直接子元素的testTag元素。 甚至處理嵌套的testTag元素。 (如果只想處理Error元素的直接子元素,請使用/Errors/Error/testTag而不是/Errors/Error//testTag 。)

這應該工作

$error='<Errors>
<Error>
    this node works fine
</Error>
<Error>
    some text <testTag>with a node</testTag> in between
</Error>
</Errors>';

$error=~ s/<testTag>/HELLO/gs;
$error=~ s/<\/testTag>/HELLO/gs;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM