-rw-r--r-- | libopie/xmltree.cc | 324 | ||||
-rw-r--r-- | libopie2/opiecore/xmltree.cc | 324 |
2 files changed, 648 insertions, 0 deletions
diff --git a/libopie/xmltree.cc b/libopie/xmltree.cc new file mode 100644 index 0000000..9b8dd05 --- a/dev/null +++ b/libopie/xmltree.cc | |||
@@ -0,0 +1,324 @@ | |||
1 | /* This file is part of the KDE project | ||
2 | Copyright (C) 2001 Simon Hausmann <hausmann@kde.org> | ||
3 | |||
4 | This program is free software; you can redistribute it and/or modify | ||
5 | it under the terms of the GNU General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or | ||
7 | (at your option) any later version. | ||
8 | |||
9 | This program is distributed in the hope that it will be useful, | ||
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | GNU General Public License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU General Public License | ||
15 | along with this program; if not, write to the Free Software | ||
16 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
17 | |||
18 | As a special exception this program may be linked with Qt non-commercial | ||
19 | edition, the resulting executable be distributed, without including the | ||
20 | source code for the Qt non-commercial edition in the source distribution. | ||
21 | |||
22 | */ | ||
23 | |||
24 | |||
25 | #include "xmltree.h" | ||
26 | |||
27 | #include <qxml.h> | ||
28 | |||
29 | #include <assert.h> | ||
30 | |||
31 | XMLElement::XMLElement() | ||
32 | : m_parent( 0 ), m_next( 0 ), m_prev( 0 ), m_first( 0 ), m_last( 0 ) | ||
33 | { | ||
34 | } | ||
35 | |||
36 | XMLElement::~XMLElement() | ||
37 | { | ||
38 | XMLElement *n = m_first; | ||
39 | |||
40 | while ( n ) | ||
41 | { | ||
42 | XMLElement *tmp = n; | ||
43 | n = n->m_next; | ||
44 | delete tmp; | ||
45 | } | ||
46 | } | ||
47 | |||
48 | void XMLElement::appendChild( XMLElement *child ) | ||
49 | { | ||
50 | if ( child->m_parent ) | ||
51 | child->m_parent->removeChild( child ); | ||
52 | |||
53 | child->m_parent = this; | ||
54 | |||
55 | if ( m_last ) | ||
56 | m_last->m_next = child; | ||
57 | |||
58 | child->m_prev = m_last; | ||
59 | |||
60 | if ( !m_first ) | ||
61 | m_first = child; | ||
62 | |||
63 | m_last = child; | ||
64 | } | ||
65 | |||
66 | void XMLElement::insertAfter( XMLElement *newChild, XMLElement *refChild ) | ||
67 | { | ||
68 | assert( newChild != refChild ); | ||
69 | |||
70 | if ( refChild == m_last ) | ||
71 | { | ||
72 | appendChild( newChild ); | ||
73 | return; | ||
74 | } | ||
75 | |||
76 | assert( refChild ); | ||
77 | assert( refChild->m_parent ); | ||
78 | assert( refChild->m_parent == this ); | ||
79 | |||
80 | if ( newChild->m_parent && newChild != refChild ) | ||
81 | newChild->m_parent->removeChild( newChild ); | ||
82 | |||
83 | newChild->m_parent = this; | ||
84 | |||
85 | XMLElement *next = refChild->m_next; | ||
86 | |||
87 | refChild->m_next = newChild; | ||
88 | |||
89 | newChild->m_prev = refChild; | ||
90 | newChild->m_next = next; | ||
91 | |||
92 | if ( next ) | ||
93 | next->m_prev = newChild; | ||
94 | } | ||
95 | |||
96 | void XMLElement::insertBefore( XMLElement *newChild, XMLElement *refChild ) | ||
97 | { | ||
98 | assert( refChild ); | ||
99 | assert( refChild->m_parent ); | ||
100 | assert( refChild->m_parent == this ); | ||
101 | assert( newChild != refChild ); | ||
102 | |||
103 | if ( newChild->m_parent && newChild != refChild ) | ||
104 | newChild->m_parent->removeChild( newChild ); | ||
105 | |||
106 | newChild->m_parent = this; | ||
107 | |||
108 | XMLElement *prev = refChild->m_prev; | ||
109 | |||
110 | refChild->m_prev = newChild; | ||
111 | |||
112 | newChild->m_prev = prev; | ||
113 | newChild->m_next = refChild; | ||
114 | |||
115 | if ( prev ) | ||
116 | prev->m_next = newChild; | ||
117 | |||
118 | if ( refChild == m_first ) | ||
119 | m_first = newChild; | ||
120 | } | ||
121 | |||
122 | void XMLElement::removeChild( XMLElement *child ) | ||
123 | { | ||
124 | if ( child->m_parent != this ) | ||
125 | return; | ||
126 | |||
127 | if ( m_first == child ) | ||
128 | m_first = child->m_next; | ||
129 | |||
130 | if ( m_last == child ) | ||
131 | m_last = child->m_prev; | ||
132 | |||
133 | if ( child->m_prev ) | ||
134 | child->m_prev->m_next = child->m_next; | ||
135 | |||
136 | if ( child->m_next ) | ||
137 | child->m_next->m_prev = child->m_prev; | ||
138 | |||
139 | child->m_parent = 0; | ||
140 | child->m_prev = 0; | ||
141 | child->m_next = 0; | ||
142 | } | ||
143 | |||
144 | void XMLElement::save( QTextStream &s, uint indent ) | ||
145 | { | ||
146 | if ( !m_value.isEmpty() ) | ||
147 | { | ||
148 | s << encodeAttr( m_value ); | ||
149 | return; | ||
150 | } | ||
151 | |||
152 | for ( uint i = 0; i < indent; ++i ) | ||
153 | s << " "; | ||
154 | |||
155 | s << "<" << m_tag; | ||
156 | |||
157 | if ( !m_attributes.isEmpty() ) | ||
158 | { | ||
159 | s << " "; | ||
160 | AttributeMap::ConstIterator it = m_attributes.begin(); | ||
161 | AttributeMap::ConstIterator end = m_attributes.end(); | ||
162 | for (; it != end; ++it ) | ||
163 | { | ||
164 | s << it.key() << "=\"" << encodeAttr( it.data() ) << "\""; | ||
165 | s << " "; | ||
166 | } | ||
167 | } | ||
168 | |||
169 | if ( m_last ) | ||
170 | { | ||
171 | if ( ( m_first && !m_first->value().isEmpty() ) || !m_parent ) | ||
172 | s << ">"; | ||
173 | else | ||
174 | s << ">" << endl; | ||
175 | |||
176 | int newIndent = indent; | ||
177 | if ( m_parent ) | ||
178 | newIndent++; | ||
179 | |||
180 | XMLElement *n = m_first; | ||
181 | while ( n ) | ||
182 | { | ||
183 | n->save( s, newIndent ); | ||
184 | n = n->nextChild(); | ||
185 | } | ||
186 | |||
187 | if ( m_last && m_last->value().isEmpty() && m_parent ) | ||
188 | for ( uint i = 0; i < indent; ++i ) | ||
189 | s << " "; | ||
190 | |||
191 | if ( m_parent ) | ||
192 | s << "</" << m_tag << ">" << endl; | ||
193 | } | ||
194 | else | ||
195 | s << "/>" << endl; | ||
196 | } | ||
197 | /** | ||
198 | Encode an attribute value upon saving. | ||
199 | replaces '"' with """ | ||
200 | replaces '<' with "<" | ||
201 | replaces '&' with "&" | ||
202 | replaces '>' with ">" | ||
203 | */ | ||
204 | QString XMLElement::encodeAttr( const QString& str ) | ||
205 | { | ||
206 | QString encAttr( str ); // cause of the const parameter | ||
207 | encAttr = encAttr.replace( QRegExp( "[<]" ), "<" ); | ||
208 | encAttr = encAttr.replace( QRegExp( "[>]" ), ">" ); | ||
209 | encAttr = encAttr.replace( QRegExp( "[\"]" ), """ ); | ||
210 | encAttr = encAttr.replace( QRegExp( "[&]" ), "&" ); | ||
211 | return encAttr; | ||
212 | } | ||
213 | |||
214 | class Handler : public QXmlDefaultHandler | ||
215 | { | ||
216 | public: | ||
217 | Handler() : m_node( 0 ), m_root( 0 ) {} | ||
218 | |||
219 | XMLElement *root() const { return m_root; } | ||
220 | |||
221 | virtual bool startDocument(); | ||
222 | virtual bool endDocument(); | ||
223 | virtual bool startElement( const QString &ns, const QString &ln, const QString &qName, | ||
224 | const QXmlAttributes &attr ); | ||
225 | virtual bool endElement( const QString &ns, const QString &ln, const QString &qName ); | ||
226 | virtual bool characters( const QString &ch ); | ||
227 | |||
228 | private: | ||
229 | XMLElement *m_node; | ||
230 | XMLElement *m_root; | ||
231 | }; | ||
232 | |||
233 | bool Handler::startDocument() | ||
234 | { | ||
235 | m_root = m_node = new XMLElement; | ||
236 | |||
237 | return true; | ||
238 | } | ||
239 | |||
240 | bool Handler::endDocument() | ||
241 | { | ||
242 | return m_root == m_node; | ||
243 | } | ||
244 | |||
245 | bool Handler::startElement( const QString &, const QString &, const QString &qName, | ||
246 | const QXmlAttributes &attr ) | ||
247 | { | ||
248 | XMLElement *bm = new XMLElement; | ||
249 | |||
250 | XMLElement::AttributeMap attributes; | ||
251 | for ( int i = 0; i < attr.length(); ++i ) | ||
252 | attributes[ attr.qName( i ) ] = attr.value( i ); | ||
253 | |||
254 | bm->setAttributes( attributes ); | ||
255 | |||
256 | bm->setTagName( qName ); | ||
257 | |||
258 | m_node->appendChild( bm ); | ||
259 | m_node = bm; | ||
260 | |||
261 | return true; | ||
262 | } | ||
263 | |||
264 | bool Handler::endElement( const QString &, const QString &, const QString & ) | ||
265 | { | ||
266 | if ( m_node == m_root ) | ||
267 | return false; | ||
268 | |||
269 | m_node = m_node->parent(); | ||
270 | return true; | ||
271 | } | ||
272 | |||
273 | bool Handler::characters( const QString &ch ) | ||
274 | { | ||
275 | XMLElement *textNode = new XMLElement; | ||
276 | textNode->setValue( ch ); | ||
277 | m_node->appendChild( textNode ); | ||
278 | return true; | ||
279 | } | ||
280 | |||
281 | XMLElement *XMLElement::namedItem( const QString &name ) | ||
282 | { | ||
283 | XMLElement *e = m_first; | ||
284 | |||
285 | for (; e; e = e->nextChild() ) | ||
286 | if ( e->tagName() == name ) | ||
287 | return e; | ||
288 | |||
289 | return 0; | ||
290 | } | ||
291 | |||
292 | XMLElement *XMLElement::clone() const | ||
293 | { | ||
294 | XMLElement *res = new XMLElement; | ||
295 | |||
296 | res->setTagName( m_tag ); | ||
297 | res->setValue( m_value ); | ||
298 | res->setAttributes( m_attributes ); | ||
299 | |||
300 | XMLElement *e = m_first; | ||
301 | for (; e; e = e->m_next ) | ||
302 | res->appendChild( e->clone() ); | ||
303 | |||
304 | return res; | ||
305 | } | ||
306 | |||
307 | XMLElement *XMLElement::load( const QString &fileName ) | ||
308 | { | ||
309 | QFile f( fileName ); | ||
310 | if ( !f.open( IO_ReadOnly ) ) | ||
311 | return 0; | ||
312 | |||
313 | QTextStream stream( &f ); | ||
314 | stream.setEncoding( QTextStream::UnicodeUTF8 ); | ||
315 | QXmlInputSource src( stream ); | ||
316 | QXmlSimpleReader reader; | ||
317 | Handler handler; | ||
318 | |||
319 | reader.setFeature( "http://trolltech.com/xml/features/report-whitespace-only-CharData", false ); | ||
320 | reader.setContentHandler( &handler ); | ||
321 | reader.parse( src ); | ||
322 | |||
323 | return handler.root();; | ||
324 | } | ||
diff --git a/libopie2/opiecore/xmltree.cc b/libopie2/opiecore/xmltree.cc new file mode 100644 index 0000000..9b8dd05 --- a/dev/null +++ b/libopie2/opiecore/xmltree.cc | |||
@@ -0,0 +1,324 @@ | |||
1 | /* This file is part of the KDE project | ||
2 | Copyright (C) 2001 Simon Hausmann <hausmann@kde.org> | ||
3 | |||
4 | This program is free software; you can redistribute it and/or modify | ||
5 | it under the terms of the GNU General Public License as published by | ||
6 | the Free Software Foundation; either version 2 of the License, or | ||
7 | (at your option) any later version. | ||
8 | |||
9 | This program is distributed in the hope that it will be useful, | ||
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
12 | GNU General Public License for more details. | ||
13 | |||
14 | You should have received a copy of the GNU General Public License | ||
15 | along with this program; if not, write to the Free Software | ||
16 | Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
17 | |||
18 | As a special exception this program may be linked with Qt non-commercial | ||
19 | edition, the resulting executable be distributed, without including the | ||
20 | source code for the Qt non-commercial edition in the source distribution. | ||
21 | |||
22 | */ | ||
23 | |||
24 | |||
25 | #include "xmltree.h" | ||
26 | |||
27 | #include <qxml.h> | ||
28 | |||
29 | #include <assert.h> | ||
30 | |||
31 | XMLElement::XMLElement() | ||
32 | : m_parent( 0 ), m_next( 0 ), m_prev( 0 ), m_first( 0 ), m_last( 0 ) | ||
33 | { | ||
34 | } | ||
35 | |||
36 | XMLElement::~XMLElement() | ||
37 | { | ||
38 | XMLElement *n = m_first; | ||
39 | |||
40 | while ( n ) | ||
41 | { | ||
42 | XMLElement *tmp = n; | ||
43 | n = n->m_next; | ||
44 | delete tmp; | ||
45 | } | ||
46 | } | ||
47 | |||
48 | void XMLElement::appendChild( XMLElement *child ) | ||
49 | { | ||
50 | if ( child->m_parent ) | ||
51 | child->m_parent->removeChild( child ); | ||
52 | |||
53 | child->m_parent = this; | ||
54 | |||
55 | if ( m_last ) | ||
56 | m_last->m_next = child; | ||
57 | |||
58 | child->m_prev = m_last; | ||
59 | |||
60 | if ( !m_first ) | ||
61 | m_first = child; | ||
62 | |||
63 | m_last = child; | ||
64 | } | ||
65 | |||
66 | void XMLElement::insertAfter( XMLElement *newChild, XMLElement *refChild ) | ||
67 | { | ||
68 | assert( newChild != refChild ); | ||
69 | |||
70 | if ( refChild == m_last ) | ||
71 | { | ||
72 | appendChild( newChild ); | ||
73 | return; | ||
74 | } | ||
75 | |||
76 | assert( refChild ); | ||
77 | assert( refChild->m_parent ); | ||
78 | assert( refChild->m_parent == this ); | ||
79 | |||
80 | if ( newChild->m_parent && newChild != refChild ) | ||
81 | newChild->m_parent->removeChild( newChild ); | ||
82 | |||
83 | newChild->m_parent = this; | ||
84 | |||
85 | XMLElement *next = refChild->m_next; | ||
86 | |||
87 | refChild->m_next = newChild; | ||
88 | |||
89 | newChild->m_prev = refChild; | ||
90 | newChild->m_next = next; | ||
91 | |||
92 | if ( next ) | ||
93 | next->m_prev = newChild; | ||
94 | } | ||
95 | |||
96 | void XMLElement::insertBefore( XMLElement *newChild, XMLElement *refChild ) | ||
97 | { | ||
98 | assert( refChild ); | ||
99 | assert( refChild->m_parent ); | ||
100 | assert( refChild->m_parent == this ); | ||
101 | assert( newChild != refChild ); | ||
102 | |||
103 | if ( newChild->m_parent && newChild != refChild ) | ||
104 | newChild->m_parent->removeChild( newChild ); | ||
105 | |||
106 | newChild->m_parent = this; | ||
107 | |||
108 | XMLElement *prev = refChild->m_prev; | ||
109 | |||
110 | refChild->m_prev = newChild; | ||
111 | |||
112 | newChild->m_prev = prev; | ||
113 | newChild->m_next = refChild; | ||
114 | |||
115 | if ( prev ) | ||
116 | prev->m_next = newChild; | ||
117 | |||
118 | if ( refChild == m_first ) | ||
119 | m_first = newChild; | ||
120 | } | ||
121 | |||
122 | void XMLElement::removeChild( XMLElement *child ) | ||
123 | { | ||
124 | if ( child->m_parent != this ) | ||
125 | return; | ||
126 | |||
127 | if ( m_first == child ) | ||
128 | m_first = child->m_next; | ||
129 | |||
130 | if ( m_last == child ) | ||
131 | m_last = child->m_prev; | ||
132 | |||
133 | if ( child->m_prev ) | ||
134 | child->m_prev->m_next = child->m_next; | ||
135 | |||
136 | if ( child->m_next ) | ||
137 | child->m_next->m_prev = child->m_prev; | ||
138 | |||
139 | child->m_parent = 0; | ||
140 | child->m_prev = 0; | ||
141 | child->m_next = 0; | ||
142 | } | ||
143 | |||
144 | void XMLElement::save( QTextStream &s, uint indent ) | ||
145 | { | ||
146 | if ( !m_value.isEmpty() ) | ||
147 | { | ||
148 | s << encodeAttr( m_value ); | ||
149 | return; | ||
150 | } | ||
151 | |||
152 | for ( uint i = 0; i < indent; ++i ) | ||
153 | s << " "; | ||
154 | |||
155 | s << "<" << m_tag; | ||
156 | |||
157 | if ( !m_attributes.isEmpty() ) | ||
158 | { | ||
159 | s << " "; | ||
160 | AttributeMap::ConstIterator it = m_attributes.begin(); | ||
161 | AttributeMap::ConstIterator end = m_attributes.end(); | ||
162 | for (; it != end; ++it ) | ||
163 | { | ||
164 | s << it.key() << "=\"" << encodeAttr( it.data() ) << "\""; | ||
165 | s << " "; | ||
166 | } | ||
167 | } | ||
168 | |||
169 | if ( m_last ) | ||
170 | { | ||
171 | if ( ( m_first && !m_first->value().isEmpty() ) || !m_parent ) | ||
172 | s << ">"; | ||
173 | else | ||
174 | s << ">" << endl; | ||
175 | |||
176 | int newIndent = indent; | ||
177 | if ( m_parent ) | ||
178 | newIndent++; | ||
179 | |||
180 | XMLElement *n = m_first; | ||
181 | while ( n ) | ||
182 | { | ||
183 | n->save( s, newIndent ); | ||
184 | n = n->nextChild(); | ||
185 | } | ||
186 | |||
187 | if ( m_last && m_last->value().isEmpty() && m_parent ) | ||
188 | for ( uint i = 0; i < indent; ++i ) | ||
189 | s << " "; | ||
190 | |||
191 | if ( m_parent ) | ||
192 | s << "</" << m_tag << ">" << endl; | ||
193 | } | ||
194 | else | ||
195 | s << "/>" << endl; | ||
196 | } | ||
197 | /** | ||
198 | Encode an attribute value upon saving. | ||
199 | replaces '"' with """ | ||
200 | replaces '<' with "<" | ||
201 | replaces '&' with "&" | ||
202 | replaces '>' with ">" | ||
203 | */ | ||
204 | QString XMLElement::encodeAttr( const QString& str ) | ||
205 | { | ||
206 | QString encAttr( str ); // cause of the const parameter | ||
207 | encAttr = encAttr.replace( QRegExp( "[<]" ), "<" ); | ||
208 | encAttr = encAttr.replace( QRegExp( "[>]" ), ">" ); | ||
209 | encAttr = encAttr.replace( QRegExp( "[\"]" ), """ ); | ||
210 | encAttr = encAttr.replace( QRegExp( "[&]" ), "&" ); | ||
211 | return encAttr; | ||
212 | } | ||
213 | |||
214 | class Handler : public QXmlDefaultHandler | ||
215 | { | ||
216 | public: | ||
217 | Handler() : m_node( 0 ), m_root( 0 ) {} | ||
218 | |||
219 | XMLElement *root() const { return m_root; } | ||
220 | |||
221 | virtual bool startDocument(); | ||
222 | virtual bool endDocument(); | ||
223 | virtual bool startElement( const QString &ns, const QString &ln, const QString &qName, | ||
224 | const QXmlAttributes &attr ); | ||
225 | virtual bool endElement( const QString &ns, const QString &ln, const QString &qName ); | ||
226 | virtual bool characters( const QString &ch ); | ||
227 | |||
228 | private: | ||
229 | XMLElement *m_node; | ||
230 | XMLElement *m_root; | ||
231 | }; | ||
232 | |||
233 | bool Handler::startDocument() | ||
234 | { | ||
235 | m_root = m_node = new XMLElement; | ||
236 | |||
237 | return true; | ||
238 | } | ||
239 | |||
240 | bool Handler::endDocument() | ||
241 | { | ||
242 | return m_root == m_node; | ||
243 | } | ||
244 | |||
245 | bool Handler::startElement( const QString &, const QString &, const QString &qName, | ||
246 | const QXmlAttributes &attr ) | ||
247 | { | ||
248 | XMLElement *bm = new XMLElement; | ||
249 | |||
250 | XMLElement::AttributeMap attributes; | ||
251 | for ( int i = 0; i < attr.length(); ++i ) | ||
252 | attributes[ attr.qName( i ) ] = attr.value( i ); | ||
253 | |||
254 | bm->setAttributes( attributes ); | ||
255 | |||
256 | bm->setTagName( qName ); | ||
257 | |||
258 | m_node->appendChild( bm ); | ||
259 | m_node = bm; | ||
260 | |||
261 | return true; | ||
262 | } | ||
263 | |||
264 | bool Handler::endElement( const QString &, const QString &, const QString & ) | ||
265 | { | ||
266 | if ( m_node == m_root ) | ||
267 | return false; | ||
268 | |||
269 | m_node = m_node->parent(); | ||
270 | return true; | ||
271 | } | ||
272 | |||
273 | bool Handler::characters( const QString &ch ) | ||
274 | { | ||
275 | XMLElement *textNode = new XMLElement; | ||
276 | textNode->setValue( ch ); | ||
277 | m_node->appendChild( textNode ); | ||
278 | return true; | ||
279 | } | ||
280 | |||
281 | XMLElement *XMLElement::namedItem( const QString &name ) | ||
282 | { | ||
283 | XMLElement *e = m_first; | ||
284 | |||
285 | for (; e; e = e->nextChild() ) | ||
286 | if ( e->tagName() == name ) | ||
287 | return e; | ||
288 | |||
289 | return 0; | ||
290 | } | ||
291 | |||
292 | XMLElement *XMLElement::clone() const | ||
293 | { | ||
294 | XMLElement *res = new XMLElement; | ||
295 | |||
296 | res->setTagName( m_tag ); | ||
297 | res->setValue( m_value ); | ||
298 | res->setAttributes( m_attributes ); | ||
299 | |||
300 | XMLElement *e = m_first; | ||
301 | for (; e; e = e->m_next ) | ||
302 | res->appendChild( e->clone() ); | ||
303 | |||
304 | return res; | ||
305 | } | ||
306 | |||
307 | XMLElement *XMLElement::load( const QString &fileName ) | ||
308 | { | ||
309 | QFile f( fileName ); | ||
310 | if ( !f.open( IO_ReadOnly ) ) | ||
311 | return 0; | ||
312 | |||
313 | QTextStream stream( &f ); | ||
314 | stream.setEncoding( QTextStream::UnicodeUTF8 ); | ||
315 | QXmlInputSource src( stream ); | ||
316 | QXmlSimpleReader reader; | ||
317 | Handler handler; | ||
318 | |||
319 | reader.setFeature( "http://trolltech.com/xml/features/report-whitespace-only-CharData", false ); | ||
320 | reader.setContentHandler( &handler ); | ||
321 | reader.parse( src ); | ||
322 | |||
323 | return handler.root();; | ||
324 | } | ||