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.