blob: 976362f123fc76525cd77c9125a9cc8f516906ac [file] [log] [blame]
/* Copyright (c) 2012-2015 Tresys Technology, LLC. All rights reserved.
*
* Developed by: Tresys Technology, LLC
* http://www.tresys.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of
* this software and associated documentation files (the "Software"), to deal with
* the Software without restriction, including without limitation the rights to
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is furnished to do
* so, subject to the following conditions:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimers.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimers in the
* documentation and/or other materials provided with the distribution.
*
* 3. Neither the names of Tresys Technology, nor the names of its contributors
* may be used to endorse or promote products derived from this Software
* without specific prior written permission.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE
* SOFTWARE.
*/
package edu.illinois.ncsa.daffodil.dsom
import scala.xml.Node
import edu.illinois.ncsa.daffodil.ExecutionMode
import edu.illinois.ncsa.daffodil.exceptions.Assert
import edu.illinois.ncsa.daffodil.xml.GetAttributesMixin
import edu.illinois.ncsa.daffodil.xml.NS
import edu.illinois.ncsa.daffodil.xml.XMLUtils
import edu.illinois.ncsa.daffodil.exceptions.SchemaFileLocatable
import edu.illinois.ncsa.daffodil.processors.NonTermRuntimeData
import edu.illinois.ncsa.daffodil.processors.RuntimeData
import scala.xml.NamespaceBinding
import edu.illinois.ncsa.daffodil.util.Maybe
import edu.illinois.ncsa.daffodil.processors.VariableMap
/**
* The core root class of the DFDL Schema object model.
*
* Every schema component has a schema document, and a schema, and a namespace.
*/
abstract class SchemaComponent(xmlArg: Node, val parent: SchemaComponent)
extends SchemaComponentBase(xmlArg, parent)
with ImplementsThrowsOrSavesSDE
with GetAttributesMixin
with SchemaComponentIncludesAndImportsMixin
with ResolvesQNames
with FindPropertyMixin
with SchemaFileLocatable
with PropTypes {
lazy val dpathCompileInfo: DPathCompileInfo =
new DPathCompileInfo(
enclosingComponent.map { _.dpathCompileInfo },
variableMap,
namespaces,
path,
schemaFileLocation)
val context: SchemaComponent = parent
/**
* Annotations can contain expressions, so we need to be able to compile them.
*
* We need our own instance so that the expression compiler has this schema
* component as its context.
*/
override lazy val lineAttribute: Option[String] = {
val attrText = xml.attribute(XMLUtils.INT_NS, XMLUtils.LINE_ATTRIBUTE_NAME).map { _.text }
if (attrText.isDefined) {
attrText
} else if (parent != null) parent.lineAttribute
else None
}
final override lazy val columnAttribute = xml.attribute(XMLUtils.INT_NS, XMLUtils.COLUMN_ATTRIBUTE_NAME) map { _.text }
final override lazy val fileAttribute: Option[String] = {
val optAttrNode = schemaFile.map { _.node.attribute(XMLUtils.INT_NS, XMLUtils.FILE_ATTRIBUTE_NAME) }.flatten
val optAttrText = optAttrNode.map { _.text }
optAttrText
}
/**
* Namespace scope for resolving QNames.
*
* We insist that the prefix "xsi" is properly defined for use
* in xsi:nil attributes, which is how we represent nilled elements
* when we convert to XML.
*/
final lazy val namespaces = {
val scope = xml.scope
val foundXsiURI = scope.getURI("xsi")
val xsiURI = XMLUtils.xsiURI.toString
val newScope =
(foundXsiURI, this) match {
case (null, e: ElementBase) => new NamespaceBinding("xsi", xsiURI, scope)
case (`xsiURI`, _) => scope
case (s: String, _) => schemaDefinitionError("Prefix 'xsi' must be bound to the namespace '%s', but was bound to the namespace '%s'.", xsiURI, s)
case (null, _) => scope
}
newScope
}
/**
* ALl non-terms get runtimeData from this definition. All Terms
* which are elements and model-groups) override this.
*
* The Term class has a generic termRuntimeData => TermRuntimeData
* function (useful since all Terms share things like having charset encoding)
* The Element classes all inherit an elementRuntimeData => ElementRuntimeData
* and the model groups all have modelGroupRuntimeData => ModelGroupRuntimeData.
*
* There is also VariableRuntimeData and SchemaSetRuntimeData.
*/
lazy val runtimeData: RuntimeData = nonTermRuntimeData // overrides in ModelGroup, ElementBase
lazy val isArray = false // overridden in local elements
final def nonTermRuntimeData = LV('nonTermRuntimeData) {
new NonTermRuntimeData(
variableMap,
schemaFileLocation,
prettyName,
path,
namespaces,
enclosingElement.map { _.erd },
Maybe.toMaybe(enclosingTerm.map { _.termRuntimeData }))
}.value
def variableMap: VariableMap = LV('variableMap) {
schemaSet.variableMap
}.value
/*
* Anything non-annotated always returns property not found
*
* Override in annotated components
*/
def findPropertyOption(pname: String): PropertyLookupResult = {
ExecutionMode.requireCompilerMode
NotFound(Nil, Nil, pname)
}
// Q: not sure why non-annotated schema components need to have findProperty
// on them at all. Who would call it polymorphically, not knowing whether they
// have an annotated schema component or not?
// A: DFDLAnnotation - the annotation objects themselves - aren't annotated
// schema components - they have a relationship to an annotated schema component
// but clearly they carry properties.
lazy val schemaFile: Option[DFDLSchemaFile] = parent.schemaFile
lazy val schemaSet: SchemaSet = parent.schemaSet
lazy val schemaDocument: SchemaDocument = parent.schemaDocument
lazy val xmlSchemaDocument: XMLSchemaDocument = parent.xmlSchemaDocument
lazy val schema: Schema = parent.schema
lazy val schemaComponent: LookupLocation = this
override lazy val uriString: String = parent.uriString
override def isHidden: Boolean = LV('isHidden) {
enclosingComponent match {
case None => Assert.invariantFailed("Root global element should be overriding this.")
case Some(ec) => ec.isHidden
}
}.value
override lazy val enclosingComponent = LV('enclosingComponent) {
val res = enclosingComponentDef.asInstanceOf[Option[SchemaComponent]]
res
}.value
/**
* All schema components except the root have an enclosing element.
*/
final lazy val enclosingElement: Option[ElementBase] = LV('enclosingElement) {
val et = enclosingTerm
val ee = et match {
case None => None
case Some(eb: ElementBase) => Some(eb)
case Some(sc: SchemaComponent) => sc.enclosingElement
}
ee
}.value
final lazy val enclosingTerm: Option[Term] = {
enclosingComponent match {
case None => None
case Some(t: Term) => Some(t)
case _ => enclosingComponent.get.enclosingTerm
}
}
/**
* path is used in diagnostic messages and code debug
* messages; hence, it is very important that it be
* very dependable.
*/
override lazy val path = {
val p = scPath.map { _.prettyName }.mkString("::")
p
}
override def toString = prettyName
/**
* Includes instances. Ie., a global element will appear inside an element ref.
* a global group inside a group ref, a global type inside an element or for
* derived simple types inside another simple type, etc.
*
* Used in diagnostic messages and code debug messages
*/
final lazy val scPath: Seq[SchemaComponent] = {
val ec = enclosingComponent
val scpOpt = ec.map {
sc =>
{
Assert.invariant(sc != null)
val parentPath = sc.scPath
parentPath
}
}
val res = scpOpt.getOrElse(Nil) :+ this
res
}
/**
* the easiest way to get an empty metadata object.
*/
private val scala.xml.Elem(_, _, emptyXMLMetadata, _, _*) = <foo/>
/**
* Used as factory for the XML Node with the right namespace and prefix etc.
*
* Given "element" it creates <dfdl:element /> with the namespace definitions
* based on this schema component's corresponding XSD construct.
*
* Makes sure to inherit the scope so we have all the namespace bindings.
*/
protected final def newDFDLAnnotationXML(label: String) = {
//
// This is not a "wired" dfdl prefix.
// Rather, we are creating a new nested binding for the dfdl prefix to the right DFDL uri.
// Which applies to this element and what is inside it only.
// So we're indifferent to what the surrounding context might be using for namespace bindings.
//
val dfdlBinding = new scala.xml.NamespaceBinding("dfdl", XMLUtils.DFDL_NAMESPACE.toString, xml.scope)
scala.xml.Elem("dfdl", label, emptyXMLMetadata, dfdlBinding, true)
}
}
/**
* Local components
*/
trait LocalComponentMixin { self: SchemaComponent =>
// nothing currently - all components have a parent.
}
/**
* A schema is all the schema documents sharing a single target namespace.
*
* That is, one can write several schema documents which all have the
* same target namespace, and in that case all those schema documents make up
* the 'schema'.
*/
final class Schema(val namespace: NS, schemaDocs: Seq[SchemaDocument], schemaSetArg: SchemaSet)
extends SchemaComponent(<fake/>, schemaSetArg) {
requiredEvaluations(schemaDocuments)
override def targetNamespace: NS = namespace
override lazy val enclosingComponent = None
override lazy val schemaDocument: SchemaDocument = Assert.usageError("schemaDocument should not be called on Schema")
override lazy val schemaSet = schemaSetArg
lazy val schemaDocuments = schemaDocs
private def noneOrOne[T](scs: Seq[T], name: String): Option[T] = {
scs match {
case Nil => None
case Seq(sc) => Some(sc) // exactly one is good
case s => {
schemaSet.schemaDefinitionError(
"More than one definition for name: %s. Defined in following locations:\n%s",
name, s.map { thing =>
thing match {
case df: DFDLDefiningAnnotation => df.asAnnotation.locationDescription
case sc: SchemaComponent => sc.locationDescription
case _ => Assert.invariantFailed("should only be a SchemaComponent or a DFDLDefiningAnnotation")
}
}.mkString("\n"))
}
}
}
/**
* Given a name, retrieve the appropriate object.
*
* This just scans each schema document in the schema, checking each one.
*/
def getGlobalElementDecl(name: String) = {
// noneOrOne(schemaDocuments.flatMap { _.getGlobalElementDecl(name) }, name)
val sds = schemaDocuments
val res = sds.flatMap {
sd =>
{
val ged = sd.getGlobalElementDecl(name)
ged
}
}
noneOrOne(res, name)
}
def getGlobalSimpleTypeDef(name: String) = noneOrOne(schemaDocuments.flatMap { _.getGlobalSimpleTypeDef(name) }, name)
def getGlobalComplexTypeDef(name: String) = noneOrOne(schemaDocuments.flatMap { _.getGlobalComplexTypeDef(name) }, name)
def getGlobalGroupDef(name: String) = noneOrOne(schemaDocuments.flatMap { _.getGlobalGroupDef(name) }, name)
def getDefineFormat(name: String) = noneOrOne(schemaDocuments.flatMap { _.getDefineFormat(name) }, name)
def getDefineFormats() = schemaDocuments.flatMap { _.defineFormats }
def getDefineVariable(name: String) = noneOrOne(schemaDocuments.flatMap { _.getDefineVariable(name) }, name)
def getDefineEscapeScheme(name: String) = noneOrOne(schemaDocuments.flatMap { _.getDefineEscapeScheme(name) }, name)
def getDefaultFormat = schemaDocuments.flatMap { x => Some(x.getDefaultFormat) }
// used for bulk checking of uniqueness
lazy val globalElementDecls = schemaDocuments.flatMap(_.globalElementDecls)
lazy val globalGroupDefs = schemaDocuments.flatMap(_.globalGroupDefs)
lazy val globalSimpleTypeDefs = schemaDocuments.flatMap(_.globalSimpleTypeDefs)
lazy val globalComplexTypeDefs = schemaDocuments.flatMap(_.globalComplexTypeDefs)
lazy val defineFormats = schemaDocuments.flatMap(_.defineFormats)
lazy val defineEscapeSchemes = schemaDocuments.flatMap(_.defineEscapeSchemes)
lazy val defineVariables = schemaDocuments.flatMap(_.defineVariables)
}