Quantcast
Channel: tmakitaのブログ
Viewing all 102 articles
Browse latest View live

tableを本文領域いっぱいに広げる table/@pgwide="1"

$
0
0
久しぶりに自分でオーサリングして、自分でスタイルシートをインプリメントしてという仕事をやっていたのですが、自分が作ったプラグインにもかかわらず、table/@pgwide="1"としても幅の小さなものは本文領域幅いっぱいに広まってくれません.何故かスタイルシートを見ていたのですが、やっていることはfo:tableの@strart-ondent="0mm"としているだけでした.これは実はDITAの仕様には沿っているのですが、テーブルの幅を広げられる保証はありません.

3.2.3.1 <table>

"For print-oriented display, the value "1" places the element on the left page margin; "0" aligns the element with the left margin of the current text line and takes indention into account."

実はDITAのtableはOASISのCALSテーブルとほぼ同じです.というよりそれを使っています.

3.2.3 Table elements

The <table> element uses the OASIS Exchange Table Model (formerly known as the CALS table model). 
 
では元のCALSテーブルの仕様はどうなっているかというと、本文領域(ページ幅から左右マージンを取ったもの)に広がることが明記されています.
 
XML Exchange Table Model Document Type Definition
3.1.2.4. pgwide — make table span full page width

  
If non-zero, the table spans the width of the entire page (possibly causing any previous multicolumn text on the page to be balanced and any extra processing associated with column balancing and page spanning to be performed).

という訳で元々のCALSテーブルの仕様をインプリメントするようにスタイルシートの仕様を変えてみました.やってみたのは、fo:tableに@start-indent="0mm"、end-indent="0mm"を指定して、width="100%"を指定することです.

<fo:table start-indent="0mm" end-indent="0mm" width="100%">
  ...
</fo:table>


ところがいろいろ試しているとこの方法でもダメなことがわかりました.問題はwidth="100%"なのです.この100%は何を参照するのでしょうか?本文領域(fo:region-body)の幅なのでしょうか?実は違うのです.XSL仕様を見ると次のように書かれています.

1. まずwidthはinline-progression-dimension(インラインの進行方向の幅)に自動的にマップされます.
2. inline-progression-dimensionの%指定は、実はその指定されているFOの「直近のブロックレベルオブジェクトで生成された幅」に対するものになります.

7.15.7 "inline-progression-dimension"

"The percentage is calculated with respect to the corresponding dimension of the closest area ancestor that was generated by a block-level formatting object."

ですから、あまりないと思いますが、tableの親がfo:blockで

<fo:block start-indent="10mm" end-indent="10mm">
  <fo:table start-indent="0mm" end-indent="0mm" width="100%">
    ...
  </fo:table>
</fo:block>

のような構造になっていると、start-indent="10mm" end-indent="10mm"のところまでしか広まってくれません.つまり本文領域いっぱいにはなってくれないのです.

このため必ず本文領域の幅全体に広げるためには上位でどのようなインデントが設定されているかわかりませんので

<fo:block-container start-indent="0mm" end-indent="0mm">
  <fo:tablewidth="100%">
    ...
  </fo:table>
</fo:block-container>

とfo:block-containerで囲んで@start-indentと@end-imdemtをリセットしてやる必要があります.これでtable/@width="100%"を指定すれば確実に本文領域幅全体に広まります.結構厄介ですね.これにDITA 1.3で加わったテーブルの回転(landscape)が加わるとなおスタイルシートは複雑になります.

ちなみにDITA-OTの最新版(DITA-OT 2.3.2)のPDF2ではどうなっているかというと、

org.dita.pdf2/xsl/fo/tables.xsl line 310

<xsl:if test="(parent::*/@pgwide) = '1'">
    <xsl:attribute name="start-indent">0</xsl:attribute> 
    <xsl:attribute name="end-indent">0</xsl:attribute> 
    <xsl:attribute name="width">auto</xsl:attribute> 
</xsl:if>

こんなコーディングが唯一@pgwide属性を参照している箇所です.width="auto"では全然ダメですよね.autoは

No constraint is imposed by this property. The inline-progression-dimension is determined by the formatter taking all other constraints into account.

と制約条件をつけないのです."other constraints"はテーブル本体の内容くらいでしょう.こんなコーディングになっているのは、テーブルの自動フォーマッティングをサポートしていないFOPなんかでやっているからではないか?と思います.

ともかく@pgwideのインプリメントは昔からなんの疑問も持っていませんでしたが、よくよく調べてみたら結構大変でした.


「何が、会社の目的(ザ・ゴール))を妨げるのか」 エリヤフ・ゴールドラット

$
0
0
このところずっとエリヤフ・ゴールドラット博士の本を読み漁っていました.博士が最初に世に出した「ザ・ゴール 企業の究極の目的とは何か?」は1894年の刊より250万部売れたにもかかわらず、博士の意向により17年もの間日本翻訳が認められず、2001年にようやく邦訳が出版された有名な本です.

私は「ザ・ゴール」以来ずっと読んでいなかったのですが、たまたまAmazonで見ると「ザ・ゴール」はマンガ版も出ているは、またその後のシリーズも続々とでているので、「これは!」と思って3冊ほどまとめ買いしました.最初は、「ザ・ゴール2思考プロセス」(これは買ってあった)、「チェンジ・ザ・ルール」、「クリティカル・チェーン」、「ザ・チョイス」、そして「何が、会社の目的(ザ・ゴール))を妨げるのか」というところです.「何が、会社の目的(ゴール)を妨げるのか」は、いわばシリーズの総まとめのような本ですね.「ザ・チョイス」(これは実に難解)を読む前にこちらを読んでしまいました.

イメージ 1


ここで「何が、会社の目的を妨げるのか」は様々なインタビューや、論文、それまでの著作からの「至言」の数々で構成されています.改めて読んでみて、新たに感銘を受けた点、うなずいた点を紹介させていただきます.

---------------------------------------------------------------------------------------------
リストラは人間の尊厳を損なう(p.13)
私はかつて「Inc.」誌で国内成長率第六位にランキングされたハイテク企業の経営者でした.当然ビジネスの原則を受け入れています.そんな私が、何故リストラを深く憂慮するのか、その理由を説明しましょう.
私はイスラエル人です.国民の義務として十八歳から三年間兵役に就き、その後も四十二歳までは毎年、最低三十日間の兵役に就きました.業績好調な米国企業のトップだった私は、毎年その時期がくると、飛行機のファーストクラスでイスラエルに帰り、軍隊で二等兵としての扱いを受けたのです.
そんなコントラストが、人間という存在について深く考えるよう私を促しました.ある年、レイオフされて一年以上職を見つけられないでいる男と同じ兵舎に入りました.厳しい環境で寝起きをともにしながら、私はどれだけ失業が人を不安に陥れ、プライドを奪う、おぞましい体験かという事を理解しました.それ以来、私は感情的と言ってもいいほどレイオフやリストラを憎むようになったのです.
...
リストラの背後にある効率至上主義を問題にする声がありますが、私に言わせれば、効率を正しく追及すれば、むしろリストラの必要はなくなるのです.マネジメントが追及すべき優先順位を間違えるから、リストラに頼らざるを得ない状況に陥ってしまうのです.
私は終身雇用は日本企業の競争力の源泉の一つだと考えています.残念ながら日本企業はこの美徳を放棄しつつあります.従業員に忠誠を尽くさない企業が、従業員からの忠誠を期待することはできません.従業員の忠誠を得られない企業は顧客からも忠誠を得ることはできず、遅かれ早かれ、市場から淘汰されてしまうでしょう.
---------------------------------------------------------------------------------------------

至言です.これは2002年の博士のインタビュー記事です.博士がアメリカから母国に帰り兵役の義務を果たしていたことは、初めて知りました.それにもまして「私は感情的と言ってもいいほどレイオフやリストラを憎むようになったのです.」は強烈です.思い返せば著作の主人公の言葉の中にも、この考え方を如実に表す言葉が出てきていたのを思い出します.

---------------------------------------------------------------------------------------------
収益体制の改善を求められると、必ず最初に叫ばれるのがコスト削減、つまり人員解雇だ.馬鹿げている.すでに何千人も切ってダウンサイズしてきたではないか.脂肪だけでなく血や肉までも削ってきた.
...
組織再編という名目で行われている努力を、市場獲得のための努力に振り向けることができれば、我が社はもっと繁栄しているはずだ.
(ザ・ゴール2 思考プロセス p.8)
---------------------------------------------------------------------------------------------

次はコストダウンの一節です.

---------------------------------------------------------------------------------------------
「さっきのレポートに書かれていることなんですが、信頼できる業者よりコストの低い業者を選んでいるんです.それで、どれだけコストを節約できたと思いますか」私はなかなかな諦めなかった.
「そんなことを訊かれても......、五パーセントくらいかな.五パーセントよりずっと多いことはないだろう」
「プロジェクトの完成が遅れた理由ですが、実はコストの低い業者からの機会の納入が遅れたことが一番の原因だったのです.」
「なるほど君の言いたいことが見えてきたぞ」そう言うと、事務はフレッドのレポートを再び手に取り、しばらくの間じっくり目を通した.「なるほど、機械のコストは約五パーセント節約できたわけか.投資額全体からするとたぶん三パーセント以下だろう.この節約のせいで三年間でペイバックできるはずのプロジェクトが...」そう言いかけたとおろで、ジムは口を閉じた.
「たった三パーセント節約するために、せっかくのプロジェクトを台無しにしてしまった」私はジムの言葉を補った.
(中略)
企業はコストを削減することばかりに目を奪われて、重要なことを忘れている.プロジェクトの目的は、コストを減らすことではなく、お金を儲けることだということを.
(クリティカルチェーン p94~95)
---------------------------------------------------------------------------------------------

この例ではありませんが、私も今までいくつ見積もりをつらされてきたでしょう?一生懸命分析して見積もりを出すと、どうも単なる合い見積もりのためだけのもの.結局相手先からは断りの返事すらありません.中にはこうして低コストの別会社に発注して破たんし、結局私の会社に何年かたったら戻ってきた仕事もありました.その会社が「お金を儲け」られなかったことだけは確かでしょう.


私は「ザ・ゴール」信者ではありませんが、(イヤというよりそこまで理解していない、実践で検証していない)このシリーズにはビジネスマンとして知るべき多くのことが書いてあります.また紹介させていただきたいと思います.

中国語簡体字のコレーション

$
0
0
dita-usersにEliot Kimberさんが辞書ベースで単語のピンインを求めてソーティングに使用できる中国語簡体字のコレーターを開発したとのポストがありました.

New DITA Community Project: org.dita-community.i18n

中国語簡体字の並び順は、「ピンイン(拼音)/画数/部首/GB0のコード」です.ところが日本語の漢字と同様に中国語の漢字でも読みが複数ある場合が多いのです.例えば「重」という字は「重さなる(chong2)」と「重い(zhong4)」の2つの読みがあります.でもJavaでコレータを作成する時、ある文字に対して割り当てられるピンインは1つだけです.ですので仕方なく、使用頻度が多い方のピンインを採用します.でもこうしてしまうと、例えば索引を作るとき「重」を先頭とする索引語が「C(chong2)」か「Z(zhopng4)」のいずれかに固定してしまいます.これは中国の方からすれば本来は許しがたいことです.しかし、これにはなかなか良い解決策がありません.

Kimberさんの辞書を使う方法は、辞書を引くことにより、複数の読みがある場合でも正確にピンインを求められるのです.ですので私は最初のコメントでは「スバラシイ!」でした.しかしよくよくプログラムを見ると、単語を辞書で引いてそのピンインを最初にそのままソートキーにしているだけなのです.これだと同じ漢字がひとかたまりにまとまってくれません.これでは使い物にならないので、「ピンイン(拼音)/画数/部首/GB0のコード」という仕様にマッチしていない旨指摘したのですが、どうもそのあたりは文化の違いもあってか理解していただけないようです.

結局議論はGitHubのissueリストに持ち越しとなりました.

Refine Sort Ordering for zh-CN 

Kimberさんは公の文書でそのようにソートキーを定めている例があったら教えてほしいとのリクエストでしたので、IBMのICUの記述を送りました.

2. Han Collation

ここには明確に以下のように書いてくれています.(strokeは画数、radical-strokeは部首の画数順⇒つまり部首順です)

1. Pinyin: compare according to the pinyin for each character. If the pinyin is the same, compare by stroke order.
2. Stroke: compare according to the total strokes for each character. If the total strokes are the same, compare by radical-stroke order.
3. Radical-Stroke: compare according to the radical-stroke for each character. If these are the same, compare by code point order.

