Skip to content

Commit

Permalink
Use targetNamespace when resolving global references without a namesp…
Browse files Browse the repository at this point in the history
…ace prefix or default namespace

When resolving global references (e.g. global element declaration,
complex types, DFDL variables, DFDL formats), if the QName does not have
a prefix then we currently use the default namespace (i.e. xmlns="...")
when resolving the reference. And if the schema does not define a
default namespace then we just assume NoNamespace.

This works fine except when a schema with a targetNamespace includes a
schema with no targetNamespace and default namespace. In that case,
resolving global references in the included schema must use the
targetNamespace of the including schema because all the global
components have been chameleoned into a different namespace.

To fix this, we now pass around targetNamespace to a number of places
where resolving a global reference might be done (which is a lot of
places), and use that targetNamespace when resolving a QName without a
prefix and no default namespace.

Note that the logic to resolve defineFormat references already did
something similar using its custom "adjustNamespace" logic. This is
removed so all global references use the same logic when resolving
references--no post-resolution adjustment is needed.

A number of functions were refactored a bit to make the logic and style
more consistent, making it easier to see where logic differed. Some
unused functions were discovered and removed.

DAFFODIL-2916
  • Loading branch information
stevedlawrence committed Sep 19, 2024
1 parent 97ea8f7 commit 2590f54
Show file tree
Hide file tree
Showing 33 changed files with 434 additions and 173 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import scala.xml.NamespaceBinding
import org.apache.daffodil.lib.api.WarnID
import org.apache.daffodil.lib.exceptions.Assert
import org.apache.daffodil.lib.util.Logger
import org.apache.daffodil.lib.xml.NS
import org.apache.daffodil.lib.xml.NamedQName
import org.apache.daffodil.lib.xml.QNameRegex
import org.apache.daffodil.runtime1.BasicComponent
Expand All @@ -52,6 +53,7 @@ class DFDLPathExpressionParser[T <: AnyRef](
qn: NamedQName,
nodeInfoKind: NodeInfo.Kind,
namespaces: NamespaceBinding,
targetNamespace: NS,
context: DPathCompileInfo,
isEvaluatedAbove: Boolean,
host: BasicComponent
Expand Down Expand Up @@ -229,7 +231,7 @@ class DFDLPathExpressionParser[T <: AnyRef](
def Comp = EqualityComp | NumberComp

def TopLevel: Parser[WholeExpression] = ("{" ~> Expr <~ "}") ^^ { xpr =>
WholeExpression(nodeInfoKind, xpr, namespaces, context, host)
WholeExpression(nodeInfoKind, xpr, namespaces, targetNamespace, context, host)
}

val SuccessAtEnd = Parser { in => Success(in, new CharSequenceReader("")) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ abstract class Expression extends OOLAGHostImpl() with BasicComponent {

lazy val namespaces: NamespaceBinding = parent.namespaces

lazy val targetNamespace: NS = parent.targetNamespace

def children: Seq[Expression]

def setContextsForChildren(context: OOLAGHost = this): Unit = {
Expand Down Expand Up @@ -219,7 +221,7 @@ abstract class Expression extends OOLAGHostImpl() with BasicComponent {

def resolveRef(qnameString: String): RefQName = {
QName
.resolveRef(qnameString, namespaces, tunable.unqualifiedPathStepPolicy)
.resolveRef(qnameString, namespaces, targetNamespace, tunable.unqualifiedPathStepPolicy)
.recover { case _: Throwable =>
SDE("The prefix of '%s' has no corresponding namespace definition.", qnameString)
}
Expand Down Expand Up @@ -458,6 +460,7 @@ case class WholeExpression(
nodeInfoKind: NodeInfo.Kind,
ifor: Expression,
nsBindingForPrefixResolution: NamespaceBinding,
targetNamespaceArg: NS,
ci: DPathCompileInfo,
host: BasicComponent
) extends Expression {
Expand All @@ -477,6 +480,8 @@ case class WholeExpression(

override lazy val namespaces = nsBindingForPrefixResolution

override lazy val targetNamespace = targetNamespaceArg

override def text = ifor.text

override lazy val targetType = nodeInfoKind
Expand Down Expand Up @@ -827,7 +832,12 @@ sealed abstract class StepExpression(val step: String, val pred: Option[Predicat
}

lazy val stepQName = {
val e = QName.resolveStep(step, namespaces, tunable.unqualifiedPathStepPolicy)
val e = QName.resolveStep(
step,
namespaces,
targetNamespace,
tunable.unqualifiedPathStepPolicy
)
e match {
case Failure(th) => SDE("Step %s prefix has no corresponding namespace.", step)
case Success(v) => v
Expand Down Expand Up @@ -1367,10 +1377,7 @@ case class VariableRef(val qnameString: String) extends PrimaryExpression(Nil) {
}
override def text = "$" + qnameString

lazy val theQName: RefQName = {
val refQ = resolveRef(qnameString)
refQ
}
lazy val theQName: RefQName = resolveRef(qnameString)

lazy val vrd = compileInfo.variableMap
.getVariableRuntimeData(theQName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import org.apache.daffodil.core.dpath._
import org.apache.daffodil.lib.exceptions.Assert
import org.apache.daffodil.lib.schema.annotation.props.Found
import org.apache.daffodil.lib.util.DPathUtil
import org.apache.daffodil.lib.xml.NS
import org.apache.daffodil.lib.xml.NamedQName
import org.apache.daffodil.runtime1.BasicComponent
import org.apache.daffodil.runtime1.dpath.NodeInfo
Expand Down Expand Up @@ -60,6 +61,7 @@ class ExpressionCompiler[T <: AnyRef] extends ExpressionCompilerBase[T] {
nodeInfoKind: NodeInfo.Kind,
exprOrLiteral: String,
namespaces: NamespaceBinding,
targetNamespace: NS,
compileInfoWhereExpressionWasLocated: DPathCompileInfo,
isEvaluatedAbove: Boolean,
host: BasicComponent,
Expand All @@ -76,6 +78,7 @@ class ExpressionCompiler[T <: AnyRef] extends ExpressionCompilerBase[T] {
nodeInfoKind,
exprOrLiteral,
namespaces,
targetNamespace,
compileInfoWhereExpressionWasLocated,
isEvaluatedAbove,
host,
Expand All @@ -87,6 +90,7 @@ class ExpressionCompiler[T <: AnyRef] extends ExpressionCompilerBase[T] {
nodeInfoKind,
exprOrLiteral,
namespaces,
targetNamespace,
compileInfoWhereExpressionWasLocated,
isEvaluatedAbove
)
Expand Down Expand Up @@ -122,6 +126,7 @@ class ExpressionCompiler[T <: AnyRef] extends ExpressionCompilerBase[T] {
nodeInfoKind,
property.value,
property.location.namespaces,
property.location.targetNamespace,
compileInfo,
isEvaluatedAbove,
host,
Expand Down Expand Up @@ -166,6 +171,7 @@ class ExpressionCompiler[T <: AnyRef] extends ExpressionCompilerBase[T] {
staticNodeInfoKind,
exprOrLiteral,
namespacesForNamespaceResolution,
property.location.targetNamespace,
compileInfoWhereExpressionWasLocated,
isEvaluatedAbove,
host,
Expand All @@ -180,6 +186,7 @@ class ExpressionCompiler[T <: AnyRef] extends ExpressionCompilerBase[T] {
runtimeNodeInfoKind,
exprOrLiteral,
namespacesForNamespaceResolution,
property.location.targetNamespace,
compileInfoWhereExpressionWasLocated,
isEvaluatedAbove,
host,
Expand Down Expand Up @@ -212,6 +219,7 @@ class ExpressionCompiler[T <: AnyRef] extends ExpressionCompilerBase[T] {
nodeInfoKind: NodeInfo.Kind,
exprOrLiteral: String,
namespaces: NamespaceBinding,
targetNamespace: NS,
compileInfoWhereExpressionWasLocated: DPathCompileInfo,
isEvaluatedAbove: Boolean,
host: BasicComponent,
Expand All @@ -231,6 +239,7 @@ class ExpressionCompiler[T <: AnyRef] extends ExpressionCompilerBase[T] {
qn,
nodeInfoKind,
namespaces,
targetNamespace,
compileInfo,
isEvaluatedAbove,
host
Expand All @@ -244,6 +253,7 @@ class ExpressionCompiler[T <: AnyRef] extends ExpressionCompilerBase[T] {
nodeInfoKind: NodeInfo.Kind,
exprOrLiteral: String,
namespaces: NamespaceBinding,
targetNamespace: NS,
compileInfoWhereExpressionWasLocated: DPathCompileInfo,
isEvaluatedAbove: Boolean
): CompiledExpression[T] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,12 @@ class DFDLDefineVariable private (node: Node, doc: SchemaDocument)
}

final lazy val typeQName = {
val eQN = QName.resolveRef(typeQNameString, namespaces, tunable.unqualifiedPathStepPolicy)
val eQN = QName.resolveRef(
typeQNameString,
namespaces,
targetNamespace,
tunable.unqualifiedPathStepPolicy
)
val res = eQN.recover { case _: Throwable =>
SDE("Variables must have primitive types. Type is '%s'.", typeQNameString)
}.get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import org.apache.daffodil.lib.api.WarnID
import org.apache.daffodil.lib.exceptions.Assert
import org.apache.daffodil.lib.schema.annotation.props.LookupLocation
import org.apache.daffodil.lib.xml.NS
import org.apache.daffodil.lib.xml.NoNamespace
import org.apache.daffodil.lib.xml.RefQName
import org.apache.daffodil.lib.xml.XMLUtils

Expand Down Expand Up @@ -124,14 +123,6 @@ abstract class DFDLFormatAnnotation(nodeArg: Node, annotatedSCArg: AnnotatedSche
}
}

private def adjustNamespace(ns: NS) = {
ns match {
case NoNamespace =>
annotatedSC.targetNamespace // this could also be NoNamespace, but that's ok.
case _ => ns
}
}

// The ListMap collection preserves insertion order.
private type NamedFormatMap = ListMap[RefQName, DFDLFormat]

Expand All @@ -144,33 +135,21 @@ abstract class DFDLFormatAnnotation(nodeArg: Node, annotatedSCArg: AnnotatedSche
val res =
qns
.map { case qn =>
// first we have to adjust the namespace
// because a file with no target namespace,
// can reference something in another file, which also has no target
// namespace. The files can collectively or by nesting, be
// included in a third file that has a namespace, and in that
// case all the format definitions being created as those
// files are loaded will be in that third namespace.
// so just because we had <dfdl:format ref="someFormat"/> and the
// ref has no namespace prefix on it, doesn't mean that the
// defineFormat we're seeking is in no namespace.
val adjustedNS = adjustNamespace(qn.namespace)
val adjustedQN = RefQName(None, qn.local, adjustedNS)
val notSeenIt = seen.get(adjustedQN) == None
val notSeenIt = seen.get(qn) == None
schemaDefinitionUnless(
notSeenIt,
"Format ref attributes form a cycle: \n%s\n%s",
(adjustedQN, locationDescription),
(qn, locationDescription),
seen.map { case (qn, fmtAnn) => (qn, fmtAnn.locationDescription) }.mkString("\n")
)
val defFmt = schemaSet.getDefineFormat(adjustedQN).getOrElse {
val defFmt = schemaSet.getDefineFormat(qn).getOrElse {
annotatedSC.schemaDefinitionError(
"defineFormat with name '%s', was not found.",
adjustedQN.toString
qn.toString
)
}
val fmt = defFmt.formatAnnotation
val newSeen = seen + (adjustedQN -> fmt)
val newSeen = seen + (qn -> fmt)
val moreRefs = fmt.getFormatRefs(newSeen)
moreRefs
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,7 @@ trait ElementDeclMixin extends ElementLikeMixin with ElementDeclView {

final lazy val optNamedComplexType: Option[GlobalComplexTypeDef] = {
namedTypeQName.flatMap { qn =>
val res = schemaSet.getGlobalComplexTypeDef(qn)
res
schemaSet.getGlobalComplexTypeDef(qn)
}
}

Expand Down Expand Up @@ -144,7 +143,9 @@ trait ElementDeclMixin extends ElementLikeMixin with ElementDeclView {

final lazy val optNamedSimpleType: Option[SimpleTypeBase] = {
namedTypeQName.flatMap { qn =>
schemaSet.getPrimitiveType(qn).orElse(schemaSet.getGlobalSimpleTypeDef(qn))
PrimType.fromQName(qn).map { PrimitiveType(_) }.orElse {
schemaSet.getGlobalSimpleTypeDef(qn)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,12 @@ final class Restriction private (xmlArg: Node, val simpleTypeDef: SimpleTypeDefB

lazy val baseQName: RefQName = {
val tryBaseQName =
QName.resolveRef(baseQNameString, xml.scope, tunable.unqualifiedPathStepPolicy)
QName.resolveRef(
baseQNameString,
xml.scope,
targetNamespace,
tunable.unqualifiedPathStepPolicy
)
schemaDefinitionUnless(
tryBaseQName.isSuccess,
"Failed to resolve base property reference for xs:restriction: " + tryBaseQName.failed.get.getMessage
Expand All @@ -102,10 +107,10 @@ final class Restriction private (xmlArg: Node, val simpleTypeDef: SimpleTypeDefB
* Exclusive - restriction either has a baseType or a direct primType.
*/
lazy val (optDirectPrimType, optBaseTypeDef: Option[GlobalSimpleTypeDef]) = {
val optPT = schemaSet.getPrimitiveType(baseQName)
val optPT = PrimType.fromQName(baseQName)
val res =
if (optPT.isDefined)
(Some(optPT.get.typeNode), None)
(optPT, None)
else {
val optFactory = schemaSet.getGlobalSimpleTypeDef(baseQName)
val bType =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ import org.apache.daffodil.lib.util.Delay
import org.apache.daffodil.lib.util.Misc
import org.apache.daffodil.lib.xml.GetAttributesMixin
import org.apache.daffodil.lib.xml.NS
import org.apache.daffodil.lib.xml.RefQName
import org.apache.daffodil.lib.xml.ResolvesQNames
import org.apache.daffodil.lib.xml.XMLUtils
import org.apache.daffodil.runtime1.BasicComponent
import org.apache.daffodil.runtime1.dpath.NodeInfo
import org.apache.daffodil.runtime1.dsom._
import org.apache.daffodil.runtime1.processors.VariableMap

Expand Down Expand Up @@ -92,6 +94,7 @@ trait SchemaComponent
Delay('nonElementParents, this, parents),
variableMap,
namespaces,
targetNamespace,
path,
schemaFileLocation,
tunable.unqualifiedPathStepPolicy
Expand Down Expand Up @@ -262,6 +265,21 @@ trait SchemaComponent
new scala.xml.NamespaceBinding("dfdl", XMLUtils.DFDL_NAMESPACE.toString, xml.scope)
scala.xml.Elem("dfdl", label, emptyXMLMetadata, dfdlBinding, true)
}

def errMissingGlobalReferenceNoPrim(
qname: RefQName,
propertyName: String,
referenceDescription: String
): Nothing = {
val isPrimitive = NodeInfo.PrimType.fromQName(qname).isDefined
val msg =
if (isPrimitive)
s"The $propertyName property cannnot resolve to a primitive type: $qname"
else
s"Failed to resolve $propertyName to a $referenceDescription: $qname"
schemaDefinitionError(msg)
}

}

object Schema {
Expand Down Expand Up @@ -334,12 +352,10 @@ final class Schema private (
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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,24 +263,13 @@ final class SchemaDocument private (xmlSDoc: XMLSchemaDocument)
/**
* by name getters for the global things that can be referenced.
*/
def getGlobalElementDecl(name: String) = {
val geds = globalElementDecls
val res = geds.find { _.name == name }
res
}
def getGlobalElementDecl(name: String) = globalElementDecls.find { _.name == name }
def getGlobalSimpleTypeDef(name: String) = globalSimpleTypeDefs.find { _.name == name }
def getGlobalComplexTypeDef(name: String) = globalComplexTypeDefs.find { _.name == name }
def getGlobalGroupDef(name: String) = globalGroupDefs.find { _.name == name }

def getDefineFormat(name: String) = defineFormats.find { df =>
val dfName = df.namedQName.local
val res = dfName == name
res
}
def getDefineVariable(name: String) = {
val res = defineVariables.find { _.name == name }
res
}
def getDefineFormat(name: String) = defineFormats.find { _.namedQName.local == name }
def getDefineVariable(name: String) = defineVariables.find { _.name == name }
def getDefaultFormat = this.defaultFormat
def getDefineEscapeScheme(name: String) = defineEscapeSchemes.find { _.name == name }

Expand Down
Loading

0 comments on commit 2590f54

Please sign in to comment.