Update copyright year to 2014 by running admin/update-copyright.
[emacs.git] / lisp / international / ja-dic-cnv.el
blobe908360c9d059aae2ac0caba23e3b023a1232c9d
1 ;;; ja-dic-cnv.el --- convert a Japanese dictionary (SKK-JISYO.L) to Emacs Lisp
3 ;; Copyright (C) 2001-2014 Free Software Foundation, Inc.
5 ;; Copyright (C) 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004,
6 ;; 2005, 2006, 2007, 2008, 2009, 2010, 2011
7 ;; National Institute of Advanced Industrial Science and Technology (AIST)
8 ;; Registration Number H14PRO021
10 ;; Keywords: i18n, mule, multilingual, Japanese
12 ;; This file is part of GNU Emacs.
14 ;; GNU Emacs is free software: you can redistribute it and/or modify
15 ;; it under the terms of the GNU General Public License as published by
16 ;; the Free Software Foundation, either version 3 of the License, or
17 ;; (at your option) any later version.
19 ;; GNU Emacs is distributed in the hope that it will be useful,
20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 ;; GNU General Public License for more details.
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
27 ;;; Commentary:
29 ;; SKK is a Japanese input method running on Mule created by Masahiko
30 ;; Sato <masahiko@sato.riec.tohoku.ac.jp>. Here we provide utilities
31 ;; to handle a dictionary distributed with SKK so that a different
32 ;; input method (e.g. quail-japanese) can utilize the dictionary.
34 ;; The format of SKK dictionary is quite simple. Each line has the
35 ;; form "KANASTRING /CONV1/CONV2/.../" which means KANASTRING (\e$B2>L>J8\e(B
36 ;; \e$B;zNs\e(B) can be converted to one of CONVi. CONVi is a Kanji (\e$B4A;z\e(B)
37 ;; and Kana (\e$B2>L>\e(B) mixed string.
39 ;; KANASTRING may have a trailing ASCII letter for Okurigana (\e$BAw$j2>L>\e(B)
40 ;; information. For instance, the trailing letter `k' means that one
41 ;; of the following Okurigana is allowed: \e$B$+$-$/$1$3\e(B. So, in that
42 ;; case, the string "KANASTRING\e$B$/\e(B" can be converted to one of "CONV1\e$B$/\e(B",
43 ;; CONV2\e$B$/\e(B, ...
45 ;;; Code:
47 ;; Name of a file to generate from SKK dictionary.
48 (defvar ja-dic-filename "ja-dic.el")
50 (defun skkdic-convert-okuri-ari (skkbuf buf)
51 (message "Processing OKURI-ARI entries ...")
52 (goto-char (point-min))
53 (with-current-buffer buf
54 (insert ";; Setting okuri-ari entries.\n"
55 "(skkdic-set-okuri-ari\n"))
56 (while (not (eobp))
57 (if (/= (following-char) ?>)
58 (let ((from (point))
59 (to (line-end-position)))
60 (with-current-buffer buf
61 (insert-buffer-substring skkbuf from to)
62 (beginning-of-line)
63 (insert "\"")
64 (search-forward " ")
65 (delete-char 1) ; delete the first '/'
66 (let ((p (point)))
67 (end-of-line)
68 (delete-char -1) ; delete the last '/'
69 (subst-char-in-region p (point) ?/ ? 'noundo))
70 (insert "\"\n"))))
72 (forward-line 1))
73 (with-current-buffer buf
74 (insert ")\n\n")))
76 (defconst skkdic-postfix-list '(skkdic-postfix-list))
78 (defconst skkdic-postfix-data
79 '(("\e$B$$$-\e(B" "\e$B9T\e(B")
80 ("\e$B$,$+$j\e(B" "\e$B78\e(B")
81 ("\e$B$,$/\e(B" "\e$B3X\e(B")
82 ("\e$B$,$o\e(B" "\e$B@n\e(B")
83 ("\e$B$7$c\e(B" "\e$B<R\e(B")
84 ("\e$B$7$e$&\e(B" "\e$B=8\e(B")
85 ("\e$B$7$g$&\e(B" "\e$B>^\e(B" "\e$B>k\e(B")
86 ("\e$B$8$g$&\e(B" "\e$B>k\e(B")
87 ("\e$B$;$s\e(B" "\e$B@~\e(B")
88 ("\e$B$@$1\e(B" "\e$B3Y\e(B")
89 ("\e$B$A$c$/\e(B" "\e$BCe\e(B")
90 ("\e$B$F$s\e(B" "\e$BE9\e(B")
91 ("\e$B$H$&$2\e(B" "\e$BF=\e(B")
92 ("\e$B$I$*$j\e(B" "\e$BDL$j\e(B")
93 ("\e$B$d$^\e(B" "\e$B;3\e(B")
94 ("\e$B$P$7\e(B" "\e$B66\e(B")
95 ("\e$B$O$D\e(B" "\e$BH/\e(B")
96 ("\e$B$b$/\e(B" "\e$BL\\e(B")
97 ("\e$B$f$-\e(B" "\e$B9T\e(B")))
99 (defun skkdic-convert-postfix (skkbuf buf)
100 (message "Processing POSTFIX entries ...")
101 (goto-char (point-min))
102 (with-current-buffer buf
103 (insert ";; Setting postfix entries.\n"
104 "(skkdic-set-postfix\n"))
106 ;; Initialize SKKDIC-POSTFIX-LIST by predefined data
107 ;; SKKDIC-POSTFIX-DATA.
108 (with-current-buffer buf
109 (let ((l skkdic-postfix-data)
110 kana candidates entry)
111 (while l
112 (setq kana (car (car l)) candidates (cdr (car l)))
113 (insert "\"" kana)
114 (while candidates
115 (insert " " (car candidates))
116 (setq entry (lookup-nested-alist (car candidates)
117 skkdic-postfix-list nil nil t))
118 (if (consp (car entry))
119 (setcar entry (cons kana (car entry)))
120 (set-nested-alist (car candidates) (list kana)
121 skkdic-postfix-list))
122 (setq candidates (cdr candidates)))
123 (insert "\"\n")
124 (setq l (cdr l)))))
126 ;; Search postfix entries.
127 (while (re-search-forward "^[#<>?]\\(\\(\\cH\\|\e$B!<\e(B\\)+\\) " nil t)
128 (let ((kana (match-string 1))
129 str candidates)
130 (while (looking-at "/[#0-9 ]*\\([^/\n]*\\)/")
131 (setq str (match-string 1))
132 (if (not (member str candidates))
133 (setq candidates (cons str candidates)))
134 (goto-char (match-end 1)))
135 (with-current-buffer buf
136 (insert "\"" kana)
137 (while candidates
138 (insert " " (car candidates))
139 (let ((entry (lookup-nested-alist (car candidates)
140 skkdic-postfix-list nil nil t)))
141 (if (consp (car entry))
142 (if (not (member kana (car entry)))
143 (setcar entry (cons kana (car entry))))
144 (set-nested-alist (car candidates) (list kana)
145 skkdic-postfix-list)))
146 (setq candidates (cdr candidates)))
147 (insert "\"\n"))))
148 (with-current-buffer buf
149 (insert ")\n\n")))
151 (defconst skkdic-prefix-list '(skkdic-prefix-list))
153 (defun skkdic-convert-prefix (skkbuf buf)
154 (message "Processing PREFIX entries ...")
155 (goto-char (point-min))
156 (with-current-buffer buf
157 (insert ";; Setting prefix entries.\n"
158 "(skkdic-set-prefix\n"))
159 (save-excursion
160 (while (re-search-forward "^\\(\\(\\cH\\|\e$B!<\e(B\\)+\\)[<>?] " nil t)
161 (let ((kana (match-string 1))
162 str candidates)
163 (while (looking-at "/\\([^/\n]+\\)/")
164 (setq str (match-string 1))
165 (if (not (member str candidates))
166 (setq candidates (cons str candidates)))
167 (goto-char (match-end 1)))
168 (with-current-buffer buf
169 (insert "\"" kana)
170 (while candidates
171 (insert " " (car candidates))
172 (set-nested-alist (car candidates) kana skkdic-prefix-list)
173 (setq candidates (cdr candidates)))
174 (insert "\"\n")))))
175 (with-current-buffer buf
176 (insert ")\n\n")))
178 ;; FROM and TO point the head and tail of "/J../J../.../".
179 (defun skkdic-get-candidate-list (from to)
180 (let (candidates)
181 (goto-char from)
182 (while (re-search-forward "/[^/ \n]+" to t)
183 (setq candidates (cons (buffer-substring (1+ (match-beginning 0))
184 (match-end 0))
185 candidates)))
186 candidates))
188 ;; Return entry for STR from nested alist ALIST.
189 (defsubst skkdic-get-entry (str alist)
190 (car (lookup-nested-alist str alist nil nil t)))
193 (defconst skkdic-word-list '(skkdic-word-list))
195 ;; Return t if substring of STR (between FROM and TO) can be broken up
196 ;; to chunks all of which can be derived from another entry in SKK
197 ;; dictionary. SKKBUF is the buffer where the original SKK dictionary
198 ;; is visited, KANA is the current entry for STR. FIRST is t only if
199 ;; this is called at top level.
201 (defun skkdic-breakup-string (skkbuf kana str from to &optional first)
202 (let ((len (- to from)))
203 (or (and (>= len 2)
204 (let ((min-idx (+ from 2))
205 (idx (if first (1- to ) to))
206 (found nil))
207 (while (and (not found) (>= idx min-idx))
208 (let ((kana2-list (skkdic-get-entry
209 (substring str from idx)
210 skkdic-word-list)))
211 (if (or (and (consp kana2-list)
212 (let ((kana-len (length kana))
213 kana2)
214 (catch 'skkdic-tag
215 (while kana2-list
216 (setq kana2 (car kana2-list))
217 (if (string-match kana2 kana)
218 (throw 'skkdic-tag t))
219 (setq kana2-list (cdr kana2-list)))))
220 (or (= idx to)
221 (skkdic-breakup-string skkbuf kana str
222 idx to)))
223 (and (stringp kana2-list)
224 (string-match kana2-list kana)))
225 (setq found t)
226 (setq idx (1- idx)))))
227 found))
228 (and first
229 (> len 2)
230 (let ((kana2 (skkdic-get-entry
231 (substring str from (1+ from))
232 skkdic-prefix-list)))
233 (and (stringp kana2)
234 (eq (string-match kana2 kana) 0)))
235 (skkdic-breakup-string skkbuf kana str (1+ from) to))
236 (and (not first)
237 (>= len 1)
238 (let ((kana2-list (skkdic-get-entry
239 (substring str from to)
240 skkdic-postfix-list)))
241 (and (consp kana2-list)
242 (let (kana2)
243 (catch 'skkdic-tag
244 (while kana2-list
245 (setq kana2 (car kana2-list))
246 (if (string= kana2
247 (substring kana (- (length kana2))))
248 (throw 'skkdic-tag t))
249 (setq kana2-list (cdr kana2-list)))))))))))
251 ;; Return list of candidates which excludes some from CANDIDATES.
252 ;; Excluded candidates can be derived from another entry.
254 (defun skkdic-reduced-candidates (skkbuf kana candidates)
255 (let (elt l)
256 (while candidates
257 (setq elt (car candidates))
258 (if (or (= (length elt) 1)
259 (and (string-match "^\\cj" elt)
260 (not (skkdic-breakup-string skkbuf kana elt 0 (length elt)
261 'first))))
262 (setq l (cons elt l)))
263 (setq candidates (cdr candidates)))
264 (nreverse l)))
266 (defvar skkdic-okuri-nasi-entries (list nil))
267 (defvar skkdic-okuri-nasi-entries-count 0)
269 (defun skkdic-collect-okuri-nasi ()
270 (message "Collecting OKURI-NASI entries ...")
271 (save-excursion
272 (let ((prev-ratio 0)
273 ratio)
274 (while (re-search-forward "^\\(\\(\\cH\\|\e$B!<\e(B\\)+\\) \\(/\\cj.*\\)/$"
275 nil t)
276 (let ((kana (match-string 1))
277 (candidates (skkdic-get-candidate-list (match-beginning 3)
278 (match-end 3))))
279 (setq skkdic-okuri-nasi-entries
280 (cons (cons kana candidates) skkdic-okuri-nasi-entries)
281 skkdic-okuri-nasi-entries-count
282 (1+ skkdic-okuri-nasi-entries-count))
283 (setq ratio (floor (/ (* (point) 100.0) (point-max))))
284 (if (/= (/ prev-ratio 10) (/ ratio 10))
285 (progn
286 (message "collected %2d%% ..." ratio)
287 (setq prev-ratio ratio)))
288 (while candidates
289 (let ((entry (lookup-nested-alist (car candidates)
290 skkdic-word-list nil nil t)))
291 (if (consp (car entry))
292 (setcar entry (cons kana (car entry)))
293 (set-nested-alist (car candidates) (list kana)
294 skkdic-word-list)))
295 (setq candidates (cdr candidates))))))))
297 (defun skkdic-convert-okuri-nasi (skkbuf buf)
298 (message "Processing OKURI-NASI entries ...")
299 (with-current-buffer buf
300 (insert ";; Setting okuri-nasi entries.\n"
301 "(skkdic-set-okuri-nasi\n")
302 (let ((l (nreverse skkdic-okuri-nasi-entries))
303 (count 0)
304 (prev-ratio 0)
305 ratio)
306 (while l
307 (let ((kana (car (car l)))
308 (candidates (cdr (car l))))
309 (setq ratio (/ (* count 100) skkdic-okuri-nasi-entries-count)
310 count (1+ count))
311 (if (/= (/ prev-ratio 10) (/ ratio 10))
312 (progn
313 (message "processed %2d%% ..." ratio)
314 (setq prev-ratio ratio)))
315 (if (setq candidates
316 (skkdic-reduced-candidates skkbuf kana candidates))
317 (progn
318 (insert "\"" kana)
319 (while candidates
320 (insert " " (car candidates))
321 (setq candidates (cdr candidates)))
322 (insert "\"\n"))))
323 (setq l (cdr l))))
324 (insert ")\n\n")))
326 (defun skkdic-convert (filename &optional dirname)
327 "Generate Emacs Lisp file form Japanese dictionary file FILENAME.
328 The format of the dictionary file should be the same as SKK dictionaries.
329 Optional argument DIRNAME if specified is the directory name under which
330 the generated Emacs Lisp is saved.
331 The name of generated file is specified by the variable `ja-dic-filename'."
332 (interactive "FSKK dictionary file: ")
333 (message "Reading file \"%s\" ..." filename)
334 (let* ((coding-system-for-read 'euc-japan)
335 (skkbuf (get-buffer-create " *skkdic-unannotated*"))
336 (buf (get-buffer-create "*skkdic-work*")))
337 ;; Set skkbuf to an unannotated copy of the dictionary.
338 (with-current-buffer skkbuf
339 (insert-file-contents (expand-file-name filename))
340 (re-search-forward "^[^;]")
341 (while (re-search-forward ";[^\n/]*/" nil t)
342 (replace-match "/")))
343 ;; Setup and generate the header part of working buffer.
344 (with-current-buffer buf
345 (erase-buffer)
346 (buffer-disable-undo)
347 (insert ";;; ja-dic.el --- dictionary for Japanese input method\n"
348 ";;\tGenerated by the command `skkdic-convert'\n"
349 ";;\tOriginal SKK dictionary file: "
350 (file-relative-name (expand-file-name filename) dirname)
351 "\n\n"
352 ";; This file is part of GNU Emacs.\n\n"
353 ";;; Start of the header of the original SKK dictionary.\n\n")
354 (set-buffer skkbuf)
355 (goto-char 1)
356 (let (pos)
357 (search-forward ";; okuri-ari")
358 (forward-line 1)
359 (setq pos (point))
360 (set-buffer buf)
361 (insert-buffer-substring skkbuf 1 pos))
362 (insert "\n"
363 ";;; Code:\n\n(eval-when-compile (require 'ja-dic-cnv))\n\n")
365 ;; Generate the body part of working buffer.
366 (set-buffer skkbuf)
367 (let ((from (point))
369 ;; Convert okuri-ari entries.
370 (search-forward ";; okuri-nasi")
371 (beginning-of-line)
372 (setq to (point))
373 (narrow-to-region from to)
374 (skkdic-convert-okuri-ari skkbuf buf)
375 (widen)
377 ;; Convert okuri-nasi postfix entries.
378 (goto-char to)
379 (forward-line 1)
380 (setq from (point))
381 (re-search-forward "^\\cH")
382 (setq to (match-beginning 0))
383 (narrow-to-region from to)
384 (skkdic-convert-postfix skkbuf buf)
385 (widen)
387 ;; Convert okuri-nasi prefix entries.
388 (goto-char to)
389 (skkdic-convert-prefix skkbuf buf)
392 (skkdic-collect-okuri-nasi)
394 ;; Convert okuri-nasi general entries.
395 (skkdic-convert-okuri-nasi skkbuf buf)
397 ;; Postfix
398 (with-current-buffer buf
399 (goto-char (point-max))
400 (insert ";;\n(provide 'ja-dic)\n\n"
401 ";; Local Variables:\n"
402 ";; version-control: never\n"
403 ";; no-update-autoloads: t\n"
404 ";; coding: utf-8\n"
405 ";; End:\n\n"
406 ";;; ja-dic.el ends here\n")))
408 ;; Save the working buffer.
409 (set-buffer buf)
410 (set-visited-file-name (expand-file-name ja-dic-filename dirname) t)
411 (set-buffer-file-coding-system 'utf-8)
412 (save-buffer 0))
413 (kill-buffer skkbuf)
414 (switch-to-buffer buf)))
416 (defun batch-skkdic-convert ()
417 "Run `skkdic-convert' on the files remaining on the command line.
418 Use this from the command line, with `-batch';
419 it won't work in an interactive Emacs.
420 For example, invoke:
421 % emacs -batch -l ja-dic-cnv -f batch-skkdic-convert SKK-JISYO.L
422 to generate \"ja-dic.el\" from SKK dictionary file \"SKK-JISYO.L\".
423 To get complete usage, invoke:
424 % emacs -batch -l ja-dic-cnv -f batch-skkdic-convert -h"
425 (defvar command-line-args-left) ; Avoid compiler warning.
426 (if (not noninteractive)
427 (error "`batch-skkdic-convert' should be used only with -batch"))
428 (if (string= (car command-line-args-left) "-h")
429 (progn
430 (message "To convert SKK-JISYO.L into skkdic.el:")
431 (message " %% emacs -batch -l ja-dic-cnv -f batch-skkdic-convert SKK-JISYO.L")
432 (message "To convert SKK-JISYO.L into DIR/ja-dic.el:")
433 (message " %% emacs -batch -l ja-dic-cnv -f batch-skkdic-convert -dir DIR SKK-JISYO.L"))
434 (let (targetdir filename)
435 (if (string= (car command-line-args-left) "-dir")
436 (progn
437 (setq command-line-args-left (cdr command-line-args-left))
438 (setq targetdir (expand-file-name (car command-line-args-left)))
439 (setq command-line-args-left (cdr command-line-args-left))))
440 (setq filename (expand-file-name (car command-line-args-left)))
441 (skkdic-convert filename targetdir)))
442 (kill-emacs 0))
445 ;; The following macros are expanded at byte-compiling time so that
446 ;; compiled code can be loaded quickly.
448 (defun skkdic-get-kana-compact-codes (kana)
449 (let* ((len (length kana))
450 (vec (make-vector len 0))
451 (i 0)
453 (while (< i len)
454 (setq ch (aref kana i))
455 (aset vec i
456 (if (< ch 128) ; CH is an ASCII letter for OKURIGANA,
457 (- ch) ; represented by a negative code.
458 (if (= ch ?\e$B!<\e(B) ; `\e$B!<\e(B' is represented by 0.
460 (- (logand (encode-char ch 'japanese-jisx0208) #xFF) 32))))
461 (setq i (1+ i)))
462 vec))
464 (defun skkdic-extract-conversion-data (entry)
465 (string-match "^\\cj+[a-z]* " entry)
466 (let ((kana (substring entry (match-beginning 0) (1- (match-end 0))))
467 (i (match-end 0))
468 candidates)
469 (while (string-match "[^ ]+" entry i)
470 (setq candidates (cons (match-string 0 entry) candidates))
471 (setq i (match-end 0)))
472 (cons (skkdic-get-kana-compact-codes kana) candidates)))
474 (defmacro skkdic-set-okuri-ari (&rest entries)
475 `(defconst skkdic-okuri-ari
476 ',(let ((l entries)
477 (map '(skkdic-okuri-ari))
478 entry)
479 (while l
480 (setq entry (skkdic-extract-conversion-data (car l)))
481 (set-nested-alist (car entry) (cdr entry) map)
482 (setq l (cdr l)))
483 map)))
485 (defmacro skkdic-set-postfix (&rest entries)
486 `(defconst skkdic-postfix
487 ',(let ((l entries)
488 (map '(nil))
489 (longest 1)
490 len entry)
491 (while l
492 (setq entry (skkdic-extract-conversion-data (car l)))
493 (setq len (length (car entry)))
494 (if (> len longest)
495 (setq longest len))
496 (let ((entry2 (lookup-nested-alist (car entry) map nil nil t)))
497 (if (consp (car entry2))
498 (let ((conversions (cdr entry)))
499 (while conversions
500 (if (not (member (car conversions) (car entry2)))
501 (setcar entry2 (cons (car conversions) (car entry2))))
502 (setq conversions (cdr conversions))))
503 (set-nested-alist (car entry) (cdr entry) map)))
504 (setq l (cdr l)))
505 (setcar map longest)
506 map)))
508 (defmacro skkdic-set-prefix (&rest entries)
509 `(defconst skkdic-prefix
510 ',(let ((l entries)
511 (map '(nil))
512 (longest 1)
513 len entry)
514 (while l
515 (setq entry (skkdic-extract-conversion-data (car l)))
516 (setq len (length (car entry)))
517 (if (> len longest)
518 (setq longest len))
519 (let ((entry2 (lookup-nested-alist (car entry) map len nil t)))
520 (if (consp (car entry2))
521 (let ((conversions (cdr entry)))
522 (while conversions
523 (if (not (member (car conversions) (car entry2)))
524 (setcar entry2 (cons (car conversions) (car entry2))))
525 (setq conversions (cdr conversions))))
526 (set-nested-alist (car entry) (cdr entry) map len)))
527 (setq l (cdr l)))
528 (setcar map longest)
529 map)))
531 (defmacro skkdic-set-okuri-nasi (&rest entries)
532 `(defconst skkdic-okuri-nasi
533 ',(let ((l entries)
534 (map '(skdic-okuri-nasi))
535 (count 0)
536 entry)
537 (while l
538 (setq count (1+ count))
539 (if (= (% count 10000) 0)
540 (message "%d entries" count))
541 (setq entry (skkdic-extract-conversion-data (car l)))
542 (set-nested-alist (car entry) (cdr entry) map)
543 (setq l (cdr l)))
544 map)))
546 (provide 'ja-dic-cnv)
548 ;; Local Variables:
549 ;; coding: iso-2022-7bit
550 ;; End:
552 ;;; ja-dic-cnv.el ends here