N°9066 - Serialization/Unserialization from XML to Forms

This commit is contained in:
Eric Espie
2026-01-07 14:29:08 +01:00
parent efb1bd765b
commit 154fb5c737
23 changed files with 553 additions and 112 deletions

View File

@@ -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);
}
}

View File

@@ -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 = <<<PHP
class FormFor__$this->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 .= <<<PHP
}
}
PHP;
$aPHPFragments[] = $sLocalPHP;
@@ -76,4 +69,14 @@ PHP;
{
return $this->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);
}
}

View File

@@ -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);
}
}

View File

@@ -0,0 +1,14 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\Serializer;
use Combodo\iTop\PropertyType\PropertyTypeException;
class SerializerException extends PropertyTypeException
{
}

View File

@@ -0,0 +1,22 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\Serializer\XMLFormat;
use Combodo\iTop\DesignElement;
use Combodo\iTop\PropertyType\ValueType\AbstractValueType;
abstract class AbstractXMLFormat
{
public function InitFromDomNode(DesignElement $oDomNode): void
{
}
abstract public function SerializeToDOMNode(mixed $value, DesignElement $oDOMNode, AbstractValueType $oValueType): void;
abstract public function UnserializeFromDOMNode(DesignElement $oDOMNode, AbstractValueType $oValueType): mixed;
}

View File

@@ -0,0 +1,27 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\Serializer\XMLFormat;
use Combodo\iTop\DesignElement;
use Combodo\iTop\PropertyType\ValueType\AbstractValueType;
class XMLFormatCSV extends AbstractXMLFormat
{
public function SerializeToDOMNode($value, $oDOMNode, AbstractValueType $oValueType): void
{
$sXmlValue = implode(',', $value);
$oTextNode = $oDOMNode->ownerDocument->createTextNode($sXmlValue);
$oDOMNode->appendChild($oTextNode);
}
public function UnserializeFromDOMNode(DesignElement $oDOMNode, AbstractValueType $oValueType): mixed
{
$sValue = $oDOMNode->GetText('');
return explode(',', $sValue);
}
}

View File

@@ -0,0 +1,56 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\Serializer\XMLFormat;
use Combodo\iTop\DesignElement;
use Combodo\iTop\PropertyType\Serializer\SerializerException;
use Combodo\iTop\PropertyType\ValueType\AbstractValueType;
use utils;
class XMLFormatFactory
{
private static XMLFormatFactory $oInstance;
protected function __construct()
{
}
final public static function GetInstance(): XMLFormatFactory
{
if (!isset(static::$oInstance)) {
static::$oInstance = new XMLFormatFactory();
}
return static::$oInstance;
}
/**
* @param \Combodo\iTop\DesignElement $oDomNode
*
* @return \Combodo\iTop\PropertyType\Serializer\XMLFormat\AbstractXMLFormat
* @throws \Combodo\iTop\PropertyType\Serializer\SerializerException
*/
public function CreateXMLFormatFromDomNode(DesignElement $oDomNode): AbstractXMLFormat
{
$sNodeType = $oDomNode->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);
}
}

View File

@@ -0,0 +1,79 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\Serializer\XMLFormat;
use Combodo\iTop\DesignElement;
use Combodo\iTop\PropertyType\Serializer\SerializerException;
use Combodo\iTop\PropertyType\Serializer\XMLFormat\AbstractXMLFormat;
use Combodo\iTop\PropertyType\ValueType\AbstractValueType;
use Combodo\iTop\PropertyType\ValueType\Branch\ValueTypeCollection;
class XMLFormatFlatArray extends AbstractXMLFormat
{
private string $sTagFormat;
private string $sCountTag;
public function InitFromDomNode(DesignElement $oDomNode): void
{
$sTagFormat = $oDomNode->GetChildText('tag-format');
if (is_null($sTagFormat)) {
throw new SerializerException('Missing <tag-name> element', $oDomNode);
}
$this->sTagFormat = $sTagFormat;
$sCountTag = $oDomNode->GetChildText('count-tag');
if (is_null($sCountTag)) {
throw new SerializerException('Missing <count-tag> 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;
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\Serializer\XMLFormat;
use Combodo\iTop\DesignElement;
use Combodo\iTop\PropertyType\Serializer\SerializerException;
use Combodo\iTop\PropertyType\Serializer\XMLFormat\AbstractXMLFormat;
use Combodo\iTop\PropertyType\ValueType\AbstractValueType;
class XMLFormatValueAsId extends AbstractXMLFormat
{
private string $sTagName;
public function InitFromDomNode(DesignElement $oDomNode): void
{
$sTagName = $oDomNode->GetChildText('tag-name');
if (is_null($sTagName)) {
throw new SerializerException("Missing <tag-name> 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;
}
}

View File

@@ -0,0 +1,57 @@
<?php
/*
* @copyright Copyright (C) 2010-2026 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
*/
namespace Combodo\iTop\PropertyType\Serializer;
use Combodo\iTop\DesignElement;
use Combodo\iTop\PropertyType\Compiler\PropertyTypeCompiler;
use Combodo\iTop\PropertyType\PropertyType;
use Combodo\iTop\PropertyType\ValueType\AbstractValueType;
class XMLSerializer
{
private static XMLSerializer $oInstance;
protected function __construct()
{
}
final public static function GetInstance(): XMLSerializer
{
if (!isset(static::$oInstance)) {
static::$oInstance = new XMLSerializer();
}
return static::$oInstance;
}
public function Serialize(mixed $value, DesignElement $oParentNode, string $sId, string $sType): void
{
$sPropertyTypeXML = PropertyTypeCompiler::GetInstance()->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);
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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');

View File

@@ -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);
}
}

View File

@@ -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);
}
}