This project has retired. For details please refer to its Attic page.
AtomPubParser xref

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.chemistry.opencmis.client.bindings.spi.atompub;
20  
21  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.ATTR_DOCUMENT_TYPE;
22  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.ATTR_FOLDER_TYPE;
23  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.ATTR_POLICY_TYPE;
24  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.ATTR_RELATIONSHIP_TYPE;
25  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.CONTENT_SRC;
26  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.LINK_HREF;
27  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.LINK_REL;
28  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.LINK_TYPE;
29  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_ACL;
30  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_ALLOWABLEACTIONS;
31  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_CHILDREN;
32  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_COLLECTION;
33  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_COLLECTION_TYPE;
34  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_CONTENT;
35  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_ENTRY;
36  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_FEED;
37  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_LINK;
38  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_NUM_ITEMS;
39  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_OBJECT;
40  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_PATH_SEGMENT;
41  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_RELATIVE_PATH_SEGMENT;
42  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_REPOSITORY_INFO;
43  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_SERVICE;
44  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_TEMPLATE_TEMPLATE;
45  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_TEMPLATE_TYPE;
46  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_TYPE;
47  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_URI_TEMPLATE;
48  import static org.apache.chemistry.opencmis.client.bindings.spi.atompub.CmisAtomPubConstants.TAG_WORKSPACE;
49  
50  import java.io.InputStream;
51  import java.math.BigInteger;
52  import java.util.HashMap;
53  import java.util.Map;
54  
55  import javax.xml.bind.JAXBElement;
56  import javax.xml.bind.Unmarshaller;
57  import javax.xml.namespace.QName;
58  import javax.xml.stream.XMLInputFactory;
59  import javax.xml.stream.XMLStreamReader;
60  
61  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.AtomAcl;
62  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.AtomAllowableActions;
63  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.AtomBase;
64  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.AtomElement;
65  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.AtomEntry;
66  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.AtomFeed;
67  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.AtomLink;
68  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.RepositoryWorkspace;
69  import org.apache.chemistry.opencmis.client.bindings.spi.atompub.objects.ServiceDoc;
70  import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
71  import org.apache.chemistry.opencmis.commons.impl.Constants;
72  import org.apache.chemistry.opencmis.commons.impl.JaxBHelper;
73  import org.apache.chemistry.opencmis.commons.impl.jaxb.CmisAccessControlListType;
74  import org.apache.chemistry.opencmis.commons.impl.jaxb.CmisAllowableActionsType;
75  import org.apache.chemistry.opencmis.commons.impl.jaxb.CmisObjectType;
76  import org.apache.chemistry.opencmis.commons.impl.jaxb.CmisProperty;
77  import org.apache.chemistry.opencmis.commons.impl.jaxb.CmisPropertyId;
78  import org.apache.chemistry.opencmis.commons.impl.jaxb.CmisRepositoryInfoType;
79  import org.apache.chemistry.opencmis.commons.impl.jaxb.CmisTypeDefinitionType;
80  import org.apache.chemistry.opencmis.commons.impl.jaxb.CmisTypeDocumentDefinitionType;
81  import org.apache.chemistry.opencmis.commons.impl.jaxb.CmisTypeFolderDefinitionType;
82  import org.apache.chemistry.opencmis.commons.impl.jaxb.CmisTypePolicyDefinitionType;
83  import org.apache.chemistry.opencmis.commons.impl.jaxb.CmisTypeRelationshipDefinitionType;
84  import org.apache.chemistry.opencmis.commons.impl.jaxb.EnumPropertiesBase;
85  
86  /**
87   * AtomPub Parser.
88   */
89  public class AtomPubParser {
90  
91      // public constants
92      public static final String LINK_REL_CONTENT = "@@content@@";
93  
94      private final InputStream stream;
95      private AtomBase parseResult;
96  
97      public AtomPubParser(InputStream stream) {
98          if (stream == null) {
99              throw new IllegalArgumentException("No stream.");
100         }
101 
102         this.stream = stream;
103     }
104 
105     /**
106      * Parses the stream.
107      */
108     public void parse() throws Exception {
109         XMLInputFactory factory = XMLInputFactory.newInstance();
110         XMLStreamReader parser = factory.createXMLStreamReader(stream);
111 
112         try {
113             while (true) {
114                 int event = parser.getEventType();
115                 if (event == XMLStreamReader.START_ELEMENT) {
116                     QName name = parser.getName();
117 
118                     if (Constants.NAMESPACE_ATOM.equals(name.getNamespaceURI())) {
119                         if (TAG_FEED.equals(name.getLocalPart())) {
120                             parseResult = parseFeed(parser);
121                             break;
122                         } else if (TAG_ENTRY.equals(name.getLocalPart())) {
123                             parseResult = parseEntry(parser);
124                             break;
125                         }
126                     } else if (Constants.NAMESPACE_CMIS.equals(name.getNamespaceURI())) {
127                         if (TAG_ALLOWABLEACTIONS.equals(name.getLocalPart())) {
128                             parseResult = parseAllowableActions(parser);
129                             break;
130                         } else if (TAG_ACL.equals(name.getLocalPart())) {
131                             parseResult = parseACL(parser);
132                             break;
133                         }
134                     } else if (Constants.NAMESPACE_APP.equals(name.getNamespaceURI())) {
135                         if (TAG_SERVICE.equals(name.getLocalPart())) {
136                             parseResult = parseServiceDoc(parser);
137                             break;
138                         }
139                     }
140                 }
141 
142                 if (!next(parser)) {
143                     break;
144                 }
145             }
146 
147             parser.close();
148         } finally {
149             // make sure the stream is read and closed in all cases
150             try {
151                 byte[] buffer = new byte[4096];
152                 while (stream.read(buffer) > -1) {
153                 }
154             } catch (Exception e) {
155             }
156 
157             try {
158                 stream.close();
159             } catch (Exception e) {
160             }
161         }
162     }
163 
164     /**
165      * Return the parse results.
166      */
167     public AtomBase getResults() {
168         return parseResult;
169     }
170 
171     /**
172      * Parses a service document.
173      */
174     private static ServiceDoc parseServiceDoc(XMLStreamReader parser) throws Exception {
175         ServiceDoc result = new ServiceDoc();
176 
177         next(parser);
178 
179         while (true) {
180             int event = parser.getEventType();
181             if (event == XMLStreamReader.START_ELEMENT) {
182                 QName name = parser.getName();
183 
184                 if (Constants.NAMESPACE_APP.equals(name.getNamespaceURI())) {
185                     if (TAG_WORKSPACE.equals(name.getLocalPart())) {
186                         result.addWorkspace(parseWorkspace(parser));
187                     } else {
188                         skip(parser);
189                     }
190                 } else {
191                     skip(parser);
192                 }
193             } else if (event == XMLStreamReader.END_ELEMENT) {
194                 break;
195             } else {
196                 if (!next(parser)) {
197                     break;
198                 }
199             }
200         }
201 
202         return result;
203     }
204 
205     /**
206      * Parses a workspace element in a service document.
207      */
208     private static RepositoryWorkspace parseWorkspace(XMLStreamReader parser) throws Exception {
209         RepositoryWorkspace workspace = new RepositoryWorkspace();
210 
211         next(parser);
212 
213         while (true) {
214             int event = parser.getEventType();
215             if (event == XMLStreamReader.START_ELEMENT) {
216                 AtomElement element = parseWorkspaceElement(parser);
217 
218                 // check if we can extract the workspace id
219                 if ((element != null) && (element.getObject() instanceof CmisRepositoryInfoType)) {
220                     workspace.setId(((CmisRepositoryInfoType) element.getObject()).getRepositoryId());
221                 }
222 
223                 // add to workspace
224                 workspace.addElement(element);
225             } else if (event == XMLStreamReader.END_ELEMENT) {
226                 break;
227             } else {
228                 if (!next(parser)) {
229                     break;
230                 }
231             }
232         }
233 
234         next(parser);
235 
236         return workspace;
237     }
238 
239     /**
240      * Parses an Atom feed.
241      */
242     private AtomFeed parseFeed(XMLStreamReader parser) throws Exception {
243         AtomFeed result = new AtomFeed();
244 
245         next(parser);
246 
247         while (true) {
248             int event = parser.getEventType();
249             if (event == XMLStreamReader.START_ELEMENT) {
250                 QName name = parser.getName();
251 
252                 if (Constants.NAMESPACE_ATOM.equals(name.getNamespaceURI())) {
253                     if (TAG_LINK.equals(name.getLocalPart())) {
254                         result.addElement(parseLink(parser));
255                     } else if (TAG_ENTRY.equals(name.getLocalPart())) {
256                         result.addEntry(parseEntry(parser));
257                     } else {
258                         skip(parser);
259                     }
260                 } else if (Constants.NAMESPACE_RESTATOM.equals(name.getNamespaceURI())) {
261                     if (TAG_NUM_ITEMS.equals(name.getLocalPart())) {
262                         result.addElement(parseBigInteger(parser));
263                     } else {
264                         skip(parser);
265                     }
266                 } else {
267                     skip(parser);
268                 }
269             } else if (event == XMLStreamReader.END_ELEMENT) {
270                 break;
271             } else {
272                 if (!next(parser)) {
273                     break;
274                 }
275             }
276         }
277 
278         next(parser);
279 
280         return result;
281     }
282 
283     /**
284      * Parses an Atom entry.
285      */
286     private AtomEntry parseEntry(XMLStreamReader parser) throws Exception {
287         AtomEntry result = new AtomEntry();
288 
289         next(parser);
290 
291         // walk through all tags in entry
292         while (true) {
293             int event = parser.getEventType();
294             if (event == XMLStreamReader.START_ELEMENT) {
295                 AtomElement element = parseElement(parser);
296                 if (element != null) {
297                     // add to entry
298                     result.addElement(element);
299 
300                     // find and set object id
301                     if (element.getObject() instanceof CmisObjectType) {
302                         for (CmisProperty prop : ((CmisObjectType) element.getObject()).getProperties().getProperty()) {
303                             if (EnumPropertiesBase.CMIS_OBJECT_ID.value().equals(prop.getPropertyDefinitionId())) {
304                                 result.setId(((CmisPropertyId) prop).getValue().get(0));
305                             }
306                         }
307                     } else if (element.getObject() instanceof CmisTypeDefinitionType) {
308                         result.setId(((CmisTypeDefinitionType) element.getObject()).getId());
309                     }
310                 }
311             } else if (event == XMLStreamReader.END_ELEMENT) {
312                 break;
313             } else {
314                 if (!next(parser)) {
315                     break;
316                 }
317             }
318         }
319 
320         next(parser);
321 
322         return result;
323     }
324 
325     /**
326      * Parses an Allowable Actions document.
327      */
328     private static AtomAllowableActions parseAllowableActions(XMLStreamReader parser) throws Exception {
329         AtomElement elemenet = unmarshalElement(parser, CmisAllowableActionsType.class);
330         return new AtomAllowableActions((CmisAllowableActionsType) elemenet.getObject());
331     }
332 
333     /**
334      * Parses an ACL document.
335      */
336     private static AtomAcl parseACL(XMLStreamReader parser) throws Exception {
337         AtomElement elemenet = unmarshalElement(parser, CmisAccessControlListType.class);
338         return new AtomAcl((CmisAccessControlListType) elemenet.getObject());
339     }
340 
341     /**
342      * Parses an element.
343      */
344     private AtomElement parseElement(XMLStreamReader parser) throws Exception {
345         QName name = parser.getName();
346 
347         if (Constants.NAMESPACE_RESTATOM.equals(name.getNamespaceURI())) {
348             if (TAG_OBJECT.equals(name.getLocalPart())) {
349                 return unmarshalElement(parser, CmisObjectType.class);
350             } else if (TAG_PATH_SEGMENT.equals(name.getLocalPart())
351                     || TAG_RELATIVE_PATH_SEGMENT.equals(name.getLocalPart())) {
352                 return parseText(parser);
353             } else if (TAG_TYPE.equals(name.getLocalPart())) {
354                 // workaround for old Chemistry code - ignore the type namespace
355                 String typeAttr = parser.getAttributeValue(Constants.NAMESPACE_XSI, "type");
356                 if (typeAttr == null) {
357                     return unmarshalElement(parser, CmisTypeDefinitionType.class);
358                 } else if (typeAttr.endsWith(ATTR_DOCUMENT_TYPE)) {
359                     return unmarshalElement(parser, CmisTypeDocumentDefinitionType.class);
360                 } else if (typeAttr.endsWith(ATTR_FOLDER_TYPE)) {
361                     return unmarshalElement(parser, CmisTypeFolderDefinitionType.class);
362                 } else if (typeAttr.endsWith(ATTR_RELATIONSHIP_TYPE)) {
363                     return unmarshalElement(parser, CmisTypeRelationshipDefinitionType.class);
364                 } else if (typeAttr.endsWith(ATTR_POLICY_TYPE)) {
365                     return unmarshalElement(parser, CmisTypePolicyDefinitionType.class);
366                 }
367                 throw new CmisRuntimeException("Cannot read type definition!");
368             } else if (TAG_CHILDREN.equals(name.getLocalPart())) {
369                 return parseChildren(parser);
370             }
371         } else if (Constants.NAMESPACE_ATOM.equals(name.getNamespaceURI())) {
372             if (TAG_LINK.equals(name.getLocalPart())) {
373                 return parseLink(parser);
374             } else if (TAG_CONTENT.equals(name.getLocalPart())) {
375                 return parseAtomContentSrc(parser);
376             }
377         }
378 
379         // we don't know it - skip it
380         skip(parser);
381 
382         return null;
383     }
384 
385     /**
386      * Unmarshals a JAXB element.
387      */
388     private static <T> AtomElement unmarshalElement(XMLStreamReader parser, Class<T> cmisType) throws Exception {
389         QName name = parser.getName();
390 
391         Unmarshaller u = JaxBHelper.createUnmarshaller();
392         JAXBElement<T> object = u.unmarshal(parser, cmisType);
393 
394         return new AtomElement(name, object.getValue());
395     }
396 
397     /**
398      * Parses a children element.
399      */
400     private AtomElement parseChildren(XMLStreamReader parser) throws Exception {
401         AtomElement result = null;
402         QName childName = parser.getName();
403 
404         next(parser);
405 
406         // walk through the children tag
407         while (true) {
408             int event = parser.getEventType();
409             if (event == XMLStreamReader.START_ELEMENT) {
410                 QName name = parser.getName();
411 
412                 if (Constants.NAMESPACE_ATOM.equals(name.getNamespaceURI())) {
413                     if (TAG_FEED.equals(name.getLocalPart())) {
414                         result = new AtomElement(childName, parseFeed(parser));
415                     } else {
416                         skip(parser);
417                     }
418                 } else {
419                     skip(parser);
420                 }
421             } else if (event == XMLStreamReader.END_ELEMENT) {
422                 break;
423             } else {
424                 if (!next(parser)) {
425                     break;
426                 }
427             }
428         }
429 
430         next(parser);
431 
432         return result;
433     }
434 
435     /**
436      * Parses a workspace element.
437      */
438     private static AtomElement parseWorkspaceElement(XMLStreamReader parser) throws Exception {
439         QName name = parser.getName();
440 
441         if (Constants.NAMESPACE_RESTATOM.equals(name.getNamespaceURI())) {
442             if (TAG_REPOSITORY_INFO.equals(name.getLocalPart())) {
443                 return unmarshalElement(parser, CmisRepositoryInfoType.class);
444             } else if (TAG_URI_TEMPLATE.equals(name.getLocalPart())) {
445                 return parseTemplate(parser);
446             }
447         } else if (Constants.NAMESPACE_ATOM.equals(name.getNamespaceURI())) {
448             if (TAG_LINK.equals(name.getLocalPart())) {
449                 return parseLink(parser);
450             }
451         } else if (Constants.NAMESPACE_APP.equals(name.getNamespaceURI())) {
452             if (TAG_COLLECTION.equals(name.getLocalPart())) {
453                 return parseCollection(parser);
454             }
455         }
456 
457         // we don't know it - skip it
458         skip(parser);
459 
460         return null;
461     }
462 
463     /**
464      * Parses a collection tag.
465      */
466     private static AtomElement parseCollection(XMLStreamReader parser) throws Exception {
467         QName name = parser.getName();
468         Map<String, String> result = new HashMap<String, String>();
469 
470         result.put("href", parser.getAttributeValue(null, "href"));
471 
472         next(parser);
473 
474         while (true) {
475             int event = parser.getEventType();
476             if (event == XMLStreamReader.START_ELEMENT) {
477                 QName tagName = parser.getName();
478                 if (Constants.NAMESPACE_RESTATOM.equals(tagName.getNamespaceURI())
479                         && TAG_COLLECTION_TYPE.equals(tagName.getLocalPart())) {
480                     result.put("collectionType", readText(parser));
481                 } else {
482                     skip(parser);
483                 }
484             } else if (event == XMLStreamReader.END_ELEMENT) {
485                 break;
486             } else {
487                 if (!next(parser)) {
488                     break;
489                 }
490             }
491         }
492 
493         next(parser);
494 
495         return new AtomElement(name, result);
496     }
497 
498     /**
499      * Parses a template tag.
500      */
501     private static AtomElement parseTemplate(XMLStreamReader parser) throws Exception {
502         QName name = parser.getName();
503         Map<String, String> result = new HashMap<String, String>();
504 
505         next(parser);
506 
507         while (true) {
508             int event = parser.getEventType();
509             if (event == XMLStreamReader.START_ELEMENT) {
510                 QName tagName = parser.getName();
511                 if (Constants.NAMESPACE_RESTATOM.equals(tagName.getNamespaceURI())) {
512                     if (TAG_TEMPLATE_TEMPLATE.equals(tagName.getLocalPart())) {
513                         result.put("template", readText(parser));
514                     } else if (TAG_TEMPLATE_TYPE.equals(tagName.getLocalPart())) {
515                         result.put("type", readText(parser));
516                     } else {
517                         skip(parser);
518                     }
519                 } else {
520                     skip(parser);
521                 }
522             } else if (event == XMLStreamReader.END_ELEMENT) {
523                 break;
524             } else {
525                 if (!next(parser)) {
526                     break;
527                 }
528             }
529         }
530 
531         next(parser);
532 
533         return new AtomElement(name, result);
534     }
535 
536     /**
537      * Parses a link tag.
538      */
539     private static AtomElement parseLink(XMLStreamReader parser) throws Exception {
540         QName name = parser.getName();
541         AtomLink result = new AtomLink();
542 
543         // save attributes
544         for (int i = 0; i < parser.getAttributeCount(); i++) {
545             if (LINK_REL.equals(parser.getAttributeLocalName(i))) {
546                 result.setRel(parser.getAttributeValue(i));
547             } else if (LINK_HREF.equals(parser.getAttributeLocalName(i))) {
548                 result.setHref(parser.getAttributeValue(i));
549             } else if (LINK_TYPE.equals(parser.getAttributeLocalName(i))) {
550                 result.setType(parser.getAttributeValue(i));
551             }
552         }
553 
554         // skip enclosed tags, if any
555         skip(parser);
556 
557         return new AtomElement(name, result);
558     }
559 
560     /**
561      * Parses a link tag.
562      */
563     private static AtomElement parseAtomContentSrc(XMLStreamReader parser) throws Exception {
564         QName name = parser.getName();
565         AtomLink result = new AtomLink();
566         result.setRel(LINK_REL_CONTENT);
567 
568         // save attributes
569         for (int i = 0; i < parser.getAttributeCount(); i++) {
570             if (CONTENT_SRC.equals(parser.getAttributeLocalName(i))) {
571                 result.setHref(parser.getAttributeValue(i));
572             }
573         }
574 
575         // skip enclosed tags, if any
576         skip(parser);
577 
578         return new AtomElement(name, result);
579     }
580 
581     /**
582      * Parses a text tag.
583      */
584     private static AtomElement parseText(XMLStreamReader parser) throws Exception {
585         QName name = parser.getName();
586         return new AtomElement(name, readText(parser));
587     }
588 
589     /**
590      * Parses a text tag and convert it into an integer.
591      */
592     private static AtomElement parseBigInteger(XMLStreamReader parser) throws Exception {
593         QName name = parser.getName();
594         return new AtomElement(name, new BigInteger(readText(parser)));
595     }
596 
597     /**
598      * Parses a tag that contains text.
599      */
600     private static String readText(XMLStreamReader parser) throws Exception {
601         StringBuilder sb = new StringBuilder();
602 
603         next(parser);
604 
605         while (true) {
606             int event = parser.getEventType();
607             if (event == XMLStreamReader.END_ELEMENT) {
608                 break;
609             } else if (event == XMLStreamReader.CHARACTERS) {
610                 String s = parser.getText();
611                 if (s != null) {
612                     sb.append(s);
613                 }
614             } else if (event == XMLStreamReader.START_ELEMENT) {
615                 throw new RuntimeException("Unexpected tag: " + parser.getName());
616             }
617 
618             if (!next(parser)) {
619                 break;
620             }
621         }
622 
623         next(parser);
624 
625         return sb.toString();
626     }
627 
628     /**
629      * Skips a tag or subtree.
630      */
631     private static void skip(XMLStreamReader parser) throws Exception {
632         int level = 1;
633         while (next(parser)) {
634             int event = parser.getEventType();
635             if (event == XMLStreamReader.START_ELEMENT) {
636                 level++;
637             } else if (event == XMLStreamReader.END_ELEMENT) {
638                 level--;
639                 if (level == 0) {
640                     break;
641                 }
642             }
643         }
644 
645         next(parser);
646     }
647 
648     private static boolean next(XMLStreamReader parser) throws Exception {
649         if (parser.hasNext()) {
650             parser.next();
651             return true;
652         }
653 
654         return false;
655     }
656 }