/src/resiprocate/rutil/XMLCursor.cxx
Line | Count | Source |
1 | | #if defined(HAVE_CONFIG_H) |
2 | | #include "config.h" |
3 | | #endif |
4 | | |
5 | | #include "rutil/XMLCursor.hxx" |
6 | | #include "rutil/Logger.hxx" |
7 | | #include "rutil/WinLeakCheck.hxx" |
8 | | |
9 | | using namespace resip; |
10 | | using namespace std; |
11 | | |
12 | | #define RESIPROCATE_SUBSYSTEM Subsystem::CONTENTS |
13 | | |
14 | | /** |
15 | | Whitespace handling: |
16 | | Are the following XML fragments equivalent? |
17 | | |
18 | | Strictly interpreted, the root of the first XML document has one |
19 | | child while the root of the second XML doucment has three children. |
20 | | The line breaks and spaces after the <root> and before </root> are |
21 | | tagless children. |
22 | | |
23 | | ---> |
24 | | <root><child>child content</child></root> |
25 | | <-- |
26 | | vs. |
27 | | ---> |
28 | | <root> |
29 | | <child>child content</child> |
30 | | </root> |
31 | | <-- |
32 | | |
33 | | Treating whitespace as children is consistent with the spec but not usually |
34 | | convenient. <!ATTLIST poem xml:space (default|preserve) 'preserve'> is used to |
35 | | control whitespace handling. Supporting this switch is painful. For now, treat |
36 | | whitespace as non-significant. |
37 | | */ |
38 | | |
39 | | static char BANG[] = "!"; |
40 | | static char HYPHEN[] = "-"; |
41 | | static char LA_QUOTE[] = "<"; |
42 | | static char RA_QUOTE[] = ">"; |
43 | | static char SLASH[] = "/"; |
44 | | static char EQUALS[] = "="; |
45 | | static char DOUBLE_QUOTE[] = "\""; |
46 | | static char SINGLE_QUOTE[] = "\'"; |
47 | | //http://www.w3.org/TR/1998/REC-xml-19980210 |
48 | | static const Data COMMENT_START("<!--"); |
49 | | static const Data COMMENT_END("-->"); |
50 | | static const Data QUESTION_RA_QUOTE("?>"); |
51 | | |
52 | | // An alternative to stripping comments out in preparse |
53 | | // is to deal with them in the parse; ignore when after non-leaf element |
54 | | // put a leaf after a comment after a leaf in the first leaf's children |
55 | | // getValue() needs to copy first leaf and all 'child' leaves to mValue |
56 | | // |
57 | | // has the advantage of allowing |
58 | | // 1. lazier parsing |
59 | | // 2. embedded weirdnesses like <! > and <? > |
60 | | XMLCursor::XMLCursor(const ParseBuffer& pb) |
61 | 0 | : mCursor(nullptr), |
62 | 0 | mAttributesSet(false) |
63 | 0 | { |
64 | 0 | ParseBuffer lPb(pb); |
65 | |
|
66 | 0 | skipProlog(lPb); |
67 | 0 | const char* start = lPb.position(); |
68 | |
|
69 | 0 | lPb.skipToChars(COMMENT_START); |
70 | 0 | if (!lPb.eof()) |
71 | 0 | { |
72 | 0 | StackLog(<< "removing comments"); |
73 | 0 | lPb.reset(start); |
74 | 0 | mData.reserve((Data::size_type)(lPb.end() - lPb.start())); |
75 | |
|
76 | 0 | const char* anchor = start; |
77 | 0 | { |
78 | 0 | DataStream str(mData); |
79 | 0 | Data temp; |
80 | 0 | while (true) |
81 | 0 | { |
82 | 0 | lPb.skipToChars(COMMENT_START); |
83 | 0 | if (!lPb.eof()) |
84 | 0 | { |
85 | 0 | lPb.data(temp, anchor); |
86 | 0 | str << temp; |
87 | 0 | anchor = Node::skipComments(lPb); |
88 | 0 | } |
89 | 0 | else |
90 | 0 | { |
91 | 0 | lPb.data(temp, anchor); |
92 | 0 | str << temp; |
93 | 0 | break; |
94 | 0 | } |
95 | 0 | } |
96 | 0 | } |
97 | 0 | mRoot.reset(new Node(ParseBuffer(mData.data(), mData.size()))); |
98 | 0 | } |
99 | 0 | else |
100 | 0 | { |
101 | 0 | mRoot.reset(new Node(ParseBuffer(start, pb.end() - start))); |
102 | 0 | } |
103 | 0 | mCursor = mRoot.get(); |
104 | |
|
105 | 0 | if (mRoot->extractTag()) |
106 | 0 | { |
107 | 0 | InfoLog(<< "XML: empty element no a legal root"); |
108 | 0 | mRoot->mPb.fail(__FILE__, __LINE__); |
109 | 0 | } |
110 | |
|
111 | 0 | mTag = mRoot->mTag; |
112 | 0 | decodeName(mRoot->mTag); |
113 | | |
114 | | // check for # & and note -- make decode, decodeName do stuff if set |
115 | | |
116 | | //<top></top> // no children |
117 | 0 | ParseBuffer pbtemp(mRoot->mPb); |
118 | 0 | pbtemp.skipToChar(RA_QUOTE[0]); |
119 | 0 | pbtemp.skipChar(); |
120 | 0 | if (!WhitespaceSignificant) |
121 | 0 | { |
122 | 0 | pbtemp.skipWhitespace(); |
123 | 0 | } |
124 | 0 | if (*pbtemp.position() == LA_QUOTE[0] && |
125 | 0 | *(pbtemp.position()+1) == SLASH[0]) |
126 | 0 | { |
127 | 0 | pbtemp.skipChar(); |
128 | 0 | pbtemp.skipChar(); |
129 | 0 | if (strncmp(mRoot->mTag.data(), pbtemp.position(), mRoot->mTag.size()) == 0) |
130 | 0 | { |
131 | | // no children ever |
132 | 0 | mRoot->mPb.reset(mRoot->mPb.end()); |
133 | 0 | return; |
134 | 0 | } |
135 | 0 | } |
136 | 0 | } |
137 | | |
138 | | XMLCursor::~XMLCursor() |
139 | 0 | { |
140 | 0 | } |
141 | | |
142 | | void |
143 | | XMLCursor::skipProlog(ParseBuffer& pb) |
144 | 0 | { |
145 | | //'<?xml' VersionInfo '<xml?' EncodingDecl '?>'? '<?xml' SDDecl '?>'? S? '?> |
146 | | |
147 | | // !dlb! much more complicated than this.. can contain comments |
148 | 0 | const char* start = pb.position(); |
149 | 0 | pb.skipToChars(QUESTION_RA_QUOTE); |
150 | 0 | if(pb.eof()) |
151 | 0 | { |
152 | | // No Prolog |
153 | 0 | pb.reset(start); |
154 | 0 | return; |
155 | 0 | } |
156 | 0 | pb.skipN(2); |
157 | 0 | pb.skipWhitespace(); |
158 | 0 | } |
159 | | |
160 | | void |
161 | | XMLCursor::decode(Data& text) |
162 | 0 | { |
163 | 0 | } |
164 | | |
165 | | void |
166 | | XMLCursor::decodeName(Data& name) |
167 | 0 | { |
168 | 0 | } |
169 | | |
170 | | void |
171 | | XMLCursor::parseNextRootChild() |
172 | 0 | { |
173 | | // no next child to parse? |
174 | 0 | if (mRoot->mPb.eof()) |
175 | 0 | { |
176 | 0 | return; |
177 | 0 | } |
178 | | |
179 | | // next child already parsed? |
180 | 0 | if (mRoot->mNext != mRoot->mChildren.end()) |
181 | 0 | { |
182 | 0 | return; |
183 | 0 | } |
184 | | |
185 | | // skip self tag |
186 | 0 | if (mRoot->mPb.position() == mRoot->mPb.start()) |
187 | 0 | { |
188 | 0 | mRoot->mPb.skipToChar(RA_QUOTE[0]); |
189 | 0 | mRoot->mPb.skipChar(); |
190 | 0 | } |
191 | |
|
192 | 0 | if (!WhitespaceSignificant) |
193 | 0 | { |
194 | 0 | mRoot->mPb.skipWhitespace(); |
195 | 0 | } |
196 | | |
197 | | // root end tag? |
198 | 0 | if (*mRoot->mPb.position() == LA_QUOTE[0]) |
199 | 0 | { |
200 | 0 | ParseBuffer pb(mRoot->mPb.position(), |
201 | 0 | mRoot->mPb.end() - mRoot->mPb.position()); |
202 | 0 | pb.skipChar(); |
203 | 0 | if (!pb.eof() && *pb.position() == SLASH[0]) |
204 | 0 | { |
205 | 0 | pb.skipChar(); |
206 | | // CodeWarrior isn't helpful enough to pick the "obvious" operator definition |
207 | | // so we add volatile here so CW is completely unconfused what to do. |
208 | | // second note - MSVC 7.0 won't compile the volatile - tried the following to fix |
209 | 0 | const char* end = pb.position(); |
210 | 0 | if ( (const char*)pb.end() < end + mTag.size() ) |
211 | 0 | { |
212 | 0 | InfoLog(<< "XML: unexpected end"); |
213 | 0 | pb.fail(__FILE__, __LINE__); |
214 | 0 | } |
215 | | |
216 | 0 | if (strncmp(mTag.data(), pb.position(), mRoot->mTag.size()) == 0) |
217 | 0 | { |
218 | 0 | mRoot->mPb.skipToEnd(); |
219 | 0 | return; |
220 | 0 | } |
221 | 0 | } |
222 | 0 | } |
223 | | |
224 | | // leaf? |
225 | 0 | if (*mRoot->mPb.position() != LA_QUOTE[0]) |
226 | 0 | { |
227 | 0 | const char* anchor = mRoot->mPb.position(); |
228 | 0 | mRoot->mPb.skipToChar(LA_QUOTE[0]); |
229 | 0 | Node* leaf = new Node(ParseBuffer(anchor, mRoot->mPb.position() - anchor)); |
230 | 0 | leaf->mIsLeaf = true; |
231 | 0 | mRoot->addChild(leaf); |
232 | 0 | } |
233 | 0 | else |
234 | 0 | { |
235 | | // Owned here until it is attached to the root: the nodes parsed below |
236 | | // it are already attached to it, so if the parse fails, freeing the |
237 | | // child frees the whole partly parsed subtree. |
238 | 0 | std::unique_ptr<Node> child(new Node(mRoot->mPb)); |
239 | 0 | child->skipToEndTag(1); // a child of the root is one level down |
240 | | |
241 | | // leave the parse buffer after the child |
242 | 0 | mRoot->mPb.reset(child->mPb.end()); |
243 | |
|
244 | 0 | mRoot->addChild(child.release()); |
245 | 0 | } |
246 | | |
247 | | // mNext always points at cursored child |
248 | 0 | mRoot->mNext = mRoot->mChildren.end(); |
249 | 0 | mRoot->mNext--; |
250 | 0 | } |
251 | | |
252 | | bool |
253 | | XMLCursor::nextSibling() |
254 | 0 | { |
255 | 0 | if (atRoot()) |
256 | 0 | { |
257 | 0 | StackLog(<< "XMLCursor::nextSibling" << *this->mCursor << " <<root>>"); |
258 | 0 | return false; |
259 | 0 | } |
260 | | |
261 | 0 | StackLog(<< "XMLCursor::nextSibling" << *this->mCursor << " " << *this->mCursor->mParent); |
262 | 0 | if (mCursor->mParent == mRoot.get()) |
263 | 0 | { |
264 | 0 | parseNextRootChild(); |
265 | 0 | } |
266 | |
|
267 | 0 | if (mCursor->mParent->mNext != mCursor->mParent->mChildren.end()) |
268 | 0 | { |
269 | 0 | mCursor = *((mCursor->mParent->mNext)++); |
270 | 0 | mAttributesSet = false; |
271 | 0 | return true; |
272 | 0 | } |
273 | 0 | else |
274 | 0 | { |
275 | 0 | return false; |
276 | 0 | } |
277 | 0 | } |
278 | | |
279 | | bool |
280 | | XMLCursor::firstChild() |
281 | 0 | { |
282 | 0 | if (atRoot() && |
283 | 0 | mRoot->mChildren.empty()) |
284 | 0 | { |
285 | 0 | parseNextRootChild(); |
286 | 0 | } |
287 | |
|
288 | 0 | if (mCursor->mChildren.empty()) |
289 | 0 | { |
290 | 0 | return false; |
291 | 0 | } |
292 | 0 | else |
293 | 0 | { |
294 | | // mNext always points after cursored child |
295 | 0 | mCursor->mNext = mCursor->mChildren.begin(); |
296 | 0 | mCursor->mNext++; |
297 | 0 | mCursor = mCursor->mChildren.front(); |
298 | 0 | mAttributesSet = false; |
299 | 0 | return true; |
300 | 0 | } |
301 | 0 | } |
302 | | |
303 | | bool |
304 | | XMLCursor::parent() |
305 | 0 | { |
306 | 0 | if (atRoot()) |
307 | 0 | { |
308 | 0 | return false; |
309 | 0 | } |
310 | | |
311 | 0 | mCursor = mCursor->mParent; |
312 | 0 | mAttributesSet = false; |
313 | 0 | return true; |
314 | 0 | } |
315 | | |
316 | | void |
317 | | XMLCursor::reset() |
318 | 0 | { |
319 | 0 | mCursor = mRoot.get(); |
320 | 0 | mAttributesSet = false; |
321 | 0 | } |
322 | | |
323 | | bool |
324 | | XMLCursor::atRoot() const |
325 | 0 | { |
326 | 0 | return mCursor == mRoot.get(); |
327 | 0 | } |
328 | | |
329 | | bool |
330 | | XMLCursor::atLeaf() const |
331 | 0 | { |
332 | 0 | return mCursor->mIsLeaf; |
333 | 0 | } |
334 | | |
335 | | const Data& |
336 | | XMLCursor::getTag() const |
337 | 0 | { |
338 | 0 | return mCursor->mTag; |
339 | 0 | } |
340 | | |
341 | | //<foo > |
342 | | //<foo> |
343 | | //<foo/> |
344 | | //<foo attr = 'value' attr="value"> |
345 | | //<foo attr = 'value' attr="value" > |
346 | | // |
347 | | //<foo attr = 'value' attr="value" /> |
348 | | static const Data RA_QUOTE_SLASH(">/"); |
349 | | const XMLCursor::AttributeMap& |
350 | | XMLCursor::getAttributes() const |
351 | 0 | { |
352 | 0 | if (!atLeaf() && |
353 | 0 | !mAttributesSet) |
354 | 0 | { |
355 | 0 | mAttributes.clear(); |
356 | |
|
357 | 0 | ParseBuffer pb(mCursor->mPb); |
358 | 0 | pb.reset(mCursor->mPb.start()); |
359 | |
|
360 | 0 | Data attribute; |
361 | 0 | Data value; |
362 | |
|
363 | 0 | pb.skipToOneOf(ParseBuffer::Whitespace, RA_QUOTE_SLASH); |
364 | 0 | pb.skipWhitespace(); |
365 | |
|
366 | 0 | while (!pb.eof() && |
367 | 0 | *pb.position() != RA_QUOTE[0] && |
368 | 0 | *pb.position() != SLASH[0]) |
369 | 0 | { |
370 | 0 | attribute.clear(); |
371 | 0 | value.clear(); |
372 | |
|
373 | 0 | const char* anchor = pb.skipWhitespace(); |
374 | 0 | pb.skipToOneOf(ParseBuffer::Whitespace, EQUALS); |
375 | 0 | pb.data(attribute, anchor); |
376 | 0 | XMLCursor::decodeName(attribute); |
377 | |
|
378 | 0 | StackLog(<< "attribute: " << attribute); |
379 | |
|
380 | 0 | pb.skipWhitespace(); |
381 | 0 | pb.skipToChar(EQUALS[0]); |
382 | 0 | pb.skipChar(); |
383 | 0 | pb.skipWhitespace(); |
384 | 0 | if (!pb.eof()) |
385 | 0 | { |
386 | 0 | const char quote = *pb.position(); |
387 | |
|
388 | 0 | StackLog(<< "quote is <" << quote << ">"); |
389 | |
|
390 | 0 | if(quote != DOUBLE_QUOTE[0] && |
391 | 0 | quote != SINGLE_QUOTE[0]) |
392 | 0 | { |
393 | 0 | InfoLog(<< "XML: badly quoted attribute value"); |
394 | 0 | pb.fail(__FILE__, __LINE__); |
395 | 0 | } |
396 | 0 | anchor = pb.skipChar(); |
397 | 0 | pb.skipToChar(quote); |
398 | 0 | pb.data(value, anchor); |
399 | 0 | XMLCursor::decode(value); |
400 | 0 | pb.skipChar(); |
401 | 0 | if (mAttributes.size() >= MaxAttributes && mAttributes.count(attribute) == 0) |
402 | 0 | { |
403 | 0 | InfoLog(<< "XML: too many attributes on element " << getTag()); |
404 | 0 | pb.fail(__FILE__, __LINE__, "Too many XML attributes"); |
405 | 0 | } |
406 | 0 | mAttributes[attribute] = value; |
407 | 0 | } |
408 | 0 | pb.skipWhitespace(); |
409 | 0 | } |
410 | | // Only once complete, so a failed parse fails again rather than leaving a partial map |
411 | 0 | mAttributesSet = true; |
412 | 0 | } |
413 | |
|
414 | 0 | return mAttributes; |
415 | 0 | } |
416 | | |
417 | | const Data& |
418 | | XMLCursor::getValue() const |
419 | 0 | { |
420 | 0 | if (atLeaf()) |
421 | 0 | { |
422 | 0 | ParseBuffer pb(mCursor->mPb); |
423 | 0 | pb.skipToEnd(); |
424 | 0 | pb.data(mValue, pb.start()); |
425 | 0 | XMLCursor::decode(mValue); |
426 | 0 | } |
427 | 0 | else |
428 | 0 | { |
429 | 0 | mValue.clear(); |
430 | 0 | } |
431 | 0 | return mValue; |
432 | 0 | } |
433 | | |
434 | | EncodeStream& |
435 | | XMLCursor::encode(EncodeStream& str, const AttributeMap& attrs) |
436 | 0 | { |
437 | 0 | for(AttributeMap::const_iterator i = attrs.begin(); |
438 | 0 | i != attrs.end(); ++i) |
439 | 0 | { |
440 | 0 | if (i != attrs.begin()) |
441 | 0 | { |
442 | 0 | str << " "; |
443 | 0 | } |
444 | | // !dlb! some sort of character encoding required here |
445 | 0 | str << i->first << "=\"" << i->second << "\""; |
446 | 0 | } |
447 | |
|
448 | 0 | return str; |
449 | 0 | } |
450 | | |
451 | | XMLCursor::Node::Node(const ParseBuffer& pb) |
452 | 0 | : mPb(pb.position(), pb.end() - pb.position()), |
453 | 0 | mParent(0), |
454 | 0 | mChildren(), |
455 | 0 | mNext(mChildren.begin()), |
456 | 0 | mIsLeaf(false) |
457 | 0 | { |
458 | 0 | mPb.assertNotEof(); |
459 | 0 | StackLog(<< "XMLCursor::Node::Node" << *this); |
460 | 0 | } |
461 | | |
462 | | XMLCursor::Node::~Node() |
463 | 0 | { |
464 | 0 | for (vector<Node*>::iterator i = mChildren.begin(); |
465 | 0 | i != mChildren.end(); ++i) |
466 | 0 | { |
467 | 0 | delete *i; |
468 | 0 | } |
469 | 0 | } |
470 | | |
471 | | // start: |
472 | | //<foo > |
473 | | //^ |
474 | | // end: |
475 | | //<foo > |
476 | | // ^ |
477 | | static Data SLASH_RA_QUOTE("/>"); |
478 | | bool |
479 | | XMLCursor::Node::extractTag() |
480 | 0 | { |
481 | 0 | ParseBuffer pb(mPb); |
482 | 0 | if (!WhitespaceSignificant) |
483 | 0 | { |
484 | 0 | pb.skipWhitespace(); |
485 | 0 | } |
486 | 0 | const char* anchor = pb.skipChar(LA_QUOTE[0]); |
487 | 0 | pb.skipToOneOf(ParseBuffer::Whitespace, SLASH_RA_QUOTE); |
488 | 0 | pb.assertNotEof(); |
489 | 0 | pb.data(mTag, anchor); |
490 | |
|
491 | 0 | return !pb.eof() && *pb.position() == SLASH[0]; |
492 | 0 | } |
493 | | |
494 | | void |
495 | | XMLCursor::Node::addChild(Node* child) |
496 | 0 | { |
497 | 0 | mChildren.push_back(child); |
498 | 0 | child->mParent = this; |
499 | 0 | } |
500 | | |
501 | | //<foo> <bar> </bar> <baz> </baz> </foo> |
502 | | //^start |
503 | | // ^child |
504 | | // ^child |
505 | | // ^end |
506 | | // |
507 | | //<foo> sdfsf sadfsf <bar> asdfdf </bar> sadfsdf </foo> |
508 | | //^start |
509 | | // ^child |
510 | | // ^child sub |
511 | | // ^child |
512 | | void |
513 | | XMLCursor::Node::skipToEndTag(unsigned int depth) |
514 | 0 | { |
515 | | // Bound the recursion. Each nested element adds a stack frame here, and |
516 | | // another in ~Node() when the tree is torn down, so without a limit a |
517 | | // document with very deep nesting (many thousands of elements) would recurse |
518 | | // until the stack is exhausted. Failing the parse throws ParseException, |
519 | | // which callers already treat as a malformed body. |
520 | 0 | if (depth > MaxNestingDepth) |
521 | 0 | { |
522 | 0 | InfoLog(<< "XML: nesting too deep"); |
523 | 0 | mPb.fail(__FILE__, __LINE__, "XML nesting too deep"); |
524 | 0 | } |
525 | |
|
526 | 0 | extractTag(); |
527 | 0 | StackLog(<< "XMLCursor::Node::skipToEndTag(" << mTag << ")"); |
528 | | //StackLog(<< "XMLCursor::Node::skipToEndTag(" << Data(mPb.position(), mPb.end() - mPb.position()) << ")"); |
529 | | |
530 | | //<foo /> |
531 | 0 | mPb.skipToChar(RA_QUOTE[0]); |
532 | 0 | if (*(mPb.position()-1) == SLASH[0]) |
533 | 0 | { |
534 | 0 | mPb.skipChar(); |
535 | 0 | mPb = ParseBuffer(mPb.start(), mPb.position() - mPb.start()); |
536 | 0 | return; |
537 | 0 | } |
538 | | |
539 | | //<foo> ...<child> ... </child> </foo> |
540 | | // ^ |
541 | 0 | mPb.skipChar(); |
542 | | //<foo> ...<child> ... </child> </foo> |
543 | | // ^ |
544 | 0 | while (true) |
545 | 0 | { |
546 | 0 | if (!WhitespaceSignificant) |
547 | 0 | { |
548 | 0 | mPb.skipWhitespace(); |
549 | 0 | } |
550 | | |
551 | | // Some text contents ...< |
552 | | // ^ ^ |
553 | 0 | if (*mPb.position() != LA_QUOTE[0]) |
554 | 0 | { |
555 | 0 | const char* anchor = mPb.position(); |
556 | 0 | mPb.skipToChar(LA_QUOTE[0]); |
557 | 0 | Node* leaf = new Node(ParseBuffer(anchor, mPb.position() - anchor)); |
558 | 0 | leaf->mIsLeaf = true; |
559 | 0 | addChild(leaf); |
560 | 0 | } |
561 | | |
562 | | //<... |
563 | | //^ |
564 | 0 | mPb.skipChar(); |
565 | | //<... |
566 | | // ^ |
567 | | |
568 | | // exit condition |
569 | | //</foo> |
570 | 0 | if (*mPb.position() == SLASH[0]) |
571 | 0 | { |
572 | 0 | mPb.skipChar(); |
573 | | // CodeWarrior isn't helpful enough to pick the "obvious" operator definition |
574 | | // so we add volatile here so CW is completely unconfused what to do. |
575 | | // second note - MSVC 7.0 won't compile the volatile - tried the following to fix |
576 | 0 | const char* end = mPb.position(); |
577 | 0 | if ( (const char*)mPb.end() < end + mTag.size() ) |
578 | 0 | { |
579 | 0 | InfoLog(<< "XML: unexpected end"); |
580 | 0 | mPb.fail(__FILE__, __LINE__); |
581 | 0 | } |
582 | |
|
583 | 0 | if (strncmp(mTag.data(), mPb.position(), mTag.size()) == 0) |
584 | 0 | { |
585 | 0 | mPb.skipToChar(RA_QUOTE[0]); |
586 | 0 | mPb.skipChar(); |
587 | 0 | mPb = ParseBuffer(mPb.start(), mPb.position() - mPb.start()); |
588 | 0 | return; |
589 | 0 | } |
590 | 0 | else |
591 | 0 | { |
592 | 0 | InfoLog(<< "Badly formed XML: unexpected endtag"); |
593 | 0 | mPb.fail(__FILE__, __LINE__); |
594 | 0 | } |
595 | 0 | } |
596 | | |
597 | | //<child>... |
598 | | // ^ |
599 | 0 | if (mPb.position() == mPb.start()) |
600 | 0 | { |
601 | 0 | InfoLog(<< "XML: badly formed element"); |
602 | 0 | mPb.fail(__FILE__, __LINE__); |
603 | 0 | } |
604 | |
|
605 | 0 | mPb.reset(mPb.position()-1); |
606 | | //<child>... |
607 | | //^ |
608 | 0 | Node* child = new Node(mPb); |
609 | 0 | addChild(child); |
610 | 0 | child->skipToEndTag(depth + 1); |
611 | 0 | mPb.reset(child->mPb.end()); |
612 | 0 | XMLCursor::decodeName(child->mTag); |
613 | 0 | StackLog(<< mTag << "(" << child->mTag << ")"); |
614 | 0 | } |
615 | 0 | } |
616 | | |
617 | | //<!-- declarations for <head> & <body> --> |
618 | | const char* |
619 | | XMLCursor::Node::skipComments(ParseBuffer& pb) |
620 | 0 | { |
621 | 0 | while (*pb.position() == LA_QUOTE[0] && |
622 | 0 | *(pb.position()+1) == BANG[0] && |
623 | 0 | *(pb.position()+2) == HYPHEN[0] && |
624 | 0 | *(pb.position()+3) == HYPHEN[0]) |
625 | 0 | { |
626 | 0 | pb.skipToChars(COMMENT_END); |
627 | 0 | pb.skipChars(COMMENT_END); |
628 | 0 | pb.skipWhitespace(); |
629 | 0 | if(pb.eof()) |
630 | 0 | { |
631 | 0 | return pb.end(); |
632 | 0 | } |
633 | 0 | } |
634 | | |
635 | 0 | return pb.position(); |
636 | 0 | } |
637 | | |
638 | | EncodeStream& |
639 | | resip::operator<<(EncodeStream& str, const XMLCursor::Node& node) |
640 | 0 | { |
641 | 0 | Data::size_type size = (Data::size_type)(node.mPb.end() - node.mPb.start()); |
642 | |
|
643 | 0 | static const Data::size_type showSize(35); |
644 | |
|
645 | 0 | str << &node << "[" |
646 | 0 | << Data(node.mPb.start(), |
647 | 0 | min(showSize, size)) |
648 | 0 | << "]" << (size ? "" : "..."); |
649 | |
|
650 | 0 | return str; |
651 | 0 | } |
652 | | |
653 | | EncodeStream& |
654 | | resip::operator<<(EncodeStream& str, const XMLCursor& cursor) |
655 | 0 | { |
656 | 0 | str << "XMLCursor " << *cursor.mCursor; |
657 | 0 | return str; |
658 | 0 | } |
659 | | |
660 | | /* ==================================================================== |
661 | | * The Vovida Software License, Version 1.0 |
662 | | * |
663 | | * Copyright (c) 2026 SIP Spectrum, Inc. https://www.sipspectrum.com |
664 | | * Copyright (c) 2000 Vovida Networks, Inc. All rights reserved. |
665 | | * |
666 | | * Redistribution and use in source and binary forms, with or without |
667 | | * modification, are permitted provided that the following conditions |
668 | | * are met: |
669 | | * |
670 | | * 1. Redistributions of source code must retain the above copyright |
671 | | * notice, this list of conditions and the following disclaimer. |
672 | | * |
673 | | * 2. Redistributions in binary form must reproduce the above copyright |
674 | | * notice, this list of conditions and the following disclaimer in |
675 | | * the documentation and/or other materials provided with the |
676 | | * distribution. |
677 | | * |
678 | | * 3. The names "VOCAL", "Vovida Open Communication Application Library", |
679 | | * and "Vovida Open Communication Application Library (VOCAL)" must |
680 | | * not be used to endorse or promote products derived from this |
681 | | * software without prior written permission. For written |
682 | | * permission, please contact vocal@vovida.org. |
683 | | * |
684 | | * 4. Products derived from this software may not be called "VOCAL", nor |
685 | | * may "VOCAL" appear in their name, without prior written |
686 | | * permission of Vovida Networks, Inc. |
687 | | * |
688 | | * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED |
689 | | * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
690 | | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND |
691 | | * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA |
692 | | * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES |
693 | | * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL, |
694 | | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
695 | | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
696 | | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
697 | | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
698 | | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE |
699 | | * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH |
700 | | * DAMAGE. |
701 | | * |
702 | | * ==================================================================== |
703 | | * |
704 | | * This software consists of voluntary contributions made by Vovida |
705 | | * Networks, Inc. and many individuals on behalf of Vovida Networks, |
706 | | * Inc. For more information on Vovida Networks, Inc., please see |
707 | | * <http://www.vovida.org/>. |
708 | | * |
709 | | */ |