実際の例は次のようなものです.これは昔(2002年頃)買った中国語-英語-日本語-韓国語のコンピュータ辞典の「工(gong1)」の箇所です.「工」の次の2文字目を見ればわかりますが、文字単位にソートされるのがあたりまえなのです.

イメージ 1


中国語簡体字の文字列比較の理想の実装は以下の様なものです.

1. 文字列が与えられる.
2. これを単語に分けて辞書を引き、正確なピンインを求める.
3. 単語の各文字単位に「ピンイン(拼音)/画数/部首/GB0のコード」を求める.
4. このキーにより2つの文字列を比較する.

むつかしい点は、辞書を使いながら文字列を正確な単語(単語でなければ単独の文字でもよい)に分割することです.辞書の性能に依存します.

これが実装できれば中国の方にストレスなしのソーティング結果を提供することができます.Kimberさんのポストでフリーの辞典があることがわかったので頑張って挑戦してみたいと思います.

辞書でピンインを求める

$
0
0
直接DITAと関係はないのですが、中国語簡体字の文字列比較の話を書きましたので、実際そこで書いた

1. 文字列が与えられる.
2. これを単語に分けて辞書を引き、正確なピンインを求める.
3. 単語の各文字単位に「ピンイン(拼音)/画数/部首/GB0のコード」を求める.
4. このキーにより2つの文字列を比較する.

という手順の2.の部分が実現できないか試してみました.Kimberさんのコードを見るとICUのBreakIteratorを使用しているようなのですが、Saxonのクラスと深く結びついているコードなのでそのままでは手出しができません.ICUのユーザーズガイドにあったサンプルで簡単なものを試してみました.以下のようなものですが、2.のとおり文字列を与えて単語に分割して結果を出力するという簡単なものです.

import com.ibm.icu.text.BreakIterator;
import java.util.Locale;

public class StringSplitTest {

public static void main(String[] args) {
//重商主义 zhong4 shang1 zhu3 yi4
//重修旧好 chong2 xiu1 jiu4 hao3
String stringToExamine = "重商主义和重修旧好"; 
//print each word in order
BreakIterator boundary = BreakIterator.getWordInstance(Locale.SIMPLIFIED_CHINESE);
boundary.setText(stringToExamine);
printEachForward(boundary, stringToExamine);
}
public static void printEachForward(BreakIterator boundary, String source) {
    int start = boundary.first();
    for (int end = boundary.next();
         end != BreakIterator.DONE;
         start = end, end = boundary.next()) {
         System.out.println(source.substring(start,end));
    }
}
}

「重商主义」は「重商主義」で「重」は「zhong4」です.「重修旧好」は「以前の交友関係を修復する」で「重修」が「改修する」の熟語で「重」は「chong2」です.これを「和」(and)でつなげています.(中国語としては意味はありません)実行すると次のような結果となります.

重商主义
重修旧好

イメージ 1


よさそうです.これで辞書を引けば「重商主义」からは「zhong4 shang1 zhu3 yi4」、「重修旧好」からは「chong2 xiu1 jiu4 hao3」が得られ、2つの「重」は適切なピンインを当てはめることができます.実際Kimberさんの使っている辞書で見るとこの2つの単語は中にあります.

CC-CEDICT

ところでこのような単語の分割はICUの中でどのように行われているのでしょうか?それも間違いなく辞書ベースで行われているはずです.ICUのソースパッケージを落としても見当たらなかったのですが、次のURLを見るとICUが使用している辞書があります.この辞書には日本語や中国語混在で約31万語が登録されています.


ところがこのcjkdict.txtのライセンスを見ると次のような一文があります.

 # Note: This data file (cjdict.txt) was originally developed by Chromium project.
 # The original work contains words taken from CC-CEDICT distributed under CC-SA
 # license. However, CC-SA license is not compatible with ICU's MIT/X style license,
 # all of CC-CEDICT unique words were removed from the data.

ICUでも上記のCC-CEDICTを当初は使っていたようなのですが、ライセンスが合わないのでその単語は除外されたということです.ということは逆にCC-CEDICTにある単語でcjkdict.txtにないものだと単語分割はうまくゆかないということになります.以下はその一例です.

String stringToExamine = "重形式轻内容"; //zhong4 xing2 shi4 qing1 nei4 rong2 (heavy on form, light on substance/to stress form at the expense of content)

で実行すると

形式
内容

と分割されてしまい、「重」が1文字で切り出されてしまいます.「重」は形容詞のように使われているのでまあ何にでもかかることができるのかもしれません.しかし1文字で切り出されると、プログラムは「zhong4」か「chong2」かの識別はつかなくなってしまいます.やむなく多く使われている方か最初に出現したピンインを選択せざるを得ないでしょう.「重形式轻内容」な単語が索引や用語として使用されるかどうかはまったくわかりません.しかしCC-CEDICTも約10万語あります.やはりcjdictに存在しないものはそれなりにあるのかもしれません.

辞書ベースの方法でも必ずしもピンインは正確に求めることはむつかしそうです.もしcjdictに存在しないものがあって困った場合、その単語を外部から例外データとしてユーザーが簡単に登録できる機構があれば実用になるかもしれません.

これができる可能性があるのは、辞書に固定されるBreakIteratorクラスではなく、RuleBasedBreakIteratorクラスです.このコンストラクタには"ルール"を文字列として渡せます.

Class RuleBasedBreakIterator

Constructors
Constructor and Description
RuleBasedBreakIterator(String rules)
Construct a RuleBasedBreakIterator from a set of rules supplied as a string.

ところがこのrulesの説明が超難解でとても理解できそうにありません.

RBBI Rules

なんとか出来るように勉強したいと考えています.これと同じように単語自体を分割する各国語のハイフネーションも例外辞書により実用的になっているのですから.

Dita Festa2016

$
0
0
今年もDITAコンソーシアムのDITA Festa 2016が12月7日(水)- 12月9日(金)に開催されます.以下に予告の基調講演、技術講演、ユーザー事例の予告が発表されています.

Dita Festa2016 予告

とても貴重な報告になりそうな予感です.技術講演のブラザー工業の浅井さんの講演は「一度作ったトピックファイルを、とことん使いまわす」「再利用の手法」の紹介だそうです.今まで(私が記憶している限り)このような実践的な再利用の紹介というのはDITA Festaでもなかったと思います.とかく再利用設計というのはよくよく考えないととかくなおざりにされがちなのではないでしょうか?DITA導入の最初から意識的に取り組まないと、似たようなトピックが乱立して何をどのように再利用したらよいのかわからなくなるというお話も聞いたことがあります.この「とことん使いまわす」というのはよほどの内容が期待できそうに思えます.

あとマツダエース株式会社の天満さんの事例発表では、「今回はDITA形式には拘らず、ドキュメント製作の品質確保~媒体多様化への取り組みを幅広くご紹介します。」とのことですが、実際にWEBを当たってみると、すでにマツダのマニュアル類は間違いなくDITAを使っているのでは?と思います.例えば以下はデミオのマニュアルですが、電子取扱説明書と呼ばれるHTMLとPDFの両方を見ることができます.HTMLはスマホでも試してみたのですがPCと同じものが見られます.つまりレスポンシブデザインなのです.

電子取扱説明書

PDF

またHTMLとPDFを見比べても、内容は同じです.本当の意味でワンソースマルチユーズを実現されているのだなと感心しました.コンテンツをこのように展開できるのも変な意味でのDITAをいろいろいじくりまわして特殊化をしたりせずピュアな形でオーサリングされているのではないかと思います.実際PDFやHTMLのページを見ても、「ああこうオーサリングされているのだな」というのが手に取るようにわかるからです.

という訳で今年のDITA Festaは行って損なしではないかと思います.参加申し込みは11月の2週からとのことですので、もうすぐですね.たぶんすぐ満席になってしまうのではないかと思います.

DITA 1.3の見果てぬ夢(1)

$
0
0
DITA 1.3がOASIS標準となったのは2015年12月17日でした.それからもう1年弱が経とうとしています.私見ですが、DITA 1.3とそのインプリメント状況にはいまだにわからないことがよくあります.私の考えが間違っているのかもしれませんが、この1年近くいろいろ触れた感じではDITA 1.3というのは少し進みすぎたのではないのか?と思えるのです.DITA-OTに代表されるインプリメントが追い付いていないように感じます.

例えば、DITA 1.2まではDTDとXML SchemaがXML grammar files(DITAのXMLの文法規定)として配布されていました.DITA 1.3になってからはこれに加えてRELAX NGも採用され、そしてしかも、RELAX NGが正式な代表スキーマとされるに至りました.

1.1 About the DITA specification: All-inclusive edition

XML grammar files
The DITA markup for DITA vocabulary modules and DITA document types is available in several XML languages: RELAX NG (RNG), XML Document-Type Definitions (DTD), and W3C XML Schema (XSD).
While the files should define the same DITA elements, the RELAX NG grammars are normative if there is a discrepancy.

実際XML SchemaとDTDはRelax NGから自動生成されているようです.2014年のDITA EuropeでEliot Kimberさんが生成するのを実演していたくらいですから、もはや3つを手でメンテナンスするなどという手間のかかることはやらないのかもしれません.(それでもDITA 1.3ではSVGやMath MLが加わったので、これらのDTDは手で作業して加わっているのでしょう.)

ところが、2016年11月現在、DITA Open ToolkitではいまだにRELAX NGによる検証がサポートされていないのです.以下にDITA-OTのGitHubのIssueがありますが、早くても次のDITA-OT 2.4で搭載されるようです.ダメなら3.0になってしまうのかもしれません.

Support RELAXNG parsing and validation #2019


※ oXygenがREALX NGでの検証を可能とするためのDITA-OTのプラグインを出しています.また以前からXMLエディタのoXygenのIDEから変換シナリオを起動するなら、RELAX NGも検証は可能です.

dita-relaxng-defaults

実際DITAのスキーマというのは、他のDocBookなどのスキーマにない特徴を持っています.それは顧客によって特殊化される、しかも元のOASISの提供するスキーマに直接手を加えることなしに行う、つまりきれいなカスタマイズを行うことが前提となっています.今まで多くのDocBook 4.x時代のDTDを見てきましたが、みんな元のDTDを自分用に結構好き勝手な変更を加え、そこにはルールも秩序も何もありませんでした.DITAはこの点で根本的に異なります.

そしてこの特殊化をRELAX NGでやってみると、如何にXML SchemaやDTDに比べて簡単に特殊化ができるかを身をもって実感することができます.DTDによる特殊化なんて一時代昔と思わせるほど優れています.RELAX NGがOASIS標準になったのは、2001年12月と古いのですが、2015年のDITAの標準スキーマ(ベーススキーマ)になっているくらいですから実によくできているのです.

そしてOASISはRELAX NGからXML Scheme、DTDを自動生成するプロジェクトをGitHubに開設しています.

oasis-open/dita-rng-converter

でも、使ってみている人はあまり多くないようです.私はDITA 1.3が出てから自分の特殊化はRELAX NGで書いて、DTDに落とそうと思い、このプロジェクトをクローンして何度もトライしたのですが、まともに動いてくれませんでした.止む無く自分でデバッグを重ねてローカル環境でなんとか動くようにしてpull requestを出したのですが、なしのつぶてです.それでもせっかくDTDに落とせたのでなんとか実用に持ってこようとしたのですが、

* 公開識別子の宣言が欠落している.
* base属性を特殊化してもドメインの実体定義に反映されない

などバグだらけで、結局手間暇かけて自分ですべてDTDを手修正をすることになりました.

という訳でRELAX NGは広まるだけの基盤をまだ持ち得ていないように思えます.まだみんなが安心して使えないのです.もちろんRELAX NGの世界だけで閉じているならよいかもしれませんが、多くのDITAユーザーはDTDの資産を持っていて、それをわざわざRELX NGに置き換えようとはしないのでしょう.またベースはRELAX NGで書いて、運用ではこれをDTDに変換して使うということも上記のプロジェクトの状況ではとても望める状態ではありません.

また新規のプロジェクトでも、CMSベンダーがサポートしなければRELAX NGは使えないのです.一例がマイクロソフトアーキテクチャーのCMSです.マイクロソフトはRELAX NGには決して手を出そうとはしません.

Relax-ng schema support - by Lionel Fourquaux 状態 : 解決済み   解決策: 修正しない

マイクロソフトはパーサーの既定値でDTDのサポートはOFFにし、XML Schemaのサポートに絞っているようです.結果的にこのようなCMSを使用するユーザーはRELAX NGの恩恵をこうむることはできないでしょう.

