/*
* XmlNode.java
*
*/
// #sijapp cond.if protocols_JABBER is "true" #
package protocol.xmpp;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import jimm.JimmException;
import jimm.comm.StringUtils;
import jimm.comm.Util;
/**
* Very light-weight xml parser
*
* @author Matej Usaj
* @author Vladimir Kryukov
*/
public final class XmlNode {
public String name;
public String value;
private Hashtable<String, String> attribs = new Hashtable<String, String>();
private Vector<XmlNode> children = new Vector<XmlNode>();
private static final int MAX_BIN_VALUE_SIZE = 54 * 1024;
private static final int MAX_VALUE_SIZE = 10 * 1024;
public final static String S_ID = "i" + "d";
public static final String S_JID = "j" + "i" + "d";
public static final String S_NICK = "n" + "ick";
public static final String S_NAME = "n" + "ame";
public static final String S_ROLE = "ro" + "le";
public static final String S_AFFILIATION = "affiliation";
private static final String S_BINVAL = "BINVAL";
public static final String S_XMLNS = "x" + "mlns";
private XmlNode() {}
private XmlNode(String name) {
this.name = name;
}
private XmlNode unsafeChildAt(int index) {
return (XmlNode)children.elementAt(index);
}
public XmlNode childAt(int index) {
if (children.size() <= index) {
return null;
}
return (XmlNode)children.elementAt(index);
}
public int childrenCount() {
return children.size();
}
public String getAttribute(String key) {
return (String)attribs.get(key);
}
private void putAttribute(String key, String value) {
if (S_JID.equals(key)) {
key = S_JID;
} else if (S_NAME.equals(key)) {
key = S_NAME;
}
attribs.put(key, value);
}
public String getXmlns() {
String xmlns = getAttribute("xmlns");
if (null == xmlns) {
Enumeration e = attribs.keys();
while (e.hasMoreElements()) {
String key = (String)e.nextElement();
if (key.startsWith("xmlns:")) {
return getAttribute(key);
}
}
}
return xmlns;
}
public String getId() {
return getAttribute(S_ID);
}
public static XmlNode parse(Socket socket) throws JimmException {
char ch = socket.readChar();
if ('<' != ch) {
return null;
}
ch = removeXmlHeader(socket);
if ('/' == ch) {
throw new JimmException(128, 0);
}
XmlNode xml = new XmlNode();
boolean parsed = xml.parseNode(socket, ch);
return parsed ? xml : null;
}
private void setName(String tagName) {
if (-1 == tagName.indexOf(':') || tagName.startsWith("stream:")) {
name = tagName;
return;
}
name = tagName.substring(tagName.indexOf(':') + 1);
}
private int getMaxDataSize(String name) {
if (S_BINVAL.equals(name)) {
if (jimm.Jimm.getJimm().phone.hasMemory(MAX_BIN_VALUE_SIZE * 2 * 2)) {
return MAX_BIN_VALUE_SIZE * 2;
}
if (jimm.Jimm.getJimm().phone.hasMemory(MAX_BIN_VALUE_SIZE * 2)) {
return MAX_BIN_VALUE_SIZE;
}
}
return MAX_VALUE_SIZE;
}
private String readCdata(Socket socket) throws JimmException {
StringBuilder out = new StringBuilder();
char ch = socket.readChar();
int maxSize = getMaxDataSize(name);
int size = 0;
for (int state = 0; state < 3;) {
ch = socket.readChar();
if (size == maxSize) {
out.append(ch);
size++;
}
if (']' == ch) {
state = Math.min(state + 1, 2);
} else if ((2 == state) && ('>' == ch)) {
state++;
} else {
state = 0;
}
}
out.delete(0, 7);
out.delete(Math.max(0, out.length() - 3), out.length());
return out.toString();
}
private void readEscapedChar(StringBuilder out, Socket socket) throws JimmException {
StringBuilder buffer = new StringBuilder(6);
int limit = 6;
char ch = socket.readChar();
while (';' != ch) {
if (0 < limit) {
buffer.append((char)ch);
limit--;
}
ch = socket.readChar();
}
if (0 == buffer.length()) {
out.append('&');
return;
}
String code = buffer.toString();
if ("quot".equals(code)) {
out.append('\"');
} else if ("gt".equals(code)) {
out.append('>');
} else if ("lt".equals(code)) {
out.append('<');
} else if ("apos".equals(code)) {
out.append('\'');
} else if ("amp".equals(code)) {
out.append('&');
} else if ('#' == buffer.charAt(0)) {
try {
buffer.deleteCharAt(0);
int radix = 10;
if ('x' == buffer.charAt(0)) {
buffer.deleteCharAt(0);
radix = 16;
}
out.append((char)Integer.parseInt(buffer.toString(), radix));
} catch (Exception e) {
out.append('?');
}
} else {
out.append('&');
out.append(code);
out.append(';');
}
}
private String readString(Socket socket, char endCh, int limit) throws JimmException {
char ch = socket.readChar();
if (endCh == ch) {
return null;
}
StringBuilder sb = new StringBuilder();
while (endCh != ch) {
if (sb.length() < limit) {
if ('\t' == ch) {
sb.append(" ");
} else if ('&' != ch) {
sb.append(ch);
} else {
readEscapedChar(sb, socket);
}
}
ch = socket.readChar();
}
return sb.toString();
}
private boolean parseNode(Socket socket, char ch0) throws JimmException {
// tag name
char ch = ch0;
if ('!' == ch) {
readCdata(socket);
return false;
}
if ('/' == ch) {
ch = socket.readChar();
while ('>' != ch) {
ch = socket.readChar();
}
return false;
}
StringBuilder tagName = new StringBuilder();
while (' ' != ch && '>' != ch) {
tagName.append((char)ch);
ch = socket.readChar();
if ('/' == ch) {
setName(tagName.toString());
ch = socket.readChar(); // '>'
return true;
}
}
setName(tagName.toString());
tagName = null;
// tag attributes
while ('>' != ch) {
while (' ' == ch) {
ch = socket.readChar();
}
if ('/' == ch) {
ch = socket.readChar(); // '>'
return true;
}
if ('>' == ch) {
break;
}
StringBuilder attrName = new StringBuilder();
while ('=' != ch) {
attrName.append((char)ch);
ch = socket.readChar();
}
char startValueCh = socket.readChar(); // '"' or '\''
String attribValue = readString(socket, startValueCh, 2*1024);
if (0 < attrName.length()) {
if (null == attribValue) {
attribValue = "";
}
putAttribute(attrName.toString(), attribValue);
}
ch = socket.readChar();
}
if ("stream:stream".equals(name)) {
return true;
}
// tag body
value = readString(socket, '<', getMaxDataSize(name));
// sub tags
while (true) {
ch = socket.readChar();
if ('!' == ch) {
value = readCdata(socket);
} else {
XmlNode xml = new XmlNode();
if (!xml.parseNode(socket, ch)) {
break;
}
children.addElement(xml);
}
ch = socket.readChar();
while ('<' != ch) {
ch = socket.readChar();
}
}
if (StringUtils.isEmpty(value)) {
value = null;
}
return true;
}
private static char removeXmlHeader(Socket socket) throws JimmException {
char ch = socket.readChar();
if ('?' != ch) {
return ch;
}
while ('?' != ch) {
ch = socket.readChar();
}
ch = socket.readChar(); // '>'
ch = socket.readChar();
while ('<' != ch) {
ch = socket.readChar();
}
return socket.readChar();
}
public final XmlNode popChildNode() {
XmlNode node = childAt(0);
children.removeElementAt(0);
return node;
}
public final void removeNode(String name) {
for (int i = 0; i < children.size(); ++i) {
if (unsafeChildAt(i).is(name)) {
children.removeElementAt(i);
return;
}
}
}
public final boolean is(String name) {
return this.name.equals(name);
}
public XmlNode getFirstNodeRecursive(String name) {
for (int i = 0; i < children.size(); ++i) {
XmlNode node = unsafeChildAt(i);
if (node.is(name)) {
return node;
}
XmlNode result = node.getFirstNodeRecursive(name);
if (null != result) {
return result;
}
}
return null;
}
public String getFirstNodeValueRecursive(String name) {
XmlNode node = getFirstNodeRecursive(name);
return (null == node) ? null : node.value;
}
/**
* Get first occurance of a node with a specified name.<br>
* This method goes in-depth first, not level-by-level
*
* @param name Name of the requested node
* @return {@link XmlNode} node or null if the node was not found.
*/
public XmlNode getFirstNode(String name) {
for (int i = 0; i < children.size(); ++i) {
if (unsafeChildAt(i).is(name)) {
return unsafeChildAt(i);
}
}
return null;
}
public XmlNode getFirstNode(String name, String xmlns) {
for (int i = 0; i < children.size(); ++i) {
XmlNode node = unsafeChildAt(i);
if (node.is(name) && xmlns.equals(node.getXmlns())) {
return node;
}
}
return null;
}
public XmlNode getXNode(String xmlns) {
return getFirstNode("x", xmlns);
}
public String getFirstNodeValue(String name) {
XmlNode node = getFirstNode(name);
return (null == node) ? null : node.value;
}
public String getFirstNodeValue(String parentNodeName, String nodeName) {
XmlNode parentNode = getFirstNode(parentNodeName);
return (null == parentNode) ? null : parentNode.getFirstNodeValue(nodeName);
}
public String getFirstNodeValue(String tag, String[] cond, String subtag) {
for (int i = 0; i < childrenCount(); ++i) {
XmlNode node = unsafeChildAt(i);
if (node.is(tag) && node.isContains(cond)) {
return node.getFirstNodeValue(subtag);
}
}
return null;
}
public String getFirstNodeValue(String tag, String[] subtags, String subtag, boolean isDefault) {
String result = getFirstNodeValue(tag, subtags, subtag);
if (null != result) {
return result;
}
for (int i = 0; i < childrenCount(); ++i) {
XmlNode node = unsafeChildAt(i);
if (node.is(tag) && (0 < node.childrenCount())) {
XmlNode firstNode = node.unsafeChildAt(0);
if (null != firstNode.value) {
return node.getFirstNodeValue(subtag);
}
}
}
return null;
}
public String getFirstNodeAttribute(String name, String key) {
XmlNode node = getFirstNode(name);
return (null == node) ? null : node.getAttribute(key);
}
/**
* Check if the xml contains a node with a specified name
*
* @param name Name of the requested node
* @return true if the node was found, false otherwise.
*/
public boolean contains(String name) {
return null != getFirstNode(name);
}
// #sijapp cond.if modules_DEBUGLOG is "true" #
private String _toString(StringBuilder sb, String spaces) {
sb.append(spaces).append("<").append(name);
if (0 != attribs.size()) {
Enumeration e = attribs.keys();
while (e.hasMoreElements()) {
Object k = e.nextElement();
sb.append(" ").append(k).append("='").append(attribs.get(k)).append("'");
}
}
if (0 != childrenCount()) {
sb.append(">");
sb.append("\n");
for (int i = 0; i < childrenCount(); ++i) {
unsafeChildAt(i)._toString(sb, spaces + " ");
sb.append("\n");
}
sb.append(spaces).append("</").append(name).append(">");
} else if (null != value) {
sb.append(">");
sb.append(value);
sb.append("</").append(name).append(">");
} else {
sb.append("/>");
}
return sb.toString();
}
public String toString() {
StringBuilder sb = new StringBuilder();
_toString(sb, "");
return sb.toString();
}
// #sijapp cond.end #
public String popValue() {
String result = value;
value = null;
return result;
}
public byte[] popBinValue() {
if (null == value) {
return null;
}
return Util.base64decode(popValue());
}
public byte[] getBinValue() {
if (null == value) {
return null;
}
return Util.base64decode(value);
}
private boolean isContains(String[] subTags) {
if (null == subTags) {
return true;
}
for (String subTag : subTags) {
if (!contains(subTag)) {
return false;
}
}
return true;
}
public void setValue(String subtag, String value) {
XmlNode content = getFirstNode(subtag);
if (null == content) {
content = new XmlNode(subtag);
children.addElement(content);
}
content.value = value;
}
public void setValue(String tag, String[] subTags, String subtag, String value) {
for (int i = 0; i < childrenCount(); ++i) {
XmlNode node = unsafeChildAt(i);
if (node.is(tag) && node.isContains(subTags)) {
node.setValue(subtag, value);
return;
}
}
if (StringUtils.isEmpty(value)) {
return;
}
XmlNode node = new XmlNode(tag);
children.addElement(node);
if (null != subTags) {
for (String subTag : subTags) {
node.children.addElement(new XmlNode(subTag));
}
}
node.setValue(subtag, value);
}
public void removeBadVCardTags(String tag) {
for (int i = childrenCount() - 1; 0 <= i; --i) {
XmlNode node = unsafeChildAt(i);
if (node.is(tag) && (0 < node.childrenCount())) {
if (null != node.unsafeChildAt(0).value) {
children.removeElementAt(i);
}
}
}
}
private boolean isEmptySubNodes() {
if (null != value) return false;
for (int i = childrenCount() - 1; i >= 0; --i) {
if (null != unsafeChildAt(i).value) {
return false;
}
}
return true;
}
public void cleanXmlTree() {
for (int i = childrenCount() - 1; i >= 0; --i) {
if (unsafeChildAt(i).isEmptySubNodes()) {
children.removeElementAt(i);
}
}
}
public void toString(StringBuilder sb) {
sb.append('<').append(name);
if (0 != attribs.size()) {
Enumeration e = attribs.keys();
while (e.hasMoreElements()) {
String k = (String)e.nextElement();
String v = (String)attribs.get(k);
sb.append(' ').append(Util.xmlEscape(k)).append("='")
.append(Util.xmlEscape(v)).append("'");
}
}
if ((0 == childrenCount()) && StringUtils.isEmpty(value)) {
sb.append("/>");
return;
}
sb.append('>');
if (0 != childrenCount()) {
for (int i = 0; i < childrenCount(); ++i) {
unsafeChildAt(i).toString(sb);
}
} else if (null != value) {
sb.append(Util.xmlEscape(value));
}
sb.append("</").append(name).append(">");
}
public static XmlNode getEmptyVCard() {
XmlNode vCard = new XmlNode("vCard");
vCard.putAttribute(S_XMLNS, "vcard-temp");
vCard.putAttribute("v"+"ersion", "2.0");
vCard.putAttribute("prodid", "-/"+"/HandGen/"+"/NONSGML vGen v1.0/"+"/EN");
return vCard;
}
}
// #sijapp cond.end #