Skip to content

Commit

Permalink
add DTD support (#3)
Browse files Browse the repository at this point in the history
  • Loading branch information
bicycle1885 authored Nov 25, 2016
1 parent 583adf8 commit 9953d11
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 0 deletions.
7 changes: 7 additions & 0 deletions docs/src/references.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ TextNode
CommentNode
CDataNode
AttributeNode
DTDNode
```

Node types
Expand Down Expand Up @@ -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
Expand All @@ -97,6 +101,7 @@ DOM tree accessors
```@docs
document
root
dtd
parentnode
parentelement
firstnode
Expand All @@ -114,6 +119,7 @@ elements
eachattribute
attributes
hasroot
hasdtd
hasnode
hasnextnode
hasprevnode
Expand All @@ -130,6 +136,7 @@ DOM tree modifiers

```@docs
setroot!
setdtd!
link!
linknext!
linkprev!
Expand Down
7 changes: 7 additions & 0 deletions src/EzXML.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export
CommentNode,
CDataNode,
AttributeNode,
DTDNode,

# document constructors
XMLDocument,
Expand Down Expand Up @@ -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,
Expand Down
51 changes: 51 additions & 0 deletions src/document.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
83 changes: 83 additions & 0 deletions src/node.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
# ---------
Expand Down Expand Up @@ -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
# ---
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
# ----------
Expand Down
67 changes: 67 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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, """
<html>
Expand All @@ -151,6 +153,7 @@ end
""")
@test isa(doc, Document)
@test nodetype(doc.node) === EzXML.HTML_DOCUMENT_NODE
@test hasdtd(doc)

doc = parse(Document, """
<!DOCTYPE html>
Expand Down Expand Up @@ -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
Expand All @@ -345,6 +376,7 @@ end
@testset "Traversal" begin
doc = parsexml("<root/>")
@test hasroot(doc)
@test !hasdtd(doc)
@test isa(root(doc), Node)
@test root(doc) == root(doc)
@test root(doc) === root(doc)
Expand All @@ -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("""
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><title>hello</title></head>
<body>Content</body>
</html>
""")
@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, """
<?xml version="1.0"?>
Expand Down Expand Up @@ -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"))

# <e1>t1<e2>t2<e3 a1="val"/></e2></e1>
doc = XMLDocument()
e1 = ElementNode("e1")
Expand Down

0 comments on commit 9953d11

Please sign in to comment.