という訳でDITA 1.3のRELAX NGは良いものであるにもかかわらず、なかなか普及するのは大変なのではないでしょうか?一度触れてみればその良さがわかるのに大変もったいない話だと思います.

早くても次のDITA-OT 2.4で搭載されるようです.
DITA-OT 2.4はミュンヘンで開催されたDITA-OT Day 2016にあわせてリリースされたようです.

DITA Open Toolkit 2.4 Release Notes

でもやはりRELAX NGによる検証はリリースノートには見当たりませんね.

DITA 1.3の見果てぬ夢(2)

$
0
0
次の問題はDITA 1.3で導入されたキースコープです.DITA 1.2ではキーが使用できるようになり、@keysでキーを定義し、@keyrefでテキストコンテンツを参照したり、@conkeyrefでキーを使って間接的にコンテンツを参照できました.しかし問題は@keysの定義はグローバルであったことです.一度定義したキーは文書全般にわたって同じ値を保持し変わることはありません.

これでは大変だという事でDITA 1.3ではスコープドキーが定義できるようになりました.雑に言えばマップで@keyscopeを記述すれば、それ以下の範囲でキーはローカルに定義/使用できるというものです.例えば次のような例はDITAコンソーシアムのプレゼンなどで幾度も出てきているはずです.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE map PUBLIC "-//OASIS//DTD DITA Map//EN" "map.dtd">
<map>
 <title/>
 <topicref href="cKeyRef.dita" keyscope="SCOPE01">
  <keydef keys="PRODUCT">
   <topicmeta>
    <keywords>
     <keyword>TYS-125F</keyword>
    </keywords>
   </topicmeta>
  </keydef>
 </topicref>
 <topicref href="cKeyRef.dita" keyscope="SCOPE02">
  <keydef keys="PRODUCT">
   <topicmeta>
    <keywords>
     <keyword>TTR-125F</keyword>
    </keywords>
   </topicmeta>
  </keydef>
 </topicref>
</map>

ここではキースコープ"SCOPE01"、"SCOPE02"で同じ名前の"PRODUCT"というキーを定義しています.これを次のようにトピックで参照すれば別々の値が出るというものです.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE concept PUBLIC "-//OASIS//DTD DITA Concept//EN" "concept.dtd">
<concept id="concept_vfd_lgw_yx">
 <title><ph keyref="PRODUCT"/> Specification</title>
</concept>

やってみれば事実次のように別々のキー値が参照されます.

イメージ 1

しかし、本当の実践例ではこんな簡単なことで片付くのだろうか?というのが私の思うところです.

あるお客様のサンプルデータを作っていたときに、別々のタスクでまったくと言って良いほど同じステップで構成されている例がありました.このような時、同じ内容を2つのタスクにオーサリングするでしょうか?再利用してやろうというのが誰しも思うところです.2つの違いは片方が全てのステップがあり、もう片方は1つだけステップがないものと簡単化して考えます.そうすれば、すべてのステップを記述した再利用用の(ライブラリもしくはコレクションと呼ぶ)タスクを用意して、それに@conkeyrefしてやれば良いでしょう.

ただ問題は、一ヶ所stepsectionがあり「~のときはステップNへ、そうでないときはステップMへ」とう記述があったことです.上記のステップの違いはこのステップセクションの前なので、ステップへのxrefで表し、当然NとMは2つのタスクで違ったステップ番号になります.

少し長いですが、マップから順に引用します.

[bookmap]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE bookmap PUBLIC "-//OASIS//DTD DITA BookMap//EN" "bookmap.dtd">
<bookmap xml:lang="en-US">
    <booktitle>
        <mainbooktitle>Online Help</mainbooktitle>
    </booktitle>
    <frontmatter>
        <mapref href="mKeyDef.ditamap"/>
    </frontmatter>
    <chapter href="topics/cUsingOnlineHelp.dita" navtitle="How to use online-help"/>
    <chapter navtitle="Prepareing to use the software" href="topics/cPreparingToUseSoftware.dita">
        <topicgroup keyscope="SINGLE-PRINTER">
            <keydef keys="CONNECTING_PRINTERS" href="collection-topics/tWhenConnectingPrintersTemplate.dita"/>
            <topicref navtitle="Connecting a single printer" keys="REFERENCE-TOPIC" format="dita" href="topics/tWhenConnectingASinglePrinter.dita"/>
        </topicgroup>
        <topicgroup keyscope="MULTIPLE-PRINTER">
            <keydef keys="CONNECTING_PRINTERS" href="collection-topics/tWhenConnectingPrintersTemplate.dita"/>
            <topicref navtitle="Connecting multiple printer"  keys="REFERENCE-TOPIC" format="dita" href="topics/tWhenConnectingMultiplePrinters.dita"/>
        </topicgroup>
    </chapter>
</bookmap>

[tWhenConnectingPrintersTemplate.dita 再利用タスク]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE task PUBLIC "-//OASIS//DTD DITA Task//EN" "task.dtd">
<task id="task_03" xml:lang="en-US">
    <title id="title_03"><uicontrol>Setting the environment</uicontrol></title>
    <taskbody>
        <steps id="steps_r2v_rg4_wx">
            <step id="step01">
                <cmd>Click <uicontrol>[PC]</uicontrol> icon.</cmd>
            </step>
            <step id="step02">
                <cmd>Click <uicontrol>[General]</uicontrol> tab.</cmd>
            </step>
            <step id="step03">
                <cmd>Click <uicontrol>[Multi Printer]</uicontrol> tab.</cmd>
            </step>
            <stepsection id="stepsection_01">To configure the settings of the color measurement instrument, go to step <xref keyref="REFERENCE-TOPIC/step-color" format="dita" type="step"/>. Otherwise, go to step <xref
                keyref="REFERENCE-TOPIC/step-other" format="dita" type="step"/>.</stepsection>
            <step id="step04">
                <cmd>Click the <uicontrol>[Color]</uicontrol> tab.</cmd>
            </step>
            <step id="step05">
                <cmd>Click <uicontrol>[OK}</uicontrol>.</cmd>
            </step>
            <step id="step06">
                <cmd>Terminate <term keyref="PRODUCT"/> and launch it again.</cmd>
            </step>
        </steps>
        <postreq id="postreq_03">
            <note>Postreq note contents</note>
        </postreq>
    </taskbody>
</task>

[tWhenConnectingASinglePrinter.dita 再利用元:conkeyrefしているだけです]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE task PUBLIC "-//OASIS//DTD DITA Task//EN" "task.dtd">
<task id="task_lbb_mc4_wx" xml:lang="en-US">
    <title>Connecting a single printer</title>
    <task id="task_e3q_mg4_wx">
        <title conkeyref="CONNECTING_PRINTERS/title_03"/>
        <taskbody>
            <steps>
                <step conkeyref="CONNECTING_PRINTERS/step01" id="step01">
                    <cmd/>
                </step>
                <step conkeyref="CONNECTING_PRINTERS/step02" id="step02">
                    <cmd/>
                </step>
                <stepsection conkeyref="CONNECTING_PRINTERS/stepsection_01"/> 
                <step conkeyref="CONNECTING_PRINTERS/step04" id="step-color">
                    <cmd/>
                </step>
                <step conkeyref="CONNECTING_PRINTERS/step05" id="step-other">
                    <cmd/>
                </step>
                <step conkeyref="CONNECTING_PRINTERS/step06" id="step06">
                    <cmd/>
                </step>
            </steps>
            <postreq conkeyref="CONNECTING_PRINTERS/postreq_03"/>
        </taskbody>
    </task>
</task>

[tWhenConnectingMultiplePrinters.dita]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE task PUBLIC "-//OASIS//DTD DITA Task//EN" "task.dtd">
<task id="task_ops_1q4_wx" xml:lang="en-US">
    <title>Connecting multiple printers</title>
    <task id="task_w4d_yjq_wx">
        <title conkeyref="CONNECTING_PRINTERS/title_03"/>
        <taskbody>
            <steps id="steps_x4d_yjq_wx">
                <step conkeyref="CONNECTING_PRINTERS/step01" id="step01">
                    <cmd/>
                </step>
                <step conkeyref="CONNECTING_PRINTERS/step02" id="step02">
                    <cmd/>
                </step>
                <step conkeyref="CONNECTING_PRINTERS/step03" id="step03">
                    <cmd/>
                </step>
                <stepsection conkeyref="CONNECTING_PRINTERS/stepsection_01"/> 
                <step conkeyref="CONNECTING_PRINTERS/step04" id="step-color">
                    <cmd/>
                </step>
                <step conkeyref="CONNECTING_PRINTERS/step05" id="step-other">
                    <cmd/>
                </step>
                <step conkeyref="CONNECTING_PRINTERS/step06" id="step06">
                    <cmd/>
                </step>
            </steps>
            <postreq conkeyref="CONNECTING_PRINTERS/postreq_03" id="postreq_y4d_yjq_wx"/>
        </taskbody>
    </task>
</task>

tWhenConnectingASinglePrinter.ditaとtWhenConnectingMultiplePrinters.ditaの違いは簡単に言えば再利用タスクのtWhenConnectingPrintersTemplate.ditaのid="step03"のステップを参照しているか否かだけかの違いです.

ところが、これをXHTMLで出力するとまったくまともな表示になりません.問題はステップセクションの箇所です.この箇所は、自分をconkeyrefしているタスクのステップをREFERENCE-TOPICというキーを使って参照しているのですが、そこで生成されるxref/@hrefはボロボロです.結果としてtWhenConnectingASinglePrinter.ditaは次のような表示になります.

イメージ 2

この部分のHTMLを見ると

<p class="li stepsection">To configure the settings of the color measurement instrument, go to step <a class="xref" href="../collection-topics/../topics/tWhenConnectingMultiplePrinters.html#task_ops_1q4_wx__step-color">Connecting a single printer</a>. Otherwise, go to step <a class="xref" href="../collection-topics/../topics/tWhenConnectingMultiplePrinters.html#task_ops_1q4_wx__step-other">Connecting a single printer</a>.</p>

と@hrefがぐちゃぐちゃです.本当は"To configure the settings of the color measurement instrument, go to step 3. Otherwise, go to step 4."と出てくれないと困ります.

念のためにtempフォルダのtWhenConnectingPrintersTemplate.ditaを見ると次のようになっています.(簡単のため余計な属性は除いてあります)

<stepsection id="stepsection_01" class="- topic/li task/stepsection ">To configure the settings of the color measurement instrument, go to step <xref keyref="REFERENCE-TOPIC/step-color" format="dita" type="step" class="- topic/xref " href="../topics/tWhenConnectingMultiplePrinters.dita#task_ops_1q4_wx/step-color"><?ditaot usertext?>Connecting a single printer</xref>. Otherwise, go to step <xref keyref="REFERENCE-TOPIC/step-other" format="dita" type="step" class="- topic/xref " href="../topics/tWhenConnectingMultiplePrinters.dita#task_ops_1q4_wx/step-other"><?ditaot usertext?>Connecting a single printer</xref>.</stepsection>

すでに@hrefの値がtWhenConnectingMultiplePrinters.ditaの値に確定してしまっています.これではキースコープを定義し、REFERENCE-TOPICというキーに別々のタスクを割り当ててもなんの意味もありません.

これはDITA-OTのissueに出してありますが、少なくともDITA 1.3でスコープドキーを導入し、同じキー名が異なるキースコープで定義可能ならば、キーの評価はスタティックに行うのではなく、キースコープによりダイナミックに行わないと意味がまったくありません.

現在のDITA-OTはまったくそのような要請に無頓着な気がします.以下のスナップは今年Eliot Kimberさんが行ったDITA 1.3の紹介スライドの一部ですが、キースコープによりxrefの結果も変わるであろうことを述べています."Different results for cross references" でも実態はそうではありません.

イメージ 3

しかし、これを実装するとなると、@conkeyrefを持つトピックは、それがどのキースコープで参照されるかによりそのコンテンツを確定できないことになります.実装は大変でしょう.しかしDITA 1.3では盛られているにもかかわらず実装が追い付いていない2番目の例になるのではないでしょうか?

(ちなみにissueはGitHubに出したばかりで意見がついていません.この話、もしかしたら私の勘違いが含まれているかもしれません.)

DITA 1.3の見果てぬ夢(3)

