Index: release/project-release-create-history.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/project/release/project-release-create-history.php,v
retrieving revision 1.29
diff -u -p -r1.29 project-release-create-history.php
--- release/project-release-create-history.php	17 Jan 2010 00:00:11 -0000	1.29
+++ release/project-release-create-history.php	16 Feb 2010 10:13:46 -0000
@@ -15,6 +15,11 @@
  *
  */
 
+define('XML_NS', 'http://www.w3.org/2000/xmlns/');
+
+// Dublin core namespace according to http://dublincore.org/documents/dcmi-namespace/
+define('XML_DC_NS', 'http://purl.org/dc/elements/1.1/');
+
 // ------------------------------------------------------------
 // Required customization
 // ------------------------------------------------------------
@@ -215,14 +220,21 @@ function project_release_history_generat
     }
   }
 
-  $xml = '<title>'. check_plain($project->title) ."</title>\n";
-  $xml .= '<short_name>'. check_plain($project->uri) ."</short_name>\n";
-  $xml .= '<dc:creator>'. check_plain($project->username) ."</dc:creator>\n";
-  $xml .= '<api_version>'. check_plain($api_version) ."</api_version>\n";
+  $dom = new DomDocument('1.0', 'utf-8');
+  $dom->formatOutput = TRUE;
+
+  $eproject = $dom->appendChild($dom->createElement('project'));
+  $eproject->setAttributeNS(XML_NS, 'xmlns:dc', XML_DC_NS);
+  _project_dom_element($eproject, 'title', $project->title);
+  _project_dom_element($eproject, 'short_name', $project->uri);
+  $eproject->appendChild($dom->createElementNS(XML_DC_NS, 'dc:creator'))
+    ->appendChild($dom->createTextNode($project->username));
+  _project_dom_element($eproject, 'api_version', $api_version);
+
   if (!$project->status) {
     // If it's not published, we can skip the rest of this and bail.
-    $xml .= "<project_status>unpublished</project_status>\n";
-    project_release_history_write_xml($xml, $project, $api_version);
+    _project_dom_element($eproject, 'project_status', 'unpublished');
+    project_release_history_write_xml($dom, $project, $api_version);
     return;
   }
 
@@ -240,31 +252,29 @@ function project_release_history_generat
       }
     }
     if (isset($recommended_major)) {
-      $xml .= '<recommended_major>'. $recommended_major ."</recommended_major>\n";
+      _project_dom_element($eproject, 'recommended_major', $recommended_major);
     }
     if (empty($supported_majors)) {
       $project_status = 'unsupported';
     }
     else {
-      $xml .= '<supported_majors>'. implode(',', $supported_majors) ."</supported_majors>\n";
+      _project_dom_element($eproject, 'supported_majors', implode(',', $supported_majors));
       // To avoid confusing existing clients, include <default_major>, too.
-      $xml .= '<default_major>'. min($supported_majors) ."</default_major>\n";
+      _project_dom_element($eproject, 'default_major', min($supported_majors));
     }
   }
 
-  $xml .= '<project_status>'. $project_status ."</project_status>\n";
-  $xml .= '<link>'. prch_url("node/$project->nid") ."</link>\n";
+  _project_dom_element($eproject, 'project_status', $project_status);
+  _project_dom_element($eproject, 'link', prch_url("node/{$project->nid}"));
 
   // To prevent the update(_status) module from having problems parsing the XML,
   // the terms need to be at the end of the information for the project.
   $term_query = db_query("SELECT v.name AS vocab_name, v.vid, td.name AS term_name, td.tid FROM {term_node} tn INNER JOIN {term_data} td ON tn.tid = td.tid INNER JOIN {vocabulary} v ON td.vid = v.vid WHERE tn.vid = %d", $project->vid);
