summaryrefslogtreecommitdiff
path: root/sources/scala/xml/dtd/ElementValidator.scala
blob: fcafcc1f48250792be31e9d640e431eb6e420d18 (plain) (blame)
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
package scala.xml.dtd;

import ContentModel.ElemName ;
import scala.util.automata._ ;

/** validate children and/or attributes of an element
 *  exceptions are created but not thrown.
 */
class ElementValidator() extends Function1[Node,Boolean] {

  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 getIterator(nodes: Seq[Node], skipPCDATA: Boolean): Iterator[ElemName] =
    nodes . toList
	  . filter { x => x match {
            case y:SpecialNode => y match {

              case a:Atom[String] if (a.data match { case t => t.trim().length == 0 }) =>
                false; // always skip all-whitespace nodes

              case _ =>
                !skipPCDATA

            }
            case _ =>
              x.namespace == null
          }}
          . map { x => ElemName(x.label) }
          . elements;

  /** check attributes, return true if md corresponds to attribute declarations in adecls.
   */
  def check(md: MetaData): Boolean = {
    //Console.println("checking md = "+md);
    //Console.println("adecls = "+adecls);
    //@todo other exceptions
    import MakeValidationException._;
    val len: Int = exc.length;
    var j = 0;
    var ok = new scala.collection.mutable.BitSet(adecls.length);
    def find(Key:String): AttrDecl = {
      var attr: AttrDecl = null;
      val jt = adecls.elements; while(j < adecls.length) {
        jt.next match {
          case a @ AttrDecl(Key, _, _) => attr = a; ok.set(j); j = adecls.length;
          case _                       => j = j + 1;
        }
      }
      attr
    }
    val it = md.elements; while(it.hasNext) {
      val attr = it.next;
      //Console.println("attr:"+attr);
      j = 0;
      find(attr.key) match {

        case null =>
          //Console.println("exc");
          exc = fromUndefinedAttribute( attr.key ) :: exc;

        case AttrDecl(_, tpe, DEFAULT(true, fixedValue)) if(attr.value != fixedValue) =>
          exc = fromFixedAttribute( attr.key, fixedValue, attr.value) :: exc;

        case s =>
          //Console.println("s: "+s);

      }
    }
    //Console.println("so far:"+(exc.length == len));

    val missing = ok.toSet( false );
    j  = 0; var kt = adecls.elements; while(kt.hasNext) {
      kt.next match {
        case AttrDecl(key, tpe, REQUIRED) if !ok(j) =>
          exc = fromMissingAttribute( key, tpe ) :: exc;
          j = j + 1;
        case _ =>
          j = j + 1;
      }
    }
    //Console.println("finish:"+(exc.length == len));
    (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       => !getIterator(nodes, false).hasNext

    case PCDATA      => !getIterator(nodes, true).hasNext;

    case MIXED(ContentModel.Alt(branches @ _*))  => //@todo
      val j = exc.length;
      def find(Key: String): Boolean =  {
        var res = false;
        val jt = branches.elements;
        while(jt.hasNext && !res)
          jt.next match {
            case ContentModel.Letter(ElemName(Key)) => res = true;
            case _                                  =>
          }
        res
      }

      var it = getIterator(nodes, true); while(it.hasNext) {
        var label = it.next.name;
        if(!find(label)) {
          exc = MakeValidationException.fromUndefinedElement(label) :: exc;
        }
      }

      (exc.length == j) //- true if no new exception

    case _:ELEMENTS =>
      var q = 0;
      val it = getIterator(nodes, false);
      //Console.println("it empty from the start? "+(!it.hasNext));
      while( it.hasNext ) {
        val e = it.next;
        dfa.delta(q).get(e) match {
          case Some(p) => q = p;
          case _       => throw ValidationException("element "+e+" not allowed here")
        }
        //Console.println("q now " + q);
      }
      dfa.isFinal(q) //- true if arrived in final state
  }

  /** 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
    var res = (null == contentModel) || check( n.child );

    //- ? check attributes
    res = ((null == adecls) || check( n.attributes )) && res;

    res
  }
}