From 5ea04625eff99c81f9335fd64a989c74a513bd55 Mon Sep 17 00:00:00 2001 From: Nav Date: Fri, 9 Feb 2024 23:32:58 +0000 Subject: [PATCH] New service for converting TDFs to/from XML documents --- .../Xml/Exceptions/XmlParsingException.php | 5 + .../Services/Xml/FromXmlService.php | 417 ++++++++++++++++++ .../Services/Xml/ToXmlService.php | 351 +++++++++++++++ .../Services/Xml/XmlService.php | 205 +++++++++ 4 files changed, 978 insertions(+) create mode 100644 build/scripts/Targets/TargetDescriptionFiles/Services/Xml/Exceptions/XmlParsingException.php create mode 100644 build/scripts/Targets/TargetDescriptionFiles/Services/Xml/FromXmlService.php create mode 100644 build/scripts/Targets/TargetDescriptionFiles/Services/Xml/ToXmlService.php create mode 100644 build/scripts/Targets/TargetDescriptionFiles/Services/Xml/XmlService.php diff --git a/build/scripts/Targets/TargetDescriptionFiles/Services/Xml/Exceptions/XmlParsingException.php b/build/scripts/Targets/TargetDescriptionFiles/Services/Xml/Exceptions/XmlParsingException.php new file mode 100644 index 00000000..1fbce904 --- /dev/null +++ b/build/scripts/Targets/TargetDescriptionFiles/Services/Xml/Exceptions/XmlParsingException.php @@ -0,0 +1,5 @@ +stringService = $stringService ?? new StringService(); + } + + public function getDeviceElement(DOMDocument $document): DOMElement + { + $deviceElement = $document->getElementsByTagName('device')->item(0); + if (!$deviceElement instanceof DOMElement) { + throw new XmlParsingException('Missing device element'); + } + + return $deviceElement; + } + + public function getNodeAttributesByName(DOMNode $node): array + { + $output = []; + foreach ($node->attributes as $attribute) { + /** @var \DOMAttr $attribute */ + $output[$attribute->name] = $attribute->value; + } + + return $output; + } + + public function getDeviceElementsFromXPath(string $expression, DOMDocument $document): DOMNodeList + { + $elements = (new \DOMXPath($document))->query('/device/' . $expression); + + if ($elements === false) { + throw new \Exception('Failed to evaluate XPath expression - malformed expression'); + } + + return $elements; + } + + public function propertyGroupFromElement(DOMElement $element): PropertyGroup + { + $attributes = $this->getNodeAttributesByName($element); + + $output = new PropertyGroup($attributes['key'] ?? null, [], []); + + foreach ($element->childNodes as $childNode) { + if (!$childNode instanceof DOMElement) { + continue; + } + + if ($childNode->nodeName === 'property') { + $output->properties[] = $this->propertyFromElement($childNode); + continue; + } + + if ($childNode->nodeName === 'property-group') { + $output->subPropertyGroups[] = $this->propertyGroupFromElement($childNode); + } + } + + return $output; + } + + public function propertyFromElement(DOMElement $element): Property + { + $attributes = $this->getNodeAttributesByName($element); + return new Property( + $attributes['key'] ?? null, + $attributes['value'] ?? null, + ); + } + + public function addressSpaceFromElement(DOMElement $element): AddressSpace + { + $attributes = $this->getNodeAttributesByName($element); + + $output = new AddressSpace( + $attributes['key'] ?? null, + $this->stringService->tryStringToInt($attributes['start'] ?? null), + $this->stringService->tryStringToInt($attributes['size'] ?? null), + $attributes['endianness'] ?? null, + ); + + foreach ($element->childNodes as $childNode) { + if (!$childNode instanceof DOMElement) { + continue; + } + + if ($childNode->nodeName === 'memory-segment') { + $output->memorySegments[] = $this->memorySegmentFromElement($childNode); + } + } + + return $output; + } + + public function memorySegmentFromElement(DOMElement $element): MemorySegment + { + $attributes = $this->getNodeAttributesByName($element); + + $output = new MemorySegment( + $attributes['key'] ?? null, + $attributes['name'] ?? null, + MemorySegmentType::tryFrom($attributes['type'] ?? null), + $this->stringService->tryStringToInt($attributes['start'] ?? null), + $this->stringService->tryStringToInt($attributes['size'] ?? null), + $this->stringService->tryStringToInt($attributes['page-size'] ?? null), + $attributes['rw'] ?? null, + [] + ); + + foreach ($element->childNodes as $childNode) { + if (!$childNode instanceof DOMElement) { + continue; + } + + if ($childNode->nodeName === 'section') { + $output->sections[] = $this->memorySegmentSectionFromElement($childNode); + } + } + + return $output; + } + + public function memorySegmentSectionFromElement(DOMElement $element): MemorySegmentSection + { + $attributes = $this->getNodeAttributesByName($element); + + $output = new MemorySegmentSection( + $attributes['key'] ?? null, + $attributes['name'] ?? null, + $this->stringService->tryStringToInt($attributes['start'] ?? null), + $this->stringService->tryStringToInt($attributes['size'] ?? null), + [] + ); + + foreach ($element->childNodes as $childNode) { + if (!$childNode instanceof DOMElement) { + continue; + } + + if ($childNode->nodeName === 'section') { + $output->subSections[] = $this->memorySegmentSectionFromElement($childNode); + } + } + + return $output; + } + + public function physicalInterfaceFromElement(DOMElement $element): PhysicalInterface + { + $attributes = $this->getNodeAttributesByName($element); + return new PhysicalInterface( + $attributes['name'] ?? null, + $attributes['type'] ?? null, + ); + } + + public function moduleFromElement(DOMElement $element): Module + { + $attributes = $this->getNodeAttributesByName($element); + + $output = new Module( + $attributes['key'] ?? null, + $attributes['name'] ?? null, + $attributes['description'] ?? null, + [] + ); + + foreach ($element->childNodes as $childNode) { + if (!$childNode instanceof DOMElement) { + continue; + } + + if ($childNode->nodeName === 'register-group') { + $output->registerGroups[] = $this->registerGroupFromElement($childNode); + } + } + + return $output; + } + + public function registerGroupFromElement(DOMElement $element): RegisterGroup + { + $attributes = $this->getNodeAttributesByName($element); + + $output = new RegisterGroup( + $attributes['key'] ?? null, + $attributes['name'] ?? null, + $this->stringService->tryStringToInt($attributes['offset'] ?? null), + [], + [], + [] + ); + + foreach ($element->childNodes as $childNode) { + if (!$childNode instanceof DOMElement) { + continue; + } + + if ($childNode->nodeName === 'register') { + $output->registers[] = $this->registerFromElement($childNode); + continue; + } + + if ($childNode->nodeName === 'register-group') { + $output->subGroups[] = $this->registerGroupFromElement($childNode); + continue; + } + + if ($childNode->nodeName === 'register-group-reference') { + $output->subGroupReferences[] = $this->registerGroupReferenceFromElement($childNode); + } + } + + return $output; + } + + public function registerGroupReferenceFromElement(DOMElement $element): RegisterGroupReference + { + $attributes = $this->getNodeAttributesByName($element); + + return new RegisterGroupReference( + $attributes['key'] ?? null, + $attributes['name'] ?? null, + $attributes['register-group-key'] ?? null, + $this->stringService->tryStringToInt($attributes['offset'] ?? null), + $attributes['description'] ?? null + ); + } + + public function registerFromElement(DOMElement $element): Register + { + $attributes = $this->getNodeAttributesByName($element); + + $output = new Register( + $attributes['key'] ?? null, + $attributes['name'] ?? null, + $attributes['description'] ?? null, + $this->stringService->tryStringToInt($attributes['offset'] ?? null), + $this->stringService->tryStringToInt($attributes['size'] ?? null), + $this->stringService->tryStringToInt($attributes['initial-value'] ?? null), + $attributes['rw'] ?? null, + isset($attributes['alternative']) ? trim($attributes['alternative']) === 'true' : null, + [] + ); + + foreach ($element->childNodes as $childNode) { + if (!$childNode instanceof DOMElement) { + continue; + } + + if ($childNode->nodeName === 'bit-field') { + $output->bitFields[] = $this->bitFieldFromElement($childNode); + } + } + + return $output; + } + + public function bitFieldFromElement(DOMElement $element): BitField + { + $attributes = $this->getNodeAttributesByName($element); + + return new BitField( + $attributes['key'] ?? null, + $attributes['name'] ?? null, + $attributes['description'] ?? null, + $this->stringService->tryStringToInt($attributes['mask'] ?? null), + $attributes['rw'] ?? null + ); + } + + public function peripheralFromElement(DOMElement $element): Peripheral + { + $attributes = $this->getNodeAttributesByName($element); + + $output = new Peripheral( + $attributes['key'] ?? null, + $attributes['name'] ?? null, + $attributes['module-key'] ?? null, + [], + [] + ); + + foreach ($element->childNodes as $childNode) { + if (!$childNode instanceof DOMElement) { + continue; + } + + if ($childNode->nodeName === 'register-group-reference') { + $output->registerGroupReferences[] = $this->registerGroupReferenceFromElement($childNode); + continue; + } + + if ($childNode->nodeName === 'signal') { + $output->signals[] = $this->signalFromElement($childNode); + } + } + + $signalsElements = $element->getElementsByTagName('signals'); + if ($signalsElements->count() > 1) { + throw new XmlParsingException('Unexpected number of "signals" elements'); + } + + $signalElement = $signalsElements->item(0); + if ($signalElement instanceof DOMElement) { + foreach ($signalElement->childNodes as $childNode) { + if (!$childNode instanceof DOMElement) { + continue; + } + + if ($childNode->nodeName === 'signal') { + $output->signals[] = $this->signalFromElement($childNode); + } + } + } + + return $output; + } + + public function signalFromElement(DOMElement $element): Signal + { + $attributes = $this->getNodeAttributesByName($element); + + return new Signal( + $attributes['pad-id'] ?? null, + $this->stringService->tryStringToInt($attributes['index'] ?? null), + $attributes['function'] ?? null, + $attributes['group'] ?? null, + $attributes['field'] ?? null + ); + } + + public function pinoutFromElement(DOMElement $element): Pinout + { + $attributes = $this->getNodeAttributesByName($element); + + $output = new Pinout( + $attributes['key'] ?? null, + $attributes['name'] ?? null, + PinoutType::tryFrom($attributes['type'] ?? null), + $attributes['function'] ?? null, + [] + ); + + foreach ($element->childNodes as $childNode) { + if (!$childNode instanceof DOMElement) { + continue; + } + + if ($childNode->nodeName === 'pin') { + $output->pins[] = $this->pinFromElement($childNode); + } + } + + return $output; + } + + public function pinFromElement(DOMElement $element): Pin + { + $attributes = $this->getNodeAttributesByName($element); + + return new Pin( + $attributes['position'] ?? null, + $attributes['pad'] ?? null + ); + } + + public function variantFromElement(DOMElement $element): Variant + { + $attributes = $this->getNodeAttributesByName($element); + + return new Variant( + $attributes['name'] ?? null, + $attributes['pinout-key'] ?? null, + $attributes['package'] ?? null + ); + } +} diff --git a/build/scripts/Targets/TargetDescriptionFiles/Services/Xml/ToXmlService.php b/build/scripts/Targets/TargetDescriptionFiles/Services/Xml/ToXmlService.php new file mode 100644 index 00000000..ed0297b2 --- /dev/null +++ b/build/scripts/Targets/TargetDescriptionFiles/Services/Xml/ToXmlService.php @@ -0,0 +1,351 @@ +stringService = $stringService ?? new StringService(); + } + + public function propertyGroupToXml(PropertyGroup $propertyGroup, DOMDocument $document): DOMElement + { + $element = $document->createElement('property-group'); + $element->setAttribute('key', strtolower($propertyGroup->key)); + + foreach ($propertyGroup->subPropertyGroups as $subPropertyGroup) { + $element->append($this->propertyGroupToXml($subPropertyGroup, $document)); + } + + foreach ($propertyGroup->properties as $property) { + $element->append($this->propertyToXml($property, $document)); + } + + return $element; + } + + public function propertyToXml(Property $property, DOMDocument $document): DOMElement + { + $element = $document->createElement('property'); + $element->setAttribute('key', strtolower($property->key)); + $element->setAttribute('value', $property->value); + + return $element; + } + + public function addressSpaceToXml(AddressSpace $addressSpace, DOMDocument $document): DOMElement + { + $element = $document->createElement('address-space'); + $element->setAttribute('key', strtolower($addressSpace->key)); + $element->setAttribute('start', $this->stringService->tryIntToHex($addressSpace->startAddress, 8)); + $element->setAttribute('size', $addressSpace->size); + + if (!empty($addressSpace->endianness)) { + $element->setAttribute('endianness', strtolower($addressSpace->endianness)); + } + + foreach ($addressSpace->memorySegments as $memorySegment) { + $element->append($this->memorySegmentToXml($memorySegment, $document)); + } + + return $element; + } + + public function memorySegmentToXml(MemorySegment $memorySegment, DOMDocument $document): DOMElement + { + $element = $document->createElement('memory-segment'); + $element->setAttribute('key', strtolower($memorySegment->key)); + $element->setAttribute('name', $memorySegment->name); + $element->setAttribute('type', $memorySegment->type->value ?? ''); + $element->setAttribute('start', $this->stringService->tryIntToHex($memorySegment->startAddress, 8)); + $element->setAttribute('size', $memorySegment->size); + + if (!empty($memorySegment->pageSize)) { + $element->setAttribute('page-size', $memorySegment->pageSize); + } + + if (!empty($memorySegment->access)) { + $element->setAttribute('rw', $memorySegment->access); + } + + foreach ($memorySegment->sections as $segmentSection) { + $element->append($this->memorySegmentSectionToXml($segmentSection, $document)); + } + + return $element; + } + + public function memorySegmentSectionToXml(MemorySegmentSection $section, DOMDocument $document): DOMElement + { + $element = $document->createElement('section'); + $element->setAttribute('key', strtolower($section->key)); + $element->setAttribute('name', $section->name); + $element->setAttribute('start', $this->stringService->tryIntToHex($section->startAddress, 8)); + $element->setAttribute('size', $section->size); + + foreach ($section->subSections as $section) { + $element->append($this->memorySegmentSectionToXml($section, $document)); + } + + return $element; + } + + public function physicalInterfaceToXml(PhysicalInterface $physicalInterface, DOMDocument $document): DOMElement + { + $element = $document->createElement('physical-interface'); + $element->setAttribute('name', $physicalInterface->name); + $element->setAttribute('type', $physicalInterface->type); + + return $element; + } + + public function peripheralToXml(Peripheral $peripheral, DOMDocument $document): DOMElement + { + $element = $document->createElement('peripheral'); + $element->setAttribute('key', $peripheral->key); + $element->setAttribute('name', $peripheral->name); + $element->setAttribute('module-key', $peripheral->moduleKey); + + foreach ($peripheral->registerGroupReferences as $registerGroupReference) { + $element->append($this->registerGroupReferenceToXml($registerGroupReference, $document)); + } + + if (!empty($peripheral->signals)) { + $signalsElement = $document->createElement('signals'); + foreach ($peripheral->signals as $signal) { + $signalsElement->append($this->signalToXml($signal, $document)); + } + + $element->append($signalsElement); + } + + return $element; + } + + public function signalToXml(Signal $signal, DOMDocument $document): DOMElement + { + $element = $document->createElement('signal'); + $element->setAttribute('pad-id', $signal->padId); + + if ($signal->index !== null) { + $element->setAttribute('index', $signal->index); + } + + if ($signal->function !== null) { + $element->setAttribute('function', $signal->function); + } + + if ($signal->group !== null) { + $element->setAttribute('group', $signal->group); + } + + if ($signal->field !== null) { + $element->setAttribute('field', $signal->field); + } + + return $element; + } + + public function moduleToXml(Module $module, DOMDocument $document): DOMElement + { + $element = $document->createElement('module'); + $element->setAttribute('key', $module->key); + $element->setAttribute('name', $module->name); + $element->setAttribute('description', trim($module->description)); + + foreach ($module->registerGroups as $registerGroup) { + $element->append($this->registerGroupToXml($registerGroup, $document)); + } + + return $element; + } + + public function registerGroupToXml(RegisterGroup $registerGroup, DOMDocument $document): DOMElement + { + $element = $document->createElement('register-group'); + $element->setAttribute('key', $registerGroup->key); + $element->setAttribute('name', $registerGroup->name); + + if ($registerGroup->offset !== null) { + $element->setAttribute('offset', $this->stringService->tryIntToHex($registerGroup->offset)); + } + + /* + * A register group can have registers, register groups (subgroups) and register group references as children. + * + * We want these to appear in the order of their offset, in the XML. So we group them into a single array, + * sort them, then generate the XML elements. + */ + + $children = array_merge( + $registerGroup->registers, + $registerGroup->subGroups, + $registerGroup->subGroupReferences + ); + + usort( + $children, + fn ( + RegisterGroup|RegisterGroupReference|Register $childA, + RegisterGroup|RegisterGroupReference|Register $childB + ): bool => $childA->offset > $childB->offset + ); + + foreach ($children as $child) { + /** + * @var RegisterGroup|RegisterGroupReference|Register $child + */ + + if ($child instanceof Register) { + $element->append($this->registerToXml($child, $document)); + continue; + } + + if ($child instanceof RegisterGroup) { + $element->append($this->registerGroupToXml($child, $document)); + continue; + } + + if ($child instanceof RegisterGroupReference) { + $element->append($this->registerGroupReferenceToXml($child, $document)); + } + } + + return $element; + } + + public function registerGroupReferenceToXml( + RegisterGroupReference $registerGroupReference, + DOMDocument $document + ): DOMElement { + $element = $document->createElement('register-group-reference'); + $element->setAttribute('key', $registerGroupReference->key); + $element->setAttribute('name', $registerGroupReference->name); + + if (!empty($registerGroupReference->description)) { + $element->setAttribute('description', $registerGroupReference->description); + } + + $element->setAttribute('register-group-key', $registerGroupReference->registerGroupKey); + $element->setAttribute('offset', $this->stringService->tryIntToHex($registerGroupReference->offset)); + + return $element; + } + + public function registerToXml(Register $register, DOMDocument $document): DOMElement + { + $element = $document->createElement('register'); + $element->setAttribute('key', $register->key); + $element->setAttribute('name', $register->name); + + if (!empty($register->description)) { + $element->setAttribute('description', trim($register->description)); + } + + $element->setAttribute('offset', $this->stringService->tryIntToHex($register->offset)); + $element->setAttribute('size', $register->size); + + if ($register->initialValue !== null) { + $element->setAttribute('initial-value', $this->stringService->tryIntToHex($register->initialValue)); + } + + if (!empty($register->access)) { + $element->setAttribute('rw', $register->access); + } + + if ($register->alternative !== null) { + $element->setAttribute('alternative', $register->alternative ? 'true' : 'false'); + } + + foreach ($register->bitFields as $bitField) { + $element->append($this->bitFieldToXml($bitField, $document)); + } + + return $element; + } + + public function bitFieldToXml(BitField $bitField, DOMDocument $document): DOMElement + { + $element = $document->createElement('bit-field'); + $element->setAttribute('key', $bitField->key); + $element->setAttribute('name', $bitField->name); + + if (!empty($bitField->description)) { + $element->setAttribute('description', trim($bitField->description)); + } + + $element->setAttribute('mask', $this->stringService->tryIntToHex($bitField->mask)); + + if (!empty($bitField->access)) { + $element->setAttribute('rw', $bitField->access); + } + + return $element; + } + + public function pinoutToXml(Pinout $pinout, DOMDocument $document): DOMElement + { + $element = $document->createElement('pinout'); + $element->setAttribute('key', $pinout->key); + $element->setAttribute('name', $pinout->name); + $element->setAttribute('type', $pinout->type->value ?? ''); + + if (!empty($pinout->function)) { + $element->setAttribute('function', $pinout->function); + } + + foreach ($pinout->pins as $pin) { + $element->append($this->pinToXml($pin, $document)); + } + + return $element; + } + + public function pinToXml(Pin $pin, DOMDocument $document): DOMElement + { + $element = $document->createElement('pin'); + $element->setAttribute('position', $pin->position); + $element->setAttribute('pad', $pin->pad); + + return $element; + } + + public function variantToXml(Variant $variant, DOMDocument $document): DOMElement + { + $element = $document->createElement('variant'); + $element->setAttribute('name', $variant->name); + $element->setAttribute('pinout-key', $variant->pinoutKey); + $element->setAttribute('package', $variant->package); + + return $element; + } +} diff --git a/build/scripts/Targets/TargetDescriptionFiles/Services/Xml/XmlService.php b/build/scripts/Targets/TargetDescriptionFiles/Services/Xml/XmlService.php new file mode 100644 index 00000000..76782b4f --- /dev/null +++ b/build/scripts/Targets/TargetDescriptionFiles/Services/Xml/XmlService.php @@ -0,0 +1,205 @@ +fromXmlService = $fromXmlService ?? new FromXmlService(); + $this->toXmlService = $toXmlService ?? new ToXmlService(); + } + + /** + * Constructs a TargetDescriptionFile from the given XML document. + * + * @param DOMDocument $document + * The XML document from which to construct the TargetDescriptionFile. + * + * @return TargetDescriptionFile + * The constructed TargetDescriptionFile. + */ + public function fromXml(DOMDocument $document): TargetDescriptionFile + { + $deviceElement = $this->fromXmlService->getDeviceElement($document); + $deviceAttributesByName = $this->fromXmlService->getNodeAttributesByName($deviceElement); + + $targetFamily = TargetFamily::tryFrom($deviceAttributesByName['family'] ?? null); + if (!$targetFamily instanceof TargetFamily) { + throw new XmlParsingException('Failed to resolve target family - missing/invalid family attribute'); + } + + $tdf = match ($targetFamily) { + TargetFamily::AVR_8 => new Avr8TargetDescriptionFile(), + default => new TargetDescriptionFile(), + }; + $tdf->deviceAttributesByName = $deviceAttributesByName; + + $propertyGroupElements = $this->fromXmlService->getDeviceElementsFromXPath( + 'property-groups/property-group', + $document + ); + foreach ($propertyGroupElements as $element) { + $tdf->propertyGroups[] = $this->fromXmlService->propertyGroupFromElement($element); + } + + $addressSpaceElements = $this->fromXmlService->getDeviceElementsFromXPath( + 'address-spaces/address-space', + $document + ); + foreach ($addressSpaceElements as $element) { + $tdf->addressSpaces[] = $this->fromXmlService->addressSpaceFromElement($element); + } + + $physicalInterfaceElements = $this->fromXmlService->getDeviceElementsFromXPath( + 'physical-interfaces/physical-interface', + $document + ); + foreach ($physicalInterfaceElements as $element) { + $tdf->physicalInterfaces[] = $this->fromXmlService->physicalInterfaceFromElement($element); + } + + $peripheralElements = $this->fromXmlService->getDeviceElementsFromXPath( + 'peripherals/peripheral', + $document + ); + foreach ($peripheralElements as $element) { + $tdf->peripherals[] = $this->fromXmlService->peripheralFromElement($element); + } + + $moduleElements = $this->fromXmlService->getDeviceElementsFromXPath( + 'modules/module', + $document + ); + foreach ($moduleElements as $element) { + $tdf->modules[] = $this->fromXmlService->moduleFromElement($element); + } + + $pinoutElements = $this->fromXmlService->getDeviceElementsFromXPath( + 'pinouts/pinout', + $document + ); + foreach ($pinoutElements as $element) { + $tdf->pinouts[] = $this->fromXmlService->pinoutFromElement($element); + } + + $variantElements = $this->fromXmlService->getDeviceElementsFromXPath( + 'variants/variant', + $document + ); + foreach ($variantElements as $element) { + $tdf->variants[] = $this->fromXmlService->variantFromElement($element); + } + + return $tdf; + } + + /** + * Generates an XML document from the given TDF. + * + * @param TargetDescriptionFile $tdf + * The TDF from which to generate the XML document. + * + * @return DOMDocument + * The generated XML document. + * + * @throws DOMException + */ + public function toXml(TargetDescriptionFile $tdf): DOMDocument + { + $document = new DOMDocument('1.0', 'UTF-8'); + $deviceElement = $document->createElement('device'); + + $deviceElement->setAttribute('name', $tdf->getName()); + $deviceElement->setAttribute('family', $tdf->getFamily()->value ?? ''); + $deviceElement->setAttribute('configuration-value', $tdf->getConfigurationValue()); + + $architecture = $tdf->getArchitecture(); + if (!empty($architecture)) { + $deviceElement->setAttribute('architecture', $architecture); + } + + $vendor = $tdf->getVendor(); + if (!empty($vendor)) { + $deviceElement->setAttribute('vendor', $vendor); + } + + foreach ($tdf->getAdditionalDeviceAttributes() as $attrName => $attrValue) { + $deviceElement->setAttribute($attrName, $attrValue); + } + + $propertyGroupsElement = $document->createElement('property-groups'); + foreach ($tdf->propertyGroups as $propertyGroup) { + $propertyGroupsElement->append($this->toXmlService->propertyGroupToXml($propertyGroup, $document)); + } + + $deviceElement->append($propertyGroupsElement); + + $addressSpacesElement = $document->createElement('address-spaces'); + foreach ($tdf->addressSpaces as $addressSpace) { + $addressSpacesElement->append($this->toXmlService->addressSpaceToXml($addressSpace, $document)); + } + + $deviceElement->append($addressSpacesElement); + + $interfacesElement = $document->createElement('physical-interfaces'); + foreach ($tdf->physicalInterfaces as $interface) { + $interfacesElement->append($this->toXmlService->physicalInterfaceToXml($interface, $document)); + } + + $deviceElement->append($interfacesElement); + + $peripheralsElement = $document->createElement('peripherals'); + foreach ($tdf->peripherals as $peripheral) { + $peripheralsElement->append($this->toXmlService->peripheralToXml($peripheral, $document)); + } + + $deviceElement->append($peripheralsElement); + + $modulesElement = $document->createElement('modules'); + foreach ($tdf->modules as $module) { + $modulesElement->append($this->toXmlService->moduleToXml($module, $document)); + } + + $deviceElement->append($modulesElement); + + $pinoutsElement = $document->createElement('pinouts'); + foreach ($tdf->pinouts as $pinout) { + $pinoutsElement->append($this->toXmlService->pinoutToXml($pinout, $document)); + } + + $deviceElement->append($pinoutsElement); + + $variantsElement = $document->createElement('variants'); + foreach ($tdf->variants as $variant) { + $variantsElement->append($this->toXmlService->variantToXml($variant, $document)); + } + + $deviceElement->append($variantsElement); + + $document->append($deviceElement); + return $document; + } +}