Skip to content

Commit 9953d11

Browse files
authored
add DTD support (#3)
1 parent 583adf8 commit 9953d11

File tree

5 files changed

+215
-0
lines changed

5 files changed

+215
-0
lines changed

docs/src/references.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ TextNode
3535
CommentNode
3636
CDataNode
3737
AttributeNode
38+
DTDNode
3839
```
3940

4041
Node types
@@ -78,9 +79,12 @@ isattribute(::Node)
7879
EzXML.istext(::Node)
7980
iscdata(::Node)
8081
iscomment(::Node)
82+
isdtd(::Node)
8183
countnodes(::Node)
8284
countelements(::Node)
8385
countattributes(::Node)
86+
systemID(::Node)
87+
externalID(::Node)
8488
```
8589

8690
Node modifiers
@@ -97,6 +101,7 @@ DOM tree accessors
97101
```@docs
98102
document
99103
root
104+
dtd
100105
parentnode
101106
parentelement
102107
firstnode
@@ -114,6 +119,7 @@ elements
114119
eachattribute
115120
attributes
116121
hasroot
122+
hasdtd
117123
hasnode
118124
hasnextnode
119125
hasprevnode
@@ -130,6 +136,7 @@ DOM tree modifiers
130136

131137
```@docs
132138
setroot!
139+
setdtd!
133140
link!
134141
linknext!
135142
linkprev!

src/EzXML.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export
1717
CommentNode,
1818
CDataNode,
1919
AttributeNode,
20+
DTDNode,
2021

2122
# document constructors
2223
XMLDocument,
@@ -52,18 +53,24 @@ export
5253
hasroot,
5354
root,
5455
setroot!,
56+
hasdtd,
57+
dtd,
58+
setdtd!,
5559
nodetype,
5660
iselement,
5761
isattribute,
5862
# istext,
5963
iscdata,
6064
iscomment,
65+
isdtd,
6166
hasdocument,
6267
document,
6368
name,
6469
setname!,
6570
content,
6671
setcontent!,
72+
systemID,
73+
externalID,
6774
eachnode,
6875
nodes,
6976
eachelement,

src/document.jl

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,3 +245,54 @@ function setroot!(doc::Document, root::Node)
245245
end
246246
return root
247247
end
248+
249+
"""
250+
hasdtd(doc::Document)
251+
252+
Return if `doc` has a DTD node.
253+
"""
254+
function hasdtd(doc::Document)
255+
dtd_ptr = ccall(
256+
(:xmlGetIntSubset, libxml2),
257+
Ptr{_Node},
258+
(Ptr{Void},),
259+
doc.node.ptr)
260+
return dtd_ptr != C_NULL
261+
end
262+
263+
"""
264+
dtd(doc::Document)
265+
266+
Return the DTD node of `doc`.
267+
"""
268+
function dtd(doc::Document)
269+
if !hasdtd(doc)
270+
throw(ArgumentError("no DTD"))
271+
end
272+
dtd_ptr = ccall(
273+
(:xmlGetIntSubset, libxml2),
274+
Ptr{_Node},
275+
(Ptr{Void},),
276+
doc.node.ptr)
277+
return Node(dtd_ptr)
278+
end
279+
280+
"""
281+
setdtd!(doc::Document, node::Node)
282+
283+
Set the DTD node of `doc` to `node` and return the DTD node.
284+
"""
285+
function setdtd!(doc::Document, node::Node)
286+
if !isdtd(node)
287+
throw(ArgumentError("not a DTD node"))
288+
elseif hasdtd(doc)
289+
unlink!(dtd(doc))
290+
end
291+
# Insert `node` as the first child of `doc.node`.
292+
if hasnode(doc.node)
293+
linkprev!(firstnode(doc.node), node)
294+
else
295+
link!(doc.node, node)
296+
end
297+
return node
298+
end

src/node.jl

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,26 @@ immutable _Attribute
163163
psvi::Ptr{Void}
164164
end
165165

166+
# Fields of DTD node (_xmlDtd).
167+
immutable _Dtd
168+
_private::Ptr{Void}
169+
typ::Cint
170+
name::Cstring
171+
children::Ptr{_Node}
172+
last::Ptr{_Node}
173+
parent::Ptr{_Node}
174+
next::Ptr{_Node}
175+
prev::Ptr{_Node}
176+
doc::Ptr{_Node}
177+
178+
notations::Ptr{Void}
179+
elements::Ptr{Void}
180+
attributes::Ptr{Void}
181+
entities::Ptr{Void}
182+
externalID::Cstring
183+
systemID::Cstring
184+
pentities::Ptr{Void}
185+
end
166186

167187
# Node type
168188
# ---------
@@ -448,6 +468,36 @@ function AttributeNode(name::AbstractString, value::AbstractString)
448468
return Node(node_ptr)
449469
end
450470

471+
"""
472+
DTDNode(name, [systemID, [externalID]])
473+
474+
Create a DTD node with `name`, `systemID`, and `externalID`.
475+
"""
476+
function DTDNode(name::AbstractString, systemID::AbstractString, externalID::AbstractString)
477+
return make_dtd_node(name, systemID, externalID)
478+
end
479+
480+
function DTDNode(name::AbstractString, systemID::AbstractString)
481+
return make_dtd_node(name, systemID, C_NULL)
482+
end
483+
484+
function DTDNode(name::AbstractString)
485+
return make_dtd_node(name, C_NULL, C_NULL)
486+
end
487+
488+
function make_dtd_node(name, systemID, externalID)
489+
doc_ptr = C_NULL
490+
node_ptr = ccall(
491+
(:xmlCreateIntSubset, libxml2),
492+
Ptr{_Node},
493+
(Ptr{Void}, Cstring, Cstring, Cstring),
494+
doc_ptr, name, externalID, systemID)
495+
if node_ptr == C_NULL
496+
throw_xml_error()
497+
end
498+
return Node(node_ptr)
499+
end
500+
451501

452502
# DOM
453503
# ---
@@ -982,6 +1032,15 @@ function iscomment(node::Node)
9821032
return nodetype(node) === COMMENT_NODE
9831033
end
9841034

1035+
"""
1036+
isdtd(node::Node)
1037+
1038+
Return if `node` is a DTD node.
1039+
"""
1040+
function isdtd(node::Node)
1041+
return nodetype(node) === DTD_NODE
1042+
end
1043+
9851044
"""
9861045
hasdocument(node::Node)
9871046
@@ -1062,6 +1121,30 @@ function setcontent!(node::Node, content::AbstractString)
10621121
return node
10631122
end
10641123

1124+
"""
1125+
systemID(node::Node)
1126+
1127+
Return the system ID of `node`.
1128+
"""
1129+
function systemID(node::Node)
1130+
if !isdtd(node)
1131+
throw(ArgumentError("not a DTD node"))
1132+
end
1133+
return unsafe_string(unsafe_load(convert(Ptr{_Dtd}, node.ptr)).systemID)
1134+
end
1135+
1136+
"""
1137+
externalID(node::Node)
1138+
1139+
Return the external ID of `node`.
1140+
"""
1141+
function externalID(node::Node)
1142+
if !isdtd(node)
1143+
throw(ArgumentError("not a DTD node"))
1144+
end
1145+
return unsafe_string(unsafe_load(convert(Ptr{_Dtd}, node.ptr)).externalID)
1146+
end
1147+
10651148

10661149
# Attributes
10671150
# ----------

test/runtests.jl

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ end
138138
""")
139139
@test isa(doc, Document)
140140
@test nodetype(doc.node) === EzXML.HTML_DOCUMENT_NODE
141+
@test hasdtd(doc)
142+
@test name(dtd(doc)) == "html"
141143

