From 5248ab23a24a01e6604fad46de07d19707a82094 Mon Sep 17 00:00:00 2001
From: tomas <tomas@cfc7b370-4200-0410-a6e3-cb6bdb053afe>
Date: Mon, 7 Feb 2005 23:17:40 +0000
Subject: [PATCH] Major changes:  added: SAX parser support, setMetadataValue
 and deleteRecord methods  modified: getMetadataValue

---
 .../modules/storageServer/var/MetaData.php    | 537 +++++++++++-------
 1 file changed, 319 insertions(+), 218 deletions(-)

diff --git a/livesupport/modules/storageServer/var/MetaData.php b/livesupport/modules/storageServer/var/MetaData.php
index 6d03bcc20..39698b0b9 100644
--- a/livesupport/modules/storageServer/var/MetaData.php
+++ b/livesupport/modules/storageServer/var/MetaData.php
@@ -23,20 +23,25 @@
  
  
     Author   : $Author: tomas $
-    Version  : $Revision: 1.15 $
+    Version  : $Revision: 1.16 $
     Location : $Source: /home/paul/cvs2svn-livesupport/newcvsrepo/livesupport/modules/storageServer/var/MetaData.php,v $
 
 ------------------------------------------------------------------------------*/
+define('DEBUG', FALSE);
+#define('DEBUG', TRUE);
+define('MODIFY_LAST_MATCH', TRUE);
+
+require_once "XML/Util.php";
 
 /**
  *  MetaData class
  *
  *  LiveSupport file storage support class.<br>
  *  Store metadata tree in relational database.<br>
- *  <b>requires DOMXML support in PHP!</b>
- *  TODO: use SAX parser instead of DOMXML
  *
  *  @see StoredFile
+ *  @see XmlParser
+ *  @see DataEngine
  */
 class MetaData{
     /**
@@ -64,13 +69,13 @@ class MetaData{
      *  Parse and store metadata from XML file or XML string
      *
      *  @param mdata string, local path to metadata XML file or XML string
-     *  @param loc string 'file'|'string'
+     *  @param loc string - location: 'file'|'string'
      *  @return true or PEAR::error
      */
     function insert($mdata, $loc='file')
     {
         if($this->exists) return FALSE;
-        $res = $this->storeXMLDoc($mdata, $loc);
+        $res = $this->storeDoc($mdata, $loc);
         if(PEAR::isError($res)) return $res;
         switch($loc){
         case"file":
@@ -113,7 +118,7 @@ class MetaData{
         return $this->replace($mdata, $loc);
         /*
         if(!$this->exists) return FALSE;
-        $res = $this->storeXMLDoc($mdata, $loc, 'update');
+        $res = $this->storeDoc($mdata, $loc, 'update');
         if(PEAR::isError($res)) return $res;
         $this->exists = TRUE;
         return TRUE;
@@ -167,12 +172,158 @@ class MetaData{
     function getMetaData()
     {
         // return $this->genXMLDoc();       // obsolete
-        if(file_exists($this->fname))
-            return file_get_contents($this->fname);
-        else
+        if(file_exists($this->fname)){
+            $res = file_get_contents($this->fname);
+#            require_once "XML/Beautifier.php";
+#            $fmt = new XML_Beautifier();
+#            $res = $fmt->formatString($res);
+            return $res;
+        }else
             return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<metadata/>\n";
     }
 
+    /**
+     *  Get metadata element value
+     *
+     *  @param category string, metadata element name
+     *  @param lang string, optional xml:lang value for select language version
+     *  @param objns string, object namespace prefix - for internal use only
+     *  @return array of matching records as hash with fields:
+     *   <ul>
+     *      <li>mid int, local metadata record id</li>
+     *      <li>value string, element value</li>
+     *      <li>attrs hasharray of element's attributes indexed by
+     *          qualified name (e.g. xml:lang)</li>
+     *   </ul>
+     *  @see BasicStor::bsGetMetadataValue
+     */
+    function getMetadataValue($category, $lang=NULL, $objns='_L')
+    {
+        // handle predicate namespace shortcut
+        $a     = XML_Util::splitQualifiedName(strtolower($category));
+        if(PEAR::isError($a)) return $a;
+        $catNs = $a['namespace'];
+        $cat   = $a['localPart'];
+        $cond = "
+                gunid=x'{$this->gunid}'::bigint AND objns='$objns' AND
+                predicate='$cat'
+        ";
+        if(!is_null($catNs)) $cond .= " AND predns='$catNs'";
+        $sql = "
+            SELECT id as mid, object as value
+            FROM {$this->mdataTable}
+            WHERE $cond
+            ORDER BY id
+        ";
+        $all = $this->dbc->getAll($sql);
+        if(PEAR::isError($all)) return $all;
+        $res = array();
+        // add attributes to result
+        foreach($all as $i=>$rec){
+            $pom = $this->getSubrows($rec['mid']);
+            if(PEAR::isError($pom)) return $pom;
+            $all[$i]['attrs'] = $pom['attrs'];
+            $atlang = $pom['attrs']['xml:lang'];
+            // select only matching lang (en is default)
+            if(
+                is_null($lang) ||
+                strtolower($lang) == strtolower($atlang) ||
+                (is_null($atlang) && strtolower($lang) == 'en')
+            ){
+                $res[] = $all[$i];
+            }
+        }
+        return $res;
+    }
+
+    /**
+     *  Set metadata element value
+     *
+     *  @param category string, metadata element name (e.g. dc:title)
+     *  @param value string/NULL value to store, if NULL then delete record
+     *  @param lang string, optional xml:lang value for select language version
+     *  @param mid int, metadata record id (OPTIONAL on unique elements)
+     *  @return boolean
+     */
+    function setMetadataValue($category, $value, $lang=NULL, $mid=NULL)
+    {
+        $rows   = $this->getMetadataValue($category, $lang);
+        $aktual = NULL;
+        if(count($rows)>1){
+            if(is_null($mid)){
+                if(MODIFY_LAST_MATCH){ $aktual = array_pop($rows); }
+                else{
+                    return PEAR::raiseError(
+                        "MetaData::setMdataValue:".
+                        " nonunique category, mid required"
+                    );
+                }
+            }else{
+                foreach($rows as $i=>$row){
+                    if($mid == intval($row['mid'])) $aktual = $row;
+                }
+            }
+        }else $aktual = $rows[0];
+        if(!is_null($aktual)){
+            if(!is_null($value)){
+                $sql = "
+                    UPDATE {$this->mdataTable}
+                    SET object='$value'
+                    WHERE id={$aktual['mid']}
+                ";
+                $res = $this->dbc->query($sql);
+            }else{
+                $res = $this->deleteRecord($aktual['mid']);
+            }
+            if(PEAR::isError($res)) return $res;
+        }else{
+            $container = $this->getMetadataValue('metadata', NULL, '_blank');
+            if(PEAR::isError($container)) return $container;
+            $id = $container[0]['mid'];
+            if(is_null($id)){
+                return PEAR::raiseError(
+                    "MetaData::setMdataValue: metadata container  not found"
+                );
+            }
+            $a     = XML_Util::splitQualifiedName(strtolower($category));
+            if(PEAR::isError($a)) return $a;
+            $catNs = $a['namespace'];
+            $cat   = $a['localPart'];
+            $nid= $this->storeRecord('_I', $id, $catNs, $cat, $predxml='T',
+                '_L', $value);
+            if(PEAR::isError($nid)) return $nid;
+            if(!is_null($lang)){
+                $res= $this->storeRecord('_I', $nid, 'xml', 'lang', $predxml='A',
+                    '_L', $lang);
+                if(PEAR::isError($res)) return $res;
+            }
+        }
+        return TRUE;
+    }
+
+    /**
+     *  Regenerate XML metadata file after category value change
+     *
+     *  @return boolean
+     */
+    function regenerateXmlFile()
+    {
+        $fn = $this->fname;
+        $xml = $this->genXMLDoc();
+        if (!$fh = fopen($fn, 'w')) {
+            return PEAR::raiseError(
+                "MetaData::regenerateXmlFile: cannot open for write ($fn)"
+            );
+        }
+        if(fwrite($fh, $xml) === FALSE) {
+            return PEAR::raiseError(
+                "MetaData::regenerateXmlFile: write error ($fn)"
+            );
+        }
+        fclose($fh);
+        return TRUE;
+    }
+    
     /**
      *  Contruct filepath of metadata file
      *
@@ -215,53 +366,44 @@ class MetaData{
      *  Parse and insert or update metadata XML to database
      *
      *  @param mdata string, local path to metadata XML file or XML string
-     *  @param loc string 'file'|'string'
-     *  @param mode string 'insert'|'update'
+     *  @param loc string, location: 'file'|'string'
      *  @return true or PEAR::error
      */
-    function storeXMLDoc($mdata='', $loc='file', $mode='insert')
+    function storeDoc($mdata='', $loc='file')
     {
         switch($loc){
         case"file":
             if(!is_file($mdata)){
                 return PEAR::raiseError(
-                    "MetaData::storeXMLDoc: metadata file not found ($mdata)"
+                    "MetaData::storeDoc: metadata file not found ($mdata)"
                 );
             }
             if(!is_readable($mdata)){
                 return PEAR::raiseError(
-                    "MetaData::storeXMLDoc: can't read metadata file ($mdata)"
+                    "MetaData::storeDoc: can't read metadata file ($mdata)"
                 );
             }
-            $mdstr = file_get_contents($mdata);
-            $xml = domxml_open_mem($mdstr);
-            break;
+            $mdata = file_get_contents($mdata);
         case"string":
-            $xml = domxml_open_mem($mdata);
+            require_once"XmlParser.php";
+            $parser =& new XmlParser($mdata);
+            if($parser->isError()){
+                return PEAR::raiseError(
+                    "MetaData::storeDoc: ".$parser->getError()
+                );
+            }
+            $tree = $parser->getTree();
             break;
         default:
             return PEAR::raiseError(
-                "MetaData::storeXMLDoc: unsupported metadata location ($loc)"
+                "MetaData::storeDoc: unsupported metadata location ($loc)"
             );
         }
-        $root = $xml->document_element();
-        if(!is_object($root)) return PEAR::raiseError(
-            "MetaData::storeXMLDoc: metadata parser failed (".gettype($root).")"
-        );
         $this->dbc->query("BEGIN");
-        if($mode == 'update') $this->nameSpaces = $this->readNamespaces();
-        $res = $this->storeXMLNode($root, NULL, $mode);
+        $res = $this->storeNode($tree);
         if(PEAR::isError($res)){
             $this->dbc->query("ROLLBACK"); return $res;
         }
-        foreach($this->nameSpaces as $prefix=>$uri){
-            $res = $this->storeRecord(
-                '_L', $prefix, NULL, '_namespace', 'T', '_L', $uri, $mode
-            );
-            if(PEAR::isError($res)){
-                $this->dbc->query("ROLLBACK"); return $res;
-            }
-        }
         $res = $this->dbc->query("COMMIT");
         if(PEAR::isError($res)){ $this->dbc->query("ROLLBACK"); return $res; }
         return TRUE;
@@ -270,109 +412,43 @@ class MetaData{
         return $root;
     }
 
-    /**
-     *  Read namespace definitions from database and return it as array
-     *
-     *  @return array or PEAR::error
-     */
-    function readNamespaces()
-    {
-        $nameSpaces = array();
-        $arr = $this->dbc->getAll("SELECT subject, object
-            FROM {$this->mdataTable}
-            WHERE gunid=x'{$this->gunid}'::bigint
-                AND subjns='_L'
-                AND predns is null AND predicate='_namespace'
-                AND objns='_L'
-        ");
-        if(PEAR::isError($arr)) return $arr;
-        if(is_array($arr)){
-            foreach($arr as $i=>$v){
-                $nameSpaces[$v['subject']] = $v['object'];
-            }
-        }
-        return $nameSpaces;
-    }
-
     /**
      *  Process one node of metadata XML for insert or update.<br>
-     *  <b>TODO: add support for other usable node types</b>
      *
-     *  @param node DOM node object
+     *  @param node object, node in tree returned by XmlParser
      *  @param parid int, parent id
-     *  @param mode 'insert'|'update'
-     *  @return
+     *  @param nSpaces array of name spaces definitions
+     *  @return int, local metadata record id
      */
-    function storeXMLNode($node, $parid=NULL, $mode='insert')
+    function storeNode($node, $parid=NULL, $nSpaces=array())
     {
         //echo $node->node_name().", ".$node->node_type().", ".$node->prefix().", $parid.\n";
-        // preprocessing:
-        switch($node->node_type()){
-            case 1:             // element
-            case 2:             // attribute
-                $subjns  = (is_null($parid)? '_G'         : '_I');
-                $subject = (is_null($parid)? $this->gunid : $parid);
-                // DOM XML extension doesn't like empty prefix - use '_d'
-                $prefix = $node->prefix(); $prefix = ($prefix === '' ? '_d' : $prefix);
-                if(!isset($this->nameSpaces[$prefix]))   
-                    $this->nameSpaces[$prefix] = $node->namespace_uri();
-            break;
+        $nSpaces = array_merge($nSpaces, $node->nSpaces);
+        // null parid = root node of metadata tree
+        $subjns  = (is_null($parid)? '_G'         : '_I');
+        $subject = (is_null($parid)? $this->gunid : $parid);
+        $object  = $node->content;
+        if(is_null($object) || $object == ''){
+            $objns  = '_blank';
+            $object = 'NULL';
+        }else $objns = '_L';
+        $id = $this->storeRecord($subjns, $subject,
+            $node->ns, $node->name, 'T', $objns, $object);
+        // process attributes
+        foreach($node->attrs as $atn=>$ato){
+            $this->storeRecord('_I', $id,
+                $ato->ns, $ato->name, 'A', '_L', $ato->val);
         }
-        // main processing:
-        switch($node->node_type()){
-            case 9:             // document
-                $this->storeXMLNode($node->document_element(), $parid, $mode);
-            break;
-            case 1:             // element
-                if($node->is_blank_node()) break;
-                $id = $this->storeRecord(
-                    $subjns, $subject, $prefix, $node->node_name(), 'T',
-                    '_blank', NULL, $mode
-                );
-                if(PEAR::isError($id)) return $id;
-                if($node->has_attributes()){
-                    foreach($node->attributes() as $attr){
-                        $res = $this->storeXMLNode($attr, $id, $mode);
-                        if(PEAR::isError($res)) return $res;
-                    }
-                }
-                if($node->has_child_nodes()){
-                    foreach($node->child_nodes() as $child){
-                        $res = $this->storeXMLNode($child, $id, $mode);
-                        if(PEAR::isError($res)) return $res;
-                    }
-                }
-            break;
-            case 2:             // attribute
-                $res = $this->storeRecord(
-                    $subjns, $subject, $prefix, $node->node_name(),
-                    'A', '_L', $node->value(), $mode
-                );
-                if(PEAR::isError($res)) return $res;
-            break;
-            case 3:             // text
-            case 4:             // cdata
-#                echo"T\n";
-                if($node->is_blank_node()) break;
-                $objns_sql  = "'_L'";
-                // coalesce ... returns the first of its arguments that is not NULL
-                $object_sql = "coalesce(object,'')||'".$node->node_value()."'";
-                $res = $this->dbc->query("
-                    UPDATE {$this->mdataTable}
-                    SET objns=$objns_sql, object=$object_sql
-                    WHERE id='$parid'
-                ");
-                if(PEAR::isError($res)) return $res;
-            break;
-            case"5": case"6": case"7": case"8":
-            break;
-            default:
-                return PEAR::raiseError(
-                    "MetaData::storeXMLNode: unsupported node type (".
-                    $node->node_type().")"
-                );
+        // process child nodes
+        foreach($node->children as $ch){
+            $this->storeNode($ch, $id, $nSpaces);
         }
-        return TRUE;
+        // process namespace definitions
+        foreach($node->nSpaces as $ns=>$uri){
+            $this->storeRecord('_I', $id,
+                'xmlns', $ns, 'N', '_L', $uri);
+        }
+        return $id;
     }
 
     /**
@@ -404,63 +480,66 @@ class MetaData{
      *  @param predns string, predicate namespace prefix, have to be defined
      *          in file's metadata (or reserved prefix)
      *  @param predicate string, predicate value, e.g. name of DC element
-     *  @param predxml string 'T'|'A' - XML tag or attribute
+     *  @param predxml string 'T'|'A'|'N' - XML tag, attribute or NS def.
      *  @param objns string, object namespace prefix, have to be defined
      *          in file's metadata (or reserved prefix)
      *  @param object string, object value, e.g. title of song
-     *  @param mode 'insert'|'update'
      *  @return int, new metadata record id
      */
     function storeRecord($subjns, $subject, $predns, $predicate, $predxml='T',
-        $objns=NULL, $object=NULL, $mode='insert')
+        $objns=NULL, $object=NULL)
     {
-        //echo "$subjns, $subject, $predns, $predicate, $predxml, $objns, $object, $mode\n";
-        $predns_sql = (is_null($predns) ? "NULL" : "'".strtolower($predns)."'" );
+        //echo "$subjns, $subject, $predns, $predicate, $predxml, $objns, $object\n";
+        $predns_sql = (is_null($predns) ? "NULL" : "'$predns'" );
         $objns_sql  = (is_null($objns) ? "NULL" : "'$objns'" );
         $object_sql = (is_null($object)? "NULL" : "'$object'");
-        $predicate  = strtolower($predicate);
-        $id = NULL;
-        if($mode == 'update'){
-            $cond = "gunid = x'{$this->gunid}'::bigint AND predns=$predns_sql
-                AND predicate='$predicate'";
-            if($subjns == '_I'){
-                $cond .= " AND subjns='_I' AND subject='$subject'";
-            }
-            $id = $this->dbc->getOne("SELECT id FROM {$this->mdataTable}
-                WHERE $cond");
-            //echo "$id, ".(is_null($id) ? 'null' : 'not null')."\n";
-        }
-        if(is_null($id)){ $mode = 'insert'; }
-        if($mode == 'insert'){
-            $id = $this->dbc->nextId("{$this->mdataTable}_id_seq");
-        }
+        $id = $this->dbc->nextId("{$this->mdataTable}_id_seq");
         if(PEAR::isError($id)) return $id;
-        if($mode == 'insert'){
-            $res = $this->dbc->query("
-                INSERT INTO {$this->mdataTable}
-                    (id , gunid           , subjns   , subject   ,
-                        predns     , predicate   , predxml   ,
-                        objns     , object
-                    )
-                VALUES
-                    ($id, x'{$this->gunid}'::bigint, '$subjns', '$subject',
-                        $predns_sql, '$predicate', '$predxml',
-                        $objns_sql, $object_sql
-                    )
-            ");
-        }else{
-            $res = $this->dbc->query("
-                UPDATE {$this->mdataTable}
-                SET subjns = '$subjns',   subject   = '$subject',
-                    objns  = $objns_sql,  object    = $object_sql
-                WHERE id='$id'
-            ");
-            //    WHERE $cond
-        }
+        $res = $this->dbc->query("
+            INSERT INTO {$this->mdataTable}
+                (id , gunid           , subjns   , subject   ,
+                    predns     , predicate   , predxml   ,
+                    objns     , object
+                )
+            VALUES
+                ($id, x'{$this->gunid}'::bigint, '$subjns', '$subject',
+                    $predns_sql, '$predicate', '$predxml',
+                    $objns_sql, $object_sql
+                )
+        ");
         if(PEAR::isError($res)) return $res;
         return $id;
     }
 
+    /**
+     *  Delete metadata record recursively
+     *
+     *  @return boolean
+     */
+    function deleteRecord($mid)
+    {
+        $sql = "
+            SELECT id FROM {$this->mdataTable}
+            WHERE subjns='_I' AND subject='{$mid}' AND
+                gunid=x'{$this->gunid}'::bigint
+        ";
+        $rh = $this->dbc->query($sql);
+        if(PEAR::isError($rh)) return $rh;
+        while($row = $rh->fetchRow()){
+            $r = $this->deleteRecord($row['id']);
+            if(PEAR::isError($r)) return $r;
+        }
+        $rh->free();
+        $sql = "
+            DELETE FROM {$this->mdataTable}
+            WHERE id={$mid} AND
+                gunid=x'{$this->gunid}'::bigint
+        ";
+        $res = $this->dbc->query($sql);
+        if(PEAR::isError($res)) return $res;
+        return TRUE;
+    }
+    
     /* =========================================== XML reconstruction from db */
     /**
      *  Generate XML document from metadata database
@@ -469,7 +548,8 @@ class MetaData{
      */
     function genXMLDoc()
     {
-        $domd =& domxml_new_xmldoc('1.0');
+        require_once "XML/Util.php";
+        $res = XML_Util::getXMLDeclaration("1.0", "UTF-8")."\n";
         $row = $this->dbc->getRow("
             SELECT * FROM {$this->mdataTable}
             WHERE gunid=x'{$this->gunid}'::bigint
@@ -477,74 +557,95 @@ class MetaData{
         ");
         if(PEAR::isError($row)) return $row;
         if(is_null($row)){
-//            return PEAR::raiseError(
-//                "MetaData::genXMLDoc: not exists ({$this->gunid})"
-//            );
-            $nxn =& $domd->create_element('metadata');
-            $domd->append_child($nxn);
+            $node = XML_Util::createTagFromArray(array(
+                'localpart'=>'none'
+            ));
         }else{
-            $rr = $this->genXMLNode($domd, $domd, $row);
-            if(PEAR::isError($rr)) return $rr;
+            $node = $this->genXMLNode($row);
+            if(PEAR::isError($node)) return $node;
         }
-        //return preg_replace("|</([^>]*)>|", "</\\1>\n", $domd->dump_mem())."\n";
-        return $domd->dump_mem(TRUE, 'UTF-8');
+        $res .= $node;
+        require_once "XML/Beautifier.php";
+        $fmt = new XML_Beautifier();
+        $res = $fmt->formatString($res);
+        return $res;
     }
 
     /**
      *  Generate XML element from database
      *
-     *  @param domd DOM document object
-     *  @param xn DOM element object
-     *  @param row array, database row with values for created element
-     *  @return void
+     *  @param row array, hash with metadata record fields
+     *  @return string, XML serialization of node
      */
-    function genXMLNode(&$domd, &$xn, $row)
+    function genXMLNode($row)
     {
-        if($row['predxml']=='T'){
-            $nxn =& $domd->create_element($row['predicate']);
-        }else{
-            $nxn =& $domd->create_attribute($row['predicate'], '');
-        }
-        $xn->append_child($nxn);
-        $uri = $this->dbc->getOne("
-            SELECT object FROM {$this->mdataTable}
-            WHERE gunid=x'{$this->gunid}'::bigint AND predicate='_namespace'
-                AND subjns='_L' AND subject='{$row['predns']}'
-        ");
-        if(!is_null($uri) && $uri !== ''){
-            $root =& $domd->document_element();
-            if($row['predns'] === '') $row['predns']='_d';
-            $root->add_namespace($uri, $row['predns']);
-            $nxn->set_namespace($uri, $row['predns']);
-        }
-        if($row['object'] != 'NULL'){
-            $tn =& $domd->create_text_node($row['object']);
-            $nxn->append_child($tn);
-        }
-        $this->genXMLTree($domd, $nxn, $row['id']);
+        if(DEBUG) echo"genXMLNode:\n";
+        if(DEBUG) var_dump($row);
+        extract($row);
+        $arr = $this->getSubrows($id);
+        if(PEAR::isError($arr)) return $arr;
+        if(DEBUG) var_dump($arr);
+        extract($arr);
+        $node = XML_Util::createTagFromArray(array(
+            'namespace' => $predns,
+            'localPart' => $predicate,
+            'attributes'=> $attrs,
+#            'content'   => $object." X ".$children,
+            'content'   => ($object == 'NULL' ? $children : $object),
+        ), FALSE);
+        return $node;
     }
 
     /**
-     *  Generate XML subtree from database
+     *  Return values of attributes, child nodes and namespaces for
+     *  one metadata record
      *
-     *  @param domd DOM document object
-     *  @param xn DOM element object
-     *  @param parid parent id
-     *  @return void
+     *  @param parid int, local id of parent metadata record
+     *  @return hash with three fields:
+     *      - attr hash, attributes
+     *      - children array, child nodes
+     *      - nSpaces hash, namespace definitions
      */
-    function genXMLTree(&$domd, &$xn, $parid)
+    function getSubrows($parid)
     {
-        $qh = $this->dbc->query("
-            SELECT * FROM {$this->mdataTable}
-            WHERE gunid=x'{$this->gunid}'::bigint AND subjns='_I'
-                AND subject='$parid'
+        if(DEBUG) echo" getSubrows:\n";
+        $qh = $this->dbc->query($q = "
+            SELECT
+                id, predxml, predns, predicate, objns,
+                coalesce(object, 'NULL')as object
+            FROM {$this->mdataTable}
+            WHERE
+                subjns='_I' AND subject='$parid' AND
+                gunid=x'{$this->gunid}'::bigint
             ORDER BY id
         ");
         if(PEAR::isError($qh)) return $qh;
+        $attrs      = array();
+        $children   = array();
+        $nSpaces    = array();
+        if(DEBUG) echo "  #=".$qh->numRows()."\n$q\n";
         while($row = $qh->fetchRow()){
-            $this->genXMLNode($domd, $xn, $row);
+            if(DEBUG) var_dump($row);
+            extract($row);
+            switch($predxml){
+            case"N":
+                $nSpaces["$predicate"] = $object;
+            case"A":
+                $sep=':';
+                if($predns=='' || $predicate=='') $sep='';
+                $attrs["{$predns}{$sep}{$predicate}"] = $object;
+                break;
+            case"T":
+                $children[] = $this->genXMLNode($row);
+                break;
+            default:
+                return PEAR::raiseError(
+                    "MetaData::getSubrows: unknown predxml ($predxml)");
+            } // switch
         }
         $qh->free();
+        $children   = join(" ", $children);
+        return compact('attrs', 'children', 'nSpaces');
     }
     
     /* ========================================================= test methods */