// -----------------------------------------------------------------------------
//
// Scalax - The Scala Community Library
// Copyright (c) 2005-8 The Scalax Project. All rights reserved.
//
// The primary distribution site is http://scalax.scalaforge.org/
//
// This software is released under the terms of the Revised BSD License.
// There is NO WARRANTY. See the file LICENSE for the full text.
//
// -----------------------------------------------------------------------------
package scalax.io
import java.io._
import java.text._
import scala.collection.mutable._
import scalax.control._
import scalax.data._
import scalax.io.Implicits._
/** An iterator interface to a CSV data stream: generates an array of strings
* representing each row. Note that the underlying Reader should be closed
* manually (or by a ManagedResource) if iteration is not completed. */
class CsvIterator(csv : Reader) extends Iterator[Array[String]] {
/** The separator character, a comma by default, but could be overridden to
* any character which is not whitespace or '"'. */
def sep = ','
/** The number of fields each row should have. Default is 0, which means
* any number. */
def arity = 0
/** If true, ignore blank lines and treat commentStart outside quotes as
* starting a comment line. Default is false. */
def comments = false
/** The comment start character. Default '#'. Only effective if comments is
* true. */
def commentStart = '#'
private val br = csv.ensureBuffered
private var nextLine = ""
private var lineNo = 0
getNext()
private def getNext() : Unit = {
nextLine = br.readLine()
lineNo += 1
if(nextLine == null) {
br.close()
} else if(comments) {
var i = 0
val len = nextLine.length
while(i < len && Character.isWhitespace(nextLine.charAt(i))) i += 1
if(i == len || nextLine.charAt(i) == commentStart) getNext()
}
}
def hasNext : Boolean = (nextLine != null)
def next : Array[String] = {
var fields = new ArrayBuffer[String]
var chars = nextLine.toCharArray()
var len = chars.length
var i = -1
while(i < len) {
val dropComment = i == -1 || chars(i) != sep
i += 1
while(i < len && Character.isWhitespace(chars(i))) i += 1
if(i < len && chars(i) == '"') {
// Quoted field
val field = new StringBuilder
i += 1
var done = false
while(!done) {
val start = i
while(i < len && chars(i) != '"') i += 1
field.append(chars, start, i - start)
if(i == len) {
field.append('\n')
nextLine = br.readLine()
if(nextLine == null) {
br.close()
throw new ParseException(lineNo+":"+(i + 1)+
": Mismatched quotes", lineNo)
}
chars = nextLine.toCharArray()
len = chars.length
lineNo += 1
i = 0
} else if(i + 1 < len && chars(i + 1) == '"') {
field.append('"')
i = i + 2
} else {
done = true
}
}
i += 1
while(i < len && chars(i) != sep && !(comments && chars(i) == commentStart)) {
if(!Character.isWhitespace(chars(i)))
throw new ParseException(lineNo+":"+(i + 1)+
": Garbage after close quote", lineNo)
i += 1
}
if(i < len && comments && chars(i) == commentStart) i -= 1
fields += field.toString()
} else if(comments && i < len && chars(i) == commentStart) {
// Comment
if(!dropComment) fields += ""
i = len
} else {
// Non-quoted field
val start = i
while(i < len && chars(i) != sep && !(comments && chars(i) == commentStart)) i += 1
var end = i - 1
if(i < len && comments && chars(i) == commentStart) i -= 1
while(end >= start && Character.isWhitespace(chars(end))) end = end - 1
fields += new String(chars, start, end - start + 1)
}
}
if(arity != 0 && fields.length != arity)
throw new ParseException(lineNo+":1: Found "+fields.length+
" fields but was expecting "+arity, lineNo)
getNext()
fields.toArray
}
}
class CsvFile(val resource : ManagedResource[Reader])
extends ManagedSequence[Array[String]] { self =>
def this(f : File) = this(f.reader)
def this(f : File, cs : String) = this(f.reader(cs))
def sep = ','
def arity = 0
def comments = false
def commentStart = '#'
type Handle = Reader
protected def iterator(v : Reader) = new CsvIterator(v) {
override def sep = self.sep
override def arity = self.arity
override def comments = self.comments
override def commentStart = self.commentStart
}
}
class KeyValueIterator(r : Reader) extends Iterator[(String, String)] {
private val csv = new CsvIterator(r) {
override def sep = '='
override def arity = 2
override def comments = true
}
def hasNext = csv.hasNext
def next = {
val a = csv.next
(a(0), a(1))
}
}
class KeyValueFile(val resource : ManagedResource[Reader])
extends ManagedSequence[(String, String)] { self =>
def this(f : File) = this(f.reader)
def this(f : File, cs : String) = this(f.reader(cs))
type Handle = Reader
protected def iterator(v : Reader) = new KeyValueIterator(v)
}