-  $xml_terms = '';
+  $xml_terms = _project_dom_element($eproject, 'terms');
   while ($term = db_fetch_object($term_query)) {
-    $xml_terms .= '   <term><name>'. check_plain($term->vocab_name) .'</name>';
-    $xml_terms .= '<value>'. check_plain($term->term_name) ."</value></term>\n";
-  }
-  if (!empty($xml_terms)) {
-    $xml .= "  <terms>\n". $xml_terms ."  </terms>\n";
+    $xml_term = _project_dom_element($xml_tems, 'term');
+    _project_dom_element($xml_term, 'name', $term->vocab_name);
+    _project_dom_element($xml_term, 'value', $term->term_name);
   }
 
   // Now, build the query for all the releases for this project and term.
@@ -320,79 +330,71 @@ function project_release_history_generat
 
   if (empty($releases)) {
     // Nothing more to include for this project, wrap up and return.
-    project_release_history_write_xml($xml, $project, $api_version);
+    project_release_history_write_xml($dom, $project, $api_version);
     return;
   }
 
   // Sort the releases based on our custom sorting function.
   usort($releases, "_release_sort");
 
-  $xml .= "<releases>\n";
+  $ereleases = _project_dom_element($eproject, 'releases');
   foreach ($releases as $release) {
-    $xml .= " <release>\n";
-    $xml .= '  <name>'. check_plain($release->title) ."</name>\n";
-    $xml .= '  <version>'. check_plain($release->version) ."</version>\n";
-    if (!empty($release->tag) && $tag = check_plain($release->tag)) {
-      $xml .= '  <tag>'. $tag ."</tag>\n";
+    $erelease = _project_dom_element($ereleases, 'release');
+    _project_dom_element($erelease, 'name', $release->title);
+    _project_dom_element($erelease, 'version', $release->version);
+    if (!empty($release->tag)) {
+      _project_dom_element($erelease, 'tag', $release->tag);
     }
     foreach (array('major', 'minor', 'patch', 'extra') as $type) {
       $vers_type = "version_$type";
       if (isset($release->$vers_type)) {
-        $xml .= "  <$vers_type>". check_plain($release->$vers_type) ."</$vers_type>\n";
+        _project_dom_element($erelease, $vers_type, $release->$vers_type);
       }
     }
     if ($release->status) {
       // Published, so we should include the links.
-      $xml .= "  <status>published</status>\n";
-      $xml .= '  <release_link>'. prch_url("node/$release->nid") ."</release_link>\n";
+      _project_dom_element($erelease, 'status', 'published');
+      _project_dom_element($erelease, 'release_link', prch_url("node/{$release->nid}"));
       if (!empty($release->filepath)) {
         $download_link = theme('project_release_download_link', $release->filepath, NULL, TRUE);
-        $xml .= '  <download_link>'. $download_link['href'] ."</download_link>\n";
+        _project_dom_element($erelease, 'download_link', $download_link['href']);
       }
     }
     else {
-      $xml .= "  <status>unpublished</status>\n";
+      _project_dom_element($erelease, 'status', 'unpublished');
     }
     // We want to include the rest of these regardless of the status.
     if (!empty($release->timestamp)) {
-      $xml .= '  <date>'. check_plain($release->timestamp) ."</date>\n";
+      _project_dom_element($erelease, 'date', $release->timestamp);
     }
     if (!empty($release->filehash)) {
-      $xml .= '  <mdhash>'. check_plain($release->filehash) ."</mdhash>\n";
+      _project_dom_element($erelease, 'mdhash', $release->filehash);
     }
     if (isset($release->filesize)) {
-      $xml .= '  <filesize>'. check_plain($release->filesize) ."</filesize>\n";
+      _project_dom_element($erelease, 'filesize', $release->filesize);
     }
     $term_query = db_query("SELECT v.name AS vocab_name, v.vid, td.name AS term_name, td.tid FROM {term_node} tn INNER JOIN {term_data} td ON tn.tid = td.tid INNER JOIN {vocabulary} v ON td.vid = v.vid WHERE tn.vid = %d AND v.vid != %d", $release->vid, $api_vid);
-    $xml_terms = '';
+    $xml_terms = _project_dom_element($erelease, 'terms');
     while ($term = db_fetch_object($term_query)) {
-      $xml_terms .= '   <term><name>'. check_plain($term->vocab_name) .'</name>';
-      $xml_terms .= '<value>'. check_plain($term->term_name) ."</value></term>\n";
+      $xml_term = _project_dom_element($xml_tems, 'term');
+      _project_dom_element($xml_term, 'name', $term->vocab_name);
+      _project_dom_element($xml_term, 'value', $term->term_name);
     }
-    if (!empty($xml_terms)) {
-      $xml .= "  <terms>\n". $xml_terms ."  </terms>\n";
-    }
-    $xml .= " </release>\n";
   }
-  $xml .= "</releases>\n";
-  project_release_history_write_xml($xml, $project, $api_version);
+  project_release_history_write_xml($dom, $project, $api_version);
 }
 
