|
| 1 | +<?php declare(strict_types=1); |
| 2 | + |
| 3 | +namespace Soap\Wsdl\Xml\Xmlns; |
| 4 | + |
| 5 | +use DOMElement; |
| 6 | +use DOMNameSpaceNode; |
| 7 | +use Psl\Option\Option; |
| 8 | +use RuntimeException; |
| 9 | +use Soap\Wsdl\Xml\Visitor\ReprefixTypeQname; |
| 10 | +use VeeWee\Xml\Dom\Collection\NodeList; |
| 11 | +use VeeWee\Xml\Dom\Document; |
| 12 | +use function Psl\Dict\merge; |
| 13 | +use function Psl\Option\none; |
| 14 | +use function Psl\Option\some; |
| 15 | +use function VeeWee\Xml\Dom\Builder\xmlns_attribute; |
| 16 | +use function VeeWee\Xml\Dom\Locator\Xmlns\linked_namespaces; |
| 17 | + |
| 18 | +/** |
| 19 | + * Cross-import schemas can contain namespace conflicts. |
| 20 | + * |
| 21 | + * For example: import1 requires import2: |
| 22 | + * |
| 23 | + * - Import 1 specifies xmlns:ns1="urn:1" |
| 24 | + * - Import 2 specifies xmlns:ns1="urn:2". |
| 25 | + * |
| 26 | + * This method will detect conflicting namespaces and resolve them. |
| 27 | + * Namespaces will be renamed to a unique name and the "type" argument with QName's will be re-prefixed. |
| 28 | + * |
| 29 | + * @psalm-type RePrefixMap=array<string, string> |
| 30 | + */ |
| 31 | +final class RegisterNonConflictingXmlnsNamespaces |
| 32 | +{ |
| 33 | + /** |
| 34 | + * @throws RuntimeException |
| 35 | + */ |
| 36 | + public function __invoke(DOMElement $existingSchema, DOMElement $newSchema): void |
| 37 | + { |
| 38 | + $existingLinkedNamespaces = linked_namespaces($existingSchema); |
| 39 | + |
| 40 | + $rePrefixMap = linked_namespaces($newSchema)->reduce( |
| 41 | + /** |
| 42 | + * @param RePrefixMap $rePrefixMap |
| 43 | + * @return RePrefixMap |
| 44 | + */ |
| 45 | + function (array $rePrefixMap, DOMNameSpaceNode $xmlns) use ($existingSchema, $existingLinkedNamespaces): array { |
| 46 | + // Skip non-named xmlns attributes: |
| 47 | + if (!$xmlns->prefix) { |
| 48 | + return $rePrefixMap; |
| 49 | + } |
| 50 | + |
| 51 | + // Check for duplicates: |
| 52 | + if ($existingSchema->hasAttribute($xmlns->nodeName) && $existingSchema->getAttribute($xmlns->nodeName) !== $xmlns->prefix) { |
| 53 | + return merge( |
| 54 | + $rePrefixMap, |
| 55 | + // Can be improved with orElse when we are using PSL V3. |
| 56 | + $this->tryUsingExistingPrefix($existingLinkedNamespaces, $xmlns) |
| 57 | + ->unwrapOrElse( |
| 58 | + fn () => $this->tryUsingUniquePrefixHash($existingSchema, $xmlns) |
| 59 | + ->unwrapOrElse( |
| 60 | + static fn () => throw new RuntimeException('Could not resolve conflicting namespace declarations whilst flattening your WSDL file.') |
| 61 | + ) |
| 62 | + ) |
| 63 | + ); |
| 64 | + } |
| 65 | + |
| 66 | + xmlns_attribute($xmlns->prefix, $xmlns->namespaceURI)($existingSchema); |
| 67 | + |
| 68 | + return $rePrefixMap; |
| 69 | + }, |
| 70 | + [] |
| 71 | + ); |
| 72 | + |
| 73 | + if (count($rePrefixMap)) { |
| 74 | + Document::fromUnsafeDocument($newSchema->ownerDocument)->traverse(new ReprefixTypeQname($rePrefixMap)); |
| 75 | + } |
| 76 | + (new FixRemovedDefaultXmlnsDeclarationsDuringImport())($existingSchema, $newSchema); |
| 77 | + } |
| 78 | + |
| 79 | + /** |
| 80 | + * @param NodeList<DOMNameSpaceNode> $existingLinkedNamespaces |
| 81 | + * |
| 82 | + * @return Option<RePrefixMap> |
| 83 | + */ |
| 84 | + private function tryUsingExistingPrefix( |
| 85 | + NodeList $existingLinkedNamespaces, |
| 86 | + DOMNameSpaceNode $xmlns |
| 87 | + ): Option { |
| 88 | + $existingPrefix = $existingLinkedNamespaces->filter( |
| 89 | + static fn (DOMNameSpaceNode $node) => $node->namespaceURI === $xmlns->namespaceURI |
| 90 | + )->first()?->prefix; |
| 91 | + |
| 92 | + if ($existingPrefix === null) { |
| 93 | + /** @var Option<RePrefixMap> */ |
| 94 | + return none(); |
| 95 | + } |
| 96 | + |
| 97 | + /** @var Option<RePrefixMap> */ |
| 98 | + return some([$xmlns->prefix => $existingPrefix]); |
| 99 | + } |
| 100 | + |
| 101 | + /** |
| 102 | + * @return Option<RePrefixMap> |
| 103 | + * |
| 104 | + * @throws RuntimeException |
| 105 | + */ |
| 106 | + private function tryUsingUniquePrefixHash( |
| 107 | + DOMElement $existingSchema, |
| 108 | + DOMNameSpaceNode $xmlns |
| 109 | + ): Option { |
| 110 | + $uniquePrefix = 'ns' . substr(md5($xmlns->namespaceURI), 0, 8); |
| 111 | + if ($existingSchema->hasAttribute('xmlns:'.$uniquePrefix)) { |
| 112 | + /** @var Option<RePrefixMap> */ |
| 113 | + return none(); |
| 114 | + } |
| 115 | + |
| 116 | + $this->copyXmlnsDeclaration($existingSchema, $xmlns->namespaceURI, $uniquePrefix); |
| 117 | + |
| 118 | + /** @var Option<RePrefixMap> */ |
| 119 | + return some([$xmlns->prefix => $uniquePrefix]); |
| 120 | + } |
| 121 | + |
| 122 | + /** |
| 123 | + * @throws RuntimeException |
| 124 | + */ |
| 125 | + private function copyXmlnsDeclaration(DOMElement $existingSchema, string $namespaceUri, string $prefix): void |
| 126 | + { |
| 127 | + xmlns_attribute($prefix, $namespaceUri)($existingSchema); |
| 128 | + } |
| 129 | +} |
0 commit comments