Bloggerを始めるにあたり、SmartDocが生成したHTMLをBlogger用に変換するScalaプログラムsdoc2bloggerを書いた。Scalaで、XML、DOM、XSLT、NekoHtmlを使う実例として参考になるかもしれない。
Scalaでは極めて簡単にXMLを扱うことができる。まず、XMLリテラルが使えるのが極めて大きい。プログラム内で生成したXMLデータ(scala.xml.Node, scala.xml.Elem)をtoStringで文字列に変換できるのも便利。さらに、XMLリテラル中に「{}」を使って式を埋め込むことができる。XMLを処理するプログラムを書くのにScalaは手離せない。
ただ、ScalaのXML操作系は独自体系(scala.xml.Node, scala.xml.Elem)になっているので、既存のDOMベースのコンポーネントを利用するにはscala.xml.NodeとDOMの相互変換が必要になる。sdoc2bloggerではSAXによる変換(DOMからscala.xml.Nodeへの変換)とテキストによる変換(XMLリテラルによるXSLTをDOMのTransformerに設定)を使っている。
sdoc2bloggerではDOMベースのコンポーネントとしてXSLTとNekoHtmlを使っている。XSLTによるXML変換はこれからニーズが高まりそうな技術の一つ。この例から分かるとおり、Scalaからも比較的簡単に行うことができるけど、もっと簡単に使えるようなラッパーがあるのが望ましい。ただし、XSLT的な変換はScalaのList処理でもっと簡単にできそうなので、XSLTプログラマが絶滅しかかっている現状では、あえてXSLTをサポートする誘引は少ないかもしれない。
NekoHtmlは、ブロークンなHTMLを読み込むことができるHTMLパーサー。今回はこれを使いたくてDOMを使うことになった。
XSLTで余分な空白を取り除く方法が分からなかったので、Scalaプログラムの方で処理を行っている。XSLTでやり方が見つからなかった場合、無理をしてXSLTの利用方法を追求するよりScalaプログラム側で対処した方が現実的である。
この他にXML要素をas-isで残す方法も見つけることができなかった。これはXSLTに地道にパターンを設定することで対処。
いずれにしてもXSLTは難しい。XSLTを触るのはかなり久々なのでカンも戻っていない。
それはともかく、XSLTは技を使わないと手続き的な処理を実現できないので、副作用のない変換はXSLTを使い、Scalaプログラムで補完するのが実用的な使い方かなと考えている。
package org.xmlsmartdoc.sdoc2blogger
import scala.xml._
import scala.xml.parsing._
import java.io.{FileReader, StringReader}
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.sax.SAXResult
import javax.xml.transform.stream.StreamSource
import org.xml.sax.InputSource
import org.cyberneko.html.parsers.DOMParser
/*
* @since Feb. 28, 2010
* @version Mar. 1, 2010
* @author ASAMI, Tomoharu
*/
object Main {
val xslt =
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
<xsl:template match="/HTML/BODY">
<div>
<xsl:apply-templates />
</div>
</xsl:template>
<xsl:template match="P">
<p>
<xsl:apply-templates />
</p>
</xsl:template>
<xsl:template match="A">
<a>
<xsl:attribute name="href">
<xsl:value-of select="@href"/>
</xsl:attribute>
<xsl:apply-templates />
</a>
</xsl:template>
<xsl:template match="PRE[@class='program']">
<pre style="background-color: gainsboro;font-family: courier, \
monospace;padding: 5pt;">
<xsl:value-of select="."/>
</pre>
</xsl:template>
<xsl:template match="PRE[@class='console']">
<pre style="background-color: black;color: white;font-family: courier, \
monospace;padding: 5pt;">
<xsl:value-of select="."/>
</pre>
</xsl:template>
<xsl:template match="DIV[@class='caption']">
<div style="background: lavender;font-weight: bold;">
<xsl:value-of select="."/>
</div>
</xsl:template>
<xsl:template match="DIV[@class='date']">
</xsl:template>
<xsl:template match="DIV[@class='author']">
</xsl:template>
<xsl:template match="DIV[@style='text-align:right']">
</xsl:template>
<xsl:template match="H1">
</xsl:template>
</xsl:stylesheet>
def main(args: Array[String]) {
val reader = new FileReader(args(0))
val is = new InputSource(reader)
val parser = new DOMParser()
parser.parse(is)
val dom = parser.getDocument
//
val saxHandler = new NoBindingFactoryAdapter()
saxHandler.scopeStack.push(TopScope)
val transFactory = TransformerFactory.newInstance
val ss = new StreamSource(new StringReader(xslt.toString))
val trans = transFactory.newTransformer(ss)
trans.transform(new DOMSource(dom), new SAXResult(saxHandler))
saxHandler.scopeStack.pop
val xml = <div>{distill_child_elements(saxHandler.rootElem.child)}</div>
println(xml)
}
private def distill_child_elements(nodes: Seq[Node]): Seq[Node] = {
nodes.filter(_.isInstanceOf[Elem])
}
}

0 件のコメント:
コメントを投稿