1package eu.siacs.conversations.xml;
  2
  3import androidx.annotation.NonNull;
  4import com.google.common.base.CaseFormat;
  5import com.google.common.base.Optional;
  6import com.google.common.base.Strings;
  7import com.google.common.primitives.Ints;
  8import com.google.common.primitives.Longs;
  9import eu.siacs.conversations.utils.XmlHelper;
 10import eu.siacs.conversations.xmpp.Jid;
 11import im.conversations.android.xmpp.model.stanza.Message;
 12import java.util.ArrayList;
 13import java.util.Hashtable;
 14import java.util.List;
 15
 16public class Element {
 17    private final String name;
 18    private Hashtable<String, String> attributes = new Hashtable<>();
 19    private String content;
 20    protected List<Element> children = 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 Element addChild(Element child) {
 32        this.content = null;
 33        children.add(child);
 34        return child;
 35    }
 36
 37    public Element addChild(String name) {
 38        this.content = null;
 39        Element child = new Element(name);
 40        children.add(child);
 41        return child;
 42    }
 43
 44    public Element addChild(String name, String xmlns) {
 45        this.content = null;
 46        Element child = new Element(name);
 47        child.setAttribute("xmlns", xmlns);
 48        children.add(child);
 49        return child;
 50    }
 51
 52    public Element setContent(String content) {
 53        this.content = content;
 54        this.children.clear();
 55        return this;
 56    }
 57
 58    public Element findChild(String name) {
 59        for (Element child : this.children) {
 60            if (child.getName().equals(name)) {
 61                return child;
 62            }
 63        }
 64        return null;
 65    }
 66
 67    public String findChildContent(String name) {
 68        Element element = findChild(name);
 69        return element == null ? null : element.getContent();
 70    }
 71
 72    public Element findChild(String name, String xmlns) {
 73        for (Element child : this.children) {
 74            if (name.equals(child.getName()) && xmlns.equals(child.getAttribute("xmlns"))) {
 75                return child;
 76            }
 77        }
 78        return null;
 79    }
 80
 81    public Element findChildEnsureSingle(String name, String xmlns) {
 82        final List<Element> results = new ArrayList<>();
 83        for (Element child : this.children) {
 84            if (name.equals(child.getName()) && xmlns.equals(child.getAttribute("xmlns"))) {
 85                results.add(child);
 86            }
 87        }
 88        if (results.size() == 1) {
 89            return results.get(0);
 90        }
 91        return null;
 92    }
 93
 94    public String findChildContent(String name, String xmlns) {
 95        Element element = findChild(name, xmlns);
 96        return element == null ? null : element.getContent();
 97    }
 98
 99    public boolean hasChild(final String name) {
100        return findChild(name) != null;
101    }
102
103    public boolean hasChild(final String name, final String xmlns) {
104        return findChild(name, xmlns) != null;
105    }
106
107    public List<Element> getChildren() {
108        return this.children;
109    }
110
111    public Element setChildren(List<Element> children) {
112        this.children = children;
113        return this;
114    }
115
116    public final String getContent() {
117        return content;
118    }
119
120    public Element setAttribute(String name, String value) {
121        if (name != null && value != null) {
122            this.attributes.put(name, value);
123        }
124        return this;
125    }
126
127    public Element setAttribute(final String name, final Enum<?> e) {
128        if (e == null) {
129            this.attributes.remove(name);
130        } else {
131            this.attributes.put(
132                    name, CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, e.toString()));
133        }
134        return this;
135    }
136
137    public Element setAttribute(String name, Jid value) {
138        if (name != null && value != null) {
139            this.attributes.put(name, value.toString());
140        }
141        return this;
142    }
143
144    public void setAttribute(final String name, final boolean value) {
145        this.setAttribute(name, value ? "1" : "0");
146    }
147
148    public void removeAttribute(final String name) {
149        this.attributes.remove(name);
150    }
151
152    public Element setAttributes(Hashtable<String, String> attributes) {
153        this.attributes = attributes;
154        return this;
155    }
156
157    public String getAttribute(String name) {
158        if (this.attributes.containsKey(name)) {
159            return this.attributes.get(name);
160        } else {
161            return null;
162        }
163    }
164
165    public long getLongAttribute(final String name) {
166        final var value = Longs.tryParse(Strings.nullToEmpty(this.attributes.get(name)));
167        return value == null ? 0 : value;
168    }
169
170    public Optional<Integer> getOptionalIntAttribute(final String name) {
171        final String value = getAttribute(name);
172        if (value == null) {
173            return Optional.absent();
174        }
175        return Optional.fromNullable(Ints.tryParse(value));
176    }
177
178    public Jid getAttributeAsJid(final String name) {
179        final String jid = this.getAttribute(name);
180        if (Strings.isNullOrEmpty(jid)) {
181            return null;
182        }
183        return Jid.ofOrInvalid(jid, this instanceof Message);
184    }
185
186    public Hashtable<String, String> getAttributes() {
187        return this.attributes;
188    }
189
190    @NonNull
191    public String toString() {
192        final StringBuilder elementOutput = new StringBuilder();
193        if (content == null && children.isEmpty()) {
194            final Tag emptyTag = Tag.empty(name);
195            emptyTag.setAttributes(this.attributes);
196            elementOutput.append(emptyTag);
197        } else {
198            final Tag startTag = Tag.start(name);
199            startTag.setAttributes(this.attributes);
200            elementOutput.append(startTag);
201            if (content != null) {
202                elementOutput.append(XmlHelper.encodeEntities(content));
203            } else {
204                for (final Element child : children) {
205                    elementOutput.append(child.toString());
206                }
207            }
208            final Tag endTag = Tag.end(name);
209            elementOutput.append(endTag);
210        }
211        return elementOutput.toString();
212    }
213
214    public final String getName() {
215        return name;
216    }
217
218    public void clearChildren() {
219        this.children.clear();
220    }
221
222    public void setAttribute(String name, long value) {
223        this.setAttribute(name, Long.toString(value));
224    }
225
226    public void setAttribute(String name, int value) {
227        this.setAttribute(name, Integer.toString(value));
228    }
229
230    public boolean getAttributeAsBoolean(String name) {
231        String attr = getAttribute(name);
232        return (attr != null && (attr.equalsIgnoreCase("true") || attr.equalsIgnoreCase("1")));
233    }
234
235    public String getNamespace() {
236        return getAttribute("xmlns");
237    }
238}