diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 4ece99a5..b58d8ebd 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -42,19 +42,19 @@ jobs: version: '${{ matrix.fhir-version }}' - load-fhir-validator: - runs-on: ubuntu-22.04 - name: 'Download FHIR Validator' - steps: - - uses: actions/checkout@v4 - - - uses: ./.github/actions/load-fhir-validator +# load-fhir-validator: +# runs-on: ubuntu-22.04 +# name: 'Download FHIR Validator' +# steps: +# - uses: actions/checkout@v4 +# +# - uses: ./.github/actions/load-fhir-validator run: runs-on: ubuntu-22.04 needs: - load-fhir-source - - load-fhir-validator +# - load-fhir-validator strategy: fail-fast: false matrix: @@ -77,8 +77,8 @@ jobs: with: version: '${{ matrix.fhir-version }}' - - name: 'Install FHIR validator' - uses: ./.github/actions/load-fhir-validator +# - name: 'Install FHIR validator' +# uses: ./.github/actions/load-fhir-validator - name: 'Install PHP' uses: shivammathur/setup-php@v2 @@ -123,11 +123,11 @@ jobs: run: | ./bin/generate.sh --useExisting --versions '${{ matrix.fhir-version }}' - - name: 'Install Java' - uses: actions/setup-java@v4 - with: - distribution: 'zulu' - java-version: 18 +# - name: 'Install Java' +# uses: actions/setup-java@v4 +# with: +# distribution: 'zulu' +# java-version: 18 - name: 'Run tests' # language=sh diff --git a/README.md b/README.md index fa6c52eb..285a5b60 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,9 @@ require 'path to PHPFHIRResponseParser.php'; // build config $config = new \YourConfiguredNamespace\PHPFHIRResponseParserConfig([ 'registerAutoloader' => true, // use if you are not using Composer - 'libxmlOpts' => LIBXML_COMPACT | LIBXML_NSCLEAN // choose different libxml arguments if you want, ymmv. + 'libxmlOpts' => LIBXML_NONET | LIBXML_BIGLINES | LIBXML_PARSEHUGE | LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NOXMLDECL // choose different libxml arguments if you want, ymmv. + 'rootXmlns' => 'https://hl7.org/fhir', // a specific root xmlns to use, if the source does not return one + 'overrideSourceXmlns' => true, // set this to true if you want the 'rootXmlns' value you defined to override any value seen from source ]); // build parser @@ -190,14 +192,19 @@ $json = json_encode($object); ## XML Serialization ```php -// To get an instance of \DOMElement... -$element = $object->xmlSerialize(); +// To get an instance of \XMLWriter... +$xw = $object->xmlSerialize(null, $yourConfigInstance); // to get as XML string... -$xml = $element->ownerDocument->saveXML($element); +$xml = $xw->outputMemory(true); + +// you can alternatively have the output written directly to a file: +$xw = new \YourConfiguredNamespace\PHPFHIRXmlWriter(); +$xw->openUri('file:///some/directory/fhir-resource.xml'); +$object->xmlSerialize($xw, $yourConfigInstance); ``` -XML Serialization utilizes [DOM](https://www.php.net/manual/en/book.dom.php). +XML Serialization utilizes [XMLWriter](https://www.php.net/manual/en/book.xmlwriter.php). # Testing diff --git a/bin/generate.php b/bin/generate.php index 5b417c92..127697f6 100644 --- a/bin/generate.php +++ b/bin/generate.php @@ -384,7 +384,8 @@ function is_dir_empty(string $dir): bool $url = $build_config->getUrl(); - $namespace = $build_config->getNamespace(true); + // build vars + $namespace = $build_config->getFullyQualifiedName(true); $version = trim($version); $schema_dir = $config->getSchemaPath() . DIRECTORY_SEPARATOR . $version; diff --git a/composer.json b/composer.json index fc32cec4..691924ca 100644 --- a/composer.json +++ b/composer.json @@ -20,14 +20,16 @@ ], "require": { "php": "^8.1", - "ext-simplexml": "*", "ext-json": "*", "ext-libxml": "*", - "ext-dom": "*", + "ext-simplexml": "*", "psr/log": "^3.0" }, "require-dev": { "ext-curl": "*", + "ext-dom": "*", + "ext-xmlreader": "*", + "ext-xmlwriter": "*", "phpunit/phpunit": "^10.5 || ^11.0", "monolog/monolog": "^3.2.0" }, diff --git a/files/constants.php b/files/constants.php index 99402777..a59c8030 100644 --- a/files/constants.php +++ b/files/constants.php @@ -85,11 +85,10 @@ const PHPFHIR_CLASSNAME_TYPEMAP = 'PHPFHIRTypeMap'; const PHPFHIR_CLASSNAME_DEBUG_CLIENT = 'PHPFHIRDebugClient'; const PHPFHIR_CLASSNAME_DEBUG_CLIENT_RESPONSE = 'PHPFHIRDebugClientResponse'; +const PHPFHIR_CLASSNAME_XML_WRITER = 'PHPFHIRXmlWriter'; // Core interface names const PHPFHIR_INTERFACE_TYPE = 'PHPFHIRTypeInterface'; -const PHPFHIR_INTERFACE_XML_SERIALIZABLE = 'PHPFHIRXmlSerializableInterface'; -const PHPFHIR_INTERFACE_XML_SERIALIZALE_CONFIG = 'PHPFHIRXmlSerializableConfigInterface'; const PHPFHIR_INTERFACE_CONTAINED_TYPE = 'PHPFHIRContainedTypeInterface'; const PHPFHIR_INTERFACE_COMMENT_CONTAINER = 'PHPFHIRCommentContainerInterface'; const PHPFHIR_INTERFACE_PRIMITIVE_TYPE = 'PHPFHIRPrimitiveTypeInterface'; @@ -98,8 +97,7 @@ const PHPFHIR_TRAIT_COMMENT_CONTAINER = 'PHPFHIRCommentContainerTrait'; const PHPFHIR_TRAIT_VALIDATION_ASSERTIONS = 'PHPFHIRValidationAssertionsTrait'; const PHPFHIR_TRAIT_CHANGE_TRACKING = 'PHPFHIRChangeTrackingTrait'; -const PHPFHIR_TRAIT_XMLNS = 'PHPFHIRXmlNamespaceTrait'; -const PHPFHIR_TRAIT_XML_SERIALIZABLE_CONFIG = 'PHPFHIRXmlSerializableConfigTrait'; +const PHPFHIR_TRAIT_SOURCE_XMLNS = 'PHPFHIRSourceXmlNamespaceTrait'; // Core enums const PHPFHIR_ENUM_CONFIG_KEY = 'PHPFHIRConfigKeyEnum'; diff --git a/src/Builder.php b/src/Builder.php index 13ea0335..762c0422 100644 --- a/src/Builder.php +++ b/src/Builder.php @@ -241,14 +241,14 @@ protected function writeCoreTypeFiles(): void $suffix = ucfirst($ftype); // define "default" namespace - $ns = $this->config->getNamespace(true); + $ns = $this->config->getFullyQualifiedName(true); if ('class' === $ftype) { // 'class' types do have suffix $suffix = ''; } else if ('test' === $ftype) { // test classes have different namespace - $ns = $this->config->getTestsNamespace(TestType::BASE, true); + $ns = $this->config->getFullyQualifiedTestsName(TestType::BASE, true); // trim subtype $fname = substr($fname, strpos($fname, '_') + 1); } diff --git a/src/Config/VersionConfig.php b/src/Config/VersionConfig.php index 692ed0f2..3bba9146 100644 --- a/src/Config/VersionConfig.php +++ b/src/Config/VersionConfig.php @@ -78,11 +78,17 @@ public function getUrl(): string /** * @param bool $leadingSlash + * @param string ...$bits * @return string */ - public function getNamespace(bool $leadingSlash): string + public function getFullyQualifiedName(bool $leadingSlash, string... $bits): string { - return $this->version->getNamespace($leadingSlash); + $ns = $this->version->getNamespace($leadingSlash); + $bits = array_filter($bits); + if ([] === $bits) { + return $ns; + } + return sprintf('%s\\%s', $ns, implode('\\' , $bits)); } /** @@ -104,18 +110,12 @@ public function getLibxmlOpts(): ?int /** * @param \DCarbone\PHPFHIR\Enum\TestType $testType * @param bool $leadingSlash + * @param string ...$bits * @return string */ - public function getTestsNamespace(TestType $testType, bool $leadingSlash): string + public function getFullyQualifiedTestsName(TestType $testType, bool $leadingSlash, string... $bits): string { - $ns = $this->getNamespace(false); - - if ('' === $ns) { - $ns = $testType->namespaceSlug(); - } else { - $ns .= '\\' . $testType->namespaceSlug(); - } - return $leadingSlash ? "\\{$ns}" : $ns; + return $this->getFullyQualifiedName($leadingSlash, $testType->namespaceSlug(), ...$bits); } /** diff --git a/src/Definition.php b/src/Definition.php index 168b6752..856a4e7d 100644 --- a/src/Definition.php +++ b/src/Definition.php @@ -84,9 +84,6 @@ public function buildDefinition(): void $log->info('Finding property types'); TypePropertyDecorator::findPropertyTypes($this->config, $this->types); - $log->info('Ensuring primitive type children have "value" property'); - TypeDecorator::ensureValueOnPrimitiveChildTypes($this->config, $this->types); - $log->info('Finding overloaded properties in child types'); TypePropertyDecorator::findOverloadedProperties($this->config, $this->types); diff --git a/src/Definition/Type.php b/src/Definition/Type.php index 2b20a5e6..088c193b 100644 --- a/src/Definition/Type.php +++ b/src/Definition/Type.php @@ -273,15 +273,7 @@ public function getClassName(): string */ public function getFullyQualifiedNamespace(bool $leadingSlash): string { - $ns = $this->getConfig()->getNamespace(false); - $typeNS = $this->getTypeNamespace(); - if ('' !== $typeNS) { - $ns = sprintf('%s\\%s', $ns, $typeNS); - } - return match ($leadingSlash) { - true => sprintf('\\%s', $ns), - false => $ns, - }; + return $this->getConfig()->getFullyQualifiedName($leadingSlash, $this->getTypeNamespace()); } /** @@ -291,15 +283,7 @@ public function getFullyQualifiedNamespace(bool $leadingSlash): string */ public function getFullyQualifiedTestNamespace(TestType $testType, bool $leadingSlash): string { - $ns = $this->getConfig()->getTestsNamespace($testType, false); - $typeNS = $this->getTypeNamespace(); - if ('' !== $typeNS) { - $ns = sprintf('%s\\%s', $ns, $typeNS); - } - return match ($leadingSlash) { - true => sprintf('\\%s', $ns), - false => $ns, - }; + return $this->getConfig()->getFullyQualifiedTestsName($testType, $leadingSlash,$this->getTypeNamespace()); } /** @@ -308,16 +292,7 @@ public function getFullyQualifiedTestNamespace(TestType $testType, bool $leading */ public function getFullyQualifiedClassName(bool $leadingSlash): string { - $cn = $this->getFullyQualifiedNamespace(false); - if ('' === $cn) { - $cn = $this->getClassName(); - } else { - $cn = sprintf('%s\\%s', $cn, $this->getClassName()); - } - return match ($leadingSlash) { - true => sprintf('\\%s', $cn), - false => $cn, - }; + return $this->getConfig()->getFullyQualifiedName($leadingSlash, $this->getTypeNamespace(), $this->getClassName()); } /** @@ -335,16 +310,7 @@ public function getTestClassName(): string */ public function getFullyQualifiedTestClassName($testType, bool $leadingSlash): string { - $cn = $this->getFullyQualifiedTestNamespace($testType, false); - if ('' === $cn) { - $cn = $this->getTestClassName(); - } else { - $cn = sprintf('%s\\%s', $cn, $this->getTestClassName()); - } - return match ($leadingSlash) { - true => sprintf('\\%s', $cn), - false => $cn, - }; + return $this->getConfig()->getFullyQualifiedTestsName($testType, $leadingSlash, $this->getTypeNamespace(), $this->getTestClassName()); } /** @@ -789,7 +755,7 @@ public function getDirectlyUsedTraits(): array $traits, PHPFHIR_TRAIT_VALIDATION_ASSERTIONS, PHPFHIR_TRAIT_CHANGE_TRACKING, - PHPFHIR_TRAIT_XMLNS, + PHPFHIR_TRAIT_SOURCE_XMLNS, ); } } else if (!$parentType->hasLocalProperties()) { @@ -799,7 +765,7 @@ public function getDirectlyUsedTraits(): array $traits, PHPFHIR_TRAIT_VALIDATION_ASSERTIONS, PHPFHIR_TRAIT_CHANGE_TRACKING, - PHPFHIR_TRAIT_XMLNS, + PHPFHIR_TRAIT_SOURCE_XMLNS, ); } diff --git a/src/Definition/TypeDecorator.php b/src/Definition/TypeDecorator.php index f4e2c7f7..243e0ab9 100644 --- a/src/Definition/TypeDecorator.php +++ b/src/Definition/TypeDecorator.php @@ -204,32 +204,6 @@ public static function determinePrimitiveTypes(VersionConfig $config, Types $typ } } - /** - * @param \DCarbone\PHPFHIR\Config\VersionConfig $config - * @param \DCarbone\PHPFHIR\Definition\Types $types - */ - public static function ensureValueOnPrimitiveChildTypes(VersionConfig $config, Types $types): void - { - $logger = $config->getLogger(); - foreach ($types->getIterator() as $type) { - if (!$type->hasPrimitiveParent() || - null !== $type->getLocalProperties()->getProperty(PHPFHIR_VALUE_PROPERTY_NAME)) { - continue; - } - $logger->warning( - sprintf( - 'Type "%s" extends primitive "%s" but is missing "%s" property. Adding...', - $type->getFHIRName(), - $type->getParentType()->getFHIRName(), - PHPFHIR_VALUE_PROPERTY_NAME - ) - ); - $property = new Property($type, $type->getSourceSXE(), $type->getSourceFilename()); - $property->setName(PHPFHIR_VALUE_PROPERTY_NAME); - $type->getLocalProperties()->addProperty($property); - } - } - /** * @param \DCarbone\PHPFHIR\Config\VersionConfig $config * @param \DCarbone\PHPFHIR\Definition\Types $types diff --git a/src/Definition/TypeImports.php b/src/Definition/TypeImports.php index 0bd9ba63..af876d2a 100644 --- a/src/Definition/TypeImports.php +++ b/src/Definition/TypeImports.php @@ -221,15 +221,15 @@ private function buildImports(): void $this->addImport($this->type->getClassName(), $this->type->getFullyQualifiedNamespace(false)); $typeNS = $this->type->getFullyQualifiedNamespace(false); - $configNS = $this->type->getConfig()->getNamespace(false); + $configNS = $this->type->getConfig()->getFullyQualifiedName(false); $sortedProperties = $this->type->getAllPropertiesIterator(); - // always add xml serializable interface and root config to all non-abstract types + // non-abstract types must import config and xml writer if (!$this->type->isAbstract()) { $this->addImport(PHPFHIR_CLASSNAME_CONFIG, $configNS); - $this->addImport(PHPFHIR_INTERFACE_XML_SERIALIZABLE, $configNS); - $this->addImport(PHPFHIR_INTERFACE_XML_SERIALIZALE_CONFIG, $configNS); + $this->addImport(PHPFHIR_CLASSNAME_XML_WRITER, $configNS); + $this->addImport(PHPFHIR_ENUM_CONFIG_KEY, $configNS); } // if this type is in a nested namespace, there are a few base interfaces, classes, and traits diff --git a/src/Utilities/FileUtils.php b/src/Utilities/FileUtils.php index f3a786c8..f14efc64 100644 --- a/src/Utilities/FileUtils.php +++ b/src/Utilities/FileUtils.php @@ -108,7 +108,7 @@ public static function buildAutoloaderRelativeFilepath(VersionConfig $config, Ty { return ltrim( str_replace( - [$config->getNamespace(false), '\\'], + [$config->getFullyQualifiedName(false), '\\'], ['', '/'], $type->getFullyQualifiedClassName(false) ), diff --git a/src/Utilities/TypeHintUtils.php b/src/Utilities/TypeHintUtils.php index 4aaa743e..7a52f546 100644 --- a/src/Utilities/TypeHintUtils.php +++ b/src/Utilities/TypeHintUtils.php @@ -174,7 +174,7 @@ public static function typeTypeDoc(VersionConfig $config, Type $type, bool $null if ($tk->isOneOf(TypeKind::RESOURCE_INLINE, TypeKind::RESOURCE_CONTAINER)) { array_push( $types, - sprintf('\\%s\\', trim($config->getNamespace(true), '\\')), + sprintf('\\%s\\', trim($config->getFullyQualifiedName(true), '\\')), PHPFHIR_INTERFACE_CONTAINED_TYPE, ); } else { diff --git a/template/core/classes/class_autoloader.php b/template/core/classes/class_autoloader.php index 79cda867..76edf0ef 100644 --- a/template/core/classes/class_autoloader.php +++ b/template/core/classes/class_autoloader.php @@ -22,8 +22,7 @@ /** @var \DCarbone\PHPFHIR\Config\VersionConfig $config */ /** @var \DCarbone\PHPFHIR\Definition\Types $types */ -$namespace = $config->getNamespace(false); -$nsPrefix = "{$namespace}\\"; +$namespace = $config->getFullyQualifiedName(false); ob_start(); @@ -38,71 +37,73 @@ echo "\n\n"; ?> // if this class is used, assume not using Composer... +// constants +if (!class_exists('getFullyQualifiedName(true, PHPFHIR_CLASSNAME_CONSTANTS); ?>', false)) { + require __DIR__ . DIRECTORY_SEPARATOR . '.php'; +} + +// config +if (!enum_exists('getFullyQualifiedName(true, PHPFHIR_ENUM_CONFIG_KEY); ?>', false)) { + require __DIR__ . DIRECTORY_SEPARATOR . '.php'; +} +if (!class_exists('getFullyQualifiedName(true, PHPFHIR_CLASSNAME_CONFIG); ?>', false)) { + require __DIR__ . DIRECTORY_SEPARATOR . '.php'; +} + +// xml writer +if (!class_exists('getFullyQualifiedName(true, PHPFHIR_CLASSNAME_XML_WRITER); ?>', false)) { + require __DIR__ . DIRECTORY_SEPARATOR . '.php'; +} + // interfaces -if (!interface_exists('\', false)) { +if (!interface_exists('getFullyQualifiedName(true, PHPFHIR_INTERFACE_TYPE); ?>', false)) { require __DIR__ . DIRECTORY_SEPARATOR . '.php'; } -if (!interface_exists('\', false)) { +if (!interface_exists('getFullyQualifiedName(true, PHPFHIR_INTERFACE_PRIMITIVE_TYPE); ?>', false)) { require __DIR__ . DIRECTORY_SEPARATOR . '.php'; } -if (!interface_exists('\', false)) { +if (!interface_exists('getFullyQualifiedName(true, PHPFHIR_INTERFACE_CONTAINED_TYPE); ?>', false)) { require __DIR__ . DIRECTORY_SEPARATOR . '.php'; } -if (!interface_exists('\', false)) { +if (!interface_exists('getFullyQualifiedName(true, PHPFHIR_INTERFACE_COMMENT_CONTAINER); ?>', false)) { require __DIR__ . DIRECTORY_SEPARATOR . '.php'; } -if (!interface_exists('\', false)) { - require __DIR__ . DIRECTORY_SEPARATOR . '.php'; -} -if (!interface_exists('\', false)) { - require __DIR__ . DIRECTORY_SEPARATOR . '.php'; -} // traits -if (!trait_exists('\', false)) { +if (!trait_exists('getFullyQualifiedName(true, PHPFHIR_TRAIT_COMMENT_CONTAINER); ?>', false)) { require __DIR__ . DIRECTORY_SEPARATOR . '.php'; } -if (!trait_exists('\', false)) { +if (!trait_exists('getFullyQualifiedName(true, PHPFHIR_TRAIT_VALIDATION_ASSERTIONS); ?>', false)) { require __DIR__ . DIRECTORY_SEPARATOR . '.php'; } -if (!trait_exists('\', false)) { +if (!trait_exists('getFullyQualifiedName(true, PHPFHIR_TRAIT_CHANGE_TRACKING); ?>', false)) { require __DIR__ . DIRECTORY_SEPARATOR . '.php'; } -if (!trait_exists('\', false)) { - require __DIR__ . DIRECTORY_SEPARATOR . '.php'; -} -if (!trait_exists('\', false)) { - require __DIR__ . DIRECTORY_SEPARATOR . '.php'; +if (!trait_exists('getFullyQualifiedName(true, PHPFHIR_TRAIT_SOURCE_XMLNS); ?>', false)) { + require __DIR__ . DIRECTORY_SEPARATOR . '.php'; } // enums -if (!enum_exists('\', false)) { - require __DIR__ . DIRECTORY_SEPARATOR . '.php'; -} -if (!enum_exists('\', false)) { +if (!enum_exists('getFullyQualifiedName(true, PHPFHIR_ENUM_TYPE); ?>', false)) { require __DIR__ . DIRECTORY_SEPARATOR . '.php'; } -if (!enum_exists('\', false)) { +if (!enum_exists('getFullyQualifiedName(true, PHPFHIR_ENUM_API_FORMAT); ?>', false)) { require __DIR__ . DIRECTORY_SEPARATOR . '.php'; } -// classes -if (!class_exists('\', false)) { - require __DIR__ . DIRECTORY_SEPARATOR . '.php'; -} -if (!class_exists('\', false)) { +// parser classes +if (!class_exists('getFullyQualifiedName(true, PHPFHIR_CLASSNAME_TYPEMAP); ?>', false)) { require __DIR__ . DIRECTORY_SEPARATOR . '.php'; } -if (!class_exists('\', false)) { - require __DIR__ . DIRECTORY_SEPARATOR . '.php'; -} -if (!class_exists('\', false)) { +if (!class_exists('getFullyQualifiedName(true, PHPFHIR_CLASSNAME_RESPONSE_PARSER); ?>', false)) { require __DIR__ . DIRECTORY_SEPARATOR . '.php'; } -if (!class_exists('\', false)) { + +// debug client +if (!class_exists('getFullyQualifiedName(true, PHPFHIR_CLASSNAME_DEBUG_CLIENT_RESPONSE); ?>', false)) { require __DIR__ . DIRECTORY_SEPARATOR . '.php'; } -if (!class_exists('\', false)) { +if (!class_exists('getFullyQualifiedName(true, PHPFHIR_CLASSNAME_DEBUG_CLIENT); ?>', false)) { require __DIR__ . DIRECTORY_SEPARATOR . '.php'; } diff --git a/template/core/classes/class_config.php b/template/core/classes/class_config.php index 642c89ae..90c49a8d 100644 --- a/template/core/classes/class_config.php +++ b/template/core/classes/class_config.php @@ -21,7 +21,7 @@ /** @var \DCarbone\PHPFHIR\Config\VersionConfig $config */ /** @var \DCarbone\PHPFHIR\Definition\Types $types */ -$namespace = $config->getNamespace(false); +$namespace = $config->getFullyQualifiedName(false); ob_start(); @@ -41,13 +41,20 @@ */ -class implements , \JsonSerializable +class implements \JsonSerializable { - use ; - /** @var bool */ private bool $registerAutoloader = false; + /** @var int */ + private int $libxmlOpts; + + /** @var string */ + private string $rootXmlns; + + /** @var bool */ + private bool $overrideSourceXmlns; + /** * Constructor * @param array $config @@ -64,7 +71,7 @@ public function __construct(array $config = []) /** * Set arbitrary key on this config * - * @param \|string $key + * @param getFullyQualifiedName(true, PHPFHIR_ENUM_CONFIG_KEY); ?>|string $key * @param mixed $value * @return static */ @@ -95,14 +102,76 @@ public function getRegisterAutoloader(): bool return $this->registerAutoloader; } + /** + * Sets the option flags to provide to libxml when unserializing XML + * + * @param int $libxmlOpts + * @return static + */ + public function setLibxmlOpts(int $libxmlOpts): self + { + $this->libxmlOpts = $libxmlOpts; + return $this; + } + + /** + * Returns set libxml option flags + * + * @return int + */ + public function getLibxmlOpts(): int + { + return $this->libxmlOpts ?? ::DEFAULT_LIBXML_OPTS; + } + + /** + * Default root xmlns to use. + * + * @param string $rootXmlns + * @return static + */ + public function setRootXmlns(string $rootXmlns): self + { + $this->rootXmlns = $rootXmlns; + return $this; + } + + /** + * @return null|string + */ + public function getRootXmlns(): null|string + { + return $this->rootXmlns ?? null; + } + + /** + * If true, overrides the xmlns entry found at the root of a source document, if there was one. + * + * @param bool $overrideSourceXmlns + * @return static + */ + public function setOverrideSourceXmlns(bool $overrideSourceXmlns): self + { + $this->overrideSourceXmlns = $overrideSourceXmlns; + return $this; + } + + /** + * @return bool + */ + public function getOverrideSourceXmlns(): bool + { + return $this->overrideSourceXmlns ?? false; + } + /** * @return \stdClass */ public function jsonSerialize(): \stdClass { $out = new \stdClass(); - foreach(::values() as $k => $_) { - $out->{$k} = $this->{'get'.$k}(); + foreach(::cases() as $key) { + $out->{$k} = $this->{$key->getter()}(); } return $out; } diff --git a/template/core/classes/class_constants.php b/template/core/classes/class_constants.php index 4fb976e3..d23f447e 100644 --- a/template/core/classes/class_constants.php +++ b/template/core/classes/class_constants.php @@ -21,7 +21,7 @@ /** @var \DCarbone\PHPFHIR\Config\VersionConfig $config */ /** @var \DCarbone\PHPFHIR\Definition\Types $types */ -$namespace = $config->getNamespace(false); +$namespace = $config->getFullyQualifiedName(false); ob_start(); @@ -42,7 +42,7 @@ */ -abstract class +final class { // FHIR source @@ -52,6 +52,10 @@ abstract class // PHPFHIR public const CODE_GENERATION_DATE = ''; + // Config Defaults + public const DEFAULT_LIBXML_OPTS = LIBXML_NONET | LIBXML_BIGLINES | LIBXML_PARSEHUGE | LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NOXMLDECL; + public const DEFAULT_XMLNS = 'https://hl7.org/fhir'; + // Common public const JSON_FIELD_RESOURCE_TYPE = 'resourceType'; public const JSON_FIELD_FHIR_COMMENTS = 'fhir_comments'; diff --git a/template/core/classes/class_debug_client.php b/template/core/classes/class_debug_client.php index 2de007d9..64cc3dff 100644 --- a/template/core/classes/class_debug_client.php +++ b/template/core/classes/class_debug_client.php @@ -21,7 +21,7 @@ /** @var \DCarbone\PHPFHIR\Config\VersionConfig $config */ /** @var \DCarbone\PHPFHIR\Definition\Types $types */ -$namespace = $config->getNamespace(false); +$namespace = $config->getFullyQualifiedName(false); ob_start(); @@ -72,6 +72,22 @@ public function __construct(string $baseUrl, array $curlOpts = [], ) $this->_curlOpts = $curlOpts; } + /** + * @return string + */ + public function _getBaseUrl(): string + { + return $this->_baseUrl; + } + + /** + * @return array + */ + public function _getBaseCurlOpts(): array + { + return $this->_curlOpts; + } + /** * @param string $path * @param array $queryParams diff --git a/template/core/classes/class_debug_client_response.php b/template/core/classes/class_debug_client_response.php index 85ebe446..58bfed0e 100644 --- a/template/core/classes/class_debug_client_response.php +++ b/template/core/classes/class_debug_client_response.php @@ -21,7 +21,7 @@ /** @var \DCarbone\PHPFHIR\Config\VersionConfig $config */ /** @var \DCarbone\PHPFHIR\Definition\Types $types */ -$namespace = $config->getNamespace(false); +$namespace = $config->getFullyQualifiedName(false); ob_start(); diff --git a/template/core/classes/class_response_parser.php b/template/core/classes/class_response_parser.php index 1d3a0e2b..85a739b5 100644 --- a/template/core/classes/class_response_parser.php +++ b/template/core/classes/class_response_parser.php @@ -21,7 +21,7 @@ /** @var \DCarbone\PHPFHIR\Config\VersionConfig $config */ /** @var \DCarbone\PHPFHIR\Definition\Types $types */ -$namespace = $config->getNamespace(false); +$namespace = $config->getFullyQualifiedName(false); ob_start(); @@ -143,7 +143,18 @@ public function parseStdClass(\stdClass $input): null| { - return $this->parseDOMDocument(dom_import_simplexml($input)->ownerDocument); + $elementName = $input->getName(); + $className = ::getTypeClass($elementName); + /** @var \ $fhirType */ + $fhirType = ::getTypeClass($elementName); + if (null === $fhirType) { + throw new \UnexpectedValueException(sprintf( + 'Unable to locate FHIR type for root XML element "%s". Input seen: %s', + $elementName, + $this->getPrintableStringInput($input->saveXML()) + )); + } + return $fhirType::xmlUnserialize($input, $this->config); } /** @@ -154,17 +165,7 @@ public function parseSimpleXMLElement(\SimpleXMLElement $input): null| { - $elementName = $input->documentElement->nodeName; - /** @var \ $fhirType */ - $fhirType = ::getTypeClass($elementName); - if (null === $fhirType) { - throw new \UnexpectedValueException(sprintf( - 'Unable to locate FHIR type for root XML element "%s". Input seen: %s', - $elementName, - $this->getPrintableStringInput($input->saveXML()) - )); - } - return $fhirType::xmlUnserialize($input->documentElement, $this->config); + return $this->parseSimpleXMLElement(simplexml_import_dom($input)); } /** @@ -193,12 +194,11 @@ public function parseXml(string $input): null|config->newDOMDocment(); - $dom->loadXML($input, $this->config->getLibxmlOpts()); + $sxe = new \SimpleXMLElement($input, $this->config->getLibxmlOpts(); $err = libxml_get_last_error(); libxml_use_internal_errors(false); if (false === $err) { - return $this->parseDOMDocument($dom); + return $this->parseSimpleXMLElement($dom); } throw new \DomainException(sprintf( 'Unable to parse provided input as XML. Error: %s; Input: %s', diff --git a/template/core/classes/class_type_map.php b/template/core/classes/class_type_map.php index 5782187b..a62055d2 100644 --- a/template/core/classes/class_type_map.php +++ b/template/core/classes/class_type_map.php @@ -22,7 +22,7 @@ /** @var \DCarbone\PHPFHIR\Config\VersionConfig $config */ /** @var \DCarbone\PHPFHIR\Definition\Types $types */ -$namespace = $config->getNamespace(false); +$namespace = $config->getFullyQualifiedName(false); $containerType = $types->getContainerType($config->getVersion()->getName()); if (null === $containerType) { @@ -121,18 +121,18 @@ public static function getContainedTypeClassName(string $typeName): null|string /** * Will attempt to determine if the provided value is or describes a containable resource type - * @param string|array|\DOMElement| $type + * @param string|array|\SimpleXMLElement| $type * @return bool * @throws \InvalidArgumentException */ - public static function isContainableResource(string|array|\DOMElement| $type): bool + public static function isContainableResource(string|array|\SimpleXMLElement| $type): bool { $tt = gettype($type); if ('object' === $tt) { if ($type instanceof ) { return in_array('\\' . get_class($type), self::CONTAINABLE_TYPES, true); } - return isset(self::CONTAINABLE_TYPES[$type->nodeName]); + return isset(self::CONTAINABLE_TYPES[$type->getName()]); } if ('string' === $tt) { return isset(self::CONTAINABLE_TYPES[$type]) || in_array('\\' . ltrim($type, '\\'), self::CONTAINABLE_TYPES, true); @@ -144,26 +144,29 @@ public static function isContainableResource(string|array|\DOMElement||null + * @param \SimpleXMLElement $node Parent element containing inline resource + * @param getFullyQualifiedName(true, PHPFHIR_CLASSNAME_CONFIG); ?> $config + * @return null|getfullyQualifiedName(true, PHPFHIR_INTERFACE_CONTAINED_TYPE); ?> + */ - public static function getContainedTypeFromXML(\DOMElement $node): ? + public static function getContainedTypeFromXML(\SimpleXMLElement $node, $config): null| { - $typeName = $node->nodeName; + $typeName = $node->getName(); $className = self::getContainedTypeClassName($typeName); if (null === $className) { throw self::createdInvalidContainedTypeException($typeName); } /** @var \ $className */ - return $className::xmlUnserialize($node); + return $className::xmlUnserialize($node, null, $config); } /** * @param array|null $data - * @return \|null + * @return null|getfullyQualifiedName(true, PHPFHIR_INTERFACE_CONTAINED_TYPE); ?> + */ - public static function getContainedTypeFromArray(null|array $data): ? + public static function getContainedTypeFromArray(null|array $data): null| { if (null === $data || [] === $data) { diff --git a/template/core/classes/class_xml_writer.php b/template/core/classes/class_xml_writer.php new file mode 100644 index 00000000..2384765c --- /dev/null +++ b/template/core/classes/class_xml_writer.php @@ -0,0 +1,172 @@ +getFullyQualifiedName(false); + +ob_start(); + +echo " +/** + * Fhir Xml Writer + * + * Class + + * @package \ + + + */ +class extends \XMLWriter +{ + private const MEM = 'memory'; + + /** @var bool */ + private bool $_docStarted = false; + /** @var bool */ + private bool $_rootOpen = false; + /** @var null|string */ + private null|string $_open = null; + + /** + * @see https://www.php.net/manual/en/xmlwriter.openmemory.php + * + * @return bool + */ + public function openMemory(): bool + { + $this->_open = self::MEM; + return parent::openMemory(); + } + + /** + * @see https://www.php.net/manual/en/xmlwriter.openuri.php + * + * @param string $uri + * @return bool + */ + public function openUri(string $uri): bool + { + $this->_open = $uri; + return parent::openUri($uri); + } + + /** + * @return bool + */ + public function isOpen(): bool + { + return null !== $this->_open; + } + + /** + * Returns the destination of writes made by this class. Value will be "null" if not opened, "memory" if writing + * opened with "openMemory()", or the $uri provided to "openUri()" + * + * @return null|string + */ + public function getWriteDestination(): null|string + { + return $this->_open; + } + + /** + * Used to track whether the document has been started + * + * @return bool + */ + public function isDocStarted(): bool + { + return $this->_docStarted; + } + + /** + * @see https://www.php.net/manual/en/xmlwriter.startdocument.php + * + * @param null|string $version + * @param null|string $encoding + * @param null|string $standalone + * @return bool + */ + public function startDocument(null|string $version = '1.0', null|string $encoding = 'UTF-8', null|string $standalone = 'yes'): bool + { + $this->_docStarted = true; + return parent::startDocument($version, $encoding, $standalone); + } + + /** + * @see https://www.php.net/manual/en/xmlwriter.startattribute.php + * + * @param string $name Attribute name + * @param string $value Attribute value + * @return bool + */ + public function writeAttribute(string $name, string $value): bool + { + return $this->startAttribute($name) + && $this->text($value) + && $this->endAttribute(); + } + + /** + * @return bool + */ + public function isRootOpen(): bool + { + return $this->_rootOpen; + } + + /** + * @param getFullyQualifiedName(true, PHPFHIR_CLASSNAME_CONFIG); ?> $config + * @param string $name + * @param string|null $sourceXmlns + * @return bool + */ + public function openRootNode( $config, string $name, null|string $sourceXmlns): bool + { + $ok = $this->startElement($name); + if (!$ok) { + return false; + } + if ($config->getOverrideSourceXmlns() || null === $sourceXmlns) { + $ns = (string)$config->getRootXmlns(); + } else { + $ns = $sourceXmlns; + } + if ('' !== $ns) { + $ok = $this->writeAttribute('xmlns', $ns); + if (!$ok) { + return false; + } + } + $this->_rootOpen = true; + return true; + } +} +getNamespace(false); +$namespace = $config->getFullyQualifiedName(false); ob_start(); diff --git a/template/core/enums/enum_config_key.php b/template/core/enums/enum_config_key.php index 04e5884b..057f1ae5 100644 --- a/template/core/enums/enum_config_key.php +++ b/template/core/enums/enum_config_key.php @@ -21,7 +21,7 @@ /** @var \DCarbone\PHPFHIR\Config\VersionConfig $config */ /** @var \DCarbone\PHPFHIR\Definition\Types $types */ -$namespace = $config->getNamespace(false); +$namespace = $config->getFullyQualifiedName(false); ob_start(); @@ -45,10 +45,24 @@ enum : string { case REGISTER_AUTOLOADER = 'registerAutoloader'; case LIBXML_OPTS = 'libxmlOpts'; - case DOM_VERSION = 'domVersion'; - case XML_ENCODING = 'encoding'; - case PRESERVE_WHITESPACE = 'preserveWhitespace'; - case FORMAT_OUTPUT = 'formatOutput'; + case ROOT_XMLNS = 'rootXmlns'; + case OVERRIDE_SOURCE_XMLNS = 'overrideSourceXmlns'; + + /** + * @return string + */ + public function setter(): string + { + return 'set' . $this->value; + } + + /** + * @return string + */ + public function getter(): string + { + return 'get' . $this->value; + } /** * @return array diff --git a/template/core/enums/enum_type.php b/template/core/enums/enum_type.php index 35fa0804..4eea013c 100644 --- a/template/core/enums/enum_type.php +++ b/template/core/enums/enum_type.php @@ -21,7 +21,7 @@ /** @var \DCarbone\PHPFHIR\Config\VersionConfig $config */ /** @var \DCarbone\PHPFHIR\Definition\Types $types */ -$namespace = $config->getNamespace(false); +$namespace = $config->getFullyQualifiedName(false); ob_start(); diff --git a/template/core/interfaces/interface_comment_container.php b/template/core/interfaces/interface_comment_container.php index 05ffc39a..031980e3 100644 --- a/template/core/interfaces/interface_comment_container.php +++ b/template/core/interfaces/interface_comment_container.php @@ -20,7 +20,7 @@ /** @var \DCarbone\PHPFHIR\Config\VersionConfig $config */ -$namespace = $config->getNamespace(false); +$namespace = $config->getFullyQualifiedName(false); ob_start(); diff --git a/template/core/interfaces/interface_contained_type.php b/template/core/interfaces/interface_contained_type.php index 01786b2c..67ff7d04 100644 --- a/template/core/interfaces/interface_contained_type.php +++ b/template/core/interfaces/interface_contained_type.php @@ -22,7 +22,7 @@ /** @var \DCarbone\PHPFHIR\Config\VersionConfig $config */ /** @var \DCarbone\PHPFHIR\Definition\Types $types */ -$namespace = $config->getNamespace(false); +$namespace = $config->getFullyQualifiedName(false); $containerType = $types->getContainerType($config->getVersion()->getName()); if (null === $containerType) { diff --git a/template/core/interfaces/interface_primitive_type.php b/template/core/interfaces/interface_primitive_type.php index 0ff59992..8f748eff 100644 --- a/template/core/interfaces/interface_primitive_type.php +++ b/template/core/interfaces/interface_primitive_type.php @@ -21,7 +21,7 @@ /** @var \DCarbone\PHPFHIR\Config\VersionConfig $config */ /** @var \DCarbone\PHPFHIR\Definition\Types $types */ -$namespace = $config->getNamespace(false); +$namespace = $config->getFullyQualifiedName(false); ob_start(); diff --git a/template/core/interfaces/interface_type.php b/template/core/interfaces/interface_type.php index 904198d8..71c1195c 100644 --- a/template/core/interfaces/interface_type.php +++ b/template/core/interfaces/interface_type.php @@ -20,7 +20,7 @@ /** @var \DCarbone\PHPFHIR\Config\VersionConfig $config */ -$namespace = $config->getNamespace(false); +$namespace = $config->getFullyQualifiedName(false); ob_start(); @@ -41,13 +41,21 @@ */ -interface extends , \JsonSerializable +interface extends \JsonSerializable { /** * Returns the FHIR name represented by this Type * @return string */ - public function _getFHIRTypeName(): string; + public function _getFhirTypeName(): string; + + /** + * Returns the root Xmlns value found in the source. Null indicates no "xmlns" was found. Only defined when + * unserializing XML, and only used when serializing XML. + * + * @return null|string + */ + public function _getSourceXmlns(): null|string; /** * Must return an associative array in structure ["field" => ["rule" => {constraint}]] to be used during validation @@ -68,6 +76,22 @@ public function _getValidationErrors(): array; */ public function _isValued(): bool; + /** + * @param null|string|\SimpleXMLElement $element + * @param null|static $type + * @param null|int|getFullyQualifiedName(true, PHPFHIR_CLASSNAME_CONFIG); ?> $config PHP FHIR config. Supports an integer value interpreted as libxml opts for backwards compatibility. + * @return null|static + */ + public static function xmlUnserialize(null|string|\SimpleXMLElement $element, $type = null, null|int| $config = null): null|self; + + /** + * @param null|getFullyQualifiedName(true, PHPFHIR_CLASSNAME_XML_WRITER); ?> $xw + * @param null|int|getFullyQualifiedName(true, PHPFHIR_CLASSNAME_CONFIG); ?> $config PHP FHIR config. Supports an integer value interpreted as libxml opts for backwards compatibility. + * @return getFullyQualifiedName(true, PHPFHIR_CLASSNAME_XML_WRITER); ?> + + */ + public function xmlSerialize(null| $xw = null, null|int| $config = null): ; + /** * @return string */ diff --git a/template/core/interfaces/interface_xml_serializable.php b/template/core/interfaces/interface_xml_serializable.php deleted file mode 100644 index 8c68e693..00000000 --- a/template/core/interfaces/interface_xml_serializable.php +++ /dev/null @@ -1,83 +0,0 @@ -getNamespace(false); - -ob_start(); - -echo " -/** - * Interface - - * @package \ - - - */ -interface - -{ - /** - * Returns the xml namespace to use for this type when serializing to XML, if applicable. - * @return string - */ - public function _getFHIRXMLNamespace(): string; - - /** - * Set the XML Namespace to be output when serializing this type to XML - * @param string $xmlNamespace - * @return static - */ - public function _setFHIRXMLNamespace(string $xmlNamespace): self; - - /** - * Returns the base xml element definition for this type - * - * @param string $elementName Name of the root element - * @return string - */ - public function _getFHIRXMLElementDefinition(string $elementName): string; - - /** - * @param null|string|\DOMElement $element - * @param null|static $type - * @param null|int|\ $config XML serialization config. Supports an integer value interpreted as libxml opts for backwards compatibility. - * @return null|static - */ - public static function xmlUnserialize(null|string|\DOMElement $element, $type = null, null|int| $config = null): null|self; - - /** - * @param null|\DOMElement $element - * @param null|int|\ $config XML serialization config. Supports an integer value interpreted as libxml opts for backwards compatibility. - * @return \DOMElement - */ - public function xmlSerialize(null|\DOMElement $element = null, null|int| $config = null): \DOMElement; -} -getNamespace(false); - -ob_start(); - -echo " -/** - * Interface - - * @package \ - - - */ -interface - -{ - public const DEFAULT_LIBXML_OPTS = LIBXML_NONET | LIBXML_PARSEHUGE | LIBXML_COMPACT; - public const DEFAULT_DOM_VERSION = '1.0'; - public const DEFAULT_ENCODING = 'UTF-8'; - public const DEFAULT_PRESERVE_WHITESPACE = true; - public const DEFAULT_FORMAT_OUTPUT = false; - - /** - * Must construct a new \DOMDocument instance based on current configuration - * - * @return \DOMDocument - */ - public function newDOMDocument(): \DOMDocument; - - /** - * Sets the option flags to provide to libxml when serializing and unserializing XML - * - * @param int $libxmlOpts - * @return static - */ - public function setLibxmlOpts(int $libxmlOpts): self; - - /** - * Must return the set libxml option flags - * - * @return int - */ - public function getLibxmlOpts(): int; - - /** - * @param string $domVersion - * @return static - */ - public function setDOMVersion(string $domVersion): self; - - /** - * @return string - */ - public function getDOMVersion(): string; - - /** - * @param string $encoding - * @return static - */ - public function setEncoding(string $encoding): self; - - /** - * @return string - */ - public function getEncoding(): string; - - /** - * Sets whether or not to preserve whitespace when rendering XML - * - * @param bool $preserveWhitespace - * @return static - */ - public function setPreserveWhitespace(bool $preserveWhitespace): self; - - /** - * @return bool - */ - public function getPreserveWhitespace(): bool; - - /** - * @param bool $formatOutput - * @return static - */ - public function setFormatOutput(bool $formatOutput): self; - - /** - * @return bool - */ - public function getFormatOutput(): bool; -} -getNamespace(false); -$testNS = $config->getTestsNamespace(TestType::BASE, false); +$rootNS = $config->getFullyQualifiedName(false); +$testNS = $config->getFullyQualifiedTestsName(TestType::BASE, false); ob_start(); echo " diff --git a/template/core/tests/test_class_type_map.php b/template/core/tests/test_class_type_map.php index cf978bdd..cb440c86 100644 --- a/template/core/tests/test_class_type_map.php +++ b/template/core/tests/test_class_type_map.php @@ -22,8 +22,8 @@ /** @var \DCarbone\PHPFHIR\Config\VersionConfig $config */ /** @var \DCarbone\PHPFHIR\Definition\Types $types */ -$rootNS = $config->getNamespace(false); -$testNS = $config->getTestsNamespace(TestType::BASE, false); +$rootNS = $config->getFullyQualifiedName(false); +$testNS = $config->getFullyQualifiedTestsName(TestType::BASE, false); ob_start(); echo " diff --git a/template/core/traits/trait_change_tracking.php b/template/core/traits/trait_change_tracking.php index b7014cbd..8c8828d2 100644 --- a/template/core/traits/trait_change_tracking.php +++ b/template/core/traits/trait_change_tracking.php @@ -20,7 +20,7 @@ /** @var \DCarbone\PHPFHIR\Config\VersionConfig $config */ -$rootNS = $config->getNamespace(false); +$rootNS = $config->getFullyQualifiedName(false); ob_start(); echo "getNamespace(false); +$rootNS = $config->getFullyQualifiedName(false); ob_start(); echo "getFullyQualifiedName(false); + +ob_start(); +echo " +/** + * Trait + + * @package \ + + + */ +trait + +{ + /** @var string */ + private string $_sourceXmlns; + + /** + * @param string $xmlns + */ + protected function _setSourceXmlns(string $xmlns): void + { + $this->_sourceXmlns = $xmlns; + } + + /** + * @return null|string + */ + public function _getSourceXmlns(): null|string + { + return $this->_sourceXmlns ?? null; + } +} +getNamespace(false); +$rootNS = $config->getFullyQualifiedName(false); ob_start(); echo "getNamespace(false); - -ob_start(); -echo " - /** - * Trait - - * @package \ - - - */ -trait - -{ - /** @var string */ - protected string $_xmlns = ''; - - /** - * @param null|string $xmlNamespace - * @return static - */ - public function _setFHIRXMLNamespace(null|string $xmlNamespace): self - { - $this->_xmlns = trim((string)$xmlNamespace); - return $this; - } - - /** - * @return string - */ - public function _getFHIRXMLNamespace(): string - { - return $this->_xmlns; - } - - /** - * @param string $elementName Name to use for the element - * @return string - * @throws \InvalidArgumentException - */ - public function _getFHIRXMLElementDefinition(string $elementName): string - { - if ('' === $elementName) { - throw new \InvalidArgumentException(sprintf('%s::_getFHIRXMLElementDefinition - $elementName is required', get_called_class())); - } - $xmlns = $this->_getFHIRXMLNamespace(); - if ('' !== $xmlns) { - $xmlns = sprintf(' xmlns="%s"', $xmlns); - } - return sprintf('<%1$s%2$s>', $elementName, $xmlns); - } -} - -getNamespace(false); - -ob_start(); -echo " - /** - * Trait - - * @package \ - - - */ -trait - -{ - /** @var int */ - private int $libxmlOpts; - /** @var string */ - private string $domVersion; - /** @var string */ - private string $encoding; - /** @var bool */ - private bool $preserveWhitespace; - /** @var bool */ - private bool $formatOutput; - - /** - * @return \DOMDocument; - */ - public function newDOMDocument(): \DOMDocument - { - $dom = new \DOMDocument($this->getDOMVersion(), $this->getEncoding()); - $dom->preserveWhiteSpace = $this->getPreserveWhitespace(); - $dom->formatOutput = $this->getFormatOutput(); - return $dom; - } - - /** - * Sets the option flags to provide to libxml when serializing and unserializing XML - * - * @param int $libxmlOpts - * @return static - */ - public function setLibxmlOpts(int $libxmlOpts): self - { - $this->libxmlOpts = $libxmlOpts; - return $this; - } - - /** - * Returns set libxml option flags - * - * @return int - */ - public function getLibxmlOpts(): int - { - return $this->libxmlOpts ?? ::DEFAULT_LIBXML_OPTS; - } - - /** - * @param string $domVersion - * @return static - */ - public function setDOMVersion(string $domVersion): self - { - $this->domVersion = $domVersion; - return $this; - } - - /** - * @return string - */ - public function getDOMVersion(): string - { - return $this->domVersion ?? ::DEFAULT_DOM_VERSION; - } - - /** - * @param string $encoding - * @return static - */ - public function setEncoding(string $encoding): self - { - $this->encoding = $encoding; - return $this; - } - - /** - * @return string - */ - public function getEncoding(): string - { - return $this->encoding ?? ::DEFAULT_ENCODING; - } - - /** - * Sets whether or not to preserve whitespace when rendering XML - * - * @param bool $preserveWhitespace - * @return static - */ - public function setPreserveWhitespace(bool $preserveWhitespace): self - { - $this->preserveWhitespace = $preserveWhitespace; - return $this; - } - - /** - * @return bool - */ - public function getPreserveWhitespace(): bool - { - return $this->preserveWhitespace ?? ::DEFAULT_PRESERVE_WHITESPACE; - } - - /** - * @param bool $formatOutput - * @return static - */ - public function setFormatOutput(bool $formatOutput): self - { - $this->formatOutput = $formatOutput; - return $this; - } - - /** - * @return bool - */ - public function getFormatOutput(): bool - { - return $this->formatOutput ?? ::DEFAULT_FORMAT_OUTPUT; - } -} - */ -class getClassName(); ?> implements , \JsonSerializable +class getClassName(); ?> implements + { use , - ; - - const _NOISE_NODES = ['html', 'head', 'body']; + , + ; - /** @var null|\DOMElement */ - private null|\DOMElement $_node = null; + /** @var null|string */ + private null|string $_xhtml = null; /** * Constructor - * @param null|string|\DOMNode $node - * @param null|getNamespace(true); ?>\ $config + * @param null|string|\DOMNode|\SimpleXMLElement $xhtml + */ + public function __construct(null|string|\DOMNode|\SimpleXmlElement $xhtml = null) + { + $this->setXhtml($xhtml); + } + + /** + * @return string */ - public function __construct(null|string|\DOMNode $node = null, null| $config = null) + public function _getFhirTypeName(): string { - $this->setNode($node, $config); + return 'Xhtml'; } /** - * @return null|\DOMNode + * @return array */ - public function getNode(): null|\DOMNode + public function _getValidationRules(): array { - return $this->_node; + return []; } /** - * @param null|string|\DOMNode $node - * @param null|getNamespace(true); ?>\ $config + * @return array + */ + public function _getValidationErrors(): array + { + return []; + } + + /** + * @return null|string + */ + public function getXhtml(): null|string + { + return $this->_xhtml; + } + + /** + * @param null|string|\DOMNode|\SimpleXmlElement $xhtml * @return static */ - public function setNode(null|string|\DOMNode $node, null| $config = null): self + public function setXhtml(null|string|\DOMNode|\SimpleXMLElement $xhtml): self { - if (null === $node) { - $this->_trackValueSet($this->_node, null); - $this->_node = null; + if (null === $xhtml) { + $this->_trackValueSet($this->_xhtml, null); + $this->_xhtml = null; return $this; } + if ($xhtml instanceof \DOMDocument) { + $xhtml = $xhtml->saveXML($xhtml->documentElement); + } else if ($xhtml instanceof \DOMNode) { + $xhtml = $xhtml->ownerDocument->saveXML($xhtml); + } else if ($xhtml instanceof \SimpleXMLElement) { + $xhtml = $xhtml->asXML(); + } + $this->_trackValueSet($this->_xhtml, $xhtml); + $this->_xhtml = $xhtml; + return $this; + } + + /** + * @param null|getFullyQualifiedName(true, PHPFHIR_CLASSNAME_CONFIG); ?> $config + * @return null|\SimpleXMLElement + * @throws \Exception + */ + public function getSimpleXMLElement(null| $config = null): null|\SimpleXMLElement + { + $xhtml = $this->getXhtml(); + if (null === $xhtml) { + return null; + } if (null === $config) { $config = new (); } - $dom = $config->newDOMDocument(); - if (is_string($node)) { - $dom->loadHTML($node); - } else if ($node instanceof \DOMDocument) { - $dom->appendChild($dom->importNode($node->documentElement, true)); - } else { - $dom->appendChild($dom->importNode($node, true)); - } - $newNode = $dom->documentElement; - while (null !== $newNode) { - if (in_array(strtolower($newNode->nodeName), self::_NOISE_NODES, true)) { - $newNode = $newNode->firstChild; - } else { - break; - } - } - if ('' !== ($ens = (string)$newNode?->namespaceURI)) { - $this->_setFHIRXMLNamespace($ens); - } - $this->_trackValueSet($this->_node, $newNode); - $this->_node = $newNode; - return $this; + return new \SimpleXMLElement($xhtml, $config->getLibxmlOpts()); + } + + /** + * @param null|getFullyQualifiedName(true, PHPFHIR_CLASSNAME_CONFIG); ?> $config + * @return null|\DOMDocument + */ + public function getDOMDocument(null| $config = null): null|\DOMDocument + { + $xhtml = $this->getXhtml(); + if (null === $xhtml) { + return null; + } + if (null === $config) { + $config = new (); + } + $dom = new \DOMDocument('1.0', 'UTF-8'); + $dom->loadXML($xhtml, $config->getLibxmlOpts()); + return $dom; + } + + /** + * Returns open \XMLReader instance with content read + * + * @param null|getFullyQualifiedName(true, PHPFHIR_CLASSNAME_CONFIG); ?> $config + * @return null|\XMLReader + */ + public function getXMLReader(null| $config = null): null|\XMLReader + { + $xhtml = $this->getXhtml(); + if (null === $xhtml) { + return null; + } + if (null === $config) { + $config = new (); + } + $xr = \XMLReader::XML($xhtml, 'UTF-8', $config->getLibxmlOpts()); + $xr->read(); + return $xr; } - $type->setNode($element); + $type->setXhtml($element); return $type; } /** - * @param null|\DOMElement $element - * @param null|int|\ $config XML serialization config. Supports an integer value interpreted as libxml opts for backwards compatibility. - * @return \DOMElement + * @param null|getFullyQualifiedName(true, PHPFHIR_CLASSNAME_XML_WRITER); ?> $xw + * @param null|int|\ $config XML serialization config. Supports an integer value interpreted as libxml opts for backwards compatibility. + * @return getFullyQualifiedName(true, PHPFHIR_CLASSNAME_XML_WRITER); ?> + */ - public function xmlSerialize(\DOMElement $element = null, null|int| $config = null): \DOMElement + public function xmlSerialize(null| $xw = null, null|int| $config = null): + { if (is_int($config)) { - $libxmlOpts = $config; - $config = new (); + $config = new ([::LIBXML_OPTS->value => $config]); } else if (null === $config) { - $libxmlOpts = ::DEFAULT_LIBXML_OPTS; $config = new (); - } else { - $libxmlOpts = $config->getLibxmlOpts(); } - if (null === $element) { - $dom = $config->newDOMDocument(); - $dom->loadXML($this->_getFHIRXMLElementDefinition(''), $libxmlOpts); - $element = $dom->documentElement; - } else if ('' !== ($ns = $this->_getFHIRXMLNamespace())) { - $element->setAttribute('xmlns', $ns); + if (null === $xw) { + $xw = new (); + } + if (!$xw->isOpen()) { + $xw->openMemory(); + } + if (!$xw->isDocStarted()) { + $docStarted = true; + $xw->startDocument(); } - $node = $this->getNode(); - if (null === $node) { - return $element; + if (!$xw->isRootOpen()) { + $rootOpened = true; + $xw->openRootNode($config, 'Xhtml', $this->_getSourceXmlns()); } - for ($i = 0; $i < $node->attributes->length; $i++) { - $attr = $node->attributes->item($i); - $element->setAttribute($attr->nodeName, $attr->nodeValue); + $xr = $this->getXMLReader($config); + if (null === $xr) { + return $xw; } - for ($i = 0; $i < $node->childNodes->length; $i++) { - $element->appendChild($element->ownerDocument->importNode($node->childNodes->item($i), true)); + while ($xr->moveToNextAttribute()) { + $xw->writeAttribute($xr->name, $xr->value); } - return $element; + $xw->writeRaw($xr->readInnerXml()); + if (isset($rootOpened) && $rootOpened) { + $xw->endElement(); + } + if (isset($docStarted) && $docStarted) { + $xw->endDocument(); + } + return $xw; } /** @@ -178,8 +248,11 @@ public function xmlSerialize(\DOMElement $element = null, null|int|getNode(); - return $node?->ownerDocument->saveXML($node); + $xhtml = $this->getXhtml(); + if (null === $xhtml) { + return null; + } + return (string)$xhtml; } /** @@ -187,6 +260,6 @@ public function jsonSerialize(): mixed */ public function __toString(): string { - return $this->jsonSerialize() ?? ''; + return (string)$this->getXhtml(); } }setValue($value); } elseif (is_array($value)) { parent::__construct($value); - if (isset($value[self::FIELD_VALUE])) { + if (array_key_exists(self::FIELD_VALUE, $value)) { $this->setValue($value[self::FIELD_VALUE]); } } else { diff --git a/template/types/methods/constructors/property_setter_default.php b/template/types/methods/constructors/property_setter_default.php index 8bb21132..05ecf69b 100644 --- a/template/types/methods/constructors/property_setter_default.php +++ b/template/types/methods/constructors/property_setter_default.php @@ -24,13 +24,10 @@ $setter = $property->getSetterName(); ob_start(); ?> - if (isset($data[self::])) { + if (array_key_exists(self::, $data)) { isCollection()) : ?> if (is_array($data[self::])) { foreach($data[self::] as $v) { - if (null === $v) { - continue; - } if ($v instanceof ) { $this->($v); } else { diff --git a/template/types/methods/constructors/property_setter_primitive.php b/template/types/methods/constructors/property_setter_primitive.php index e9fc8ef1..a51d9a63 100644 --- a/template/types/methods/constructors/property_setter_primitive.php +++ b/template/types/methods/constructors/property_setter_primitive.php @@ -22,13 +22,10 @@ $setter = $property->getSetterName(); ob_start(); ?> - if (isset($data[self::])) { + if (array_key_exists(self::, $data)) { isCollection()) : ?> if (is_array($data[self::])) { foreach($data[self::] as $v) { - if (null === $v) { - continue; - } $this->($v); } } else { diff --git a/template/types/methods/constructors/property_setter_primitive_container.php b/template/types/methods/constructors/property_setter_primitive_container.php index 9336b894..b1afb79f 100644 --- a/template/types/methods/constructors/property_setter_primitive_container.php +++ b/template/types/methods/constructors/property_setter_primitive_container.php @@ -27,7 +27,7 @@ // these types are a pain in the ass ob_start(); ?> - if (isset($data[self::]) || isset($data[self::])) { + if (array_key_exists(self::, $data) || array_key_exists(self::, $data)) { $value = $data[self::] ?? null; $ext = (isset($data[self::]) && is_array($data[self::])) ? $data[self::] : []; if (null !== $value) { @@ -59,6 +59,8 @@ $this->(new ($ext)); + } else { + $this->(new (null)); } } * - * @param null|getNamespace(true) . '\\' . PHPFHIR_INTERFACE_CONTAINED_TYPE; ?> $ + * @param null|getFullyQualifiedName(true) . '\\' . PHPFHIR_INTERFACE_CONTAINED_TYPE; ?> $ * @return static */ diff --git a/template/types/properties/methods/default/setter_contained_resource_collection.php b/template/types/properties/methods/default/setter_contained_resource_collection.php index a746befc..990617fa 100644 --- a/template/types/properties/methods/default/setter_contained_resource_collection.php +++ b/template/types/properties/methods/default/setter_contained_resource_collection.php @@ -38,7 +38,7 @@ * - * @param getNamespace(true) . '\\' . PHPFHIR_INTERFACE_CONTAINED_TYPE; ?>[] $ + * @param getFullyQualifiedName(true) . '\\' . PHPFHIR_INTERFACE_CONTAINED_TYPE; ?>[] $ * @return static */ diff --git a/template/types/serialization/xml.php b/template/types/serialization/xml.php index 0023bb81..ac9fb460 100644 --- a/template/types/serialization/xml.php +++ b/template/types/serialization/xml.php @@ -92,6 +92,12 @@ ); } } ?> - return $element; + if (isset($openedRoot) && $openedRoot) { + $xw->endElement(); + } + if (isset($docStarted) && $docStarted) { + $xw->endDocument(); + } + return $xw; } getValueFHIRType()) { + if ($property->isCollection()) { + continue; + } + $pt = $property->getValueFHIRType(); + if (null === $pt) { echo require_with( - __DIR__ . DIRECTORY_SEPARATOR . 'body_typed.php', + __DIR__ . DIRECTORY_SEPARATOR . 'body_untyped.php', [ 'config' => $config, - 'property' => $property, ] ); - } elseif (0 < count($localProperties)) { + } else if ($pt->hasPrimitiveParent() || $pt->getKind() === TypeKind::PRIMITIVE) { echo require_with( - __DIR__ . DIRECTORY_SEPARATOR . 'body_untyped.php', + __DIR__ . DIRECTORY_SEPARATOR . 'body_typed.php', [ 'config' => $config, + 'property' => $property, ] ); } } + +if ($type->hasParentWithLocalProperties()) : ?> + parent::xmlSerialize($xw, $config); +getValueFHIRType(); + if (!$property->isCollection() && (null === $pt || $pt->hasPrimitiveParent() || $pt->getKind() === TypeKind::PRIMITIVE)) { + continue; + } + echo require_with( + __DIR__ . DIRECTORY_SEPARATOR . 'body_typed.php', + [ + 'config' => $config, + 'property' => $property, + ] + ); +} return ob_get_clean(); diff --git a/template/types/serialization/xml/serialize/body_typed.php b/template/types/serialization/xml/serialize/body_typed.php index 9b0017a8..d9484658 100644 --- a/template/types/serialization/xml/serialize/body_typed.php +++ b/template/types/serialization/xml/serialize/body_typed.php @@ -24,7 +24,7 @@ $propertyTypeKind = $property->getValueFHIRType()->getKind(); ob_start(); -if ($propertyTypeKind->isOneOf(TypeKind::RESOURCE_CONTAINER, TypeKind::RESOURCE_INLINE)) : +if ($propertyTypeKind->isOneOf(TypeKind::RESOURCE_CONTAINER, TypeKind::RESOURCE_INLINE)) { echo require_with( __DIR__ . DIRECTORY_SEPARATOR . 'body_typed_resource_container.php', [ @@ -32,7 +32,15 @@ 'property' => $property, ] ); -else : +} else if ($propertyTypeKind === TypeKind::PHPFHIR_XHTML) { + echo require_with( + __DIR__ . DIRECTORY_SEPARATOR . 'body_typed_xhtml.php', + [ + 'config' => $config, + 'property' => $property, + ] + ); +} else { echo require_with( __DIR__ . DIRECTORY_SEPARATOR . 'body_typed_default.php', [ @@ -40,5 +48,5 @@ 'property' => $property, ] ); -endif; +} return ob_get_clean(); \ No newline at end of file diff --git a/template/types/serialization/xml/serialize/body_typed_default.php b/template/types/serialization/xml/serialize/body_typed_default.php index 9c02b823..41bb9965 100644 --- a/template/types/serialization/xml/serialize/body_typed_default.php +++ b/template/types/serialization/xml/serialize/body_typed_default.php @@ -18,6 +18,7 @@ use DCarbone\PHPFHIR\Enum\TypeKind; +/** @var \DCarbone\PHPFHIR\Config\VersionConfig $config */ /** @var \DCarbone\PHPFHIR\Definition\Property $property */ $propertyType = $property->getValueFHIRType(); @@ -26,24 +27,19 @@ ob_start(); if ($property->isCollection()) : // collection fields ?> - if ([] !== ($vs = $this->())) { - foreach($vs as $v) { - if (null === $v) { - continue; - } - $telement = $element->ownerDocument->createElement(self::); - $element->appendChild($telement); - $v->xmlSerialize($telement); - } + foreach ($this->() as $v) { + $xw->startElement(self::); + $v->xmlSerialize($xw, $config); + $xw->endElement(); } if (null !== ($v = $this->())) { hasPrimitiveParent() || $propertyType->getKind() === TypeKind::PRIMITIVE) : ?> - $element->setAttribute(self::, (string)$v); + $xw->writeAttribute(self::, $v->getFormattedValue()); - $telement = $element->ownerDocument->createElement(self::); - $element->appendChild($telement); - $v->xmlSerialize($telement); + $xw->startElement(self::); + $v->xmlSerialize($xw, $config); + $xw->endElement(); } isCollection()) : ?> if ([] !== ($vs = $this->())) { foreach($vs as $v) { - if (null === $v) { - continue; - } - $e2 = $element->ownerDocument->createElement(self::); - $element->appendChild($e2); - $e3 = $element->ownerDocument->createElement($v->_getFHIRTypeName()); - $e2->appendChild($e3); - $v->xmlSerialize($e3); + $xw->startElement(self::); + $xw->startElement($v->_getFhirTypeName()); + $v->xmlSerialize($xw, $config); + $xw->endElement(); + $xw->endElement(); } } if (null !== ($v = $this->())) { - $e2 = $element->ownerDocument->createElement(self::); - $element->appendChild($e2); - $e3 = $element->ownerDocument->createElement($v->_getFHIRTypeName()); - $e2->appendChild($e3); - $v->xmlSerialize($e3); + $xw->startElement(self::); + $xw->startElement($v->_getFhirTypeName()); + $v->xmlSerialize($xw, $config); + $xw->endElement(); + $xw->endElement(); } getGetterName(); + +ob_start(); ?> + if (null !== ($v = $this->())) { + $xw->startElement(self::getFieldConstantName(); ?>); + $v->xmlSerialize($xw, $config); + $xw->endElement(); + } + - $element->setAttribute(self::FIELD_VALUE, (string)$this); + $xw->writeAttribute(self::FIELD_VALUE, $this->getFormattedValue()); getNamespace(false); +$namespace = $config->getFullyQualifiedName(false); $typeKind = $type->getKind(); $xmlName = NameUtils::getTypeXMLElementName($type); ob_start(); ?> /** - * @param null|\DOMElement $element - * @param null|int|\ $config XML serialization config. Supports an integer value interpreted as libxml opts for backwards compatibility. - * @return \DOMElementhasParent()) : ?> - - * @throws \DOMException + * @param null|getFullyQualifiedName(true, PHPFHIR_CLASSNAME_XML_WRITER); ?> $xw + * @param null|int|\ $config PHP FHIR config. Supports an integer value interpreted as libxml opts for backwards compatibility. + * @return getFullyQualifiedName(true, PHPFHIR_CLASSNAME_XML_WRITER); ?> */ - public function xmlSerialize(\DOMElement $element = null, null|int| $config = null): \DOMElement + public function xmlSerialize(null| $xw = null, null|int| $config = null): + { if (is_int($config)) { - $libxmlOpts = $config; - $config = new (); + $config = new ([::LIBXML_OPTS->value => $config]); } else if (null === $config) { - $libxmlOpts = ::DEFAULT_LIBXML_OPTS; $config = new (); - } else { - $libxmlOpts = $config->getLibxmlOpts(); } - if (null === $element) { - $dom = $config->newDOMDocument(); - $dom->loadXML($this->_getFHIRXMLElementDefinition(''), $libxmlOpts); - $element = $dom->documentElement; + if (null === $xw) { + $xw = new (); + } + if (!$xw->isOpen()) { + $xw->openMemory(); + } + if (!$xw->isDocStarted()) { + $docStarted = true; + $xw->startDocument(); + } + if (!$xw->isRootOpen()) { + $openedRoot = true; + $xw->openRootNode($config, '', $this->_getSourceXmlns()); } -hasParentWithLocalProperties()) : ?> - parent::xmlSerialize($element); -getNamespace(false); +$namespace = $config->getFullyQualifiedName(false); $localProperties = $type->getLocalProperties()->localPropertiesIterator(); ob_start(); ?> /** - * @param null|\DOMElement $element - * @param null|int|\ $config XML serialization config. Supports an integer value interpreted as libxml opts for backwards compatibility. - * @return \DOMElement + * @param null|getFullyQualifiedName(true, PHPFHIR_CLASSNAME_XML_WRITER); ?> $xw + * @param null|int|\ $config PHP FHIR config. Supports an integer value interpreted as libxml opts for backwards compatibility. + * @return getFullyQualifiedName(true, PHPFHIR_CLASSNAME_XML_WRITER); ?> + */ - public function xmlSerialize(null|\DOMElement $element = null, null|int| $config = null): \DOMElement + public function xmlSerialize(null| $xw = null, null|int| $config = null): + { if (is_int($config)) { - $libxmlOpts = $config; - $config = new (); + $config = new ([::LIBXML_OPTS->value => $config]); } else if (null === $config) { - $libxmlOpts = ::DEFAULT_LIBXML_OPTS; $config = new (); - } else { - $libxmlOpts = $config->getLibxmlOpts(); + } + if (null === $xw) { + $xw = new (); + } + if (!$xw->isOpen()) { + $xw->openMemory(); + } + if (!$xw->isDocStarted()) { + $docStarted = true; + $xw->startDocument(); } - if (null !== ($v = $this->getgetGetterName(); ?>())) { - return $v->xmlSerialize($element, $libxmlOpts); + if (null !== ($v = $this->getGetterName(); ?>())) { + return $v->xmlSerialize($xw, $config); } - if (null === $element) { - $dom = $config->newDOMDocument(); - $dom->loadXML($this->_getFHIRXMLElementDefinition(), $libxmlOpts); - $element = $dom->documentElement; + if (!$xw->isRootOpen()) { + $openedRoot = true; + $xw->openRootNode($config, '', $this->_getSourceXmlns()); } - for ($i = 0; $i < $element->childNodes->length; $i++) { - $n = $element->childNodes->item($i); - if (!($n instanceof \DOMElement)) { - continue; - } + foreach ($element->children() as $n) { + $childName = $n->getName(); $property) { if (null !== $property->getValueFHIRType()) { echo require_with( @@ -51,6 +48,7 @@ }; ?> } + $attributes = $element->attributes(); $property) { if (null !== $property->getValueFHIRType()) { echo require_with( diff --git a/template/types/serialization/xml/unserialize/body_parse_attr_primitive.php b/template/types/serialization/xml/unserialize/body_parse_attr_primitive.php index b7407aea..ddba335a 100644 --- a/template/types/serialization/xml/unserialize/body_parse_attr_primitive.php +++ b/template/types/serialization/xml/unserialize/body_parse_attr_primitive.php @@ -23,8 +23,7 @@ $propType = $property->getValueFHIRType(); ob_start(); ?> - $n = $element->attributes->getNamedItem(self::getFieldConstantName(); ?>); - if (null !== $n) { - $type->setValue($n->nodeValue); + if (isset($attributes[self::getFieldConstantName(); ?>])) { + $type->setValue((string)$attributes[self::getFieldConstantName(); ?>]); } getKind()->isOneOf(TypeKind::PRIMITIVE, TypeKind::LIST, TypeKind::PRIMITIVE_CONTAINER)) : ?> - $n = $element->attributes->getNamedItem(self::); - if (null !== $n) { -isCollection()) : ?> + if (isset($attributes[self::])) { +isCollection()) : ?> + $type->((string)$attributes[self::]); + $pt = $type->getGetterName(); ?>(); if (null !== $pt) { - $pt->setValue($n->nodeValue); + $pt->setValue((string)$attributes[self::]); } else { - $type->($n->nodeValue); + $type->((string)$attributes[self::]); } - - $type->($n->nodeValue); } getFieldConstantName(); ob_start(); -if ($i > 0) : ?> else if (self:: === $n->nodeName) { - $valueAttr = $n->attributes->getNamedItem('value'); +if ($i > 0) : ?> else if (self:: === $childName) { + $valueAttr = $n->attributes()[self::FIELD_VALUE] ?? null; if (null !== $valueAttr) { - $type->setValue($valueAttr->nodeValue); - } elseif ($n->hasChildNodes()) { - $type->setValue($n->ownerDocument->saveXML($n)); + $type->setValue((string)$valueAttr); + } elseif ($n->hasChildren()) { + $type->setValue($n->saveXML()); } else { - $type->setValue($n->textContent); + $type->setValue((string)$n); } -getKind() === TypeKind::PHPFHIR_XHTML) : ?> - $type->_setElementName($n->nodeName); - }getSetterName(); ob_start(); -if ($i > 0) : ?> else if (self:: === $n->nodeName) { - $type->(::xmlUnserialize($n)); +if ($i > 0) : ?> else if (self:: === $childName) { + $type->(::xmlUnserialize($n, null, $config)); }getFieldConstantName(); ob_start(); -if ($i > 0) : ?> else if (self:: === $n->nodeName) { - for ($ni = 0; $ni < $n->childNodes->length; $ni++) { - $nn = $n->childNodes->item($ni); - if ($nn instanceof \DOMElement) { - $type->(PHPFHIRTypeMap::getContainedTypeFromXML($nn)); - } +if ($i > 0) : ?> else if (self:: === $childName) { + foreach ($n->children() as $nn) { + $type->(::getContainedTypeFromXML($nn, $config)); } }getNamespace(false); +$namespace = $config->getFullyQualifiedName(false); $versionName = $config->getVersion()->getName(); ob_start(); ?> /** - * @param null|string|\DOMElement $element + * @param null|string|\SimpleXMLElement $element * @param null|getFullyQualifiedClassName(true); ?> $type - * @param null|int|\ $config XML serialization config. Supports an integer value interpreted as libxml opts for backwards compatibility. + * @param null|int|\ $config PHP FHIR config. Supports an integer value interpreted as libxml opts for backwards compatibility. * @return null|getFullyQualifiedClassName(true); ?> */ - public static function xmlUnserialize(null|string|\DOMElement $element, null| $type = null, null|int| $config = null): null|self + public static function xmlUnserialize(null|string|\SimpleXMLElement $element, null| $type = null, null|int| $config = null): null|self { if (null === $element) { return null; } if (is_int($config)) { - $libxmlOpts = $config; - $config = new (); + $config = new ([::LIBXML_OPTS->value => $config]); } else if (null === $config) { - $libxmlOpts = ::DEFAULT_LIBXML_OPTS; $config = new (); - } else { - $libxmlOpts = $config->getLibxmlOpts(); } if (is_string($element)) { - libxml_use_internal_errors(true); - $dom = $config->newDOMDocument(); - if (false === $dom->loadXML($element, $libxmlOpts)) { - throw new \DomainException(sprintf( - '%s::xmlUnserialize - String provided is not parseable as XML: %s', - ltrim(substr(__CLASS__, (int)strrpos(__CLASS__, '\\')), '\\'), - implode(', ', array_map(function(\libXMLError $err) { return $err->message; }, libxml_get_errors())) - )); - } - libxml_use_internal_errors(false); - $element = $dom->documentElement; + $element = new \SimpleXMLElement($element, $config->getLibxmlOpts()); } isAbstract()) : // abstract types may not be instantiated directly ?> if (null === $type) { @@ -74,7 +60,7 @@ public static function xmlUnserialize(null|string|\DOMElement $element, null|_getFHIRXMLNamespace() && '' !== ($ens = (string)$element->namespaceURI)) { - $type->_setFHIRXMLNamespace($ens); + if (null !== ($ns = $element->getNamespaces()[''] ?? null)) { + $type->_setSourceXmlns((string)$ns); } getFullyQualifiedClassName(false); ?>; use getFullyQualifiedClassName(false); ?>; -use getNamespace(false); ?>\; -use getNamespace(false); ?>\; +use getFullyQualifiedName(false, PHPFHIR_CLASSNAME_DEBUG_CLIENT); ?>; +use getFullyQualifiedName(false, PHPFHIR_ENUM_TYPE); ?>; use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\TestCase; @@ -81,7 +81,7 @@ */ class extends TestCase { - /** @var getNamespace(true); ?>\ */ + /** @var getFullyQualifiedName(true, PHPFHIR_CLASSNAME_DEBUG_CLIENT); ?> */ private $client; /** @var array */ @@ -103,7 +103,11 @@ protected function fetchResource(string $format): string } $rc = $this->client->get(sprintf('/%s', ::getConstName(false); ?>->value), ['_count' => '1', '_format' => $format]); $this->assertEmpty($rc->err, sprintf('curl error seen: %s', $rc->err)); - $this->assertEquals(200, $rc->code, 'Expected 200 OK'); + if (404 === $rc->code) { + $this->markTestSkipped(sprintf('Endpoint "%s" has no resources of type "%s"', $this->client->_getBaseUrl(), ::getConstName(false); ?>->value)); + } else { + $this->assertEquals(200, $rc->code, 'Expected 200 OK'); + } $this->assertIsString($rc->resp); $this->_fetchedResources[$format] = $rc->resp; $fname = sprintf('%s%sgetFHIRName(); ?>--source.%s', PHPFHIR_OUTPUT_TMP_DIR, DIRECTORY_SEPARATOR, $format); @@ -164,26 +168,26 @@ public function testXML(): void $resource = $entry->getResource(); - $resourceElement = $resource->xmlSerialize(); - $resourceXML = $resourceElement->ownerDocument->saveXML($resourceElement); + $resourceXmlWriter = $resource->xmlSerialize(); + $resourceXml = $resourceXmlWriter->outputMemory(); try { - $type = getClassName(); ?>::xmlUnserialize($resourceXML); + $type = getClassName(); ?>::xmlUnserialize($resourceXml); } catch (\Exception $e) { throw new AssertionFailedError( sprintf( 'Error building type "getFHIRName(); ?>" from XML: %s; XML: %s', $e->getMessage(), - $resourceXML + $resourceXml ), $e->getCode(), $e ); } $this->assertInstanceOf(getClassName(); ?>::class, $type); - $typeElement = $type->xmlSerialize(); - $this->assertEquals($resourceXML, $typeElement->ownerDocument->saveXML($typeElement)); - $bundleElement = $bundle->xmlSerialize(); - $this->assertXmlStringEqualsXmlString($sourceXML, $bundleElement->ownerDocument->saveXML()); + $typeXmlWriter = $type->xmlSerialize(); + $this->assertEquals($resourceXml, $typeXmlWriter->outputMemory()); + $bundleXmlWriter = $bundle->xmlSerialize(); + $this->assertXmlStringEqualsXmlString($sourceXML, $bundleXmlWriter->outputMemory()); } public function testJSON(): void diff --git a/template/types/tests/validation/class.php b/template/types/tests/validation/class.php index 13a9a4df..1599c3ca 100644 --- a/template/types/tests/validation/class.php +++ b/template/types/tests/validation/class.php @@ -68,8 +68,8 @@ use getFullyQualifiedClassName(false); ?>; use getFullyQualifiedClassName(false); ?>; -use getNamespace(false); ?>\; -use getNamespace(false); ?>\; +use getFullyQualifiedName(false); ?>\; +use getFullyQualifiedName(false); ?>\; use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\TestCase; @@ -102,7 +102,7 @@ class extends TestCase 'If a code for the unit is present, the system SHALL also be present', ]; - /** @var getNamespace(true); ?>\ */ + /** @var getFullyQualifiedName(true); ?>\ */ private $client; /** @var array */ @@ -214,7 +214,7 @@ public function testFHIRValidationXML(): void $resource = $entry->getResource(); - $fname = PHPFHIR_OUTPUT_TMP_DIR . DIRECTORY_SEPARATOR . $resource->_getFHIRTypeName() . '-.xml'; + $fname = PHPFHIR_OUTPUT_TMP_DIR . DIRECTORY_SEPARATOR . $resource->_getFhirTypeName() . '-.xml'; file_put_contents($fname, $bundle->xmlSerialize()->ownerDocument->saveXML()); $this->assertFileExists($fname); @@ -271,7 +271,7 @@ public function testFHIRValidationJSON() $resource = $entry->getResource(); - $fname = PHPFHIR_OUTPUT_TMP_DIR . DIRECTORY_SEPARATOR . $resource->_getFHIRTypeName() . '-.json'; + $fname = PHPFHIR_OUTPUT_TMP_DIR . DIRECTORY_SEPARATOR . $resource->_getFhirTypeName() . '-.json'; file_put_contents($fname, json_encode($bundle)); $this->assertFileExists($fname);