PerlでおてがるXML（2つのモジュールを合せ技にしてみた）

Perlはテキスト処理の高速さを売りとするのは知られていますが、XML解析や処理などにおいても大いに役立ち、過去にも専門書籍が出ていたりします。また、Perlを使用することによって柔軟にXMLを操作でき、XSLTといった便利なテンプレートモジュールを活用できる上、大量のデータでも円滑に処理できます（海外サイトでも、2023年度記事においてXML構築に最適な言語としてJAVAの次に挙げられていました）。

そして、世間で使用されているXMLモジュールですが、有名なものに以下の2つがあります。

- XML::Simple
- XML::LibXML

そして、比較されることも多いこの2つですが、根本的な目的が異なります。

XML::Simpleは単純に、データからXMLツリー構造（以下、データ構造と記述）を作成するモジュールなので操作が至ってシンプルなのですが、大きな問題があります。それは**nameで紐づけたXMLタグに対し、出力される順序が保証されない**ので、順番を整えるためにXML::XSLTを使用するなどして対応します。

```pl:pl
$xslt_file = "change.xslt"; #変更予定の形式
my $xslt = XML::XSLT -> new($xslt_file);
$xslt -> transform($xml); #抽出したXML
$output = $xslt -> toString;
```
ところが、XML::XSLTだと**出力結果に対し改行コードが全部消えてしまう**ので、XSLT上で逐一改行コード制御が必要になり、相当手間がかかります。その上に、XML::XSLTは動作が遅いです。

対する**XML::LibXSLTの場合は複雑なXML解析を前提としたモジュールなので、改行コードがしっかり保持**され、動きも高速です。ですが、その場合はXML::LibXMLでないとうまくデータを読み込めません。しかも、LibXMLはかなり使用に癖があり、使いづらさがあります。

なので、一長一短の2つのモジュールを**合体**させてみます。

# 実際にやってみた
結論からいえば、libXMLでの入力を、ファイルからではなくて、XML::Simpleで出力されたXMLデータ構造を取り込むようにします。

また、LibXMLは世間で紹介されている方法だとうまくいかないことが多いので、海外サイトや公式チュートリアルなどを参考に、構築してみたのが以下のプログラムです。

※各種モジュールはCPANなどからインストールしておいてください。

```pl:pl
use XML::Simple;
use XML::LibXSLT;
use XML::LibXML;

$xslt = "test.xslt"; #XSLTファイル
#まずはXML::SimpleでデータをXMLにする
my $x = new XML::Simple; 
my $xml = qq(<?xml version="1.0" encoding="UTF-8" ?>\n);
$Val = {
		'data' => $data  #任意のデータ構造
	};
$xml .= $x -> XMLout($Val, RootName => 'root', NoAttr=>1); #XMLデータの抽出
my $xml = XML::LibXML -> load_xml(string => $xml); #データ構造として取り込む(string設定必須)
my $xslt = XML::LibXSLT->new()->parse_stylesheet_file($xslt_file); #XSLTの取り込み
my $result = $xslt->transform($xml); #変換
$output.= $xslt -> output_as_chars($result); #出力
```

ポイントとしては、load_xmlのときにキー設定を`string`にしておくこと（こうしないと、オブジェクトを読み込めない）、それから出力時には`output_as_chars`としておくことです。

これで、各種ファイルで取り込んだXMLの値を自在に変形でき、改行コードを保持したXMLファイルが簡単に作れます。

# 任意のデータ構造について
任意のデータ構造はハッシュリファレンスで作成していきます（※例は閉業となった新潟市レインボータワーのもの）。
```pl
my $data = {};
$data -> {'build'} = "レインボータワー";
$data -> {'tel'} = 025－246－6426;
$data -> {'address'} = "新潟市中央区万代１丁目６−１";
$data -> {'status'} = "閉業";
$data -> {'demorished'} = 2018;
my @traffic = ("自家用車","バス","鉄道");
$data -> {'traffics'} = \@traffic;
```
このキーが、XML::simpleによってタグに変換されます。

```xml
<data>
    <address>新潟市中央区万代１丁目６−１</address>
    <build>レインボータワー</build>
    <demorished>2018</demorished>
    <status>2</status>
    <tel>025－246－6426</tel>
    <traffics>
        <0>自家用車</0>
        <1>バス</1>
        <2>鉄道</2>
    </traffics>
</data>
```

XML::Simpleで準備するのはここまで、あとはlibXMLの出番です。また、ここでデータが順不同になっても全く問題ありません。

