user3068854
user3068854

Reputation: 13

Java Parsing iTunes XML library using XPath

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

Answers (1)

Mathias M&#252;ller
Mathias M&#252;ller

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

Related Questions