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 # The locator that is used to indicate the current location.
40 var locator
: nullable SAXLocatorImpl = null is writable
43 # TODO: Handle these features.
45 private var features
: Map[String, Bool] = new HashMap[String, Bool]
48 var feature_namespaces_uri
=
49 "http://xml.org/sax/features/namespaces"
52 var feature_namespace_prefixes_uri
=
53 "http://xml.org/sax/features/namespace-prefixes"
56 # SEE: `sax::XMLReader.entity_resolver`
57 var entity_resolver
: nullable EntityResolver = null is writable
59 # SEE: `sax::XMLReader.dtd_handler`
60 var dtd_handler
: nullable DTDHandler = null is writable
62 # SEE: `sax::XMLReader.content_handler`
63 var content_handler
: nullable ContentHandler = null is writable
65 # SEE: `sax::XMLReader.error_handler`
66 var error_handler
: nullable ErrorHandler = null is writable
70 qname_re
.optimize_is_in
= true
71 features
[feature_namespaces_uri
] = true
72 features
[feature_namespace_prefixes_uri
] = false
75 # SEE: `sax::XMLReader.feature_recognized`
76 fun feature_recognized
(name
: String): Bool do
77 return features
.keys
.has
(name
)
80 # SEE: `sax::XMLReader.feature_readable`
81 fun feature_readable
(name
: String): Bool do
82 return features
.keys
.has
(name
)
85 # SEE: `sax::XMLReader.feature_writable`
86 fun feature_writable
(name
: String): Bool do
87 return features
.keys
.has
(name
)
90 # SEE: `sax::XMLReader.feature`
91 fun feature
(name
: String): Bool do
92 assert feature_recognized
: feature_recognized
(name
)
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")
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")
108 features
[name
] = value
111 # SEE: `sax::XMLReader.property_recognized`
112 fun property_recognized
(name
: String): Bool do return false
114 # SEE: `sax::XMLReader.property_readable`
115 fun property_readable
(name
: String): Bool do return false
117 # SEE: `sax::XMLReader.property_writable`
118 fun property_writable
(name
: String): Bool do return false
120 # SEE: `sax::XMLReader.property`
121 fun property
(name
: String): nullable Object do
122 assert property_recognized
: false
126 # SEE: `sax::XMLReader.property=`
127 fun property
=(name
: String, value
: nullable Object) do
128 assert property_recognized
: false
131 # Is the root element closed?
132 fun root_closed
: Bool do return element_path
.length
<= 0
134 # Expect the root element is closed.
135 fun expect_root_closed
: Bool do
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)
142 return fire_fatal_error
("Reached the end of the file with an " +
143 "open element.", null)
148 ###########################################################################
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)
158 # Fire the start of the document.
159 fun fire_start_document
do
160 if content_handler
!= null then
161 content_handler
.start_document
166 # Fire the end of the document.
167 fun fire_end_document
do
168 if content_handler
!= null then
169 content_handler
.end_document
173 # Fire the start of an attribute list.
174 fun fire_start_attributes
do
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
)
184 if not prefix
.has
(":") then
185 fire_start_prefix_mapping
(prefix
, value
)
186 if not feature
(feature_namespace_prefixes_uri
) then return
190 atts
.add
("", "", qname
, "CDATA", value
)
193 # Fire the start of an element.
194 fun fire_start_element
(name
: String) do
195 var parts
= ["", "", ""]
197 for i
in [0..atts
.length
[ do
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
)
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
= ["", "", ""]
211 process_name
(atts
.qname
(index
).as(not null), name
, true)
212 atts
.uri
(index
) = name
[0]
213 atts
.local_name
(index
) = name
[1]
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 " +
224 parts
= ["", qname
, qname
]
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
]
233 # Fire the end of an element.
235 # Return `true` on success.
236 fun fire_end_element
(name
: String):Bool do
237 var peek_name
= element_path
.last
239 if peek_name
.qname
== name
then
241 if content_handler
!= null then
242 content_handler
.end_element
(peek_name
.uri
,
243 peek_name
.local_name
, peek_name
.qname
)
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)
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)
261 if content_handler
!= null then
262 content_handler
.start_prefix_mapping
(prefix
, uri
)
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
)
273 # Fire the appearance of a comment.
274 fun fire_comment
(content
: String) do
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
)
285 # Fire the start of a `CDATA` section.
286 fun fire_start_cdata
do
290 # Fire the end of a `CDATA` section.
291 fun fire_end_cdata
do
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
)
302 private fun exception
(message
: String, cause
: nullable Error):
304 var e
: SAXParseException
306 if locator
== null then
307 e
= new SAXParseException(message
)
309 e
= new SAXParseException.with_locator
(message
, locator
.as(not null))
315 # Fire a fatal error with the specified message and cause.
318 fun fire_fatal_error
(message
: String, cause
: nullable Error):Bool do
319 var e
= exception
(message
, cause
)
321 if error_handler
== null then
324 error_handler
.fatal_error
(e
)
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
)
333 if error_handler
!= null then
334 error_handler
.error
(e
)
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
)
342 if error_handler
!= null then
343 error_handler
.warning
(e
)
348 # An XML expanded name.
349 private class XmlName
350 # Namespace IRI or `""`.
353 # Local name or `""`.
354 var local_name
: String
356 # Original qualified name.