From bf0402afa1eb5b6bbada133a0af3d1210f353bcd Mon Sep 17 00:00:00 2001 From: Greg Bolsinga Date: Sat, 23 May 2020 19:58:19 -0700 Subject: [PATCH] Parse itunes plist file without JAXB (#11) Use built in parsing instead. --- build.xml | 31 +---- src/com/bolsinga/itunes/Parser.java | 151 +++++++-------------- src/com/bolsinga/itunes/ParserHandler.java | 134 ++++++++++++++++++ xml/PropertyList-1.0.xsd | 74 ---------- 4 files changed, 185 insertions(+), 205 deletions(-) create mode 100644 src/com/bolsinga/itunes/ParserHandler.java delete mode 100644 xml/PropertyList-1.0.xsd diff --git a/build.xml b/build.xml index 93208eea..b105139e 100644 --- a/build.xml +++ b/build.xml @@ -37,40 +37,11 @@ - - - - - - - - - - - - - - - - - - - - - - - - + createPlist(final String sourceFile) throws ParserException { InputStream is = null; try { - try { - is = new FileInputStream(sourceFile); - } catch (FileNotFoundException e) { - StringBuilder sb = new StringBuilder(); - sb.append("Can't find plist file: "); - sb.append(sourceFile); - throw new ParserException(sb.toString(), e); - } - - javax.xml.stream.XMLStreamReader xmlStreamReader = null; - try { - javax.xml.stream.XMLInputFactory xmlInputFactory = javax.xml.stream.XMLInputFactory.newInstance(); - xmlInputFactory.setProperty(javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD, "http"); - xmlStreamReader = xmlInputFactory.createXMLStreamReader(is); - } catch (javax.xml.stream.XMLStreamException e) { - StringBuilder sb = new StringBuilder(); - sb.append("Can't create XML Reader: "); - sb.append(is); - throw new ParserException(sb.toString(), e); - } - - try { - JAXBContext jc = JAXBContext.newInstance("com.bolsinga.plist.data"); - Unmarshaller u = jc.createUnmarshaller(); - - plist = (com.bolsinga.plist.data.Plist)u.unmarshal(xmlStreamReader); - } catch (JAXBException e) { - StringBuilder sb = new StringBuilder(); - sb.append("Can't unmarshal plist file: "); - sb.append(sourceFile); - throw new ParserException(sb.toString(), e); - } - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException e) { - StringBuilder sb = new StringBuilder(); - sb.append("Unable to close plist file: "); - sb.append(sourceFile); - throw new ParserException(sb.toString(), e); - } - } + is = new FileInputStream(sourceFile); + } catch (FileNotFoundException e) { + StringBuilder sb = new StringBuilder(); + sb.append("Can't find plist file: "); + sb.append(sourceFile); + throw new ParserException(sb.toString(), e); } - return plist; - } - - private static List createTracks(final String sourceFile) throws ParserException { - com.bolsinga.plist.data.Dict dict = createTracksDict(sourceFile); - - ArrayList tracks = new ArrayList(); + XMLReader parser = null; + try { + parser = XMLReaderFactory.createXMLReader(); + } catch (SAXException e) { + StringBuilder sb = new StringBuilder(); + sb.append("Can't create XMLReader"); + throw new ParserException(sb.toString(), e); + } - Iterator i = dict.getKeyAndArrayOrData().iterator(); - while (i.hasNext()) { - Object key = i.next(); // key not used + ParserHandler handler = new ParserHandler(); + parser.setContentHandler(handler); - com.bolsinga.plist.data.Dict trackDict = (com.bolsinga.plist.data.Dict)i.next(); - Track track = createTrack(trackDict); - tracks.add(track); + InputSource source = new InputSource(is); + try { + parser.parse(source); + } catch (IOException | SAXException e) { + StringBuilder sb = new StringBuilder(); + sb.append("Can't parse InputSource"); + throw new ParserException(sb.toString(), e); } - return tracks; + return handler.plist; } - private static com.bolsinga.plist.data.Dict createTracksDict(final String sourceFile) throws ParserException { - com.bolsinga.plist.data.Plist plist = createPlist(sourceFile); + private static Map createTracksDict(final String sourceFile) throws ParserException { + Map plist = createPlist(sourceFile); - Iterator i = plist.getDict().getKeyAndArrayOrData().iterator(); - while (i.hasNext()) { - JAXBElement jo = (JAXBElement)i.next(); - String key = (String)jo.getValue(); - if (key.equals("Tracks")) { - com.bolsinga.plist.data.Dict dict = (com.bolsinga.plist.data.Dict)i.next(); + for (Map.Entry entry : plist.entrySet()) { + if (entry.getKey().equals("Tracks")) { + @SuppressWarnings("unchecked") + Map dict = (Map)entry.getValue(); return dict; - } else { - Object o = i.next(); } } throw new ParserException("No Tracks key in plist: " + plist.toString()); } - private static Track createTrack(final com.bolsinga.plist.data.Dict trackDict) throws ParserException { - Iterator i = trackDict.getKeyAndArrayOrData().iterator(); - - Track t = new Track(); - - while (i.hasNext()) { - JAXBElement jokey = (JAXBElement)i.next(); - String key = (String)jokey.getValue(); - - // always pull off the value, it may be unused. - JAXBElement jovalue = (JAXBElement)i.next(); - - Object value = jovalue.getValue(); - String stringValue = null; - if (value instanceof java.lang.String) { - stringValue = (String)value; - } else if (value instanceof java.lang.Number) { - stringValue = String.valueOf((Number)value); - } else if (value instanceof javax.xml.datatype.XMLGregorianCalendar) { - GregorianCalendar gc = ((XMLGregorianCalendar)value).toGregorianCalendar(); - stringValue = com.bolsinga.web.Util.toJSONCalendar(gc); - } else if (value instanceof java.lang.Object && ("true".equals(jovalue.getName().getLocalPart()) || "false".equals(jovalue.getName().getLocalPart()))) { - stringValue = jovalue.getName().getLocalPart(); - } else { - throw new ParserException("Unhandled JAXB Name: " + jovalue.getName() + " Value: " + value.toString() + " Type: " + jovalue.getDeclaredType().toString()); + private static List createTracks(final String sourceFile) throws ParserException { + Map tracksDict = createTracksDict(sourceFile); + + ArrayList tracks = new ArrayList(); + + for (Object o : tracksDict.values()) { + @SuppressWarnings("unchecked") + Map trackDict = (Map)o; + + Track track = new Track(); + + for (Map.Entry entry : trackDict.entrySet()) { + track.set(entry.getKey(), (String)entry.getValue()); } - t.set(key, stringValue); + tracks.add(track); } - return t; + return tracks; } } diff --git a/src/com/bolsinga/itunes/ParserHandler.java b/src/com/bolsinga/itunes/ParserHandler.java new file mode 100644 index 00000000..3ea98e0e --- /dev/null +++ b/src/com/bolsinga/itunes/ParserHandler.java @@ -0,0 +1,134 @@ +package com.bolsinga.itunes; + +import java.util.*; +import org.xml.sax.*; +import org.xml.sax.helpers.*; + +class ParserHandler extends DefaultHandler { + public Map plist; + + private Stack> mapEntryStack = new Stack>(); + + private Stack elementStack = new Stack(); + + private Stack> dictStack = new Stack>(); + + private Stack parsingArrayStack = new Stack(); + + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + this.elementStack.push(qName); + + if ("array".equals(qName)) { + if (this.parsingArrayStack.empty()) { + this.mapEntryStack.pop(); // Remove the key for this array. + } + this.parsingArrayStack.push(Boolean.TRUE); + } else { + if (this.parsingArrayStack.empty()) { + if ("plist".equals(qName)) { + // Nothing + } else if ("dict".equals(qName)) { + Map dictionary = new LinkedHashMap(); + this.dictStack.push(dictionary); + } else if ("key".equals(qName)) { + // Nothing + } else { + if ("data".equals(qName) || + "date".equals(qName) || + "false".equals(qName) || + "integer".equals(qName) || + "string".equals(qName) || + "true".equals(qName)) { + // Nothing + } else { + throw new SAXException("Unknown element: " + qName); + } + } + } + } + } + + public void endElement(String uri, String localName, String qName) throws SAXException { + this.elementStack.pop(); + + if ("array".equals(qName)) { + this.parsingArrayStack.pop(); + } else { + if (this.parsingArrayStack.empty()) { + if ("plist".equals(qName)) { + plist = dictStack.pop(); + } else if ("dict".equals(qName)) { + AbstractMap.SimpleEntry currentMapEntry = currentMapEntry(); + if (currentMapEntry != null) { + if (currentMapEntry.getValue() != null) { + throw new SAXException("Can't set dictionary value for: " + currentMapEntry.toString()); + } + Map endedDict = dictStack.pop(); + currentMapEntry.setValue(endedDict); + + // Now pop it + currentMapEntry = this.mapEntryStack.pop(); + + // need to re-access the new current dict + currentDict().put(currentMapEntry.getKey(), currentMapEntry.getValue()); + } else { + // A-OK + } + } else if ("key".equals(qName)) { + // Nothing + } else { + if ("data".equals(qName) || + "date".equals(qName) || + "integer".equals(qName) || + "string".equals(qName)) { + AbstractMap.SimpleEntry mapEntry = this.mapEntryStack.pop(); + currentDict().put(mapEntry.getKey(), mapEntry.getValue()); + } else if ("true".equals(qName) || "false".equals(qName)) { + AbstractMap.SimpleEntry mapEntry = this.mapEntryStack.pop(); + mapEntry.setValue(qName); + currentDict().put(mapEntry.getKey(), mapEntry.getValue()); + } else { + throw new SAXException("Unknown element: " + qName); + } + } + } + } + } + + public void characters(char[] ch, int start, int length) throws SAXException { + String value = new String(ch, start, length); + + if (this.parsingArrayStack.empty()) { + if ("key".equals(elementStack.peek())) { + AbstractMap.SimpleEntry mapEntry = new AbstractMap.SimpleEntry(value, null); + this.mapEntryStack.push(mapEntry); + } else { + AbstractMap.SimpleEntry currentMapEntry = currentMapEntry(); + String currentValue = (String)currentMapEntry.getValue(); + if (currentValue != null) { + StringBuffer sb = new StringBuffer(currentValue); + sb.append(value); + currentMapEntry.setValue(sb.toString()); + } else { + currentMapEntry.setValue(value); + } + } + } + } + + private Map currentDict() { + if (this.dictStack.empty()) { + return null; + } else { + return this.dictStack.peek(); + } + } + + private AbstractMap.SimpleEntry currentMapEntry() { + if (this.mapEntryStack.empty()) { + return null; + } else { + return this.mapEntryStack.peek(); + } + } +} diff --git a/xml/PropertyList-1.0.xsd b/xml/PropertyList-1.0.xsd deleted file mode 100644 index 42f7e7c5..00000000 --- a/xml/PropertyList-1.0.xsd +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -