All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scala.xml.NodeSeq.scala Maven / Gradle / Ivy

/*                     __                                               *\
**     ________ ___   / /  ___     Scala API                            **
**    / __/ __// _ | / /  / _ |    (c) 2003-2020, LAMP/EPFL             **
**  __\ \/ /__/ __ |/ /__/ __ |    (c) 2011-2020, Lightbend, Inc.       **
** /____/\___/_/ |_/____/_/ | |    http://scala-lang.org/               **
**                          |/                                          **
\*                                                                      */

package scala
package xml

import scala.collection.{ mutable, immutable, AbstractSeq }
import mutable.{ Builder, ListBuffer }
import ScalaVersionSpecific.CBF
import scala.language.implicitConversions
import scala.collection.Seq

/**
 * This object ...
 *
 *  @author  Burak Emir
 */
object NodeSeq {
  final val Empty = fromSeq(Nil)
  def fromSeq(s: Seq[Node]): NodeSeq = new NodeSeq {
    def theSeq = s
  }

  // ---
  // For 2.11 / 2.12 only. Moving the implicit to a parent trait of `object NodeSeq` and keeping it
  // in ScalaVersionSpecific doesn't work because the implicit becomes less specific, which leads to
  // ambiguities.
  type Coll = NodeSeq
  implicit def canBuildFrom: CBF[Coll, Node, NodeSeq] = ScalaVersionSpecific.NodeSeqCBF
  // ---

  def newBuilder: Builder[Node, NodeSeq] = new ListBuffer[Node] mapResult fromSeq
  implicit def seqToNodeSeq(s: Seq[Node]): NodeSeq = fromSeq(s)
}

/**
 * This class implements a wrapper around `Seq[Node]` that adds XPath
 *  and comprehension methods.
 *
 *  @author  Burak Emir
 */
abstract class NodeSeq extends AbstractSeq[Node] with immutable.Seq[Node] with ScalaVersionSpecificNodeSeq with Equality with Serializable {
  def theSeq: Seq[Node]
  def length = theSeq.length
  override def iterator = theSeq.iterator

  def apply(i: Int): Node = theSeq(i)
  def apply(f: Node => Boolean): NodeSeq = filter(f)

  def xml_sameElements[A](that: Iterable[A]): Boolean = {
    val these = this.iterator
    val those = that.iterator
    while (these.hasNext && those.hasNext)
      if (these.next xml_!= those.next)
        return false

    !these.hasNext && !those.hasNext
  }

  protected def basisForHashCode: Seq[Any] = theSeq

  override def canEqual(other: Any) = other match {
    case _: NodeSeq => true
    case _          => false
  }

  override def strict_==(other: Equality) = other match {
    case x: NodeSeq => (length == x.length) && (theSeq sameElements x.theSeq)
    case _          => false
  }

  /**
   * Projection function, which returns  elements of `this` sequence based
   *  on the string `that`. Use:
   *   - `this \ "foo"` to get a list of all children that are labelled with `"foo"`;
   *   - `this \ "_"` to get a list of all child elements (wildcard);
   *   - `this \ "@foo"` to get the unprefixed attribute `"foo"` of `this`;
   *   - `this \ "@{uri}foo"` to get the prefixed attribute `"pre:foo"` whose
   *     prefix `"pre"` is resolved to the namespace `"uri"`.
   *
   *  For attribute projections, the resulting [[scala.xml.NodeSeq]] attribute
   *  values are wrapped in a [[scala.xml.Group]].
   *
   *  There is no support for searching a prefixed attribute by its literal prefix.
   *
   *  The document order is preserved.
   */
  def \(that: String): NodeSeq = {
    def fail = throw new IllegalArgumentException(that)
    def atResult = {
      lazy val y = this(0)
      val attr =
        if (that.length == 1) fail
        else if (that(1) == '{') {
          val i = that indexOf '}'
          if (i == -1) fail
          val (uri, key) = (that.substring(2, i), that.substring(i + 1, that.length()))
          if (uri == "" || key == "") fail
          else y.attribute(uri, key)
        } else y.attribute(that drop 1)

      attr match {
        case Some(x) => Group(x)
        case _       => NodeSeq.Empty
      }
    }

    def makeSeq(cond: (Node) => Boolean) =
      NodeSeq fromSeq (this flatMap (_.child) filter cond)

    that match {
      case ""                                        => fail
      case "_"                                       => makeSeq(!_.isAtom)
      case "@"                                       => fail
      case _ if (that(0) == '@' && this.length == 1) => atResult
      case _                                         => makeSeq(_.label == that)
    }
  }

  /**
   * Projection function, which returns elements of `this` sequence and of
   *  all its subsequences, based on the string `that`. Use:
   *   - `this \\ "foo" to get a list of all elements that are labelled with `"foo"`,
   *     including `this`;
   *   - `this \\ "_"` to get a list of all elements (wildcard), including `this`;
   *   - `this \\ "@foo"` to get all unprefixed attributes `"foo"`;
   *   - `this \\ "@{uri}foo"` to get all prefixed attribute `"pre:foo"` whose
   *     prefix `"pre"` is resolved to the namespace `"uri"`.
   *
   *  For attribute projections, the resulting [[scala.xml.NodeSeq]] attribute
   *  values are wrapped in a [[scala.xml.Group]].
   *
   *  There is no support for searching a prefixed attribute by its literal prefix.
   *
   *  The document order is preserved.
   */
  def \\(that: String): NodeSeq = {
    def fail = throw new IllegalArgumentException(that)
    def filt(cond: (Node) => Boolean) = this flatMap (_.descendant_or_self) filter cond
    that match {
      case ""                  => fail
      case "_"                 => filt(!_.isAtom)
      case _ if that(0) == '@' => filt(!_.isAtom) flatMap (_ \ that)
      case _                   => filt(x => !x.isAtom && x.label == that)
    }
  }

  /**
   * Convenience method which returns string text of the named attribute. Use:
   *   - `that \@ "foo"` to get the string text of attribute `"foo"`;
   */
  def \@(attributeName: String): String = (this \ ("@" + attributeName)).text

  override def toString(): String = theSeq.mkString

  def text: String = (this map (_.text)).mkString
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy