* API: A new ApiPageSet class to retrieve page data and resolve redirects.
authorYuri Astrakhan <yurik@users.mediawiki.org>
Mon, 25 Sep 2006 04:12:07 +0000 (04:12 +0000)
committerYuri Astrakhan <yurik@users.mediawiki.org>
Mon, 25 Sep 2006 04:12:07 +0000 (04:12 +0000)
api.php
includes/api/ApiFormatBase.php
includes/api/ApiFormatXml.php
includes/api/ApiMain.php
includes/api/ApiPageSet.php [new file with mode: 0644]
includes/api/ApiQuery.php
includes/api/ApiQueryBase.php
includes/api/ApiResult.php

diff --git a/api.php b/api.php
index a286134..25191ca 100644 (file)
--- a/api.php
+++ b/api.php
 \r
 $apiStartTime = microtime(true);\r
 \r
+/**\r
+ * When no format parameter is given, this format will be used\r
+ */\r
+define('API_DEFAULT_FORMAT', 'xmlfm');\r
+\r
+$apidir = 'includes/api';\r
 /**\r
  * List of classes and containing files.\r
  */\r
 $apiAutoloadClasses = array (\r
-       'ApiBase' => 'includes/api/ApiBase.php',\r
-       'ApiMain' => 'includes/api/ApiMain.php',\r
-       'ApiResult' => 'includes/api/ApiResult.php',\r
-\r
-       // Available modules - should match the $apiModules list\r
-       'ApiHelp' => 'includes/api/ApiHelp.php',\r
-       'ApiLogin' => 'includes/api/ApiLogin.php',\r
-       'ApiQuery' => 'includes/api/ApiQuery.php'\r
+\r
+       'ApiMain' => "$apidir/ApiMain.php",\r
+\r
+               // Utility classes\r
+       'ApiBase' => "$apidir/ApiBase.php",\r
+       'ApiQueryBase' => "$apidir/ApiQueryBase.php",\r
+       'ApiResult' => "$apidir/ApiResult.php",\r
+\r
+               // Formats\r
+       'ApiFormatBase' => "$apidir/ApiFormatBase.php",\r
+       'ApiFormatYaml' => "$apidir/ApiFormatYaml.php",\r
+       'ApiFormatXml' => "$apidir/ApiFormatXml.php",\r
+       'ApiFormatJson' => "$apidir/ApiFormatJson.php",\r
+\r
+               // Modules (action=...) - should match the $apiModules list\r
+       'ApiHelp' => "$apidir/ApiHelp.php",\r
+       'ApiLogin' => "$apidir/ApiLogin.php",\r
+       'ApiQuery' => "$apidir/ApiQuery.php",\r
+\r
+               // Query items (what/list=...)\r
+       'ApiQueryContent' => "$apidir/ApiQueryContent.php",\r
+\r
+       'ApiPageSet' => "$apidir/ApiPageSet.php"\r
 );\r
 \r
 /**\r
  * List of available modules: action name => module class\r
  * The class must also be listed in the $apiAutoloadClasses array. \r
- */ \r
+ */\r
 $apiModules = array (\r
        'help' => 'ApiHelp',\r
        'login' => 'ApiLogin',\r
        'query' => 'ApiQuery'\r
 );\r
 \r
+/**\r
+ * List of available formats: format name => format class\r
+ * The class must also be listed in the $apiAutoloadClasses array. \r
+ */\r
+$apiFormats = array (\r
+       'json' => 'ApiFormatJson',\r
+       'jsonfm' => 'ApiFormatJson',\r
+       'xml' => 'ApiFormatXml',\r
+       'xmlfm' => 'ApiFormatXml',\r
+       'yaml' => 'ApiFormatYaml',\r
+       'yamlfm' => 'ApiFormatYaml'\r
+);\r
 \r
 // Initialise common code\r
 require_once ('./includes/WebStart.php');\r
 wfProfileIn('api.php');\r
 \r
-\r
 // Verify that the API has not been disabled\r
 // The next line should be \r
 //      if (isset ($wgEnableAPI) && !$wgEnableAPI) {\r
@@ -64,16 +96,14 @@ if (!isset ($wgEnableAPI) || !$wgEnableAPI) {
        die(-1);\r
 }\r
 \r
-\r
 ApiInitAutoloadClasses($apiAutoloadClasses);\r
