1package eu.siacs.conversations.xml;
2
3import android.util.Log;
4import android.util.Xml;
5import eu.siacs.conversations.Config;
6import im.conversations.android.xmpp.ExtensionFactory;
7import im.conversations.android.xmpp.model.StreamElement;
8import java.io.Closeable;
9import java.io.IOException;
10import java.io.InputStream;
11import java.io.InputStreamReader;
12import org.xmlpull.v1.XmlPullParser;
13import org.xmlpull.v1.XmlPullParserException;
14
15public class XmlReader implements Closeable {
16 private final XmlPullParser parser;
17 private InputStream is;
18
19 public XmlReader() {
20 this.parser = Xml.newPullParser();
21 try {
22 this.parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
23 } catch (XmlPullParserException e) {
24 Log.d(Config.LOGTAG, "error setting namespace feature on parser");
25 }
26 }
27
28 public void setInputStream(InputStream inputStream) throws IOException {
29 if (inputStream == null) {
30 throw new IOException();
31 }
32 this.is = inputStream;
33 try {
34 parser.setInput(new InputStreamReader(this.is));
35 } catch (XmlPullParserException e) {
36 throw new IOException("error resetting parser");
37 }
38 }
39
40 public void reset() throws IOException {
41 if (this.is == null) {
42 throw new IOException();
43 }
44 try {
45 parser.setInput(new InputStreamReader(this.is));
46 } catch (XmlPullParserException e) {
47 throw new IOException("error resetting parser");
48 }
49 }
50
51 @Override
52 public void close() {
53 this.is = null;
54 }
55
56 public Tag readTag() throws IOException {
57 try {
58 while (this.is != null && parser.next() != XmlPullParser.END_DOCUMENT) {
59 if (parser.getEventType() == XmlPullParser.START_TAG) {
60 Tag tag = Tag.start(parser.getName());
61 final String xmlns = parser.getNamespace();
62 for (int i = 0; i < parser.getAttributeCount(); ++i) {
63 final var prefix = parser.getAttributePrefix(i);
64 final var ns = parser.getAttributeNamespace(i);
65 String name;
66 if ("xml".equals(prefix)) {
67 name = "xml:" + parser.getAttributeName(i);
68 } else if (ns != null && !ns.isEmpty()) {
69 name = "{" + ns + "}" + parser.getAttributeName(i);
70 } else {
71 name = parser.getAttributeName(i);
72 }
73 tag.setAttribute(name,parser.getAttributeValue(i));
74 }
75 if (xmlns != null) {
76 tag.setAttribute("xmlns", xmlns);
77 }
78 return tag;
79 } else if (parser.getEventType() == XmlPullParser.END_TAG) {
80 return Tag.end(parser.getName());
81 } else if (parser.getEventType() == XmlPullParser.TEXT) {
82 return Tag.no(parser.getText());
83 }
84 }
85
86 } catch (Throwable throwable) {
87 throw new IOException(
88 "xml parser mishandled "
89 + throwable.getClass().getSimpleName()
90 + "("
91 + throwable.getMessage()
92 + ")",
93 throwable);
94 }
95 return null;
96 }
97
98 public <T extends StreamElement> T readElement(final Tag current, final Class<T> clazz)
99 throws IOException {
100 final Element element = readElement(current);
101 if (clazz.isInstance(element)) {
102 return clazz.cast(element);
103 }
104 throw new IOException(
105 String.format("Read unexpected {%s}%s", element.getNamespace(), element.getName()));
106 }
107
108 public Element readElement(final Tag currentTag) throws IOException {
109 final var attributes = currentTag.getAttributes();
110 final var namespace = attributes.get("xmlns");
111 final var name = currentTag.getName();
112 final Element element = ExtensionFactory.create(name, namespace);
113 element.setAttributes(currentTag.getAttributes());
114 Tag nextTag = this.readTag();
115 if (nextTag == null) {
116 throw new IOException("interrupted mid tag");
117 }
118 while (!nextTag.isEnd(element.getName())) {
119 if (nextTag.isNo()) {
120 if (nextTag.getName() != null) element.addChild(new TextNode(nextTag.getName()));
121 } else {
122 Element child = this.readElement(nextTag);
123 element.addChild(child);
124 }
125 nextTag = this.readTag();
126 if (nextTag == null) {
127 throw new IOException("interrupted mid tag");
128 }
129 }
130 return element;
131 }
132}