XML
XML copied to clipboard
Iteratively using replace() on nodes gives unexpected results
For example:
my @elements = $mydocument.root.nodes; for @elements -> $element { my $new-element = @new-elements.pop; $mydocument.root.replace($element, $new-element); }
seems to result in only the first and last $new-element ending up in the document. Sorry, I have not golfed this down further. I see a similar result with the replaceChild() syntax.
Also weird, when I look at the code for the other bug, I'll look into this one too.
Confirm dropping elements (...in 2024), not sure if my syntax is valid. Still, replacing nodes with self drops 1/2 the elements:
~$ raku -MXML -e 'my $mydocument=open-xml($*ARGFILES.Str); \
my @elements = $mydocument.root.nodes; \
my @new-elements = $mydocument.root.nodes; \
@elements.say; say "\n____\n"; @new-elements.say; say "\n____\n"; \
for @elements -> $element { \
my $new-element = @new-elements.pop; \
$mydocument.root.replace($element, $new-element); \
}; .say for $mydocument;' ~/exemel_text.xml
[
<file>text1</file>
<file>text2</file>
<file>text3</file>
<file>text4</file>
]
____
[
<file>text1</file>
<file>text2</file>
<file>text3</file>
<file>text4</file>
]
____
<?xml version="1.0"?><root>
<file>text1</file>
<file>text2</file>
</root>
~$
Try reversing the @new-elements replacement, now all 4 elements are replaced with blanks! (Also...weirdness with printing reversed array):
~$ raku -MXML -e 'my $mydocument=open-xml($*ARGFILES.Str); \
my @elements = $mydocument.root.nodes; \
my @new-elements = $mydocument.root.nodes.reverse; \
@elements.say; say "\n____\n"; @new-elements.say; say "\n____\n"; \
for @elements -> $element { \
my $new-element = @new-elements.pop; \
$mydocument.root.replace($element, $new-element); \
}; .say for $mydocument;' ~/exemel_text.xml
[
<file>text1</file>
<file>text2</file>
<file>text3</file>
<file>text4</file>
]
____
[
<file>text4</file>
<file>text3</file>
<file>text2</file>
<file>text1</file>
]
____
<?xml version="1.0"?><root>
</root>
~$
Different syntax but still errors: "No such method 'replace' for invocant of type 'Array'"
~$ raku -MXML -e 'my $mydocument=open-xml($*ARGFILES.Str); \
my @elements = $mydocument.root.nodes; \
my @new-elements = $mydocument.root.nodes.reverse; \
for @(@elements,@new-elements) -> $old,$new { \
$mydocument.root.nodes.replace($old, $new); \
}; .say for $mydocument;' ~/exemel_text.xml
No such method 'replace' for invocant of type 'Array'
in block <unit> at -e line 1
Maybe related to #69 ?
@supernovus @bronco-creek @jonathanstowe @lizmat @dwarring @vrurg
Here's a working one-liner for replacing text within user identified nodes ( restricted to top-level with :RECURSE(0) and restricted to :TAG{"title"}):
~$ raku -MXML -e 'my $xml = open-xml( $*ARGFILES.Str );
for $xml.elements( :RECURSE(0), :TAG{"title"} ) -> $E {
my $old = $E.contents[0];
my $new = XML::Text.new( text => $old.text.subst(/^old-text$/, "new-text" ) );
$E.replace( $old, $new );
}; .say for $xml;' file.xml
See: https://unix.stackexchange.com/a/771574/227738
Maybe add a warning to the XML docs page about trying to replace Elements without converting to XML::Element first?
I added a bunch of debug output so you can see a little bit more clearly what's going on:
full execution with some unnecessary extra output
raku -MXML -e 'sub debugout($n) { $n.^name ~ " " ~ $n.gist.raku }; my $mydocument=open-xml($*ARGFILES.Str); \
my @elements = $mydocument.root.nodes.eager; \
my @new-elements = $mydocument.root.nodes.eager; \
.&debugout.say for @elements; say "\n____\n"; .&debugout.say for @new-elements; say "\n____\n"; \
for @elements -> $element { \
my $new-element = @new-elements.pop; \
say "replacing", debugout($element), " -> ", debugout($new-element); say ("replacement result: " ~ debugout $mydocument.root.replace($element, $new-element)).indent(4); say " entire document now:"; .&debugout.indent(8).say for $mydocument.nodes; \
}; say "\n____\n"; .&debugout.say for $mydocument.nodes;' exemel_text.xml
XML::Text "\n "
XML::Element "<file>text1</file>"
XML::Text "\n "
XML::Element "<file>text2</file>"
XML::Text "\n "
XML::Element "<file>text3</file>"
XML::Text "\n "
XML::Element "<file>text4</file>"
XML::Text "\n"
____
XML::Text "\n "
XML::Element "<file>text1</file>"
XML::Text "\n "
XML::Element "<file>text2</file>"
XML::Text "\n "
XML::Element "<file>text3</file>"
XML::Text "\n "
XML::Element "<file>text4</file>"
XML::Text "\n"
____
replacingXML::Text "\n " -> XML::Text "\n"
replacement result: Array "[\n ]"
entire document now:
XML::Text "\n"
XML::Element "<file>text1</file>"
XML::Text "\n "
XML::Element "<file>text2</file>"
XML::Text "\n "
XML::Element "<file>text3</file>"
XML::Text "\n "
XML::Element "<file>text4</file>"
replacingXML::Element "<file>text1</file>" -> XML::Element "<file>text4</file>"
replacement result: Array "[<file>text1</file>]"
entire document now:
XML::Text "\n"
XML::Element "<file>text4</file>"
XML::Text "\n "
XML::Element "<file>text2</file>"
XML::Text "\n "
XML::Element "<file>text3</file>"
XML::Text "\n "
replacingXML::Text "\n " -> XML::Text "\n "
replacement result: Array "[\n ]"
entire document now:
XML::Text "\n"
XML::Element "<file>text4</file>"
XML::Text "\n "
XML::Element "<file>text2</file>"
XML::Text "\n "
XML::Element "<file>text3</file>"
replacingXML::Element "<file>text2</file>" -> XML::Element "<file>text3</file>"
replacement result: Array "[<file>text2</file>]"
entire document now:
XML::Text "\n"
XML::Element "<file>text4</file>"
XML::Text "\n "
XML::Element "<file>text3</file>"
XML::Text "\n "
replacingXML::Text "\n " -> XML::Text "\n "
replacement result: Array "[]"
entire document now:
XML::Text "\n"
XML::Element "<file>text4</file>"
XML::Text "\n "
XML::Element "<file>text3</file>"
XML::Text "\n "
replacingXML::Element "<file>text3</file>" -> XML::Element "<file>text2</file>"
replacement result: Array "[<file>text3</file>]"
entire document now:
XML::Text "\n"
XML::Element "<file>text4</file>"
XML::Text "\n "
XML::Element "<file>text2</file>"
XML::Text "\n "
replacingXML::Text "\n " -> XML::Text "\n "
replacement result: Array "[\n ]"
entire document now:
XML::Text "\n"
XML::Element "<file>text4</file>"
XML::Text "\n "
XML::Element "<file>text2</file>"
XML::Text "\n "
replacingXML::Element "<file>text4</file>" -> XML::Element "<file>text1</file>"
replacement result: Array "[<file>text4</file>]"
entire document now:
XML::Text "\n"
XML::Element "<file>text1</file>"
XML::Text "\n "
XML::Element "<file>text2</file>"
XML::Text "\n "
replacingXML::Text "\n" -> XML::Text "\n "
replacement result: Array "[\n]"
entire document now:
XML::Text "\n "
XML::Element "<file>text1</file>"
XML::Text "\n "
XML::Element "<file>text2</file>"
XML::Text "\n "
____
XML::Text "\n "
XML::Element "<file>text1</file>"
XML::Text "\n "
XML::Element "<file>text2</file>"
XML::Text "\n "
here's a bit shorter version that you get when you remove spaces and newlines from the exemel_text.xml since the text nodes are a bit distracting:
raku -MXML -e 'sub debugout($n) { $n.^name ~ " " ~ $n.gist.raku }; my $mydocument=open-xml($*ARGFILES.Str); \
my @elements = $mydocument.root.nodes.eager; \
my @new-elements = $mydocument.root.nodes.eager; \
.&debugout.say for @elements; say "\n____\n"; .&debugout.say for @new-elements; say "\n____\n"; \
for @elements -> $element { \
my $new-element = @new-elements.pop; \
say "replacing ", debugout($element), " -> ", debugout($new-element); say ("replacement result: " ~ debugout $mydocument.root.replace($element, $new-element)).indent(4); say " entire document now:"; .&debugout.indent(8).say for $mydocument.nodes; \
}; say "\n____\n"; .&debugout.say for $mydocument.nodes;' exemel_text_nospace.xml
XML::Element "<file>text1</file>"
XML::Element "<file>text2</file>"
XML::Element "<file>text3</file>"
XML::Element "<file>text4</file>"
____
XML::Element "<file>text1</file>"
XML::Element "<file>text2</file>"
XML::Element "<file>text3</file>"
XML::Element "<file>text4</file>"
____
replacing XML::Element "<file>text1</file>" -> XML::Element "<file>text4</file>"
replacement result: Array "[<file>text1</file>]"
entire document now:
XML::Element "<file>text4</file>"
XML::Element "<file>text2</file>"
XML::Element "<file>text3</file>"
replacing XML::Element "<file>text2</file>" -> XML::Element "<file>text3</file>"
replacement result: Array "[<file>text2</file>]"
entire document now:
XML::Element "<file>text4</file>"
XML::Element "<file>text3</file>"
replacing XML::Element "<file>text3</file>" -> XML::Element "<file>text2</file>"
replacement result: Array "[<file>text3</file>]"
entire document now:
XML::Element "<file>text4</file>"
XML::Element "<file>text2</file>"
replacing XML::Element "<file>text4</file>" -> XML::Element "<file>text1</file>"
replacement result: Array "[<file>text4</file>]"
entire document now:
XML::Element "<file>text1</file>"
XML::Element "<file>text2</file>"
____
XML::Element "<file>text1</file>"
XML::Element "<file>text2</file>"
As you can see, we are:
starting state: 1 2 3 4
replacing 1 with 4, result: 4 2 3
replacing 2 with 3, result: 4 3
replacing 3 with 2, result: 4 2
replacing 4 with 1, result: 1 2
The code does pretty much exactly what you would expect, but you need to be aware that one node cannot be in more than one place at a time, which necessitates that the first two steps reduce the number of nodes, leaving us with 2 at the end.
@timo thanks for all your hard work!
So "using replace() on nodes" essentially creates a pointer from an existing node "X" to existing node "Y", effectively eliminating node "X"?
Thx.
I would not call it a pointer. replace goes through the children on the node you call .replace on, finds the spot where the node you pass as first argument sits, puts the node you pass as the second argument in that same place, also reparenting it so that the tree stays correct (nodes under a node are supposed to have their $.parent attribute set to the node that directly contains it)
now that i look more closely at this:
https://github.com/raku-community-modules/XML/blob/83e00879f2e8aa1018faa0a22a50be17138c61f4/lib/XML/Element.rakumod#L71-L76
which calls this:
https://github.com/raku-community-modules/XML/blob/83e00879f2e8aa1018faa0a22a50be17138c61f4/lib/XML/Node.rakumod#L36-L40
I don't see any mention of the original node getting .remove called on it. This would result in a Node that things $foo is its parent, but $foo doesn't have it inside its @.nodes.
I haven't looked closely enough at the code to know whether this is a problem or not.