Code refactoring: eliminated duplicate code between MFDocument and ModuleDesign

SVN:trunk[3860]
This commit is contained in:
Romain Quetiez
2016-01-14 14:11:25 +00:00
parent 8c4e84dfaf
commit 6d23d64e8f
3 changed files with 282 additions and 366 deletions

View File

@@ -0,0 +1,265 @@
<?php
// Copyright (C) 2016 Combodo SARL
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// iTop 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 Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Design document and associated nodes
* @package Core
*/
namespace Combodo\iTop;
/**
* Class \Combodo\iTop\DesignDocument
*
* A design document is the DOM tree that modelize behaviors. One of its
* characteristics is that it can be altered by the mean of the same kind of document.
*
*/
class DesignDocument extends \DOMDocument
{
/**
* @throws \Exception
*/
public function __construct()
{
parent::__construct('1.0', 'UTF-8');
$this->Init();
}
/**
* Overloadable. Called prior to data loading.
*/
protected function Init()
{
$this->registerNodeClass('DOMElement', '\Combodo\iTop\DesignElement');
$this->formatOutput = true; // indent (must be loaded with option LIBXML_NOBLANKS)
$this->preserveWhiteSpace = true; // otherwise the formatOutput option would have no effect
}
/**
* Overload of the standard API
*/
public function load($filename, $options = 0)
{
parent::load($filename, LIBXML_NOBLANKS);
}
/**
* Overload of the standard API
*/
public function save($filename, $options = 0)
{
$this->documentElement->setAttribute('xmlns:xsi', "http://www.w3.org/2001/XMLSchema-instance");
return parent::save($filename, LIBXML_NOBLANKS);
}
/**
* Create an HTML representation of the DOM, for debugging purposes
* @param bool|false $bReturnRes Echoes or returns the HTML representation
* @return mixed void or the HTML representation of the DOM
*/
public function Dump($bReturnRes = false)
{
$sXml = $this->saveXML();
if ($bReturnRes)
{
return $sXml;
}
else
{
echo "<pre>\n";
echo htmlentities($sXml);
echo "</pre>\n";
}
}
/**
* Quote and escape strings for use within an XPath expression
* Usage: DesignDocument::GetNodes('class[@id='.DesignDocument::XPathQuote($sId).']');
* @param $sValue The value to be quoted
* @return string to be used within an XPath
*/
public static function XPathQuote($sValue)
{
if (strpos($sValue, '"') !== false)
{
$aParts = explode('"', $sValue);
$sRet = 'concat("'.implode('", \'"\', "', $aParts).'")';
}
else
{
$sRet = '"'.$sValue.'"';
}
return $sRet;
}
/**
* Extracts some nodes from the DOM
* @param string $sXPath A XPath expression
* @param DesignNode|null $oContextNode The node to start the search from
* @return \DOMNodeList
*/
public function GetNodes($sXPath, $oContextNode = null)
{
$oXPath = new \DOMXPath($this);
if (is_null($oContextNode))
{
$oResult = $oXPath->query($sXPath);
}
else
{
$oResult = $oXPath->query($sXPath, $oContextNode);
}
return $oResult;
}
/**
* An alternative to getNodePath, that gives the id of nodes instead of the position within the children
* @param $oNode The node to describe
* @return string
*/
public static function GetItopNodePath($oNode)
{
if ($oNode instanceof \DOMDocument) return '';
if (is_null($oNode)) return '';
$sId = $oNode->getAttribute('id');
$sNodeDesc = ($sId != '') ? $oNode->nodeName.'['.$sId.']' : $oNode->nodeName;
return self::GetItopNodePath($oNode->parentNode).'/'.$sNodeDesc;
}
}
/**
* DesignElement: helper to read/change the DOM
* @package ModelFactory
*/
class DesignElement extends \DOMElement
{
/**
* Extracts some nodes from the DOM
* @param string $sXPath A XPath expression
* @return \DOMNodeList
*/
public function GetNodes($sXPath)
{
return $this->ownerDocument->GetNodes($sXPath, $this);
}
/**
* Create an HTML representation of the DOM, for debugging purposes
* @param bool|false $bReturnRes Echoes or returns the HTML representation
* @return mixed void or the HTML representation of the DOM
*/
public function Dump($bReturnRes = false)
{
$oDoc = new DesignDocument();
$oClone = $oDoc->importNode($this->cloneNode(true), true);
$oDoc->appendChild($oClone);
$sXml = $oDoc->saveXML($oClone);
if ($bReturnRes)
{
return $sXml;
}
else
{
echo "<pre>\n";
echo htmlentities($sXml);
echo "</pre>\n";
}
}
/**
* Returns the node directly under the given node
* @param $sTagName
* @param bool|true $bMustExist
* @return null
* @throws DOMFormatException
*/
public function GetUniqueElement($sTagName, $bMustExist = true)
{
$oNode = null;
foreach($this->childNodes as $oChildNode)
{
if ($oChildNode->nodeName == $sTagName)
{
$oNode = $oChildNode;
break;
}
}
if ($bMustExist && is_null($oNode))
{
throw new DOMFormatException('Missing unique tag: '.$sTagName);
}
return $oNode;
}
/**
* Returns the node directly under the current node, or null if missing
* @param $sTagName
* @return null
* @throws DOMFormatException
*/
public function GetOptionalElement($sTagName)
{
return $this->GetUniqueElement($sTagName, false);
}
/**
* Returns the TEXT of the current node (possibly from several child nodes)
* @param null $sDefault
* @return null|string
*/
public function GetText($sDefault = null)
{
$sText = null;
foreach($this->childNodes as $oChildNode)
{
if ($oChildNode instanceof \DOMText)
{
if (is_null($sText)) $sText = '';
$sText .= $oChildNode->wholeText;
}
}
if (is_null($sText))
{
return $sDefault;
}
else
{
return $sText;
}
}
/**
* Get the TEXT value from a child node
* @param string $sTagName
* @param string|null $sDefault
* @return string
*/
public function GetChildText($sTagName, $sDefault = null)
{
$sRet = $sDefault;
if ($oChild = $this->GetOptionalElement($sTagName))
{
$sRet = $oChild->GetText($sDefault);
}
return $sRet;
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2015 Combodo SARL
// Copyright (C) 2015-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -24,6 +24,7 @@
*/
require_once(APPROOT.'application/utils.inc.php');
require_once(APPROOT.'core/designdocument.class.inc.php');
/**
@@ -54,7 +55,7 @@ require_once(APPROOT.'application/utils.inc.php');
* ...
* }
*/
class ModuleDesign extends DOMDocument
class ModuleDesign extends \Combodo\iTop\DesignDocument
{
/**
* @param string|null $sDesignSourceId Identifier of the section module_design (generally a module name), null to build an empty design
@@ -62,8 +63,7 @@ class ModuleDesign extends DOMDocument
*/
public function __construct($sDesignSourceId = null)
{
parent::__construct('1.0', 'UTF-8');
$this->Init();
parent::__construct();
if (!is_null($sDesignSourceId))
{
@@ -71,20 +71,9 @@ class ModuleDesign extends DOMDocument
}
}
/**
* Overloadable. Called prior to data loading.
*/
protected function Init()
{
$this->registerNodeClass('DOMElement', 'ModuleDesignElement');
$this->formatOutput = true; // indent (must be loaded with option LIBXML_NOBLANKS)
$this->preserveWhiteSpace = true; // otherwise the formatOutput option would have no effect
}
/**
* Gets the data where the compiler has left them...
* @param $sDesignSourceId Identifier of the section module_design (generally a module name)
* @param $sDesignSourceId String Identifier of the section module_design (generally a module name)
* @throws Exception
*/
protected function LoadFromCompiledDesigns($sDesignSourceId)
@@ -128,213 +117,4 @@ class ModuleDesign extends DOMDocument
throw new Exception("Invalid XML in '$sFile'. Errors: ".implode(', ', $aDisplayErrors));
}
}
/**
* Overload of the standard API
*/
public function load($filename, $options = 0)
{
parent::load($filename, LIBXML_NOBLANKS);
}
/**
* Overload of the standard API
*/
public function save($filename, $options = 0)
{
$this->documentElement->setAttribute('xmlns:xsi', "http://www.w3.org/2001/XMLSchema-instance");
return parent::save($filename, LIBXML_NOBLANKS);
}
/**
* Create an HTML representation of the DOM, for debugging purposes
* @param bool|false $bReturnRes Echoes or returns the HTML representation
* @return mixed void or the HTML representation of the DOM
*/
public function Dump($bReturnRes = false)
{
$sXml = $this->saveXML();
if ($bReturnRes)
{
return $sXml;
}
else
{
echo "<pre>\n";
echo htmlentities($sXml);
echo "</pre>\n";
}
}
/**
* Quote and escape strings for use within an XPath expression
* Usage: DesignDocument::GetNodes('class[@id='.DesignDocument::XPathQuote($sId).']');
* @param $sValue The value to be quoted
* @return string to be used within an XPath
*/
public static function XPathQuote($sValue)
{
if (strpos($sValue, '"') !== false)
{
$aParts = explode('"', $sValue);
$sRet = 'concat("'.implode('", \'"\', "', $aParts).'")';
}
else
{
$sRet = '"'.$sValue.'"';
}
return $sRet;
}
/**
* Extracts some nodes from the DOM
* @param string $sXPath A XPath expression
* @param DesignNode|null $oContextNode The node to start the search from
* @return DOMNodeList
*/
public function GetNodes($sXPath, $oContextNode = null)
{
$oXPath = new DOMXPath($this);
if (is_null($oContextNode))
{
$oResult = $oXPath->query($sXPath);
}
else
{
$oResult = $oXPath->query($sXPath, $oContextNode);
}
return $oResult;
}
/**
* An alternative to getNodePath, that gives the id of nodes instead of the position within the children
*/
public static function GetItopNodePath($oNode)
{
if ($oNode instanceof DOMDocument) return '';
$sId = $oNode->getAttribute('id');
$sNodeDesc = ($sId != '') ? $oNode->nodeName.'['.$sId.']' : $oNode->nodeName;
return self::GetItopNodePath($oNode->parentNode).'/'.$sNodeDesc;
}
}
/**
* ModuleDesignElement: helper to read/change the DOM
* @package ModelFactory
*/
class ModuleDesignElement extends DOMElement
{
/**
* Extracts some nodes from the DOM
* @param string $sXPath A XPath expression
* @return DOMNodeList
*/
public function GetNodes($sXPath)
{
return $this->ownerDocument->GetNodes($sXPath, $this);
}
/**
* Create an HTML representation of the DOM, for debugging purposes
* @param bool|false $bReturnRes Echoes or returns the HTML representation
* @return mixed void or the HTML representation of the DOM
*/
public function Dump($bReturnRes = false)
{
$oDoc = new ModuleDesign();
$oClone = $oDoc->importNode($this->cloneNode(true), true);
$oDoc->appendChild($oClone);
$sXml = $oDoc->saveXML($oClone);
if ($bReturnRes)
{
return $sXml;
}
else
{
echo "<pre>\n";
echo htmlentities($sXml);
echo "</pre>\n";
}
}
/**
* Returns the node directly under the given node
* @param $sTagName
* @param bool|true $bMustExist
* @return null
* @throws DOMFormatException
*/
public function GetUniqueElement($sTagName, $bMustExist = true)
{
$oNode = null;
foreach($this->childNodes as $oChildNode)
{
if ($oChildNode->nodeName == $sTagName)
{
$oNode = $oChildNode;
break;
}
}
if ($bMustExist && is_null($oNode))
{
throw new DOMFormatException('Missing unique tag: '.$sTagName);
}
return $oNode;
}
/**
* Returns the node directly under the current node, or null if missing
* @param $sTagName
* @return null
* @throws DOMFormatException
*/
public function GetOptionalElement($sTagName)
{
return $this->GetUniqueElement($sTagName, false);
}
/**
* Returns the TEXT of the current node (possibly from several child nodes)
* @param null $sDefault
* @return null|string
*/
public function GetText($sDefault = null)
{
$sText = null;
foreach($this->childNodes as $oChildNode)
{
if ($oChildNode instanceof DOMText)
{
if (is_null($sText)) $sText = '';
$sText .= $oChildNode->wholeText;
}
}
if (is_null($sText))
{
return $sDefault;
}
else
{
return $sText;
}
}
/**
* Get the TEXT value from a child node
* @param string $sTagName
* @param string|null $sDefault
* @return string
*/
public function GetChildText($sTagName, $sDefault = null)
{
$sRet = $sDefault;
if ($oChild = $this->GetOptionalElement($sTagName))
{
$sRet = $oChild->GetText($sDefault);
}
return $sRet;
}
}

