yonetch Works XML化プロジェクトページ

〜XMLおよび周辺技術習得のためのメモ〜

本サイト自体のコンテンツを例題として、XMLを中心とした(必ずしもコンテンツ管理に限らない)データ処理に役立つ要素技術を集めるためのメモページです。かなり取り留めのないものになりそうな予感...

各リンク凡例: [>] … Netflix、[>] … Amazon Prime Video、[>] … YouTube、[>] … WOWOW on Demand
(リンク先で配信終了となっている場合があります)


2024.01.21
●中間ファイル増強
スタトレの新シリーズのリリースが相次ぎ、ここのところエピソードリストやツッコミ集計リストの更新に無視しがたい処理時間がかかるようになってきた。主な要因は、ツッコミリンク、動画リンクをつけるところである。これまではエピソード毎にツッコミの有無、動画リンクの有無をチェックして都度htmlのリンクを生成してきた。だが、総エピソード数が数百にのぼる今、負担が顕在化したのだ。
●項目の追加
以前、ツッコミ評価値をあらかじめ集計した中間ファイルを作成した。ここに、さらに情報を追加し、ツッコミリンク、動画リンク情報を HTML 形式で納めておくことにした。
  <記事 日付="2023-12-03" 見出し="STRANGE NEW WORLDS" 状態="公開">
    <エピ情報>
      <シリーズ名>SNW</シリーズ名>
      <シーズン番号>1</シーズン番号>
      <話数>1</話数>
      <t:エピタイ xml:lang="en" is原題="true" 直訳="奇妙な新世界">STRANGE NEW WORLDS</t:エピタイ>
      <t:エピタイ xml:lang="ja">ストレンジ・ニュー・ワールド</t:エピタイ>
      <翻訳>佐藤恵子</翻訳>
      <翻訳>川嶋加奈子</翻訳>
      <ツッコミファイル名>SNW1.html</ツッコミファイル名>
      <ツッコミリンク>
        <a href="SNW1.html#STRANGE NEW WORLDS">STRANGE NEW WORLDS</a>
      </ツッコミリンク>
      <評価>
        <値 name="A">1</値>
        <値 name="B">0</値>
        <値 name="C">9</値>
        <値 name="D">9</値>
        <値 name="E">1</値>
        <値 name="F">0</値>
        <値 name="総合">-14</値>
      </評価>
      <動画リンク>
        <a href="https://wod.wowow.co.jp/content/148475" target="new">
          <img src="http://www1.coralnet.or.jp/yonetch/img/play_button_wow.png" alt="シーズン1-1: ストレンジ・ニュー・ワールド" title="シーズン1-1: ストレンジ・ニュー・ワールド" class="t_size" />
        </a>
      </動画リンク>
    </エピ情報>
    <カテゴリ>スター・トレック</カテゴリ>
    <サブカテゴリ>翻訳ツッコミ
        <サブカテゴリ> SNW第1シーズン</サブカテゴリ></サブカテゴリ>
  </記事>
  <記事 日付="" 見出し="" 状態="下書き">
    <エピ情報>
      <シリーズ名>SNW</シリーズ名>
      <シーズン番号>2</シーズン番号>
      <話数>1</話数>
      <t:エピタイ xml:lang="en" is原題="true">THE BROKEN CIRCLE</t:エピタイ>
      <t:エピタイ xml:lang="ja">壊れた環</t:エピタイ>
      <ツッコミファイル名>SNW2.html</ツッコミファイル名>
      <動画リンク>
        <a href="https://wod.wowow.co.jp/content/154648" target="new">
          <img src="http://www1.coralnet.or.jp/yonetch/img/play_button_wow.png" alt="シーズン2-1: 壊れた環" title="シーズン2-1: 壊れた環" class="t_size" />
        </a>
      </動画リンク>
    </エピ情報>
    <カテゴリ>スター・トレック</カテゴリ>
    <サブカテゴリ>翻訳ツッコミ
        <サブカテゴリ> SNW第2シーズン</サブカテゴリ></サブカテゴリ>
  </記事>

【tsukkomi_list.xml(一部)】

これで表の作成時は、改めて検索・生成する必要なく、単にコピーするだけですみ、処理時間の短縮が望める。なお、以前はエピソード情報の有無そのものでツッコミ情報の有無を示していたが、今後は全エピソードの中間ファイルとなるので、「状態」という属性値も追加し「公開/下書き」という値をツッコミ情報の有無として使用する。
●処理上の注意
場当たり的に仕様変更/追加をしているため、他に影響があるかもしれない。今思い出せるだけの仕様をメモしておく。
2024.01.08 Internet Explore 健在
●IEとの別れ
Windows11に移行して、InternetExploreが使えなくなった。C:\Program Files (x86)\Internet Explorer\iexplore.exeを実行してもEdgeが立ち上がる。ワタシはIEをXMLビューワとして使っていたため、なくなると地味に不便。
●IEとの再会
例によって検索の結果、QuiitaにIEの起動法が載っていた。
以下のスクリプトを ie.vbs という名前で保存し、実行する
CreateObject("InternetExplorer.Application").Visible=true
ちなみに、XML Notepad のプレビュー画面にリンクを作ってクリックすると、立ち上がるのはIEである。多分中で同じことをやってるんだろう。
備考: まあ、EdgeのIEモードが全く使えないというわけではないが、やはりIEの方が軽いのと、英単語の意味をちょっと調べたいときに、右クリックでBing翻訳が使えるのが少し便利(なぜかEdgeのIEモードだとBing翻訳は動かない)
2024.01.03 Strawberry Perl
●環境再構築に際して
仮想PCのOSをWin11にして再構築したことは別に述べた。その際、本稿でも言及したPerlによるXMLファイル操作環境についてもゼロから作り直しをした。まず、インストールしたのは、ActivePerlなのだけれども、そこにCPANからXML::LibXMLをインストールしようとしたところ、gccがないとやら云々、どうもすんなりいかない。
●まったく記憶になし
そこで旧環境を再度引っ張り出して確認したところ、なんと入っていたのはStrawberry Perlだったのだ。元々は旧環境でもActivePerlを使っていたはず... いつ入れ替えたのか... やはり LibXML のインストールの際だったかな?とにかく、こちらなら gcc も同梱されているため、CPAN のインストールがよりスムーズにいく。備忘録として記しておく。
備考: ちなみに Strawberry Perl でも留意点がある。最新の 5.38.xx をインストールすると、「日本語をサポートしない」のような警告がでるようになっている。検索の結果、5.28.xx なら出ないという情報があり、そちらを使うことにした。
●xml:idの値について
話は変わるが、そのようにして構築した Perl 環境で LibXML による XMLファイルの操作を行ったところ、ここでもつまずきがあった。それは字幕データとして使っているTTMLについてである。元々は Netflix で使われていた字幕データをダウンロードしたものをパクって自己流TTMLにしているのだが、各セリフのIDである xml:id 属性値を、ワタシはただの数字にしている(SRT形式の通番をそのまま使っているため)。しかし定義上、IDの先頭は文字にしなければならないらしい。XML::LibXML ではこれが原因でエラーではねれられて処理できない。今更すべてを変更する余力はないので、過去のデータについてはあきらめ、今後のものはIDの先頭に文字を持ってくることにした。
備考: 元々のネトフリオリジナルのTTMLでは、当然正しい書式のIDを使っていた。ワタシにとっては単に冗長にしか思えなったのでカットしたのだ。msxsl.exe や XML Notepad では何も問題なかったのに...
2019.05.19 汎用性を求めて
●いろいろ持ってます
スタトレのエピソードDBの運用が軌道に乗り、Netflixや所蔵のDVD等の自分個人としての管理DBをつくろうと思い立った(前回記した Perl によるXMLファイル操作というのはそのための要素技術だった)。
●日本のアニメなんかで...
どう活用するにせよ、まずはデータの登録からってことでエピソードDBを膨らませることに着手して、ハタと手が止まった。今までスタトレ(海外ドラマ)前提のつくりだったため、「原題」「邦題」という項目で登録していたからだ。これが日本のものだったら?そのタイトルは「原題」か「邦題」か?
●おかしいよね
どちらを選んでも違和感がある... ってことで、項目名を変更することにした。
●メディアじゃないよ
この辺りの汎用性の追及というのは、Netflixが出てきたときに円盤に依存しない情報をどう扱うか悩んでいたことにも通じるものがある。すなわち収録メディアが何か(DVDかBDかNetflixか)という情報は、むろん重要ではあるが最早プライオリティは低い。あるディスクの収録内容が何か、ではなく、あるエピソードが収録されているのがどこか、という観点でDBを組み立てることにして今に至っている。つまり
エピソード指向
というヤツである。以前、ツッコミもネトフリリンクも「エピソードの情報」という観点から述べたが、違う観点からもエピソードそのものを中心に据えるべきという結論に至った。設計の是非についてより支持度が上がった気がしてちょっとウレシイ(^^)
2019.06.01追記: アマゾン・プライム・ビデオの配信情報に対応して拡張。同じエピソードが複数の配信元にリンクできる。テンプレート「エピソード」について、タイムシークリンクの際には専らネットフリックスの字幕idと決め打ちしていたが、他メディア由来のものも使えるように変更した。アマゾンへのリンクの際でも、ネトフリやDVDの字幕情報を参照できる。
2019.05.05 Perl で XML
●XML様様
これまで様々なXMLファイルを作成し、HTML化することでサイトコンテンツ管理においての多大な効率化をすすめることができた。XMLはHTMLと誠に親和性がよく、XSLTによって容易に変換できることが大きい。
●活用の道スジ
他方、ワタシの環境ではXSLT変換以外にXMLファイルの利用手段がなく、データとしてのXMLファイルを活用できていなかったことも事実である。そこで今回は、Perl で XML ファイルをパースすることについて調べた。一応の課題として、これまで作成したスター・トレックのエピソード情報XMLファイルを対象にを試みる。ちなみに以下に述べる2つのパーサについて調べた結果先に断っておくことがある。日本語ファイルに関しては(特に要素名にまで日本語を使った場合は尚更)、これらのパーサを使ってXMLへ書き出すことは止めた方がよさそうだということだ(少なくともワタシは挫折し諦めた^^;;)。
●XMLパーサ その1
さて気を取り直して、まずは読み込みから見ていく。Perl用のXMLパーサといえばまずこれ、
XML::Simple
だろう。インストールは CPAN から簡単に行える。
●さて!
早速以下のコードとデータで読み込みをやってみると...
use XML::Simple;
use Data::Dumper;

my $xml = new XML::Simple();
my $data = $xml->XMLin('DB_07_DSC.xml');

print Dumper($data);

【xmlsimle_test1.pl】

<?xml version="1.0" encoding="shift_jis"?>
<t:エピソード集 xmlns:t="urn:tables">
  <t:エピ>
    <t:シリーズ名>DSC</t:シリーズ名>
    <t:シーズン番号>1</t:シーズン番号>
    <t:話数>1</t:話数>
    <t:原題 直訳="">THE VULCAN HELLO</t:原題>
    <t:邦題>バルカンの挨拶</t:邦題>
    <t:収録 メディア="DVD" ディスク番号="1" タイトル番号="1">
      <t:尺 />
    </t:収録>
    <t:収録 メディア="Netflix" タイトル番号="1" URL="https://www.netflix.com/watch/80126025">
      <字幕ファイル>subtitles\yn_DSC1-1.ttml</字幕ファイル>
      <翻訳>大岩剛</翻訳>
      <t:尺>43</t:尺>
    </t:収録>
    <t:ツッコミファイル>tsukkomi\DSC1-1.xml</t:ツッコミファイル>
  </t:エピ>
 (略)
</t:エピソード集>

【DB_07_DSC.xml(エピソード情報ファイル)】

●んん〜?
いきなり以下の様なエラーであえなく玉砕。
C:\Users\Owner\Documents\xmltest\xmlsimple>perl test.pl
Couldn't open encmap shift_jis.enc:
No such file or directory
 at C:/Perl/lib/XML/Parser.pm line 187.
XML::Simple called at test.pl line 4.
'shift_jis.enc' というファイルがないと言っている。が、さんざんググってみたところ、それを配布していたサイトが現在は閉鎖され入手不可。すなわち、入力ファイルを SJIS で記述することが問題の根幹である。
●改良版
その他さらなる紆余曲折もあったが、以下のように改変することで Dumper による標準出力への書き出しができた。
use XML::Simple;
use Encode;

use utf8;
use Data::Dumper;
{
    package Data::Dumper;
    sub qquote { return shift; }
}
$Data::Dumper::Useperl = 1;

binmode STDIN, ':encoding(cp932)';
binmode STDOUT, ':encoding(cp932)';
binmode STDERR, ':encoding(cp932)';

$file = "DB_07_DSC_sjis.xml";

open(IN, $file);
while(<IN>)
{
	s@encoding=\"Shift_JIS\"@encoding=\"UTF-8\"@i;
	$line .= encode "utf-8", decode("shift_jis", $_);
}
close IN;

my $xml = new XML::Simple();
my $data = $xml->XMLin($line);

print Dumper($data);

【xmlsimle_test2.pl】

