1 # This file is part of NIT ( http://www.nitlanguage.org ).
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
12 module saxophonit
::reader_model
15 import sax
::helpers
::sax_locator_impl
16 import sax
::helpers
::attributes_impl
17 import sax
::helpers
::namespace_support
21 # Handle event dispatching, settings and internal state.
22 class XophonReaderModel
24 # Stack of the current element type qname and of the qnames of the ancestors.
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]
30 # Current element’s attributes
31 private var atts
= new AttributesImpl
33 private var ns
= new NamespaceSupport
35 # Regular expression to match a `Name` against the `QName` production of
36 # “Namespaces in XML”.
37 private var qname_re
: Regex = "^[^:]+(:[^:]+)?$".to_re
39 var locator
: nullable SAXLocatorImpl = null is writable
42 # TODO: Handle these features.
44 private var features
: Map[String, Bool] = new HashMap[String, Bool]
47 var feature_namespaces_uri
=
48 "http://xml.org/sax/features/namespaces"
51 var feature_namespace_prefixes_uri
=
52 "http://xml.org/sax/features/namespace-prefixes"
55 # SEE: `sax::XMLReader.entity_resolver`
56 var entity_resolver
: nullable EntityResolver = null is writable
58 # SEE: `sax::XMLReader.dtd_handler`
59 var dtd_handler
: nullable DTDHandler = null is writable
61 # SEE: `sax::XMLReader.content_handler`
62 var content_handler
: nullable ContentHandler = null is writable
64 # SEE: `sax::XMLReader.error_handler`
65 var error_handler
: nullable ErrorHandler = null is writable
69 qname_re
.optimize_is_in
= true
70 features
[feature_namespaces_uri
] = true
71 features
[feature_namespace_prefixes_uri
] = false
74 # SEE: `sax::XMLReader.feature_recognized`
75 fun feature_recognized
(name
: String): Bool do
76 return features
.keys
.has
(name
)
79 # SEE: `sax::XMLReader.feature_readable`
80 fun feature_readable
(name
: String): Bool do
81 return features
.keys
.has
(name
)
84 # SEE: `sax::XMLReader.feature_writable`
85 fun feature_writable
(name
: String): Bool do
86 return features
.keys
.has
(name
)
89 # SEE: `sax::XMLReader.feature`
90 fun feature
(name
: String): Bool do
91 assert feature_recognized
: feature_recognized
(name
)
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")
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")
107 features
[name
] = value
110 # SEE: `sax::XMLReader.property_recognized`
111 fun property_recognized
(name
: String): Bool do return false
113 # SEE: `sax::XMLReader.property_readable`
114 fun property_readable
(name
: String): Bool do return false
116 # SEE: `sax::XMLReader.property_writable`
117 fun property_writable
(name
: String): Bool do return false
119 # SEE: `sax::XMLReader.property`
120 fun property
(name
: String): nullable Object do
121 assert property_recognized
: false
125 # SEE: `sax::XMLReader.property=`
126 fun property
=(name
: String, value
: nullable Object) do
127 assert property_recognized
: false
130 # Is the root element closed?
131 fun root_closed
: Bool do return element_path
.length
<= 0
133 # Expect the root element is closed.
134 fun expect_root_closed
: Bool do
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)
141 return fire_fatal_error
("Reached the end of the file with an " +
142 "open element.", null)
147 ###########################################################################
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)
157 # Fire the start of the document.
158 fun fire_start_document
do
159 if content_handler
!= null then
160 content_handler
.start_document
165 # Fire the end of the document.
166 fun fire_end_document
do
167 if content_handler
!= null then
168 content_handler
.end_document
172 # Fire the start of an attribute list.
173 fun fire_start_attributes
do
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
)
183 if not prefix
.has
(":") then
184 fire_start_prefix_mapping
(prefix
, value
)
185 if not feature
(feature_namespace_prefixes_uri
) then return
189 atts
.add
("", "", qname
, "CDATA", value
)
192 # Fire the start of an element.
193 fun fire_start_element
(name
: String) do
194 var parts
= ["", "", ""]
196 for i
in [0..atts
.length
[ do
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
)
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
= ["", "", ""]
210 process_name
(atts
.qname
(index
).as(not null), name
, true)
211 atts
.uri
(index
) = name
[0]
212 atts
.local_name
(index
) = name
[1]
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 " +
223 parts
= ["", qname
, qname
]
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
]
232 # Fire the end of an element.
234 # Return `true` on success.
235 fun fire_end_element
(name
: String):Bool do
236 var peek_name
= element_path
.last
238 if peek_name
.qname
== name
then
240 if content_handler
!= null then
241 content_handler
.end_element
(peek_name
.uri
,
242 peek_name
.local_name
, peek_name
.qname
)
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)
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)
260 if content_handler
!= null then
261 content_handler
.start_prefix_mapping
(prefix
, uri
)
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
)
272 # Fire the appearance of a comment.
273 fun fire_comment
(content
: String) do
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
)
284 # Fire the start of a `CDATA` section.
285 fun fire_start_cdata
do
289 # Fire the end of a `CDATA` section.
290 fun fire_end_cdata
do
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
)
301 private fun exception
(message
: String, cause
: nullable Error):
303 var e
: SAXParseException
305 if locator
== null then
306 e
= new SAXParseException(message
)
308 e
= new SAXParseException.with_locator
(message
, locator
.as(not null))
314 # Fire a fatal error with the specified message and cause.
317 fun fire_fatal_error
(message
: String, cause
: nullable Error):Bool do
318 var e
= exception
(message
, cause
)
320 if error_handler
== null then
323 error_handler
.fatal_error
(e
)
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
)
332 if error_handler
!= null then
333 error_handler
.error
(e
)
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
)
341 if error_handler
!= null then
342 error_handler
.warning
(e
)
347 # An XML expanded name.
348 private class XmlName
349 # Namespace IRI or `""`.
352 # Local name or `""`.
353 var local_name
: String
355 # Original qualified name.