142144
doc = parse(Document, """
143145
<html>
@@ -151,6 +153,7 @@ end
151153
""")
152154
@test isa(doc, Document)
153155
@test nodetype(doc.node) === EzXML.HTML_DOCUMENT_NODE
156+
@test hasdtd(doc)
154157

155158
doc = parse(Document, """
156159
<!DOCTYPE html>
@@ -319,6 +322,34 @@ end
319322
@test isattribute(n)
320323
@test_throws ArgumentError document(n)
321324

325+
n = DTDNode("open-hatch")
326+
@test isa(n, Node)
327+
@test isdtd(n)
328+
@test n.owner === n
329+
@test nodetype(n) === EzXML.DTD_NODE
330+
@test name(n) == "open-hatch"
331+
@test_throws ArgumentError systemID(n)
332+
@test_throws ArgumentError externalID(n)
333+
334+
n = DTDNode("open-hatch",
335+
"http://www.textuality.com/boilerplate/OpenHatch.xml")
336+
@test isa(n, Node)
337+
@test isdtd(n)
338+
@test n.owner === n
339+
@test nodetype(n) === EzXML.DTD_NODE
340+
@test systemID(n) == "http://www.textuality.com/boilerplate/OpenHatch.xml"
341+
@test_throws ArgumentError externalID(n)
342+
343+
n = DTDNode("open-hatch",
344+
"http://www.textuality.com/boilerplate/OpenHatch.xml",
345+
"-//Textuality//TEXT Standard open-hatch boilerplate//EN")
346+
@test isa(n, Node)
347+
@test isdtd(n)
348+
@test n.owner === n
349+
@test nodetype(n) === EzXML.DTD_NODE
350+
@test systemID(n) == "http://www.textuality.com/boilerplate/OpenHatch.xml"
351+
@test externalID(n) == "-//Textuality//TEXT Standard open-hatch boilerplate//EN"
352+
322353
doc = XMLDocument()
323354
@test isa(doc, Document)
324355
@test doc.node.owner === doc.node
@@ -345,6 +376,7 @@ end
345376
@testset "Traversal" begin
346377
doc = parsexml("<root/>")
347378
@test hasroot(doc)
379+
@test !hasdtd(doc)
348380
@test isa(root(doc), Node)
349381
@test root(doc) == root(doc)
350382
@test root(doc) === root(doc)
@@ -358,6 +390,26 @@ end
358390
@test_throws ArgumentError parentnode(doc.node)
359391
@test hasparentnode(root(doc))
360392
@test parentnode(root(doc)) === doc.node
393+
@test_throws ArgumentError dtd(doc)
394+
395+
doc = parsexml("""
396+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
397+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
398+
399+
<html xmlns="http://www.w3.org/1999/xhtml">
400+
<head><title>hello</title></head>
401+
<body>Content</body>
402+
</html>
403+
""")
404+
@test hasroot(doc)
405+
@test hasdtd(doc)
406+
@test isa(dtd(doc), Node)
407+
@test isdtd(dtd(doc))
408+
@test dtd(doc) === dtd(doc)
409+
@test name(dtd(doc)) == "html"
410+
@test systemID(dtd(doc)) == "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
411+
@test externalID(dtd(doc)) == "-//W3C//DTD XHTML 1.0 Transitional//EN"
412+
@test parentnode(dtd(doc)) === doc.node
361413

362414
doc = parse(Document, """
363415
<?xml version="1.0"?>
@@ -602,6 +654,21 @@ end
602654
setcontent!(el, "some content")
603655
@test content(el) == "some content"
604656

657+
doc = XMLDocument()
658+
@test countnodes(doc.node) === 0
659+
d1 = DTDNode("hello", "hello.dtd")
660+
@test setdtd!(doc, d1) === d1
661+
@test countnodes(doc.node) === 1
662+
@test dtd(doc) === d1
663+
setroot!(doc, ElementNode("root"))
664+
@test countnodes(doc.node) === 2
665+
@test nextnode(d1) === root(doc)
666+
d2 = DTDNode("hello", "hello2.dtd")
667+
@test setdtd!(doc, d2) === d2
668+
@test countnodes(doc.node) === 2
669+
@test dtd(doc) === d2
670+
@test_throws ArgumentError setdtd!(doc, ElementNode("foo"))
671+
605672
# <e1>t1<e2>t2<e3 a1="val"/></e2></e1>
606673
doc = XMLDocument()
607674
e1 = ElementNode("e1")

0 commit comments

Comments
 (0)