diff --git a/ts-reaktive-xsd/src/main/scala/com/tradeshift/reaktive/xsd/Schema.scala b/ts-reaktive-xsd/src/main/scala/com/tradeshift/reaktive/xsd/Schema.scala index 67f304f3..b8f4902f 100644 --- a/ts-reaktive-xsd/src/main/scala/com/tradeshift/reaktive/xsd/Schema.scala +++ b/ts-reaktive-xsd/src/main/scala/com/tradeshift/reaktive/xsd/Schema.scala @@ -22,6 +22,9 @@ sealed trait RootType { // Fixme rename ElementType /** Finds a declared, non-xsd:any element as a child of this one */ def findChildElement(name: QName): Option[RootElement] = None + /** Returns whether a given child element could occur more than once underneath this parent */ + def isChildMultiValued(name: QName): Boolean = false + /** Returns whether this type is, or derives from, a type named [name] */ def hasBase(b: QName): Boolean = b == name } @@ -45,6 +48,9 @@ case class RestrictionType(name: QName, _base: Resolve[RootType], attributes: Se (attributes ++ _base.get.allowedAttributes).groupBy(_.name).mapValues(_.last).values def base: RootType = _base.get override def hasBase(b: QName) = b == name || base.hasBase(b) + + // Restriction types only allow their specified content (which is a subsection of the base type's content). + override def isChildMultiValued(name: QName): Boolean = content.exists(_.isMultiValued(name)) } case class ExtensionType(name: QName, _base: Resolve[RootType], attributes: Seq[Attribute], content: Option[Content]) extends RootType { @@ -52,12 +58,18 @@ case class ExtensionType(name: QName, _base: Resolve[RootType], attributes: Seq[ (attributes ++ _base.get.allowedAttributes).groupBy(_.name).mapValues(_.last).values def base: RootType = _base.get override def hasBase(b: QName) = b == name || base.hasBase(b) + + // Extension types allow the base type's content, AND THEN their own content. + override def isChildMultiValued(name: QName): Boolean = + base.isChildMultiValued(name) || content.exists(_.isMultiValued(name)) } case class ComplexType(name: QName, content: Option[Content], attributes: Seq[Attribute]) extends RootType { override def allowedAttributes = attributes /** Finds a declared, non-xsd:any element as a child of this one */ override def findChildElement(name: QName): Option[RootElement] = content.flatMap(_.findElement(name)) + + override def isChildMultiValued(name: QName): Boolean = content.exists(_.isMultiValued(name)) } case class Content(element: ElementRef, minOccurs: Int, maxOccurs: Int) { @@ -78,6 +90,20 @@ case class Content(element: ElementRef, minOccurs: Int, maxOccurs: Int) { case Choice(elements) => elements.flatMap(_.potentialChildren) case _ => Nil } + + /** Returns whether the given child could occur more than once in this content */ + def isMultiValued(name: QName): Boolean = { + def moreThanOnce = (maxOccurs > 1 || maxOccurs == -1) + + element match { + case elem@RootElement(n, _) if n == name => moreThanOnce + case RootElementRef(ref) if ref.get.name == name => moreThanOnce + case AnyElement => moreThanOnce + case Sequence(elements) => elements.exists(_.isMultiValued(name)) + case Choice(elements) => elements.exists(_.isMultiValued(name)) + case _ => false + } + } } case class Sequence(elements: Seq[Content]) extends ElementRef diff --git a/ts-reaktive-xsd/src/test/scala/com/tradeshift/reaktive/xsd/SchemaLoaderSpec.scala b/ts-reaktive-xsd/src/test/scala/com/tradeshift/reaktive/xsd/SchemaLoaderSpec.scala index e455fe17..63ff3b94 100644 --- a/ts-reaktive-xsd/src/test/scala/com/tradeshift/reaktive/xsd/SchemaLoaderSpec.scala +++ b/ts-reaktive-xsd/src/test/scala/com/tradeshift/reaktive/xsd/SchemaLoaderSpec.scala @@ -15,6 +15,7 @@ import java.nio.file.Paths import org.scalatest.AsyncFunSpecLike import scala.concurrent.Await import scala.concurrent.duration._ +import javax.xml.namespace.QName class SchemaLoaderSpec extends SharedActorSystemSpec with FunSpecLike with Matchers { implicit val m = materializer @@ -47,10 +48,20 @@ class SchemaLoaderSpec extends SharedActorSystemSpec with FunSpecLike with Match .via(AaltoReader.instance) }), 30.seconds) - val t = System.nanoTime() - start - println(s"Took ${(t / 1000000)}ms") - stuff = stuff :+ schema - schema.namespaces should have size(14) + val t = System.nanoTime() - start + println(s"Took ${(t / 1000000)}ms") + stuff = stuff :+ schema + schema.namespaces should have size(14) + + val invoiceTag = + schema.rootElements(new QName("urn:oasis:names:specification:ubl:schema:xsd:Invoice-2", "Invoice")) + + invoiceTag.elementType + .isChildMultiValued(new QName("urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2", "InvoiceLine")) shouldBe(true) + invoiceTag.elementType + .isChildMultiValued(new QName("urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2", "BuyerCustomerParty")) shouldBe(false) + invoiceTag.elementType + .isChildMultiValued(new QName("urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2", "NonExistingTag")) shouldBe(false) } it("should resolve references to an existing Schema") {