$
0
0
2014年の11月に行われたDITA Festa 2014で、DITAコンソーシアムジャパンのIA部会のプレゼンがありました.そこでは(その時はまだ来るべき)DITA 1.3の様々な特徴が紹介されました.そのひとつに「分冊間参照(異なる分冊同士のリンク)がDITA 1.3では出来るようになります」という言及がありました.例えばPDFではあるPDFから別のPDFへリンクを張れるという事になります.

私はそれを聞いていて、「PDFにおいて分冊間参照が如何に大変か?」また「実際にDITA-OTでインプリメントできるのか?」という点で疑問の意見を出しました.もうそれから2年が経とうとしています.確かに分冊間参照はDITA 1.3では仕様として記述されています.

2.3.4.5 Cross-deliverable addressing and linking

しかし実際に今のDITA-OTでどのようになっているのでしょうか?やってみるとすぐわかります.

1. mapの準備
DITA-OT 1.8.5までおまけで付いてきたsamplesフォルダのsequence.ditamapに次のような記述を加えます.

<map id="map_2E3A2E4C04634118AD48D94CD092E90A" title="Eclipse content aggregated by a map"> 
  ...
  <topicref href="concepts/wwfluid.xml" navtitle="Windshield washer fluid" type="concept"/> 
  <mapref keyscope="other-map" scope="peer" href="taskbook.ditamap"/>
  <keydef keys="other-topic-installing" keyref="other-map.installing"/>
</map> 

意味としてはmaprefでscope="peer"をつけることにより、taskbook.ditamapがこのmapではない別のpublicationであることを表現します.そしてkeydefでこのtaskbook.ditamapの中のinstallingというkeyが付けられたtopicref(従ってそれが指すtopic)を参照することを定義します.

<bookmap id="taskbook">
  <booktitle>
  <booklibrary>Retro Tools</booklibrary>
  <mainbooktitle>Product tasks</mainbooktitle>
  ...
  <chapter href="taskbook/installing.dita" keys="installing">
  ...
</bookmap>

2. トピックからの参照

sequence.ditamapの中のトピックからは、@keyrefを使用して、次のようにしてこれを参照します.

<p>Refer to <xref keyref="other-topic-installing" format="dita"/></p>

3. ビルド

これで最新のDITA-OT 2.4でPDFをビルドしてみます.すると結果のxrefの箇所には何も現れません.ログには次のように出ます.

keyref:
   [keyref] Reading file:/D:/DITA-OT/DITA-OT1.8.5/samples/temp/pdf/sequence.ditamap
   [keyref] Processing file:/D:/DITA-OT/DITA-OT1.8.5/samples/temp/pdf/sequence.ditamap
   [keyref] file:/D:/DITA-OT/DITA-OT1.8.5/samples/sequence.ditamap:28:72: [DOTJ047I][INFO] Unable to find key definition for key reference "other-map.installing" in root scope. The href attribute may be used as fallback if it exists
   [keyref] Processing file:/D:/DITA-OT/DITA-OT1.8.5/samples/temp/pdf/concepts/wwfluid.xml
   [keyref] Processing file:/D:/DITA-OT/DITA-OT1.8.5/samples/temp/pdf/sequence.ditamap
   [keyref] file:/D:/DITA-OT/DITA-OT1.8.5/samples/sequence.ditamap:28:72: [DOTJ047I][INFO] Unable to find key definition for key reference "other-map.installing" in root scope. The href attribute may be used as fallback if it exists
   [mapref] Transforming into D:\DITA-OT\DITA-OT1.8.5\samples\temp\pdf
   [mapref] Loading stylesheet D:\DITA-OT\dita-ot-2.4\xsl\preprocess\mapref.xsl
   [mapref] Processing D:\DITA-OT\DITA-OT1.8.5\samples\temp\pdf\sequence.ditamap
   [mapref] Recoverable error on line 356 of maprefImpl.xsl:
   [mapref]   FODC0002: java.io.FileNotFoundException:
   [mapref]   D:\DITA-OT\DITA-OT1.8.5\samples\temp\pdf\taskbook.ditamap (指定されたファイルが見つかりません。)

つまり、分冊間参照はDITA-OTにはまったく実装されていません.これは無理からぬことです.いくらDITAの仕様の上で分冊間参照が定義されても、DITA-OTは

・ 参照しているtaskbook.ditamapからどういう名称のPDFが生成できるかわからない.
・ 参照している<chapter href="taskbook/installing.dita" keys="installing">のinstalling.ditaが(PDF上で)どのような外部からアクセスできるIDをエキスポートするのかわからない.

からなのです.でも上記の2つがわかれば別にDITA 1.3を採用しなくとも分冊間参照は実現できます.実際DITA 1.2で分冊間参照を実現しているお客様は現に存在します.

分冊間参照はDITA 1.3の仕様の上では「解決」されました、しかし(たぶん)「永久」にDITA-OTでは実装されることはないだろうと思います.上記の2つの情報をDITA-OTに与えることは実は極めて困難であるからです.

まず現実問題で実装できないことを仕様上で解決したとしてもそれは(私にとっては)絵に描いた餅にしか過ぎないように思えます.(言いすぎでしょうか???)


XSLT3.0への道(21) マップ

$
0
0
プログラミングで良く使用するデータ構造に、キーと値の関連性を保持するものがあります.多くのプログラミング言語ではmapという名前でこの機能をサポートしています.XSLT 3.0でもmapという「データ型」がサポートされるようになりました.今回はこれを簡単に紹介したいと思います.

まずXSLT 3.0より以前では、様々な工夫をしてキーと対応する値を表現してきました.いろいろなスタイルシートを見てきましたが、次のようなものがあります.一つはこの構造を外部のXMLとして与えるというものです.例えば次のようなXMLファイルを用意します.


[mapdata.xml]
<?xml version="1.0" encoding="UTF-8"?>
<items>
  <item key="red" value="赤"/>
  <item key="green" value="緑"/>
  <item key="blue" value="青"/>
</items>


これに対して次のようなスタイルシートでキーに対する値を求めることができます.


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    exclude-result-prefixes="xs"
    version="2.0">
    
    <xsl:variable name="mapDoc" as="document-node()" select="document('mapdata.xml')"/>

    <xsl:function name="tmf:getColor" as="xs:string">
        <xsl:param name="prmKey" as="xs:string"/>
        <xsl:variable name="result" as="attribute()?" select="($mapDoc//item[string(@key) eq $prmKey]/@value)[1]"/>
        <xsl:sequence select="if (exists($result)) then string($result) else '???'"/>
    </xsl:function>
  
    <xsl:template match="/">
      <xsl:message select="'red=',tmf:getColor('red')"/>
      <xsl:message select="'green=',tmf:getColor('green')"/>
      <xsl:message select="'blue=',tmf:getColor('blue')"/>
      <xsl:message select="'purple=',tmf:getColor('purple')"/>
    </xsl:template>
  
</xsl:stylesheet>

結果は次のようにメッセージ出力されるでしょう.

red= 赤
green= 緑
blue= 青
purple= ???

またxs:stringのシーケンスを2つ用意して次のように定義しても同じ結果を得ることができます.

  <xsl:variable name="key" as="xs:string+" select="('red','green','blue')"/>
  <xsl:variable name="value" as="xs:string+" select="('赤','緑','青')"/>
  
  <xsl:function name="tmf:getColor" as="xs:string">
    <xsl:param name="prmKey" as="xs:string"/>
    <xsl:variable name="index" as="xs:integer?" select="index-of($key,$prmKey)"/>
    <xsl:sequence select="if (exists($index)) then $value[$index] else '???'"/>
  </xsl:function>

しかしいずれにせよキーと値を素直に表せるデータ構造ではありませんでした.XSLT 3.0ではダイレクトに次のようにスタイルシートに書くことができます.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    exclude-result-prefixes="xs map"
    version="3.0">
    
    <xsl:variable name="mapColors" as="map(xs:string,xs:string)">
        <xsl:map>
          <xsl:map-entry key="'red'"   select="'赤'"/>
          <xsl:map-entry key="'green'" select="'緑'"/>
          <xsl:map-entry key="'blue'"  select="'青'"/>
        </xsl:map>
    </xsl:variable>

    <xsl:function name="tmf:getColor" as="xs:string">
        <xsl:param name="prmKey" as="xs:string"/>
        <xsl:variable name="result" as="xs:string?" select="(map:get($mapColors,$prmKey))[1]"/>
        <xsl:sequence select="if (exists($result)) then $result else '???'"/>
    </xsl:function>
    
    <xsl:template match="/">
      <xsl:message select="'red=',tmf:getColor('red')"/>
      <xsl:message select="'green=',tmf:getColor('green')"/>
      <xsl:message select="'blue=',tmf:getColor('blue')"/>
      <xsl:message select="'purple=',tmf:getColor('purple')"/>
    </xsl:template>
  
</xsl:stylesheet>

map()はいわばコンストラクタの関数なのですが、XSLT 3.0では関数そのものもデータ型と定義出来ます.ここがすごいところです.map:getはマップ型の変数とキーを与えて、対応する値を取りだすことができます.なければ空シーケンスを返します.

もしマップの定義が「ダサイ!」とお考えならば次のようにXPath式で一発で書くことも可能です.

    <xsl:variable name="mapColors" as="map(xs:string,xs:string)" select="map{'red':'赤','green':'緑','blue':'青'}"/>

前者は動的にスタイルシートでマップを作れます.マップの内容が静的に与えうるものならば、XPathで書いた方が楽ですね.

動的にマップを生成する例として、xsl:paramから作る例を試してみました.これも上記と同じ結果となります.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    exclude-result-prefixes="xs map"
    version="3.0">
    
    <xsl:param name="PRM_COLORS" as="xs:string" select="'red=赤,green=緑,blue=青'"/>
    
    <xsl:variable name="mapColors" as="map(xs:string,xs:string)">
        <xsl:map>
            <xsl:variable name="colorDefinitions" as="xs:string*">
                <xsl:analyze-string select="$PRM_COLORS" regex="[,]">
                    <xsl:matching-substring/>
                    <xsl:non-matching-substring>
                        <xsl:sequence select="."/>
                    </xsl:non-matching-substring>
                </xsl:analyze-string>
            </xsl:variable>
            <xsl:for-each select="$colorDefinitions">
                <xsl:variable name="colorDefinition" as="xs:string" select="."/>
                <xsl:variable name="key" as="xs:string" select="substring-before($colorDefinition,'=')"/>
                <xsl:variable name="value" as="xs:string"  select="substring-after($colorDefinition,'=')"/>
                <xsl:map-entry key="$key" select="$value"/>
            </xsl:for-each>
        </xsl:map>
    </xsl:variable>
    
    <xsl:function name="tmf:getColor" as="xs:string">
        <xsl:param name="prmKey" as="xs:string"/>
        <xsl:variable name="result" as="xs:string?" select="(map:get($mapColors,$prmKey))[1]"/>
        <xsl:sequence select="if (exists($result)) then $result else '???'"/>
    </xsl:function>
    
    <xsl:template match="/">
    <xsl:message select="'red=',tmf:getColor('red')"/>
    <xsl:message select="'green=',tmf:getColor('green')"/>
    <xsl:message select="'blue=',tmf:getColor('blue')"/>
    <xsl:message select="'purple=',tmf:getColor('purple')"/>
    </xsl:template>
</xsl:stylesheet>


マップは非常に便利です.XSLT 3.0は2015年11月にCandidate Recommendationになっていますが、早く勧告になってもらいたいものです.


XSLT 2.0で便利になった機能(58) キャラクタマップ(2)

$
0
0
キャラクタマップ(xsl:character-map)についてはすでに以前書きましたのでダブルポストです.xsl:character-mapは、XSLTスタイルシートで入力XMLを出力XMLに変換し、それを実際のファイルに書き出す(シリアライズ)するときに、自動的に「指定した文字」を「指定した文字列」に置換してくれる機能です.

あまり使う機会はなかったのですが、最近Word2003のWordMLをフィルタリングしてコンバージョンするプログラムを書くことがあり、これを使わさせてもらいました.Word2003はWord文書をXMLで表現可能にした最初のWordのバージョンです.それ以来様々な変遷がありましたが、いまやWordでは.docxフォーマット(XMLをフォルダ階層ごとZIPしたファイル)が標準の保存形式になっています.

ところで何故キャラクタマップを使ったかなのですが、WordMLや.docxの中のWordML(XML)はMicrosoft Wordが書き出したものです.なので行末の改行コードがCR/LFになってしまっています.ところがXSLTスタイルシートで書き出したXMLは基本的に行末コードはLFです.フィルタリングしてコンバージョンするので、WinDiffのようなコンペアツールでフィルタリング前後を検証のため比べるのですが、ここで改行コードが違っているとアウトです.文書全体が違っているという表示になってしまいます.

