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 var attr = getSerializableAttributes(mutns);
220		final StringBuilder elementOutput = new StringBuilder();
221		if (childNodes.size() == 0) {
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			Tag startTag = Tag.start(name);
228			startTag.setAttributes(attr);
229			elementOutput.append(startTag);
230			for (Node child : ImmutableList.copyOf(childNodes)) {
231				elementOutput.append(child.toString(ns));
232			}
233			Tag endTag = Tag.end(name);
234			elementOutput.append(endTag);
235		}
236		return elementOutput.toString();
237	}
238
239	protected Hashtable<String, String> getSerializableAttributes(Hashtable<String, String> ns) {
240		final var result = new Hashtable<String, String>();
241		for (final var attr : attributes.entrySet()) {
242			if (attr.getKey().charAt(0) == '{') {
243				final var uriIdx = attr.getKey().indexOf('}');
244				final var uri = attr.getKey().substring(1, uriIdx - 1);
245				if (!ns.containsKey(uri)) {
246					result.put("xmlns:ns" + ns.size(), uri);
247					ns.put(uri, "ns" + ns.size());
248				}
249				result.put(ns.get(uri) + ":" + attr.getKey().substring(uriIdx + 1), attr.getValue());
250			} else {
251				result.put(attr.getKey(), attr.getValue());
252			}
253		}
254
255		return result;
256	}
257
258	public Element removeAttribute(final String name) {
259		this.attributes.remove(name);
260		return this;
261	}
262
263	public Element setAttributes(Hashtable<String, String> attributes) {
264		this.attributes = attributes;
265		return this;
266	}
267
268	public String getAttribute(String name) {
269		if (this.attributes.containsKey(name)) {
270			return this.attributes.get(name);
271		} else {
272			return null;
273		}
274	}
275
276	public Hashtable<String, String> getAttributes() {
277		return this.attributes;
278	}
279
280	public final String getName() {
281		return name;
282	}
283
284	public void clearChildren() {
285		this.children.clear();
286		this.childNodes.clear();
287	}
288
289	public void setAttribute(String name, long value) {
290		this.setAttribute(name, Long.toString(value));
291	}
292
293	public void setAttribute(String name, int value) {
294		this.setAttribute(name, Integer.toString(value));
295	}
296
297	public boolean getAttributeAsBoolean(String name) {
298		String attr = getAttribute(name);
299		return (attr != null && (attr.equalsIgnoreCase("true") || attr.equalsIgnoreCase("1")));
300	}
301
302	public String getNamespace() {
303		return getAttribute("xmlns");
304	}
305}