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