Skip to content

Instantly share code, notes, and snippets.

@jprudent
Created September 21, 2011 16:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jprudent/1232619 to your computer and use it in GitHub Desktop.
Save jprudent/1232619 to your computer and use it in GitHub Desktop.
Adding a given class on first level paragraph and heading elements of XHTML document (for memo)
object XhtmlTransformer {
import xml.transform.RuleTransformer
/
val className = MonocleReader.className
object XhtmlRewriter extends RewriteRule {
/**
* A regex to match our className
*/
val markerRE = """\b%s\b""".format(className).r
/**
* @param node may be any element (p,div,span)
* @return true if root is a paragraph or a heading
*/
def isReadingMaterial(node: Node): Boolean = node.label.matches("p|h\\d")
/**
* @param node may be any element (p,div,span)
* @return true if node has a class attribute which value contains className
*/
def hasMark(node: Node): Boolean = {
import xml.Text
//an attribute has several nodes
//we try to Find a Text node which contains our className
node.attribute("class").map(_.exists(_ match {
case Text(value) => markerRE.findFirstIn(value).isDefined
case _ => false
})).getOrElse(false)
}
/**
* @param node should receive a reading material element
* @return the modified node withe an attribute class that contains
* className. The subtree is not modified
*/
def addMarker(n: Node): Seq[Node] = {
import xml.{Null, UnprefixedAttribute, Elem, Text}
val newClassAttributeValue = if (n.attribute("class").isDefined) {
//an attribute value is a Seq[Node]
n.attribute("class").get.map(_ match {
case Text(value) => new Text(value + " " + className)
case n => n
})
} else {
new Text(MonocleReader.className)
}
val newClassAttribute = new UnprefixedAttribute("class", newClassAttributeValue, Null)
val newAttributes = n.attributes.remove("class").append(newClassAttribute)
Elem(n.prefix, n.label, newAttributes, n.scope, n.child: _*)
}
override def transform(n: Node): Seq[Node] = {
if (isReadingMaterial(n) && !hasMark(n)) addMarker(n)
else n
}
}
object XhtmlRuleTransformer extends RuleTransformer(XhtmlRewriter)
/**
* @param node is the XML to modify
* @return An XML where a given class on first level paragraph and heading
* elements of XHTML document is added to attribute "class" or created if
* it doesn't exists
*/
def transform(node: Node): Node = XhtmlRuleTransformer(node)
}
@jprudent
Copy link
Author

Here is some tests:

it should "transform an xml" in {
import service.reader.XhtmlTransformer
import play.Logger
import xml.{Node, PrettyPrinter}

val simpleCase = <p foo="bar">blabla</p>
val s: Node = XhtmlTransformer.transform(simpleCase)
Logger.debug(new PrettyPrinter(80 /*width*/ , 3 /*indent*/).format(s))
s should equal(<p foo="bar" class={service.reader.MonocleReader.className}>blabla</p>)

val nestedCase =
  <h1>
    <p foo="bar">blabla</p>
  </h1>
val t: Node = XhtmlTransformer.transform(nestedCase)
Logger.debug(new PrettyPrinter(80 /*width*/ , 3 /*indent*/).format(t))
t should equal(
  <h1 class={service.reader.MonocleReader.className}>
    <p foo="bar">blabla</p>
  </h1>)

val alreadyMarkedCase =
  <h1 class={service.reader.MonocleReader.className}>
    <p foo="bar">blabla</p>
  </h1>
val u: Node = XhtmlTransformer.transform(alreadyMarkedCase)
Logger.debug(new PrettyPrinter(80 /*width*/ , 3 /*indent*/).format(u))
u should equal(alreadyMarkedCase)

 val multiClassCase =
  <h1 class="foo bar">
    <p foo="bar">blabla</p>
  </h1>
val v: Node = XhtmlTransformer.transform(multiClassCase)
Logger.debug(new PrettyPrinter(80 /*width*/ , 3 /*indent*/).format(v))
v should equal(
<h1 class="foo bar vbm">
    <p foo="bar">blabla</p>
  </h1>
)

}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment