| 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(); |
| } |
| } |