Perl с XML::LibXML Dom (глобальный поиск и замена XML)

Я новичок в DOM и XML-LibXML.

Это мой пример файла mathml (XML). Имя моего XML-файла — in.xml, и мне нужно, чтобы окончательное выходное имя XML-файла было out.xml. Я хочу найти <mi>bcde</mi> и мне нужно глобально изменить <mtext>pqsd</mtext> и сохранить в out.xml. Как этого добиться.

<math xmlns="http://www.w3.org/1998/Math/MathML">
    <mfrac>
         <mi>a</mi>
         <mrow>
            <mi>bcde</mi>
         </mrow>
    </mfrac>
    <msqrt>
        <mi>s</mi>
        <mi>e</mi>
        <mi>f</mi>
    </msqrt>
</math> 


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

my $mediaIdFrom = "MEDIAID_TEST";
my $VodItemIdFrom = "VODITEM_ID_TEST";
my $mediaId="";
my $vodItemId="";

my $filename = 'sample1.xml';
my $out_filename = "sample2.xml";

my $dom = XML::LibXML -> load_xml(location => $filename);

foreach $mediaId ($dom->findnodes('/ScheduleProvider/Episode/Media/@id')) {
    $mediaId->setValue("xx " . $mediaIdFrom . " yy");
}

foreach $vodItemId ($dom->findnodes('/ScheduleProvider/VoidItem/@id')) {
    $vodItemId->setValue($VodItemIdFrom);
}
#### for storing the output separate XML file
$dom->toFile($out_filename);`

person user49758    schedule 14.12.2018    source источник
comment
Дубликат 23733255?   -  person Stefan Becker    schedule 14.12.2018
comment
@Stefan: я обновил свой вопрос, и я хотел бы изменить тег элемента ‹mi›, который должен измениться на ‹mtext› глобально. Как этого добиться?   -  person user49758    schedule 14.12.2018


Ответы (1)


У вашего XML есть пространство имен, но у ваших запросов XPath его нет, см. примечание под findnodes в man XML::LibXML::Node. Этот код должен работать:

#!/usr/bin/perl
use strict;
use warnings;

use XML::LibXML;
use XML::LibXML::XPathContext;

my $dom = XML::LibXML->load_xml(string => <<'END_OF_XML');
<math xmlns="http://www.w3.org/1998/Math/MathML">
    <mfrac>
         <mi>a</mi>
         <mrow>
            <mi>bcde</mi>
         </mrow>
    </mfrac>
    <msqrt>
        <mi>s</mi>
        <mi>e</mi>
        <mi>f</mi>
    </msqrt>
</math>
END_OF_XML

my $xpc = XML::LibXML::XPathContext->new();
$xpc->registerNs('math', 'http://www.w3.org/1998/Math/MathML');

foreach my $node ($xpc->findnodes('/math:math/math:mfrac/math:mrow/math:mi', $dom)) {
    my $newNode = XML::LibXML::Element->new('mtext');
    $newNode->appendText('pqsd');

    $node->replaceNode($newNode);
}

print $dom->toString();

Вывод:

$ perl dummy.pl
<?xml version="1.0"?>
<math xmlns="http://www.w3.org/1998/Math/MathML">
    <mfrac>
         <mi>a</mi>
         <mrow>
             <mtext>pqsd</mtext>
         </mrow>
    </mfrac>
    <msqrt>
        <mi>s</mi>
        <mi>e</mi>
        <mi>f</mi>
    </msqrt>
</math>

EDIT Может быть, я неправильно понял ваш вопрос, и вы хотите заменить все вхождения <mi>bcde</mi>? Затем foreach изменится на

foreach my $node ($xpc->findnodes('//math:mi[text()="bcde"]', $dom)) {

ИЗМЕНИТЬ 2, чтобы найти несколько <mi>xyz</mi> и заменить их, вы можете использовать параметры командной строки text=replacement, т.е.

foreach my $argv (@ARGV) {
    next
        unless my($find, $replace) = ($argv =~ /^([^=]+)=(.*)$/);

    foreach my $node ($xpc->findnodes(qq{//math:mi[text()="${find}"]}, $dom)) {
        my $newNode = XML::LibXML::Element->new('mtext');
        $newNode->appendText($replace);

        $node->replaceNode($newNode);
    }
}

и ваш пример замены будет

$ perl dummy.pl bcde=pqsd

ИЗМЕНИТЬ 3 заменить все <mi>xxx</mi>, где xxx содержит более одного символа, на mtext:

foreach my $node ($xpc->findnodes('//math:mi', $dom)) {
    my $text = $node->textContent();

    # strip surrounding white space from text
    $text =~ s/^\s+//;
    $text =~ s/\s+$//;

    # if text has more than one character then replace "mi" with "mtext"
    if (length($text) > 1) {
        my $newNode = XML::LibXML::Element->new('mtext');
        $newNode->appendText($text);

        $node->replaceNode($newNode);
    }
}
person Stefan Becker    schedule 14.12.2018
comment
Я обнаружил, что xpath решение $newNode->appendText($node->firstChild->data);. Но мне нужно изменить <mi> на <mtext> более одного буквенного символа. Например <mi>ab</mi> необходимо заменить <mtext>ab</mtext>. а <mi>d</mi> менять не надо. Как это исправить? - person user49758; 17.12.2018
comment
Спасибо за ваш ответ и мой текст замены на любой текст в соответствии с mathml IN-TEXT. Это означает ‹mi›abc‹/mi›, ‹mi›dsa‹/mi›, ‹mi›и т. д.‹/mi›. Не могли бы вы посоветовать, и как избежать одной буквы, не нужно менять <mtext>. Например, <mi>a</mi> нужно поддерживать только <mi>a</mi>. - person user49758; 20.12.2018
comment
Это работает нормально, и спасибо, что потратили на это свое драгоценное время. - person user49758; 21.12.2018