-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
Copy pathxml.cr
130 lines (121 loc) · 3.76 KB
/
xml.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# The XML module allows parsing and generating [XML](https://www.w3.org/XML/) documents.
#
# NOTE: To use `XML`, you must explicitly import it with `require "xml"`
#
# ### Parsing
#
# `XML#parse` will parse xml from `String` or `IO` and return xml document as an `XML::Node` which represents all kinds of xml nodes.
#
# Example:
#
# ```
# require "xml"
#
# xml = <<-XML
# <person id="1">
# <firstname>Jane</firstname>
# <lastname>Doe</lastname>
# </person>
# XML
#
# document = XML.parse(xml) # : XML::Node
# person = document.first_element_child # : XML::Node?
# if person
# puts person["id"] # "1" : String?
#
# puts typeof(person.children) # XML::NodeSet
# person.children.select(&.element?).each do |child| # Select only element children
# puts typeof(child) # XML::Node
# puts child.name # firstname : String
# puts child.content # Jane : String?
# end
# end
# ```
#
# ## Generating
#
# Use `XML.build`, which uses an `XML::Builder`:
#
# ```
# require "xml"
#
# string = XML.build(indent: " ") do |xml|
# xml.element("person", id: 1) do
# xml.element("firstname") { xml.text "Jane" }
# xml.element("lastname") { xml.text "Doe" }
# end
# end
#
# string # => "<?xml version=\"1.0\"?>\n<person id=\"1\">\n <firstname>Jane</firstname>\n <lastname>Doe</lastname>\n</person>\n"
# ```
module XML
# Parses an XML document from *string* with *options* into an `XML::Node`.
#
# See `ParserOptions.default` for default options.
def self.parse(string : String, options : ParserOptions = ParserOptions.default) : Node
raise XML::Error.new("Document is empty", 0) if string.empty?
from_ptr { LibXML.xmlReadMemory(string, string.bytesize, nil, nil, options) }
end
# Parses an XML document from *io* with *options* into an `XML::Node`.
#
# See `ParserOptions.default` for default options.
def self.parse(io : IO, options : ParserOptions = ParserOptions.default) : Node
from_ptr { LibXML.xmlReadIO(
->(ctx, buffer, len) {
LibC::Int.new(Box(IO).unbox(ctx).read Slice.new(buffer, len))
},
->(ctx) { 0 },
Box(IO).box(io),
nil,
nil,
options,
) }
end
# Parses an HTML document from *string* with *options* into an `XML::Node`.
#
# See `HTMLParserOptions.default` for default options.
def self.parse_html(string : String, options : HTMLParserOptions = HTMLParserOptions.default) : Node
raise XML::Error.new("Document is empty", 0) if string.empty?
from_ptr { LibXML.htmlReadMemory(string, string.bytesize, nil, "utf-8", options) }
end
# Parses an HTML document from *io* with *options* into an `XML::Node`.
#
# See `HTMLParserOptions.default` for default options.
def self.parse_html(io : IO, options : HTMLParserOptions = HTMLParserOptions.default) : Node
from_ptr { LibXML.htmlReadIO(
->(ctx, buffer, len) {
LibC::Int.new(Box(IO).unbox(ctx).read Slice.new(buffer, len))
},
->(ctx) { 0 },
Box(IO).box(io),
nil,
"utf-8",
options,
) }
end
protected def self.from_ptr(& : -> LibXML::Doc*)
errors = [] of XML::Error
doc = XML::Error.collect(errors) { yield }
raise Error.new(LibXML.xmlGetLastError) unless doc
Node.new(doc, errors)
end
protected def self.with_indent_tree_output(indent : Bool, &)
ptr = LibXML.__xmlIndentTreeOutput
old, ptr.value = ptr.value, indent ? 1 : 0
begin
yield
ensure
ptr.value = old
end
end
protected def self.with_tree_indent_string(string : String, &)
ptr = LibXML.__xmlTreeIndentString
old, ptr.value = ptr.value, string.to_unsafe
begin
yield
ensure
ptr.value = old
end
end
end
require "./xml/*"