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