From 154fb5c737fb63c626dbb973a07b86593557763a Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Wed, 7 Jan 2026 14:29:08 +0100 Subject: [PATCH] =?UTF-8?q?N=C2=B09066=20-=20Serialization/Unserialization?= =?UTF-8?q?=20from=20XML=20to=20Forms?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/datamodel.application.xml | 28 +----- lib/composer/autoload_classmap.php | 7 ++ lib/composer/autoload_static.php | 7 ++ .../Compiler/PropertyTypeCompiler.php | 26 +---- sources/PropertyType/PropertyType.php | 23 +++-- .../PropertyType/PropertyTypeException.php | 10 ++ .../Serializer/SerializerException.php | 14 +++ .../XMLFormat/AbstractXMLFormat.php | 22 +++++ .../Serializer/XMLFormat/XMLFormatCSV.php | 27 ++++++ .../Serializer/XMLFormat/XMLFormatFactory.php | 56 +++++++++++ .../XMLFormat/XMLFormatFlatArray.php | 79 +++++++++++++++ .../XMLFormat/XMLFormatValueAsId.php | 48 ++++++++++ .../PropertyType/Serializer/XMLSerializer.php | 57 +++++++++++ .../ValueType/AbstractValueType.php | 21 ++++ .../ValueType/Branch/ValueTypeCollection.php | 17 ++++ .../Branch/ValueTypePropertyTree.php | 40 +++++--- .../ValueType/Leaf/ValueTypeClass.php | 2 +- .../Leaf/ValueTypeCollectionOfValues.php | 16 ++++ .../ValueType/ValueTypeFactory.php | 4 +- sources/alias.php | 4 + .../src/BaseTestCase/ItopTestCase.php | 31 ++++++ .../unitary-tests/setup/ModelFactoryTest.php | 30 ------ .../Compiler/FormsCompilerTest.php | 96 +++++++++++++++++-- 23 files changed, 553 insertions(+), 112 deletions(-) create mode 100644 sources/PropertyType/Serializer/SerializerException.php create mode 100644 sources/PropertyType/Serializer/XMLFormat/AbstractXMLFormat.php create mode 100644 sources/PropertyType/Serializer/XMLFormat/XMLFormatCSV.php create mode 100644 sources/PropertyType/Serializer/XMLFormat/XMLFormatFactory.php create mode 100644 sources/PropertyType/Serializer/XMLFormat/XMLFormatFlatArray.php create mode 100644 sources/PropertyType/Serializer/XMLFormat/XMLFormatValueAsId.php create mode 100644 sources/PropertyType/Serializer/XMLSerializer.php diff --git a/application/datamodel.application.xml b/application/datamodel.application.xml index a294dbe2a..eb18e600f 100644 --- a/application/datamodel.application.xml +++ b/application/datamodel.application.xml @@ -863,22 +863,13 @@ Call $this->AddInitialAttributeFlags($sAttCode, $iFlags) for all the initial att - - - - - - {{query.selected_class}} - - bars - @@ -893,17 +884,11 @@ Call $this->AddInitialAttributeFlags($sAttCode, $iFlags) for all the initial att - - - {{query.selected_class}} {{aggregation_function.value != 'count'}} - - - {{query.selected_class}} numeric @@ -924,9 +909,6 @@ Call $this->AddInitialAttributeFlags($sAttCode, $iFlags) for all the initial att - - - @@ -966,23 +948,15 @@ Call $this->AddInitialAttributeFlags($sAttCode, $iFlags) for all the initial att - - - - - - {{query.selected_class}} enum - - - + {{query.selected_class}} {{group_by.attribute}} diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index 95caade5c..6bc2e551d 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -556,6 +556,13 @@ return array( 'Combodo\\iTop\\PropertyType\\PropertyTypeException' => $baseDir . '/sources/PropertyType/PropertyTypeException.php', 'Combodo\\iTop\\PropertyType\\PropertyTypeFactory' => $baseDir . '/sources/PropertyType/PropertyTypeFactory.php', 'Combodo\\iTop\\PropertyType\\PropertyTypeService' => $baseDir . '/sources/PropertyType/PropertyTypeService.php', + 'Combodo\\iTop\\PropertyType\\Serializer\\SerializerException' => $baseDir . '/sources/PropertyType/Serializer/SerializerException.php', + 'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\AbstractXMLFormat' => $baseDir . '/sources/PropertyType/Serializer/XMLFormat/AbstractXMLFormat.php', + 'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatCSV' => $baseDir . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatCSV.php', + 'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatFactory' => $baseDir . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatFactory.php', + 'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatFlatArray' => $baseDir . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatFlatArray.php', + 'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatValueAsId' => $baseDir . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatValueAsId.php', + 'Combodo\\iTop\\PropertyType\\Serializer\\XMLSerializer' => $baseDir . '/sources/PropertyType/Serializer/XMLSerializer.php', 'Combodo\\iTop\\PropertyType\\ValueType\\AbstractValueType' => $baseDir . '/sources/PropertyType/ValueType/AbstractValueType.php', 'Combodo\\iTop\\PropertyType\\ValueType\\Branch\\AbstractBranchValueType' => $baseDir . '/sources/PropertyType/ValueType/Branch/AbstractBranchValueType.php', 'Combodo\\iTop\\PropertyType\\ValueType\\Branch\\ValueTypeCollection' => $baseDir . '/sources/PropertyType/ValueType/Branch/ValueTypeCollection.php', diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 63fcc0a7f..7d7e438e8 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -942,6 +942,13 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f 'Combodo\\iTop\\PropertyType\\PropertyTypeException' => __DIR__ . '/../..' . '/sources/PropertyType/PropertyTypeException.php', 'Combodo\\iTop\\PropertyType\\PropertyTypeFactory' => __DIR__ . '/../..' . '/sources/PropertyType/PropertyTypeFactory.php', 'Combodo\\iTop\\PropertyType\\PropertyTypeService' => __DIR__ . '/../..' . '/sources/PropertyType/PropertyTypeService.php', + 'Combodo\\iTop\\PropertyType\\Serializer\\SerializerException' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/SerializerException.php', + 'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\AbstractXMLFormat' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/XMLFormat/AbstractXMLFormat.php', + 'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatCSV' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatCSV.php', + 'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatFactory' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatFactory.php', + 'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatFlatArray' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatFlatArray.php', + 'Combodo\\iTop\\PropertyType\\Serializer\\XMLFormat\\XMLFormatValueAsId' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/XMLFormat/XMLFormatValueAsId.php', + 'Combodo\\iTop\\PropertyType\\Serializer\\XMLSerializer' => __DIR__ . '/../..' . '/sources/PropertyType/Serializer/XMLSerializer.php', 'Combodo\\iTop\\PropertyType\\ValueType\\AbstractValueType' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/AbstractValueType.php', 'Combodo\\iTop\\PropertyType\\ValueType\\Branch\\AbstractBranchValueType' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/Branch/AbstractBranchValueType.php', 'Combodo\\iTop\\PropertyType\\ValueType\\Branch\\ValueTypeCollection' => __DIR__ . '/../..' . '/sources/PropertyType/ValueType/Branch/ValueTypeCollection.php', diff --git a/sources/PropertyType/Compiler/PropertyTypeCompiler.php b/sources/PropertyType/Compiler/PropertyTypeCompiler.php index fe97afb16..d7a1e7672 100644 --- a/sources/PropertyType/Compiler/PropertyTypeCompiler.php +++ b/sources/PropertyType/Compiler/PropertyTypeCompiler.php @@ -43,7 +43,7 @@ class PropertyTypeCompiler * @throws \Combodo\iTop\PropertyType\PropertyTypeException * @throws \DOMFormatException */ - protected function CompilePropertyTypeFromXML(string $sXMLContent): PropertyType + public function CompilePropertyTypeFromXML(string $sXMLContent): PropertyType { $oDoc = new DesignDocument(); libxml_clear_errors(); @@ -66,7 +66,7 @@ class PropertyTypeCompiler * @return string * @throws \Combodo\iTop\PropertyType\Compiler\PropertyTypeCompilerException */ - protected function GetXMLContent(string $sId, string $sType): string + public function GetXMLContent(string $sId, string $sType): string { $sPath = utils::GetAbsoluteModulePath('core')."property_types/$sType/$sId.xml"; if (!file_exists($sPath)) { @@ -93,21 +93,6 @@ class PropertyTypeCompiler return $oPropertyType->ToPHPFormBlock(); } - /** - * @param string $sXMLContent - * - * @return string - * @throws \Combodo\iTop\PropertyType\Compiler\PropertyTypeCompilerException - * @throws \Combodo\iTop\PropertyType\PropertyTypeException - * @throws \DOMFormatException - */ - public function CompileEntityFromXML(string $sXMLContent): string - { - $oPropertyType = $this->CompilePropertyTypeFromXML($sXMLContent); - - return $oPropertyType->ToPHPEntity(); - } - /** * @param string $sId * @param string $sType @@ -123,11 +108,4 @@ class PropertyTypeCompiler return $this->CompileFormFromXML($sXMLContent); } - - public function CompileEntity(string $sId, string $sType): string - { - $sXMLContent = $this->GetXMLContent($sId, $sType); - - return $this->CompileEntityFromXML($sXMLContent); - } } diff --git a/sources/PropertyType/PropertyType.php b/sources/PropertyType/PropertyType.php index e01611c42..4f27fea99 100644 --- a/sources/PropertyType/PropertyType.php +++ b/sources/PropertyType/PropertyType.php @@ -37,7 +37,7 @@ class PropertyType $sDefinitionNodeType = $oDefinitionNode->getAttribute('xsi:type'); if (!is_a($sDefinitionNodeType, AbstractValueType::class, true)) { - throw new PropertyTypeException('Unsupported type '.json_encode($sDefinitionNodeType).' in '); + throw new PropertyTypeException('Unsupported xsi:type '.json_encode($sDefinitionNodeType), $oDomNode); } $this->oValueType = new $sDefinitionNodeType(); @@ -50,18 +50,11 @@ class PropertyType $aPHPFragments = []; if ($this->oValueType->IsLeaf()) { + $sFormBlockClass = $this->oValueType->GetFormBlockClass(); $sLocalPHP = <<sId extends Combodo\iTop\Forms\Block\Base\FormBlock +class FormFor__$this->sId extends $sFormBlockClass { - protected function BuildForm(): void - { -PHP; - - $sLocalPHP .= "\n".$this->oValueType->ToPHPFormBlock($aPHPFragments); - - $sLocalPHP .= <<sParentType; } + + public function SerializeToDOMNode(mixed $value, DesignElement$oDOMNode): void + { + $this->oValueType->SerializeToDOMNode($value, $oDOMNode); + } + + public function UnserializeFromDOMNode(DesignElement $oDOMNode): mixed + { + return $this->oValueType->UnserializeFromDOMNode($oDOMNode); + } } diff --git a/sources/PropertyType/PropertyTypeException.php b/sources/PropertyType/PropertyTypeException.php index 2f433c0ac..620e187c2 100644 --- a/sources/PropertyType/PropertyTypeException.php +++ b/sources/PropertyType/PropertyTypeException.php @@ -7,11 +7,21 @@ namespace Combodo\iTop\PropertyType; +use Combodo\iTop\DesignDocument; +use Combodo\iTop\DesignElement; use Exception; +use Throwable; /** * @since 3.3.0 */ class PropertyTypeException extends Exception { + public function __construct(string $message = "", ?DesignElement $oNode = null, ?Throwable $previous = null) + { + if (!is_null($oNode)) { + $message = DesignDocument::GetItopNodePath($oNode).': '.$message; + } + parent::__construct($message, 0, $previous); + } } diff --git a/sources/PropertyType/Serializer/SerializerException.php b/sources/PropertyType/Serializer/SerializerException.php new file mode 100644 index 000000000..f9ca12fc8 --- /dev/null +++ b/sources/PropertyType/Serializer/SerializerException.php @@ -0,0 +1,14 @@ +ownerDocument->createTextNode($sXmlValue); + $oDOMNode->appendChild($oTextNode); + } + + public function UnserializeFromDOMNode(DesignElement $oDOMNode, AbstractValueType $oValueType): mixed + { + $sValue = $oDOMNode->GetText(''); + return explode(',', $sValue); + } +} diff --git a/sources/PropertyType/Serializer/XMLFormat/XMLFormatFactory.php b/sources/PropertyType/Serializer/XMLFormat/XMLFormatFactory.php new file mode 100644 index 000000000..79e787b03 --- /dev/null +++ b/sources/PropertyType/Serializer/XMLFormat/XMLFormatFactory.php @@ -0,0 +1,56 @@ +getAttribute('xsi:type'); + + if (utils::IsNullOrEmptyString($sNodeType)) { + throw new SerializerException("Missing xsi:type in node specification", $oDomNode); + } + + if (is_a($sNodeType, AbstractXMLFormat::class, true)) { + $oNode = new $sNodeType(); + $oNode->InitFromDomNode($oDomNode); + + return $oNode; + } + + throw new SerializerException("Unknown type node class: ".json_encode($sNodeType), $oDomNode); + } + +} diff --git a/sources/PropertyType/Serializer/XMLFormat/XMLFormatFlatArray.php b/sources/PropertyType/Serializer/XMLFormat/XMLFormatFlatArray.php new file mode 100644 index 000000000..cdc2d2b1f --- /dev/null +++ b/sources/PropertyType/Serializer/XMLFormat/XMLFormatFlatArray.php @@ -0,0 +1,79 @@ +GetChildText('tag-format'); + if (is_null($sTagFormat)) { + throw new SerializerException('Missing element', $oDomNode); + } + $this->sTagFormat = $sTagFormat; + + $sCountTag = $oDomNode->GetChildText('count-tag'); + if (is_null($sCountTag)) { + throw new SerializerException('Missing element', $oDomNode); + } + $this->sCountTag = $sCountTag; + } + + public function SerializeToDOMNode($value, $oDOMNode, AbstractValueType $oValueType): void + { + if (!$oValueType instanceof ValueTypeCollection) { + throw new SerializerException('XMLFormatFlatArray is allowed only in ValueTypeCollection nodes'); + } + + $oCountNode = $oDOMNode->ownerDocument->createElement($this->sCountTag, count($value)); + $oDOMNode->appendChild($oCountNode); + foreach ($value as $iRank => $aValues) { + foreach ($oValueType->GetChildren() as $oChild) { + $sId = $oChild->GetId(); + if (isset($aValues[$sId])) { + $sTagName = \MetaModel::ApplyParams($this->sTagFormat, ['rank' => $iRank, 'id' => $sId]); + $oChildNode = $oDOMNode->ownerDocument->createElement($sTagName); + $oDOMNode->appendChild($oChildNode); + $oChild->SerializeToDOMNode($aValues[$sId], $oChildNode); + } + } + } + } + + public function UnserializeFromDOMNode(DesignElement $oDOMNode, AbstractValueType $oValueType): mixed + { + $aResults = []; + + if (!$oValueType instanceof ValueTypeCollection) { + throw new SerializerException('XMLFormatFlatArray is allowed only in ValueTypeCollection nodes'); + } + + $iCount = $oDOMNode->GetUniqueElement($this->sCountTag)->GetText(0); + for ($iRank = 0; $iRank < $iCount; $iRank++) { + foreach ($oValueType->GetChildren() as $oChild) { + $sId = $oChild->GetId(); + $sTagName = \MetaModel::ApplyParams($this->sTagFormat, ['rank' => $iRank, 'id' => $sId]); + $oChildNode = $oDOMNode->GetOptionalElement($sTagName); + if ($oChildNode) { + $aResults[$iRank][$sId] = $oChildNode->GetText(''); + } + } + } + + return $aResults; + } +} diff --git a/sources/PropertyType/Serializer/XMLFormat/XMLFormatValueAsId.php b/sources/PropertyType/Serializer/XMLFormat/XMLFormatValueAsId.php new file mode 100644 index 000000000..59e275a32 --- /dev/null +++ b/sources/PropertyType/Serializer/XMLFormat/XMLFormatValueAsId.php @@ -0,0 +1,48 @@ +GetChildText('tag-name'); + if (is_null($sTagName)) { + throw new SerializerException("Missing element", $oDomNode); + } + $this->sTagName = $sTagName; + } + + public function SerializeToDOMNode($value, $oDOMNode, AbstractValueType $oValueType): void + { + foreach ($value as $item) { + $oChildNode = $oDOMNode->ownerDocument->createElement($this->sTagName); + $oChildNode->setAttribute('id', "$item"); + $oDOMNode->appendChild($oChildNode); + } + } + + public function UnserializeFromDOMNode(DesignElement $oDOMNode, AbstractValueType $oValueType): mixed + { + $aResult = []; + + foreach ($oDOMNode->getElementsByTagName($this->sTagName) as $oNode) { + $sValue = $oNode->getAttribute('id'); + $aResult[] = $sValue; + } + + return $aResult; + } +} diff --git a/sources/PropertyType/Serializer/XMLSerializer.php b/sources/PropertyType/Serializer/XMLSerializer.php new file mode 100644 index 000000000..1d3187998 --- /dev/null +++ b/sources/PropertyType/Serializer/XMLSerializer.php @@ -0,0 +1,57 @@ +GetXMLContent($sId, $sType); + + $this->SerializeForPropertyType($value, $oParentNode, $sPropertyTypeXML); + } + + public function Unserialize(DesignElement $oDOMNode, string $sId, string $sType): mixed + { + return null; + } + + public function SerializeForPropertyType(mixed $value, DesignElement $oParentNode, string $sPropertyTypeXML): void + { + $oPropertyType = PropertyTypeCompiler::GetInstance()->CompilePropertyTypeFromXML($sPropertyTypeXML); + + $oPropertyType->SerializeToDOMNode($value, $oParentNode); + } + + public function UnserializeForPropertyType(DesignElement $oParentNode, string $sPropertyTypeXML): mixed + { + $oPropertyType = PropertyTypeCompiler::GetInstance()->CompilePropertyTypeFromXML($sPropertyTypeXML); + + return $oPropertyType->UnserializeFromDOMNode($oParentNode); + } +} diff --git a/sources/PropertyType/ValueType/AbstractValueType.php b/sources/PropertyType/ValueType/AbstractValueType.php index effdd22c2..3786e8d0a 100644 --- a/sources/PropertyType/ValueType/AbstractValueType.php +++ b/sources/PropertyType/ValueType/AbstractValueType.php @@ -219,6 +219,11 @@ PHP; } } + public function GetId(): string + { + return $this->sId; + } + protected function GetSibling(string $sId): ?AbstractValueType { if (is_null($this->oParent)) { @@ -228,4 +233,20 @@ PHP; return $this->oParent->GetChild($sId); } + public function SerializeToDOMNode(mixed $value, DesignElement $oDOMNode): void + { + $sXmlValue = $value; + $oTextNode = $oDOMNode->ownerDocument->createTextNode($sXmlValue); + $oDOMNode->appendChild($oTextNode); + } + + /** + * @param $oDOMNode + * + * @return mixed + */ + public function UnserializeFromDOMNode(DesignElement $oDOMNode): mixed + { + return $oDOMNode->GetText(); + } } diff --git a/sources/PropertyType/ValueType/Branch/ValueTypeCollection.php b/sources/PropertyType/ValueType/Branch/ValueTypeCollection.php index e1d735d3b..cb1fd8a69 100644 --- a/sources/PropertyType/ValueType/Branch/ValueTypeCollection.php +++ b/sources/PropertyType/ValueType/Branch/ValueTypeCollection.php @@ -9,6 +9,8 @@ namespace Combodo\iTop\PropertyType\ValueType\Branch; use Combodo\iTop\DesignElement; use Combodo\iTop\Forms\Block\Base\CollectionBlock; +use Combodo\iTop\PropertyType\Serializer\XMLFormat\AbstractXMLFormat; +use Combodo\iTop\PropertyType\Serializer\XMLFormat\XMLFormatFactory; use Combodo\iTop\PropertyType\ValueType\AbstractValueType; use Combodo\iTop\PropertyType\ValueType\ValueTypeFactory; use utils; @@ -18,6 +20,8 @@ use utils; */ class ValueTypeCollection extends ValueTypePropertyTree { + private AbstractXMLFormat $oXMLFormat; + /** * @param \Combodo\iTop\DesignElement $oDomNode * @param \Combodo\iTop\PropertyType\ValueType\Branch\AbstractBranchValueType|null $oParent @@ -33,6 +37,9 @@ class ValueTypeCollection extends ValueTypePropertyTree $this->sSubTreeClass = 'SubFormFor__'.$this->sIdWithPath; $this->aFormBlockOptionsForPHP['block_entry_type'] = utils::QuoteForPHP($this->sSubTreeClass); + $oNode = $oDomNode->GetUniqueElement('xml-format'); + $this->oXMLFormat = XMLFormatFactory::GetInstance()->CreateXMLFormatFromDomNode($oNode); + // read child properties foreach ($oDomNode->GetUniqueElement('prototype')->childNodes as $oNode) { if ($oNode instanceof DesignElement) { @@ -74,4 +81,14 @@ class ValueTypeCollection extends ValueTypePropertyTree return $this->GetLocalPHPForValueType(); } + + public function SerializeToDOMNode(mixed $value, DesignElement $oDOMNode): void + { + $this->oXMLFormat->SerializeToDOMNode($value, $oDOMNode, $this); + } + + public function UnserializeFromDOMNode(DesignElement $oDOMNode): mixed + { + return $this->oXMLFormat->UnserializeFromDOMNode($oDOMNode, $this); + } } diff --git a/sources/PropertyType/ValueType/Branch/ValueTypePropertyTree.php b/sources/PropertyType/ValueType/Branch/ValueTypePropertyTree.php index 599926b8f..9d39442a3 100644 --- a/sources/PropertyType/ValueType/Branch/ValueTypePropertyTree.php +++ b/sources/PropertyType/ValueType/Branch/ValueTypePropertyTree.php @@ -9,19 +9,7 @@ namespace Combodo\iTop\PropertyType\ValueType\Branch; use Combodo\iTop\DesignElement; use Combodo\iTop\Forms\Block\Base\FormBlock; -use Combodo\iTop\Forms\Block\Expression\BooleanExpressionFormBlock; -use Combodo\iTop\Forms\Block\Expression\NumberExpressionFormBlock; -use Combodo\iTop\Forms\Block\Expression\StringExpressionFormBlock; -use Combodo\iTop\Forms\IO\Format\BooleanIOFormat; -use Combodo\iTop\Forms\IO\Format\ClassIOFormat; -use Combodo\iTop\Forms\IO\Format\NumberIOFormat; -use Combodo\iTop\Forms\IO\Format\StringIOFormat; -use Combodo\iTop\PropertyType\PropertyTypeException; -use Combodo\iTop\PropertyType\ValueType\AbstractValueType; use Combodo\iTop\PropertyType\ValueType\ValueTypeFactory; -use Exception; -use Expression; -use utils; /** * @since 3.3.0 @@ -86,4 +74,32 @@ PHP; return $this->GetLocalPHPForValueType($this->sSubTreeClass); } + + public function SerializeToDOMNode(mixed $value, DesignElement $oDOMNode): void + { + foreach ($this->aChildren as $oChild) { + $sId = $oChild->sId; + if (isset($value[$sId])) { + /** @var DesignElement $oChildNode */ + $oChildNode = $oDOMNode->ownerDocument->createElement($sId); + $oDOMNode->appendChild($oChildNode); + $oChild->SerializeToDOMNode($value[$sId], $oChildNode); + } + } + } + + public function UnserializeFromDOMNode(DesignElement $oDOMNode): mixed + { + $aResults = []; + + foreach ($this->aChildren as $oChild) { + $sId = $oChild->sId; + $oChildNode = $oDOMNode->GetOptionalElement($sId); + if ($oChildNode) { + $aResults[$sId] = $oChild->UnserializeFromDOMNode($oChildNode); + } + } + + return $aResults; + } } diff --git a/sources/PropertyType/ValueType/Leaf/ValueTypeClass.php b/sources/PropertyType/ValueType/Leaf/ValueTypeClass.php index 56568ef46..7f747ea6e 100644 --- a/sources/PropertyType/ValueType/Leaf/ValueTypeClass.php +++ b/sources/PropertyType/ValueType/Leaf/ValueTypeClass.php @@ -30,7 +30,7 @@ class ValueTypeClass extends AbstractLeafValueType { parent::InitFromDomNode($oDomNode, $oParent); - $sCategories = $oDomNode->GetChildText('categories-csv'); + $sCategories = $oDomNode->GetChildText('categories-csv', ''); /** @var \ModelReflection $oModelReflection */ $oModelReflection = ServiceLocator::GetInstance()->get('ModelReflection'); diff --git a/sources/PropertyType/ValueType/Leaf/ValueTypeCollectionOfValues.php b/sources/PropertyType/ValueType/Leaf/ValueTypeCollectionOfValues.php index 693a80bd8..eb3b5ee41 100644 --- a/sources/PropertyType/ValueType/Leaf/ValueTypeCollectionOfValues.php +++ b/sources/PropertyType/ValueType/Leaf/ValueTypeCollectionOfValues.php @@ -10,6 +10,8 @@ namespace Combodo\iTop\PropertyType\ValueType\Leaf; use Combodo\iTop\DesignElement; use Combodo\iTop\Forms\Block\Base\ChoiceFormBlock; use Combodo\iTop\Forms\Block\Base\FormBlock; +use Combodo\iTop\PropertyType\Serializer\XMLFormat\AbstractXMLFormat; +use Combodo\iTop\PropertyType\Serializer\XMLFormat\XMLFormatFactory; use Combodo\iTop\PropertyType\ValueType\Branch\AbstractBranchValueType; use Combodo\iTop\PropertyType\ValueType\Leaf\AbstractLeafValueType; use Combodo\iTop\PropertyType\ValueType\ValueTypeFactory; @@ -17,6 +19,7 @@ use Combodo\iTop\PropertyType\ValueType\ValueTypeFactory; class ValueTypeCollectionOfValues extends AbstractLeafValueType { private string $sFormBlockClass; + private AbstractXMLFormat $oXMLFormat; public function GetFormBlockClass(): string { @@ -33,6 +36,19 @@ class ValueTypeCollectionOfValues extends AbstractLeafValueType $this->aFormBlockOptionsForPHP['multiple'] = 'true'; } + $oNode = $oDomNode->GetUniqueElement('xml-format'); + $this->oXMLFormat = XMLFormatFactory::GetInstance()->CreateXMLFormatFromDomNode($oNode); + parent::InitFromDomNode($oDomNode, $oParent); } + + public function SerializeToDOMNode(mixed $value, DesignElement $oDOMNode): void + { + $this->oXMLFormat->SerializeToDOMNode($value, $oDOMNode, $this); + } + + public function UnserializeFromDOMNode(DesignElement $oDOMNode): mixed + { + return $this->oXMLFormat->UnserializeFromDOMNode($oDOMNode, $this); + } } diff --git a/sources/PropertyType/ValueType/ValueTypeFactory.php b/sources/PropertyType/ValueType/ValueTypeFactory.php index c0ffaa000..3d158f190 100644 --- a/sources/PropertyType/ValueType/ValueTypeFactory.php +++ b/sources/PropertyType/ValueType/ValueTypeFactory.php @@ -46,7 +46,7 @@ class ValueTypeFactory if (utils::IsNullOrEmptyString($sNodeType)) { $sId = $oDomNode->getAttribute('id'); - throw new PropertyTypeException("Node: $sId, missing value-type in node specification"); + throw new PropertyTypeException("Missing value-type in node specification", $oDomNode); } if (is_a($sNodeType, AbstractValueType::class, true)) { @@ -57,6 +57,6 @@ class ValueTypeFactory } $sId = $oDomNode->getAttribute('id'); - throw new PropertyTypeException("Node: $sId, unknown type node class: ".json_encode($sNodeType)); + throw new PropertyTypeException("Unknown value-type node class: ".json_encode($sNodeType), $oDomNode); } } diff --git a/sources/alias.php b/sources/alias.php index b3183cfe4..56dc31c5b 100644 --- a/sources/alias.php +++ b/sources/alias.php @@ -117,3 +117,7 @@ class_alias(\Combodo\iTop\PropertyType\ValueType\Leaf\ValueTypeLabel::class, 'Co class_alias(\Combodo\iTop\PropertyType\ValueType\Leaf\ValueTypeOQL::class, 'Combodo-ValueType-OQL'); class_alias(\Combodo\iTop\PropertyType\ValueType\Leaf\ValueTypeString::class, 'Combodo-ValueType-String'); class_alias(\Combodo\iTop\PropertyType\ValueType\Leaf\ValueTypeText::class, 'Combodo-ValueType-Text'); + +class_alias(\Combodo\iTop\PropertyType\Serializer\XMLFormat\XMLFormatCSV::class, 'Combodo-XMLFormat-CSV'); +class_alias(\Combodo\iTop\PropertyType\Serializer\XMLFormat\XMLFormatValueAsId::class, 'Combodo-XMLFormat-ValueAsId'); +class_alias(\Combodo\iTop\PropertyType\Serializer\XMLFormat\XMLFormatFlatArray::class, 'Combodo-XMLFormat-FlatArray'); diff --git a/tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php b/tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php index ca3df00f4..7ef63081d 100644 --- a/tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php +++ b/tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php @@ -9,6 +9,7 @@ namespace Combodo\iTop\Test\UnitTest; use CMDBSource; use DeprecatedCallsLog; +use DOMDocument; use MySQLTransactionNotClosedException; use ParseError; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; @@ -717,4 +718,34 @@ abstract class ItopTestCase extends KernelTestCase return $this->CallUrl($sUrl, $aPostFields, $aCurlOptions, $bXDebugEnabled); } + + /** + * @param $sXML + * + * @return false|string + */ + protected function CanonicalizeXML($sXML) + { + // Canonicalize the expected XML (to cope with indentation) + $oExpectedDocument = new DOMDocument(); + $oExpectedDocument->preserveWhiteSpace = false; + $oExpectedDocument->formatOutput = true; + $oExpectedDocument->loadXML($sXML); + + $sSavedXML = $oExpectedDocument->SaveXML(); + + return str_replace(' encoding="UTF-8"', '', $sSavedXML); + } + + /** + * @param $sExpected + * @param $sActual + * @param string $sMessage + */ + protected function AssertEqualiTopXML($sExpected, $sActual, string $sMessage = '') + { + // Note: assertEquals reports the differences in a diff which is easier to interpret (in PHPStorm) + // as compared to the report given by assertEqualXMLStructure + static::assertEquals($this->CanonicalizeXML($sExpected), $this->CanonicalizeXML($sActual), $sMessage); + } } diff --git a/tests/php-unit-tests/unitary-tests/setup/ModelFactoryTest.php b/tests/php-unit-tests/unitary-tests/setup/ModelFactoryTest.php index bce50f46f..16e1aec59 100644 --- a/tests/php-unit-tests/unitary-tests/setup/ModelFactoryTest.php +++ b/tests/php-unit-tests/unitary-tests/setup/ModelFactoryTest.php @@ -64,36 +64,6 @@ class ModelFactoryTest extends ItopTestCase return $oFactory; } - /** - * @param $sXML - * - * @return false|string - */ - protected function CanonicalizeXML($sXML) - { - // Canonicalize the expected XML (to cope with indentation) - $oExpectedDocument = new DOMDocument(); - $oExpectedDocument->preserveWhiteSpace = false; - $oExpectedDocument->formatOutput = true; - $oExpectedDocument->loadXML($sXML); - - $sSavedXML = $oExpectedDocument->SaveXML(); - - return str_replace(' encoding="UTF-8"', '', $sSavedXML); - } - - /** - * @param $sExpected - * @param $sActual - * @param string $sMessage - */ - protected function AssertEqualiTopXML($sExpected, $sActual, string $sMessage = '') - { - // Note: assertEquals reports the differences in a diff which is easier to interpret (in PHPStorm) - // as compared to the report given by assertEqualXMLStructure - static::assertEquals($this->CanonicalizeXML($sExpected), $this->CanonicalizeXML($sActual), $sMessage); - } - /** * Assertion ignoring some of the unexpected decoration brought by DOM Elements. */ diff --git a/tests/php-unit-tests/unitary-tests/sources/PropertyType/Compiler/FormsCompilerTest.php b/tests/php-unit-tests/unitary-tests/sources/PropertyType/Compiler/FormsCompilerTest.php index e6542d163..52965df43 100644 --- a/tests/php-unit-tests/unitary-tests/sources/PropertyType/Compiler/FormsCompilerTest.php +++ b/tests/php-unit-tests/unitary-tests/sources/PropertyType/Compiler/FormsCompilerTest.php @@ -136,7 +136,7 @@ PHP, - test + test @@ -244,6 +244,10 @@ PHP, + + item_count + item_\$rank\$_\$id\$ + @@ -580,6 +584,7 @@ PHP, + Contact status @@ -600,6 +605,22 @@ class FormFor__CollectionOfValuesTest extends Combodo\iTop\Forms\Block\Base\Form ]); } } +PHP, + ], + 'Single value' => [ + 'sXMLContent' => << + + Dashlet + + + + +XML, + 'sExpectedPHP' => << [ @@ -731,7 +752,7 @@ XML, 'Missing value-type in node specification' => [ 'sXMLContent' => << - + Dashlet @@ -743,13 +764,12 @@ XML, XML, 'sExpectedClass' => 'Combodo\iTop\PropertyType\PropertyTypeException', - 'sExpectedMessage' => 'Node: source_property, missing value-type in node specification', + 'sExpectedMessage' => '/property_type[WrongDefinition]/definition/nodes/node[source_property]: Missing value-type in node specification', ], - 'Wrong class for value-type in node specification' => [ 'sXMLContent' => << - + Dashlet @@ -761,7 +781,71 @@ XML, XML, 'sExpectedClass' => 'Combodo\iTop\PropertyType\PropertyTypeException', - 'sExpectedMessage' => 'Node: source_property, unknown type node class: "Test-Combodo"', + 'sExpectedMessage' => '/property_type[WrongDefinition]/definition/nodes/node[source_property]: Unknown value-type node class: "Test-Combodo"', + ], + 'Wrong class for xml-format in node specification' => [ + 'sXMLContent' => << + + Dashlet + + + + + + + Contact + status + + + + + +XML, + 'sExpectedClass' => 'Combodo\iTop\PropertyType\Serializer\SerializerException', + 'sExpectedMessage' => '/property_type[WrongDefinition]/definition/nodes/node[coll]/xml-format: Unknown type node class: "Combodo-test"', + ], + 'Missing class for xml-format in node specification' => [ + 'sXMLContent' => << + + Dashlet + + + + + + + Contact + status + + + + + +XML, + 'sExpectedClass' => 'Combodo\iTop\PropertyType\Serializer\SerializerException', + 'sExpectedMessage' => '/property_type[WrongDefinition]/definition/nodes/node[coll]/xml-format: Missing xsi:type in node specification', + ], + 'Missing tag in xml-format ' => [ + 'sXMLContent' => << + + Dashlet + + + + + + + + + + + +XML, + 'sExpectedClass' => 'Combodo\iTop\PropertyType\Serializer\SerializerException', + 'sExpectedMessage' => '/property_type[WrongDefinition]/definition/nodes/node/xml-format: Missing element', ], ]; }