-
 /**
  * Write out the XML history for a given project and version to a file.
  *
  * @param $xml
- *   String containing the XML representation of the history.
+ *   DomDocument representing the history.
  * @param $project
  *   An object containing (at least) the title and uri of project.
  * @param $api_version
  *   The API compatibility version the history is for.
  */
-function project_release_history_write_xml($xml, $project = NULL, $api_version = NULL) {
-
-  // Dublin core namespace according to http://dublincore.org/documents/dcmi-namespace/
-  $dc_namespace = 'xmlns:dc="http://purl.org/dc/elements/1.1/"';
+function project_release_history_write_xml($dom, $project = NULL, $api_version = NULL) {
   if (!isset($project)) {
     // We are outputting a global project list.
     $project_dir = HISTORY_ROOT .'/project-list';
@@ -412,8 +414,6 @@ function project_release_history_write_x
         'args' => array('@old' => $tmp_filename, '@new' => $filename),
       ),
     );
-    $full_xml = '<?xml version="1.0" encoding="utf-8"?>' . "\n";
-    $full_xml .= '<projects '. $dc_namespace .">\n" . $xml . "</projects>\n";
   }
   else {
     // Setup the filenames we'll be using.  Normally, we'd have to be
@@ -442,8 +442,6 @@ function project_release_history_write_x
         'args' => array('@old' => $tmp_filename, '@new' => $filename, '%project' => $project->title),
       ),
     );
-    $full_xml = '<?xml version="1.0" encoding="utf-8"?>' . "\n";
-    $full_xml .= '<project '. $dc_namespace .">\n" . $xml . "</project>\n";
   }
 
   // Make sure we've got the right project-specific subdirectory.
@@ -461,6 +459,8 @@ function project_release_history_write_x
     wd_err(array('message' => "ERROR: fopen(@file, 'xb') failed", 'args' => array('@file' => $tmp_filename)));
     return FALSE;
   }
