package com.android.hotspot2.omadm;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class XMLNode {
private final String mTag;
private final Map<String, NodeAttribute> mAttributes;
private final List<XMLNode> mChildren;
private final XMLNode mParent;
private MOTree mMO;
private StringBuilder mTextBuilder;
private String mText;
private static final String XML_SPECIAL_CHARS = "\"'<>&";
private static final Set<Character> XML_SPECIAL = new HashSet<>();
private static final String CDATA_OPEN = "<![CDATA[";
private static final String CDATA_CLOSE = "]]>";
static {
for (int n = 0; n < XML_SPECIAL_CHARS.length(); n++) {
XML_SPECIAL.add(XML_SPECIAL_CHARS.charAt(n));
}
}
public XMLNode(XMLNode parent, String tag, Attributes attributes) throws SAXException {
mTag = tag;
mAttributes = new HashMap<>();
if (attributes.getLength() > 0) {
for (int n = 0; n < attributes.getLength(); n++)
mAttributes.put(attributes.getQName(n), new NodeAttribute(attributes.getQName(n),
attributes.getType(n), attributes.getValue(n)));
}
mParent = parent;
mChildren = new ArrayList<>();
mTextBuilder = new StringBuilder();
}
public XMLNode(XMLNode parent, String tag, Map<String, String> attributes) {
mTag = tag;
mAttributes = new HashMap<>(attributes == null ? 0 : attributes.size());
if (attributes != null) {
for (Map.Entry<String, String> entry : attributes.entrySet()) {
mAttributes.put(entry.getKey(),
new NodeAttribute(entry.getKey(), "", entry.getValue()));
}
}
mParent = parent;
mChildren = new ArrayList<>();
mTextBuilder = new StringBuilder();
}
public void setText(String text) {
mText = text;
mTextBuilder = null;
}
public void addText(char[] chs, int start, int length) {
String s = new String(chs, start, length);
String trimmed = s.trim();
if (trimmed.isEmpty())
return;
if (s.charAt(0) != trimmed.charAt(0))
mTextBuilder.append(' ');
mTextBuilder.append(trimmed);
if (s.charAt(s.length() - 1) != trimmed.charAt(trimmed.length() - 1))
mTextBuilder.append(' ');
}
public void addChild(XMLNode child) {
mChildren.add(child);
}
public void close() throws IOException, SAXException {
String text = mTextBuilder.toString().trim();
StringBuilder filtered = new StringBuilder(text.length());
for (int n = 0; n < text.length(); n++) {
char ch = text.charAt(n);
if (ch >= ' ')
filtered.append(ch);
}
mText = filtered.toString();
mTextBuilder = null;
if (MOTree.hasMgmtTreeTag(mText)) {
try {
NodeAttribute urn = mAttributes.get(OMAConstants.SppMOAttribute);
OMAParser omaParser = new OMAParser();
mMO = omaParser.parse(mText, urn != null ? urn.getValue() : null);
} catch (SAXException | IOException e) {
mMO = null;
}
}
}
public String getTag() {
return mTag;
}
public String getNameSpace() throws OMAException {
String[] nsn = mTag.split(":");
if (nsn.length != 2) {
throw new OMAException("Non-namespaced tag: '" + mTag + "'");
}
return nsn[0];
}
public String getStrippedTag() throws OMAException {
String[] nsn = mTag.split(":");
if (nsn.length != 2) {
throw new OMAException("Non-namespaced tag: '" + mTag + "'");
}
return nsn[1].toLowerCase();
}
public XMLNode getSoleChild() throws OMAException {
if (mChildren.size() != 1) {
throw new OMAException("Expected exactly one child to " + mTag);
}
return mChildren.get(0);
}
public XMLNode getParent() {
return mParent;
}
public String getText() {
return mText;
}
public Map<String, NodeAttribute> getAttributes() {
return Collections.unmodifiableMap(mAttributes);
}
public Map<String, String> getTextualAttributes() {
Map<String, String> map = new HashMap<>(mAttributes.size());
for (Map.Entry<String, NodeAttribute> entry : mAttributes.entrySet()) {
map.put(entry.getKey(), entry.getValue().getValue());
}
return map;
}
public String getAttributeValue(String name) {
NodeAttribute nodeAttribute = mAttributes.get(name);
return nodeAttribute != null ? nodeAttribute.getValue() : null;
}
public List<XMLNode> getChildren() {
return mChildren;
}
public MOTree getMOTree() {
return mMO;
}
private void toString(char[] indent, StringBuilder sb) {
Arrays.fill(indent, ' ');
sb.append(indent).append('<').append(mTag);
for (Map.Entry<String, NodeAttribute> entry : mAttributes.entrySet()) {
sb.append(' ').append(entry.getKey()).append("='")
.append(entry.getValue().getValue()).append('\'');
}
if (mText != null && !mText.isEmpty()) {
sb.append('>').append(escapeCdata(mText)).append("</").append(mTag).append(">\n");
} else if (mChildren.isEmpty()) {
sb.append("/>\n");
} else {
sb.append(">\n");
char[] subIndent = Arrays.copyOf(indent, indent.length + 2);
for (XMLNode child : mChildren) {
child.toString(subIndent, sb);
}
sb.append(indent).append("</").append(mTag).append(">\n");
}
}
private static String escapeCdata(String text) {
if (!escapable(text)) {
return text;
}
// Any appearance of ]]> in the text must be split into "]]" | "]]>" | <![CDATA[ | ">"
// i.e. "split the sequence by putting a close CDATA and a new open CDATA before the '>'
StringBuilder sb = new StringBuilder();
sb.append(CDATA_OPEN);
int start = 0;
for (; ; ) {
int etoken = text.indexOf(CDATA_CLOSE);
if (etoken >= 0) {
sb.append(text.substring(start, etoken + 2)).append(CDATA_CLOSE).append(CDATA_OPEN);
start = etoken + 2;
} else {
if (start < text.length() - 1) {
sb.append(text.substring(start));
}
break;
}
}
sb.append(CDATA_CLOSE);
return sb.toString();
}
private static boolean escapable(String s) {
for (int n = 0; n < s.length(); n++) {
if (XML_SPECIAL.contains(s.charAt(n))) {
return true;
}
}
return false;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
toString(new char[0], sb);
return sb.toString();
}
}