/**
* Copyright (C) 2010 Orbeon, Inc.
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation; either version
* 2.1 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 Lesser General Public License for more details.
*
* The full text of the license is available at http://www.gnu.org/copyleft/lesser.html
*/
package org.orbeon.oxf.xml;
import org.orbeon.oxf.common.OXFException;
import org.orbeon.oxf.util.ContentHandlerOutputStream;
import org.orbeon.oxf.util.NetUtils;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import java.io.*;
import java.util.HashSet;
import java.util.Set;
public class SAXUtils {
public static final Attributes EMPTY_ATTRIBUTES = new Attributes() {
@Override
public int getLength() {
return 0;
}
@Override
public String getURI(int i) {
return null;
}
@Override
public String getLocalName(int i) {
return null;
}
@Override
public String getQName(int i) {
return null;
}
@Override
public String getType(int i) {
return null;
}
@Override
public String getValue(int i) {
return null;
}
@Override
public int getIndex(String s, String s1) {
return -1;
}
@Override
public int getIndex(String s) {
return -1;
}
@Override
public String getType(String s, String s1) {
return null;
}
@Override
public String getType(String s) {
return null;
}
@Override
public String getValue(String s, String s1) {
return null;
}
@Override
public String getValue(String s) {
return null;
}
};
private SAXUtils() {}
/**
* Convert an Object to a String and generate SAX characters events.
*/
public static void objectToCharacters(Object o, ContentHandler contentHandler) {
try {
char[] charValue = (o == null) ? null : o.toString().toCharArray();
if (charValue != null)
contentHandler.characters(charValue, 0, charValue.length);
} catch (Exception e) {
throw new OXFException(e);
}
}
/**
* @param attributes source attributes
* @return new AttributesImpl containing all attributes that were in src attributes and that were
* in the default name space.
*/
public static AttributesImpl getAttributesFromDefaultNamespace(final Attributes attributes) {
final AttributesImpl ret = new AttributesImpl();
final int size = attributes.getLength();
for (int i = 0; i < size; i++) {
final String ns = attributes.getURI(i);
if (!"".equals(ns)) continue;
final String lnam = attributes.getLocalName(i);
final String qnam = attributes.getQName(i);
final String typ = attributes.getType(i);
final String val = attributes.getValue(i);
ret.addAttribute(ns, lnam, qnam, typ, val);
}
return ret;
}
/**
* Append classes to existing attributes. This creates a new AttributesImpl object.
*
* @param attributes existing attributes
* @param newClasses new classes to append
* @return new attributes
*/
public static AttributesImpl appendToClassAttribute(Attributes attributes, String newClasses) {
final String oldClassAttribute = attributes.getValue("class");
final String newClassAttribute = oldClassAttribute == null ? newClasses : oldClassAttribute + ' ' + newClasses;
return addOrReplaceAttribute(attributes, "", "", "class", newClassAttribute);
}
/**
* Append an attribute value to existing mutable attributes.
*
* @param attributes existing attributes
* @param attributeName attribute name
* @param attributeValue value to set or append
*/
public static void addOrAppendToAttribute(AttributesImpl attributes, String attributeName, String attributeValue) {
final int oldAttributeIndex = attributes.getIndex(attributeName);
if (oldAttributeIndex == -1) {
// No existing attribute
attributes.addAttribute("", attributeName, attributeName, XMLReceiverHelper.CDATA, attributeValue);
} else {
// Existing attribute
final String oldAttributeValue = attributes.getValue(oldAttributeIndex);
final String newAttributeValue = oldAttributeValue + ' ' + attributeValue;
attributes.setValue(oldAttributeIndex, newAttributeValue);
}
}
public static AttributesImpl addOrReplaceAttribute(Attributes attributes, String uri, String prefix, String localname, String value) {
final AttributesImpl newAttributes = new AttributesImpl();
boolean replaced = false;
for (int i = 0; i < attributes.getLength(); i++) {
final String attributeURI = attributes.getURI(i);
final String attributeValue = attributes.getValue(i);
final String attributeType = attributes.getType(i);
final String attributeQName = attributes.getQName(i);
final String attributeLocalname = attributes.getLocalName(i);
if (uri.equals(attributeURI) && localname.equals(attributeLocalname)) {
// Found existing attribute
replaced = true;
newAttributes.addAttribute(uri, localname, XMLUtils.buildQName(prefix, localname), XMLReceiverHelper.CDATA, value);
} else {
// Not a matched attribute
newAttributes.addAttribute(attributeURI, attributeLocalname, attributeQName, attributeType, attributeValue);
}
}
if (!replaced) {
// Attribute did not exist already so add it
newAttributes.addAttribute(uri, localname, XMLUtils.buildQName(prefix, localname), XMLReceiverHelper.CDATA, value);
}
return newAttributes;
}
public static AttributesImpl removeAttribute(Attributes attributes, String uri, String localname) {
final AttributesImpl newAttributes = new AttributesImpl();
for (int i = 0; i < attributes.getLength(); i++) {
final String attributeURI = attributes.getURI(i);
final String attributeValue = attributes.getValue(i);
final String attributeType = attributes.getType(i);
final String attributeQName = attributes.getQName(i);
final String attributeLocalname = attributes.getLocalName(i);
if (!uri.equals(attributeURI) || !localname.equals(attributeLocalname)) {
// Not a matched attribute
newAttributes.addAttribute(attributeURI, attributeLocalname, attributeQName, attributeType, attributeValue);
}
}
return newAttributes;
}
public static void streamNullDocument(ContentHandler contentHandler) throws SAXException {
contentHandler.startDocument();
contentHandler.startPrefixMapping(XMLConstants.XSI_PREFIX, XMLConstants.XSI_URI);
final AttributesImpl attributes = new AttributesImpl();
attributes.addAttribute(XMLConstants.XSI_URI, "nil", "xsi:nil", "CDATA", "true");
contentHandler.startElement("", "null", "null", attributes);
contentHandler.endElement("", "null", "null");
contentHandler.endPrefixMapping(XMLConstants.XSI_PREFIX);
contentHandler.endDocument();
}
private static void mapPrefixIfNeeded(Set<String> declaredPrefixes, String uri, String qName, StringBuilder sb) {
final String prefix = XMLUtils.prefixFromQName(qName);
if (prefix.length() > 0 && !declaredPrefixes.contains(prefix)) {
sb.append(" xmlns:");
sb.append(prefix);
sb.append("=\"");
sb.append(uri);
sb.append("\"");
declaredPrefixes.add(prefix);
}
}
public static String saxElementToDebugString(String uri, String qName, Attributes attributes) {
// Open start tag
final StringBuilder sb = new StringBuilder("<");
sb.append(qName);
final Set<String> declaredPrefixes = new HashSet<String>();
mapPrefixIfNeeded(declaredPrefixes, uri, qName, sb);
// Attributes if any
for (int i = 0; i < attributes.getLength(); i++) {
mapPrefixIfNeeded(declaredPrefixes, attributes.getURI(i), attributes.getQName(i), sb);
sb.append(' ');
sb.append(attributes.getQName(i));
sb.append("=\"");
sb.append(attributes.getValue(i));
sb.append('\"');
}
// Close start tag
sb.append('>');
// Content
sb.append("[...]");
// Close element with end tag
sb.append("</");
sb.append(qName);
sb.append('>');
return sb.toString();
}
/**
* Read bytes from an InputStream and generate SAX characters events in Base64 encoding. The
* InputStream is closed when done.
*
* The caller has to close the stream if needed.
*/
public static void inputStreamToBase64Characters(InputStream is, ContentHandler contentHandler) {
try {
final OutputStream os = new ContentHandlerOutputStream(contentHandler, false);
NetUtils.copyStream(new BufferedInputStream(is), os);
os.close(); // necessary with ContentHandlerOutputStream to make sure all extra characters are written
} catch (Exception e) {
throw new OXFException(e);
}
}
/**
* Read characters from a Reader and generate SAX characters events.
*
* The caller has to close the Reader if needed.
*/
public static void readerToCharacters(Reader reader, ContentHandler contentHandler) {
try {
// Work with buffered Reader
reader = new BufferedReader(reader);
// Read and write in chunks
char[] buf = new char[1024];
int count;
while ((count = reader.read(buf)) != -1)
contentHandler.characters(buf, 0, count);
} catch (Exception e) {
throw new OXFException(e);
}
}
}