RM-BLOG

IT系技術職のおっさんがIT技術とかライブとか日常とか雑多に語るブログです。* 本ブログに書かれている内容は個人の意見・感想であり、特定の組織に属するものではありません。/All opinions are my own.*

【java】XMLからXSLを通じてHTMLを出力する整形をJavaで指示する

XMLをXSLを通じてHTMLに変換するやり方のサンプル(javaソース)

 

import java.io.*;

import org.w3c.dom.Document;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource; 


public class ConvertXMLToHtmlWithXSLTest {

	private static final File INPUT_XML_FILE = new File("TEST.xml");
	private static final File INPUT_XSL_FILE = new File("test_include_org01.xsl");
	private static final File OUTPUT_HTML_FILE = new File("TEST.html");
	
	private static final String ENCODING_UTF8 = "UTF-8";

	public static void main(String[] args) {
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder documentBuilder = factory.newDocumentBuilder();
			Document document = documentBuilder.parse(INPUT_XML_FILE);
			
			if (OUTPUT_HTML_FILE.exists()) {
				OUTPUT_HTML_FILE.delete();
			}
			
			convertXMLToHTMLWithXSL(document,OUTPUT_HTML_FILE);
			
		} catch(Throwable e) {
			e.printStackTrace();
		}
	}

	private static void convertXMLToHTMLWithXSL(Document doc,File file) throws Throwable {
		OutputStreamWriter osw = null;
		try {
			StreamSource xslSource = new StreamSource(INPUT_XSL_FILE);
			
			osw = new OutputStreamWriter(new FileOutputStream(file,true),ENCODING_UTF8);
			StreamResult sr = new javax.xml.transform.stream.StreamResult(osw);
			
			TransformerFactory tfactory = javax.xml.transform.TransformerFactory.newInstance(); 
			Transformer transformer  = tfactory.newTransformer(xslSource);
			transformer.setOutputProperty("indent","yes");
			
			transformer.transform(new DOMSource(doc),sr);
			
		} catch(Throwable e) {
			throw e;
		} finally {
			if (osw != null) {
				osw.close();
			}
		}
	}
}

 



