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
|
/* __ *\
** ________ ___ / / ___ Scala API **
** / __/ __// _ | / / / _ | (c) 2002-2009, LAMP/EPFL **
** __\ \/ /__/ __ |/ /__/ __ | http://www.scala-lang.org/ **
** /____/\___/_/ |_/____/_/ | | **
** |/ **
\* */
// $Id$
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.isEmpty => 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
* @pre 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))
}
|