Reputation: 13
So I'm trying to create a method that allows me to input a track ID that will then return the track name which belongs to the track ID.
I am required to use XPath to parse the XML document into java which will in turn serialize a new library. A sample of my XML document is here:
<plist version="1.0">
<dict>
<key>Major Version</key>
<integer>1</integer>
<key>Minor Version</key>
<integer>1</integer>
<key>Date</key>
<date>2015-03-16T15:04:23Z</date>
<key>Application Version</key>
<string>12.1.0.71</string>
<key>Features</key>
<integer>5</integer>
<key>Show Content Ratings</key>
<true/>
<key>Music Folder</key>
<string>
file://localhost/C:/Users/Mark/Music/iTunes/iTunes%20Media/
</string>
<key>Library Persistent ID</key>
<string>3B01AE08EA513C21</string>
<key>Tracks</key>
<dict>
<key>646</key>
<dict>
<key>Track ID</key>
<integer>646</integer>
<key>Name</key>
<string>Save Me</string>
<key>Artist</key>
<string>Avenged Sevenfold</string>
<key>Album Artist</key>
<string>Avenged Sevenfold</string>
<key>Album</key>
<string>Nightmare</string>
<key>Genre</key>
<string>Metal</string>
<key>Kind</key>
<string>MPEG audio file</string>
<key>Size</key>
<integer>23257166</integer>
<key>Total Time</key>
<integer>656535</integer>
<key>Disc Number</key>
<integer>1</integer>
<key>Disc Count</key>
<integer>1</integer>
<key>Track Number</key>
<integer>11</integer>
<key>Track Count</key>
<integer>11</integer>
<key>Year</key>
<integer>2010</integer>
<key>Date Modified</key>
<date>2012-10-21T22:07:20Z</date>
<key>Date Added</key>
<date>2012-10-21T22:07:20Z</date>
<key>Bit Rate</key>
<integer>276</integer>
<key>Sample Rate</key>
<integer>44100</integer>
<key>Play Count</key>
<integer>2</integer>
<key>Play Date</key>
<integer>3415934327</integer>
<key>Play Date UTC</key>
<date>2012-03-30T06:38:47Z</date>
<key>Artwork Count</key>
<integer>1</integer>
<key>Persistent ID</key>
<string>0000000000001389</string>
<key>Track Type</key>
<string>File</string>
<key>Location</key>
<string>
file://localhost/C:/Users/Mark/Music/Avenged%20Sevenfold/Nightmare/11%20-%20Save%20Me.mp3
</string>
<key>File Folder Count</key>
<integer>2</integer>
<key>Library Folder Count</key>
<integer>1</integer>
</dict>
<key>648</key>
<dict>
<key>Track ID</key>
<integer>648</integer>
<key>Name</key>
<string>Welcome 2 Hell</string>
<key>Artist</key>
<string>Bad Meets Evil</string>
<key>Album Artist</key>
<string>Bad Meets Evil</string>
<key>Composer</key>
<string>Havoc, Magnedo7</string>
<key>Album</key>
<string>Hell: The Sequel (Deluxe Edition)</string>
<key>Genre</key>
<string>Rap</string>
<key>Kind</key>
<string>MPEG audio file</string>
<key>Size</key>
<integer>7467977</integer>
<key>Total Time</key>
<integer>177606</integer>
<key>Track Number</key>
<integer>1</integer>
<key>Year</key>
<integer>2011</integer>
<key>Date Modified</key>
<date>2012-10-21T22:07:20Z</date>
<key>Date Added</key>
<date>2012-10-21T22:07:20Z</date>
<key>Bit Rate</key>
<integer>320</integer>
<key>Sample Rate</key>
<integer>44100</integer>
<key>Play Count</key>
<integer>3</integer>
<key>Play Date</key>
<integer>3424485861</integer>
<key>Play Date UTC</key>
<date>2012-07-07T06:04:21Z</date>
<key>Skip Count</key>
<integer>2</integer>
<key>Skip Date</key>
<date>2012-11-26T14:02:44Z</date>
<key>Artwork Count</key>
<integer>1</integer>
<key>Persistent ID</key>
<string>000000000000138A</string>
<key>Track Type</key>
<string>File</string>
<key>Location</key>
<string>
file://localhost/C:/Users/Mark/Music/Bad%20Meets%20Evil/Hell_%20The%20Sequel%20(Deluxe%20Edition)/01%20-%20Welcome%202%20Hell.mp3
</string>
<key>File Folder Count</key>
<integer>2</integer>
<key>Library Folder Count</key>
<integer>1</integer>
</dict>
</plist>
Now, I'm fairly new to XPath and XML in general and am struggling to navigate the iTunes XML file, due to it's complexity and massive size.
So far my thoughts are to navigate to the <key>646</key>
to check the id, then to the track name <string>Save Me</string>
using "//dict/key[.="646"]/string[1]/text()"
.
This produces NULL. The code i have written in Java so far is:
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public class XMLparse {
public XMLparse(){
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder;
Document doc = null;
try {
builder = factory.newDocumentBuilder();
doc = builder.parse(new File("C:\\musicLibrary.xml"));
// Create XPathFactory object
XPathFactory xpathFactory = XPathFactory.newInstance();
// Create XPath object
XPath xpath = xpathFactory.newXPath();
int id = 646;
String name = getTrackNameById(doc, xpath, id);
System.out.println("Track Name with ID " + id + ": " + name);
} catch (ParserConfigurationException | SAXException | IOException e) {
e.printStackTrace();
}
}
public static String getTrackNameById(Document doc, XPath xpath, int id) {
String name = null;
try {
XPathExpression expr = xpath.compile("//dict/integer[.="+id+"]/string[1]/text()");
name = (String) expr.evaluate(doc, XPathConstants.STRING);
} catch (XPathExpressionException e) {
e.printStackTrace();
}
return name;
}
}
Any help will be greatly appreciated.
EDIT:
Using Mathias Müller's suggestion produced the correct result, "Save Me" for track 646 as expected. However when I input another track ID it also returns "Save Me", which is incorrect.
I've no idea why it is doing this as I thought it would only return the track name of the ID i input, yet it returns a different track name?
SECOND EDIT:
-Included more of the XML
THIRD EDIT:
Changed the XPath expression to //dict[integer ="+id+"]/string[1]/text()
, using Mathias' advice. This is working perfectly.
Upvotes: 1
Views: 1172
Reputation: 22637
I cannot comment on the Java code, but I can explain to you the XPath expression you should use. Assuming the input sample you have shown, applying
//dict[key='646']/dict/key[. = 'Name']/following-sibling::*[1]
will return
<string>Save Me</string>
which is the element you were looking for. To only select its text content, use
//dict[key='646']/dict/key[. = 'Name']/following-sibling::*[1]/text()
and the result will be
Save Me
The path expression works as follows:
//dict select `dict` elements anywhere in the document
[key='646'] but only if they have an immediate child `key` whose text
content is equal to "646"
/dict select their child elements called `dict`
/key[. = 'Name'] of those `dict` elements select their child elements `key`,
but only if their text content is equal to "Name"
/following-sibling::*[1] of those `key` elements, select the first following sibling
element
/text() and select its text content
Your original expression, relying on the position of the string
element, also works with minor changes:
//dict[key ="646"]/dict/string[1]/text()
Upvotes: 1