+
+  $full_xml = $dom->saveXML();
   if (!fwrite($hist_fd, $full_xml)) {
     wd_err(array('message' => "ERROR: fwrite(@file) failed" . '<pre>' . check_plain($full_xml), 'args' => array('@file' => $tmp_filename)));
     return FALSE;
@@ -484,46 +484,47 @@ function project_list_generate() {
   
   $query = db_query("SELECT n.title, n.nid, n.vid, n.status, p.uri, u.name AS username FROM {node} n INNER JOIN {project_projects} p ON n.nid = p.nid INNER JOIN {users} u ON n.uid = u.uid");
 
-  $xml = '';
+  $has_projects = FALSE;
+  $dom = new DomDocument('1.0', 'utf-8');
+  $dom->formatOutput = TRUE;
+
+  $eprojects = $dom->appendChild($dom->createElement('projects'));
+  $eprojects->setAttributeNS(XML_NS, 'xmlns:dc', XML_DC_NS);
   while ($project = db_fetch_object($query)) {
-    $xml .= " <project>\n";
-    $xml .= '  <title>'. check_plain($project->title) ."</title>\n";
-    $xml .= '  <short_name>'. check_plain($project->uri) ."</short_name>\n";
-    $xml .= '  <link>'. prch_url("node/$project->nid") ."</link>\n";
-    $xml .= '  <dc:creator>'. check_plain($project->username). "</dc:creator>\n";
+    $has_projects = TRUE;
+    $eproject = _project_dom_element($eprojects, 'project');
+    _project_dom_element($eproject, 'title', $project->title);
+    _project_dom_element($eproject, 'short_name', $project->uri);
+    _project_dom_element($eproject, 'link', prch_url("node/$project->nid"));
+    $eproject->appendChild($dom->createElementNS(XML_DC_NS, 'dc:creator'))
+      ->appendChild($dom->createTextNode($project->username));
+
     $term_query = db_query("SELECT v.name AS vocab_name, v.vid, td.name AS term_name, td.tid FROM {term_node} tn INNER JOIN {term_data} td ON tn.tid = td.tid INNER JOIN {vocabulary} v ON td.vid = v.vid WHERE tn.vid = %d", $project->vid);
-    $xml_terms = '';
+    $xml_terms = _project_dom_element($eproject, 'terms');
     while ($term = db_fetch_object($term_query)) {
-      $xml_terms .= '   <term><name>'. check_plain($term->vocab_name) .'</name>';
-      $xml_terms .= '<value>'. check_plain($term->term_name) ."</value></term>\n";
-    }
-    if (!empty($xml_terms)) {
-      $xml .= "  <terms>\n". $xml_terms ."  </terms>\n";
+      $xml_term = _project_dom_element($xml_terms, 'term');
+      _project_dom_element($xml_term, 'name', $term->vocab_name);
+      _project_dom_element($xml_term, 'value', $term->term_name);
     }
     if (!$project->status) {
       // If it's not published, we can skip the rest for this project.
-      $xml .= "  <project_status>unpublished</project_status>\n";
+      _project_dom_element($eproject, 'status', 'unpublished');
     }
     else {
-      $xml .= "  <project_status>published</project_status>\n";
+      _project_dom_element($eproject, 'status', 'published');
       // Include a list of API terms if available.
       $term_query = db_query("SELECT DISTINCT(td.tid), td.name AS term_name FROM {project_release_nodes} prn INNER JOIN {term_data} td ON prn.version_api_tid = td.tid WHERE prn.pid = %d AND td.vid = %d ORDER BY td.weight ASC", $project->nid, $api_vid);
-      $xml_api_terms = '';
+      $xml_api_terms = _project_dom_element($eproject, 'api_versions');
       while ($api_term = db_fetch_object($term_query)) {
-        $xml_api_terms .= '   <api_version>'. check_plain($api_term->term_name) ."</api_version>\n";
-      }      
-      if (!empty($xml_api_terms)) {
-        $xml .= "  <api_versions>\n". $xml_api_terms ."  </api_versions>\n";
+        _project_dom_element($xml_api_terms, 'api_version', $api_term->term_name);
       }
     }
-    
-    $xml .= " </project>\n";
   }
-  if (empty($xml)) {
+  if (!$has_projects) {
     wd_err(array('message' => 'No projects found on this server.'));
     return FALSE;
   }
-  project_release_history_write_xml($xml);
+  project_release_history_write_xml($dom);
 }
 
 // ------------------------------------------------------------
@@ -531,6 +532,24 @@ function project_list_generate() {
 // ------------------------------------------------------------
 
 /**
+ * Utility function for adding elements, optionally with text content.
+ *
+ * @param DomNode $element
+ * @param string $name
+ * @param string $text
+ *  Optional. The text contents of the new node.
+ * @return DomElement
+ *  The created element.
+ */
+function _project_dom_element($parent, $name, $text=NULL) {
+  $child = $parent->appendChild($parent->ownerDocument->createElement($name));
+  if (!empty($text)) {
+    $child->appendChild($parent->ownerDocument->createTextNode($text));
+  }
+  return $child;
+}
+
+/**
  * Wrapper function for watchdog() to log notice messages.
  * 
  * @param $notice