コードの実行結果及び紆余曲折の中身は割愛し、以下に要点のみ掲げる。
●XML to hash
無事読み込めたところで、次のファイル出力に移る前に個々のデータへのアクセス方法を確認せねばならない。XML::Simple の場合、XMLファイルを読み込んで Perl のハッシュ変数に格納する。後はXMLとは無関係の Perl 内のハナシである...
●知らんがな
って、ええっ?!実はワタシ、ハッシュ変数を扱った経験がないに等しい。しかもこの場合、幾重にも重なった多段ハッシュでもあり、目的のデータに到達することが実に難しい。解説サイトを探そうとしても、Data::Dumper で全出力して終わりという記事ばかりで参考にならない。唯一見つけたのが「 XML::Simpleを使ったXMLデータへのアクセス方法」という記事だが、リファレンスとデリファレンスを組み合わせたゴチャゴチャなコードでワケがわからなかった。
捕捉: 「ゴチャゴチャ」「ワケがわからん」というのは、ハッシュの理解ができていないワタシ個人の責任であり、当該のコードが不出来という意味合いは一切ございません(^^;
●XMLパーサ その2
この時点で XML::Simple を使う気は失せた(^^;)。次なるパーサは
XML::LibXML
である。これもインストールは CPAN から簡単に行える。このパーサのいいところは、取り込んだ後のデータアクセスに XPath が使えることだ。すなわち、いままでXSLTで培ってきたデータ操作手法をそのままPerlに持ち込むことができる(と思われる)。
●ある程度は
XML::Simpleでの経験も生き、参考サイトのコードを引用して以下のようにできた。
参考サイト: PerlのXML::LibXMLモジュールでShift_JISのXMLのパース
use utf8;
use warnings;
use XML::LibXML;
use Encode;

binmode STDIN, ':encoding(cp932)';
binmode STDOUT, ':encoding(cp932)';
binmode STDERR, ':encoding(cp932)';

$file = "DB_07_DSC_sjis.xml";

open(IN, $file);
while(<IN>)
{
	s@encoding=\"Shift_JIS\"@encoding=\"UTF-8\"@i;
	$line .= encode "utf-8", decode("shift_jis", $_);
}
close IN;

my $parser = XML::LibXML->new();
my $dom = $parser->parse_string($line);

my $tags = $dom->findnodes("//t:エピ[t:原題/\@直訳!='']/t:原題");

my @eachLines;
foreach my $tag(@$tags){
  push (@eachLines, $tag->serialize);
}
 
my $joinedTxt = join("\n",@eachLines);
print $joinedTxt . "\n";

【libxml_test.pl】

<t:原題 直訳="臨機応変は王者の特権">CONTEXT IS FOR KINGS</t:原題>
<t:原題 直訳="肉屋のナイフは子羊の悲鳴にも躊躇しない">THE BUTCHER'S KNIFE CARES NOT FOR THE LAMB'S CRY</t:原題>
<t:原題 直訳="汝の苦痛を選べ">CHOOSE YOUR PAIN</t:原題>
<t:原題 直訳="忘却の川">LETHE</t:原題>
<t:原題 直訳="まっとう至極な男を狂わす魔法">MAGIC TO MAKE THE SANEST MAN GO MAD</t:原題>
<t:原題 直訳="汝平和を欲さば、戦への備えをせよ">SI VIS PACEM, PARA BELLUM</t:原題>
<t:原題 直訳="我の行く森の奥深くへ">INTO THE FOREST I GO</t:原題>
<t:原題 直訳="思いがけずして">DESPITE YOURSELF</t:原題>
<t:原題 直訳="内なる狼">THE WOLF INSIDE</t:原題>
<t:原題 直訳="はやり立つ野心">VAULTING AMBITION</t:原題>
<t:原題 直訳="ここからがいよいよ本題だ">WHAT'S PAST IS PROLOGUE</t:原題>
<t:原題 直訳="戦争の内と外で">THE WAR WITHOUT, THE WAR WITHIN</t:原題>
<t:原題 直訳="手を引いてくれる?">WILL YOU TAKE MY HAND?</t:原題>
<t:原題 直訳="兄弟">BROTHER</t:原題>
<t:原題 直訳="光の点">POINT OF LIGHT</t:原題>
<t:原題 直訳="逝く者の手向け">AN OBOL FOR CHARON</t:原題>
<t:原題 直訳="欠陥の聖者">SAINTS OF IMPERFECTION</t:原題>
<t:原題 直訳="雷の音">THE SOUNDS OF THUNDER</t:原題>
<t:原題 直訳="もしも記憶が確かなら">IF MEMORY SERVES</t:原題>
<t:原題 直訳="ダイダロス計画">PROJECT DAEDALUS</t:原題>
<t:原題 直訳="赤い天使">THE RED ANGEL</t:原題>
<t:原題 直訳="絶え間なき永遠">PERPETUAL INFINITY</t:原題>
<t:原題 直訳="影の谷をぬけて">THROUGH THE VALLEY OF SHADOWS</t:原題>

【実行結果(結局XMLジャンというツッコミはなし^^;)】

ここでもポイントだけ挙げておくと(ファイルの文字コードに関する制限は前と同様として)、といったところだろうか。
●書き出し
さて最後はファイル出力である。任意の形式への出力、あるいは多数のファイルに出力する場合に、XSLTでは無理があるので Perl を使用したいワケだ。出力自体は一般的な Perl のハナシなので、ここも要点だけ述べる。
●おわりに
さて何とかカンとか Perl で XMLデータを加工する手法についてある程度の結果を得られたと思う。再度断っておくが、ワタシの Perl の理解度は左程ではなく、ここに述べた内容は自分メモレベルに過ぎない。識者の方によるツッコミは大歓迎である(^^;
2019.03.13 ツッコミ指向からの脱却〜下準備編〜
●さてと、
なんだかんだでエピソード指向化のインプリが場当たり的に進んでいる。いいかげん収拾がつかなくなりそうなので、思い出せるだけメモ。
●エピソードDB改変
エピソードDBに、ツッコミ原稿ファイル情報を追加した
  <t:エピ>
    <t:シリーズ名>DSC</t:シリーズ名>
    <t:シーズン番号>2</t:シーズン番号>
    <t:話数>7</t:話数>
    <t:原題 直訳="">LIGHT AND SHADOWS</t:原題>
    <t:邦題>光と影</t:邦題>
    <t:収録 メディア="Netflix" タイトル番号="7" URL="https://www.netflix.com/watch/80992617">
      <字幕ファイル>subtitles\yn_DSC2-7.ttml</字幕ファイル>
      <t:尺>56</t:尺>
      <翻訳>大岩剛</翻訳>
    </t:収録>
    <t:ツッコミファイル>tsukkomi\DSC2-7.xml</t:ツッコミファイル>
  </t:エピ>
補足: この前提として、シーズン毎にまとまっていたツッコミ原稿をエピソード毎に分割した。そもそも以前に一部は行っていたが、ここへきてそれが奏功しているのがちょっとウレシイ(^^)
●ツッコミリスト生成方法改変
これまでは擬似的に統合した全エピソードツッコミ原稿から、ツッコミの有無だけを抽出したツッコミ情報DBファイル(tsukkomi_list.xml)を生成していた。この過程を以下のように変更した。これにより、これも以前行っていたENTITYによるムリヤリな結合を行う必要はなくなった。
●三種の表
で、ここで「エピソード指向」の件は脇におき、XSLTコーディングで新たに習得したことを書き留めておく。題材は上記の新版ツッコミ情報ファイルを使った評価表の生成についてである。そもそも現状評価表は以下の3種類がある。
  1. 各シリーズ評価表 … シリーズ、シーズン毎にまとめられたもの
  2. 評価ランキング表 … 評価総合点順に並んだもの
  3. 最近の更新リスト … 更新日付順に並んだもの
これらは、並べ方が違うだけで内容は同じである。当然ながら共通のコードで生成できると考えるところだ。しかし当初は(同じコードを流用しつつ)個々に3種類のスタイルシートを作っていた。何かもっとスマートな方法はあるはずと思いながら...
●XSLT: テンプレートのオーバーライド
ワタシなりに理解したところでは、こういう場合はことで動作を変える。
備考: XMLでは「オーバーライド」という言い方はしないのかもしれないが、カッコいいから使ってみた(^^)
●基本とは
評価表における基本形とは一つの表のことだ。ここでは評価ランキング表を基本とした(全エピソードが一つの表にまとまっているから)。ポイントは以下の通り。
<?xml version="1.0" encoding="shift_jis"?> 
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:t="urn:tables" >
  <xsl:output method="html" encoding="Shift_JIS" />
  <xsl:include href="st_eval_base.xsl" />
  <xsl:template match="/">
  <HTML>
    <head>
      <meta http-equiv="Content-Type" content="text/html; charset=shift_jis" />
      <link rel="stylesheet" type="text/css" href="http://www1.coralnet.or.jp/yonetch/hpbsite.css"/>
    </head>
    <BODY>
      <xsl:comment>解析</xsl:comment>
      <H1>翻訳ツッコミ 評価ランキング(ver. 2.0α)</H1>「クソ訳」係数導入、及びTAS対応版
      <P align="right">
        <FONT size="-1"> 
          &lt;<A href="#bottom">BOTTOM</A>&gt;
        </FONT>
        <A name="top"></A>
        <BR/>
      </P>
      <xsl:call-template name="評価表">
        <xsl:with-param name="エピ">
          <xsl:copy-of select="//記事[エピ情報]"/>
        </xsl:with-param>
      </xsl:call-template>
      <BR/>
      <HR/>
      <P align="right">
        <FONT size="-1">
          <A name="bottom">
            &lt;<A href="#top">TOP</A>&gt;
          </A>
        </FONT>
      </P>
    </BODY>
  </HTML>
  </xsl:template>
  <xsl:template name="評価表">
  <xsl:param name="エピ"/>
  <TABLE cellSpacing="0" cellPadding="0" align="center" border="0">
    <TBODY>
      <TR>
        <TD class="td0">
          <TABLE cellSpacing="1" cellPadding="4" align="center" border="0">
            <THEAD>
              <TR>
                <TH align="middle" width="260" rowSpan="2">
                  <B>
                    <span style="letter-spacing:1em;">サブタイトル</span>
                  </B>
                </TH>
                <TH align="middle" colSpan="6">
                  <B>
                    <span style="letter-spacing:3em;">評価</span>
                  </B>
                </TH>
                <TH align="middle" width="50" rowSpan="2">
                  <B>総<BR />合 
                  </B>
                </TH>
              </TR>
              <TR>
                <xsl:for-each select="$台帳//t:評価/t:レベル">
                  <TH align="middle" width="50">
                    <FONT size="-1">
                      <xsl:value-of select="t:値[@name='表現']"/>
                    </FONT>
                  </TH>
                </xsl:for-each>
              </TR>
            </THEAD>
            <TBODY>
              <xsl:call-template name="apply-sort">
                <xsl:with-param name="エピ">
                  <xsl:copy-of select="$エピ"/>
                </xsl:with-param>
              </xsl:call-template>
            </TBODY>
          </TABLE>
        </TD>
      </TR>
    </TBODY>
  </TABLE>
  </xsl:template>

<xsl:template name="apply-sort">
  <xsl:param name="エピ"/>
  <xsl:apply-templates select="msxsl:node-set($エピ)/*">
    <xsl:sort select="エピ情報/評価/値[@name='総合']" data-type="number" order="descending"/>
  </xsl:apply-templates>
</xsl:template>

  <xsl:template match="記事[not(エピ情報)]"/>

  <xsl:template match="記事[エピ情報]">
  <xsl:variable name="更新日MJD">
    <xsl:call-template name="ymd2mjd">
      <xsl:with-param name="ymd">
        <xsl:value-of select="@日付"/>
      </xsl:with-param>
    </xsl:call-template>
  </xsl:variable>
  <tr>
    <xsl:attribute name="class">
      <xsl:if test="(position() mod 2)=1">td1</xsl:if>
      <xsl:if test="(position() mod 2)=0">td2</xsl:if>
    </xsl:attribute>
    <td align="left">
      <xsl:call-template name="エピ情報">
        <xsl:with-param name="en_subtitle">
          <xsl:value-of select="@見出し" />
        </xsl:with-param>
        <xsl:with-param name="option">0x09 </xsl:with-param>
      </xsl:call-template>
      <br/>
      <xsl:value-of select="エピ情報/邦題"/>
      <xsl:call-template name="シリーズ名">
        <xsl:with-param name="series">
          <xsl:value-of select="エピ情報/シリーズ名"/>
        </xsl:with-param>
      </xsl:call-template>
      <xsl:if test="$基準日MJD - $更新日MJD &lt; 365">
        <font size="-1">
          <xsl:attribute name="color">
            <xsl:choose>
              <xsl:when test="$基準日MJD - $更新日MJD &lt;  30">#ff0000</xsl:when>
              <xsl:when test="$基準日MJD - $更新日MJD &lt;  90">#ff7f7f</xsl:when>
              <xsl:when test="$基準日MJD - $更新日MJD &lt; 180">#ff8989</xsl:when>
              <xsl:when test="$基準日MJD - $更新日MJD &lt; 270">#ffc489</xsl:when>
              <xsl:otherwise>#ffcccc</xsl:otherwise>
            </xsl:choose>
          </xsl:attribute>
          <b>
            <i>New!</i>
          </b>
        </font>
      </xsl:if>
    </td>
    <xsl:for-each select="エピ情報/評価/値[@name!='総合']">
      <td>
        <xsl:value-of select="."/>
      </td>
    </xsl:for-each>
    <td>
      <b>
        <xsl:value-of select="エピ情報/評価/値[@name='総合']"/>
      </b>
    </td>
  </tr>
  </xsl:template>

  <xsl:template name="シリーズ名">
  <xsl:param name="series"/>
  <font size="-1">
    [<xsl:value-of select="$series"/>] 
  </font>
  </xsl:template>

</xsl:stylesheet>

【st_eval_rank.xsl】

●流用(インポート)とは
次に別形式の表として更新日付順表を考える。
<?xml version="1.0" encoding="shift_jis"?> 
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:dt="urn:schemas-microsoft-com:datatypes" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:t="urn:tables" >
  <xsl:import href="st_eval_rank.xsl" />
  <xsl:output method="html" encoding="Shift_JIS" />
  <xsl:template match="/">
    <HTML>
      <head>
        <link rel="stylesheet" type="text/css" href="http://www1.coralnet.or.jp/yonetch/hpbsite.css"/>
      </head>
      <BODY>
        <xsl:comment>解析</xsl:comment>
        <H1>翻訳ツッコミ 更新順リスト</H1>
        <P align="right">
          <FONT size="-1">
            &lt;<A href="#bottom">BOTTOM</A>&gt;
          </FONT>
          <A name="top"></A>
          <BR/>
        </P>
        <xsl:call-template name="評価表">
          <xsl:with-param name="エピ">
            <xsl:for-each select="//記事[エピ情報 and @日付!='']">
              <xsl:variable name="更新日MJD">
                <xsl:call-template name="ymd2mjd">
                  <xsl:with-param name="ymd">
                    <xsl:value-of select="@日付"/>
                  </xsl:with-param>
                </xsl:call-template>
              </xsl:variable>
              <xsl:if test="$基準日MJD - $更新日MJD &lt; 270">
                <xsl:copy-of select="."/>
              </xsl:if>
            </xsl:for-each>
          </xsl:with-param>
        </xsl:call-template>
        <BR/>
        <HR/>
        <P align="right">
          <FONT size="-1">
            <A name="bottom">
              &lt;<A href="#top">TOP</A>&gt;
            </A>
          </FONT>
        </P>
      </BODY>
    </HTML>
  </xsl:template>

  <xsl:template name="apply-sort">
    <xsl:param name="エピ"/>
    <xsl:apply-templates select="msxsl:node-set($エピ)/*">
      <xsl:sort select="@日付" order="descending"/>
    </xsl:apply-templates>
  </xsl:template>

</xsl:stylesheet>

【st_eval_moddt.xsl】

断っておくが、上記は抜粋ではなく全ソースである。インポートを使うことで改変部のみの記述ですむため、なんとシンプルで美しいコードであることか!これまでは共通のスタイルシートを使い、コマンドラインオプションを指定してそれぞれ場合分けをするようなことをしていた。通常の手続き型言語の呪縛にどっぷり浸かっていたことがよくわかる。この、
コードで場合分けせず、スタイルシート自体を変えてオーバーライドによって場合分けする
というのが、XSLTらしい書き方なのだろうとようやくわかった気がする(飽くまで自分の勝手な理解に過ぎないが)。
●流用とは(その2)
さらにシリーズ毎評価表を考える。集計表を作成、公表するようになった当初からの(概念的には)基本となる表だが、生成手順としては一番複雑となる。
<?xml version="1.0" encoding="shift_jis"?> 
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:dt="urn:schemas-microsoft-com:datatypes" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:t="urn:tables" >
  <xsl:import href="st_eval_moddt.xsl" />
  <xsl:import href="st_table.xsl"/>
  <xsl:output method="html" encoding="Shift_JIS" />
  <xsl:variable name="記事">
    <xsl:copy-of select="document('tsukkomi_list.xml')/日記/記事[エピ情報]" />
  </xsl:variable>
  <xsl:template match="/">
    <HTML>
      <head>
        <meta http-equiv="Content-Type" content="text/html; charset=shift_jis" />
        <link rel="stylesheet" type="text/css" href="http://www1.coralnet.or.jp/yonetch/hpbsite.css"/>
        <title>スター・トレック エピソードリスト</title>
      </head>
      <BODY>
        <xsl:comment>解析</xsl:comment>
        <H1>翻訳ツッコミこ〜な〜</H1>吹替版の翻訳に対して入れたツッコミを集計しました。以下のような数量化を行っています。閲覧の際の参考にしてください。
        <ul>
          <xsl:apply-templates select="msxsl:node-set($台帳)/t:台帳/t:評価/t:レベル"/>
        </ul>
        <A name="top"></A>
        <P align="right">
          <FONT size="-1">&lt;
            <A href="#bottom">BOTTOM</A>&gt;
          </FONT>
        </P>
        <xsl:apply-templates />
        <A name="bottom"></A>
      </BODY>
    </HTML>
  </xsl:template>
  <xsl:template match="t:略称[@name!='MOV']">
    <xsl:variable name="シリーズ名">
      <xsl:value-of select="@name" />
    </xsl:variable>
    <H2>
      <A>
        <xsl:attribute name="name">
          <xsl:value-of select="@name" />
        </xsl:attribute>
        <xsl:value-of select="concat(t:値[@name='邦題'],' (',@name,')')" />
      </A>
      <xsl:value-of select="concat(' ',' ')" />
      <xsl:for-each select="t:値[@name='シーズン']">
        <font size="-1">[
          <A>
            <xsl:attribute name="href">
              <xsl:value-of select="concat('#',../@name,.)" />
            </xsl:attribute>
            <xsl:call-template name="序数">
              <xsl:with-param name="no">
                <xsl:value-of select="." />
              </xsl:with-param>
            </xsl:call-template>
          </A>]
        </font>
      </xsl:for-each>
    </H2>
    <xsl:for-each select="t:値[@name='シーズン']">
      <xsl:variable name="シーズン番号">
        <xsl:value-of select="."/>
      </xsl:variable>
      <H3>
        <A>
          <xsl:attribute name="name">
            <xsl:value-of select="concat(../@name,.)" />
          </xsl:attribute>
          <xsl:value-of select="concat('第',.,'シーズン')" />
        </A>
      </H3>
      <xsl:call-template name="評価表">
        <xsl:with-param name="エピ">
          <xsl:copy-of select="msxsl:node-set($記事)/記事[エピ情報/シリーズ名=$シリーズ名 and エピ情報/シーズン番号=$シーズン番号]"/>
        </xsl:with-param>
      </xsl:call-template>
      <P align="right">
        <FONT size="-1">
          <A name="bottom">&lt; 
            <A href="#top">TOP</A>&gt;
          </A>
        </FONT>
      </P>
      <HR/>
    </xsl:for-each>
    <xsl:if test="position()!=last()">
      <HR/>
    </xsl:if>
  </xsl:template>
  <xsl:template match="t:略称[@name='MOV']">
    <xsl:variable name="シリーズ名">
      <xsl:value-of select="@name" />
    </xsl:variable>
    <H2>
      <A>
        <xsl:attribute name="name">
          <xsl:value-of select="@name" />
        </xsl:attribute>
        <xsl:value-of select="concat(t:値[@name='邦題'],' (',@name,')')" />
      </A>
      <xsl:value-of select="concat(' ',' ')" />
    </H2>
    <xsl:call-template name="評価表">
      <xsl:with-param name="エピ">
        <xsl:copy-of select="msxsl:node-set($記事)/記事[エピ情報/シリーズ名=$シリーズ名]"/>
      </xsl:with-param>
    </xsl:call-template>
    <P align="right">
      <FONT size="-1">
        <A name="bottom">&lt; 
          <A href="#top">TOP</A>&gt;
        </A>
      </FONT>
    </P>
    <HR/>
    <HR/>
  </xsl:template>
  <xsl:template match="t:シリーズ">
    <xsl:apply-templates>
    <xsl:sort order="ascending" select="t:値[@name='シリーズ番号']"/>
    </xsl:apply-templates>
    <HR/>
  </xsl:template>
  <xsl:template match="t:シリーズ" mode="index">
    <HR/>
    <B>
      <FONT size="+1">
        <xsl:for-each select="t:略称">
          <xsl:sort order="ascending" select="t:値[@name='シリーズ番号']"/>[
          <A>
            <xsl:attribute name="href">
              <xsl:value-of select="concat('#',@name)" />
            </xsl:attribute>
            <xsl:value-of select="@name" />
          </A>] 
        </xsl:for-each>
      </FONT>
    </B>
    <HR/>
  </xsl:template>
  <xsl:template match="t:レベル">
    <LI>
      <B>
        <span style="letter-spacing:1em;">
          <xsl:value-of select="t:値[@name='表現']" />
        </span>(係数: 
        <xsl:value-of select="t:値[@name='係数']" />)
      </B>
      <BR />
      <xsl:value-of select="t:値[@name='説明']" />
    </LI>
  </xsl:template>
  <xsl:template name="シリーズ名">
    <xsl:param name="series"/>
  </xsl:template>
  <xsl:template name="apply-sort">
    <xsl:param name="エピ"/>
    <xsl:apply-templates select="msxsl:node-set($エピ)/*">
    </xsl:apply-templates>
  </xsl:template>
</xsl:stylesheet>

【st_eval_tbl.xsl】

ここでのポイントは、テンプレート「シリーズ名」で、『何もしない』ことである。全シリーズ混在の表ではシリーズ名を付記していたが、シリーズ毎の表でそれは必要ない。だから『何もしない』のだ。この辺りがオーバーライドを駆使した美しい書法だと思う。
●オマケ
おまけと言ってはナンだが一連の評価リストの一種として、サーバサイド集計用のCSVファイルの生成スタイルシートも挙げておこう。以前のバージョンとは異なり、基本的にはtsukkomi_list.xml を CSV形式に変換するだけである。
<?xml version="1.0" encoding="shift_jis"?> 
<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:t="urn:tables" >
  <xsl:output method="text" encoding="Shift_JIS" />
  <xsl:strip-space elements="*"/>
  <xsl:include href="st_eval_base.xsl" />

<xsl:template match="記事[エピ情報 and .//シリーズ名!='MOV']">
  <xsl:variable name="原題">
    <xsl:value-of select="@見出し"/>
  </xsl:variable>
  <xsl:value-of select="concat(
	エピ情報/ツッコミファイル名,',',
	'&quot;',$原題,'&quot;,',
	エピ情報/評価/値[@name='A'],',',
	エピ情報/評価/値[@name='B'],',',
	エピ情報/評価/値[@name='C'],',',
	エピ情報/評価/値[@name='D'],',',
	エピ情報/評価/値[@name='E']+エピ情報/評価/値[@name='F'],','
	)"/>
  <xsl:variable name="更新日MJD">
    <xsl:call-template name="ymd2mjd">
      <xsl:with-param name="ymd">
        <xsl:value-of select="@日付"/>
      </xsl:with-param>
    </xsl:call-template>
  </xsl:variable>
  <xsl:choose>
    <xsl:when test="$基準日MJD - $更新日MJD &lt; 180">1</xsl:when>
    <xsl:otherwise>0</xsl:otherwise>
  </xsl:choose>
  <xsl:text>
</xsl:text>
</xsl:template>

<xsl:template match="記事[not(エピ情報) or .//シリーズ名='MOV']"/>

<xsl:template match="シリーズ名|シーズン番号|話数|邦題|ツッコミファイル名|評価|値|カテゴリ|サブカテゴリ"/>

</xsl:stylesheet>

【st_eval_csv.xsl】

2019.02.21 ツッコミ指向からの脱却
●ツッコミと無関係に
最近ネトフリリンクに馴染み、翻訳ツッコミとは無関係なネトフリリンクをするようになってきた。ネトフリ視聴の可能なシーンへのタイムシークリンクがメインである。その具現化方法はスタトレと同様字幕TTMLのリンクを伴っている。
●無関係なのに?
ワタシの頭の中では、飽くまでツッコミリンクの特殊例という扱いだったので、XML的には”ツッコミ”要素のオプションに「ネトフリリンクだけ」というものを加えていたが... ネトフリリンクをしたいだけなのに”ツッコミ”?という違和感は持っていた。
●エピソード指向への脱却
これからは、”ツッコミ”に付随した各種リンクでなく、”エピソード”に各種リンク(ツッコミリンクも含む)が付随すると考える。今はまだ構想段階だが、XML的構成としても、とすることを考える。さあ、コーディングを検討するべ〜。
2019.02.17 エピソードDB分割
●タートルズ降臨!
ついに「スター・トレック」のみならず、全くの別作品である「ミュータント・タートルズ」についてもエピソードDBに追加することとなった。そこで元になっているXMLファイルだが、構造としては全てフラット。数百件のエピソード情報がただ並んでいる。
捕捉: ただし、飽くまでネトフリリンク目的の裏方扱いであり、公開版のエピソードリストには掲載しない。
●分割必至
このファイル、XML Notepad での編集時その反応の重さに辟易とするほどになっている。DSCシーズン2の追加は元より、そこへ「タートルズ」も登録することとすると、もう堪忍袋がナントヤラ。せめてシリーズごとにファイル分割することにした。
<?xml version="1.0" encoding="shift_jis"?>
<t:エピソード集 シリーズ名="DSC" xmlns:t="urn:tables">
  <t:エピ>
    <t:シリーズ名>DSC</t:シリーズ名>
    <t:シーズン番号>1</t:シーズン番号>
    <t:話数>1</t:話数>
    <t:原題 直訳="">THE VULCAN HELLO</t:原題>
    <t:邦題>バルカンの挨拶</t:邦題>
    <t:収録 メディア="DVD" ディスク番号="1" タイトル番号="1">
      <t:尺 />
    </t:収録>
    <t:収録 メディア="Netflix" タイトル番号="1" URL="https://www.netflix.com/watch/80126025">
      <字幕ファイル>subtitles\yn_DSC1-1.ttml</字幕ファイル>
      <翻訳>大岩剛</翻訳>
      <t:尺>43</t:尺>
    </t:収録>
  </t:エピ>
  <t:エピ>
    <t:シリーズ名>DSC</t:シリーズ名>
    <t:シーズン番号>1</t:シーズン番号>
    <t:話数>2</t:話数>
    <t:原題 直訳="">BATTLE AT THE BINARY STARS</t:原題>
    <t:邦題>連星系の戦い</t:邦題>
    <t:収録 メディア="DVD" ディスク番号="1" タイトル番号="2">
      <t:尺 />
    </t:収録>
    <t:収録 メディア="Netflix" タイトル番号="2" URL="https://www.netflix.com/watch/80194752">
      <字幕ファイル>subtitles\yn_DSC1-2.ttml</字幕ファイル>
      <翻訳>大岩剛</翻訳>
      <t:尺>39</t:尺>
    </t:収録>
  </t:エピ>
(略)
</t:エピソード集>

【DB_06_DSC.xml】

<?xml version="1.0" encoding="shift_jis"?>
<t:エピソード集 xmlns:t="urn:tables" t:シリーズ名="TMNT">
  <t:エピ>
    <t:シーズン番号>1</t:シーズン番号>
    <t:話数>1</t:話数>
    <t:原題>RISE OF THE TURTLES (PART 1)</t:原題>
    <t:邦題>タートルズ参上!(前編)</t:邦題>
    <t:収録 メディア="Netflix" タイトル番号="1" URL="https://www.netflix.com/watch/80080493">
      <字幕ファイル>subtitles\yn_TMNT1-1.ttml</字幕ファイル>
      <尺>23</尺>
    </t:収録>
  </t:エピ>
  <t:エピ>
    <t:シーズン番号>1</t:シーズン番号>
    <t:話数>2</t:話数>
    <t:原題>RISE OF THE TURTLES (PART 2)</t:原題>
    <t:邦題>タートルズ参上!(後編)</t:邦題>
    <t:収録 メディア="Netflix" タイトル番号="2" URL="https://www.netflix.com/watch/80080494">
      <字幕ファイル>subtitles\yn_TMNT1-2.ttml</字幕ファイル>
      <尺>23</尺>
    </t:収録>
  </t:エピ>
(略)
</t:エピソード集>

【DB_20_TMNT.xml】

捕捉: ちなみにDBファイルには、エピソード情報の他に評価係数とシリーズ情報も含まれる。これも別ファイルに分割した。
●再結合
と、ここまではアタリマエ。問題はここからである。編集時には分割されていた方が都合がいいが、XSLTで処理する際には結合されていた方がいい。そもそも分割版に対応させようとすると既存のスタイルシート (XSL) の改変が必要となる。これはメンドウなので、当面は分割DBから結合DBに変換して従来通り使用することとした。そのためのXSLファイルが以下の通り。
<?xml version="1.0" encoding="shift_jis"?> 
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:t="urn:tables" >
  <xsl:output method="xml" encoding="Shift_JIS" />

  <xsl:template match="t:台帳"/>

  <xsl:template match="/">
  <xsl:processing-instruction name="xml-stylesheet">
    <xsl:text>type="text/xsl" href="st_table.xsl"</xsl:text>
  </xsl:processing-instruction>
  <t:台帳>
    <xsl:copy-of select="document('DB_99_OTHERS.xml')/t:台帳/t:評価"/>
    <xsl:copy-of select="document('DB_99_OTHERS.xml')/t:台帳/t:シリーズ"/>
    <t:エピソード集>
      <xsl:copy-of select="document('DB_00_MOV.xml')//t:エピ"/>
      <xsl:copy-of select="document('DB_01_TOS.xml')//t:エピ"/>
      <xsl:copy-of select="document('DB_02_TAS.xml')//t:エピ"/>
      <xsl:copy-of select="document('DB_03_TNG.xml')//t:エピ"/>
      <xsl:copy-of select="document('DB_04_DS9.xml')//t:エピ"/>
      <xsl:copy-of select="document('DB_05_VGR.xml')//t:エピ"/>
      <xsl:copy-of select="document('DB_06_ENT.xml')//t:エピ"/>
      <xsl:copy-of select="document('DB_07_DSC.xml')//t:エピ"/>
      <xsl:copy-of select="document('DB_20_TMNT.xml')//t:エピ"/>
    </t:エピソード集>
  </t:台帳>
  </xsl:template>

</xsl:stylesheet>

【mergedb.xsl】

んで、これを以下のようにつかって、元の結合DB化する(全てのデータとなるXMLファイルをXSL内で読み込んでいるので、実行時に指定するXMLファイルは単なるダミーである)。
msxsl -u 4.0 DB_99_OTHERS.xml mergedb.xsl -o ST_def.xml
捕捉: ここまでの説明、かなり端折っている。これをまともに使うための前提は他にいろいろあるが、単なる自分メモとしてはこれで十分としておく(「ソース読め!」>自分)
2018.12.23 agent属性の廃止(yonetch版TTMLについて)
●いつのころからか
いささか唐突ながら、字幕ファイルのTTMLの扱いを変更した(している)ので備忘録として記す。具体的には、掲題のとおり各台詞の役名情報をTTML内で持たせるために当初導入した、
agent属性を廃止
することに決めた。本稿で以前導入について述べているが、これをやめるということである。
●なぜならば
本来の構成から言えば、台詞をしゃべる役名の情報は引用先(ツッコミ原稿)側でなく、引用元である字幕ファイルにあるべきだと今でも思ってはいる。だが現状の編集環境では、翻訳ツッコミ原稿作成時にツッコミ原稿と字幕ファイルの二つを整合性をとりながら編集していくのは作業効率を下げるだけだと気付いた。
●枯れ木も山の...
ただし、既に入ってしまっている既存TTMLからわざわざ取り除くことはしないし、今後も元TTMLから変換する際に本文に記述されている役名をagent属性に取り込むことは止めない。
●余談
本文内の役名の件で、以前以下のことを気にしていた。
DSCの場合、役名が本文に出た際、[Burnham]のように角カッコで囲まれるが、他にも [growls] (うなる)などのように、状況説明の場合にも角カッコで囲まれる。役名なのか状況説明なのか、これを認識させるにはどうしたらよいのか。
これを、暫定的に以下のアルゴリズムで対処することにした。
2018.11.07 XML Notepad 対策
●気に入ってはいるんだが
このサイトの制作環境として使ってきた XML Notepad だが、(フリーソフトとしては)かなり優秀なXMLエディタだと思う。だがしかし、かれこれ1年以上使い込んでみると、少々不満がないわけではない。
●クソ重い!
その中でもどうしてもガマンできないものがある。機能上の不備とか、ワタシの要求が特殊すぎるとかいうことなら仕方がないが、そうではない。それというのも、
重いXMLファイルを読み込むと、最初に編集するときだけ、10分以上もの無応答が起こる
のだ。この「編集」とは、何かテキスト入力するとか、要素等を追加するとかの、ごく普通のことである。そして、その無応答タイムをやりすごすと、その後は何の問題もなく編集ができるからワケがわからない(ちなみに無応答の間、CPU使用率は100%で張り付くので何らかの前処理的なことをしているのだろうが...)。
備考: 翻訳ツッコミで劇場版を採りあげたとき、3000余ある字幕台詞を全て事前変換しておくと重くて使い物にならないと前に述べたが、主な現象はコレだった。
備考: 本来ならXML Notepadの方での問題として何か対処法を探したいところだが、サポートサイトを覗いても作者からの返信が絶えて久しいようだし、如何ともしがたい
●省略形
今回この現状に対応すべく、ツッコミ原稿形式のマイナーチェンジを検討した。とにかく無駄な情報を減らし、絶対量を圧縮することを考える。日頃冗長と考えていたのは、ある台詞を構成する字幕情報を記述する際に、個々のIDをすべて列挙していることだ(以下参照)。
備考: 値のない<字幕id>要素は、[omit]を意味する
<原語> <字幕id>subtitle1</字幕id> <字幕id>subtitle2</字幕id> <字幕id>subtitle3</字幕id> <字幕id></字幕id> <字幕id>subtitle5</字幕id> <字幕id>subtitle6</字幕id> <字幕id>subtitle7</字幕id> <字幕id>subtitle8</字幕id> <字幕id>subtitle9</字幕id> <字幕id>subtitle10</字幕id> </原語>

【従来形式】

だが台詞引用においては、字幕IDは降順に連続するものがほとんどである。そこで以下のように連続する数を「続」という属性値に入れておくものとした。
<原語> <字幕id 続="2">subtitle1</字幕id> <字幕id></字幕id> <字幕id 続="5">subtitle5</字幕id> </原語>

【新形式】

●インプリメンテーション
さてそれを具現化するアルゴリズムだが、普通の手続き型原語ならば For ループの類で処理すればいいだけの単純なことだ。だが!XSLTの場合は再起的にやるしかない。メンドクサ〜... と嘆いても始まらないので頑張って書いてみたコードが以下のとおり(抜粋)。

<xsl:template match="字幕id">
<xsl:value-of select="concat(msxsl:node-set($字幕)//tt:p[@xml:id=$id],' ')"/> <xsl:value-of select="@続"/> <xsl:if test="@続 and @続 &gt; 0"> <xsl:call-template name="loop"> <xsl:with-param name="i" select="1"/> <xsl:with-param name="i_limit" select="@続"/> <xsl:with-param name="字幕" select="$字幕"/> <xsl:with-param name="id" select="$id"/> </xsl:call-template> </xsl:if> </xsl:template> <xsl:template name="loop"> <xsl:param name="i"/> <xsl:param name="i_limit"/> <xsl:param name="i_inc">1</xsl:param> <xsl:param name="字幕"/> <xsl:param name="id"/> <xsl:if test="$i &lt;= $i_limit"> <xsl:value-of select="concat(' ',msxsl:node-set($字幕)//tt:p[@xml:id=$id]/following-sibling::*[$i])"/> <xsl:variable name="i1" select="$i + $i_inc"/> <xsl:call-template name="loop"> <xsl:with-param name="i" select="$i1"/> <xsl:with-param name="i_limit" select="$i_limit"/> <xsl:with-param name="i_inc" select="$i_inc"/> <xsl:with-param name="字幕" select="msxsl:node-set($字幕)"/> <xsl:with-param name="id" select="$id"/> </xsl:call-template> </xsl:if>
●既存ファイルにも
次はこの形式に対応したXMLファイルを生成するスタイルシートが必要だ。TTMLから直接このように変換する(連続する字幕idを数える)ことに少し挑戦したが、挫折したorz。経緯は省くが、一旦従来形式で作ったXMLファイルに対して、再度XSLTで変換をかける方がラクだと思い、そのようにした。
<?xml version="1.0" encoding="Shift_JIS" ?> 
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
  <xsl:output method="xml" encoding="Shift_JIS"/>
  <xsl:include href="..\..\xml\commonlib.xsl" />
  <xsl:template match="エピ">
    <xsl:element name="エピ">
      <xsl:attribute name="状態">下書き</xsl:attribute>
      <xsl:apply-templates/>
    </xsl:element>
  </xsl:template>
  <xsl:template match="台詞">
    <xsl:element name="台詞">
      <xsl:attribute name="状態">
        <xsl:value-of select="@状態"/>
      </xsl:attribute>
      <xsl:attribute name="人物">
        <xsl:value-of select="@人物"/>
      </xsl:attribute>
      <xsl:apply-templates/>
    </xsl:element>
  </xsl:template>

  <xsl:template match="原語[字幕id!='']">
    <xsl:element name="原語">
      <xsl:element name="字幕id">
        <xsl:attribute name="続">
          <xsl:if test="count(字幕id) &gt; 1">
            <xsl:value-of select="count(字幕id)-1"/>
          </xsl:if>
        </xsl:attribute>
        <xsl:value-of select="字幕id[1]"/>
      </xsl:element>
    </xsl:element>
  </xsl:template>
  <xsl:template match="原語[not(字幕id) or 字幕id='']">
    <xsl:copy-of select="."/>
  </xsl:template>
  <xsl:template match="吹替|直訳|所感|余談|原題|更新日|字幕ソース">
    <xsl:copy-of select="."/>
  </xsl:template>
</xsl:stylesheet>
●他にもあるよ
一応できた... この方法で圧縮したところ、300KBのXMLファイルが200KBぐらいにはなった。大して変化がないようにも思うが、無応答タイムもほぼなくなったので、とりあえずの目的は達したとしておこう。ただし!XML Notepad にはなんとかしてほしいところがまだある。自分メモ的に記しておく。
備考: ちなみに今は XML Notepadのプレビュータブは使わず、IEをプレビュー用に使っている。これならAlt+Tabで切り替え、[F5]で表示することができるので多少はマシ。
2018.08.16 ついにトップが!
●更新日記とともに
RSS生成に成功して以来、更新日記とともに自動生成がかかり、ずいぶんコンテンツ更新作業が楽になった。にもかかわらず、当初目標の最後のひとつ、
トップページの What's New (直近の更新3項目のリスト)
だけは手作業での更新を続けていた。
●まだメドが...
それというのも、「直近の更新3項目」のみを拾うのが案外難しいからだ。機械的に3項目だけなら左程でもないが、一度に翻訳ツッコミ3件を更新したら?それだけがトップページに並んでいてもツマラナイではないか。これはまとめて1件の更新とし、他の更新情報をできるだけ表示するなどしたいものだ。
●考えすぎか?
などなど、今まで手作業で行ったような内容を自動化しようとゴチャゴチャ考えていると、例によってそれをインプリするにはまだまだスキルの不足をかんじる... が!ちょっと角度を変えて考えてみると、自動化をゼロイチで諦めるのではなく、手作業の前段階と捉えるべきと気づいた。すなわち、せめて日を変えた更新3日分ぐらいを自動抽出し、あとは手作業で直すようにする。
●できた!のだが...
ということで、更新情報の表示他、トップページHTMLの生成はできたのだが、アクセスカウンターのCGI部分でつまずいたorz。そもそもサイト開設当初の2004年当時から以下の形式で置いてある。
<img src="/cgi-bin/Count.cgi&df=yonetch.dat&dd=C&ft=0" alt="***" align=absmiddle>
... ちょっとコレ、XHTML的見地からすると、酷すぎだろっ!
●ひでぇ...
何が問題って、
  1. 閉じタグがない
  2. リテラルを"(ダブルクォート)で囲ってない
  3. &(アンパサンド)がそのまま
とまぁ、こんな短いタグに3個も問題が... まぁ、1.、2.はまだいい。ないなら付けりゃいいだけだ。しかし3.が問題ありすぎ!XHTML的には & は &amp;としなければならない。だがこれはCGIのパラメータのセパレータなワケで、どうすりゃいいのか皆目見当もつかなかった。
●そっちが本物?
単純に考えると&amp;と書き換えるしかないのだけど、ソレがサーバに渡って動くわけないし... と思ってググってみると、本当にそれでいい(というか、本来そうあるべき)らしい。
参考: ばっちり書けてますかhref= – 特に&(&amp;)などを含む場合 – 属性値としてのCDATA
ただ、Coralnetのサーバでは、そうはなっておらず、XHTML的に
<img src="/cgi-bin/Count.cgi&amp;df=yonetch.dat&amp;dd=C&amp;ft=0" alt="***" align="absmiddle"/>
とやるとカウンターは表示されない。それどころか、リテラルを"でかこっただけでもダメだったのだからあきれる(^^;
●また小手先で...
しかたがないので、カウンター設置部分にコメントで特定の文字列を入れておき、それを最後にPerlで文字列置換することでしのぐことにした。
備考: ちなみに、FC2のアクセス解析タグも同じく & の問題があったのだが、こちらは &amp; を受け入れるようだ。
2018.08.15 スタックオーバーフロー
●SRTソース再び
DS9ツッコミの過程で、ネトフリ字幕でなくDVD字幕をソースとして使うことにした。SRT形式のDVD字幕をTTML形式に変換する(これは去年既出のスクリプトを使えばOK)が、これでつくったTTMLから、ツッコミ原稿形式への変換でハマった。台詞がどれひとつ拾われず、エピの外枠のみ吐いて終了、だった。行末を判定している正規表現で、対象に改行(\n?の部分)が入っているかどうかでマッチ/アンマッチが変わってしまっていた(少々納得いかないが)。
  <xsl:if test="yn:contains(string(.),'[^\.][\)\]\?\.!]&quot;?\n?$')">

【行末判定部】

●システムエラー
ツッコミ原稿形式への変換でもうひとつ重大な問題が。これは前々からのことではあったのだけれど、対象TTMLによって、以下のシステムエラーが出るものがあるのだ。
C:\>msxsl -u 4.0 yn_DS9_3-12.ttml ttml2tsukkomi.xsl

Error occurred while executing stylesheet 'ttml2tsukkomi.xsl'.

Code:   0x80004005
システム エラー: -2146828260

プロパティまたはメソッド 'contains' の呼び出し中にエラーが発生しました。
'contains'の呼び出し中に、とあるからには、正規表現対応させたJScript版のyn:containsが原因... なのか?
●スタックオーバーフロー?
このシステムエラーについて、散々ググってみたが、類例は見つからなかった。エラーがでたりでなかったりというのは本当に頭を抱える。テキスト内の特定のパターンとか特殊文字とか何が悪いのか散々探したが徒労に終わった。そこでふと思い当たったのは、「兄弟要素のグループ化」ルーチンのポイントが、
再起呼び出し
だということだ。上記のコードの意味はよくわからないが、ひょっとしてスタックオーバーフローなのではないか?つまり、JScriptの関数そのものが原因なのではなく、スタック領域を食い尽くしているだけなのかもしれない。
●判定法変更
そこで、TTML自身に行末であるか否かの情報を埋め込むことにした。
<p xml:id="172" term="">
First Officer's Log,
supplemental.
</p>
<p xml:id="173" term="">
Somehow, Sisko, Dax and Bashir
have altered Earth's history.
</p>
<p xml:id="174">
We have no choice but to send
an away team into the past
</p>
<p xml:id="175" term="">
to try to find them and correct
the changes to the timeline.
</p>

【ツッコミ形式変換用TTML(DS9"PAST TENSE, PART II"より)】

変更前: <xsl:if test="yn:contains(string(.),'[^\.][\)\]\?\.!]&quot;?\n?$')">
変更後: <xsl:if test="@term">

【ttml2tsukkomi.xsl 判定部変更内容】

●すんなり
ちょっとゴチャゴチャしてしまったが、今回のミソは、本物の(?)TTMLのほかに、ツッコミ形式への変換を前提とした中間ファイル的なTTMLファイルを作ることにある。
  1. オリジナルTTML or SRT → yonetch版TTML & 中間形式TTML
  2. 中間形式TTML → ツッコミ形式ファイル(←yonetch版TTMLを参照する)
以上の手順で変換を行うとごくすんなり成功した。結果的に、スタックを消費するような部分では、JScriptは使うべきでない、というところだろうか(根拠に乏しいが)。
2018.08.04 yonetch版TTMLモドキ生成、再び
●時系列メモ更新に当たって
前回本稿で述べたとおり、TTMLをツッコミ原稿形式に変換することで、作業効率アップを図ることができた。その結果新エピのネトフリTTMLをyonetch版TTMLに変換するという作業も増えた。現状の対象はDS9、VGRで、それらのネトフリTTMLは台詞行ごとに<p>タグに分割されており、(DSCと比べて)変換のしやすい単純な構成となっていた。
<p begin="15419992503t" end="15438760002t" region="region_00" tts:extent="40.00% 5.33%" tts:origin="10.00% 79.29%" xml:id="subtitle722" agent="">What do you think</p>
<p begin="15419992503t" end="15438760002t" region="region_01" tts:extent="47.50% 5.33%" tts:origin="10.00% 84.62%" xml:id="subtitle723" agent="">you're doing, Quark?</p>
<p begin="15467955001t" end="15489645003t" region="region_00" tts:extent="27.50% 5.33%" tts:origin="47.50% 79.29%" xml:id="subtitle727" agent="">Oh, you mean</p>
<p begin="15467955001t" end="15489645003t" region="region_01" tts:extent="40.00% 5.33%" tts:origin="47.50% 84.62%" xml:id="subtitle728" agent="">this holo-imager.</p>
<p begin="15489645003t" end="15506325000t" region="region_00" tts:extent="47.50% 5.33%" tts:origin="27.50% 79.29%" xml:id="subtitle729" agent="">I was just recording</p>
<p begin="15489645003t" end="15506325000t" region="region_01" tts:extent="60.00% 5.33%" tts:origin="27.50% 84.62%" xml:id="subtitle730" agent="">an image of the Promenade</p>
<p begin="15506325000t" end="15529685003t" region="region_00" tts:extent="27.50% 5.33%" tts:origin="57.50% 68.63%" xml:id="subtitle731" agent="">to send home</p>
<p begin="15506325000t" end="15529685003t" region="region_01" tts:extent="30.00% 5.33%" tts:origin="57.50% 73.96%" xml:id="subtitle732" agent="">to my mother.</p>

【ネトフリTTML(DS9"MERIDIAN"より)】

●アチャー
しかし、DS9"FASCINATION"シーズン3-10: 恋の感謝祭は違っていた。一画面上の台詞2行ごとに<p>タグ化され、中に<br/>、すなわち改行が入っている。さらには、2人の台詞が一画面に1行ずつ表示されるパターンもあった。
<p begin="7680589582t" end="7698524166t" region="region.after" style="defaultStyle" xml:id="subtitle210">If you need to sleep, go ahead.</p>
<p begin="7699358332t" end="7719378332t" region="region.after" style="defaultStyle" xml:id="subtitle211">-I'll understand.<br/>-No, you won't.</p>
<p begin="7720212499t" end="7731473749t" region="region.after" style="defaultStyle" xml:id="subtitle212">You'll be disappointed</p>
<p begin="7732307916t" end="7764840416t" region="region.after" style="defaultStyle" xml:id="subtitle213">and you'll start brooding<br/>and stomping around like an Andorian bull.</p>

【ネトフリTTML(DS9"FASCINATION"より)】

●ハラくくって
これはDSCとよく似たパターンだ(しかし、DSCとも若干異なっているが)。最近のツッコミ対象がDS9だったから目を逸らしていたが、仕方がないのでDSCタイプも含めてXSLTによるTTMLモドキ作成の xsl テンプレート作成にとりかかった。以前は Perl で変換スクリプトを書いた(が、多分にハンパなものだった)。その理由は、前の記事でも書いたが、台詞分割の部分を XSLT でやるだけの知識がなかったからだ。
●満を持して
だがあれから半年、アレやコレやでスタイルシートを書いてきた知識を総動員して、何とか形にすることができた(まだまだ完全ではないが)。
●御開帳〜
そういうワケで、できたのが以下のスタイルシートです。まだ対応しきれていない点として、ということがあります。
<?xml version="1.0" encoding="shift_jis"?> 
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:tt="http://www.w3.org/ns/ttml" xmlns:ttm="http://www.w3.org/ns/ttml#metadata" xmlns:ttp="http://www.w3.org/ns/ttml#parameter" xmlns:tts="http://www.w3.org/ns/ttml#styling" xmlns="http://www.w3.org/ns/ttml" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:yn="urn:yonetch">
  <xsl:output method="xml" encoding="Shift_JIS"/>

  <!-- 07-29版 台詞内に改行が入っている場合に対応 -->
  <!-- 08-02版 DSC式に [Burnham] のように役名が入っている場合の agent化に対応 -->
  <!-- 08-04版 DS9式に SISCO: のように役名が入っている場合の agent化にに対応 -->

  <xsl:include href="..\mysite\xml\commonlib.xsl" />
  <xsl:key name="スタイル情報" match="tt:style" use="@tts:fontStyle"/>
  <xsl:variable name="ItaSty">
    <xsl:value-of select="key('スタイル情報','italic')/@xml:id"/>
  </xsl:variable>

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

  <xsl:template match="tt:head|tt:styling|tt:layout|ttp:profile|tt:region">
    <xsl:copy>
    <xsl:for-each select="@*">
      <xsl:copy />
    </xsl:for-each>
    <xsl:apply-templates />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="tt:style[@tts:fontStyle='italic']">
    <xsl:copy>
    <xsl:for-each select="@*">
      <xsl:choose>
        <xsl:when test="name()='xml:id'">
          <xsl:attribute name="xml:id">italic
          </xsl:attribute>
        </xsl:when>
        <xsl:otherwise>
          <xsl:copy />
        </xsl:otherwise>
      </xsl:choose>
    </xsl:for-each>
    <xsl:apply-templates />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="tt:style[not(@tts:fontStyle)]">
    <xsl:copy>
    <xsl:for-each select="@*">
      <xsl:copy />
    </xsl:for-each>
    <xsl:apply-templates />
    </xsl:copy>
  </xsl:template>
  <xsl:template match="tt:body">
    <body>
      <xsl:apply-templates/>
    </body>
  </xsl:template>

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

  <xsl:template match="tt:p[not(tt:br)]">
    <xsl:copy>
    <xsl:for-each select="@*">
      <xsl:copy />
    </xsl:for-each>
          <xsl:attribute name="agent">
	<xsl:value-of select="yn:replace(string(.),'\[([^\]]+)\].+','$1')"/>
	<xsl:value-of select="yn:replace(string(.),'([^:]+):.+','$1')"/>
	<!-- DSC式とDS9式が同時に使われることはないという前提で併記 -->
          </xsl:attribute>
    <xsl:apply-templates/>
    </xsl:copy>
  </xsl:template>

  <xsl:template match="tt:p/text()">
	<xsl:value-of select="yn:replace(string(.),'([^:]+:)?(.+)','$2')"/>
	<!-- DS9式の地の文から役名を削除 -->
	<!-- 役名以外の目的で:(コロン)が使われていたら誤動作するだろう -->
  </xsl:template>

  <xsl:template match="tt:p[tt:br]">
    <xsl:apply-templates select="tt:br"/>
	<!-- br要素のあるp要素について、br要素のみ評価 -->
  </xsl:template>

  <xsl:template match="tt:br">
    <xsl:choose>
      <xsl:when test="yn:contains(string(preceding-sibling::node()[last()]),'^\-')">
      <!-- 行頭が '-' で始まっていたら(正規表現 ^\-)-->
      <!-- 2つの台詞に分割 -->
        <p>
          <xsl:for-each select="../@*">
            <xsl:copy/>
          </xsl:for-each>
          <xsl:attribute name="agent">
	<xsl:value-of select="yn:replace(string(preceding-sibling::node()[last()]),'-\[([^\]]+)\].+','$1')"/>
          </xsl:attribute>
          <xsl:for-each select="preceding-sibling::node()">
          <!-- brより前の、ノードかテキストである兄弟要素についてループ -->	
            <xsl:apply-templates select="."/>
          </xsl:for-each>
        </p>
        <xsl:text>
</xsl:text>
        <p>
          <xsl:for-each select="../@*">
            <xsl:if test="not(contains(.,'xml:id'))">
              <xsl:copy />
            </xsl:if>
          </xsl:for-each>
          <xsl:attribute name="xml:id">
          <xsl:value-of select="concat(../@xml:id,'-2')"/>
          <!-- 台詞idをコピーして接尾詞'-2'をつける -->
          </xsl:attribute>
          <xsl:attribute name="agent">
	<xsl:value-of select="yn:replace(string(following-sibling::node()[1]),'-\[([^\]]+)\].+','$1')"/>
          </xsl:attribute>
          <xsl:for-each select="following-sibling::node()">
          <!-- brより後の、ノードかテキストである兄弟要素についてループ -->
            <xsl:apply-templates select="."/>
          </xsl:for-each>
        </p>
          <xsl:attribute name="agent">
	<xsl:value-of select="yn:replace(string(following-sibling::node()[1]),'-\[([^\]]+)\].+','$1')"/>
          </xsl:attribute>
      </xsl:when>
      <xsl:otherwise>
      <!-- それ以外(行頭が '-' で始まっていなかったら -->
      <!-- brノード以外をコピー(改行を削除) -->
        <p>
          <xsl:for-each select="../@*">
            <xsl:copy/>
          </xsl:for-each>
          <xsl:attribute name="agent">
	<xsl:value-of select="yn:replace(string(preceding-sibling::node()[1]),'\[([^\]]+)\].+','$1')"/>
          </xsl:attribute>
          <xsl:for-each select="preceding-sibling::node()">
            <xsl:apply-templates select="."/>
          </xsl:for-each>
          <xsl:text>
</xsl:text>
          <xsl:for-each select="following-sibling::node()">
            <xsl:apply-templates select="."/>
          </xsl:for-each>
        </p>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template match="tt:span">
    <span>
      <xsl:choose>
        <xsl:when test="@style=$ItaSty">
          <xsl:attribute name="style">italic
          </xsl:attribute>
        </xsl:when>
      </xsl:choose>
      <xsl:value-of select="yn:replace(string(.),'(-)?(\[[^\]]+\]\s)?([^\[\]]*)','$1$3')"/>
	<!-- DSC式のspanタグ内から役名を削除 -->
    </span>
  </xsl:template>

</xsl:stylesheet>

【make_ynttml.xsl】

2018.07.14 字幕TTMLの翻訳ツッコミ化
●ツッコミ手法の変更
スタトレ Personal Log の方で概要は述べましたが、掲題のとおり、字幕TTMLを一括して翻訳ツッコミ原稿形式に変換する手法に着手しました。たたき台として全台詞をツッコミ形式にしておき、そこに実際のツッコミを追記したり、対象外のものを削除したりするという趣旨です。
●アレが使える
アルゴリズム的には、前にとりあげた
兄弟要素をグループ化する
手法を使います。ひとことでいうと、
全要素を順番に処理して、終端要素が出たら、そこまでをグループとする
となります。フラットな字幕データを台詞(文章)としてグループ化するための終端要素は、当然ピリオド、クエスチョンマーク等です。台詞文字列の中にそれらの終端文字があるかどうかを contain() 関数でチェックします。
補足: ただし、複数の文字をひとつずつチェックするのは冗長なので、正規表現に対応した yn:contain() 関数を作りました。それについてはまた別の機会にまとめたい思います。また、このテンプレートは、DS9のように全てを<p>要素で記述しているものにしか適用できません(DSCは<p>の下に<span>を使っているのでもうひとひねり必要)。
<?xml version="1.0" encoding="Shift_JIS" ?> 
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:tt="http://www.w3.org/ns/ttml" xmlns:ttm="http://www.w3.org/ns/ttml#metadata" xmlns:ttp="http://www.w3.org/ns/ttml#parameter" xmlns:tts="http://www.w3.org/ns/ttml#styling" xmlns:yn="urn:yonetch" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl" >

  <xsl:output method="xml" encoding="Shift_JIS"/>
  <xsl:include href="..\..\..\xml\commonlib.xsl" />

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

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

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

  <xsl:template match="tt:div">
    <xsl:element name="エピ">
    <xsl:attribute name="状態">下書き
    </xsl:attribute>
    <xsl:element name="原題"/>
    <xsl:element name="更新日"/>
    <xsl:element name="字幕ソース">Netflix
    </xsl:element>
    <xsl:text></xsl:text>
    <xsl:for-each select="tt:p">
      <xsl:if test="yn:contains(string(.),'[^\.][\)\]\?\.!]&quot;?$')">
        <xsl:element name="台詞">
        <xsl:attribute name="状態">下書き
        </xsl:attribute>
        <xsl:attribute name="人物">
        </xsl:attribute>
        <xsl:element name="原語">
        <xsl:text></xsl:text>
        <xsl:if test="preceding-sibling::*[1]">
          <xsl:call-template name="func">
            <xsl:with-param name="node" select="preceding-sibling::*[1]" />
          </xsl:call-template>
          <字幕id>
            <xsl:value-of select="@xml:id" />
          </字幕id>
          <xsl:text></xsl:text>
        </xsl:if>
        </xsl:element>
        <xsl:text></xsl:text>
        <xsl:element name="吹替">
        <xsl:attribute name="評価"/>
        </xsl:element>
        <xsl:text></xsl:text>
        <xsl:element name="直訳"/>
        <xsl:text></xsl:text>
        <xsl:element name="所感"/>
        <xsl:text></xsl:text>
        </xsl:element>
        <xsl:text></xsl:text>
      </xsl:if>
    </xsl:for-each>
    </xsl:element>
  </xsl:template>

  <xsl:template name="func">
    <xsl:param name="node" />
    <xsl:if test="not(yn:contains(string($node/.),'[^\.][\)\]\?\.!]&quot;?$'))">
      <xsl:call-template name="func">
        <xsl:with-param name="node" select="$node/preceding-sibling::*[1]" />
      </xsl:call-template>
      <字幕id>
        <xsl:value-of select="$node/@xml:id" />
      </字幕id>
      <xsl:text></xsl:text>
    </xsl:if>
  </xsl:template>

</xsl:stylesheet>

【ttml2tsukkomi_DS9.xsl】

●そうは問屋が
これでめでたしめでたし、と終われればよかったのですが、そうはいきませんでした。というのも、全台詞を(非公開ながらも)ツッコミデータの中に持つとなると、各エピソードのデータ量が今の100倍は下りません。さらにシーズンごとにファイル分けしている現状では、どれほど処理が重くなることか... 言っちゃナンですが、XML Notepad ってあんまりサクサク動く方ではないですし。
●ファイル分割再び
そんなわけで、ツッコミファイルを、シーズン毎からエピソード毎にさらに分割することにしました。それ自体はそんなに深くは考えていませんでした。前回RSS生成のときと同様、DOCTYPE によるファイル結合法を多段階に使えば、エピソード>シーズン>シリーズという風にすればよいと思ったからです。ところが!これは仕様上ダメなんですね(泣)。全シリーズを結合しているファイルからみると、DOCTYPE が2か所にあることになり、エラーとなりました。
●同じ定義をそれぞれに
結局、あまり美しくないですが、シーズン毎のツッコミ本体ファイルと全シリーズをまとめた集計用ファイルで、それぞれ同じ定義を記述するしかないという結論に至りました。以下のようなカンジです。
<?xml version="1.0" encoding="shift_jis"?>
<?xml-stylesheet type="text/xsl" href="st_eval.xsl"?>
<!DOCTYPE startrek [
<!ENTITY DS9_1 SYSTEM "DS9_1.xml">
<!ENTITY DS9_2 SYSTEM "DS9_2.xml">
<!ENTITY DS9_3-1 SYSTEM "DS9_3-1.xml">
<!ENTITY DS9_3-2 SYSTEM "DS9_3-2.xml">
<!ENTITY DS9_3-3 SYSTEM "DS9_3-3.xml">
<!ENTITY DS9_3-4 SYSTEM "DS9_3-4.xml">
<!ENTITY DS9_3-5 SYSTEM "DS9_3-5.xml">
<!ENTITY DS9_3-8 SYSTEM "DS9_3-8.xml">
<!ENTITY DS9_3-9 SYSTEM "DS9_3-9.xml">
<!ENTITY DS9_3-11 SYSTEM "DS9_3-11.xml">
<!ENTITY DS9_3-12 SYSTEM "DS9_3-12.xml">
<!ENTITY DS9_3-14 SYSTEM "DS9_3-14.xml">
<!ENTITY DS9_3-15 SYSTEM "DS9_3-15.xml">
<!ENTITY DS9_3-25 SYSTEM "DS9_3-25.xml">
]>
<シーズン シーズン番号="3" 略称="DS9">
&DS9_3-1;
&DS9_3-2;
&DS9_3-3;
&DS9_3-4;
&DS9_3-5;
&DS9_3-8;
&DS9_3-9;
&DS9_3-11;
&DS9_3-12;
&DS9_3-14;
&DS9_3-15;
&DS9_3-25;
</シーズン>

【DS9第3シーズン】

<?xml version="1.0" encoding="shift_jis"?>
<?xml-stylesheet type="text/xsl" href="st_eval_tbl.xsl"?>
<!DOCTYPE startrek [
<!ENTITY 台帳 SYSTEM "ST_def.xml">
<!ENTITY TOS1 SYSTEM "TOS1.xml">
<!ENTITY TOS2 SYSTEM "TOS2.xml">
<!ENTITY TOS3 SYSTEM "TOS3.xml">
<!ENTITY TAS1 SYSTEM "TAS1.xml">
<!ENTITY TAS2 SYSTEM "TAS2.xml">
<!ENTITY TNG_1 SYSTEM "TNG_1.xml">
<!ENTITY TNG_2 SYSTEM "TNG_2.xml">
<!ENTITY TNG_3 SYSTEM "TNG_3.xml">
<!ENTITY TNG_4 SYSTEM "TNG_4.xml">
<!ENTITY TNG_5 SYSTEM "TNG_5.xml">
<!ENTITY TNG_6 SYSTEM "TNG_6.xml">
<!ENTITY TNG_7 SYSTEM "TNG_7.xml">
<!ENTITY DS9_1 SYSTEM "DS9_1.xml">
<!ENTITY DS9_2 SYSTEM "DS9_2.xml">
<!ENTITY DS9_3-1 SYSTEM "DS9_3-1.xml">
<!ENTITY DS9_3-11 SYSTEM "DS9_3-11.xml">
<!ENTITY DS9_3-12 SYSTEM "DS9_3-12.xml">
<!ENTITY DS9_3-14 SYSTEM "DS9_3-14.xml">
<!ENTITY DS9_3-15 SYSTEM "DS9_3-15.xml">
<!ENTITY DS9_3-2 SYSTEM "DS9_3-2.xml">
<!ENTITY DS9_3-25 SYSTEM "DS9_3-25.xml">
<!ENTITY DS9_3-3 SYSTEM "DS9_3-3.xml">
<!ENTITY DS9_3-4 SYSTEM "DS9_3-4.xml">
<!ENTITY DS9_3-5 SYSTEM "DS9_3-5.xml">
<!ENTITY DS9_3-8 SYSTEM "DS9_3-8.xml">
<!ENTITY DS9_3-9 SYSTEM "DS9_3-9.xml">
<!ENTITY DS9_4 SYSTEM "DS9_4.xml">
<!ENTITY DS9_5 SYSTEM "DS9_5.xml">
<!ENTITY DS9_6 SYSTEM "DS9_6.xml">
<!ENTITY DS9_7 SYSTEM "DS9_7.xml">
<!ENTITY VGR1 SYSTEM "VGR1.xml">
<!ENTITY VGR2 SYSTEM "VGR2.xml">
<!ENTITY VGR3 SYSTEM "VGR3.xml">
<!ENTITY VGR4 SYSTEM "VGR4.xml">
<!ENTITY VGR5 SYSTEM "VGR5.xml">
<!ENTITY VGR6 SYSTEM "VGR6.xml">
<!ENTITY VGR7 SYSTEM "VGR7.xml">
<!ENTITY ENT1 SYSTEM "ENT1.xml">
<!ENTITY ENT2 SYSTEM "ENT2.xml">
<!ENTITY ENT3 SYSTEM "ENT3.xml">
<!ENTITY ENT4 SYSTEM "ENT4.xml">
<!ENTITY DSC1 SYSTEM "DSC1.xml">
]>
<スタートレック xmlns:t="urn:tables">
  <?xml-stylesheet type="text/xsl" href="st_table.xsl"?>
&台帳;
    <シリーズ 略称="TOS" xmlns:t="urn:tables">&TOS1;
&TOS2;
&TOS3;
</シリーズ>
    <シリーズ 略称="TAS" xmlns:t="urn:tables">&TAS1;
&TAS2;
</シリーズ>
    <シリーズ 略称="TNG" xmlns:t="urn:tables">&TNG_1;
&TNG_2;
&TNG_3;
&TNG_4;
&TNG_5;
&TNG_6;
&TNG_7;
</シリーズ>
    <シリーズ 略称="DS9" xmlns:t="urn:tables">&DS9_1;
&DS9_2;
<シーズン シーズン番号="3" 略称="DS9">
&DS9_3-1;
&DS9_3-11;
&DS9_3-12;
&DS9_3-14;
&DS9_3-15;
&DS9_3-2;
&DS9_3-25;
&DS9_3-3;
&DS9_3-4;
&DS9_3-5;
&DS9_3-8;
&DS9_3-9;
</シーズン>
&DS9_4;
&DS9_5;
&DS9_6;
&DS9_7;
</シリーズ>
    <シリーズ 略称="VGR" xmlns:t="urn:tables">&VGR1;
&VGR2;
&VGR3;
&VGR4;
&VGR5;
&VGR6;
&VGR7;
</シリーズ>
    <シリーズ 略称="ENT" xmlns:t="urn:tables">&ENT1;
&ENT2;
&ENT3;
&ENT4;
</シリーズ>
    <シリーズ 略称="DSC" xmlns:t="urn:tables">&DSC1;
</シリーズ>
</スタートレック>

【全シリーズ結合】

●余談
ここへ行き着くまでは、結構いろいろ試したんですが、ボツ案のひとつとして
XInclude
というのがかなり有力な案でした。実際、以下のようにすると、できちゃうんですよ!... ただし XML Notepad のプレビュー上だけですが(泣)。
<?xml version="1.0" encoding="shift_jis"?>
<?xml-stylesheet type="text/xsl" href="st_eval.xsl"?>
<シーズン シーズン番号="3" 略称="DS9" xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="DS9_3-1.xml"/>
<xi:include href="DS9_3-2.xml"/>
<xi:include href="DS9_3-3.xml"/>
<xi:include href="DS9_3-4.xml"/>
<xi:include href="DS9_3-5.xml"/>
<xi:include href="DS9_3-8.xml"/>
<xi:include href="DS9_3-9.xml"/>
<xi:include href="DS9_3-11.xml"/>
<xi:include href="DS9_3-12.xml"/>
<xi:include href="DS9_3-14.xml"/>
<xi:include href="DS9_3-15.xml"/>
<xi:include href="DS9_3-25.xml"/>
</シーズン>

【DS9第3シーズン XInclude版】

●またか!
またしても XML Notepad と IE、msxsl.exe の間で結果が異なる!!実に憤慨でした... いろいろ検索してみましたが、MSXSLでは XIncludeはサポートしていないというのが結論のよう。XML Notepad 2.7 で独自サポートされているだけでした(なんつっても、XML Notepad では msxsl?.dllを使ってないみたいだし)。これ、バッチモードで msxsl.exe の代わりに使えるようにしてくんないかなぁ...
2018.07.09 XML Notepad プレビューと msxsl.exeコマンドによる変換結果の違い
●順当に
RDFが自動生成できるようになったところで、次は更新日記だ!という順当な流れに沿ってコーディングを行っていた。いつもの手順通り、XML Notepadのプレビューで確認し、msxsl.exeコマンドで html ファイル化を行った。
●あれ?!
なんとか形になったと思い、変換結果をサーバにアップしておいたのだが、よくよくみると間違った内容となっている部分が散見された。あれ?!と、あわててプレビューで再度確認したが、何度やっても問題ない。
●びっくり!
そう、なぜか XML Notepad と msxsl.exe コマンドの結果が異なっているのである。詳細は別の機会にまとめるつもりだが、とりあえずの備忘録として結果だけ記す。
msxsl.exe -u 4.0
というオプションを付加し、MSXSL4.0 を使うように明示する。これで結果は XML Notepad と一致した。
これに悩んで、本稿が半年以上もほったらかされることになったんだぞ!チクショー!!
備考: ちなみに、ワタシの通常環境である Windows7 Pro SP1 には msxsl4.dll がインストールされていなかったために、当初このオプションはエラーとなっていた。MSXML 4.0 Service Pack 3 (Microsoft XML Core Services) をインストールする必要があった(msxsl6.dllはあったから、てっきり上位互換のものかと思ってた...)。
2017.12.03 RSS生成
●ようやくここまできたか
さて、XML化プロジェクトである。一番最初にこれという目標をたてた。そのうちのひとつに、
(RSSを記述した)rdfファイルを生成する
というのがあった。いよいよ目途がたったのでコレに取り掛かりたいと思う。
●とうぜんXML
RSSは、サイトの更新情報をXML形式で記述したものだ。これまでは、 フリーソフトのHeadline-editor Lite版を使って記事とは別に作成していたが、もうその必要はない。XML to XML のスタイルシートによって変換をかけるだけでよい。
●更新記録総ざらえ
更新記録を作るにあたり、まずは、すべての記事をひとまとめにする必要がある。日記形式のものはまだいい。同じ形式なんだからなんとかくっつくだろう。問題は、スター・トレックの翻訳ツッコミ記事である。大分構成が違う。まずはソッチの形式変換か... と思ったところで、「ツッコミリスト」を思い出した。このリストの形式を日記と同じにすることで、他の日記と同列に扱うことができるようになった。
補足: 少々ハナシが前後してしまっているが、元々「ツッコミリスト」は日記形式ではなく、この形式変換が必要になった段で変更した、という経緯があった。
●くっつけ!
で、具体的に複数ファイルのXML文書をひとつにまとめるには?いろいろ検索してみるんだけど、スタイルシート(XSL)の方は、xsl:include、xsl:import、xsl:document等があるんだけど、文書側にはそういう方法がみつからない。結局、見様見真似で(技術的背景も理解せず)、以下のようにしている。元々ルート要素である<日記>が、<日記総合>の下に入るような恰好。スタイルシートはそのまま適用可能だ。
<?xml version="1.0" encoding="shift_jis"?>
<!DOCTYPE GeneralDialy [
<!ENTITY 雑記 SYSTEM "diary2017-2018.xml">
<!ENTITY XML化 SYSTEM "XML-ization.xml">
<!ENTITY マネー SYSTEM "money.xml">
<!ENTITY 弥生元帳印刷 SYSTEM "yayoiprint.xml">
<!ENTITY パーソナルログ SYSTEM "..\startrek\xml\personal_log.xml">
<!ENTITY ツッコミ SYSTEM "..\startrek\xml\tsukkomi_list.xml">
]>
<日記総合>
&雑記;
&XML化;
&マネー;
&弥生元帳印刷;
&パーソナルログ;
&ツッコミ;
</日記総合>
●前フリ、長っ!
以上が前提。とにかく(XML化の終わった最近の)全ての日記形式記事から、RSS形式へと変換する。例によってRSS本来の記述上の意味等は全く理解していないが、Headline-editorで作られたrdfファイルを元に、同様の形式で更新履歴に変換するスタイルシートを作った。
<?xml version="1.0" encoding="shift_jis"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" 
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
  xmlns:dc="http://purl.org/dc/elements/1.1/" 
  xmlns="http://purl.org/rss/1.0/"
  xmlns:t="urn:tables" xmlns:yn="urn:yonetch" xmlns:msxsl="urn:schemas-microsoft-com:xslt">
  <xsl:output method="xml" encoding="UTF-8" />
  <xsl:include href="commonlib.xsl" />

  <xsl:variable name="記事">
    <xsl:for-each select="//記事[@状態!='テンプレ' and @状態!='下書き' and @日付!='']">
      <xsl:variable name="日付">
        <xsl:call-template name="日付表示">
          <xsl:with-param name="形式" select="'YYYYMMDD'" />
          <xsl:with-param name="日付">
            <xsl:value-of select="@日付" />
          </xsl:with-param>
        </xsl:call-template>
      </xsl:variable>
      <xsl:if test="$日付 &gt; 20170101">
        <xsl:copy-of select="." />
      </xsl:if>
    </xsl:for-each>
  </xsl:variable>

  <xsl:template match="/">
    <rdf:RDF xmlns="http://purl.org/rss/1.0/" xmlns:admin="http://webns.net/mvcb/" xmlns:lang="ja">
      <channel>
        <xsl:attribute name="rdf:about">
          <xsl:value-of select="concat($サイト情報//総合情報/URL,$サイト情報//総合情報/RDF)" />
        </xsl:attribute>
        <title>
          <xsl:value-of select="$サイト情報//総合情報/サイト名" />
        </title>
        <link>
          <xsl:value-of select="$サイト情報//総合情報/URL" />
        </link>
        <dc:date>
          <xsl:value-of select="yn:getDateTime()" />
        </dc:date>
        <items>
          <rdf:Seq>
            <xsl:apply-templates select="msxsl:node-set($記事)/*" mode="Seq">
              <xsl:sort select="@日付" order="descending" />
            </xsl:apply-templates>
          </rdf:Seq>
        </items>
      </channel>
      <xsl:apply-templates select="msxsl:node-set($記事)/*">
        <xsl:sort select="@日付" order="descending" />
      </xsl:apply-templates>
    </rdf:RDF>
  </xsl:template>

  <xsl:template match="記事" mode="Seq">
    <xsl:variable name="cat" select="カテゴリ" />
    <xsl:variable name="subcat" select="サブカテゴリ" />
    <xsl:variable name="en_subtitle" select="@見出し" />
    <xsl:variable name="dtlabel">
      <xsl:call-template name="日付表示">
        <xsl:with-param name="形式" select="'YYYYMMDD'" />
        <xsl:with-param name="日付">
          <xsl:value-of select="@日付" />
        </xsl:with-param>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="link">
      <xsl:choose>
        <xsl:when test="contains($subcat,'翻訳ツッコミ')">
          <xsl:value-of select="concat($サイト情報//カテゴリ情報/カテゴリ[@name=$cat]/サブカテゴリ[@name='翻訳ツッコミ']/値[@name='リンク'],'#',$台帳//t:エピ[t:原題=$en_subtitle]/t:シリーズ名,$台帳//t:エピ[t:原題=$en_subtitle]/t:シーズン番号)" />
        </xsl:when>
        <xsl:otherwise>
          <xsl:choose>
            <xsl:when test="count($サイト情報//カテゴリ情報/カテゴリ[@name=$cat]/サブカテゴリ)">
              <xsl:value-of select="concat($サイト情報//カテゴリ情報/カテゴリ[@name=$cat]/サブカテゴリ[@name=$subcat]/値[@name='リンク'],'#',$dtlabel)" />
            </xsl:when>
            <xsl:otherwise>
              <xsl:value-of select="concat($サイト情報//カテゴリ情報/カテゴリ[@name=$cat]/値[@name='リンク'],'#',$dtlabel)" />
            </xsl:otherwise>
          </xsl:choose>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>
    <rdf:li>
      <xsl:attribute name="rdf:resouce">
        <xsl:value-of select="concat($サイト情報//総合情報/URL,$link,'#he',$dtlabel)" />
      </xsl:attribute>
    </rdf:li>
  </xsl:template>

  <xsl:template match="記事">
    <xsl:variable name="cat" select="カテゴリ" />
    <xsl:variable name="subcat" select="サブカテゴリ" />
    <xsl:variable name="en_subtitle" select="@見出し" />
    <xsl:variable name="dtlabel">
      <xsl:call-template name="日付表示">
        <xsl:with-param name="形式" select="'YYYYMMDD'" />
        <xsl:with-param name="日付">
          <xsl:value-of select="@日付" />
        </xsl:with-param>
      </xsl:call-template>
    </xsl:variable>
    <xsl:variable name="link">
      <xsl:choose>
        <xsl:when test="contains($subcat,'翻訳ツッコミ')">
          <xsl:value-of select="concat($サイト情報//カテゴリ情報/カテゴリ[@name=$cat]/サブカテゴリ[@name='翻訳ツッコミ']/値[@name='リンク'],'#',$台帳//t:エピ[t:原題=$en_subtitle]/t:シリーズ名,$台帳//t:エピ[t:原題=$en_subtitle]/t:シーズン番号)" />
        </xsl:when>
        <xsl:otherwise>
          <xsl:choose>
            <xsl:when test="count($サイト情報//カテゴリ情報/カテゴリ[@name=$cat]/サブカテゴリ)">
              <xsl:value-of select="concat($サイト情報//カテゴリ情報/カテゴリ[@name=$cat]/サブカテゴリ[@name=$subcat]/値[@name='リンク'],'#',$dtlabel)" />
            </xsl:when>
            <xsl:otherwise>
              <xsl:value-of select="concat($サイト情報//カテゴリ情報/カテゴリ[@name=$cat]/値[@name='リンク'],'#',$dtlabel)" />
            </xsl:otherwise>
          </xsl:choose>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:variable>
    <item>
      <xsl:attribute name="rdf:about">
        <xsl:value-of select="concat($サイト情報//総合情報/URL,$link,'#he',$dtlabel)" />
      </xsl:attribute>
      <title>
        <xsl:value-of select="concat('【',$サイト情報//カテゴリ情報/カテゴリ[@name=$cat]/値[@name='リンク名'],'】 ',$subcat)" />
      </title>
      <link>
        <xsl:value-of select="concat($サイト情報//総合情報/URL,$link)" />
      </link>
      <dc:date>
        <xsl:value-of select="@日付" />
      </dc:date>
      <description>
        <xsl:value-of select="@見出し" />
      </description>
    </item>
  </xsl:template>
  <xsl:template match="表題|副題|要旨|段落|カテゴリ|サブカテゴリ|キーワード"></xsl:template>
</xsl:stylesheet>

【スタイルシート(rdf.xsl)】

●限定するには?
上記のスタイルシートにおいてのポイントとして、
対象期間の限定
がある。全日記のXML化が完了している「ぷろぐらまyonetch」「マネーの虎yonetch」は、十数年分の記事がすべて含まれる。が、RDFで更新を告知する対象に、今更そこまで含める必要はないから、まぁ、今年分だけを... と思って日付で限定するというのが、まことにメンドウだった。
●くっつけたのに、また離す
まず、以下のようにすれば、今年の記事のみの集合を変数に入れられる。
  <xsl:variable name="記事">
    <xsl:for-each select="//記事[@状態!='テンプレ' and @状態!='下書き' and @日付!='']">
      <xsl:variable name="日付">
        <xsl:call-template name="日付表示">
          <xsl:with-param name="形式" select="'YYYYMMDD'" />
          <xsl:with-param name="日付">
            <xsl:value-of select="@日付" />
          </xsl:with-param>
        </xsl:call-template>
      </xsl:variable>
      <xsl:if test="$日付 &gt; 20170101">
        <xsl:copy-of select="." />
      </xsl:if>
    </xsl:for-each>
  </xsl:variable>

【スタイルシートより抜粋(rdf.xsl)】

後は、この部分集合変数に対して、同じようにテンプレートを適用して... と思ったらそれができないのだ。その説明は、このサイトが参考になった。以下に抜粋引用する。
Firefox 2のXSLTプロセッサはXSLT 1.0のdocument関数が使えるのはいいんですが、結果ツリーフラグメント(Result Tree Fragment)をノードセットとして評価することができないようです。

そもそも結果ツリーフラグメントって何かというと、大雑把に言えばxsl:variable要素やxsl:param要素の内容としての値です。例えば、
<xsl:variable name="result"> <level>3</level> <number>1-1</number> </xsl:variable>
上記のような変数resultがあった場合、$resultはlevelとnumberの二つのノードを持つノードセットのように見えますが、これはノードセットにはなりません。結果ツリーフラグメントと呼ばれます。結果ツリーフラグメントに対しては文字列操作しか許されず、勿論/や//、[]などのノードセットに対してのみ使える演算子は使えません。
ではどうするかというと、上の記事と異なり msxsl では、
      <xsl:apply-templates select="msxsl:node-set($記事)/*">
        <xsl:sort select="@日付" order="descending" />
      </xsl:apply-templates>

【スタイルシートより抜粋(rdf.xsl)】

というように、msxsl:node-set(ツリーフラグメント)を使用することでノードセット化できる。
●ついにでけたー!!
そんなこんなで総合日記から、以下のようにrdfファイルを生成することができた。ばんざーい!
追記: 実は、この記事は書きかけでほったらかされており、半年以上を経て公開している。この当時書いていたxslファイルの中身は、半ば忘却の彼方なのであった...(^^;;
<?xml version="1.0" encoding="UTF-8"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:t="urn:tables" xmlns:yn="urn:yonetch" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns="http://purl.org/rss/1.0/" xmlns:admin="http://webns.net/mvcb/" xmlns:lang="ja">
  <channel rdf:about="http://www1.coralnet.or.jp/yonetch/yonetch.rdf">
    <title>yonetch Works</title>
    <link>http://www1.coralnet.or.jp/yonetch/</link>
    <dc:date>2017-12-03T20:42:03+09:00</dc:date>
    <items>
      <rdf:Seq>
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/XML-ization.html#20171203#he20171203" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/XML-ization.html#20171202#he20171202" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/personal_log.html#20171121#he20171121" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DS91#he20171121" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1#he20171115" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/XML-ization.html#20171112#he20171112" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#TNG7#he20171111" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1#he20171110" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1#he20171104" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/##he20171028" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/personal_log.html#20171025#he20171025" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1#he20171025" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1#he20171021" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1#he20171014" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1#he20171007" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/personal_log.html#20171002#he20171002" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1#he20171002" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1#he20170930" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/personal_log.html#20170926#he20170926" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT4#he20170910" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT4#he20170909" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT4#he20170909" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT4#he20170903" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/personal_log.html#20170902#he20170902" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170827" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170826" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170826" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170820" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170814" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170814" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170716" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170710" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170709" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170703" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/#20170622#he20170622" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170620" />
        <rdf:li rdf:resouce="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170608" />
      </rdf:Seq>
    </items>
  </channel>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/XML-ization.html#20171203#he20171203">
    <title>【XML化プロジェクト】 技術メモ</title>
    <link>http://www1.coralnet.or.jp/yonetch/XML-ization.html#20171203</link>
    <dc:date>2017-12-03</dc:date>
    <description>RSS生成</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/XML-ization.html#20171202#he20171202">
    <title>【XML化プロジェクト】 技術メモ</title>
    <link>http://www1.coralnet.or.jp/yonetch/XML-ization.html#20171202</link>
    <dc:date>2017-12-02</dc:date>
    <description>大元の変換テンプレートっす</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/personal_log.html#20171121#he20171121">
    <title>【トレッカーyonetch】 Personal Log</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/personal_log.html#20171121</link>
    <dc:date>2017-11-21</dc:date>
    <description>シリーズ中休み</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DS91#he20171121">
    <title>【トレッカーyonetch】 翻訳ツッコミ DS9第1シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DS91</link>
    <dc:date>2017-11-21</dc:date>
    <description>THE NAGUS</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1#he20171115">
    <title>【トレッカーyonetch】 翻訳ツッコミ DSC第1シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1</link>
    <dc:date>2017-11-15</dc:date>
    <description>INTO THE FOREST I GO</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/XML-ization.html#20171112#he20171112">
    <title>【XML化プロジェクト】 </title>
    <link>http://www1.coralnet.or.jp/yonetch/XML-ization.html#20171112</link>
    <dc:date>2017-11-12</dc:date>
    <description>リンクの自動生成</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#TNG7#he20171111">
    <title>【トレッカーyonetch】 翻訳ツッコミ TNG第7シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#TNG7</link>
    <dc:date>2017-11-11</dc:date>
    <description>PARALLELS</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1#he20171110">
    <title>【トレッカーyonetch】 翻訳ツッコミ DSC第1シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1</link>
    <dc:date>2017-11-10</dc:date>
    <description>SI VIS PACEM, PARA BELLUM</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1#he20171104">
    <title>【トレッカーyonetch】 翻訳ツッコミ DSC第1シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1</link>
    <dc:date>2017-11-04</dc:date>
    <description>MAGIC TO MAKE THE SANEST MAN GO MAD</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/##he20171028">
    <title>【XML化プロジェクト】 スター・トレック翻訳ツッコミ</title>
    <link>http://www1.coralnet.or.jp/yonetch/#</link>
    <dc:date>2017-10-28</dc:date>
    <description>TTML正式採用</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/personal_log.html#20171025#he20171025">
    <title>【トレッカーyonetch】 Personal Log</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/personal_log.html#20171025</link>
    <dc:date>2017-10-25</dc:date>
    <description>制作快調!</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1#he20171025">
    <title>【トレッカーyonetch】 翻訳ツッコミ DSC第1シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1</link>
    <dc:date>2017-10-25</dc:date>
    <description>LETHE</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1#he20171021">
    <title>【トレッカーyonetch】 翻訳ツッコミ DSC第1シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1</link>
    <dc:date>2017-10-21</dc:date>
    <description>CHOOSE YOUR PAIN</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1#he20171014">
    <title>【トレッカーyonetch】 翻訳ツッコミ DSC第1シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1</link>
    <dc:date>2017-10-14</dc:date>
    <description>THE BUTCHER'S KNIFE CARES NOT FOR THE LAMB'S CRY</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1#he20171007">
    <title>【トレッカーyonetch】 翻訳ツッコミ DSC第1シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1</link>
    <dc:date>2017-10-07</dc:date>
    <description>CONTEXT IS FOR KINGS</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/personal_log.html#20171002#he20171002">
    <title>【トレッカーyonetch】 Personal Log</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/personal_log.html#20171002</link>
    <dc:date>2017-10-02</dc:date>
    <description>本格始動!</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1#he20171002">
    <title>【トレッカーyonetch】 翻訳ツッコミ DSC第1シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1</link>
    <dc:date>2017-10-02</dc:date>
    <description>BATTLE AT THE BINARY STARS</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1#he20170930">
    <title>【トレッカーyonetch】 翻訳ツッコミ DSC第1シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#DSC1</link>
    <dc:date>2017-09-30</dc:date>
    <description>THE VULCAN HELLO</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/personal_log.html#20170926#he20170926">
    <title>【トレッカーyonetch】 Personal Log</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/personal_log.html#20170926</link>
    <dc:date>2017-09-26</dc:date>
    <description>新作がやってきた!</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT4#he20170910">
    <title>【トレッカーyonetch】 翻訳ツッコミ ENT第4シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT4</link>
    <dc:date>2017-09-10</dc:date>
    <description>DEMONS</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT4#he20170909">
    <title>【トレッカーyonetch】 翻訳ツッコミ ENT第4シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT4</link>
    <dc:date>2017-09-09</dc:date>
    <description>AFFLICTION</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT4#he20170909">
    <title>【トレッカーyonetch】 翻訳ツッコミ ENT第4シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT4</link>
    <dc:date>2017-09-09</dc:date>
    <description>DIVERGENCE</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT4#he20170903">
    <title>【トレッカーyonetch】 翻訳ツッコミ ENT第4シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT4</link>
    <dc:date>2017-09-03</dc:date>
    <description>THE FORGE</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/personal_log.html#20170902#he20170902">
    <title>【トレッカーyonetch】 Personal Log</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/personal_log.html#20170902</link>
    <dc:date>2017-09-02</dc:date>
    <description>新作がやってくる!</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170827">
    <title>【トレッカーyonetch】 翻訳ツッコミ ENT第3シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3</link>
    <dc:date>2017-08-27</dc:date>
    <description>COUNTDOWN</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170826">
    <title>【トレッカーyonetch】 翻訳ツッコミ ENT第3シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3</link>
    <dc:date>2017-08-26</dc:date>
    <description>DAMAGE</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170826">
    <title>【トレッカーyonetch】 翻訳ツッコミ ENT第3シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3</link>
    <dc:date>2017-08-26</dc:date>
    <description>THE COUNCIL</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170820">
    <title>【トレッカーyonetch】 翻訳ツッコミ ENT第3シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3</link>
    <dc:date>2017-08-20</dc:date>
    <description>AZATI PRIME</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170814">
    <title>【トレッカーyonetch】 翻訳ツッコミ ENT第3シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3</link>
    <dc:date>2017-08-14</dc:date>
    <description>STRATAGEM</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170814">
    <title>【トレッカーyonetch】 翻訳ツッコミ ENT第3シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3</link>
    <dc:date>2017-08-14</dc:date>
    <description>HATCHERY</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170716">
    <title>【トレッカーyonetch】 翻訳ツッコミ ENT第3シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3</link>
    <dc:date>2017-07-16</dc:date>
    <description>PROVING GROUND</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170710">
    <title>【トレッカーyonetch】 翻訳ツッコミ ENT第3シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3</link>
    <dc:date>2017-07-10</dc:date>
    <description>CHOSEN REALM</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170709">
    <title>【トレッカーyonetch】 翻訳ツッコミ ENT第3シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3</link>
    <dc:date>2017-07-09</dc:date>
    <description>THE SHIPMENT</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170703">
    <title>【トレッカーyonetch】 翻訳ツッコミ ENT第3シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3</link>
    <dc:date>2017-07-03</dc:date>
    <description>EXILE</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/#20170622#he20170622">
    <title>【更新日記】 </title>
    <link>http://www1.coralnet.or.jp/yonetch/#20170622</link>
    <dc:date>2017-06-22</dc:date>
    <description>Outlook - iCloud 連携に問題がッ!</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170620">
    <title>【トレッカーyonetch】 翻訳ツッコミ ENT第3シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3</link>
    <dc:date>2017-06-20</dc:date>
    <description>IMPULSE</description>
  </item>
  <item rdf:about="http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3#he20170608">
    <title>【トレッカーyonetch】 翻訳ツッコミ ENT第3シーズン</title>
    <link>http://www1.coralnet.or.jp/yonetch/startrek/eval.html#ENT3</link>
    <dc:date>2017-06-08</dc:date>
    <description>RAJIIN</description>
  </item>
</rdf:RDF>

【生成されたRDFファイル】

●さぁて、来週のyonetchさんは?
いい感じである!この勢いを駆って、トップページの生成、更新日記の生成へと一気に行くぞ!... と言いたいところだが、更新日記に掲示している更新内容の形式は、少々やっかいな処理を必要としている。今少し時間がかかりそうだ。
2017.12.02 大元の変換テンプレートっす
●回顧録的に
ここで述べている「XML化プロジェクト」とは、(今更しつこいが)HTMLからXMLへの移行である。だが、これも何度も述べたことがあるとおり、制作に使用していた(いる)のは、ホームページビルダー v6.5で、ふっるぅ〜いHTML仕様のコンテンツを吐く。これをチマチマXMLに置き換えるなど、考えただけでも気が遠くなる。HTMLとXMLの根本的な違いは、
閉じタグの有無
だ。まともな(?) XMLへの道の第一歩は、XHTML化することだった。何が違うって、dlタグ内の、dt、ddタグに、HTMLでは閉じタグがないのである。ワタシの日記は、これをベースにしているので最も使用頻度が高い(と思う)。まずはこれを何とかせねば...
●「すっきりと」
いろいろググってみて、以下のサイト(及びツール)を見つけた。
Clean up your Web pages
with HTML TIDY
詳細は省くが、これで整形をかけるとHTMLがXHTMLになって出てくる。ほぼそのままで、XML Notepad で読みこめるようになった。
●ようやくスタートライン
ここからがスタートである。が、のっけから壁にぶつかった。XMLの基本はツリー構造だ(とワタシは認識している)が、前述のdlタグ、単純なツリーではないのだ。Definition List というくらいなのだから、定義を表すことを目的としているのは明白だ。だったら、<dl><item><dt></dt><dd></dd></item></dl>と、各項目をまず要素とし、その子要素としてタイトルと情報のペア、となっているのが自然なツリー構造だと思う。だが、実際は項目という(一種の)パラグラフは存在しないし、<dd>タグもいくつでもいいという、フラットっぽい構造だ。
●どうやってパースすればいいの?
このdlを使った形式の日記から、dtにある日付、見出しを、ddにある小見出しと本文を取り出して<段落>タグに納めたい。そういうxhtml2xmlのフィルタをxsltを使って作りたいところであるが、xslを上辺だけナメたぐらいのレベルでは、兄弟(同じレベルの)要素を走査する方法は思いつかなかった。
補足: for-eachループとposition()を使って、奇数行、偶数行を決め打ちでやってみたりもしたが、途中からタイトルと本文がずれていく。何か誤認識してるな... ボツ。
●兄弟要素へのアクセス
流れとしては、dtを始まり、ddを終わりとして順次<段落>タグの中に落とし込んでいく、という風だろうか。だがその方法は?マジメに系統だった理解をせずに何となく使ってきた報いを受けている...
●ググってみた
いろいろ検索結果をたどってみて、以下のやり取りを発見。これ、まさに兄弟要素をまとめるハナシで、<GROUP>=<段落>、<A>=<dt>、<B>=<dd>とすれば、マンマいただけるんじゃね?(<C>は不要)。
XSLTにて、同一階層にあるElementのグループ化について良い方法をご存じでしたらご教授いただけませんでしょうか?

[やりたいこと]
Aから次のAまでElementを1つのグループにする

#XML
<ROOT>
 <A>A1</A>
 <B>B1</B>
 <C>C1</C>
 <A>A2</A>
 <C>C2</C>
 <A>A3</A>
 <A>A4</A>
 <B>B2</B>
</ROOT> 


#結果の想定
<ROOT>
 <GROUP>
  <A>A1</A>
  <B>B1</B>
  <C>C1</C>
 </GROUP>
 <GROUP>
  <A>A2</A>
  <C>C2</C>
 </GROUP>
 <GROUP>
  <A>A3</A>
 </GROUP>
 <GROUP>
  <A>A4</A>
  <B>B2</B>
 </GROUP>
</ROOT> 

(略)

あ、なるほど。
matchを使っての再帰とかもできるんですね。(そりゃそうですよね。)
再帰 = call-template のイメージがあったので勉強になりました。

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
  <xsl:output method="xml" />
  <xsl:template match="/">
    <xsl:element name="ROOT">
      <xsl:for-each select="ROOT/A">
        <GROUP>
          <xsl:copy-of select="." />
          <xsl:call-template name="func">
            <xsl:with-param name="node" select="following-sibling::*[1]" />
          </xsl:call-template>
        </GROUP>
      </xsl:for-each>
    </xsl:element>
  </xsl:template>
  <xsl:template name="func">
    <xsl:param name="node" />
    <xsl:if test="name($node)='B' or name($node)='C'">
      <xsl:copy-of select="$node" />
      <xsl:call-template name="func">
        <xsl:with-param name="node" select="$node/following-sibling::*[1]" />
      </xsl:call-template>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>

【[XSLT]同一階層のElementのグループ化】

●段階を経て
以下のようにして、<記事>タグに見出しと日付、その他(=本文)という形で落とし込むことはできた。
備考: その本文を、さらに小見出しと地の文に分けて<段落>タグに落とさねばならないが、それはまた別の機会に譲る
<xsl:template match="dl">
  <xsl:for-each select="dt">
    <記事>
      <xsl:attribute name="日付">
        <xsl:value-of select="@日付"/>
      </xsl:attribute>
      <xsl:attribute name="見出し">
        <xsl:value-of select="."/>
      </xsl:attribute>
      <xsl:attribute name="状態">公開</xsl:attribute>
      <xsl:call-template name="mydl">
        <xsl:with-param name="node" select="following-sibling::*[1]" />
      </xsl:call-template>
    </記事>
  </xsl:for-each>
</xsl:template>

<xsl:template name="mydl">
  <xsl:param name="node" />
  <xsl:if test="name($node)='dd'">
    <xsl:apply-templates select="$node" />
    <xsl:call-template name="mydl">
      <xsl:with-param name="node" select="$node/following-sibling::*[1]" />
    </xsl:call-template>
  </xsl:if>
</xsl:template>
●バツの道?
上記のコードの中で、キモとなるのは、following-sibling::*[1]の部分だろう。現時点では何のこっちゃかサッパリであるが(^^;)。ともかく、「異なる名前の兄弟要素を関連付けて処理」という目的を果たすためには、
XPath
の理解に努めること、それを避けて通ることはできなさそうだ。ちょっとWikiPedia「XML Path Language」から引用してみよう。
  • 省略構文による簡単なロケーションパスの記述例
    /A/B/C
  • 少し複雑なロケーションパスの例
    A//B/*[1]
  • 省略しない完全な構文によって書き直す
    /child::A/child::B/child::Cchild::A/descendant-or-self::node()/child::B/child::*[1]
ここまで自分が使ってきたのは、最初の省略形。ごくたまに2番目の形。最後の「完全な構文」なんぞ、全く認識したことがなかった。少なくとも上のコードを読み下せるよう、理解を進めていきたい。
●mydl
ついでといってはナンだが、このコードを別に流用して自分流のDefinition List(mydl)を実装してみた。何かの定義を掲示する際に、ワタシ流では以下の形を使っている(上でも使ったが)。
  • タイトル
    説明
  • タイトル
    説明
つまり、表現的には dl でなく ul で、一行目にタイトルを強調表示し、改行後説明を表示する。本サイトではそこら中にあるパターンだが、ul で書くのもメンドウなので、ワタシのXML内でdlを使った場合は、上記のように表現することにした。
補足: ちなみに、似たような表現をCSSでなんとかできないかとイジってみたが、(理解不足もあって)思ったようにはならなかった。
<xsl:template match="dl">
  <xsl:element name="ul">
    <xsl:for-each select="dt">
      <li>
        <b>
          <xsl:apply-templates />
        </b>
        <br />
        <xsl:call-template name="mydl">
          <xsl:with-param name="node" select="following-sibling::*[1]" />
        </xsl:call-template>
      </li>
    </xsl:for-each>
  </xsl:element>
</xsl:template>

<xsl:template name="mydl">
  <xsl:param name="node" />
  <xsl:if test="name($node)='dd'">
    <xsl:value-of select="$node" />
    <xsl:call-template name="mydl">
      <xsl:with-param name="node" select="$node/following-sibling::*[1]" />
    </xsl:call-template>
  </xsl:if>
</xsl:template>

【mydl実装部】

2017.11.12 リンクの自動生成
●遡って備忘録
スタトレ記事の更新が軌道にのり、少々こちらのメモがおろそかになっていましたが、アッチが順調なのはコッチの自前CMSが基底にあって順調なればこそ。少し突っかかってはチョコチョコと手直しをしつつ、今日に至っています。
●スタトレを前提とするがゆえに
さて本題です。本稿を含む、日記形式のXSLテンプレートは単純なところは大体の完成をみています。が、それをスタトレの Personal Log に適用しようとすると問題がありました。それは、
ツッコミ記事へのリンク
です。
●地味にメンドウ
ツッコミ記事や Personal Log では、他のエピソードを引き合いに出すことがよくあり、その時(すでに存在すれば)ツッコミ記事へのリンクも張ります。HTML版ではイチイチ他のファイルのラベルに対してのリンクを作っていましたがコレが結構大変だし、ツッコミ記事の有無によってリンクの有無も変わってきます。これをXMLを使って自動化することを考えました。
●要件定義
まず以下の通り要件を定義します。
●2パスになるか...
以上を具現化するにあたり、以下のテーブルが必要となります前者に関しては、従前のツッコミ評価表で作ったものが既にありました。問題は後者です。実際はこれを作っておく必要があるのかどうかわかりません。ただ、つくらなければ、都度ツッコミ記事の有無をスキャンするような恰好になるので何となく動作が重そうな気がしました。
●スキャン〜
そこで、全ツッコミ済み原稿ファイルから、そのタイトルだけを抜き出すようなxslファイルを作りました。ここで!タグ他の形式は、日記形式にしています(本文のない、日付と見出しだけの日記ってカンジ)。
<?xml version="1.0" encoding="shift_jis"?> <?xml-stylesheet type="text/xsl" href="diary.xsl"?> 
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:t="urn:tables" >
  <xsl:output method="xml" encoding="Shift_JIS" />
  <xsl:key name="シリーズ情報" match="t:略称" use="@name"/>
  <xsl:key name="評価情報" match="t:レベル" use="@name"/>
  <xsl:key name="エピソード情報" match="t:エピ" use="t:原題"/>
  <xsl:template match="/">
    <日記>
      <xsl:for-each select="スタートレック//エピ[(not(@状態) and ./原題!='テンプレ') or @状態='公開']">
        <xsl:sort select="更新日" order="descending"/>
        <記事>
          <xsl:attribute name="日付">
            <xsl:value-of select="更新日"/>
          </xsl:attribute>
          <xsl:attribute name="見出し">
            <xsl:value-of select="原題"/>
          </xsl:attribute>
          <xsl:attribute name="状態">公開</xsl:attribute>
          <カテゴリ>スター・トレック</カテゴリ>
          <サブカテゴリ>翻訳ツッコミ</サブカテゴリ>
        </記事>
      </xsl:for-each>
    </日記>
  </xsl:template>
</xsl:stylesheet>

【tsukkomi_list.xsl】

<日記>
  <記事 日付="2017-11-11" 見出し="PARALLELS" 状態="公開">
    <カテゴリ>スター・トレック</カテゴリ>
    <サブカテゴリ>翻訳ツッコミ</サブカテゴリ>
  </記事>
  <記事 日付="2017-11-10" 見出し="SI VIS PACEM, PARA BELLUM" 状態="公開">
    <カテゴリ>スター・トレック</カテゴリ>
    <サブカテゴリ>翻訳ツッコミ</サブカテゴリ>
  </記事>
  <記事 日付="2017-11-04" 見出し="MAGIC TO MAKE THE SANEST MAN GO MAD" 状態="公開">
    <カテゴリ>スター・トレック</カテゴリ>
    <サブカテゴリ>翻訳ツッコミ</サブカテゴリ>
  </記事>
  <記事 日付="2017-10-25" 見出し="LETHE" 状態="公開">
    <カテゴリ>スター・トレック</カテゴリ>
    <サブカテゴリ>翻訳ツッコミ</サブカテゴリ>
  </記事>
  <記事 日付="2017-10-21" 見出し="CHOOSE YOUR PAIN" 状態="公開">
    <カテゴリ>スター・トレック</カテゴリ>
    <サブカテゴリ>翻訳ツッコミ</サブカテゴリ>
  </記事>
</日記>

【tsukkomi_list.xml (変換結果)】

●道具がそろったところで
で、この 'tsukkomi_list.xml' を読み込んで、リンクを張る <ツッコミ> タグの定義が以下の通り。 "ツッコミ" というテンプレートを <ツッコミ> タグから呼ぶ、という少々紛らわしい構成となっております。
補足: ちなみに、ツッコミテンプレートの方は、もう一つ、with-quotというオプションがあります
  • option 1:邦題表示
  • option 2:シリーズ名表示
  • それ以外 :シリーズ名、邦題表示
  • with-quot 0: ダブルクォーテーションなし
  • それ以外 :ダブルクォーテーションあり
<xsl:variable name="ツッコミリスト" select="document('tsukkomi_list.xml')"/>

<xsl:template name="ツッコミ">
  <xsl:param name="en_subtitle"/>
  <xsl:param name="option">-1 
  </xsl:param>
  <xsl:param name="with-quot">1 
  </xsl:param>

  <xsl:variable name="series" select="$台帳//t:エピ[t:原題=$en_subtitle]/t:シリーズ名"/>
  <xsl:variable name="html_fn" select="concat($series,$台帳//t:シリーズ/t:略称[@name=$series]/t:値[@name='シリーズシーズンセパレータ'],$台帳//t:エピ[t:原題=$en_subtitle]/t:シーズン番号,'.html')"/>
  <xsl:variable name="ja_subtitle" select="concat('(邦題:「',$台帳//t:エピ[t:原題=$en_subtitle]/t:邦題,'」)')"/>

  <xsl:variable name="ep_subtitle">
    <xsl:choose>
      <xsl:when test="$option='0'">
        <xsl:choose>
          <xsl:when test="$with-quot='0'">
            <xsl:value-of select="$en_subtitle"/>
          </xsl:when>
          <xsl:otherwise>
            <xsl:value-of select="concat('"',$en_subtitle,'"')"/>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:when>
      <xsl:when test="$option='1'">
        <xsl:value-of select="concat('"',$en_subtitle,'"',$ja_subtitle)"/>
      </xsl:when>
      <xsl:when test="$option='2'">
        <xsl:value-of select="concat($series,'"',$en_subtitle,'"')"/>
      </xsl:when>
      <xsl:otherwise>
        <xsl:value-of select="concat($series,'"',$en_subtitle,$ja_subtitle)"/>
      </xsl:otherwise>
    </xsl:choose>
  </xsl:variable>

  <xsl:choose>
    <xsl:when test="count($ツッコミリスト//記事[@見出し=$en_subtitle]) > 0">
      <A>
        <xsl:attribute name="href">
          <xsl:value-of select="concat($html_fn,'#',$en_subtitle)"/>
        </xsl:attribute>
        <xsl:value-of select="$ep_subtitle"/>
      </A>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="$ep_subtitle"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

<xsl:template match="ツッコミ">
  <xsl:call-template name="ツッコミ">
    <xsl:with-param name="en_subtitle">
      <xsl:value-of select="."/>
    </xsl:with-param>
    <xsl:with-param name="option">
      <xsl:value-of select="@オプション"/>
    </xsl:with-param>
  </xsl:call-template>
</xsl:template>
●余談
さてちょっと話題はかわりますが、オマケレベルの話として、ふと思い立ち旧ツッコミ集計表用のデータを更新してみようと思いました。ざっと言って、ツッコミ評価をCSVファイルとしてサーバにアップし、サーバサイドで集計表を生成する、という構成です。データ形式は以下の通りです。
htmlファイル名, 原題, 評価Aの個数, 評価Bの個数, ..., 評価Eの個数, Newの有無
このCSVデータを生成するのが、以下のXSLファイルです(字下げ位置改変)。
<?xml version="1.0" encoding="shift_jis"?> <?xml-stylesheet type="text/xsl" href="diary.xsl"?> 
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:t="urn:tables" >
  <xsl:output method="text" encoding="Shift_JIS" />
  <xsl:include href="commonlib.xsl" />

  <xsl:template match="/">
    <xsl:for-each select="スタートレック//エピ[(not(@状態) and ./原題!='テンプレ') or @状態='公開']">
      <xsl:variable name="更新日MJD">
      <xsl:call-template name="ymd2mjd">
        <xsl:with-param name="ymd">
          <xsl:value-of select="更新日"/>
        </xsl:with-param>
      </xsl:call-template>
      </xsl:variable>
      <xsl:variable name="serif" select="台詞" />
      <xsl:value-of select="concat(../../@略称,key('シリーズ情報',../../@略称)/t:値[@name='シリーズシーズンセパレータ'] ,../@シーズン番号,'.html')"/>,&quot;
      <xsl:value-of select="原題"/>&quot;,
      <xsl:value-of select="count($serif/吹替[@評価='A'])" />,
      <xsl:value-of select="count($serif/吹替[@評価='B'])" />,
      <xsl:value-of select="count($serif/吹替[@評価='C'])" />,
      <xsl:value-of select="count($serif/吹替[@評価='D'])" />,
      <xsl:value-of select="count($serif/吹替[@評価='E' or @評価='F'])" />,
      <xsl:choose>
        <xsl:when test="$基準日MJD - $更新日MJD &lt; 180">1</xsl:when>
        <xsl:otherwise>0</xsl:otherwise>
      </xsl:choose>
      <xsl:text></xsl:text>

    </xsl:for-each>
  </xsl:template>
</xsl:stylesheet>
これで作ったCSVファイルを、サーバにアップしました。前にも別件で書いたとおり、サーバ側の集計CGIは(スクリプト作成以後にできた)TASやDSCのことを考慮していません。さてどうなるのかと思ったんですが、少々の不具合はあるものの、一応集計表としては機能できているようです。結果オーライ(^^)/
2017.10.28 TTML正式採用
●う、運命だ...
5か月も放置状態だった本稿。とはいえサイト記事更新こそサボっていたものの、XML関係の技術革新は続いている(オオゲサ^^;)。スタトレの Personal Logの方で既に触れているが、
ネトフリの字幕情報がTTMLだった
のである!
●か、感動...
なんという僥倖か。全く意識していなかったのに、たまたまデータ形式が合致するとは。これまではTTMLの仕様的な部分を理解しきれず、表面的な流用にとどまっていたが、お手本が手に入ったワケだ。このネトフリデータが正規のTTMLファイルだと仮定して、これに合致する処理系を作ることにした。
●さっそく拝見
さて、内容を見てみると、以下の部分で対応が必要だ。
●さて変換は
本来なら、ネトフリのTTMLファイルには一切手を入れず対処する方が望ましい気はするのだが、上記の台詞分割の部分でどうしても手をいれることになる。それならばと一種開き直りの境地でイジってみた。元々XMLなのだから、XML to XML となるスタイルシートを作ればいい... ハズだが、台詞の分割部分が少々難しかった。今は先を急ぐため、例によって Perl でスクリプトを書いてみた。
備考: ネトフリのTTMLは、台詞毎に一行にまとまっているため、行ベースの処理系である Perl の方が手っ取り早かった、ということもある
11/2追記: 表示タイミングをhh:mm:ss形式に変換するように変更。DSC第7話で、同じセリフが何度も出てきて映像との照らし合わせがメンドウだったから(^^;)
11/6追記: 下のスクリプト、スタイル名の置換のところでバグってましたんで暫定版に書き換えました。
#ネトフリの字幕TTMLファイルを、yonetch式TTMLモドキに変換する
##########################################################
#斜体スタイル名は "italic" と書き換える
#タイミング指定をtick単位から、hh:mm:ss形式に変換
#本部の先頭に「-(マイナス記号)」がついていて、かつ<br/>(改行)が入っている台詞を2つの要素に分割する
#台詞の先頭に役名が入っているものを、agent属性にする
#agent属性が入っていない場合も、属性のみ追加する(値はヌル)

 while(<>)
 {

	#tickRateを探す
	if(/<tt[^<>]+ttp:tickRate="(\d+)"/){
		$rate = int($1);
		print $rate;
	}

	#フォントにitalicを使っているスタイルを探す
	if(/<style[^<>]+tts:fontStyle="italic"[^<>x]+xml:id="([^<>]+)"[^<>\/]?\/>/){
		$italic_style = $1;
		#print $italic_style;
	}
		
	
	#s/$italic_style/italic/g; #斜体スタイル名は "italic" と書き換える
	s/style_3/italic/g; #style_3 を italic と書き換える
	s/<span[^<>]+\/>//g; # まれに値のない<span />タグがあるのであらかじめ削除

	#タイミング指定をtick単位から、hh:mm:ss形式に変換
	if(/^(<p )begin="(\d+)t" end="(\d+)t"(.+)$/){
		$_ = sprintf("%sbegin=\"%s\" end=\"%s\"%s\n",$1,&serial2time($2,$rate),&serial2time($3,$rate),$4);
	}

	#本部の先頭に「-(マイナス記号)」がついていて、かつ<br/>(改行)が入っている台詞を2つの要素に分割する
	# 台詞の先頭に役名が入っているものを、agent属性にする
	# agent属性が入っていない場合も、属性のみ追加する(値はヌル)
	if(/<br ?\/>/ and /<span[^<>]+>\-/){
		/^<p (.+) xml:id="([^<>]+)">(<span[^<>]+>)\-(\[([^\[\]]+)\])?(.*)(<\/span>)<br ?\/>(<span[^<>]+>)\-(\[([^\[\]]+)\])?(.*)(<\/span>)<\/p>$/;
		print "<p $1 xml:id=\"$2\" ";
		print $6 eq '' ? "agent=\"\">$3-$4$7<\/p>\n": "agent=\"$5\">$3-$6$7<\/p>\n";
		print "<p $1 xml:id=\"$2-2\" ";
		print $11 eq '' ? "agent=\"\">$8-$9$12<\/p>\n": "agent=\"$10\">$8-$11$12<\/p>\n";
	}elsif(/^<p (.+) xml:id="([^<>]+)">(<span[^<>]+>)(\[([^\[\]]+)\])?(.*)(<\/span>)<\/p>$/){
		print "<p $1 xml:id=\"$2\" ";
		print $6 eq '' ? "agent=\"\">$3$4$7<\/p>\n": "agent=\"$5\">$3$6$7<\/p>\n";
	}else{
		print;
	}

}

sub serial2time {
	my ($serial,$rate) = @_;
	my $sec = $serial/$rate;
	
	return sprintf("%02d:%02d:%02d",$sec/(60*60),$sec/60,$sec%60);
}

【make_ynttml.pl】

<tt ttp:tickRate="10000000" ttp:timeBase="media">
<head>
 <ttp:profile use="http://netflix.com/ttml/profile/dfxp-ls-sdh"/>
<styling>
 <style tts:color="white" tts:fontSize="100%" tts:fontStyle="italic" tts:fontWeight="normal" xml:id="style_3"/>
 <style tts:color="white" tts:fontSize="100%" tts:fontWeight="normal" xml:id="style_4"/>
 </styling>
<layout>
 <region tts:displayAlign="before" tts:extent="80.00% 40.00%" tts:origin="10.00% 10.00%" tts:textAlign="center" xml:id="region_1"/>
 <region tts:displayAlign="after" tts:extent="80.00% 40.00%" tts:origin="10.00% 50.00%" tts:textAlign="center" xml:id="region_2"/>
 </layout>
 </head>
<body>
<div xml:space="preserve">
(略)
<p begin="1997412084t" end="2022854167t" region="region_2" xml:id="subtitle44">
 <span style="style_4">Georgiou to </span>
 <span style="style_3">Shenzhou:</span>
 <span style="style_4"> two to transport.</span>
 </p>
<p begin="2048296250t" end="2064562500t" region="region_2" xml:id="subtitle45">
 <span style="style_4">[thunder crashing nearby]</span>
 </p>
<p begin="2065400000t" end="2085000000t" region="region_2" xml:id="subtitle46">
 <span style="style_4">[Burnham]</span>
 <br/>
 <span style="style_4">The storm is faster than I thought.</span>
 </p>
(略)
<p begin="3060560000t" end="3095600000t" region="region_2" xml:id="subtitle71">
 <span style="style_4">And you? What will you do</span>
 <br/>
 <span style="style_4">if we're trapped here for 89 years?</span>
 </p>
<p begin="3096426667t" end="3115195417t" region="region_2" xml:id="subtitle72">
 <span style="style_4">That's easy. I'd escape.</span>
 </p>
<p begin="3129380000t" end="3144810000t" region="region_2" xml:id="subtitle73">
 <span style="style_4">[Burnham] These are our footprints.</span>
 </p>
<p begin="3151064584t" end="3184014167t" region="region_2" xml:id="subtitle74">
 <span style="style_4">-You've walked us in a circle.</span>
 <br/>
 <span style="style_4">-Not exactly a circle.</span>
 </p>
(略)
 </div>
 </body>
 </tt>

【ネトフリ版TTML】

<tt ttp:tickRate="10000000" ttp:timeBase="media">
<head>
 <ttp:profile use="http://netflix.com/ttml/profile/dfxp-ls-sdh"/>
<styling>
 <style tts:color="white" tts:fontSize="100%" tts:fontStyle="italic" tts:fontWeight="normal" xml:id="italic"/>
 <style tts:color="white" tts:fontSize="100%" tts:fontWeight="normal" xml:id="style_4"/>
 </styling>
<layout>
 <region tts:displayAlign="before" tts:extent="80.00% 40.00%" tts:origin="10.00% 10.00%" tts:textAlign="center" xml:id="region_1"/>
 <region tts:displayAlign="after" tts:extent="80.00% 40.00%" tts:origin="10.00% 50.00%" tts:textAlign="center" xml:id="region_2"/>
 </layout>
 </head>
<body>
<div xml:space="preserve">
(略)
<p begin="00:03:19" end="00:03:22" region="region_2" xml:id="subtitle44" agent="">
 <span style="style_4">Georgiou to </span>
 <span style="italic">Shenzhou:</span>
 <span style="style_4"> two to transport.</span>
 </p>
<p begin="00:03:26" end="00:03:28" region="region_2" xml:id="subtitle45" agent="">
 <span style="style_4">[thunder crashing nearby]</span>
 </p>
<p begin="00:03:26" end="00:03:28" region="region_2" xml:id="subtitle46" agent="Burnham">
 <span style="style_4"/>
 <br/>
 <span style="style_4">The storm is faster than I thought.</span>
 </p>
(略)
<p begin="00:03:26" end="00:03:28" region="region_2" xml:id="subtitle71" agent="">
 <span style="style_4">And you? What will you do</span>
 <br/>
 <span style="style_4">if we're trapped here for 89 years?</span>
 </p>
<p begin="00:03:26" end="00:03:28" region="region_2" xml:id="subtitle72" agent="">
 <span style="style_4">That's easy. I'd escape.</span>
 </p>
<p begin="00:03:26" end="00:03:28 region="region_2" xml:id="subtitle73" agent="Burnham">
 <span style="style_4"> These are our footprints.</span>
 </p>
<p begin="00:03:26" end="00:03:28 region="region_2" xml:id="subtitle74" agent="">
 <span style="style_4">-You've walked us in a circle.</span>
 </p>
<p begin="00:03:26" end="00:03:28 region="region_2" xml:id="subtitle74-2" agent="">
 <span style="style_4">-Not exactly a circle.</span>
 </p>
(略)
 </div>
 </body>
 </tt>

【yonetch版TTML】

2017.05.28 三段階化の可否
●公開前のチェックが...
日記記事に関し、「状態」という属性を取り入れ、公開/下書きという値によって制御するようにした、ということは前述した。しかし、XML Notepadでプレビューしながら入力していると、それだけでは足りないことに気付いた。msxslコマンドによる変換の対象とするか否かという状態も必要なのである。すなわち、まだ公開段階ではないが、プレビューによるチェックはする必要がある、という状態だ。
●引数があるジャン!
XML Notepadにしても、msxslコマンドにしても、同じxslファイルを元に整形するから、どうにかしてどちらでの処理かを認識する必要がある。以下のように、コマンドの引数で処理しようとしたが、あえなく失敗。「そこでは変数は使えないよ」だと。
<xsl:param name="cmdline" select="cmdline"/>
<xsl:template match="/ and $cmdline='公開'">
  :
(略)
  :
</xsl:template>
matchの引数に使えなくとも、メイン部分では使えるはずだ。ということで、少々読みづらいが次のようにした。
<xsl:param name="cmdline" select="cmdline"/>
<xsl:template match="/">
<xsl:if test="not(cmdline) or not(@状態) or @状態!='下書き'">
  :
(略)
  :
</xsl:if>
</xsl:template>
msxslコマンドの引数に、cmdline=true という引数を与える(値は何でもいい)と、xslでは状態属性が下書きのものは処理しない。これにより、XML Notepadではプレビューされるが、msxslでは変換されないという目的を果たせた。
備考: ついでといってはナンだが、状態属性の値として'テンプレ'も加えた。この場合は、XML Notepadでもmsxslコマンドでも一切処理されない。
追記: ENT第2シーズンのコンテンツが空であることに気付いた(遅っ)。確認すると、この「状態」属性の扱いにバグがあった。状態属性を取り入れる前に書いた記事に後からその属性を追加することはしていない(手抜き^^;)。そのために、属性のないものは公開対象にならなかったことが原因だった。xslを修正した。
2017.05.18 字幕情報のXML化
●字幕の電子テキスト化
スタトレ翻訳ツッコミについて、最大の資料はDVDの英語字幕である。本サイト立ち上げ当初は、画面に英語字幕を表示し、リモコン片手に再生/一時停止を繰り返しつつ手入力していた。今考えると何と原始的なことをやっていたことか。まるでエアチェックと称してテレビをビデオカメラで撮影するようなものだ(^^)。
●テキストデータといえども
さすがに現在はソレはやっていない。VGRのDVDがリリースされた辺りからだったかと思うが、SRT形式の字幕ファイルを元に、コピペしながら今に至っているのだが、改めてこのコピペという作業を軽減できないかと考えた。何しろ字幕形式なので台詞はブツブツと断片化されているし、表示タイミングの情報も、翻訳ツッコミ用としてはジャマなだけであった(以下にサンプルを示す)。
1
02:51:21,184 --> 02:51:24,415
<i>Captain's Starlog, March 21, 2153.</i>

2
02:51:24,721 --> 02:51:27,212
<i>After three days exploring</i>
<i>an uninhabited planet...</i>

3
02:51:27,324 --> 02:51:30,259
<i>Commander Tucker and I have been</i>
<i>called back to</i> Enterprise...

4
02:51:30,360 --> 02:51:32,157
<i>to greet an unexpected visitor.</i>

5
02:51:32,262 --> 02:51:34,389
Maybe you were light-headed
from the altitude.

6
02:51:34,531 --> 02:51:37,898
I didn't slip. That overhang gave way
the moment I put my foot on it.

7
02:51:38,035 --> 02:51:41,004
- I walked on the same rocks you did.
- Maybe you loosened them.

【SRT形式】

●そこでXML!
だったらこの字幕ファイルもXML化してしまえばよいのでは、と考えた次第。字幕内容そのものをコピペするのではなく、別ファイルの台詞にリンクするような形にすれば、パンチミスなども減らせるだろうし、タイミング情報もうまくやれば翻訳ツッコミ側で時系列的に活用できるかもしれない、と。
●字幕データの標準とは?
で、早速字幕XML書式を考え始めたのだが... 待て待て、そもそも字幕の電子データ形式ってSRTしかないのか?既にXML化されたものもあるんじゃないのか?と思ってググってみたら、いろいろヒットした。経緯は省略するが、TTML (Timed Text Markup Language) なるものがXMLベースの字幕データ形式のようだったので、これを参考にすることにした。
●そこは参考程度に
厳密なことをいうとキリがなくなるので、翻訳ツッコミに必要な最低限のタグのみを流用するにとどめた。すなわち、 という程度。なお、2人の台詞が同時に表示される部分がある。その場合は、タイミング属性はそのままに、台詞を分割し、idを変更(とりあえず7なら7-2、というように)した(サンプルを以下に示す)。
補足: 分割した場合は、idでのソートで順番が狂わないように気をつけて附番する必要があるだろう。
7/17追記: 台詞分割の手間が割とバカにならないので、srt2xmlの時点で分割してしまうことにした(本来のTTMLからどんどん遠ざかる...^^;)
余談: 定義もスキーマ形式で書ければいいのだけれど、現状理解が追い付いてない(>_<)
<tt xmlns:tt="http://www.w3.org/ns/ttml" xmlns:ttm="http://www.w3.org/ns/ttml#metadata" xmlns:ttp="http://www.w3.org/ns/ttml#parameter" xmlns:tts="http://www.w3.org/ns/ttml#styling" ttp:tickRate="10000000" ttp:timeBase="media" xmlns="http://www.w3.org/ns/ttml">
<head>
<ttp:profile use="http://netflix.com/ttml/profile/dfxp-ls-sdh"/>
<styling>
<style tts:color="white" tts:fontSize="100%" tts:fontWeight="normal" xml:id="style_1"/>
<style tts:color="white" tts:fontSize="100%" tts:fontStyle="italic" tts:fontWeight="normal" xml:id="italic"/>
</styling>
</head>
  <body>
    <div>
      <p xml:id="1" begin="02:51:21,184" end="02:51:24,415" agent="Archer">
        <span style="italic">Captain's Starlog, March 21, 2153.</span>
      </p>
      <p xml:id="2" begin="02:51:24,721" end="02:51:27,212" agent="Archer">
        <span style="italic">After three days exploring</span>
        <br />
        <span style="italic">an uninhabited planet...</span>
      </p>
      <p xml:id="3" begin="02:51:27,324" end="02:51:30,259" agent="Archer">
        <span style="italic">Commander Tucker and I have been</span>
        <br />
        <span style="italic">called back to</span> Enterprise...
        </p>
      <p xml:id="4" begin="02:51:30,360" end="02:51:32,157" agent="Archer">
        <span style="italic">to greet an unexpected visitor.</span>
      </p>
      <p xml:id="5" begin="02:51:32,262" end="02:51:34,389" agent="Archer">
        Maybe you were light-headed<br />
        from the altitude.
      </p>
      <p xml:id="6" begin="02:51:34,531" end="02:51:37,898" agent="Tucker">
        I didn't slip. That overhang gave way<br />
        the moment I put my foot on it.
      </p>
      <p xml:id="7" begin="02:51:38,035" end="02:51:41,004" agent="Archer">
        - I walked on the same rocks you did.</p>
      <p xml:id="7-2" begin="02:51:38,035" end="02:51:41,004" agent="Tucker">
        - Maybe you loosened them.
      </p>
    </div>
  </body>
</tt>

【ネトフリ版TTML形式(改)】

●変換は自動で
元々持っているデータはSRT形式なので、自作TTMLモドキ ネトフリTTMLモドキに変換するPerlスクリプトを書いた(例によって動くだけの適当コーディング^^;)。
7/17追記: 台詞分割は、行頭に'-(ハイフン)'がついていたら分割、とした。それに合致しないケースもあるかもしれない。盲信はしないようにしよう。
10/26追記: ヘッダー部をネトフリ仕様になるように変更。台詞分割は依然必要なので、「モドキ」である状態は変わらない(^^)
'18/7/18追記: 斜体指定の<i>タグを、TTML仕様の<span>タグに置換するよう変更
use Switch;

$flag_in = 0;
$processing = 0;
$tmp_tag ="";

#print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
#print "<tt xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n";
#print "<body><div>\n";

print <<"EOM";
<tt xmlns:tt="http://www.w3.org/ns/ttml" xmlns:ttm="http://www.w3.org/ns/ttml#metadata" xmlns:ttp="http://www.w3.org/ns/ttml#parameter" xmlns:tts="http://www.w3.org/ns/ttml#styling" ttp:tickRate="10000000" ttp:timeBase="media" xmlns="http://www.w3.org/ns/ttml">
<head>
<ttp:profile use="http://netflix.com/ttml/profile/dfxp-ls-sdh"/>
<styling>
<style tts:color="white" tts:fontSize="100%" tts:fontWeight="normal" xml:id="style_1"/>
<style tts:color="white" tts:fontSize="100%" tts:fontStyle="italic" tts:fontWeight="normal" xml:id="italic"/>
</styling>
</head>
<body><div>
EOM

while(<>)
{
	s/<i>/<span style="italic">/g;
	s/<\/i>/<\/span>/g;

	switch ($_){
		
		case /^(\d+)$/
		{
			$flag_in = 1;
			$id = $_+0;
			print "<p xml:id=\"$id\"";
		}
		case /-->/
		{
			s/^([\d:,]+) --> ([\d:,]+)$/\tbegin=\"\1\" end=\"\2\" agent=\"\" >/;
			$idx = $_;
			print $idx;
			
		}
		case /^$/
		{
			s/^$/<\/p>/;
			print "$line$_";
			$flag_in = 0;
			$processing = 0;
			$line="";
		}
		else
		{
			$line =~ s/(?=\n)/<tmp>/mg;
			if($line =~ /^\-/){
				$line =~ s/<tmp>/<\/p><p xml:id=\"$id-2\" $idx/mg;
			} else {
				$line =~ s/<tmp>/<br \/>/mg;
			}
			$line = "$line$_";
			$processing=$processing+1;	
		}
	}
}

print "</div></body></tt>\n";

【srt2xml.pl (ネトフリTTML対応版)】

●実データ
以上の前提を元に、ツッコミ原稿XML及び、そのXSLファイルは以下のようになっている。ポイントは、原語タグ内に字幕idタグを並べるところ。前述のTTMLファイルのidを差し、字幕内容を列挙する。
    <台詞>
      <原語>
        <字幕id>5</字幕id>
      </原語>
      <吹替 評価="">高さに足がすくんで滑ったんだろう。</吹替>
      <直訳 />
      <所感 />
    </台詞>
    <台詞>
      <原語>
        <字幕id>6</字幕id>
      </原語>
      <吹替 評価="">滑ったんじゃありません。足をかけた途端に崩れたんです。</吹替>
      <直訳 />
      <所感 />
    </台詞>
    <台詞>
      <原語>
        <字幕id>7</字幕id>
      </原語>
      <吹替 評価="">あの岩は私も踏んだ。</吹替>
      <直訳 />
      <所感 />
    </台詞>
    <台詞>
      <原語>
        <字幕id>7-2</字幕id>
        <字幕id>8</字幕id>
      </原語>
      <吹替 評価="C">だから崩れたんだ。少しダイエットした方がいいですよ。</吹替>
      <直訳>多分船長が乗ったから弛んだんですよ。船長は私より数キロ重いですからね。</直訳>
      <所感>許容範囲とは思うが... やはり端折り傾向は否めない。</所感>
    </台詞>

【ENT2.xml(抜粋)】

<xsl:template match="原語[字幕id]">
<B>●
<xsl:variable name="en_subtitle" select="../../原題"/>
<!--
<xsl:variable name="字幕" select="document($台帳//t:エピ[t:原題=$en_subtitle]/t:収録[@メディア='DVD']/字幕ファイル)"/>
-->
<xsl:variable name="字幕">
 select="document($台帳//t:エピ[t:原題=$en_subtitle]/t:収録[@メディア='DVD']/字幕ファイル)"/>
<xsl:for-each select="字幕id">
	<xsl:variable name="id" select="text()"/>
	<xsl:variable name="soushoku" select="装飾"/>
	<xsl:for-each select="$字幕//p[@xml:id=$id]">
		<xsl:choose>
		<xsl:when test="$soushoku!=''">
			<xsl:value-of select="substring-before(.,$soushoku)"/>
			<xsl:element name="{$soushoku/@tag}"><xsl:value-of select="$soushoku"/></xsl:element>
			<xsl:value-of select="substring-after(.,$soushoku)"/>
		</xsl:when>
		<xsl:otherwise>
		<xsl:apply-templates select="."/>
		</xsl:otherwise>
		</xsl:choose>
	</xsl:for-each>
	<xsl:if test="position()=last()"> [<xsl:value-of select="$字幕//p[@xml:id=$id]/@agent"/>]</xsl:if>
</xsl:for-each>
</B><BR/>
</xsl:template>

【st_eval.xsl(原語台詞を字幕ファイルとリンクする部分)】

●Maybe you were light-headed from the altitude. [Archer]
【吹替】高さに足がすくんで滑ったんだろう。
●I didn't slip. That overhang gave way the moment I put my foot on it. [Tucker]
【吹替】滑ったんじゃありません。足をかけた途端に崩れたんです。
●- I walked on the same rocks you did. [Archer]
【吹替】あの岩は私も踏んだ。
●- Maybe you loosened them. You do weigh a few kilos more than I do. [Tucker]
【吹替】だから崩れたんだ。少しダイエットした方がいいですよ。[ふーん]
【直訳】多分船長が乗ったから弛んだんですよ。船長は私より数キロ重いですからね。
【所感】許容範囲とは思うが... やはり端折り傾向は否めない。

【HTML変換結果】

2017.05.03 サイト(内部的に)リニューアル計画
●というワケで
さて、経緯の方は更新日記の方で述べましたが、概要だけ再掲しておきます。本サイトyonetch WorksをXML化します。当面の目標は以下の通りです。 以上三点です。なお、XML書式については、暫定的に作成、今このページはXMLで書いています。基本のフォーマットは押さえていますが、既存の日記では他にもいろんなHTMLタグを使っているので、その辺の対応を随時進めていきます(以下のXSLでのポイントは、<xsl:sort>を使うことで、元データの記述によらず、日付順でのソートすることでした)。
<?xml version="1.0" encoding="shift_jis"?>
<?xml-stylesheet type="text/xsl" href="diary.xsl"?>
<日記>
  <表題>yonetch Works XML化プロジェクトページ</表題>
  <副題>〜XMLおよび周辺技術習得のためのメモ〜</副題>
  <要旨>本サイト自体のコンテンツを例題として、XMLを中心とした(必ずしもコンテンツ管理に限らない)データ処理に役立つ要素技術を集めるためのメモページです。かなり取り留めのないものになりそうな予感...</要旨>
  <記事 見出し="サイト(内部的に)リニューアル計画" 日付="2017-05-03" 状態="公開">
    <段落 小見出し="というワケで">さて、経緯の方は<a href="diary2017-2018.html#20170503">更新日記</a>の方で述べましたが、概要だけ再掲しておきます。本サイトyonetch WorksをXML化します。当面の目標は以下の通りです。
  <ul><li>日記形式ページのXML書式を策定する</li><li>更新日記をXMLl化する</li><li>更新日記XMLから、以下の各HTMLファイルを自動で生成できるようにする<ul><li>更新日記</li><li>トップページの What's New (直近の更新三項目のリスト)、及び</li><li>rdfファイル</li></ul></li></ul>
  以上三点です。なお、XML書式については、暫定的に作成、今このページはXMLで書いています。基本のフォーマットは押さえていますが、既存の日記では他にもいろんなHTMLタグを使っているので、その辺の対応を随時進めていきます。</段落>
    <段落 小見出し="早速とばかり">で、ちょっと先を考えると頭が痛いハナシがあります。ワタシ的XML化の本来の目的として、情報と表現の分離、というのがあります。記事の見出しが何、小見出しが何、日付がいつ... というのは本来それをどう表示するかとは別の話です。</段落>
    <段落 小見出し="アレもコレも">ですが、最終的な目的が表示にあることも明白です。では現存のHTMLコンテンツで様々なタグを使って作った記事はどうXML化すべきなのか?どう情報と表現を分離すべきなのか?野良プログラマたるワタシは、その辺の系統だった指針とか今時流行の形式、あるいは標準仕様みたいなものには疎いのです。何かありそうな気はしますが... <追記 キャプション="参考">デジタル日記の最たるモノであるブログ、そのデータ形式であるMovable Type形式を調べてみました(<a href="https://www.sixapart.jp/movabletype/manual/3.3/f_import_format/" target="_blank">こちら</a>)。うーん、XML形式じゃないですね(^^;)。ただ、項目とかはこのままパクってウチに合わせて拡張するのでもいいかも... もしかして、この書式に従えば、トラックバックもできるのかな?(それこそ10年遅れてるが...)</追記></段落>
    <カテゴリ>XML化プロジェクト</カテゴリ>
    <サブカテゴリ>その他</サブカテゴリ>
    <キーワード></キーワード>
  </記事>
  <記事 見出し="" 日付="2017-05-03" 状態="下書き">
    <段落 小見出し=""></段落>
    <カテゴリ>XML化プロジェクト</カテゴリ>
    <サブカテゴリ></サブカテゴリ>
    <キーワード></キーワード>
  </記事>
</日記>

【XMLファイル(抜粋)】

<?xml version="1.0" encoding="shift_jis"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/xsl/transform" version="1.0">
  <?xml-stylesheet type="text/css" href="hpbsite.css"?>
  <xsl:output method="html" encoding="shift_jis" />
  <xsl:template match="/">
    <html>
      <head>
        <title>
          <xsl:value-of select="//表題" />
        </title>
        <link rel="stylesheet" type="text/css" href="http://www1.coralnet.or.jp/yonetch/hpbsite.css" />
      </head>
      <body>
        <h1>
          <xsl:value-of select="//表題" />
        </h1>
        <blockquote>
          <h2>
            <xsl:value-of select="//副題" />
          </h2>
          <xsl:value-of select="//要旨" />
        </blockquote>
        <hr />
        <dl>
          <xsl:for-each select="//記事[@状態='公開']">
            <xsl:sort select="@日付" order="descending" />
            <dt>
              <xsl:call-template name="日付表示">
                <xsl:with-param name="日付">
                  <xsl:value-of select="@日付" />
                </xsl:with-param>
              </xsl:call-template>
              <xsl:value-of select="concat(' ',@見出し)" />
            </dt>
            <xsl:apply-templates />
          </xsl:for-each>
        </dl>
        <hr />
        <br />
      </body>
    </html>
  </xsl:template>
  <xsl:template match="段落[not(@状態) or @状態='公開']">
    <dd>
      <b>●<xsl:value-of select="@小見出し" /></b>
      <br />
      <xsl:apply-templates />
    </dd>
  </xsl:template>
  <xsl:template match="ul|li">
    <xsl:copy>
      <xsl:for-each select="@*">
        <xsl:copy />
      </xsl:for-each>
      <xsl:apply-templates />
    </xsl:copy>
  </xsl:template>
  <xsl:template match="追記">
    <blockquote>
      <font size="-1">
        <xsl:if test="@日付 and @日付!=''">
          <xsl:call-template name="日付表示">
            <xsl:with-param name="日付">
              <xsl:value-of select="@日付" />
            </xsl:with-param>
            <xsl:with-param name="形式">
              <xsl:value-of select="@形式" />
            </xsl:with-param>
            <xsl:with-param name="ラベル付">
              <xsl:value-of select="@ラベル付" />
            </xsl:with-param>
          </xsl:call-template>
        </xsl:if>
        <xsl:choose>
          <xsl:when test="@キャプション!=''">
            <xsl:value-of select="@キャプション" />
          </xsl:when>
          <xsl:otherwise>追記</xsl:otherwise>
        </xsl:choose>
        <xsl:value-of select="': '" />
        <xsl:apply-templates />
      </font>
    </blockquote>
  </xsl:template>
  <xsl:template match="ソース">
    <pre>
      <xsl:apply-templates />
    </pre>
  </xsl:template>
</xsl:stylesheet>

【XSLファイル "diary.xsl"(抜粋)】

●早速とばかり
で、ちょっと先を考えると頭が痛いハナシがあります。ワタシ的XML化の本来の目的として、情報と表現の分離、というのがあります。記事の見出しが何、小見出しが何、日付がいつ... というのは本来それをどう表示するかとは別の話です。
●アレもコレも
ですが、最終的な目的が表示にあることも明白です。では現存のHTMLコンテンツで様々なタグを使って作った記事はどうXML化すべきなのか?どう情報と表現を分離すべきなのか?野良プログラマたるワタシは、その辺の系統だった指針とか今時流行の形式、あるいは標準仕様みたいなものには疎いのです。何かありそうな気はしますが...
参考: デジタル日記の最たるモノであるブログ、そのデータ形式であるMovable Type形式を調べてみました(こちら)。うーん、XML形式じゃないですね(^^;)。ただ、項目とかはこのままパクってウチに合わせて拡張するのでもいいかも... もしかして、この書式に従えば、トラックバックもできるのかな?(それこそ10年遅れてるが...)
追記: MT形式に倣い、<記事>タグの属性値に「状態」を追加。“公開”“下書き”の値によって変換対象とするかどうかを使い分けられるようにした(これまでは、適当な属性なり要素の値に“テンプレ”という値を入れるなどしていた)。