ff2299ab85ae364b9abf4aa5cb26b3483f18cbb3
[nit.git] / lib / saxophonit / reader_model.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # This file is free software, which comes along with NIT. This software is
4 # distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
5 # without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
6 # PARTICULAR PURPOSE. You can modify it is you want, provided this header
7 # is kept unaltered, and a notification of the changes is added.
8 # You are allowed to redistribute it and sell it, alone or is a part of
9 # another product.
10
11 # Reader’s model.
12 module saxophonit::reader_model
13
14 import sax
15 import sax::helpers::sax_locator_impl
16 import sax::helpers::attributes_impl
17 import sax::helpers::namespace_support
18
19 # Reader’s model.
20 #
21 # Handle event dispatching, settings and internal state.
22 class XophonReaderModel
23
24 # Stack of the current element type qname and of the qnames of the ancestors.
25 #
26 # Current element is at the peek, the root element is at the bottom.
27 # Used to check if end tags match start tags.
28 private var element_path = new Array[XmlName]
29
30 # Current element’s attributes
31 private var atts = new AttributesImpl
32
33 private var ns = new NamespaceSupport
34
35 # Regular expression to match a `Name` against the `QName` production of
36 # “Namespaces in XML”.
37 private var qname_re: Regex = "^[^:]+(:[^:]+)?$".to_re
38
39 var locator: nullable SAXLocatorImpl = null is writable
40
41
42 # TODO: Handle these features.
43
44 private var features: Map[String, Bool] = new HashMap[String, Bool]
45
46 # SEE: `sax`
47 var feature_namespaces_uri =
48 "http://xml.org/sax/features/namespaces"
49
50 # SEE: `sax`
51 var feature_namespace_prefixes_uri =
52 "http://xml.org/sax/features/namespace-prefixes"
53
54
55 # SEE: `sax::XMLReader.entity_resolver`
56 var entity_resolver: nullable EntityResolver = null is writable
57
58 # SEE: `sax::XMLReader.dtd_handler`
59 var dtd_handler: nullable DTDHandler = null is writable
60
61 # SEE: `sax::XMLReader.content_handler`
62 var content_handler: nullable ContentHandler = null is writable
63
64 # SEE: `sax::XMLReader.error_handler`
65 var error_handler: nullable ErrorHandler = null is writable
66
67
68 init do
69 qname_re.optimize_is_in = true
70 features[feature_namespaces_uri] = true
71 features[feature_namespace_prefixes_uri] = false
72 end
73
74 # SEE: `sax::XMLReader.feature_recognized`
75 fun feature_recognized(name: String): Bool do
76 return features.keys.has(name)
77 end
78
79 # SEE: `sax::XMLReader.feature_readable`
80 fun feature_readable(name: String): Bool do
81 return features.keys.has(name)
82 end
83
84 # SEE: `sax::XMLReader.feature_writable`
85 fun feature_writable(name: String): Bool do
86 return features.keys.has(name)
87 end
88
89 # SEE: `sax::XMLReader.feature`
90 fun feature(name: String): Bool do
91 assert feature_recognized: feature_recognized(name)
92 return features[name]
93 end
94
95 # SEE: `sax::XMLReader.feature=`
96 fun feature=(name: String, value: Bool) do
97 assert feature_recognized: feature_recognized(name)
98 if name == feature_namespaces_uri then
99 assert legal_state: value or features[feature_namespace_prefixes_uri] else
100 sys.stderr.write("At least one of <{feature_namespaces_uri}> or <{feature_namespace_prefixes_uri}> must be true.\n")
101 end
102 else if name == feature_namespace_prefixes_uri then
103 assert legal_state: value or features[feature_namespaces_uri] else
104 sys.stderr.write("At least one of <{feature_namespaces_uri}> or <{feature_namespace_prefixes_uri}> must be true.\n")
105 end
106 end
107 features[name] = value
108 end
109
110 # SEE: `sax::XMLReader.property_recognized`
111 fun property_recognized(name: String): Bool do return false
112
113 # SEE: `sax::XMLReader.property_readable`
114 fun property_readable(name: String): Bool do return false
115
116 # SEE: `sax::XMLReader.property_writable`
117 fun property_writable(name: String): Bool do return false
118
119 # SEE: `sax::XMLReader.property`
120 fun property(name: String): nullable Object do
121 assert property_recognized: false
122 return null
123 end
124
125 # SEE: `sax::XMLReader.property=`
126 fun property=(name: String, value: nullable Object) do
127 assert property_recognized: false
128 end
129
130 # Is the root element closed?
131 fun root_closed: Bool do return element_path.length <= 0
132
133 # Expect the root element is closed.
134 fun expect_root_closed: Bool do
135 if root_closed then
136 return true
137 else if element_path.length > 1 then
138 return fire_fatal_error("Reached the end of the file with " +
139 "{element_path.length.to_s} open elements.", null)
140 else
141 return fire_fatal_error("Reached the end of the file with an " +
142 "open element.", null)
143 end
144 end
145
146
147 ###########################################################################
148 # Dispatching
149
150 # Set the document locator of the content handler, if needed.
151 fun fire_document_locator do
152 if content_handler != null then
153 content_handler.document_locator = locator.as(not null)
154 end
155 end
156
157 # Fire the start of the document.
158 fun fire_start_document do
159 if content_handler != null then
160 content_handler.start_document
161 end
162 ns.reset
163 end
164
165 # Fire the end of the document.
166 fun fire_end_document do
167 if content_handler != null then
168 content_handler.end_document
169 end
170 end
171
172 # Fire the start of an attribute list.
173 fun fire_start_attributes do
174 atts.clear
175 ns.push_context
176 end
177
178 # Fire the appearance of an attribute.
179 fun fire_attribute(qname: String, value: String) do
180 if "xmlns" == qname or qname.has_prefix("xmlns:") then
181 var prefix = qname.substring_from("xmlns:".length)
182
183 if not prefix.has(":") then
184 fire_start_prefix_mapping(prefix, value)
185 if not feature(feature_namespace_prefixes_uri) then return
186 end
187 end
188 # TODO: Types.
189 atts.add("", "", qname, "CDATA", value)
190 end
191
192 # Fire the start of an element.
193 fun fire_start_element(name: String) do
194 var parts = ["", "", ""]
195
196 for i in [0..atts.length[ do
197 set_attribute_ns(i)
198 end
199 process_name(name, parts, false)
200 element_path.push(new XmlName(parts[0], parts[1], parts[2]))
201 if content_handler != null then
202 content_handler.start_element(parts[0], parts[1], parts[2], atts)
203 end
204 end
205
206 # Now prefixes are mapped, set the expanded name of the attribute at `index`.
207 private fun set_attribute_ns(index: Int) do
208 var name = ["", "", ""]
209
210 process_name(atts.qname(index).as(not null), name, true)
211 atts.uri(index) = name[0]
212 atts.local_name(index) = name[1]
213 end
214
215 # Like `ns.process_name`, but with error handling.
216 private fun process_name(qname: String, parts: Array[String],
217 is_attribute: Bool) do
218 if qname.has(qname_re) then
219 if ns.process_name(qname, parts, is_attribute) == null then
220 fire_error("The namespace IRI of `{qname}` was not found in " +
221 "this scope. Passing the original name as the local " +
222 "name.", null)
223 parts = ["", qname, qname]
224 end
225 else
226 fire_error("The name `{qname}` contains more than one colon. " +
227 "Passing the original name as the local name.", null)
228 parts = ["", qname, qname]
229 end
230 end
231
232 # Fire the end of an element.
233 #
234 # Return `true` on success.
235 fun fire_end_element(name: String):Bool do
236 var peek_name = element_path.last
237
238 if peek_name.qname == name then
239 element_path.pop
240 if content_handler != null then
241 content_handler.end_element(peek_name.uri,
242 peek_name.local_name, peek_name.qname)
243 end
244 return true
245 else
246 fire_fatal_error("The type in the closing tag (`{name}`) does " +
247 "not match the type in the opening tag " +
248 "(`{element_path.last.qname}`).", null)
249 return false
250 end
251 end
252
253 # Fire the start of a mapping between `prefix` and `uri`.
254 private fun fire_start_prefix_mapping(prefix: String, uri: String) do
255 if not ns.declare_prefix(prefix, uri) then
256 fire_error("The mapping between the prefix `{prefix}` and " +
257 "the namespace IRI `{uri}` breaks a built-in " +
258 "mapping. Ignoring the declaration.", null)
259 end
260 if content_handler != null then
261 content_handler.start_prefix_mapping(prefix, uri)
262 end
263 end
264
265 # Fire the end of the current mapping of `prefix`.
266 private fun end_prefix_mapping(prefix: String) do
267 if content_handler != null then
268 content_handler.end_prefix_mapping(prefix)
269 end
270 end
271
272 # Fire the appearance of a comment.
273 fun fire_comment(content: String) do
274 # TODO
275 end
276
277 # Fire the appearance of a processing instruction.
278 fun fire_processing_instruction(target: String, data: nullable String) do
279 if content_handler != null then
280 content_handler.processing_instruction(target, data)
281 end
282 end
283
284 # Fire the start of a `CDATA` section.
285 fun fire_start_cdata do
286 # TODO
287 end
288
289 # Fire the end of a `CDATA` section.
290 fun fire_end_cdata do
291 # TODO
292 end
293
294 # Fire the appearance of a text node.
295 fun fire_characters(str: String) do
296 if content_handler != null then
297 content_handler.characters(str)
298 end
299 end
300
301 private fun exception(message: String, cause: nullable Error):
302 SAXParseException do
303 var e: SAXParseException
304
305 if locator == null then
306 e = new SAXParseException(message)
307 else
308 e = new SAXParseException.with_locator(message, locator.as(not null))
309 end
310 e.cause = cause
311 return e
312 end
313
314 # Fire a fatal error with the specified message and cause.
315 #
316 # Return `false`.
317 fun fire_fatal_error(message: String, cause: nullable Error):Bool do
318 var e = exception(message, cause)
319
320 if error_handler == null then
321 e.throw
322 else
323 error_handler.fatal_error(e)
324 end
325 return false
326 end
327
328 # Fire an error with the specified message and cause.
329 fun fire_error(message: String, cause: nullable Error) do
330 var e = exception(message, cause)
331
332 if error_handler != null then
333 error_handler.error(e)
334 end
335 end
336
337 # Fire a warning with the specified message and cause.
338 fun fire_warning(message: String, cause: nullable Error) do
339 var e = exception(message, cause)
340
341 if error_handler != null then
342 error_handler.warning(e)
343 end
344 end
345 end
346
347 # An XML expanded name.
348 private class XmlName
349 # Namespace IRI or `""`.
350 var uri: String
351
352 # Local name or `""`.
353 var local_name: String
354
355 # Original qualified name.
356 var qname: String
357 end