package scala.xml
import collection.mutable
import mutable.{ Set, HashSet }
import parsing.XhtmlEntities
object Utility extends AnyRef with parsing.TokenTests
{
final val SU = '\u001A'
implicit def implicitSbToString(sb: StringBuilder) = sb.toString()
private [xml] def sbToString(f: (StringBuilder) => Unit): String = {
val sb = new StringBuilder
f(sb)
sb.toString
}
private[xml] def isAtomAndNotText(x: Node) = x.isAtom && !x.isInstanceOf[Text]
def trim(x: Node): Node = x match {
case Elem(pre, lab, md, scp, child@_*) =>
Elem(pre, lab, md, scp, (child flatMap trimProper):_*)
}
def trimProper(x:Node): Seq[Node] = x match {
case Elem(pre,lab,md,scp,child@_*) =>
Elem(pre,lab,md,scp, (child flatMap trimProper):_*)
case Text(s) =>
new TextBuffer().append(s).toText
case _ =>
x
}
def sort(md: MetaData): MetaData = if((md eq Null) || (md.next eq Null)) md else {
val key = md.key
val smaller = sort(md.filter { m => m.key < key })
val greater = sort(md.filter { m => m.key > key })
smaller.append( Null ).append(md.copy ( greater ))
}
def sort(n:Node): Node = n match {
case Elem(pre,lab,md,scp,child@_*) =>
Elem(pre,lab,sort(md),scp, (child map sort):_*)
case _ => n
}
final def escape(text: String): String = sbToString(escape(text, _))
object Escapes {
val pairs = Map(
"lt" -> '<',
"gt" -> '>',
"amp" -> '&',
"quot" -> '"'
)
val escMap = pairs map { case (s, c) => c-> ("&%s;" format s) }
val unescMap = pairs ++ Map("apos" -> '\'')
}
import Escapes.{ escMap, unescMap }
final def escape(text: String, s: StringBuilder): StringBuilder = {
val len = text.length
var pos = 0
while (pos < len) {
text.charAt(pos) match {
case '<' => s.append("<")
case '>' => s.append(">")
case '&' => s.append("&")
case '"' => s.append(""")
case '\n' => s.append('\n')
case '\r' => s.append('\r')
case '\t' => s.append('\t')
case c => if (c >= ' ') s.append(c)
}
pos += 1
}
s
}
final def unescape(ref: String, s: StringBuilder): StringBuilder =
(unescMap get ref) map (s append _) orNull
def collectNamespaces(nodes: Seq[Node]): mutable.Set[String] =
nodes.foldLeft(new HashSet[String]) { (set, x) => collectNamespaces(x, set) ; set }
def collectNamespaces(n: Node, set: mutable.Set[String]) {
if (n.doCollectNamespaces) {
set += n.namespace
for (a <- n.attributes) a match {
case _:PrefixedAttribute =>
set += a.getNamespace(n)
case _ =>
}
for (i <- n.child)
collectNamespaces(i, set)
}
}
def toXML(
x: Node,
pscope: NamespaceBinding = TopScope,
sb: StringBuilder = new StringBuilder,
stripComments: Boolean = false,
decodeEntities: Boolean = true,
preserveWhitespace: Boolean = false,
minimizeTags: Boolean = false): StringBuilder =
{
x match {
case c: Comment => if (!stripComments) c buildString sb else sb
case x: SpecialNode => x buildString sb
case g: Group =>
g.nodes foreach {toXML(_, x.scope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags)}
sb
case _ =>
sb.append('<')
x.nameToString(sb)
if (x.attributes ne null) x.attributes.buildString(sb)
x.scope.buildString(sb, pscope)
if (x.child.isEmpty && minimizeTags) {
sb.append(" />")
} else {
sb.append('>')
sequenceToXML(x.child, x.scope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags)
sb.append("</")
x.nameToString(sb)
sb.append('>')
}
}
}
def sequenceToXML(
children: Seq[Node],
pscope: NamespaceBinding = TopScope,
sb: StringBuilder = new StringBuilder,
stripComments: Boolean = false,
decodeEntities: Boolean = true,
preserveWhitespace: Boolean = false,
minimizeTags: Boolean = false): Unit =
{
if (children.isEmpty) return
else if (children forall isAtomAndNotText) {
val it = children.iterator
val f = it.next
toXML(f, pscope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags)
while (it.hasNext) {
val x = it.next
sb.append(' ')
toXML(x, pscope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags)
}
}
else children foreach { toXML(_, pscope, sb, stripComments, decodeEntities, preserveWhitespace, minimizeTags) }
}
final def prefix(name: String): Option[String] = (name indexOf ':') match {
case -1 => None
case i => Some(name.substring(0, i))
}
def hashCode(pre: String, label: String, attribHashCode: Int, scpeHash: Int, children: Seq[Node]) = {
val h = new util.MurmurHash[Node](pre.##)
h.append(label.##)
h.append(attribHashCode)
h.append(scpeHash)
children.foreach(h)
h.hash
}
def appendQuoted(s: String): String = sbToString(appendQuoted(s, _))
def appendQuoted(s: String, sb: StringBuilder) = {
val ch = if (s contains '"') '\'' else '"'
sb.append(ch).append(s).append(ch)
}
def appendEscapedQuoted(s: String, sb: StringBuilder): StringBuilder = {
sb.append('"')
for (c <- s) c match {
case '"' => sb.append('\\'); sb.append('"')
case _ => sb.append(c)
}
sb.append('"')
}
def getName(s: String, index: Int): String = {
if (index >= s.length) null
else {
val xs = s drop index
if (xs.nonEmpty && isNameStart(xs.head)) xs takeWhile isNameChar
else ""
}
}
def checkAttributeValue(value: String): String = {
var i = 0
while (i < value.length) {
value.charAt(i) match {
case '<' =>
return "< not allowed in attribute value";
case '&' =>
val n = getName(value, i+1)
if (n eq null)
return "malformed entity reference in attribute value ["+value+"]";
i = i + n.length + 1
if (i >= value.length || value.charAt(i) != ';')
return "malformed entity reference in attribute value ["+value+"]";
case _ =>
}
i = i + 1
}
null
}
def parseAttributeValue(value: String): Seq[Node] = {
val sb = new StringBuilder
var rfb: StringBuilder = null
val nb = new NodeBuffer()
val it = value.iterator
while (it.hasNext) {
var c = it.next
if (c == '&') {
c = it.next
if (c == '#') {
c = it.next
val theChar = parseCharRef ({ ()=> c },{ () => c = it.next },{s => throw new RuntimeException(s)}, {s => throw new RuntimeException(s)})
sb.append(theChar)
}
else {
if (rfb eq null) rfb = new StringBuilder()
rfb append c
c = it.next
while (c != ';') {
rfb.append(c)
c = it.next
}
val ref = rfb.toString()
rfb.setLength(0)
unescape(ref,sb) match {
case null =>
if (sb.length > 0) {
nb += Text(sb.toString())
sb.setLength(0)
}
nb += EntityRef(sb.toString())
case _ =>
}
}
}
else sb append c
}
if (sb.length > 0) {
val x = Text(sb.toString())
if (nb.length == 0)
return x
else
nb += x
}
nb
}
def parseCharRef(ch: () => Char, nextch: () => Unit, reportSyntaxError: String => Unit, reportTruncatedError: String => Unit): String = {
val hex = (ch() == 'x') && { nextch(); true }
val base = if (hex) 16 else 10
var i = 0
while (ch() != ';') {
ch() match {
case '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' =>
i = i * base + ch().asDigit
case 'a' | 'b' | 'c' | 'd' | 'e' | 'f'
| 'A' | 'B' | 'C' | 'D' | 'E' | 'F' =>
if (! hex)
reportSyntaxError("hex char not allowed in decimal char ref\n" +
"Did you mean to write &#x ?")
else
i = i * base + ch().asDigit
case SU =>
reportTruncatedError("")
case _ =>
reportSyntaxError("character '" + ch() + "' not allowed in char ref\n")
}
nextch()
}
new String(Array(i), 0, 1)
}
}