package com.limegroup.gnutella.xml; import java.io.IOException; import java.io.StringReader; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParserFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.limewire.service.ErrorService; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; /** * 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<LimeParser> _parserContainer = new ThreadLocal<LimeParser>() { @Override protected LimeParser initialValue() { return new LimeParser(); } }; /** * Parses our simplified XML. */ public static ParseResult parse(String xml, int responseCount) throws IOException, SAXException { xml = LimeXMLUtils.scanForBadCharacters(xml); 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 = _parserContainer.get(); parser.parse(result,inputSource); return result; } /** * Splits an aggregated XML string into individual XML strings. * @return List of Strings */ public static List<String> split(String aggregatedXmlDocuments) { List<String> results = new ArrayList<String>(); 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<Map<String, String>> { 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 = SAXParserFactory.newInstance().newSAXParser().getXMLReader(); reader.setContentHandler(this); reader.setErrorHandler(this); reader.setFeature("http://xml.org/sax/features/namespaces", false); }catch(SAXException bad) { ErrorService.error(bad); reader = null; } catch (ParserConfigurationException 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); } @Override public void startElement(String namespaceUri, String localName, String qualifiedName, Attributes attributes) { int attributesLength; String qName; 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+"__"; } attributesLength = attributes.getLength(); //convert prefix to lower case to prevent capitalized tags from appearing _result.canonicalKeyPrefix = _result.canonicalKeyPrefix.toLowerCase(Locale.US); if(attributesLength > 0) { Map<String, String> attributeMap = new HashMap<String, String>(attributesLength); for(int i = 0; i < attributesLength; i++) { //everything goes to lowercase qName = attributes.getQName(i).toLowerCase(Locale.US); if(!qName.equals("comments")) { // ignore comments! attributeMap.put((_result.canonicalKeyPrefix + qName + "__").intern(), attributes.getValue(i).trim().intern()); } } _result.add(attributeMap); } else { Map<String, String> empty = Collections.emptyMap(); _result.add(empty); } } @Override public void fatalError(SAXParseException e) throws SAXException { LOG.fatal("Fatal parsing error", e); throw e; } @Override public void warning(SAXParseException e) throws SAXException { LOG.warn("parse warning", e); } @Override public void error(SAXParseException e) throws SAXException { LOG.error("Parsing error", e); } } }