org.fusesource.scalate.tool.commands.ToScaml.scala Maven / Gradle / Ivy
The newest version!
/**
* Copyright (C) 2009-2011 the original author or authors.
* See the notice.md file distributed with this work for additional
* information regarding copyright ownership.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fusesource.scalate.tool.commands
import org.apache.felix.gogo.commands.{Action, Option => option, Argument => argument, Command => command}
import scala.xml._
import java.io._
import java.net.URL
import org.fusesource.scalate.util.IOUtil
import org.fusesource.scalate.InvalidSyntaxException
import util.parsing.input.CharSequenceReader
import org.fusesource.scalate.support.{Text=>SSPText, ScalaParseSupport}
import org.fusesource.scalate.ssp._
import org.w3c.tidy.{TidyMessage, TidyMessageListener, Tidy}
import org.apache.felix.service.command.CommandSession
/* an even simpler ssp parser */
class SspParser extends ScalaParseSupport {
var skipWhitespaceOn = false
override def skipWhitespace = skipWhitespaceOn
def skip_whitespace[T](p: => Parser[T]): Parser[T] = Parser[T] {
in =>
skipWhitespaceOn = true
val result = p(in)
skipWhitespaceOn = false
result
}
val anySpace = text("""[ \t]*""".r)
val identifier = text("""[a-zA-Z0-9\$_]+""".r)
val typeName = text(scalaType)
val someText = text(""".+""".r)
val attribute = skip_whitespace(opt(text("import")) ~ text("var" | "val") ~ identifier ~ (":" ~> typeName)) ~ ("""\s*""".r ~> opt("""=\s*""".r ~> upto("""\s*%>""".r))) ^^ {
case (p_import ~ p_kind ~ p_name ~ p_type) ~ p_default => ScriptletFragment(p_kind+" "+p_name+":"+p_type+" //attribute")
}
val literalPart: Parser[SSPText] =
upto("<%" | """\<%""" | """\\<%""" | "${" | """\${""" | """\\${""" | """\#""" | """\\#""" | directives) ~
opt(
"""\<%""" ~ opt(literalPart) ^^ {case x ~ y => "<%" + y.getOrElse("")} |
"""\${""" ~ opt(literalPart) ^^ {case x ~ y => "${" + y.getOrElse("")} |
"""\#""" ~ opt(literalPart) ^^ {case x ~ y => "#" + y.getOrElse("")} |
"""\\""" ^^ {s => """\"""}
) ^^ {
case x ~ Some(y) => x + y
case x ~ None => x
}
val tagEnding = "+%>" | """%>[ \t]*\r?\n""".r | "%>"
val commentFragment = wrapped("<%--", "--%>") ^^ {CommentFragment(_)}
val altCommentFragment = wrapped("<%#", "%>") ^^ {CommentFragment(_)}
val dollarExpressionFragment = wrapped("${", "}") ^^ {ExpressionFragment(_)}
val expressionFragment = wrapped("<%=", "%>") ^^ {ExpressionFragment(_)}
val attributeFragement = prefixed("<%@", attribute <~ anySpace ~ tagEnding)
val scriptletFragment = wrapped("<%", tagEnding) ^^ {ScriptletFragment(_)}
val textFragment = literalPart ^^ {TextFragment(_)}
def directives = ("#" ~> identifier ~ anySpace ~ opt("(" ~> scalaExpression <~ ")")) ^^ {
case a ~ b ~ c => ScriptletFragment(a+c.map("("+_+")").getOrElse(""))
} | "#(" ~> identifier <~ ")" ^^ {ScriptletFragment(_)}
def scalaExpression: Parser[SSPText] = {
text(
(rep(nonParenText) ~ opt("(" ~> scalaExpression <~ ")") ~ rep(nonParenText)) ^^ {
case a ~ b ~ c =>
val mid = b match {
case Some(tb) => "(" + tb + ")"
case tb => ""
}
a.mkString("") + mid + c.mkString("")
})
}
val nonParenText = characterLiteral | stringLiteral | """[^\(\)\'\"]+""".r
val pageFragment: Parser[PageFragment] = directives | commentFragment | altCommentFragment | dollarExpressionFragment |
attributeFragement | expressionFragment | scriptletFragment |
textFragment
val pageFragments = rep(pageFragment)
private def phraseOrFail[T](p: Parser[T], in: String): T = {
var x = phrase(p)(new CharSequenceReader(in))
x match {
case Success(result, _) => result
case NoSuccess(message, next) => throw new InvalidSyntaxException(message, next.pos);
}
}
def getPageFragments(in: String): List[PageFragment] = {
phraseOrFail(pageFragments, in)
}
}
/**
*
*
*
* @author Hiram Chirino
*/
@command(scope = "scalate", name = "toscaml", description = "Converts an XML or HTML file to Scaml")
class ToScaml extends Action {
@option(name = "--tidy", description = "Should html be tidied first?")
var tidy = true
@argument(index = 0, name = "from", description = "The input file or http URL. If ommited, input is read from the console")
var from: String = _
@argument(index = 1, name = "to", description = "The output file. If ommited, output is written to the console")
var to: File = _
var out:IndentPrintStream = _
def execute(session: CommandSession): AnyRef = {
def doit:Unit = {
var in = if( from==null ) {
session.getKeyboard
} else {
if( from.startsWith("http://") || from.startsWith("https://") ) {
new URL(from).openStream
} else {
new FileInputStream(from)
}
}
var data = IOUtil.loadBytes(in)
//println("original: "+new String(data, "UTF-8"))
// Parse out the code bits and wrap them in script tags so that
// we can tidy the document.
val fragments = (new SspParser).getPageFragments(new String(data, "UTF-8"))
data = ("" + (fragments.map(_ match {
case ExpressionFragment(code) => "#{" + code.value + "}"
case ScriptletFragment(code) => """ """
case CommentFragment(comment) => """"""
case TextFragment(text) => text.value
case unexpected: PageFragment =>
System.err.println("Unexpected page fragment " + unexpected)
"" // skip it
}).mkString("")) + "").getBytes("UTF-8")
// println("escaped: "+new String(data, "UTF-8"))
// try to tidy the html first before we try to parse it as XML
if (tidy) {
val tidy = new Tidy
tidy.setXHTML(true)
tidy.setXmlTags(true)
tidy.setIndentCdata(false)
tidy.setEscapeCdata(false)
tidy.setQuiet(true)
val out = new ByteArrayOutputStream()
tidy.parse(new ByteArrayInputStream(data), out);
data = out.toByteArray
// println("tidy: "+new String(data, "UTF-8"))
}
// Try to strip out the doc type... stuff..
{
val text = new String(data, "UTF-8").trim
if( text.startsWith("')+1).getBytes("UTF-8")
// println("doctype: "+new String(data, "UTF-8"))
}
}
val doc = try {
XML.load(new ByteArrayInputStream(data))
} catch {
case e:SAXParseException =>
// save the tidy version...
System.err.println("Could not parse the html markup: "+e.getMessage+" at "+e.getLineNumber+":"+e.getColumnNumber)
out.write(data)
return
case e:Throwable =>
// save the tidy version...
System.err.println("Could not parse the html markup: "+e.getMessage)
out.write(data)
return
}
doc.child.foreach(process(_))
}
if( to!=null ) {
out = new IndentPrintStream(new FileOutputStream(to));
doit
out.close()
} else {
out = new IndentPrintStream(session.getConsole);
doit
out.flush()
}
null
}
def to_text(line: String): String = {
line
}
def to_element(tag: String): String = {
var rc = tag
if( rc.startsWith("div.") || tag.startsWith("div#") ) {
rc = rc.stripPrefix("div")
}
"%"+rc
}
def process(value:AnyRef):Unit = {
val t = out
import t._
def tag(name:String) = {
if( name.matches("""^[\w:_\-]+$""") ) {
name
} else {
"'"+name+"'"
}
}
value match {
case x:Elem =>
var id=""
var clazz=""
var atts=""
def add(key:String, value:String) = {
if( atts!="" ) {
atts += " "
}
atts += key+"=\""+value+"\""
}
x.attributes.foreach{ a=>
val key = a.key
val value = a.value.toString
if( key=="id" ) {
if( value.matches("""^[\w_\-]+$""") )
id = "#"+value
else
add(key,value)
} else if( key=="class" ) {
if( value.matches("""^[\w\s_\-]+$""") ) {
value.split("""\s""").foreach{ c=>
clazz += "."+c
}
} else {
add(key,value)
}
} else {
add(key,value)
}
}
if(x.label=="scriptlet") {
for( line <- x.child.text.trim().split("""\r?\n""").filter( _.length()!=0) ) {
pi.pl("- "+line)
}
} else {
pi.p(to_element(tag(x.label)+id+clazz))
if( atts!="" ) {
p("("+atts+")")
}
x.child match {
case Seq(x:Text) =>
val value = x.text.trim
if (value.contains("\n")) {
pl()
indent {
process(x)
}
} else {
pl(" "+value)
}
case x =>
pl()
indent {
x.foreach{ process _ }
}
}
}
case x:Text =>
val value = x.text.trim
value.split("\r?\n").map(_.trim).foreach{ line =>
if(line != "" ) {
pi.pl(to_text(line))
}
}
case x:AnyRef =>
throw new Exception("Unhandled type: "+x.getClass);
}
}
class IndentPrintStream(out:OutputStream) extends PrintStream(out) {
var level=0
def indent[T](op: => T): T = {level += 1; val rc = op; level -= 1; rc}
def pi = { for (i <- 0 until level) { print(" ") }; this }
def p(line: String) = { print(line); this }
def pl(line: String="") = { println(line); this }
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy