Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #4476: Correct the XML pretty printer to always preserve spaces #441

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ import org.eclipse.jgit.lib.PersonIdent
import net.liftweb.common._
import net.liftweb.util.Helpers.tryo
import scala.xml.Elem
import scala.xml.PrettyPrinter
import scala.collection.mutable.Buffer
import scala.collection.JavaConversions._
import com.normation.cfclerk.domain.TechniqueId
Expand All @@ -81,7 +80,7 @@ class GitRuleArchiverImpl(
, override val gitRootDirectory : File
, ruleSerialisation : RuleSerialisation
, ruleRootDir : String //relative path !
, override val xmlPrettyPrinter : PrettyPrinter
, override val xmlPrettyPrinter : RudderPrettyPrinter
, override val gitModificationRepository : GitModificationRepository
, override val encoding : String = "UTF-8"
) extends
Expand Down Expand Up @@ -189,7 +188,7 @@ class GitActiveTechniqueCategoryArchiverImpl(
, override val gitRootDirectory : File
, activeTechniqueCategorySerialisation: ActiveTechniqueCategorySerialisation
, techniqueLibraryRootDir : String //relative path !
, override val xmlPrettyPrinter : PrettyPrinter
, override val xmlPrettyPrinter : RudderPrettyPrinter
, override val gitModificationRepository : GitModificationRepository
, override val encoding : String = "UTF-8"
, serializedCategoryName : String = "category.xml"
Expand Down Expand Up @@ -316,7 +315,7 @@ trait ActiveTechniqueModificationCallback {
class UpdatePiOnActiveTechniqueEvent(
gitDirectiveArchiver: GitDirectiveArchiver
, techniqeRepository : TechniqueRepository
, directiveRepository : RoDirectiveRepository
, directiveRepository : RoDirectiveRepository
) extends ActiveTechniqueModificationCallback with Loggable {
override val uptModificationCallbackName = "Update PI on UPT events"

Expand Down Expand Up @@ -367,7 +366,7 @@ class GitActiveTechniqueArchiverImpl(
, override val gitRootDirectory : File
, activeTechniqueSerialisation : ActiveTechniqueSerialisation
, techniqueLibraryRootDir : String //relative path !
, override val xmlPrettyPrinter : PrettyPrinter
, override val xmlPrettyPrinter : RudderPrettyPrinter
, override val gitModificationRepository : GitModificationRepository
, override val encoding : String = "UTF-8"
, val uptModificationCallback : Buffer[ActiveTechniqueModificationCallback] = Buffer()
Expand Down Expand Up @@ -479,7 +478,7 @@ class GitDirectiveArchiverImpl(
, override val gitRootDirectory : File
, directiveSerialisation : DirectiveSerialisation
, techniqueLibraryRootDir : String //relative path !
, override val xmlPrettyPrinter : PrettyPrinter
, override val xmlPrettyPrinter : RudderPrettyPrinter
, override val gitModificationRepository : GitModificationRepository
, override val encoding : String = "UTF-8"
) extends GitDirectiveArchiver with Loggable with GitArchiverUtils with BuildCategoryPathName[ActiveTechniqueCategoryId] {
Expand Down Expand Up @@ -565,24 +564,24 @@ class GitDirectiveArchiverImpl(
*
* Basically, we directly map the category tree to file-system directories,
* with the root category being the file denoted by "nodeGroupLibrary
*/
*/
class GitNodeGroupArchiverImpl(
override val gitRepo : GitRepositoryProvider
, override val gitRootDirectory : File
, nodeGroupSerialisation : NodeGroupSerialisation
, nodeGroupCategorySerialisation: NodeGroupCategorySerialisation
, groupLibraryRootDir : String //relative path !
, override val xmlPrettyPrinter : PrettyPrinter
, override val xmlPrettyPrinter : RudderPrettyPrinter
, override val gitModificationRepository : GitModificationRepository
, override val encoding : String = "UTF-8"
, serializedCategoryName : String = "category.xml"
) extends
GitNodeGroupArchiver with
GitNodeGroupArchiver with
Loggable with
GitArchiverUtils with
BuildCategoryPathName[NodeGroupCategoryId] with
GitArchiverFullCommitUtils {


override lazy val relativePath = groupLibraryRootDir
override def getCategoryName(categoryId:NodeGroupCategoryId) = categoryId.value
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ package com.normation.rudder.repository.xml
import java.io.File
import scala.collection.JavaConversions.asScalaSet
import scala.xml.Elem
import scala.xml.PrettyPrinter
import org.apache.commons.io.FileUtils
import org.eclipse.jgit.api.Git
import org.joda.time.format.ISODateTimeFormat
Expand All @@ -64,7 +63,7 @@ trait GitArchiverUtils extends Loggable {
def gitRepo : GitRepositoryProvider
def gitRootDirectory : File
def relativePath : String
def xmlPrettyPrinter : PrettyPrinter
def xmlPrettyPrinter : RudderPrettyPrinter
def encoding : String
def gitModificationRepository : GitModificationRepository

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
/* __ *\
** ________ ___ / / ___ Scala API **
** / __/ __// _ | / / / _ | (c) 2003-2013, LAMP/EPFL **
** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ **
** /____/\___/_/ |_/____/_/ | | **
** |/ **
\* */

/*
* License notice:
* This class is a copy of scala.xml.PrettyPrinter class of Scala 2.10.3.
*
* The only changes in it are:
* - the method doPreserve always returning true
* to workaround https://issues.scala-lang.org/browse/SI-4303
* and correct http://www.rudder-project.org/redmine/issues/4476
* - the copy&paste of scala.xml.Utility.sbToString because of its
* visibility modifier.
*
*
* The class name is altered so that the good dependency is used
* every where in Rudder project.
*/

package com.normation.rudder.repository.xml




class RudderPrettyPrinter(width: Int, step: Int) {

import scala.xml._

def sbToString(f: (StringBuilder) => Unit): String = {
val sb = new StringBuilder
f(sb)
sb.toString
}

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 def reset() = {
cur = 0
items = Nil
}

/** Try to cut at whitespace.
*/
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.
*/
protected def makeBox(ind: Int, s: String) =
if (cur + s.length > width) { // fits in this line
items ::= Box(ind, s)
cur += s.length
}
else try cut(s, ind) foreach (items ::= _) // break it up
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
}

protected def leafTag(n: Node) = {
def mkLeaf(sb: StringBuilder) {
sb append '<'
n nameToString sb
n.attributes buildString sb
sb append "/>"
}
sbToString(mkLeaf)
}

protected def startTag(n: Node, pscope: NamespaceBinding): (String, Int) = {
var i = 0
def mkStart(sb: StringBuilder) {
sb append '<'
n nameToString sb
i = sb.length + 1
n.attributes buildString sb
n.scope.buildString(sb, pscope)
sb append '>'
}
(sbToString(mkStart), i)
}

protected def endTag(n: Node) = {
def mkEnd(sb: StringBuilder) {
sb append "</"
n nameToString sb
sb append '>'
}
sbToString(mkEnd)
}

protected def childrenAreLeaves(n: Node): Boolean = {
def isLeaf(l: Node) = l match {
case _:Atom[_] | _:Comment | _:EntityRef | _:ProcInstr => true
case _ => false
}
n.child forall isLeaf
}

protected def fits(test: String) =
test.length < width - cur

private def doPreserve(node: Node) = true

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.iterator, pscope, ind)
case _ =>
val test = {
val sb = new StringBuilder()
Utility.serialize(node, pscope, sb, false)
if (doPreserve(node)) 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.iterator, 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.iterator;
it.next;
for (c <- it) {
makeBox(ind+len2-2, c)
makeBreak()
}
}*/
makeBox(ind, stg.substring(len2, stg.length))
makeBreak()
traverse(node.child.iterator, 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 sb the stringbuffer to append to
*/
def format(n: Node, sb: StringBuilder) { // entry point
format(n, null, sb)
}

def format(n: Node, pscope: NamespaceBinding, sb: StringBuilder) { // 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 += 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
* given namespace to prefix mapping.
*
* @param n the node to be serialized
* @param pscope the namespace to prefix mapping
* @return the formatted string
*/
def format(n: Node, pscope: NamespaceBinding = null): String =
sbToString(format(n, pscope, _))

/** Returns a formatted string containing well-formed XML.
*
* @param nodes the sequence of nodes to be serialized
* @param pscope the namespace to prefix mapping
*/
def formatNodes(nodes: Seq[Node], pscope: NamespaceBinding = null): String =
sbToString(formatNodes(nodes, pscope, _))

/** Appends a formatted string containing well-formed XML with
* the given namespace to prefix mapping to the given stringbuffer.
*
* @param nodes the nodes to be serialized
* @param pscope 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 =
nodes foreach (n => sb append format(n, pscope))
}
3 changes: 1 addition & 2 deletions rudder-web/src/main/scala/bootstrap/liftweb/AppConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ import com.normation.rudder.services.servers.NodeConfigurationChangeDetectServic
import org.apache.commons.dbcp.BasicDataSource
import com.normation.rudder.services.eventlog.HistorizationServiceImpl
import com.normation.rudder.services.policies.DeployOnTechniqueCallback
import scala.xml.PrettyPrinter
import com.normation.rudder.services.marshalling._
import com.normation.utils.ScalaLock
import com.normation.rudder.web.rest._
Expand Down Expand Up @@ -241,7 +240,7 @@ object RudderConfig extends Loggable {

val licensesConfiguration = "licenses.xml"
val logentries = "logentries.xml"
val prettyPrinter = new PrettyPrinter(120, 2)
val prettyPrinter = new RudderPrettyPrinter(120, 2)
val userLibraryDirectoryName = "directives"
val groupLibraryDirectoryName = "groups"
val rulesDirectoryName = "rules"
Expand Down