以下がその表示例です.全体が黄土色ですべてが差分ありになってしまっています.

イメージ 1


これでは検証のしようがないのでいろいろ考えた末に使用したのがキャラクタマップでした.使い方は以下の様なものです.

<xsl:character-map name="windows-newlines">
    <xsl:output-character character="&#xFFFD;" string="&#x0D;&#x0A;"/>
</xsl:character-map>

<xsl:output method="xml" version="1.0" encoding="UTF-8" byte-order-mark="no" indent="no" use-character-maps="windows-newlines"/>

<!--テキストノードのテンプレート
    改行をU+FFFDに置き換えます.
    別にU+FFFDでなくともつかわれないコードだったらなんでもOKです.
  -->
<xsl:template match="text()">
    <xsl:analyze-string select="." regex="[\n]">
        <xsl:matching-substring>
            <xsl:text>&#xFFFD;</xsl:text>
        </xsl:matching-substring>
        <xsl:non-matching-substring>
            <xsl:value-of select="."/>
        </xsl:non-matching-substring>
    </xsl:analyze-string>
</xsl:template>

こうすればWordの出力と同様に行末をCR/LFにすることができます.例えば以下の様な感じです.こんどは本当の差分の箇所(黄土色の線で示されている)だけが比べられます.

イメージ 2

まあWordMLなんかを変換する機会はあまりないかもしれませんが、キャラクタマップがあってくれると比較/検証は非常に楽にすることができます.

DITA-OT標準のリテラル文字列を変更/追加する

$
0
0
ずっとXL-FO⇒PDF一本でしたが最近はHTMLもやっています.HTMLというとDITA-OTについてくるプラグインはまだまだそっけないですが、oXygenについてくるWeb Helpはそこそこの見栄えもあって完成度も高く、使用しておられるお客様も多いようです.

さて、Web Helpのスタイルシートを解析して行くと、これは完全にoXygenが作ったものではなく、DITA-OTのXHTMLプラグインのスタイルシートをインポートしていることがわかります.例えば

C:\Program Files\Oxygen XML Editor 18.1\frameworks\dita\DITA-OT2.x\plugins\com.oxygenxml.webhelp\xsl\dita\dita2webhelp.xsl

  <xsl:import href="plugin:org.dita.xhtml:xsl/dita2xhtml.xsl"/>

普通のパス記述でなくてビックリするかもしれませんが、これはカタログファイルを使っているからです.

C:\Program Files\Oxygen XML Editor 18.1\frameworks\dita\DITA-OT2.x\catalog-dita.xml

<rewriteURI uriStartString="plugin:org.dita.xhtml:" rewritePrefix="plugins/org.dita.xhtml/"/>

さて標準のDITA-OTのXHTMLプラグインを使っていると、日本語のリテラル文字列もoXygenのWeb Help側にある訳ではなく、DITA-OTで標準で持っているものが使用されます.これをたまたま修正したい場合があります.例えば

<note type="note">この機能はNX-9000シリーズのみで有効です。</note>

などといったオーサリングがあった場合、DITA-OTは見出しの文字列として「注」を割り当てます.これはけっして間違いではありませんが、お客様によってはnoteを「参考」というコンテキストで使用されているケースがあります.このようなお客様向けにカスタマイズしたいのですが、私は(恥ずかしながら)その方法を知りませんでした.そこでDITA-OT Usersグループで聞いてみたところ次のURLを教えてもらえました.

Modifying or adding generated text

ここのガイドラインに従うと、新しい文字列を定義したプラグインを作れば良いようです.以下はそのサンプルです.

1. [DITA-OT]/com.acme.dita.stringというプラグインフォルダを作ります.
2. [DITA-OT]/com.acme.dita.string/plugin.xmlは次のようにします.

<plugin id="com.acme.dita.string">
  <feature extension="dita.xsl.strings" file="xsl/strings-acme.xml"/>
</plugin>

3. 書き換える文字列は言語別に定義します.

[DITA-OT]/com.acme.dita.string/xsl/strings-acme.xml

<?xml version="1.0" encoding="UTF-8"?>
<langlist>  
  <lang xml:lang="ja" filename="strings-ja-jp.xml"/>
  <lang xml:lang="ja-jp" filename="strings-ja-jp.xml"/>
</langlist>

[DITA-OT]/com.acme.dita.string/xsl/strings-ja-JP.xml

<?xml version="1.0" encoding="UTF-8"?>
<strings xml:lang="ja-JP">
  <str name="Note">参考</str>
  <str name="Notes">参考</str>
</strings>

أNoteとNotesの使い分けがいまいちわからないのですが(オイオイ)このプラグインを作って、[IDTA-OT]でコマンドプロンプトを立ち上げ、bin\dita -installでプラグインを組み込むとnote要素の見出し文字列はめでたく「注」から「参考」に替わってくれました.

実は組み込みによって以下のファイルが変わっています.

D:\DITA-OT\dita-ot-2.4.2\xsl\common\strings.xml

  <lang xml:lang="ja" filename="../../plugins/com.acme.dita.string/xsl/strings-ja-jp.xml"/>
  <lang xml:lang="ja-jp" filename="../../plugins/com.acme.dita.string/xsl/strings-ja-jp.xml"/>


このようなプラグインを使っての文字列の書き換えは確かに便利なのですが、特定のプラグインのみ有効にするということが出来ないので注意が必要です.つまりいったんこのプラグインを組み込んでしまうと、DITA-OTの標準のリテラル文字列を使っているプラグインはすべて「注」→「参考」になってしまうのです.更に文字列の書き換えが競合した場合の動作は「he results will be non-deterministic」(予測がつかない!)になります.

まあDITA-OTはシステムに一個だけしか置けないという決まりはないので用途によりいくらでもDITA-OTをフォルダを分けて使うことはできるのですが、それでも1つのプラグインのカスタマイズが全体に影響を与えてしまうというのはちょっといただけない感じもします.

あとソフトウェアのユーザーズマニュアルなんかを作っているとき、メニュー操作で「[ファイル] > [名前をつけて保存]を選択します.」などと書くことが頻繁にありますが、このような場合たいてい次のようにオーサリングします.

<menucascade><uicontrol>ファイル</uicontrol><uicontrol>名前をつけて保存</uicontrol></menucascade>

ところがここでも発生するのですが、お客様によっては「[ファイル] > [名前をつけて保存]を選択します.」でなくて「[ファイル]-[名前をつけて保存]を選択します.」と表記したい場合もあるのです.ところがあにはからんやこれは文字列の書き換えでは対応できません.何故かというとmenucascadeの区切り文字" > "はスタイルシート中にハードコードされているからです.例えばXHTMLでしたら

C:\Program Files\Oxygen XML Editor 18.1\frameworks\dita\DITA-OT2.x\plugins\org.dita.xhtml\xsl\xslhtml\ui-d.xsl

<xsl:template match="*[contains(@class,' ui-d/uicontrol ')]" name="topic.ui-d.uicontrol">
<!-- insert an arrow with leading/trailing spaces before all but the first uicontrol in a menucascade -->
<xsl:if test="ancestor::*[contains(@class,' ui-d/menucascade ')]">
 <xsl:variable name="uicontrolcount"><xsl:number count="*[contains(@class,' ui-d/uicontrol ')]"/></xsl:variable>
  <xsl:if test="$uicontrolcount&gt;'1'">
    <xsl:text> > </xsl:text>
  </xsl:if>
</xsl:if>
 <span class="uicontrol">
  <xsl:call-template name="commonattributes"/>
  <xsl:call-template name="setidaname"/>
  <xsl:apply-templates/>
 </span>
</xsl:template>

oXygenのWeb Helpの場合、テンプレートのオーバーライドをする拡張機能がありますので、それを使用して次のようなプラグインのテンプレートを作ります.

C:\Program Files\Oxygen XML Editor 18.1\frameworks\dita\DITA-OT2.x\plugins\com.acme.xhtml\plugin.xml

<plugin id="com.acme.dita.xhtml-extensions">
    <feature extension="com.oxygenxml.webhelp.xsl.dita2webhelp" file="xsl/ui-d.xsl"/>
</plugin>

C:\Program Files\Oxygen XML Editor 18.1\frameworks\dita\DITA-OT2.x\plugins\com.acme.xhtml\xsl\ui-d.xsl

<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="2.0"
     exclude-result-prefixes="dita-ot">
<xsl:template match="*[contains(@class,' ui-d/uicontrol ')]" name="topic.ui-d.uicontrol">
  <xsl:if test="ancestor::*[contains(@class,' ui-d/menucascade ')]">
    <xsl:variable name="uicontrolcount"><xsl:number count="*[contains(@class,' ui-d/uicontrol ')]"/></xsl:variable>
      <xsl:if test="$uicontrolcount&gt;'1'">
        <xsl:call-template name="getVariable">
            <xsl:with-param name="id" select="'Menucascade'"/>
        </xsl:call-template>
     </xsl:if>
   </xsl:if>
   <span class="uicontrol">
     <xsl:call-template name="commonattributes"/>
     <xsl:call-template name="setidaname"/>
     <xsl:apply-templates/>
   </span>
</xsl:template>

</xsl:stylesheet>

これで先ほどの文字列の変更に戻って、次のようにmenucascadeの区切り文字列を新たに定義してやります.

[DITA-OT]/com.acme.dita.string/xsl/strings-acme.xml

<?xml version="1.0" encoding="UTF-8"?>
<langlist>  
  <lang xml:lang="ja" filename="strings-ja-jp.xml"/>
  <lang xml:lang="ja-jp" filename="strings-ja-jp.xml"/>
  <lang xml:lang="en" filename="strings-en-us.xml"/>
  <lang xml:lang="en-us" filename="strings-en-us.xml"/>
</langlist>

[DITA-OT]/com.acme.dita.string/xsl/strings-ja-JP.xml

<?xml version="1.0" encoding="UTF-8"?>
<strings xml:lang="ja-JP">
  <str name="Note">参考</str>
  <str name="Notes">参考</str>
  <str name="Menucascade"> - </str>
</strings>

[DITA-OT]/com.acme.dita.string/xsl/strings-en-US.xml(英語は"-"でなく"-"を使う)
<?xml version="1.0" encoding="UTF-8"?>
<strings xml:lang="en-US">
  <str name="Menucascade"> - </str>
</strings>

"getVariable"というテンプレートはそれなりにxml:lnagを識別して、該当の文字列を取ってきてくれるようです.これで「[ファイル]-[名前をつけて保存]を選択します.」が実現できました.(注:英語の方はまだデータがないので未テストです.)

oXygenのWeb Helpはカスタマイズが可能で、まだまだ奥深いところがあります.例えばキーワードによる文字列検索を実現していますが、これをどのように効果的に使うかなど、けっこう調べなければなりません.開発者はoXygenのRadu Pisoiという方ですが、oXygenのフォーラムでの応答もすぐですし今後が楽しみです.

HTMLから覗き見るCSS組版とXSL-FO組版

$
0
0
DITAのHTMLへの変換をやっていてしみじみわかるのですが、HTMLへの変換をカスタマイズするのにCSSをちょこちょこっと修正して、それが一発で生成したHTMLに反映されるのはある意味「快感」です.何かとても自分がすべてをコントロールしているように思えてくるのです.HTMLへの変換というのはざっくり言えば

 ・ブロック要素のテーブルやリストはHTMLのtable/ul/olに変換される.
 ・その他のブロック要素はdivに変換される.
 ・インライン要素はspanに変換される.

と極めて単純です.そして重要なのは、DITAの要素毎にDTDやスキーマで定義されたclass属性の値を参考にして、HTMLのクラス属性にそれが反映されるという事です.

例えばよくあるパターンですが、figに番号付きの画像を配置して、その下にリストで番号に対する解説を付けるという場合があります.普通この解説はdlでやるらしいのですが、dlはオーサリングの手間が結構あるので次のようにsl(Simple List)で表現してみました.

    <fig id="fig_et5_btx_wx">
       <image placement="break" keyref="IMG_006_02" id="image_lr2_2tx_wx"/>
       <sl>
          <sli><fpnum>1</fpnum> フォルダアイコン</sli>
          <sli><fpnum>2</fpnum> パソコンアイコン</sli>
          <sli><fpnum>3</fpnum> プリンタアイコン </sli>
          <sli><fpnum>4</fpnum>  ステータス表示エリア</sli>
       </sl>
    </fig>

