28 | /** @fileoverview
29 | * 漢文訓読 JavaScript --
30 | * 注記を施した漢文を、漢文または書き下し文のHTMLに変換する。
31 | *
32 | * @author KAWABATA, Taichi
33 | * @version 0.1
34 | */
35 |
36 | /*
37 | 漢文注記記法:
38 | 漢文 := 漢文単位+
39 | 漢文単位 := 漢字単位 読み仮名? 送り仮名* 再読仮名? 再読送り* 竪点? 訓点?
40 | 漢字単位 := 漢字 異体字選択? | 句読点
41 | 漢字 := "[㐀-\9fff]|[豈-\faff]|[\ud840-\ud87f][\udc00-\udfff]"
42 | 句読点 := "[。、]"
43 | 異体字選択 := "\udb40[\udd00-\uddef]" (異体字選択子:U+E0100...U+E01EF)
44 | 読み仮名 := 表示読み | 非表示読み
45 | 表示読み := "《" 仮名漢字* "》"
46 | ※ 表示読みは、漢文・書き下し文の両方で漢字の脇に読みを表示する。
47 | 仮名漢字 := 仮名 | 漢字
48 | 仮名 := [ぁ-ヿ]
49 | 非表示読み := "〈" 仮名漢字* "〉"
50 | ※ 非表示読みは、漢文では読みを表示せず、書き下し文では読みのみ表示する。
51 | ※ 置き字は非表示読みを空の〈〉として表現する。
52 | 送り仮名 := 仮名+ | "[#(" 仮名漢字+ ")]"
53 | ※ 万葉仮名がある場合は、後者を使用する。
54 | 再読仮名 := 表示読み | 非表示読み
55 | 再読送り := 仮名+ | "[#(" 仮名漢字+ ")]"
56 | ※ 万葉仮名がある場合は、後者を使用する。
57 | 竪点 := "‐" ※ (仮定)竪点は2つ連続しない。
58 | 訓点 := "[#" 訓点文字 "]"
59 | 訓点文字 := 順序点 | "[一上天甲]?レ"
60 | 順序点 := [一二三四上中下天地人甲乙丙丁]
61 | */
62 | var kanbun_regex;
63 |
64 | (function kanbun_regex_setup () {
65 | var kanji = "[㐀-\u9fff]|[豈-\ufaff]|[\ud840-\ud87f][\udc00-\udfff]";
66 | var vselector = "\udb40[\udd00-\uddef]";
67 | var kutouten = "[。、]";
68 | var kanji_unit = "(?:" + kanji+"(?:"+vselector+")?|"+kutouten+")";
69 | var kana = "[ぁ-ヿ]";
70 | var kana_kanji = kana+"|(?:"+kanji_unit+")";
71 | var hyouji = "《(?:"+ kana_kanji + ")*》";
72 | var hihyouji = "〈(?:"+ kana_kanji + ")*〉";
73 | var yomi = "(?:" + hyouji +")|(?:" + hihyouji +")";
74 | var okuri = kana + "+|[#((?:" + kana_kanji + ")+)]";
75 | var saidoku = "(?:" + hyouji +")|(?:" + hihyouji +")";
76 | var saiokuri = kana + "+|[#((?:" + kana_kanji + ")+)]";
77 | var tateten = "‐";
78 | var junjo = "[一二三四上中下天地人甲乙丙丁]";
79 | var kunten = "[#(?:"+junjo+"|[一上天甲]?レ)]";
80 |
81 | var kanbun = "(" +kanji_unit +")(" + yomi + ")?(" + okuri + ")?(" + saidoku +
82 | ")?(" + saiokuri + ")?(" + tateten +")?(" + kunten + ")?";
83 | kanbun_regex = new RegExp (kanbun);
84 | } ());
85 |
86 | // ******** 基本機能 ********
87 |
88 | /**
89 | * 配列からの要素の削除
90 | * @private
91 | */
92 | Array.prototype.remove = function() {
93 | var what, a = arguments, L = a.length, ax;
94 | while (L && this.length) {
95 | what = a[--L];
96 | while ((ax = this.indexOf(what)) !== -1) {
97 | this.splice(ax, 1);
98 | }
99 | }
100 | return this;
101 | };
102 |
103 | /**
104 | * 配列の複製(配列要素の複製はしない)
105 | * @private
106 | */
107 | Array.prototype.clone = function(){
108 | return Array.apply(null,this);
109 | };
110 |
111 |
112 | // ******** 漢文の分割と関連ツール ********
113 |
114 | /**
115 | * 漢文を漢字単位に分割する。
116 | * @private
117 | * @param {string} text 元データ
118 | * @returns matchの配列
119 | * @type {Array}
120 | */
121 | function kanbun_split (text) {
122 | var result=[];
123 | while (text.length > 0) {
124 | var match = text.match(kanbun_regex);
125 | if (match == null) {
126 | console.log(text);
127 | alert("Parse Error! see console log.");
128 | return -1;
129 | }
130 | if (match["index"]!=0) {
131 | console.log(match);
132 | alert("Parse Error! see console log.");
133 | return -1;
134 | }
135 | text=text.substring(match[0].length);
136 | result = result.concat([match]);
137 | }
138 | return result;
139 | }
140 |
141 | /**
142 | * match の漢字・読みの部分をHTMLにする。
143 | * @private
144 | * @param {Array} match
145 | * @param {boolean} yomi_p 漢字に読みを表示する。
146 | * @param {boolean} kanbun_p 漢文ならtrue, 書き下し文ならfalse。
147 | * trueなら、〈…〉は、漢字のみを表示し、falseなら仮名のみを表示する。
148 | * @param {boolean} saidoku_p 再読文字をHTML5 左ルビ仕様で表示する。
149 | * @returns {string} 読み付き漢字。rubyがfalseか読みがない場合は漢字のみ。
150 | * 〈…〉は kanbun がtrueなら漢字のみ。falseなら読みのみ。
151 | * 《…》なら<ruby>タグで返す。
152 | */
153 | // TODO 再読文字の左ルビは未対応。
154 | function kanbun_match_yomi(match,yomi_p,kanbun_p) {
155 | var kanji=match[1];
156 | var yomi=match[2];
157 | var saidoku=match[4];
158 | // 特殊ケース
159 | if (yomi != undefined && yomi.match(/^〈/)) {
160 | if (kanbun_p) {
161 | return kanji;
162 | } else {
163 | return yomi.slice(1,-1);
164 | }
165 | } else if (!yomi_p) return kanji;
166 | // ruby
167 | if (yomi == undefined && saidoku==undefined) {return kanji;}
168 | var result="<ruby>"+kanji+
169 | ((yomi == undefined)?
170 | "<rt></rt>":"<rp>(</rp><rt>"+yomi.slice(1,-1)+"</rt><rp>)</rp>")+
171 | // 書き下しでは再読文字にルビは入れない。
172 | ((kanbun_p==false || saidoku==undefined)?
173 | "":"<rp>[</rp><rt>"+saidoku.slice(1,-1)+"</rt><rp>]</rp>")+
174 | "</ruby>";
175 | return result;
176 | }
177 |
178 | var kanbun_unicode = {"‐":"㆐","レ":"㆑","一":"㆒","二":"㆓",
179 | "三":"㆔","四":"㆕","上":"㆖","中":"㆗",
180 | "下":"㆘","甲":"㆙","乙":"㆚","丙":"㆛",
181 | "丁":"㆜","天":"㆝","地":"㆞","人":"㆟"};
182 |
183 | /**
184 | * match の送り仮名部分を返す。
185 | * (送り仮名がない場合は空文字を返す。)
186 | * @private
187 | * @param {Array} match
188 | * @returns {string} 送り仮名部分
189 | */
190 | function kanbun_match_okuri (match) {
191 | var okuri = (match[3] != undefined)? match[3] : "";
192 | return (okuri.match(/^[#(/)) ? okuri.slice(3,-2):okuri;
193 | }
194 |
195 | /**
196 | * match の送り・訓点部分をHTMLにする。(漢文訓読)
197 | * @private
198 | * @param {Array} match
199 | * @param {boolean} unicode_p 訓点をUniocodeで表示する
200 | * @param {boolean} okuri_p 送り仮名を表示する
201 | * @param {boolean} ten_p 訓点を表示する
202 | * @returns {string} 訓点HTML。
203 | */
204 | function kanbun_match_okuri_ten(match,unicode_p,okuri_p,ten_p) {
205 | // 送り文字
206 | var okuri = (okuri_p == false) ? "" : kanbun_match_okuri(match);
207 | var tate= ((ten_p == false) || (match[6] == undefined))? "" : match[6];
208 | var ten = ((ten_p == false) || (match[7] == undefined))? "" : match[7].slice(2,-1);
209 |
210 | if (okuri == "" && ten != "") okuri = " ";
211 | if (okuri != "" && ten == "") ten = " ";
212 | if (okuri != "" && ten != "") {
213 | return "<table cellspacing='0' cellpadding='0' style='vertical-align: middle; display: inline-block; font-size: 50%;'><tr><td>"
214 | + okuri + "</td></tr><tr><td>" + ten + "</td></tr></table>";
215 | // TODO tate shori.
216 | } else
217 | return "";
218 | }
219 |
220 | // ******** 漢文 ********
221 |
222 | /**
223 | * 文字列中のカタカナを平仮名にする。
224 | * @private
225 | * @param {string} text 元文字列
226 | * @returns 変換文字列
227 | * @type string
228 | */
229 | function kanbun_katakana_to_hiragana (text) {
230 | var result=text.replace(/[ァ-ヶ]/g,function(whole) {
231 | var hiragana= whole.charCodeAt(0)-96;
232 | return String.fromCharCode(hiragana);
233 | });
234 | return result;
235 | }
236 |
237 | /**
238 | * 句読点の前の<wbr>を除去する。
239 | * @private
240 | * @param {string} html 元HTML
241 | * @returns 変換HTML
242 | * @type string
243 | */
244 | function kanbun_remove_kutou_break(html) {
245 | var result=html.replace(/<\/nobr><wbr\/><nobr>([。、])/g,"$1");
246 | return result;
247 | }
248 |
249 | /**
250 | * 漢文をHTMLに変換する。
251 | * @param {string} text 元データ
252 | * @param {boolean} yomi 読み表示
253 | * @param {boolean} okuri 送り仮名表示
254 | * @param {boolean} ten 漢文順序点表示
255 | * @param {boolean} kutou 句読点表示
256 | * @param {boolean} unicode 漢文順序にユニコードを使用
257 | * @returns HTMLデータ
258 | * @type string
259 | */
260 | function kanbun_to_kanbun (text, yomi, okuri, ten, kutou, unicode) {
261 | var split=kanbun_split(text);
262 | var result="";
263 | split.forEach(function(match) {
264 | if (!(!kutou && match[1].match(/[。、]/))) {
265 | var kanji_part = kanbun_match_yomi(match,yomi,true);
266 | var okuri_ten_part = kanbun_match_okuri_ten(match,unicode,okuri,ten);
267 | result+="<nobr>"+ kanji_part + okuri_ten_part +"</nobr><wbr/>";
268 | }
269 | });
270 | return kanbun_remove_kutou_break(result);
271 | }
272 |
273 | // ******** 書き下し文 ********
274 |
275 | /**
276 | * 漢文の順序を書き下し文の順序の分割した配列に変換する。
277 | * @private
278 | * @param {string} text 入力
279 | * @param {boolean} unicode 訓点文字
280 | * @returns {array} 順序を入れ替えた配列
281 | */
282 | function kanbun_reorder (text){
283 | var kunten_flag="", reten_flag=false, tate_flag=false;
284 | var split_text=kanbun_split (text);
285 | var kanbun_two=null, kanbun_three=null, kanbun_four=null;
286 | var kanbun_middle=null, kanbun_down=null;
287 | var kanbun_otsu=null, kanbun_hei=null, kanbun_tei=null;
288 | var kanbun_chi=null, kanbun_jin=null;
289 | var stock=new Array();
290 | var result=new Array();
291 |
292 | split_text.forEach(function (match) {
293 | var tateten=(typeof match[6] != 'undefined')?match[6]:"";
294 | var kunten=(typeof match[7] != 'undefined')?match[7].slice(2,-1):"";
295 | var saidoku = match[4];
296 | var saiyomi = match[5];
297 | if (typeof saidoku != 'undefined') {
298 | // 再読処理。matchをそのままresultに入れ、
299 | // matchはclone()してそこの読み・送りを再読・再送りにする。
300 | result.push(match);
301 | match = match.clone();
302 | match[2]=saidoku;
303 | match[3]=saiyomi;
304 | }
305 | // 前の漢字のレ点・竪点への対応
306 | if (reten_flag) {
307 | stock.unshift(match);
308 | reten_flag=false;
309 | } else if (tate_flag) {
310 | stock.push(match);
311 | tate_flag=false;
312 | } else {
313 | stock = new Array(match);
314 | }
315 | // 訓点処理
316 | if (kunten.match(/[一二三四上中下天地人甲乙丙丁]/)) {
317 | kunten_flag = RegExp.lastMatch;
318 | }
319 | if (kunten.match(/レ/)) {
320 | reten_flag=true; // 「一レ」の場合は reten_flag だけ true にして次に。
321 | } else if (tateten == "‐") {
322 | tate_flag=true;
323 | } else if (kunten_flag.length > 0) {
324 | // 現在漢字にレ点・竪点がない場合は訓点処理に入る
325 | if (kunten_flag.match(/一/)) {
326 | result = result.concat(stock).concat(kanbun_two);
327 | result = result.concat(kanbun_three).concat(kanbun_four);
328 | kanbun_two=null;
329 | kanbun_three=null;
330 | kanbun_four=null;}
331 | else if (kunten_flag.match(/二/)) kanbun_two=stock;
332 | else if (kunten_flag.match(/三/)) kanbun_three=stock;
333 | else if (kunten_flag.match(/四/)) kanbun_four=stock;
334 | else if (kunten_flag.match(/上/)) {
335 | result = result.concat(stock).concat(kanbun_middle);
336 | result = result.concat(kanbun_down);
337 | kanbun_middle=null;
338 | kanbun_down=null;}
339 | else if (kunten_flag.match(/中/)) kanbun_middle=stock;
340 | else if (kunten_flag.match(/下/)) kanbun_down=stock;
341 | else if (kunten_flag.match(/甲/)) {
342 | result = result.concat(stock).concat(kanbun_otsu);
343 | result = result.concat(kanbun_hei).concat(kanbun_tei);
344 | kanbun_otsu=null;
345 | kanbun_hei=null;
346 | kanbun_tei=null;
347 | }
348 | else if (kunten_flag.match(/乙/)) kanbun_otsu=stock;
349 | else if (kunten_flag.match(/丙/)) kanbun_hei=stock;
350 | else if (kunten_flag.match(/丁/)) kanbun_tei=stock;
351 | else if (kunten_flag.match(/天/)) {
352 | result = result.concat(stock).concat(kanbun_chi);
353 | result = result.concat(kanbun_jin);
354 | kanbun_chi=null;
355 | kanbun_jin=null;}
356 | else if (kunten_flag.match(/地/)) kanbun_chi=stock;
357 | else if (kunten_flag.match(/人/)) kanbun_jin=stock;
358 | else console.log ("error! match="+match);
359 | kunten_flag="";
360 | } else {
361 | result = result.concat(stock);
362 | }
363 | });
364 | return result.remove(null);
365 | }
366 |
367 | /**
368 | * 漢文を書き下し文に変換する。
369 | * @param {string} text 元データ
370 | * @param {boolean} yomi 読み表示
371 | * @param {boolean} hiragana カタカナを平仮名に変換
372 | * @returns HTMLデータ
373 | * @type string
374 | */
375 | function kanbun_to_kakikudashi (text,yomi,hiragana){
376 | var reordered=kanbun_reorder(text);
377 | var result="";
378 | reordered.forEach(function(match) {
379 | var kanji_part = kanbun_match_yomi(match,yomi,false);
380 | var okuri_part = kanbun_match_okuri(match);
381 | result+="<nobr>"+kanji_part+okuri_part +"</nobr><wbr/>";
382 | });
383 | if (hiragana) result=kanbun_katakana_to_hiragana(result);
384 | return kanbun_remove_kutou_break(result);
385 | }
386 |
387 |
388 | // ******** HTML処理(要jquery) ********
389 | //
390 | // 元のデータを <!--XXXX--> とコメントに入れて保存する。
391 | //
392 |
393 | /**
394 | * text中の <!--XXXX--> の部分を返す。
395 | * @private
396 | */
397 | function kanbun_orig_text (text) {
398 | var orig_text;
399 | if (text.match(/<!--([^>]+)-->/)) {
400 | orig_text=RegExp.$1;
401 | } else {
402 | orig_text=text;
403 | }
404 | return orig_text;
405 | };
406 |
407 | /**
408 | * HTMLのIDノードの、<!--XXXX--> で保存されている原データをもとに戻す。
409 | * @private
410 | * @param {string} id HTML node
411 | * @returns {none}
412 | */
413 | function kanbun_html_to_original (id) {
414 | $(id+"[class=kanbun]").each(function () {
415 | var orig_text=kanbun_orig_text($(this).html());
416 | $(this).html(orig_text);
417 | });
418 | }
419 |
420 | /**
421 | * HTMLのIDノードを漢文に変換する。(要jQuery)
422 | * @param {string} id HTML node
423 | * @param {boolean} yomi 読み仮名表示
424 | * @param {boolean} okuri 送り仮名表示
425 | * @param {boolean} ten 漢文順序点表示
426 | * @param {boolean} kutou 句読点表示
427 | * @param {boolean} unicode 漢文順序にユニコードを使用
428 | * @type {none}
429 | */
430 | function kanbun_html_to_kanbun (id,yomi,okuri,ten,kutou,unicode) {
431 | $(id+"[class=kanbun]").each(function () {
432 | var orig_text=kanbun_orig_text($(this).html());
433 | var new_text=kanbun_to_kanbun(orig_text,yomi,okuri,ten,kutou,unicode);
434 | $(this).html(new_text+"<!--"+orig_text+"-->");
435 | });
436 | }
437 |
438 | /**
439 | * HTMLのIDノードを書き下し文に変換する。(要jQuery)
440 | * @param {string} id HTML node
441 | * @param {boolean} yomi 読み表示
442 | * @param {boolean} hiragana カタカナを平仮名に変換
443 | * @type {none}
444 | */
445 | function kanbun_html_to_kakikudashi (id,yomi,hiragana) {
446 | $(id+"[class=kanbun]").each(function () {
447 | var orig_text=kanbun_orig_text($(this).html());
448 | var new_text=kanbun_to_kakikudashi(orig_text,yomi,hiragana);
449 | $(this).html(new_text+"<!--"+orig_text+"-->");
450 | });
451 | }
452 |
453 |
454 |