diff --git a/composer.json b/composer.json
index c9ad66a..aca1439 100644
--- a/composer.json
+++ b/composer.json
@@ -17,7 +17,8 @@
],
"autoload": {
"psr-4": {
- "SimpleSAML\\XML\\": "src/"
+ "SimpleSAML\\XML\\": "src/",
+ "SimpleSAML\\XML\\xsd\\": "src/XML/xsd"
}
},
"autoload-dev": {
diff --git a/resources/schemas/XMLSchema.xsd b/resources/schemas/XMLSchema.xsd
new file mode 100644
index 0000000..f0c9505
--- /dev/null
+++ b/resources/schemas/XMLSchema.xsd
@@ -0,0 +1,2534 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ]>
+
+
+
+ Part 1 version: Id: structures.xsd,v 1.2 2004/01/15 11:34:25 ht Exp
+ Part 2 version: Id: datatypes.xsd,v 1.3 2004/01/23 18:11:13 ht Exp
+
+
+
+
+
+ The schema corresponding to this document is normative,
+ with respect to the syntactic constraints it expresses in the
+ XML Schema language. The documentation (within <documentation> elements)
+ below, is not normative, but rather highlights important aspects of
+ the W3C Recommendation of which this is a part
+
+
+
+
+ The simpleType element and all of its members are defined
+ towards the end of this schema document
+
+
+
+
+
+ Get access to the xml: attribute groups for xml:lang
+ as declared on 'schema' and 'documentation' below
+
+
+
+
+
+
+
+ This type is extended by almost all schema types
+ to allow attributes from other namespaces to be
+ added to user schemas.
+
+
+
+
+
+
+
+
+
+
+
+
+ This type is extended by all types which allow annotation
+ other than <schema> itself
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This group is for the
+ elements which occur freely at the top level of schemas.
+ All of their types are based on the "annotated" type by extension.
+
+
+
+
+
+
+
+
+
+
+
+
+ This group is for the
+ elements which can self-redefine (see <redefine> below).
+
+
+
+
+
+
+
+
+
+
+
+
+ A utility type, not for public use
+
+
+
+
+
+
+
+
+
+
+ A utility type, not for public use
+
+
+
+
+
+
+
+
+
+
+ A utility type, not for public use
+
+ #all or (possibly empty) subset of {extension, restriction}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A utility type, not for public use
+
+
+
+
+
+
+
+
+
+
+
+
+ A utility type, not for public use
+
+ #all or (possibly empty) subset of {extension, restriction, list, union}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ for maxOccurs
+
+
+
+
+
+
+
+
+
+
+
+ for all particles
+
+
+
+
+
+
+ for element, group and attributeGroup,
+ which both define and reference
+
+
+
+
+
+
+
+ 'complexType' uses this
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This branch is short for
+ <complexContent>
+ <restriction base="xs:anyType">
+ ...
+ </restriction>
+ </complexContent>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Will be restricted to required or forbidden
+
+
+
+
+
+ Not allowed if simpleContent child is chosen.
+ May be overriden by setting on complexContent child.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This choice is added simply to
+ make this a valid restriction per the REC
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Overrides any setting on complexType parent.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This choice is added simply to
+ make this a valid restriction per the REC
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ No typeDefParticle group reference
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A utility type, not for public use
+
+ #all or (possibly empty) subset of {substitution, extension,
+ restriction}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The element element can be used either
+ at the top level to define an element-type binding globally,
+ or within a content model to either reference a globally-defined
+ element or type or declare an element-type binding locally.
+ The ref form is not allowed at the top level.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ group type for explicit groups, named top-level groups and
+ group references
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ group type for the three kinds of group
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This choice with min/max is here to
+ avoid a pblm with the Elt:All/Choice/Seq
+ Particle derivation constraint
+
+
+
+
+
+
+
+
+
+ restricted max/min
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Only elements allowed inside
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ simple type for the value of the 'namespace' attr of
+ 'any' and 'anyAttribute'
+
+
+
+ Value is
+ ##any - - any non-conflicting WFXML/attribute at all
+
+ ##other - - any non-conflicting WFXML/attribute from
+ namespace other than targetNS
+
+ ##local - - any unqualified non-conflicting WFXML/attribute
+
+ one or - - any non-conflicting WFXML/attribute from
+ more URI the listed namespaces
+ references
+ (space separated)
+
+ ##targetNamespace or ##local may appear in the above list, to
+ refer to the targetNamespace of the enclosing
+ schema or an absent targetNamespace respectively
+
+
+
+
+
+ A utility type, not for public use
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A subset of XPath expressions for use
+in selectors
+ A utility type, not for public
+use
+
+
+
+ The following pattern is intended to allow XPath
+ expressions per the following EBNF:
+ Selector ::= Path ( '|' Path )*
+ Path ::= ('.//')? Step ( '/' Step )*
+ Step ::= '.' | NameTest
+ NameTest ::= QName | '*' | NCName ':' '*'
+ child:: is also allowed
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A subset of XPath expressions for use
+in fields
+ A utility type, not for public
+use
+
+
+
+ The following pattern is intended to allow XPath
+ expressions per the same EBNF as for selector,
+ with the following change:
+ Path ::= ('.//')? ( Step '/' )* ( Step | '@' NameTest )
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The three kinds of identity constraints, all with
+ type of or derived from 'keybase'.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A utility type, not for public use
+
+ A public identifier, per ISO 8879
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ notations for use within XML Schema schemas
+
+
+
+
+
+
+
+
+ Not the real urType, but as close an approximation as we can
+ get in the XML representation
+
+
+
+
+
+
+
+
+
+ First the built-in primitive datatypes. These definitions are for
+ information only, the real built-in definitions are magic.
+
+
+
+ For each built-in datatype in this schema (both primitive and
+ derived) can be uniquely addressed via a URI constructed
+ as follows:
+ 1) the base URI is the URI of the XML Schema namespace
+ 2) the fragment identifier is the name of the datatype
+
+ For example, to address the int datatype, the URI is:
+
+ http://www.w3.org/2001/XMLSchema#int
+
+ Additionally, each facet definition element can be uniquely
+ addressed via a URI constructed as follows:
+ 1) the base URI is the URI of the XML Schema namespace
+ 2) the fragment identifier is the name of the facet
+
+ For example, to address the maxInclusive facet, the URI is:
+
+ http://www.w3.org/2001/XMLSchema#maxInclusive
+
+ Additionally, each facet usage in a built-in datatype definition
+ can be uniquely addressed via a URI constructed as follows:
+ 1) the base URI is the URI of the XML Schema namespace
+ 2) the fragment identifier is the name of the datatype, followed
+ by a period (".") followed by the name of the facet
+
+ For example, to address the usage of the maxInclusive facet in
+ the definition of int, the URI is:
+
+ http://www.w3.org/2001/XMLSchema#int.maxInclusive
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ NOTATION cannot be used directly in a schema; rather a type
+ must be derived from it by specifying at least one enumeration
+ facet whose value is the name of a NOTATION declared in the
+ schema.
+
+
+
+
+
+
+
+
+
+ Now the derived primitive types
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pattern specifies the content of section 2.12 of XML 1.0e2
+ and RFC 3066 (Revised version of RFC 1766).
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pattern matches production 7 from the XML spec
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pattern matches production 5 from the XML spec
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ pattern matches production 4 from the Namespaces in XML spec
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ A utility type, not for public use
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ #all or (possibly empty) subset of {restriction, union, list}
+
+
+ A utility type, not for public use
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Can be restricted to required or forbidden
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Required at the top level
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Forbidden when nested
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ We should use a substitution group for facets, but
+ that's ruled out because it would allow users to
+ add their own, which we're not ready for yet.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ base attribute and simpleType child are mutually
+ exclusive, but one or other is required
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ itemType attribute and simpleType child are mutually
+ exclusive, but one or other is required
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ memberTypes attribute must be non-empty or there must be
+ at least one simpleType child
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/XML/element.registry.php b/src/XML/element.registry.php
new file mode 100644
index 0000000..ff665c7
--- /dev/null
+++ b/src/XML/element.registry.php
@@ -0,0 +1,12 @@
+ [
+ 'annotation' => '\SimpleSAML\XML\xsd\Annotation',
+ 'appinfo' => '\SimpleSAML\XML\xsd\Appinfo',
+ 'documentation' => '\SimpleSAML\XML\xsd\Documentation',
+ 'import' => '\SimpleSAML\XML\xsd\Import',
+ ],
+];
diff --git a/src/XML/xsd/AbstractAnnotated.php b/src/XML/xsd/AbstractAnnotated.php
new file mode 100644
index 0000000..2da564c
--- /dev/null
+++ b/src/XML/xsd/AbstractAnnotated.php
@@ -0,0 +1,92 @@
+ $namespacedAttributes
+ */
+ public function __construct(
+ protected ?Annotation $annotation = null,
+ protected ?string $id,
+ array $namespacedAttributes = [],
+ ) {
+ Assert::nullOrValidNCName($id, SchemaViolationException::class);
+
+ parent::__construct($namespacedAttributes);
+ }
+
+
+ /**
+ * Collect the value of the annotation-property
+ *
+ * @return \SimpleSAML\XML\xsd\Annotation|null
+ */
+ public function getAnnotation(): ?Annotation
+ {
+ return $this->annotation;
+ }
+
+
+ /**
+ * Collect the value of the id-property
+ *
+ * @return string|null
+ */
+ public function getId(): ?string
+ {
+ return $this->id;
+ }
+
+
+ /**
+ * Test if an object, at the state it's in, would produce an empty XML-element
+ *
+ * @return bool
+ */
+ public function isEmptyElement(): bool
+ {
+ return parent::isEmptyElement() &&
+ empty($this->getAnnotation()) &&
+ empty($this->id);
+ }
+
+
+ /**
+ * Add this Annotated to an XML element.
+ *
+ * @param \DOMElement $parent The element we should append this Annotated to.
+ * @return \DOMElement
+ */
+ public function toXML(DOMElement $parent = null): DOMElement
+ {
+ $e = parent::toXML($parent);
+
+ if ($this->getId() !== null) {
+ $e->setAttribute('id', $this->getId());
+ }
+
+ if ($this->getAnnotation() !== null) {
+ $annotation->toXML($e);
+ }
+
+ return $e;
+ }
+}
diff --git a/src/XML/xsd/AbstractOpenAttrs.php b/src/XML/xsd/AbstractOpenAttrs.php
new file mode 100644
index 0000000..9f40111
--- /dev/null
+++ b/src/XML/xsd/AbstractOpenAttrs.php
@@ -0,0 +1,63 @@
+ $namespacedAttributes
+ */
+ public function __construct(
+ array $namespacedAttributes = [],
+ ) {
+ $this->setAttributesNS($namespacedAttributes);
+ }
+
+
+ /**
+ * Test if an object, at the state it's in, would produce an empty XML-element
+ *
+ * @return bool
+ */
+ public function isEmptyElement(): bool
+ {
+ return empty($this->getAttributesNS());
+ }
+
+
+ /**
+ * Add this OpenAttrs to an XML element.
+ *
+ * @param \DOMElement $parent The element we should append this OpenAttrs to.
+ * @return \DOMElement
+ */
+ public function toXML(DOMElement $parent = null): DOMElement
+ {
+ $e = parent::instantiateParentElement($parent);
+
+ foreach ($this->getAttributesNS() as $attr) {
+ $attr->toXML($e);
+ }
+
+ return $e;
+ }
+}
diff --git a/src/XML/xsd/AbstractXsdElement.php b/src/XML/xsd/AbstractXsdElement.php
new file mode 100644
index 0000000..25a2f80
--- /dev/null
+++ b/src/XML/xsd/AbstractXsdElement.php
@@ -0,0 +1,22 @@
+ $appinfo
+ * @param array<\SimpleSAML\XML\xsd\Documentation> $documentation
+ * @param string|null $id
+ * @param array<\SimpleSAML\XML\Attribute> $namespacedAttributes
+ */
+ public function __construct(
+ protected array $appinfo,
+ protected array $documentation,
+ protected ?string $id,
+ array $namespacedAttributes = [],
+ ) {
+ Assert::allIsInstanceOf($appinfo, Appinfo::class, SchemaViolationException::class);
+ Assert::allIsInstanceOf($documentation, Documentation::class, SchemaViolationException::class);
+ Assert::nullOrValidNCName($id, SchemaViolationException::class);
+
+ parent::__construct($namespacedAttributes);
+ }
+
+
+ /**
+ * Collect the value of the appinfo-property
+ *
+ * @return \SimpleSAML\XML\xsd\Appinfo[]
+ */
+ public function getAppinfo(): array
+ {
+ return $this->appinfo;
+ }
+
+
+ /**
+ * Collect the value of the documentation-property
+ *
+ * @return \SimpleSAML\XML\xsd\Documentation[]
+ */
+ public function getDocumentation(): array
+ {
+ return $this->documentation;
+ }
+
+
+ /**
+ * Collect the value of the id-property
+ *
+ * @return string|null
+ */
+ public function getId(): ?string
+ {
+ return $this->id;
+ }
+
+
+ /**
+ * Test if an object, at the state it's in, would produce an empty XML-element
+ *
+ * @return bool
+ */
+ public function isEmptyElement(): bool
+ {
+ return parent::isEmptyElement() &&
+ empty($this->getAppinfo()) &&
+ empty($this->getDocumentation()) &&
+ empty($this->id);
+ }
+
+
+ /**
+ * Create a class from XML
+ *
+ * @param \DOMElement $xml
+ * @return static
+ */
+ public static function fromXML(DOMElement $xml): static
+ {
+ Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class);
+ Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class);
+
+ return new static(
+ Appinfo::getChildrenOfClass($xml),
+ Documentation::getChildrenOfClass($xml),
+ self::getOptionalAttribute($xml, 'id', null),
+ self::getAttributesNSFromXML($xml),
+ );
+ }
+
+
+ /**
+ * Add this Annotation to an XML element.
+ *
+ * @param \DOMElement $parent The element we should append this Annotation to.
+ * @return \DOMElement
+ */
+ public function toXML(DOMElement $parent = null): DOMElement
+ {
+ $e = parent::toXML($parent);
+
+ if ($this->getId() !== null) {
+ $e->setAttribute('id', $this->getId());
+ }
+
+ foreach ($this->getAppinfo() as $appinfo) {
+ $appinfo->toXML($e);
+ }
+
+ foreach ($this->getDocumentation() as $documentation) {
+ $documentation->toXML($e);
+ }
+
+ return $e;
+ }
+}
diff --git a/src/XML/xsd/Appinfo.php b/src/XML/xsd/Appinfo.php
new file mode 100644
index 0000000..cba778e
--- /dev/null
+++ b/src/XML/xsd/Appinfo.php
@@ -0,0 +1,128 @@
+ $namespacedAttributes
+ */
+ final public function __construct(
+ protected DOMNodeList $content,
+ protected ?string $source = null,
+ array $namespacedAttributes = [],
+ ) {
+ Assert::nullOrValidURI($source);
+ $this->setAttributesNS($namespacedAttributes);
+ }
+
+
+ /**
+ * Get the content property.
+ *
+ * @return \DOMNodeList
+ */
+ public function getContent(): DOMNodeList
+ {
+ return $this->content;
+ }
+
+
+ /**
+ * Get the source property.
+ *
+ * @return string|null
+ */
+ public function getSource(): ?string
+ {
+ return $this->source;
+ }
+
+
+ /**
+ * Test if an object, at the state it's in, would produce an empty XML-element
+ *
+ * @return bool
+ */
+ public function isEmptyElement(): bool
+ {
+ return empty($this->getContent())
+ && empty($this->getSource())
+ && empty($this->getAttributesNS());
+ }
+
+
+ /**
+ * Create an instance of this object from its XML representation.
+ *
+ * @param \DOMElement $xml
+ * @return static
+ *
+ * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException
+ * if the qualified name of the supplied element is wrong
+ */
+ public static function fromXML(DOMElement $xml): static
+ {
+ Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class);
+ Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class);
+
+ return new static(
+ $xml->childNodes,
+ self::getOptionalAttribute($xml, 'source', null),
+ self::getAttributesNSFromXML($xml),
+ );
+ }
+
+
+ /**
+ * Add this Appinfo to an XML element.
+ *
+ * @param \DOMElement $parent The element we should append this Appinfo to.
+ * @return \DOMElement
+ */
+ public function toXML(DOMElement $parent = null): DOMElement
+ {
+ $e = parent::instantiateParentElement($parent);
+
+ if ($this->getSource() !== null) {
+ $e->setAttribute('source', $this->getSource());
+ }
+
+ foreach ($this->getAttributesNS() as $attr) {
+ $attr->toXML($e);
+ }
+
+ foreach ($this->getContent() as $i) {
+ $e->appendChild($e->ownerDocument->importNode($i, true));
+ }
+
+ return $e;
+ }
+}
diff --git a/src/XML/xsd/Documentation.php b/src/XML/xsd/Documentation.php
new file mode 100644
index 0000000..829be48
--- /dev/null
+++ b/src/XML/xsd/Documentation.php
@@ -0,0 +1,158 @@
+ $namespacedAttributes
+ */
+ final public function __construct(
+ protected DOMNodeList $content,
+ protected ?string $lang = null,
+ protected ?string $source = null,
+ array $namespacedAttributes = [],
+ ) {
+ Assert::nullOrValidURI($source);
+ $this->setAttributesNS($namespacedAttributes);
+ }
+
+
+ /**
+ * Get the content property.
+ *
+ * @return \DOMNodeList
+ */
+ public function getContent(): DOMNodeList
+ {
+ return $this->content;
+ }
+
+
+ /**
+ * Get the lang property.
+ *
+ * @return string|null
+ */
+ public function getLang(): ?string
+ {
+ return $this->lang;
+ }
+
+
+ /**
+ * Get the source property.
+ *
+ * @return string|null
+ */
+ public function getSource(): ?string
+ {
+ return $this->source;
+ }
+
+
+ /**
+ * Test if an object, at the state it's in, would produce an empty XML-element
+ *
+ * @return bool
+ */
+ public function isEmptyElement(): bool
+ {
+ return empty($this->getContent())
+ && empty($this->getLang())
+ && empty($this->getSource())
+ && empty($this->getAttributesNS());
+ }
+
+
+ /**
+ * Create an instance of this object from its XML representation.
+ *
+ * @param \DOMElement $xml
+ * @return static
+ *
+ * @throws \SimpleSAML\XML\Exception\InvalidDOMElementException
+ * if the qualified name of the supplied element is wrong
+ */
+ public static function fromXML(DOMElement $xml): static
+ {
+ Assert::same($xml->localName, static::getLocalName(), InvalidDOMElementException::class);
+ Assert::same($xml->namespaceURI, static::NS, InvalidDOMElementException::class);
+
+ $lang = null;
+ if ($xml->hasAttributeNS(C::NS_XML, 'lang')) {
+ $lang = $xml->getAttributeNS(C::NS_XML, 'lang');
+ }
+
+ return new static(
+ $xml->childNodes,
+ $lang,
+ self::getOptionalAttribute($xml, 'source', null),
+ self::getAttributesNSFromXML($xml),
+ );
+ }
+
+
+ /**
+ * Add this Documentation to an XML element.
+ *
+ * @param \DOMElement $parent The element we should append this Documentation to.
+ * @return \DOMElement
+ */
+ public function toXML(DOMElement $parent = null): DOMElement
+ {
+ $e = parent::instantiateParentElement($parent);
+
+ if ($this->getSource() !== null) {
+ $e->setAttribute('source', $this->getSource());
+ }
+
+ if ($this->getLang() !== null) {
+ $e->setAttributeNS(C::NS_XML, 'xml:lang', $this->getLang());
+ }
+
+ foreach ($this->getAttributesNS() as $attr) {
+ $attr->toXML($e);
+ }
+
+ foreach ($this->getContent() as $i) {
+ $e->appendChild($e->ownerDocument->importNode($i, true));
+ }
+
+ return $e;
+ }
+}
diff --git a/src/XML/xsd/FormChoiceTrait.php b/src/XML/xsd/FormChoiceTrait.php
new file mode 100644
index 0000000..7f65e50
--- /dev/null
+++ b/src/XML/xsd/FormChoiceTrait.php
@@ -0,0 +1,43 @@
+formChoice;
+ }
+
+
+ /**
+ * Set the value of the formChoice-property
+ *
+ * @param string $formChoice
+ */
+ protected function setFormChoice(string $formChoice): void
+ {
+ Assert::regex($formChoice, '/\c+/', SchemaViolationException::class);
+ $this->formChoice = $formChoice;
+ }
+}
diff --git a/tests/XML/xsd/AnnotationTest.php b/tests/XML/xsd/AnnotationTest.php
new file mode 100644
index 0000000..41c6564
--- /dev/null
+++ b/tests/XML/xsd/AnnotationTest.php
@@ -0,0 +1,108 @@
+appendChild($text);
+
+ $otherAppinfoDocument = DOMDocumentFactory::create();
+ $otherText = new DOMText('Other Application Information');
+ $otherAppinfoDocument->appendChild($otherText);
+
+ $documentationDocument = DOMDocumentFactory::create();
+ $text = new DOMText('Some Documentation');
+ $documentationDocument->appendChild($text);
+
+ $otherDocumentationDocument = DOMDocumentFactory::create();
+ $text = new DOMText('Other Documentation');
+ $otherDocumentationDocument->appendChild($text);
+
+ $attr1 = new XMLAttribute('urn:x-simplesamlphp:namespace', 'ssp', 'attr1', 'value1');
+ $attr2 = new XMLAttribute('urn:x-simplesamlphp:namespace', 'ssp', 'attr2', 'value2');
+ $attr3 = new XMLAttribute('urn:x-simplesamlphp:namespace', 'ssp', 'attr3', 'value3');
+
+ $appinfo1 = new Appinfo($appinfoDocument->childNodes, 'urn:x-simplesamlphp:source', [$attr1]);
+ $appinfo2 = new Appinfo($otherAppinfoDocument->childNodes, 'urn:x-simplesamlphp:source', [$attr2]);
+
+ $documentation1 = new Documentation(
+ $documentationDocument->childNodes,
+ 'nl',
+ 'urn:x-simplesamlphp:source',
+ [$attr1],
+ );
+ $documentation2 = new Documentation(
+ $otherDocumentationDocument->childNodes,
+ 'nl',
+ 'urn:x-simplesamlphp:source',
+ [$attr2],
+ );
+
+ $annotation = new Annotation(
+ [$appinfo1, $appinfo2],
+ [$documentation1, $documentation2],
+ 'phpunit',
+ [$attr3],
+ );
+
+ $this->assertEquals(
+ self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement),
+ strval($annotation),
+ );
+ }
+}
diff --git a/tests/XML/xsd/AppinfoTest.php b/tests/XML/xsd/AppinfoTest.php
new file mode 100644
index 0000000..30315c0
--- /dev/null
+++ b/tests/XML/xsd/AppinfoTest.php
@@ -0,0 +1,69 @@
+appendChild($text);
+
+ $attr1 = new XMLAttribute('urn:x-simplesamlphp:namespace', 'ssp', 'attr1', 'value1');
+ $appinfo = new Appinfo($document->childNodes, 'urn:x-simplesamlphp:source', [$attr1]);
+
+ $this->assertEquals(
+ self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement),
+ strval($appinfo),
+ );
+ }
+}
diff --git a/tests/XML/xsd/DocumentationTest.php b/tests/XML/xsd/DocumentationTest.php
new file mode 100644
index 0000000..a28676d
--- /dev/null
+++ b/tests/XML/xsd/DocumentationTest.php
@@ -0,0 +1,69 @@
+appendChild($text);
+
+ $attr1 = new XMLAttribute('urn:x-simplesamlphp:namespace', 'ssp', 'attr1', 'value1');
+ $documentation = new Documentation($document->childNodes, 'nl', 'urn:x-simplesamlphp:source', [$attr1]);
+
+ $this->assertEquals(
+ self::$xmlRepresentation->saveXML(self::$xmlRepresentation->documentElement),
+ strval($documentation),
+ );
+ }
+}
diff --git a/tests/resources/xml/xsd/annotation.xml b/tests/resources/xml/xsd/annotation.xml
new file mode 100644
index 0000000..9ec533b
--- /dev/null
+++ b/tests/resources/xml/xsd/annotation.xml
@@ -0,0 +1,6 @@
+
+ Application Information
+ Other Application Information
+ Some Documentation
+ Other Documentation
+
diff --git a/tests/resources/xml/xsd/appinfo.xml b/tests/resources/xml/xsd/appinfo.xml
new file mode 100644
index 0000000..48d1523
--- /dev/null
+++ b/tests/resources/xml/xsd/appinfo.xml
@@ -0,0 +1 @@
+Application Information
diff --git a/tests/resources/xml/xsd/documentation.xml b/tests/resources/xml/xsd/documentation.xml
new file mode 100644
index 0000000..dc0c01a
--- /dev/null
+++ b/tests/resources/xml/xsd/documentation.xml
@@ -0,0 +1 @@
+Some Documentation