# Reliably make a clean copy of an XML node

**URL:** <https://discourse.julialang.org/t/reliably-make-a-clean-copy-of-an-xml-node/129495>\
**Category:** General Usage\
**Tags:** xml\
**Created:** [May 31, 2025, 7:57am UTC](https://discourse.julialang.org/t/reliably-make-a-clean-copy-of-an-xml-node/129495 "2025-05-31T07:57:07Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![TimG](https://avatars.discourse-cdn.com/v4/letter/t/82dd89/32.png) [@TimG](https://discourse.julialang.org/u/TimG)\
**Post date:** [May 31, 2025, 7:57am UTC](https://discourse.julialang.org/t/reliably-make-a-clean-copy-of-an-xml-node/129495/1 "2025-05-31T07:57:08Z")

</div>

I am using XML.jl.

I have a basic XML node that I use as a base from which I wish to construct many slightly different instances. On each occasion, I want to copy the basic node and build from there. The Readme page for XML.jl says:

> `Node` is an immutable type. However, you can easily create a copy with one or more field values changed by using the `Node(::Node; kw...)` constructor where `kw` are the fields you want to change.

I don’t want to change any fields, So I just tried `new_node=XML.Node(old_node)`.

I then build up the rest of the `new_node` as I want it.

The consequence of this is that the changes I make to `new_node` also propagate back to `old_node` - specfically to its child nodes.

I have experimented with many different ways of specifying the `kw...` options in `XML.Node` but to no avail. For example, I tried variations on `new_node=XML.Node(old_node, children=[x for x in XML.children(old_node)])` to try to copy the child nodes, too.

Two ways I have found to make this work are to try `deepcopy(old_node)` and `XML.parse(Node, XML.write(old_node))[1]`.

The first of these seems not to be recommended (“A brief conversation of [`deepcopy` vs `copy`](https://discourse.julialang.org/t/when-should-i-define-my-own-deepcopy-and-copy-functions-for-types-if-ever/123447) in Julia and why `deepcopy` should be avoided unless you know you have a good reason to use it (serialization-like)” ([here](https://julialang.org/blog/2024/12/this-month-in-julia-world/))).

The second is very inefficient.

> **MWE below the fold**
>
> ```julia
> using XML
> copynode(o)=Node(o)
> deepcopynode(o) = deepcopy(o)
> parsenode(o) = XML.parse(Node, XML.write(o))[1]
> function createRule()
> rule = XML.Element("x14:cfRule", type="iconSet", priority="1", id="XXXX-xxxx-XXXX")
> icon = XML.Element("x14:iconSet", iconSet="3Arrows", custom="1")
> cfvo = XML.Element("x14:cfvo", type="percent")
> push!(cfvo, XML.Element("xm:f", XML.Text("0")))
> push!(icon, cfvo)
> push!(rule, icon)
> return rule
> end
> function createCfx(rule, f)
> cfx=f(rule)
> cfvo = XML.Element("x14:cfvo", type="percent")
> push!(cfvo, XML.Element("xm:f", XML.Text("dummy")))
> push!(cfx[1], f(cfvo))
> push!(cfx[1], f(cfvo))
> push!(cfx[1], f(cfvo))
> cfx[1]["iconSet"] = "4Arrows"
> return cfx
> end
> 
> println("\nusing XML.Node()")
> rule = createRule()
> println("\n `rule` before:\n",XML.write(rule))
> cfx=createCfx(rule, copynode)
> println("\n `cfx`:\n",XML.write(cfx))
> println("\n `rule` after:\n",XML.write(rule))
> 
> println("\nusing deepcopy()")
> rule = createRule()
> println("\n `rule` before:\n",XML.write(rule))
> cfx=createCfx(rule, deepcopynode)
> println("\n `cfx`:\n",XML.write(cfx))
> println("\n `rule` after:\n",XML.write(rule))
> 
> println("\nusing XML.parse()")
> rule = createRule()
> println("\n `rule` before:\n",XML.write(rule))
> cfx=createCfx(rule, parsenode)
> println("\n `cfx`:\n",XML.write(cfx))
> println("\n `rule` after:\n",XML.write(rule))
> 
> ```
> 
> ```julia
> using XML.Node()
> 
> `rule` before:
> <x14:cfRule type="iconSet" priority="1" id="XXXX-xxxx-XXXX">
> <x14:iconSet iconSet="3Arrows" custom="1">
> <x14:cfvo type="percent">
> <xm:f>0</xm:f>
> </x14:cfvo>
> </x14:iconSet>
> </x14:cfRule>
> 
> `cfx`:
> <x14:cfRule type="iconSet" priority="1" id="XXXX-xxxx-XXXX">
> <x14:iconSet iconSet="4Arrows" custom="1">
> <x14:cfvo type="percent">
> <xm:f>0</xm:f>
> </x14:cfvo>
> <x14:cfvo type="percent">
> <xm:f>dummy</xm:f>
> </x14:cfvo>
> <x14:cfvo type="percent">
> <xm:f>dummy</xm:f>
> </x14:cfvo>
> <x14:cfvo type="percent">
> <xm:f>dummy</xm:f>
> </x14:cfvo>
> </x14:iconSet>
> </x14:cfRule>
> 
> `rule` after:
> <x14:cfRule type="iconSet" priority="1" id="XXXX-xxxx-XXXX">
> <x14:iconSet iconSet="4Arrows" custom="1">
> <x14:cfvo type="percent">
> <xm:f>0</xm:f>
> </x14:cfvo>
> <x14:cfvo type="percent">
> <xm:f>dummy</xm:f>
> </x14:cfvo>
> <x14:cfvo type="percent">
> <xm:f>dummy</xm:f>
> </x14:cfvo>
> <x14:cfvo type="percent">
> <xm:f>dummy</xm:f>
> </x14:cfvo>
> </x14:iconSet>
> </x14:cfRule>
> 
> using deepcopy()
> 
> `rule` before:
> <x14:cfRule type="iconSet" priority="1" id="XXXX-xxxx-XXXX">
> <x14:iconSet iconSet="3Arrows" custom="1">
> <x14:cfvo type="percent">
> <xm:f>0</xm:f>
> </x14:cfvo>
> </x14:iconSet>
> </x14:cfRule>
> 
> `cfx`:
> <x14:cfRule type="iconSet" priority="1" id="XXXX-xxxx-XXXX">
> <x14:iconSet iconSet="4Arrows" custom="1">
> <x14:cfvo type="percent">
> <xm:f>0</xm:f>
> </x14:cfvo>
> <x14:cfvo type="percent">
> <xm:f>dummy</xm:f>
> </x14:cfvo>
> <x14:cfvo type="percent">
> <xm:f>dummy</xm:f>
> </x14:cfvo>
> <x14:cfvo type="percent">
> <xm:f>dummy</xm:f>
> </x14:cfvo>
> </x14:iconSet>
> </x14:cfRule>
> 
> `rule` after:
> <x14:cfRule type="iconSet" priority="1" id="XXXX-xxxx-XXXX">
> <x14:iconSet iconSet="3Arrows" custom="1">
> <x14:cfvo type="percent">
> <xm:f>0</xm:f>
> </x14:cfvo>
> </x14:iconSet>
> </x14:cfRule>
> 
> using XML.parse()
> 
> `rule` before:
> <x14:cfRule type="iconSet" priority="1" id="XXXX-xxxx-XXXX">
> <x14:iconSet iconSet="3Arrows" custom="1">
> <x14:cfvo type="percent">
> <xm:f>0</xm:f>
> </x14:cfvo>
> </x14:iconSet>
> </x14:cfRule>
> 
> `cfx`:
> <x14:cfRule type="iconSet" priority="1" id="XXXX-xxxx-XXXX">
> <x14:iconSet iconSet="4Arrows" custom="1">
> <x14:cfvo type="percent">
> <xm:f>0</xm:f>
> </x14:cfvo>
> <x14:cfvo type="percent">
> <xm:f>dummy</xm:f>
> </x14:cfvo>
> <x14:cfvo type="percent">
> <xm:f>dummy</xm:f>
> </x14:cfvo>
> <x14:cfvo type="percent">
> <xm:f>dummy</xm:f>
> </x14:cfvo>
> </x14:iconSet>
> </x14:cfRule>
> 
> `rule` after:
> <x14:cfRule type="iconSet" priority="1" id="XXXX-xxxx-XXXX">
> <x14:iconSet iconSet="3Arrows" custom="1">
> <x14:cfvo type="percent">
> <xm:f>0</xm:f>
> </x14:cfvo>
> </x14:iconSet>
> </x14:cfRule>
> 
> ```

Does anyone have any suggestions?

---

<div class="post-metadata">

**Author:** ![TimG](https://avatars.discourse-cdn.com/v4/letter/t/82dd89/32.png) [@TimG](https://discourse.julialang.org/u/TimG)\
**Post date:** [May 31, 2025, 9:19am UTC](https://discourse.julialang.org/t/reliably-make-a-clean-copy-of-an-xml-node/129495/3 "2025-05-31T09:19:11Z")

</div>

Thank you for the suggestion @Pamela329Lac.

I did try `copynode(o)=XML.Node(o, children=[XML.Node(x) for x in o.children])` but I this gives

```julia
 `rule` after:
<x14:cfRule type="iconSet" priority="1" id="XXXX-xxxx-XXXX">
  <x14:iconSet iconSet="4Arrows" custom="1">
    <x14:cfvo type="percent">
      <xm:f>0</xm:f>
    </x14:cfvo>
    <x14:cfvo children="Node[Node Element <xm:f> (1 child)]" type="percent">
      <xm:f>dummy</xm:f>
    </x14:cfvo>
    <x14:cfvo children="Node[Node Element <xm:f> (1 child)]" type="percent">
      <xm:f>dummy</xm:f>
    </x14:cfvo>
    <x14:cfvo children="Node[Node Element <xm:f> (1 child)]" type="percent">
      <xm:f>dummy</xm:f>
    </x14:cfvo>
  </x14:iconSet>
</x14:cfRule>

```

This suggests that `Node` thinks that `children=...` is a keyword argument specifying a `children` attribute - which it isn’t.

`copynode(o)=XML.Node(o, [XML.Node(x) for x in o.children])` does no better:

```julia
 `rule` after:
<x14:cfRule type="iconSet" priority="1" id="XXXX-xxxx-XXXX">
  <x14:iconSet iconSet="3Arrows" custom="1">
    <x14:cfvo type="percent">
      <xm:f>0</xm:f>
    </x14:cfvo>
    <x14:cfvo type="percent">
      <xm:f>dummy</xm:f>
      Node[Node Element <xm:f> (1 child)]
    </x14:cfvo>
    <x14:cfvo type="percent">
      <xm:f>dummy</xm:f>
      Node[Node Element <xm:f> (1 child)]
    </x14:cfvo>
    <x14:cfvo type="percent">
      <xm:f>dummy</xm:f>
      Node[Node Element <xm:f> (1 child)]
    </x14:cfvo>
  </x14:iconSet>
</x14:cfRule>

```

---

<div class="post-metadata">

**Author:** ![nilshg](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nilshg/32/2283_2.png) [@nilshg](https://discourse.julialang.org/u/nilshg)\
**Post date:** [May 31, 2025, 10:46am UTC](https://discourse.julialang.org/t/reliably-make-a-clean-copy-of-an-xml-node/129495/4 "2025-05-31T10:46:31Z")

</div>

Tim, I have no answer to your question but because this has happened to you for the second time in the last couple of days I thought I’d mention that these plausible-sounding replies from `Firstname[Number]Lastname` users are GenAI spam, so there’s little point engaging with them.

(If you do find them helpful of course you might want to try simply talking to one of the better models directly before posting here!)

---

<div class="post-metadata">

**Author:** ![TimG](https://avatars.discourse-cdn.com/v4/letter/t/82dd89/32.png) [@TimG](https://discourse.julialang.org/u/TimG)\
**Post date:** [May 31, 2025, 11:24am UTC](https://discourse.julialang.org/t/reliably-make-a-clean-copy-of-an-xml-node/129495/5 "2025-05-31T11:24:03Z")

</div>

Understood! The like was to try to be welcoming 🙂

---

<div class="post-metadata">

**Author:** ![TimG](https://avatars.discourse-cdn.com/v4/letter/t/82dd89/32.png) [@TimG](https://discourse.julialang.org/u/TimG)\
**Post date:** [May 31, 2025, 12:17pm UTC](https://discourse.julialang.org/t/reliably-make-a-clean-copy-of-an-xml-node/129495/6 "2025-05-31T12:17:51Z")

</div>

On a random walk through options, I seem to have hit upon

```julia
copynode(o)=XML.Node(o.nodetype, o.tag, o.attributes, o.value, isnothing(o.children) ? nothing : [copynode(x) for x in o.children])

```

Which recursively copies a node and all its children.

As far as I can tell, this gives the same result as `deepcopy` but is quite a bit faster. Here, I’m just using an XML file representing an Excel worksheet (nested a few levels deep).

```julia
julia> using XML, BenchmarkTools, XLSX

julia> copynode(o)=XML.Node(o.nodetype, o.tag, o.attributes, o.value, isnothing(o.children) ? nothing : [copynode(x) for x in o.children])
copynode (generic function with 1 method)

julia> deepcopynode(o) = deepcopy(o)
deepcopynode (generic function with 1 method)

julia> function createCfx(rule, f)                                                                                                                                                                     
           cfx=f(rule)                                                                                                                                                                                 
           cfvo = XML.Element("x14:cfvo", type="percent")                                                                                                                                              
           push!(cfvo, XML.Element("xm:f", XML.Text("dummy")))                                                                                                                                     
           push!(cfx[end], f(cfvo))                                                                                                                                                            
           push!(cfx[end], f(cfvo))                                                                                                                                                        
           push!(cfx[end], f(cfvo))                                                                                                                                                                    
           cfx[1]["iconSet"] = "4Arrows"                                                                                                                                                               
           return cfx                                                                                                                                                                              
       end
createCfx (generic function with 1 method)

julia> f=XLSX.opentemplate(raw"C:\Users\tim\OneDrive\Documents\Julia\XLSX\iconKey.xlsx")
XLSXFile("C:\Users\tim\OneDrive\Documents\Julia\XLSX\iconKey.xlsx") containing 1 Worksheet
            sheetname size range
-------------------------------------------------
               Sheet1 4x13 A1:M4

julia> rule = XLSX.get_worksheet_xml_document(f[1])
Node Document (2 children)

julia> @benchmark cfx=createCfx($rule, $copynode)
BenchmarkTools.Trial: 10000 samples with 1 evaluation per sample.
 Range (min … max): 86.100 μs … 7.979 ms ┊ GC (min … max): 0.00% … 97.99%
 Time (median): 93.900 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 109.910 μs ± 153.160 μs ┊ GC (mean ± σ): 6.75% ± 5.06%

     ▁▇█▃
  ▁▂▄████▇▄▂▂▂▁▁▁▁▂▂▂▂▃▃▂▂▂▂▂▂▁▂▂▃▃▃▃▂▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▂
  86.1 μs Histogram: frequency by time 159 μs <

 Memory estimate: 205.73 KiB, allocs estimate: 4520.

julia> rule2 = XLSX.get_worksheet_xml_document(f[1])
Node Document (2 children)

julia> cfx2=createCfx(rule2, deepcopynode)
Node Document (2 children)

julia> rule2 = XLSX.get_worksheet_xml_document(f[1])
Node Document (2 children)

julia> @benchmark cfx2=createCfx($rule2, $deepcopynode)
BenchmarkTools.Trial: 9872 samples with 1 evaluation per sample.
 Range (min … max): 438.400 μs … 4.798 ms ┊ GC (min … max): 0.00% … 87.00%
 Time (median): 456.900 μs ┊ GC (median): 0.00%
 Time (mean ± σ): 505.863 μs ± 259.013 μs ┊ GC (mean ± σ): 4.52% ± 7.66%

   ▅▇█▇▆▆▅▄▂▁▁▁ ▁▂▂▂▂▁ ▁▁▁▁▁▁▁▂▁▁ ▂
  ▆██████████████▇▆▆▅▆▇▇▆▇██████████▇▆▇▇▇▇████████████▇▇▆▅▅▄▄▄▄ █
  438 μs Histogram: log(frequency) by time 698 μs <

 Memory estimate: 537.81 KiB, allocs estimate: 12190.

julia> cfx=createCfx(rule, copynode)
Node Document (2 children)

julia> rule = XLSX.get_worksheet_xml_document(f[1])
Node Document (2 children)

julia> cfx=createCfx(rule, copynode)
Node Document (2 children)

julia> rule2 = XLSX.get_worksheet_xml_document(f[1])
Node Document (2 children)

julia> cfx2=createCfx(rule2, copynode)
Node Document (2 children)

julia> cfx==cfx2
true

```
