summaryrefslogtreecommitdiff
path: root/sources/scala/xml/NodeSeq.scala
blob: 105c2e70b4fc209cec89f85b431e8d27a393eb96 (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
/*                     __                                               *\
**     ________ ___   / /  ___     Scala API                            **
**    / __/ __// _ | / /  / _ |    (c) 2003-2004, LAMP/EPFL             **
**  __\ \/ /__/ __ |/ /__/ __ |                                         **
** /____/\___/_/ |_/____/_/ | |                                         **
**                          |/                                          **
** $Id$
\*                                                                      */

package scala.xml ;

object NodeSeq {
  final val Empty = new NodeSeq { def theSeq = Nil; }
  def fromSeq(s:Seq[Node]):NodeSeq = new NodeSeq {
    def theSeq = s;
  }
  implicit def view(s:Seq[Node]):NodeSeq = fromSeq(s);
}

/** a wrapper around Seq[Node] that adds XPath and comprehension methods */
abstract class NodeSeq extends Seq[Node] {
  import NodeSeq.view; // import view magic for NodeSeq wrappers
  def theSeq: Seq[Node];
  def length = theSeq.length;
  def elements = theSeq.elements ;
  def apply(i: int ): Node = theSeq.apply( i );

  def apply(f: Node => Boolean): NodeSeq = filter(f);

  /** structural equality */
  override def equals(x: Any) = x match {
    case z:Node      => (length == 1) && z == apply(0)
    case z:Seq[Node] => sameElements( z )
    case z:String    => text == z
    case _           => false;
  }

  /** projection function. Similar to XPath, use this \ "foo" to get a list
   *  of all elements of this sequence that are labelled with "foo".
   *  Use \ "_" as a wildcard. The document order is preserved.
   */
  def \(that: String):NodeSeq = {
    var res: NodeSeq = NodeSeq.Empty;
    that match {
      case "_" =>
        res = for( val x <- this; val y <- x.child: NodeSeq) yield { y }

      case _ if (that.charAt(0) == '@') && (this.length == 1) =>
        val k = that.substring(1);
        val y = this(0);
        val v = y.attribute(k);
        if( v != null ) {
          res = NodeSeq.fromSeq(Seq.single(new Atom(v)));
        }

      case _   =>
        res = for( val x <- this; val y <- x.child: NodeSeq; y.label == that )
              yield { y }
    }
    res
  }

  /** projection function. Similar to XPath, use this \\ 'foo to get a list
   *  of all elements of this sequence that are labelled with "foo".
   *  Use \ "_" as a wildcard. The document order is preserved.
   */

  def \\ ( that:String ): NodeSeq = that match {
      case "_" => for( val x <- this;
                       val y <- x.descendant_or_self: NodeSeq )
                  yield { y }
      case _ if that.charAt(0) == '@' =>
        val attrib = that.substring(1);
        (for(val x <- this;
             val y <- x.descendant_or_self: NodeSeq;
             val z <- y \ that)
         yield { z }):NodeSeq
      case _ => for( val x <- this;
                     val y <- x.descendant_or_self: NodeSeq;
                     y.label == that)
                  yield { y }
  }

  override def toString():String = theSeq.elements.foldLeft ("") {
    (s:String,x:Node) => s + x.toString()
  }

  def asList = elements.toList;

  def map(f: Node => Node): NodeSeq = { val x = asList map f; x }

  def flatMap(f:Node => NodeSeq): NodeSeq = { val y = asList flatMap { x => f(x).asList }; y }

  def filter(f:Node => Boolean): NodeSeq = { val x = asList filter f; x }

  def text: String = {
    val sb = new StringBuffer();
    val it = elements;
    while(it.hasNext) {
      sb.append(it.next.text);
    }
    sb.toString();
  }
}