package com.limegroup.gnutella.xml;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Collections;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.apache.xerces.parsers.SAXParser;
import com.limegroup.gnutella.ErrorService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Provides just enough functionality for our simple schemas,
* based on SAX
* @author tjones
*/
public class XMLParsingUtils {
private static final Log LOG = LogFactory.getLog(XMLParsingUtils.class);
static final private String XML_START = "<?xml";
/**
* a ThreadLocal to contain the instance of the Lime parser
*/
private static ThreadLocal _parserContainer = new ThreadLocal() {
protected Object initialValue() {
return new LimeParser();
}
};
/**
* Parses our simplified XML
*/
public static ParseResult parse(String xml, int responseCount)
throws IOException, SAXException {
return parse(new InputSource(new StringReader(xml)),responseCount);
}
public static ParseResult parse(InputSource inputSource)
throws IOException,SAXException {
return parse(inputSource, 8);
}
/**
* Parses our simplified XML
*/
public static ParseResult parse(InputSource inputSource, int responseCount)
throws IOException, SAXException {
ParseResult result = new ParseResult(responseCount);
LimeParser parser = (LimeParser)_parserContainer.get();
parser.parse(result,inputSource);
return result;
}
/**
* Splits an aggregated XML string into individual XML strings
* @param aggregatedXmlDocuments
* @return List of Strings
*/
public static List split(String aggregatedXmlDocuments) {
List results = new ArrayList();
int begin=aggregatedXmlDocuments.indexOf(XML_START);
int end=aggregatedXmlDocuments.indexOf(XML_START,begin+1);
while(end!=-1) {
results.add(aggregatedXmlDocuments.substring(begin,end));
begin = end;
end = aggregatedXmlDocuments.indexOf(XML_START,begin+1);
}
if(begin!=-1)
results.add(aggregatedXmlDocuments.substring(begin));
return results;
}
/**
* A list of maps, also containing the Schema URI, the type and
* the canonical key prefix
*/
public static class ParseResult extends ArrayList {
public ParseResult(int size) {
super(size*2/3);
}
public String schemaURI; //like http://www.limewire.com/schemas/audio.xsd
public String type; //e.g. audio, video, etc.
public String canonicalKeyPrefix; //like audios__audio__
}
/**
* this class does the actual parsing of the document. It is a reusable
* DocumentHandler.
*/
private static class LimeParser extends DefaultHandler {
private final XMLReader _reader;
private ParseResult _result;
boolean _isFirstElement=true;
LimeParser() {
XMLReader reader;
try {
reader = new SAXParser();
reader.setContentHandler(this);
reader.setFeature("http://xml.org/sax/features/namespaces", false);
}catch(SAXException bad) {
ErrorService.error(bad);
reader = null;
}
_reader=reader;
}
/**
* parses the given document input. Any state from previous parsing is
* discarded.
*/
public void parse(ParseResult dest, InputSource input)
throws SAXException, IOException {
//if parser creation failed, do not try to parse.
if (_reader==null)
return;
_isFirstElement=true;
_result = dest;
_reader.parse(input);
}
public void startElement(String namespaceUri, String localName,
String qualifiedName, Attributes attributes) {
if(_isFirstElement) {
_isFirstElement=false;
_result.canonicalKeyPrefix = qualifiedName;
return;
}
if(_result.type==null) {
_result.type = qualifiedName;
_result.schemaURI = "http://www.limewire.com/schemas/"+_result.type+".xsd";
_result.canonicalKeyPrefix += "__"+qualifiedName+"__";
}
int attributesLength = attributes.getLength();
if(attributesLength > 0) {
Map attributeMap = new HashMap(attributesLength);
for(int i = 0; i < attributesLength; i++) {
attributeMap.put(_result.canonicalKeyPrefix +
attributes.getQName(i) + "__",
attributes.getValue(i).trim());
}
_result.add(attributeMap);
} else {
_result.add(Collections.EMPTY_MAP);
}
}
}
}