Skip to content

Commit

Permalink
Merge pull request #27 from goetas-webservices/dependent-import
Browse files Browse the repository at this point in the history
dependent imports
  • Loading branch information
goetas authored Jan 18, 2018
2 parents 55881e5 + 6bd2264 commit 26cc594
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 107 deletions.
198 changes: 91 additions & 107 deletions src/SchemaReader.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ class SchemaReader
*/
private $loadedFiles = array();

/**
* @var Schema[][]
*/
private $loadedSchemas = array();

/**
* @var string[]
*/
Expand Down Expand Up @@ -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();
}
};
Expand All @@ -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();
Expand Down Expand Up @@ -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 <xsd:schema/> nodes, for instance in a WSDL file.
*
* Each of these <xsd:schema/> 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;
}

/**
Expand Down Expand Up @@ -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(
Expand Down
38 changes: 38 additions & 0 deletions tests/ImportTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -37,4 +40,39 @@ public function testBase()

$this->assertSame($remoteAttr, $localAttrs[0]);
}

public function testBaseNode()
{
$dom = new \DOMDocument();
$dom->loadXML('
<xs:schema targetNamespace="http://www.example.com" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:attribute name="myAttribute" type="xs:string"></xs:attribute>
</xs:schema>
');
$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('
<types xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:schema targetNamespace="http://tempuri.org/1" xmlns:t2="http://tempuri.org/2">
<xs:import namespace="http://tempuri.org/2"/>
<xs:element name="outerEl" type="t2:inner"/>
</xs:schema>
<xs:schema targetNamespace="http://tempuri.org/2">
<xs:complexType name="inner">
<xs:attribute name="inner_attr"/>
</xs:complexType>
</xs:schema>
</types>
');
$schema = $this->reader->readNodes(iterator_to_array($dom->documentElement->childNodes));

$this->assertInstanceOf(ElementDef::class, $schema->findElement('outerEl', 'http://tempuri.org/1'));
}
}

0 comments on commit 26cc594

Please sign in to comment.