XML, Menus, and PHP
XML, Menus, and PHP The menu you see on the left is dynamically driven from an XML File using PHP. (that menu is no longer there but go with it anyway -ED) Why, you ask? What would possess me to do such a thing? Very simple, I didn't want to pay for a DB server on the host. Also I wanted it to be dynamically driven. The answer? A text file. But how to easily get information out of the text file and onto the page? Again an answer presents itself....
- XML: A way to store and parse data in text files
I was going to use XML to store my Data. It offered the following benefits: It was a text file, and it had a number of ready to use parsers in all the common Server Scripting platforms. I could use the file anywhere. The first step, of course, was to decide on the XML elements to use and how they would be used in the document. I had to write an XML spec of sorts so I knew how to interpret the file.
First, I needed a Root element. XML documents require a document root element in order to be valid. We'll call that element the "map" element. After all, this document is going to amount to a sitemap of sorts. Which brings us to a side benefit of using XML. I can use the same file to generate a dynamic sitemap should I wish to and so can anyone else. I can provide this document publicly and anyone can host a way to get anywhere on my site from theirs. Who knows? It may be useful some day. Right now, our document looks like this:
< ?xml version='1.0' ?>;
Next, we need to have an element that holds all the data about one link. We'll call that the "section" element since it describes a section of the site. We also need elements inside this element to hold all the pieces we need to know to build our menu. In this case we are storing the link, the description, and the name of each link for the menu. Those elements are:
- link
- description
- name
< ?xml version='1.0' ?>
Each section element can be repeated as many times as necessary. The section element can only hold one link, description, and name element. That concludes our specs for the XML document.
- The Parser.
Now I had to select a parser to use, my platform for development at the time happened to be PHP. So what did PHP offer in the way of XML parsers? PHP actually had two parsers available to use. One was a SAX parser and the other is a DOM parser. At the time of this project, however, only the SAX Parser was included in the default distribution of PHP. So Sax it was.
SAX parsers are event driven parsers. They are simpler to learn but harder to implement than DOM parsers. Event driven parsers work by firing events when something happens as it goes through the text line by line. The events that fire are:
- element start
- element end
- cdata
So, you write handlers for each element and the parser calls them as each event fires. First, we need to create a parser object using $parser = xml_parser_create(). Then, we need to create the handler functions, and assign them to the parser object using xml_set_element_handler($parser, 'startElement', 'endElement'). We also need to set our parser options using xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0). Here is our php code so far:
$currentElement = ""; // Begin code to create menu from xml file. $varFileXmlFile = "xml_databases/sitemap.xml"; $xmlFile = fopen ($varFileXmlFile, "r"); $xmlString = fread ($xmlFile, filesize ($varFileXmlFile) ); $strMenu = ""; $currentElement = ""; $name = "1"; $link = "2"; $title = "3"; function startElement ($parserHandle, $elementName, $attributes) { // declare the global variables here } // function to handle the end of an element function endElement ($parserHandle, $elementName) { //declare global variables here } // function to handle the data in an element. function cData ($parserHandle, $cdata) { //declare global variables here } $parser = xml_parser_create(); xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); xml_set_element_handler($parser, 'startElement', 'endElement'); xml_set_character_data_handler($parser, 'cData');
Now we need to decide what happens when a start element is reached in our document. Since our goal is to retrieve the info from certain elements when they arrive, all we really need to know from this event is what element we are on. When the parser runs the startElement function it passes in the parser handle, element name and attribute list. All we have to do is add code to the function that stores the element's name in a global variable for other functions to use.
function startElement ($parserHandle, $elementName, $attributes) { // declare the global variables here global $currentElement; //identify current element here $currentElement = $elementName; }
We declare $currentElement using the global keyword so the function will use the variable declared earlier in the script instead of creating a function specific variable to store it. We want other functions to be able to access the current element so they know what element they are working on.
We also need to decide what happens when an end element is reached in our document. We only really care about the end element for section elements since this is when we will store the values we gathered from the child elements for section. When the parser fires the endElement function it passes in the parser handle and the element name. So all we have to do is add a test to see if its a section end element, and then output the values we will store using the CDATA handler. Additionally we need to clear the $currentElement variable since we are no longer in that element any longer.
// function to handle the end of an element function endElement ($parserHandle, $elementName) { //declare global variables here global $currentElement; global $strMenu; global $name; global $title; global $link; $currentElement = ""; if ($elementName == "section") { $strMenu .= "
- " $strMenu .= $name . "
\n"; } }Again we declared our variables with the global keyword so we would be able to retrieve the data from the variables we will store globally using the CDATA handler. In our case we want to output list item elements for inclusion in an unordered list later.
The last event we have to handle is when CDATA is reached. CDATA is text data that is not an XML element. In other words it's the data the elements are holding for us. When the cData Function is called by the parser it passes in the parser handle and the value it retrieved. For our purposes, we need to do something different with the data depending on which element we are inside of. If we are in a link element we store the value in our link variable and so on for all the other section sub elements like this:
// function to handle the data in an element. function cData ($parserHandle, $cdata) { //declare global variables here global $currentElement; switch ($currentElement) { case "link" : global $link; $link = $cdata; break; case "description" : global $title; $title = $cdata; break; case "name" : global $name; $name = $cdata; break; default : break; } }
Now that we have handled all the events, we are ready to retrieve our XML file. So, we run the parser with the stored string from our XML file.
$varFileXmlFile = "xml_databases/sitemap.xml"; $xmlFile = fopen ($varFileXmlFile, "r"); $xmlString = fread ($xmlFile, filesize ($varFileXmlFile) ); xml_parse($parser, $xmlString, true);
- Now how do we use it, you ask? Simple, just include the source code in our page and output the contents of the $strMenu variable into an unordered list and style to suite. The complete source code for this project is shown below. Feel free to copy and use if you wish, but make sure you give me some credit first.
// Begin code to create menu from xml file. $varFileXmlFile = "xml_databases/sitemap.xml"; $xmlFile = fopen ($varFileXmlFile, "r"); $xmlString = fread ($xmlFile, filesize ($varFileXmlFile) ); $strMenu = ""; $currentElement = ""; $name = "1"; $link = "2"; $title = "3"; // create handler functions here // function to handle the beginning of an element function startElement ($parserHandle, $elementName, $attributes) { // declare the global variables here global $currentElement; //identify current element here $currentElement = $elementName; } // function to handle the end of an element function endElement ($parserHandle, $elementName) { //declare global variables here global $currentElement; global $strMenu; global $name; global $title; global $link; $currentElement = ""; if ($elementName == "section") { $strMenu .= "
- " $strMenu .= $name . " \n"; } } // function to handle the data in an element. function cData ($parserHandle, $cdata) { //declare global variables here global $currentElement; switch ($currentElement) { case "link" : global $link; $link = $cdata; break; case "description" : global $title; $title = $cdata; break; case "name" : global $name; $name = $cdata; break; default : break; } } $parser = xml_parser_create(); xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); xml_set_element_handler($parser, 'startElement', 'endElement'); xml_set_character_data_handler($parser, 'cData'); xml_parse($parser, $xmlString, true);