-$processor = new ApiMain($apiStartTime, $apiModules);\r
+$processor = new ApiMain($apiStartTime, $apiModules, $apiFormats);\r
 $processor->Execute();\r
 \r
 wfProfileOut('api.php');\r
 wfLogProfilingData();\r
 exit; // Done!\r
 \r
-\r
 function ApiInitAutoloadClasses($apiAutoloadClasses) {\r
 \r
        // Append $apiAutoloadClasses to $wgAutoloadClasses\r
@@ -84,4 +114,4 @@ function ApiInitAutoloadClasses($apiAutoloadClasses) {
                $wgAutoloadClasses = $apiAutoloadClasses;\r
        }\r
 }\r
-?>\r
+?>
\ No newline at end of file
index a6847e3..c26d62a 100644 (file)
@@ -55,6 +55,10 @@ abstract class ApiFormatBase extends ApiBase {
         */
        public abstract function GetMimeType();
 
+       public function GetNeedsRawData() {
+               return false;
+       }
+
        /**
         * Returns true when an HTML filtering printer should be used.
         * The default implementation assumes that formats ending with 'fm' 
index 3c30e75..301130b 100644 (file)
@@ -38,6 +38,10 @@ class ApiFormatXml extends ApiFormatBase {
        public function GetMimeType() {
                return 'text/xml';
        }
+       
+       public function GetNeedsRawData() {
+               return true;
+       }
 
        public function Execute() {
                $xmlindent = null;
index 2a6da70..2b61f25 100644 (file)
@@ -114,6 +114,8 @@ class ApiMain extends ApiBase {
         */\r
        private function PrintResult($isError) {\r
                $this->mPrinter->InitPrinter($isError);\r
+               if (!$this->mPrinter->GetNeedsRawData())\r
+                       $this->GetResult()->SanitizeData();\r
                $this->mPrinter->Execute();\r
                $this->mPrinter->ClosePrinter();\r
        }\r
@@ -148,7 +150,7 @@ class ApiMain extends ApiBase {
                $msg = parent :: MakeHelpMsg();\r
 \r
                $astriks = str_repeat('*** ', 10);\r
-               $msg .= "\n\n$astriks  Modules  $astriks\n\n";\r
+               $msg .= "\n\n$astriks Modules  $astriks\n\n";\r
                foreach ($this->mModules as $moduleName => $moduleClass) {\r
                        $msg .= "* action=$moduleName *";\r
                        $module = new $this->mModules[$moduleName] ($this, $moduleName);\r
@@ -158,7 +160,7 @@ class ApiMain extends ApiBase {
                        $msg .= "\n";\r
                }\r
 \r
-               $msg .= "\n$astriks  Formats  $astriks\n\n";\r
+               $msg .= "\n$astriks Formats  $astriks\n\n";\r
                foreach ($this->mFormats as $moduleName => $moduleClass) {\r
                        $msg .= "* format=$moduleName *";\r
                        $module = new $this->mFormats[$moduleName] ($this, $moduleName);\r
diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php
new file mode 100644 (file)
index 0000000..20fae3d
--- /dev/null
@@ -0,0 +1,182 @@
+<?php
+
+
+/*
+ * Created on Sep 24, 2006
+ *
+ * API for MediaWiki 1.8+
+ *
+ * Copyright (C) 2006 Yuri Astrakhan <FirstnameLastname@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ */
+
+if (!defined('MEDIAWIKI')) {
+       // Eclipse helper - will be ignored in production
+       require_once ("ApiBase.php");
+}
+
+class ApiPageSet {
+
+       private $allPages; // [ns][dbkey] => page_id or 0 when missing
+       private $db, $resolveRedirs;
+       private $goodTitles, $missingTitles, $redirectTitles;
+
+       public function __construct($db, $resolveRedirs) {
+               $this->db = $db;
+               $this->resolveRedirs = $resolveRedirs;
+
+               $this->allPages = array ();
+               $this->goodTitles = array ();
+               $this->missingTitles = array ();
+
+               // only when resolving redirects:
+               if ($resolveRedirs) {
+                       $this->redirectTitles = array ();
+               }
+       }
+
+       /**
+        * Title objects that were found in the database.
+        * @return array page_id (int) => Title (obj)
+        */
+       public function GetGoodTitles() {
+               return $this->goodTitles;
+       }
+
+       /**
+        * Title objects that were NOT found in the database.
+        * @return array of Title objects
+        */
+       public function GetMissingTitles() {
+               return $this->missingTitles;
+       }
+
+       /**
+        * Get a list of redirects when doing redirect resolution
+        * @return array prefixed_title (string) => prefixed_title (string)
+        */
+       public function GetRedirectTitles() {
+               return $this->redirectTitles;
+       }
+
+       /**
+        * This method populates internal variables with page information
+        * based on the list of page titles given as a LinkBatch object.
+        * 
+        * Steps:
+        * #1 For each title, get data from `page` table
+        * #2 If page was not found in the DB, store it as missing
+        * 
+        * Additionally, when resolving redirects:
+        * #3 If no more redirects left, stop.
+        * #4 For each redirect, get its links from `pagelinks` table.
+        * #5 Substitute the original LinkBatch object with the new list
+        * #6 Repeat from step #1     
+        */
+       public function PopulateTitles($linkBatch) {
+               $pageFlds = array (
+                       'page_id',
+                       'page_namespace',
+                       'page_title'
+               );
+               if ($this->resolveRedirs) {
+                       $pageFlds[] = 'page_is_redirect';
+               }
+
+               //
+               // Repeat until all redirects have been resolved
+               //
+               while (false !== ($set = $linkBatch->constructSet('page', $this->db))) {
+
+                       // Hack: Get the ns:titles stored in array(ns => array(titles)) format
+                       $remaining = $linkBatch->data;
+
+                       if ($this->resolveRedirs)
+                               $redirectIds = array ();
+
+                       //
+                       // Get data about $linkBatch from `page` table
+                       //
+                       $res = $this->db->select('page', $pageFlds, $set, __CLASS__ . '::' . __FUNCTION__);
+                       while ($row = $this->db->fetchObject($res)) {
+
+                               unset ($remaining[$row->page_namespace][$row->page_title]);
+                               $title = Title :: makeTitle($row->page_namespace, $row->page_title);
+                               $this->allPages[$row->page_namespace][$row->page_title] = $row->page_id;
+
+                               if ($this->resolveRedirs && $row->page_is_redirect == '1') {
+                                       $redirectIds[$row->page_id] = $title;
+                               } else {
+                                       $this->goodTitles[$row->page_id] = $title;
+                               }
+                       }
+                       $this->db->freeResult($res);
+
+                       //
+                       // The remaining titles in $remaining are non-existant pages
+                       //
+                       foreach ($remaining as $ns => $dbkeys) {
+                               foreach ($dbkeys as $dbkey => $nothing) {
+                                       $this->missingTitles[] = Title :: makeTitle($ns, $dbkey);
+                                       $this->allPages[$ns][$dbkey] = 0;
+                               }
+                       }
+
+                       if (!$this->resolveRedirs || empty ($redirectIds))
+                               break;
+
+                       //
+                       // Resolve redirects by querying the pagelinks table, and repeat the process
+                       //
+                       
+                       // Create a new linkBatch object for the next pass
+                       $linkBatch = new LinkBatch();
+
+                       // find redirect targets for all redirect pages
+                       $res = $this->db->select('pagelinks', array (
+                               'pl_from',
+                               'pl_namespace',
+                               'pl_title'
+                       ), array (
+                               'pl_from' => array_keys($redirectIds
+                       )), __CLASS__ . '::' . __FUNCTION__);
+
+                       while ($row = $this->db->fetchObject($res)) {
+
+                               // Bug 7304 workaround 
+                               // ( http://bugzilla.wikipedia.org/show_bug.cgi?id=7304 )
+                               // A redirect page may have more than one link.
+                               // This code will only use the first link returned. 
+                               if (isset ($redirectIds[$row->pl_from])) {      // remove line when 7304 is fixed 
+
+                                       $titleStrFrom = $redirectIds[$row->pl_from]->getPrefixedText();
+                                       $titleStrTo = Title :: makeTitle($row->pl_namespace, $row->pl_title)->getPrefixedText();
+                                       $this->redirectTitles[$titleStrFrom] = $titleStrTo;
+
+                                       unset ($redirectIds[$row->pl_from]);    // remove line when 7304 is fixed
+
+                                       // Avoid an infinite loop by checking if we have already processed this target
+                                       if (!isset ($this->allPages[$row->pl_namespace][$row->pl_title])) {
+                                               $linkBatch->add($row->pl_namespace, $row->pl_title);
+                                       }
+                               }
+                       }
+                       $this->db->freeResult($res);
+               }
+       }
+}
+?>
\ No newline at end of file
index 501ebb0..bc73f48 100644 (file)
@@ -34,32 +34,38 @@ class ApiQuery extends ApiBase {
        var $mMetaModuleNames, $mPropModuleNames, $mListModuleNames;\r
 \r
        private $mQueryMetaModules = array (\r
-//             'siteinfo' => 'ApiQuerySiteinfo',\r
-//             'userinfo' => 'ApiQueryUserinfo'\r
+               'siteinfo' => 'ApiQuerySiteinfo',\r
+                       //'userinfo' => 'ApiQueryUserinfo'      \r
+\r
+       \r
        );\r
+\r
        private $mQueryPropModules = array (\r
-//             'info' => 'ApiQueryInfo',\r
-//             'categories' => 'ApiQueryCategories',\r
-//             'imageinfo' => 'ApiQueryImageinfo',\r
-//             'langlinks' => 'ApiQueryLanglinks',\r
-//             'links' => 'ApiQueryLinks',\r
-//             'templates' => 'ApiQueryTemplates',\r
-//             'revisions' => 'ApiQueryRevisions',\r
+                       //              'info' => 'ApiQueryInfo',\r
+               //              'categories' => 'ApiQueryCategories',\r
+               //              'imageinfo' => 'ApiQueryImageinfo',\r
+               //              'langlinks' => 'ApiQueryLanglinks',\r
+               //              'links' => 'ApiQueryLinks',\r
+               //              'templates' => 'ApiQueryTemplates',\r
+               //              'revisions' => 'ApiQueryRevisions',\r
 \r
                // Should be removed\r
-               'content' => 'ApiQueryContent'\r
+       'content' => 'ApiQueryContent'\r
        );\r
+\r
        private $mQueryListModules = array (\r
-//             'allpages' => 'ApiQueryAllpages',\r
-//             'backlinks' => 'ApiQueryBacklinks',\r
-//             'categorymembers' => 'ApiQueryCategorymembers',\r
-//             'embeddedin' => 'ApiQueryEmbeddedin',\r
-//             'imagelinks' => 'ApiQueryImagelinks',\r
-//             'logevents' => 'ApiQueryLogevents',\r
-//             'recentchanges' => 'ApiQueryRecentchanges',\r
-//             'usercontribs' => 'ApiQueryUsercontribs',\r
-//             'users' => 'ApiQueryUsers',\r
-//             'watchlist' => 'ApiQueryWatchlist',\r
+               'allpages' => 'ApiQueryAllpages',\r
+                       //              'backlinks' => 'ApiQueryBacklinks',\r
+               //              'categorymembers' => 'ApiQueryCategorymembers',\r
+               //              'embeddedin' => 'ApiQueryEmbeddedin',\r
+               //              'imagelinks' => 'ApiQueryImagelinks',\r
+               //              'logevents' => 'ApiQueryLogevents',\r
+               //              'recentchanges' => 'ApiQueryRecentchanges',\r
+               //              'usercontribs' => 'ApiQueryUsercontribs',\r
+               //              'users' => 'ApiQueryUsers',\r
+               //              'watchlist' => 'ApiQueryWatchlist'\r
+\r
+       \r
        );\r
 \r
        private $mSlaveDB = null;\r
@@ -69,8 +75,8 @@ class ApiQuery extends ApiBase {
                $this->mMetaModuleNames = array_keys($this->mQueryMetaModules);\r
                $this->mPropModuleNames = array_keys($this->mQueryPropModules);\r
                $this->mListModuleNames = array_keys($this->mQueryListModules);\r
-               \r
-               $this->mAllowedGenerators = array_merge( $this->mListModuleNames, $this->mPropModuleNames);\r
+\r
+               $this->mAllowedGenerators = array_merge($this->mListModuleNames, $this->mPropModuleNames);\r
        }\r
 \r
        public function GetDB() {\r
@@ -88,46 +94,75 @@ class ApiQuery extends ApiBase {
                // Only one of the titles/pageids/revids is allowed at the same time\r
                //\r
                $dataSource = null;\r
-               if (isset($titles))\r
+               if (isset ($titles))\r
                        $dataSource = 'titles';\r
-               if (isset($pageids)) {\r
-                       if (isset($dataSource))\r
+               if (isset ($pageids)) {\r
+                       if (isset ($dataSource))\r
                                $this->DieUsage("Cannot use 'pageids' at the same time as '$dataSource'", 'multisource');\r
                        $dataSource = 'pageids';\r
                }\r
-               if (isset($revids)) {\r
-                       if (isset($dataSource))\r
+               if (isset ($revids)) {\r
+                       if (isset ($dataSource))\r
                                $this->DieUsage("Cannot use 'revids' at the same time as '$dataSource'", 'multisource');\r
                        $dataSource = 'revids';\r
                }\r
-               \r
-               //\r
+\r
+               if (isset($dataSource) && $dataSource !== 'titles')\r
+                       $this->DieUsage('Currently only titles= parameter is supported.', 'notimplemented');\r
+\r
                // Normalize titles\r
-               //\r
-               if ($dataSource === 'titles') {\r
-                       $linkBatch = new LinkBatch;\r
-                       foreach ( $titles as &$titleString ) {\r
-                               $titleObj = &Title::newFromText( $titleString );\r
-\r
-                               // Validation\r
-                               if (!$titleObj)\r
-                                       $this->dieUsage( "bad title $titleString", 'pi_invalidtitle' );\r
-                               if ($titleObj->getNamespace() < 0)\r
-                                       $this->dieUsage( "No support for special page $titleString has been implemented", 'pi_unsupportednamespace' );\r
-                               if (!$titleObj->userCanRead())\r
-                                       $this->dieUsage( "No read permission for $titleString", 'pi_titleaccessdenied' );\r
-\r
-                $linkBatch->addObj( $titleObj );\r
-\r
-                               // Make sure we remember the original title that was given to us\r
-                               // This way the caller can correlate new titles with the originally requested, i.e. namespace is localized or capitalization\r
-                               if( $titleString !== $titleObj->getPrefixedText() ) {\r
-                                       $this->GetResult()->AddMessage('query', 'normalized', array($titleString => $titleObj->getPrefixedText()));\r
-                               }\r
+               $linkBatch = $this->ProcessTitles($titles);\r
+\r
+               // Get titles info from DB\r
+               $data = new ApiPageSet($this->GetDB(), $redirects);\r
+               $data->PopulateTitles($linkBatch);\r
+\r
+               // Show redirects information\r
+               if ($redirects) {\r
+                       foreach ($data->GetRedirectTitles() as $titleStrFrom => $titleStrTo) {\r
+                               $this->GetResult()->AddMessage('query', 'redirects', array (\r
+                                       'from' => $titleStrFrom,\r
+                                       'to' => $titleStrTo\r
+                               ), 'r');\r
                        }\r
                }\r
        }\r
 \r
+       /**\r
+        * Given an array of title strings, convert them into Title objects.\r
+        * This method validates access rights for the title, \r
+        * and appends normalization values to the output.\r
+        * @return LinkBatch of title objects.\r
+        */\r
+       protected function ProcessTitles($titles) {\r
+\r
+               $linkBatch = new LinkBatch();\r
+\r
+               foreach ($titles as $titleString) {\r
+                       $titleObj = Title :: newFromText($titleString);\r
+\r
+                       // Validation\r
+                       if (!$titleObj)\r
+                               $this->dieUsage("bad title $titleString", 'invalidtitle');\r
+                       if ($titleObj->getNamespace() < 0)\r
+                               $this->dieUsage("No support for special page $titleString has been implemented", 'unsupportednamespace');\r
+                       if (!$titleObj->userCanRead())\r
+                               $this->dieUsage("No read permission for $titleString", 'titleaccessdenied');\r
+\r
+                       $linkBatch->addObj($titleObj);\r
+\r
+                       // Make sure we remember the original title that was given to us\r
+                       // This way the caller can correlate new titles with the originally requested, i.e. namespace is localized or capitalization\r
+                       if ($titleString !== $titleObj->getPrefixedText()) {\r
+                               $this->GetResult()->AddMessage('query', 'normalized', array (\r
+                                       'from' => $titleString,\r
+                               'to' => $titleObj->getPrefixedText()), 'n');\r
+                       }\r
+               }\r
+\r
+               return $linkBatch;\r
+       }\r
+\r
        protected function GetAllowedParams() {\r
                return array (\r
                        'meta' => array (\r
@@ -155,7 +190,8 @@ class ApiQuery extends ApiBase {
                        'revids' => array (\r
                                GN_ENUM_TYPE => 'integer',\r
                                GN_ENUM_ISMULTI => true\r
-                       )\r
+                       ),\r
+                       'redirects' => false\r
                );\r
        }\r
 \r
@@ -172,10 +208,11 @@ class ApiQuery extends ApiBase {
        }\r
 \r
        protected function GetDescription() {\r
-               return array(\r
-                               'Query API module allows applications to get needed pieces of data from the MediaWiki databases,',\r
-                               'and is loosely based on the Query API interface currently available on all MediaWiki servers.',\r
-                               'All data modifications will first have to use query to acquire a token to prevent abuse from malicious sites.');\r
+               return array (\r
+                       'Query API module allows applications to get needed pieces of data from the MediaWiki databases,',\r
+                       'and is loosely based on the Query API interface currently available on all MediaWiki servers.',\r
+                       'All data modifications will first have to use query to acquire a token to prevent abuse from malicious sites.'\r
+               );\r
        }\r
 \r
        protected function GetExamples() {\r
index 78d98ff..0b5879a 100644 (file)
@@ -33,16 +33,28 @@ abstract class ApiQueryBase extends ApiBase {
 \r
        private $mQueryModule;\r
        \r
-       /**\r
-       * Constructor\r
-       */\r
        public function __construct($main, $query) {\r
                parent :: __construct($main);\r
                $this->mQueryModule = $query;\r
        }\r
        \r
+       /**\r
+        * Get the name of the query being executed by this instance \r
+        */\r
        public function GetQuery() {\r
                return $this->mQueryModule;\r
        }\r
+       \r
+       /**\r
+        * Derived classes return true when they can be used as title generators for other query modules.\r
+        */\r
+       protected static abstract function GetCanGenerate();\r
+       \r
+       /**\r
+        * Return true if this instance is being used as a generator.\r
+        */\r
+       protected function GetIsGenerator() {\r
+               return false;\r
+       }\r
 }\r
 ?>
\ No newline at end of file
index 0a5abd3..f99273b 100644 (file)
@@ -49,14 +49,7 @@ class ApiResult extends ApiBase {
                return $this->mData;\r
        }\r
 \r
-       /*      function addPage($title)\r
-               {\r
-                       if (!isset($this->mPages))\r
-                               $this->mPages &= $this->mData['pages'];\r
-               }\r
-       */\r
-       \r
-       function AddMessage($mainSection, $subSection, $value, $preserveXmlSpacing = false) {\r
+       function AddMessage($mainSection, $subSection, $value, $multiitem = false, $preserveXmlSpacing = false) {\r
                if (!array_key_exists($mainSection, $this->mData)) {\r
                        $this->mData[$mainSection] = array ();\r
                }\r
@@ -68,19 +61,24 @@ class ApiResult extends ApiBase {
                } else {\r
                        $element = & $this->mData[$mainSection];\r
                }\r
-               if (is_array($value)) {\r
-                       $element = array_merge($element, $value);\r
-                       if (!array_key_exists('*', $element)) {\r
-                               $element['*'] = '';\r
-                       }\r
+               if( $multiitem ) {\r
+                       $element['_element'] = $multiitem;\r
+                       $element[] = $value;\r
                } else {\r
-                       if (array_key_exists('*', $element)) {\r
-                               $element['*'] .= $value;\r
+                       if (is_array($value)) {\r
+                               $element = array_merge($element, $value);\r
+                               if (!array_key_exists('*', $element)) {\r
+                                       $element['*'] = '';\r
+                               }\r
                        } else {\r
-                               $element['*'] = $value;\r
-                       }\r
-                       if ($preserveXmlSpacing) {\r
-                               $element['xml:space'] = 'preserve';\r
+                               if (array_key_exists('*', $element)) {\r
+                                       $element['*'] .= $value;\r
+                               } else {\r
+                                       $element['*'] = $value;\r
+                               }\r
+                               if ($preserveXmlSpacing) {\r
+                                       $element['xml:space'] = 'preserve';\r
+                               }\r
                        }\r
                }\r
        }\r