/
ChoiceGroup.scala
250 lines (227 loc) · 10.8 KB
/
ChoiceGroup.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.daffodil.dsom
import scala.xml.Node
import scala.xml._
import org.apache.daffodil.schema.annotation.props.gen.Choice_AnnotationMixin
import org.apache.daffodil.schema.annotation.props.gen.ChoiceAGMixin
import org.apache.daffodil.grammar.ChoiceGrammarMixin
import org.apache.daffodil.exceptions.Assert
import org.apache.daffodil.schema.annotation.props.gen.YesNo
import org.apache.daffodil.schema.annotation.props.gen.ChoiceKeyKindType
/**
*
* Captures concepts common to all choice definitions, which includes both
* local and global choice definitions, but not choice group references.
*
* Choices are a bit complicated.
*
* They can have initiators and terminators. This is most easily thought of as wrapping each choice
* inside a sequence having only the choice inside it, and moving the initiator and terminator specification to that
* sequence.
*
* That sequence is then the term replacing the choice wherever the choice is.
*
* Choices can have children which are scalar elements. In this case, that scalar element behaves as if it were
* a child of the enclosing sequence (which could be the one we just injected above the choice.
*
* Choices can have children which are recurring elements. In this case, the behavior is as if the recurring child was
* placed inside a sequence which has no initiator nor terminator, but repeats the separator specification from
* the sequence context that encloses the choice.
*
* All that, and the complexities of separator suppression too.
*
* There's also issues like this:
*
* <choice>
* <element .../>
* <sequence/>
* </choice>
*
* in the above, one alternative is an empty sequence. So this choice may produce an element which takes up
* a child position, and potentially requires separation, or it may produce nothing at all.
*
*/
trait ChoiceDefMixin
extends AnnotatedSchemaComponent
with GroupDefLike {
protected def isMyFormatAnnotation(a: DFDLAnnotation) = a.isInstanceOf[DFDLChoice]
protected override def annotationFactory(node: Node): Option[DFDLAnnotation] = {
node match {
case <dfdl:choice>{ contents @ _* }</dfdl:choice> => Some(new DFDLChoice(node, this))
case _ => annotationFactoryForDFDLStatement(node, this)
}
}
protected def emptyFormatFactory: DFDLFormatAnnotation = new DFDLChoice(newDFDLAnnotationXML("choice"), this)
lazy val xmlChildren = xml match {
case <choice>{ c @ _* }</choice> => c
case <group>{ _* }</group> => {
val ch = this match {
case cgd: GlobalChoiceGroupDef => cgd.xml \ "choice"
}
val <choice>{ c @ _* }</choice> = ch(0)
c
}
}
}
abstract class ChoiceTermBase(
final override val xml: Node,
final override val optLexicalParent: Option[SchemaComponent],
final override val position: Int)
extends ModelGroup(position)
with Choice_AnnotationMixin
with RawDelimitedRuntimeValuedPropertiesMixin // initiator and terminator (not separator)
with ChoiceGrammarMixin
with ChoiceAGMixin
with HasOptRepTypeMixinImpl {
requiredEvaluationsIfActivated(branchesAreNonOptional)
requiredEvaluationsIfActivated(branchesAreNotIVCElements)
requiredEvaluationsIfActivated(modelGroupRuntimeData.preSerialization)
final protected lazy val optionChoiceDispatchKeyRaw = findPropertyOption("choiceDispatchKey")
final protected lazy val choiceDispatchKeyRaw = requireProperty(optionChoiceDispatchKeyRaw)
lazy val optionChoiceDispatchKeyKindRaw = findPropertyOption("choiceDispatchKeyKind")
lazy val defaultableChoiceDispatchKeyKind =
if (tunable.requireChoiceDispatchKeyKindProperty) {
choiceDispatchKeyKind
} else {
val asString = optionChoiceDispatchKeyKindRaw.toOption.getOrElse("implicit")
ChoiceKeyKindType(asString, this)
}
lazy val optionChoiceBranchKeyKindRaw = findPropertyOption("choiceBranchKeyKind")
lazy val defaultableChoiceBranchKeyKind =
if (tunable.requireChoiceBranchKeyKindProperty) {
choiceDispatchKeyKind
} else {
val asString = optionChoiceBranchKeyKindRaw.toOption.getOrElse("implicit")
ChoiceKeyKindType(asString, this)
}
final lazy val isDirectDispatch = {
val isDD: Boolean = defaultableChoiceDispatchKeyKind match {
case ChoiceKeyKindType.ByType => true
case ChoiceKeyKindType.Explicit => true
case ChoiceKeyKindType.Implicit => optionChoiceDispatchKeyRaw.isDefined
case ChoiceKeyKindType.Speculative => false
}
if (isDD && initiatedContent == YesNo.Yes) {
SDE("dfdl:initiatedContent must not equal 'yes' when dfdl:choiceDispatchKey is defined")
}
isDD
}
// If choiceDispatchKeyKind is byType, verify that all our children share a repType,
// and use that. Otherwise, there is no need to associate a repType with this choice
override final lazy val optRepType: Option[SimpleTypeDefBase] = defaultableChoiceDispatchKeyKind match {
case ChoiceKeyKindType.ByType => {
val branchReptypes: Seq[SimpleTypeDefBase] = groupMembers.map(term => {
term match {
case e: ElementDeclMixin => e.typeDef match {
case t: SimpleTypeDefBase => t.optRepTypeDef match {
case None => SDE("When <xs:choice> has choiceBranchKey=\"byType\", all branches must have a type which defines a repType")
case Some(x) => x
}
case _: SimpleTypeBase => SDE("When <xs:choice> has choiceBranchKey=\"byType\", no branch can have a primitive xsd type")
case _ => SDE("When <xs:choice> has choiceBranchKey=\"byType\", all branches must be a simple type")
}
case _ => SDE("When <xs:choice> has choiceBranchKey=\"byType\", all branches must be a simple element")
}
})
val ans = branchReptypes.reduce((a, b) => {
/*
* We tolerate type objects being copies or the same object because we compare the QNames.
*/
if (a.namedQName != b.namedQName) {
SDE("All children of an <xs:choice> with choiceDispatchKeyKind=byType must have the same reptype")
}
a
})
Some(ans)
}
case ChoiceKeyKindType.Speculative => None
case ChoiceKeyKindType.Explicit => None
case ChoiceKeyKindType.Implicit => None
}
/*
* When choiceBranchKeyKind=byType or choiceDispatchKeyKind=byType, we are associated with one or more repTypes (corresponding to
* the reptypes of our choices).
* Since the repValueSet represents the set of repValues that we expect to be able to be associated with an element, it does, in theory,
* make sense to assign a repValueSet to an entire choiceGroup. For instance, in the common case of choiceBranchKeyKind=byType and choiceDispatchKeyKind=byType,
* this would just be the union of the repValueSets of all of the individual choices.
* However, figuring this out becomes more complicated when we start having to deal with choiceBranchKeyKind=explicit
* There is no indication yet that this problem is particuarly intractable, however it is non-trivial. Since we never
* actually need to know the optRepValueSet of an entire choiceGroup, we can simple avoid thinking about it until such a time that we do.
*/
override final lazy val optRepValueSet = Assert.invariantFailed("We shouldn't need to compute the optRepValueSet of a choiceGroup")
final override lazy val hasKnownRequiredSyntax = LV('hasKnownRequiredSyntax) {
if (hasFraming) true
// else if (isKnownToBeAligned) true //TODO: Alignment may not occur; hence, cannot be part of determining whether there is known syntax.
else {
val res =
// For a group, we only have known required syntax if all branches do.
// If even one branch doesn't, then we don't have "known required" because the data could match
// that branch, meaning we wouldn't, in that case, have syntax.
groupMembers.forall { member =>
val res =
member.hasKnownRequiredSyntax
res
}
res
}
}.value
/**
* The DFDL Spec as of this writing 2015-06-02 says that branches cannot be optional, but then
* specifies that what it means is that if a choice branch root is an element/element-ref, it
* cannot have minOccurs="0". This is a different notion of optional.
*
* The DFDL Working group has Action 280 which is about whether this should be corrected/modified.
* There are two notions of optionality in DFDL. First is optional meaning 0 or 1 occurrence of an
* element. The second is "not required", and that is defined in terms of dfdl:occursCountKind.
*
* For the time being, the definition of non-optional used here is that is not minOccurs="0" if an
* element, and that unless it is a calculated element, it must have some footprint syntactically
* from the data value or framing.
*
* Open issues:
* 1) Is alignment or leading/trailing skip to be considered syntax. Alignment might not be there.
* 2) What about an empty sequence that only carries statement annotations such as dfdl:assert or
* dfdl:setVariable
*
* This latter need to be allowed, because while they do not have known required syntax they do
* have to be executed for side-effect.
*/
final def branchesAreNonOptional = LV('branchesAreNonOptional) {
val branchesOk = groupMembers map { branch =>
if (branch.isOptional) {
schemaDefinitionErrorButContinue("Branch of choice %s must be non-optional.".format(branch.path))
false
} else true
}
assuming(branchesOk.forall { x => x })
}.value
final def branchesAreNotIVCElements = LV('branchesAreNotIVCElements) {
val branchesOk = groupMembers map { branch =>
if (!branch.isRepresented) {
schemaDefinitionErrorButContinue("Branch of choice %s cannot have the dfdl:inputValueCalc property.".format(branch.path))
false
} else true
}
assuming(branchesOk.forall { x => x })
}.value
}
final class Choice(xmlArg: Node, lexicalParent: SchemaComponent, position: Int)
extends ChoiceTermBase(xmlArg, Option(lexicalParent), position)
with ChoiceDefMixin {
override lazy val optReferredToComponent = None
}