ここでfpnumというのは、image中の番号を参照するものとして特殊化で追加した要素です.一般にはここに番号を入れます.

これはDITA-OTのHTMLへの変換では次のようになります.

    <figure class="fig fignone" id="concept_2MainWindow__fig_et5_btx_wx">
       <br><img class="image" id="concept_MainWindow_image_lr2_2tx_wx" src="../image/fig_02.gif"><br>
       <ul class="sl simple">
          <li class="sli"><span class="ph fpnum">1</span> フォルダアイコン</li>
          <li class="sli"><span class="ph fpnum">2</span> パソコンアイコン</li>
          <li class="sli"><span class="ph fpnum">3</span> プリンタアイコン </li>
          <li class="sli"><span class="ph fpnum">4</span>  ステータス表示エリア</li>
       </ul>
    </figure>

注意いただきたいのですが、ここでspan/@classには次のfpnumのDTDの定義が反映されているということです.

<!ATTLIST  fpnum %global-atts;  class CDATA "+ topic/ph num-d/fpnum ">

そしてDITA-OTは実によくやってくれるのですが、例えばfpnumに@outputclass="folder"などと書けばそれも@class属性に反映してくれます.

    <sli><fpnum outputclass="folder">1</fpnum> フォルダアイコン</sli>


    <span class="ph fpnum folder">1</span>
    
となります.あとはこれに従って特別なスタイル付けをするCSSを書けばよい訳です.例えば

    span.fpnum{
         font-weight: bold;
    }

これでfpnumは太字表示になってくれます.outputclassを尊重することもできるでしょう.(CSSの当てられるプライオリティを考慮する必要があります.)

基本はこの筋書きで、もっと特殊なことをやりたければ、自分でHTML(HTML5もしくはXHTML)のプラグインを直してやるしかありません.しかしCSSでスタイリングするという手法はこのように「技術的には極めて単純な仕組み」以上の何物でもありません.

CSS組版は基本的にはXHTMLを作ってそこから組版してPDFを生成します.HTMLを作ることと基本的に等価なわけです.もちろんHTMLとして作る場合のCSSとページメディアに出力する場合のCSSはそもそも異なるでしょう.HTMLはピクセルユニットが基本ですが、ページメディアではピクセル単位は意味を成しません.(それでもともかくPDFになってほしいというならCSSを共通にもできるでしょうけれども)

XSL-FOはロジックを重ねれば、極めて複雑なレイアウトをFO(フォーマッティングオブジェクト)の組み合わせで表現できますが、CSSでは複雑なことをやろうとすればするだけどんどんスタイルの迷路にはまり込むことは明白です.そもそものアーキテクチャから逃れることはできないからです.

たぶんそうだと思うのですが、HTML+CSSから入ってきた人は、XSL-FOなんてなんでこんなコストの高いことをやらなければならないんだ!と思うのでしょう.確かにXSL-FOはHTMLのように「ちょこちょこっと」は作れません.ページマスター(fo:layout-master-set)を作らなければならないし、ページシーケンス(fo:page-sequence)を作らねばなりません.つまるところ固定費が高いのです.ですのでなじまない人もいるのでしょう.

固定費が高い代わりにそこには高いビルディングを立てることが出来ます.でもCSSでは固定費がない代わりに3階建て以上のもの(もしくは複雑な仕様の建物)を立てようとするとどんどん大変になります.

簡単なことを簡単な方法でつくることは賛成ですが、それ以上のことに頭をつっこまないのが身のためだと思います.CSSはHTMLのクラス属性にスタイルを関連付けるという極めて単純な仕組みを越えられる技術ではないからです.

以下はCSSを推し進める側のW3CのLiam Quin自身がこの時点でCSSがXSL-FOを越えられない(?)一覧をリストアップしたものです.XSL-FOは実に良くできているのです.

CSS XSLFO Gap Analysis

CSS組版をやろうとする場合には、あらかじめよくその本質と限界を見定める必要があるのではないでしょうか?決して「CSSなら安く上げられる!」などとは思わないことです.

XSLT3.0への道(22) 配列型

$
0
0
次々と技術が進化するのでとてもキャッチアップできませんが、2月7日はXSLT 3.0にとって特別な日になったようです.

XSL Transformations (XSLT) Version 3.0
W3C Candidate Recommendation 7 February 2017


ついにというかようやくXSLT 3.0はW3Cの最終Candidate Recommendation(勧告候補)となりました.そしてXSLT 3.0のインプリメントはXPath 3.1のデータモデルをサポートします.(というかしなければなりません.)

XML Path Language (XPath) 3.1
W3C Proposed Recommendation 17 January 2017

XPath 3.1で重要なデータ型の追加になったのはマップ型(map)と配列型(array)です.この日に同時にリリースされたSaxonでちゃんとサポートされたようなので配列型をすこし触ってみました.といってもちょうどSaxon PE(プロフェッショナルエディション)のバージョンアップの期間が切れてしまい、新しいのはオープンソースのHE(ホームエディション)でしかテストできません.

例として、用紙とその幅、高さを表現することを考えてみました.

Letterは幅215.9mm、高さ279.4mm
Legalは幅215.9mm、高さ355.6mm
B4は幅250mm、高さ353mm
A4は幅210mm、高さ297mm

というようなものです.これは今まではitem()*で表すしか手がなかったように思えます.例えば

<xsl:variable name="paperInfoSeq" as="item()*" select="('Letter',215.9,279.4,'Legal',215.9,355.6,'B4',250,353,'A4',210,297)"/>

という感じです.あまりきれいではありません.用紙単位に情報がグルーピングできないからです.配列を使えば次のように素直に表現できます.

<xsl:variable name="paperInfoArray" select="[('Letter',215.9,279.4),('Legal',215.9,355.6),('B4',250,353),('A4',210,297)]"/>

さっそく次のようなスタイルシートを書いてみます.oXygen 18.1でやっていますが、まだoXygenは古いSaxonをバリデーションに使用しているのでボロボロエラーが出ますが気にせずコーディングします.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    exclude-result-prefixes="xs array"
    version="3.0">
    
    <xsl:function name="ahf:returnPaperInfoArray" as="array(item()*)">
        <xsl:sequence select="[('Letter',215.9,279.4),('Legal',215.9,355.6),('B4',250,353),('A4',210,297)]"/>
    </xsl:function>

    <xsl:template match="/">
        <xsl:variable name="arrayVar" as="array(item()*)" select="ahf:returnPaperInfoArray()"/>
        <xsl:for-each select="1 to array:size($arrayVar)">
            <xsl:variable name="paperInfo" as="item()*" select="$arrayVar(position())"/>
            <xsl:message select="concat('[',position(),'] paper=',string($paperInfo[1]),' width=',string($paperInfo[2]),'mm height=', string($paperInfo[3]),'mm')"/>
        </xsl:for-each>
    </xsl:template>
    
</xsl:stylesheet>

最初のahf:returnPaperInfoArrayという関数は、配列がXPath 3.1では「型」になったことを確認するものです.as="array(item()*)で型として記述できます."["と"]"で配列のコンストラクトができます.これは次のようにも記述できます.

<xsl:sequence select="array{('Letter',215.9,279.4),('Legal',215.9,355.6),('B4',250,353),('A4',210,297)}"/>

その下のテンプレートは、配列を$arrayVarに格納し、配列の各要素をxsl:messageで表示させています.配列のサイズはarray:size()で取得できます.

これを動かした結果は次のようなものになります.

イメージ 1

ここで大事なことは配列の要素(member)の自由度が高いことです.配列のメンバーには関数やマップなども可能なはずです、Saxon Heでは残念ながらここまでしか試せませんが、新しいSaxon PE 9.7.0.15が手に入れられたら試してみたいと思います.

XSLT3.0への道(23) 配列型(オマケ)

$
0
0
前にも述べましたがXSLT 3.0の「高階関数」の機能はオープンソースのSaxon HEでは使えません.と思っていたら最新のoXygenで外部プラグインとして、最新の9.5.0.15が使用できるとのメールがありました.早速ダウンロードして見ると次のように組み込むことが出来ます.

イメージ 1

oXygenにはXSLTスタイルシートのデバッグができる機能があるのですが、それは使えないとのこと.これには特別なビルドをしたSaxonが必要なのです.しかし、最新のSaxon EEを使ったXSLTスタイルシートの検証と実行はできます.つぎのようにオプションでXSLT 3.0のValidation Engineの設定を変えてやれば良いのです.

イメージ 2


これで単に文字列を返す関数を作りそれを配列にしてみました.最新のSaxonを使用しているので、今度は検証エラーは出ません.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    exclude-result-prefixes="xs"
    version="3.0">
    
    <xsl:function name="ahf:returnFuncArray" as="array(function(*)*)">
        <xsl:variable name="f1" as="function(*)" select="function() as xs:string {'売っちゃった'}"/>
        <xsl:variable name="f2" as="function(*)" select="function() as xs:string {'タダ同然で'}"/>
        <xsl:variable name="f3" as="function(*)" select="function() as xs:string {'国有地!'}"/>
        <xsl:variable name="f4" as="function(*)" select="function() as xs:string {'「云々」も'}"/>
        <xsl:variable name="f5" as="function(*)" select="function() as xs:string {'読めずに話した'}"/>
        <xsl:variable name="f6" as="function(*)" select="function() as xs:string {'下手英語!'}"/>
        <xsl:variable name="f7" as="function(*)" select="function() as xs:string {'媚びるのと'}"/>
        <xsl:variable name="f8" as="function(*)" select="function() as xs:string {'ゴルフだけは'}"/>
        <xsl:variable name="f9" as="function(*)" select="function() as xs:string {'うまくなり'}"/>
        <xsl:sequence select="[($f1,$f2,$f3),($f4,$f5,$f6),($f7,$f8,$f9)]"/>
    </xsl:function>

    <xsl:template match="/">
        <xsl:variable name="funcVar" as="array(function(*)*)" select="ahf:returnFuncArray()"/>
        <xsl:for-each select="1 to array:size($funcVar)">
            <xsl:variable name="functions" as="function(*)*" select="$funcVar(position())"/>
            <xsl:variable name="haiku" as="xs:string*" select="for $n in 1 to count($functions) return $functions[$n]()"/>
            <xsl:message select="string-join($haiku,' ')"/>
        </xsl:for-each>
    </xsl:template>
    
</xsl:stylesheet>

いざこれを実行してみると次のような画面になります.ちゃんと関数の配列は機能していてくれます.

イメージ 3


でも高階関数(High Order Function)はとても便利な機能です.オープンソース版でこれが出来てしまえばSaxonicaのビジネスモデルはなくなっちゃうのでしょうけれども、ちょっと残念ではあります.

年度末がやってきた!

$
0
0
3月です!年度末です.毎年ほぼ同じようなパターンでお仕事がやってきます.年度内検収必須!という訳で結構忙しくなります.あと今年の年度末はちょっと違います.いよいよ新入社員が4月から来てくれます.今どき貴重な新入社員に呆れられないようにと、オフイスの席替えの準備と不用品のかた付けが始まりました.わたしのデスクも例外ではありません.ここ数年ほったらかしだったので整理するといろいろヨタモノが出てきました.以下その写真です.

[北京の地下鉄とバスの乗車券]

イメージ 1


もう十数年前に北京の子会社にプログラムを作ってもらうため出張していた時期がありました.ある年など1年のうち半年くらい行っていました.ほとんど仕事漬けなのですが、たまに煮詰まると、土日にホテルを出て、北京市内をいろいろ散策しました.左側はその時乗った地下鉄の乗車券、右は市内バスの乗車券です.このころ北京は一歩主要道から外れると、結構「汚い」というか庶民の生活丸出しのところがあって、逆にそれは私にとっては昔の昭和30年代の日本を見ているようで懐かしかったのですが...

あと市内バスと言えば、乗るのはほとんど戦争でした.バス停に居てバスが来てドアが開くと、降りる人と乗る人が同時にわっとぶつかり合うのです.降りる人を待ってからなんていうマナーなんて全然ありません.乗るのも降りるのも大変なので、片道40分で徒歩で子会社まで歩くこともしばしばでした.


[Windows 3.1のインストールディスク1]

イメージ 2


いったい何のマシンについていたのかすっかり忘れてしまいましたが、何故かIBM製のものです.昔買ったThink Padについていたものかとも思うのですが、当時Think Padなんて安月給の私には高嶺の花だったので、もしかしたら違うかもしれません.でもフロッピーディスクなんて今は死語ですね.でもこのころはソフトのインストールと言えば、これをとっかえひっかえしてやったものです.

[XSLTの本]