====
XMLファイル(TEST.xml

<?xml version="1.0" encoding="UTF-8"?> 
<?xml-stylesheet type="text/xsl" href="test_include_org01.xsl"?>
<TEST>
   <CONTENTS>
    <DETAIL_INFO>
        <NO>1</NO>
        <NAME>TEST_NUMBER_1</NAME>
    </DETAIL_INFO>
    <DETAIL_INFO>
        <NO>2</NO>
        <NAME>TEST_NUMBER_2(あああ)</NAME>
    </DETAIL_INFO>
    <DETAIL_INFO>
        <NO>3</NO>
        <NAME>TEST_NUMBER_3(WWW@@@)</NAME>
    </DETAIL_INFO>
  </CONTENTS>
</TEST>



■XSLファイル1(test_include_org01.xsl)

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
   <xsl:output method="html" encoding="UTF-8"/>
   

  <xsl:template match="/">
    <xsl:apply-templates/>
  </xsl:template> 

  <xsl:template match="TEST">
    <html>
        <head>
            <title>TEST TITLE</title>
            <link rel="stylesheet" type="text/css" href="TEST_XML_STYLE_SHEET.css" />
            <style type="text/css">
            font.test {
                color:blue;
                font-weight:bold;
            }
            .clear_style {
                color:initial;
            }
            </style>
        </head>
        <body>
            <h1 class="h1_test clear_style">TEST HEAD 1</h1>
            <font class="test">This is stylesheet test.</font>
            <br/>
            <xsl:apply-templates/>
        </body>
    </html>
  </xsl:template> 

  <xsl:include href="test_include_call01.xsl"/>

  
</xsl:stylesheet>



■XSLファイル2:include先(test_include_call01.xsl)

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
   <xsl:output method="html" encoding="UTF-8"/>
   

  <xsl:template match="/">
    <xsl:apply-templates/>
  </xsl:template> 

  <xsl:template match="TEST">
    <html>
        <head>
            <title>TEST TITLE</title>
            <link rel="stylesheet" type="text/css" href="TEST_XML_STYLE_SHEET.css" />
            <style type="text/css">
            font.test {
                color:blue;
                font-weight:bold;
            }
            .clear_style {
                color:initial;
            }
            </style>
        </head>
        <body>
            <h1 class="h1_test clear_style">TEST HEAD 1</h1>
            <font class="test">This is stylesheet test.</font>
            <br/>
            <xsl:apply-templates/>
        </body>
    </html>
  </xsl:template> 

  <xsl:include href="test_include_call01.xsl"/>

  
</xsl:stylesheet>




「TEST.xml」「test_include_org01.xsl」「test_include_call01.xsl」をそれぞれ同階層に配置して
「TEST.xml」をブラウザで表示させると、通常タグがそのまま見えるXMLと違ってHTML的に装飾された情報で表示される。
この「装飾」の部分をXMLスタイルシートである「test_include_org01.xsl」(と「test_include_call01.xsl」)が補っている。
ただ、「TEST.xml」をブラウザで表示させるだけでは、
あくまで見栄えがHTMLになってるだけであって「TEST.xml」がHTMLファイルになっているわけではない。
それを実際に(物理的に)HTMLファイルに変換する、というのがこの実装。
すごい簡単に言えば

(1)XMLファイルを読み来んで
(2)Transformer#transformに渡す

だけでHTMLファイルになる。
↑の処理を実行後、作成される「TEST.html」をブラウザで開いた場合と、「TEST.xml」をブラウザで開いた場合の
画面上での表示内容は完全一致する。



この実装例では実際のXSLファイルとしては「test_include_org01.xsl」しか指定していないが、
「test_include_org01.xsl」は実態として「test_include_call01.xsl」を内部でincludeしており
両者合わせて初めて「TEST.xml」をブラウザ表示したときの見栄えと同じHTMLが出来上がる。
ただjava実装コード上ではinclude先のxslファイル→ここでいう「test_include_call01.xsl」は指定不要で、
Transformerがいい感じに読み込んで処理してくれる。
この場合重要になるのは呼び出す方(includeを指示している側)、
つまり「test_include_org01.xsl」における「test_include_call01.xsl」の指定パスになり、
逆に言うとこのパス指定が誤っている(存在しないパスを指してるとか)とTransformer#transformで落ちる。



なお、Transformer#transformは割と厳密にタグの解釈をする。
(なんか指定で緩められるのかもしれない、がよく知らない)
簡単にいうと単一タグでも閉じ表現をしないと怒られる。(最終的に落ちる)

例えば改行の<br>タグなどだが、単に「<br>」とだけ書くと変換処理時に落ちる。
変換処理に失敗しないように書くには<br/>となる。
<font>や<span>タグ等は、「囲った範囲にスタイル適用」するから必然的に「閉じタグ」を書くことが多いが
<br>タグや<hr>タグ等、それ単一で完結するタグについては末尾の"/"を付けないことが多い。
厳密なHTMLの定義でいうと本来こう書くのが正しいのかもしれないが、
個人的な癖で改行タグはいつも<br>で書いてしまっていたのでこれは結構悩まされた。
上で載せている実装例ではその辺を一応クリアしているが割とはまった結果の産物である。



こういう場合の書き方にXSLとしての何か特殊なお作法があるのか、それとも設定に依存するのかわからないが、
XML→HTMLの変換においてHTML上に書いたstyleタグの中身を反映させる方法が普通にHTML上にべた書きするときとちょっと違う。

普通、html上にスタイル情報を記載する場合、

<style>
<!--
span test {
	font-size:14px;
}
-->
</style>

↑というようにHTMLのコメントで囲うのが通例だが(画面上に<style>~</style>の中身を表示させないようにするための考慮だそうだが………すごい古いブラウザならともかく最近のやつなら表示させないように対応してそうな気もするが)
実際にHTMLのコメントでかこうと↑の実装例では<style>~</style>の中で指定したスタイル情報が反映されない。
なのでHTMLコメントをつけずに<style>の直後からスタイル情報を書き出している。

2015/6/30追記

HTMLのコメントを書き出す場合は、<xsl:comment>タグを書き出せばよいようだ。↓

<style type="text/css">
<xsl:comment>
    font.test {
        color:red;
    }
</xsl:comment>
</style>

↑こう書いておくと、HTMLにした後<xsl:comment>に該当する部分がHTMLコメントに変わってくれる。




XSLを介在するものの、「TEST.xml」をブラウザに単純表示させるだけのテストにおいては、
xslを変更して「TEST.xml」を表示しているブラウザをF5やCtrl+F5でリロードしても
結果が全然反映されないことがよくあった。
ブラウザの設定とかに依存しそうだが(IE11とGoogle Chrome最新版では同じだった)、
この場合ブラウザ閉じてもっかい「TEST.xml」を開きなおすとちゃんと反映された。
なんかあるのだろう。
試行する場合は注意したい。