/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.j2db.util;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jabsorb.JSONSerializer;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;
import com.servoy.base.query.BaseColumnType;
import com.servoy.j2db.persistence.RepositoryException;
import com.servoy.j2db.query.ColumnType;
public class XMLUtils
{
public static NodeList getChildNodesByName(Node node, String name)
{
if (node == null) return null;
XMLUtilsNodeList list = new XMLUtilsNodeList();
NodeList childs = node.getChildNodes();
for (int i = 0; i < childs.getLength(); i++)
{
Node child = childs.item(i);
if (child.getNodeName().equalsIgnoreCase(name))
{
list.addNode(child);
}
}
return list;
}
public static Node getChildNodeByName(Node node, String name)
{
if (node == null) return null;
NodeList childs = node.getChildNodes();
for (int i = 0; i < childs.getLength(); i++)
{
Node child = childs.item(i);
if (child.getNodeName().equalsIgnoreCase(name))
{
return child;
}
}
// not found
return null;
}
public static String getNodeValue(Node n)
{
if (n == null) return null;
String val = n.getNodeValue();
if (val == null)
{
Node child = n.getFirstChild();
if (child != null)
{
val = child.getNodeValue();
}
}
return val;
}
public static String getNodeAttribute(Node n, String name)
{
if (n == null) return null;
NamedNodeMap attr = n.getAttributes();
Node nameNode = attr.getNamedItem(name);
if (nameNode != null)
{
String name_value = getNodeValue(nameNode);
return name_value;
}
else
{
return null;
}
}
/**
* Convert a string so that it can be put as PCDATA in an XML tag. This basically escapes &, <, and > so that they are replaced by their XML
* entities.
*
* @param string the string to be escaped
*
* @return the escaped string
*/
public static String toPCDATA(String string)
{
if (string == null)
{
return null;
}
StringBuilder sb = new StringBuilder(string.length() * 2);
int index = 0;
while (index < string.length())
{
int point = string.codePointAt(index);
int count = Character.charCount(point);
if (isValidCharCode(point))
{
String value = quoteCharCode(point);
if (value != null)
{
sb.append(value);
}
else
{
sb.appendCodePoint(point);
}
}
else
{
// we could in the future map these as well to a separate custom tag that puts the code as string and we can decode that afterwards (or use BASE64 instead)
Debug.error("Invalid XML 1.0 / UTF-8 character '" + new StringBuilder().appendCodePoint(point).toString() + "' found in string '" + string + "', deleting..."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
index += count;
}
return sb.toString();
}
private static String quoteCharCode(int code)
{
switch (code)
{
case '&' :
return "&";
case '<' :
return "<";
case '>' :
return ">";
default :
return null;
}
}
/**
* Checks for valid UTF-8 chars in XML 1.0 standard. All chars that are not in this ranges should be escaped &decimal; or hexa;
* @param code the char code.
*/
private static boolean isValidCharCode(int code)
{
return (0x0020 <= code && code <= 0xD7FF) || (0x000A == code) || (0x0009 == code) || (0x000D == code) || (0xE000 <= code && code <= 0xFFFD) ||
(0x10000 <= code && code <= 0x10ffff);
}
/**
* Checks that the two provided arguments are equal, and throws an exception complaining that the element was not expected if they are not.
*
* @param element the element being checked
* @param against the string that it should be equal to
*
* @throws SAXException if the two are not equal
*/
public static void checkElement(String element, String against) throws SAXException
{
if (!against.equals(element))
{
throw new SAXException("Missing expected '" + against + "' element"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
/**
* Checks that the two provided attributed is not null, throwing an exception if it is.
*
* @param attribute the attribute being checked
* @param name the name of the attribute being checked
* @param element the name of the element to which the attribute belongs
*
* @throws SAXException if the two are not equal
*/
public static void checkAttributeNotNull(String attribute, String name, String element) throws SAXException
{
if (attribute == null)
{
throw new SAXException("Missing required '" + name + "' attribute from '" + element + "' element"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
}
}
/**
* Checks that the xml_version attribute matches the expected version, and throws an exception if this is not the case.
*
* @param version the value of the xml_version attribute
* @param against the version number it should equal
* @param element the name of the element which has the xml_version attribute
*
* @throws SAXException if the versions do not match
*/
public static void checkXMLVersion(String version, int against, String element) throws SAXException
{
checkAttributeNotNull(version, "xml_version", element); //$NON-NLS-1$
if (parseInt(version) != against)
{
throw new SAXException("Invalid XML version of import, specified version " + version + ", software version " + against); //$NON-NLS-1$//$NON-NLS-2$
}
}
/**
* Checks that the repository_version attribute matches the expected version, and throws an exception if this is not the case.
*
* @param version the value of the repository_version attribute
* @param against the version number it should equal
* @param element the name of the element which has the repository_version attribute
*
* @throws SAXException if the versions do not match
*/
public static void checkRepositoryVersion(String version, int against, String element) throws SAXException
{
checkAttributeNotNull(version, "repository_version", element); //$NON-NLS-1$
if (parseInt(version) != against)
{
throw new SAXException("Invalid repository version of import, specified version " + version + ", software version " + against); //$NON-NLS-1$ //$NON-NLS-2$
}
}
/**
* Tries to parse the specified string to an integer and throws an exception if there is a parser error.
*
* @param string the string to parse to an integer
*
* @return the value of the string as an <code>int</code>
*
* @throws SAXException if the string cannot be parsed
*/
public static int parseInt(String string) throws SAXException
{
try
{
return Integer.parseInt(string);
}
catch (NumberFormatException e)
{
throw new SAXException(e);
}
}
/**
* Format the contents of a <code>SAXParseException</code> to the trace stream of the <code>Debug</code> class. This method is called by the SAX parsers
* whenever a warning or error occurs which should be sent to the tracing output.
*
* @param type the type of error as a string, i.e. "warning"
* @param e the <code>SAXParseException</code> of which to output the info
*/
public static void trace(String type, SAXParseException e)
{
Debug.trace("[XML " + type + "] " + e.getPublicId() + "(" + e.getLineNumber() + ", " + e.getColumnNumber() + "): " + e.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$//$NON-NLS-5$
}
/**
* Format the contents of a <code>SAXParseException</code> to the error stream of the <code>Debug</code> class. This method is called by the SAX parsers
* whenever a warning or error occurs which should be sent to the error output.
*
* @param type the type of error as a string, i.e. "warning"
* @param e the <code>SAXParseException</code> of which to output the info
*/
public static void error(String type, SAXParseException e)
{
Debug.error("[XML " + type + "] " + e.getPublicId() + "(" + e.getLineNumber() + ", " + e.getColumnNumber() + "): " + e.getMessage()); //$NON-NLS-1$//$NON-NLS-2$//$NON-NLS-3$//$NON-NLS-4$ //$NON-NLS-5$
}
public static XMLReader createXMLReader() throws RepositoryException
{
try
{
return XMLReaderFactory.createXMLReader();
}
catch (Exception e)
{
throw new RepositoryException(e);
}
}
/**
* Remove xml comments from the xml string.
*
* @param xml
* @return
*/
public static String stripXMLComment(CharSequence xml)
{
if (xml == null)
{
return null;
}
// option DOTALL: '.' matches newlines
// use '.+?' in stead of '.*': non-greedy matching ("<!-- --> <important-stuff\> <!-- -->" is replaced with " <important-stuff\> ")
Pattern p = Pattern.compile("<!--.+?-->", Pattern.DOTALL); //$NON-NLS-1$
Matcher m = p.matcher(xml);
StringBuffer sb = new StringBuffer();
while (m.find())
{
m.appendReplacement(sb, ""); //$NON-NLS-1$
}
m.appendTail(sb);
return sb.toString();
}
/**
* Parse an array string '[[tp,len,scale], [tp,len,scale], ...]' as ColumnType list
*/
public static List<ColumnType> parseColumnTypeArray(String s)
{
if (s == null) return null;
List<ColumnType> list = null;
JSONSerializer serializer = new JSONSerializer();
try
{
serializer.registerDefaultSerializers();
Integer[][] array = (Integer[][])serializer.fromJSON(s);
if (array != null && array.length > 0)
{
list = new ArrayList<ColumnType>(array.length);
for (Integer[] elem : array)
{
list.add(ColumnType.getInstance(elem[0].intValue(), elem[1].intValue(), elem[2].intValue()));
}
}
}
catch (Exception e)
{
Debug.error(e);
}
return list;
}
/**
* Serialize a ColumnType list as an array string '[[tp,len,scale], [tp,len,scale], ...]'
*/
public static String serializeColumnTypeArray(List<ColumnType> columnTypes)
{
if (columnTypes == null || columnTypes.size() == 0)
{
return null;
}
StringBuilder sb = new StringBuilder();
sb.append('[');
for (int i = 0; i < columnTypes.size(); i++)
{
if (i > 0) sb.append(',');
sb.append(serializeColumnType(columnTypes.get(i)));
}
sb.append(']');
return sb.toString();
}
public static String serializeColumnType(BaseColumnType columnType)
{
if (columnType == null)
{
return null;
}
return new StringBuilder() //
.append('[') //
/* */.append(String.valueOf(columnType.getSqlType())) //
.append(',').append(String.valueOf(columnType.getLength())) //
.append(',').append(String.valueOf(columnType.getScale())) //
.append(']').toString();
}
}