イメージ 3


ひっかき集めたらこんなになりました.一番下がMichael KayのXSLT Programmer's Referenceの初版です.これと2番目の第2版はボロボロになるまでよく読みました.第2版はインプレスから「XSLTバイブル」という題で和訳が出ていました.でも恰好をつけて、こちらは買わずに英文の方を必死に読んでいました.あと読んだのがXSLT 2.0 and XPath 2.0 Programmers Referenceです.大型本なのですが、手脇に抱えて会社と家を往復していました.上2冊のJeni Tennisonの本も買いましたがこちらはあまり読まなかったです.もうすぐXSLT 3.0が出たらXSLT 3.0 and XPath 3.1 Programmers Referenceというのが出るんでしょう.これらの本だけは捨てないで取っておきたいと思います.


XSLT3.0への道(24) 閑話休題;その昔と今のマイクロソフト

$
0
0
もう昔なので確たる年は忘れてしまいましたが社長から「XMLに命をかけろ(!?)」と言われて、XSLTの勉強をしだしたのが2000年頃だっと思います.XSLT 1.0は1999年11月に勧告になっていましたが、そのころ和書でXSLTが載っている本と言えばこのCD-ROMがオマケで付いてきた「XML + XSL サンプル集」というCQ出版から出ていた本しかなかったのではと思います.

イメージ 1


早速買って(確かその当時すでにダウンロード可能だった)MSXMLやSaxonで試してみたのですが、サンプルはまったく動いてくれませんでした.理由はとても簡単、この本の元になってたのは当時マイクロソフトがXSLTのワーキングドラフトを元にしてインプリメントしたIEにバンドルされたMSXMLでしか動かなかったのです.という訳で高いお金を出したのも関わらずお話にならなかったので本は怒って破って捨てました.(筆者のみなさんゴメンナサイ)

今回年度末の大掃除をしていたら、オマケのCD-ROMが出てきました.せっかくなので少し中のコードを見てみるとXSLT 1.0が勧告になるまでの足取りを見ることができます.例えば次のようなコードがあります(一部省略)

<?xml version="1.0" encoding="Shift_JIS"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/TR/WD-xsl" xml:lang="ja">
    ...
  <xsl:template match="全体">
    <table>
      <tr>
        <th>番号</th>
        <th>品名</th>
        <th>価格</th>
      </tr>
      <xsl:for-each select="商品">
        <xsl:if test="価格[.$gt$ 150000]">
          <tr>
            <td>
              <xsl:value-of select="番号"/>
            </td>
            <td>
              <xsl:value-of select="品名"/>
            </td>
            <td style="text-align:right">
              <xsl:value-of select="価格"/>
            </td>
          </tr>
        </xsl:if>
      </xsl:for-each>
    </table>
  </xsl:template>
</xsl:stylesheet>

ワーキングドラフトの段階では数値比較の演算子は "$gt$"なんて表されていたのですね.まあ昔をしのぶのは良いのですが、このマイクロソフトのフライイングによるXSLTの実装は不評を買い、それ以降マイクロソフトが先行してこのような仕様を先行実装するという事はなかったと思います.そしてXSLT 2.0の時代になるとマイクロソフトはまったくXSLTからそっぽを向いてしましました.

でも2017年の今になって、何やら動きがあるようです.いわくマイクロソフトがXSLT 3.0を.NETに実装する(かも!?)というものです.

XSLT 2.0/3.0 in .Net Core

Implement XSLT 3.0 for .Net

まあまだ賛同者を募っている状態なのでどうなるかはわかりません.しかし私がみた時点では1,344人の賛同者がいます.もしあなたも賛同者に加わっていただければ、巨人マイクロソフトも動くやもしれません.

私はStackOverflowのXSLTの質問を購読していますが、ある意味は二極分化しているようにも見えます.どうしてもXSLT 1.0でやるにはどうするのか?という質問(たぶんMXSMLや.NETのXSLT)と、XSLT 2.0やXSLT3.0でもOK(JavaのSaxonなど)という質問です.前者の質問はいわばマイクロソフトプラットフォームの制約に縛られていて哀れでもあります.マイクロソフトの.NET戦略の中でもいつまでもXSLT 1.0(XPath 1.0)ではやってゆけないと考えるときが来てくれるのかもしれません.

スキーマトロンを作ってみる.

$
0
0
どうもいつまでたってもDITAのスキーマはDTDが主流のようです.でもDTDではいつまでたっても属性値や要素の値を適切な値に強制することができません.そういうときに力を発揮してくれるのがSchematron(スキーマトロン)です.今まであまり作ったことがなかったのでサンプルを作ってみました.

対象として考えたのが単位値です.機械などのメンテナンスの文書を作る場合、どこどこのナットはこの強度で締めろとか、オイルは何CC入れろとかいろいろな単位が登場します.単位値は一般に、数値+単位の形式をしているのですけれども、これを適切にチェックするのは結構難しそうです.例えば

・数値は整数値も実数値もある.下手をするとマイナス値もある.また10-20℃のように範囲値の場合もある.
・単位はいろんな種類がある.大抵数値の後につくが、例えばpH値のような場合、単位が前につく場合もある.

しかしいろいろ考えているとたぶん出来なくなってしまうので、

・マイナス値、範囲値はなし
・単位はいろいろカスタマイズできるように固定的なものとしない

という前提で考えることにしました.いろいろ試して作ってみたのが次のようなスキーマトロンです.スキーマトロンにはいろんな規格がありますが、一番強力なISO Schematronにしました.長さや、重さやいろんな単位を入れるようにしてあるのがご覧いただけると思いまうす.また、チェックには正規表現のパターンマッチングを使用し、単位値と単位をmatches()とreplace()を使って別々に取り出す工夫をしています.

[unit.sch]
<?xml version="1.0" encoding="UTF-8"?>
    abstract="true" id="validate-unit">
    <let name="lengthUnit"   value="('mm','cm','m','km','in','ft')"/>
    <let name="areaUnit"     value="('cm2','m2','in2','ft2')"/>
    <let name="volumeUnit"   value="('cm3','m3','l','ml','in3','ft3')"/>
    <let name="weightUnit"   value="('mg','g','kg','t','bl','oz')"/>
    <let name="powerUnit"    value="('N','dyn','kgf','lbf')"/>
    <let name="pressureUnit" value="('Pa','kPa','MPa','mmHg','mmH2O')"/>
    <let name="timeUnit"     value="('s','sec','min','h','ms')"/>
    <let name="speedUnit"    value="('m/s','ft/s','km/h')"/>
    <let name="temparatureUnit" value="('C','F')"/>
    <let name="resistanceUnit"  value="('Ω','kΩ','MΩ')"/>
    <let name="currentUnit"  value="('A','mA')"/>
    <let name="voltageUnit"  value="('V','mV')"/>
    <let name="rotationUnit" value="('rpm','rps')"/>
    <let name="ratioUnit"    value="('%')"/>
    <let name="validUnits"   value="($lengthUnit, $areaUnit, $volumeUnit, $weightUnit, $powerUnit, $pressureUnit, $timeUnit, $speedUnit, $temparatureUnit, $resistanceUnit, $currentUnit, $voltageUnit, $rotationUnit, $ratioUnit)"/>
    <rule context="$unitValue">
        <let name="sepStr" value="'&#xFFFD;'"/>
        <let name="uvRegExp" value="'^([\d]+?\.{1}?[\d]+?|[\d]+?)([\D23]+)$'"/>
        <let name="match" value="matches(.,$uvRegExp)"/>
        <let name="valueAndUnit" value="replace(.,$uvRegExp,concat('$1',$sepStr,'$2'))"/>
        <let name="vPart" value="substring-before($valueAndUnit,$sepStr)"/>
        <let name="uPart" value="substring-after($valueAndUnit,$sepStr)"/>
        <report test="not($match)">Wrong unit value format. It should be numeric value part plus unit part.</report>
        <let name="uValid" value="$uPart = ($validUnits)"/>
        <report test="not($uValid) and $match">Wrong unit format.</report>
    </rule>
    <rule context="$unit">
        <let name="uValid" value="string(.) = ($validUnits)"/>
        <report test="not($uValid)">Wrong unit format.</report>
    </rule>
</pattern>

これは、abstract="true"となっているので、特定のコンテキスト(バリデーションする対象)にバインディングされていません.そこで次のようなスキーマトロンで実際バリデーションする要素に結びつけます.

[validate-concept-unitval.sch]

<?xml version="1.0" encoding="UTF-8"?>
<schema xmlns="http://purl.oclc.org/dsdl/schematron" queryBinding="xslt2"
    <include href="unit.sch"/>
    <pattern id="ph-unit" is-a="validate-unit">
        <param name="unitValue" value="ph[string(@outputclass) eq 'uv']"/>
        <param name="unit" value="ph[string(@outputclass) eq 'unit']"/>
    </pattern>
</schema>

ここでは、(特殊化するのが厄介だったので)、<ph outputclass="uv">で単位値を、<ph outputclass="unit">で単位を記述するものとしてあります.

実際にこのスキーマトロンを検証に使用するには、oXygenでは次のように検証シナリオにvalidate-concept-unitval.schをスキーマとして指定します.

イメージ 1


そしてこのバリデーションシナリオをチェックしてCtrl + Shift + Vとやると、以下のconceptでは"Document is valid"と出てくれます.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE concept PUBLIC "-//OASIS//DTD DITA Concept//EN" "concept.dtd">
<concept id="concept_m4n_zxq_2z">
 <title>Unit value test</title>
 <conbody>
  <p><ph outputclass="uv">0.456kg</ph></p>
  <p><ph outputclass="unit">m2</ph></p>
 </conbody>
</concept>

イメージ 2


でもこれでは実際動いているのか不安なので、データをいじくると

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE concept PUBLIC "-//OASIS//DTD DITA Concept//EN" "concept.dtd">
<concept id="concept_m4n_zxq_2z">
 <title>Unit value test</title>
 <conbody>
  <p><ph outputclass="uv">0.456kgZZZ</ph></p>
  <p><ph outputclass="unit">m2</ph></p>
 </conbody>
</concept>

ちゃんと"Wrong unit format"と出てくれました.メデタシメデタシ!

イメージ 3

残念なのはこのスキーマトロンはoXygenでは動かすことが出来るのですが、XMetaLでは無理な点です.スキーマトロンはたぶん間違いなくXSLTスタイルシートに内部的に変換されて動作するのでしょう.このスキーマトロンはXPath 2.0をふんだんにつかっているので、XSLT 1.0しかサポートしないXMetaLでは動いてくれようがないのです.

スキーマトロンは非常に便利で効果的なのですが日本ではあまり使われているような話を聞きません.もっとオーサリングを簡単で確実にし生産効率を上げるためにも活用されて良いのではないかな~と思います.

DITA 1.3の見果てぬ夢(3)

$
0
0
2014年の11月に行われたDITA Festa 2014で、DITAコンソーシアムジャパンのIA部会のプレゼンがありました.そこでは(その時はまだ来るべき)DITA 1.3の様々な特徴が紹介されました.そのひとつに「分冊間参照(異なる分冊同士のリンク)がDITA 1.3では出来るようになります」という言及がありました.例えばPDFではあるPDFから別のPDFへリンクを張れるという事になります.

私はそれを聞いていて、「PDFにおいて分冊間参照が如何に大変か?」また「実際にDITA-OTでインプリメントできるのか?」という点で疑問の意見を出しました.もうそれから2年が経とうとしています.確かに分冊間参照はDITA 1.3では仕様として記述されています.

2.3.4.5 Cross-deliverable addressing and linking

しかし実際に今のDITA-OTでどのようになっているのでしょうか?やってみるとすぐわかります.

1. mapの準備
DITA-OT 1.8.5までおまけで付いてきたsamplesフォルダのsequence.ditamapに次のような記述を加えます.

<map id="map_2E3A2E4C04634118AD48D94CD092E90A" title="Eclipse content aggregated by a map"> 
  ...
  <topicref href="concepts/wwfluid.xml" navtitle="Windshield washer fluid" type="concept"/> 
  <mapref keyscope="other-map" scope="peer" href="taskbook.ditamap"/>
  <keydef keys="other-topic-installing" keyref="other-map.installing"/>
</map> 

意味としてはmaprefでscope="peer"をつけることにより、taskbook.ditamapがこのmapではない別のpublicationであることを表現します.そしてkeydefでこのtaskbook.ditamapの中のinstallingというkeyが付けられたtopicref(従ってそれが指すtopic)を参照することを定義します.

