Home | History | Annotate | Download | only in omadm
      1 package com.android.hotspot2.omadm;
      2 
      3 import org.xml.sax.Attributes;
      4 import org.xml.sax.SAXException;
      5 
      6 import java.io.IOException;
      7 import java.util.ArrayList;
      8 import java.util.Arrays;
      9 import java.util.Collections;
     10 import java.util.HashMap;
     11 import java.util.HashSet;
     12 import java.util.List;
     13 import java.util.Map;
     14 import java.util.Set;
     15 
     16 public class XMLNode {
     17     private final String mTag;
     18     private final Map<String, NodeAttribute> mAttributes;
     19     private final List<XMLNode> mChildren;
     20     private final XMLNode mParent;
     21     private MOTree mMO;
     22     private StringBuilder mTextBuilder;
     23     private String mText;
     24 
     25     private static final String XML_SPECIAL_CHARS = "\"'<>&";
     26     private static final Set<Character> XML_SPECIAL = new HashSet<>();
     27     private static final String CDATA_OPEN = "<![CDATA[";
     28     private static final String CDATA_CLOSE = "]]>";
     29 
     30     static {
     31         for (int n = 0; n < XML_SPECIAL_CHARS.length(); n++) {
     32             XML_SPECIAL.add(XML_SPECIAL_CHARS.charAt(n));
     33         }
     34     }
     35 
     36     public XMLNode(XMLNode parent, String tag, Attributes attributes) throws SAXException {
     37         mTag = tag;
     38 
     39         mAttributes = new HashMap<>();
     40 
     41         if (attributes.getLength() > 0) {
     42             for (int n = 0; n < attributes.getLength(); n++)
     43                 mAttributes.put(attributes.getQName(n), new NodeAttribute(attributes.getQName(n),
     44                         attributes.getType(n), attributes.getValue(n)));
     45         }
     46 
     47         mParent = parent;
     48         mChildren = new ArrayList<>();
     49 
     50         mTextBuilder = new StringBuilder();
     51     }
     52 
     53     public XMLNode(XMLNode parent, String tag, Map<String, String> attributes) {
     54         mTag = tag;
     55 
     56         mAttributes = new HashMap<>(attributes == null ? 0 : attributes.size());
     57 
     58         if (attributes != null) {
     59             for (Map.Entry<String, String> entry : attributes.entrySet()) {
     60                 mAttributes.put(entry.getKey(),
     61                         new NodeAttribute(entry.getKey(), "", entry.getValue()));
     62             }
     63         }
     64 
     65         mParent = parent;
     66         mChildren = new ArrayList<>();
     67 
     68         mTextBuilder = new StringBuilder();
     69     }
     70 
     71     public void setText(String text) {
     72         mText = text;
     73         mTextBuilder = null;
     74     }
     75 
     76     public void addText(char[] chs, int start, int length) {
     77         String s = new String(chs, start, length);
     78         String trimmed = s.trim();
     79         if (trimmed.isEmpty())
     80             return;
     81 
     82         if (s.charAt(0) != trimmed.charAt(0))
     83             mTextBuilder.append(' ');
     84         mTextBuilder.append(trimmed);
     85         if (s.charAt(s.length() - 1) != trimmed.charAt(trimmed.length() - 1))
     86             mTextBuilder.append(' ');
     87     }
     88 
     89     public void addChild(XMLNode child) {
     90         mChildren.add(child);
     91     }
     92 
     93     public void close() throws IOException, SAXException {
     94         String text = mTextBuilder.toString().trim();
     95         StringBuilder filtered = new StringBuilder(text.length());
     96         for (int n = 0; n < text.length(); n++) {
     97             char ch = text.charAt(n);
     98             if (ch >= ' ')
     99                 filtered.append(ch);
    100         }
    101 
    102         mText = filtered.toString();
    103         mTextBuilder = null;
    104 
    105         if (MOTree.hasMgmtTreeTag(mText)) {
    106             try {
    107                 NodeAttribute urn = mAttributes.get(OMAConstants.SppMOAttribute);
    108                 OMAParser omaParser = new OMAParser();
    109                 mMO = omaParser.parse(mText, urn != null ? urn.getValue() : null);
    110             } catch (SAXException | IOException e) {
    111                 mMO = null;
    112             }
    113         }
    114     }
    115 
    116     public String getTag() {
    117         return mTag;
    118     }
    119 
    120     public String getNameSpace() throws OMAException {
    121         String[] nsn = mTag.split(":");
    122         if (nsn.length != 2) {
    123             throw new OMAException("Non-namespaced tag: '" + mTag + "'");
    124         }
    125         return nsn[0];
    126     }
    127 
    128     public String getStrippedTag() throws OMAException {
    129         String[] nsn = mTag.split(":");
    130         if (nsn.length != 2) {
    131             throw new OMAException("Non-namespaced tag: '" + mTag + "'");
    132         }
    133         return nsn[1].toLowerCase();
    134     }
    135 
    136     public XMLNode getSoleChild() throws OMAException {
    137         if (mChildren.size() != 1) {
    138             throw new OMAException("Expected exactly one child to " + mTag);
    139         }
    140         return mChildren.get(0);
    141     }
    142 
    143     public XMLNode getParent() {
    144         return mParent;
    145     }
    146 
    147     public String getText() {
    148         return mText;
    149     }
    150 
    151     public Map<String, NodeAttribute> getAttributes() {
    152         return Collections.unmodifiableMap(mAttributes);
    153     }
    154 
    155     public Map<String, String> getTextualAttributes() {
    156         Map<String, String> map = new HashMap<>(mAttributes.size());
    157         for (Map.Entry<String, NodeAttribute> entry : mAttributes.entrySet()) {
    158             map.put(entry.getKey(), entry.getValue().getValue());
    159         }
    160         return map;
    161     }
    162 
    163     public String getAttributeValue(String name) {
    164         NodeAttribute nodeAttribute = mAttributes.get(name);
    165         return nodeAttribute != null ? nodeAttribute.getValue() : null;
    166     }
    167 
    168     public List<XMLNode> getChildren() {
    169         return mChildren;
    170     }
    171 
    172     public MOTree getMOTree() {
    173         return mMO;
    174     }
    175 
    176     private void toString(char[] indent, StringBuilder sb) {
    177         Arrays.fill(indent, ' ');
    178 
    179         sb.append(indent).append('<').append(mTag);
    180         for (Map.Entry<String, NodeAttribute> entry : mAttributes.entrySet()) {
    181             sb.append(' ').append(entry.getKey()).append("='")
    182                     .append(entry.getValue().getValue()).append('\'');
    183         }
    184 
    185         if (mText != null && !mText.isEmpty()) {
    186             sb.append('>').append(escapeCdata(mText)).append("</").append(mTag).append(">\n");
    187         } else if (mChildren.isEmpty()) {
    188             sb.append("/>\n");
    189         } else {
    190             sb.append(">\n");
    191             char[] subIndent = Arrays.copyOf(indent, indent.length + 2);
    192             for (XMLNode child : mChildren) {
    193                 child.toString(subIndent, sb);
    194             }
    195             sb.append(indent).append("</").append(mTag).append(">\n");
    196         }
    197     }
    198 
    199     private static String escapeCdata(String text) {
    200         if (!escapable(text)) {
    201             return text;
    202         }
    203 
    204         // Any appearance of ]]> in the text must be split into "]]" | "]]>" | <![CDATA[ | ">"
    205         // i.e. "split the sequence by putting a close CDATA and a new open CDATA before the '>'
    206         StringBuilder sb = new StringBuilder();
    207         sb.append(CDATA_OPEN);
    208         int start = 0;
    209         for (; ; ) {
    210             int etoken = text.indexOf(CDATA_CLOSE);
    211             if (etoken >= 0) {
    212                 sb.append(text.substring(start, etoken + 2)).append(CDATA_CLOSE).append(CDATA_OPEN);
    213                 start = etoken + 2;
    214             } else {
    215                 if (start < text.length() - 1) {
    216                     sb.append(text.substring(start));
    217                 }
    218                 break;
    219             }
    220         }
    221         sb.append(CDATA_CLOSE);
    222         return sb.toString();
    223     }
    224 
    225     private static boolean escapable(String s) {
    226         for (int n = 0; n < s.length(); n++) {
    227             if (XML_SPECIAL.contains(s.charAt(n))) {
    228                 return true;
    229             }
    230         }
    231         return false;
    232     }
    233 
    234     @Override
    235     public String toString() {
    236         StringBuilder sb = new StringBuilder();
    237         toString(new char[0], sb);
    238         return sb.toString();
    239     }
    240 }
    241