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