<bookmap id="taskbook">
  <booktitle>
  <booklibrary>Retro Tools</booklibrary>
  <mainbooktitle>Product tasks</mainbooktitle>
  ...
  <chapter href="taskbook/installing.dita" keys="installing">
  ...
</bookmap>

2. トピックからの参照

sequence.ditamapの中のトピックからは、@keyrefを使用して、次のようにしてこれを参照します.

<p>Refer to <xref keyref="other-topic-installing" format="dita"/></p>

3. ビルド

これで最新のDITA-OT 2.4でPDFをビルドしてみます.すると結果のxrefの箇所には何も現れません.ログには次のように出ます.

keyref:
   [keyref] Reading file:/D:/DITA-OT/DITA-OT1.8.5/samples/temp/pdf/sequence.ditamap
   [keyref] Processing file:/D:/DITA-OT/DITA-OT1.8.5/samples/temp/pdf/sequence.ditamap
   [keyref] file:/D:/DITA-OT/DITA-OT1.8.5/samples/sequence.ditamap:28:72: [DOTJ047I][INFO] Unable to find key definition for key reference "other-map.installing" in root scope. The href attribute may be used as fallback if it exists
   [keyref] Processing file:/D:/DITA-OT/DITA-OT1.8.5/samples/temp/pdf/concepts/wwfluid.xml
   [keyref] Processing file:/D:/DITA-OT/DITA-OT1.8.5/samples/temp/pdf/sequence.ditamap
   [keyref] file:/D:/DITA-OT/DITA-OT1.8.5/samples/sequence.ditamap:28:72: [DOTJ047I][INFO] Unable to find key definition for key reference "other-map.installing" in root scope. The href attribute may be used as fallback if it exists
   [mapref] Transforming into D:\DITA-OT\DITA-OT1.8.5\samples\temp\pdf
   [mapref] Loading stylesheet D:\DITA-OT\dita-ot-2.4\xsl\preprocess\mapref.xsl
   [mapref] Processing D:\DITA-OT\DITA-OT1.8.5\samples\temp\pdf\sequence.ditamap
   [mapref] Recoverable error on line 356 of maprefImpl.xsl:
   [mapref]   FODC0002: java.io.FileNotFoundException:
   [mapref]   D:\DITA-OT\DITA-OT1.8.5\samples\temp\pdf\taskbook.ditamap (指定されたファイルが見つかりません。)

つまり、分冊間参照はDITA-OTにはまったく実装されていません.これは無理からぬことです.いくらDITAの仕様の上で分冊間参照が定義されても、DITA-OTは

・ 参照しているtaskbook.ditamapからどういう名称のPDFが生成できるかわからない.
・ 参照している<chapter href="taskbook/installing.dita" keys="installing">のinstalling.ditaが(PDF上で)どのような外部からアクセスできるIDをエキスポートするのかわからない.

からなのです.でも上記の2つがわかれば別にDITA 1.3を採用しなくとも分冊間参照は実現できます.実際DITA 1.2で分冊間参照を実現しているお客様は現に存在します.

分冊間参照はDITA 1.3の仕様の上では「解決」されました、しかし(たぶん)「永久」にDITA-OTでは実装されることはないだろうと思います.上記の2つの情報をDITA-OTに与えることは実は極めて困難であるからです.

まず現実問題で実装できないことを仕様上で解決したとしてもそれは(私にとっては)絵に描いた餅にしか過ぎないように思えます.(言いすぎでしょうか???)

XSLT3.0への道(21) マップ

$
0
0
プログラミングで良く使用するデータ構造に、キーと値の関連性を保持するものがあります.多くのプログラミング言語ではmapという名前でこの機能をサポートしています.XSLT 3.0でもmapという「データ型」がサポートされるようになりました.今回はこれを簡単に紹介したいと思います.

まずXSLT 3.0より以前では、様々な工夫をしてキーと対応する値を表現してきました.いろいろなスタイルシートを見てきましたが、次のようなものがあります.一つはこの構造を外部のXMLとして与えるというものです.例えば次のようなXMLファイルを用意します.


[mapdata.xml]
<?xml version="1.0" encoding="UTF-8"?>
<items>
  <item key="red" value="赤"/>
  <item key="green" value="緑"/>
  <item key="blue" value="青"/>
</items>


これに対して次のようなスタイルシートでキーに対する値を求めることができます.


<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    exclude-result-prefixes="xs"
    version="2.0">
    
    <xsl:variable name="mapDoc" as="document-node()" select="document('mapdata.xml')"/>

    <xsl:function name="tmf:getColor" as="xs:string">
        <xsl:param name="prmKey" as="xs:string"/>
        <xsl:variable name="result" as="attribute()?" select="($mapDoc//item[string(@key) eq $prmKey]/@value)[1]"/>
        <xsl:sequence select="if (exists($result)) then string($result) else '???'"/>
    </xsl:function>
  
    <xsl:template match="/">
      <xsl:message select="'red=',tmf:getColor('red')"/>
      <xsl:message select="'green=',tmf:getColor('green')"/>
      <xsl:message select="'blue=',tmf:getColor('blue')"/>
      <xsl:message select="'purple=',tmf:getColor('purple')"/>
    </xsl:template>
  
</xsl:stylesheet>

結果は次のようにメッセージ出力されるでしょう.

red= 赤
green= 緑
blue= 青
purple= ???

またxs:stringのシーケンスを2つ用意して次のように定義しても同じ結果を得ることができます.

  <xsl:variable name="key" as="xs:string+" select="('red','green','blue')"/>
  <xsl:variable name="value" as="xs:string+" select="('赤','緑','青')"/>
  
  <xsl:function name="tmf:getColor" as="xs:string">
    <xsl:param name="prmKey" as="xs:string"/>
    <xsl:variable name="index" as="xs:integer?" select="index-of($key,$prmKey)"/>
    <xsl:sequence select="if (exists($index)) then $value[$index] else '???'"/>
  </xsl:function>

しかしいずれにせよキーと値を素直に表せるデータ構造ではありませんでした.XSLT 3.0ではダイレクトに次のようにスタイルシートに書くことができます.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    exclude-result-prefixes="xs map"
    version="3.0">
    
    <xsl:variable name="mapColors" as="map(xs:string,xs:string)">
        <xsl:map>
          <xsl:map-entry key="'red'"   select="'赤'"/>
          <xsl:map-entry key="'green'" select="'緑'"/>
          <xsl:map-entry key="'blue'"  select="'青'"/>
        </xsl:map>
    </xsl:variable>

    <xsl:function name="tmf:getColor" as="xs:string">
        <xsl:param name="prmKey" as="xs:string"/>
        <xsl:variable name="result" as="xs:string?" select="(map:get($mapColors,$prmKey))[1]"/>
        <xsl:sequence select="if (exists($result)) then $result else '???'"/>
    </xsl:function>
    
    <xsl:template match="/">
      <xsl:message select="'red=',tmf:getColor('red')"/>
      <xsl:message select="'green=',tmf:getColor('green')"/>
      <xsl:message select="'blue=',tmf:getColor('blue')"/>
      <xsl:message select="'purple=',tmf:getColor('purple')"/>
    </xsl:template>
  
</xsl:stylesheet>

map()はいわばコンストラクタの関数なのですが、XSLT 3.0では関数そのものもデータ型と定義出来ます.ここがすごいところです.map:getはマップ型の変数とキーを与えて、対応する値を取りだすことができます.なければ空シーケンスを返します.

もしマップの定義が「ダサイ!」とお考えならば次のようにXPath式で一発で書くことも可能です.

    <xsl:variable name="mapColors" as="map(xs:string,xs:string)" select="map{'red':'赤','green':'緑','blue':'青'}"/>

前者は動的にスタイルシートでマップを作れます.マップの内容が静的に与えうるものならば、XPathで書いた方が楽ですね.

動的にマップを生成する例として、xsl:paramから作る例を試してみました.これも上記と同じ結果となります.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    exclude-result-prefixes="xs map"
    version="3.0">
    
    <xsl:param name="PRM_COLORS" as="xs:string" select="'red=赤,green=緑,blue=青'"/>
    
    <xsl:variable name="mapColors" as="map(xs:string,xs:string)">
        <xsl:map>
            <xsl:variable name="colorDefinitions" as="xs:string*">
                <xsl:analyze-string select="$PRM_COLORS" regex="[,]">
                    <xsl:matching-substring/>
                    <xsl:non-matching-substring>
                        <xsl:sequence select="."/>
                    </xsl:non-matching-substring>
                </xsl:analyze-string>
            </xsl:variable>
            <xsl:for-each select="$colorDefinitions">
                <xsl:variable name="colorDefinition" as="xs:string" select="."/>
                <xsl:variable name="key" as="xs:string" select="substring-before($colorDefinition,'=')"/>
                <xsl:variable name="value" as="xs:string"  select="substring-after($colorDefinition,'=')"/>
                <xsl:map-entry key="$key" select="$value"/>
            </xsl:for-each>
        </xsl:map>
    </xsl:variable>
    
    <xsl:function name="tmf:getColor" as="xs:string">
        <xsl:param name="prmKey" as="xs:string"/>
        <xsl:variable name="result" as="xs:string?" select="(map:get($mapColors,$prmKey))[1]"/>
        <xsl:sequence select="if (exists($result)) then $result else '???'"/>
    </xsl:function>
    
    <xsl:template match="/">
    <xsl:message select="'red=',tmf:getColor('red')"/>
    <xsl:message select="'green=',tmf:getColor('green')"/>
    <xsl:message select="'blue=',tmf:getColor('blue')"/>
    <xsl:message select="'purple=',tmf:getColor('purple')"/>
    </xsl:template>
</xsl:stylesheet>


マップは非常に便利です.XSLT 3.0は2015年11月にCandidate Recommendationになっていますが、早く勧告になってもらいたいものです.


XSLT 2.0で便利になった機能(58) キャラクタマップ(2)

$
0
0
キャラクタマップ(xsl:character-map)についてはすでに以前書きましたのでダブルポストです.xsl:character-mapは、XSLTスタイルシートで入力XMLを出力XMLに変換し、それを実際のファイルに書き出す(シリアライズ)するときに、自動的に「指定した文字」を「指定した文字列」に置換してくれる機能です.

あまり使う機会はなかったのですが、最近Word2003のWordMLをフィルタリングしてコンバージョンするプログラムを書くことがあり、これを使わさせてもらいました.Word2003はWord文書をXMLで表現可能にした最初のWordのバージョンです.それ以来様々な変遷がありましたが、いまやWordでは.docxフォーマット(XMLをフォルダ階層ごとZIPしたファイル)が標準の保存形式になっています.

ところで何故キャラクタマップを使ったかなのですが、WordMLや.docxの中のWordML(XML)はMicrosoft Wordが書き出したものです.なので行末の改行コードがCR/LFになってしまっています.ところがXSLTスタイルシートで書き出したXMLは基本的に行末コードはLFです.フィルタリングしてコンバージョンするので、WinDiffのようなコンペアツールでフィルタリング前後を検証のため比べるのですが、ここで改行コードが違っているとアウトです.文書全体が違っているという表示になってしまいます.

以下がその表示例です.全体が黄土色ですべてが差分ありになってしまっています.

イメージ 1


これでは検証のしようがないのでいろいろ考えた末に使用したのがキャラクタマップでした.使い方は以下の様なものです.

<xsl:character-map name="windows-newlines">
    <xsl:output-character character="&#xFFFD;" string="&#x0D;&#x0A;"/>
</xsl:character-map>

<xsl:output method="xml" version="1.0" encoding="UTF-8" byte-order-mark="no" indent="no" use-character-maps="windows-newlines"/>

<!--テキストノードのテンプレート
    改行をU+FFFDに置き換えます.
    別にU+FFFDでなくともつかわれないコードだったらなんでもOKです.
  -->
<xsl:template match="text()">
    <xsl:analyze-string select="." regex="[\n]">
        <xsl:matching-substring>
            <xsl:text>&#xFFFD;</xsl:text>
        </xsl:matching-substring>
        <xsl:non-matching-substring>
            <xsl:value-of select="."/>
        </xsl:non-matching-substring>
    </xsl:analyze-string>
</xsl:template>

こうすればWordの出力と同様に行末をCR/LFにすることができます.例えば以下の様な感じです.こんどは本当の差分の箇所(黄土色の線で示されている)だけが比べられます.

イメージ 2

まあWordMLなんかを変換する機会はあまりないかもしれませんが、キャラクタマップがあってくれると比較/検証は非常に楽にすることができます.

Viewing all 102 articles
Browse latest View live