@@ -21,6 +21,33 @@ public class XmlHelper {
return content;
}
+ public static void appendEncodedEntities(final String content, final StringBuilder sb) {
+ final var length = content.length();
+ var needsEscape = false;
+ for (int i = 0; i < length; i++) {
+ final var c = content.charAt(i);
+ if (c == '<' || c == '&' || c == '"') {
+ needsEscape = true;
+ break;
+ }
+ }
+ if (needsEscape) {
+ for (int i = 0; i < length; i++) {
+ final var c = content.charAt(i);
+ switch (c) {
+ case '&': sb.append("&"); break;
+ case '<': sb.append("<"); break;
+ case '>': sb.append(">"); break;
+ case '"': sb.append("""); break;
+ case '\'': sb.append("'"); break;
+ default: sb.append(c);
+ }
+ }
+ } else {
+ sb.append(content);
+ }
+ }
+
public static String printElementNames(final Element element) {
final List<String> features =
element == null
@@ -12,6 +12,7 @@ import eu.siacs.conversations.xmpp.Jid;
import im.conversations.android.xmpp.model.stanza.Message;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Map;
import java.util.Hashtable;
import java.util.List;
import java.util.stream.Collectors;
@@ -214,31 +215,34 @@ public class Element implements Node {
return toString(ImmutableMap.of());
}
- public String toString(final ImmutableMap<String, String> parentNS) {
- final var mutns = new Hashtable<>(parentNS);
- final StringBuilder elementOutput = new StringBuilder();
+ public void appendToBuilder(final Map<String, String> parentNS, final StringBuilder elementOutput, final int skipEnd) {
+ final var mutns = new CopyOnWriteMap<>(parentNS);
if (childNodes.size() == 0) {
final var attr = getSerializableAttributes(mutns);
Tag emptyTag = Tag.empty(name);
emptyTag.setAttributes(attr);
- elementOutput.append(emptyTag.toString());
+ emptyTag.appendToBuilder(elementOutput);
} else {
- final var ns = ImmutableMap.copyOf(mutns);
final var startTag = startTag(mutns);
- elementOutput.append(startTag);
+ startTag.appendToBuilder(elementOutput);
for (Node child : ImmutableList.copyOf(childNodes)) {
- elementOutput.append(child.toString(ns));
+ child.appendToBuilder(mutns.toMap(), elementOutput, Math.max(0, skipEnd - 1));
}
- elementOutput.append(endTag());
+ if (skipEnd < 1) endTag().appendToBuilder(elementOutput);
}
+ }
+
+ public String toString(final ImmutableMap<String, String> parentNS) {
+ final StringBuilder elementOutput = new StringBuilder();
+ appendToBuilder(parentNS, elementOutput, 0);
return elementOutput.toString();
}
public Tag startTag() {
- return startTag(new Hashtable<>());
+ return startTag(new CopyOnWriteMap<>(new Hashtable<>()));
}
- public Tag startTag(final Hashtable<String, String> mutns) {
+ public Tag startTag(final CopyOnWriteMap<String, String> mutns) {
final var attr = getSerializableAttributes(mutns);
final var startTag = Tag.start(name);
startTag.setAttributes(attr);
@@ -249,8 +253,8 @@ public class Element implements Node {
return Tag.end(name);
}
- protected Hashtable<String, String> getSerializableAttributes(Hashtable<String, String> ns) {
- final var result = new Hashtable<String, String>();
+ protected Hashtable<String, String> getSerializableAttributes(CopyOnWriteMap<String, String> ns) {
+ final var result = new Hashtable<String, String>(attributes.size());
for (final var attr : attributes.entrySet()) {
if (attr.getKey().charAt(0) == '{') {
final var uriIdx = attr.getKey().indexOf('}');
@@ -315,4 +319,36 @@ public class Element implements Node {
public String getNamespace() {
return getAttribute("xmlns");
}
+
+ static class CopyOnWriteMap<K,V> {
+ protected final Map<K,V> original;
+ protected Hashtable<K,V> mut = null;
+
+ public CopyOnWriteMap(Map<K,V> original) {
+ this.original = original;
+ }
+
+ public int size() {
+ return mut == null ? original.size() : mut.size();
+ }
+
+ public boolean containsKey(K k) {
+ return mut == null ? original.containsKey(k) : mut.containsKey(k);
+ }
+
+ public V get(K k) {
+ return mut == null ? original.get(k) : mut.get(k);
+ }
+
+ public void put(K k, V v) {
+ if (mut == null) {
+ mut = new Hashtable<>(original);
+ }
+ mut.put(k, v);
+ }
+
+ public Map<K, V> toMap() {
+ return mut == null ? original : mut;
+ }
+ }
}
@@ -1,8 +1,10 @@
package eu.siacs.conversations.xml;
+import java.util.Map;
import com.google.common.collect.ImmutableMap;
public interface Node {
public String getContent();
public String toString(final ImmutableMap<String, String> ns);
+ public void appendToBuilder(final Map<String, String> ns, final StringBuilder elementOutput, final int skipEnd);
}
@@ -80,9 +80,7 @@ public class Tag {
return (this.type == NO);
}
- @NonNull
- public String toString() {
- final StringBuilder tagOutput = new StringBuilder();
+ public void appendToBuilder(final StringBuilder tagOutput) {
tagOutput.append('<');
if (type == END) {
tagOutput.append('/');
@@ -94,7 +92,7 @@ public class Tag {
tagOutput.append(' ');
tagOutput.append(entry.getKey());
tagOutput.append("=\"");
- tagOutput.append(XmlHelper.encodeEntities(entry.getValue()));
+ XmlHelper.appendEncodedEntities(entry.getValue(), tagOutput);
tagOutput.append('"');
}
}
@@ -102,6 +100,12 @@ public class Tag {
tagOutput.append('/');
}
tagOutput.append('>');
+ }
+
+ @NonNull
+ public String toString() {
+ final StringBuilder tagOutput = new StringBuilder();
+ appendToBuilder(tagOutput);
return tagOutput.toString();
}
@@ -1,5 +1,6 @@
package eu.siacs.conversations.xml;
+import java.util.Map;
import com.google.common.collect.ImmutableMap;
import eu.siacs.conversations.utils.XmlHelper;
@@ -16,6 +17,10 @@ public class TextNode implements Node {
return content;
}
+ public void appendToBuilder(final Map<String, String> parentNS, final StringBuilder elementOutput, final int skipEnd) {
+ XmlHelper.appendEncodedEntities(content, elementOutput);
+ }
+
public String toString() {
return XmlHelper.encodeEntities(content);
}