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

// $Id: FactoryAdapter.scala 18387 2009-07-24 15:28:37Z odersky $


package scala.xml
package parsing

import java.io.{InputStream, Reader, File, FileDescriptor, FileInputStream}
import scala.collection.mutable.Stack

import org.xml.sax.{ Attributes, InputSource }
import org.xml.sax.helpers.DefaultHandler
import javax.xml.parsers.{ SAXParser, SAXParserFactory }

// can be mixed into FactoryAdapter if desired
trait ConsoleErrorHandler extends DefaultHandler
{
  import org.xml.sax.SAXParseException
  
  // ignore warning, crimson warns even for entity resolution!
  override def warning(ex: SAXParseException): Unit = { }
  override def error(ex: SAXParseException): Unit = printError("Error", ex) 
  override def fatalError(ex: SAXParseException): Unit = printError("Fatal Error", ex)

  protected def printError(errtype: String, ex: SAXParseException): Unit =
    Console.withOut(Console.err) {
      val s = "[%s]:%d:%d: %s".format(
        errtype, ex.getLineNumber, ex.getColumnNumber, ex.getMessage)
      Console.println(s)
      Console.flush
    }
}

/** SAX adapter class, for use with Java SAX parser. Keeps track of 
 *  namespace bindings, without relying on namespace handling of the 
 *  underlying SAX parser.
 */
abstract class FactoryAdapter extends DefaultHandler with factory.XMLLoader[Node]
{
  var rootElem: Node = null
  
  val buffer      = new StringBuilder()
  val attribStack = new Stack[MetaData]
  val hStack      = new Stack[Node]   // [ element ] contains siblings
  val tagStack    = new Stack[String]
  var scopeStack  = new Stack[NamespaceBinding]

  var curTag : String = null
  var capture: Boolean = false

  // abstract methods

  /** Tests if an XML element contains text.
   * @return true if element named <code>localName</code> contains text.
   */
  def nodeContainsText(localName: String): Boolean // abstract
  
  /** creates an new non-text(tree) node.
   * @param elemName
   * @param attribs
   * @param chIter
   * @return a new XML element.
   */
  def createNode(pre: String, elemName: String, attribs: MetaData,
                 scope: NamespaceBinding, chIter: List[Node]): Node //abstract 
  
  /** creates a Text node.
   * @param text
   * @return a new Text node.
   */
  def createText(text: String): Text // abstract

  /** creates a new processing instruction node.
  */
  def createProcInstr(target: String, data: String): Seq[ProcInstr] 
    
  //
  // ContentHandler methods
  //
  
  val normalizeWhitespace = false

  /** Characters.
  * @param ch
  * @param offset
  * @param length
  */
  override def characters(ch: Array[Char], offset: Int, length: Int): Unit = {
    if (!capture) return
    if (!normalizeWhitespace) {
      // compliant: report every character
      return buffer.appendAll(ch, offset, length)
    }
    
    // normalizing whitespace is not compliant, but useful
    var i: Int = offset
    while (i < offset + length) {
      val c = if (ch(i).isWhitespace) ' ' else ch(i)
      buffer append c
      i += 1
      // if that was whitespace, drop until non whitespace
      if (c == ' ') while (ch(i).isWhitespace) i += 1
    }
  }

  /* ContentHandler methods */

  /* Start element. */
  override def startElement(
    uri: String,
    _localName: String,
    qname: String,
    attributes: Attributes): Unit =
  {
    /*elemCount = elemCount + 1; STATISTICS */
    captureText()
    //Console.println("FactoryAdapter::startElement("+uri+","+_localName+","+qname+","+attributes+")");
    tagStack.push(curTag)
    curTag = qname; //localName ;

    val colon = qname.indexOf(':'.asInstanceOf[Int])
    val localName = if(-1 == colon) qname else qname.substring(colon+1,qname.length())

    //Console.println("FactoryAdapter::startElement - localName ="+localName);

    capture = nodeContainsText(localName)

    hStack.push(null)
    var m: MetaData = Null

    var scpe = scopeStack.top
    for (i <- List.range(0, attributes.getLength())) {
      //val attrType = attributes.getType(i); // unused for now
      val qname = attributes.getQName(i)
      val value = attributes.getValue(i)
      val colon = qname.indexOf(':'.asInstanceOf[Int])
      if (-1 != colon) {                     // prefixed attribute
        val pre = qname.substring(0, colon)
        val key = qname.substring(colon+1, qname.length())
        if ("xmlns" /*XML.xmlns*/ == pre)
          scpe = value.length() match {
            case 0 => new NamespaceBinding(key, null,  scpe)
            case _ => new NamespaceBinding(key, value, scpe)
          }
        else
          m = new PrefixedAttribute(pre, key, Text(value), m)
      } else if ("xmlns" /*XML.xmlns*/ == qname)
        scpe = value.length() match {
          case 0 => new NamespaceBinding(null, null,  scpe)
          case _ => new NamespaceBinding(null, value, scpe)
        }
      else
        m = new UnprefixedAttribute(qname, Text(value), m)
    }
    scopeStack.push(scpe)
    attribStack.push(m)
    ()
  } // startElement(String,String,String,Attributes)
  

  /** captures text, possibly normalizing whitespace
   */
  def captureText(): Unit = {
    if (capture) {
      val text = buffer.toString()
      if (text.length() > 0)
        hStack.push(createText(text))
    }
    buffer.setLength(0)
  }

  /** End element.
   * @param uri
   * @param localName
   * @param qname
   * @throws org.xml.sax.SAXException if ..
   */
  override def endElement(uri: String , _localName: String , qname: String): Unit = {
    captureText()

    val metaData = attribStack.pop

    // reverse order to get it right
    var v: List[Node] = Nil
    var child: Node = hStack.pop
    while (child ne null) {
      v = child::v
      child = hStack.pop
    }

    val colon = qname.indexOf(':'.asInstanceOf[Int])
    val localName =
      if (-1 == colon) qname
      else qname.substring(colon+1, qname.length())  

    val scp = scopeStack.pop
    // create element
    val pre = if (-1 == colon) null else qname.substring(0, colon)
    rootElem = createNode(pre, localName, metaData, scp, v)

    hStack.push(rootElem)

    // set
    curTag = tagStack.pop

    capture =
      if (curTag ne null) nodeContainsText(curTag) // root level
      else false
  } // endElement(String,String,String)
  
  /** Processing instruction.
  */
  override def processingInstruction(target: String, data: String) {
    for (pi <- createProcInstr(target, data))
      hStack.push(pi)
  }
}