diff --git a/application/exceptions/iTopXmlException.php b/application/exceptions/iTopXmlException.php new file mode 100644 index 0000000000..4c2fad22eb --- /dev/null +++ b/application/exceptions/iTopXmlException.php @@ -0,0 +1,10 @@ +aLog; } + /** + * @throws \iTopXmlException if root node not found + */ + public function GetITopDesignNode(): DOMNode + { + $oXPath = new DOMXPath($this->oDocument); + // Retrieve the version number + $oNodeList = $oXPath->query('/itop_design'); + if ($oNodeList->length === 0) { + throw new iTopXmlException('File format: no root tag found'); + } + + return $oNodeList->item(0); + } + + /** + * @return string + * @throws \iTopXmlException + */ + public function GetVersion() + { + $oITopDesignNode = $this->GetITopDesignNode(); + + $sVersion = $oITopDesignNode->getAttribute('version'); + if (utils::IsNullOrEmptyString($sVersion)) { + // Originally, the information was missing: default to 1.0 + $sVersion = '1.0'; + } + + return $sVersion; + } + /** * An alternative to getNodePath, that gives the id of nodes instead of the position within the children + * * @param $oNode + * * @return string */ 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; @@ -247,30 +282,24 @@ class iTopDesignFormat $this->aLog = array(); $this->bStatus = true; - $oXPath = new DOMXPath($this->oDocument); - // Retrieve the version number - $oNodeList = $oXPath->query('/itop_design'); - if ($oNodeList->length == 0) - { - // Hmm, not an iTop Data Model file... - $this->LogError('File format, no root tag found'); + try { + $sVersion = $this->GetVersion(); } - else - { - $sVersion = $oNodeList->item(0)->getAttribute('version'); - if ($sVersion == '') - { - // Originaly, the information was missing: default to 1.0 - $sVersion = '1.0'; - } - $this->LogInfo("Converting from $sVersion to $sTargetVersion"); - $this->DoConvert($sVersion, $sTargetVersion, $oFactory); - if ($this->bStatus) - { - // Update the version number - $oNodeList->item(0)->setAttribute('version', $sTargetVersion); - } + catch (iTopXmlException $e) { + $this->LogError($e->getMessage()); + + return $this->bStatus; } + + $this->LogInfo("Converting from $sVersion to $sTargetVersion"); + $this->DoConvert($sVersion, $sTargetVersion, $oFactory); + if ($this->bStatus) { + /** @noinspection PhpUnhandledExceptionInspection already called earlier so should not crash */ + $oITopDesignNode = $this->GetITopDesignNode(); + // Update the version number + $oITopDesignNode->setAttribute('version', $sTargetVersion); + } + return $this->bStatus; } @@ -318,22 +347,39 @@ class iTopDesignFormat } // Transform to the intermediate format $aCallSpec = array($this, $sTransform); - try - { + try { call_user_func($aCallSpec, $oFactory); // Recurse $this->DoConvert($sIntermediate, $sTo, $oFactory); } - catch (Exception $e) - { + catch (Exception $e) { $this->LogError($e->getMessage()); } } + /** + * @param \DOMNode|null $node + * @param bool $bFormatOutput + * @param bool $bPreserveWhiteSpace + * + * @return false|string + * + * @uses \DOMDocument::saveXML() + */ + public function GetXmlAsString($node = null, $bFormatOutput = true, $bPreserveWhiteSpace = false) + { + $this->oDocument->formatOutput = $bFormatOutput; + $this->oDocument->preserveWhiteSpace = $bPreserveWhiteSpace; + + return $this->oDocument->saveXML($node = null); + } + /** * Upgrade the format from version 1.0 to 1.1 + * * @param \ModelFactory $oFactory + * * @return void (Errors are logged) */ protected function From10To11($oFactory) diff --git a/test/setup/iTopDesignFormat/iTopDesignFormatTest.php b/test/setup/iTopDesignFormat/iTopDesignFormatTest.php index acb8783779..e6a6639490 100644 --- a/test/setup/iTopDesignFormat/iTopDesignFormatTest.php +++ b/test/setup/iTopDesignFormat/iTopDesignFormatTest.php @@ -28,47 +28,57 @@ class iTopDesignFormatTest extends ItopTestCase * @covers iTopDesignFormat::Convert * @dataProvider ConvertProvider * - * @param string $sTargetVersion * @param string $sXmlFileName Corresponding files should exist in the `Convert-samples` dir with the `.expected` and `.input` suffixes * Example "1.7_to_1.6" for `Convert-samples/1.7_to_1.6.expected.xml` and `Convert-samples/1.7_to_1.6.input.xml` * * @throws \Exception */ - public function testConvert($sTargetVersion, $sXmlFileName, $iExpectedErrors = 0, $sFirstErrorMessage = '') + public function testConvert($sXmlFileName, $iExpectedErrors = 0, $sFirstErrorMessage = '') { $sSamplesRelDirPath = 'Convert-samples/'; - $sInputXml = $this->GetFileContent($sSamplesRelDirPath.$sXmlFileName.'.input'); - $oInputDocument = new DOMDocument(); - libxml_clear_errors(); - $oInputDocument->preserveWhiteSpace = false; - $oInputDocument->loadXML($sInputXml); - $oInputDocument->formatOutput = true; - $oDesignFormat = new iTopDesignFormat($oInputDocument); - $bResult = $oDesignFormat->Convert($sTargetVersion); - $aErrors = $oDesignFormat->GetErrors(); + $sExpectedXml = $this->GetFileContent($sSamplesRelDirPath.$sXmlFileName.'.expected'); + $oExpectedDesignFormat = static::GetItopFormatFromString($sExpectedXml); + $sTargetVersion = $oExpectedDesignFormat->GetVersion(); + + $sInputXml = $this->GetFileContent($sSamplesRelDirPath.$sXmlFileName.'.input'); + $oInputDesignFormat = static::GetItopFormatFromString($sInputXml); + $bResult = $oInputDesignFormat->Convert($sTargetVersion); + $aErrors = $oInputDesignFormat->GetErrors(); $this->assertCount($iExpectedErrors, $aErrors); if ($iExpectedErrors > 0) { $this->assertFalse($bResult); $this->assertEquals($sFirstErrorMessage, $aErrors[0]); } - $sConvertedXml = $oInputDocument->saveXML(); + /** @noinspection PhpRedundantOptionalArgumentInspection We REALLY want those options so specifying it anyway */ + $sConvertedXml = $oInputDesignFormat->GetXmlAsString(null, true, false); // Erase dynamic values $sConvertedXml = preg_replace('@GetFileContent($sSamplesRelDirPath.$sXmlFileName.'.expected'); $this->assertEquals($sExpectedXml, $sConvertedXml); } + private static function GetItopFormatFromString(string $sFileContent): iTopDesignFormat + { + $oInputDocument = new DOMDocument(); + /** @noinspection PhpComposerExtensionStubsInspection */ + libxml_clear_errors(); + $oInputDocument->formatOutput = true; + $oInputDocument->preserveWhiteSpace = false; + $oInputDocument->loadXML($sFileContent); + + return new iTopDesignFormat($oInputDocument); + } + public function ConvertProvider() { return [ - '1.6 to 1.7 2' => ['sTargetVersion' => '1.7', 'sXmlFileName' => '1.6_to_1.7_2'], - '1.7 to 1.6' => ['sTargetVersion' => '1.6', 'sXmlFileName' => '1.7_to_1.6'], - '1.7 to 1.6 2' => ['sTargetVersion' => '1.6', 'sXmlFileName' => '1.7_to_1.6_2'], - '1.7 to 3.0' => ['sTargetVersion' => '3.0', 'sXmlFileName' => '1.7_to_3.0'], - '3.0 to 1.7' => ['sTargetVersion' => '1.7', 'sXmlFileName' => '3.0_to_1.7'], - 'Bug_4569' => ['sTargetVersion' => '1.7', 'sXmlFileName' => 'Bug_4569'], + '1.6 to 1.7 2' => ['sXmlFileName' => '1.6_to_1.7_2'], + '1.7 to 1.6' => ['sXmlFileName' => '1.7_to_1.6'], + '1.7 to 1.6 2' => ['sXmlFileName' => '1.7_to_1.6_2'], + '1.7 to 3.0' => ['sXmlFileName' => '1.7_to_3.0'], + '3.0 to 1.7' => ['sXmlFileName' => '3.0_to_1.7'], + 'Bug_4569' => ['sXmlFileName' => 'Bug_4569'], ]; }