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 // TODO we would also look at parser.getAttributeNamespace()
64 final String prefix = parser.getAttributePrefix(i);
65 String name;
66 if (prefix != null && !prefix.isEmpty()) {
67 name = prefix + ":" + parser.getAttributeName(i);
68 } else {
69 name = parser.getAttributeName(i);
70 }
71 tag.setAttribute(name, parser.getAttributeValue(i));
72 }
73 if (xmlns != null) {
74 tag.setAttribute("xmlns", xmlns);
75 }
76 return tag;
77 } else if (parser.getEventType() == XmlPullParser.END_TAG) {
78 return Tag.end(parser.getName());
79 } else if (parser.getEventType() == XmlPullParser.TEXT) {
80 return Tag.no(parser.getText());
81 }
82 }
83
84 } catch (Throwable throwable) {
85 throw new IOException(
86 "xml parser mishandled "
87 + throwable.getClass().getSimpleName()
88 + "("
89 + throwable.getMessage()
90 + ")",
91 throwable);
92 }
93 return null;
94 }
95
96 public <T extends StreamElement> T readElement(final Tag current, final Class<T> clazz)
97 throws IOException {
98 final Element element = readElement(current);
99 if (clazz.isInstance(element)) {
100 return clazz.cast(element);
101 }
102 throw new IOException(
103 String.format("Read unexpected {%s}%s", element.getNamespace(), element.getName()));
104 }
105
106 public Element readElement(final Tag currentTag) throws IOException {
107 final var attributes = currentTag.getAttributes();
108 final var namespace = attributes.get("xmlns");
109 final var name = currentTag.getName();
110 final Element element = ExtensionFactory.create(name, namespace);
111 element.setAttributes(currentTag.getAttributes());
112 Tag nextTag = this.readTag();
113 if (nextTag == null) {
114 throw new IOException("interrupted mid tag");
115 }
116 if (nextTag.isNo()) {
117 element.setContent(nextTag.getName());
118 nextTag = this.readTag();
119 if (nextTag == null) {
120 throw new IOException("interrupted mid tag");
121 }
122 }
123 while (!nextTag.isEnd(element.getName())) {
124 if (!nextTag.isNo()) {
125 Element child = this.readElement(nextTag);
126 element.addChild(child);
127 }
128 nextTag = this.readTag();
129 if (nextTag == null) {
130 throw new IOException("interrupted mid tag");
131 }
132 }
133 return element;
134 }
135}