Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse itunes plist file without JAXB #11

Merged
merged 1 commit into from
May 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 1 addition & 30 deletions build.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,40 +37,11 @@
<mkdir dir="${dst.lib}" />
</target>

<target name="plist_binding" depends="prepare">
<exec executable="xjc" >
<arg line="-p com.bolsinga.plist.data -d ${build.gensrc} ${basedir}/xml/PropertyList-1.0.xsd" />
</exec>
<javac destdir="${build.classes}"
debug="${javac.debug}"
deprecation="on"
includeantruntime="false">
<src path="${build.gensrc}" />
<include name="com/bolsinga/plist/data/**" />
</javac>
<copy todir="${build.classes}">
<fileset dir="${build.gensrc}" >
<exclude name="**/*.java" />
</fileset>
</copy>
</target>

<path id="web_classpath">
<pathelement path="${lib.dir}/ecs-1.4.2.jar" />
<pathelement path="${lib.dir}/json.jar" />
</path>

<target name="plist_utilities" depends="plist_binding">
<javac destdir="${build.classes}"
debug="${javac.debug}"
deprecation="on"
includeantruntime="false">
<compilerarg value="${javac.lint}" />
<src path="${src.dir}" />
<include name="com/bolsinga/plist/*.java" />
</javac>
</target>

<target name="web">
<javac destdir="${build.classes}"
debug="${javac.debug}"
Expand All @@ -97,7 +68,7 @@
</copy>
</target>

<target name="itunes" depends="plist_utilities">
<target name="itunes" depends="prepare">
<javac destdir="${build.classes}"
debug="${javac.debug}"
deprecation="on"
Expand Down
151 changes: 50 additions & 101 deletions src/com/bolsinga/itunes/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,8 @@
import java.io.*;
import java.util.*;
import java.util.regex.*;

import javax.xml.bind.*;
import javax.xml.datatype.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;

public class Parser {
private static final String TK_GENRE_VOICE_MEMO = "Voice Memo";
Expand Down Expand Up @@ -82,7 +81,8 @@ private void addTrack(final Track track) {
int year = (track.getYear() != null) ? Integer.parseInt(track.getYear()) : 0;
int trackNumber = (track.getTrack_Number() != null) ? Integer.parseInt(track.getTrack_Number()) : 0;
int playCount = (track.getPlay_Count() != null) ? Integer.parseInt(track.getPlay_Count()) : 0;
GregorianCalendar lastPlayed = (track.getPlay_Date_UTC() != null) ? com.bolsinga.web.Util.fromJSONCalendar(track.getPlay_Date_UTC()) : null;
java.time.ZonedDateTime lastPlayedZDT = (track.getPlay_Date_UTC() != null) ? java.time.ZonedDateTime.parse(track.getPlay_Date_UTC()) : null;
GregorianCalendar lastPlayed = (lastPlayedZDT != null) ? GregorianCalendar.from(lastPlayedZDT): null;
createTrack(track.getArtist(), track.getSort_Artist(), track.getName(), track.getAlbum(), year, trackNumber, track.getGenre(), lastPlayed, playCount, compilation);
}
}
Expand Down Expand Up @@ -203,123 +203,72 @@ private void setAlbumYears() {
}
}

private static com.bolsinga.plist.data.Plist createPlist(final String sourceFile) throws ParserException {
com.bolsinga.plist.data.Plist plist = null;

private static Map<String, Object> 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<Track> createTracks(final String sourceFile) throws ParserException {
com.bolsinga.plist.data.Dict dict = createTracksDict(sourceFile);

ArrayList<Track> tracks = new ArrayList<Track>();
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<Object> 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<String, Object> createTracksDict(final String sourceFile) throws ParserException {
Map<String, Object> plist = createPlist(sourceFile);

Iterator<Object> i = plist.getDict().getKeyAndArrayOrData().iterator();
while (i.hasNext()) {
JAXBElement<? extends Object> jo = (JAXBElement<? extends Object>)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<String, Object> entry : plist.entrySet()) {
if (entry.getKey().equals("Tracks")) {
@SuppressWarnings("unchecked")
Map<String, Object> dict = (Map<String, Object>)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<Object> i = trackDict.getKeyAndArrayOrData().iterator();

Track t = new Track();

while (i.hasNext()) {
JAXBElement<? extends Object> jokey = (JAXBElement<? extends Object>)i.next();
String key = (String)jokey.getValue();

// always pull off the value, it may be unused.
JAXBElement<? extends Object> jovalue = (JAXBElement<? extends Object>)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<Track> createTracks(final String sourceFile) throws ParserException {
Map<String, Object> tracksDict = createTracksDict(sourceFile);

ArrayList<Track> tracks = new ArrayList<Track>();

for (Object o : tracksDict.values()) {
@SuppressWarnings("unchecked")
Map<String, Object> trackDict = (Map<String, Object>)o;

Track track = new Track();

for (Map.Entry<String, Object> entry : trackDict.entrySet()) {
track.set(entry.getKey(), (String)entry.getValue());
}

t.set(key, stringValue);
tracks.add(track);
}

return t;
return tracks;
}
}
134 changes: 134 additions & 0 deletions src/com/bolsinga/itunes/ParserHandler.java
Original file line number Diff line number Diff line change
@@ -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<String, Object> plist;

private Stack<AbstractMap.SimpleEntry<String, Object>> mapEntryStack = new Stack<AbstractMap.SimpleEntry<String, Object>>();

private Stack<String> elementStack = new Stack<String>();

private Stack<Map<String, Object>> dictStack = new Stack<Map<String, Object>>();

private Stack<Boolean> parsingArrayStack = new Stack<Boolean>();

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<String, Object> dictionary = new LinkedHashMap<String, Object>();
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<String, Object> currentMapEntry = currentMapEntry();
if (currentMapEntry != null) {
if (currentMapEntry.getValue() != null) {
throw new SAXException("Can't set dictionary value for: " + currentMapEntry.toString());
}
Map<String, Object> 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<String, Object> mapEntry = this.mapEntryStack.pop();
currentDict().put(mapEntry.getKey(), mapEntry.getValue());
} else if ("true".equals(qName) || "false".equals(qName)) {
AbstractMap.SimpleEntry<String, Object> 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<String, Object> mapEntry = new AbstractMap.SimpleEntry<String, Object>(value, null);
this.mapEntryStack.push(mapEntry);
} else {
AbstractMap.SimpleEntry<String, Object> 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<String, Object> currentDict() {
if (this.dictStack.empty()) {
return null;
} else {
return this.dictStack.peek();
}
}

private AbstractMap.SimpleEntry<String, Object> currentMapEntry() {
if (this.mapEntryStack.empty()) {
return null;
} else {
return this.mapEntryStack.peek();
}
}
}
Loading