diff --git a/Project.toml b/Project.toml index 08285a0..cfe864e 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "XML" uuid = "72c71f33-b9b6-44de-8c94-c961784809e2" authors = ["Josh Day and contributors"] -version = "0.3.2" +version = "0.3.3" [deps] Mmap = "a63ad114-7e13-5084-954f-fe012c677804" diff --git a/README.md b/README.md index 5f565c1..7c7e802 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,6 @@ doc[end][2] # Second child of root ## `Node`: Probably What You're Looking For - `read`-ing a `Node` loads the entire XML DOM in memory. -- **This is what you would use to build an XML document programmatically.** - See the table above for convenience constructors. - `Node`s have some additional methods that aid in construction/mutation: @@ -114,6 +113,24 @@ simplevalue(node2) # "changed" ``` +### Writing `Element` `Node`s with `XML.h` + +Similar to [Cobweb.jl](https://github.com/JuliaComputing/Cobweb.jl#-creating-nodes-with-cobwebh), `XML.h` enables you to write elements with a simpler syntax: + +```julia +using XML: h + +julia> node = h.parent( + h.child("content", id="my id") + ) +# Node Element (1 child) + +julia> XML.write(node) +# +# content +# +``` +
## `XML.LazyNode`: For Fast Iteration through an XML File @@ -173,45 +190,47 @@ XML.write(node) # String ``` julia> versioninfo() -Julia Version 1.8.5 -Commit 17cfb8e65ea (2023-01-08 06:45 UTC) +Julia Version 1.9.4 +Commit 8e5136fa297 (2023-11-14 08:46 UTC) +Build Info: + Official https://julialang.org/ release Platform Info: - OS: macOS (arm64-apple-darwin21.5.0) + OS: macOS (arm64-apple-darwin22.4.0) CPU: 10 × Apple M1 Pro WORD_SIZE: 64 LIBM: libopenlibm - LLVM: libLLVM-13.0.1 (ORCJIT, apple-m1) - Threads: 1 on 8 virtual cores + LLVM: libLLVM-14.0.6 (ORCJIT, apple-m1) + Threads: 8 on 8 virtual cores ``` ### Reading an XML File ``` - XML.LazyNode 0.012084 - XML.Node ■■■■■■■■■■■■■■■■■■■■■■■■■■■ 888.367 - EzXML.readxml ■■■■■■ 200.009 - XMLDict.xml_dict ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 1350.63 + XML.LazyNode 0.009583 + XML.Node ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 1071.32 + EzXML.readxml ■■■■■■■■■ 284.346 + XMLDict.xml_dict ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 1231.47 ``` ### Writing an XML File ``` - Write: XML ■■■■■■■■■■■■■■■■■■■■■■ 244.261 - Write: EzXML ■■■■■■■■■■ 106.953 + Write: XML ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 289.638 + Write: EzXML ■■■■■■■■■■■■■ 93.4631 ``` ### Lazily Iterating over Each Node ``` - LazyNode ■■■■■■■■■■■■■■■■ 55.1 - EzXML.StreamReader ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 142.515 + LazyNode ■■■■■■■■■ 51.752 + EzXML.StreamReader ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 226.271 ``` ### Collecting All Names/Tags in an XML File ``` - XML.LazyNode ■■■■■■■■■■■■■■■■■■■■■■■■■■ 152.298 - EzXML.StreamReader ■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 165.21 - EzXML.readxml ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 239.197 + XML.LazyNode ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 210.482 + EzXML.StreamReader ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 276.238 + EzXML.readxml ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■ 263.269 ```
diff --git a/benchmarks/Project.toml b/benchmarks/Project.toml index 3556d60..ed90996 100644 --- a/benchmarks/Project.toml +++ b/benchmarks/Project.toml @@ -1,5 +1,6 @@ [deps] BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" EzXML = "8f5d6c58-4d21-5cfd-889c-e3ad7ee6a615" OrderedCollections = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" UnicodePlots = "b8865327-cd53-5732-bb35-84acbb429228" diff --git a/src/XML.jl b/src/XML.jl index 4813b11..88d6d43 100644 --- a/src/XML.jl +++ b/src/XML.jl @@ -146,7 +146,16 @@ struct Node <: AbstractXMLNode end end -Node(o::Node; kw...) = isempty(kw) ? o : Node((get(kw, x, getfield(o, x)) for x in fieldnames(Node))...) +function Node(o::Node, x...; kw...) + attrs = !isnothing(kw) ? + merge( + OrderedDict(string(k) => string(v) for (k,v) in pairs(kw)), + isnothing(o.attributes) ? OrderedDict{String, String}() : o.attributes + ) : + o.attributes + children = isempty(x) ? o.children : vcat(isnothing(o.children) ? [] : o.children, collect(x)) + Node(o.nodetype, o.tag, attrs, o.value, children) +end function Node(node::LazyNode) nodetype = node.nodetype @@ -162,6 +171,9 @@ Node(data::Raw) = Node(LazyNode(data)) # Anything that's not Vector{UInt8} or a (Lazy)Node is converted to a Text Node Node(x) = Node(Text, nothing, nothing, string(x), nothing) +h(tag::Union{Symbol, String}, children...; kw...) = Node(Element, tag, kw, nothing, children) +Base.getproperty(::typeof(h), tag::Symbol) = h(tag) +(o::Node)(children...; kw...) = Node(o, Node.(children)...; kw...) # NOT in-place for Text Nodes function escape!(o::Node, warn::Bool=true) diff --git a/test/runtests.jl b/test/runtests.jl index 2c75c17..70e401b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,5 +1,5 @@ using XML -using XML: Document, Element, Declaration, Comment, CData, DTD, ProcessingInstruction, Text, escape, unescape, OrderedDict +using XML: Document, Element, Declaration, Comment, CData, DTD, ProcessingInstruction, Text, escape, unescape, OrderedDict, h using Downloads: download using Test import AbstractTrees @@ -15,6 +15,13 @@ simple_dtd = joinpath("data", "simple_dtd.xml") all_files = [xml_xsd, kml_xsd, books_xml, example_kml, simple_dtd] +#-----------------------------------------------------------------------------# h +@testset "h function" begin + @test h.tag == XML.Element("tag") + @test h.tag(id="id") == XML.Element("tag"; id="id") + @test h.tag(1, 2, a="a", b="b") == XML.Element("tag", 1, 2; a="a", b="b") +end + #-----------------------------------------------------------------------------# escaping/unescaping @testset "escaping/unescaping" begin s = "This > string < has & some \" special ' characters"