View File

@@ -1,5 +1,5 @@
<?php
// Copyright (C) 2010-2013 Combodo SARL
// Copyright (C) 2010-2016 Combodo SARL
//
// This file is part of iTop.
//
@@ -19,13 +19,14 @@
/**
* ModelFactory: in-memory manipulation of the XML MetaModel
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @copyright Copyright (C) 2010-2016 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
require_once(APPROOT.'setup/moduleinstaller.class.inc.php');
require_once(APPROOT.'setup/itopdesignformat.class.inc.php');
require_once(APPROOT.'core/designdocument.class.inc.php');
/**
* ModelFactoryModule: the representation of a Module (i.e. element that can be selected during the setup)
@@ -316,7 +317,7 @@ class ModelFactory
{
echo "Dumping target doc - looking for '$sParentId'<br/>\n";
$this->oDOMDocument->firstChild->Dump();
throw new Exception(MFDocument::GetItopNodePath($oSourceNode).' at line '.$oSourceNode->getLineNo().": could not find parent with id $sParentId");
throw new Exception(MFDocument::GetItopNodePath($oSourceNode).' at line '.$oSourceNode->getLineNo().": could not find parent with id '$sParentId'");
}
}
else
@@ -1316,7 +1317,7 @@ class DOMElement {function __construct(){throw new Exception('The dom extension
* MFElement: helper to read/change the DOM
* @package ModelFactory
*/
class MFElement extends DOMElement
class MFElement extends Combodo\iTop\DesignElement
{
/**
* Extracts some nodes from the DOM
@@ -1338,28 +1339,6 @@ class MFElement extends DOMElement
return $this->ownerDocument->GetNodeById($sXPath, $sId, $this);
}
/**
* For debugging purposes
*/
public function Dump($bReturnRes = false)
{
$oMFDoc = new MFDocument();
$oClone = $oMFDoc->importNode($this->cloneNode(true), true);
$oMFDoc->appendChild($oClone);
$sXml = $oMFDoc->saveXML($oClone);
if ($bReturnRes)
{
return $sXml;
}
else
{
echo "<pre>\n";
echo htmlentities($sXml);
echo "</pre>\n";
}
}
/**
* Returns the node directly under the given node
*/
@@ -1381,52 +1360,6 @@ class MFElement extends DOMElement
return $oNode;
}
/**
* Returns the node directly under the current node, or null if missing
*/
public function GetOptionalElement($sTagName)
{
return $this->GetUniqueElement($sTagName, false);
}
/**
* Returns the TEXT of the current node (possibly from several subnodes)
*/
public function GetText($sDefault = null)
{
$sText = null;
foreach($this->childNodes as $oChildNode)
{
if ($oChildNode instanceof DOMText)
{
if (is_null($sText)) $sText = '';
$sText .= $oChildNode->wholeText;
}
}
if (is_null($sText))
{
return $sDefault;
}
else
{
return $sText;
}
}
/**
* Get the TEXT value from the child node
*/
public function GetChildText($sTagName, $sDefault = null)
{
$sRet = $sDefault;
if ($oChild = $this->GetOptionalElement($sTagName))
{
$sRet = $oChild->GetText($sDefault);
}
return $sRet;
}
/**
* Assumes the current node to be either a text or
* <items>
@@ -1979,38 +1912,22 @@ class MFElement extends DOMElement
*/
if (!class_exists('DOMDocument'))
{
class DOMDocument {function __construct(){throw new Exception('The dom extension is not enabled');}}
class DOMDocument {function __construct(){throw new Exception('The dom extension is not enabled');}}
}
/**
* MFDocument - formating rules for XML input/output
* @package ModelFactory
*/
class MFDocument extends DOMDocument
class MFDocument extends \Combodo\iTop\DesignDocument
{
public function __construct()
/**
* Overloadable. Called prior to data loading.
*/
protected function Init()
{
parent::__construct('1.0', 'UTF-8');
parent::Init();
$this->registerNodeClass('DOMElement', 'MFElement');
$this->formatOutput = true; // indent (must be loaded with option LIBXML_NOBLANKS)
$this->preserveWhiteSpace = true; // otherwise the formatOutput option would have no effect
}
/**
* Overload of the standard API
*/
public function load($filename, $options = 0)
{
parent::load($filename, LIBXML_NOBLANKS);
}
/**
* Overload of the standard API
*/
public function loadXML($source, $options = 0)
{
parent::loadXML($source, LIBXML_NOBLANKS);
}
/**
@@ -2045,24 +1962,6 @@ class MFDocument extends DOMDocument
return $oElement;
}
/**
* For debugging purposes
*/
public function Dump($bReturnRes = false)
{
$sXml = $this->saveXML();
if ($bReturnRes)
{
return $sXml;
}
else
{
echo "<pre>\n";
echo htmlentities($sXml);
echo "</pre>\n";
}
}
/**
* Find the child node matching the given node
* A method with the same signature MUST exist in MFElement for the recursion to work fine
@@ -2113,34 +2012,6 @@ class MFDocument extends DOMDocument
return $oXPath->query($sXPath, $oContextNode);
}
}
public static function XPathQuote($sValue)
{
if (strpos($sValue, '"') !== false)
{
$aParts = explode('"', $sValue);
$sRet = 'concat("'.implode('", \'"\', "', $aParts).'")';
}
else
{
$sRet = '"'.$sValue.'"';
}
return $sRet;
}
/**
* An alternative to getNodePath, that gives the id of nodes instead of the position within the children
*/
public static function GetItopNodePath($oNode)
{
if ($oNode instanceof DOMDocument) return '';
if (is_null($oNode)) return '';
$sId = $oNode->getAttribute('id');
$sNodeDesc = ($sId != '') ? $oNode->nodeName.'['.$sId.']' : $oNode->nodeName;
return self::GetItopNodePath($oNode->parentNode).'/'.$sNodeDesc;
}
}
/**