1<refentry id="{@id}">
2 <refnamediv>
3  <refname>XML_Query2XML</refname>
4  <refpurpose>Generating XML data from SQL queries</refpurpose>
5 </refnamediv>
6 <refsynopsisdiv>
7
8  <author>
9  	Lukas Feiler
10  	<authorblurb>
11  	 {@link mailto:lukas.feiler@lukasfeiler.com lukas.feiler@lukasfeiler.com}
12  	</authorblurb>
13  </author>
14  <copyright>Copyright 2006 by Lukas Feiler</copyright>
15 </refsynopsisdiv>
16 {@toc}
17 <refsect1 id="{@id intro}">
18  <title>Introduction</title>
19  <para>
20  	XML_Query2XML allows you to transform the records retrieved with one or more
21  	SQL SELECT queries into XML data. Very simple to highly complex
22  	transformations are supported. Is was written with performance in mind and
23  	can handle large amounts of data. No XSLT needed!
24  </para>
25  <para>
26    Both methods {@link XML_Query2XML::getXML()} and {@link XML_Query2XML::getFlatXML()} return an instance
27    of {@link http://www.php.net/manual/en/ref.dom.php#dom.class.domdocument DOMDocument}.
28    The class DOMDocument is provided by {@link http://www.php.net/manual/en/ref.dom.php PHP5's built-in DOM API}.
29  </para>
30 </refsect1>
31
32 <refsect1 id="{@id requirements}">
33  <title>Requirements</title>
34  <para>
35    XML_Query2XML requires
36    <itemizedlist>
37     <listitem>
38      <emphasis>PHP5</emphasis>: XML_Query2XML heavily uses the new exception handling and object orientation features.
39     </listitem>
40     <listitem>
41      <emphasis>PHP5's built-in DOM API</emphasis>
42     </listitem>
43     <listitem>
44      <emphasis>{@link http://www.php.net/PDO PDO} (PHP5's built-in database abstraction class)</emphasis>
45      <emphasis>{@link http://pear.php.net/package/DB PEAR DB}</emphasis>,
46      <emphasis>{@link http://pear.php.net/package/MDB2 PEAR MDB2}</emphasis> or
47      <emphasis>{@link http://adodb.sourceforge.net ADOdb}</emphasis>.
48     </listitem>
49    </itemizedlist>
50    The following packages are optional:
51    <itemizedlist>
52     <listitem>
53      <emphasis>{@link http://pear.php.net/package/I18N_UnicodeString PEAR I18N_UnicodeString}</emphasis>:
54      this is only required if you want to use XML_Query2XML's ability to
55      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_mapper.9075mapping map SQL identifiers to XML names in accordance with ISO/IEC 9075-14:2005}.
56     </listitem>
57    </itemizedlist>
58  </para>
59 </refsect1>
60
61 <refsect1 id="{@id migration}">
62  <title>Migrating from v0.6.x and v0.7.x to v1.x.x</title>
63  <para>
64  	The release 0.8.0 of XML_Query2XML is not backward compatible!
65  	Due to security considerations XML_Query2XML does not use the
66  	native function eval() anymore. Therefore
67    <itemizedlist>
68     <listitem>
69      the "!" prefix is not supported anymore; this affects
70      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications},
71      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_value},
72      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn},
73      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes} and
74      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.complexqueryspecification Complex Query Specifications}
75     </listitem>
76     <listitem>
77      the behaviour of {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_condition} was changed
78     </listitem>
79     <listitem>
80      the new callback prefix "#" was added (use it instead of the "!" prefix)
81     </listitem>
82    </itemizedlist>
83  </para>
84  <para>
85    Proposed migration strategy:
86    <itemizedlist>
87     <listitem>
88      Wherever you currently use the "!" prefix, use the new callback prefix "#" instead.
89      The first argument passed to the callback function/method is always the current record ($record).
90      You can supply additional static arguments by placing them within the braces, e.g.
91      'MyClass:myMethod(arg2, arg3)' will result in MyClass:myMethod() being called with the current
92      record as the first, the string 'arg2' as the second and 'arg3' as the third argument. In
93      most cases you will want to put whatever code you used after the "!" prefix into a
94      separate function or static method. That function/method is what you call using the callback prefix "#".
95     </listitem>
96     <listitem>
97      The migration for {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_condition} works
98      similarly. Move the PHP code into a separate function/method and call it using the callback
99      prefix "#".
100     </listitem>
101    </itemizedlist>
102  </para>
103 </refsect1>
104
105 <refsect1 id="{@id query2xml_factory}">
106  <title>XML_Query2XML::factory()</title>
107  <para>
108    {@link XML_Query2XML::factory() XML_Query2XML::factory}($db)
109  </para>
110  <para>
111   This is the factory method that will return a new instance of {@link XML_Query2XML}.
112   The argument passed to the factory method can be an instance of
113   {@link http://www.php.net/PDO PDO},
114   {@link http://pear.php.net/package/DB PEAR DB},
115   {@link http://pear.php.net/package/MDB2 PEAR MDB2},
116   {@link http://adodb.sourceforge.net ADOdb},
117   {@link http://pear.php.net/package/Net_LDAP PEAR Net_LDAP},
118   {@link http://pear.php.net/package/Net_LDAP2 PEAR Net_LDAP2} or
119   any class that extends {@link XML_Query2XML_Driver}
120  </para>
121  <refsect2 id="{@id database_drivers}">
122   <title>Database Drivers for PDO, PEAR MDB2, PEAR DB, ADOdb</title>
123   <para>
124    XML_Query2XML has drivers for the database abstraction layers PDO, PEAR MDB2, PEAR DB and ADOdb.
125   </para>
126   <para>
127    Using PDO with XML_Query2XML works like this:
128    <programlisting role="php">
129    <![CDATA[
130<?php
131require_once 'XML/Query2XML.php';
132$pdo = new PDO('mysql://root@localhost/Query2XML_Tests');
133$query2xml = XML_Query2XML::factory($pdo);
134?>
135    ]]>
136    </programlisting>
137   </para>
138   <para>
139    Using MDB2 with XML_Query2XML works like this:
140    <programlisting role="php">
141    <![CDATA[
142<?php
143require_once 'XML/Query2XML.php';
144require_once 'MDB2.php';
145$mdb2 = MDB2::factory('mysql://root@localhost/Query2XML_Tests');
146$query2xml = XML_Query2XML::factory($mdb2);
147?>
148    ]]>
149    </programlisting>
150   </para>
151   <para>
152    The same thing with PEAR DB looks like that:
153    <programlisting role="php">
154    <![CDATA[
155<?php
156require_once 'XML/Query2XML.php';
157require_once 'DB.php';
158$db = DB::connect('mysql://root@localhost/Query2XML_Tests');
159$query2xml = XML_Query2XML::factory($db);
160?>
161    ]]>
162    </programlisting>
163   </para>
164   <para>
165    And again the same thing with ADOdb:
166    <programlisting role="php">
167    <![CDATA[
168<?php
169require_once 'XML/Query2XML.php';
170require_once 'adodb/adodb.inc.php';
171//require_once 'adodb/adodb-exceptions.inc.php';
172//require_once 'adodb/adodb-pear.inc.php';
173$adodb = ADONewConnection('mysql');
174$adodb->Connect('localhost', 'root', '', 'Query2XML_Tests');
175$query2xml = XML_Query2XML::factory($adodb);
176?>
177    ]]>
178    </programlisting>
179    Note that XML_Query2XML works with ADOdb with the default error handling (no additional include file),
180    error handling using exceptions (adodb-exceptions.inc.php) and error handling using PEAR_Error
181    (adodb-pear.inc.php).
182   </para>
183   <para>
184    I would recommend using MDB2 as it can be considered more advanced than DB
185    and much better designed and documented than ADOdb. MDB2 also provides more
186    flexibility than PDO. If you want to access a SQLite 3 database use PDO - MDB2
187    does only support SQLite 2 as of this writing.
188    But use whichever you like - XML_Query2XML works with all of them.
189    For the sake of simplicity all the examples will use PEAR MDB2.
190   </para>
191  </refsect2>
192  <refsect2 id="{@id ldap_driver}">
193   <title>LDAP Driver for PEAR Net_LDAP</title>
194   <para>
195    Since v1.6.0RC1 XML_Query2XML comes with a driver for {@link http://pear.php.net/package/Net_LDAP PEAR Net_LDAP}.
196    The driver for {@link http://pear.php.net/package/Net_LDAP2 PEAR Net_LDAP2} is available since v1.7.0RC1.
197   </para>
198   <para>
199    Using Net_LDAP(2) with XML_Query2XML works like this:
200    <programlisting role="php">
201    <![CDATA[
202<?php
203require_once 'XML/Query2XML.php';
204$ldap = Net_LDAP::connect(
205    'host'     => 'ldap.example.com',
206    'port'     => 389,
207    'version'  => 3,
208    'starttls' => true,
209    'binddn'   => 'cn=Manager,ou=people,dc=example,dc=com',
210    'bindpw'   => 'secret'
211);
212$query2xml = XML_Query2XML::factory($ldap);
213?>
214    ]]>
215    </programlisting>
216    The driver for Net_LDAP(2) uses a diffrent format for
217    {@tutorial XML_Query2XML.pkg#query2xml_getxml.sql}. Instead of
218    a string it expects an associative array with the following elements:
219    <itemizedlist>
220     <listitem>
221      <emphasis>'base'</emphasis>: the base search DN
222     </listitem>
223     <listitem>
224      <emphasis>'filter'</emphasis>: the query filter that determines which results are returned
225     </listitem>
226     <listitem>
227      <emphasis>'options'</emphasis>: an array of configuration options for the current query
228     </listitem>
229    </itemizedlist>
230    More information on how to use the LDAP drivers can be found under
231    {@tutorial XML_Query2XML.pkg#ldap}
232   </para>
233  </refsect2>
234 </refsect1>
235
236 <refsect1 id="{@id query2xml_getflatxml}">
237  <title>XML_Query2XML::getFlatXML()</title>
238  <para>
239    {@link XML_Query2XML::getFlatXML() XML_Query2XML::getFlatXML}($sql, $rootTagName = 'root', $rowTagName = 'row')
240  </para>
241  <para>
242    This method transforms the data retrieved by a single SQL query into flat XML data. Pass the SQL SELECT statement
243    as first, the root tag's name as second and the row tag's name as third argument.
244  </para>
245  <para>
246    In most cases you will want to use {@tutorial XML_Query2XML.pkg#query2xml_getxml} instead.
247    Please see {@tutorial XML_Query2XML.pkg#casestudies.case01} for an example usage of getFlatXML().
248  </para>
249 </refsect1>
250
251 <refsect1 id="{@id query2xml_getxml}">
252  <title>XML_Query2XML::getXML()</title>
253  <para>
254    {@link XML_Query2XML::getXML() XML_Query2XML::getXML}($sql, $options)
255  </para>
256  <para>
257  	This method is the most powerful transformation method. It returns an instance of
258  	DOMDocument (part of PHP5's built-in DOM API). The records returned by the query/queries will be processed
259  	one after another. The $options argument is a rather complex, associative,
260  	multi dimensional array. The $sql argument can be a string or as well an associative array.
261  </para>
262
263  <refsect2 id="{@id sql}">
264   <title>$sql</title>
265   <para>
266    This option is almost exactly like {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql}: you
267    can specify the query with a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.simplequeryspecification}
268    or a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.complexqueryspecification}.
269    What is different from {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql} is that
270    you can also specify a boolean value of false.
271   </para>
272   <para>
273    Here is an example of a simple query specification (<emphasis>WARNING:</emphasis> to prevent SQL injection
274    vulerabilities you should use a complex query specification when dealing with non-static queries like this one):
275    <programlisting role="php">
276    <![CDATA[
277<?php
278require_once 'XML/Query2XML.php';
279require_once 'MDB2.php';
280$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
281if (isset($_REQUEST['artistid']) && is_numeric($_REQUEST['artistid'])) {
282    $artistid =  $_REQUEST['artistid'];
283} else {
284    $artistid = 1;
285}
286$dom = $query2xml->getXML(
287  "SELECT * FROM artist WHERE artistid = $artistid",
288  array(
289    'rootTag' => 'favorite_artist',
290    'idColumn' => 'artistid',
291    'rowTag' => 'artist',
292    'elements' => array(
293        'name',
294        'birth_year',
295        'music_genre' => 'genre'
296    )
297  )
298);
299header('Content-Type: application/xml');
300$dom->formatOutput = true;
301print $dom->saveXML();
302?>
303    ]]>
304    </programlisting>
305    With simple query specifications you have to prevent SQL injection yourself. Here I ensured
306    that $artistid is numeric by calling is_numeric().
307   </para>
308   <para>
309    Next we use a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.complexqueryspecification}
310    and prevent SQL injections by using PDO's/MDB2's/DB's/ADOdb's prepare() and execute() methods.
311    <programlisting role="php">
312    <![CDATA[
313<?php
314require_once 'XML/Query2XML.php';
315require_once 'MDB2.php';
316$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
317$artistid = $_REQUEST['artistid'];
318$dom = $query2xml->getXML(
319  array(
320    'data' => array(
321        ":$artistid"
322    ),
323    'query' => 'SELECT * FROM artist WHERE artistid = ?'
324  ),
325  array(
326    'rootTag' => 'favorite_artist',
327    'idColumn' => 'artistid',
328    'rowTag' => 'artist',
329    'elements' => array(
330      'name',
331      'birth_year',
332      'music_genre' => 'genre'
333    )
334  )
335);
336header('Content-Type: application/xml');
337$dom->formatOutput = true;
338print $dom->saveXML();
339?>
340    ]]>
341    </programlisting>
342    The resulting XML data is identical in both cases (given that artistid was submitted as 1):
343    <programlisting role="tutorial">
344    <![CDATA[
345<?xml version="1.0" encoding="UTF-8"?>
346<favorite_artist>
347  <artist>
348    <name>Curtis Mayfield</name>
349    <birth_year>1920</birth_year>
350    <music_genre>Soul</music_genre>
351  </artist>
352</favorite_artist>
353    ]]>
354    </programlisting>
355   </para>
356   <para>
357    As stated above $sql can also be a boolean value of false. This will only be useful in
358    scenarios where you want to combine the results of multiple unrelated queries into
359    a single XML document. XML_Query2XML will deal with an $sql argument that has a value
360    of false as if it executed a query that returned a single record with no colunns.
361   </para>
362   <para>
363    If you simpy wanted all the records of the table "album" and all
364    the records of the table "artist" you could write code like this:
365    <programlisting role="php">
366    <![CDATA[
367<?php
368require_once 'XML/Query2XML.php';
369require_once 'MDB2.php';
370
371$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
372
373$dom = $query2xml->getXML(
374    false,
375    array(
376        'idColumn' => false,
377        'rowTag' => '__tables',
378        'rootTag' => 'music_store',
379        'elements' => array(
380            'artists' => array(
381                'rootTag' => 'artists',
382                'rowTag' => 'artist',
383                'idColumn' => 'artistid',
384                'sql' => 'SELECT * FROM artist',
385                'elements' => array(
386                    '*'
387                )
388            ),
389            'albums' => array(
390                'rootTag' => 'albums',
391                'rowTag' => 'album',
392                'idColumn' => 'albumid',
393                'sql' => 'SELECT * FROM album',
394                'elements' => array(
395                    '*'
396                )
397            )
398        )
399    )
400);
401
402header('Content-Type: application/xml');
403$dom->formatOutput = true;
404print $dom->saveXML();
405?>
406    ]]>
407    </programlisting>
408    In this case we actually are not interested in $sql at all; all we want is to get our
409    {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql}s executed.
410    Also note that we used '__tables' for {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_rowtag}
411    at the root level: this is because we don't have anything to loop over at the root level - remember
412    using false for $sql is like using a query that returns a single record with no columns.
413   </para>
414   <para>
415    The resulting XML looks like this:
416    <programlisting role="tutorial">
417    <![CDATA[
418<?xml version="1.0" encoding="UTF-8"?>
419<music_store>
420  <artists>
421    <artist>
422      <artistid>1</artistid>
423      <name>Curtis Mayfield</name>
424      <birth_year>1920</birth_year>
425      <birth_place>Chicago</birth_place>
426      <genre>Soul</genre>
427    </artist>
428    <artist>
429      <artistid>2</artistid>
430      <name>Isaac Hayes</name>
431      <birth_year>1942</birth_year>
432      <birth_place>Tennessee</birth_place>
433      <genre>Soul</genre>
434    </artist>
435    <artist>
436      <artistid>3</artistid>
437      <name>Ray Charles</name>
438      <birth_year>1930</birth_year>
439      <birth_place>Mississippi</birth_place>
440      <genre>Country and Soul</genre>
441    </artist>
442  </artists>
443  <albums>
444    <album>
445      <albumid>1</albumid>
446      <artist_id>1</artist_id>
447      <title>New World Order</title>
448      <published_year>1990</published_year>
449      <comment>the best ever!</comment>
450    </album>
451    <album>
452      <albumid>2</albumid>
453      <artist_id>1</artist_id>
454      <title>Curtis</title>
455      <published_year>1970</published_year>
456      <comment>that man's got somthin' to say</comment>
457    </album>
458    <album>
459      <albumid>3</albumid>
460      <artist_id>2</artist_id>
461      <title>Shaft</title>
462      <published_year>1972</published_year>
463      <comment>he's the man</comment>
464    </album>
465  </albums>
466</music_store>
467    ]]>
468    </programlisting>
469   </para>
470   <para>
471    Want to dump not just two but all of your table? Have a look at
472    {@tutorial XML_Query2XML.pkg#dumpdatabase}.
473   </para>
474  </refsect2>
475
476  <refsect2 id="{@id options_elements}">
477   <title>$options['elements']</title>
478   <para>
479    This option is an array that basically holds column names to include in the XML data as child
480    elements. There are two types of element specifications:
481    <itemizedlist>
482     <listitem>
483      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}
484     </listitem>
485     <listitem>
486      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications}
487     </listitem>
488    </itemizedlist>
489   </para>
490   <refsect3 id="{@id simple_element_specifications}">
491    <title>Simple Element Specifications</title>
492    <para>
493      These allow you to use an array to specify elements that have only two properties: a name and a value.
494      The array values are used to specify the XML element values whereas the array keys are used to specify the
495      XML element names. For all array elements that are defined without a key, the array values will be used for
496      the XML element names. If no prefix is used (see below) the contents of the array values are interpreted
497      as column names. The following example illustrates the most basic usage of a simple element specification:
498      <programlisting role="php">
499      <![CDATA[
500array(
501    'COLUMN1',
502    'COLUMN2'
503);
504      ]]>
505      </programlisting>
506      This might result in XML data like this:
507      <programlisting role="tutorial">
508      <![CDATA[
509<COLUMN1>this was the contents of COLUMN1</COLUMN1>
510<COLUMN2>this was the contents of COLUMN2</COLUMN2>
511      ]]>
512      </programlisting>
513      If you do not want your XML elements named after your database columns you have to work with array keys
514      (ELEMENT1 and ELEMENT2 in our example):
515      while the element specification
516      <programlisting role="php">
517      <![CDATA[
518array(
519    'ELEMENT1' => 'COLUMN1',
520    'ELEMENT2' => 'COLUMN2'
521);
522      ]]>
523      </programlisting>
524      This would make the same data appear like this:
525      <programlisting role="tutorial">
526      <![CDATA[
527<ELEMENT1>this was the contents of COLUMN1</ELEMENT1>
528<ELEMENT2>this was the contents of COLUMN2</ELEMENT2>
529      ]]>
530      </programlisting>
531    </para>
532    <para>
533      If you use both, the array key and the array value to specify an XML element, the array value can be of the following types:
534      <itemizedlist>
535       <listitem>
536        <emphasis>COLUMN NAME (string)</emphasis>: this is the default if not preceeded by ':' or '#'.
537        If the column does not exist, an {@link XML_Query2XML_ConfigException} will be thrown.
538       </listitem>
539       <listitem>
540        <emphasis>STATIC TEXT with a : prefix (string)</emphasis>: if the value is preceeded by a colon (':'), it is
541        interpreted as static text.
542       </listitem>
543       <listitem>
544        <emphasis>CALLBACK FUNCTION with a # prefix (string)</emphasis>: if the value is preceeded by a pound sign ('#'), it
545        is interpreted as a callback function. You can use a regular function (e.g. '#myFunction()') or a static
546        method (e.g. '#MyClass::myFunction()') - for how to use a non-static method, see the type COMMAND OBJECT.
547        The current record will be passed to the callback function
548        as an associative array. You can also pass additional string arguments to the callback function by specifing
549        them within the opening and closing brace; e.g. '#Utils::limit(12)' will result in Util::limit() being
550        called with the current record as the first and '12' as the second argument. If you do not want
551        to pass additional arguments to the callback function, the opening and closing brace are optional.
552        The callback function's return value will be converted to a string and used as the child text node
553        if it is anything but an object or an array. If you do return an object or an array from a callback
554        function it has to be an instance of {@link http://www.php.net/manual/en/ref.dom.php DOMNode} or
555        an array of DOMNode instances. Please see {@tutorial XML_Query2XML.pkg#other_xml_data_sources} for examples
556        and further details. If an instances of any other class is returned, a XML_Query2XML_XMLException will be thrown.
557       </listitem>
558       <listitem>
559        <emphasis>COMMAND OBJECT (object)</emphasis>: If you want to use a non-static method as a
560        callback function, you can do so by specifying the value as an instance of a class that
561        implements the {@link XML_Query2XML_Callback} interface. This implementation of the
562        {@link http://en.wikipedia.org/wiki/Command_pattern command pattern} gives you all the
563        flexibility. The disadvantage ist that you cannot use the XML UNSERIALIZATION prefix
564        or the CONDITIONAL prefix. Note: you have to require_once 'XML/Query2XML/Callback.php'
565        before using the XML_Query2XML_Callback interface. The return value of a COMMAND OBJECT's
566        execute() method is treated exactly the same as the return value of a CALLBACK FUNCTION.
567       </listitem>
568      </itemizedlist>
569      There are four more prefixes available that can be used in conjunction with all the prifixes described above:
570      <itemizedlist>
571       <listitem>
572        <emphasis>XML UNSERIALIZATION prefix &amp;</emphasis>: the ampersand (&amp;) prefix allows you to
573        automatically unserialize string data, i.e. transform a string into a {@link http://www.php.net/manual/en/ref.dom.php DOMDocument}. DOMDocument's
574        {@link http://www.php.net/manual/en/function.dom-domdocument-loadxml.php loadXML()} method will be
575        used for this purpose. You can combine all three types with this prefix: '&amp;COLUMN_NAME',
576        '&amp;#function()' or '&amp;:&lt;name&gt;John&lt;/name&gt;' will all work. You can even use the CONDITIONAL prefix
577        which has to preceed all other prefixes. If the data cannot be unserialized i.e.
578        {@link http://www.php.net/manual/en/function.dom-domdocument-loadxml.php DOMDocument::loadXML()}
579        returns false, a XML_Query2XML_XMLException will be thrown. Please see
580        {@tutorial XML_Query2XML.pkg#other_xml_data_sources} for examples and further
581        details.
582       </listitem>
583       <listitem>
584        <emphasis>BASE64 ENCODING prefix ^</emphasis>: if the specification starts with a carrat sign ('^'),
585        the element value will be passed to {@link http://www.php.net/base64_encode base64_encode()}.
586        The BASE64 ENCODING prefix can be used with all the prefixes described above (just put the BASE64 ENCODING prefix first):
587        e.g. '^#', '^:' or '^COLUMN_NAME'.
588       </listitem>
589       <listitem>
590        <emphasis>CDATA SECTION prefix =</emphasis>: if the specification starts with an equal sign ('='),
591        the element value will be enclosed in a CDATA section. A CDATA section starts with
592        "&lt;![CDATA[" and ends with "]]&gt;".
593        The CDATA SECTION prefix can be used with all the prefixes described above (just put the CDATA SECTION prefix first):
594        e.g. '=#', '=:', '=COLUMN_NAME' or '=^'.
595       </listitem>
596       <listitem>
597        <emphasis>CONDITIONAL prefix ?</emphasis>: if the specification starts with a question mark ('?'),
598        the whole element will be skipped if the value equals (==) an empty string. The CONDITIONAL prefix
599        can be combined with all types described above: if you do this you have to write the CONDITIONAL
600        prefix first e.g. '?#', '?:', '?&amp;', '?=', '?^', or '?COLUMN_NAME'.
601       </listitem>
602      </itemizedlist>
603      Note: for ovious reasons, the prefix cannot be combined with a COMMAND OBJECT.
604    <para>
605    </para>
606      Basically, the same syntax can be use for
607      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_value},
608      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes},
609      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.complexqueryspecification} and
610      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn} because the private method
611      {@link XML_Query2XML::_applyColumnStringToRecord()} is used in all cases.
612    </para>
613    <para>
614      Let's start out with a very simple example. It will use
615      the column name as the XML element name for the first two columns but the
616      custom element name 'music_genre' for the column 'genre':
617      <programlisting role="php">
618      <![CDATA[
619<?php
620require_once 'XML/Query2XML.php';
621require_once 'MDB2.php';
622$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
623$dom = $query2xml->getXML(
624  "SELECT * FROM artist",
625  array(
626    'rootTag' => 'favorite_artists',
627    'idColumn' => 'artistid',
628    'rowTag' => 'artist',
629    'elements' => array(
630        'name',
631        'birth_year',
632        'music_genre' => 'genre'
633    )
634  )
635);
636header('Content-Type: application/xml');
637$dom->formatOutput = true;
638print $dom->saveXML();
639?>
640      ]]>
641      </programlisting>
642      This results in the following XML data:
643      <programlisting role="tutorial">
644      <![CDATA[
645<?xml version="1.0" encoding="UTF-8"?>
646<favorite_artists>
647  <artist>
648    <name>Curtis Mayfield</name>
649    <birth_year>1920</birth_year>
650    <music_genre>Soul</music_genre>
651  </artist>
652  <artist>
653    <name>Isaac Hayes</name>
654    <birth_year>1942</birth_year>
655    <music_genre>Soul</music_genre>
656  </artist>
657  <artist>
658    <name>Ray Charles</name>
659    <birth_year>1930</birth_year>
660    <music_genre>Country and Soul</music_genre>
661  </artist>
662</favorite_artists>
663      ]]>
664      </programlisting>
665     </para>
666
667     <para>
668     The following example demonstrates the usage of all different types:
669     <programlisting role="php">
670     <![CDATA[
671<?php
672require_once 'XML/Query2XML.php';
673require_once 'XML/Query2XML/Callback.php';
674require_once 'MDB2.php';
675
676class Utils
677{
678    function trim($record, $columnName)
679    {
680        return trim($record[$columnName]);
681    }
682
683    function getPublishedYearCentury($record)
684    {
685        return floor($record['published_year']/100);
686    }
687}
688
689class ToLowerCallback implements XML_Query2XML_Callback
690{
691    private $_columnName = '';
692
693    public function __construct($columnName)
694    {
695        $this->_columnName = $columnName;
696    }
697
698    public function execute(array $record)
699    {
700        return strtolower($record[$this->_columnName]);
701    }
702}
703
704$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
705$dom = $query2xml->getXML(
706  "SELECT
707    *
708   FROM
709    sale,
710    store,
711    album
712   WHERE
713    sale.store_id = store.storeid
714    AND
715    sale.album_id = album.albumid
716    AND
717    sale.timestamp < '2005-06-01'",
718  array(
719    'rootTag' => 'sales',
720    'idColumn' => 'saleid',
721    'rowTag' => 'sale',
722    'elements' => array(
723        'saleid',
724        'sale_timestamp' => 'timestamp',
725        'static' => ':some static text',
726        'now' => ':' . time(),
727        'album_century' => '#Utils::getPublishedYearCentury()',
728        'album_title' => '?#Utils::trim(title)',
729        'album_comment' => new ToLowerCallback('comment'),
730        'storeid',
731        'store_building1' => '?&building_xmldata',
732        'store_building2' => '?=building_xmldata',
733        'store_building3' => '?^building_xmldata'
734    )
735  )
736);
737
738header('Content-Type: application/xml');
739$dom->formatOutput = true;
740print $dom->saveXML();
741?>
742      ]]>
743      </programlisting>
744      Let's go through all simple element specifications, one by one:
745      <itemizedlist>
746       <listitem>
747        <emphasis>'saleid'</emphasis>: this is as simple as it can get. The value of the column saleid will be used for an element named saleid.
748       </listitem>
749       <listitem>
750        <emphasis>'sale_timestamp' => 'timestamp'</emphasis>: here we want to place the value of the
751        column timestamp in an element named sale_timestamp; we therefore use sale_timestamp as the array key.
752       </listitem>
753       <listitem>
754        <emphasis>'static' => ':some static text'</emphasis>: the STATIC TEXT (note the ":" prefix) "some static text" will be placed inside an element named static.
755       </listitem>
756       <listitem>
757        <emphasis>'now' => ':' . time()</emphasis>: here the static text is computed at run time; however it will be the same for all "now" elements.
758       </listitem>
759       <listitem>
760        <emphasis>'album_century' => '#Utils::getPublishedYearCentury()'</emphasis>: here we use
761        a CALLBACK FUNCTION with a "#" prefix; the return value of Utils::getPublishedYearCentury() is used as the XML element value.
762        Note that the callback function will automatically be called with the current $record as the first argument.
763       </listitem>
764       <listitem>
765        <emphasis>'album_title' => '?#Utils::trim(title)'</emphasis>: we also use a CALLBACK FUNCTION with a "#" prefix, but
766        this time we pass an additional string argument to our callback function by specifing it within the opening and
767        closing brace. Also, we use the CONDITIONAL prefix ? which means that the album_title element will only appear in
768        the generated XML data if Utils::trim() returned a non-empty string (to be precise a string that != "").
769       </listitem>
770       <listitem>
771        <emphasis>'album_comment' => new ToLowerCallback('comment')</emphasis>: here we use a COMMAND OBJECT implementing
772        the {@link XML_Query2XML_Callback} interface. This is the object oriented way to use callbacks! Note how we pass the
773        column name to the callback class constructor, so that it's execute() method will now what column to work on.
774       </listitem>
775       <listitem>
776        <emphasis>'storeid'</emphasis>: plain an simple again
777       </listitem>
778       <listitem>
779        <emphasis>'store_building1' =>  '?&amp;building_xmldata'</emphasis>: here we use the XML UNSERIALIZATION prefix "&amp;"
780        to transform the value of the building_xmldata column into a DOMDocument. Using the CONDITIONAL prefix ? means
781        that store_building1 will only appear if building_xmldata is non-empty (!= "" to be precise).
782       </listitem>
783       <listitem>
784        <emphasis>'store_building2' => '?=building_xmldata'</emphasis>: CDATA SECTION prefix "=" is another way
785        incorporate XML data; the contents of the column building_xmldata will be surrounded by
786         "&lt;![CDATA[" and "]]&gt;". Using the CONDITIONAL prefix ? means
787        that store_building2 will only appear if building_xmldata is non-empty (!= "" to be precise).
788       </listitem>
789       <listitem>
790        <emphasis>'store_building3' => '?^building_xmldata'</emphasis>: here we use the BASE64 ENCODING prefix "^"
791        to first base64-encode the contents of the building_xmldata column. We again use the CONDITIONAL prefix "?".
792       </listitem>
793      </itemizedlist>
794      The resulting XML data looks like this:
795      <programlisting role="tutorial">
796      <![CDATA[
797<?xml version="1.0" encoding="UTF-8"?>
798<sales>
799  <sale>
800    <saleid>1</saleid>
801    <sale_timestamp>2005-05-25 07:32:00</sale_timestamp>
802    <static>some static text</static>
803    <now>1187498966</now>
804    <album_century>19</album_century>
805    <album_title>New World Order</album_title>
806    <album_comment>the best ever!</album_comment>
807    <storeid>1</storeid>
808    <store_building1>
809      <building>
810        <floors>4</floors>
811        <elevators>2</elevators>
812        <square_meters>3200</square_meters>
813      </building>
814    </store_building1>
815    <store_building2>< ![CDATA[<building><floors>4</floors><elevators>2</elevators><square_meters>3200</square_meters></building>]] ></store_building2>
816    <store_building3>PGJ1aWxkaW5nPjxmbG9vcnM+NDwvZmxvb3JzPjxlbGV2YXRvcnM+MjwvZWxldmF0b3JzPjxzcXVhcmVfbWV0ZXJzPjMyMDA8L3NxdWFyZV9tZXRlcnM+PC9idWlsZGluZz4=</store_building3>
817  </sale>
818  <sale>
819    <saleid>11</saleid>
820    <sale_timestamp>2005-05-25 07:23:00</sale_timestamp>
821    <static>some static text</static>
822    <now>1187498966</now>
823    <album_century>19</album_century>
824    <album_title>Curtis</album_title>
825    <album_comment>that man's got somthin' to say</album_comment>
826    <storeid>2</storeid>
827    <store_building1>
828      <building>
829        <floors>2</floors>
830        <elevators>1</elevators>
831        <square_meters>400</square_meters>
832      </building>
833    </store_building1>
834    <store_building2>< ![CDATA[<building><floors>2</floors><elevators>1</elevators><square_meters>400</square_meters></building>]] ></store_building2>
835    <store_building3>PGJ1aWxkaW5nPjxmbG9vcnM+MjwvZmxvb3JzPjxlbGV2YXRvcnM+MTwvZWxldmF0b3JzPjxzcXVhcmVfbWV0ZXJzPjQwMDwvc3F1YXJlX21ldGVycz48L2J1aWxkaW5nPg==</store_building3>
836  </sale>
837</sales>
838      ]]>
839      </programlisting>
840      Note: due to a bug in phpDocumentor I had to cheat a little bit in the above XML; as you might have noticed
841      there was a space between "&lt;" and "![CDATA[".
842     </para>
843    </refsect3>
844    <refsect3 id="{@id complex_element_specifications}">
845     <title>Complex Element Specifications</title>
846     <para>
847      A complex element specification consists of an array that can have all options
848      that can be present on the root level plus {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql}
849      and {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options}.
850      This allows for complete (and theoretically infinite) nesting. You will need to use it if the
851      child element should have attributes or child elements.
852     </para>
853     <para>
854      The following example is like the first one in
855      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}
856      with one difference: the XML element 'name' should have the attribute 'type' set to the
857      static value 'full_name'. As attributes are not supported by simple elements specifications,
858      we have to use a complex element specification for the element 'name':
859      <programlisting role="php">
860      <![CDATA[
861<?php
862require_once 'XML/Query2XML.php';
863require_once 'MDB2.php';
864$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
865$dom = $query2xml->getXML(
866  "SELECT * FROM artist",
867  array(
868    'rootTag' => 'favorite_artists',
869    'idColumn' => 'artistid',
870    'rowTag' => 'artist',
871    'elements' => array(
872        'name' => array(
873            'value' => 'name',
874            'attributes' => array(
875                'type' => ':full_name'
876            )
877        ),
878        'birth_year',
879        'music_genre' => 'genre'
880    )
881  )
882);
883header('Content-Type: application/xml');
884$dom->formatOutput = true;
885print $dom->saveXML();
886?>
887      ]]>
888      </programlisting>
889      This results in the following XML data:
890      <programlisting role="tutorial">
891      <![CDATA[
892<?xml version="1.0" encoding="UTF-8"?>
893<favorite_artists>
894  <artist>
895    <name type="full_name">Curtis Mayfield</name>
896    <birth_year>1920</birth_year>
897    <music_genre>Soul</music_genre>
898  </artist>
899  <artist>
900    <name type="full_name">Isaac Hayes</name>
901    <birth_year>1942</birth_year>
902    <music_genre>Soul</music_genre>
903  </artist>
904  <artist>
905    <name type="full_name">Ray Charles</name>
906    <birth_year>1930</birth_year>
907    <music_genre>Country and Soul</music_genre>
908  </artist>
909</favorite_artists>
910      ]]>
911      </programlisting>
912      </para>
913      <para>
914       Here is another little example:
915       <programlisting role="php">
916       <![CDATA[
917<?php
918require_once 'XML/Query2XML.php';
919require_once 'MDB2.php';
920$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
921$dom = $query2xml->getXML(
922    "SELECT * FROM artist LEFT JOIN album ON album.artist_id = artist.artistid",
923    array(
924        'rootTag' => 'music_library',
925        'rowTag' => 'artist',
926        'idColumn' => 'artistid',
927        'elements' => array(
928            'artistid',
929            'name',
930            'birth_year',
931            'birth_place',
932            'genre',
933            'albums' => array(
934              'rootTag' => 'albums',
935              'rowTag' => 'album',
936              'idColumn' => 'albumid',
937              'elements' => array('albumid', 'title', 'published_year')
938            )
939        )
940    )
941);
942header('Content-Type: application/xml');
943$dom->formatOutput = true;
944print $dom->saveXML();
945?>
946      ]]>
947      </programlisting>
948      This results in the following XML data:
949      <programlisting role="tutorial">
950      <![CDATA[
951<?xml version="1.0" encoding="UTF-8"?>
952<music_library>
953  <artist>
954    <artistid>1</artistid>
955    <name>Curtis Mayfield</name>
956    <birth_year>1920</birth_year>
957    <birth_place>Chicago</birth_place>
958    <genre>Soul</genre>
959    <albums>
960      <album>
961        <albumid>1</albumid>
962        <title>New World Order</title>
963        <published_year>1990</published_year>
964      </album>
965      <album>
966        <albumid>2</albumid>
967        <title>Curtis</title>
968        <published_year>1970</published_year>
969      </album>
970    </albums>
971  </artist>
972  <artist>
973    <artistid>2</artistid>
974    <name>Isaac Hayes</name>
975    <birth_year>1942</birth_year>
976    <birth_place>Tennessee</birth_place>
977    <genre>Soul</genre>
978    <albums>
979      <album>
980        <albumid>3</albumid>
981        <title>Shaft</title>
982        <published_year>1972</published_year>
983      </album>
984    </albums>
985  </artist>
986  <artist>
987    <artistid>3</artistid>
988    <name>Ray Charles</name>
989    <birth_year>1930</birth_year>
990    <birth_place>Mississippi</birth_place>
991    <genre>Country and Soul</genre>
992    <albums />
993  </artist>
994</music_library>
995      ]]>
996      </programlisting>
997     </para>
998     <para>
999      As we want for every artist only a single tag we need to identify each artist by the primary
1000      key of the table artist. Note that there is a second record for Curtis Mayfield (related to
1001      the album Curtis), but we don't want something like
1002      <programlisting role="tutorial">
1003      <![CDATA[
1004<artist>
1005  <name>Curtis Mayfield</name>
1006  <album>
1007    <name>New World Order</name>
1008  </album>
1009</artist>
1010<artist>
1011  <name>Curtis Mayfield</name>
1012  <album>
1013    <name>Curits</name>
1014  </album>
1015</artist>
1016      ]]>
1017      </programlisting>
1018      but rather
1019      <programlisting role="tutorial">
1020      <![CDATA[
1021<artist>
1022  <name>Curtis Mayfield</name>
1023  <albums>
1024    <album>
1025     <name>New World Order</name>
1026    </album>
1027    <albums>
1028     <name>Curtis</name>
1029    </albums>
1030  </albums>
1031</artist>
1032      ]]>
1033      </programlisting>
1034      This is achieved by telling XML_Query2XML which entity to focus on (on this level): the artist, as it
1035      is identified by the artist table's primary key. Once XML_Query2XML get's to the second Curtis Mayfield
1036      record, it can tell by the artistid 1 that an XML element was already created for this artist.
1037     </para>
1038     <para>
1039      For a one more example and a detailed explanation of complex child elements that have
1040      child elements themselves, see {@tutorial XML_Query2XML.pkg#casestudies.case02}.
1041      For an advanced example, see {@tutorial XML_Query2XML.pkg#casestudies.case05}.
1042     </para>
1043    </refsect3>
1044    <refsect3 id="{@id asterisk_shortcut}">
1045     <title>Using the Asterisk Shortcut</title>
1046     <para>
1047      The asterisk shortcut only works with {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}
1048      (and {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications}).
1049     </para>
1050     <para>
1051      In some scenarios you will just want to use all columns found in the result set
1052      for {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}.
1053      This is where the asterisk shortcut can come in very handy. An element specification that
1054      contains an asterisk (an "asterisk element specification") will be duplicated for each
1055      column present in the result set ($record). The simplest way of using the asterisk shortcut is this:
1056      <programlisting role="php">
1057      <![CDATA[
1058<?php
1059require_once 'XML/Query2XML.php';
1060require_once 'MDB2.php';
1061$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
1062$dom = $query2xml->getXML(
1063  "SELECT * FROM artist",
1064  array(
1065    'rootTag' => 'favorite_artists',
1066    'idColumn' => 'artistid',
1067    'rowTag' => 'artist',
1068    'elements' => array(
1069        '*'
1070    )
1071  )
1072);
1073header('Content-Type: application/xml');
1074$dom->formatOutput = true;
1075print $dom->saveXML();
1076?>
1077      ]]>
1078      </programlisting>
1079      As the result set contains the column artistid, name, birth_year, birth_place and genre the XML data will look like this:
1080      <programlisting role="tutorial">
1081      <![CDATA[
1082<?xml version="1.0" encoding="UTF-8"?>
1083<favorite_artists>
1084  <artist>
1085    <artistid>1</artistid>
1086    <name>Curtis Mayfield</name>
1087    <birth_year>1920</birth_year>
1088    <birth_place>Chicago</birth_place>
1089    <genre>Soul</genre>
1090  </artist>
1091  <artist>
1092    <artistid>2</artistid>
1093    <name>Isaac Hayes</name>
1094    <birth_year>1942</birth_year>
1095    <birth_place>Tennessee</birth_place>
1096    <genre>Soul</genre>
1097  </artist>
1098  <artist>
1099    <artistid>3</artistid>
1100    <name>Ray Charles</name>
1101    <birth_year>1930</birth_year>
1102    <birth_place>Mississippi</birth_place>
1103    <genre>Country and Soul</genre>
1104  </artist>
1105</favorite_artists>
1106      ]]>
1107      </programlisting>
1108      This is because internally, the array
1109      <programlisting role="php">
1110      <![CDATA[
1111'elements' => array(
1112  '*'
1113)
1114      ]]>
1115      </programlisting>
1116      is expanded to
1117      <programlisting role="php">
1118      <![CDATA[
1119'elements' => array(
1120  'artistid',
1121  'name',
1122  'birth_year',
1123  'birth_place',
1124  'genre'
1125)
1126      ]]>
1127      </programlisting>
1128     </para>
1129     <para>
1130      Think of the asterisk as a variable that will get replaced with each column name found in the result set:
1131      <programlisting role="php">
1132      <![CDATA[
1133<?php
1134require_once 'XML/Query2XML.php';
1135require_once 'MDB2.php';
1136$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
1137$dom = $query2xml->getXML(
1138  "SELECT * FROM artist",
1139  array(
1140    'rootTag' => 'favorite_artists',
1141    'idColumn' => 'artistid',
1142    'rowTag' => 'artist',
1143    'elements' => array(
1144        'TAG_*' => '#padWithHyphens(*)'
1145    )
1146  )
1147);
1148header('Content-Type: application/xml');
1149$dom->formatOutput = true;
1150print $dom->saveXML();
1151
1152function padWithHyphens($record, $columnName)
1153{
1154    return '--' . $record[$columnName] . '--';
1155}
1156?>
1157      ]]>
1158      </programlisting>
1159      The above code would result in the following data:
1160      <programlisting role="tutorial">
1161      <![CDATA[
1162<?xml version="1.0" encoding="UTF-8"?>
1163<favorite_artists>
1164  <artist>
1165    <TAG_artistid>--1--</TAG_artistid>
1166    <TAG_name>--Curtis Mayfield--</TAG_name>
1167    <TAG_birth_year>--1920--</TAG_birth_year>
1168    <TAG_birth_place>--Chicago--</TAG_birth_place>
1169    <TAG_genre>--Soul--</TAG_genre>
1170  </artist>
1171  <artist>
1172    <TAG_artistid>--2--</TAG_artistid>
1173    <TAG_name>--Isaac Hayes--</TAG_name>
1174    <TAG_birth_year>--1942--</TAG_birth_year>
1175    <TAG_birth_place>--Tennessee--</TAG_birth_place>
1176    <TAG_genre>--Soul--</TAG_genre>
1177  </artist>
1178  <artist>
1179    <TAG_artistid>--3--</TAG_artistid>
1180    <TAG_name>--Ray Charles--</TAG_name>
1181    <TAG_birth_year>--1930--</TAG_birth_year>
1182    <TAG_birth_place>--Mississippi--</TAG_birth_place>
1183    <TAG_genre>--Country and Soul--</TAG_genre>
1184  </artist>
1185</favorite_artists>
1186      ]]>
1187      </programlisting>
1188     </para>
1189     <para>
1190      You can also combine a simple element specification containing an asterisk shortcut with other (simple and complex) element specifications.
1191      The additional element specifications will be treated as an exception to the general rule set up by the asterisk element specification.
1192      The following code will produce a tag for each column in the result set containing the column's value. The only exeption is the column
1193      "genre" which we want to be different: the value should be all uppercase:
1194      <programlisting role="php">
1195      <![CDATA[
1196<?php
1197require_once 'XML/Query2XML.php';
1198require_once 'MDB2.php';
1199$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
1200$dom = $query2xml->getXML(
1201  "SELECT * FROM artist",
1202  array(
1203    'rootTag' => 'favorite_artists',
1204    'idColumn' => 'artistid',
1205    'rowTag' => 'artist',
1206    'elements' => array(
1207        '*' => '*',
1208        'genre' => '#genre2uppercase()'
1209    )
1210  )
1211);
1212header('Content-Type: application/xml');
1213$dom->formatOutput = true;
1214print $dom->saveXML();
1215
1216function genre2uppercase($record)
1217{
1218    return strtoupper($record['genre']);
1219}
1220?>
1221      ]]>
1222      </programlisting>
1223      The resulting XML data looks like this:
1224      <programlisting role="tutorial">
1225      <![CDATA[
1226<?xml version="1.0" encoding="UTF-8"?>
1227<favorite_artists>
1228  <artist>
1229    <artistid>1</artistid>
1230    <name>Curtis Mayfield</name>
1231    <birth_year>1920</birth_year>
1232    <birth_place>Chicago</birth_place>
1233    <genre>SOUL</genre>
1234  </artist>
1235  <artist>
1236    <artistid>2</artistid>
1237    <name>Isaac Hayes</name>
1238    <birth_year>1942</birth_year>
1239    <birth_place>Tennessee</birth_place>
1240    <genre>SOUL</genre>
1241  </artist>
1242  <artist>
1243    <artistid>3</artistid>
1244    <name>Ray Charles</name>
1245    <birth_year>1930</birth_year>
1246    <birth_place>Mississippi</birth_place>
1247    <genre>COUNTRY AND SOUL</genre>
1248  </artist>
1249</favorite_artists>
1250      ]]>
1251      </programlisting>
1252      This is because internally, the array
1253      <programlisting role="php">
1254      <![CDATA[
1255'elements' => array(
1256  '*' => '*',
1257  'genre' => '#genre2uppercase()'
1258)
1259      ]]>
1260      </programlisting>
1261      is expanded to
1262      <programlisting role="php">
1263      <![CDATA[
1264'elements' => array(
1265  'artistid',
1266  'name',
1267  'birth_year',
1268  'birth_place',
1269  'genre' => '#genre2uppercase()'
1270)
1271      ]]>
1272      </programlisting>
1273      Please keep in mind that this also applies when combining an asterisk element specification with a complex element specification.
1274      That's why the following code would produce exactly the same XML data:
1275      <programlisting role="php">
1276      <![CDATA[
1277<?php
1278require_once 'XML/Query2XML.php';
1279require_once 'MDB2.php';
1280$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
1281$dom = $query2xml->getXML(
1282  "SELECT * FROM artist",
1283  array(
1284    'rootTag' => 'favorite_artists',
1285    'idColumn' => 'artistid',
1286    'rowTag' => 'artist',
1287    'elements' => array(
1288        '*' => '*',
1289        'genre' => array(
1290            'value' => '#genre2uppercase()'
1291        )
1292    )
1293  )
1294);
1295header('Content-Type: application/xml');
1296$dom->formatOutput = true;
1297print $dom->saveXML();
1298
1299function genre2uppercase($record)
1300{
1301    return strtoupper($record['genre']);
1302}
1303?>
1304      ]]>
1305      </programlisting>
1306     </para>
1307     <para>
1308      If we wanted to include all columns in the XML output except "genre" we could use a little trick:
1309      <programlisting role="php">
1310      <![CDATA[
1311<?php
1312require_once 'XML/Query2XML.php';
1313require_once 'MDB2.php';
1314$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
1315$dom = $query2xml->getXML(
1316  "SELECT * FROM artist",
1317  array(
1318    'rootTag' => 'favorite_artists',
1319    'idColumn' => 'artistid',
1320    'rowTag' => 'artist',
1321    'elements' => array(
1322        '*' => '*',
1323        'genre' => '?:'
1324    )
1325  )
1326);
1327header('Content-Type: application/xml');
1328$dom->formatOutput = true;
1329print $dom->saveXML();
1330?>
1331      ]]>
1332      </programlisting>
1333      In the resulting XML data the column "genre" is missing because we used the CONDITIONAL prefix '?' in combination with a static empty text:
1334      <programlisting role="tutorial">
1335      <![CDATA[
1336<?xml version="1.0" encoding="UTF-8"?>
1337<favorite_artists>
1338  <artist>
1339    <artistid>1</artistid>
1340    <name>Curtis Mayfield</name>
1341    <birth_year>1920</birth_year>
1342    <birth_place>Chicago</birth_place>
1343  </artist>
1344  <artist>
1345    <artistid>2</artistid>
1346    <name>Isaac Hayes</name>
1347    <birth_year>1942</birth_year>
1348    <birth_place>Tennessee</birth_place>
1349  </artist>
1350  <artist>
1351    <artistid>3</artistid>
1352    <name>Ray Charles</name>
1353    <birth_year>1930</birth_year>
1354    <birth_place>Mississippi</birth_place>
1355  </artist>
1356</favorite_artists>
1357      ]]>
1358      </programlisting>
1359      The exact same result could of course also be achieved using the "condition" option:
1360      <programlisting role="php">
1361      <![CDATA[
1362<?php
1363require_once 'XML/Query2XML.php';
1364require_once 'MDB2.php';
1365$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
1366$dom = $query2xml->getXML(
1367  "SELECT * FROM artist",
1368  array(
1369    'rootTag' => 'favorite_artists',
1370    'idColumn' => 'artistid',
1371    'rowTag' => 'artist',
1372    'elements' => array(
1373        '*' => '*',
1374        'genre' => array(
1375            'condition' => '#returnFalse()'
1376            //this would also work: 'condition' => ':'
1377        )
1378    )
1379  )
1380);
1381header('Content-Type: application/xml');
1382$dom->formatOutput = true;
1383print $dom->saveXML();
1384
1385function returnFalse()
1386{
1387    return false;
1388}
1389?>
1390      ]]>
1391      </programlisting>
1392     </para>
1393     <para>
1394      Another example of how to use the asterisk shortcut can be found in
1395      {@tutorial XML_Query2XML.pkg#casestudies.case07}.
1396     </para>
1397     <para>
1398      One final note on the asterisk shortcut: if you explicitly specify a tag name (an array element key) it has to contain an asterisk. The following code
1399      would cause a {@link XML_Query2XML_ConfigException} to be thrown:
1400      <programlisting role="php">
1401      <![CDATA[
1402<?php
1403require_once 'XML/Query2XML.php';
1404require_once 'MDB2.php';
1405$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
1406$dom = $query2xml->getXML(
1407  "SELECT * FROM artist",
1408  array(
1409    'rootTag' => 'favorite_artists',
1410    'idColumn' => 'artistid',
1411    'rowTag' => 'artist',
1412    'elements' => array(
1413        'tag' => '*'
1414    )
1415  )
1416);
1417header('Content-Type: application/xml');
1418$dom->formatOutput = true;
1419print $dom->saveXML();
1420?>
1421      ]]>
1422      </programlisting>
1423      This is because expanding
1424      <programlisting role="php">
1425      <![CDATA[
1426'elements' => array(
1427  'tag' => '*'
1428)
1429      ]]>
1430      </programlisting>
1431      to
1432      <programlisting role="php">
1433      <![CDATA[
1434'elements' => array(
1435  'tag' => 'artistid',
1436  'tag' => 'name',
1437  'tag' => 'birth_year',
1438  'tag' => 'birth_place',
1439  'tag' => 'genre'
1440)
1441      ]]>
1442      </programlisting>
1443      just makes no sense and therfore "*" is treated as a regular column name - which does not exist in this case!
1444      The exception's message would read: [elements]: The column "*" used in the option "tag" does not exist in the result set.
1445     </para>
1446    </refsect3>
1447  </refsect2>
1448
1449  <refsect2 id="{@id options_idcolumn}">
1450   <title>$options['idColumn']</title>
1451   <para>
1452    In most cases this will be the name of the column by which a record is identified as unique, aka the
1453    primary key. This is especially important within a
1454    {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications Complex Element Specification}.
1455    See there for an example. This option is obligatory at the root level! The idColumn specification can be
1456    of the following types:
1457    <itemizedlist>
1458     <listitem>
1459      <emphasis>COLUMN NAME</emphasis>: this is the default if not preceeded by ':' or '#'.
1460      If the column does not exist, an {@link XML_Query2XML_ConfigException} will be thrown.
1461      The current record (not the one of the parent level) will be used.
1462     </listitem>
1463     <listitem>
1464      <emphasis>STATIC TEXT with a : prefix</emphasis>: if the value is preceeded by a colon (':'), it is
1465      interpreted as static text.
1466     </listitem>
1467     <listitem>
1468      <emphasis>CALLBACK FUNCTION with a # prefix</emphasis>: if the value is preceeded by a pound sign ('#'), it
1469      is interpreted as a callback function. You can use a regular function (e.g. '#myFunction()') or a static
1470      method (e.g. '#MyClass::myFunction()') - for how to use a non-static method, see the type COMMAND OBJECT.
1471      The current record will be passed to the callback function
1472      as an associative array. You can also pass additional string arguments to the callback function by specifing
1473      them within the opening and closing brace; e.g. '#Utils::limit(12)' will result in Util::limit() being
1474      called with the current record as the first and '12' as the second argument. If you do not want
1475      to pass additional arguments to the callback function, the opening and closing brace are optional.
1476     </listitem>
1477     <listitem>
1478      <emphasis>COMMAND OBJECT (object)</emphasis>: If you want to use a non-static method as a
1479      callback function, you can do so by specifying the value as an instance of a class that
1480      implements the {@link XML_Query2XML_Callback} interface. This implementation of the
1481      {@link http://en.wikipedia.org/wiki/Command_pattern command pattern} gives you all the
1482      flexibility. Note: you have to require_once 'XML/Query2XML/Callback.php'
1483      before using the XML_Query2XML_Callback interface. The return value of a COMMAND OBJECT's
1484      execute() method is treated exactly the same as the return value of a CALLBACK FUNCTION.
1485     </listitem>
1486     <listitem>
1487      <emphasis>FALSE (boolean)</emphasis>: Only use this if you don't have a primary key
1488      (which is a very bad idea) or you have a very simple tasks at hand like retrieving all
1489      records from a table. Using the value FALSE will make XML_Query2XML treat every record
1490      as unique. WARNING: it is considered very bad practice to use a value of
1491      FALSE if you have a way to specify your primar key. This is because your code might
1492      change over time and having your primary key specified will just make your more stable.
1493      For a legitimate use of the value FALSE for the option idColumn, please see
1494      {@tutorial XML_Query2XML.pkg#dumpdatabase}.
1495     </listitem>
1496    </itemizedlist>
1497    The same syntax (with the additional '?' prefix but without the boolean value FALSE) can
1498    be use for
1499    {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_value},
1500    {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications},
1501    {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.complexqueryspecification} and
1502    {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications} because the private method
1503    {@link XML_Query2XML::_applyColumnStringToRecord()} is used in all cases.
1504   </para>
1505   <para>
1506    For example and further discussion of {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn}
1507    please see {@tutorial XML_Query2XML.pkg#casestudies.case02}.
1508   </para>
1509   <refsect3 id="{@id multicolumn_pk}">
1510    <title>Handling Multi-Column Primary Keys</title>
1511    <para>
1512     Sometimes your primary key will consist of multiple columns. For example, this might
1513     be the case when you implement a many-to-many relationship using an intersection table.
1514    </para>
1515    <para>
1516     But as you know by now,
1517     $options['idColumn'] has to evaluate to one unique ID for every record.
1518     Depending on the type of the primary key columns you will want to choose a different strategy to
1519     compute that unique ID for every record. To begin with, you have to choose whether
1520     you want to compute that unique ID within the database or using PHP. To do it within the
1521     database you will have to define an alias using the "... AS alias_name" syntax. Using
1522     PHP you have to use a callback function to generate the ID. When generating the ID, you
1523     again have different options.
1524    </para>
1525    <para>
1526     If your primary key columns are of a numeric type, you can
1527     <itemizedlist>
1528      <listitem>
1529       Concatenate your primary key columns using a separator. In the database
1530       you would use something like "CONCAT(column1, '_', column2) AS id" and
1531       when implemented in PHP you would write a callback that returns something
1532       like "$record['column1'] . '_' . $record['column2']".
1533      </listitem>
1534      <listitem>
1535       If Leftshift the first column by the number of bits the second column consumes
1536       and OR the leftshifted first column with the second column. E.g. if you had
1537       two columns defined as TINYINT UNSIGNED (0 to 255, i.e. 8 bits), you could
1538       generate the ID by
1539       <programlisting role="php">
1540       <![CDATA[
1541$id = ($column1 << 8) | $column2;
1542       ]]>
1543       </programlisting>
1544       Think of like this:
1545       <![CDATA[
1546$column1 (set to 255):           00000000000000000000000011111111
1547$column2 left-shifted by 8 bits: 00000000000000001111111100000000
1548
1549now we OR the left-shifted $column1 with $column2 (both were
1550originally set to the maximum of 255):
1551$column1 (left-shifted 255): 00000000000000001111111100000000
1552$column2 (set to 255):       00000000000000000000000011111111
1553-------------------------------------------------------------
1554result:                      00000000000000001111111111111111
1555As you can see two different combinations of $column1 and $column2
1556will always result in a different ID. This is because the left-shifted
1557$column1 does not intersect with $column2.
1558       ]]>
1559       <emphasis>WARNING: an integer in PHP has 32 bits</emphasis> -
1560       the tecnique described above therefore only works if the sum
1561       of the bits consumed by your primary key columns is less than
1562       or equal to 32 (i.e. two 16 bit numbers, four 8 bit numbers or
1563       two 8 bit numbers and one 16 bit number).
1564      </listitem>
1565      <listitem>
1566       Use the generateIdFromMultiKeyPK() method described below.
1567      </listitem>
1568     </itemizedlist>
1569    </para>
1570    <para>
1571     If your primary key columns are of a character type (e.g. CHAR, VARCHAR) you
1572     have to come up with something else. Before you read on, I strongly urge
1573     you to reconsider your choice for the primary key (does it really meet the
1574     requirements of minimality and stability, i.e. is immutable?).
1575     <emphasis>SECURITY WARNING:</emphasis>
1576     Do not simply concatenate your character type columns (with or without a separator).
1577     The following example shows why:
1578     <![CDATA[
1579record1: column1='a_b' column2='c'
1580record2: column1='a'   column2='b_c'
1581When using the separator '_' both records would have an ID of 'a_b_c'.
1582     ]]>
1583     A malicious attacker could use your separator within
1584     one of the column values to force ID collisions, which potentially
1585     lead to an exploitable security vulnerability. Great care should therefore
1586     be taken when choosing a separator - and relying on its confidentiality is
1587     not a good strategy. What you might do is to use a separator that is longer
1588     than the maximum character length of your primary key columns. But this only
1589     makes sense if that maximum is rather low. For example, if you have two CHAR(2)
1590     columns, it is reasonable to use the separator '---' which is three characters long.
1591    </para>
1592    <para>
1593     Another thing one might think of is to use a hash function like sha1() or md5().
1594     But that's not really an option as it would really kill the performance of your
1595     application.
1596    </para>
1597    <para>
1598     The most bullet proof solution to the problem of generating a unique ID from
1599     two character type columns is to use a callback function that works with
1600     an array. The following function can be used as a callback whenever
1601     you need to generate an ID from two character type columns.
1602     <programlisting role="php">
1603     <![CDATA[
1604<?php
1605/**Returns a unique ID base on the values stored in
1606* $record[$columnName1] and $record[$columnName2].
1607*
1608* @param array $record An associative array.
1609* @param string $columnName1 The name of the first column.
1610* @param string $columnName2 The name of the second column.
1611* @return int The ID.
1612*/
1613function generateIdFromMultiKeyPK($record, $columnName1, $columnName2)
1614{
1615    static $ids = array();
1616    static $idCounter = 0;
1617
1618    $column1 = $record[$columnName1];
1619    $column2 = $record[$columnName2];
1620    if (!isset($ids[$column1])) {
1621        $ids[$column1] = array();
1622    }
1623    if (!isset($ids[$column1][$column2])) {
1624        $ids[$column1][$column2] = $idCounter++;
1625    }
1626    return $ids[$column1][$column2];
1627}
1628?>
1629     ]]>
1630     </programlisting>
1631    </para>
1632    <para>
1633     All you have to do is to specify $options['idColumn'] as:
1634     <![CDATA[
1635'#generateIdFromMultiKeyPK(name_of_column1, name_of_column2)'
1636     ]]>
1637     Remember: $record is automatically passed as the first argument
1638     to the callback function.
1639    </para>
1640   </refsect3>
1641  </refsect2>
1642
1643  <refsect2 id="{@id options_attributes}">
1644   <title>$options['attributes']</title>
1645   <para>
1646    This option is an array that holds columns to include in the XML data as
1647    attributes. {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications Simple}
1648    and {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.complex_attribute_specifications complex} attribute
1649    specifications are supported.
1650   </para>
1651   <para>
1652    If you want to add attributes to the root element (i.e. the first child of the DOMDocument instance returned by {@link XML_Query2XML::getXML() getXML()}),
1653    please see {@tutorial XML_Query2XML.pkg#query2xml_getxml.return_value_modification}.
1654   </para>
1655   <refsect3 id="{@id simple_attribute_specifications}">
1656    <title>Simple Attribute Specifications</title>
1657    <para>
1658     It works like
1659     {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}:
1660     the column names are the array values. By default the column's value
1661     will be put into an attribute named after the column. If you're
1662     unhappy with the default you can specify an other attribute name by using
1663     it as the array key. As documented for {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}
1664     the prefixes "?", "#", "^" and ":" or a COMMAND OBJECT can be used. Only the UNSERIALIZATION prefix &amp; and the CDATA SECTION prefix ^ which are valid for
1665     a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications Simple Element Specification}
1666     cannot be used for a Simple Attribute Specification.
1667    </para>
1668    <para>
1669     The follwing example will use
1670     the column name as the attribute name for the first two columns but the
1671     custom attribute name 'music_genre' for the column 'genre':
1672     <programlisting role="php">
1673     <![CDATA[
1674<?php
1675require_once 'XML/Query2XML.php';
1676require_once 'MDB2.php';
1677$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
1678$dom = $query2xml->getXML(
1679  "SELECT * FROM artist",
1680  array(
1681    'rootTag' => 'favorite_artists',
1682    'idColumn' => 'artistid',
1683    'rowTag' => 'artist',
1684    'elements' => array(),
1685    'attributes' => array(
1686      'name',
1687      'birth_year',
1688      'music_genre' => 'genre'
1689    )
1690  )
1691);
1692header('Content-Type: application/xml');
1693$dom->formatOutput = true;
1694print $dom->saveXML();
1695?>
1696     ]]>
1697     </programlisting>
1698     This results in the following XML data:
1699     <programlisting role="tutorial">
1700     <![CDATA[
1701<?xml version="1.0" encoding="UTF-8"?>
1702<favorite_artists>
1703  <artist name="Curtis Mayfield" birth_year="1920" music_genre="Soul"/>
1704  <artist name="Isaac Hayes" birth_year="1942" music_genre="Soul"/>
1705  <artist name="Ray Charles" birth_year="1930" music_genre="Country and Soul"/>
1706</favorite_artists>
1707    ]]>
1708     </programlisting>
1709    </para>
1710   </refsect3>
1711   <refsect3 id="{@id complex_attribute_specifications}">
1712    <title>Complex Attribute Specifications</title>
1713    <para>
1714     A complex attribute specification consists of an array that must contain
1715     <itemizedlist>
1716      <listitem>
1717      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_value}: the attribute's value
1718      (note: you cannot use the UNSERIALIZATION prefix &amp; or the the CDATA SECTION prefix ^ for an attribute specification)
1719      </listitem>
1720     </itemizedlist>
1721     and optionally can contain
1722     <itemizedlist>
1723      <listitem>
1724      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_condition}: condition for the inclusion of the attribute
1725      </listitem>
1726      <listitem>
1727      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_encoder}: encoding of the attribute value
1728      </listitem>
1729      <listitem>
1730      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql}: allows you to fetch more data
1731      </listitem>
1732      <listitem>
1733      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options}: options for the sql option
1734      </listitem>
1735     </itemizedlist>
1736     The array key used to store the complex attribute specification is always used as the attribute's name.
1737     Unlike {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications}
1738     complex attribute specifications cannot be nested for obvious reasons. Complex attribute specifications
1739     should only be used for the following reasons:
1740     <itemizedlist>
1741      <listitem>
1742      the attribute is only to be included under a condition that cannot be expressed using the '?' prefix
1743      within a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications simple attribute specification}
1744      </listitem>
1745      <listitem>
1746      additional data is needed from the database
1747      </listitem>
1748     </itemizedlist>
1749     In all other cases {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications}
1750     should be used as they will make your code run faster.
1751    </para>
1752    <para>
1753     To add a "bornBefore1940" attribute only to those artists that were born before 1940 we could write:
1754     <programlisting role="php">
1755     <![CDATA[
1756<?php
1757require_once 'XML/Query2XML.php';
1758require_once 'MDB2.php';
1759$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
1760$dom = $query2xml->getXML(
1761  "SELECT * FROM artist",
1762  array(
1763    'rootTag' => 'favorite_artists',
1764    'idColumn' => 'artistid',
1765    'rowTag' => 'artist',
1766    'attributes' => array(
1767      'name',
1768      'birth_year',
1769      'bornBefore1940' => array(
1770        'value' => ':true',
1771        'condition' => '#lessThan(birth_year, 1940)'
1772      )
1773    )
1774  )
1775);
1776header('Content-Type: application/xml');
1777$dom->formatOutput = true;
1778print $dom->saveXML();
1779
1780function lessThan($record, $columnName, $num)
1781{
1782    return $record[$columnName] < $num;
1783}
1784?>
1785     ]]>
1786     </programlisting>
1787     This results in the following XML data:
1788     <programlisting role="tutorial">
1789     <![CDATA[
1790<?xml version="1.0" encoding="UTF-8"?>
1791<favorite_artists>
1792  <artist birth_year="1920" bornBefore1940="true" name="Curtis Mayfield" />
1793  <artist birth_year="1942" name="Isaac Hayes" />
1794  <artist birth_year="1930" bornBefore1940="true" name="Ray Charles" />
1795</favorite_artists>
1796    ]]>
1797     </programlisting>
1798    </para>
1799    <para>
1800     In the next example we want a "firstAlbumTitle" attribute for each artist.
1801     For the purpose of the example we will not use a single left outer join but a complex attribute specification with the "sql" option.
1802     As retrieving more than one record for a single attribute makes no sense
1803     {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.single_record} is always automatically set to true when fetching records
1804     for attributes.
1805     <programlisting role="php">
1806     <![CDATA[
1807<?php
1808require_once 'XML/Query2XML.php';
1809require_once 'MDB2.php';
1810$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
1811$dom = $query2xml->getXML(
1812  "SELECT * FROM artist",
1813  array(
1814    'rootTag' => 'favorite_artists',
1815    'idColumn' => 'artistid',
1816    'rowTag' => 'artist',
1817    'attributes' => array(
1818      'name',
1819      'birth_year',
1820      'firstAlbumTitle' => array(
1821        'value' => 'title',
1822        'sql' => array(
1823          'data' => array(
1824            'artistid'
1825          ),
1826          'query' => "SELECT * FROM album WHERE artist_id = ? ORDER BY published_year"
1827        )
1828      )
1829    )
1830  )
1831);
1832header('Content-Type: application/xml');
1833$dom->formatOutput = true;
1834print $dom->saveXML();
1835?>
1836     ]]>
1837     </programlisting>
1838     This results in the following XML data:
1839     <programlisting role="tutorial">
1840     <![CDATA[
1841<?xml version="1.0" encoding="UTF-8"?>
1842<favorite_artists>
1843  <artist birth_year="1920" firstAlbumTitle="Curtis" name="Curtis Mayfield" />
1844  <artist birth_year="1942" firstAlbumTitle="Shaft" name="Isaac Hayes" />
1845  <artist birth_year="1930" name="Ray Charles" />
1846</favorite_artists>
1847    ]]>
1848     </programlisting>
1849     As you can see, the firstAlbumTitle attribute is missing for Ray Charles.
1850     This is because he does not have any albums in our test database and processing
1851     the "value" option without any records just makes no sense.
1852    </para>
1853    <para>
1854     In the last example I'd like to demonstrate the use of {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options} within
1855     a complex attribute specification. As stated before, {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.single_record}
1856     is always automatically set to true - no matter what you assign to it.
1857     This time, we want a "firstAlbum" attribute that has a value of "TITLE (GENRE)" - remember that
1858     "genre" is a colum of the artist table while "title" is a column of the album table.
1859     <programlisting role="php">
1860     <![CDATA[
1861<?php
1862require_once 'XML/Query2XML.php';
1863require_once 'MDB2.php';
1864$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
1865$dom = $query2xml->getXML(
1866  "SELECT * FROM artist",
1867  array(
1868    'rootTag' => 'favorite_artists',
1869    'idColumn' => 'artistid',
1870    'rowTag' => 'artist',
1871    'attributes' => array(
1872      'name',
1873      'birth_year',
1874      'firstAlbum' => array(
1875        'value' => '#combineTitleAndGenre()',
1876        'sql' => array(
1877          'data' => array(
1878            'artistid'
1879          ),
1880          'query' => "SELECT * FROM album WHERE artist_id = ? ORDER BY published_year"
1881        ),
1882        'sql_options' => array(
1883          'merge_selective' => array('genre')
1884        )
1885      )
1886    )
1887  )
1888);
1889header('Content-Type: application/xml');
1890$dom->formatOutput = true;
1891print $dom->saveXML();
1892
1893function combineTitleAndGenre($record)
1894{
1895    return $record['title'] . ' (' . $record['genre'] . ')';
1896}
1897?>
1898     ]]>
1899     </programlisting>
1900     This results in the following XML data:
1901     <programlisting role="tutorial">
1902     <![CDATA[
1903<?xml version="1.0" encoding="UTF-8"?>
1904<favorite_artists>
1905  <artist name="Curtis Mayfield" birth_year="1920" firstAlbum="Curtis (Soul)"/>
1906  <artist name="Isaac Hayes" birth_year="1942" firstAlbum="Shaft (Soul)"/>
1907  <artist name="Ray Charles" birth_year="1930"/>
1908</favorite_artists>
1909    ]]>
1910     </programlisting>
1911    </para>
1912   </refsect3>
1913   <refsect3 id="{@id asterisk_shortcut}">
1914    <title>Using the Asterisk Shortcut</title>
1915    <para>
1916     The asterisk shortcut only works with {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications}
1917     (and {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}).
1918    </para>
1919    <para>
1920     Everything said about
1921     {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.asterisk_shortcut Using the Asterisk Shortcut with simple element specifications}
1922     applies here to!
1923     The simplest example of using the asterisk shortcut with the attributes option is as follows:
1924     <programlisting role="php">
1925    <![CDATA[
1926<?php
1927require_once 'XML/Query2XML.php';
1928require_once 'MDB2.php';
1929$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
1930$dom = $query2xml->getXML(
1931  "SELECT * FROM artist",
1932  array(
1933    'rootTag' => 'favorite_artists',
1934    'idColumn' => 'artistid',
1935    'rowTag' => 'artist',
1936    'attributes' => array(
1937      '*'
1938    )
1939  )
1940);
1941header('Content-Type: application/xml');
1942$dom->formatOutput = true;
1943print $dom->saveXML();
1944?>
1945    ]]>
1946    </programlisting>
1947    This produces this XML data:
1948    <programlisting role="tutorial">
1949    <![CDATA[
1950<?xml version="1.0" encoding="UTF-8"?>
1951<favorite_artists>
1952  <artist artistid="1" birth_place="Chicago" birth_year="1920" genre="Soul" name="Curtis Mayfield" />
1953  <artist artistid="2" birth_place="Tennessee" birth_year="1942" genre="Soul" name="Isaac Hayes" />
1954  <artist artistid="3" birth_place="Mississippi" birth_year="1930" genre="Country and Soul" name="Ray Charles" />
1955</favorite_artists>
1956    ]]>
1957    </programlisting>
1958    </para>
1959   </refsect3>
1960  </refsect2>
1961
1962  <refsect2 id="{@id options_rowtag}">
1963   <title>$options['rowTag']</title>
1964   <para>
1965    The name of the tag that encloses each record. The default is 'row'.
1966   </para>
1967   <para>
1968    Here goes an example of 'rowTag' being used at the root level:
1969    <programlisting role="php">
1970    <![CDATA[
1971<?php
1972require_once 'XML/Query2XML.php';
1973require_once 'MDB2.php';
1974$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
1975$dom = $query2xml->getXML(
1976  "SELECT * FROM artist",
1977  array(
1978    'rootTag' => 'favorite_artists',
1979    'idColumn' => 'artistid',
1980    'rowTag' => 'artist',
1981    'elements' => array(
1982        'name',
1983        'birth_year',
1984        'genre'
1985    )
1986  )
1987);
1988header('Content-Type: application/xml');
1989$dom->formatOutput = true;
1990print $dom->saveXML();
1991?>
1992    ]]>
1993    </programlisting>
1994    'rowTag' was set to 'artist' therefore the resulting XML data is:
1995    <programlisting role="tutorial">
1996    <![CDATA[
1997<?xml version="1.0" encoding="UTF-8"?>
1998<favorite_artists>
1999  <artist>
2000    <name>Curtis Mayfield</name>
2001    <birth_year>1920</birth_year>
2002    <genre>Soul</genre>
2003  </artist>
2004  <artist>
2005    <name>Isaac Hayes</name>
2006    <birth_year>1942</birth_year>
2007    <genre>Soul</genre>
2008  </artist>
2009  <artist>
2010    <name>Ray Charles</name>
2011    <birth_year>1930</birth_year>
2012    <genre>Country and Soul</genre>
2013  </artist>
2014</favorite_artists>
2015    ]]>
2016    </programlisting>
2017   </para>
2018   <para>
2019    Now let's have a look at a more advanced example:
2020    <programlisting role="php">
2021    <![CDATA[
2022<?php
2023require_once 'XML/Query2XML.php';
2024require_once 'MDB2.php';
2025$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
2026$dom = $query2xml->getXML(
2027    "SELECT
2028        *
2029     FROM
2030        artist
2031        LEFT JOIN album ON album.artist_id = artist.artistid",
2032    array(
2033        'rootTag' => 'music_library',
2034        'rowTag' => 'artist',
2035        'idColumn' => 'artistid',
2036        'elements' => array(
2037            'artistid',
2038            'name',
2039            'birth_year',
2040            'birth_place',
2041            'genre',
2042            'albums' => array(
2043                'rootTag' => 'albums',
2044                'rowTag' => 'album',
2045                'idColumn' => 'albumid',
2046                'elements' => array(
2047                    'albumid',
2048                    'title',
2049                    'published_year',
2050                    'comment'
2051                )
2052            )
2053        )
2054    )
2055);
2056
2057header('Content-Type: application/xml');
2058$dom->formatOutput = true;
2059print $dom->saveXML();
2060?>
2061    ]]>
2062    </programlisting>
2063    Here 'rowTag' on the root level is set to 'artist' while ['elements']['albums']['rowTag']
2064    is set to 'album'. This example is taken from {@tutorial XML_Query2XML.pkg#casestudies.case02},
2065    so please see there for the resulting XML data and further discussion.
2066   </para>
2067   <para>
2068    In some situations, 'rowTag' can be omitted all together:
2069    <programlisting role="php">
2070    <![CDATA[
2071<?php
2072require_once 'XML/Query2XML.php';
2073require_once 'MDB2.php';
2074$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
2075$dom = $query2xml->getXML(
2076  "SELECT * FROM artist",
2077  array(
2078    'rootTag' => 'favorite_artists',
2079    'idColumn' => 'artistid',
2080    'rowTag' => 'artist',
2081    'elements' => array(
2082        'name' => array(
2083            'value' => 'name',
2084            'attributes' => array(
2085                'type' => ':full_name'
2086            )
2087        ),
2088        'birth_year',
2089        'music_genre' => 'genre'
2090    )
2091  )
2092);
2093header('Content-Type: application/xml');
2094$dom->formatOutput = true;
2095print $dom->saveXML();
2096?>
2097    ]]>
2098    </programlisting>
2099    Here the complex element definition ['elements']['name'] has no 'rowTag' option. This is alright
2100    because the specification's array key ('name' in this case) is used by default.
2101   </para>
2102  </refsect2>
2103
2104  <refsect2 id="{@id options_dynamicrowtag}">
2105   <title>$options['dynamicRowTag']</title>
2106   <para>
2107    Use this option if you want the name of an XML element determined at run time (e.g. you want to pull the
2108    XML element name from the database). Note: if this option is present,
2109    {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_rowtag} will be ignored.
2110   </para>
2111   <para>
2112    What you can assign to $options['dynamicRowTag'] is very similar as what you can use for
2113    {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_value}
2114    or a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications Simple Element Specification}.
2115
2116    $options['dynamicRowTag'] can be of the following types:
2117    <itemizedlist>
2118     <listitem>
2119      <emphasis>COLUMN NAME</emphasis>: this is the default if not preceeded by ':' or '#'.
2120      If the column does not exist, an {@link XML_Query2XML_ConfigException} will be thrown.
2121     </listitem>
2122     <listitem>
2123      <emphasis>STATIC TEXT with a : prefix</emphasis>: if the value is preceeded by a colon (':'), it is
2124      interpreted as static text.
2125     </listitem>
2126     <listitem>
2127      <emphasis>CALLBACK FUNCTION with a # prefix</emphasis>: if the value is preceeded by a pound sign ('#'), it
2128       is interpreted as a callback function. You can use a regular function (e.g. '#myFunction()') or a static
2129       method (e.g. '#MyClass::myFunction()') - for how to use a non-static method, see the type COMMAND OBJECT.
2130       The current record will be passed to the callback function
2131       as an associative array. You can also pass additional string arguments to the callback function by specifing
2132       them within the opening and closing brace; e.g. '#Utils::limit(12)' will result in Util::limit() being
2133       called with the current record as the first and '12' as the second argument. If you do not want
2134       to pass additional arguments to the callback function, the opening and closing brace are optional.
2135       The callback function's return value obviously has to be a string that is a valid XML element name.
2136     </listitem>
2137     <listitem>
2138       <emphasis>COMMAND OBJECT (object)</emphasis>: If you want to use a non-static method as a
2139       callback function, you can do so by specifying the value as an instance of a class that
2140       implements the {@link XML_Query2XML_Callback} interface. This implementation of the
2141       {@link http://en.wikipedia.org/wiki/Command_pattern command pattern} gives you all the
2142       flexibility. The disadvantage ist that you cannot use the XML UNSERIALIZATION prefix
2143       or the CONDITIONAL prefix. Note: you have to require_once 'XML/Query2XML/Callback.php'
2144       before using the XML_Query2XML_Callback interface. The return value of a COMMAND OBJECT's
2145       execute() method is treated exactly the same as the return value of a CALLBACK FUNCTION.
2146      </listitem>
2147    </itemizedlist>
2148   </para>
2149   <para>
2150    Let's have a look at a straightforward example: we want our customer's email addresses inside a tag named
2151    after the customer's first name, e.g. &lt;John&gt;john.doe@example.com&lt;/John&gt;:
2152    <programlisting role="php">
2153    <![CDATA[
2154<?php
2155require_once 'XML/Query2XML.php';
2156require_once 'MDB2.php';
2157$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
2158$dom = $query2xml->getXML(
2159  "SELECT * FROM customer",
2160  array(
2161    'rootTag' => 'customers',
2162    'idColumn' => 'customerid',
2163    'rowTag' => 'customer',
2164    'elements' => array(
2165        'customerid',
2166        'name_and_email' => array(
2167            'dynamicRowTag' => 'first_name',
2168            'value' => 'email'
2169        )
2170    )
2171  )
2172);
2173header('Content-Type: application/xml');
2174$dom->formatOutput = true;
2175print $dom->saveXML();
2176?>
2177   ]]>
2178   </programlisting>
2179   The resulting XML looks like this:
2180   <programlisting role="tutorial">
2181   <![CDATA[
2182<?xml version="1.0" encoding="UTF-8"?>
2183<customers>
2184  <customer>
2185    <customerid>1</customerid>
2186    <Jane>jane.doe@example.com</Jane>
2187  </customer>
2188  <customer>
2189    <customerid>2</customerid>
2190    <John>john.doe@example.com</John>
2191  </customer>
2192  <customer>
2193    <customerid>3</customerid>
2194    <Susan>susan.green@example.com</Susan>
2195  </customer>
2196  <customer>
2197    <customerid>4</customerid>
2198    <Victoria>victory.alt@example.com</Victoria>
2199  </customer>
2200  <customer>
2201    <customerid>5</customerid>
2202    <Will>will.wippy@example.com</Will>
2203  </customer>
2204  <customer>
2205    <customerid>6</customerid>
2206    <Tim>tim.raw@example.com</Tim>
2207  </customer>
2208  <customer>
2209    <customerid>7</customerid>
2210    <Nick>nick.fallow@example.com</Nick>
2211  </customer>
2212  <customer>
2213    <customerid>8</customerid>
2214    <Ed>ed.burton@example.com</Ed>
2215  </customer>
2216  <customer>
2217    <customerid>9</customerid>
2218    <Jack>jack.woo@example.com</Jack>
2219  </customer>
2220  <customer>
2221    <customerid>10</customerid>
2222    <Maria>maria.gonzales@example.com</Maria>
2223  </customer>
2224</customers>
2225   ]]>
2226   </programlisting>
2227
2228   </para>
2229  </refsect2>
2230
2231  <refsect2 id="{@id options_roottag}">
2232   <title>$options['rootTag']</title>
2233   <para>
2234    The name of the root tag that encloses all other tags. On the root level, the default is 'root'.
2235    On all other levels omitting the rootTag option means that the row tags will not be enclosed by
2236    a root tag but will directly be placed inside the parent tag.
2237   </para>
2238   <para>
2239    Here goes an example of 'rootTag' being used at the root level:
2240    <programlisting role="php">
2241    <![CDATA[
2242<?php
2243require_once 'XML/Query2XML.php';
2244require_once 'MDB2.php';
2245$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
2246$dom = $query2xml->getXML(
2247  "SELECT * FROM artist",
2248  array(
2249    'rootTag' => 'favorite_artists',
2250    'idColumn' => 'artistid',
2251    'rowTag' => 'artist',
2252    'elements' => array(
2253        'name',
2254        'birth_year',
2255        'genre'
2256    )
2257  )
2258);
2259header('Content-Type: application/xml');
2260$dom->formatOutput = true;
2261print $dom->saveXML();
2262?>
2263    ]]>
2264    </programlisting>
2265    'rootTag' was set to 'favorite_artists'. The resulting XML data therefore is:
2266    <programlisting role="tutorial">
2267    <![CDATA[
2268<?xml version="1.0" encoding="UTF-8"?>
2269<favorite_artists>
2270<artist>
2271  <name>Curtis Mayfield</name>
2272  <birth_year>1920</birth_year>
2273  <genre>Soul</genre>
2274</artist>
2275<artist>
2276  <name>Isaac Hayes</name>
2277  <birth_year>1942</birth_year>
2278  <genre>Soul</genre>
2279</artist>
2280<artist>
2281  <name>Ray Charles</name>
2282  <birth_year>1930</birth_year>
2283  <genre>Country and Soul</genre>
2284</artist>
2285</favorite_artists>
2286    ]]>
2287    </programlisting>
2288   </para>
2289   <para>
2290    Here goes an example with the rootTag being used at a lower level:
2291    <programlisting role="php">
2292    <![CDATA[
2293<?php
2294require_once 'XML/Query2XML.php';
2295require_once 'MDB2.php';
2296$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
2297$dom = $query2xml->getXML(
2298    "SELECT
2299        *
2300     FROM
2301        artist
2302        LEFT JOIN album ON album.artist_id = artist.artistid",
2303    array(
2304        'rootTag' => 'music_library',
2305        'rowTag' => 'artist',
2306        'idColumn' => 'artistid',
2307        'elements' => array(
2308            'artistid',
2309            'name',
2310            'birth_year',
2311            'birth_place',
2312            'genre',
2313            'albums' => array(
2314                'rootTag' => 'albums',
2315                'rowTag' => 'album',
2316                'idColumn' => 'albumid',
2317                'elements' => array(
2318                    'albumid',
2319                    'title',
2320                    'published_year',
2321                    'comment'
2322                )
2323            )
2324        )
2325    )
2326);
2327
2328header('Content-Type: application/xml');
2329$dom->formatOutput = true;
2330print $dom->saveXML();
2331?>
2332    ]]>
2333    </programlisting>
2334    ['elements']['albums']['rootTag'] is set to 'albums'. Therefore all 'album' tags of a single
2335    artist will be enclosed by a singel 'albums' tag. This example is actually taken from
2336    {@tutorial XML_Query2XML.pkg#casestudies.case02}, so please see there for the resulting XML data
2337    and further discussion.
2338   </para>
2339   <para>
2340    As shown in {@tutorial XML_Query2XML.pkg#casestudies.case04} is is also possible to assign
2341    an empty string to the rootTag option or to omit it at all. In our case this results in
2342    all the album tags not being surrounded by a single 'albums' tag but being directly placed
2343    inside the 'artist' tag:
2344    <programlisting role="php">
2345    <![CDATA[
2346<?php
2347require_once 'XML/Query2XML.php';
2348require_once 'MDB2.php';
2349$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
2350$dom = $query2xml->getXML(
2351    "SELECT
2352        *
2353     FROM
2354        artist
2355        LEFT JOIN album ON album.artist_id = artist.artistid",
2356    array(
2357        'rootTag' => 'music_library',
2358        'rowTag' => 'artist',
2359        'idColumn' => 'artistid',
2360        'elements' => array(
2361            'artistid',
2362            'name',
2363            'birth_year',
2364            'birth_place',
2365            'genre',
2366            'albums' => array(
2367                'rowTag' => 'album',
2368                'idColumn' => 'albumid',
2369                'elements' => array(
2370                    'albumid',
2371                    'title',
2372                    'published_year',
2373                    'comment'
2374                )
2375            )
2376        )
2377    )
2378);
2379
2380header('Content-Type: application/xml');
2381$dom->formatOutput = true;
2382print $dom->saveXML();
2383?>
2384    ]]>
2385    </programlisting>
2386    The resulting XML looks like this:
2387    <programlisting role="tutorial">
2388    <![CDATA[
2389<?xml version="1.0" encoding="UTF-8"?>
2390<music_library>
2391  <artist>
2392    <artistid>1</artistid>
2393    <name>Curtis Mayfield</name>
2394    <birth_year>1920</birth_year>
2395    <birth_place>Chicago</birth_place>
2396    <genre>Soul</genre>
2397    <album>
2398      <albumid>1</albumid>
2399      <title>New World Order</title>
2400      <published_year>1990</published_year>
2401      <comment>the best ever!</comment>
2402    </album>
2403    <album>
2404      <albumid>2</albumid>
2405      <title>Curtis</title>
2406      <published_year>1970</published_year>
2407      <comment>that man's got somthin' to say</comment>
2408    </album>
2409  </artist>
2410  <artist>
2411    <artistid>2</artistid>
2412    <name>Isaac Hayes</name>
2413    <birth_year>1942</birth_year>
2414    <birth_place>Tennessee</birth_place>
2415    <genre>Soul</genre>
2416    <album>
2417      <albumid>3</albumid>
2418      <title>Shaft</title>
2419      <published_year>1972</published_year>
2420      <comment>he's the man</comment>
2421    </album>
2422  </artist>
2423  <artist>
2424    <artistid>3</artistid>
2425    <name>Ray Charles</name>
2426    <birth_year>1930</birth_year>
2427    <birth_place>Mississippi</birth_place>
2428    <genre>Country and Soul</genre>
2429  </artist>
2430</music_library>
2431    ]]>
2432    </programlisting>
2433   <para>
2434   </para>
2435    Note however that a hidden child element is used as a container
2436    to ensure the order of the generated XML elements. Internally all elements with a name
2437    that starts with '__' are hidden. An explicit definition of the hidden complex element would look
2438    like this:
2439    <programlisting role="php">
2440    <![CDATA[
2441<?php
2442require_once 'XML/Query2XML.php';
2443require_once 'MDB2.php';
2444$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
2445$dom = $query2xml->getXML(
2446    "SELECT
2447        *
2448     FROM
2449        artist
2450        LEFT JOIN album ON album.artist_id = artist.artistid",
2451    array(
2452        'rootTag' => 'music_library',
2453        'rowTag' => 'artist',
2454        'idColumn' => 'artistid',
2455        'elements' => array(
2456            'artistid',
2457            'name',
2458            'birth_year',
2459            'birth_place',
2460            'genre',
2461            'albums' => array(
2462                'rootTag' => '__albums',
2463                'rowTag' => 'album',
2464                'idColumn' => 'albumid',
2465                'elements' => array(
2466                    'albumid',
2467                    'title',
2468                    'published_year',
2469                    'comment'
2470                )
2471            )
2472        )
2473    )
2474);
2475
2476header('Content-Type: application/xml');
2477$dom->formatOutput = true;
2478print $dom->saveXML();
2479?>
2480    ]]>
2481    </programlisting>
2482   </para>
2483  </refsect2>
2484
2485  <refsect2 id="{@id options_value}">
2486   <title>$options['value']</title>
2487   <para>
2488    The value of an XML element's child text node. The specification can be of the following types:
2489     <itemizedlist>
2490      <listitem>
2491       <emphasis>COLUMN NAME</emphasis>: this is the default if not preceeded by ':' or '#'.
2492       If the column does not exist, an {@link XML_Query2XML_ConfigException} will be thrown.
2493      </listitem>
2494      <listitem>
2495       <emphasis>STATIC TEXT with a : prefix</emphasis>: if the value is preceeded by a colon (':'), it is
2496       interpreted as static text.
2497      </listitem>
2498      <listitem>
2499       <emphasis>CALLBACK FUNCTION with a # prefix</emphasis>: if the value is preceeded by a pound sign ('#'), it
2500        is interpreted as a callback function. You can use a regular function (e.g. '#myFunction()') or a static
2501        method (e.g. '#MyClass::myFunction()') - for how to use a non-static method, see the type COMMAND OBJECT.
2502        The current record will be passed to the callback function
2503        as an associative array. You can also pass additional string arguments to the callback function by specifing
2504        them within the opening and closing brace; e.g. '#Utils::limit(12)' will result in Util::limit() being
2505        called with the current record as the first and '12' as the second argument. If you do not want
2506        to pass additional arguments to the callback function, the opening and closing brace are optional.
2507        The callback function's return value will be converted to a string and used as the child text node
2508        if it is anything but an object or an array. If you do return an object or an array from a callback
2509        function it has to be an instance of {@link http://www.php.net/manual/en/ref.dom.php DOMNode} or
2510        an array of DOMNode instances. Please see {@tutorial XML_Query2XML.pkg#other_xml_data_sources} for examples
2511        and further details. If an instances of any other class is returned, a XML_Query2XML_XMLException will be thrown.
2512      </listitem>
2513      <listitem>
2514        <emphasis>COMMAND OBJECT (object)</emphasis>: If you want to use a non-static method as a
2515        callback function, you can do so by specifying the value as an instance of a class that
2516        implements the {@link XML_Query2XML_Callback} interface. This implementation of the
2517        {@link http://en.wikipedia.org/wiki/Command_pattern command pattern} gives you all the
2518        flexibility. The disadvantage ist that you cannot use the XML UNSERIALIZATION prefix
2519        or the CONDITIONAL prefix. Note: you have to require_once 'XML/Query2XML/Callback.php'
2520        before using the XML_Query2XML_Callback interface. The return value of a COMMAND OBJECT's
2521        execute() method is treated exactly the same as the return value of a CALLBACK FUNCTION.
2522       </listitem>
2523     </itemizedlist>
2524     There are four more prefixes available that can be used in conjunction with all the prifixes described above:
2525     <itemizedlist>
2526      <listitem>
2527       <emphasis>XML UNSERIALIZATION prefix &amp;</emphasis>: the ampersand (&amp;) prefix allows you to
2528       automatically unserialize string data, i.e. transform a string into a
2529       {@link http://www.php.net/manual/en/ref.dom.php#dom.class.domdocument DOMDocument}. DOMDocument's
2530       {@link http://www.php.net/manual/en/function.dom-domdocument-loadxml.php loadXML()} method will be
2531       used for this purpose. You can combine all three types with this prefix: '&amp;COLUMN_NAME',
2532       '&amp;#function()' or '&amp;:&lt;name&gt;John&lt;/name&gt;' will all work. You can even use the CONDITIONAL prefix
2533       which has to preceed all other prefixes. If the data cannot be unserialized i.e.
2534       {@link http://www.php.net/manual/en/function.dom-domdocument-loadxml.php DOMDocument::loadXML()}
2535       returns false, a XML_Query2XML_XMLException will be thrown. Please see
2536       {@tutorial XML_Query2XML.pkg#other_xml_data_sources} for examples and further
2537       details.
2538      </listitem>
2539      <listitem>
2540       <emphasis>BASE64 ENCODING prefix ^</emphasis>: if the specification starts with a carrat sign ('^'),
2541       the element value will be passed to {@link http://www.php.net/base64_encode base64_encode()}.
2542       The BASE64 ENCODING prefix can be used with all the prefixes described above (just put the BASE64 ENCODING prefix first):
2543       e.g. '^#', '^:' or '^COLUMN_NAME'.
2544      </listitem>
2545      <listitem>
2546       <emphasis>CDATA SECTION prefix =</emphasis>: if the specification starts with an equal sign ('='),
2547       the element value will be enclosed in a CDATA section. A CDATA section starts with
2548       "&lt;![CDATA[" and ends with "]]&gt;".
2549       The CDATA SECTION prefix can be used with all the prefixes described above (just put the CDATA SECTION prefix first):
2550       e.g. '=#', '=:', '=COLUMN_NAME' or '=^'.
2551      </listitem>
2552      <listitem>
2553       <emphasis>CONDITIONAL prefix ?</emphasis>: if the specification starts with a question mark ('?'),
2554       the whole element will be skipped if the value equals (==) an empty string. The CONDITIONAL prefix
2555       can be combined with all types described above: if you do this you have to write the CONDITIONAL
2556       prefix first e.g. '?#', '?:', '?&amp;', '?=', '?^', or '?COLUMN_NAME'.
2557      </listitem>
2558     </itemizedlist>
2559     Note: for ovious reasons, the XML UNSERIALIZATION prefix and the CONDITIONAL prefix cannot be
2560     combined with a COMMAND OBJECT.
2561   </para>
2562   <para>
2563     Basically, the same syntax can be use for
2564     {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications},
2565     {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications},
2566     {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.complexqueryspecification} and
2567     {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn} because the private method
2568     {@link XML_Query2XML::_applyColumnStringToRecord()} is used in all cases.
2569   </para>
2570   <para>
2571     The following example demonstrates the usage of some of the types (for a full demonstration of all types
2572     see the second example under {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}).
2573     The comment element will be skipped if its value == "".
2574     Same holds true for the genre element which uses the trim'ed version of the value stored in the genre column. The comment
2575     tag has an attribute named type with a static value of "short text". The published_century element gets the century
2576     calculated using floor and has the attribute digitCount with a static value of 2.
2577     <programlisting role="php">
2578     <![CDATA[
2579<?php
2580require_once 'XML/Query2XML.php';
2581require_once 'MDB2.php';
2582$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
2583$dom = $query2xml->getXML(
2584  "SELECT
2585    *
2586   FROM
2587    album al,
2588    artist ar
2589   WHERE
2590    al.artist_id = ar.artistid",
2591  array(
2592    'rootTag' => 'albums',
2593    'idColumn' => 'albumid',
2594    'rowTag' => 'album',
2595    'elements' => array(
2596        'albumid',
2597        'title',
2598        'published_year',
2599        'published_century' => array(
2600            'value' => "#Utils::getPublishedYearCentury()",
2601            'attributes' => array(
2602                'digitCount' => ':2'
2603            )
2604        ),
2605        'comment' => array(
2606            'value' => '?comment',
2607            'attributes' => array(
2608                'type' => ':short text'
2609            )
2610        ),
2611        'genre' => array(
2612            'value' => "?#Utils::trimGenre()"
2613        )
2614    )
2615  )
2616);
2617header('Content-Type: application/xml');
2618$dom->formatOutput = true;
2619print $dom->saveXML();
2620
2621class Utils
2622{
2623    function trimGenre($record)
2624    {
2625        return trim($record['genre']);
2626    }
2627
2628    function getPublishedYearCentury($record)
2629    {
2630        return floor($record['published_year']/100);
2631    }
2632}
2633?>
2634     ]]>
2635     </programlisting>
2636     The resulting XML data looks like this:
2637     <programlisting role="tutorial">
2638     <![CDATA[
2639<?xml version="1.0" encoding="UTF-8"?>
2640<albums>
2641  <album>
2642    <albumid>1</albumid>
2643    <title>New World Order</title>
2644    <published_year>1990</published_year>
2645    <published_century digitCount="2">19</published_century>
2646    <comment type="short text">the best ever!</comment>
2647    <genre>Soul</genre>
2648  </album>
2649  <album>
2650    <albumid>2</albumid>
2651    <title>Curtis</title>
2652    <published_year>1970</published_year>
2653    <published_century digitCount="2">19</published_century>
2654    <comment type="short text">that man's got somthin' to say</comment>
2655    <genre>Soul</genre>
2656  </album>
2657  <album>
2658    <albumid>3</albumid>
2659    <title>Shaft</title>
2660    <published_year>1972</published_year>
2661    <published_century digitCount="2">19</published_century>
2662    <comment type="short text">he's the man</comment>
2663    <genre>Soul</genre>
2664  </album>
2665</albums>
2666     ]]>
2667     </programlisting>
2668   </para>
2669  </refsect2>
2670
2671  <refsect2 id="{@id options_condition}">
2672   <title>$options['condition']</title>
2673   <para>
2674    This option allows you to specify a condition for the element to be included.
2675    The string assigned to the condition option can be of the following types:
2676      <itemizedlist>
2677       <listitem>
2678        <emphasis>COLUMN NAME</emphasis>: this is the default if not preceeded by ':' or '#'.
2679        If the column does not exist, an {@link XML_Query2XML_ConfigException} will be thrown.
2680        Remember that the string '0' or '' will both evaluate to false which
2681        means that the element would be skipped. Note: in most cases you will be much
2682        better off changing your WHERE clause than using this type of condition.
2683       </listitem>
2684       <listitem>
2685        <emphasis>STATIC TEXT with a : prefix</emphasis>: if the value is preceeded by a colon (':'), it is
2686        interpreted as static text. Remember that the string '0' or '' will both evaluate to false which
2687        means that the element would be skipped.
2688       </listitem>
2689       <listitem>
2690        <emphasis>CALLBACK FUNCTION with a # prefix</emphasis>: if the value is preceeded by a pound sign ('#'), it
2691        is interpreted as a callback function. You can use a regular function (e.g. '#myFunction()') or a static
2692        method (e.g. '#MyClass::myFunction()') - for how to use a non-static method, see the type COMMAND OBJECT.
2693        The current record will be passed to the callback function
2694        as an associative array. You can also pass additional string arguments to the callback function by specifing
2695        them within the opening and closing brace; e.g. '#Utils::limit(12)' will result in Util::limit() being
2696        called with the current record as the first and '12' as the second argument. If you do not want
2697        to pass additional arguments to the callback function, the opening and closing brace are optional.
2698       </listitem>
2699       <listitem>
2700        <emphasis>COMMAND OBJECT (object)</emphasis>: If you want to use a non-static method as a
2701        callback function, you can do so by specifying the value as an instance of a class that
2702        implements the {@link XML_Query2XML_Callback} interface. This implementation of the
2703        {@link http://en.wikipedia.org/wiki/Command_pattern command pattern} gives you all the
2704        flexibility. Note: you have to require_once 'XML/Query2XML/Callback.php'
2705        before using the XML_Query2XML_Callback interface. The return value of a COMMAND OBJECT's
2706        execute() method is treated exactly the same as the return value of a CALLBACK FUNCTION.
2707       </listitem>
2708      </itemizedlist>
2709
2710    This option provides a similar function as the "?" prefix for column specifications - see
2711    {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications},
2712    {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications} and {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_value}.
2713    The difference is that $options['condition'] is more powerful: you can call any external function you like
2714    to determin whether the element shall be included.
2715    Here goes a little example:
2716    <programlisting role="php">
2717    <![CDATA[
2718<?php
2719if (isset($_REQUEST['includeCondition'])) {
2720    $includeCondition = ($_REQUEST['includeCondition'] == '1');
2721} else {
2722    $includeCondition = false;
2723}
2724require_once 'XML/Query2XML.php';
2725require_once 'MDB2.php';
2726$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
2727$dom =& $query2xml->getXML(
2728    "SELECT
2729        *
2730     FROM
2731        artist
2732        LEFT JOIN album ON album.artist_id = artist.artistid",
2733    array(
2734        'rootTag' => 'music_library',
2735        'rowTag' => 'artist',
2736        'idColumn' => 'artistid',
2737        'elements' => array(
2738            'artistid',
2739            'name',
2740            'birth_year',
2741            'birth_place',
2742            'genre',
2743            'albums' => array(
2744                'rootTag' => 'albums',
2745                'rowTag' => 'album',
2746                'idColumn' => 'albumid',
2747                'condition' => '#isSpecialPublishedYear()',
2748                'elements' => array(
2749                    'albumid',
2750                    'title',
2751                    'published_year',
2752                    'comment' => array(
2753                        'value' => 'comment',
2754                        'condition' => ':' . ($includeCondition ? '1' : '0')
2755                    )
2756                )
2757            )
2758        )
2759    )
2760);
2761header('Content-Type: application/xml');
2762$dom->formatOutput = true;
2763print $dom->saveXML();
2764
2765/**Returns whether $year is 1970 or 1972.
2766*/
2767function isSpecialPublishedYear($record)
2768{
2769    //do some highly complex calculations ...
2770    return $record['published_year'] == 1970 || $record['published_year'] == 1972;
2771}
2772?>
2773    ]]>
2774    </programlisting>
2775    The resulting XML data is:
2776    <programlisting role="tutorial">
2777    <![CDATA[
2778<?xml version="1.0" encoding="UTF-8"?>
2779<music_library>
2780  <artist>
2781    <artistid>1</artistid>
2782    <name>Curtis Mayfield</name>
2783    <birth_year>1920</birth_year>
2784    <birth_place>Chicago</birth_place>
2785    <genre>Soul</genre>
2786    <albums>
2787      <album>
2788        <albumid>2</albumid>
2789        <title>Curtis</title>
2790        <published_year>1970</published_year>
2791      </album>
2792    </albums>
2793  </artist>
2794  <artist>
2795    <artistid>2</artistid>
2796    <name>Isaac Hayes</name>
2797    <birth_year>1942</birth_year>
2798    <birth_place>Tennessee</birth_place>
2799    <genre>Soul</genre>
2800    <albums>
2801      <album>
2802        <albumid>3</albumid>
2803        <title>Shaft</title>
2804        <published_year>1972</published_year>
2805      </album>
2806    </albums>
2807  </artist>
2808  <artist>
2809    <artistid>3</artistid>
2810    <name>Ray Charles</name>
2811    <birth_year>1930</birth_year>
2812    <birth_place>Mississippi</birth_place>
2813    <genre>Country and Soul</genre>
2814    <albums />
2815  </artist>
2816</music_library>
2817    ]]>
2818    </programlisting>
2819   </para>
2820   <para>
2821    Note that (if present) {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql} will
2822    get processed *before* evaluating the condition. This allows you to wirte code
2823    like the following:
2824    <programlisting role="php">
2825    <![CDATA[
2826<?php
2827require_once 'XML/Query2XML.php';
2828require_once 'MDB2.php';
2829$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
2830$dom = $query2xml->getXML(
2831  "SELECT
2832    *
2833   FROM
2834    artist",
2835  array(
2836    'rootTag' => 'artists',
2837    'idColumn' => 'artistid',
2838    'rowTag' => 'artist',
2839    'elements' => array(
2840        'artistid',
2841        'name',
2842        'albums' => array(
2843            'idColumn' => 'albumid',
2844            'sql' => array(
2845                'data' => array(
2846                    'artistid'
2847                ),
2848                'query' => "SELECT * FROM album WHERE artist_id = ?",
2849            ),
2850            'condition' => '#isGT1980()',
2851            'elements' => array(
2852                'title',
2853                'published_year'
2854            )
2855        )
2856    )
2857  )
2858);
2859header('Content-Type: application/xml');
2860$dom->formatOutput = true;
2861print $dom->saveXML();
2862
2863function isGT1980($record)
2864{
2865    return $record['published_year'] > 1980;
2866}
2867?>
2868    ]]>
2869    </programlisting>
2870    "published_year" is a column of the table album but as the "sql" option is processed before evaluating
2871    the "condition" option everything works just fine:
2872    <programlisting role="tutorial">
2873    <![CDATA[
2874<?xml version="1.0" encoding="UTF-8"?>
2875<artists>
2876  <artist>
2877    <artistid>1</artistid>
2878    <name>Curtis Mayfield</name>
2879    <albums>
2880      <title>New World Order</title>
2881      <published_year>1990</published_year>
2882    </albums>
2883  </artist>
2884  <artist>
2885    <artistid>2</artistid>
2886    <name>Isaac Hayes</name>
2887  </artist>
2888  <artist>
2889    <artistid>3</artistid>
2890    <name>Ray Charles</name>
2891  </artist>
2892</artists>
2893    ]]>
2894    </programlisting>
2895   </para>
2896  </refsect2>
2897
2898  <refsect2 id="{@id options_sql}">
2899   <title>$options['sql']</title>
2900   <para>
2901    <emphasis>Note:</emphasis> This option is driver-specific. The following discussion is limited
2902    to the database-related drivers.
2903   </para>
2904   <para>
2905    This and {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options} are the only options
2906    that can only be present within
2907    {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications}.
2908    If given at the root level, it would be just ignored. $options['sql'] allows you to split
2909    up one huge JOIN into multiple smaller queries. You might want (or have) to do this in
2910    several scenarios:
2911    <itemizedlist>
2912     <listitem>
2913      Your RDBMS has a maximum number of fields it can return in a single query and you've reached it.
2914     </listitem>
2915     <listitem>
2916      You are short on memory: let's say your big JOIN returns 100 fields and you have 10 000 records.
2917      It might turn out that the memory consumption is lower if you split up the single big JOIN into
2918      multiple quieres that have smaller result sets. As all the data won't be in memory at once,
2919      it might even run faster.
2920     </listitem>
2921     <listitem>
2922      You are too lazy to think about how to best join these 8 tables :)
2923     </listitem>
2924    </itemizedlist>
2925    You will definitively want to do some {@tutorial XML_Query2XML.pkg#profiling} before deciding whether
2926    or not to split up one big JOIN into multiple smaller JOINs.
2927   </para>
2928   <para>
2929    There are two ways of specifying $options['sql']:
2930    <itemizedlist>
2931     <listitem>
2932      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.simplequeryspecification}: uses the query() method provided by the database abstraction layer (PDO/MDB2/DB/ADOdb) - use it with care
2933     </listitem>
2934     <listitem>
2935      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.complexqueryspecification}: uses
2936      the prepare() and execute() methods provided by the database abstraction layer and
2937      can therefore prevent SQL injection and is also faster in most scenarios
2938     </listitem>
2939    </itemizedlist>
2940   </para>
2941   <refsect3 id="{@id simplequeryspecification}">
2942    <title>Simple Query Specification</title>
2943    <para>
2944     Since v0.8.0 a simple query specifications are purely static strings (in most cases
2945     you will want to use a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.complexqueryspecification}):
2946     <programlisting role="php">
2947     <![CDATA[
2948<?php
2949require_once 'XML/Query2XML.php';
2950require_once 'MDB2.php';
2951$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
2952$dom = $query2xml->getXML(
2953    "SELECT
2954        *
2955     FROM
2956        artist
2957     WHERE
2958        artistid = 1",
2959    array(
2960        'rootTag' => 'music_library',
2961        'rowTag' => 'artist',
2962        'idColumn' => 'artistid',
2963        'elements' => array(
2964            'artistid',
2965            'name',
2966            'birth_year',
2967            'birth_place',
2968            'genre',
2969            'albums' => array(
2970                'sql' => 'SELECT * FROM album WHERE artist_id = 1',
2971                'rootTag' => 'albums',
2972                'rowTag' => 'album',
2973                'idColumn' => 'albumid',
2974                'elements' => array(
2975                    'albumid',
2976                    'title',
2977                    'published_year',
2978                    'comment'
2979                )
2980            )
2981        )
2982    )
2983);
2984
2985header('Content-Type: application/xml');
2986$dom->formatOutput = true;
2987print $dom->saveXML();
2988?>
2989     ]]>
2990    </programlisting>
2991    </para>
2992    <para>
2993     To understand how $options['sql'] really works, some knowledge of XML_Query2XML's internals might be helpful:
2994     {@link XML_Query2XML::getXML()} calls the private method {@link XML_Query2XML::_getNestedXMLRecord()} for
2995     every record retrieved from the database using the SQL statement passed to getXML() as first
2996     argument. XML_Query2XML::_getNestedXMLRecord() will then process the current record according
2997     to the settings specified in $options. The processing of all
2998     {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications}
2999     is handed off to the private method {@link XML_Query2XML::_processComplexElementSpecification()}.
3000     XML_Query2XML::_processComplexElementSpecification() in turn will call the private method
3001     {@link XML_Query2XML::_applySqlOptionsToRecord()} to interpret $options['sql'] and {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options}.
3002     XML_Query2XML::_processComplexElementSpecification() will then call again {@link XML_Query2XML::_getNestedXMLRecord()} for
3003     every record retrieved using the query specified in the 'sql' option.
3004    </para>
3005   </refsect3>
3006   <refsect3 id="{@id complexqueryspecification}">
3007    <title>Complex Query Specification</title>
3008    <para>
3009    A Complex Query Specification uses the database abstraction layer's prepare() and execute() methods
3010    and therefore prevents SQL injection and is also faster
3011    than a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.simplequeryspecification} in most scenarios.
3012    It can consist of multiple parts (only $options['sql']['query'] is mandatory):
3013    <itemizedlist>
3014     <listitem>
3015      <emphasis>$options['sql']['query']</emphasis>: the SQL query as a string that contains a placeholder
3016      for each element of $options['sql']['data'].
3017     </listitem>
3018     <listitem>
3019      <emphasis>$options['sql']['driver']</emphasis>: allows you to use a different XML_Query2XML_Driver
3020      for this complex query than the one passed to
3021      {@tutorial XML_Query2XML.pkg#query2xml_factory}. Please see
3022      {@tutorial XML_Query2XML.pkg#multipledrivers} for details. $options['sql']['driver'] is optional.
3023     </listitem>
3024     <listitem>
3025      <emphasis>$options['sql']['limit']</emphasis>: allows you to limit the number of records returned from the
3026      query. It has to be a numeric value. Please note that a value of 0 (or '0') is equivalent to not
3027      setting $options['sql']['limit'] at all. $options['sql']['limit'] and $options['sql']['offset'] are
3028      only interpreted by the drivers for PEAR MDB2 and PEAR DB. All other drivers simply ignore these two
3029      options.
3030     </listitem>
3031     <listitem>
3032      <emphasis>$options['sql']['offset']</emphasis>: allows you to set the number of the first record
3033      to retrieve. This has to be a numeric value. The default is 0. Please note that this option will
3034      be ignored unless $options['sql']['limit'] is set. $options['sql']['offset'] and
3035      $options['sql']['limit'] are only interpreted by the drivers for PEAR MDB2 and PEAR DB.
3036      All other drivers simply ignore these two options.
3037     </listitem>
3038     <listitem>
3039      <emphasis>$options['sql']['data']</emphasis>: an indexed array of values. This is optional.
3040      The specification can be of the following types:
3041      <itemizedlist>
3042       <listitem>
3043        <emphasis>COLUMN NAME</emphasis>: this is the default if not preceeded by ':' or '#'.
3044        If the column does not exist, an {@link XML_Query2XML_ConfigException} will be thrown. Note
3045        that the parent record will be used! This is quite logic as this SQL statement has not been executed yet :)
3046       </listitem>
3047       <listitem>
3048        <emphasis>STATIC TEXT with a : prefix</emphasis>: if the value is preceeded by a colon (':'), it is
3049        interpreted as static text.
3050       </listitem>
3051       <listitem>
3052        <emphasis>CALLBACK FUNCTION with a # prefix</emphasis>: if the value is preceeded by a pound sign ('#'), it
3053        is interpreted as a callback function. You can use a regular function (e.g. '#myFunction()') or a static
3054        method (e.g. '#MyClass::myFunction()') - for how to use a non-static method, see the type COMMAND OBJECT.
3055        The current record will be passed to the callback function
3056        as an associative array. You can also pass additional string arguments to the callback function by specifing
3057        them within the opening and closing brace; e.g. '#Utils::limit(12)' will result in Util::limit() being
3058        called with the current record as the first and '12' as the second argument. If you do not want
3059        to pass additional arguments to the callback function, the opening and closing brace are optional.
3060       </listitem>
3061       <listitem>
3062        <emphasis>COMMAND OBJECT (object)</emphasis>: If you want to use a non-static method as a
3063        callback function, you can do so by specifying the value as an instance of a class that
3064        implements the {@link XML_Query2XML_Callback} interface. This implementation of the
3065        {@link http://en.wikipedia.org/wiki/Command_pattern command pattern} gives you all the
3066        flexibility. Note: you have to require_once 'XML/Query2XML/Callback.php'
3067        before using the XML_Query2XML_Callback interface. The return value of a COMMAND OBJECT's
3068        execute() method is treated exactly the same as the return value of a CALLBACK FUNCTION.
3069       </listitem>
3070      </itemizedlist>
3071      The same syntax (with the additional '?' prefix) can be use for
3072      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications},
3073      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_value},
3074      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications} and
3075      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn} because the private method
3076      {@link XML_Query2XML::_applyColumnStringToRecord()} is used in all cases.
3077      <emphasis>Note:</emphasis> $options['sql']['data'] is optional!
3078     </listitem>
3079    </itemizedlist>
3080    Here is a simple example similar to {@tutorial XML_Query2XML.pkg#casestudies.case03}:
3081    </para>
3082    <para>
3083     <programlisting role="php">
3084    <![CDATA[
3085<?php
3086require_once 'XML/Query2XML.php';
3087require_once 'MDB2.php';
3088$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
3089$dom = $query2xml->getXML(
3090    "SELECT
3091        *
3092     FROM
3093        artist",
3094    array(
3095        'rootTag' => 'music_library',
3096        'rowTag' => 'artist',
3097        'idColumn' => 'artistid',
3098        'elements' => array(
3099            'artistid',
3100            'name',
3101            'birth_year',
3102            'birth_place',
3103            'genre',
3104            'albums' => array(
3105                'sql' => array(
3106                    'data' => array(
3107                        'artistid'
3108                    ),
3109                    'query' => "SELECT * FROM album WHERE artist_id = ?"
3110                ),
3111                'rootTag' => 'albums',
3112                'rowTag' => 'album',
3113                'idColumn' => 'albumid',
3114                'elements' => array(
3115                    'albumid',
3116                    'title',
3117                    'published_year',
3118                    'comment'
3119                )
3120            )
3121        )
3122    )
3123);
3124
3125header('Content-Type: application/xml');
3126$dom->formatOutput = true;
3127print $dom->saveXML();
3128?>
3129     ]]>
3130    </programlisting>
3131    </para>
3132   </refsect3>
3133  </refsect2>
3134
3135  <refsect2 id="{@id options_sql_options}">
3136   <title>$options['sql_options']</title>
3137   <para>
3138    This allows you to specify how {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql} is
3139    handled. $options['sql_options'] is an associative array that can have the following fileds:
3140    <itemizedlist>
3141     <listitem>
3142      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.cached}
3143     </listitem>
3144     <listitem>
3145      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.single_record}
3146     </listitem>
3147     <listitem>
3148      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.merge}
3149     </listitem>
3150     <listitem>
3151      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.merge_selective}
3152     </listitem>
3153     <listitem>
3154      {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.merge_master}
3155     </listitem>
3156    </itemizedlist>
3157    Per default all options are set to the boolean value false.
3158   </para>
3159   <refsect3 id="{@id cached}">
3160    <title>$options['sql_options']['cached']</title>
3161    <para>
3162     Since 1.5.0RC1 Caching is deactivated by default.
3163     If caching is activated the result of a query is stored in the private associative array
3164     {@link XML_Query2XML::$_recordCache} using the SQL query string as key. If the exact same
3165     query needs to be executed a second time, its results can be retrieved from cache.
3166    </para>
3167    <para>
3168     Before setting $options['sql_options']['cached'] to true, do some
3169     {@tutorial XML_Query2XML.pkg#profiling}. As documented in
3170     {@tutorial XML_Query2XML.pkg#profiling.getprofile} the CACHED column in the profile output will
3171     show 'true!' if caching is performed without being necessary.
3172    </para>
3173    <para>
3174     Caching only makes sense, if you have to run exactly the same query multiple times.
3175    </para>
3176   </refsect3>
3177   <refsect3 id="{@id single_record}">
3178    <title>$options['sql_options']['single_record']</title>
3179    <para>
3180     Use this option to make sure that the SQL query you specified in
3181     {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql} returns only a single record.
3182     This option is in fact of limited use. Do not use it to fetch only the first record
3183     from a large result set. (SQL is your friend: use a better WHERE clause!)
3184    </para>
3185   </refsect3>
3186   <refsect3 id="{@id merge}">
3187    <title>$options['sql_options']['merge']</title>
3188    <para>
3189     By default no merging is done so that less memory is used. This means that the data of the record
3190     present on the parent level will not be available at this level. Only the data returned by
3191     {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql} will be available (and therefore
3192     use up memory). If you also need the data of the record present on the parent level the two arrays
3193     have to be merged using {@link http://www.php.net/array_merge array_merge()}. If
3194     {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql} returned multiple records, each of them
3195     has to be merged with the one of the parent level separatly:
3196     <programlisting role="php">
3197     <![CDATA[
3198<?php
3199require_once 'XML/Query2XML.php';
3200require_once 'MDB2.php';
3201$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
3202$dom = $query2xml->getXML(
3203    "SELECT
3204        *
3205     FROM
3206        artist",
3207    array(
3208        'rootTag' => 'MUSIC_LIBRARY',
3209        'rowTag' => 'ARTIST',
3210        'idColumn' => 'artistid',
3211        'elements' => array(
3212            'NAME' => 'name',
3213            'BIRTH_YEAR' => 'birth_year',
3214            'GENRE' => 'genre',
3215            'albums' => array(
3216                'sql' => array(
3217                    'data' => array('artistid'),
3218                    'query' => "SELECT * FROM album WHERE artist_id = ?"
3219                ),
3220                'sql_options' => array(
3221                    'merge' => true
3222                ),
3223                'rootTag' => '',
3224                'rowTag' => 'ALBUM',
3225                'idColumn' => 'albumid',
3226                'elements' => array(
3227                    'TITLE' => 'title',
3228                    'PUBLISHED_YEAR' => 'published_year',
3229                    'COMMENT' => 'comment',
3230                    'GENRE' => 'genre'
3231                )
3232            )
3233        )
3234    )
3235);
3236
3237header('Content-Type: application/xml');
3238$dom->formatOutput = true;
3239print $dom->saveXML();
3240?>
3241     ]]>
3242     </programlisting>
3243     This produces quite some overhead. It is therefore highly recommended to use
3244     {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.merge_selective}
3245     described in the next section.
3246    </para>
3247   </refsect3>
3248   <refsect3 id="{@id merge_selective}">
3249    <title>$options['sql_options']['merge_selective']</title>
3250    <para>
3251     As a full merge with the parent record might severly affect the performance, the sql option
3252     merge_selective allows you to only merge the current record with specific columns of the
3253     parent record. Just place the names of all columns of the parent record you want to be
3254     available in the current record in an array and assign it to the merge_selective option.
3255     Here goes an example:
3256     <programlisting role="php">
3257     <![CDATA[
3258<?php
3259require_once 'XML/Query2XML.php';
3260require_once 'MDB2.php';
3261$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
3262$dom = $query2xml->getXML(
3263    "SELECT
3264        *
3265     FROM
3266        artist",
3267    array(
3268        'rootTag' => 'MUSIC_LIBRARY',
3269        'rowTag' => 'ARTIST',
3270        'idColumn' => 'artistid',
3271        'elements' => array(
3272            'NAME' => 'name',
3273            'BIRTH_YEAR' => 'birth_year',
3274            'GENRE' => 'genre',
3275            'albums' => array(
3276                'sql' => array(
3277                    'data' => array('artistid'),
3278                    'query' => "SELECT * FROM album WHERE artist_id = ?"
3279                ),
3280                'sql_options' => array(
3281                    'merge_selective' => array('genre')
3282                ),
3283                'rootTag' => '',
3284                'rowTag' => 'ALBUM',
3285                'idColumn' => 'albumid',
3286                'elements' => array(
3287                    'TITLE' => 'title',
3288                    'PUBLISHED_YEAR' => 'published_year',
3289                    'COMMENT' => 'comment',
3290                    'GENRE' => 'genre'
3291                )
3292            )
3293        )
3294    )
3295);
3296
3297header('Content-Type: application/xml');
3298$dom->formatOutput = true;
3299print $dom->saveXML();
3300?>
3301     ]]>
3302     </programlisting>
3303     Please see {@tutorial XML_Query2XML.pkg#casestudies.case04} for a similar example and
3304     more discussion of {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.merge_selective}.
3305    </para>
3306   </refsect3>
3307   <refsect3 id="{@id merge_master}">
3308    <title>$options['sql_options']['merge_master']</title>
3309    <para>
3310     If (selective) merging is performed, it might become important which record overwrites the data of the other.
3311     As soon as both result sets have a column with the same name, there is a confilict that has to
3312     be resolved. By default, the record of the parent level is the master and overwrites the
3313     record(s) returned by {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql}. If you want
3314     the new records to overwrite the record of the parent level, set
3315     $options['sql_options']['merge_master'] to true. Note that this option only has an effect if
3316     {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.merge} is set to true or
3317     {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.merge_selective} is used.
3318    </para>
3319   </refsect3>
3320  </refsect2>
3321
3322  <refsect2 id="{@id options_mapper}">
3323   <title>$options['mapper']</title>
3324   <para>
3325    This option allows you to specifiy a function for mapping SQL identifiers to XML names.
3326    Whenever you use a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications Simple Element Specification}
3327    or a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications Simple Attribute Specification}
3328    only with a column name and without a tag/attribute name, the specified column name will be used for the
3329    tag/attribute name. Please note that mapping is also performed when the
3330    {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.asterisk_shortcut} is used.
3331    Per default $options['mapper'] is set to false which means that no special mapping is used.
3332    $options['mapper'] can have one of the following formats:
3333    <itemizedlist>
3334      <listitem>
3335       <emphasis>'CLASS::STATIC_METHOD'</emphasis>: this syntax allows you to use a static method
3336       for mapping:
3337       <programlisting role="php">
3338       <![CDATA[
3339'mapper' => 'MyMapper::map'
3340       ]]>
3341       </programlisting>
3342      </listitem>
3343      <listitem>
3344       <emphasis>array('CLASS', 'STATIC_METHOD')</emphasis>: this syntax also allows you to use a static
3345       method for mapping:
3346       <programlisting role="php">
3347       <![CDATA[
3348'mapper' => array('MyMapper', 'map')
3349       ]]>
3350       </programlisting>
3351      </listitem>
3352      <listitem>
3353       <emphasis>array($instance, 'METHOD')</emphasis>: this syntax allows you to use a non-static
3354       method for mapping:
3355       <programlisting role="php">
3356       <![CDATA[
3357'mapper' => array($myMap, 'map')
3358       ]]>
3359       </programlisting>
3360      </listitem>
3361      <listitem>
3362       <emphasis>'FUNCTION'</emphasis>: this syntax allows you to use a regular function
3363       for mapping:
3364       <programlisting role="php">
3365       <![CDATA[
3366'mapper' => 'myUppercaseMapper'
3367       ]]>
3368       </programlisting>
3369      </listitem>
3370      <listitem>
3371       <emphasis>false</emphasis>: use the boolean value false (or any other value that == false) to
3372       deactivate any special mapping:
3373       <programlisting role="php">
3374       <![CDATA[
3375'mapper' => false
3376       ]]>
3377       </programlisting>
3378      </listitem>
3379    </itemizedlist>
3380    Remember that the mapping only applies to
3381    {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}
3382    and {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications}
3383    that do not explicitly have a tag/attribute name or those that have a tag/attribute name that contains
3384    an {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.asterisk_shortcut asterisk shortcut}.
3385    The following example will also show that a mapper defined at the root level is also used at all lower levels
3386    (unless it gets overwritten, see
3387    {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_mapper.multiple_mappers}):
3388    <programlisting role="php">
3389     <![CDATA[
3390<?php
3391class SomeMapper
3392{
3393    public function map($str)
3394    {
3395        //do something with $str
3396        return $str;
3397    }
3398}
3399
3400require_once 'XML/Query2XML.php';
3401require_once 'XML/Query2XML/ISO9075Mapper.php';
3402require_once 'MDB2.php';
3403$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
3404$dom = $query2xml->getXML(           //
3405  "SELECT * FROM artist",            //
3406  array(                             //
3407    'rootTag' => 'favorite_artists', //no mapping
3408    'idColumn' => 'artistid',        //nothing to map
3409    'rowTag' => 'artist',            //no mapping
3410    'mapper' => 'SomeMapper::map',   //
3411    'elements' => array(             //
3412      'artistid',                 //mapping
3413      'NAME' => 'name',           //no mapping as the tag name is specified
3414      '*',                        //mapping
3415      'TAG_*' => '*',             //does a mapping too!
3416      'albums' => array(          //nothing to map
3417        'sql' => array(           //
3418          'data' => array(        //
3419            'artistid'            //nothing to map
3420          ),                      //
3421          'query' => 'SELECT * FROM album WHERE artist_id = ?'      //
3422        ),                      //
3423        'rootTag' => 'albums',  //no mapping
3424        'rowTag' => 'album',    //no mapping
3425        'idColumn' => 'albumid',//nothing to map
3426        'elements' => array(    //
3427          'albumid',          //mapping using the mapper specified at the root level
3428          'title',            //mapping using the mapper specified at the root level
3429          'published_year',   //mapping using the mapper specified at the root level
3430          'comment'           //mapping using the mapper specified at the root level
3431        )                     //
3432      )                       //
3433    ),                        //
3434    'attributes' => array(        //
3435      'artistid',                 //mapping
3436      'NAME' => 'name',           //no mapping as the tag name is specified
3437      '*',                        //mapping
3438      'TAG_*' => '*'              //does a mapping too!
3439    )                             //
3440  )                               //
3441);                                //
3442header('Content-Type: application/xml');    //
3443print $dom->saveXML();                      //
3444?>
3445     ]]>
3446     </programlisting>
3447
3448   </para>
3449
3450   <refsect3 id="{@id 9075mapping}">
3451    <title>Mapping SQL identifiers to XML names in accordance with ISO/IEC 9075-14:2005</title>
3452    <para>
3453     The package XML_Query2XML also implements the Final Committee Draft for ISO/IEC 9075-14:2005,
3454     section "9.1 Mapping SQL &lt;identifier&gt;s to XML Names". ISO/IEC 9075-14:2005 is available
3455     online at {@link http://www.sqlx.org/SQL-XML-documents/5FCD-14-XML-2004-07.pdf}.
3456    </para>
3457    <para>
3458     A lot of characters are legal in SQL identifiers but cannot be used within
3459     XML names. To begin with, SQL identifiers can contain any Unicode character
3460     while XML names are limited to a certain set of characters. E.g the
3461     SQL identifier "&lt;21yrs in age" obviously is not a valid XML name.
3462     '#', '{', and '}' are also not allowed. Fully escaped SQL identifiers
3463     also must not contain a column (':') or start with "xml" (in any case
3464     combination). Illegal characters are mapped to a string of the form
3465     _xUUUU_ where UUUU is the Unicode value of the character.
3466    </para>
3467    <para>
3468     The following is a table of example mappings:
3469     <![CDATA[
3470+----------------+------------------------+------------------------------------+
3471| SQL-Identifier | Fully escaped XML name | Comment                            |
3472+----------------+------------------------+------------------------------------+
3473| dept:id        | dept_x003A_id          | ":" is illegal                     |
3474| xml_name       | _x0078_ml_name         | must not start with [Xx][Mm][Ll]   |
3475| XML_name       | _x0058_ML_name         | must not start with [Xx][Mm][Ll]   |
3476| hire date      | hire_x0020_date        | space is illegal too               |
3477| Works@home     | Works_x0040_home       | "@" is illegal                     |
3478| file_xls       | file_x005F_xls         | "_" gets mapped if followed by "x" |
3479| FIRST_NAME     | FIRST_NAME             | no problem here                    |
3480+----------------+------------------------+------------------------------------+
3481     ]]>
3482    </para>
3483    <para>
3484     The ISO 9075-mapping does produce some overhead which might not be needed in
3485     a lot of situations. Therefore it is not the default mapper. In most cases
3486     it will be sufficient to validate your XML schema once using tools like the free
3487     {@link http://www.altova.com/download_spy_home.html XMLSpy Home Edition}.
3488    </para>
3489    <para>
3490     To use the ISO 9075-mapper that comes with XML_Query2XML you have to:
3491     <itemizedlist>
3492      <listitem>
3493       make sure that {@link http://pear.php.net/package/I18N_UnicodeString PEAR I18N_UnicodeString}
3494       is installed - see {@tutorial XML_Query2XML.pkg#requirements}.
3495      </listitem>
3496      <listitem>
3497       require XML/Query2XML/ISO9075Mapper.php
3498      </listitem>
3499      <listitem>
3500       set the option "mapper" to "XML_Query2XML_ISO9075Mapper::map"
3501      </listitem>
3502     </itemizedlist>
3503     Here goes an example:
3504     <programlisting role="php">
3505     <![CDATA[
3506<?php
3507require_once 'XML/Query2XML.php';
3508require_once 'XML/Query2XML/ISO9075Mapper.php';
3509require_once 'MDB2.php';
3510$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
3511$dom = $query2xml->getXML(
3512  "SELECT * FROM artist",
3513  array(
3514    'rootTag' => 'favorite_artists',
3515    'idColumn' => 'artistid',
3516    'rowTag' => 'artist',
3517    'mapper' => 'XML_Query2XML_ISO9075Mapper::map',
3518    'elements' => array(
3519        '*'
3520    )
3521  )
3522);
3523header('Content-Type: application/xml');
3524$dom->formatOutput = true;
3525print $dom->saveXML();
3526?>
3527     ]]>
3528     </programlisting>
3529    </para>
3530   </refsect3>
3531   <refsect3 id="{@id own_mappers}">
3532    <title>Building your own mappers</title>
3533    <para>
3534     There are cases when you will want the tag and attribute names to be somehow different from
3535     the column names. Let's say you want to use the column names as tag and attribute names
3536     but make them all uppercase. Certainly you could write code like this:
3537     <programlisting role="php">
3538     <![CDATA[
3539<?php
3540require_once 'XML/Query2XML.php';
3541require_once 'XML/Query2XML/ISO9075Mapper.php';
3542require_once 'MDB2.php';
3543$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
3544$dom = $query2xml->getXML(
3545  "SELECT * FROM artist",
3546  array(
3547    'rootTag' => 'favorite_artists',
3548    'idColumn' => 'artistid',
3549    'rowTag' => 'artist',
3550    'elements' => array(
3551        'NAME' => 'name',
3552        'BIRTH_YEAR' => 'birth_year',
3553        'BIRTH_PLACE' => 'birth_place',
3554        'GENRE' => 'genre',
3555    ),
3556    'attributes' => array(
3557        'ARTISTID' => 'artistid'
3558    )
3559  )
3560);
3561header('Content-Type: application/xml');
3562$dom->formatOutput = true;
3563print $dom->saveXML();
3564?>
3565     ]]>
3566     </programlisting>
3567     But that seems a little redundant, doesn't it? In cases like these it is recommended
3568     to write your own mapper. As we want to write OO code we don't implement our mapper
3569     as a function but as a static public method of the new class UppercaseMapper. The
3570     mapper must take a string as an argument and must return a string:
3571     <programlisting role="php">
3572     <![CDATA[
3573<?php
3574class UppercaseMapper
3575{
3576    public function map($str)
3577    {
3578        return strtoupper($str);
3579    }
3580}
3581
3582require_once 'XML/Query2XML.php';
3583require_once 'MDB2.php';
3584$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
3585$dom = $query2xml->getXML(
3586  "SELECT * FROM artist",
3587  array(
3588    'rootTag' => 'favorite_artists',
3589    'idColumn' => 'artistid',
3590    'rowTag' => 'artist',
3591    'mapper' => 'UppercaseMapper::map',
3592    'elements' => array(
3593        'name',
3594        'birth_year',
3595        'birth_place',
3596        'genre',
3597    ),
3598    'attributes' => array(
3599        'artistid'
3600    )
3601  )
3602);
3603header('Content-Type: application/xml');
3604$dom->formatOutput = true;
3605print $dom->saveXML();
3606?>
3607     ]]>
3608     </programlisting>
3609     The resulting XML data looks like this:
3610     <programlisting role="tutorial">
3611     <![CDATA[
3612<?xml version="1.0" encoding="UTF-8"?>
3613<favorite_artists>
3614  <artist ARTISTID="1">
3615    <NAME>Curtis Mayfield</NAME>
3616    <BIRTH_YEAR>1920</BIRTH_YEAR>
3617    <BIRTH_PLACE>Chicago</BIRTH_PLACE>
3618    <GENRE>Soul</GENRE>
3619  </artist>
3620  <artist ARTISTID="2">
3621    <NAME>Isaac Hayes</NAME>
3622    <BIRTH_YEAR>1942</BIRTH_YEAR>
3623    <BIRTH_PLACE>Tennessee</BIRTH_PLACE>
3624    <GENRE>Soul</GENRE>
3625  </artist>
3626  <artist ARTISTID="3">
3627    <NAME>Ray Charles</NAME>
3628    <BIRTH_YEAR>1930</BIRTH_YEAR>
3629    <BIRTH_PLACE>Mississippi</BIRTH_PLACE>
3630    <GENRE>Country and Soul</GENRE>
3631  </artist>
3632</favorite_artists>
3633     ]]>
3634     </programlisting>
3635    </para>
3636   </refsect3>
3637   <refsect3 id="{@id multiple_mappers}">
3638    <title>Using multiple mappers</title>
3639    <para>
3640     Let's say we want to force all tags corresponding to columns of the artist table to be uppercase
3641     and all tags corresponding to columns of the album table to be lowercase. This can be done
3642     using two mappers:
3643     <programlisting role="php">
3644     <![CDATA[
3645<?php
3646class MyMappers
3647{
3648    public function uppercaseMapper($str)
3649    {
3650        return strtoupper($str);
3651    }
3652
3653    public function lowercaseMapper($str)
3654    {
3655        return strtolower($str);
3656    }
3657}
3658
3659require_once 'XML/Query2XML.php';
3660require_once 'MDB2.php';
3661$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
3662$dom = $query2xml->getXML(
3663    "SELECT
3664        *
3665     FROM
3666        artist",
3667    array(
3668        'rootTag' => 'music_library',
3669        'rowTag' => 'artist',
3670        'idColumn' => 'artistid',
3671        'mapper' => 'MyMappers::uppercaseMapper',
3672        'elements' => array(
3673            '*',
3674            'albums' => array(
3675                'sql' => array(
3676                    'data' => array(
3677                        'artistid'
3678                    ),
3679                    'query' => 'SELECT * FROM album WHERE artist_id = ?'
3680                ),
3681                'rootTag' => 'albums',
3682                'rowTag' => 'album',
3683                'idColumn' => 'albumid',
3684                'mapper' => 'MyMappers::lowercaseMapper',
3685                'elements' => array(
3686                    '*',
3687                    'artist_id' => '?:'
3688                )
3689            )
3690        )
3691    )
3692);
3693header('Content-Type: application/xml');
3694$dom->formatOutput = true;
3695print $dom->saveXML();
3696?>
3697     ]]>
3698     </programlisting>
3699     As we know that the columns of the album table already are lowercase we could as well
3700     use one mapper and just deactivate that for the complex element "albums':
3701     <programlisting role="php">
3702     <![CDATA[
3703<?php
3704class MyMappers
3705{
3706    public function uppercaseMapper($str)
3707    {
3708        return strtoupper($str);
3709    }
3710}
3711
3712require_once 'XML/Query2XML.php';
3713require_once 'MDB2.php';
3714$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
3715$dom = $query2xml->getXML(
3716    "SELECT
3717        *
3718     FROM
3719        artist",
3720    array(
3721        'rootTag' => 'music_library',
3722        'rowTag' => 'artist',
3723        'idColumn' => 'artistid',
3724        'mapper' => 'MyMappers::uppercaseMapper',
3725        'elements' => array(
3726            '*',
3727            'albums' => array(
3728                'sql' => array(
3729                    'data' => array(
3730                        'artistid'
3731                    ),
3732                    'query' => 'SELECT * FROM album WHERE artist_id = ?'
3733                ),
3734                'rootTag' => 'albums',
3735                'rowTag' => 'album',
3736                'idColumn' => 'albumid',
3737                'mapper' => false,
3738                'elements' => array(
3739                    '*',
3740                    'artist_id' => '?:'
3741                )
3742            )
3743        )
3744    )
3745);
3746header('Content-Type: application/xml');
3747$dom->formatOutput = true;
3748print $dom->saveXML();
3749?>
3750     ]]>
3751     </programlisting>
3752     In both cases the resulting XML data will look like this:
3753     <programlisting role="tutorial">
3754     <![CDATA[
3755<?xml version="1.0" encoding="UTF-8"?>
3756<music_library>
3757  <artist>
3758    <ARTISTID>1</ARTISTID>
3759    <NAME>Curtis Mayfield</NAME>
3760    <BIRTH_YEAR>1920</BIRTH_YEAR>
3761    <BIRTH_PLACE>Chicago</BIRTH_PLACE>
3762    <GENRE>Soul</GENRE>
3763    <albums>
3764      <album>
3765        <albumid>1</albumid>
3766        <title>New World Order</title>
3767        <published_year>1990</published_year>
3768        <comment>the best ever!</comment>
3769      </album>
3770      <album>
3771        <albumid>2</albumid>
3772        <title>Curtis</title>
3773        <published_year>1970</published_year>
3774        <comment>that man's got somthin' to say</comment>
3775      </album>
3776    </albums>
3777  </artist>
3778  <artist>
3779    <ARTISTID>2</ARTISTID>
3780    <NAME>Isaac Hayes</NAME>
3781    <BIRTH_YEAR>1942</BIRTH_YEAR>
3782    <BIRTH_PLACE>Tennessee</BIRTH_PLACE>
3783    <GENRE>Soul</GENRE>
3784    <albums>
3785      <album>
3786        <albumid>3</albumid>
3787        <title>Shaft</title>
3788        <published_year>1972</published_year>
3789        <comment>he's the man</comment>
3790      </album>
3791    </albums>
3792  </artist>
3793  <artist>
3794    <ARTISTID>3</ARTISTID>
3795    <NAME>Ray Charles</NAME>
3796    <BIRTH_YEAR>1930</BIRTH_YEAR>
3797    <BIRTH_PLACE>Mississippi</BIRTH_PLACE>
3798    <GENRE>Country and Soul</GENRE>
3799    <albums />
3800  </artist>
3801</music_library>
3802     ]]>
3803     </programlisting>
3804    </para>
3805   </refsect3>
3806  </refsect2>
3807
3808  <refsect2 id="{@id options_encoder}">
3809   <title>$options['encoder']</title>
3810   <para>
3811    This option allows you to specifiy a function/method that performs the
3812    XML encoding for node and attribute values. Per default it is assumed
3813    that all data is in ISO-8859-1 (Latin-1) and will be encoded to UTF-8 using
3814    {@link http://at.php.net/mb_convert_encoding mb_convert_encoding()} or if not available
3815    using {@link http://at.php.net/utf8_encode utf8_encode()}.
3816   </para>
3817   <para>
3818    For some introduction to XML encoding please see
3819    {@link http://www.w3schools.com/xml/xml_encoding.asp} and
3820    {@link http://www.opentag.com/xfaq_enc.htm}.
3821    Note: I highly recommend to use UTF-8 for XML if you don't have a compelling reason
3822    to use an other encoding standard.
3823   </para>
3824   <para>
3825    The default encoding mechanism (ISO-8859-1 to UTF-8) will be just fine in most cases
3826    but sometimes your data might already be in in UTF-8 or you might not want
3827    your XML to be UTF-8 encoded at all.
3828   </para>
3829   <para>
3830    Please see {@tutorial XML_Query2XML.pkg#encoding} for how to change the encoding
3831    standard used in the XML declaration.
3832   </para>
3833   <para>
3834    $options['encoder'] can have one of the following formats:
3835    <itemizedlist>
3836      <listitem>
3837       <emphasis>'CLASS::STATIC_METHOD'</emphasis>: this syntax allows you to use a static method
3838       for encoding:
3839       <programlisting role="php">
3840       <![CDATA[
3841'encoder' => 'MyEncoder::encode'
3842       ]]>
3843       </programlisting>
3844      </listitem>
3845      <listitem>
3846       <emphasis>array('CLASS', 'STATIC_METHOD')</emphasis>: this syntax also allows you to use a static
3847       method for encoding:
3848       <programlisting role="php">
3849       <![CDATA[
3850'encoder' => array('MyEncoder', 'encode')
3851       ]]>
3852       </programlisting>
3853      </listitem>
3854      <listitem>
3855       <emphasis>array($instance, 'METHOD')</emphasis>: this syntax allows you to use a non-static
3856       method for encoding:
3857       <programlisting role="php">
3858       <![CDATA[
3859'encoder' => array($myEncoder, 'encode')
3860       ]]>
3861       </programlisting>
3862      </listitem>
3863      <listitem>
3864       <emphasis>'FUNCTION'</emphasis>: this syntax allows you to use a regular function
3865       for encoding:
3866       <programlisting role="php">
3867       <![CDATA[
3868'encoder' => 'myUTF8toISO88591Encoder'
3869       ]]>
3870       </programlisting>
3871      </listitem>
3872      <listitem>
3873       <emphasis>false</emphasis>: use the boolean value false to deactivate encoding:
3874       <programlisting role="php">
3875       <![CDATA[
3876'encoder' => false
3877       ]]>
3878       </programlisting>
3879      </listitem>
3880      <listitem>
3881       <emphasis>null</emphasis>: use NULL to reset encoding to the built-in default encoding.
3882       This default assumes that all data is in ISO-8859-1 (Latin-1) and will encode it to
3883       UTF-8 using {@link http://at.php.net/mb_convert_encoding mb_convert_encoding()}
3884       or if not available using {@link http://at.php.net/utf8_encode utf8_encode()}.
3885       <programlisting role="php">
3886       <![CDATA[
3887'encoder' => null
3888       ]]>
3889       </programlisting>
3890      </listitem>
3891    </itemizedlist>
3892    One thing you should keep in mind when writing your own encoding (wrapper) functions is
3893    that the encoder will only be called if the current record has a string value for
3894    that column; i.e. the encoder will not be called if the column value is NULL.
3895   </para>
3896   <para>
3897    The following example will show that an encoder defined at the root level is also used
3898    at all lower levels (unless it gets overwritten, see
3899    {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_encoder.multiple_encoders}):
3900    <programlisting role="php">
3901    <![CDATA[
3902<?php
3903class SomeEncoder
3904{
3905    public function encode($str)
3906    {
3907        //do something with $str
3908        return $str;
3909    }
3910}
3911
3912require_once 'XML/Query2XML.php';
3913require_once 'XML/Query2XML/ISO9075Mapper.php';
3914require_once 'MDB2.php';
3915$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
3916$dom = $query2xml->getXML(
3917  "SELECT * FROM artist",
3918  array(
3919    'rootTag' => 'favorite_artists',
3920    'idColumn' => 'artistid',
3921    'rowTag' => 'artist',
3922    'encoder' => 'SomeEncoder::encode',     /* we define an encoder at the root level */
3923    'elements' => array(
3924      'artistid',                           // encoding will be
3925      'name',                               // performed on these
3926      'albums' => array(
3927        'sql' => array(
3928          'data' => array(
3929            'artistid'
3930          ),
3931          'query' => 'SELECT * FROM album WHERE artist_id = ?'
3932        ),
3933        'rootTag' => 'albums',
3934        'rowTag' => 'album',
3935        'idColumn' => 'albumid',
3936        'elements' => array(
3937          'albumid',            // encoder setting is affective on all lower
3938          'title'               // levels
3939        ),
3940        'attributes' => array(
3941          'comment'             // note: encoding is also performed for attributes
3942        )
3943      )
3944    )
3945  )
3946);
3947header('Content-Type: application/xml');
3948$dom->formatOutput = true;
3949print $dom->saveXML();
3950?>
3951     ]]>
3952     </programlisting>
3953    </para>
3954    <refsect3 id="{@id iso88591toutf8}">
3955     <title>ISO-8859-1 to UTF-8 encoding (default)</title>
3956     <para>
3957      This is what will automatically be performed if you do not use $options['encoder']
3958      at all. This is because most databases use ISO-8859-1 (aka Latin-1) by default.
3959      As previously stated, XML_Query2XML will use
3960      {@link http://at.php.net/mb_convert_encoding mb_convert_encoding()}
3961      or if that is not available {@link http://at.php.net/utf8_encode utf8_encode()} for the
3962      actual encoding.
3963     </para>
3964     <para>
3965      If you have set $options['encoder'] on the root level but wish to switch back to
3966      the default on a lower level all you have to do is to use the NULL value:
3967      <programlisting role="php">
3968      <![CDATA[
3969'encoder' => null
3970      ]]>
3971      </programlisting>
3972     </para>
3973    </refsect3>
3974    <refsect3 id="{@id utf8toiso88591}">
3975     <title>UTF-8 to ISO-8859-1 encoding</title>
3976     <para>
3977      If your data is in UTF-8 but you would like your XML to be in ISO-8859-1 (Latin-1),
3978      you can use {@link http://at.php.net/utf8_decode utf8_decode()}:
3979      <programlisting role="php">
3980      <![CDATA[
3981'encoder' => 'utf8_decode'
3982      ]]>
3983      </programlisting>
3984      or define a wrapper for {@link http://at.php.net/mb_convert_encoding mb_convert_encoding()}
3985      and use that:
3986      <programlisting role="php">
3987      <![CDATA[
3988function utf8ToLatin1($str)
3989{
3990    //hint: mb_convert_encoding (str, to_encoding, from_encoding)
3991    return mb_convert_encoding($str, 'iso-8859-1', 'UTF-8');
3992}
3993      ]]>
3994      </programlisting>
3995      specified as encoder:
3996      <programlisting role="php">
3997      <![CDATA[
3998'encoder' => 'utf8ToLatin1'
3999      ]]>
4000      </programlisting>
4001     </para>
4002    </refsect3>
4003    <refsect3 id="{@id disabling}">
4004     <title>Disabling encoding</title>
4005     <para>
4006      If you data already is in the character set you wish to use for the XML, all you
4007      have to do is to disable the encoding by using a boolean value of false:
4008      <programlisting role="php">
4009      <![CDATA[
4010'encoder' => false
4011      ]]>
4012      </programlisting>
4013     </para>
4014    </refsect3>
4015    <refsect3 id="{@id multiple_encoders}">
4016     <title>Using multiple encoders</title>
4017     <para>
4018      It might happen to you that some of your data sources are in one character set
4019      while others are in another. This means that you need different encoding procedures
4020      to convert them all to the same character set you wish to use for the XML (usually UTF-8).
4021     </para>
4022     <para>
4023      In the first example we will assume that all columns of the table artist are in
4024      ISO-8859-1 (Latin-1) while all columns of the table album are in UTF-8.
4025      <programlisting role="php">
4026      <![CDATA[
4027<?php
4028require_once 'XML/Query2XML.php';
4029require_once 'MDB2.php';
4030$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
4031$dom = $query2xml->getXML(
4032    "SELECT
4033        *
4034     FROM
4035        artist
4036        LEFT JOIN album ON album.artist_id = artist.artistid",
4037    array(
4038        'rootTag' => 'music_library',
4039        'rowTag' => 'artist',
4040        'idColumn' => 'artistid',
4041        'elements' => array(    // all columns of the table artist are in
4042            'artistid',         // ISO-8859-1; the default conversion therefore
4043            'name',             // is just fine
4044            'birth_year',
4045            'birth_place',
4046            'genre',
4047            'albums' => array(
4048                'rootTag' => 'albums',
4049                'rowTag' => 'album',
4050                'idColumn' => 'albumid',
4051                'encoder' => false,     // the columns of the album table already are in UTF-8;
4052                'elements' => array(    // we therefore have to disable encoding
4053                    'albumid',
4054                    'title',
4055                    'published_year',
4056                    'comment'
4057                )
4058            )
4059        )
4060    )
4061);
4062
4063header('Content-Type: application/xml');
4064
4065$dom->formatOutput = true;
4066print $dom->saveXML();
4067?>
4068      ]]>
4069      </programlisting>
4070
4071     </para>
4072     <para>
4073      For our second example, let's assume that the following columns use the following
4074      character sets:
4075      <![CDATA[
4076+----------------------------------------+
4077| Column         | Character Set         |
4078+----------------------------------------+
4079| artist.name    | ISO-8859-1 (Latin-1)  |
4080| artist.genre   | UTF-8                 |
4081| album.title    | UTF-16                |
4082| album.comment  | Windows-1252          |
4083+----------------+-----------------------+
4084      ]]>
4085      As our XML output shall be in UTF-8 we have to use multiple encoders on a
4086      per-column basis:
4087      <programlisting role="php">
4088      <![CDATA[
4089<?php
4090function latin1ToUTF8($str)
4091{
4092    return utf8_decode($str);
4093    // alternatively we could have used
4094    // return mb_convert_encoding($str, 'UTF-8', 'iso-8859-1');
4095}
4096
4097function utf16ToUTF8($str)
4098{
4099    return mb_convert_encoding($str, 'UTF-8', 'UTF-16');
4100}
4101
4102function windows1252ToUTF8($str)
4103{
4104    return mb_convert_encoding($str, 'UTF-8', 'windows-1252');
4105}
4106
4107require_once 'XML/Query2XML.php';
4108require_once 'MDB2.php';
4109$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
4110$dom = $query2xml->getXML(
4111    "SELECT
4112        *
4113     FROM
4114        artist
4115        LEFT JOIN album ON album.artist_id = artist.artistid",
4116    array(
4117        'rootTag' => 'music_library',
4118        'rowTag' => 'artist',
4119        'idColumn' => 'artistid',
4120        'elements' => array(
4121            'artistid',
4122            'name', // name is in ISO-8859-1 and therefore will be handled by the default conversion
4123            'birth_year',
4124            'birth_place',
4125            'genre' => array(
4126                'value' => 'genre',
4127                'encoder' => false  // genre already is in UTF-8
4128            ),
4129            'albums' => array(
4130                'rootTag' => 'albums',
4131                'rowTag' => 'album',
4132                'idColumn' => 'albumid',
4133                'elements' => array(
4134                    'albumid',
4135                    'title' => array(
4136                        'value' => 'title',
4137                        'encoder' => 'utf16ToUTF8'  // title is in UTF-16 and therefore needs
4138                    ),                              // special treatment
4139                    'published_year'
4140                ),
4141                'attributes' => array(
4142                    'comment' => array(
4143                        'value' => 'comment',
4144                        'encoder' => 'windows1252ToUTF8'    // comment is in Windows-1252
4145                    )
4146                )
4147            )
4148        )
4149    )
4150);
4151
4152header('Content-Type: application/xml');
4153
4154$dom->formatOutput = true;
4155print $dom->saveXML();
4156?>
4157      ]]>
4158      </programlisting>
4159
4160     </para>
4161    </refsect3>
4162  </refsect2>
4163
4164  <refsect2 id="{@id return_value_modification}">
4165   <title>Modifying the returned DOMDocument instance</title>
4166   <para>
4167    {@link XML_Query2XML::getXML()} returns an instance of
4168    {@link http://www.php.net/manual/en/ref.dom.php#dom.class.domdocument DOMDocument}.
4169    I recommend that you do some reading about
4170    {@link http://www.php.net/manual/en/ref.dom.php PHP5's DOM extension}.
4171   </para>
4172   <para>
4173    Let's see how we can add attributes to the root element
4174    (i.e. the first child of the DOMDocument instance returned by {@link XML_Query2XML::getXML() getXML()}):
4175    <programlisting role="php">
4176    <![CDATA[
4177<?php
4178require_once 'XML/Query2XML.php';
4179require_once 'MDB2.php';
4180$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
4181
4182$doc = $query2xml->getXML(
4183    "SELECT
4184        *
4185     FROM
4186        artist",
4187    array(
4188        'rootTag' => 'music_library',
4189        'rowTag' => 'artist',
4190        'idColumn' => 'artistid',
4191        'elements' => array(
4192            'artistid',
4193            'name',
4194            'birth_year',
4195            'birth_place',
4196            'genre'
4197        )
4198    )
4199);
4200$root = $doc->firstChild;
4201$root->setAttribute('copyright', 'John Doe 2007');
4202
4203header('Content-Type: application/xml');
4204$doc->formatOutput = true;
4205print $doc->saveXML();
4206?>
4207    ]]>
4208    </programlisting>
4209   </para>
4210   <para>
4211    This adds an attribute named 'copyright' with a value of 'John Doe 2007'
4212    to the root element &lt;music_library&gt;:
4213    <programlisting role="tutorial">
4214    <![CDATA[
4215<?xml version="1.0" encoding="UTF-8"?>
4216<music_library copyright="John Doe 2007">
4217  <artist>
4218    <artistid>1</artistid>
4219    <name>Curtis Mayfield</name>
4220    <birth_year>1920</birth_year>
4221    <birth_place>Chicago</birth_place>
4222    <genre>Soul</genre>
4223  </artist>
4224  <artist>
4225    <artistid>2</artistid>
4226    <name>Isaac Hayes</name>
4227    <birth_year>1942</birth_year>
4228    <birth_place>Tennessee</birth_place>
4229    <genre>Soul</genre>
4230  </artist>
4231  <artist>
4232    <artistid>3</artistid>
4233    <name>Ray Charles</name>
4234    <birth_year>1930</birth_year>
4235    <birth_place>Mississippi</birth_place>
4236    <genre>Country and Soul</genre>
4237  </artist>
4238</music_library>
4239    ]]>
4240    </programlisting>
4241   </para>
4242  </refsect2>
4243
4244
4245  <refsect2 id="{@id final_notes}">
4246   <title>Final Notes on XML_Query2XML::getXML()</title>
4247   <para>
4248    You might also want to read the API docs: {@link XML_Query2XML}.
4249   </para>
4250  </refsect2>
4251 </refsect1>
4252
4253 <refsect1 id="{@id other_xml_data_sources}">
4254  <title>Integrating other XML data sources</title>
4255  <refsect2 id="{@id intro}">
4256   <title>Introduction</title>
4257   <para>
4258    Since release 1.1.0 it is possible to integrate other XML data sources into
4259    the XML data that is returned by {@link XML_Query2XML::getXML()}. For example
4260    you might want to store XML data in your relational database and integrate that
4261    into your xml feed. In this case the XML data is only present in serialized form
4262    and therefore needs to be unserialized first. This means that the data has to be
4263    converted into a {@link http://www.php.net/manual/en/ref.dom.php DOMDocument} using
4264    {@link http://www.php.net/manual/en/function.dom-domdocument-loadxml.php DOMDocument::loadXML()}.
4265    It might as well be that you already have a PHP application running that creates a DOMDocument.
4266    In that case no unserialization is needed.
4267   </para>
4268  </refsect2>
4269  <refsect2 id="{@id unserialization_prefix}">
4270   <title>The unserialization prefix (&amp;)</title>
4271   <para>
4272    To unserialize xml data you can use the UNSERIALIZATION prefix &amp;. It has the following
4273    characteristics:
4274    <itemizedlist>
4275     <listitem>
4276      {@link http://www.php.net/manual/en/function.dom-domdocument-loadxml.php DOMDocument::loadXML()}
4277      is used for unserialization.
4278     </listitem>
4279     <listitem>
4280      If the data to unserialize is an empty string ('') or null the data will be silently
4281      ignored and no XML elements will be created.
4282     </listitem>
4283     <listitem>
4284      A XML_Query2XML_XMLException will be thrown if the unserialization fails, i.e.
4285      {@link http://www.php.net/manual/en/function.dom-domdocument-loadxml.php DOMDocument::loadXML()}
4286      returns false. This will happen for example if the data you try to unserialize is 'John Doe' (no xml tags at all),
4287      '&lt;name&gt;John Doe' (no closing tag) or '&lt;name&gt;John Doe&lt;/name&gt;&lt;name&gt;Jane Doe&lt;/name&gt;' (no root tag).
4288     </listitem>
4289    </itemizedlist>
4290   </para>
4291   <refsect3 id="{@id usage_scenarios}">
4292    <title>Usage scenarios of the unserialization prefix (&amp;)</title>
4293    <para>
4294     Regarding a container (the root element of your unserialized data)
4295     there are 3 different things you might want when unserializing
4296     the data:
4297     <itemizedlist>
4298      <listitem>
4299       Container always present
4300      </listitem>
4301      <listitem>
4302       Container only present if there are children
4303      </listitem>
4304      <listitem>
4305       No Container
4306      </listitem>
4307     </itemizedlist>
4308     For the detailed description of each of the three possibilities that is to follow, we will assume
4309     that your XML data is stored in the database. Therefore &amp; will be used in the form
4310     of '&amp;COLUMN_NAME'. If you wanted to unserialize static data you would write something
4311     like
4312     <programlisting role="php">
4313     <![CDATA[
4314'&:<name>John Doe</name>'
4315     ]]>
4316     </programlisting>
4317     or if you wanted to unserialize a string returned from a callback function, you would use
4318     <programlisting role="php">
4319     <![CDATA[
4320'&#MyClass::myFunction()'
4321     ]]>
4322     </programlisting>
4323     <itemizedlist>
4324      <listitem>
4325       <emphasis>Container always present</emphasis>: The container element will be present
4326       even if there is no data to be unserialized. This is the default behaviour:
4327       <programlisting role="php">
4328       <![CDATA[
4329<?php
4330require_once 'XML/Query2XML.php';
4331require_once 'MDB2.php';
4332$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
4333$dom =& $query2xml->getXML(
4334    "SELECT
4335        *, NULL AS additional_xml
4336     FROM
4337        store",
4338    array(
4339        'rootTag' => 'music_stores',
4340        'rowTag' => 'store',
4341        'idColumn' => 'storeid',
4342        'elements' => array(
4343            'storeid',
4344            'country',
4345            'state',
4346            'city',
4347            'street',
4348            'building_xmldata' => '&building_xmldata',
4349            'additional_xml' => '&additional_xml',
4350        )
4351    )
4352);
4353$dom->formatOutput = true;
4354print $dom->saveXML();
4355?>
4356       ]]>
4357       </programlisting>
4358       For both records the 'building_xmldata' column contains a &lt;building&gt; element
4359       that has 3 children: &lt;floors&gt;, &lt;elevators&gt; and &lt;square_meters&gt;. But
4360       there is always a surrounding &lt;building_xmldata&gt; tag. The 'additional_xml' column
4361       is NULL for both records but an empty &lt;additional_xml/&gt; element gets created for
4362       both of them.
4363       <programlisting role="tutorial">
4364       <![CDATA[
4365<?xml version="1.0" encoding="UTF-8"?>
4366<music_stores>
4367  <store>
4368    <storeid>1</storeid>
4369    <country>US</country>
4370    <state>New York</state>
4371    <city>New York</city>
4372    <street>Broadway &amp; 72nd Str</street>
4373    <building_xmldata>
4374      <building>
4375        <floors>4</floors>
4376        <elevators>2</elevators>
4377        <square_meters>3200</square_meters>
4378      </building>
4379    </building_xmldata>
4380    <additional_xml/>
4381  </store>
4382  <store>
4383    <storeid>2</storeid>
4384    <country>US</country>
4385    <state>New York</state>
4386    <city>Larchmont</city>
4387    <street>Palmer Ave 71</street>
4388    <building_xmldata>
4389      <building>
4390        <floors>2</floors>
4391        <elevators>1</elevators>
4392        <square_meters>400</square_meters>
4393      </building>
4394    </building_xmldata>
4395    <additional_xml/>
4396  </store>
4397</music_stores>
4398       ]]>
4399       </programlisting>
4400       Note: you would get exactly the same result by using the 'value' option within
4401       a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications}
4402       instead of a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}.
4403       Instead of
4404       <programlisting role="php">
4405       <![CDATA[
4406'building_xmldata' => '&building_xmldata'
4407       ]]>
4408       </programlisting>
4409       you would write
4410       <programlisting role="php">
4411       <![CDATA[
4412'building_xmldata' => array(
4413    'value' => '&building_xmldata'
4414)
4415       ]]>
4416       </programlisting>
4417      </listitem>
4418      <listitem>
4419       <emphasis>Container only present if there are children</emphasis>: The container will only be
4420       present if the unserialization produces at least one XML element. This is achieved
4421       by using the CONDITIONAL prefix (?):
4422       <programlisting role="php">
4423       <![CDATA[
4424<?php
4425require_once 'XML/Query2XML.php';
4426require_once 'MDB2.php';
4427$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
4428$dom =& $query2xml->getXML(
4429    "SELECT
4430        *, NULL AS additional_xml
4431     FROM
4432        store",
4433    array(
4434        'rootTag' => 'music_stores',
4435        'rowTag' => 'store',
4436        'idColumn' => 'storeid',
4437        'elements' => array(
4438            'storeid',
4439            'country',
4440            'state',
4441            'city',
4442            'street',
4443            'building_xmldata' => '?&building_xmldata',
4444            'additional_xml' => '?&additional_xml',
4445        )
4446    )
4447);
4448$dom->formatOutput = true;
4449print $dom->saveXML();
4450?>
4451       ]]>
4452       </programlisting>
4453       The resulting XML data shows that the unserialized XML is still enclosed
4454       by a &lt;building_xmldata&gt; tag but the &lt;additional_xml&gt; elements are gone:
4455       <programlisting role="tutorial">
4456       <![CDATA[
4457<?xml version="1.0" encoding="UTF-8"?>
4458<music_stores>
4459  <store>
4460    <storeid>1</storeid>
4461    <country>US</country>
4462    <state>New York</state>
4463    <city>New York</city>
4464    <street>Broadway &amp; 72nd Str</street>
4465    <building_xmldata>
4466      <building>
4467        <floors>4</floors>
4468        <elevators>2</elevators>
4469        <square_meters>3200</square_meters>
4470      </building>
4471    </building_xmldata>
4472  </store>
4473  <store>
4474    <storeid>2</storeid>
4475    <country>US</country>
4476    <state>New York</state>
4477    <city>Larchmont</city>
4478    <street>Palmer Ave 71</street>
4479    <building_xmldata>
4480      <building>
4481        <floors>2</floors>
4482        <elevators>1</elevators>
4483        <square_meters>400</square_meters>
4484      </building>
4485    </building_xmldata>
4486  </store>
4487</music_stores>
4488       ]]>
4489       </programlisting>
4490       Again: the same results can be achieved by using the 'value' option within
4491       a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications}
4492       instead of a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}.
4493       Instead of
4494       <programlisting role="php">
4495       <![CDATA[
4496'building_xmldata' => '?&building_xmldata'
4497       ]]>
4498       </programlisting>
4499       you would write
4500       <programlisting role="php">
4501       <![CDATA[
4502'building_xmldata' => array(
4503    'value' => '?&building_xmldata'
4504)
4505       ]]>
4506       </programlisting>
4507      </listitem>
4508      <listitem>
4509       <emphasis>No Container</emphasis>: Even if the unserialization produces an XML element
4510       no container will be used. You have to effectively hide the container by using the
4511       hidden_container_prefix that can be set using
4512       {@tutorial XML_Query2XML.pkg#global_options.setglobaloption} and defaults to '__'. Here
4513       goes an example:
4514       <programlisting role="php">
4515       <![CDATA[
4516<?php
4517require_once 'XML/Query2XML.php';
4518require_once 'MDB2.php';
4519$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
4520$dom =& $query2xml->getXML(
4521    "SELECT
4522        *
4523     FROM
4524        store",
4525    array(
4526        'rootTag' => 'music_stores',
4527        'rowTag' => 'store',
4528        'idColumn' => 'storeid',
4529        'elements' => array(
4530            'storeid',
4531            'country',
4532            'state',
4533            'city',
4534            'street',
4535            '__building_xmldata' => '&building_xmldata'
4536        )
4537    )
4538);
4539$dom->formatOutput = true;
4540print $dom->saveXML();
4541?>
4542       ]]>
4543       </programlisting>
4544       The resulting XML now does not contain &lt;building_xmldata&gt; tags that surround
4545       the &lt;building&gt; elements:
4546       <programlisting role="tutorial">
4547       <![CDATA[
4548<?xml version="1.0" encoding="UTF-8"?>
4549<music_stores>
4550  <store>
4551    <storeid>1</storeid>
4552    <country>US</country>
4553    <state>New York</state>
4554    <city>New York</city>
4555    <street>Broadway &amp; 72nd Str</street>
4556    <building>
4557      <floors>4</floors>
4558      <elevators>2</elevators>
4559      <square_meters>3200</square_meters>
4560    </building>
4561  </store>
4562  <store>
4563    <storeid>2</storeid>
4564    <country>US</country>
4565    <state>New York</state>
4566    <city>Larchmont</city>
4567    <street>Palmer Ave 71</street>
4568    <building>
4569      <floors>2</floors>
4570      <elevators>1</elevators>
4571      <square_meters>400</square_meters>
4572    </building>
4573  </store>
4574</music_stores>
4575       ]]>
4576       </programlisting>
4577       The same results can be achieved by using the 'value' option within
4578       a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications}
4579       instead of a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}.
4580       Instead of
4581       <programlisting role="php">
4582       <![CDATA[
4583'__building_xmldata' => '&building_xmldata'
4584       ]]>
4585       </programlisting>
4586       you would write
4587       <programlisting role="php">
4588       <![CDATA[
4589'__building_xmldata' => array(
4590    'value' => '&building_xmldata'
4591)
4592       ]]>
4593       </programlisting>
4594       or (which is effectively the same)
4595       <programlisting role="php">
4596       <![CDATA[
4597'building_xmldata' => array(
4598    'rowTag' => '__building_xmldata'
4599    'value' => '&building_xmldata'
4600)
4601       ]]>
4602       </programlisting>
4603       Please also note that using the CONDITIONAL prefix (?) in conjunction with
4604       the hidden_container_prefix '__' does not change the resulting XML data in any way:
4605       <programlisting role="php">
4606       <![CDATA[
4607'__building_xmldata' => '?&building_xmldata'
4608       ]]>
4609       </programlisting>
4610      </listitem>
4611     </itemizedlist>
4612    </para>
4613   </refsect3>
4614   <refsect3 id="{@id writing_your_own}">
4615    <title>Writing your own unserialization method</title>
4616    <para>
4617     If you are unhappy with these chracteristics (e.g. you want invalid XML data to be ignored
4618     rather than causing an exception to be thrown) you could do your own unserialization using
4619     a CALLBACK FUNCTION (i.e. the # prefix). Here is what the unserialization performed by
4620     the &amp; prefix looks like
4621     <programlisting role="php">
4622     <![CDATA[
4623if (strlen($xmlData)) {
4624    $doc = new DOMDocument();
4625    if (!@$doc->loadXML($xmlData)) {
4626        throw new XML_Query2XML_XMLException(
4627            'Could not unserialize the following XML data: '
4628            . $ret
4629        );
4630    }
4631    return $doc->documentElement;
4632} else {
4633    return null;
4634}
4635     ]]>
4636     </programlisting>
4637    </para>
4638   </refsect3>
4639  </refsect2>
4640  <refsect2 id="{@id callbacks}">
4641   <title>Returning DOMNode instances from callbacks</title>
4642   <para>
4643    If you want to integrate XML_Query2XML with another PHP application that uses
4644    PHP5's {@link http://www.php.net/manual/en/ref.dom.php DOM},
4645    you can make your callbacks return an instance of
4646    {@link http://www.php.net/manual/en/ref.dom.php DOMNode} or an array of DOMNode instances.
4647   </para>
4648   <para>
4649    In the following example we return a &lt;unixtime&gt; tag from the callback function
4650    getTime(). It will be placed inside a &lt;time&gt; tag.
4651    <programlisting role="php">
4652    <![CDATA[
4653<?php
4654require_once 'XML/Query2XML.php';
4655require_once 'MDB2.php';
4656$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
4657$dom =& $query2xml->getXML(
4658    "SELECT
4659        *
4660     FROM
4661        album",
4662    array(
4663        'rootTag' => 'music_store',
4664        'rowTag' => 'album',
4665        'idColumn' => 'albumid',
4666        'elements' => array(
4667            'albumid',
4668            'title',
4669            'time' => '#getTime()'
4670        )
4671    )
4672);
4673$dom->formatOutput = true;
4674print $dom->saveXML();
4675
4676function getTime()
4677{
4678    $dom = new DOMDocument();
4679    $unixtime = $dom->createElement('unixtime');
4680    $unixtime->appendChild($dom->createTextNode(time()));
4681    return $unixtime;
4682}
4683?>
4684    ]]>
4685    </programlisting>
4686    Have a look at the resulting XML data:
4687    <programlisting role="tutorial">
4688    <![CDATA[
4689<?xml version="1.0" encoding="UTF-8"?>
4690<music_store>
4691  <album>
4692    <albumid>1</albumid>
4693    <title>New World Order</title>
4694    <time>
4695      <unixtime>1167167461</unixtime>
4696    </time>
4697  </album>
4698  <album>
4699    <albumid>2</albumid>
4700    <title>Curtis</title>
4701    <time>
4702      <unixtime>1167167461</unixtime>
4703    </time>
4704  </album>
4705  <album>
4706    <albumid>3</albumid>
4707    <title>Shaft</title>
4708    <time>
4709      <unixtime>1167167461</unixtime>
4710    </time>
4711  </album>
4712</music_store>
4713    ]]>
4714    </programlisting>
4715    Now we modify the example so that getTime() returns multiple DOMNode instances in an array.
4716    We will also "hide" the surrounding element using the hidden_container_prefix that can be
4717    set using {@tutorial XML_Query2XML.pkg#global_options.setglobaloption} and defaults to '__':
4718    <programlisting role="php">
4719    <![CDATA[
4720<?php
4721require_once 'XML/Query2XML.php';
4722require_once 'MDB2.php';
4723$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
4724$dom =& $query2xml->getXML(
4725    "SELECT
4726        *
4727     FROM
4728        album",
4729    array(
4730        'rootTag' => 'music_store',
4731        'rowTag' => 'album',
4732        'idColumn' => 'albumid',
4733        'elements' => array(
4734            'albumid',
4735            'title',
4736            '__time' => '#getTime()'
4737        )
4738    )
4739);
4740$dom->formatOutput = true;
4741print $dom->saveXML();
4742
4743function getTime()
4744{
4745    $dom = new DOMDocument();
4746    $unixtime = $dom->createElement('unixtime');
4747    $unixtime->appendChild($dom->createTextNode(time()));
4748
4749    $rfc2822date = $dom->createElement('rfc2822date');
4750    $rfc2822date->appendChild($dom->createTextNode(date('r')));
4751    return array($unixtime, $rfc2822date);
4752}
4753?>
4754    ]]>
4755    </programlisting>
4756    The surrounding &lt;time&gt; element is now gone and we have both tags &lt;unixtime&gt;
4757    and &lt;rfc2822date&gt;:
4758    <programlisting role="tutorial">
4759    <![CDATA[
4760<?xml version="1.0" encoding="UTF-8"?>
4761<music_store>
4762  <album>
4763    <albumid>1</albumid>
4764    <title>New World Order</title>
4765    <unixtime>1167169325</unixtime>
4766    <rfc2822date>Tue, 26 Dec 2006 22:42:05 +0100</rfc2822date>
4767  </album>
4768  <album>
4769    <albumid>2</albumid>
4770    <title>Curtis</title>
4771    <unixtime>1167169325</unixtime>
4772    <rfc2822date>Tue, 26 Dec 2006 22:42:05 +0100</rfc2822date>
4773  </album>
4774  <album>
4775    <albumid>3</albumid>
4776    <title>Shaft</title>
4777    <unixtime>1167169325</unixtime>
4778    <rfc2822date>Tue, 26 Dec 2006 22:42:05 +0100</rfc2822date>
4779  </album>
4780</music_store>
4781    ]]>
4782    </programlisting>
4783   </para>
4784  </refsect2>
4785 </refsect1>
4786
4787 <refsect1 id="{@id exception_handling}">
4788  <title>Exception Handling</title>
4789  <para>
4790   The public methods {@link XML_Query2XML::factory()}, {@link XML_Query2XML::getFlatXML()}
4791   and {@link XML_Query2XML::getXML()} all may throw exceptions. For production use you will
4792   have to implement the security principle "secure failure". This means that you will have to
4793   catch exceptions and deal with them. XML_Query2XML makes this task easy as all exceptions this
4794   package will ever throw extend {@link XML_Query2XML_Exception}. Therefore it is possible to catch all
4795   exceptions by catching {@link XML_Query2XML_Exception}:
4796  </para>
4797  <para>
4798   <programlisting role="php">
4799   <![CDATA[
4800<?php
4801require_once 'XML/Query2XML.php';
4802try {
4803    require_once 'MDB2.php';
4804    $query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
4805    $dom = $query2xml->getXML(
4806      "SELECT * FROM artist",
4807      array(
4808        'rootTag' => 'favorite_artist',
4809        'idColumn' => 'artistid',
4810        'rowTag' => 'artist',
4811        'elements' => array(
4812            'name',
4813            'birth_year',
4814            'genre'
4815        )
4816      )
4817    );
4818    echo $dom->saveXML();
4819} catch(XML_Query2XML_Exception $e) {
4820    /*
4821    * log this exceptions
4822    * display some error message that does not disclose sensitive information
4823    */
4824}
4825?>
4826   ]]>
4827   </programlisting>
4828  </para>
4829  <para>
4830   Here is a list of the exceptions the public methods of XML_Query2XML will throw:
4831   <itemizedlist>
4832    <listitem>
4833     {@link XML_Query2XML::factory()}: {@link XML_Query2XML_DBException} and {@link XML_Query2XML_ConfigException}
4834    </listitem>
4835    <listitem>
4836     {@link XML_Query2XML::getFlatXML()}: {@link XML_Query2XML_DBException} and
4837     {@link XML_Query2XML_XMLException}
4838    </listitem>
4839    <listitem>
4840     {@link XML_Query2XML::getXML()}: {@link XML_Query2XML_DBException},
4841     {@link XML_Query2XML_XMLException} and {@link XML_Query2XML_ConfigException}
4842    </listitem>
4843   </itemizedlist>
4844   As you can see, XML_Query2XML_Exception itself is never thrown.
4845  </para>
4846  <para>
4847   To treat different exceptions differently you would write code like this:
4848<programlisting role="php">
4849   <![CDATA[
4850<?php
4851require_once 'XML/Query2XML.php';
4852try {
4853    require_once 'MDB2.php';
4854    $query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
4855    $dom = $query2xml->getXML(
4856      "SELECT * FROM artist",
4857      array(
4858        'rootTag' => 'favorite_artist',
4859        'idColumn' => 'artistid',
4860        'rowTag' => 'artist',
4861        'elements' => array(
4862            'name',
4863            'birth_year',
4864            'genre'
4865        )
4866      )
4867    );
4868    echo $dom->saveXML();
4869} catch(XML_Query2XML_DBException $e) {
4870    //handle DB error
4871} catch(XML_Query2XML_XMLException $e) {
4872    //handle XML error
4873} catch(XML_Query2XML_Exception $e) {
4874    /*
4875    * Handle all other errors/exceptions; this will not only catch
4876    * XML_Query2XML_ConfigException but also all other exceptions that might be
4877    * added in future releases of XML_Query2XML.
4878    */
4879}
4880?>
4881   ]]>
4882   </programlisting>
4883   Bottom line: make sure you at least have a catch block for XML_Query2XML_Exception.
4884  </para>
4885 </refsect1>
4886
4887 <refsect1 id="{@id formatting}">
4888  <title>Output formatting</title>
4889  <para>
4890   Before calling the saveXML() method on your DOMDocument instance set its public property
4891   {@link http://at.php.net/manual/en/ref.dom.php#dom.class.domdocument.properties formatOutput} to true!
4892   Here goes an example:
4893   <programlisting role="php">
4894   <![CDATA[
4895<?php
4896require_once 'XML/Query2XML.php';
4897require_once 'MDB2.php';
4898$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
4899$dom = $query2xml->getXML(
4900  "SELECT * FROM artist",
4901  array(
4902    'rootTag' => 'favorite_artist',
4903    'idColumn' => 'artistid',
4904    'rowTag' => 'artist',
4905    'elements' => array(
4906        'name',
4907        'birth_year',
4908        'genre'
4909    )
4910  )
4911);
4912header('Content-Type: application/xml');
4913
4914$dom->formatOutput = true;
4915print $dom->saveXML();
4916?>
4917   ]]>
4918   </programlisting>
4919  </para>
4920  <para>
4921   Alternatively you could also use {@link http://pear.php.net/package/XML_Beautifier PEAR XML_Beautifier}.
4922   Here goes an example:
4923   <programlisting role="php">
4924   <![CDATA[
4925<?php
4926require_once 'XML/Query2XML.php';
4927require_once 'MDB2.php';
4928$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
4929$dom = $query2xml->getXML(
4930  "SELECT * FROM artist",
4931  array(
4932    'rootTag' => 'favorite_artist',
4933    'idColumn' => 'artistid',
4934    'rowTag' => 'artist',
4935    'elements' => array(
4936        'name',
4937        'birth_year',
4938        'genre'
4939    )
4940  )
4941);
4942header('Content-Type: application/xml');
4943print '<?xml version="1.0" encoding="UTF-8"?>' . "\n";
4944
4945require_once 'XML/Beautifier.php';
4946$beautifier = new XML_Beautifier();
4947print $beautifier->formatString($dom->saveXML());
4948?>
4949   ]]>
4950   </programlisting>
4951  </para>
4952 </refsect1>
4953
4954 <refsect1 id="{@id output_caching}">
4955  <title>Output Caching</title>
4956  <para>
4957   If your XML data is rather static in nature, i.e. exactely the same XML
4958   data is created over and over again you might want to use some kind of output caching.
4959   I will demonstrate the usage of {@link http://pear.php.net/package/Cache_Lite PEAR Cache_Lite} here:
4960  </para>
4961  <para>
4962   <programlisting role="php">
4963   <![CDATA[
4964<?php
4965require_once('Cache/Lite.php');
4966/*
4967* Set a id for this cache; if you generate the XML data based
4968* on values passed via POST/GET/COOKIE, include these values
4969* in $id. This does not need to be an integer; it's md5sum
4970* will be used.
4971*/
4972$id = '123';
4973
4974//set a few options
4975$options = array(
4976    'cacheDir' => "/tmp/",
4977    'lifeTime' => 3600
4978);
4979
4980//create a Cache_Lite object
4981$cache = new Cache_Lite($options);
4982
4983// content type for xml data
4984header('Content-Type: application/xml');
4985
4986//test if there is a valide cache for this id
4987if ($data = $cache->get($id)) {
4988    print $data;
4989} else {
4990    //no valid cache found
4991    require_once 'XML/Query2XML.php';
4992    require_once 'MDB2.php';
4993    $query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
4994    $dom = $query2xml->getXML(...);
4995    $dom->formatOutput = true;
4996
4997    //cache the XML data
4998    $data = $dom->saveXML();
4999    $cache->save($data, $id);
5000
5001    print $data;
5002}
5003?>
5004   ]]>
5005   </programlisting>
5006   For more information on PEAR Cache_Lite, please see the
5007   {@link http://pear.php.net/manual/en/package.caching.cache-lite.php Cache_Lite manual}.
5008  </para>
5009 </refsect1>
5010
5011 <refsect1 id="{@id encoding}">
5012  <title>XML encoding</title>
5013  <para>
5014   It is highly recommended to use UTF-8 for your XML data. But if you want
5015   to use another encoding standard you have to:
5016   <itemizedlist>
5017    <listitem>
5018     Use {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_encoder} to convert
5019     all node and attribute values to the desired encoding standard. If source and
5020     destination encoding standards are the same, you just have to set
5021     {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_encoder} to false.
5022    </listitem>
5023    <listitem>
5024     Set the encoding property of the DOMDocument instance returned by XML_Query2XML::getXML().
5025    </listitem>
5026   </itemizedlist>
5027  </para>
5028  <para>
5029    For some introduction to XML encoding please see
5030    {@link http://www.w3schools.com/xml/xml_encoding.asp} and
5031    {@link http://www.opentag.com/xfaq_enc.htm}.
5032    Note: I highly recommend to use UTF-8 for XML if you don't have a compelling reason
5033    to use an other encoding standard.
5034   </para>
5035  <para>
5036   Here goes an example that shows how to use ISO-8859-1 (Latin-1) for XML encoding. We
5037   will assume that your data is in ISO-8859-1 and therefore does not need any conversion.
5038   <programlisting role="php">
5039   <![CDATA[
5040<?php
5041require_once 'XML/Query2XML.php';
5042require_once 'MDB2.php';
5043$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
5044$dom = $query2xml->getXML(
5045    "SELECT
5046        *
5047     FROM
5048        artist",
5049    array(
5050        'rootTag' => 'music_library',
5051        'rowTag' => 'artist',
5052        'idColumn' => 'artistid',
5053        'encoder' => false,     // disable default ISO-8859-1 to UTF-8 encoding
5054        'elements' => array(
5055            'artistid',
5056            'name',
5057            'birth_year',
5058            'birth_place',
5059            'genre'
5060        )
5061    )
5062);
5063
5064header('Content-Type: application/xml');
5065$dom->formatOutput = true;
5066
5067$dom->encoding = 'iso-8859-1';  //setting XML encoding
5068
5069print $dom->saveXML();
5070?>
5071   ]]>
5072   </programlisting>
5073   This results in the following XML:
5074   <programlisting role="tutorial">
5075   <![CDATA[
5076<?xml version="1.0" encoding="iso-8859-1"?>
5077<music_library>
5078  <artist>
5079    <artistid>1</artistid>
5080    <name>Curtis Mayfield</name>
5081    <birth_year>1920</birth_year>
5082    <birth_place>Chicago</birth_place>
5083    <genre>Soul</genre>
5084  </artist>
5085  <artist>
5086    <artistid>2</artistid>
5087    <name>Isaac Hayes</name>
5088    <birth_year>1942</birth_year>
5089    <birth_place>Tennessee</birth_place>
5090    <genre>Soul</genre>
5091  </artist>
5092  <artist>
5093    <artistid>3</artistid>
5094    <name>Ray Charles</name>
5095    <birth_year>1930</birth_year>
5096    <birth_place>Mississippi</birth_place>
5097    <genre>Country and Soul</genre>
5098  </artist>
5099</music_library>
5100   ]]>
5101   </programlisting>
5102  </para>
5103 </refsect1>
5104
5105
5106 <refsect1 id="{@id binary_data}">
5107  <title>Handling Binary Data</title>
5108  <para>
5109   If you want to include binary data (e.g. JPEG data) in XML data you should encode your binary data
5110   to make sure that it does not include a sequence of bytes that represent the characters "&lt;/"
5111   or a null byte (which usually denotes the end of a string). The most common binary data
5112   encoding for XML is base64. The most straightforward way to do base64 encoding for
5113   an element or attribute value is to use the BASE64 ENCODING shortcut '^'.
5114  </para>
5115  <para>
5116   In the following example we will assume that the column album.comment contains binary data:
5117   <programlisting role="php">
5118   <![CDATA[
5119<?php
5120require_once 'XML/Query2XML.php';
5121require_once 'MDB2.php';
5122
5123$mdb2 = MDB2::factory('mysql://root@localhost/Query2XML_Tests');
5124$query2xml = XML_Query2XML::factory($mdb2);
5125
5126$dom = $query2xml->getXML(
5127    'SELECT * FROM album',
5128    array(
5129        'idColumn' => 'albumid',
5130        'rowTag' => 'album',
5131        'rootTag' => 'music_store',
5132        'elements' => array(
5133            'albumid',
5134            'title',
5135            'comment' => '^comment'
5136        )
5137    )
5138);
5139
5140header('Content-Type: application/xml');
5141$dom->formatOutput = true;
5142print $dom->saveXML();
5143?>
5144   ]]>
5145   </programlisting>
5146   The resulting XML data looks like this:
5147   <programlisting role="tutorial">
5148   <![CDATA[
5149<?xml version="1.0" encoding="UTF-8"?>
5150<music_store>
5151  <album>
5152    <albumid>1</albumid>
5153    <title>New World Order</title>
5154    <comment>dGhlIGJlc3QgZXZlciE=</comment>
5155  </album>
5156  <album>
5157    <albumid>2</albumid>
5158    <title>Curtis</title>
5159    <comment>dGhhdCBtYW4ncyBnb3Qgc29tdGhpbicgdG8gc2F5</comment>
5160  </album>
5161  <album>
5162    <albumid>3</albumid>
5163    <title>Shaft</title>
5164    <comment>aGUncyB0aGUgbWFu</comment>
5165  </album>
5166</music_store>
5167   ]]>
5168   </programlisting>
5169  </para>
5170 </refsect1>
5171
5172 <refsect1 id="{@id dumpdatabase}">
5173  <title>Using dynamic $options to dump all data of your database</title>
5174  <para>
5175   For some reason you might simply want to dump every table in your database
5176   with all their records. If you don't want to go over your code everytime a
5177   new table was added, you need to generate (parts of) the $options argument on
5178   the fly. Here is one way to do it:
5179   <programlisting role="php">
5180   <![CDATA[
5181<?php
5182require_once 'XML/Query2XML.php';
5183require_once 'MDB2.php';
5184
5185$mdb2 = MDB2::factory('mysql://root@localhost/Query2XML_Tests');
5186$query2xml = XML_Query2XML::factory($mdb2);
5187
5188
5189//we need MDB2's manager module to get the list of tables in a database independent way
5190$mdb2->loadModule('Manager');
5191$elements = array();
5192$mdb2->setOption('portability', MDB2_PORTABILITY_NONE);
5193$tables = $mdb2->listTables();
5194$mdb2->setOption('portability', MDB2_PORTABILITY_ALL);
5195for ($i = 0; $i < count($tables); $i++) {
5196    $elements['table' . $i] = array(
5197        'rowTag' => 'table',
5198        'attributes' => array(
5199            'name' => ':' . $tables[$i]
5200        ),
5201        'elements' => array(
5202            'record' => array(
5203                'idColumn' => false,
5204                'sql' => 'SELECT * FROM ' . $tables[$i],
5205                'elements' => array(
5206                    '*'
5207                )
5208            )
5209        )
5210    );
5211}
5212
5213$dom = $query2xml->getXML(
5214    false,
5215    array(
5216        'idColumn' => false,
5217        'rowTag' => '__tables',
5218        'rootTag' => 'database',
5219        'elements' => $elements
5220    )
5221);
5222
5223header('Content-Type: application/xml');
5224$dom->formatOutput = true;
5225print $dom->saveXML();
5226?>
5227   ]]>
5228   </programlisting>
5229  </para>
5230  <para>
5231   Notice how we used MDB2's manager module to get a list of all tables in our database.
5232   We then loop over the names and create
5233   {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements}
5234   at run time.
5235  </para>
5236  <para>
5237   Side note: unfortunately MDB2's portability feature per default makes listTabes() return all table names lower-cased.
5238   To circumvent this we have to temporarily change the portability option. See {@link http://pear.php.net/bugs/bug.php?id=11215}
5239   which describes the issue in greater detail.
5240  </para>
5241  <para>
5242   Each table tag will have an attribute "name". We use the ':' prefix to indicate that
5243   what follows is a static text not to be interpreted as a column name.
5244  </para>
5245  <para>
5246   When looking at the getXML() call, you'll notice that we didn't pass a query as the
5247   first argument ({@tutorial XML_Query2XML.pkg#query2xml_getxml.sql}) but rather a boolean
5248   value of false. As documented at {@tutorial XML_Query2XML.pkg#query2xml_getxml.sql}
5249   this will make XML_Query2XML behave as if we used a query that returned a single record
5250   with no columns. This is necessary because in our example we use multiple unrelated queries
5251   that we simply want to palce inside a database tag.
5252  </para>
5253  <para>
5254   Also notice that we use the boolean value of FALSE for
5255   {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn}
5256   at the root level and for each of the
5257   tables. It is OK to do so on the root level because we are actually only dealing with a
5258   single fake record there. To use FALSE for {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn}
5259   on the sub-levels within the $elements array
5260   generated at run-time is also OK because we will always want all records and (which is
5261   the reason why we are not violating best practices) we simply don't know the primary key
5262   columns for all tables that might get created in the future.
5263  </para>
5264  <para>
5265   Also note that we used '__tables' for {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_rowtag}
5266   at the root level: this is because we don't have anything to loop over at the root level - remember
5267   using false for {@tutorial XML_Query2XML.pkg#query2xml_getxml.sql} is like using a
5268   query that returns a single record with no columns.
5269  </para>
5270 </refsect1>
5271
5272 <refsect1 id="{@id without_prefixes}">
5273  <title>Working without Shortcuts and Prefixes</title>
5274  <para>
5275   You think having to deal with all these different prefixes (e.g. #, ?, :, &amp;, ^ and =) when defining
5276   {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_value} and
5277   {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}
5278   just makes things more complicated or your code less readable? In that case, just don't use them! Below is
5279   way of getting almost the same functionality with a command object pattern implementation. If you have
5280   to deal with some 10,000+ records in your XML, using command objects might even improve performance
5281   a little bit (~1-3%).
5282  </para>
5283  <para>
5284   The following example first provides command classes that implement the functionality
5285   of the currently available prefixes.
5286   <programlisting role="php">
5287   <![CDATA[
5288<?php
5289/**All command objects have to implement the interface XML_Query2XML_Callback.
5290*/
5291require_once 'XML/Query2XML/Callback.php';
5292
5293/**All command classes that work on a single column value will extend this abstract class.
5294* Classes extending this class will have to implement the execute(array $record) method
5295* defined by the XML_Query2XML_Callback interface.
5296*/
5297abstract class Callback_SingleColumn implements XML_Query2XML_Callback
5298{
5299    /**The column name
5300    * @var string
5301    */
5302    protected $_column = '';
5303
5304    /**Constructor
5305    * @param string $column The name of the column this instance will work on.
5306    */
5307    public function __construct($column)
5308    {
5309        $this->_column = $column;
5310    }
5311
5312    /**Get the value of the column passed to the constructor.
5313    * @param array $record An associative array as it will be passed
5314    *                      to the execute(array $record) method.
5315    * @throws XML_Query2XML_Exception If the column name passed
5316    *                      to the constructor was not found in $record.
5317    */
5318    protected function _getColumnValue(array $record)
5319    {
5320        if (array_key_exists($this->_column, $record)) {
5321            return $record[$this->_column];
5322        }
5323        throw new XML_Query2XML_Exception(
5324            'Column ' . $this->_column . ' was not found in the result set'
5325        );
5326    }
5327}
5328
5329/**Use an instance of this class to get the base64 encoded value of a column.
5330*/
5331class Callback_Base64 extends Callback_SingleColumn
5332{
5333    /**Called by XML_Query2XML for every record.
5334    * @param array $record An associative array.
5335    * @return string
5336    */
5337    public function execute(array $record)
5338    {
5339        return base64_encode($this->_getColumnValue($record));
5340    }
5341}
5342
5343/**Use an instance of this class to unserialize XML data stored in a column.
5344*/
5345class Callback_Unserialization extends Callback_SingleColumn
5346{
5347    /**Called by XML_Query2XML for every record.
5348    * @param array $record An associative array.
5349    * @return DOMElement
5350    * @throws XML_Query2XML_XMLException If unserialization fails.
5351    */
5352    public function execute(array $record)
5353    {
5354        $doc = new DOMDocument();
5355        $xml = $this->_getColumnValue($record);
5356        if (!@$doc->loadXML($xml)) {
5357            throw new XML_Query2XML_XMLException(
5358                'Could not unserialize the following XML data: '
5359                . $xml
5360            );
5361        }
5362        return $doc->documentElement;
5363    }
5364}
5365
5366/**Use an instance of this class to place a CDATA section around the value of a column.
5367*/
5368class Callback_CDATA extends Callback_SingleColumn
5369{
5370    /**Called by XML_Query2XML for every record.
5371    * @param array $record An associative array.
5372    * @return DOMCDATASection
5373    */
5374    public function execute(array $record)
5375    {
5376        $doc = new DOMDocument();
5377        return $doc->createCDATASection($this->_getColumnValue($record));
5378    }
5379}
5380
5381/**Use an instance of this class to return a static data.
5382*/
5383class Callback_StaticData implements XML_Query2XML_Callback
5384{
5385    /**The static data
5386    * @var mixed
5387    */
5388    private $_data = null;
5389
5390    /**Constructor
5391    * @param mixed $data The static date to return for every record.
5392    */
5393    public function __construct($data)
5394    {
5395        $this->_data = $data;
5396    }
5397
5398    /**Called by XML_Query2XML for every record.
5399    * This method will always return the same data, no matter what
5400    * is passed as $record.
5401    *
5402    * @param array $record An associative array.
5403    * @return mixed
5404    */
5405    public function execute(array $record)
5406    {
5407        return $this->_data;
5408    }
5409}
5410
5411require_once 'XML/Query2XML.php';
5412require_once 'MDB2.php';
5413
5414$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
5415
5416$xml = $query2xml->getXML(
5417    'SELECT * FROM store',
5418    array(
5419        'rootTag' => 'stores',
5420        'rowTag' => 'store',
5421        'idColumn' => 'storeid',
5422        'elements' => array(
5423            'base64' => new Callback_Base64('building_xmldata'),
5424            'cdata' => new Callback_CDATA('building_xmldata'),
5425            'static' => new Callback_StaticData('my static data'),
5426            'unserialized' => new Callback_Unserialization('building_xmldata')
5427        )
5428    )
5429);
5430
5431$xml->formatOutput = true;
5432print $xml->saveXML();
5433?>
5434    ]]>
5435    </programlisting>
5436    Note that the above code is equivalent to
5437    <programlisting role="php">
5438    <![CDATA[
5439<?php
5440require_once 'XML/Query2XML.php';
5441require_once 'MDB2.php';
5442
5443$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
5444
5445$xml = $query2xml->getXML(
5446    'SELECT * FROM store',
5447    array(
5448        'rootTag' => 'stores',
5449        'rowTag' => 'store',
5450        'idColumn' => 'storeid',
5451        'elements' => array(
5452            'base64' => '?^building_xmldata',
5453            'cdata' => '=building_xmldata',
5454            'static' => ':my static data',
5455            'unserialized' => '&building_xmldata'
5456        )
5457    )
5458);
5459
5460$xml->formatOutput = true;
5461print $xml->saveXML();
5462?>
5463    ]]>
5464    </programlisting>
5465    Both produce the following XML data:
5466    <programlisting role="tutorial">
5467    <![CDATA[
5468<?xml version="1.0" encoding="UTF-8"?>
5469<stores>
5470  <store>
5471    <base64>PGJ1aWxkaW5nPjxmbG9vcnM+NDwvZmxvb3JzPjxlbGV2YXRvcnM+MjwvZWxldmF0b3JzPjxzcXVhcmVfbWV0ZXJzPjMyMDA8L3NxdWFyZV9tZXRlcnM+PC9idWlsZGluZz4=</base64>
5472    <cdata>< ![CDATA[<building><floors>4</floors><elevators>2</elevators><square_meters>3200</square_meters></building>]] ></cdata>
5473    <static>my static data</static>
5474    <unserialized>
5475      <building>
5476        <floors>4</floors>
5477        <elevators>2</elevators>
5478        <square_meters>3200</square_meters>
5479      </building>
5480    </unserialized>
5481  </store>
5482  <store>
5483    <base64>PGJ1aWxkaW5nPjxmbG9vcnM+MjwvZmxvb3JzPjxlbGV2YXRvcnM+MTwvZWxldmF0b3JzPjxzcXVhcmVfbWV0ZXJzPjQwMDwvc3F1YXJlX21ldGVycz48L2J1aWxkaW5nPg==</base64>
5484    <cdata>< ![CDATA[<building><floors>2</floors><elevators>1</elevators><square_meters>400</square_meters></building>]] ></cdata>
5485    <static>my static data</static>
5486    <unserialized>
5487      <building>
5488        <floors>2</floors>
5489        <elevators>1</elevators>
5490        <square_meters>400</square_meters>
5491      </building>
5492    </unserialized>
5493  </store>
5494</stores>
5495    ]]>
5496    </programlisting>
5497  </para>
5498  <para>
5499   Finally, note that you may also explecitely unregister a specific prefix using {@link XML_Query2XML::unregisterPrefix()}
5500   are unregister all previously defined prefixes using {@link XML_Query2XML::unregisterAllPrefixes()}.
5501  </para>
5502 </refsect1>
5503
5504 <refsect1 id="{@id your_own_prefixes}">
5505  <title>Defining your own Prefixes</title>
5506  <para>
5507   You may want to define your own prefixes in order to have a simple and quick way to invoke complex functionality you need often.
5508   The registration of the prefix is very much straight forward:
5509  </para>
5510  <para>
5511   {@link XML_Query2XML::registerPrefix() XML_Query2XML::registerPrefix}($prefix, $className, $filePath = '')
5512  </para>
5513  <para>
5514   Note that you may also register a prefix that has been registered before (or is registered by default).
5515   In that case, the new registration will simply overwrite the old one.
5516  </para>
5517  <para>
5518   More difficult than simply registering the prefix, is writing the class that will implement its functionality.
5519   All such classes will have to implement the interface {@link XML_Query2XML_Data}. However, in practice you will
5520   want to extend one of the following abstract classes, depending on the type of functionality you need:
5521   <itemizedlist>
5522    <listitem>
5523     <emphasis>{@link XML_Query2XML_Data_Processor}</emphasis>: extend this class if you want to use any data
5524      as input other than the string specified right after the prefix. For example, if you want to use a column
5525      value as input data, extend this class and let the built-in code handle the rest. (Note: the
5526      BASE64 ENCODING prefix ^, CDATA SECTION prefix =, and the XML UNSERIALIZATION prefix &amp; are built using
5527      this class.)
5528    </listitem>
5529    <listitem>
5530     <emphasis>{@link XML_Query2XML_Data_Source}</emphasis>: extend this class if you only want to use the string
5531     specified right after the prefix as your data source. (Note: the STATIC TEXT prefix : and the CALLBACK FUNCTION
5532     prefix # are built using this class.)
5533    </listitem>
5534    <listitem>
5535     <emphasis>{@link XML_Query2XML_Data_Condition}</emphasis>: extend this class if you want to implement some
5536     kind of condition that will determine whether the XML element for which the value was to be used will be
5537     included in the resulting output. (Note: the CONDITIONAL prefix ? is built using this class.)
5538    </listitem>
5539   </itemizedlist>
5540  </para>
5541  <para>
5542   Now, let's suppose you want to use the prefix "§" for your command class Year2UnixTime which calculates
5543   the Unix time (seconds since the Unix Epoch, January 1 1970 00:00:00 GMT) from a given year. In such a
5544   case you clearly want to extend {@link XML_Query2XML_Data_Processor}:
5545   <programlisting role="php">
5546   <![CDATA[
5547<?php
5548require_once 'XML/Query2XML.php';
5549require_once 'XML/Query2XML/Data/Processor.php';
5550require_once 'MDB2.php';
5551
5552class Year2UnixTime extends XML_Query2XML_Data_Processor
5553{
5554    /**
5555     * Create a new instance of this class.
5556     *
5557     * @param mixed  $preProcessor The pre-processor to be used. An instance of
5558     *                             XML_Query2XML_Data or null.
5559     * @param string $configPath   The configuration path within the $options
5560     *                             array.
5561     *
5562     * @return XML_Query2XML_Data_Processor_Base64
5563     */
5564    public function create($preProcessor, $configPath)
5565    {
5566        $processor = new Year2UnixTime($preProcessor);
5567        // The create() method of every class extending
5568        // XML_Query2XML_Data_Processor has to call
5569        // setConfigPath() manually!
5570        $processor->setConfigPath($configPath);
5571        return $processor;
5572    }
5573
5574    /**
5575     * Called by XML_Query2XML for every record in the result set.
5576     *
5577     * @param array $record An associative array.
5578     *
5579     * @return string The base64-encoded version the string returned
5580     *                by the pre-processor.
5581     * @throws XML_Query2XML_ConfigException If the pre-processor returns
5582     *                something that cannot be converted to a string
5583     *                (i.e. an object or an array).
5584     */
5585    public function execute(array $record)
5586    {
5587        $data = $this->runPreProcessor($record);
5588        if (is_array($data) || is_object($data)) {
5589            throw new XML_Query2XML_ConfigException(
5590                $this->getConfigPath()
5591                . ': XML_Query2XML_Data_Processor_Base64: string '
5592                . 'expected from pre-processor, but ' . gettype($data) . ' returned.'
5593            );
5594        }
5595        return DateTime::createFromFormat('Y-m-d H:i:s', $data . '-01-01 00:00:00', new DateTimeZone('Etc/GMT+0'))->format('U');
5596    }
5597}
5598
5599$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
5600$query2xml->registerPrefix('§', 'Year2UnixTime');
5601$dom = $query2xml->getXML(
5602  "SELECT * FROM artist",
5603  array(
5604    'rootTag' => 'artists',
5605    'idColumn' => 'artistid',
5606    'rowTag' => 'artist',
5607    'elements' => array(
5608        'name',
5609        'birth_year',
5610        'birth_year_in_unix_time' => '§birth_year'
5611    )
5612  )
5613);
5614
5615header('Content-Type: application/xml');
5616$dom->formatOutput = true;
5617print $dom->saveXML();
5618?>
5619    ]]>
5620    </programlisting>
5621    The resulting XML data looks like this:
5622    <programlisting role="tutorial">
5623    <![CDATA[
5624<?xml version="1.0" encoding="UTF-8"?>
5625<artists>
5626  <artist>
5627    <name>Curtis Mayfield</name>
5628    <birth_year>1920</birth_year>
5629    <birth_year_in_unix_time>-1577923200</birth_year_in_unix_time>
5630  </artist>
5631  <artist>
5632    <name>Isaac Hayes</name>
5633    <birth_year>1942</birth_year>
5634    <birth_year_in_unix_time>-883612800</birth_year_in_unix_time>
5635  </artist>
5636  <artist>
5637    <name>Ray Charles</name>
5638    <birth_year>1930</birth_year>
5639    <birth_year_in_unix_time>-1262304000</birth_year_in_unix_time>
5640  </artist>
5641</artists>
5642   ]]>
5643   </programlisting>
5644  </para>
5645 </refsect1>
5646
5647 <refsect1 id="{@id global_options}">
5648  <title>Global Options</title>
5649  <para>
5650   Global options can be set using the public method
5651   {@tutorial XML_Query2XML.pkg#global_options.setglobaloption} and retrieved using
5652   {@tutorial XML_Query2XML.pkg#global_options.getglobaloption}. Currently the
5653   following global options are available:
5654   <itemizedlist>
5655    <listitem>
5656     {@tutorial XML_Query2XML.pkg#global_options.hidden_container_prefix}
5657    </listitem>
5658   </itemizedlist>
5659  </para>
5660  <refsect2 id="{@id setglobaloption}">
5661   <title>XML_Query2XML::setGlobalOption()</title>
5662   <para>
5663    {@link XML_Query2XML::setGlobalOption() XML_Query2XML::setGlobalOption}($option, $value)
5664   </para>
5665   <para>
5666    Currently there is only one global option: {@tutorial XML_Query2XML.pkg#global_options.hidden_container_prefix}
5667    which has to be set to a non empty string. If you try to set a non existing global option or
5668    try to set an existing one to an invalid value a {@link XML_Query2XML_ConfigException}
5669    will be thrown.
5670   </para>
5671   <para>
5672    Here goes an example:
5673    <programlisting role="php">
5674    <![CDATA[
5675$query2xml =& XML_Query2XML::factory($db);
5676$query2xml->setGlobalOption('hidden_container_prefix', '___');
5677    ]]>
5678    </programlisting>
5679   </para>
5680  </refsect2>
5681  <refsect2 id="{@id getglobaloption}">
5682   <title>XML_Query2XML::getGlobalOption()</title>
5683   <para>
5684    {@link XML_Query2XML::getGlobalOption() XML_Query2XML::getGlobalOption}($option)
5685   </para>
5686   <para>
5687    Currently there is only one global option: {@tutorial XML_Query2XML.pkg#global_options.hidden_container_prefix}.
5688    Use getGlobalOption() to retrieve a global option's current value:
5689    <programlisting role="php">
5690    <![CDATA[
5691$query2xml =& XML_Query2XML::factory($db);
5692echo $query2xml->getGlobalOption('hidden_container_prefix');
5693    ]]>
5694    </programlisting>
5695    If you try to retrieve the value of a non existing global option a
5696    {@link XML_Query2XML_ConfigException} will be thrown.
5697   </para>
5698  </refsect2>
5699  <refsect2 id="{@id hidden_container_prefix}">
5700   <title>hidden_container_prefix</title>
5701   <para>
5702    All elements whose name start with the string specified in the
5703    hidden_container_prefix option are stripped from the DOMDocument that is finally
5704    returned by {@tutorial XML_Query2XML.pkg#query2xml_getxml}. The container's child
5705    elements are effectively replacing their parent. The default value of the
5706    hidden_container_prefix option is '__'.
5707    Here is an example using the default:
5708    <programlisting role="php">
5709    <![CDATA[
5710<?php
5711require_once 'XML/Query2XML.php';
5712require_once 'MDB2.php';
5713$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
5714$dom =& $query2xml->getXML(
5715    "SELECT
5716        *
5717     FROM
5718        album",
5719    array(
5720        'rootTag' => 'music_store',
5721        'rowTag' => 'album',
5722        'idColumn' => 'albumid',
5723        'elements' => array(
5724            'albumid',
5725            'title',
5726            '__info' => '&:<name>John Doe</name>'
5727        )
5728    )
5729);
5730$dom->formatOutput = true;
5731print $dom->saveXML();
5732?>
5733    ]]>
5734    </programlisting>
5735    Instead of
5736    <programlisting role="php">
5737    <![CDATA[
5738'__info' => '&:<name>John Doe</name>'
5739    ]]>
5740    </programlisting>
5741    we also could have written
5742    <programlisting role="php">
5743    <![CDATA[
5744'name' => ':John Doe'
5745    ]]>
5746    </programlisting>
5747    Both versions produce the same XML data. Note how the contents of the &lt;__info&gt;
5748    element effectively replaced the &lt;__info&gt; tag itself. It results in the &lt;name&gt; element
5749    being directly placed inside the &lt;album&gt; element.
5750    <programlisting role="tutorial">
5751    <![CDATA[
5752<?xml version="1.0" encoding="UTF-8"?>
5753<music_store>
5754  <album>
5755    <albumid>1</albumid>
5756    <title>New World Order</title>
5757    <name>John Doe</name>
5758  </album>
5759  <album>
5760    <albumid>2</albumid>
5761    <title>Curtis</title>
5762    <name>John Doe</name>
5763  </album>
5764  <album>
5765    <albumid>3</albumid>
5766    <title>Shaft</title>
5767    <name>John Doe</name>
5768  </album>
5769</music_store>
5770    ]]>
5771    </programlisting>
5772   </para>
5773   <para>
5774    Now imagine that we actually do want an XML element to be named '__info'. This
5775    means that we have to change the hidden_container_prefix option:
5776    <programlisting role="php">
5777    <![CDATA[
5778<?php
5779require_once 'XML/Query2XML.php';
5780require_once 'MDB2.php';
5781$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
5782$query2xml->setGlobalOption('hidden_container_prefix', '___');
5783$dom =& $query2xml->getXML(
5784    "SELECT
5785        *
5786     FROM
5787        album",
5788    array(
5789        'rootTag' => 'music_store',
5790        'rowTag' => 'album',
5791        'idColumn' => 'albumid',
5792        'elements' => array(
5793            'albumid',
5794            'title',
5795            '__info' => '&:<name>John Doe</name>', //will not be hidden
5796            '___info' => '&:<name>John Doe</name>' //will be hidden
5797        )
5798    )
5799);
5800$dom->formatOutput = true;
5801print $dom->saveXML();
5802?>
5803    ]]>
5804    </programlisting>
5805    This produces the following XML data:
5806    <programlisting role="tutorial">
5807    <![CDATA[
5808<?xml version="1.0" encoding="UTF-8"?>
5809<music_store>
5810  <album>
5811    <albumid>1</albumid>
5812    <title>New World Order</title>
5813    <__info>
5814      <name>John Doe</name>
5815    </__info>
5816    <name>John Doe</name>
5817  </album>
5818  <album>
5819    <albumid>2</albumid>
5820    <title>Curtis</title>
5821    <__info>
5822      <name>John Doe</name>
5823    </__info>
5824    <name>John Doe</name>
5825  </album>
5826  <album>
5827    <albumid>3</albumid>
5828    <title>Shaft</title>
5829    <__info>
5830      <name>John Doe</name>
5831    </__info>
5832    <name>John Doe</name>
5833  </album>
5834</music_store>
5835    ]]>
5836    </programlisting>
5837   </para>
5838  </refsect2>
5839 </refsect1>
5840 <refsect1 id="{@id debugging}">
5841  <title>Logging and Debugging XML_Query2XML</title>
5842  <para>
5843  	If you need to debug your XML_Query2XML or an application using it, use {@link XML_Query2XML::enableDebugLog()}
5844  	and {@link XML_Query2XML::disableDebugLog()}. It is recommended to use
5845  	{@link http://pear.php.net/package/Log PEAR Log}.
5846  </para>
5847  <para>
5848  	The following information is logged:
5849     <itemizedlist>
5850      <listitem>
5851       the beginning of the execution of a SQL query in the database:
5852       this includes the SQL statement itself followed by the values used
5853       when executing a prepared statement.
5854      </listitem>
5855      <listitem>
5856       the end of the execution of a SQL query in the database
5857      </listitem>
5858      <listitem>
5859       caching of a query's result
5860      </listitem>
5861      <listitem>
5862       retrieving previously cached data
5863      </listitem>
5864     </itemizedlist>
5865     When using PEAR::Log, the date, time, a custom string and '[info]' will preceed every
5866     entry.
5867  </para>
5868  <para>
5869   Here is how it's done:
5870   <programlisting role="php">
5871   <![CDATA[
5872<?php
5873require_once 'XML/Query2XML.php';
5874$query2xml = XML_Query2XML::factory($db);
5875
5876//create a new instance of PEAR::Log
5877require_once 'Log.php';
5878$debugLogger = Log::factory('file', 'debug.log', 'XML_Query2XML');
5879
5880//start debugging
5881$query2xml->enableDebugLog($debugLogger);
5882
5883$dom = $query2xml->getXML(
5884    "SELECT
5885        *
5886     FROM
5887        artist
5888     ORDER BY
5889        artistid",
5890    array(
5891        'rootTag' => 'music_library',
5892        'rowTag' => 'artist',
5893        'idColumn' => 'artistid',
5894        'elements' => array(
5895            'name',
5896            'albums' => array(
5897                'sql' => array(
5898                    'data' => array(
5899                        'artistid'
5900                    ),
5901                    'query' => 'SELECT * FROM album WHERE artist_id = ?'
5902                ),
5903                'rowTag' => 'album',
5904                'idColumn' => 'albumid',
5905                'elements' => array(
5906                    'title'
5907                )
5908            )
5909        )
5910    )
5911);
5912print $dom->saveXML();
5913?>
5914   ]]>
5915   </programlisting>
5916   This will write the following to debug.log:
5917   <![CDATA[
5918Apr 18 20:54:30 XML_Query2XML [info] QUERY: SELECT
5919        *
5920     FROM
5921        artist
5922     ORDER BY
5923        artistid
5924Apr 18 20:54:30 XML_Query2XML [info] DONE
5925Apr 18 20:54:30 XML_Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:1
5926Apr 18 20:54:30 XML_Query2XML [info] DONE
5927Apr 18 20:54:30 XML_Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:2
5928Apr 18 20:54:30 XML_Query2XML [info] DONE
5929Apr 18 20:54:30 XML_Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:3
5930Apr 18 20:54:30 XML_Query2XML [info] DONE
5931   ]]>
5932   XML_Query2XML simply logs the SQL SELECT string the way it was passed to getXML(), including all
5933   whitespace characters. If the query in question is a complex join over multiple tables this
5934   might be a good thing. On the other hand you might just want to have a single line per log entry.
5935   This is best achieved by building a wrapper around PEAR Log:
5936   <programlisting role="php">
5937   <![CDATA[
5938class MyLogger {
5939    private $_logger = null;
5940    public function __construct($logger)
5941    {
5942        $this->_logger = $logger;
5943    }
5944
5945    public function log($msg)
5946    {
5947        $this->_logger->log(preg_replace('/\n\ +/m', ' ', $msg));
5948    }
5949}
5950   ]]>
5951   </programlisting>
5952   Using this wrapper like this:
5953   <programlisting role="php">
5954   <![CDATA[
5955$query2xml->enableDebugLog(
5956    new MyLogger(
5957        Log::factory('file', 'debug.log', 'XML_Query2XML')
5958    )
5959);
5960   ]]>
5961   </programlisting>
5962   will make the log file from the prvious example look like this:
5963   <![CDATA[
5964Apr 18 20:55:51 XML_Query2XML [info] QUERY: SELECT * FROM artist ORDER BY artistid
5965Apr 18 20:55:51 XML_Query2XML [info] DONE
5966Apr 18 20:55:51 XML_Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:1
5967Apr 18 20:55:51 XML_Query2XML [info] DONE
5968Apr 18 20:55:51 XML_Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:2
5969Apr 18 20:55:51 XML_Query2XML [info] DONE
5970Apr 18 20:55:51 XML_Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:3
5971Apr 18 20:55:51 XML_Query2XML [info] DONE
5972   ]]>
5973   Note how the first query is now placed on a single line.
5974  </para>
5975
5976  <refsect2 id="{@id enabledebuglog}">
5977   <title>XML_Query2XML::enableDebugLog()</title>
5978   <para>
5979    Please see the API docs at {@link XML_Query2XML::enableDebugLog()}.
5980   </para>
5981  </refsect2>
5982
5983  <refsect2 id="{@id disabledebuglog}">
5984   <title>XML_Query2XML::disableDebugLog()</title>
5985   <para>
5986    Please see the API docs at {@link XML_Query2XML::disableDebugLog()}.
5987   </para>
5988  </refsect2>
5989
5990 </refsect1>
5991
5992 <refsect1 id="{@id profiling}">
5993  <title>Profiling and Performance Tuning</title>
5994  <para>
5995  	When the amount of data you have to deal with is getting bigger and bigger you will start to ask yourself
5996  	questions like:
5997  	<itemizedlist>
5998     <listitem>
5999  	  Are two smaller joins faster than a single huge one?
6000  	 </listitem>
6001  	 <listitem>
6002  	  Should I use {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql} with or without caching?
6003  	 </listitem>
6004  	 <listitem>
6005  	  How often does a certain query get executed and how long does it take?
6006  	 </listitem>
6007  	</itemizedlist>
6008  	XML_Query2XML's profiling provides help on giving answers to these questions.
6009  </para>
6010
6011  <refsect2 id="{@id example}">
6012   <title>Example</title>
6013   <para>
6014     {@tutorial XML_Query2XML.pkg#profiling.startprofiling} should be the first and
6015     {@tutorial XML_Query2XML.pkg#profiling.getprofile} the last method you call on your XML_Query2XML instance.
6016     <programlisting role="php">
6017     <![CDATA[
6018<?php
6019require_once 'XML/Query2XML.php';
6020$query2xml = XML_Query2XML::factory($db);
6021
6022//start the profiling as soon as possible
6023$query2xml->startProfiling();
6024
6025//do the real work
6026$dom = $query2xml->getXML(...);
6027print $dom->saveXML();
6028
6029//save the profile in a separate file
6030require_once 'File.php';
6031$fp = new File();
6032$fp->write('/tmp/query2xml_profile.txt', $query2xml->getProfile(), FILE_MODE_WRITE);
6033?>
6034     ]]>
6035     </programlisting>
6036   </para>
6037  </refsect2>
6038
6039  <refsect2 id="{@id startprofiling}">
6040   <title>XML_Query2XML::startProfiling()</title>
6041   <para>
6042     {@link XML_Query2XML::startProfiling()} will start the profiling by
6043     initializing the private variable {@link XML_Query2XML::$_profile}. See
6044     {@tutorial XML_Query2XML.pkg#profiling.getrawprofile} for details on its data format.
6045   </para>
6046  </refsect2>
6047
6048  <refsect2 id="{@id stopprofiling}">
6049   <title>XML_Query2XML::stopProfiling()</title>
6050   <para>
6051     {@link XML_Query2XML::stopProfiling()} will stop the profiling. In most cases you will not
6052     need to call this method as {@tutorial XML_Query2XML.pkg#profiling.getprofile} will do so implicitly.
6053   </para>
6054  </refsect2>
6055
6056  <refsect2 id="{@id getprofile}">
6057   <title>XML_Query2XML::getProfile()</title>
6058   <para>
6059     {@link XML_Query2XML::getProfile()} will return the profile as a multiline string.
6060     It is a table with the following columns:
6061     <itemizedlist>
6062      <listitem>
6063       FROM_DB: number of times this type of query executed in the database
6064      </listitem>
6065      <listitem>
6066       FROM_CACHE: number of times the results could be retrieved from cache
6067      </listitem>
6068      <listitem>
6069       CACHED: whether caching was performed (true or false); if caching was performed but FROM_CACHE
6070       is 0, the value will be "true!" to indicate that no caching is necessary
6071      </listitem>
6072      <listitem>
6073       AVG_DURATION: average duration of executing the query and getting it's results
6074      </listitem>
6075      <listitem>
6076       DURATION_SUM: total duration for all queries of this type
6077      </listitem>
6078      <listitem>
6079       SQL: the query itself
6080      </listitem>
6081     </itemizedlist>
6082     Additionally there will be a summary at the end of the file. It will contain two fields:
6083     <itemizedlist>
6084      <listitem>
6085       TOTAL_DURATION: number of seconds the whole operation took (including outputting everything).
6086       Whether you output the generated XML data will only affect TOTAL_DURATION but not DB_DURATION.
6087      </listitem>
6088      <listitem>
6089       DB_DURATION: number of seconds spent executing SQL queries and retrieving their results.
6090      </listitem>
6091     </itemizedlist>
6092   </para>
6093  </refsect2>
6094
6095  <refsect2 id="{@id getrawprofile}">
6096   <title>XML_Query2XML::getRawProfile()</title>
6097   <para>
6098     {@link XML_Query2XML::getRawProfile()} will return the raw profile data as a multi dimensional associative array.
6099     It has the following format:
6100
6101     <programlisting role="php">
6102     <![CDATA[
6103$this->_profile = array(
6104    'queries'    => array(),
6105    'start'      => microtime(1),
6106    'stop'       => 0,
6107    'duration'   => 0,
6108    'dbStop'     => 0,
6109    'dbDuration' => 0
6110);
6111     ]]>
6112     </programlisting>
6113     The element 'queries' is itself an associative array that
6114     uses $sql as the array key;:
6115     <programlisting role="php">
6116     <![CDATA[
6117$this->_profile['queries'][$sql] = array(
6118    'fromDB' => 0,
6119    'fromCache' => 0,
6120    'cached' => false,
6121    'runTimes' => array()
6122);
6123     ]]>
6124     </programlisting>
6125     The element 'runTimes' is an indexed array that stores multiple
6126     arrays that have the following format:
6127     <programlisting role="php">
6128     <![CDATA[
6129array('start' => microtime(true), 'stop' => 0);
6130     ]]>
6131     </programlisting>
6132   </para>
6133  </refsect2>
6134
6135  <refsect2 id="{@id clearprofile}">
6136   <title>XML_Query2XML::clearProfile()</title>
6137   <para>
6138    Please see the API docs at {@link XML_Query2XML::clearProfile()}.
6139   </para>
6140  </refsect2>
6141 </refsect1>
6142
6143 <refsect1 id="{@id ldap}">
6144  <title>The LDAP Driver</title>
6145  <para>
6146   Since v1.6.0RC1 XML_Query2XML comes with a driver for {@link http://pear.php.net/package/Net_LDAP PEAR Net_LDAP}.
6147   The driver for {@link http://pear.php.net/package/Net_LDAP2 PEAR Net_LDAP2} is available since v1.7.0RC1.
6148   This allows you to use LDAP instead of an RDBMS as your primary data source. To use the PEAR Net_LDAP
6149   driver you can pass an instance of Net_LDAP to {@tutorial XML_Query2XML.pkg#query2xml_factory}. The only
6150   thing that changes is the format of {@tutorial XML_Query2XML.pkg#query2xml_getxml.sql} and
6151   {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql}.
6152  </para>
6153  <refsect2 id="{@id queryspecification}">
6154   <title>LDAP Query Specification</title>
6155   <para>
6156    All LDAP queries have to be specified as an array.
6157    LDAP query specifications can be used
6158    for {@tutorial XML_Query2XML.pkg#query2xml_getxml.sql} and
6159    {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql}. They look as follows:
6160    <programlisting role="php">
6161    <![CDATA[
6162array(
6163    'data' => array(...),  // optional; only relevant if placeholders are used in 'base' or 'filter'
6164    'base' => 'ou=peopole,dc=example,dc=com',
6165    'filter' => '(objectclass=inetOrgPerson)',
6166    'options' => array(
6167        'attributes' => array(
6168            ...
6169        ), ...
6170    )
6171)
6172    ]]>
6173    </programlisting>
6174    These are the arguments as they will be passed to
6175    {@link http://pear.php.net/package/Net_LDAP/docs/latest/Net_LDAP/Net_LDAP.html#methodsearch Net_LDAP::search()}.
6176    Please see the {@link http://pear.php.net/manual/en/package.networking.net-ldap.search.php Net_LDAP manual}
6177    for details.
6178   </para>
6179   <para>
6180    Note that $ldapQuery['options']['attributes'] will be used by the LDAP driver if it is present. It allows the driver
6181    to set the specified attributes to null if they are not present in the returned records.
6182    It is therefore highly recommended to always set the option 'attributes'.
6183    See {@tutorial XML_Query2XML.pkg#ldap.optionalattributes} for details.
6184    <programlisting role="php">
6185    <![CDATA[
6186array(
6187    'base' => 'ou=peopole,dc=example,dc=com',
6188    'filter' => '(objectclass=inetOrgPerson)',
6189    'options' => array(
6190        'attributes' => array(
6191            'cn',
6192            'mobile'
6193        )
6194    )
6195)
6196    ]]>
6197    </programlisting>
6198   </para>
6199   <para>
6200    Now let's look at an example. We want all entries from ou=people,dc=example,dc=com that
6201    have an objectclass of inetOrgPerson. For each of these entries we want to output
6202    the attributes cn and mobile.
6203    <programlisting role="php">
6204    <![CDATA[
6205<?php
6206require_once 'XML/Query2XML.php';
6207require_once 'Net/LDAP.php';
6208
6209/*
6210* just pass an instance of Net_LDAP to the factory method
6211* as you would do it with an MDB2 or PDO instance.
6212*/
6213$query2xml = XML_Query2XML::factory(Net_LDAP::connect());
6214
6215$dom = $query2xml->getXML(
6216    array(  //this is different than what you're used to from the other drivers
6217        'base' => 'ou=people,dc=example,dc=com',
6218        'filter' => '(objectclass=inetOrgPerson)',
6219        'options' => array(
6220            'attributes' => array( //to tell the LDAP driver which columns to watch out for
6221                'cn',
6222                'mobile',
6223            )
6224        )
6225    ),
6226    array(
6227        'rootTag' => 'employees',
6228        'idColumn' => 'cn',
6229        'rowTag' => 'employee',
6230        'elements' => array(
6231            'cn',
6232            'mobile'
6233        )
6234    )
6235);
6236header('Content-Type: application/xml');
6237
6238$dom->formatOutput = true;
6239print $dom->saveXML();
6240?>
6241    ]]>
6242    </programlisting>
6243    This would produce the following XML data:
6244    <programlisting role="tutorial">
6245    <![CDATA[
6246<?xml version="1.0" encoding="UTF-8"?>
6247<employees>
6248  <employee>
6249    <cn>John Doe</cn>
6250    <mobile>666-777-888</mobile>
6251  </employee>
6252  <employee>
6253    <cn>Jane Doe</cn>
6254    <mobile>555-777-888</mobile>
6255  </employee>
6256</employees>
6257    ]]>
6258    </programlisting>
6259   </para>
6260   <para>
6261    An LDAP Query Specification that makes use of placeholders and the option 'data' looks like this:
6262    <programlisting role="php">
6263    <![CDATA[
6264array(
6265    'data' => array(':John Doe'),
6266    'base' => 'ou=peopole,dc=example,dc=com',
6267    'filter' => '(&(objectclass=inetOrgPerson)(cn=?))',
6268    'options' => array(
6269        'attributes' => array(
6270            'cn',
6271            'mobile'
6272        ),
6273        'query2xml_placeholder' => '?'
6274    )
6275)
6276    ]]>
6277    </programlisting>
6278    An LDAP query specification provides a prepare&amp;execute-like funktionality: a placeholder
6279    (by default a question mark) can be used in ['base'] and ['filter'].
6280    That placeholder will be replaced with the values from ['data'].
6281    Please note that a placeholder will only be treated as such if there is a corresponsing element
6282    in the 'data' array. That means that if the 'data' array has only one element and 'base' and
6283    'filter' contain more than one quesion mark, all but the first quesion mark will not be replaced.
6284   </para>
6285   <para>
6286    Also note that there is another option the LDAP driver will understand:
6287    'query2xml_placeholder'. It allows you to define the placeholder used in 'base' and 'filter'. The default
6288    is a question mark ('?'). The placeholder you define can also consist of multiple chracters.
6289   </para>
6290   <para>
6291    Let's have a look at a full example: we want all entries of the class inetOrgPerson that have a cn attribute
6292    that contains the string submitted vi $_GET['cn']. Please note that the LDAP driver internally uses
6293    {@link http://pear.php.net/package/Net_LDAP/docs/latest/Net_LDAP/Net_LDAP_Util.html#methodescape_filter_value Net_LDAP_Util::escape_filter_value()}
6294    for all elements of the 'data' array to prevent injection attacks.
6295    <programlisting role="php">
6296    <![CDATA[
6297<?php
6298require_once 'XML/Query2XML.php';
6299require_once 'Net/LDAP.php';
6300
6301$query2xml = XML_Query2XML::factory(Net_LDAP::connect());
6302
6303$dom = $query2xml->getXML(
6304    array(
6305        'data' => array(':' . $_GET['cn']),
6306        'base' => 'ou=people,dc=example,dc=com',
6307        'filter' => '(&(objectclass=inetOrgPerson)(cn=*?*))', //the question mark will be replace
6308                                                              //with the contents of $_GET['cn']
6309        'options' => array(
6310            'attributes' => array( //to tell the LDAP driver which columns to watch out for
6311                'cn',
6312                'mobile',
6313            )
6314        )
6315    ),
6316    array(
6317        'rootTag' => 'employees',
6318        'idColumn' => 'cn',
6319        'rowTag' => 'employee',
6320        'elements' => array(
6321            'cn',
6322            'mobile'
6323        )
6324    )
6325);
6326header('Content-Type: application/xml');
6327
6328$dom->formatOutput = true;
6329print $dom->saveXML();
6330?>
6331    ]]>
6332    </programlisting>
6333    if $_GET['cn'] was set to 'John' the following XML data would be produced:
6334    <programlisting role="tutorial">
6335    <![CDATA[
6336<?xml version="1.0" encoding="UTF-8"?>
6337<employees>
6338  <employee>
6339    <cn>John Doe</cn>
6340    <mobile>666-777-888</mobile>
6341  </employee>
6342</employees>
6343    ]]>
6344    </programlisting>
6345   </para>
6346  </refsect2>
6347  <refsect2 id="{@id optionalattributes}">
6348   <title>Handling Optional Attributes - $options['query']['options']['attributes']</title>
6349   <para>
6350    In an LDAP directory an entry does not have to use all attributes that are
6351    provided by the entry's objectClass. In the following example the attributes
6352    'mail' and 'mobile' (among others) are missing from the first entry.
6353    <![CDATA[
6354dn: cn=Jane Doe,ou=people,dc=example,dc=com
6355cn: Jane Doe
6356objectClass: inetOrgPerson
6357
6358dn: cn=John Doe,ou=people,dc=example,dc=com
6359cn: John Doe
6360mail: john.doe@example.com
6361mobile: 555-666-777
6362objectClass: inetOrgPerson
6363    ]]>
6364    This would potentially lead to problems
6365    as it is contrary to what is possible in a RDBMS, where every record has to have a value for each
6366    column (even if it's NULL). XML_Query2XML was primarily built with an RDBMS in mind and therefore
6367    expects all data returned by a driver to be records represented as an array of associative arrays
6368    (where each associative array uses the same keys).
6369   </para>
6370   <para>
6371    To solve this problem the columns corresponding
6372    to missing attributes have to be set to null. But to tell the LDAP driver which columns to check for, you
6373    have to use the $options['query']['options']['attributes']. (Note: $options['query'] is
6374    an alias for $options['sql'])
6375   </para>
6376   <para>
6377    $options['query']['options']['attributes'] will be used by Net_LDAP to limit the
6378    attributes returned for each record (see the
6379    {@link http://pear.php.net/manual/en/package.networking.net-ldap.search.php Net_LDAP manual}).
6380    In addition to this, XML_Query2XML's LDAP driver will look at this option to
6381    determine which columns to set to null if they do not exist in the returned records.
6382   </para>
6383   <para>
6384    Let's look at what happens if you do not specify the 'attributes' option:
6385    <programlisting role="php">
6386    <![CDATA[
6387<?php
6388require_once 'XML/Query2XML.php';
6389require_once 'Net/LDAP.php';
6390
6391/*
6392* just pass an instance of Net_LDAP to the factory method
6393* as you would do it with an MDB2 or PDO instance.
6394*/
6395$query2xml = XML_Query2XML::factory(Net_LDAP::connect());
6396
6397$dom = $query2xml->getXML(
6398    array(
6399        'base' => 'ou=people,dc=example,dc=com',
6400        'filter' => '(objectclass=inetOrgPerson)'
6401        //we completely omit 'options' here
6402    ),
6403    array(
6404        'rootTag' => 'employees',
6405        'idColumn' => 'cn',
6406        'rowTag' => 'employee',
6407        'elements' => array(
6408            'cn',
6409            'pager'
6410        )
6411    )
6412);
6413header('Content-Type: application/xml');
6414
6415$dom->formatOutput = true;
6416print $dom->saveXML();
6417?>
6418    ]]>
6419    </programlisting>
6420    You would get something like this:
6421    <![CDATA[
6422Fatal error: Uncaught XML_Query2XML_ConfigException: [elements][pager]: The column "pager" was not found in the result set.
6423    ]]>
6424    This is because Jane Doe does not have a pager. As the LDAP driver was not told
6425    that the 'pager' attribute might be missing from some entries, it returned Jane
6426    Doe's record without the column 'pager'.
6427    An LDAP query specification should therefore always use the 'attributes' option!
6428   </para>
6429  </refsect2>
6430  <refsect2 id="{@id multivalueattributes}">
6431   <title>Handling Multi-Value Attributes</title>
6432   <para>
6433    In an LDAP directory, attributes are mutli-valued, that is they can hold multiple values.
6434    This is nice because it allows you to easily store, say not just one email
6435    address, but as many as you like. This is contrary to what you can usually
6436    do in an RDBMS. As XML_Query2XML was built primarily with an RDBMS in mind,
6437    it expects simple records, each represented by a one-dimensional array to be
6438    returned by the LDAP driver.
6439   </para>
6440   <para>
6441    The LDAP driver therefore creates multiple records for each entry that
6442    has multi-value attributes. An entry like
6443    <![CDATA[
6444## LDIF entry for "John Doe"
6445dn: cn=John Doe,ou=people,dc=example,dc=com
6446cn: John Doe
6447sn: Doe
6448mail: john@example.com
6449mail: johndoe@example.com
6450mail: john.doe@example.com
6451telephoneNumber: 555-111-222
6452telephoneNumber: 555-222-333
6453mobile: 666-777-888
6454    ]]>
6455    therefore has to be converted into multiple one-dimensional associative
6456    arrays (i.e. records):
6457    <![CDATA[
6458    cn        mail                  telephoneNumber  mobile
6459    -------------------------------------------------------
6460    John Doe  john@example.com      555-111-222      666-777-888
6461    John Doe  johndoe@example.com   555-222-333      666-777-888
6462    John Doe  john.doe@example.com  555-111-222      666-777-888
6463    ]]>
6464    Note that no cartasian product of the mail-values and the mobile-values
6465    is produced (that would result in six instead of three records in the
6466    above example). The number of records returned is equal to the number
6467    values assigned to the attribute that has the most values (here
6468    it's the mail attribute that has 3 values). To make sure that every
6469    record has valid values for all attributes/columns, we continiusly loop
6470    over the available value until the attribute with the most values
6471    is done. In the above example the attribute telephoneNumber had only
6472    two values while the mail attribute had three. The record for
6473    the third mail attribute value therefore contains the first value of
6474    the telephoneNumber attribute.
6475   </para>
6476   <para>
6477    So what does that mean for $options (the second argument passed to getXML())?
6478    It means that not much changes compared to how you would define $options
6479    when using a database-related driver. As shown above, using
6480    {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.simple_element_specifications}
6481    will work, but will only produce the first value for each attribute:
6482    <programlisting role="php">
6483    <![CDATA[
6484<?php
6485require_once 'XML/Query2XML.php';
6486require_once 'Net/LDAP.php';
6487
6488$query2xml = XML_Query2XML::factory(Net_LDAP::connect());
6489
6490$dom = $query2xml->getXML(
6491    array(
6492        'base' => 'ou=people,dc=example,dc=com',
6493        'filter' => '(objectclass=inetOrgPerson)',
6494        'options' => array(
6495            'attributes' => array( //to tell the LDAP driver which columns to watch out for
6496                'cn',
6497                'mail',
6498            )
6499        )
6500    ),
6501    array(
6502        'rootTag' => 'employees',
6503        'idColumn' => 'cn',
6504        'rowTag' => 'employee',
6505        'elements' => array(
6506            'cn',     //simple element specification
6507            'mail'    //simple element specification
6508        )
6509    )
6510);
6511header('Content-Type: application/xml');
6512
6513$dom->formatOutput = true;
6514print $dom->saveXML();
6515?>
6516    ]]>
6517    </programlisting>
6518    notice how the resulting XML only shows the first value of the mail attribute:
6519    <programlisting role="tutorial">
6520    <![CDATA[
6521<?xml version="1.0" encoding="UTF-8"?>
6522<employees>
6523  <employee>
6524    <cn>John Doe</cn>
6525    <mail>john@example.com</mail>
6526  </employee>
6527  <employee>
6528    <cn>Jane Doe</cn>
6529    <mail>jane@example.com</mail>
6530  </employee>
6531</employees>
6532    ]]>
6533    </programlisting>
6534    To get a "mail" XML element for each value of the "mail" LDAP attribute, we have
6535    to use {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications}
6536    and set {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn}:
6537    <programlisting role="php">
6538    <![CDATA[
6539<?php
6540require_once 'XML/Query2XML.php';
6541require_once 'Net/LDAP.php';
6542
6543$query2xml = XML_Query2XML::factory(Net_LDAP::connect());
6544
6545$dom = $query2xml->getXML(
6546    array(
6547        'base' => 'ou=people,dc=example,dc=com',
6548        'filter' => '(objectclass=inetOrgPerson)',
6549        'options' => array(
6550            'attributes' => array( //to tell the LDAP driver which columns to watch out for
6551                'cn',
6552                'mail',
6553            )
6554        )
6555    ),
6556    array(
6557        'rootTag' => 'employees',
6558        'idColumn' => 'cn',
6559        'rowTag' => 'employee',
6560        'elements' => array(
6561            'cn',     //simple element specification
6562            'mail' => array(
6563                'idColumn' => 'mail',
6564                'value' => 'mail'
6565            )
6566        )
6567    )
6568);
6569header('Content-Type: application/xml');
6570
6571$dom->formatOutput = true;
6572print $dom->saveXML();
6573?>
6574    ]]>
6575    </programlisting>
6576    This produces the following XML data:
6577    <programlisting role="tutorial">
6578    <![CDATA[
6579<?xml version="1.0" encoding="UTF-8"?>
6580<employees>
6581  <employee>
6582    <cn>John Doe</cn>
6583    <mail>john@example.com</mail>
6584    <mail>johndoe@example.com</mail>
6585    <mail>john.doe@example.com</mail>
6586  </employee>
6587  <employee>
6588    <cn>Jane Doe</cn>
6589    <mail>jane@example.com</mail>
6590    <mail>jane.doe@example.com</mail>
6591  </employee>
6592</employees>
6593    ]]>
6594    </programlisting>
6595    This is because we set {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn}
6596    within the complex element specification to "mail". Every record with a unique value
6597    for the column "mail" is therefore processed.
6598   </para>
6599   <para>
6600    As all LDAP attributes can have multiple values it is therefore highly recommended to
6601    always use a complex element specification with $options['idColumn'] as described above.
6602    The only exception is when you just want a single value (and don't care which one).
6603   </para>
6604  </refsect2>
6605 </refsect1>
6606
6607 <refsect1 id="{@id multipledrivers}">
6608  <title>Using Multiple Drivers</title>
6609  <para>
6610   There might be a situation where you want to genearte your XML data from multiple RDBMSes or
6611   an RDBMS and an LDAP server. By using the option 'driver' within a
6612   {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.complexqueryspecification} or a
6613   {@tutorial XML_Query2XML.pkg#ldap.queryspecification} you can specify any driver you want.
6614   To create an instance of one of the drivers that come with XML_Query2XML use
6615   {@link XML_Query2XML_Driver::factory()}. If you want to write your own driver, please see
6616   {@tutorial XML_Query2XML.pkg#yourdriver}. Let's start with an example that pulls data
6617   from a MySQL and a ProstgreSQL database:
6618   <programlisting role="php">
6619   <![CDATA[
6620<?php
6621require_once 'XML/Query2XML.php';
6622require_once 'MDB2.php';
6623
6624$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
6625
6626$pgsqlDriver = XML_Query2XML_Driver::factory(MDB2::factory('pgsql://postgres:test@localhost/query2xml_tests'));
6627$dom = $query2xml->getXML(
6628    'SELECT * FROM artist',
6629    array(
6630        'rootTag' => 'artists',
6631        'idColumn' => 'artistid',
6632        'rowTag' => 'artist',
6633        'elements' => array(
6634            'name',
6635            'genre',
6636            'album' => array(
6637                'sql' => array(
6638                    'driver' => $pgsqlDriver,
6639                    'data' => array(
6640                        'artistid'
6641                    ),
6642                    'query' => 'SELECT * FROM album WHERE artist_id = ?'
6643                ),
6644                'idColumn' => 'albumid',
6645                'elements' => array(
6646                    'title',
6647                    'published_year'
6648                )
6649            )
6650        )
6651    )
6652);
6653header('Content-Type: application/xml');
6654
6655$dom->formatOutput = true;
6656print $dom->saveXML();
6657?>
6658    ]]>
6659    </programlisting>
6660    The resulting XML being:
6661    <programlisting role="tutorial">
6662    <![CDATA[
6663<?xml version="1.0" encoding="UTF-8"?>
6664<artists>
6665  <artist>
6666    <name>Curtis Mayfield</name>
6667    <genre>Soul</genre>
6668    <album>
6669      <title>New World Order</title>
6670      <published_year>1990</published_year>
6671    </album>
6672    <album>
6673      <title>Curtis</title>
6674      <published_year>1970</published_year>
6675    </album>
6676  </artist>
6677  <artist>
6678    <name>Isaac Hayes</name>
6679    <genre>Soul</genre>
6680    <album>
6681      <title>Shaft</title>
6682      <published_year>1972</published_year>
6683    </album>
6684  </artist>
6685  <artist>
6686    <name>Ray Charles</name>
6687    <genre>Country and Soul</genre>
6688  </artist>
6689</artists>
6690    ]]>
6691    </programlisting>
6692  </para>
6693  <para>
6694   Now let's combine an RDBMS with the data from an LDAP server:
6695   <programlisting role="php">
6696   <![CDATA[
6697<?php
6698require_once 'XML/Query2XML.php';
6699require_once 'Net/LDAP.php';
6700require_once 'MDB2.php';
6701$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
6702
6703$dom = $query2xml->getXML(
6704  "SELECT * FROM employee",
6705  array(
6706    'rootTag' => 'employees',
6707    'idColumn' => 'employeeid',
6708    'rowTag' => 'employee',
6709    'elements' => array(
6710        'name' => 'employeename',
6711        'contact_details' => array(
6712            'query' => array(
6713                'driver' => XML_Query2XML_Driver::factory(Net_LDAP::connect()),
6714                'data' => array(
6715                    'employeename'
6716                ),
6717                'base' => 'ou=people,dc=example,dc=com',
6718                'filter' => '(&(objectclass=inetOrgPerson)(cn=?))',
6719                'options' => array(
6720                    'attributes' => array(
6721                        'cn',
6722                        'telephoneNumber',
6723                        'mobile',
6724                        'mail',
6725                        'labeledURI'
6726                    )
6727                )
6728            ),
6729            'idColumn' => 'cn',
6730            'elements' => array(
6731                'telephoneNumber' => array(
6732                    'idColumn' => 'telephoneNumber',
6733                    'value' => 'telephoneNumber'
6734                ),
6735                'mobile' => array(
6736                    'idColumn' => 'mobile',
6737                    'value' => 'mobile'
6738                ),
6739                'mail' => array(
6740                    'idColumn' => 'mail',
6741                    'value' => 'mail'
6742                ),
6743                'labeledURI' => array(
6744                    'idColumn' => 'labeledURI',
6745                    'value' => 'labeledURI'
6746                )
6747            )
6748        )
6749    ),
6750  )
6751);
6752$dom->formatOutput = true;
6753print $dom->saveXML();
6754?>
6755   ]]>
6756   </programlisting>
6757   As we only have corresponsing entries in the LDAP directory for two of our
6758   employees in the mysql database the resulting XML looks like this:
6759   <programlisting role="tutorial">
6760   <![CDATA[
6761<?xml version="1.0" encoding="UTF-8"?>
6762<employees>
6763  <employee>
6764    <name>Michael Jones</name>
6765  </employee>
6766  <employee>
6767    <name>Susi Weintraub</name>
6768    <contact_details>
6769      <telephoneNumber>555-111-222</telephoneNumber>
6770      <mobile>555-666-777</mobile>
6771      <mobile>555-777-888</mobile>
6772      <mail>susi@example.com</mail>
6773      <labeledURI>http://susi.example.com</labeledURI>
6774      <labeledURI>http://susiweintraub.example.com</labeledURI>
6775      <labeledURI>http://susi.weintraub.example.com</labeledURI>
6776    </contact_details>
6777  </employee>
6778  <employee>
6779    <name>Steve Hack</name>
6780  </employee>
6781  <employee>
6782    <name>Joan Kerr</name>
6783  </employee>
6784  <employee>
6785    <name>Marcus Roth</name>
6786  </employee>
6787  <employee>
6788    <name>Jack Mack</name>
6789  </employee>
6790  <employee>
6791    <name>Rita Doktor</name>
6792  </employee>
6793  <employee>
6794    <name>David Til</name>
6795  </employee>
6796  <employee>
6797    <name>Pia Eist</name>
6798  </employee>
6799  <employee>
6800    <name>Hanna Poll</name>
6801  </employee>
6802  <employee>
6803    <name>Jim Wells</name>
6804    <contact_details>
6805      <telephoneNumber>555-444-888</telephoneNumber>
6806      <mobile>555-666-777</mobile>
6807      <mail>jim@example.com</mail>
6808      <mail>jim.wells@example.com</mail>
6809      <mail>jimwells@example.com</mail>
6810      <mail>jwells@example.com</mail>
6811      <labeledURI>http://jimwells.example.com</labeledURI>
6812      <labeledURI>http://jim.wells.example.com</labeledURI>
6813    </contact_details>
6814  </employee>
6815  <employee>
6816    <name>Sandra Wilson</name>
6817  </employee>
6818</employees>
6819   ]]>
6820   </programlisting>
6821  </para>
6822 </refsect1>
6823
6824 <refsect1 id="{@id x_to_n}">
6825  <title>Implementing 1:N and N:N relationships</title>
6826  <para>
6827   Often times you will have a one-to-many (1:N) or many-to-many (N:N) relationship between
6828   two tables. For example, the
6829   {@link http://query2xml.sourceforge.net/docs/Query2XML_Tests.jpg ER diagram}
6830   of our sample applications shows an 1:N relationship between the tables artist and album
6831   (<emphasis>one</emphasis> artist can perform <emphasis>many</emphasis> albums). For
6832   the sake of simplicity we will be using this 1:N relationship in this section (note that
6833   the intersection table employee_department implements an N:N relationship between employee
6834   and department).
6835  </para>
6836  <para>
6837   When dealing with 1:N or N:N relationships there are two basic questions that determine
6838   how you will use XML_Query2XML to implement your solution:
6839   <itemizedlist>
6840  	<listitem>
6841  	 Do you want to use {@tutorial XML_Query2XML.pkg#x_to_n.one_or_many_queries}
6842  	</listitem>
6843  	<listitem>
6844  	 Do you want to produce {@tutorial XML_Query2XML.pkg#x_to_n.one_or_many_elements}
6845  	</listitem>
6846   </itemizedlist>
6847  </para>
6848  <refsect2 id="{@id one_or_many_queries}">
6849   <title>One or Many Queries?</title>
6850   <para>
6851    In almost every situation you will be better of using one big query than running
6852    multiple smaller queries. Anyway, XML_Query2XML gives you the option of
6853    <itemizedlist>
6854  	 <listitem>
6855  	  {@tutorial XML_Query2XML.pkg#x_to_n.one_or_many_queries.join} (one query)
6856  	 </listitem>
6857  	 <listitem>
6858  	  {@tutorial XML_Query2XML.pkg#x_to_n.one_or_many_queries.options_sql} (multiple queries)
6859  	 </listitem>
6860    </itemizedlist>
6861   </para>
6862   <refsect3 id="{@id join}">
6863    <title>Using an SQL JOIN</title>
6864    <para>
6865     The best way to query two tables is to JOIN them together using an SQL JOIN.
6866     If you are new to SQL check out first what wikipedia has to say about {@link http://en.wikipedia.org/wiki/Join_(SQL) Joins}.
6867     I could also recommend
6868     {@link http://www.amazon.com/Learning-SQL-Alan-Beaulieu/dp/0596007272/ref=pd_bbs_sr_1?ie=UTF8&amp;s=books&amp;qid=1206554386&amp;sr=1-1 Learning SQL}
6869     by Alan Beaulieu.
6870    </para>
6871    <para>
6872     Use {@link XML_Query2XML::getXML()}'s first argument {@tutorial XML_Query2XML.pkg#query2xml_getxml.sql}
6873     to pass the SQL JOIN to XML_Query2XML.
6874    </para>
6875    <para>
6876     A very important thing to keep in mind is the correct specification of
6877     {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn}. On
6878     the root level it is set to 'artistid' (the parent entity's primary key) while
6879     on the level of $options['elements']['albums'] it is set to 'albumid'
6880     (the child entity's primary key). Here goes the code:
6881     <programlisting role="php">
6882     <![CDATA[
6883<?php
6884require_once 'XML/Query2XML.php';
6885require_once 'MDB2.php';
6886$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
6887$dom = $query2xml->getXML(
6888    'SELECT
6889        *
6890     FROM
6891        artist, album
6892     WHERE
6893        album.artist_id = artist.artistid',
6894    array(
6895        'rootTag' => 'music_library',
6896        'rowTag' => 'artist',
6897        'idColumn' => 'artistid',
6898        'elements' => array(
6899            'artistid',
6900            'name',
6901            'albums' => array(
6902                'rootTag' => 'albums',
6903                'rowTag' => 'album',
6904                'idColumn' => 'albumid',
6905                'elements' => array(
6906                    'albumid',
6907                    'title'
6908                )
6909            )
6910        )
6911    )
6912);
6913
6914header('Content-Type: application/xml');
6915
6916$dom->formatOutput = true;
6917print $dom->saveXML();
6918?>
6919     ]]>
6920     </programlisting>
6921     And the resulting XML data looks as follows:
6922     <programlisting role="tutorial">
6923     <![CDATA[
6924<?xml version="1.0" encoding="UTF-8"?>
6925<music_library>
6926  <artist>
6927    <artistid>1</artistid>
6928    <name>Curtis Mayfield</name>
6929    <albums>
6930      <album>
6931        <albumid>1</albumid>
6932        <title>New World Order</title>
6933      </album>
6934      <album>
6935        <albumid>2</albumid>
6936        <title>Curtis</title>
6937      </album>
6938    </albums>
6939  </artist>
6940  <artist>
6941    <artistid>2</artistid>
6942    <name>Isaac Hayes</name>
6943    <albums>
6944      <album>
6945        <albumid>3</albumid>
6946        <title>Shaft</title>
6947      </album>
6948    </albums>
6949  </artist>
6950</music_library>
6951     ]]>
6952     </programlisting>
6953     Note: as we used a traditional join (and therefore implecitely an INNER JOIN) only those
6954     artists are included that also have albums in our database. To also get those artists
6955     that don't have any albums we would have to use a LEFT OUTER JOIN.
6956    </para>
6957   </refsect3>
6958   <refsect3 id="{@id options_sql}">
6959    <title>Using $options['sql']</title>
6960    <para>
6961     If for whatever reason you cannot join the two tables, you can use
6962     {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql} to specify
6963     a separate query for the second table. This will however result in the
6964     second query being executed as many times as the there are records in
6965     the first table. It is therefore highly recommended to use a JOIN
6966     whenever possible.
6967    </para>
6968    <para>
6969     To produce a similar XML as with the JOIN above we have to use a
6970     {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.complexqueryspecification}
6971     with artist.artistid for $options['elements']['albums']['sql']['data'] and
6972     'SELECT * FROM album WHERE artist_id = ?' for $options['elements']['albums']['sql']['query']:
6973     <programlisting role="php">
6974     <![CDATA[
6975<?php
6976require_once 'XML/Query2XML.php';
6977require_once 'MDB2.php';
6978$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
6979$dom = $query2xml->getXML(
6980    'SELECT * FROM artist',
6981    array(
6982        'rootTag' => 'music_library',
6983        'rowTag' => 'artist',
6984        'idColumn' => 'artistid',
6985        'elements' => array(
6986            'artistid',
6987            'name',
6988            'albums' => array(
6989                'rootTag' => 'albums',
6990                'rowTag' => 'album',
6991                'idColumn' => 'albumid',
6992                'sql' => array(
6993                    'data' => array(
6994                        'artistid'
6995                    ),
6996                    'query' => 'SELECT * FROM album WHERE artist_id = ?'
6997                ),
6998                'elements' => array(
6999                    'albumid',
7000                    'title'
7001                )
7002            )
7003        )
7004    )
7005);
7006
7007header('Content-Type: application/xml');
7008
7009$dom->formatOutput = true;
7010print $dom->saveXML();
7011?>
7012     ]]>
7013     </programlisting>
7014     Note that $options['elements']['albums']['idColumn'] is again set to 'albumid'.
7015    </para>
7016    <para>
7017     The difference to the above XML is that it also includes artists that have no albums in our database.
7018     This is because using two separate queries is like using an OUTER JOIN while in the above
7019     example we were using an INNER JOIN.
7020     <programlisting role="tutorial">
7021     <![CDATA[
7022<?xml version="1.0" encoding="UTF-8"?>
7023<music_library>
7024  <artist>
7025    <artistid>1</artistid>
7026    <name>Curtis Mayfield</name>
7027    <albums>
7028      <album>
7029        <albumid>1</albumid>
7030        <title>New World Order</title>
7031      </album>
7032      <album>
7033        <albumid>2</albumid>
7034        <title>Curtis</title>
7035      </album>
7036    </albums>
7037  </artist>
7038  <artist>
7039    <artistid>2</artistid>
7040    <name>Isaac Hayes</name>
7041    <albums>
7042      <album>
7043        <albumid>3</albumid>
7044        <title>Shaft</title>
7045      </album>
7046    </albums>
7047  </artist>
7048  <artist>
7049    <artistid>3</artistid>
7050    <name>Ray Charles</name>
7051    <albums/>
7052  </artist>
7053</music_library>
7054     ]]>
7055     </programlisting>
7056    </para>
7057   </refsect3>
7058  </refsect2>
7059  <refsect2 id="{@id one_or_many_elements}">
7060   <title>One or Many Elements Per Related Entity?</title>
7061   <para>
7062    Depending on whether you want one or multiple XML element per related entity,
7063    you have two options here:
7064    <itemizedlist>
7065  	 <listitem>
7066      {@tutorial XML_Query2XML.pkg#x_to_n.one_or_many_elements.options_elements}.
7067     </listitem>
7068     <listitem>
7069      {@tutorial XML_Query2XML.pkg#x_to_n.one_or_many_elements.options_value}.
7070     </listitem>
7071    </itemizedlist>
7072   </para>
7073   <refsect3 id="{@id options_elements}">
7074    <title>Multiple Elements Per Entity - $options['elements']</title>
7075    <para>
7076     In most cases you will need to create multiple XML elements for each
7077     related entity. In our example we might want the albumid and the title
7078     for each album. This means that we have to assign
7079     <programlisting role="php">
7080     <![CDATA[
7081array(
7082    'albumid',
7083    'title'
7084);
7085     ]]>
7086     </programlisting>
7087     to $options['elements']['albums']['elements'].
7088    </para>
7089    <para>
7090     Please see the {@tutorial XML_Query2XML.pkg#x_to_n.one_or_many_queries} section above
7091     for two example - one using a JOIN and the second using two queries.
7092    </para>
7093   </refsect3>
7094   <refsect3 id="{@id options_value}">
7095    <title>One Element Per Entity - $options['value']</title>
7096    <para>
7097     In our example, if you want just a single XML element for each album like below,
7098     we can use {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_value}.
7099     <programlisting role="tutorial">
7100     <![CDATA[
7101<?xml version="1.0" encoding="UTF-8"?>
7102<music_library>
7103  <artist>
7104    <name>Curtis Mayfield</name>
7105    <album>New World Order</album>
7106    <album>Curtis</album>
7107  </artist>
7108  <artist>
7109    <name>Isaac Hayes</name>
7110    <album>Shaft</album>
7111  </artist>
7112</music_library>
7113     ]]>
7114     </programlisting>
7115     The code to generate the above XML would look like this:
7116     <programlisting role="php">
7117     <![CDATA[
7118<?php
7119require_once 'XML/Query2XML.php';
7120require_once 'MDB2.php';
7121$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
7122$dom = $query2xml->getXML(
7123    'SELECT
7124        *
7125     FROM
7126        artist, album
7127     WHERE
7128        album.artist_id = artist.artistid',
7129    array(
7130        'rootTag' => 'music_library',
7131        'rowTag' => 'artist',
7132        'idColumn' => 'artistid',
7133        'elements' => array(
7134            'name',
7135            'album' => array(
7136                'idColumn' => 'albumid',
7137                'value' => 'title'
7138            )
7139        )
7140    )
7141);
7142
7143header('Content-Type: application/xml');
7144
7145$dom->formatOutput = true;
7146print $dom->saveXML();
7147?>
7148     ]]>
7149     </programlisting>
7150     The critical part here is $options['elements']['albums']['idColumn'].
7151     It is set to 'albumid'. This means that an XML element will be created
7152     for $options['elements']['albums'] for each unique albumid.
7153    </para>
7154    <para>
7155     The above example uses a JOIN which is most efficient.
7156     But as shown above under
7157     {@tutorial XML_Query2XML.pkg#x_to_n.one_or_many_queries.options_sql}
7158     there might be situations where you want to use multiple SQL queries
7159     instead of a single JOIN:
7160     <programlisting role="php">
7161     <![CDATA[
7162<?php
7163require_once 'XML/Query2XML.php';
7164require_once 'MDB2.php';
7165$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
7166$dom = $query2xml->getXML(
7167    'SELECT * FROM artist',
7168    array(
7169        'rootTag' => 'music_library',
7170        'rowTag' => 'artist',
7171        'idColumn' => 'artistid',
7172        'elements' => array(
7173            'name',
7174            'album' => array(
7175                'sql' => array(
7176                    'data' => array(
7177                        'artistid'
7178                    ),
7179                    'query' => 'SELECT * FROM album WHERE artist_id = ?'
7180                ),
7181                'idColumn' => 'albumid',
7182                'value' => 'title'
7183            )
7184        )
7185    )
7186);
7187
7188header('Content-Type: application/xml');
7189
7190$dom->formatOutput = true;
7191print $dom->saveXML();
7192?>
7193     ]]>
7194     </programlisting>
7195     The resulting XML is similar to the one generated above. There is just
7196     one difference: artists that have no albums are included as well:
7197     <programlisting role="tutorial">
7198     <![CDATA[
7199<?xml version="1.0" encoding="UTF-8"?>
7200<music_library>
7201  <artist>
7202    <name>Curtis Mayfield</name>
7203    <album>New World Order</album>
7204    <album>Curtis</album>
7205  </artist>
7206  <artist>
7207    <name>Isaac Hayes</name>
7208    <album>Shaft</album>
7209  </artist>
7210  <artist>
7211    <name>Ray Charles</name>
7212  </artist>
7213</music_library>
7214     ]]>
7215     </programlisting>
7216    </para>
7217   </refsect3>
7218  </refsect2>
7219  <refsect2 id="{@id joins}">
7220   <title>A last word on JOINs</title>
7221   <para>
7222    As a general rule, it is wise to use as few SQL queries as possible.
7223    This means that you have to JOIN you tables. It is essential to understand
7224    the differences between INNER and OUTER joins and how they affect your
7225    result set.
7226   </para>
7227  </refsect2>
7228 </refsect1>
7229
7230 <refsect1 id="{@id yourdriver}">
7231  <title>Writing Your Own Driver</title>
7232  <para>
7233   If you want to work with a primary data source other than
7234   PDO, MDB2, DB, ADOdb or Net_LDAP, you might want to write
7235   your own driver.
7236  </para>
7237  <para>
7238   Start out by extending the abstract class
7239   {@link XML_Query2XML_Driver} and implementing its abstract method
7240   getAllRecords():
7241   <programlisting role="php">
7242   <![CDATA[
7243/**Contains the abstract class XML_Query2XML_Driver and the
7244* exception class XML_Query2XML_DBException.
7245*/
7246require_once 'XML/Query2XML.php';
7247
7248class MyFirebirdDriver extends XML_Query2XML_Driver
7249{
7250    /*This method is defined as an abstract method within
7251    * XML_Query2XML_Driver and therefore has to be implemented.
7252    */
7253    public function getAllRecords($sql, $configPath)
7254    {
7255    }
7256}
7257?>
7258   ]]>
7259   </programlisting>
7260   For the sake of example, we'll be building a Firebird driver using
7261   PHP's native interbase API.
7262  </para>
7263  <para>
7264   Now let's add a constructor method that accepts the arguments needed to
7265   set up an Interbase/Firebird connection:
7266   <programlisting role="php">
7267   <![CDATA[
7268<?php
7269public function __construct($database, $username, $password)
7270{
7271    $this->_dbh = @ibase_pconnect($database, $username, $password);
7272    if ($this->_dbh === false) {
7273        throw new XML_Query2XML_DBException(
7274            'Could not connect to database: ' . ibase_errmsg()
7275            . '(error code: ' . ibase_errcode() . ')'
7276        );
7277    }
7278}
7279?>
7280   ]]>
7281   </programlisting>
7282  </para>
7283  <para>
7284   Before we code the body of our getAllRecords() method, we have to
7285   decide whether to overwrite the method {@link XML_Query2XML_Driver::preprocessQuery()}.
7286   This method does two things:
7287   <itemizedlist>
7288  	<listitem>
7289  	 pre-process {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql}
7290  	</listitem>
7291  	<listitem>
7292  	 return the query statement as a string that will be used by XML_Query2XML
7293  	 for logging, profiling and caching
7294  	</listitem>
7295   </itemizedlist>
7296   The importance of preprocessQuery() lies in the fact that it determines
7297   the format of {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql}.
7298   As implemented in XML_Query2XML_Driver $options['sql'] can be either
7299   a string or an array containing the element 'query'. If it is a string
7300   it will be transformed to an array with the element 'query' holding
7301   the string. This means that getAllRecords() only needs to deal with
7302   a first argument that is a string and has a 'query' element. In this
7303   example we will not overwrite preprocessQuery().
7304  </para>
7305  <para>
7306   One aspect that is not controlled by preprocessQuery() is $options['sql']['data'].
7307   If it is set, XML_Query2XML requires it to be an array of strings or instances of
7308   classes that implement XML_Query2XML_Callback. The evaluations of the specifications
7309   in $options['sql']['data'] are entirely handled by XML_Query2XML.
7310  </para>
7311  <para>
7312   All that is left now is writing the body of the getAllRecords() method.
7313   Regarding its first argument, {@link XML_Query2XML_Driver::preprocessQuery()} enforces the following rules
7314   <itemizedlist>
7315  	<listitem>
7316  	 It will be an associative array.
7317  	</listitem>
7318  	<listitem>
7319  	 The array will have a key named 'query'.
7320  	</listitem>
7321   </itemizedlist>
7322  </para>
7323  <para>
7324   A first version of getAllRecords() might therefore look like this:
7325   <programlisting role="php">
7326   <![CDATA[
7327<?php
7328public function getAllRecords($sql, $configPath)
7329{
7330    if (!isset($sql['data'])) {
7331        //do not presume that $sql['data'] is present
7332        $sql['data'] = array();
7333    }
7334
7335    //prepare
7336    $statement = @ibase_prepare($this->_dbh, $sql['query']);
7337
7338    //execute
7339    $args = $sql['data'];
7340    array_unshift($args, $statement);
7341    $result = call_user_func_array('ibase_execute', $args);
7342
7343    //fetch all records
7344    while ($record = ibase_fetch_assoc($result, IBASE_TEXT)) {
7345        $records[] = $record;
7346    }
7347    ibase_free_result($result);
7348
7349    return $records;
7350}
7351?>
7352   ]]>
7353   </programlisting>
7354   The above code is already functional but is still missings some important
7355   error checking. Below is the final version of our MyFirebirdDriver class,
7356   that includes a more robust version of getAllRecords(). Note how getAllRecords()'s
7357   second argument, $configPath is used for the exception messages.
7358   <programlisting role="php">
7359   <![CDATA[
7360<?php
7361/**Contains the abstract class XML_Query2XML_Driver and the
7362* exception class XML_Query2XML_DBException.
7363*/
7364require 'XML/Query2XML.php';
7365class MyFirebirdDriver extends XML_Query2XML_Driver
7366{
7367    private $_dhb = null;
7368
7369    public function __construct($database, $username, $password)
7370    {
7371        $this->_dbh = @ibase_pconnect($database, $username, $password);
7372        if ($this->_dbh === false) {
7373            throw new XML_Query2XML_DBException(
7374                'Could not connect to database: ' . ibase_errmsg()
7375                . '(error code: ' . ibase_errcode() . ')'
7376            );
7377        }
7378    }
7379
7380    /**Returns are records retrieved by running an SQL query.
7381    * @param mixed $sql A query string or an array with the elements
7382    *                   'query' (and optionally 'data').
7383    * @param string $configPath Where in $options the query was defined.
7384    * @return array All records as a two-dimensional array.
7385    */
7386    public function &getAllRecords($sql, $configPath)
7387    {
7388        if (!isset($sql['data'])) {
7389            //do not presume that $sql['data'] is present
7390            $sql['data'] = array();
7391        }
7392
7393        /*
7394        * prepare
7395        */
7396        $statement = @ibase_prepare($this->_dbh, $sql['query']);
7397        if ($statement === false) {
7398            throw new XML_Query2XML_DBException(
7399                /*
7400                * Note how we use $configPath here. This will make
7401                * the exception message tell you where in $options
7402                * the error needs to be fixed.
7403                */
7404                $configPath . ': Could not prepare query "' . $sql['query'] . '": '
7405                . ibase_errmsg() . '(error code: ' . ibase_errcode() . ')'
7406            );
7407        }
7408
7409        /*
7410        * execute
7411        */
7412        $args = $sql['data'];
7413        array_unshift($args, $statement);
7414        $result = call_user_func_array(
7415            'ibase_execute',
7416            $args
7417        );
7418        if ($result === false) {
7419            throw new XML_Query2XML_DBException(
7420                /*
7421                * Note again how we use $configPath here.
7422                */
7423                $configPath . ': Could not execute query: "' . $sql['query'] . '": '
7424                . ibase_errmsg() . '(error code: ' . ibase_errcode() . ')'
7425            );
7426        } elseif ($result === true) {
7427            //empty result set
7428            $records = array();
7429        } else {
7430            //fetch all records
7431            while ($record = ibase_fetch_assoc($result, IBASE_TEXT)) {
7432                $records[] = $record;
7433            }
7434            ibase_free_result($result);
7435        }
7436
7437        /*
7438        * return a two dimensional array: numeric indexes in the first
7439        * and associative arrays in the second dimension
7440        */
7441        return $records;
7442    }
7443}
7444
7445//test the driver directly
7446$driver = new MyFirebirdDriver('localhost:/tmp/test.fdb', 'SYSDBA', 'test');
7447$records = $driver->getAllRecords(
7448    array(
7449        'data' => array(1),
7450        'query' => 'SELECT * FROM test WHERE id = ?'
7451    ),
7452    '[config]'
7453);
7454print_r($records);
7455?>
7456   ]]>
7457   </programlisting>
7458  </para>
7459  <para>
7460   Using XML_Query2XML_DBException for a database-related driver makes sense of course.
7461   But when your driver has nothing to do with an RDBMS, it is recommended to create
7462   your own exception class by extending {@link XML_Query2XML_DriverException}:
7463<programlisting role="php">
7464   <![CDATA[
7465<?php
7466/**Exception for MyDriver errors
7467*/
7468class MyDriverException extends XML_Query2XML_DriverException
7469{
7470    /**Constructor
7471    * @param string $message The error message.
7472    */
7473    public function __construct($message)
7474    {
7475        parent::__construct($message);
7476    }
7477}
7478?>
7479   ]]>
7480   </programlisting>
7481   Note: it is in no way required but I would recommend that
7482   all exceptions your driver throws extend {@link XML_Query2XML_DriverException}. That way
7483   you can catch driver related exceptions in a consistent way - no matter what driver is used.
7484  </para>
7485 </refsect1>
7486
7487 <refsect1 id="{@id casestudies}">
7488  <title>Case Studies</title>
7489  <para>
7490  	Now let's have a look at some of XML_Query2XML's features in action. We'll start out with simple cases.
7491  	We'll turn to rather complex ones as we proceed. All cases are included in the source distribution.
7492  	Each case has its own directory cases/caseXX and will consist of 5 files (Case 01 contains only
7493  	the first 2):
7494  	<itemizedlist>
7495  	 <listitem>
7496  	  <emphasis>caseXX.php</emphasis>: generates the XML data.
7497  	 </listitem>
7498  	 <listitem>
7499  	  <emphasis>caseXX.xml</emphasis>: the generated the XML data saved to a file.
7500  	 </listitem>
7501  	 <listitem>
7502  	  <emphasis>caseXX_debug.php</emphasis>: does debugging and profiling and generates
7503  	  caseXX.log and caseXX.profile.
7504  	 </listitem>
7505  	 <listitem>
7506  	  <emphasis>caseXX.log</emphasis>: the generated debug log
7507  	 </listitem>
7508  	 <listitem>
7509  	  <emphasis>caseXX.profile</emphasis>: the generated profile
7510  	 </listitem>
7511  	</itemizedlist>
7512  </para>
7513  <para>
7514   The SQL DDL used in all cases can be found in tests/Query2XML_Tests.sql and
7515   {@tutorial XML_Query2XML.pkg#sqlddl}.
7516  </para>
7517  <refsect2 id="{@id case01}">
7518    <title>Case 01: simple SELECT with getFlatXML</title>
7519    <para>
7520     Case 01 will teach you:
7521     <itemizedlist>
7522      <listitem>
7523       How to use {@link XML_Query2XML::getFlatXML()}.
7524      </listitem>
7525     </itemizedlist>
7526    </para>
7527    <para>
7528     Case 01 is as simple as it can get. We use {@link XML_Query2XML::getFlatXML()} to generate
7529     flat XML data.
7530    </para>
7531
7532    <refsect3 id="{@id case01_php}">
7533     <title>case01.php</title>
7534     <para>
7535     <programlisting role="php">
7536     <![CDATA[
7537<?php
7538require_once 'XML/Query2XML.php';
7539require_once 'MDB2.php';
7540$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
7541$dom = $query2xml->getFlatXML(
7542    "SELECT
7543        *
7544     FROM
7545        artist",
7546    'music_library',
7547    'artist');
7548
7549header('Content-Type: application/xml');
7550
7551$dom->formatOutput = true;
7552print $dom->saveXML();
7553?>
7554     ]]>
7555     </programlisting>
7556     </para>
7557    </refsect3>
7558    <refsect3 id="{@id case01_xml}">
7559     <title>case01.xml</title>
7560     <para>
7561     The result looks like this:
7562     <programlisting role="tutorial">
7563     <![CDATA[
7564<?xml version="1.0" encoding="UTF-8"?>
7565<music_library>
7566  <artist>
7567    <artistid>1</artistid>
7568    <name>Curtis Mayfield</name>
7569    <birth_year>1920</birth_year>
7570    <birth_place>Chicago</birth_place>
7571    <genre>Soul</genre>
7572  </artist>
7573  <artist>
7574    <artistid>2</artistid>
7575    <name>Isaac Hayes</name>
7576    <birth_year>1942</birth_year>
7577    <birth_place>Tennessee</birth_place>
7578    <genre>Soul</genre>
7579  </artist>
7580  <artist>
7581    <artistid>3</artistid>
7582    <name>Ray Charles</name>
7583    <birth_year>1930</birth_year>
7584    <birth_place>Mississippi</birth_place>
7585    <genre>Country and Soul</genre>
7586  </artist>
7587</music_library>
7588     ]]>
7589     </programlisting>
7590     </para>
7591    </refsect3>
7592   </refsect2>
7593
7594   <refsect2 id="{@id case02}">
7595    <title>Case 02: LEFT OUTER JOIN</title>
7596    <para>
7597     Case 02 will teach you:
7598     <itemizedlist>
7599      <listitem>
7600       The basics of {@link XML_Query2XML::getXML()}.
7601      </listitem>
7602      <listitem>
7603       How to use {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn}.
7604      </listitem>
7605      <listitem>
7606       How to use {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_roottag}.
7607      </listitem>
7608      <listitem>
7609       How to use {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_rowtag}.
7610      </listitem>
7611      <listitem>
7612       The basics of {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications}.
7613      </listitem>
7614     </itemizedlist>
7615    </para>
7616    <para>
7617     Once you have to deal with LEFT JOINs and similar "complex" SQL queries, you have to use
7618     {@link XML_Query2XML::getXML()}. The challenge is to get the $options array
7619     (getXML's second argument) right:
7620    </para>
7621    <refsect3 id="{@id case02_php}">
7622     <title>case02.php</title>
7623     <para>
7624     case02.php looks like this:
7625     <programlisting role="php">
7626     <![CDATA[
7627<?php
7628require_once 'XML/Query2XML.php';
7629require_once 'MDB2.php';
7630$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
7631$dom = $query2xml->getXML(
7632    "SELECT
7633        *
7634     FROM
7635        artist
7636        LEFT JOIN album ON album.artist_id = artist.artistid",
7637    array(
7638        'rootTag' => 'music_library',
7639        'rowTag' => 'artist',
7640        'idColumn' => 'artistid',
7641        'elements' => array(
7642            'artistid',
7643            'name',
7644            'birth_year',
7645            'birth_place',
7646            'genre',
7647            'albums' => array(
7648                'rootTag' => 'albums',
7649                'rowTag' => 'album',
7650                'idColumn' => 'albumid',
7651                'elements' => array(
7652                    'albumid',
7653                    'title',
7654                    'published_year',
7655                    'comment'
7656                )
7657            )
7658        )
7659    )
7660);
7661
7662header('Content-Type: application/xml');
7663
7664$dom->formatOutput = true;
7665print $dom->saveXML();
7666?>
7667      ]]>
7668      </programlisting>
7669      getXML's first argument is the SQL query as a string. The second is the $options array.
7670      Let's go through all options step by step:
7671      <itemizedlist>
7672       <listitem>
7673  	    <emphasis>'rootTag'</emphasis>: we use 'music_library' as the name for the root tag.
7674  	   </listitem>
7675  	   <listitem>
7676  	    <emphasis>'rowTag'</emphasis>: all tags on the first level will represent an artis; therefore we name it 'artis'.
7677  	   </listitem>
7678  	   <listitem>
7679  	    <emphasis>'idColumn'</emphasis>: we set this to 'artistid' because this is the primary key column of the table
7680  	    artist. At least at this point it should be clear why this option is essential: our LEFT JOIN
7681  	    returns something like this:
7682        <![CDATA[
7683artistid  name                birth_year  albumid album_title     album_published
7684-------------------------------------------------------------------------------
76851          Curtis Mayfield    1920        1        New World Order  1990
76861          Curtis Mayfield    1920        2        Curtis           1970
76872          Isaac Hayes        1942        3        Shaft            1972
76883          Ray Charles        1930        NULL     NULL             NULL
7689        ]]>
7690        As we want for every artist only a single tag we need to identify each artist by the primary
7691        key of the table artist. Note that there is a second record for Curtis Mayfield (related to
7692        the album Curtis), but we don't want something like
7693        <programlisting role="tutorial">
7694        <![CDATA[
7695<artist>
7696  <name>Curtis Mayfield</name>
7697  <album>
7698    <name>New World Order</name>
7699  </album>
7700</artist>
7701<artist>
7702  <name>Curtis Mayfield</name>
7703  <album>
7704    <name>Curits</name>
7705  </album>
7706</artist>
7707        ]]>
7708        </programlisting>
7709        but rather
7710        <programlisting role="tutorial">
7711        <![CDATA[
7712<artist>
7713  <name>Curtis Mayfield</name>
7714  <albums>
7715    <album>
7716     <name>New World Order</name>
7717    </album>
7718    <albums>
7719     <name>Curtis</name>
7720    </albums>
7721  </albums>
7722</artist>
7723        ]]>
7724        </programlisting>
7725        This is achieved by telling XML_Query2XML which entity to focus on (on this level): the artist, as it
7726        is identified by the artist table's primary key. Once XML_Query2XML get's to the second Curtis Mayfield
7727        record, it can tell by the artistid 1 that an XML element was already created for this artist.
7728  	   </listitem>
7729  	   <listitem>
7730  	    <emphasis>'elements'</emphasis>: this is a (not necessarily associative) array of child elements.
7731  	    <itemizedlist>
7732  	     <listitem>
7733  	      <emphasis>'artistid', 'name', 'birth_year', 'birth_place', 'genre'</emphasis>: These are simple element
7734  	      specifications. The column name will be used as the element name and the element will only
7735  	      contain the column's value.
7736  	     </listitem>
7737  	     <listitem>
7738  	      <emphasis>'artists'</emphasis>: here we use a complex element specification. It is an array
7739  	      that can have all the options that can be present on the root level.
7740  	      <itemizedlist>
7741  	       <listitem>
7742  	        <emphasis>'rootTag'</emphasis>: we want all albums to be contained in a singel element named
7743  	        'albums'.
7744  	       </listitem>
7745  	       <listitem>
7746  	        <emphasis>'rowTag'</emphasis>: each album should be contained in an element named 'album'.
7747  	       </listitem>
7748  	       <listitem>
7749  	        <emphasis>'idColumn'</emphasis>: here we have to use 'albumid' for the ID column as this
7750  	        is the primary key column for our albums.
7751  	       </listitem>
7752  	       <listitem>
7753  	        <emphasis>'elements'</emphasis>: this time, a simple element specification is all we need:
7754  	        'albumid', 'title', 'published_year', 'comment'
7755  	       </listitem>
7756  	      </itemizedlist>
7757  	     </listitem>
7758  	    </itemizedlist>
7759  	   </listitem>
7760  	  </itemizedlist>
7761     </para>
7762    </refsect3>
7763    <refsect3 id="{@id case02_xml}">
7764     <title>case02.xml</title>
7765     <para>
7766     The resulting XML data looks like this:
7767     <programlisting role="tutorial">
7768     <![CDATA[
7769<?xml version="1.0" encoding="UTF-8"?>
7770<music_library>
7771  <artist>
7772    <artistid>1</artistid>
7773    <name>Curtis Mayfield</name>
7774    <birth_year>1920</birth_year>
7775    <birth_place>Chicago</birth_place>
7776    <genre>Soul</genre>
7777    <albums>
7778      <album>
7779        <albumid>1</albumid>
7780        <title>New World Order</title>
7781        <published_year>1990</published_year>
7782        <comment>the best ever!</comment>
7783      </album>
7784      <album>
7785        <albumid>2</albumid>
7786        <title>Curtis</title>
7787        <published_year>1970</published_year>
7788        <comment>that man's got somthin' to say</comment>
7789      </album>
7790    </albums>
7791  </artist>
7792  <artist>
7793    <artistid>2</artistid>
7794    <name>Isaac Hayes</name>
7795    <birth_year>1942</birth_year>
7796    <birth_place>Tennessee</birth_place>
7797    <genre>Soul</genre>
7798    <albums>
7799      <album>
7800        <albumid>3</albumid>
7801        <title>Shaft</title>
7802        <published_year>1972</published_year>
7803        <comment>he's the man</comment>
7804      </album>
7805    </albums>
7806  </artist>
7807  <artist>
7808    <artistid>3</artistid>
7809    <name>Ray Charles</name>
7810    <birth_year>1930</birth_year>
7811    <birth_place>Mississippi</birth_place>
7812    <genre>Country and Soul</genre>
7813    <albums />
7814  </artist>
7815</music_library>
7816     ]]>
7817     </programlisting>
7818     </para>
7819    </refsect3>
7820    <refsect3 id="{@id case02_debug_php}">
7821     <title>case02_debug.php</title>
7822     <para>
7823     {@link XML_Query2XML::getXML()} allows us to debug and to profile the operation.
7824     <programlisting role="php">
7825     <![CDATA[
7826<?php
7827require_once 'XML/Query2XML.php';
7828require_once 'MDB2.php';
7829$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
7830
7831require_once 'Log.php';
7832$debugLogger = Log::factory('file', 'case02.log', 'Query2XML');
7833$query2xml->enableDebugLog($debugLogger);
7834
7835$query2xml->startProfiling();
7836
7837
7838$dom = $query2xml->getXML(
7839    "SELECT
7840        *
7841     FROM
7842        artist
7843        LEFT JOIN album ON album.artist_id = artist.artistid",
7844    array(
7845        'rootTag' => 'music_library',
7846        'rowTag' => 'artist',
7847        'idColumn' => 'artistid',
7848        'elements' => array(
7849            'artistid',
7850            'name',
7851            'birth_year',
7852            'birth_place',
7853            'genre',
7854            'albums' => array(
7855                'rootTag' => 'albums',
7856                'rowTag' => 'album',
7857                'idColumn' => 'albumid',
7858                'elements' => array(
7859                    'albumid',
7860                    'title',
7861                    'published_year',
7862                    'comment'
7863                )
7864            )
7865        )
7866    )
7867);
7868
7869header('Content-Type: application/xml');
7870
7871$dom->formatOutput = true;
7872print $dom->saveXML();
7873
7874require_once 'File.php';
7875$fp = new File();
7876$fp->write('case02.profile', $query2xml->getProfile(), FILE_MODE_WRITE);
7877?>
7878     ]]>
7879     </programlisting>
7880     The lines 5-7 do the debugging, line 9 and 50-52 the profiling. This will create
7881     case02.log and case02.profile.
7882     </para>
7883    </refsect3>
7884    <refsect3 id="{@id case02_log}">
7885     <title>case02.log</title>
7886     <para>
7887     The format of a debug log file is documented at {@tutorial XML_Query2XML.pkg#debugging}.
7888     Our debug log shows that the query runs once.
7889     <![CDATA[
7890Feb 11 16:10:36 Query2XML [info] QUERY: SELECT
7891        *
7892     FROM
7893        artist
7894        LEFT JOIN album ON album.artist_id = artist.artistid
7895Feb 11 16:10:36 Query2XML [info] DONE
7896     ]]>
7897     </para>
7898    </refsect3>
7899    <refsect3 id="{@id case02_profile}">
7900     <title>case02.profile</title>
7901     <para>
7902     Profiling is essential for performance tuning. The format of the output is documented under
7903     {@tutorial XML_Query2XML.pkg#profiling.getprofile}. Our profile looks like this:
7904     <![CDATA[
7905FROM_DB FROM_CACHE CACHED AVG_DURATION DURATION_SUM SQL
79061       0          false  0.0056409835 0.0056409835 SELECT
7907        *
7908     FROM
7909        artist
7910        LEFT JOIN album ON album.artist_id = artist.artistid
7911
7912TOTAL_DURATION: 0.06843900680542
7913DB_DURATION:    0.015194892883301
7914     ]]>
7915     </para>
7916    </refsect3>
7917    The value "false" in the CACHED column tells us that no caching was performed. As we can see
7918    in the FROM_DB column, the query ran once.
7919   </refsect2>
7920
7921   <refsect2 id="{@id case03}">
7922    <title>Case 03: Two SELECTs instead of a LEFT OUTER JOIN</title>
7923    <para>
7924     Case 03 will teach you:
7925     <itemizedlist>
7926      <listitem>
7927       How to use {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql}.
7928      </listitem>
7929      <listitem>
7930       Prevent SQL injection.
7931      </listitem>
7932     </itemizedlist>
7933    </para>
7934    <para>
7935     When your query is getting bigger and bigger (say, 6 or more JOINs) you might want to (or have
7936     to, if the maximum number of fields your RDBMS will return has been reached) split up the big join into multiple
7937     smaller joins. Here we will just do exactly the same as in
7938     {@tutorial XML_Query2XML.pkg#casestudies.case02}, but with two separate SELECT queries.
7939    </para>
7940    <refsect3 id="{@id case03_php}">
7941     <title>case03.php</title>
7942     <para>
7943     case03.php looks like this:
7944     <programlisting role="php">
7945     <![CDATA[
7946<?php
7947require_once 'XML/Query2XML.php';
7948require_once 'MDB2.php';
7949$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
7950$dom = $query2xml->getXML(
7951    "SELECT
7952        *
7953     FROM
7954        artist",
7955    array(
7956        'rootTag' => 'music_library',
7957        'rowTag' => 'artist',
7958        'idColumn' => 'artistid',
7959        'elements' => array(
7960            'artistid',
7961            'name',
7962            'birth_year',
7963            'birth_place',
7964            'genre',
7965            'albums' => array(
7966                'sql' => array(
7967                    'data' => array(
7968                        'artistid'
7969                    ),
7970                    'query' => 'SELECT * FROM album WHERE artist_id = ?'
7971                ),
7972                'rootTag' => 'albums',
7973                'rowTag' => 'album',
7974                'idColumn' => 'albumid',
7975                'elements' => array(
7976                    'albumid',
7977                    'title',
7978                    'published_year',
7979                    'comment'
7980                )
7981            )
7982        )
7983    )
7984);
7985
7986header('Content-Type: application/xml');
7987
7988$dom->formatOutput = true;
7989print $dom->saveXML();
7990?>
7991      ]]>
7992      </programlisting>
7993      We won't go over every option as we did for {@tutorial XML_Query2XML.pkg#casestudies.case02.case02_php}.
7994      We will only focus on the differences. The first argument to {@link XML_Query2XML::getXML()}
7995      is a simple SELECT query over one table. What also changed is the complex element
7996      specification of 'albums'. It has a new option:
7997      <itemizedlist>
7998  	   <listitem>
7999  	    <emphasis>'sql'</emphasis>: ['sql']['query'] will be executed for every record retrieved with
8000  	    the first SELECT query. In our case, we want all albums for the current artist record.
8001  	    We use a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql.complexqueryspecification} here:
8002  	    ['sql']['data'] contains an array of values that will ultimately be passed to the database abstraction
8003  	    layer's execute() method. As we do not prefix 'artistid' with anything it is interpreted as a column
8004  	    name (of the parent record) - which is just what we want. This completely prevents SQL injection attacks.
8005  	   </listitem>
8006      </itemizedlist>
8007     </para>
8008    </refsect3>
8009    <refsect3 id="{@id case03_xml}">
8010     <title>case03.xml</title>
8011     <para>
8012     The resulting XML data looks exactly like {@tutorial XML_Query2XML.pkg#casestudies.case02.case02_xml}:
8013     <programlisting role="tutorial">
8014     <![CDATA[
8015<?xml version="1.0" encoding="UTF-8"?>
8016<music_library>
8017  <artist>
8018    <artistid>1</artistid>
8019    <name>Curtis Mayfield</name>
8020    <birth_year>1920</birth_year>
8021    <birth_place>Chicago</birth_place>
8022    <genre>Soul</genre>
8023    <albums>
8024      <album>
8025        <albumid>1</albumid>
8026        <title>New World Order</title>
8027        <published_year>1990</published_year>
8028        <comment>the best ever!</comment>
8029      </album>
8030      <album>
8031        <albumid>2</albumid>
8032        <title>Curtis</title>
8033        <published_year>1970</published_year>
8034        <comment>that man's got somthin' to say</comment>
8035      </album>
8036    </albums>
8037  </artist>
8038  <artist>
8039    <artistid>2</artistid>
8040    <name>Isaac Hayes</name>
8041    <birth_year>1942</birth_year>
8042    <birth_place>Tennessee</birth_place>
8043    <genre>Soul</genre>
8044    <albums>
8045      <album>
8046        <albumid>3</albumid>
8047        <title>Shaft</title>
8048        <published_year>1972</published_year>
8049        <comment>he's the man</comment>
8050      </album>
8051    </albums>
8052  </artist>
8053  <artist>
8054    <artistid>3</artistid>
8055    <name>Ray Charles</name>
8056    <birth_year>1930</birth_year>
8057    <birth_place>Mississippi</birth_place>
8058    <genre>Country and Soul</genre>
8059    <albums />
8060  </artist>
8061</music_library>
8062     ]]>
8063     </programlisting>
8064     </para>
8065    </refsect3>
8066    <refsect3 id="{@id case03_debug_php}">
8067     <title>case03_debug.php</title>
8068     <para>
8069     case03_debug.php is similar to {@tutorial XML_Query2XML.pkg#casestudies.case02.case02_debug_php}:
8070     <programlisting role="php">
8071     <![CDATA[
8072<?php
8073require_once 'XML/Query2XML.php';
8074require_once 'MDB2.php';
8075$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
8076
8077require_once 'Log.php';
8078$debugLogger = Log::factory('file', 'case03.log', 'Query2XML');
8079$query2xml->enableDebugLog($debugLogger);
8080
8081$query2xml->startProfiling();
8082
8083
8084$dom = $query2xml->getXML(
8085    "SELECT
8086        *
8087     FROM
8088        artist",
8089    array(
8090        'rootTag' => 'music_library',
8091        'rowTag' => 'artist',
8092        'idColumn' => 'artistid',
8093        'elements' => array(
8094            'artistid',
8095            'name',
8096            'birth_year',
8097            'birth_place',
8098            'genre',
8099            'albums' => array(
8100                'sql' => array(
8101                    'data' => array(
8102                        'artistid'
8103                    ),
8104                    'query' => 'SELECT * FROM album WHERE artist_id = ?'
8105                ),
8106                'rootTag' => 'albums',
8107                'rowTag' => 'album',
8108                'idColumn' => 'albumid',
8109                'elements' => array(
8110                    'albumid',
8111                    'title',
8112                    'published_year',
8113                    'comment'
8114                )
8115            )
8116        )
8117    )
8118);
8119
8120header('Content-Type: application/xml');
8121
8122$dom->formatOutput = true;
8123print $dom->saveXML();
8124
8125require_once 'File.php';
8126$fp = new File();
8127$fp->write('case03.profile', $query2xml->getProfile(), FILE_MODE_WRITE);
8128?>
8129     ]]>
8130     </programlisting>
8131     The lines 6-8 do the debugging, line 10 and 54-56 the profiling. This will create
8132     case03.log and case03.profile.
8133     </para>
8134    </refsect3>
8135    <refsect3 id="{@id case03_log}">
8136     <title>case03.log</title>
8137     <para>
8138     The format of a debug log file is documented at {@tutorial XML_Query2XML.pkg#debugging}.
8139     Our debug log now contains 4 queries:
8140     <![CDATA[
8141Apr 18 19:00:20 Query2XML [info] QUERY: SELECT
8142        *
8143     FROM
8144        artist
8145     ORDER BY
8146        artistid
8147Apr 18 19:00:20 Query2XML [info] DONE
8148Apr 18 19:00:20 Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:1
8149Apr 18 19:00:20 Query2XML [info] DONE
8150Apr 18 19:00:20 Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:2
8151Apr 18 19:00:20 Query2XML [info] DONE
8152Apr 18 19:00:20 Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:3
8153Apr 18 19:00:20 Query2XML [info] DONE
8154     ]]>
8155     </para>
8156     The debug log shows what we expected: the first SELECT over the artist table runs once
8157     and the SELECT over the album table runs three times (once for every record found in
8158     the artist table). As the log shows no 'CACHING' entries we also know that no cashing
8159     was performed ({@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.cached}
8160     was not set to true).
8161    </refsect3>
8162    <refsect3 id="{@id case03_profile}">
8163     <title>case03.profile</title>
8164     <para>
8165     Profiling is essential for performance tuning. The format of the output is documented under
8166     {@tutorial XML_Query2XML.pkg#profiling.getprofile}. Our profile looks like this:
8167     <![CDATA[
8168FROM_DB FROM_CACHE CACHED AVG_DURATION DURATION_SUM SQL
81691       0          false  0.0030851364 0.0030851364 SELECT
8170        *
8171     FROM
8172        artist
81733       0          false  0.0035093625 0.0105280876 SELECT * FROM album WHERE artist_id = ?
8174
8175TOTAL_DURATION: 0.090610980987549
8176DB_DURATION:    0.024358034133911
8177     ]]>
8178     If you compare our DB_DURATION value to the one in
8179     {@tutorial XML_Query2XML.pkg#casestudies.case02.case02_profile} you will see that the single LEFT JOIN
8180     was faster than the four separate queries.
8181     </para>
8182    </refsect3>
8183   </refsect2>
8184   <refsect2 id="{@id case04}">
8185    <title>Case 04: Case 03 with custom tag names, attributes, merge_selective and more</title>
8186    <para>
8187     Case 04 will teach you:
8188     <itemizedlist>
8189      <listitem>
8190       How to use alternative tag names.
8191      </listitem>
8192      <listitem>
8193       How to use callbacks with the '#' prefix.
8194      </listitem>
8195      <listitem>
8196       How to define static node and attribute values using the ':' prefix.
8197      </listitem>
8198      <listitem>
8199       How to prevent the creation of a root tag, using {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_roottag}.
8200      </listitem>
8201     </itemizedlist>
8202    </para>
8203    <para>
8204     This is very much like {@tutorial XML_Query2XML.pkg#casestudies.case03}, but with a demonstration
8205     of some splecial features.
8206    </para>
8207    <para>
8208     In contrast to Case 03 we want:
8209     <itemizedlist>
8210      <listitem>
8211       all tag names should be uppercase
8212      </listitem>
8213      <listitem>
8214       an additional child tag for ARTIST: BIRTH_YEAR_TWO_DIGIT that will contain
8215       only the last two digets of BIRTH_YERAR
8216      </listitem>
8217      <listitem>
8218       the ARTIST tag should have two attributes: ARTISTID and MAINTAINER set to the static value
8219       'Lukas Feiler'.
8220      </listitem>
8221      <listitem>
8222       the ALBUM tags should not be contained in an ALBUMS tag but should be directly within the
8223       ARTIST tag, e.g.
8224       <programlisting role="tutorial">
8225       <![CDATA[
8226<artist>
8227  ...
8228  <album>...</album>
8229  <album>...</album>
8230</artist>
8231       ]]>
8232       </programlisting>
8233       instead of
8234       <programlisting role="tutorial">
8235       <![CDATA[
8236<artist>
8237  ...
8238  <album>
8239    <album>...</album>
8240    <album>...</album>
8241  </albums>
8242</artist>
8243       ]]>
8244       </programlisting>
8245      </listitem>
8246      <listitem>
8247       the ALBUM tag should have one attribute: ALBUMID
8248      </listitem>
8249      <listitem>
8250       the ALBUM tag should have an additional child tag: GENRE; note that this is a column of
8251       the table artist!
8252      </listitem>
8253     </itemizedlist>
8254    </para>
8255    <refsect3 id="{@id case04_php}">
8256     <title>case04.php</title>
8257     <para>
8258     case04.php looks like this:
8259     <programlisting role="php">
8260     <![CDATA[
8261<?php
8262require_once 'XML/Query2XML.php';
8263require_once 'MDB2.php';
8264$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
8265$dom = $query2xml->getXML(
8266    "SELECT
8267        *
8268     FROM
8269        artist",
8270    array(
8271        'rootTag' => 'MUSIC_LIBRARY',
8272        'rowTag' => 'ARTIST',
8273        'idColumn' => 'artistid',
8274        'elements' => array(
8275            'NAME' => 'name',
8276            'BIRTH_YEAR' => 'birth_year',
8277            'BIRTH_YEAR_TWO_DIGIT' => "#firstTwoChars()",
8278            'BIRTH_PLACE' => 'birth_place',
8279            'GENRE' => 'genre',
8280            'albums' => array(
8281                'sql' => array(
8282                    'data' => array(
8283                        'artistid'
8284                    ),
8285                    'query' => 'SELECT * FROM album WHERE artist_id = ?'
8286                ),
8287                'sql_options' => array(
8288                    'merge_selective' => array('genre')
8289                ),
8290                'rootTag' => '',
8291                'rowTag' => 'ALBUM',
8292                'idColumn' => 'albumid',
8293                'elements' => array(
8294                    'TITLE' => 'title',
8295                    'PUBLISHED_YEAR' => 'published_year',
8296                    'COMMENT' => 'comment',
8297                    'GENRE' => 'genre'
8298                ),
8299                'attributes' => array(
8300                    'ALBUMID' => 'albumid'
8301                )
8302            )
8303        ),
8304        'attributes' => array(
8305            'ARTISTID' => 'artistid',
8306            'MAINTAINER' => ':Lukas Feiler'
8307        )
8308    )
8309);
8310
8311header('Content-Type: application/xml');
8312
8313$dom->formatOutput = true;
8314print $dom->saveXML();
8315
8316function firstTwoChars($record)
8317{
8318    return substr($record['birth_year'], 2);
8319}
8320?>
8321     ]]>
8322     </programlisting>
8323     Let's go over the changes:
8324     <itemizedlist>
8325      <listitem>
8326       as we wanted all tag names uppercased, all elements were specified like
8327       <![CDATA[
8328       'TAG_NAME' => 'column_name'
8329       ]]>
8330       This is because XML_Query2XML will use the array key as the tag name if it is not
8331       numeric.
8332      </listitem>
8333      <listitem>
8334       BIRTH_YEAR_TWO_DIGIT was specified as
8335       <![CDATA[
8336       'BIRTH_YEAR_TWO_DIGIT' => "#firstTwoChars()",
8337       ]]>
8338       The prefix '#' tells XML_Query2XML that the following string is a function to call.
8339       The current record is passed as argument to that function. firstTwoChars in our case
8340       returns the first two characters of the string stored in $record['birth_year'].
8341      </listitem>
8342      <listitem>
8343       the ARTIST tag now has two attributes: they are specified in an array using
8344       {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes the 'attribute' option}.
8345       Both use a {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes.simple_attribute_specifications}.
8346       The ARTISTID attribute simply uses the column name 'artistid'. In the MAINTAINER attribute
8347       we specify a static value. This is done by prefixing it by a colon (':'). Without the
8348       colon, XML_Query2XML would treat it as a column name.
8349      </listitem>
8350      <listitem>
8351       the ALBUM tags are now not contained in an ALBUMS tag anymore but directly within the
8352       ARTIST tag; this is done by setting 'rootTag' to an empty string. Alternatively we just could
8353       have omitted the rootTag option.
8354      </listitem>
8355      <listitem>
8356       ALBUM's new attribute ALBUMID is specified using
8357       {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_attributes the 'attribute' option}.
8358      </listitem>
8359      <listitem>
8360       ALBUM's new child tag GENRE contains the value of a column of the table artist.
8361       If we had used the sql default options we would have seen a
8362       XML_Query2XML_ConfigException with the following message:
8363       <![CDATA[
8364[elements][albums][elements][GENRE]: The column "genre" was not found in the result set.
8365       ]]>
8366       This is because the result of the first SQL query is not available at this level. As far as
8367       this level is concerned, it got
8368       overwritten with the result of our second query. But as we need both to be present, we
8369       selectively merger them using {@link http://www.php.net/array_merge array_merge()}.
8370       This is achieved by setting
8371       {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.merge_selective the sql_option 'merge_selective'}
8372       to an array that contains
8373       all columns of the parent record that should also be available on the current level.
8374       As we do not have any confilicting column names, we just leave
8375       {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.merge_master the sql_option 'merge_master'}
8376       set to false which means that the results of the parent level's query is the 'master', i.e. overwrite
8377       the results from the query on this level.
8378      </listitem>
8379     </itemizedlist>
8380     </para>
8381    </refsect3>
8382    <refsect3 id="{@id case04_xml}">
8383     <title>case04.xml</title>
8384     <para>
8385     The resulting XML data looks like this:
8386     <programlisting role="tutorial">
8387     <![CDATA[
8388<?xml version="1.0" encoding="UTF-8"?>
8389<MUSIC_LIBRARY>
8390  <ARTIST ARTISTID="1" MAINTAINER="Lukas Feiler">
8391    <NAME>Curtis Mayfield</NAME>
8392    <BIRTH_YEAR>1920</BIRTH_YEAR>
8393    <BIRTH_YEAR_TWO_DIGIT>20</BIRTH_YEAR_TWO_DIGIT>
8394    <BIRTH_PLACE>Chicago</BIRTH_PLACE>
8395    <GENRE>Soul</GENRE>
8396    <ALBUM ALBUMID="1">
8397      <TITLE>New World Order</TITLE>
8398      <PUBLISHED_YEAR>1990</PUBLISHED_YEAR>
8399      <COMMENT>the best ever!</COMMENT>
8400      <GENRE>Soul</GENRE>
8401    </ALBUM>
8402    <ALBUM ALBUMID="2">
8403      <TITLE>Curtis</TITLE>
8404      <PUBLISHED_YEAR>1970</PUBLISHED_YEAR>
8405      <COMMENT>that man's got somthin' to say</COMMENT>
8406      <GENRE>Soul</GENRE>
8407    </ALBUM>
8408  </ARTIST>
8409  <ARTIST ARTISTID="2" MAINTAINER="Lukas Feiler">
8410    <NAME>Isaac Hayes</NAME>
8411    <BIRTH_YEAR>1942</BIRTH_YEAR>
8412    <BIRTH_YEAR_TWO_DIGIT>42</BIRTH_YEAR_TWO_DIGIT>
8413    <BIRTH_PLACE>Tennessee</BIRTH_PLACE>
8414    <GENRE>Soul</GENRE>
8415    <ALBUM ALBUMID="3">
8416      <TITLE>Shaft</TITLE>
8417      <PUBLISHED_YEAR>1972</PUBLISHED_YEAR>
8418      <COMMENT>he's the man</COMMENT>
8419      <GENRE>Soul</GENRE>
8420    </ALBUM>
8421  </ARTIST>
8422  <ARTIST ARTISTID="3" MAINTAINER="Lukas Feiler">
8423    <NAME>Ray Charles</NAME>
8424    <BIRTH_YEAR>1930</BIRTH_YEAR>
8425    <BIRTH_YEAR_TWO_DIGIT>30</BIRTH_YEAR_TWO_DIGIT>
8426    <BIRTH_PLACE>Mississippi</BIRTH_PLACE>
8427    <GENRE>Country and Soul</GENRE>
8428  </ARTIST>
8429</MUSIC_LIBRARY>
8430     ]]>
8431     </programlisting>
8432     </para>
8433    </refsect3>
8434    <refsect3 id="{@id case04_debug_php}">
8435     <title>case04_debug.php</title>
8436     <para>
8437     case04_debug.php reveals nothing new compared to
8438     {@tutorial XML_Query2XML.pkg#casestudies.case03.case03_debug_php} but it's included for
8439     completeness.
8440     <programlisting role="php">
8441     <![CDATA[
8442<?php
8443require_once 'XML/Query2XML.php';
8444require_once 'MDB2.php';
8445$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
8446
8447require_once 'Log.php';
8448$debugLogger = Log::factory('file', 'case04.log', 'Query2XML');
8449$query2xml->enableDebugLog($debugLogger);
8450
8451$query2xml->startProfiling();
8452
8453
8454$dom = $query2xml->getXML(
8455    "SELECT
8456        *
8457     FROM
8458        artist",
8459    array(
8460        'rootTag' => 'MUSIC_LIBRARY',
8461        'rowTag' => 'ARTIST',
8462        'idColumn' => 'artistid',
8463        'elements' => array(
8464            'NAME' => 'name',
8465            'BIRTH_YEAR' => 'birth_year',
8466            'BIRTH_YEAR_TWO_DIGIT' => "#firstTwoChars()",
8467            'BIRTH_PLACE' => 'birth_place',
8468            'GENRE' => 'genre',
8469            'albums' => array(
8470                'sql' => array(
8471                    'data' => array(
8472                        'artistid'
8473                    ),
8474                    'query' => 'SELECT * FROM album WHERE artist_id = ?'
8475                ),
8476                'sql_options' => array(
8477                    'merge_selective' => array('genre')
8478                ),
8479                'rootTag' => '',
8480                'rowTag' => 'ALBUM',
8481                'idColumn' => 'albumid',
8482                'elements' => array(
8483                    'TITLE' => 'title',
8484                    'PUBLISHED_YEAR' => 'published_year',
8485                    'COMMENT' => 'comment',
8486                    'GENRE' => 'genre'
8487                ),
8488                'attributes' => array(
8489                    'ALBUMID' => 'albumid'
8490                )
8491            )
8492        ),
8493        'attributes' => array(
8494            'ARTISTID' => 'artistid',
8495            'MAINTAINER' => ':Lukas Feiler'
8496        )
8497    )
8498);
8499
8500header('Content-Type: application/xml');
8501
8502$dom->formatOutput = true;
8503print $dom->saveXML();
8504
8505require_once 'File.php';
8506$fp = new File();
8507$fp->write('case04.profile', $query2xml->getProfile(), FILE_MODE_WRITE);
8508
8509function firstTwoChars($record)
8510{
8511    return substr($record['birth_year'], 2);
8512}
8513?>
8514     ]]>
8515     </programlisting>
8516     The lines 6-8 do the debugging, line 10 and 64-66 the profiling. This will create
8517     case04.log and case04.profile.
8518     </para>
8519    </refsect3>
8520    <refsect3 id="{@id case04_log}">
8521     <title>case04.log</title>
8522     <para>
8523     The format of a debug log file is documented at {@tutorial XML_Query2XML.pkg#debugging}.
8524     Our debug log now contains 4 queries and is exactly the same as
8525     {@tutorial XML_Query2XML.pkg#casestudies.case03.case03_log}:
8526     <![CDATA[
8527Apr 18 19:01:25 Query2XML [info] QUERY: SELECT
8528        *
8529     FROM
8530        artist
8531     ORDER BY
8532        artistid
8533Apr 18 19:01:25 Query2XML [info] DONE
8534Apr 18 19:01:25 Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:1
8535Apr 18 19:01:25 Query2XML [info] DONE
8536Apr 18 19:01:25 Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:2
8537Apr 18 19:01:25 Query2XML [info] DONE
8538Apr 18 19:01:25 Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:3
8539Apr 18 19:01:25 Query2XML [info] DONE
8540     ]]>
8541     </para>
8542    </refsect3>
8543    <refsect3 id="{@id case04_profile}">
8544     <title>case04.profile</title>
8545     <para>
8546     Profiling is essential for performance tuning. The format of the output is documented under
8547     {@tutorial XML_Query2XML.pkg#profiling.getprofile}. Our profile looks exactly like
8548     {@tutorial XML_Query2XML.pkg#casestudies.case03.case03_profile}:
8549     <![CDATA[
8550FROM_DB FROM_CACHE CACHED AVG_DURATION DURATION_SUM SQL
85511       0          false  0.0034000873 0.0034000873 SELECT
8552        *
8553     FROM
8554        artist
85553       0          false  0.0035278797 0.0105836391 SELECT * FROM album WHERE artist_id = ?
8556
8557TOTAL_DURATION: 0.081415891647339
8558DB_DURATION:    0.026465892791748
8559     ]]>
8560     </para>
8561    </refsect3>
8562   </refsect2>
8563   <refsect2 id="{@id case05}">
8564    <title>Case 05: three LEFT OUTER JOINs</title>
8565    <para>
8566     Case 05 will teach you:
8567     <itemizedlist>
8568      <listitem>
8569       How to write {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications}
8570       that in turn contain {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications}.
8571      </listitem>
8572      <listitem>
8573       How to use static methods for callbacks with the '#' prefix.
8574      </listitem>
8575      <listitem>
8576       How to modify the {@link http://www.php.net/manual/en/ref.dom.php#dom.class.domdocument DOMDocument}
8577       instance returned by getXML().
8578      </listitem>
8579     </itemizedlist>
8580    </para>
8581    <para>
8582     Case 05 is a demonstration of complex element specifications.
8583    </para>
8584    <refsect3 id="{@id case05_php}">
8585     <title>case05.php</title>
8586     <para>
8587     case05.php looks like this:
8588     <programlisting role="php">
8589     <![CDATA[
8590<?php
8591require_once 'XML/Query2XML.php';
8592require_once 'MDB2.php';
8593$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
8594$dom = $query2xml->getXML(
8595    "SELECT
8596         *
8597     FROM
8598         customer c
8599         LEFT JOIN sale s ON c.customerid = s.customer_id
8600         LEFT JOIN album al ON s.album_id = al.albumid
8601         LEFT JOIN artist ar ON al.artist_id = ar.artistid",
8602    array(
8603        'rootTag' => 'music_store',
8604        'rowTag' => 'customer',
8605        'idColumn' => 'customerid',
8606        'elements' => array(
8607            'customerid',
8608            'first_name',
8609            'last_name',
8610            'email',
8611            'sales' => array(
8612                'rootTag' => 'sales',
8613                'rowTag' => 'sale',
8614                'idColumn' => 'saleid',
8615                'elements' => array(
8616                    'saleid',
8617                    'timestamp',
8618                    'date' => '#Callbacks::getFirstWord()',
8619                    'time' => '#Callbacks::getSecondWord()',
8620                    'album' => array(
8621                        'rootTag' => '',
8622                        'rowTag' => 'album',
8623                        'idColumn' => 'albumid',
8624                        'elements' => array(
8625                            'albumid',
8626                            'title',
8627                            'published_year',
8628                            'comment',
8629                            'artist' => array(
8630                                'rootTag' => '',
8631                                'rowTag' => 'artist',
8632                                'idColumn' => 'artistid',
8633                                'elements' => array(
8634                                    'artistid',
8635                                    'name',
8636                                    'birth_year',
8637                                    'birth_place',
8638                                    'genre'
8639                                ) //artist elements
8640                            ) //artist array
8641                        ) //album elements
8642                    ) //album array
8643                ) //sales elements
8644            ) //sales array
8645        ) //root elements
8646    ) //root
8647); //getXML method call
8648
8649$root = $dom->firstChild;
8650$root->setAttribute('date_generated', date("Y-m-d\TH:i:s", 1124801570));
8651
8652header('Content-Type: application/xml');
8653
8654$dom->formatOutput = true;
8655print $dom->saveXML();
8656
8657class Callbacks
8658{
8659    function getFirstWord($record)
8660    {
8661        return substr($record['timestamp'], 0, strpos($record['timestamp'], ' '));
8662    }
8663
8664    function getSecondWord($record)
8665    {
8666        return substr($record['timestamp'], strpos($record['timestamp'], ' ') + 1);
8667    }
8668}
8669?>
8670     ]]>
8671     </programlisting>
8672     ['elements']['sales']['elements']['date'] and ['time'] contain portions of the timestamp column.
8673     Also note that a separate call to DOMNode::setAttribute() is used to
8674     set the attribute date_generated in the root element.
8675     </para>
8676    </refsect3>
8677    <refsect3 id="{@id case05_xml}">
8678     <title>case05.xml</title>
8679     <para>
8680     The resulting XML data looks like this:
8681     <programlisting role="tutorial">
8682     <![CDATA[
8683<?xml version="1.0" encoding="UTF-8"?>
8684<music_store date_generated="2005-08-23T14:52:50">
8685  <customer>
8686    <customerid>1</customerid>
8687    <first_name>Jane</first_name>
8688    <last_name>Doe</last_name>
8689    <email>jane.doe@example.com</email>
8690    <sales>
8691      <sale>
8692        <saleid>1</saleid>
8693        <timestamp>2005-05-25 16:32:00</timestamp>
8694        <date>2005-05-25</date>
8695        <time>16:32:00</time>
8696        <album>
8697          <albumid>1</albumid>
8698          <title>New World Order</title>
8699          <published_year>1990</published_year>
8700          <comment>the best ever!</comment>
8701          <artist>
8702            <artistid>1</artistid>
8703            <name>Curtis Mayfield</name>
8704            <birth_year>1920</birth_year>
8705            <birth_place>Chicago</birth_place>
8706            <genre>Soul</genre>
8707          </artist>
8708        </album>
8709      </sale>
8710      <sale>
8711        <saleid>11</saleid>
8712        <timestamp>2005-05-25 16:23:00</timestamp>
8713        <date>2005-05-25</date>
8714        <time>16:23:00</time>
8715        <album>
8716          <albumid>2</albumid>
8717          <title>Curtis</title>
8718          <published_year>1970</published_year>
8719          <comment>that man's got somthin' to say</comment>
8720          <artist>
8721            <artistid>1</artistid>
8722            <name>Curtis Mayfield</name>
8723            <birth_year>1920</birth_year>
8724            <birth_place>Chicago</birth_place>
8725            <genre>Soul</genre>
8726          </artist>
8727        </album>
8728      </sale>
8729    </sales>
8730  </customer>
8731  <customer>
8732    <customerid>2</customerid>
8733    <first_name>John</first_name>
8734    <last_name>Doe</last_name>
8735    <email>john.doe@example.com</email>
8736    <sales>
8737      <sale>
8738        <saleid>2</saleid>
8739        <timestamp>2005-06-05 12:56:00</timestamp>
8740        <date>2005-06-05</date>
8741        <time>12:56:00</time>
8742        <album>
8743          <albumid>1</albumid>
8744          <title>New World Order</title>
8745          <published_year>1990</published_year>
8746          <comment>the best ever!</comment>
8747          <artist>
8748            <artistid>1</artistid>
8749            <name>Curtis Mayfield</name>
8750            <birth_year>1920</birth_year>
8751            <birth_place>Chicago</birth_place>
8752            <genre>Soul</genre>
8753          </artist>
8754        </album>
8755      </sale>
8756      <sale>
8757        <saleid>16</saleid>
8758        <timestamp>2005-06-05 12:56:12</timestamp>
8759        <date>2005-06-05</date>
8760        <time>12:56:12</time>
8761        <album>
8762          <albumid>3</albumid>
8763          <title>Shaft</title>
8764          <published_year>1972</published_year>
8765          <comment>he's the man</comment>
8766          <artist>
8767            <artistid>2</artistid>
8768            <name>Isaac Hayes</name>
8769            <birth_year>1942</birth_year>
8770            <birth_place>Tennessee</birth_place>
8771            <genre>Soul</genre>
8772          </artist>
8773        </album>
8774      </sale>
8775    </sales>
8776  </customer>
8777  <customer>
8778    <customerid>3</customerid>
8779    <first_name>Susan</first_name>
8780    <last_name>Green</last_name>
8781    <email>susan.green@example.com</email>
8782    <sales>
8783      <sale>
8784        <saleid>3</saleid>
8785        <timestamp>2005-07-10 11:03:00</timestamp>
8786        <date>2005-07-10</date>
8787        <time>11:03:00</time>
8788        <album>
8789          <albumid>1</albumid>
8790          <title>New World Order</title>
8791          <published_year>1990</published_year>
8792          <comment>the best ever!</comment>
8793          <artist>
8794            <artistid>1</artistid>
8795            <name>Curtis Mayfield</name>
8796            <birth_year>1920</birth_year>
8797            <birth_place>Chicago</birth_place>
8798            <genre>Soul</genre>
8799          </artist>
8800        </album>
8801      </sale>
8802      <sale>
8803        <saleid>12</saleid>
8804        <timestamp>2005-07-10 11:56:00</timestamp>
8805        <date>2005-07-10</date>
8806        <time>11:56:00</time>
8807        <album>
8808          <albumid>2</albumid>
8809          <title>Curtis</title>
8810          <published_year>1970</published_year>
8811          <comment>that man's got somthin' to say</comment>
8812          <artist>
8813            <artistid>1</artistid>
8814            <name>Curtis Mayfield</name>
8815            <birth_year>1920</birth_year>
8816            <birth_place>Chicago</birth_place>
8817            <genre>Soul</genre>
8818          </artist>
8819        </album>
8820      </sale>
8821    </sales>
8822  </customer>
8823  <customer>
8824    <customerid>4</customerid>
8825    <first_name>Victoria</first_name>
8826    <last_name>Alt</last_name>
8827    <email>victory.alt@example.com</email>
8828    <sales>
8829      <sale>
8830        <saleid>4</saleid>
8831        <timestamp>2005-07-10 10:03:00</timestamp>
8832        <date>2005-07-10</date>
8833        <time>10:03:00</time>
8834        <album>
8835          <albumid>1</albumid>
8836          <title>New World Order</title>
8837          <published_year>1990</published_year>
8838          <comment>the best ever!</comment>
8839          <artist>
8840            <artistid>1</artistid>
8841            <name>Curtis Mayfield</name>
8842            <birth_year>1920</birth_year>
8843            <birth_place>Chicago</birth_place>
8844            <genre>Soul</genre>
8845          </artist>
8846        </album>
8847      </sale>
8848      <sale>
8849        <saleid>17</saleid>
8850        <timestamp>2005-07-10 10:03:32</timestamp>
8851        <date>2005-07-10</date>
8852        <time>10:03:32</time>
8853        <album>
8854          <albumid>3</albumid>
8855          <title>Shaft</title>
8856          <published_year>1972</published_year>
8857          <comment>he's the man</comment>
8858          <artist>
8859            <artistid>2</artistid>
8860            <name>Isaac Hayes</name>
8861            <birth_year>1942</birth_year>
8862            <birth_place>Tennessee</birth_place>
8863            <genre>Soul</genre>
8864          </artist>
8865        </album>
8866      </sale>
8867    </sales>
8868  </customer>
8869  <customer>
8870    <customerid>5</customerid>
8871    <first_name>Will</first_name>
8872    <last_name>Rippy</last_name>
8873    <email>will.wippy@example.com</email>
8874    <sales>
8875      <sale>
8876        <saleid>5</saleid>
8877        <timestamp>2005-07-10 13:03:00</timestamp>
8878        <date>2005-07-10</date>
8879        <time>13:03:00</time>
8880        <album>
8881          <albumid>1</albumid>
8882          <title>New World Order</title>
8883          <published_year>1990</published_year>
8884          <comment>the best ever!</comment>
8885          <artist>
8886            <artistid>1</artistid>
8887            <name>Curtis Mayfield</name>
8888            <birth_year>1920</birth_year>
8889            <birth_place>Chicago</birth_place>
8890            <genre>Soul</genre>
8891          </artist>
8892        </album>
8893      </sale>
8894      <sale>
8895        <saleid>13</saleid>
8896        <timestamp>2005-07-10 13:12:00</timestamp>
8897        <date>2005-07-10</date>
8898        <time>13:12:00</time>
8899        <album>
8900          <albumid>2</albumid>
8901          <title>Curtis</title>
8902          <published_year>1970</published_year>
8903          <comment>that man's got somthin' to say</comment>
8904          <artist>
8905            <artistid>1</artistid>
8906            <name>Curtis Mayfield</name>
8907            <birth_year>1920</birth_year>
8908            <birth_place>Chicago</birth_place>
8909            <genre>Soul</genre>
8910          </artist>
8911        </album>
8912      </sale>
8913    </sales>
8914  </customer>
8915  <customer>
8916    <customerid>6</customerid>
8917    <first_name>Tim</first_name>
8918    <last_name>Raw</last_name>
8919    <email>tim.raw@example.com</email>
8920    <sales>
8921      <sale>
8922        <saleid>6</saleid>
8923        <timestamp>2005-07-10 14:03:00</timestamp>
8924        <date>2005-07-10</date>
8925        <time>14:03:00</time>
8926        <album>
8927          <albumid>1</albumid>
8928          <title>New World Order</title>
8929          <published_year>1990</published_year>
8930          <comment>the best ever!</comment>
8931          <artist>
8932            <artistid>1</artistid>
8933            <name>Curtis Mayfield</name>
8934            <birth_year>1920</birth_year>
8935            <birth_place>Chicago</birth_place>
8936            <genre>Soul</genre>
8937          </artist>
8938        </album>
8939      </sale>
8940      <sale>
8941        <saleid>18</saleid>
8942        <timestamp>2005-07-10 14:03:52</timestamp>
8943        <date>2005-07-10</date>
8944        <time>14:03:52</time>
8945        <album>
8946          <albumid>3</albumid>
8947          <title>Shaft</title>
8948          <published_year>1972</published_year>
8949          <comment>he's the man</comment>
8950          <artist>
8951            <artistid>2</artistid>
8952            <name>Isaac Hayes</name>
8953            <birth_year>1942</birth_year>
8954            <birth_place>Tennessee</birth_place>
8955            <genre>Soul</genre>
8956          </artist>
8957        </album>
8958      </sale>
8959    </sales>
8960  </customer>
8961  <customer>
8962    <customerid>7</customerid>
8963    <first_name>Nick</first_name>
8964    <last_name>Fallow</last_name>
8965    <email>nick.fallow@example.com</email>
8966    <sales>
8967      <sale>
8968        <saleid>7</saleid>
8969        <timestamp>2005-07-10 15:03:00</timestamp>
8970        <date>2005-07-10</date>
8971        <time>15:03:00</time>
8972        <album>
8973          <albumid>1</albumid>
8974          <title>New World Order</title>
8975          <published_year>1990</published_year>
8976          <comment>the best ever!</comment>
8977          <artist>
8978            <artistid>1</artistid>
8979            <name>Curtis Mayfield</name>
8980            <birth_year>1920</birth_year>
8981            <birth_place>Chicago</birth_place>
8982            <genre>Soul</genre>
8983          </artist>
8984        </album>
8985      </sale>
8986      <sale>
8987        <saleid>14</saleid>
8988        <timestamp>2005-07-10 15:09:00</timestamp>
8989        <date>2005-07-10</date>
8990        <time>15:09:00</time>
8991        <album>
8992          <albumid>2</albumid>
8993          <title>Curtis</title>
8994          <published_year>1970</published_year>
8995          <comment>that man's got somthin' to say</comment>
8996          <artist>
8997            <artistid>1</artistid>
8998            <name>Curtis Mayfield</name>
8999            <birth_year>1920</birth_year>
9000            <birth_place>Chicago</birth_place>
9001            <genre>Soul</genre>
9002          </artist>
9003        </album>
9004      </sale>
9005    </sales>
9006  </customer>
9007  <customer>
9008    <customerid>8</customerid>
9009    <first_name>Ed</first_name>
9010    <last_name>Burton</last_name>
9011    <email>ed.burton@example.com</email>
9012    <sales>
9013      <sale>
9014        <saleid>8</saleid>
9015        <timestamp>2005-07-10 16:03:00</timestamp>
9016        <date>2005-07-10</date>
9017        <time>16:03:00</time>
9018        <album>
9019          <albumid>1</albumid>
9020          <title>New World Order</title>
9021          <published_year>1990</published_year>
9022          <comment>the best ever!</comment>
9023          <artist>
9024            <artistid>1</artistid>
9025            <name>Curtis Mayfield</name>
9026            <birth_year>1920</birth_year>
9027            <birth_place>Chicago</birth_place>
9028            <genre>Soul</genre>
9029          </artist>
9030        </album>
9031      </sale>
9032      <sale>
9033        <saleid>19</saleid>
9034        <timestamp>2005-07-10 16:03:01</timestamp>
9035        <date>2005-07-10</date>
9036        <time>16:03:01</time>
9037        <album>
9038          <albumid>3</albumid>
9039          <title>Shaft</title>
9040          <published_year>1972</published_year>
9041          <comment>he's the man</comment>
9042          <artist>
9043            <artistid>2</artistid>
9044            <name>Isaac Hayes</name>
9045            <birth_year>1942</birth_year>
9046            <birth_place>Tennessee</birth_place>
9047            <genre>Soul</genre>
9048          </artist>
9049        </album>
9050      </sale>
9051    </sales>
9052  </customer>
9053  <customer>
9054    <customerid>9</customerid>
9055    <first_name>Jack</first_name>
9056    <last_name>Woo</last_name>
9057    <email>jack.woo@example.com</email>
9058    <sales>
9059      <sale>
9060        <saleid>9</saleid>
9061        <timestamp>2005-07-10 18:03:00</timestamp>
9062        <date>2005-07-10</date>
9063        <time>18:03:00</time>
9064        <album>
9065          <albumid>1</albumid>
9066          <title>New World Order</title>
9067          <published_year>1990</published_year>
9068          <comment>the best ever!</comment>
9069          <artist>
9070            <artistid>1</artistid>
9071            <name>Curtis Mayfield</name>
9072            <birth_year>1920</birth_year>
9073            <birth_place>Chicago</birth_place>
9074            <genre>Soul</genre>
9075          </artist>
9076        </album>
9077      </sale>
9078      <sale>
9079        <saleid>15</saleid>
9080        <timestamp>2005-07-10 18:49:00</timestamp>
9081        <date>2005-07-10</date>
9082        <time>18:49:00</time>
9083        <album>
9084          <albumid>2</albumid>
9085          <title>Curtis</title>
9086          <published_year>1970</published_year>
9087          <comment>that man's got somthin' to say</comment>
9088          <artist>
9089            <artistid>1</artistid>
9090            <name>Curtis Mayfield</name>
9091            <birth_year>1920</birth_year>
9092            <birth_place>Chicago</birth_place>
9093            <genre>Soul</genre>
9094          </artist>
9095        </album>
9096      </sale>
9097    </sales>
9098  </customer>
9099  <customer>
9100    <customerid>10</customerid>
9101    <first_name>Maria</first_name>
9102    <last_name>Gonzales</last_name>
9103    <email>maria.gonzales@example.com</email>
9104    <sales>
9105      <sale>
9106        <saleid>10</saleid>
9107        <timestamp>2005-07-10 19:03:00</timestamp>
9108        <date>2005-07-10</date>
9109        <time>19:03:00</time>
9110        <album>
9111          <albumid>1</albumid>
9112          <title>New World Order</title>
9113          <published_year>1990</published_year>
9114          <comment>the best ever!</comment>
9115          <artist>
9116            <artistid>1</artistid>
9117            <name>Curtis Mayfield</name>
9118            <birth_year>1920</birth_year>
9119            <birth_place>Chicago</birth_place>
9120            <genre>Soul</genre>
9121          </artist>
9122        </album>
9123      </sale>
9124      <sale>
9125        <saleid>20</saleid>
9126        <timestamp>2005-07-10 19:03:50</timestamp>
9127        <date>2005-07-10</date>
9128        <time>19:03:50</time>
9129        <album>
9130          <albumid>3</albumid>
9131          <title>Shaft</title>
9132          <published_year>1972</published_year>
9133          <comment>he's the man</comment>
9134          <artist>
9135            <artistid>2</artistid>
9136            <name>Isaac Hayes</name>
9137            <birth_year>1942</birth_year>
9138            <birth_place>Tennessee</birth_place>
9139            <genre>Soul</genre>
9140          </artist>
9141        </album>
9142      </sale>
9143    </sales>
9144  </customer>
9145</music_store>
9146     ]]>
9147     </programlisting>
9148     </para>
9149    </refsect3>
9150    <refsect3 id="{@id case05_debug_php}">
9151     <title>case05_debug.php</title>
9152     <para>
9153     case05_debug.php:
9154     <programlisting role="php">
9155     <![CDATA[
9156<?php
9157require_once 'XML/Query2XML.php';
9158require_once 'MDB2.php';
9159$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
9160
9161require_once 'Log.php';
9162$debugLogger = Log::factory('file', 'case05.log', 'Query2XML');
9163$query2xml->enableDebugLog($debugLogger);
9164
9165$query2xml->startProfiling();
9166
9167
9168$dom = $query2xml->getXML(
9169    "SELECT
9170         *
9171     FROM
9172         customer c
9173         LEFT JOIN sale s ON c.customerid = s.customer_id
9174         LEFT JOIN album al ON s.album_id = al.albumid
9175         LEFT JOIN artist ar ON al.artist_id = ar.artistid",
9176    array(
9177        'rootTag' => 'music_store',
9178        'rowTag' => 'customer',
9179        'idColumn' => 'customerid',
9180        'elements' => array(
9181            'customerid',
9182            'first_name',
9183            'last_name',
9184            'email',
9185            'sales' => array(
9186                'rootTag' => 'sales',
9187                'rowTag' => 'sale',
9188                'idColumn' => 'saleid',
9189                'elements' => array(
9190                    'saleid',
9191                    'timestamp',
9192                    'date' => '#Callbacks::getFirstWord()',
9193                    'time' => '#Callbacks::getSecondWord()',
9194                    'album' => array(
9195                        'rootTag' => '',
9196                        'rowTag' => 'album',
9197                        'idColumn' => 'albumid',
9198                        'elements' => array(
9199                            'albumid',
9200                            'title',
9201                            'published_year',
9202                            'comment',
9203                            'artist' => array(
9204                                'rootTag' => '',
9205                                'rowTag' => 'artist',
9206                                'idColumn' => 'artistid',
9207                                'elements' => array(
9208                                    'artistid',
9209                                    'name',
9210                                    'birth_year',
9211                                    'birth_place',
9212                                    'genre'
9213                                ) //artist elements
9214                            ) //artist array
9215                        ) //album elements
9216                    ) //album array
9217                ) //sales elements
9218            ) //sales array
9219        ) //root elements
9220    ) //root
9221); //getXML method call
9222
9223$root = $dom->firstChild;
9224$root->setAttribute('date_generated', date("Y-m-d\TH:i:s", 1124801570));
9225
9226header('Content-Type: application/xml');
9227
9228$dom->formatOutput = true;
9229print $dom->saveXML();
9230
9231require_once 'File.php';
9232$fp = new File();
9233$fp->write('case05.profile', $query2xml->getProfile(), FILE_MODE_WRITE);
9234
9235class Callbacks
9236{
9237    function getFirstWord($record)
9238    {
9239        return substr($record['timestamp'], 0, strpos($record['timestamp'], ' '));
9240    }
9241
9242    function getSecondWord($record)
9243    {
9244        return substr($record['timestamp'], strpos($record['timestamp'], ' ') + 1);
9245    }
9246}
9247?>
9248     ]]>
9249     </programlisting>
9250     The lines 6-8 do the debugging, line 10 and 76-78 the profiling. This will create
9251     case05.log and case05.profile.
9252     </para>
9253    </refsect3>
9254    <refsect3 id="{@id case05_log}">
9255     <title>case05.log</title>
9256     <para>
9257     The format of a debug log file is documented at {@tutorial XML_Query2XML.pkg#debugging}.
9258     Our debug log now contains a single query:
9259     <![CDATA[
9260Feb 11 17:27:19 Query2XML [info] QUERY: SELECT
9261         *
9262     FROM
9263         customer c
9264         LEFT JOIN sale s ON c.customerid = s.customer_id
9265         LEFT JOIN album al ON s.album_id = al.albumid
9266         LEFT JOIN artist ar ON al.artist_id = ar.artistid
9267Feb 11 17:27:19 Query2XML [info] DONE
9268     ]]>
9269     </para>
9270    </refsect3>
9271    <refsect3 id="{@id case05_profile}">
9272     <title>case05.profile</title>
9273     <para>
9274     The format of the output is documented under
9275     {@tutorial XML_Query2XML.pkg#profiling.getprofile}:
9276     <![CDATA[
9277FROM_DB FROM_CACHE CACHED AVG_DURATION DURATION_SUM SQL
92781       0          false  0.0074028968 0.0074028968 SELECT
9279         *
9280     FROM
9281         customer c
9282         LEFT JOIN sale s ON c.customerid = s.customer_id
9283         LEFT JOIN album al ON s.album_id = al.albumid
9284         LEFT JOIN artist ar ON al.artist_id = ar.artistid
9285
9286TOTAL_DURATION: 0.22688508033752
9287DB_DURATION:    0.050441980361938
9288     ]]>
9289     </para>
9290    </refsect3>
9291   </refsect2>
9292
9293   <refsect2 id="{@id case06}">
9294    <title>Case 06: BIG join over 10 tables</title>
9295    <para>
9296     Case 06 will teach you:
9297     <itemizedlist>
9298      <listitem>
9299       How to write nested {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.complex_element_specifications}.
9300      </listitem>
9301      <listitem>
9302       How to use {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn} when
9303       things get really complex.
9304      </listitem>
9305      <listitem>
9306       How to use COMMAND OBJECTS implementing the {@link XML_Query2XML_Callback} interface.
9307      </listitem>
9308      <listitem>
9309       How to use the CONDITIONAL prefix '?' in combination with the '#' prefix.
9310      </listitem>
9311     </itemizedlist>
9312    </para>
9313    <para>
9314     Case 06 demonstrates how complex things can get :) First have a look at the
9315     {@link http://query2xml.sourceforge.net/docs/Query2XML_Tests.jpg ER diagram}.
9316     It shows a company that sells records. The basic structure of the generated
9317     XML document is as follows:
9318     <itemizedlist>
9319      <listitem>
9320       for each store we want a list of all departments that are located in this store
9321      </listitem>
9322      <listitem>
9323       for each department we want a list of all employees that work in this department
9324      </listitem>
9325      <listitem>
9326       for each employee we want a list of all his sales
9327      </listitem>
9328      <listitem>
9329       for each sale we want to know the customer and the album sold
9330      </listitem>
9331      <listitem>
9332       for each album we want to know the artist that performed the music
9333      </listitem>
9334     </itemizedlist>
9335    </para>
9336    <refsect3 id="{@id case06_php}">
9337     <title>case06.php</title>
9338     <para>
9339     case06.php looks like this:
9340     <programlisting role="php">
9341     <![CDATA[
9342<?php
9343require_once 'XML/Query2XML.php';
9344require_once 'XML/Query2XML/Callback.php';
9345require_once 'MDB2.php';
9346
9347/**Static class that provides validation and parsing methods for
9348* generating XML.
9349*
9350* It is static so that we can easyly call its methods from inside
9351* Query2XML using eval'd code.
9352*/
9353class Helper
9354{
9355    /**Associative array of US postal state codes*/
9356    public static $statePostalCodes = array(
9357        'ALABAMA' => 'AL', 'ALASKA' => 'AK', 'AMERICAN SAMOA' => 'AS', 'ARIZONA' => 'AZ', 'ARKANSAS' => 'AR', 'CALIFORNIA' => 'CA',
9358        'COLORADO' => 'CO', 'CONNECTICUT' => 'CT', 'DELAWARE' => 'DE', 'DISTRICT OF COLUMBIA' => 'DC', 'FEDERATED STATES OF MICRONESIA' => 'FM',
9359        'FLORIDA' => 'FL', 'GEORGIA' => 'GA', 'GUAM' => 'GU', 'HAWAII' => 'HI', 'IDAHO' => 'ID', 'ILLINOIS' => 'IL', 'INDIANA' => 'IN',
9360        'IOWA' => 'IA', 'KANSAS' => 'KS', 'KENTUCKY' => 'KY', 'LOUISIANA' => 'LA', 'MAINE' => 'ME', 'MARSHALL ISLANDS' => 'MH', 'MARYLAND' => 'MD',
9361        'MASSACHUSETTS' => 'MA', 'MICHIGAN' => 'MI', 'MINNESOTA' => 'MN', 'MISSISSIPPI' => 'MS', 'MISSOURI' => 'MO', 'MONTANA' => 'MT',
9362        'NEBRASKA' => 'NE', 'NEVADA' => 'NV', 'NEW HAMPSHIRE' => 'NH', 'NEW JERSEY' => 'NJ', 'NEW JESEY' => 'NJ', 'NEW MEXICO' => 'NM', 'NEW YORK' => 'NY',
9363        'NORTH CAROLINA' => 'NC', 'NORTH DAKOTA' => 'ND', 'NORTHERN MARIANA ISLANDS' => 'MP', 'OHIO' => 'OH', 'OKLAHOMA' => 'OK', 'OREGON' => 'OR',
9364        'PALAU' => 'PW', 'PENNSYLVANIA' => 'PA', 'PUERTO RICO' => 'PR', 'RHODE ISLAND' => 'RI', 'SOUTH CAROLINA' => 'SC', 'SOUTH DAKOTA' => 'SD',
9365        'TENNESSEE' => 'TN', 'TEXAS' => 'TX', 'UTAH' => 'UT', 'VERMONT' => 'VT', 'VIRGIN ISLANDS' => 'VI', 'VIRGINIA' => 'VA', 'WASHINGTON' => 'WA',
9366        'WEST VIRGINIA' => 'WV', 'WISCONSIN' => 'WI', 'WYOMING' => 'WY'
9367    );
9368
9369    /**Translates a US state name into its two-letter postal code.
9370    * If the translation fails, $state is returned unchanged
9371    * @param $record The record
9372    */
9373    public static function getStatePostalCode($record)
9374    {
9375        $state = $record["state"];
9376        $s = str_replace("  ", " ", trim(strtoupper($state)));
9377        if (isset(self::$statePostalCodes[$s])) {
9378            return self::$statePostalCodes[$s];
9379        } else {
9380            return $state;
9381        }
9382    }
9383
9384    function summarize($str, $limit=50, $appendString=' ...')
9385    {
9386        if (strlen($str) > $limit) {
9387            $str = substr($str, 0, $limit - strlen($appendString)) . $appendString;
9388        }
9389        return $str;
9390    }
9391
9392    function summarizeComment($record, $limit)
9393    {
9394        return self::summarize($record["comment"], $limit);
9395    }
9396}
9397
9398/**Command class that implements the command pattern.
9399* It implements the XML_Query2XML_Callback interface
9400* and therefore has to provide the public non-static
9401* method execute(array $record).
9402*/
9403class UppercaseColumnCommand implements XML_Query2XML_Callback
9404{
9405    public function __construct($columnName)
9406    {
9407        $this->_columnName = $columnName;
9408    }
9409    public function execute(array $record)
9410    {
9411        return strtoupper($record[$this->_columnName]);
9412    }
9413}
9414
9415$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
9416$dom = $query2xml->getXML(
9417    "SELECT
9418         s.*,
9419         manager.employeeid AS manager_employeeid,
9420         manager.employeename AS manager_employeename,
9421         d.*,
9422         department_head.employeeid AS department_head_employeeid,
9423         department_head.employeename AS department_head_employeename,
9424         e.*,
9425         sa.*,
9426         c.*,
9427         al.*,
9428         ar.*,
9429         (SELECT COUNT(*) FROM sale WHERE sale.store_id = s.storeid) AS store_sales,
9430         (SELECT
9431            COUNT(*)
9432          FROM
9433            sale, employee, employee_department
9434          WHERE
9435            sale.employee_id = employee.employeeid
9436            AND
9437            employee_department.employee_id = employee.employeeid
9438            AND
9439            employee_department.department_id = d.departmentid
9440         ) AS department_sales,
9441         (SELECT
9442            COUNT(*)
9443          FROM
9444            employee, employee_department, department
9445          WHERE
9446            employee_department.employee_id = employee.employeeid
9447            AND
9448            employee_department.department_id = department.departmentid
9449            AND
9450            department.store_id = s.storeid
9451         ) AS store_employees,
9452         (SELECT
9453            COUNT(*)
9454          FROM
9455            employee, employee_department
9456          WHERE
9457            employee_department.employee_id = employee.employeeid
9458            AND
9459            employee_department.department_id = d.departmentid
9460         ) AS department_employees
9461     FROM
9462         store s
9463          LEFT JOIN employee manager ON s.manager = manager.employeeid
9464         LEFT JOIN department d ON d.store_id = s.storeid
9465          LEFT JOIN employee department_head ON department_head.employeeid = d.department_head
9466          LEFT JOIN employee_department ed ON ed.department_id = d.departmentid
9467           LEFT JOIN employee e ON e.employeeid = ed.employee_id
9468            LEFT JOIN sale sa ON sa.employee_id = e.employeeid
9469             LEFT JOIN customer c ON c.customerid = sa.customer_id
9470             LEFT JOIN album al ON al.albumid = sa.album_id
9471              LEFT JOIN artist ar ON ar.artistid = al.artist_id",
9472    array(
9473        'rootTag' => 'music_company',
9474        'rowTag' => 'store',
9475        'idColumn' => 'storeid',
9476        'attributes' => array(
9477            'storeid'
9478        ),
9479        'elements' => array(
9480            'store_sales',
9481            'store_employees',
9482            'manager' => array(
9483                'idColumn' => 'manager_employeeid',
9484                'attributes' => array(
9485                    'manager_employeeid'
9486                ),
9487                'elements' => array(
9488                    'manager_employeename'
9489                )
9490            ),
9491            'address' => array(
9492                'elements' => array(
9493                    'country',
9494                    'state' => '#Helper::getStatePostalCode()',
9495                    'city' => new UppercaseColumnCommand('city'),
9496                    'street',
9497                    'phone'
9498                )
9499            ),
9500            'department' => array(
9501                'idColumn' => 'departmentid',
9502                'attributes' => array(
9503                    'departmentid'
9504                ),
9505                'elements' => array(
9506                    'department_sales',
9507                    'department_employees',
9508                    'departmentname',
9509                    'department_head' => array(
9510                        'idColumn' => 'department_head_employeeid',
9511                        'attributes' => array(
9512                            'department_head_employeeid'
9513                        ),
9514                        'elements' => array(
9515                            'department_head_employeename'
9516                        )
9517                    ),
9518                    'employees' => array(
9519                        'rootTag' => 'employees',
9520                        'rowTag' => 'employee',
9521                        'idColumn' => 'employeeid',
9522                        'attributes' => array(
9523                            'employeeid'
9524                        ),
9525                        'elements' => array(
9526                            'employeename',
9527                            'sales' => array(
9528                                'rootTag' => 'sales',
9529                                'rowTag' => 'sale',
9530                                'idColumn' => 'saleid',
9531                                'attributes' => array(
9532                                    'saleid'
9533                                ),
9534                                'elements' => array(
9535                                    'timestamp',
9536                                    'customer' => array(
9537                                        'idColumn' => 'customerid',
9538                                        'attributes' => array(
9539                                            'customerid'
9540                                        ),
9541                                        'elements' => array(
9542                                            'first_name',
9543                                            'last_name',
9544                                            'email'
9545                                        )
9546                                    ),
9547                                    'album' => array(
9548                                        'idColumn' => 'albumid',
9549                                        'attributes' => array(
9550                                            'albumid'
9551                                        ),
9552                                        'elements' => array(
9553                                            'title',
9554                                            'published_year',
9555                                            'comment' => '?#Helper::summarizeComment(12)',
9556                                            'artist' => array(
9557                                                'idColumn' => 'artistid',
9558                                                'attributes' => array(
9559                                                    'artistid'
9560                                                ),
9561                                                'elements' => array(
9562                                                    'name',
9563                                                    'birth_year',
9564                                                    'birth_place',
9565                                                    'genre'
9566                                                )
9567                                            )
9568                                        ) // album elements
9569                                    ) //album array
9570                                ) //sales elements
9571                            ) //sales array
9572                        ) //employees elements
9573                    ) //employees array
9574                ) //department elements
9575            ) // department array
9576        ) //root elements
9577    ) //root
9578); //getXML method call
9579
9580$root = $dom->firstChild;
9581$root->setAttribute('date_generated', date("Y-m-d\TH:i:s", 1124801570));
9582
9583header('Content-Type: application/xml');
9584
9585$dom->formatOutput = true;
9586print $dom->saveXML();
9587?>
9588     ]]>
9589     </programlisting>
9590    </para>
9591    <para>
9592     Note how {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn} is used at the different
9593     levels:
9594     <itemizedlist>
9595      <listitem>
9596       on the root level we want a record for each store and therefore use the primary key of the table 'store' as
9597       the idColumn:
9598       <programlisting role="php">
9599       <![CDATA[
9600'idColumn' => 'storeid',
9601       ]]>
9602       </programlisting>
9603      </listitem>
9604      <listitem>
9605       [elements][address] does not use {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_idcolumn}
9606       at all because all columns used at this level ('country',
9607       'state', 'city', 'street' and 'phone') are all columns of the table 'store'.
9608      </listitem>
9609      <listitem>
9610       on the level [elements][department] we want a record for each department (within the current store)
9611       and therefore use the primary key of the table 'department' as the idColumn:
9612       <programlisting role="php">
9613       <![CDATA[
9614'idColumn' => 'departmentid',
9615       ]]>
9616       </programlisting>
9617       The reason we can use department.departmentid without listing all departments underneath all stores is of course that
9618       our LEFT JOIN has the condition "LEFT JOIN department ON department.store_id = store.storeid".
9619      </listitem>
9620      <listitem>
9621       on the level [elements][department][elements][employees] we want a record for each employee (within
9622       the current department) and therefore use the primary key of the table 'employee' as the idColumn:
9623       <programlisting role="php">
9624       <![CDATA[
9625'idColumn' => 'employeeid',
9626       ]]>
9627       </programlisting>
9628       The reason we can use employee.employeeid without listing all employees underneath all departments is of course that
9629       our LEFT JOIN does the trick for us (implementing a n:n relationship via the table employee_department).
9630      </listitem>
9631      <listitem>
9632       on the level [elements][department][elements][employees][elements][sales] we want a record for each sale
9633       (perfmormed by the current employee) and therefore use the primary key of the table 'sale' as the idColumn:
9634       <programlisting role="php">
9635       <![CDATA[
9636'idColumn' => 'saleid',
9637       ]]>
9638       </programlisting>
9639       The reason we can use sale.saleid without listing all sales underneath all employees is of course that
9640       our LEFT JOIN has the condition "LEFT JOIN sale ON sale.employee_id = employee.employeeid".
9641      </listitem>
9642      <listitem>
9643       on the level [elements][department][elements][employees][elements][sales][elements][customer] we want a
9644       record for each customer (which was a party to the current sale) and therefore use the primary key of
9645       the table 'customer' as the idColumn:
9646       <programlisting role="php">
9647       <![CDATA[
9648'idColumn' => 'customerid',
9649       ]]>
9650       </programlisting>
9651       Logically speaking we are of course using the foreign key sale.employee_id, but as the equality of
9652       sale.employee_id and employee.employeeid is the condition for our LEFT JOIN, both are the same.
9653      </listitem>
9654      <listitem>
9655       on the level [elements][department][elements][employees][elements][sales][elements][album] we want a
9656       record for each album (which was subject to the current sale) and therefore use the primary key of
9657       the table 'album' as the idColumn:
9658       <programlisting role="php">
9659       <![CDATA[
9660'idColumn' => 'albumid',
9661       ]]>
9662       </programlisting>
9663       Logically speaking we are of course using the foreign key sale.album_id, but as the equality of
9664       sale.album_id and album.albumid is the condition for our LEFT JOIN, both are the same.
9665      </listitem>
9666      <listitem>
9667       on the level [elements][department][elements][employees][elements][sales][elements][album][elements][artist]
9668       we want a record for each artist (who permormed the current album) and therefore use the primary key of
9669       the table 'artist' as the idColumn:
9670       <programlisting role="php">
9671       <![CDATA[
9672'idColumn' => 'artistid',
9673       ]]>
9674       </programlisting>
9675       Logically speaking we are of course using the foreign key album.artist_id, but as the equality of
9676       album.artist_id and artist.artistid is the condition for our LEFT JOIN, both are the same.
9677      </listitem>
9678     </itemizedlist>
9679    </para>
9680    <para>
9681     We also use a static class called Helper here. Note how Helper::summarizeComment() is called with a second argument.
9682     The current record is always passed as first argument to the callback function. So we specify the "comment"
9683     element as
9684     <programlisting role="php">
9685     <![CDATA[
9686'comment' => '?#Helper::summarizeComment(12)',
9687     ]]>
9688     </programlisting>
9689     which means that the string '12' will be passed as second argument to Helper::summarizeComment().
9690     The CONDITIONAL prefix ? means that the comment element will only appear if the (summarized)
9691     comment != "".
9692     </para>
9693     <para>
9694      In addition to the CALLBACK FUNCTION we also use a COMMAND OBJECT. In our case it is
9695      an instance of the class UppercaseColumnCommand which implements the {@link XML_Query2XML_Callback}
9696      interface. We pass the column's name as constructor argument, so that UppercaseColumnCommand::execute()
9697      knows which column (i.e. which element of the $record array) to pass to strtoupper().
9698     </para>
9699    </refsect3>
9700    <refsect3 id="{@id case06_xml}">
9701     <title>case06.xml</title>
9702     <para>
9703     The resulting XML data looks like this:
9704     <programlisting role="tutorial">
9705     <![CDATA[
9706<?xml version="1.0" encoding="UTF-8"?>
9707<music_company date_generated="2005-08-23T14:52:50">
9708  <store storeid="1">
9709    <store_sales>10</store_sales>
9710    <store_employees>6</store_employees>
9711    <manager manager_employeeid="1">
9712      <manager_employeename>Michael Jones</manager_employeename>
9713    </manager>
9714    <address>
9715      <country>US</country>
9716      <state>NY</state>
9717      <city>NEW YORK</city>
9718      <street>Broadway &amp; 72nd Str</street>
9719      <phone>123 456 7890</phone>
9720    </address>
9721    <department departmentid="1">
9722      <department_sales>10</department_sales>
9723      <department_employees>3</department_employees>
9724      <departmentname>Sales</departmentname>
9725      <department_head department_head_employeeid="1">
9726        <department_head_employeename>Michael Jones</department_head_employeename>
9727      </department_head>
9728      <employees>
9729        <employee employeeid="1">
9730          <employeename>Michael Jones</employeename>
9731          <sales>
9732            <sale saleid="1">
9733              <timestamp>2005-05-25 16:32:00</timestamp>
9734              <customer customerid="1">
9735                <first_name>Jane</first_name>
9736                <last_name>Doe</last_name>
9737                <email>jane.doe@example.com</email>
9738              </customer>
9739              <album albumid="1">
9740                <title>New World Order</title>
9741                <published_year>1990</published_year>
9742                <comment>the best ...</comment>
9743                <artist artistid="1">
9744                  <name>Curtis Mayfield</name>
9745                  <birth_year>1920</birth_year>
9746                  <birth_place>Chicago</birth_place>
9747                  <genre>Soul</genre>
9748                </artist>
9749              </album>
9750            </sale>
9751            <sale saleid="7">
9752              <timestamp>2005-07-10 15:03:00</timestamp>
9753              <customer customerid="7">
9754                <first_name>Nick</first_name>
9755                <last_name>Fallow</last_name>
9756                <email>nick.fallow@example.com</email>
9757              </customer>
9758              <album albumid="1">
9759                <title>New World Order</title>
9760                <published_year>1990</published_year>
9761                <comment>the best ...</comment>
9762                <artist artistid="1">
9763                  <name>Curtis Mayfield</name>
9764                  <birth_year>1920</birth_year>
9765                  <birth_place>Chicago</birth_place>
9766                  <genre>Soul</genre>
9767                </artist>
9768              </album>
9769            </sale>
9770            <sale saleid="16">
9771              <timestamp>2005-06-05 12:56:12</timestamp>
9772              <customer customerid="2">
9773                <first_name>John</first_name>
9774                <last_name>Doe</last_name>
9775                <email>john.doe@example.com</email>
9776              </customer>
9777              <album albumid="3">
9778                <title>Shaft</title>
9779                <published_year>1972</published_year>
9780                <comment>he's the man</comment>
9781                <artist artistid="2">
9782                  <name>Isaac Hayes</name>
9783                  <birth_year>1942</birth_year>
9784                  <birth_place>Tennessee</birth_place>
9785                  <genre>Soul</genre>
9786                </artist>
9787              </album>
9788            </sale>
9789            <sale saleid="19">
9790              <timestamp>2005-07-10 16:03:01</timestamp>
9791              <customer customerid="8">
9792                <first_name>Ed</first_name>
9793                <last_name>Burton</last_name>
9794                <email>ed.burton@example.com</email>
9795              </customer>
9796              <album albumid="3">
9797                <title>Shaft</title>
9798                <published_year>1972</published_year>
9799                <comment>he's the man</comment>
9800                <artist artistid="2">
9801                  <name>Isaac Hayes</name>
9802                  <birth_year>1942</birth_year>
9803                  <birth_place>Tennessee</birth_place>
9804                  <genre>Soul</genre>
9805                </artist>
9806              </album>
9807            </sale>
9808          </sales>
9809        </employee>
9810        <employee employeeid="2">
9811          <employeename>Susi Weintraub</employeename>
9812          <sales>
9813            <sale saleid="3">
9814              <timestamp>2005-07-10 11:03:00</timestamp>
9815              <customer customerid="3">
9816                <first_name>Susan</first_name>
9817                <last_name>Green</last_name>
9818                <email>susan.green@example.com</email>
9819              </customer>
9820              <album albumid="1">
9821                <title>New World Order</title>
9822                <published_year>1990</published_year>
9823                <comment>the best ...</comment>
9824                <artist artistid="1">
9825                  <name>Curtis Mayfield</name>
9826                  <birth_year>1920</birth_year>
9827                  <birth_place>Chicago</birth_place>
9828                  <genre>Soul</genre>
9829                </artist>
9830              </album>
9831            </sale>
9832            <sale saleid="9">
9833              <timestamp>2005-07-10 18:03:00</timestamp>
9834              <customer customerid="9">
9835                <first_name>Jack</first_name>
9836                <last_name>Woo</last_name>
9837                <email>jack.woo@example.com</email>
9838              </customer>
9839              <album albumid="1">
9840                <title>New World Order</title>
9841                <published_year>1990</published_year>
9842                <comment>the best ...</comment>
9843                <artist artistid="1">
9844                  <name>Curtis Mayfield</name>
9845                  <birth_year>1920</birth_year>
9846                  <birth_place>Chicago</birth_place>
9847                  <genre>Soul</genre>
9848                </artist>
9849              </album>
9850            </sale>
9851            <sale saleid="17">
9852              <timestamp>2005-07-10 10:03:32</timestamp>
9853              <customer customerid="4">
9854                <first_name>Victoria</first_name>
9855                <last_name>Alt</last_name>
9856                <email>victory.alt@example.com</email>
9857              </customer>
9858              <album albumid="3">
9859                <title>Shaft</title>
9860                <published_year>1972</published_year>
9861                <comment>he's the man</comment>
9862                <artist artistid="2">
9863                  <name>Isaac Hayes</name>
9864                  <birth_year>1942</birth_year>
9865                  <birth_place>Tennessee</birth_place>
9866                  <genre>Soul</genre>
9867                </artist>
9868              </album>
9869            </sale>
9870            <sale saleid="20">
9871              <timestamp>2005-07-10 19:03:50</timestamp>
9872              <customer customerid="10">
9873                <first_name>Maria</first_name>
9874                <last_name>Gonzales</last_name>
9875                <email>maria.gonzales@example.com</email>
9876              </customer>
9877              <album albumid="3">
9878                <title>Shaft</title>
9879                <published_year>1972</published_year>
9880                <comment>he's the man</comment>
9881                <artist artistid="2">
9882                  <name>Isaac Hayes</name>
9883                  <birth_year>1942</birth_year>
9884                  <birth_place>Tennessee</birth_place>
9885                  <genre>Soul</genre>
9886                </artist>
9887              </album>
9888            </sale>
9889          </sales>
9890        </employee>
9891        <employee employeeid="3">
9892          <employeename>Steve Hack</employeename>
9893          <sales>
9894            <sale saleid="5">
9895              <timestamp>2005-07-10 13:03:00</timestamp>
9896              <customer customerid="5">
9897                <first_name>Will</first_name>
9898                <last_name>Rippy</last_name>
9899                <email>will.wippy@example.com</email>
9900              </customer>
9901              <album albumid="1">
9902                <title>New World Order</title>
9903                <published_year>1990</published_year>
9904                <comment>the best ...</comment>
9905                <artist artistid="1">
9906                  <name>Curtis Mayfield</name>
9907                  <birth_year>1920</birth_year>
9908                  <birth_place>Chicago</birth_place>
9909                  <genre>Soul</genre>
9910                </artist>
9911              </album>
9912            </sale>
9913            <sale saleid="18">
9914              <timestamp>2005-07-10 14:03:52</timestamp>
9915              <customer customerid="6">
9916                <first_name>Tim</first_name>
9917                <last_name>Raw</last_name>
9918                <email>tim.raw@example.com</email>
9919              </customer>
9920              <album albumid="3">
9921                <title>Shaft</title>
9922                <published_year>1972</published_year>
9923                <comment>he's the man</comment>
9924                <artist artistid="2">
9925                  <name>Isaac Hayes</name>
9926                  <birth_year>1942</birth_year>
9927                  <birth_place>Tennessee</birth_place>
9928                  <genre>Soul</genre>
9929                </artist>
9930              </album>
9931            </sale>
9932          </sales>
9933        </employee>
9934      </employees>
9935    </department>
9936    <department departmentid="2">
9937      <department_sales>0</department_sales>
9938      <department_employees>3</department_employees>
9939      <departmentname>Marketing</departmentname>
9940      <department_head department_head_employeeid="4">
9941        <department_head_employeename>Joan Kerr</department_head_employeename>
9942      </department_head>
9943      <employees>
9944        <employee employeeid="4">
9945          <employeename>Joan Kerr</employeename>
9946          <sales/>
9947        </employee>
9948        <employee employeeid="5">
9949          <employeename>Marcus Roth</employeename>
9950          <sales/>
9951        </employee>
9952        <employee employeeid="6">
9953          <employeename>Jack Mack</employeename>
9954          <sales/>
9955        </employee>
9956      </employees>
9957    </department>
9958  </store>
9959  <store storeid="2">
9960    <store_sales>10</store_sales>
9961    <store_employees>6</store_employees>
9962    <manager manager_employeeid="2">
9963      <manager_employeename>Susi Weintraub</manager_employeename>
9964    </manager>
9965    <address>
9966      <country>US</country>
9967      <state>NY</state>
9968      <city>LARCHMONT</city>
9969      <street>Palmer Ave 71</street>
9970      <phone>456 7890</phone>
9971    </address>
9972    <department departmentid="3">
9973      <department_sales>10</department_sales>
9974      <department_employees>3</department_employees>
9975      <departmentname>Sales</departmentname>
9976      <department_head department_head_employeeid="7">
9977        <department_head_employeename>Rita Doktor</department_head_employeename>
9978      </department_head>
9979      <employees>
9980        <employee employeeid="7">
9981          <employeename>Rita Doktor</employeename>
9982          <sales>
9983            <sale saleid="2">
9984              <timestamp>2005-06-05 12:56:00</timestamp>
9985              <customer customerid="2">
9986                <first_name>John</first_name>
9987                <last_name>Doe</last_name>
9988                <email>john.doe@example.com</email>
9989              </customer>
9990              <album albumid="1">
9991                <title>New World Order</title>
9992                <published_year>1990</published_year>
9993                <comment>the best ...</comment>
9994                <artist artistid="1">
9995                  <name>Curtis Mayfield</name>
9996                  <birth_year>1920</birth_year>
9997                  <birth_place>Chicago</birth_place>
9998                  <genre>Soul</genre>
9999                </artist>
10000              </album>
10001            </sale>
10002            <sale saleid="8">
10003              <timestamp>2005-07-10 16:03:00</timestamp>
10004              <customer customerid="8">
10005                <first_name>Ed</first_name>
10006                <last_name>Burton</last_name>
10007                <email>ed.burton@example.com</email>
10008              </customer>
10009              <album albumid="1">
10010                <title>New World Order</title>
10011                <published_year>1990</published_year>
10012                <comment>the best ...</comment>
10013                <artist artistid="1">
10014                  <name>Curtis Mayfield</name>
10015                  <birth_year>1920</birth_year>
10016                  <birth_place>Chicago</birth_place>
10017                  <genre>Soul</genre>
10018                </artist>
10019              </album>
10020            </sale>
10021            <sale saleid="11">
10022              <timestamp>2005-05-25 16:23:00</timestamp>
10023              <customer customerid="1">
10024                <first_name>Jane</first_name>
10025                <last_name>Doe</last_name>
10026                <email>jane.doe@example.com</email>
10027              </customer>
10028              <album albumid="2">
10029                <title>Curtis</title>
10030                <published_year>1970</published_year>
10031                <comment>that man ...</comment>
10032                <artist artistid="1">
10033                  <name>Curtis Mayfield</name>
10034                  <birth_year>1920</birth_year>
10035                  <birth_place>Chicago</birth_place>
10036                  <genre>Soul</genre>
10037                </artist>
10038              </album>
10039            </sale>
10040            <sale saleid="14">
10041              <timestamp>2005-07-10 15:09:00</timestamp>
10042              <customer customerid="7">
10043                <first_name>Nick</first_name>
10044                <last_name>Fallow</last_name>
10045                <email>nick.fallow@example.com</email>
10046              </customer>
10047              <album albumid="2">
10048                <title>Curtis</title>
10049                <published_year>1970</published_year>
10050                <comment>that man ...</comment>
10051                <artist artistid="1">
10052                  <name>Curtis Mayfield</name>
10053                  <birth_year>1920</birth_year>
10054                  <birth_place>Chicago</birth_place>
10055                  <genre>Soul</genre>
10056                </artist>
10057              </album>
10058            </sale>
10059          </sales>
10060        </employee>
10061        <employee employeeid="8">
10062          <employeename>David Til</employeename>
10063          <sales>
10064            <sale saleid="4">
10065              <timestamp>2005-07-10 10:03:00</timestamp>
10066              <customer customerid="4">
10067                <first_name>Victoria</first_name>
10068                <last_name>Alt</last_name>
10069                <email>victory.alt@example.com</email>
10070              </customer>
10071              <album albumid="1">
10072                <title>New World Order</title>
10073                <published_year>1990</published_year>
10074                <comment>the best ...</comment>
10075                <artist artistid="1">
10076                  <name>Curtis Mayfield</name>
10077                  <birth_year>1920</birth_year>
10078                  <birth_place>Chicago</birth_place>
10079                  <genre>Soul</genre>
10080                </artist>
10081              </album>
10082            </sale>
10083            <sale saleid="10">
10084              <timestamp>2005-07-10 19:03:00</timestamp>
10085              <customer customerid="10">
10086                <first_name>Maria</first_name>
10087                <last_name>Gonzales</last_name>
10088                <email>maria.gonzales@example.com</email>
10089              </customer>
10090              <album albumid="1">
10091                <title>New World Order</title>
10092                <published_year>1990</published_year>
10093                <comment>the best ...</comment>
10094                <artist artistid="1">
10095                  <name>Curtis Mayfield</name>
10096                  <birth_year>1920</birth_year>
10097                  <birth_place>Chicago</birth_place>
10098                  <genre>Soul</genre>
10099                </artist>
10100              </album>
10101            </sale>
10102            <sale saleid="12">
10103              <timestamp>2005-07-10 11:56:00</timestamp>
10104              <customer customerid="3">
10105                <first_name>Susan</first_name>
10106                <last_name>Green</last_name>
10107                <email>susan.green@example.com</email>
10108              </customer>
10109              <album albumid="2">
10110                <title>Curtis</title>
10111                <published_year>1970</published_year>
10112                <comment>that man ...</comment>
10113                <artist artistid="1">
10114                  <name>Curtis Mayfield</name>
10115                  <birth_year>1920</birth_year>
10116                  <birth_place>Chicago</birth_place>
10117                  <genre>Soul</genre>
10118                </artist>
10119              </album>
10120            </sale>
10121            <sale saleid="15">
10122              <timestamp>2005-07-10 18:49:00</timestamp>
10123              <customer customerid="9">
10124                <first_name>Jack</first_name>
10125                <last_name>Woo</last_name>
10126                <email>jack.woo@example.com</email>
10127              </customer>
10128              <album albumid="2">
10129                <title>Curtis</title>
10130                <published_year>1970</published_year>
10131                <comment>that man ...</comment>
10132                <artist artistid="1">
10133                  <name>Curtis Mayfield</name>
10134                  <birth_year>1920</birth_year>
10135                  <birth_place>Chicago</birth_place>
10136                  <genre>Soul</genre>
10137                </artist>
10138              </album>
10139            </sale>
10140          </sales>
10141        </employee>
10142        <employee employeeid="9">
10143          <employeename>Pia Eist</employeename>
10144          <sales>
10145            <sale saleid="6">
10146              <timestamp>2005-07-10 14:03:00</timestamp>
10147              <customer customerid="6">
10148                <first_name>Tim</first_name>
10149                <last_name>Raw</last_name>
10150                <email>tim.raw@example.com</email>
10151              </customer>
10152              <album albumid="1">
10153                <title>New World Order</title>
10154                <published_year>1990</published_year>
10155                <comment>the best ...</comment>
10156                <artist artistid="1">
10157                  <name>Curtis Mayfield</name>
10158                  <birth_year>1920</birth_year>
10159                  <birth_place>Chicago</birth_place>
10160                  <genre>Soul</genre>
10161                </artist>
10162              </album>
10163            </sale>
10164            <sale saleid="13">
10165              <timestamp>2005-07-10 13:12:00</timestamp>
10166              <customer customerid="5">
10167                <first_name>Will</first_name>
10168                <last_name>Rippy</last_name>
10169                <email>will.wippy@example.com</email>
10170              </customer>
10171              <album albumid="2">
10172                <title>Curtis</title>
10173                <published_year>1970</published_year>
10174                <comment>that man ...</comment>
10175                <artist artistid="1">
10176                  <name>Curtis Mayfield</name>
10177                  <birth_year>1920</birth_year>
10178                  <birth_place>Chicago</birth_place>
10179                  <genre>Soul</genre>
10180                </artist>
10181              </album>
10182            </sale>
10183          </sales>
10184        </employee>
10185      </employees>
10186    </department>
10187    <department departmentid="4">
10188      <department_sales>0</department_sales>
10189      <department_employees>3</department_employees>
10190      <departmentname>Marketing</departmentname>
10191      <department_head department_head_employeeid="10">
10192        <department_head_employeename>Hanna Poll</department_head_employeename>
10193      </department_head>
10194      <employees>
10195        <employee employeeid="10">
10196          <employeename>Hanna Poll</employeename>
10197          <sales/>
10198        </employee>
10199        <employee employeeid="11">
10200          <employeename>Jim Wells</employeename>
10201          <sales/>
10202        </employee>
10203        <employee employeeid="12">
10204          <employeename>Sandra Wilson</employeename>
10205          <sales/>
10206        </employee>
10207      </employees>
10208    </department>
10209  </store>
10210</music_company>
10211     ]]>
10212     </programlisting>
10213     </para>
10214    </refsect3>
10215    <refsect3 id="{@id case06_debug_php}">
10216     <title>case06_debug.php</title>
10217     <para>
10218     case06_debug.php:
10219     <programlisting role="php">
10220     <![CDATA[
10221<?php
10222require_once 'XML/Query2XML.php';
10223require_once 'XML/Query2XML/Callback.php';
10224require_once 'MDB2.php';
10225
10226/**Static class that provides validation and parsing methods for
10227* generating XML.
10228*
10229* It is static so that we can easyly call its methods from inside
10230* Query2XML using eval'd code.
10231*/
10232class Helper
10233{
10234    /**Associative array of US postal state codes*/
10235    public static $statePostalCodes = array(
10236        'ALABAMA' => 'AL', 'ALASKA' => 'AK', 'AMERICAN SAMOA' => 'AS', 'ARIZONA' => 'AZ', 'ARKANSAS' => 'AR', 'CALIFORNIA' => 'CA',
10237        'COLORADO' => 'CO', 'CONNECTICUT' => 'CT', 'DELAWARE' => 'DE', 'DISTRICT OF COLUMBIA' => 'DC', 'FEDERATED STATES OF MICRONESIA' => 'FM',
10238        'FLORIDA' => 'FL', 'GEORGIA' => 'GA', 'GUAM' => 'GU', 'HAWAII' => 'HI', 'IDAHO' => 'ID', 'ILLINOIS' => 'IL', 'INDIANA' => 'IN',
10239        'IOWA' => 'IA', 'KANSAS' => 'KS', 'KENTUCKY' => 'KY', 'LOUISIANA' => 'LA', 'MAINE' => 'ME', 'MARSHALL ISLANDS' => 'MH', 'MARYLAND' => 'MD',
10240        'MASSACHUSETTS' => 'MA', 'MICHIGAN' => 'MI', 'MINNESOTA' => 'MN', 'MISSISSIPPI' => 'MS', 'MISSOURI' => 'MO', 'MONTANA' => 'MT',
10241        'NEBRASKA' => 'NE', 'NEVADA' => 'NV', 'NEW HAMPSHIRE' => 'NH', 'NEW JERSEY' => 'NJ', 'NEW JESEY' => 'NJ', 'NEW MEXICO' => 'NM', 'NEW YORK' => 'NY',
10242        'NORTH CAROLINA' => 'NC', 'NORTH DAKOTA' => 'ND', 'NORTHERN MARIANA ISLANDS' => 'MP', 'OHIO' => 'OH', 'OKLAHOMA' => 'OK', 'OREGON' => 'OR',
10243        'PALAU' => 'PW', 'PENNSYLVANIA' => 'PA', 'PUERTO RICO' => 'PR', 'RHODE ISLAND' => 'RI', 'SOUTH CAROLINA' => 'SC', 'SOUTH DAKOTA' => 'SD',
10244        'TENNESSEE' => 'TN', 'TEXAS' => 'TX', 'UTAH' => 'UT', 'VERMONT' => 'VT', 'VIRGIN ISLANDS' => 'VI', 'VIRGINIA' => 'VA', 'WASHINGTON' => 'WA',
10245        'WEST VIRGINIA' => 'WV', 'WISCONSIN' => 'WI', 'WYOMING' => 'WY'
10246    );
10247
10248    /**Translates a US state name into its two-letter postal code.
10249    * If the translation fails, $state is returned unchanged
10250    * @param $record The record
10251    */
10252    public static function getStatePostalCode($record)
10253    {
10254        $state = $record["state"];
10255        $s = str_replace("  ", " ", trim(strtoupper($state)));
10256        if (isset(self::$statePostalCodes[$s])) {
10257            return self::$statePostalCodes[$s];
10258        } else {
10259            return $state;
10260        }
10261    }
10262
10263    function summarize($str, $limit=50, $appendString=' ...')
10264    {
10265        if (strlen($str) > $limit) {
10266            $str = substr($str, 0, $limit - strlen($appendString)) . $appendString;
10267        }
10268        return $str;
10269    }
10270
10271    function summarizeComment($record, $limit)
10272    {
10273        return self::summarize($record["comment"], $limit);
10274    }
10275}
10276
10277/**Command class that implements the command pattern.
10278* It implements the XML_Query2XML_Callback interface
10279* and therefore has to provide the public non-static
10280* method execute(array $record).
10281*/
10282class UppercaseColumnCommand implements XML_Query2XML_Callback
10283{
10284    public function __construct($columnName)
10285    {
10286        $this->_columnName = $columnName;
10287    }
10288    public function execute(array $record)
10289    {
10290        return strtoupper($record[$this->_columnName]);
10291    }
10292}
10293
10294$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
10295
10296require_once 'Log.php';
10297$debugLogger = Log::factory('file', 'case06.log', 'XML_Query2XML');
10298$query2xml->enableDebugLog($debugLogger);
10299
10300$query2xml->startProfiling();
10301
10302
10303$dom = $query2xml->getXML(
10304    "SELECT
10305         s.*,
10306         manager.employeeid AS manager_employeeid,
10307         manager.employeename AS manager_employeename,
10308         d.*,
10309         department_head.employeeid AS department_head_employeeid,
10310         department_head.employeename AS department_head_employeename,
10311         e.*,
10312         sa.*,
10313         c.*,
10314         al.*,
10315         ar.*,
10316         (SELECT COUNT(*) FROM sale WHERE sale.store_id = s.storeid) AS store_sales,
10317         (SELECT
10318            COUNT(*)
10319          FROM
10320            sale, employee, employee_department
10321          WHERE
10322            sale.employee_id = employee.employeeid
10323            AND
10324            employee_department.employee_id = employee.employeeid
10325            AND
10326            employee_department.department_id = d.departmentid
10327         ) AS department_sales,
10328         (SELECT
10329            COUNT(*)
10330          FROM
10331            employee, employee_department, department
10332          WHERE
10333            employee_department.employee_id = employee.employeeid
10334            AND
10335            employee_department.department_id = department.departmentid
10336            AND
10337            department.store_id = s.storeid
10338         ) AS store_employees,
10339         (SELECT
10340            COUNT(*)
10341          FROM
10342            employee, employee_department
10343          WHERE
10344            employee_department.employee_id = employee.employeeid
10345            AND
10346            employee_department.department_id = d.departmentid
10347         ) AS department_employees
10348     FROM
10349         store s
10350          LEFT JOIN employee manager ON s.manager = manager.employeeid
10351         LEFT JOIN department d ON d.store_id = s.storeid
10352          LEFT JOIN employee department_head ON department_head.employeeid = d.department_head
10353          LEFT JOIN employee_department ed ON ed.department_id = d.departmentid
10354           LEFT JOIN employee e ON e.employeeid = ed.employee_id
10355            LEFT JOIN sale sa ON sa.employee_id = e.employeeid
10356             LEFT JOIN customer c ON c.customerid = sa.customer_id
10357             LEFT JOIN album al ON al.albumid = sa.album_id
10358              LEFT JOIN artist ar ON ar.artistid = al.artist_id",
10359    array(
10360        'rootTag' => 'music_company',
10361        'rowTag' => 'store',
10362        'idColumn' => 'storeid',
10363        'attributes' => array(
10364            'storeid'
10365        ),
10366        'elements' => array(
10367            'store_sales',
10368            'store_employees',
10369            'manager' => array(
10370                'idColumn' => 'manager_employeeid',
10371                'attributes' => array(
10372                    'manager_employeeid'
10373                ),
10374                'elements' => array(
10375                    'manager_employeename'
10376                )
10377            ),
10378            'address' => array(
10379                'elements' => array(
10380                    'country',
10381                    'state' => '#Helper::getStatePostalCode()',
10382                    'city' => new UppercaseColumnCommand('city'),
10383                    'street',
10384                    'phone'
10385                )
10386            ),
10387            'department' => array(
10388                'idColumn' => 'departmentid',
10389                'attributes' => array(
10390                    'departmentid'
10391                ),
10392                'elements' => array(
10393                    'department_sales',
10394                    'department_employees',
10395                    'departmentname',
10396                    'department_head' => array(
10397                        'idColumn' => 'department_head_employeeid',
10398                        'attributes' => array(
10399                            'department_head_employeeid'
10400                        ),
10401                        'elements' => array(
10402                            'department_head_employeename'
10403                        )
10404                    ),
10405                    'employees' => array(
10406                        'rootTag' => 'employees',
10407                        'rowTag' => 'employee',
10408                        'idColumn' => 'employeeid',
10409                        'attributes' => array(
10410                            'employeeid'
10411                        ),
10412                        'elements' => array(
10413                            'employeename',
10414                            'sales' => array(
10415                                'rootTag' => 'sales',
10416                                'rowTag' => 'sale',
10417                                'idColumn' => 'saleid',
10418                                'attributes' => array(
10419                                    'saleid'
10420                                ),
10421                                'elements' => array(
10422                                    'timestamp',
10423                                    'customer' => array(
10424                                        'idColumn' => 'customerid',
10425                                        'attributes' => array(
10426                                            'customerid'
10427                                        ),
10428                                        'elements' => array(
10429                                            'first_name',
10430                                            'last_name',
10431                                            'email'
10432                                        )
10433                                    ),
10434                                    'album' => array(
10435                                        'idColumn' => 'albumid',
10436                                        'attributes' => array(
10437                                            'albumid'
10438                                        ),
10439                                        'elements' => array(
10440                                            'title',
10441                                            'published_year',
10442                                            'comment' => '?#Helper::summarizeComment(12)',
10443                                            'artist' => array(
10444                                                'idColumn' => 'artistid',
10445                                                'attributes' => array(
10446                                                    'artistid'
10447                                                ),
10448                                                'elements' => array(
10449                                                    'name',
10450                                                    'birth_year',
10451                                                    'birth_place',
10452                                                    'genre'
10453                                                )
10454                                            )
10455                                        ) // album elements
10456                                    ) //album array
10457                                ) //sales elements
10458                            ) //sales array
10459                        ) //employees elements
10460                    ) //employees array
10461                ) //department elements
10462            ) // department array
10463        ) //root elements
10464    ) //root
10465); //getXML method call
10466
10467$root = $dom->firstChild;
10468$root->setAttribute('date_generated', date("Y-m-d\TH:i:s", 1124801570));
10469
10470header('Content-Type: application/xml');
10471
10472$dom->formatOutput = true;
10473print $dom->saveXML();
10474
10475require_once 'File.php';
10476$fp = new File();
10477$fp->write('case06.profile', $query2xml->getProfile(), FILE_MODE_WRITE);
10478?>
10479     ]]>
10480     </programlisting>
10481     The lines 75-77 do the debugging, line 79 and 254-256 the profiling. This will create
10482     case06.log and case06.profile.
10483     </para>
10484    </refsect3>
10485    <refsect3 id="{@id case06_log}">
10486     <title>case06.log</title>
10487     <para>
10488     The format of a debug log file is documented at {@tutorial XML_Query2XML.pkg#debugging}.
10489     Our debug log contains just a single query:
10490     <![CDATA[
10491Feb 11 17:39:46 XML_Query2XML [info] QUERY: SELECT
10492         s.*,
10493         manager.employeeid AS manager_employeeid,
10494         manager.employeename AS manager_employeename,
10495         d.*,
10496         department_head.employeeid AS department_head_employeeid,
10497         department_head.employeename AS department_head_employeename,
10498         e.*,
10499         sa.*,
10500         c.*,
10501         al.*,
10502         ar.*,
10503         (SELECT COUNT(*) FROM sale WHERE sale.store_id = s.storeid) AS store_sales,
10504         (SELECT
10505            COUNT(*)
10506          FROM
10507            sale, employee, employee_department
10508          WHERE
10509            sale.employee_id = employee.employeeid
10510            AND
10511            employee_department.employee_id = employee.employeeid
10512            AND
10513            employee_department.department_id = d.departmentid
10514         ) AS department_sales,
10515         (SELECT
10516            COUNT(*)
10517          FROM
10518            employee, employee_department, department
10519          WHERE
10520            employee_department.employee_id = employee.employeeid
10521            AND
10522            employee_department.department_id = department.departmentid
10523            AND
10524            department.store_id = s.storeid
10525         ) AS store_employees,
10526         (SELECT
10527            COUNT(*)
10528          FROM
10529            employee, employee_department
10530          WHERE
10531            employee_department.employee_id = employee.employeeid
10532            AND
10533            employee_department.department_id = d.departmentid
10534         ) AS department_employees
10535     FROM
10536         store s
10537          LEFT JOIN employee manager ON s.manager = manager.employeeid
10538         LEFT JOIN department d ON d.store_id = s.storeid
10539          LEFT JOIN employee department_head ON department_head.employeeid = d.department_head
10540          LEFT JOIN employee_department ed ON ed.department_id = d.departmentid
10541           LEFT JOIN employee e ON e.employeeid = ed.employee_id
10542            LEFT JOIN sale sa ON sa.employee_id = e.employeeid
10543             LEFT JOIN customer c ON c.customerid = sa.customer_id
10544             LEFT JOIN album al ON al.albumid = sa.album_id
10545              LEFT JOIN artist ar ON ar.artistid = al.artist_id
10546Feb 11 17:39:46 XML_Query2XML [info] DONE
10547     ]]>
10548     </para>
10549    </refsect3>
10550    <refsect3 id="{@id case06_profile}">
10551     <title>case06.profile</title>
10552     <para>
10553     The format of the output is documented under
10554     {@tutorial XML_Query2XML.pkg#profiling.getprofile}:
10555     <![CDATA[
10556FROM_DB FROM_CACHE CACHED AVG_DURATION DURATION_SUM SQL
105571       0          false  0.0172939300 0.0172939300 SELECT
10558         s.*,
10559         manager.employeeid AS manager_employeeid,
10560         manager.employeename AS manager_employeename,
10561         d.*,
10562         department_head.employeeid AS department_head_employeeid,
10563         department_head.employeename AS department_head_employeename,
10564         e.*,
10565         sa.*,
10566         c.*,
10567         al.*,
10568         ar.*,
10569         (SELECT COUNT(*) FROM sale WHERE sale.store_id = s.storeid) AS store_sales,
10570         (SELECT
10571            COUNT(*)
10572          FROM
10573            sale, employee, employee_department
10574          WHERE
10575            sale.employee_id = employee.employeeid
10576            AND
10577            employee_department.employee_id = employee.employeeid
10578            AND
10579            employee_department.department_id = d.departmentid
10580         ) AS department_sales,
10581         (SELECT
10582            COUNT(*)
10583          FROM
10584            employee, employee_department, department
10585          WHERE
10586            employee_department.employee_id = employee.employeeid
10587            AND
10588            employee_department.department_id = department.departmentid
10589            AND
10590            department.store_id = s.storeid
10591         ) AS store_employees,
10592         (SELECT
10593            COUNT(*)
10594          FROM
10595            employee, employee_department
10596          WHERE
10597            employee_department.employee_id = employee.employeeid
10598            AND
10599            employee_department.department_id = d.departmentid
10600         ) AS department_employees
10601     FROM
10602         store s
10603          LEFT JOIN employee manager ON s.manager = manager.employeeid
10604         LEFT JOIN department d ON d.store_id = s.storeid
10605          LEFT JOIN employee department_head ON department_head.employeeid = d.department_head
10606          LEFT JOIN employee_department ed ON ed.department_id = d.departmentid
10607           LEFT JOIN employee e ON e.employeeid = ed.employee_id
10608            LEFT JOIN sale sa ON sa.employee_id = e.employeeid
10609             LEFT JOIN customer c ON c.customerid = sa.customer_id
10610             LEFT JOIN album al ON al.albumid = sa.album_id
10611              LEFT JOIN artist ar ON ar.artistid = al.artist_id
10612
10613TOTAL_DURATION: 0.29014992713928
10614DB_DURATION:    0.1554069519043
10615     ]]>
10616     </para>
10617    </refsect3>
10618   </refsect2>
10619   <refsect2 id="{@id case07}">
10620    <title>Case 07: Case 03 with Asterisk Shortcuts</title>
10621    <para>
10622     Case 07 will teach you:
10623     <itemizedlist>
10624      <listitem>
10625       How to use the {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.asterisk_shortcut}
10626      </listitem>
10627     </itemizedlist>
10628    </para>
10629    <para>
10630     As documented under {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_elements.asterisk_shortcut}
10631     an asterisk can be used to treat all columns found in the result set the same.
10632    </para>
10633    <refsect3 id="{@id case07_php}">
10634     <title>case07.php</title>
10635     <para>
10636     case07.php looks like this:
10637     <programlisting role="php">
10638     <![CDATA[
10639<?php
10640require_once 'XML/Query2XML.php';
10641require_once 'MDB2.php';
10642$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
10643$dom = $query2xml->getXML(
10644    "SELECT
10645        *
10646     FROM
10647        artist",
10648    array(
10649        'rootTag' => 'music_library',
10650        'rowTag' => 'artist',
10651        'idColumn' => 'artistid',
10652        'elements' => array(
10653            '*',
10654            'albums' => array(
10655                'sql' => array(
10656                    'data' => array(
10657                        'artistid'
10658                    ),
10659                    'query' => 'SELECT * FROM album WHERE artist_id = ?'
10660                ),
10661                'rootTag' => 'albums',
10662                'rowTag' => 'album',
10663                'idColumn' => 'albumid',
10664                'elements' => array(
10665                    '*',
10666                    'artist_id' => '?:'
10667                )
10668            )
10669        )
10670    )
10671);
10672
10673header('Content-Type: application/xml');
10674
10675$dom->formatOutput = true;
10676print $dom->saveXML();
10677?>
10678      ]]>
10679      </programlisting>
10680       As in {@tutorial XML_Query2XML.pkg#casestudies.case03} we use two separate
10681       queries for the artist and the album tables. All columns of the artist
10682       table are child elements of the artist tags. The columns of the album
10683       table are all below the album tags - with one exception: the artist_id
10684       column is excluded as it only contains redundant information.
10685     </para>
10686    </refsect3>
10687    <refsect3 id="{@id case07_xml}">
10688     <title>case07.xml</title>
10689     <para>
10690     The resulting XML data is identical with {@tutorial XML_Query2XML.pkg#casestudies.case03.case03_xml}:
10691     <programlisting role="tutorial">
10692     <![CDATA[
10693<?xml version="1.0" encoding="UTF-8"?>
10694<music_library>
10695  <artist>
10696    <artistid>1</artistid>
10697    <name>Curtis Mayfield</name>
10698    <birth_year>1920</birth_year>
10699    <birth_place>Chicago</birth_place>
10700    <genre>Soul</genre>
10701    <albums>
10702      <album>
10703        <albumid>1</albumid>
10704        <title>New World Order</title>
10705        <published_year>1990</published_year>
10706        <comment>the best ever!</comment>
10707      </album>
10708      <album>
10709        <albumid>2</albumid>
10710        <title>Curtis</title>
10711        <published_year>1970</published_year>
10712        <comment>that man's got somthin' to say</comment>
10713      </album>
10714    </albums>
10715  </artist>
10716  <artist>
10717    <artistid>2</artistid>
10718    <name>Isaac Hayes</name>
10719    <birth_year>1942</birth_year>
10720    <birth_place>Tennessee</birth_place>
10721    <genre>Soul</genre>
10722    <albums>
10723      <album>
10724        <albumid>3</albumid>
10725        <title>Shaft</title>
10726        <published_year>1972</published_year>
10727        <comment>he's the man</comment>
10728      </album>
10729    </albums>
10730  </artist>
10731  <artist>
10732    <artistid>3</artistid>
10733    <name>Ray Charles</name>
10734    <birth_year>1930</birth_year>
10735    <birth_place>Mississippi</birth_place>
10736    <genre>Country and Soul</genre>
10737    <albums />
10738  </artist>
10739</music_library>
10740     ]]>
10741     </programlisting>
10742     </para>
10743    </refsect3>
10744    <refsect3 id="{@id case07_debug_php}">
10745     <title>case07_debug.php</title>
10746     <para>
10747     case07_debug.php is very similar to {@tutorial XML_Query2XML.pkg#casestudies.case03.case03_debug_php}:
10748     <programlisting role="php">
10749     <![CDATA[
10750<?php
10751require_once 'XML/Query2XML.php';
10752require_once 'MDB2.php';
10753$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
10754
10755require_once 'Log.php';
10756$debugLogger = Log::factory('file', 'case07.log', 'Query2XML');
10757$query2xml->enableDebugLog($debugLogger);
10758
10759$query2xml->startProfiling();
10760
10761
10762$dom = $query2xml->getXML(
10763    "SELECT
10764        *
10765     FROM
10766        artist",
10767    array(
10768        'rootTag' => 'music_library',
10769        'rowTag' => 'artist',
10770        'idColumn' => 'artistid',
10771        'elements' => array(
10772            '*',
10773            'albums' => array(
10774                'sql' => array(
10775                    'data' => array(
10776                        'artistid'
10777                    ),
10778                    'query' => 'SELECT * FROM album WHERE artist_id = ?'
10779                ),
10780                'rootTag' => 'albums',
10781                'rowTag' => 'album',
10782                'idColumn' => 'albumid',
10783                'elements' => array(
10784                    '*',
10785                    'artist_id' => '?:'
10786                )
10787            )
10788        )
10789    )
10790);
10791
10792header('Content-Type: application/xml');
10793
10794$dom->formatOutput = true;
10795print $dom->saveXML();
10796
10797require_once 'File.php';
10798$fp = new File();
10799$fp->write('case07.profile', $query2xml->getProfile(), FILE_MODE_WRITE);
10800?>
10801     ]]>
10802     </programlisting>
10803     The lines 6-8 do the debugging, line 10 and 48-50 the profiling. This will create
10804     case07.log and case07.profile.
10805     </para>
10806    </refsect3>
10807    <refsect3 id="{@id case07_log}">
10808     <title>case07.log</title>
10809     <para>
10810     The format of a debug log file is documented at {@tutorial XML_Query2XML.pkg#debugging}.
10811     Our debug log now contains 4 queries:
10812     <![CDATA[
10813Apr 18 19:02:48 Query2XML [info] QUERY: SELECT
10814        *
10815     FROM
10816        artist
10817     ORDER BY
10818        artistid
10819Apr 18 19:02:48 Query2XML [info] DONE
10820Apr 18 19:02:48 Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:1
10821Apr 18 19:02:48 Query2XML [info] DONE
10822Apr 18 19:02:48 Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:2
10823Apr 18 19:02:48 Query2XML [info] DONE
10824Apr 18 19:02:48 Query2XML [info] QUERY: SELECT * FROM album WHERE artist_id = ?; DATA:3
10825Apr 18 19:02:48 Query2XML [info] DONE
10826     ]]>
10827     </para>
10828     The debug log shows what we expected: the first SELECT over the artist table runs once
10829     and the SELECT over the album table runs three times (once for every record found in
10830     the artist table). As the log shows no 'CACHING' entries we also know that no cashing
10831     was performed ({@tutorial XML_Query2XML.pkg#query2xml_getxml.options_sql_options.cached}
10832     was not set to true).
10833    </refsect3>
10834    <refsect3 id="{@id case07_profile}">
10835     <title>case07.profile</title>
10836     <para>
10837     Profiling is essential for performance tuning. The format of the output is documented under
10838     {@tutorial XML_Query2XML.pkg#profiling.getprofile}. Our profile looks like this:
10839     <![CDATA[
10840FROM_DB FROM_CACHE CACHED AVG_DURATION DURATION_SUM SQL
108411       0          false  0.0018889904 0.0018889904 SELECT
10842        *
10843     FROM
10844        artist
108453       0          false  0.0021993319 0.0065979957 SELECT * FROM album WHERE artist_id = ?
10846
10847TOTAL_DURATION: 0.052389144897461
10848DB_DURATION:    0.016173124313354
10849     ]]>
10850     </para>
10851    </refsect3>
10852   </refsect2>
10853   <refsect2 id="{@id case08}">
10854    <title>Case 08: Case 06 revisited: Making use of Mappers</title>
10855    <para>
10856     Case 08 will teach you:
10857     <itemizedlist>
10858      <listitem>
10859       How to do mapping of SQL identifiers to XML element names in accordenace with
10860       {@link http://www.sqlx.org/SQL-XML-documents/5FCD-14-XML-2004-07.pdf ISO/IEC 9075-14:2005}.
10861      </listitem>
10862      <listitem>
10863       How to write your own mappers.
10864      </listitem>
10865      <listitem>
10866       How to use different mappers at different levels.
10867      </listitem>
10868     </itemizedlist>
10869    </para>
10870    <para>
10871     Make sure you are familiar with {@tutorial XML_Query2XML.pkg#casestudies.case06}
10872     before proceeding. Case 08 is different from Case 06 in the following aspects:
10873     <itemizedlist>
10874      <listitem>
10875       All tags and attributes directly related to columns of the table "store" shall be uppercase;
10876       we'll directly use the PHP function strtoupper() and the 'FUNCTION' syntax for this purpose.
10877      </listitem>
10878      <listitem>
10879       All tags and attributes directly related to columns of the table "department" shall use
10880       a naming convention like "oneTwoThree" instead of "one_two_three";
10881       we'll use Mappers::departmentMapper() and the 'CLASS::STATIC_METHOD' syntax for this purpose.
10882      </listitem>
10883      <listitem>
10884       All tags and attributes directly related to columns of the table "employee" shall use
10885       a naming convention like "OneTwoThree" instead of "one_two_three";
10886       we'll use Mappers::employeeMapper() and the array('CLASS', 'STATIC_METHOD') syntax for this purpose.
10887      </listitem>
10888      <listitem>
10889       All tags and attributes directly related to columns of the table "sale" shall use
10890       a naming convention like "ONETWOTHREE" instead of "one_two_three";
10891       we'll use the non-static Mappers::saleMapper() and the array($instance, 'METHOD') syntax for this purpose.
10892      </listitem>
10893      <listitem>
10894       All tags and attributes directly related to columns of the table "customer" shall use
10895       the column names as they are defined in the database;
10896       we'll set the mapper option to false for this purpose.
10897      </listitem>
10898      <listitem>
10899       All tags and attributes directly related to columns of the table "album" shall use
10900       the ISO 9075-mapper; we'll use XML_Query2XML_ISO9075Mapper::map() and the 'CLASS::STATIC_METHOD'
10901       syntax for this purpose.
10902      </listitem>
10903      <listitem>
10904       All tags and attributes directly related to columns of the table "artist" shall use
10905       a naming convention like "onetwothree" instead of "one_two_three";
10906       we'll the function mapArtist as the mapper and the 'FUNCTION' syntax.
10907      </listitem>
10908      <listitem>
10909       Due to the fact that SQLite prior to 3.1 does not support
10910       a subselect backreferencing to a field in its parent select
10911       (see {@link http://www.sqlite.org/cvstrac/wiki?p=UnsupportedSql}) Case 08 will not work
10912       with SQLite &lt; 3.1.
10913      </listitem>
10914     </itemizedlist>
10915     This certainly is not a very realistic scenario but it was chosen to demonstrate the
10916     different syntaxes for specifying a mapper function and how to use multiple
10917     mappers. This case also demonstrates that all XML names explicitly specified are
10918     not subject to any special mapping! For example the tag names "music_company" and
10919     "store" are left untouched because they are explicitly specified using the options
10920     "rootTag" and "rowTag". An other case is the tag "manager" which also stays untouched.
10921     This is because the array key ("manager" in this case) of a complex element specification
10922     is used for {@tutorial XML_Query2XML.pkg#query2xml_getxml.options_rowtag the option "rowTag"}
10923     per default. XML_Query2XML just does not pretend to be smart than you, the programmer. If you don't
10924     like the tag name "manager", use another array key for the complex element specification or
10925     use the "rowTag" option within the complex element specification.
10926    </para>
10927    <refsect3 id="{@id case08_php}">
10928     <title>case08.php</title>
10929     <para>
10930     case08.php looks like this:
10931     <programlisting role="php">
10932     <![CDATA[
10933<?php
10934class Mappers
10935{
10936    public static function departmentMapper($str)
10937    {
10938        //maps 'one_two_three' to 'oneTwoThree'
10939        return preg_replace("/(_)([a-z])/e", "strtoupper('\\2')", $str);
10940    }
10941
10942    public static function employeeMapper($str)
10943    {
10944        //maps 'one_two_three' to 'OneTwoThree'
10945        return ucfirst(preg_replace("/(_)([a-z])/e", "strtoupper('\\2')", $str));
10946    }
10947
10948    public function saleMapper($str)
10949    {
10950        //maps 'one_two_three' to 'ONETWOTHREE'
10951        return strtoupper(str_replace('_', '', $str));
10952    }
10953}
10954
10955function mapArtist($str)
10956{
10957    //maps 'one_two_three' to 'onetwothree'
10958    return strtolower(str_replace('_', '', $str));
10959}
10960
10961$myMappers = new Mappers();
10962
10963require_once 'XML/Query2XML.php';
10964require_once 'XML/Query2XML/ISO9075Mapper.php';
10965require_once 'MDB2.php';
10966$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
10967$dom = $query2xml->getXML(
10968    "SELECT
10969         s.*,
10970         manager.employeeid AS manager_employeeid,
10971         manager.employeename AS manager_employeename,
10972         d.*,
10973         department_head.employeeid AS department_head_employeeid,
10974         department_head.employeename AS department_head_employeename,
10975         e.*,
10976         sa.*,
10977         c.*,
10978         al.*,
10979         ar.*,
10980         (SELECT COUNT(*) FROM sale WHERE sale.store_id = s.storeid) AS store_sales,
10981         (SELECT
10982            COUNT(*)
10983          FROM
10984            sale, employee, employee_department
10985          WHERE
10986            sale.employee_id = employee.employeeid
10987            AND
10988            employee_department.employee_id = employee.employeeid
10989            AND
10990            employee_department.department_id = d.departmentid
10991         ) AS department_sales,
10992         (SELECT
10993            COUNT(*)
10994          FROM
10995            employee, employee_department, department
10996          WHERE
10997            employee_department.employee_id = employee.employeeid
10998            AND
10999            employee_department.department_id = department.departmentid
11000            AND
11001            department.store_id = s.storeid
11002         ) AS store_employees,
11003         (SELECT
11004            COUNT(*)
11005          FROM
11006            employee, employee_department
11007          WHERE
11008            employee_department.employee_id = employee.employeeid
11009            AND
11010            employee_department.department_id = d.departmentid
11011         ) AS department_employees
11012     FROM
11013         store s
11014          LEFT JOIN employee manager ON s.manager = manager.employeeid
11015         LEFT JOIN department d ON d.store_id = s.storeid
11016          LEFT JOIN employee department_head ON department_head.employeeid = d.department_head
11017          LEFT JOIN employee_department ed ON ed.department_id = d.departmentid
11018           LEFT JOIN employee e ON e.employeeid = ed.employee_id
11019            LEFT JOIN sale sa ON sa.employee_id = e.employeeid
11020             LEFT JOIN customer c ON c.customerid = sa.customer_id
11021             LEFT JOIN album al ON al.albumid = sa.album_id
11022              LEFT JOIN artist ar ON ar.artistid = al.artist_id",
11023    array(
11024        'rootTag' => 'music_company',
11025        'rowTag' => 'store',
11026        'idColumn' => 'storeid',
11027        'mapper' => 'strtoupper',
11028        'attributes' => array(
11029            'storeid'
11030        ),
11031        'elements' => array(
11032            'store_sales',
11033            'store_employees',
11034            'manager' => array(
11035                'idColumn' => 'manager_employeeid',
11036                'attributes' => array(
11037                    'manager_employeeid'
11038                ),
11039                'elements' => array(
11040                    'manager_employeename'
11041                )
11042            ),
11043            'address' => array(
11044                'elements' => array(
11045                    'country',
11046                    'state' => '#Helper::getStatePostalCode()',
11047                    'city',
11048                    'street',
11049                    'phone'
11050                )
11051            ),
11052            'department' => array(
11053                'idColumn' => 'departmentid',
11054                'mapper' => 'Mappers::departmentMapper',
11055                'attributes' => array(
11056                    'departmentid'
11057                ),
11058                'elements' => array(
11059                    'department_sales',
11060                    'department_employees',
11061                    'departmentname',
11062                    'department_head' => array(
11063                        'idColumn' => 'department_head_employeeid',
11064                        'attributes' => array(
11065                            'department_head_employeeid'
11066                        ),
11067                        'elements' => array(
11068                            'department_head_employeename'
11069                        )
11070                    ),
11071                    'employees' => array(
11072                        'rootTag' => 'employees',
11073                        'rowTag' => 'employee',
11074                        'idColumn' => 'employeeid',
11075                        'mapper' => array('Mappers', 'employeeMapper'),
11076                        'attributes' => array(
11077                            'employeeid'
11078                        ),
11079                        'elements' => array(
11080                            'employeename',
11081                            'sales' => array(
11082                                'rootTag' => 'sales',
11083                                'rowTag' => 'sale',
11084                                'idColumn' => 'saleid',
11085                                'mapper' => array($myMappers, 'saleMapper'),
11086                                'attributes' => array(
11087                                    'saleid'
11088                                ),
11089                                'elements' => array(
11090                                    'timestamp',
11091                                    'customer' => array(
11092                                        'idColumn' => 'customerid',
11093                                        'mapper' => false,
11094                                        'attributes' => array(
11095                                            'customerid'
11096                                        ),
11097                                        'elements' => array(
11098                                            'first_name',
11099                                            'last_name',
11100                                            'email'
11101                                        )
11102                                    ),
11103                                    'album' => array(
11104                                        'idColumn' => 'albumid',
11105                                        'mapper' => 'XML_Query2XML_ISO9075Mapper::map',
11106                                        'attributes' => array(
11107                                            'albumid'
11108                                        ),
11109                                        'elements' => array(
11110                                            'title',
11111                                            'published_year',
11112                                            'comment' => '?#Helper::summarizeComment(12)',
11113                                            'artist' => array(
11114                                                'idColumn' => 'artistid',
11115                                                'mapper' => 'mapArtist',
11116                                                'attributes' => array(
11117                                                    'artistid'
11118                                                ),
11119                                                'elements' => array(
11120                                                    'name',
11121                                                    'birth_year',
11122                                                    'birth_place',
11123                                                    'genre'
11124                                                )
11125                                            )
11126                                        ) // album elements
11127                                    ) //album array
11128                                ) //sales elements
11129                            ) //sales array
11130                        ) //employees elements
11131                    ) //employees array
11132                ) //department elements
11133            ) // department array
11134        ) //root elements
11135    ) //root
11136); //getXML method call
11137
11138$root = $dom->firstChild;
11139$root->setAttribute('date_generated', date("Y-m-d\TH:i:s", 1124801570));
11140
11141header('Content-Type: application/xml');
11142
11143$dom->formatOutput = true;
11144print $dom->saveXML();
11145
11146
11147
11148/**Static class that provides validation and parsing methods for
11149* generating XML.
11150*
11151* It is static so that we can easyly call its methods from inside
11152* Query2XML using eval'd code.
11153*/
11154class Helper
11155{
11156    /**Associative array of US postal state codes*/
11157    public static $statePostalCodes = array(
11158        'ALABAMA' => 'AL', 'ALASKA' => 'AK', 'AMERICAN SAMOA' => 'AS', 'ARIZONA' => 'AZ', 'ARKANSAS' => 'AR', 'CALIFORNIA' => 'CA',
11159        'COLORADO' => 'CO', 'CONNECTICUT' => 'CT', 'DELAWARE' => 'DE', 'DISTRICT OF COLUMBIA' => 'DC', 'FEDERATED STATES OF MICRONESIA' => 'FM',
11160        'FLORIDA' => 'FL', 'GEORGIA' => 'GA', 'GUAM' => 'GU', 'HAWAII' => 'HI', 'IDAHO' => 'ID', 'ILLINOIS' => 'IL', 'INDIANA' => 'IN',
11161        'IOWA' => 'IA', 'KANSAS' => 'KS', 'KENTUCKY' => 'KY', 'LOUISIANA' => 'LA', 'MAINE' => 'ME', 'MARSHALL ISLANDS' => 'MH', 'MARYLAND' => 'MD',
11162        'MASSACHUSETTS' => 'MA', 'MICHIGAN' => 'MI', 'MINNESOTA' => 'MN', 'MISSISSIPPI' => 'MS', 'MISSOURI' => 'MO', 'MONTANA' => 'MT',
11163        'NEBRASKA' => 'NE', 'NEVADA' => 'NV', 'NEW HAMPSHIRE' => 'NH', 'NEW JERSEY' => 'NJ', 'NEW JESEY' => 'NJ', 'NEW MEXICO' => 'NM', 'NEW YORK' => 'NY',
11164        'NORTH CAROLINA' => 'NC', 'NORTH DAKOTA' => 'ND', 'NORTHERN MARIANA ISLANDS' => 'MP', 'OHIO' => 'OH', 'OKLAHOMA' => 'OK', 'OREGON' => 'OR',
11165        'PALAU' => 'PW', 'PENNSYLVANIA' => 'PA', 'PUERTO RICO' => 'PR', 'RHODE ISLAND' => 'RI', 'SOUTH CAROLINA' => 'SC', 'SOUTH DAKOTA' => 'SD',
11166        'TENNESSEE' => 'TN', 'TEXAS' => 'TX', 'UTAH' => 'UT', 'VERMONT' => 'VT', 'VIRGIN ISLANDS' => 'VI', 'VIRGINIA' => 'VA', 'WASHINGTON' => 'WA',
11167        'WEST VIRGINIA' => 'WV', 'WISCONSIN' => 'WI', 'WYOMING' => 'WY'
11168    );
11169
11170    /**Translates a US state name into its two-letter postal code.
11171    * If the translation fails, $state is returned unchanged
11172    * @param $record The record
11173    */
11174    public static function getStatePostalCode($record)
11175    {
11176        $state = $record["state"];
11177        $s = str_replace("  ", " ", trim(strtoupper($state)));
11178        if (isset(self::$statePostalCodes[$s])) {
11179            return self::$statePostalCodes[$s];
11180        } else {
11181            return $state;
11182        }
11183    }
11184
11185    function summarize($str, $limit=50, $appendString=' ...')
11186    {
11187        if (strlen($str) > $limit) {
11188            $str = substr($str, 0, $limit - strlen($appendString)) . $appendString;
11189        }
11190        return $str;
11191    }
11192
11193
11194    function summarizeComment($record, $limit)
11195    {
11196        return self::summarize($record["comment"], $limit);
11197    }
11198}
11199?>
11200     ]]>
11201     </programlisting>
11202     </para>
11203    </refsect3>
11204    <refsect3 id="{@id case08_xml}">
11205     <title>case08.xml</title>
11206     <para>
11207     The resulting XML data looks like this:
11208     <programlisting role="tutorial">
11209     <![CDATA[
11210<?xml version="1.0" encoding="UTF-8"?>
11211<music_company date_generated="2005-08-23T14:52:50">
11212    <store STOREID="1">
11213        <STORE_SALES>10</STORE_SALES>
11214        <STORE_EMPLOYEES>6</STORE_EMPLOYEES>
11215        <manager MANAGER_EMPLOYEEID="1">
11216            <MANAGER_EMPLOYEENAME>Michael Jones</MANAGER_EMPLOYEENAME>
11217        </manager>
11218        <address>
11219            <COUNTRY>US</COUNTRY>
11220            <STATE>NY</STATE>
11221            <CITY>New York</CITY>
11222            <STREET>Broadway &amp; 72nd Str</STREET>
11223            <PHONE>123 456 7890</PHONE>
11224        </address>
11225        <department departmentid="1">
11226            <departmentSales>10</departmentSales>
11227            <departmentEmployees>3</departmentEmployees>
11228            <departmentname>Sales</departmentname>
11229            <department_head departmentHeadEmployeeid="1">
11230                <departmentHeadEmployeename>Michael Jones</departmentHeadEmployeename>
11231            </department_head>
11232            <employees>
11233                <employee Employeeid="1">
11234                    <Employeename>Michael Jones</Employeename>
11235                    <sales>
11236                        <sale SALEID="1">
11237                            <TIMESTAMP>2005-05-25 16:32:00</TIMESTAMP>
11238                            <customer customerid="1">
11239                                <first_name>Jane</first_name>
11240                                <last_name>Doe</last_name>
11241                                <email>jane.doe@example.com</email>
11242                            </customer>
11243                            <album albumid="1">
11244                                <title>New World Order</title>
11245                                <published_year>1990</published_year>
11246                                <comment>the best ...</comment>
11247                                <artist artistid="1">
11248                                    <name>Curtis Mayfield</name>
11249                                    <birthyear>1920</birthyear>
11250                                    <birthplace>Chicago</birthplace>
11251                                    <genre>Soul</genre>
11252                                </artist>
11253                            </album>
11254                        </sale>
11255                        <sale SALEID="7">
11256                            <TIMESTAMP>2005-07-10 15:03:00</TIMESTAMP>
11257                            <customer customerid="7">
11258                                <first_name>Nick</first_name>
11259                                <last_name>Fallow</last_name>
11260                                <email>nick.fallow@example.com</email>
11261                            </customer>
11262                            <album albumid="1">
11263                                <title>New World Order</title>
11264                                <published_year>1990</published_year>
11265                                <comment>the best ...</comment>
11266                                <artist artistid="1">
11267                                    <name>Curtis Mayfield</name>
11268                                    <birthyear>1920</birthyear>
11269                                    <birthplace>Chicago</birthplace>
11270                                    <genre>Soul</genre>
11271                                </artist>
11272                            </album>
11273                        </sale>
11274                        <sale SALEID="16">
11275                            <TIMESTAMP>2005-06-05 12:56:12</TIMESTAMP>
11276                            <customer customerid="2">
11277                                <first_name>John</first_name>
11278                                <last_name>Doe</last_name>
11279                                <email>john.doe@example.com</email>
11280                            </customer>
11281                            <album albumid="3">
11282                                <title>Shaft</title>
11283                                <published_year>1972</published_year>
11284                                <comment>he's the man</comment>
11285                                <artist artistid="2">
11286                                    <name>Isaac Hayes</name>
11287                                    <birthyear>1942</birthyear>
11288                                    <birthplace>Tennessee</birthplace>
11289                                    <genre>Soul</genre>
11290                                </artist>
11291                            </album>
11292                        </sale>
11293                        <sale SALEID="19">
11294                            <TIMESTAMP>2005-07-10 16:03:01</TIMESTAMP>
11295                            <customer customerid="8">
11296                                <first_name>Ed</first_name>
11297                                <last_name>Burton</last_name>
11298                                <email>ed.burton@example.com</email>
11299                            </customer>
11300                            <album albumid="3">
11301                                <title>Shaft</title>
11302                                <published_year>1972</published_year>
11303                                <comment>he's the man</comment>
11304                                <artist artistid="2">
11305                                    <name>Isaac Hayes</name>
11306                                    <birthyear>1942</birthyear>
11307                                    <birthplace>Tennessee</birthplace>
11308                                    <genre>Soul</genre>
11309                                </artist>
11310                            </album>
11311                        </sale>
11312                    </sales>
11313                </employee>
11314                <employee Employeeid="2">
11315                    <Employeename>Susi Weintraub</Employeename>
11316                    <sales>
11317                        <sale SALEID="3">
11318                            <TIMESTAMP>2005-07-10 11:03:00</TIMESTAMP>
11319                            <customer customerid="3">
11320                                <first_name>Susan</first_name>
11321                                <last_name>Green</last_name>
11322                                <email>susan.green@example.com</email>
11323                            </customer>
11324                            <album albumid="1">
11325                                <title>New World Order</title>
11326                                <published_year>1990</published_year>
11327                                <comment>the best ...</comment>
11328                                <artist artistid="1">
11329                                    <name>Curtis Mayfield</name>
11330                                    <birthyear>1920</birthyear>
11331                                    <birthplace>Chicago</birthplace>
11332                                    <genre>Soul</genre>
11333                                </artist>
11334                            </album>
11335                        </sale>
11336                        <sale SALEID="9">
11337                            <TIMESTAMP>2005-07-10 18:03:00</TIMESTAMP>
11338                            <customer customerid="9">
11339                                <first_name>Jack</first_name>
11340                                <last_name>Woo</last_name>
11341                                <email>jack.woo@example.com</email>
11342                            </customer>
11343                            <album albumid="1">
11344                                <title>New World Order</title>
11345                                <published_year>1990</published_year>
11346                                <comment>the best ...</comment>
11347                                <artist artistid="1">
11348                                    <name>Curtis Mayfield</name>
11349                                    <birthyear>1920</birthyear>
11350                                    <birthplace>Chicago</birthplace>
11351                                    <genre>Soul</genre>
11352                                </artist>
11353                            </album>
11354                        </sale>
11355                        <sale SALEID="17">
11356                            <TIMESTAMP>2005-07-10 10:03:32</TIMESTAMP>
11357                            <customer customerid="4">
11358                                <first_name>Victoria</first_name>
11359                                <last_name>Alt</last_name>
11360                                <email>victory.alt@example.com</email>
11361                            </customer>
11362                            <album albumid="3">
11363                                <title>Shaft</title>
11364                                <published_year>1972</published_year>
11365                                <comment>he's the man</comment>
11366                                <artist artistid="2">
11367                                    <name>Isaac Hayes</name>
11368                                    <birthyear>1942</birthyear>
11369                                    <birthplace>Tennessee</birthplace>
11370                                    <genre>Soul</genre>
11371                                </artist>
11372                            </album>
11373                        </sale>
11374                        <sale SALEID="20">
11375                            <TIMESTAMP>2005-07-10 19:03:50</TIMESTAMP>
11376                            <customer customerid="10">
11377                                <first_name>Maria</first_name>
11378                                <last_name>Gonzales</last_name>
11379                                <email>maria.gonzales@example.com</email>
11380                            </customer>
11381                            <album albumid="3">
11382                                <title>Shaft</title>
11383                                <published_year>1972</published_year>
11384                                <comment>he's the man</comment>
11385                                <artist artistid="2">
11386                                    <name>Isaac Hayes</name>
11387                                    <birthyear>1942</birthyear>
11388                                    <birthplace>Tennessee</birthplace>
11389                                    <genre>Soul</genre>
11390                                </artist>
11391                            </album>
11392                        </sale>
11393                    </sales>
11394                </employee>
11395                <employee Employeeid="3">
11396                    <Employeename>Steve Hack</Employeename>
11397                    <sales>
11398                        <sale SALEID="5">
11399                            <TIMESTAMP>2005-07-10 13:03:00</TIMESTAMP>
11400                            <customer customerid="5">
11401                                <first_name>Will</first_name>
11402                                <last_name>Rippy</last_name>
11403                                <email>will.wippy@example.com</email>
11404                            </customer>
11405                            <album albumid="1">
11406                                <title>New World Order</title>
11407                                <published_year>1990</published_year>
11408                                <comment>the best ...</comment>
11409                                <artist artistid="1">
11410                                    <name>Curtis Mayfield</name>
11411                                    <birthyear>1920</birthyear>
11412                                    <birthplace>Chicago</birthplace>
11413                                    <genre>Soul</genre>
11414                                </artist>
11415                            </album>
11416                        </sale>
11417                        <sale SALEID="18">
11418                            <TIMESTAMP>2005-07-10 14:03:52</TIMESTAMP>
11419                            <customer customerid="6">
11420                                <first_name>Tim</first_name>
11421                                <last_name>Raw</last_name>
11422                                <email>tim.raw@example.com</email>
11423                            </customer>
11424                            <album albumid="3">
11425                                <title>Shaft</title>
11426                                <published_year>1972</published_year>
11427                                <comment>he's the man</comment>
11428                                <artist artistid="2">
11429                                    <name>Isaac Hayes</name>
11430                                    <birthyear>1942</birthyear>
11431                                    <birthplace>Tennessee</birthplace>
11432                                    <genre>Soul</genre>
11433                                </artist>
11434                            </album>
11435                        </sale>
11436                    </sales>
11437                </employee>
11438            </employees>
11439        </department>
11440        <department departmentid="2">
11441            <departmentSales>0</departmentSales>
11442            <departmentEmployees>3</departmentEmployees>
11443            <departmentname>Marketing</departmentname>
11444            <department_head departmentHeadEmployeeid="4">
11445                <departmentHeadEmployeename>Joan Kerr</departmentHeadEmployeename>
11446            </department_head>
11447            <employees>
11448                <employee Employeeid="4">
11449                    <Employeename>Joan Kerr</Employeename>
11450                    <sales />
11451                </employee>
11452                <employee Employeeid="5">
11453                    <Employeename>Marcus Roth</Employeename>
11454                    <sales />
11455                </employee>
11456                <employee Employeeid="6">
11457                    <Employeename>Jack Mack</Employeename>
11458                    <sales />
11459                </employee>
11460            </employees>
11461        </department>
11462    </store>
11463    <store STOREID="2">
11464        <STORE_SALES>10</STORE_SALES>
11465        <STORE_EMPLOYEES>6</STORE_EMPLOYEES>
11466        <manager MANAGER_EMPLOYEEID="2">
11467            <MANAGER_EMPLOYEENAME>Susi Weintraub</MANAGER_EMPLOYEENAME>
11468        </manager>
11469        <address>
11470            <COUNTRY>US</COUNTRY>
11471            <STATE>NY</STATE>
11472            <CITY>Larchmont</CITY>
11473            <STREET>Palmer Ave 71</STREET>
11474            <PHONE>456 7890</PHONE>
11475        </address>
11476        <department departmentid="3">
11477            <departmentSales>10</departmentSales>
11478            <departmentEmployees>3</departmentEmployees>
11479            <departmentname>Sales</departmentname>
11480            <department_head departmentHeadEmployeeid="7">
11481                <departmentHeadEmployeename>Rita Doktor</departmentHeadEmployeename>
11482            </department_head>
11483            <employees>
11484                <employee Employeeid="7">
11485                    <Employeename>Rita Doktor</Employeename>
11486                    <sales>
11487                        <sale SALEID="2">
11488                            <TIMESTAMP>2005-06-05 12:56:00</TIMESTAMP>
11489                            <customer customerid="2">
11490                                <first_name>John</first_name>
11491                                <last_name>Doe</last_name>
11492                                <email>john.doe@example.com</email>
11493                            </customer>
11494                            <album albumid="1">
11495                                <title>New World Order</title>
11496                                <published_year>1990</published_year>
11497                                <comment>the best ...</comment>
11498                                <artist artistid="1">
11499                                    <name>Curtis Mayfield</name>
11500                                    <birthyear>1920</birthyear>
11501                                    <birthplace>Chicago</birthplace>
11502                                    <genre>Soul</genre>
11503                                </artist>
11504                            </album>
11505                        </sale>
11506                        <sale SALEID="8">
11507                            <TIMESTAMP>2005-07-10 16:03:00</TIMESTAMP>
11508                            <customer customerid="8">
11509                                <first_name>Ed</first_name>
11510                                <last_name>Burton</last_name>
11511                                <email>ed.burton@example.com</email>
11512                            </customer>
11513                            <album albumid="1">
11514                                <title>New World Order</title>
11515                                <published_year>1990</published_year>
11516                                <comment>the best ...</comment>
11517                                <artist artistid="1">
11518                                    <name>Curtis Mayfield</name>
11519                                    <birthyear>1920</birthyear>
11520                                    <birthplace>Chicago</birthplace>
11521                                    <genre>Soul</genre>
11522                                </artist>
11523                            </album>
11524                        </sale>
11525                        <sale SALEID="11">
11526                            <TIMESTAMP>2005-05-25 16:23:00</TIMESTAMP>
11527                            <customer customerid="1">
11528                                <first_name>Jane</first_name>
11529                                <last_name>Doe</last_name>
11530                                <email>jane.doe@example.com</email>
11531                            </customer>
11532                            <album albumid="2">
11533                                <title>Curtis</title>
11534                                <published_year>1970</published_year>
11535                                <comment>that man ...</comment>
11536                                <artist artistid="1">
11537                                    <name>Curtis Mayfield</name>
11538                                    <birthyear>1920</birthyear>
11539                                    <birthplace>Chicago</birthplace>
11540                                    <genre>Soul</genre>
11541                                </artist>
11542                            </album>
11543                        </sale>
11544                        <sale SALEID="14">
11545                            <TIMESTAMP>2005-07-10 15:09:00</TIMESTAMP>
11546                            <customer customerid="7">
11547                                <first_name>Nick</first_name>
11548                                <last_name>Fallow</last_name>
11549                                <email>nick.fallow@example.com</email>
11550                            </customer>
11551                            <album albumid="2">
11552                                <title>Curtis</title>
11553                                <published_year>1970</published_year>
11554                                <comment>that man ...</comment>
11555                                <artist artistid="1">
11556                                    <name>Curtis Mayfield</name>
11557                                    <birthyear>1920</birthyear>
11558                                    <birthplace>Chicago</birthplace>
11559                                    <genre>Soul</genre>
11560                                </artist>
11561                            </album>
11562                        </sale>
11563                    </sales>
11564                </employee>
11565                <employee Employeeid="8">
11566                    <Employeename>David Til</Employeename>
11567                    <sales>
11568                        <sale SALEID="4">
11569                            <TIMESTAMP>2005-07-10 10:03:00</TIMESTAMP>
11570                            <customer customerid="4">
11571                                <first_name>Victoria</first_name>
11572                                <last_name>Alt</last_name>
11573                                <email>victory.alt@example.com</email>
11574                            </customer>
11575                            <album albumid="1">
11576                                <title>New World Order</title>
11577                                <published_year>1990</published_year>
11578                                <comment>the best ...</comment>
11579                                <artist artistid="1">
11580                                    <name>Curtis Mayfield</name>
11581                                    <birthyear>1920</birthyear>
11582                                    <birthplace>Chicago</birthplace>
11583                                    <genre>Soul</genre>
11584                                </artist>
11585                            </album>
11586                        </sale>
11587                        <sale SALEID="10">
11588                            <TIMESTAMP>2005-07-10 19:03:00</TIMESTAMP>
11589                            <customer customerid="10">
11590                                <first_name>Maria</first_name>
11591                                <last_name>Gonzales</last_name>
11592                                <email>maria.gonzales@example.com</email>
11593                            </customer>
11594                            <album albumid="1">
11595                                <title>New World Order</title>
11596                                <published_year>1990</published_year>
11597                                <comment>the best ...</comment>
11598                                <artist artistid="1">
11599                                    <name>Curtis Mayfield</name>
11600                                    <birthyear>1920</birthyear>
11601                                    <birthplace>Chicago</birthplace>
11602                                    <genre>Soul</genre>
11603                                </artist>
11604                            </album>
11605                        </sale>
11606                        <sale SALEID="12">
11607                            <TIMESTAMP>2005-07-10 11:56:00</TIMESTAMP>
11608                            <customer customerid="3">
11609                                <first_name>Susan</first_name>
11610                                <last_name>Green</last_name>
11611                                <email>susan.green@example.com</email>
11612                            </customer>
11613                            <album albumid="2">
11614                                <title>Curtis</title>
11615                                <published_year>1970</published_year>
11616                                <comment>that man ...</comment>
11617                                <artist artistid="1">
11618                                    <name>Curtis Mayfield</name>
11619                                    <birthyear>1920</birthyear>
11620                                    <birthplace>Chicago</birthplace>
11621                                    <genre>Soul</genre>
11622                                </artist>
11623                            </album>
11624                        </sale>
11625                        <sale SALEID="15">
11626                            <TIMESTAMP>2005-07-10 18:49:00</TIMESTAMP>
11627                            <customer customerid="9">
11628                                <first_name>Jack</first_name>
11629                                <last_name>Woo</last_name>
11630                                <email>jack.woo@example.com</email>
11631                            </customer>
11632                            <album albumid="2">
11633                                <title>Curtis</title>
11634                                <published_year>1970</published_year>
11635                                <comment>that man ...</comment>
11636                                <artist artistid="1">
11637                                    <name>Curtis Mayfield</name>
11638                                    <birthyear>1920</birthyear>
11639                                    <birthplace>Chicago</birthplace>
11640                                    <genre>Soul</genre>
11641                                </artist>
11642                            </album>
11643                        </sale>
11644                    </sales>
11645                </employee>
11646                <employee Employeeid="9">
11647                    <Employeename>Pia Eist</Employeename>
11648                    <sales>
11649                        <sale SALEID="6">
11650                            <TIMESTAMP>2005-07-10 14:03:00</TIMESTAMP>
11651                            <customer customerid="6">
11652                                <first_name>Tim</first_name>
11653                                <last_name>Raw</last_name>
11654                                <email>tim.raw@example.com</email>
11655                            </customer>
11656                            <album albumid="1">
11657                                <title>New World Order</title>
11658                                <published_year>1990</published_year>
11659                                <comment>the best ...</comment>
11660                                <artist artistid="1">
11661                                    <name>Curtis Mayfield</name>
11662                                    <birthyear>1920</birthyear>
11663                                    <birthplace>Chicago</birthplace>
11664                                    <genre>Soul</genre>
11665                                </artist>
11666                            </album>
11667                        </sale>
11668                        <sale SALEID="13">
11669                            <TIMESTAMP>2005-07-10 13:12:00</TIMESTAMP>
11670                            <customer customerid="5">
11671                                <first_name>Will</first_name>
11672                                <last_name>Rippy</last_name>
11673                                <email>will.wippy@example.com</email>
11674                            </customer>
11675                            <album albumid="2">
11676                                <title>Curtis</title>
11677                                <published_year>1970</published_year>
11678                                <comment>that man ...</comment>
11679                                <artist artistid="1">
11680                                    <name>Curtis Mayfield</name>
11681                                    <birthyear>1920</birthyear>
11682                                    <birthplace>Chicago</birthplace>
11683                                    <genre>Soul</genre>
11684                                </artist>
11685                            </album>
11686                        </sale>
11687                    </sales>
11688                </employee>
11689            </employees>
11690        </department>
11691        <department departmentid="4">
11692            <departmentSales>0</departmentSales>
11693            <departmentEmployees>3</departmentEmployees>
11694            <departmentname>Marketing</departmentname>
11695            <department_head departmentHeadEmployeeid="10">
11696                <departmentHeadEmployeename>Hanna Poll</departmentHeadEmployeename>
11697            </department_head>
11698            <employees>
11699                <employee Employeeid="10">
11700                    <Employeename>Hanna Poll</Employeename>
11701                    <sales />
11702                </employee>
11703                <employee Employeeid="11">
11704                    <Employeename>Jim Wells</Employeename>
11705                    <sales />
11706                </employee>
11707                <employee Employeeid="12">
11708                    <Employeename>Sandra Wilson</Employeename>
11709                    <sales />
11710                </employee>
11711            </employees>
11712        </department>
11713    </store>
11714</music_company>
11715     ]]>
11716     </programlisting>
11717     </para>
11718    </refsect3>
11719    <refsect3 id="{@id case08_debug_php}">
11720     <title>case08_debug.php</title>
11721     <para>
11722     case08_debug.php:
11723     <programlisting role="php">
11724     <![CDATA[
11725<?php
11726class Mappers
11727{
11728    public static function departmentMapper($str)
11729    {
11730        //maps 'one_two_three' to 'oneTwoThree'
11731        return preg_replace("/(_)([a-z])/e", "strtoupper('\\2')", $str);
11732    }
11733
11734    public static function employeeMapper($str)
11735    {
11736        //maps 'one_two_three' to 'OneTwoThree'
11737        return ucfirst(preg_replace("/(_)([a-z])/e", "strtoupper('\\2')", $str));
11738    }
11739
11740    public function saleMapper($str)
11741    {
11742        //maps 'one_two_three' to 'ONETWOTHREE'
11743        return strtoupper(str_replace('_', '', $str));
11744    }
11745}
11746
11747function mapArtist($str)
11748{
11749    //maps 'one_two_three' to 'onetwothree'
11750    return strtolower(str_replace('_', '', $str));
11751}
11752
11753$myMappers = new Mappers();
11754
11755require_once 'XML/Query2XML.php';
11756require_once 'XML/Query2XML/ISO9075Mapper.php';
11757require_once 'MDB2.php';
11758$query2xml = XML_Query2XML::factory(MDB2::factory('mysql://root@localhost/Query2XML_Tests'));
11759
11760require_once 'Log.php';
11761$debugLogger = Log::factory('file', 'case08.log', 'XML_Query2XML');
11762$query2xml->enableDebugLog($debugLogger);
11763
11764$query2xml->startProfiling();
11765
11766
11767$dom = $query2xml->getXML(
11768    "SELECT
11769         s.*,
11770         manager.employeeid AS manager_employeeid,
11771         manager.employeename AS manager_employeename,
11772         d.*,
11773         department_head.employeeid AS department_head_employeeid,
11774         department_head.employeename AS department_head_employeename,
11775         e.*,
11776         sa.*,
11777         c.*,
11778         al.*,
11779         ar.*,
11780         (SELECT COUNT(*) FROM sale WHERE sale.store_id = s.storeid) AS store_sales,
11781         (SELECT
11782            COUNT(*)
11783          FROM
11784            sale, employee, employee_department
11785          WHERE
11786            sale.employee_id = employee.employeeid
11787            AND
11788            employee_department.employee_id = employee.employeeid
11789            AND
11790            employee_department.department_id = d.departmentid
11791         ) AS department_sales,
11792         (SELECT
11793            COUNT(*)
11794          FROM
11795            employee, employee_department, department
11796          WHERE
11797            employee_department.employee_id = employee.employeeid
11798            AND
11799            employee_department.department_id = department.departmentid
11800            AND
11801            department.store_id = s.storeid
11802         ) AS store_employees,
11803         (SELECT
11804            COUNT(*)
11805          FROM
11806            employee, employee_department
11807          WHERE
11808            employee_department.employee_id = employee.employeeid
11809            AND
11810            employee_department.department_id = d.departmentid
11811         ) AS department_employees
11812     FROM
11813         store s
11814          LEFT JOIN employee manager ON s.manager = manager.employeeid
11815         LEFT JOIN department d ON d.store_id = s.storeid
11816          LEFT JOIN employee department_head ON department_head.employeeid = d.department_head
11817          LEFT JOIN employee_department ed ON ed.department_id = d.departmentid
11818           LEFT JOIN employee e ON e.employeeid = ed.employee_id
11819            LEFT JOIN sale sa ON sa.employee_id = e.employeeid
11820             LEFT JOIN customer c ON c.customerid = sa.customer_id
11821             LEFT JOIN album al ON al.albumid = sa.album_id
11822              LEFT JOIN artist ar ON ar.artistid = al.artist_id",
11823    array(
11824        'rootTag' => 'music_company',
11825        'rowTag' => 'store',
11826        'idColumn' => 'storeid',
11827        'mapper' => 'strtoupper',
11828        'attributes' => array(
11829            'storeid'
11830        ),
11831        'elements' => array(
11832            'store_sales',
11833            'store_employees',
11834            'manager' => array(
11835                'idColumn' => 'manager_employeeid',
11836                'attributes' => array(
11837                    'manager_employeeid'
11838                ),
11839                'elements' => array(
11840                    'manager_employeename'
11841                )
11842            ),
11843            'address' => array(
11844                'elements' => array(
11845                    'country',
11846                    'state' => '#Helper::getStatePostalCode()',
11847                    'city',
11848                    'street',
11849                    'phone'
11850                )
11851            ),
11852            'department' => array(
11853                'idColumn' => 'departmentid',
11854                'mapper' => 'Mappers::departmentMapper',
11855                'attributes' => array(
11856                    'departmentid'
11857                ),
11858                'elements' => array(
11859                    'department_sales',
11860                    'department_employees',
11861                    'departmentname',
11862                    'department_head' => array(
11863                        'idColumn' => 'department_head_employeeid',
11864                        'attributes' => array(
11865                            'department_head_employeeid'
11866                        ),
11867                        'elements' => array(
11868                            'department_head_employeename'
11869                        )
11870                    ),
11871                    'employees' => array(
11872                        'rootTag' => 'employees',
11873                        'rowTag' => 'employee',
11874                        'idColumn' => 'employeeid',
11875                        'mapper' => array('Mappers', 'employeeMapper'),
11876                        'attributes' => array(
11877                            'employeeid'
11878                        ),
11879                        'elements' => array(
11880                            'employeename',
11881                            'sales' => array(
11882                                'rootTag' => 'sales',
11883                                'rowTag' => 'sale',
11884                                'idColumn' => 'saleid',
11885                                'mapper' => array($myMappers, 'saleMapper'),
11886                                'attributes' => array(
11887                                    'saleid'
11888                                ),
11889                                'elements' => array(
11890                                    'timestamp',
11891                                    'customer' => array(
11892                                        'idColumn' => 'customerid',
11893                                        'mapper' => false,
11894                                        'attributes' => array(
11895                                            'customerid'
11896                                        ),
11897                                        'elements' => array(
11898                                            'first_name',
11899                                            'last_name',
11900                                            'email'
11901                                        )
11902                                    ),
11903                                    'album' => array(
11904                                        'idColumn' => 'albumid',
11905                                        'mapper' => 'XML_Query2XML_ISO9075Mapper::map',
11906                                        'attributes' => array(
11907                                            'albumid'
11908                                        ),
11909                                        'elements' => array(
11910                                            'title',
11911                                            'published_year',
11912                                            'comment' => '?#Helper::summarizeComment(12)',
11913                                            'artist' => array(
11914                                                'idColumn' => 'artistid',
11915                                                'mapper' => 'mapArtist',
11916                                                'attributes' => array(
11917                                                    'artistid'
11918                                                ),
11919                                                'elements' => array(
11920                                                    'name',
11921                                                    'birth_year',
11922                                                    'birth_place',
11923                                                    'genre'
11924                                                )
11925                                            )
11926                                        ) // album elements
11927                                    ) //album array
11928                                ) //sales elements
11929                            ) //sales array
11930                        ) //employees elements
11931                    ) //employees array
11932                ) //department elements
11933            ) // department array
11934        ) //root elements
11935    ) //root
11936); //getXML method call
11937
11938$root = $dom->firstChild;
11939$root->setAttribute('date_generated', date("Y-m-d\TH:i:s", 1124801570));
11940
11941header('Content-Type: application/xml');
11942
11943$dom->formatOutput = true;
11944print $dom->saveXML();
11945
11946require_once 'File.php';
11947$fp = new File();
11948$fp->write('case08.profile', $query2xml->getProfile(), FILE_MODE_WRITE);
11949
11950
11951/**Static class that provides validation and parsing methods for
11952* generating XML.
11953*
11954* It is static so that we can easyly call its methods from inside
11955* Query2XML using eval'd code.
11956*/
11957class Helper
11958{
11959    /**Associative array of US postal state codes*/
11960    public static $statePostalCodes = array(
11961        'ALABAMA' => 'AL', 'ALASKA' => 'AK', 'AMERICAN SAMOA' => 'AS', 'ARIZONA' => 'AZ', 'ARKANSAS' => 'AR', 'CALIFORNIA' => 'CA',
11962        'COLORADO' => 'CO', 'CONNECTICUT' => 'CT', 'DELAWARE' => 'DE', 'DISTRICT OF COLUMBIA' => 'DC', 'FEDERATED STATES OF MICRONESIA' => 'FM',
11963        'FLORIDA' => 'FL', 'GEORGIA' => 'GA', 'GUAM' => 'GU', 'HAWAII' => 'HI', 'IDAHO' => 'ID', 'ILLINOIS' => 'IL', 'INDIANA' => 'IN',
11964        'IOWA' => 'IA', 'KANSAS' => 'KS', 'KENTUCKY' => 'KY', 'LOUISIANA' => 'LA', 'MAINE' => 'ME', 'MARSHALL ISLANDS' => 'MH', 'MARYLAND' => 'MD',
11965        'MASSACHUSETTS' => 'MA', 'MICHIGAN' => 'MI', 'MINNESOTA' => 'MN', 'MISSISSIPPI' => 'MS', 'MISSOURI' => 'MO', 'MONTANA' => 'MT',
11966        'NEBRASKA' => 'NE', 'NEVADA' => 'NV', 'NEW HAMPSHIRE' => 'NH', 'NEW JERSEY' => 'NJ', 'NEW JESEY' => 'NJ', 'NEW MEXICO' => 'NM', 'NEW YORK' => 'NY',
11967        'NORTH CAROLINA' => 'NC', 'NORTH DAKOTA' => 'ND', 'NORTHERN MARIANA ISLANDS' => 'MP', 'OHIO' => 'OH', 'OKLAHOMA' => 'OK', 'OREGON' => 'OR',
11968        'PALAU' => 'PW', 'PENNSYLVANIA' => 'PA', 'PUERTO RICO' => 'PR', 'RHODE ISLAND' => 'RI', 'SOUTH CAROLINA' => 'SC', 'SOUTH DAKOTA' => 'SD',
11969        'TENNESSEE' => 'TN', 'TEXAS' => 'TX', 'UTAH' => 'UT', 'VERMONT' => 'VT', 'VIRGIN ISLANDS' => 'VI', 'VIRGINIA' => 'VA', 'WASHINGTON' => 'WA',
11970        'WEST VIRGINIA' => 'WV', 'WISCONSIN' => 'WI', 'WYOMING' => 'WY'
11971    );
11972
11973    /**Translates a US state name into its two-letter postal code.
11974    * If the translation fails, $state is returned unchanged
11975    * @param $record The record
11976    */
11977    public static function getStatePostalCode($record)
11978    {
11979        $state = $record["state"];
11980        $s = str_replace("  ", " ", trim(strtoupper($state)));
11981        if (isset(self::$statePostalCodes[$s])) {
11982            return self::$statePostalCodes[$s];
11983        } else {
11984            return $state;
11985        }
11986    }
11987
11988    function summarize($str, $limit=50, $appendString=' ...')
11989    {
11990        if (strlen($str) > $limit) {
11991            $str = substr($str, 0, $limit - strlen($appendString)) . $appendString;
11992        }
11993        return $str;
11994    }
11995
11996    function summarizeComment($record, $limit)
11997    {
11998        return self::summarize($record["comment"], $limit);
11999    }
12000}
12001?>
12002     ]]>
12003     </programlisting>
12004     The lines 36-38 do the debugging, line 40 and 222-224 the profiling. This will create
12005     case08.log and case08.profile.
12006     </para>
12007    </refsect3>
12008    <refsect3 id="{@id case08_log}">
12009     <title>case08.log</title>
12010     <para>
12011     The format of a debug log file is documented at {@tutorial XML_Query2XML.pkg#debugging}.
12012     Our debug log contains just a single query:
12013     <![CDATA[
12014Apr 20 13:33:47 XML_Query2XML [info] QUERY: SELECT
12015         s.*,
12016         manager.employeeid AS manager_employeeid,
12017         manager.employeename AS manager_employeename,
12018         d.*,
12019         department_head.employeeid AS department_head_employeeid,
12020         department_head.employeename AS department_head_employeename,
12021         e.*,
12022         sa.*,
12023         c.*,
12024         al.*,
12025         ar.*,
12026         (SELECT COUNT(*) FROM sale WHERE sale.store_id = s.storeid) AS store_sales,
12027         (SELECT
12028            COUNT(*)
12029          FROM
12030            sale, employee, employee_department
12031          WHERE
12032            sale.employee_id = employee.employeeid
12033            AND
12034            employee_department.employee_id = employee.employeeid
12035            AND
12036            employee_department.department_id = d.departmentid
12037         ) AS department_sales,
12038         (SELECT
12039            COUNT(*)
12040          FROM
12041            employee, employee_department, department
12042          WHERE
12043            employee_department.employee_id = employee.employeeid
12044            AND
12045            employee_department.department_id = department.departmentid
12046            AND
12047            department.store_id = s.storeid
12048         ) AS store_employees,
12049         (SELECT
12050            COUNT(*)
12051          FROM
12052            employee, employee_department
12053          WHERE
12054            employee_department.employee_id = employee.employeeid
12055            AND
12056            employee_department.department_id = d.departmentid
12057         ) AS department_employees
12058     FROM
12059         store s
12060          LEFT JOIN employee manager ON s.manager = manager.employeeid
12061         LEFT JOIN department d ON d.store_id = s.storeid
12062          LEFT JOIN employee department_head ON department_head.employeeid = d.department_head
12063          LEFT JOIN employee_department ed ON ed.department_id = d.departmentid
12064           LEFT JOIN employee e ON e.employeeid = ed.employee_id
12065            LEFT JOIN sale sa ON sa.employee_id = e.employeeid
12066             LEFT JOIN customer c ON c.customerid = sa.customer_id
12067             LEFT JOIN album al ON al.albumid = sa.album_id
12068              LEFT JOIN artist ar ON ar.artistid = al.artist_id
12069Apr 20 13:33:47 XML_Query2XML [info] DONE
12070     ]]>
12071     </para>
12072    </refsect3>
12073    <refsect3 id="{@id case08_profile}">
12074     <title>case08.profile</title>
12075     <para>
12076     The format of the output is documented under
12077     {@tutorial XML_Query2XML.pkg#profiling.getprofile}:
12078     <![CDATA[
12079FROM_DB FROM_CACHE CACHED AVG_DURATION DURATION_SUM SQL
120801       0          false  0.0101430416 0.0101430416 SELECT
12081         s.*,
12082         manager.employeeid AS manager_employeeid,
12083         manager.employeename AS manager_employeename,
12084         d.*,
12085         department_head.employeeid AS department_head_employeeid,
12086         department_head.employeename AS department_head_employeename,
12087         e.*,
12088         sa.*,
12089         c.*,
12090         al.*,
12091         ar.*,
12092         (SELECT COUNT(*) FROM sale WHERE sale.store_id = s.storeid) AS store_sales,
12093         (SELECT
12094            COUNT(*)
12095          FROM
12096            sale, employee, employee_department
12097          WHERE
12098            sale.employee_id = employee.employeeid
12099            AND
12100            employee_department.employee_id = employee.employeeid
12101            AND
12102            employee_department.department_id = d.departmentid
12103         ) AS department_sales,
12104         (SELECT
12105            COUNT(*)
12106          FROM
12107            employee, employee_department, department
12108          WHERE
12109            employee_department.employee_id = employee.employeeid
12110            AND
12111            employee_department.department_id = department.departmentid
12112            AND
12113            department.store_id = s.storeid
12114         ) AS store_employees,
12115         (SELECT
12116            COUNT(*)
12117          FROM
12118            employee, employee_department
12119          WHERE
12120            employee_department.employee_id = employee.employeeid
12121            AND
12122            employee_department.department_id = d.departmentid
12123         ) AS department_employees
12124     FROM
12125         store s
12126          LEFT JOIN employee manager ON s.manager = manager.employeeid
12127         LEFT JOIN department d ON d.store_id = s.storeid
12128          LEFT JOIN employee department_head ON department_head.employeeid = d.department_head
12129          LEFT JOIN employee_department ed ON ed.department_id = d.departmentid
12130           LEFT JOIN employee e ON e.employeeid = ed.employee_id
12131            LEFT JOIN sale sa ON sa.employee_id = e.employeeid
12132             LEFT JOIN customer c ON c.customerid = sa.customer_id
12133             LEFT JOIN album al ON al.albumid = sa.album_id
12134              LEFT JOIN artist ar ON ar.artistid = al.artist_id
12135
12136TOTAL_DURATION: 0.36341714859009
12137DB_DURATION:    0.20340394973755
12138     ]]>
12139     </para>
12140    </refsect3>
12141   </refsect2>
12142   <refsect2 id="{@id finalnotes}">
12143    <title>Final Notes on the case studies</title>
12144    <para>
12145     The files of all cases are installed in $PHP_PEAR_DOC_DIR/XML_Query2XML/cases/.
12146    </para>
12147   </refsect2>
12148  </refsect1>
12149
12150  <refsect1 id="{@id sqlddl}">
12151   <title>SQL DDL used in all examples</title>
12152   <para>
12153   	In all the examples a MySQL database created with the SQL DDL shown below is used.
12154   	You might also want to have a look at the
12155   	{@link http://query2xml.sourceforge.net/docs/Query2XML_Tests.jpg ER diagram}.
12156   	Note: the unit tests by default use the SQLite 2 database found at
12157   	$PHP_PEAR_TEST_DIR/XML_Query2XML/tests/Query2XML_Tests. If you want to run
12158   	all unit tests you have to install
12159   	the MySQL DDL $PHP_PEAR_TEST_DIR/XML_Query2XML/tests/Query2XML_Tests.sql
12160   	or the PostgreSQL DDL $PHP_PEAR_TEST_DIR/XML_Query2XML/tests/Query2XML_Tests.psql.
12161   </para>
12162   <para>
12163    Please we use a certain naming convention for primary and foreign keys:
12164    <itemizedlist>
12165     <listitem>
12166      For primary keys we use "&lt;table_name&gt;id".
12167     </listitem>
12168     <listitem>
12169      For foreign keys we use "&lt;foreign_table_name&gt;_id".
12170     </listitem>
12171    </itemizedlist>
12172    <![CDATA[
12173CREATE DATABASE Query2XML_Tests;
12174USE Query2XML_Tests;
12175
12176
12177CREATE TABLE artist (
12178    artistid INT NOT NULL AUTO_INCREMENT,
12179    name VARCHAR(255),
12180    birth_year Int,
12181    birth_place VARCHAR(255),
12182    genre VARCHAR(255),
12183    UNIQUE (artistid),
12184    PRIMARY KEY (artistid)
12185);
12186
12187CREATE TABLE customer (
12188    customerid INT NOT NULL AUTO_INCREMENT,
12189    first_name VARCHAR(255),
12190    last_name VARCHAR(255),
12191    email VARCHAR(255),
12192    UNIQUE (customerid),
12193    PRIMARY KEY (customerid)
12194);
12195
12196CREATE TABLE album (
12197    albumid INT NOT NULL AUTO_INCREMENT,
12198    artist_id INT NOT NULL,
12199    title VARCHAR(255),
12200    published_year Int,
12201    comment VARCHAR(255),
12202    UNIQUE (albumid),
12203    PRIMARY KEY (albumid),
12204    FOREIGN KEY (artist_id) REFERENCES artist (artistid)
12205);
12206
12207CREATE TABLE employee (
12208    employeeid INT NOT NULL AUTO_INCREMENT,
12209    employeename VARCHAR(255),
12210    UNIQUE (employeeid),
12211    PRIMARY KEY (employeeid)
12212);
12213
12214CREATE TABLE store (
12215    storeid INT NOT NULL AUTO_INCREMENT,
12216    manager INT NOT NULL,
12217    country VARCHAR(255),
12218    state VARCHAR(255),
12219    city VARCHAR(255),
12220    street VARCHAR(255),
12221    phone VARCHAR(255),
12222    building_xmldata TEXT,
12223    UNIQUE (storeid),
12224    PRIMARY KEY (storeid),
12225    FOREIGN KEY (manager) REFERENCES employee (employeeid)
12226);
12227
12228CREATE TABLE department (
12229    departmentid INT NOT NULL AUTO_INCREMENT,
12230    store_id INT NOT NULL,
12231    department_head INT NOT NULL,
12232    departmentname VARCHAR(255),
12233    UNIQUE (departmentid),
12234    PRIMARY KEY (departmentid),
12235    FOREIGN KEY (department_head) REFERENCES employee (employeeid),
12236    FOREIGN KEY (store_id) REFERENCES store (storeid)
12237);
12238
12239CREATE TABLE employee_department (
12240    employee_id INT NOT NULL,
12241    department_id INT NOT NULL,
12242    PRIMARY KEY (employee_id,department_id),
12243    FOREIGN KEY (employee_id) REFERENCES employee (employeeid),
12244    FOREIGN KEY (department_id) REFERENCES department (departmentid)
12245);
12246
12247CREATE TABLE sale (
12248    saleid INT NOT NULL AUTO_INCREMENT,
12249    album_id INT NOT NULL,
12250    customer_id INT NOT NULL,
12251    employee_id INT NOT NULL,
12252    store_id INT NOT NULL,
12253    timestamp Timestamp(14),
12254    UNIQUE (saleid),
12255    PRIMARY KEY (saleid),
12256    FOREIGN KEY (employee_id) REFERENCES employee (employeeid),
12257    FOREIGN KEY (album_id) REFERENCES album (albumid),
12258    FOREIGN KEY (customer_id) REFERENCES customer (customerid),
12259    FOREIGN KEY (store_id) REFERENCES store (storeid)
12260);
12261
12262
12263
12264INSERT INTO artist (artistid, name, birth_year, birth_place, genre) VALUES(1, 'Curtis Mayfield', 1920, 'Chicago', 'Soul');
12265INSERT INTO artist (artistid, name, birth_year, birth_place, genre) VALUES(2, 'Isaac Hayes', 1942, 'Tennessee', 'Soul');
12266INSERT INTO artist (artistid, name, birth_year, birth_place, genre) VALUES(3, 'Ray Charles', 1930, 'Mississippi', 'Country and Soul');
12267
12268INSERT INTO album (albumid, artist_id, title, published_year, comment) VALUES(1, 1, 'New World Order', 1990, 'the best ever!');
12269INSERT INTO album (albumid, artist_id, title, published_year, comment) VALUES(2, 1, 'Curtis', 1970, 'that man\'s got somthin\' to say');
12270INSERT INTO album (albumid, artist_id, title, published_year, comment) VALUES(3, 2, 'Shaft', 1972, 'he\'s the man');
12271
12272INSERT INTO customer (customerid, first_name, last_name, email) VALUES(1, 'Jane', 'Doe', 'jane.doe@example.com');
12273INSERT INTO customer (customerid, first_name, last_name, email) VALUES(2, 'John', 'Doe', 'john.doe@example.com');
12274INSERT INTO customer (customerid, first_name, last_name, email) VALUES(3, 'Susan', 'Green', 'susan.green@example.com');
12275INSERT INTO customer (customerid, first_name, last_name, email) VALUES(4, 'Victoria', 'Alt', 'victory.alt@example.com');
12276INSERT INTO customer (customerid, first_name, last_name, email) VALUES(5, 'Will', 'Rippy', 'will.wippy@example.com');
12277INSERT INTO customer (customerid, first_name, last_name, email) VALUES(6, 'Tim', 'Raw', 'tim.raw@example.com');
12278INSERT INTO customer (customerid, first_name, last_name, email) VALUES(7, 'Nick', 'Fallow', 'nick.fallow@example.com');
12279INSERT INTO customer (customerid, first_name, last_name, email) VALUES(8, 'Ed', 'Burton', 'ed.burton@example.com');
12280INSERT INTO customer (customerid, first_name, last_name, email) VALUES(9, 'Jack', 'Woo', 'jack.woo@example.com');
12281INSERT INTO customer (customerid, first_name, last_name, email) VALUES(10, 'Maria', 'Gonzales', 'maria.gonzales@example.com');
12282
12283INSERT INTO employee (employeeid, employeename) VALUES(1, 'Michael Jones');
12284INSERT INTO employee (employeeid, employeename) VALUES(2, 'Susi Weintraub');
12285INSERT INTO employee (employeeid, employeename) VALUES(3, 'Steve Hack');
12286INSERT INTO employee (employeeid, employeename) VALUES(4, 'Joan Kerr');
12287INSERT INTO employee (employeeid, employeename) VALUES(5, 'Marcus Roth');
12288INSERT INTO employee (employeeid, employeename) VALUES(6, 'Jack Mack');
12289INSERT INTO employee (employeeid, employeename) VALUES(7, 'Rita Doktor');
12290INSERT INTO employee (employeeid, employeename) VALUES(8, 'David Til');
12291INSERT INTO employee (employeeid, employeename) VALUES(9, 'Pia Eist');
12292INSERT INTO employee (employeeid, employeename) VALUES(10, 'Hanna Poll');
12293INSERT INTO employee (employeeid, employeename) VALUES(11, 'Jim Wells');
12294INSERT INTO employee (employeeid, employeename) VALUES(12, 'Sandra Wilson');
12295
12296INSERT INTO store (storeid, manager, country, state, city, street, phone, building_xmldata) VALUES(1, 1, 'US', 'New York', 'New York', 'Broadway & 72nd Str', '123 456 7890', '<building><floors>4</floors><elevators>2</elevators><square_meters>3200</square_meters></building>');
12297INSERT INTO store (storeid, manager, country, state, city, street, phone, building_xmldata) VALUES(2, 2, 'US', 'New York', 'Larchmont', 'Palmer Ave 71', '456 7890', '<building><floors>2</floors><elevators>1</elevators><square_meters>400</square_meters></building>');
12298
12299INSERT INTO department (departmentid, store_id, department_head, departmentname) VALUES(1, 1, 1, 'Sales');
12300INSERT INTO department (departmentid, store_id, department_head, departmentname) VALUES(2, 1, 4, 'Marketing');
12301INSERT INTO department (departmentid, store_id, department_head, departmentname) VALUES(3, 2, 7, 'Sales');
12302INSERT INTO department (departmentid, store_id, department_head, departmentname) VALUES(4, 2, 10, 'Marketing');
12303
12304INSERT INTO employee_department (employee_id, department_id) VALUES(1, 1);
12305INSERT INTO employee_department (employee_id, department_id) VALUES(2, 1);
12306INSERT INTO employee_department (employee_id, department_id) VALUES(3, 1);
12307INSERT INTO employee_department (employee_id, department_id) VALUES(4, 2);
12308INSERT INTO employee_department (employee_id, department_id) VALUES(5, 2);
12309INSERT INTO employee_department (employee_id, department_id) VALUES(6, 2);
12310INSERT INTO employee_department (employee_id, department_id) VALUES(7, 3);
12311INSERT INTO employee_department (employee_id, department_id) VALUES(8, 3);
12312INSERT INTO employee_department (employee_id, department_id) VALUES(9, 3);
12313INSERT INTO employee_department (employee_id, department_id) VALUES(10, 4);
12314INSERT INTO employee_department (employee_id, department_id) VALUES(11, 4);
12315INSERT INTO employee_department (employee_id, department_id) VALUES(12, 4);
12316
12317INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (1,  1,  1, 1, 1, '2005-05-25 16:32:00');
12318INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (2,  2,  1, 7, 2, '2005-06-05 12:56:00');
12319INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (3,  3,  1, 2, 1, '2005-07-10 11:03:00');
12320INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (4,  4,  1, 8, 2, '2005-07-10 10:03:00');
12321INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (5,  5,  1, 3, 1, '2005-07-10 13:03:00');
12322INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (6,  6,  1, 9, 2, '2005-07-10 14:03:00');
12323INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (7,  7,  1, 1, 1, '2005-07-10 15:03:00');
12324INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (8,  8,  1, 7, 2, '2005-07-10 16:03:00');
12325INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (9,  9,  1, 2, 1, '2005-07-10 18:03:00');
12326INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (10, 10, 1, 8, 2, '2005-07-10 19:03:00');
12327INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (11, 1,  2, 7, 2, '2005-05-25 16:23:00');
12328INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (12, 3,  2, 8, 2, '2005-07-10 11:56:00');
12329INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (13, 5,  2, 9, 2, '2005-07-10 13:12:00');
12330INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (14, 7,  2, 7, 2, '2005-07-10 15:09:00');
12331INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (15, 9,  2, 8, 2, '2005-07-10 18:49:00');
12332INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (16, 2,  3, 1, 1, '2005-06-05 12:56:12');
12333INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (17, 4,  3, 2, 1, '2005-07-10 10:03:32');
12334INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (18, 6,  3, 3, 1, '2005-07-10 14:03:52');
12335INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (19, 8,  3, 1, 1, '2005-07-10 16:03:01');
12336INSERT INTO sale (saleid, customer_id, album_id, employee_id, store_id, timestamp) VALUES (20, 10, 3, 2, 1, '2005-07-10 19:03:50');
12337   ]]>
12338  </para>
12339 </refsect1>
12340
12341 <refsect1 id="{@id copyright_notice}">
12342  <title>Copyright Notice</title>
12343  <para>
12344   Copyright: Lukas Feiler 2006
12345  </para>
12346  <para>
12347  	<emphasis>LICENSE:</emphasis>This source file is subject to version 2.1 of the LGPL that is bundled with this package in the file LICENSE.
12348  </para>
12349 </refsect1>
12350</refentry>