diff options
Diffstat (limited to 'src/library/scala/xml/PrettyPrinter.scala')
-rw-r--r-- | src/library/scala/xml/PrettyPrinter.scala | 308 |
1 files changed, 308 insertions, 0 deletions
diff --git a/src/library/scala/xml/PrettyPrinter.scala b/src/library/scala/xml/PrettyPrinter.scala new file mode 100644 index 0000000000..05c612ce81 --- /dev/null +++ b/src/library/scala/xml/PrettyPrinter.scala @@ -0,0 +1,308 @@ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2003-2007, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +// $Id$ + + +package scala.xml + +import scala.collection.Map + +/** Class for pretty printing. After instantiating, you can use the + * toPrettyXML methods to convert XML to a formatted string. The class + * can be reused to pretty print any number of XML nodes. + * + * @author Burak Emir + * @version 1.0 + * + * @param width the width to fit the output into + * @step indentation + */ +class PrettyPrinter( width:Int, step:Int ) { + + class BrokenException() extends java.lang.Exception + + class Item + case object Break extends Item { + override def toString() = "\\" + } + case class Box(col: Int, s: String) extends Item + case class Para(s: String) extends Item + + protected var items: List[Item] = Nil + + protected var cur = 0 + //protected var pmap:Map[String,String] = _ + + protected def reset() = { + cur = 0 + items = Nil + } + + /** Try to cut at whitespace. + * + * @param s ... + * @param ind ... + * @return ... + */ + protected def cut(s: String, ind: Int): List[Item] = { + val tmp = width - cur + if (s.length < tmp) + return List(Box(ind, s)) + val sb = new StringBuilder() + var i = s.indexOf(' ') + if (i > tmp || i == -1) throw new BrokenException() // cannot break + + var last: List[Int] = Nil + while (i != -1 && i < tmp) { + last = i::last + i = s.indexOf(' ', i+1) + } + var res: List[Item] = Nil + while (Nil != last) try { + val b = Box(ind, s.substring(0, last.head)) + cur = ind + res = b :: Break :: cut(s.substring(last.head, s.length), ind) + // backtrack + last = last.tail + } catch { + case _:BrokenException => last = last.tail + } + throw new BrokenException() + } + + /** Try to make indented box, if possible, else para. + * + * @param ind ... + * @param s ... + * @return ... + */ + protected def makeBox(ind: Int, s: String) = { + if (cur < ind) + cur == ind + if (cur + s.length > width) { // fits in this line + items = Box(ind, s) :: items + cur += s.length + } else try { + for (b <- cut(s, ind).elements) // break it up + items = b :: items + } catch { + case _:BrokenException => makePara(ind, s) // give up, para + } + } + + // dont respect indent in para, but afterwards + protected def makePara(ind: Int, s: String) = { + items = Break::Para(s)::Break::items + cur = ind + } + + // respect indent + protected def makeBreak() = { // using wrapping here... + items = Break :: items + cur = 0 + } + + /** + * @param n ... + * @return ... + */ + protected def leafTag(n: Node) = { + val sb = new StringBuilder("<") + n.nameToString(sb) + //Utility.appendPrefixedName( n.prefix, n.label, pmap, sb ); + n.attributes.toString(sb) + //Utility.attr2xml( n.scope, n.attributes, pmap, sb ); + sb.append("/>") + sb.toString() + } + + protected def startTag(n: Node, pscope: NamespaceBinding): (String, Int) = { + val sb = new StringBuilder("<") + n.nameToString(sb) //Utility.appendPrefixedName( n.prefix, n.label, pmap, sb ); + val i = sb.length + 1 + n.attributes.toString(sb) + n.scope.toString(sb, pscope) + sb.append('>') + (sb.toString(), i) + } + + protected def endTag(n: Node) = { + val sb = new StringBuilder("</") + n.nameToString(sb) //Utility.appendPrefixedName( n.prefix, n.label, pmap, sb ); + sb.append('>') + sb.toString() + } + + protected def childrenAreLeaves(n: Node): Boolean = { + val it = n.child.elements + while (it.hasNext) + it.next match { + case _:Atom[_] | _:Comment | _:EntityRef | _:ProcInstr => + case _:Node => + return false + } + true + } + + protected def fits(test: String) = + test.length < width - cur + + /** @param tail: what we'd like to sqeeze in */ + protected def traverse(node: Node, pscope: NamespaceBinding, ind: Int): Unit = node match { + + case Text(s) if s.trim() == "" => + ; + case _:Atom[_] | _:Comment | _:EntityRef | _:ProcInstr => + makeBox( ind, node.toString().trim() ) + case g @ Group(xs) => + traverse(xs.elements, pscope, ind) + case _ => + val test = { + val sb = new StringBuilder() + Utility.toXML(node, pscope, sb, false) + if (node.attribute("http://www.w3.org/XML/1998/namespace", "space") == "preserve") + sb.toString() + else + TextBuffer.fromString(sb.toString()).toText(0)._data + } + if (childrenAreLeaves(node) && fits(test)) { + makeBox(ind, test) + } else { + val (stg, len2) = startTag(node, pscope) + val etg = endTag(node) + if (stg.length < width - cur) { // start tag fits + makeBox(ind, stg) + makeBreak() + traverse(node.child.elements, node.scope, ind + step) + makeBox(ind, etg) + } else if (len2 < width - cur) { + // <start label + attrs + tag + content + end tag + makeBox(ind, stg.substring(0, len2)) + makeBreak() // todo: break the rest in pieces + /*{ //@todo + val sq:Seq[String] = stg.split(" "); + val it = sq.elements; + it.next; + for (c <- it) { + makeBox(ind+len2-2, c) + makeBreak() + } + }*/ + makeBox(ind, stg.substring(len2, stg.length)) + makeBreak() + traverse(node.child.elements, node.scope, ind + step) + makeBox(cur, etg) + makeBreak() + } else { // give up + makeBox(ind, test) + makeBreak() + } + } + } + + protected def traverse(it: Iterator[Node], scope: NamespaceBinding, ind: Int ): Unit = + for (c <- it) { + traverse(c, scope, ind) + makeBreak() + } + + /** Appends a formatted string containing well-formed XML with + * given namespace to prefix mapping to the given string buffer. + * + * @param n the node to be serialized + * @param pmap the namespace to prefix mapping + * @param sb the stringbuffer to append to + */ + def format(n: Node, sb: StringBuilder ): Unit = // entry point + format(n, null, sb) + + def format(n: Node, pscope: NamespaceBinding, sb: StringBuilder): Unit = { // entry point + var lastwasbreak = false + reset() + traverse(n, pscope, 0) + var cur = 0 + for (b <- items.reverse) b match { + case Break => + if (!lastwasbreak) sb.append('\n') // on windows: \r\n ? + lastwasbreak = true + cur = 0 +// while( cur < last ) { +// sb.append(' '); +// cur = cur + 1; +// } + + case Box(i, s) => + lastwasbreak = false + while (cur < i) { + sb.append(' ') + cur += 1 + } + sb.append(s) + case Para( s ) => + lastwasbreak = false + sb.append(s) + } + } + + // public convenience methods + + /** returns a formatted string containing well-formed XML with + * default namespace prefix mapping + * + * @param n the node to be serialized + * @return ... + */ + def format(n: Node): String = format(n, null) //Utility.defaultPrefixes(n)) + + /** Returns a formatted string containing well-formed XML with + * given namespace to prefix mapping. + * + * @param n the node to be serialized + * @param pmap the namespace to prefix mapping + * @return ... + */ + def format(n: Node, pscope: NamespaceBinding): String = { + val sb = new StringBuilder() + format(n, pscope, sb) + sb.toString() + } + + /** Returns a formatted string containing well-formed XML nodes with + * default namespace prefix mapping. + * + * @param nodes ... + * @return ... + */ + def formatNodes(nodes: Seq[Node]): String = + formatNodes(nodes, null) + + /** Returns a formatted string containing well-formed XML. + * + * @param nodes the sequence of nodes to be serialized + * @param pmap the namespace to prefix mapping + */ + def formatNodes(nodes: Seq[Node], pscope: NamespaceBinding): String = { + var sb = new StringBuilder() + formatNodes(nodes, pscope, sb) + sb.toString() + } + + /** Appends a formatted string containing well-formed XML with + * the given namespace to prefix mapping to the given stringbuffer. + * + * @param n the node to be serialized + * @param pmap the namespace to prefix mapping + * @param sb the string buffer to which to append to + */ + def formatNodes(nodes: Seq[Node], pscope: NamespaceBinding, sb: StringBuilder): Unit = + for (n <- nodes.elements) { + sb.append(format(n, pscope)) + } + +} |