Element.java

  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}