diff --git a/src/SchemaReader.php b/src/SchemaReader.php index 65de3f4f..5fb1d66e 100644 --- a/src/SchemaReader.php +++ b/src/SchemaReader.php @@ -54,6 +54,11 @@ class SchemaReader */ private $loadedFiles = array(); + /** + * @var Schema[][] + */ + private $loadedSchemas = array(); + /** * @var string[] */ @@ -1061,112 +1066,69 @@ private function loadImport( Schema $schema, DOMElement $node ): Closure { - $base = urldecode($node->ownerDocument->documentURI); - $file = UrlUtils::resolveRelativeUrl($base, $node->getAttribute('schemaLocation')); - $namespace = $node->getAttribute('namespace'); - - $keys = $this->loadImportFreshKeys($namespace, $file); - - foreach ($keys as $key) { - if (isset($this->loadedFiles[$key])) { - $schema->addSchema($this->loadedFiles[$key]); - - return function () { - }; - } + $schemaLocation = $node->getAttribute('schemaLocation'); + + // postpone schema loading + if ($namespace && !$schemaLocation && !isset(self::$globalSchemaInfo[$namespace])) { + return function () use ($schema, $namespace) { + if (!empty($this->loadedSchemas[$namespace])) { + foreach ($this->loadedSchemas[$namespace] as $s) { + $schema->addSchema($s, $namespace); + } + } + }; + } elseif ($namespace && !$schemaLocation && isset($this->globalSchema[$namespace])) { + $schema->addSchema($this->globalSchema[$namespace]); } - return $this->loadImportFresh($namespace, $schema, $file); - } - - /** - * @return string[] - */ - private function loadImportFreshKeys( - string $namespace, - string $file - ): array { - $globalSchemaInfo = $this->getGlobalSchemaInfo(); + $base = urldecode($node->ownerDocument->documentURI); + $file = UrlUtils::resolveRelativeUrl($base, $node->getAttribute('schemaLocation')); - $keys = []; + if (isset($this->loadedFiles[$file])) { + $schema->addSchema($this->loadedFiles[$file]); - if (isset($globalSchemaInfo[$namespace])) { - $keys[] = $globalSchemaInfo[$namespace]; + return function () { + }; } - $keys[] = $this->getNamespaceSpecificFileIndex( - $file, - $namespace - ); - - $keys[] = $file; - - return $keys; + return $this->loadImportFresh($namespace, $schema, $file); } - private function loadImportFreshCallbacksNewSchema( - string $namespace, + private function createOrUseSchemaForNs( Schema $schema, - string $file + string $namespace ): Schema { - /** - * @var Schema $newSchema - */ - $newSchema = $this->setLoadedFile( - $file, - (('' !== trim($namespace)) ? new Schema() : $schema) - ); - - if ('' !== trim($namespace)) { + if (('' !== trim($namespace))) { + $newSchema = new Schema(); $newSchema->addSchema($this->getGlobalSchema()); $schema->addSchema($newSchema); + } else { + $newSchema = $schema; } return $newSchema; } - /** - * @return Closure[] - */ - private function loadImportFreshCallbacks( - string $namespace, - Schema $schema, - string $file - ): array { - /** - * @var string - */ - $file = $file; - - return $this->schemaNode( - $this->loadImportFreshCallbacksNewSchema( - $namespace, - $schema, - $file - ), - $this->getDOM( - isset($this->knownLocationSchemas[$file]) - ? $this->knownLocationSchemas[$file] - : $file - )->documentElement, - $schema - ); - } - private function loadImportFresh( string $namespace, Schema $schema, string $file ): Closure { return function () use ($namespace, $schema, $file) { - foreach ( - $this->loadImportFreshCallbacks( - $namespace, - $schema, - $file - ) as $callback - ) { + $dom = $this->getDOM( + isset($this->knownLocationSchemas[$file]) + ? $this->knownLocationSchemas[$file] + : $file + ); + + $schemaNew = $this->createOrUseSchemaForNs($schema, $namespace); + + $this->setLoadedFile($file, $schemaNew); + + $callbacks = $this->schemaNode($schemaNew, $dom->documentElement, $schema); + + foreach ($callbacks as $callback) { $callback(); } }; @@ -1177,18 +1139,10 @@ private function loadImportFresh( */ protected $globalSchema; - /** - * @return string[] - */ - public function getGlobalSchemaInfo(): array - { - return self::$globalSchemaInfo; - } - /** * @return Schema */ - public function getGlobalSchema(): Schema + private function getGlobalSchema(): Schema { if (!($this->globalSchema instanceof Schema)) { $callbacks = array(); @@ -1240,30 +1194,55 @@ public function getGlobalSchema(): Schema return $out; } - private function readNode(DOMElement $node, string $file = 'schema.xsd'): Schema + public function readNodes(array $nodes, string $file = null) { - $fileKey = $node->hasAttribute('targetNamespace') ? $this->getNamespaceSpecificFileIndex($file, $node->getAttribute('targetNamespace')) : $file; - $this->setLoadedFile($fileKey, $rootSchema = new Schema()); - + $rootSchema = new Schema(); $rootSchema->addSchema($this->getGlobalSchema()); - $callbacks = $this->schemaNode($rootSchema, $node); - foreach ($callbacks as $callback) { + if ($file !== null) { + $this->setLoadedFile($file, $rootSchema); + } + + $all = array(); + foreach ($nodes as $k => $node) { + if (($node instanceof \DOMElement) && $node->namespaceURI === self::XSD_NS && $node->localName == 'schema') { + $holderSchema = new Schema(); + $holderSchema->addSchema($this->getGlobalSchema()); + + $this->setLoadedSchema($node, $holderSchema); + + $rootSchema->addSchema($holderSchema); + + $callbacks = $this->schemaNode($holderSchema, $node); + $all = array_merge($callbacks, $all); + } + } + + foreach ($all as $callback) { call_user_func($callback); } return $rootSchema; } - /** - * It is possible that a single file contains multiple nodes, for instance in a WSDL file. - * - * Each of these nodes typically target a specific namespace. Append the target namespace to the - * file to distinguish between multiple schemas in a single file. - */ - private function getNamespaceSpecificFileIndex(string $file, string $targetNamespace): string + public function readNode(DOMElement $node, string $file = null): Schema { - return $file.'#'.$targetNamespace; + $rootSchema = new Schema(); + $rootSchema->addSchema($this->getGlobalSchema()); + + if ($file !== null) { + $this->setLoadedFile($file, $rootSchema); + } + + $this->setLoadedSchema($node, $rootSchema); + + $callbacks = $this->schemaNode($rootSchema, $node); + + foreach ($callbacks as $callback) { + call_user_func($callback); + } + + return $rootSchema; } /** @@ -1398,11 +1377,16 @@ private function findSomethingLikeAttributeGroup( $addToThis->addAttribute($attribute); } - private function setLoadedFile(string $key, Schema $schema): Schema + private function setLoadedFile(string $key, Schema $schema): void { $this->loadedFiles[$key] = $schema; + } - return $schema; + private function setLoadedSchema(DOMElement $node, Schema $schema): void + { + if ($node->hasAttribute('targetNamespace')) { + $this->loadedSchemas[$node->getAttribute('targetNamespace')][] = $schema; + } } private function setSchemaThingsFromNode( diff --git a/tests/ImportTest.php b/tests/ImportTest.php index b5303a76..7e698623 100644 --- a/tests/ImportTest.php +++ b/tests/ImportTest.php @@ -4,6 +4,9 @@ namespace GoetasWebservices\XML\XSDReader\Tests; +use GoetasWebservices\XML\XSDReader\Schema\Attribute\AttributeItem; +use GoetasWebservices\XML\XSDReader\Schema\Element\ElementDef; + class ImportTest extends BaseTest { public function testBase() @@ -37,4 +40,39 @@ public function testBase() $this->assertSame($remoteAttr, $localAttrs[0]); } + + public function testBaseNode() + { + $dom = new \DOMDocument(); + $dom->loadXML(' + + + + '); + $schema = $this->reader->readNode($dom->documentElement); + $attr = $schema->findAttribute('myAttribute', 'http://www.example.com'); + + $this->assertInstanceOf(AttributeItem::class, $attr); + } + + public function testDependentImport() + { + $dom = new \DOMDocument(); + $dom->loadXML(' + + + + + + + + + + + + '); + $schema = $this->reader->readNodes(iterator_to_array($dom->documentElement->childNodes)); + + $this->assertInstanceOf(ElementDef::class, $schema->findElement('outerEl', 'http://tempuri.org/1')); + } }