core::hash_collection: native array storage is no more nullable
[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 # The locator that is used to indicate the current location.
40 var locator: nullable SAXLocatorImpl = null is writable
41
42
43 # TODO: Handle these features.
44
45 private var features: Map[String, Bool] = new HashMap[String, Bool]
46
47 # SEE: `sax`
48 var feature_namespaces_uri =
49 "http://xml.org/sax/features/namespaces"
50
51 # SEE: `sax`
52 var feature_namespace_prefixes_uri =
53 "http://xml.org/sax/features/namespace-prefixes"
54
55
56 # SEE: `sax::XMLReader.entity_resolver`
57 var entity_resolver: nullable EntityResolver = null is writable
58
59 # SEE: `sax::XMLReader.dtd_handler`
60 var dtd_handler: nullable DTDHandler = null is writable
61
62 # SEE: `sax::XMLReader.content_handler`
63 var content_handler: nullable ContentHandler = null is writable
64
65 # SEE: `sax::XMLReader.error_handler`
66 var error_handler: nullable ErrorHandler = null is writable
67
68
69 init do
70 qname_re.optimize_has = true
71 features[feature_namespaces_uri] = true
72 features[feature_namespace_prefixes_uri] = false
73 end
74
75 # SEE: `sax::XMLReader.feature_recognized`
76 fun feature_recognized(name: String): Bool do
77 return features.keys.has(name)
78 end
79
80 # SEE: `sax::XMLReader.feature_readable`
81 fun feature_readable(name: String): Bool do
82 return features.keys.has(name)
83 end
84
85 # SEE: `sax::XMLReader.feature_writable`
86 fun feature_writable(name: String): Bool do
87 return features.keys.has(name)
88 end
89
90 # SEE: `sax::XMLReader.feature`
91 fun feature(name: String): Bool do
92 assert feature_recognized: feature_recognized(name)
93 return features[name]
94 end
95
96 # SEE: `sax::XMLReader.feature=`
97 fun feature=(name: String, value: Bool) do
98 assert feature_recognized: feature_recognized(name)
99 if name == feature_namespaces_uri then
100 assert legal_state: value or features[feature_namespace_prefixes_uri] else
101 sys.stderr.write("At least one of <{feature_namespaces_uri}> or <{feature_namespace_prefixes_uri}> must be true.\n")
102 end
103 else if name == feature_namespace_prefixes_uri then
104 assert legal_state: value or features[feature_namespaces_uri] else
105 sys.stderr.write("At least one of <{feature_namespaces_uri}> or <{feature_namespace_prefixes_uri}> must be true.\n")
106 end
107 end
108 features[name] = value
109 end
110
111 # SEE: `sax::XMLReader.property_recognized`
112 fun property_recognized(name: String): Bool do return false
113
114 # SEE: `sax::XMLReader.property_readable`
115 fun property_readable(name: String): Bool do return false
116
117 # SEE: `sax::XMLReader.property_writable`
118 fun property_writable(name: String): Bool do return false
119
120 # SEE: `sax::XMLReader.property`
121 fun property(name: String): nullable Object do
122 assert property_recognized: false
123 return null
124 end
125
126 # SEE: `sax::XMLReader.property=`
127 fun property=(name: String, value: nullable Object) do
128 assert property_recognized: false
129 end
130
131 # Is the root element closed?
132 fun root_closed: Bool do return element_path.length <= 0
133
134 # Expect the root element is closed.
135 fun expect_root_closed: Bool do
136 if root_closed then
137 return true
138 else if element_path.length > 1 then
139 return fire_fatal_error("Reached the end of the file with " +
140 "{element_path.length.to_s} open elements.", null)
141 else
142 return fire_fatal_error("Reached the end of the file with an " +
143 "open element.", null)
144 end
145 end
146
147
148 ###########################################################################
149 # Dispatching
150
151 # Set the document locator of the content handler, if needed.
152 fun fire_document_locator do
153 if content_handler != null then
154 content_handler.document_locator = locator.as(not null)
155 end
156 end
157
158 # Fire the start of the document.
159 fun fire_start_document do
160 if content_handler != null then
161 content_handler.start_document
162 end
163 ns.reset
164 end
165
166 # Fire the end of the document.
167 fun fire_end_document do
168 if content_handler != null then
169 content_handler.end_document
170 end
171 end
172
173 # Fire the start of an attribute list.
174 fun fire_start_attributes do
175 atts.clear
176 ns.push_context
177 end
178
179 # Fire the appearance of an attribute.
180 fun fire_attribute(qname: String, value: String) do
181 if "xmlns" == qname or qname.has_prefix("xmlns:") then
182 var prefix = qname.substring_from("xmlns:".length)
183
184 if not prefix.has(":") then
185 fire_start_prefix_mapping(prefix, value)
186 if not feature(feature_namespace_prefixes_uri) then return
187 end
188 end
189 # TODO: Types.
190 atts.add("", "", qname, "CDATA", value)
191 end
192
193 # Fire the start of an element.
194 fun fire_start_element(name: String) do
195 var parts = ["", "", ""]
196
197 for i in [0..atts.length[ do
198 set_attribute_ns(i)
199 end
200 process_name(name, parts, false)
201 element_path.push(new XmlName(parts[0], parts[1], parts[2]))
202 if content_handler != null then
203 content_handler.start_element(parts[0], parts[1], parts[2], atts)
204 end
205 end
206
207 # Now prefixes are mapped, set the expanded name of the attribute at `index`.
208 private fun set_attribute_ns(index: Int) do
209 var name = ["", "", ""]
210
211 process_name(atts.qname(index).as(not null), name, true)
212 atts.uri(index) = name[0]
213 atts.local_name(index) = name[1]
214 end
215
216 # Like `ns.process_name`, but with error handling.
217 private fun process_name(qname: String, parts: Array[String],
218 is_attribute: Bool) do
219 if qname.has(qname_re) then
220 if ns.process_name(qname, parts, is_attribute) == null then
221 fire_error("The namespace IRI of `{qname}` was not found in " +
222 "this scope. Passing the original name as the local " +
223 "name.", null)
224 parts = ["", qname, qname]
225 end
226 else
227 fire_error("The name `{qname}` contains more than one colon. " +
228 "Passing the original name as the local name.", null)
229 parts = ["", qname, qname]
230 end
231 end
232
233 # Fire the end of an element.
234 #
235 # Return `true` on success.
236 fun fire_end_element(name: String):Bool do
237 var peek_name = element_path.last
238
239 if peek_name.qname == name then
240 element_path.pop
241 if content_handler != null then
242 content_handler.end_element(peek_name.uri,
243 peek_name.local_name, peek_name.qname)
244 end
245 return true
246 else
247 fire_fatal_error("The type in the closing tag (`{name}`) does " +
248 "not match the type in the opening tag " +
249 "(`{element_path.last.qname}`).", null)
250 return false
251 end
252 end
253
254 # Fire the start of a mapping between `prefix` and `uri`.
255 private fun fire_start_prefix_mapping(prefix: String, uri: String) do
256 if not ns.declare_prefix(prefix, uri) then
257 fire_error("The mapping between the prefix `{prefix}` and " +
258 "the namespace IRI `{uri}` breaks a built-in " +
259 "mapping. Ignoring the declaration.", null)
260 end
261 if content_handler != null then
262 content_handler.start_prefix_mapping(prefix, uri)
263 end
264 end
265
266 # Fire the end of the current mapping of `prefix`.
267 private fun end_prefix_mapping(prefix: String) do
268 if content_handler != null then
269 content_handler.end_prefix_mapping(prefix)
270 end
271 end
272
273 # Fire the appearance of a comment.
274 fun fire_comment(content: String) do
275 # TODO
276 end
277
278 # Fire the appearance of a processing instruction.
279 fun fire_processing_instruction(target: String, data: nullable String) do
280 if content_handler != null then
281 content_handler.processing_instruction(target, data)
282 end
283 end
284
285 # Fire the start of a `CDATA` section.
286 fun fire_start_cdata do
287 # TODO
288 end
289
290 # Fire the end of a `CDATA` section.
291 fun fire_end_cdata do
292 # TODO
293 end
294
295 # Fire the appearance of a text node.
296 fun fire_characters(str: String) do
297 if content_handler != null then
298 content_handler.characters(str)
299 end
300 end
301
302 private fun exception(message: String, cause: nullable Error):
303 SAXParseException do
304 var e: SAXParseException
305
306 if locator == null then
307 e = new SAXParseException(message)
308 else
309 e = new SAXParseException.with_locator(message, locator.as(not null))
310 end
311 e.cause = cause
312 return e
313 end
314
315 # Fire a fatal error with the specified message and cause.
316 #
317 # Return `false`.
318 fun fire_fatal_error(message: String, cause: nullable Error):Bool do
319 var e = exception(message, cause)
320
321 if error_handler == null then
322 e.throw
323 else
324 error_handler.fatal_error(e)
325 end
326 return false
327 end
328
329 # Fire an error with the specified message and cause.
330 fun fire_error(message: String, cause: nullable Error) do
331 var e = exception(message, cause)
332
333 if error_handler != null then
334 error_handler.error(e)
335 end
336 end
337
338 # Fire a warning with the specified message and cause.
339 fun fire_warning(message: String, cause: nullable Error) do
340 var e = exception(message, cause)
341
342 if error_handler != null then
343 error_handler.warning(e)
344 end
345 end
346 end
347
348 # An XML expanded name.
349 private class XmlName
350 # Namespace IRI or `""`.
351 var uri: String
352
353 # Local name or `""`.
354 var local_name: String
355
356 # Original qualified name.
357 var qname: String
358 end