26de0d2f44a61296ec37af8c9ccb443f012f303c
[nit.git] / contrib / neo_doxygen / src / doxml / listener.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Basic SAX listeners.
16 module doxml::listener
17
18 import saxophonit
19 import model
20 import language_specific
21
22 # Common abstractions for SAX listeners reading XML documents generated by Doxygen.
23 abstract class DoxmlListener
24 super ContentHandler
25
26 # The locator setted by calling `document_locator=`.
27 protected var locator: nullable SAXLocator = null
28
29 # The project graph.
30 fun graph: ProjectGraph is abstract
31
32 # The language-specific strategies to use.
33 fun source_language: SourceLanguage is abstract
34
35 redef fun document_locator=(locator: SAXLocator) do
36 self.locator = locator
37 end
38
39 protected fun dox_uri: String do return ""
40
41 redef fun start_element(uri: String, local_name: String, qname: String,
42 atts: Attributes) do
43 super
44 if uri != dox_uri then return # None of our business.
45 start_dox_element(local_name, atts)
46 end
47
48 # Process the start of an element in the Doxygen’s namespace.
49 #
50 # See `ContentHandler.start_element` for the description of the parameters.
51 protected fun start_dox_element(local_name: String, atts: Attributes) do end
52
53 redef fun end_element(uri: String, local_name: String, qname: String) do
54 super
55 if uri != dox_uri then return # None of our business.
56 end_dox_element(local_name)
57 end
58
59 # Process the end of an element in the Doxygen’s namespace.
60 #
61 # See `ContentHandler.start_element` for the description of the parameters.
62 protected fun end_dox_element(local_name: String) do end
63
64 protected fun get_bool(atts: Attributes, local_name: String): Bool do
65 return get_optional(atts, local_name, "no") == "yes"
66 end
67
68 # Get the value of an optional attribute.
69 #
70 # Parameters:
71 #
72 # * `atts`: attribute list.
73 # * `local_name`: local name of the attribute.
74 # * `default`: value to return when the specified attribute is not found.
75 protected fun get_optional(atts: Attributes, local_name: String,
76 default: String): String do
77 return atts.value_ns(dox_uri, local_name) or else default
78 end
79
80 # Get the value of an required attribute.
81 #
82 # Parameters:
83 #
84 # * `atts`: attribute list.
85 # * `local_name`: local name of the attribute.
86 protected fun get_required(atts: Attributes, local_name: String): String do
87 var value = atts.value_ns(dox_uri, local_name)
88 if value == null then
89 throw_error("The `{local_name}` attribute is required.")
90 return ""
91 else
92 return value
93 end
94 end
95
96 redef fun end_document do
97 locator = null
98 end
99
100 # Throw an error with the specified message by prepending the current location.
101 protected fun throw_error(message: String) do
102 var e: SAXParseException
103
104 if locator != null then
105 e = new SAXParseException.with_locator(message, locator.as(not null))
106 else
107 e = new SAXParseException(message)
108 end
109 e.throw
110 end
111 end
112
113 # A `DoxmlListener` that read only a part of a document.
114 #
115 # Temporary redirect events to itself until it ends processing its part.
116 abstract class StackableListener
117 super DoxmlListener
118
119 # The associated reader.
120 var reader: XMLReader
121
122 # The parent listener.
123 var parent: DoxmlListener
124
125 # Namespace’s IRI of the element at the root of the part to process.
126 private var root_uri: String = ""
127
128 # Local name of the element at the root of the part to process.
129 private var root_local_name: String = ""
130
131 # The number of open element of the same type than the root of the part to process.
132 private var depth = 0
133
134 # The project graph.
135 private var p_graph: ProjectGraph is noinit
136
137 # The language-specific strategies to use.
138 private var p_source: SourceLanguage is noinit
139
140
141 init do
142 super
143 p_graph = parent.graph
144 p_source = parent.source_language
145 end
146
147 redef fun graph do return p_graph
148 redef fun source_language do return p_source
149
150 # Temporary redirect events to itself until the end of the specified element.
151 fun listen_until(uri: String, local_name: String) do
152 root_uri = uri
153 root_local_name = local_name
154 depth = 1
155 reader.content_handler = self
156 locator = parent.locator
157 end
158
159 redef fun start_element(uri: String, local_name: String, qname: String,
160 atts: Attributes) do
161 super
162 if uri == root_uri and local_name == root_local_name then
163 depth += 1
164 end
165 end
166
167 redef fun end_element(uri: String, local_name: String, qname: String) do
168 super
169 if uri == root_uri and local_name == root_local_name then
170 depth -= 1
171 if depth <= 0 then
172 end_listening
173 parent.end_element(uri, local_name, qname)
174 end
175 end
176 end
177
178 # Reset the reader’s listener to the parent.
179 fun end_listening do
180 reader.content_handler = parent
181 locator = null
182 end
183
184 redef fun end_document do
185 end_listening
186 end
187 end
188
189 # A SAX listener that skips any event except the end of the part to process.
190 #
191 # Used to skip an entire element.
192 class NoopListener
193 super StackableListener
194 end
195
196 # Concatenates any text node found.
197 class TextListener
198 super StackableListener
199
200 protected var buffer: Buffer = new FlatBuffer
201 private var sp: Bool = false
202
203 redef fun listen_until(uri: String, local_name: String) do
204 buffer.clear
205 sp = false
206 super
207 end
208
209 redef fun characters(str: String) do
210 if sp then
211 if buffer.length > 0 then buffer.append(" ")
212 sp = false
213 end
214 buffer.append(str)
215 end
216
217 redef fun ignorable_whitespace(str: String) do
218 sp = true
219 end
220
221 # Flush the buffer.
222 protected fun flush_buffer: String do
223 var s = buffer.to_s
224
225 buffer.clear
226 sp = false
227 return s
228 end
229
230 redef fun to_s do return buffer.to_s
231 end
232
233 # Processes a content of type `linkedTextType`.
234 abstract class LinkedTextListener[T: LinkedText]
235 super TextListener
236
237 # The read text.
238 var linked_text: T is noinit
239
240 private var refid = ""
241
242 # Create a new instance of `T`.
243 protected fun create_linked_text: T is abstract
244
245 redef fun listen_until(uri: String, local_name: String) do
246 linked_text = create_linked_text
247 refid = ""
248 super
249 end
250
251 redef fun start_dox_element(local_name: String, atts: Attributes) do
252 super
253 push_part
254 if "ref" == local_name then refid = get_required(atts, "refid")
255 end
256
257 redef fun end_dox_element(local_name: String) do
258 super
259 push_part
260 if "ref" == local_name then refid = ""
261 end
262
263 private fun push_part do
264 var s = flush_buffer
265
266 if not s.is_empty then
267 linked_text.add_part(s, refid)
268 end
269 end
270
271 redef fun to_s do return linked_text.to_s
272 end
273
274 # Processes the content of a `<type>` element.
275 class TypeListener
276 super LinkedTextListener[RawType]
277
278 private var raw_type: RawType is noinit
279
280 redef fun create_linked_text do return new RawType(graph)
281 end