# XSLTファイルについて
XSLTファイルでの制御も簡潔になります。データソースをXML::Simpleで作成してあるので、XSLTファイルもデータを紐づけるnameプロパティと階層化制御のループ処理以外のこまごまとした関数は、ほとんど使用しなくて済むようになります（フォームのvalueプロパティの要領で、selectプロパティにハッシュのキー値を記述しておき、適切な位置にはめこんでいくだけ）。

今回はハッシュ変数dataに全データを代入している形なので、XMLの階層もdataタグが先頭へ来ることになります。これを逐一記述するのは面倒なので、その場合はtemplate構文を用いるといいでしょう(matchプロパティの値が対応するディレクトリ階層となります)。

```xml:xslt
<xsl:template match="data">
<概要>
　　<建物名><xsl:value-of select="build"/></建物名>
    <住所><xsl:value-of select="address"/></住所>
    <電話番号><xsl:value-of select="tel"/></電話番号>
</概要>
<状況>
    <運営状況><xsl:value-of select="status"/></運営状況>
    <所在><xsl:value-of select="pos"/></所在>
</状況>
</xsl:template>
```

## XSLTの基本構文
ただ、value-of文に代入するだけで追いつかない部分に対しては、分岐や繰り返しで対応していきます。

### 分岐
後述するxsl:if構文というのもありますが、これはelseに対応していません。なので、xsl:choose構文を使って分岐していきます。これはSQLのwhen構文によく似ており、when文のtestプロパティの中に検査文を記述します。また、otherwise文がelseの代わりとなります。

それから、動的な値を検査対象としたい場合は**variable**文を使って変数定義しておきます。nameプロパティが変数名、selectプロパティが変数に代入する値となります。その変数を展開する場合は$hogeとプレフィックスの付与が必須となります。

```xml:xslt
<xsl:variable name="flg_open" select="status">
<運営状況>
<xsl:choose>
    <xsl:when test="$flg_open='閉業'">
        <xsl:value-of select="2">
    </xsl:when>
    <xsl:when test="$flg_open='休業'">
        <xsl:value-of select="1">
    </xsl:when>
    <xsl:otherwise>
    　　<xsl:value-of select="0">
    </xsl:otherwise>
</xsl:choose>
</運営状況>
```

```xml:xslt
<xsl:variable name="demorished" select="demorished"/>
<xsl:if test="$demorished!=''">
　　<所在>現存せず</所在>
</xsl:if>
```

### 繰り返し
配列の中身を繰り返し展開したい場合はfor-each構文を使用します。また、その値の展開は`"."`で大丈夫のようです（実際はループではなくて、並列処理のようですが）。

```xml:xslt
<交通手段>
<xsl:for-each select="traffics">
   <交通><xsl:value-of select="."/><交通>
</xsl:for-each>
</交通手段>
```

# 動作速度について
使用環境に対し、ある程度メモリを開放しておくこと、そしてプログラムのループ文の中に適宜、undefを用いてメモリを開放しておくことがポイントのようです。

こうすることによって、一件あたり2000行のxmlファイルに対し、300件で3分ほどかかってた操作が3000件で数分と十分、実用的な速度となりました（環境による差異は大いに発生します）。

# XML::Parserを使ってみた結果
XML::Simpleはそのままだと動作が遅いとのことで、XML::Parserを導入してみたのですが、制御タイムは変わりませんでした。XML::Simpleで操作しているのはハッシュからのXML構築だけなので干渉してこないのだと思います。

# 応用編（xsdを活用）
libXMLにはもう一つ利点があります。それはxsdといったxmlスキーマを活用することができるので、定義通りのデータ構造かどうか確認することができます。また、これがJSONにはなくXMLが持っているメリットにも挙げられています。

※重要なポイントは**改行コードを必ず動作環境に適した形式（今回の場合はLinuxなのでCRLF）にしておくこと**（こうしないと、不正なタグという警告表示が出ます）、そして精査用には**必ず一度保存したXMLファイルを使用すること**です（出力データそのものをxsdにかけようとすると、ファイル名が長すぎますという警告が出ます）。それと、**精査用のファイルは必ずcloseすること**、でないとファイル占有の問題が発生して正しく動作しません。

これで構造上のエラーが発生した場合、$@にリスト表示されます。

```pl
	open(DATAFILE, "> :utf8 :crlf", "hoge.xml") or die("ERR:$!");
	print DATAFILE $output;
	close(DATAFILE); #精査前には必ず閉じておくこと
	$xsd_file = "xsd/hoge.xsd";
	my $schema = XML::LibXML::Schema->new(location => $xsd_file);
	my $parser = XML::LibXML->new(XML_LIBXML_LINENUMBERS => 1 );
	my $tree = $parser->parse_file("hoge.xml");
	eval { $schema->validate($tree) };
	if ( $@ ) {
	  #ここにエラーの場合の処理を書く。
	}
```