wCMF  3.6
 All Classes Namespaces Files Functions Variables Groups Pages
class.NodeXMLMapper.php
Go to the documentation of this file.
1 <?php
2 /**
3  * wCMF - wemove Content Management Framework
4  * Copyright (C) 2005-2014 wemove digital solutions GmbH
5  *
6  * Licensed under the terms of any of the following licenses
7  * at your choice:
8  *
9  * - GNU Lesser General Public License (LGPL)
10  * http://www.gnu.org/licenses/lgpl.html
11  * - Eclipse Public License (EPL)
12  * http://www.eclipse.org/org/documents/epl-v10.php
13  *
14  * See the license.txt file distributed with this work for
15  * additional information.
16  *
17  * $Id: class.NodeXMLMapper.php 1462 2014-02-04 23:52:27Z iherwig $
18  */
19 require_once(BASE."wcmf/lib/core/class.WCMFException.php");
20 require_once(BASE."wcmf/lib/persistence/class.PersistenceMapper.php");
21 require_once(BASE."wcmf/lib/model/class.Node.php");
22 require_once(BASE."wcmf/lib/model/class.NodeIterator.php");
23 require_once(BASE."wcmf/lib/visitor/class.OutputVisitor.php");
24 require_once(BASE."wcmf/lib/output/class.XMLOutputStrategy.php");
25 
26 /**
27  * Some constants describing the data types
28  */
29 define("DATATYPE_DONTCARE", 0);
30 define("DATATYPE_ATTRIBUTE", 1);
31 define("DATATYPE_ELEMENT", 2);
32 define("DATATYPE_IGNORE", 3); // all data items >= DATATYPE_IGNORE wont be shown in human readable node discriptions
33 /**
34  * Some constants describing the build process
35  */
36 define("BUILDDEPTH_INFINITE", -1); // build complete tree from given root on
37 define("BUILDDEPTH_SINGLE", -2); // build only given node
38 define("BUILDDEPTH_GROUPED", -3); // build tree from given root on respecting the root property defined in element relations
39  // NODE: BUILDDEPTH_GROUPED is not supported yet!
40 
41 define("NEXTID_NODE", 2); // the id of the node that holds the next insert id
42 
43 /**
44  * @class NodeXMLMapper
45  * @ingroup Mapper
46  * @brief NodeXMLMapper maps nodes to a xml file.
47  * @deprecated Use NodeXMLDBMapper instead
48  *
49  * @todo: add DataConverter, Logging
50  *
51  * @author ingo herwig <ingo@wemove.com>
52  */
54 {
55  // xml file variables
56  var $_filename = '';
57  var $_doctype = '';
58  var $_dtd = '';
59  // build process variables
60  var $_root = null; // the root node of the built tree
61  var $_prevId = -1; // the id of the previously read node
62  var $_curParent = null; // the parent node where the current built node will be added to
63  var $_curNode = null; // the current node in build process
64  var $_startId = null; // the id we start at for loading
65  var $_buildDepth = 0; // the depth of the tree tor build
66  var $_curDepth = -1; // the current depth in the xml file tree
67  var $_startDepth = -1; // the depth of the root node of the built tree in the xml file tree
68  var $_saveTree = null; // the temporary tree that will be modified during a transaction and saved afterwards
69  // state variables
70  var $_loading = false; // indicates that we are inside the node that we are loading
71  var $_intransaction = false; // indicates that we are inside a transaction
72 
73  /**
74  * Constructor.
75  * @param params Initialization data given in an assoziative array with the following keys:
76  * filename, doctype, dtd
77  */
78  function NodeXMLMapper($params)
79  {
80  $this->_filename = $params['filename'];
81  $this->_doctype = $params['doctype'];
82  $this->_dtd = $params['dtd'];
83  }
84  /**
85  * Construct a Node from the xml file.
86  * @param oid Id of the Node to construct
87  * @param buildDepth One of the BUILDDEPTH constants or a number describing the number of generations to build
88  * @return A reference to the Node, null if oid does not exist.
89  */
90  function &load($oid, $buildDepth)
91  {
92  $this->_root = null;
93  $this->_prevId = -1;
94  $this->_curParent = null;
95  $this->_curNode = null;
96  $this->_startId = $oid;
97  if ($buildDepth == BUILDDEPTH_SINGLE)
98  $buildDepth = 0;
99  $this->_buildDepth = $buildDepth;
100  $this->_curDepth = -1;
101  $this->_startDepth = -1;
102  $this->_loading = false;
103  $this->_intransaction = false;
104 
105  // setup xml parser
106  $xml_parser = xml_parser_create();
107  xml_set_object($xml_parser,&$this);
108  xml_parser_set_option($xml_parser, XML_OPTION_CASE_FOLDING, false);
109  xml_set_element_handler($xml_parser, "parseStartTag", "parseEndTag");
110  xml_set_character_data_handler($xml_parser, "parseElement");
111  if (!($fp = fopen($this->_filename, "r")))
112  WCMFException::throwEx("Could not open XML input: ".$this->_filename, __FILE__, __LINE__);
113 
114  // parse xml
115  while ($data = fread($fp, 4096))
116  if (!xml_parse($xml_parser, $data, feof($fp)))
117  WCMFException::throwEx("XML error: ".xml_error_string(xml_get_error_code($xml_parser))." at line ".xml_get_current_line_number($xml_parser), __FILE__, __LINE__);
118 
119  xml_parser_free($xml_parser);
120 
121  // return built tree
122  if ($this->_root != null)
123  $this->_root->setState(STATE_CLEAN);
124  return $this->_root;
125  }
126  /**
127  * Construct the template of a Node (defined by element name).
128  * @param type The element's type
129  * @param buildDepth One of the BUILDDEPTH constants or a number describing the number of generations to build
130  * @note BUILDDEPTH is not supported yet!
131  * @return A reference to the Node.
132  */
133  function &create($type, $buildDepth)
134  {
135  // NODE: The creation is hardcoded by now.
136  // It should be done using a dtd and supplement specifications for the node (CMS) properties in future.
137  $node = new Node($type);
138  $node->setMapper($this);
139 
140  // set attributes / element
141  if ($type == 'news')
142  $node->setValue('index', '', DATATYPE_ATTRIBUTE);
143 
144  else if ($type == 'date')
145  $node->setValue('date', '', DATATYPE_ELEMENT);
146 
147  else if ($type == 'headline')
148  $node->setValue('headline', '', DATATYPE_ELEMENT);
149 
150  else if ($type == 'textblock')
151  $node->setValue('textblock', '', DATATYPE_ELEMENT);
152 
153  else if ($type == 'image')
154  {
155  $node->setValue('src', '', DATATYPE_ATTRIBUTE);
156  $node->setValue('width', '', DATATYPE_ATTRIBUTE);
157  $node->setValue('height', '', DATATYPE_ATTRIBUTE);
158  }
159 
160  return $node;
161  }
162  /**
163  * Save a Node to the xml file (inserted if it is new).
164  * @param node A reference to the Node to safe
165  * @return True/False depending on success of operation
166  */
167  function save(&$node)
168  {
169  // NODE: changes will be done to the savetree, which is built by a call to startTransaction()
170  // these changes will be made permanent by a call to commitTransaction().
171 
172  if ($this->_saveTree == null)
173  return false;
174 
175  $saveIter = new NodeIterator($this->_saveTree);
176  if ($node->getState() == STATE_NEW)
177  {
178  // insert new node
179  // precondition: the node has a parent and its id exists in the database
180  $parent = &$node->getParent();
181  if ($parent != null)
182  {
183  // save node by adding into the tree
184  // set new insert id
185  $nextId = $this->getNextInsertId();
186  $node->setOID($nextId);
187  // search for parent and add node to it
188  while(!$saveIter->isEnd())
189  {
190  $currentNode = &$saveIter->getCurrentObject();
191  if ($currentNode->getOID() == NEXTID_NODE)
192  $currentNode->setValue('value', ++$nextId, DATATYPE_ATTRIBUTE);
193 
194  if ($currentNode->getOID() == $parent->getOID())
195  {
196  // make a copy of the node, so that the original node without children
197  // children will be saved in their own call to save()
198  $insertNode = $node;
199  foreach($insertNode->getChildren() as $child)
200  $insertNode->deleteChild($child->getOID(), true);
201  // determine add type of new child
202  // NODE: we only distinguish between ADDCHILD_FRONT and default
203  $children = $parent->getChildren();
204  if ($children[0]->getOID() == $node->getOID())
205  $currentNode->addChild($insertNode, ADDCHILD_FRONT);
206  else
207  $currentNode->addChild($insertNode);
208  break;
209  }
210  $saveIter->proceed();
211  }
212  }
213  else
214  return false;
215  }
216  else if ($node->getState() == STATE_DIRTY)
217  {
218  // save existing node
219  // precondition: the node exists in the database
220  // search for parent
221  while(!$saveIter->isEnd())
222  {
223  $currentNode = &$saveIter->getCurrentObject();
224  if ($currentNode->getOID() == $node->getOID())
225  {
226  // save node by copying values
227  foreach ($node->getDataTypes() as $type)
228  foreach ($node->getValueNames($type) as $valueName)
229  {
230  $currentNode->setValue($valueName, $node->getValue($valueName, $type), $type);
231  $currentNode->setValueProperties($valueName, $node->getValueProperties($valueName, $type), $type);
232  }
233  break;
234  }
235  $saveIter->proceed();
236  }
237  }
238  unset($saveIter);
239  $node->setState(STATE_CLEAN, false);
240  // postcondition: the node is saved to the db
241  // the node id is updated
242  // the node state is STATE_CLEAN
243  // attributes are only inserted if their values differ from ''
244  return true;
245  }
246  /**
247  * Delete a Node from the xml file (together with all of its children).
248  * @param oid The database id of the Node to delete
249  * @param recursive True/False whether to physically delete it's children too [default: true]
250  * @return True/False depending on success of operation
251  * @attention recursive parameter is ignored here
252  */
253  function delete($oid, $recursive=true)
254  {
255  // NODE: changes will be done to the savetree, which is built by a call to startTransaction()
256  // these changes will be made permanent by a call to commitTransaction().
257 
258  // precondition: node exists in savetree
259  if ($this->_saveTree == null)
260  return false;
261 
262  $saveIter = new NodeIterator($this->_saveTree);
263  // search for parent
264  while(!$saveIter->isEnd())
265  {
266  $currentNode = &$saveIter->getCurrentObject();
267  if ($currentNode->getOID() == $oid)
268  {
269  // delete the child
270  $parent = &$currentNode->getParent();
271  $parent->deleteChild($oid, true);
272  break;
273  }
274  $saveIter->proceed();
275  }
276  unset($saveIter);
277  // postcondition: the node and all of its children are deleted from savetree
278  return true;
279  }
280  /**
281  * @see PersistenceMapper::startTransaction()
282  * From now on all calls to save() and delete() will be executed to a temporary tree
283  * that will be saved by the call to commitTransaction().
284  */
285  function startTransaction()
286  {
287  // build save tree
288  $saveMapper = new NodeXMLMapper(array('filename' => $this->_filename));
289  $this->_saveTree = &$saveMapper->load(1, BUILDDEPTH_INFINITE);
290  $this->_intransaction = true;
291  unset($saveMapper);
292  }
293  /**
294  * @see PersistenceMapper::commitTransaction()
295  * Save the temporary tree.
296  */
297  function commitTransaction()
298  {
299  // escape all values in save tree (they were unescaped while loading)
300  $saveIter = new NodeIterator($this->_saveTree);
301  while(!$saveIter->isEnd())
302  {
303  $currentNode = &$saveIter->getCurrentObject();
304  foreach ($currentNode->getDataTypes() as $type)
305  foreach ($currentNode->getValueNames($type) as $valueName)
306  $currentNode->setValue($valueName, htmlspecialchars($currentNode->getValue($valueName, $type), ENT_QUOTES), $type);
307  $saveIter->proceed();
308  }
309  // save modified xml tree
310  $xmlos = new XMLOutputStrategy(strtolower($this->_filename), $this->_doctype, $this->_dtd);
311  $saveIter->reset($this->_saveTree);
312  $ov = new OutputVisitor($xmlos);
313  $ov->startIterator($saveIter);
314  $this->_intransaction = false;
315  // clean up
316  unset($saveIter);
317  unset($ov);
318  unset($xmlos);
319  }
320  /**
321  * @see PersistenceMapper::rollbackTransaction()
322  * Nothing to do since the changes have to be explicitely committed.
323  */
325  {
326  }
327  /**
328  * Get the next insert id.
329  * @return next insert id
330  */
331  function getNextInsertId()
332  {
333  if (!$this->_intransaction)
334  {
335  // we are not in a transaction -> get id from file
336  $insertMapper = new NodeXMLMapper(array('filename' => $this->_filename));
337  $node = &$insertMapper->load(NEXTID_NODE, BUILDDEPTH_SINGLE);
338  unset($insertMapper);
339  return $node->getValue('value', DATATYPE_ATTRIBUTE);
340  }
341  else
342  {
343  // we are in a transaction -> get id from savetree
344  $iter = new NodeIterator($this->_saveTree);
345  while(!$iter->isEnd())
346  {
347  $currentNode = &$iter->getCurrentObject();
348  if ($currentNode->getOID() == NEXTID_NODE)
349  {
350  unset($iter);
351  return $currentNode->getValue('value', DATATYPE_ATTRIBUTE);
352  }
353  $iter->proceed();
354  }
355  }
356  }
357 
358  /**
359  * XML parser functions.
360  */
361 
362  /**
363  * Start Element Handler.
364  * @param parser A reference to the XML parser calling the handler
365  * @param name The name of the element for which this handler is called
366  * @param attribs An associative array with the element's attributes
367  */
368  function parseStartTag($parser, $name, $attribs)
369  {
370  $this->_curDepth++;
371  if ($attribs['id'] == $this->_startId || $this->_loading && $this->isDepthValid())
372  {
373  // construct node
374  $node = &NodeXMLMapper::create(strtolower($name), BUILDDEPTH_SINGLE);
375  $node->setOID($attribs['id']);
376  $node->setMapper($this);
377  $this->_curNode = &$node;
378 
379  // set node's attributes
380  foreach($attribs as $key => $value)
381  if (strtolower($key) != 'id')
382  $node->setValue(strtolower($key), stripslashes(stripslashes($value)), DATATYPE_ATTRIBUTE);
383 
384  if ($attribs['id'] == $this->_startId)
385  {
386  // start node -> init
387  $this->_loading = true;
388  $this->_startDepth = $this->_curDepth;
389  if ($this->_prevId != -1)
390  $node->setProperty('parentdbid', $this->_prevId);
391  $this->_root = &$node;
392  }
393  else if ($this->_loading)
394  {
395  // child node -> add to parent
396  $node->setProperty('parentdbid', $this->_curParent->getOID());
397  $this->_curParent->addChild($node);
398  }
399 
400  // update current parent
401  $this->_curParent = &$node;
402  }
403 
404  $this->_prevId = $attribs['id'];
405  }
406  /**
407  * End Element Handler.
408  * @param parser A reference to the XML parser calling the handler
409  * @param name The name of the element for which this handler is called
410  */
411  function parseEndTag($parser, $name)
412  {
413  $this->_curDepth--;
414 
415  if ($this->_loading && $this->isDepthValid())
416  {
417  // update current parent
418  $this->_curParent = &$this->_curParent->getParent();
419  // stop loading if we reach the end tag of root (which has no parent)
420  if (!$this->_curParent)
421  $this->_loading = false;
422  }
423  }
424  /**
425  * Element Handler.
426  * @param parser A reference to the XML parser calling the handler
427  * @param data The character data as a string
428  */
429  function parseElement($parser, $data)
430  {
431  $data = stripslashes(stripslashes(trim($data)));
432  if ($data != '' && $this->_loading && $this->isDepthValid())
433  {
434  $value = $this->_curNode->getValue($this->_curNode->getType(), DATATYPE_ELEMENT).$data;
435  $this->_curNode->setValue($this->_curNode->getType(), $value, DATATYPE_ELEMENT);
436  }
437  }
438  /**
439  * Check if the current depth is valid for building.
440  * @return True/False whether the depth is valid
441  */
442  function isDepthValid()
443  {
444  return (($this->_curDepth-$this->_startDepth) <= $this->_buildDepth) || ($this->_buildDepth == BUILDDEPTH_INFINITE);
445  }
446 }
447 ?>
448 
const STATE_DIRTY
parseStartTag($parser, $name, $attribs)
const DATATYPE_ATTRIBUTE
Node is the basic component for building trees (although a Node can have one than more parents)...
Definition: class.Node.php:118
NodeXMLMapper maps nodes to a xml file.
const ADDCHILD_FRONT
Definition: class.Node.php:30
const BUILDDEPTH_INFINITE
& load($oid, $buildDepth)
This OutputStrategy outputs an object's content in a xml file using the default format.
parseElement($parser, $data)
NodeIterator is used to iterate over a tree/list build of objects using a Depth-First-Algorithm. Classes used with the NodeIterator must implement the getChildren() and getOID() methods. NodeIterator implements the 'Iterator Pattern'. The base class NodeIterator defines the interface for all specialized Iterator classes.
The OutputVisitor is used to output an object's content to different destinations and formats...
throwEx($message, $file='', $line='')
const STATE_CLEAN
const BUILDDEPTH_SINGLE
& create($type, $buildDepth)
const STATE_NEW
const DATATYPE_ELEMENT
parseEndTag($parser, $name)
PersistenceMapper is the base class for all mapper classes.
const NEXTID_NODE