5a6cf7eb83dbdeedf6ec4d698ef49620d01e143d
[chise/ruby.git] / chise / parser.rb
1 # Copyright (C) 2002-2004 Kouichirou Eto, All rights reserved.
2
3 require "chise/chisedb"
4
5 module CHISE
6   module EntityReference
7     PART    = "&([-+0-9A-Za-z#]+);"
8     ALL     = '\A'+PART+'\Z'
9     PART_RE = Regexp.new(PART)
10     ALL_RE  = Regexp.new(ALL)
11
12     def contain_er?(s)  (PART_RE =~ s) != nil;  end
13     def is_er?(s)       (ALL_RE =~ s)  != nil;  end
14
15     # the order is important.  The primary charset should be selectable.
16     CODESYS_TABLE = [
17       %w( =jis-x0208-1990       J90- 4 X),
18       %w( =jis-x0208-1983       J83- 4 X),
19       %w( =jis-x0208-1978       J78- 4 X),
20       %w( =jis-x0208            J90- 4 X), # \8cp\8f³\82Ì\83A\83h\83z\83b\83N\82È\8eÀ\91\95
21       %w( =jis-x0208            J83- 4 X), # \8cp\8f³\82Ì\83A\83h\83z\83b\83N\82È\8eÀ\91\95
22       %w( =jis-x0208            J78- 4 X), # \8cp\8f³\82Ì\83A\83h\83z\83b\83N\82È\8eÀ\91\95
23       %w( =jis-x0213-1-2000     JX1- 4 X),
24       %w( =jis-x0213-2-2000     JX2- 4 X),
25       %w( =jis-x0212            JSP- 4 X),
26       %w( =big5-cdp             CDP- 4 X),
27       %w( =cns11643-1           C1-  4 X),
28       %w( =cns11643-2           C2-  4 X),
29       %w( =cns11643-3           C3-  4 X),
30       %w( =cns11643-4           C4-  4 X),
31       %w( =cns11643-5           C5-  4 X),
32       %w( =cns11643-6           C6-  4 X),
33       %w( =cns11643-7           C7-  4 X),
34       %w( =ks-x1001             K0-  4 X),
35       %w( =daikanwa             M-   5 d),
36       %w( =cbeta                CB   5 d),
37       %w( =gt                   GT-  5 d),
38       %w( =gt-k                 GT-K 5 d),
39     ]
40     PRIVATE_USE_AREA = 0xe000
41   end
42
43   class CharacterParser
44     include EntityReference
45
46     def parse(c) # parse a value and return a number (MCS)
47       raise "c is nil" if c.nil?
48
49       if c.kind_of?(String)
50         if /\A\?/ =~ c
51           c = c.sub(/\A\?/, "") # remove "?" in the head
52           u4 = c.u8tou32 # translate from UTF-8 to UTF-32
53           return u4.u32to_i # translate UTF-32 to UCS number
54         end
55
56         return parse_er(c) if is_er?(c) # ER?
57
58         return c.to_i if /^\d+$/ =~ c # only numbers?
59
60         raise "unknown format"
61       end
62
63       if c.kind_of?(Numeric)
64         c = 0x80000000 + c if c < 0 # negative value
65         return c.to_i
66       end
67       
68       raise "unknown object"
69     end
70
71     def parse_er(s) # parse a Entity Reference and return a number (MCS)
72       raise "wrong ER." unless ALL_RE =~ s # don't use is_er? for getting $1.
73
74       s = $1 # extract the part of ER
75
76       return $1.hex if s =~ /\AMCS-([0-9A-Fa-f]+)\Z/ # MCS. It's a mystery.
77
78       return $1.hex if s =~ /\AU[-+]?([0-9A-Fa-f]+)\Z/ ||
79           s =~ /\A#x([0-9A-Fa-f]+)\Z/ # Unicode code point in Hex.
80
81       return $1.to_i if s =~ /\A#([0-9]+)\Z/ # Unicode code point in Decimal.
82
83       if s =~ /\Amy-([0-9]+)\Z/ # my own code point. It's a secret.
84         return PRIVATE_USE_AREA + $1.to_i # private use area of Unicode.
85       end
86
87       if s =~ /\AI-/ # I- stands for Isolated character. It's a wonder.
88         s = s.sub(/\AI-/, "")
89       end
90
91       CODESYS_TABLE.each {|codesys, er_prefix, keta, numtype|
92         if numtype == "d"
93           nre = '\d'
94         elsif numtype == "X"
95           nre = "[0-9A-Fa-f]"
96         else
97           next
98         end
99
100         re = "\\A#{er_prefix}(#{nre}{#{keta},#{keta}})\\Z"
101         next unless Regexp.new(re) =~ s
102
103         codestr = $1
104         if numtype == "d"
105           code = codestr.to_i
106         else
107           code = codestr.hex
108         end
109
110         u8 = get_ccs(codesys, code)
111 #       qp s, u8
112         next if u8.nil?
113
114         num = parse(u8)
115         next if num.nil?
116
117         return num
118       }
119
120       raise "unknown Entity Reference"
121     end
122
123     private
124     def get_ccs(ccs, code_point)
125       cd = ChiseDB.instance
126       cd.decode_char(ccs, code_point)
127     end
128   end
129
130   class EntityReferenceParser
131     include EntityReference
132
133     def de_er(s) # replace EntityReference with corresponding character.
134       return s unless PART_RE =~ s # don't use contain_er? to get $1
135
136       er = "&"+$1+";"
137       char = Character.get(er)
138       ss = s.sub(Regexp.new(Regexp.escape(er)), char.utf8_mcs)
139
140       return de_er(ss) if contain_er?(ss) # recursive
141       ss
142     end
143   end
144
145   class EntityReferenceEncoder
146     include EntityReference
147
148     def to_er(char)
149       cid = char.char_id
150       return "&#x%04x;" % cid if cid <=  0xffff
151       return "&#x%05x;" % cid if cid <= 0xfffff
152
153       CODESYS_TABLE.each {|codesys, er_prefix, keta, numtype|
154         code = char[codesys]
155         next if code.nil?
156         return "&#{er_prefix}%0#{keta}#{numtype};" % code
157       }
158
159       "&MCS-%08X;" % cid # the last answer
160     end
161
162     def to_er_by_ccs(cid, codesys) # not yet
163     end
164
165   end
166 end