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

scales.xml.PullIteratees.scala Maven / Gradle / Ivy

There is a newer version: 0.5.0-M1
Show newest version
package scales.xml

import scales.utils._

/**
 * Iteratees related to pull parsing
 */ 
trait PullIteratees {

  // enumerators and iteratees follow

  import scalaz._
  import Scalaz._
  import scalaz.IterV._

  type QNamesMatch = (List[QName], Option[XmlPath])

  /**
   * Collects all data belonging to an element that matches
   * the list.  content  content 
   * as a path (each parent node containing only one child node).
   */
  def onQNames(qnames: List[QName]): ResumableIter[PullType, QNamesMatch] = {

    /*
     * The pairs allow the depth of each element to be followed.  In particular this stops both descent and ascent problems in the
     * pushing and popping on the stack.  I.e. it covers the case where you have nested repeating QNames, both when you are looking for them
     * and when your are not.  Don't pop to early and don't incorrectly force a done.
     */

    lazy val starter = Cont(step(Nil, (qnames.head, 0), qnames.tail.map((_, 0)), noXmlPath, false))

    def step(before: List[(QName, Int)], focus: (QName, Int), toGo: List[(QName, Int)], path: XmlPath, collecting: Boolean)(s: Input[PullType]): ResumableIter[PullType, QNamesMatch] =
      s(el = { e => //println(e +" "+before+" "+focus+" " + toGo); 
        e match {

          case Left(elem@Elem(q, a, n)) => {
            val nfocus = if (q == focus._1) (focus._1, focus._2 + 1)
            else focus
            lazy val npath = addAndFocus(path, elem)

            val shouldCollect = collecting || (toGo.isEmpty && q == focus._1)

            Cont(
              // is it our head?
              if ((!toGo.isEmpty) && q == focus._1)
                // move down
                step(before :+ focus, toGo.head, toGo.tail, npath, false)
              else
                // wait for a down
                step(before, nfocus, toGo, npath, shouldCollect))
          }

          case Left(x: XmlItem) =>
            if (collecting) // collect
              Cont(step(before, focus, toGo, addChild(path, x), true))
            else
              Cont(step(before, focus, toGo, path, false)) // don't collect

          case Right(EndElem(q, n)) =>

            if (q == focus._1) {
              val ncfocus = (focus._1, focus._2 - 1)

              if (toGo.isEmpty && ncfocus._2 == 0) // we are popping to the selected level
                Done(((qnames, Some(path)),
                  Cont(step(before, ncfocus, toGo,
                    // remove all children on the next iteration
                    path.removeAndUp.getOrElse(noXmlPath), false))), IterV.Empty[PullType])
              else {
                if (before.isEmpty)
                  starter // only when the root is asked for, could just refuse that of course?		
                else {
                  if (collecting)
                    // we are collecting but we still have more than 0 repeated qnames deep
                    Cont(step(before, ncfocus, toGo, path.zipUp, true))
                  else {
                    // we aren't collecting but we are moving up, we just have repeated names 
                    val nfocus = before.last
                    val nbefore = before.dropRight(1)
                    Cont(step(nbefore, nfocus, focus :: toGo,
                      path.removeAndUp.getOrElse(noXmlPath), false // we have NOT been collecting	
                      ))
                  }
                }
              }
            } else {
              Cont(step(before, focus, toGo,
                if (collecting) // empty is not enough, it should also be definitely collecting
                  path.zipUp
                else
                  path.removeAndUp.getOrElse(noXmlPath), collecting))
            }

        }
      },
        empty = Cont(step(before, focus, toGo, path, false)),
        eof = Done(((qnames, None), starter), IterV.EOF[PullType]))

    if (qnames.isEmpty) error("Qnames is empty")

    starter
  }

  type PeekMatch = Option[XmlPath]

  def skipv(downTo: Int*): IterV[PullType, PeekMatch] = skip(List(downTo: _*))

  /**
   * Skips all events until the indexes match downTo, can be seen as
   * \*\*[b]\*[c] skipping until c with skip(List(b,c)).
   * This can be used, for example, to identify qnames within a message and combined with capture to allow replaying.
   * Identifying a soap doc-lit request would be skip(List(2,1)).
   * It returns the XmlPath to the skipped position, for soap /Envelope/Body/Request but does not collect the contents of that node.
   * An empty list will simply return the first Element found.
   */
  def skip(downTo: => List[Int]): IterV[PullType, PeekMatch] = {

    lazy val dEof: IterV[PullType, PeekMatch] = Done(None, IterV.EOF[PullType])

    def step(before: List[Int], pos: List[Int], toGo: List[Int], path: XmlPath)(s: Input[PullType]): IterV[PullType, PeekMatch] =
      s(el = { e =>
        e match {

          case Left(elem@Elem(q, a, n)) => {
            lazy val npath = addAndFocus(path, elem)
            val npos = pos.head + 1 :: pos.tail
            val could = toGo.head == npos.head
            //println("pos "+pos+ " npos "+npos+" before "+before+" toGo "+toGo)
            if (pos.size == (before.size + 1)) // correct level
              if (toGo.size == 1 && could)
                Done(Some(npath), IterV.Empty[PullType])
              else if (npos.head > toGo.head)
                dEof
              else if (could)
                // pop and move down
                Cont(step(before :+ toGo.head, 0 :: npos, toGo.tail, npath))
              else
                Cont(step(before, 0 :: npos, toGo, npath))
            else
              Cont(step(before, 0 :: npos, toGo, npath))

          }

          // just return this again
          case Left(x: XmlItem) =>
            Cont(step(before, pos, toGo, path))

          // pop up no collecting, loose the head as we are moving up again
          case Right(EndElem(q, n)) =>
            // get or else end doc elem
            if (pos.size > 0 && pos.size == before.size + 1)
              // we have moved down in toGo
              Cont(step(before.dropRight(1), pos.tail, before.last :: toGo, path.removeAndUp().getOrElse(noXmlPath)))
            else
              Cont(step(before, pos.tail, toGo, path.removeAndUp().getOrElse(noXmlPath)))

        }
      },
        empty = Cont(step(before, pos, toGo, path)),
        eof = dEof //Done((downTo, None),IterV.EOF[PullType])
        )

    Cont(step(List[Int](), List(0), 1 :: downTo, noXmlPath))
  }

  /**
   * Wraps XmlPull
   */ 
  def iterate(path: List[QName], xml: XmlPull): FlatMapIterator[XmlPath] = iterate(path, xml.it)

  class Iterate(path: List[QName], xml: Iterator[PullType]) extends FlatMapIterator[XmlPath] { self =>
    import ScalesXml._
    import ScalesUtils._

    val orig = withIter(xml)(onDone(List(onQNames(path))))
    def getNext = {
      if (orig.hasNext) {
        val t = orig.next
        if (t.size == 1)
          (true, t.head._2)
        else (false, None)
      } else (false, None)
    }
    var cur = getNext
    def hasNext = cur._1 && cur._2.isDefined
    def next = {
      val t = cur._2
      cur = getNext
      t.get
    }
        
  }

  /**
   * A wrapping around withIter(onDone(List(onQNames(path))))(enumXml(xml, _))
   * it unwraps the data providing an Iterator[XPath]
   */
  def iterate(path: List[QName], xml: Iterator[PullType]): FlatMapIterator[XmlPath] =
    new Iterate(path, xml)

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy