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 件のコメント:
コメントを投稿