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
|
/* __ *\
** ________ ___ / / ___ Scala API **
** / __/ __// _ | / / / _ | (c) 2002-2010, LAMP/EPFL **
** __\ \/ /__/ __ |/ /__/ __ | http://www.scala-lang.org/ **
** /____/\___/_/ |_/____/_/ | | **
** |/ **
\* */
package scala.xml
package dtd
import PartialFunction._
import ContentModel.ElemName
import MakeValidationException._ // @todo other exceptions
import scala.util.automata._
import scala.collection.mutable.BitSet
/** validate children and/or attributes of an element
* exceptions are created but not thrown.
*/
class ElementValidator() extends Function1[Node,Boolean] {
private var exc: List[ValidationException] = Nil
protected var contentModel: ContentModel = _
protected var dfa: DetWordAutom[ElemName] = _
protected var adecls: List[AttrDecl] = _
/** set content model, enabling element validation */
def setContentModel(cm: ContentModel) = {
contentModel = cm
cm match {
case ELEMENTS(r) =>
val nfa = ContentModel.Translator.automatonFrom(r, 1)
dfa = new SubsetConstruction(nfa).determinize
case _ =>
dfa = null
}
}
def getContentModel = contentModel
/** set meta data, enabling attribute validation */
def setMetaData(adecls: List[AttrDecl]) { this.adecls = adecls }
def getIterable(nodes: Seq[Node], skipPCDATA: Boolean): Iterable[ElemName] = {
def isAllWhitespace(a: Atom[_]) = cond(a.data) { case s: String if s.trim == "" => true }
nodes.filter {
case y: SpecialNode => y match {
case a: Atom[_] if isAllWhitespace(a) => false // always skip all-whitespace nodes
case _ => !skipPCDATA
}
case x => x.namespace eq null
} . map (x => ElemName(x.label))
}
/** check attributes, return true if md corresponds to attribute declarations in adecls.
*/
def check(md: MetaData): Boolean = {
val len: Int = exc.length
var ok = new BitSet(adecls.length)
for (attr <- md) {
def attrStr = attr.value.toString
def find(Key: String): Option[AttrDecl] = {
adecls.zipWithIndex find {
case (a @ AttrDecl(Key, _, _), j) => ok += j ; return Some(a)
case _ => false
}
None
}
find(attr.key) match {
case None =>
exc ::= fromUndefinedAttribute(attr.key)
case Some(AttrDecl(_, tpe, DEFAULT(true, fixedValue))) if attrStr != fixedValue =>
exc ::= fromFixedAttribute(attr.key, fixedValue, attrStr)
case _ =>
}
}
adecls.zipWithIndex foreach {
case (AttrDecl(key, tpe, REQUIRED), j) if !ok(j) => exc ::= fromMissingAttribute(key, tpe)
case _ =>
}
exc.length == len //- true if no new exception
}
/** check children, return true if conform to content model
* @note contentModel != null
*/
def check(nodes: Seq[Node]): Boolean = contentModel match {
case ANY => true
case EMPTY => getIterable(nodes, false).isEmpty
case PCDATA => getIterable(nodes, true).isEmpty
case MIXED(ContentModel.Alt(branches @ _*)) => // @todo
val j = exc.length
def find(Key: String): Boolean =
branches exists { case ContentModel.Letter(ElemName(Key)) => true ; case _ => false }
getIterable(nodes, true) map (_.name) filterNot find foreach {
exc ::= MakeValidationException fromUndefinedElement _
}
(exc.length == j) // - true if no new exception
case _: ELEMENTS =>
dfa isFinal {
getIterable(nodes, false).foldLeft(0) { (q, e) =>
(dfa delta q).getOrElse(e, throw ValidationException("element %s not allowed here" format e))
}
}
}
/** applies various validations - accumulates error messages in exc
* @todo fail on first error, ignore other errors (rearranging conditions)
*/
def apply(n: Node): Boolean =
//- ? check children
((contentModel == null) || check(n.child)) &&
//- ? check attributes
((adecls == null) || check(n.attributes))
}
|