iTunes library import
Posted: Sun Feb 25, 2007 12:38 pm
I wrote this class as the first part of a suite I'm building to compare 2 iTunes libraries. This class will open an iTunes library/playlist export file (File->Export, select 'XML') & transfer it into a PHP array. I was going to use PHP5 & SimpleXML, but SimpleXML doesn't duplicate the structure of the file & does so in a pretty much random fashion. So, I'm back to the old way.
Let me know if any of the comments aren't clear/to many comments/to few comments. I'm trying to improve my documentation. Heck, if you can find any optimization, that won't hurt either.
Update: I've just discovered that having an ampersand (&) in the data somewhere will bugger up the xml parser. The string that gets sent to xml_character_data() arrives with the only the text after the ampersand (ie: '(Above & Beyond Mix)' in the XML file gets turned into ' Beyond Mix)'). I've discovered the xml_parse_into_struct() function which I can use to keep that ampersand & drastically cut down on the code. However, it does take about 33% longer to run, so I'd like a fix using this method if possible.
Any ideas anyone? If I don't get any in a couple days, I'll add another post below with the new code.
Usage is in the comments at the head of the code.
Let me know if any of the comments aren't clear/to many comments/to few comments. I'm trying to improve my documentation. Heck, if you can find any optimization, that won't hurt either.
Update: I've just discovered that having an ampersand (&) in the data somewhere will bugger up the xml parser. The string that gets sent to xml_character_data() arrives with the only the text after the ampersand (ie: '(Above & Beyond Mix)' in the XML file gets turned into ' Beyond Mix)'). I've discovered the xml_parse_into_struct() function which I can use to keep that ampersand & drastically cut down on the code. However, it does take about 33% longer to run, so I'd like a fix using this method if possible.
Any ideas anyone? If I don't get any in a couple days, I'll add another post below with the new code.
Code: Select all
<?PHP
/*******************
* iTunesImport
*
* A class for importing your iTunes library into an array
* After running, your iTunes tracklist will be stored in $this->tracks
*
* Usage:
* $iTunesImport = new iTunesImport('/path/to/iTunes/generated/xml');
* $iTunesImport->parse();
* $my_XML_as_an_array = $iTunesImport->tracks();
*
* Author: Dylan Anderson
* Date: February 25, 2007
* License: http://www.gnu.org/licenses/lgpl.txt Lesser GNU Public License
* Website: http://nderson.ca
*
* Copyright: copyright 2007 Dylan Anderson - All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to
*
* The Free Software Foundation, Inc.,
* 51 Franklin Street,
* Fifth Floor,
* Boston,
* MA 02110-1301 USA
*/
class iTunesImport
{
//The file to import: full file-system path
//ie: /usr/local/xml/library.xml
var $file = FALSE;
//The array of tracks. This will be populated after
//calling $this->parse()
var $tracks = array();
//The XML parser resource
var $parser = FALSE;
//Used to keep track of what song is currently being parsed
//set by xml_start_element()
//read by xml_character_data()
//initialized to -1 so that the array is 0-based.
var $track_index = -1;
//Used to keep track of the parsers location in the file
//set by xml_start_element(),xml_end_element()
//read by xml_start_element(),xml_end_element(),xml_character_data()
var $depth = 0;
//Used to tell xml_character_data what to do with the data it receives
//set by xml_start_element(),xml_end_element()
//read by xml_character_data()
var $data_is_key = FALSE;
var $data_is_value = FALSE;
//Used by xml_character_data() to match up key-value pairs when inserting
//data into the array
//set & read by xml_character_data()
var $curr_key = FALSE;
/**************************
* Function: iTunesImport()
* Purpose: Constructor of iTunesImport class
* Parameters: @file: a full file system path to the xml file to parse
* ie: /usr/local/xml/library.xml
*/
function iTunesImport($file)
{
if(file_exists($file))
$this->file = $file;
}
/**************************
* Function: parse()
* Purpose: To parse through $this->file & insert the data found
* into the $this->tracks array
* Parameters: None
* Returns: TRUE if the file was read in correctly,
* FALSE if not
* FALSE if no file was found
* References: Nothing directly, but sets up $this->xml_start_element(),
* $this->xml_end_element() & $this->xml_character_data() as
* xml parsing functions
*/
function parse()
{
if($this->file)
{
$this->parser = xml_parser_create();
xml_set_object($this->parser,&$this);
xml_set_element_handler($this->parser,'xml_start_element','xml_end_element');
xml_set_character_data_handler($this->parser,'xml_character_data');
$success = xml_parse($this->parser,file_get_contents($this->file));
return ($success == 1) ? TRUE : FALSE;
}
else
return FALSE;
}
/**************************
* Function: xml_start_element()
* Purpose: This function is called whenever the XML parser comes across an
* opening tag. Depending on the tag, this function updates
* different object variables. See comments in the switch() for more
* info.
* Parameters: @parser_obj: the parser resource
* @element_name: the name of the XML node currently being parsed
* @attributes: an array of any attributes the current element has
* indexed by attribute name
* Note: This function by definition must accept these three
* arguments even though $parser_obj & $attributes aren't
* referenced.
* Returns: Nothing
* References: $this->depth,
* $this->track_index,
* $this->data_is_key,
* $this->data_is_value
*
* Note: By default, 'CASE FOLDING' is turned on. This uppercases all tags.
*/
function xml_start_element($parser_obj,$element_name,$attributes)
{
switch($element_name)
{
//PLIST is the opening tag
case 'PLIST':
$this->depth = 1;
break;
//DICT is found in a number of levels. The 'KEY' case acts differently
//accordingly to which level is currently being iterated through, so this
//needs to be updated
case 'DICT':
$this->depth++;
break;
//The KEY tag has multiple uses:
// If inside the <DICT> tag that denotes the file list (depth == 3),
// then it means the next song has been reached.
// If inside the <DICT> tag that denotes the track properties list
// (depth == 4), then it means the next data retrieved will be a key
// value to use in $this->tracks
case 'KEY':
if($this->depth == 3)
$this->track_index++;
else if($this->depth == 4)
$this->data_is_key = TRUE;
break;
//If tag is any of these & inside the <DICT> tag that denotes the track
//properties list, then the next data retrieved will be a value to insert
//into $this->tracks
case 'STRING':
case 'DATE':
case 'INTEGER':
if($this->depth == 4)
$this->data_is_value = TRUE;
break;
}
}
/**************************
* Function: xml_end_element()
* Purpose: This function is called whenever the XML parser comes across a
* closing XML tag. Depending on the tag, this function updates
* different object variables. See comments in the switch() for more
* info.
* Parameters: @parser_obj: the parser resource
* @element_name: the name of the XML node currently being parsed
* Note: This function by definition must accept these two
* arguments even though $parser_obj isn't referenced.
* Returns: Nothing
* References: $this->depth,
* $this->track_index,
* $this->data_is_key,
* $this->data_is_value
*/
function xml_end_element($parser_obj,$element_name)
{
switch($element_name)
{
//a closing PLIST tag means we're done with the file
case 'PLIST':
$this->depth = 0;
break;
//DICT occurs on many levels, so we need to update $this->depth to keep
//track of where we are
case 'DICT':
$this->depth--;
break;
//a closing KEY tag means the next data retrieved cannot be key data
case 'KEY':
$this->data_is_key = FALSE;
break;
//these closing tags mean the next data retrieved is not necessarily
//a value
case 'STRING':
case 'DATE':
case 'INTEGER':
$this->data_is_value = FALSE;
break;
}
}
/**************************
* Function: xml_character_data()
* Purpose: This function is called whenever data between an opening and
* closing tag is found & when that data isn't other opening & closing
* tags.
* This function takes the data that is found & either stores it in
* $this->curr_key for use when the relevant data is found, or stores
* the said relevant data in $this->tracks
* Parameters: @parser_obj: the parser resource
* @data: the data (usually text) that was found
* Note: This function by definition must accept these two
* arguments even though $parser_obj isn't referenced.
* Returns: Nothing
* References: $this->depth,
* $this->track_index,
* $this->data_is_key,
* $this->data_is_value
*/
function xml_character_data($parser_obj,$data)
{
//if this data is key data, store it for when we next come across value data
if($this->data_is_key)
$this->curr_key = $data;
//if this data is value data, use the current key to store it in
//$this->tracks
else if($this->data_is_value)
$this->tracks[$this->track_index][$this->curr_key] = $data;
}
}
?>Usage is in the comments at the head of the code.