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