171a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvistpackage com.android.server.wifi.hotspot2.omadm;
271a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
371a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvistimport org.xml.sax.Attributes;
471a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvistimport org.xml.sax.SAXException;
571a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
671a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvistimport java.io.IOException;
771a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvistimport java.util.ArrayList;
871a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvistimport java.util.Arrays;
971a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvistimport java.util.Collections;
1071a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvistimport java.util.HashMap;
111d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvistimport java.util.HashSet;
12c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvistimport java.util.Iterator;
1371a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvistimport java.util.List;
1471a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvistimport java.util.Map;
151d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvistimport java.util.Set;
1671a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
1771a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvistpublic class XMLNode {
1871a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    private final String mTag;
1971a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    private final Map<String, NodeAttribute> mAttributes;
2071a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    private final List<XMLNode> mChildren;
2171a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    private final XMLNode mParent;
2271a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    private MOTree mMO;
2371a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    private StringBuilder mTextBuilder;
2471a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    private String mText;
2571a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
261d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist    private static final String XML_SPECIAL_CHARS = "\"'<>&";
271d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist    private static final Set<Character> XML_SPECIAL = new HashSet<>();
281d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist    private static final String CDATA_OPEN = "<![CDATA[";
291d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist    private static final String CDATA_CLOSE = "]]>";
301d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist
311d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist    static {
321d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        for (int n = 0; n < XML_SPECIAL_CHARS.length(); n++) {
331d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            XML_SPECIAL.add(XML_SPECIAL_CHARS.charAt(n));
341d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        }
351d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist    }
361d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist
3771a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    public XMLNode(XMLNode parent, String tag, Attributes attributes) throws SAXException {
3871a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        mTag = tag;
3971a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
401d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        mAttributes = new HashMap<>();
4171a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
4271a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        if (attributes.getLength() > 0) {
4371a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist            for (int n = 0; n < attributes.getLength(); n++)
4471a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist                mAttributes.put(attributes.getQName(n), new NodeAttribute(attributes.getQName(n),
4571a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist                        attributes.getType(n), attributes.getValue(n)));
4671a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        }
4771a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
4871a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        mParent = parent;
491d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        mChildren = new ArrayList<>();
5071a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
5171a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        mTextBuilder = new StringBuilder();
5271a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    }
5371a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
541d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist    public XMLNode(XMLNode parent, String tag, Map<String, String> attributes) {
551d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        mTag = tag;
561d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist
571d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        mAttributes = new HashMap<>(attributes == null ? 0 : attributes.size());
581d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist
591d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        if (attributes != null) {
601d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            for (Map.Entry<String, String> entry : attributes.entrySet()) {
611d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist                mAttributes.put(entry.getKey(), new NodeAttribute(entry.getKey(), "", entry.getValue()));
621d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            }
631d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        }
641d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist
651d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        mParent = parent;
661d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        mChildren = new ArrayList<>();
671d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist
681d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        mTextBuilder = new StringBuilder();
691d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist    }
701d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist
71c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist    @Override
72c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist    public boolean equals(Object thatObject) {
73c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist        if (thatObject == this) {
74c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist            return true;
75c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist        } else if (thatObject.getClass() != XMLNode.class) {
76c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist            return false;
77c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist        }
78c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist
79c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist        XMLNode that = (XMLNode) thatObject;
80c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist        if (!getTag().equals(that.getTag())
81c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist                || mAttributes.size() != that.mAttributes.size()
82c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist                || mChildren.size() != that.mChildren.size()) {
83c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist            return false;
84c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist        }
85c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist
86c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist        for (Map.Entry<String, NodeAttribute> entry : mAttributes.entrySet()) {
87c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist            if (!entry.getValue().equals(that.mAttributes.get(entry.getKey()))) {
88c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist                return false;
89c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist            }
90c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist        }
91c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist
92c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist        List<XMLNode> cloneOfThat = new ArrayList<>(that.mChildren);
93c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist        for (XMLNode child : mChildren) {
94c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist            Iterator<XMLNode> thatChildren = cloneOfThat.iterator();
95c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist            boolean found = false;
96c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist            while (thatChildren.hasNext()) {
97c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist                XMLNode thatChild = thatChildren.next();
98c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist                if (child.equals(thatChild)) {
99c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist                    found = true;
100c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist                    thatChildren.remove();
101c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist                    break;
102c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist                }
103c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist            }
104c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist            if (!found) {
105c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist                return false;
106c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist            }
107c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist        }
108c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist        return true;
109c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist    }
110c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist
1111d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist    public void setText(String text) {
1121d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        mText = text;
1131d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        mTextBuilder = null;
1141d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist    }
1151d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist
11671a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    public void addText(char[] chs, int start, int length) {
11771a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        String s = new String(chs, start, length);
11871a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        String trimmed = s.trim();
11971a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        if (trimmed.isEmpty())
12071a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist            return;
12171a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
12271a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        if (s.charAt(0) != trimmed.charAt(0))
12371a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist            mTextBuilder.append(' ');
12471a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        mTextBuilder.append(trimmed);
12571a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        if (s.charAt(s.length() - 1) != trimmed.charAt(trimmed.length() - 1))
12671a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist            mTextBuilder.append(' ');
12771a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    }
12871a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
12971a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    public void addChild(XMLNode child) {
13071a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        mChildren.add(child);
13171a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    }
13271a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
13371a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    public void close() throws IOException, SAXException {
13471a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        String text = mTextBuilder.toString().trim();
13571a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        StringBuilder filtered = new StringBuilder(text.length());
13671a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        for (int n = 0; n < text.length(); n++) {
13771a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist            char ch = text.charAt(n);
13871a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist            if (ch >= ' ')
13971a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist                filtered.append(ch);
14071a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        }
14171a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
14271a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        mText = filtered.toString();
14371a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        mTextBuilder = null;
14471a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
1451d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        if (MOTree.hasMgmtTreeTag(mText)) {
1461d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            try {
1471d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist                NodeAttribute urn = mAttributes.get(OMAConstants.SppMOAttribute);
1481d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist                OMAParser omaParser = new OMAParser();
1491d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist                mMO = omaParser.parse(mText, urn != null ? urn.getValue() : null);
1501d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            }
1511d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            catch (SAXException | IOException e) {
1521d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist                mMO = null;
1531d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            }
15471a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        }
15571a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    }
15671a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
15771a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    public String getTag() {
15871a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        return mTag;
15971a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    }
16071a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
1611d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist    public String getNameSpace() throws OMAException {
1621d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        String[] nsn = mTag.split(":");
1631d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        if (nsn.length != 2) {
1641d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            throw new OMAException("Non-namespaced tag: '" + mTag + "'");
1651d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        }
1661d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        return nsn[0];
1671d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist    }
1681d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist
1691d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist    public String getStrippedTag() throws OMAException {
1701d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        String[] nsn = mTag.split(":");
1711d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        if (nsn.length != 2) {
1721d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            throw new OMAException("Non-namespaced tag: '" + mTag + "'");
1731d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        }
1741d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        return nsn[1].toLowerCase();
1751d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist    }
1761d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist
1771d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist    public XMLNode getSoleChild() throws OMAException{
1781d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        if (mChildren.size() != 1) {
1791d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            throw new OMAException("Expected exactly one child to " + mTag);
1801d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        }
1811d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        return mChildren.get(0);
1821d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist    }
1831d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist
18471a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    public XMLNode getParent() {
18571a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        return mParent;
18671a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    }
18771a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
18871a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    public String getText() {
18971a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        return mText;
19071a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    }
19171a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
19271a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    public Map<String, NodeAttribute> getAttributes() {
19371a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        return Collections.unmodifiableMap(mAttributes);
19471a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    }
19571a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
196c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist    /**
197c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist     * Get the attributes of this node as a map of attribute name to attribute value.
198c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist     * @return The attribute mapping.
199c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist     */
200c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist    public Map<String, String> getTextualAttributes() {
201c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist        Map<String, String> map = new HashMap<>(mAttributes.size());
202c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist        for (Map.Entry<String, NodeAttribute> entry : mAttributes.entrySet()) {
203c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist            map.put(entry.getKey(), entry.getValue().getValue());
204c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist        }
205c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist        return map;
206c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist    }
207c9b88562541a0c2acd60d0a01ac1e182e73c79f9Jan Nordqvist
20871a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    public String getAttributeValue(String name) {
20971a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        NodeAttribute nodeAttribute = mAttributes.get(name);
21071a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        return nodeAttribute != null ? nodeAttribute.getValue() : null;
21171a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    }
21271a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
21371a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    public List<XMLNode> getChildren() {
21471a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        return mChildren;
21571a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    }
21671a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
21771a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    public MOTree getMOTree() {
21871a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        return mMO;
21971a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    }
22071a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
22171a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    private void toString(char[] indent, StringBuilder sb) {
22271a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        Arrays.fill(indent, ' ');
22371a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
2241d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        sb.append(indent).append('<').append(mTag);
2251d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        for (Map.Entry<String, NodeAttribute> entry : mAttributes.entrySet()) {
2261d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            sb.append(' ').append(entry.getKey()).append("='").append(entry.getValue().getValue()).append('\'');
2271d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        }
22871a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
2291d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        if (mText != null && !mText.isEmpty()) {
2301d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            sb.append('>').append(escapeCdata(mText)).append("</").append(mTag).append(">\n");
2311d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        }
2321d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        else if (mChildren.isEmpty()) {
2331d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            sb.append("/>\n");
2341d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        }
2351d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        else {
2361d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            sb.append(">\n");
2371d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            char[] subIndent = Arrays.copyOf(indent, indent.length + 2);
2381d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            for (XMLNode child : mChildren) {
2391d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist                child.toString(subIndent, sb);
2401d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            }
2411d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            sb.append(indent).append("</").append(mTag).append(">\n");
2421d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        }
2431d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist    }
24471a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
2451d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist    private static String escapeCdata(String text) {
2461d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        if (!escapable(text)) {
2471d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            return text;
2481d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        }
24971a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
2501d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        // Any appearance of ]]> in the text must be split into "]]" | "]]>" | <![CDATA[ | ">"
2511d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        // i.e. "split the sequence by putting a close CDATA and a new open CDATA before the '>'
2521d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        StringBuilder sb = new StringBuilder();
2531d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        sb.append(CDATA_OPEN);
2541d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        int start = 0;
2551d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        for (;;) {
2561d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            int etoken = text.indexOf(CDATA_CLOSE);
2571d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            if (etoken >= 0) {
2581d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist                sb.append(text.substring(start, etoken + 2)).append(CDATA_CLOSE).append(CDATA_OPEN);
2591d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist                start = etoken + 2;
2601d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            }
2611d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            else {
2621d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist                if (start < text.length() - 1) {
2631d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist                    sb.append(text.substring(start));
2641d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist                }
2651d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist                break;
2661d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            }
2671d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        }
2681d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        sb.append(CDATA_CLOSE);
2691d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        return sb.toString();
2701d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist    }
2711d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist
2721d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist    private static boolean escapable(String s) {
2731d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        for (int n = 0; n < s.length(); n++) {
2741d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            if (XML_SPECIAL.contains(s.charAt(n))) {
2751d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist                return true;
2761d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist            }
2771d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        }
2781d5cd3938f9191184cd9aea3059a3b62bf3a0372Jan Nordqvist        return false;
27971a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    }
28071a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist
28171a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    @Override
28271a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    public String toString() {
28371a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        StringBuilder sb = new StringBuilder();
28471a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        toString(new char[0], sb);
28571a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist        return sb.toString();
28671a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist    }
28771a988c8e9859244b83cd55bb6b6ee913fcaf95cJan Nordqvist}
288