diff --git a/docs/src/references.md b/docs/src/references.md index 2aafdc5..459c822 100644 --- a/docs/src/references.md +++ b/docs/src/references.md @@ -35,6 +35,7 @@ TextNode CommentNode CDataNode AttributeNode +DTDNode ``` Node types @@ -78,9 +79,12 @@ isattribute(::Node) EzXML.istext(::Node) iscdata(::Node) iscomment(::Node) +isdtd(::Node) countnodes(::Node) countelements(::Node) countattributes(::Node) +systemID(::Node) +externalID(::Node) ``` Node modifiers @@ -97,6 +101,7 @@ DOM tree accessors ```@docs document root +dtd parentnode parentelement firstnode @@ -114,6 +119,7 @@ elements eachattribute attributes hasroot +hasdtd hasnode hasnextnode hasprevnode @@ -130,6 +136,7 @@ DOM tree modifiers ```@docs setroot! +setdtd! link! linknext! linkprev! diff --git a/src/EzXML.jl b/src/EzXML.jl index 3dc602e..c1a1195 100644 --- a/src/EzXML.jl +++ b/src/EzXML.jl @@ -17,6 +17,7 @@ export CommentNode, CDataNode, AttributeNode, + DTDNode, # document constructors XMLDocument, @@ -52,18 +53,24 @@ export hasroot, root, setroot!, + hasdtd, + dtd, + setdtd!, nodetype, iselement, isattribute, # istext, iscdata, iscomment, + isdtd, hasdocument, document, name, setname!, content, setcontent!, + systemID, + externalID, eachnode, nodes, eachelement, diff --git a/src/document.jl b/src/document.jl index 42e822b..0be2899 100644 --- a/src/document.jl +++ b/src/document.jl @@ -245,3 +245,54 @@ function setroot!(doc::Document, root::Node) end return root end + +""" + hasdtd(doc::Document) + +Return if `doc` has a DTD node. +""" +function hasdtd(doc::Document) + dtd_ptr = ccall( + (:xmlGetIntSubset, libxml2), + Ptr{_Node}, + (Ptr{Void},), + doc.node.ptr) + return dtd_ptr != C_NULL +end + +""" + dtd(doc::Document) + +Return the DTD node of `doc`. +""" +function dtd(doc::Document) + if !hasdtd(doc) + throw(ArgumentError("no DTD")) + end + dtd_ptr = ccall( + (:xmlGetIntSubset, libxml2), + Ptr{_Node}, + (Ptr{Void},), + doc.node.ptr) + return Node(dtd_ptr) +end + +""" + setdtd!(doc::Document, node::Node) + +Set the DTD node of `doc` to `node` and return the DTD node. +""" +function setdtd!(doc::Document, node::Node) + if !isdtd(node) + throw(ArgumentError("not a DTD node")) + elseif hasdtd(doc) + unlink!(dtd(doc)) + end + # Insert `node` as the first child of `doc.node`. + if hasnode(doc.node) + linkprev!(firstnode(doc.node), node) + else + link!(doc.node, node) + end + return node +end diff --git a/src/node.jl b/src/node.jl index 05ec53c..f34d60a 100644 --- a/src/node.jl +++ b/src/node.jl @@ -163,6 +163,26 @@ immutable _Attribute psvi::Ptr{Void} end +# Fields of DTD node (_xmlDtd). +immutable _Dtd + _private::Ptr{Void} + typ::Cint + name::Cstring + children::Ptr{_Node} + last::Ptr{_Node} + parent::Ptr{_Node} + next::Ptr{_Node} + prev::Ptr{_Node} + doc::Ptr{_Node} + + notations::Ptr{Void} + elements::Ptr{Void} + attributes::Ptr{Void} + entities::Ptr{Void} + externalID::Cstring + systemID::Cstring + pentities::Ptr{Void} +end # Node type # --------- @@ -448,6 +468,36 @@ function AttributeNode(name::AbstractString, value::AbstractString) return Node(node_ptr) end +""" + DTDNode(name, [systemID, [externalID]]) + +Create a DTD node with `name`, `systemID`, and `externalID`. +""" +function DTDNode(name::AbstractString, systemID::AbstractString, externalID::AbstractString) + return make_dtd_node(name, systemID, externalID) +end + +function DTDNode(name::AbstractString, systemID::AbstractString) + return make_dtd_node(name, systemID, C_NULL) +end + +function DTDNode(name::AbstractString) + return make_dtd_node(name, C_NULL, C_NULL) +end + +function make_dtd_node(name, systemID, externalID) + doc_ptr = C_NULL + node_ptr = ccall( + (:xmlCreateIntSubset, libxml2), + Ptr{_Node}, + (Ptr{Void}, Cstring, Cstring, Cstring), + doc_ptr, name, externalID, systemID) + if node_ptr == C_NULL + throw_xml_error() + end + return Node(node_ptr) +end + # DOM # --- @@ -982,6 +1032,15 @@ function iscomment(node::Node) return nodetype(node) === COMMENT_NODE end +""" + isdtd(node::Node) + +Return if `node` is a DTD node. +""" +function isdtd(node::Node) + return nodetype(node) === DTD_NODE +end + """ hasdocument(node::Node) @@ -1062,6 +1121,30 @@ function setcontent!(node::Node, content::AbstractString) return node end +""" + systemID(node::Node) + +Return the system ID of `node`. +""" +function systemID(node::Node) + if !isdtd(node) + throw(ArgumentError("not a DTD node")) + end + return unsafe_string(unsafe_load(convert(Ptr{_Dtd}, node.ptr)).systemID) +end + +""" + externalID(node::Node) + +Return the external ID of `node`. +""" +function externalID(node::Node) + if !isdtd(node) + throw(ArgumentError("not a DTD node")) + end + return unsafe_string(unsafe_load(convert(Ptr{_Dtd}, node.ptr)).externalID) +end + # Attributes # ---------- diff --git a/test/runtests.jl b/test/runtests.jl index e26c063..263fd10 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -138,6 +138,8 @@ end """) @test isa(doc, Document) @test nodetype(doc.node) === EzXML.HTML_DOCUMENT_NODE + @test hasdtd(doc) + @test name(dtd(doc)) == "html" doc = parse(Document, """ @@ -151,6 +153,7 @@ end """) @test isa(doc, Document) @test nodetype(doc.node) === EzXML.HTML_DOCUMENT_NODE + @test hasdtd(doc) doc = parse(Document, """ @@ -319,6 +322,34 @@ end @test isattribute(n) @test_throws ArgumentError document(n) + n = DTDNode("open-hatch") + @test isa(n, Node) + @test isdtd(n) + @test n.owner === n + @test nodetype(n) === EzXML.DTD_NODE + @test name(n) == "open-hatch" + @test_throws ArgumentError systemID(n) + @test_throws ArgumentError externalID(n) + + n = DTDNode("open-hatch", + "http://www.textuality.com/boilerplate/OpenHatch.xml") + @test isa(n, Node) + @test isdtd(n) + @test n.owner === n + @test nodetype(n) === EzXML.DTD_NODE + @test systemID(n) == "http://www.textuality.com/boilerplate/OpenHatch.xml" + @test_throws ArgumentError externalID(n) + + n = DTDNode("open-hatch", + "http://www.textuality.com/boilerplate/OpenHatch.xml", + "-//Textuality//TEXT Standard open-hatch boilerplate//EN") + @test isa(n, Node) + @test isdtd(n) + @test n.owner === n + @test nodetype(n) === EzXML.DTD_NODE + @test systemID(n) == "http://www.textuality.com/boilerplate/OpenHatch.xml" + @test externalID(n) == "-//Textuality//TEXT Standard open-hatch boilerplate//EN" + doc = XMLDocument() @test isa(doc, Document) @test doc.node.owner === doc.node @@ -345,6 +376,7 @@ end @testset "Traversal" begin doc = parsexml("") @test hasroot(doc) + @test !hasdtd(doc) @test isa(root(doc), Node) @test root(doc) == root(doc) @test root(doc) === root(doc) @@ -358,6 +390,26 @@ end @test_throws ArgumentError parentnode(doc.node) @test hasparentnode(root(doc)) @test parentnode(root(doc)) === doc.node + @test_throws ArgumentError dtd(doc) + + doc = parsexml(""" + + + + hello + Content + + """) + @test hasroot(doc) + @test hasdtd(doc) + @test isa(dtd(doc), Node) + @test isdtd(dtd(doc)) + @test dtd(doc) === dtd(doc) + @test name(dtd(doc)) == "html" + @test systemID(dtd(doc)) == "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" + @test externalID(dtd(doc)) == "-//W3C//DTD XHTML 1.0 Transitional//EN" + @test parentnode(dtd(doc)) === doc.node doc = parse(Document, """ @@ -602,6 +654,21 @@ end setcontent!(el, "some content") @test content(el) == "some content" + doc = XMLDocument() + @test countnodes(doc.node) === 0 + d1 = DTDNode("hello", "hello.dtd") + @test setdtd!(doc, d1) === d1 + @test countnodes(doc.node) === 1 + @test dtd(doc) === d1 + setroot!(doc, ElementNode("root")) + @test countnodes(doc.node) === 2 + @test nextnode(d1) === root(doc) + d2 = DTDNode("hello", "hello2.dtd") + @test setdtd!(doc, d2) === d2 + @test countnodes(doc.node) === 2 + @test dtd(doc) === d2 + @test_throws ArgumentError setdtd!(doc, ElementNode("foo")) + # t1t2 doc = XMLDocument() e1 = ElementNode("e1")