1package eu.siacs.conversations.xml;
2
3import androidx.annotation.NonNull;
4
5import com.google.common.base.Optional;
6import com.google.common.collect.ImmutableList;
7import com.google.common.collect.ImmutableMap;
8import com.google.common.base.Strings;
9import com.google.common.primitives.Ints;
10import com.google.common.primitives.Longs;
11
12import java.util.ArrayList;
13import java.util.Collection;
14import java.util.Hashtable;
15import java.util.List;
16import java.util.stream.Collectors;
17
18import eu.siacs.conversations.utils.XmlHelper;
19import eu.siacs.conversations.xmpp.InvalidJid;
20import eu.siacs.conversations.xmpp.Jid;
21import im.conversations.android.xmpp.model.stanza.Message;
22
23public class Element implements Node {
24 private final String name;
25 private Hashtable<String, String> attributes = new Hashtable<>();
26 private List<Element> children = new ArrayList<>();
27 private List<Node> childNodes = new ArrayList<>();
28
29 public Element(String name) {
30 this.name = name;
31 }
32
33 public Element(String name, String xmlns) {
34 this.name = name;
35 this.setAttribute("xmlns", xmlns);
36 }
37
38 public Node prependChild(Node child) {
39 childNodes.add(0, child);
40 if (child instanceof Element) children.add(0, (Element) child);
41 return child;
42 }
43
44 public Node addChild(Node child) {
45 childNodes.add(child);
46 if (child instanceof Element) children.add((Element) child);
47 return child;
48 }
49
50 public Element addChild(String name) {
51 Element child = new Element(name);
52 childNodes.add(child);
53 children.add(child);
54 return child;
55 }
56
57 public Element addChild(String name, String xmlns) {
58 Element child = new Element(name);
59 child.setAttribute("xmlns", xmlns);
60 childNodes.add(child);
61 children.add(child);
62 return child;
63 }
64
65 public void addChildren(final Collection<? extends Node> children) {
66 if (children == null) return;
67
68 this.childNodes.addAll(children);
69 for (Node node : children) {
70 if (node instanceof Element) {
71 this.children.add((Element) node);
72 }
73 }
74 }
75
76 public void removeChild(Node child) {
77 if (child == null) return;
78
79 this.childNodes.remove(child);
80 if (child instanceof Element) this.children.remove(child);
81 }
82
83 public Element setContent(String content) {
84 clearChildren();
85 if (content != null) this.childNodes.add(new TextNode(content));
86 return this;
87 }
88
89 public Element findChild(String name) {
90 for (Element child : this.children) {
91 if (child.getName().equals(name)) {
92 return child;
93 }
94 }
95 return null;
96 }
97
98 public String findChildContent(String name) {
99 Element element = findChild(name);
100 return element == null ? null : element.getContent();
101 }
102
103 public LocalizedContent findInternationalizedChildContentInDefaultNamespace(String name) {
104 return LocalizedContent.get(this, name);
105 }
106
107 public Element findChild(String name, String xmlns) {
108 for (Element child : getChildren()) {
109 if (name.equals(child.getName()) && xmlns.equals(child.getAttribute("xmlns"))) {
110 return child;
111 }
112 }
113 return null;
114 }
115
116 public Element findChildEnsureSingle(String name, String xmlns) {
117 final List<Element> results = new ArrayList<>();
118 for (Element child : getChildren()) {
119 if (name.equals(child.getName()) && xmlns.equals(child.getAttribute("xmlns"))) {
120 results.add(child);
121 }
122 }
123 if (results.size() == 1) {
124 return results.get(0);
125 }
126 return null;
127 }
128
129 public String findChildContent(String name, String xmlns) {
130 Element element = findChild(name,xmlns);
131 return element == null ? null : element.getContent();
132 }
133
134 public boolean hasChild(final String name) {
135 return findChild(name) != null;
136 }
137
138 public boolean hasChild(final String name, final String xmlns) {
139 return findChild(name, xmlns) != null;
140 }
141
142 public final List<Element> getChildren() {
143 return ImmutableList.copyOf(this.children);
144 }
145
146 public void setAttribute(final String name, final boolean value) {
147 this.setAttribute(name, value ? "1" : "0");
148 }
149
150 // Deprecated: you probably want bindTo or replaceChildren
151 public Element setChildren(List<Element> children) {
152 this.childNodes = new ArrayList(children);
153 this.children = new ArrayList(children);
154 return this;
155 }
156
157 public void replaceChildren(List<Element> children) {
158 this.childNodes.clear();
159 this.childNodes.addAll(children);
160 this.children.clear();
161 this.children.addAll(children);
162 }
163
164 public void bindTo(Element original) {
165 this.attributes = original.attributes;
166 this.childNodes = original.childNodes;
167 this.children = original.children;
168 }
169
170 public final String getContent() {
171 return this.childNodes.stream().map(Node::getContent).filter(c -> c != null).collect(Collectors.joining());
172 }
173
174 public long getLongAttribute(final String name) {
175 final var value = Longs.tryParse(Strings.nullToEmpty(this.attributes.get(name)));
176 return value == null ? 0 : value;
177 }
178
179 public Optional<Integer> getOptionalIntAttribute(final String name) {
180 final String value = getAttribute(name);
181 if (value == null) {
182 return Optional.absent();
183 }
184 return Optional.fromNullable(Ints.tryParse(value));
185 }
186
187 public Jid getAttributeAsJid(String name) {
188 final String jid = this.getAttribute(name);
189 if (jid != null && !jid.isEmpty()) {
190 try {
191 return Jid.ofEscaped(jid);
192 } catch (final IllegalArgumentException e) {
193 return InvalidJid.of(jid, this instanceof Message);
194 }
195 }
196 return null;
197 }
198
199 public Element setAttribute(String name, String value) {
200 if (name != null && value != null) {
201 this.attributes.put(name, value);
202 }
203 return this;
204 }
205
206 public Element setAttribute(String name, Jid value) {
207 if (name != null && value != null) {
208 this.attributes.put(name, value.toEscapedString());
209 }
210 return this;
211 }
212
213 public String toString() {
214 return toString(ImmutableMap.of());
215 }
216
217 public String toString(final ImmutableMap<String, String> parentNS) {
218 final var mutns = new Hashtable<>(parentNS);
219 final StringBuilder elementOutput = new StringBuilder();
220 if (childNodes.size() == 0) {
221 final var attr = getSerializableAttributes(mutns);
222 Tag emptyTag = Tag.empty(name);
223 emptyTag.setAttributes(attr);
224 elementOutput.append(emptyTag.toString());
225 } else {
226 final var ns = ImmutableMap.copyOf(mutns);
227 final var startTag = startTag(mutns);
228 elementOutput.append(startTag);
229 for (Node child : ImmutableList.copyOf(childNodes)) {
230 elementOutput.append(child.toString(ns));
231 }
232 elementOutput.append(endTag());
233 }
234 return elementOutput.toString();
235 }
236
237 public Tag startTag() {
238 return startTag(new Hashtable<>());
239 }
240
241 public Tag startTag(final Hashtable<String, String> mutns) {
242 final var attr = getSerializableAttributes(mutns);
243 final var startTag = Tag.start(name);
244 startTag.setAttributes(attr);
245 return startTag;
246 }
247
248 public Tag endTag() {
249 return Tag.end(name);
250 }
251
252 protected Hashtable<String, String> getSerializableAttributes(Hashtable<String, String> ns) {
253 final var result = new Hashtable<String, String>();
254 for (final var attr : attributes.entrySet()) {
255 if (attr.getKey().charAt(0) == '{') {
256 final var uriIdx = attr.getKey().indexOf('}');
257 final var uri = attr.getKey().substring(1, uriIdx - 1);
258 if (!ns.containsKey(uri)) {
259 result.put("xmlns:ns" + ns.size(), uri);
260 ns.put(uri, "ns" + ns.size());
261 }
262 result.put(ns.get(uri) + ":" + attr.getKey().substring(uriIdx + 1), attr.getValue());
263 } else {
264 result.put(attr.getKey(), attr.getValue());
265 }
266 }
267
268 return result;
269 }
270
271 public Element removeAttribute(final String name) {
272 this.attributes.remove(name);
273 return this;
274 }
275
276 public Element setAttributes(Hashtable<String, String> attributes) {
277 this.attributes = attributes;
278 return this;
279 }
280
281 public String getAttribute(String name) {
282 if (this.attributes.containsKey(name)) {
283 return this.attributes.get(name);
284 } else {
285 return null;
286 }
287 }
288
289 public Hashtable<String, String> getAttributes() {
290 return this.attributes;
291 }
292
293 public final String getName() {
294 return name;
295 }
296
297 public void clearChildren() {
298 this.children.clear();
299 this.childNodes.clear();
300 }
301
302 public void setAttribute(String name, long value) {
303 this.setAttribute(name, Long.toString(value));
304 }
305
306 public void setAttribute(String name, int value) {
307 this.setAttribute(name, Integer.toString(value));
308 }
309
310 public boolean getAttributeAsBoolean(String name) {
311 String attr = getAttribute(name);
312 return (attr != null && (attr.equalsIgnoreCase("true") || attr.equalsIgnoreCase("1")));
313 }
314
315 public String getNamespace() {
316 return getAttribute("xmlns");
317 }
318}