Skip to content

Commit

Permalink
Merge pull request #137 from Tradeshift/xsd_multivalue
Browse files Browse the repository at this point in the history
Add ability for XSD type to report whether a child can be 0..*
  • Loading branch information
jypma authored Nov 11, 2019
2 parents 13cf254 + 1b5a94c commit 8d10d21
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -45,19 +48,28 @@ 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 {
override lazy val allowedAttributes =
(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) {
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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") {
Expand Down

0 comments on commit 8d10d21

Please sign in to comment.