summaryrefslogtreecommitdiff
path: root/src/library/scala/xml/Node.scala
blob: c5b990fe88e238fd3fe01a564ce2be1552424e55 (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
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
/*                     __                                               *\
**     ________ ___   / /  ___     Scala API                            **
**    / __/ __// _ | / /  / _ |    (c) 2003-2009, LAMP/EPFL             **
**  __\ \/ /__/ __ |/ /__/ __ |    http://scala-lang.org/               **
** /____/\___/_/ |_/____/_/ | |                                         **
**                          |/                                          **
\*                                                                      */

// $Id$


package scala.xml

import collection.Seq
import collection.immutable.{List, Nil}
import collection.mutable.StringBuilder

/**
 * This object provides methods ...
 *
 * @author  Burak Emir
 * @version 1.0
 */
object Node {

  /** the constant empty attribute sequence */
  final def NoAttributes: MetaData = Null

  /** the empty namespace */
  val EmptyNamespace = ""

  def unapplySeq(n: Node) = Some((n.label, n.attributes, n.child))

}

/**
 * An abstract class representing XML with nodes of a labelled tree.
 * This class contains an implementation of a subset of XPath for navigation.
 *
 * @author  Burak Emir and others
 * @version 1.1
 */
abstract class Node extends NodeSeq {

  /** prefix of this node */
  def prefix: String = null

  /** label of this node. I.e. "foo" for <foo/>) */
  def label: String

  /** used internally. Atom/Molecule = -1 PI = -2 Comment = -3 EntityRef = -5
   */
  def isAtom = this.isInstanceOf[Atom[_]]

  /** The logic formerly found in typeTag$, as best I could infer it. */
  def doCollectNamespaces = true  // if (tag >= 0) DO collect namespaces
  def doTransform         = true  // if (tag < 0) DO NOT transform

  /**
   *  method returning the namespace bindings of this node. by default, this
   *  is TopScope, which means there are no namespace bindings except the
   *  predefined one for "xml".
   */
  def scope: NamespaceBinding = TopScope

  /**
   *  convenience, same as <code>getNamespace(this.prefix)</code>
   */
  def namespace = getNamespace(this.prefix)

  /**
   * Convenience method, same as <code>scope.getURI(pre)</code> but additionally
   * checks if scope is <code>null</code>.
   *
   * @param pre the prefix whose namespace name we would like to obtain
   * @return    the namespace if <code>scope != null</code> and prefix was
   *            found, else <code>null</code>
   */
  def getNamespace(pre: String): String = if (scope eq null) null else scope.getURI(pre)

  /**
   * Convenience method, looks up an unprefixed attribute in attributes of this node.
   * Same as <code>attributes.getValue(key)</code>
   *
   * @param  key of queried attribute.
   * @return value of <code>UnprefixedAttribute</code> with given key
   *         in attributes, if it exists, otherwise <code>null</code>.
   */
  final def attribute(key: String): Option[Seq[Node]] = attributes.get(key)

  /**
   * Convenience method, looks up a prefixed attribute in attributes of this node.
   * Same as <code>attributes.getValue(uri, this, key)</code>
   *
   * @param  uri namespace of queried attribute (may not be null).
   * @param  key of queried attribute.
   * @return value of <code>PrefixedAttribute</code> with given namespace
   *         and given key, otherwise <code>null</code>.
   */
  final def attribute(uri: String, key: String): Option[Seq[Node]] =
    attributes.get(uri, this, key)

  /**
   * Returns attribute meaning all attributes of this node, prefixed and
   * unprefixed, in no particular order. In class <code>Node</code>, this
   * defaults to <code>Null</code> (the empty attribute list).
   *
   * @return all attributes of this node
   */
  def attributes: MetaData = Null

  /**
   * Returns child axis i.e. all children of this node.
   *
   * @return all children of this node
   */
  def child: Seq[Node]

  /**
   * Descendant axis (all descendants of this node, not including node itself)
   * includes all text nodes, element nodes, comments and processing instructions.
   */
  def descendant: List[Node] =
    child.toList.flatMap { x => x::x.descendant }

  /**
   * Descendant axis (all descendants of this node, including thisa node)
   * includes all text nodes, element nodes, comments and processing instructions.
   */
  def descendant_or_self: List[Node] = this :: descendant

  /**
   * Returns true if x is structurally equal to this node. Compares prefix,
   * label, attributes and children.
   *
   * @param x ...
   * @return  <code>true</code> if ..
   */
  override def equals(x: Any): Boolean = x match {
    case g: Group   => false
    case that: Node =>
      this.prefix == that.prefix &&
      this.label == that.label &&
      this.attributes == that.attributes &&
      this.scope == that.scope &&
      equalChildren(that)
    case _          => false
  }

  // children comparison has to be done carefully - see bug #1773.
  // It would conceivably be a better idea for a scala block which
  // generates the empty string not to generate a child rather than
  // our having to filter it later, but that approach would be more
  // delicate to implement.
  private def equalChildren(that: Node) = {
    def noEmpties(xs: Seq[Node]) = xs filter (_.toString() != "")
    noEmpties(this.child) sameElements noEmpties(that.child)
  }

  /** <p>
   *    Returns a hashcode.
   *  </p>
   */
  override def hashCode(): Int =
    Utility.hashCode(prefix, label, attributes.hashCode(), scope.hashCode(), child)

  // implementations of NodeSeq methods

  /**
   *  returns a sequence consisting of only this node
   */
  def theSeq: Seq[Node] = this :: Nil

  /**
   * String representation of this node
   *
   * @param stripComment if true, strips comment nodes from result
   * @return ...
   */
  def buildString(stripComments: Boolean): String =
    Utility.toXML(this, stripComments = stripComments).toString

  /**
   * Same as <code>toString(false)</code>.
   *
   * @see <code><a href="#toString">toString(Boolean)</a></code>
   */
  override def toString(): String = buildString(false)

  /**
   * Appends qualified name of this node to <code>StringBuilder</code>.
   *
   * @param sb ...
   * @return   ...
   */
  def nameToString(sb: StringBuilder): StringBuilder = {
    if (null != prefix) {
      sb.append(prefix)
      sb.append(':')
    }
    sb.append(label)
  }

  /**
   * Returns a type symbol (e.g. DTD, XSD), default <code>null</code>.
   */
  def xmlType(): TypeSymbol = null

  /**
   * Returns a text representation of this node. Note that this is not equivalent to
   * the XPath node-test called text(), it is rather an implementation of the
   * XPath function string()
   *  Martin to Burak: to do: if you make this method abstract, the compiler will now
   *  complain if there's no implementation in a subclass. Is this what we want? Note that
   *  this would break doc/DocGenator and doc/ModelToXML, with an error message like:
doc\DocGenerator.scala:1219: error: object creation impossible, since there is a deferred declaration of method text in class Node of type => String which is not implemented in a subclass
    new SpecialNode {
    ^
   */
  override def text: String = super.text
}