zlocalize 4.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,756 @@
1
+ # -*- encoding : utf-8 -*-
2
+
3
+ require 'rdoc'
4
+ require 'rdoc/options'
5
+ require 'rdoc/ruby_lex'
6
+ require File.join(File.dirname(__FILE__),'translation_file')
7
+ require File.join(File.dirname(__FILE__),'harvester')
8
+
9
+ module ZLocalize
10
+
11
+ # base class for Expressions we're interested in
12
+ class Expression #:nodoc: all
13
+ attr_accessor :line_no
14
+ attr_accessor :char_no
15
+ attr_accessor :text
16
+ attr_accessor :sub_method
17
+
18
+ def initialize(line_no,char_no,text = '')
19
+ @line_no, @char_no, @text = line_no, char_no, text
20
+ end
21
+
22
+ def set_text(t)
23
+ @text = t
24
+ self
25
+ end
26
+
27
+ # puts spaces before the output generated by +display+. Used for debugging purposes.
28
+ def prefix(indent)
29
+ ' ' * (indent*INDENT_STEP)
30
+ end
31
+
32
+ # display this Expression as a string. Used for debugging purposes
33
+ def display(indent = 0)
34
+ i = prefix(indent)
35
+ i << "#{self.class.name} (#{line_no},#{char_no}): #{@text}\n"
36
+ end
37
+
38
+ # Since Expression is an abstract class, no output is generated
39
+ # (descendants redefine this method)
40
+ def to_translation_entry(filename)
41
+ nil
42
+ end
43
+
44
+ def to_entry_string
45
+ @text.force_encoding("UTF-8")
46
+ end
47
+
48
+ def is_translate_call
49
+ false
50
+ end
51
+ end # class Expression
52
+
53
+ # Basic identifier expression
54
+ class IdentifierExpression < Expression #:nodoc: all
55
+ attr_accessor :name
56
+ attr_accessor :parameters
57
+
58
+ def initialize(line_no,char_no,name)
59
+ @name = name
60
+ @parameters = []
61
+ @sub_method = nil
62
+ super(line_no,char_no)
63
+ end
64
+
65
+ # Used for debugging
66
+ def display(indent = 0)
67
+ i = prefix(indent)
68
+ i << "#{self.class.name} (#{line_no},#{char_no}): #{@name} (#{parameters.size} parameters)\n"
69
+ @parameters.each { |p| i << p.display(indent+2) }
70
+ unless @sub_method.nil?
71
+ i << prefix(indent+1) + "sub-method:\n"
72
+ i << @sub_method.display(indent+2)
73
+ end
74
+ i
75
+ end
76
+
77
+ # Convert ourselves to a Hash or an Array of Hashes which each represent
78
+ # a call to a translate method.
79
+ # Will only output something if this expression is an actual call to <tt>_</tt> or <tt>n_</tt>
80
+ def to_translation_entry(filename)
81
+ if ['_','n_'].include?(@name)
82
+ params = @parameters.dup
83
+ elsif @name == 'ZLocalize' && @sub_method.is_a?(IdentifierExpression) && ['pluralize','translate'].include?(@sub_method.name)
84
+ params = @sub_method.parameters.dup
85
+ else
86
+ return nil
87
+ end
88
+ if params.size > 0
89
+ p1 = params.shift
90
+ # we only collect litteral strings and Arrays
91
+ entry = []
92
+ if [StringExpression,ArrayExpression].include?(p1.class)
93
+ entry << TranslationEntry.new( 'plural' => @name == 'n_',
94
+ 'source' => p1.to_entry_string,
95
+ 'references' => [ "#{filename}:#{p1.line_no}" ])
96
+ end
97
+ # collect all other parameters to this call, in case they are themselves calls to nested _("")
98
+ params.each { |p| entry << p.to_translation_entry(filename) }
99
+ return entry.flatten.compact
100
+ else
101
+ nil
102
+ end
103
+ end
104
+
105
+ def to_entry_string
106
+ "(identifier '#{name}')".force_encoding("UTF-8")
107
+ end
108
+
109
+ end # class IdentifierExpression
110
+
111
+ # a String Expression (litteral occurence of a string)
112
+ class StringExpression < IdentifierExpression #:nodoc: all
113
+
114
+ def display(indent = 0)
115
+ i = prefix(indent)
116
+ i << "#{self.class.name} (#{line_no},#{char_no}): #{@name}\n"
117
+ unless @sub_method.nil?
118
+ i << prefix(indent+1) + "sub-method:\n"
119
+ i << @sub_method.display(indent+2)
120
+ end
121
+ i
122
+ end
123
+
124
+ def to_entry_string
125
+ ZLocalize.clean_ruby_string(@name).force_encoding("UTF-8")
126
+ # @name
127
+ end
128
+
129
+ end #class StringExpression
130
+
131
+ # a Number Expression
132
+ class NumberExpression < IdentifierExpression #:nodoc: all
133
+
134
+ def display(indent = 0)
135
+ i = prefix(indent)
136
+ i << "#{self.class.name} (#{line_no},#{char_no}): #{@name}\n"
137
+ unless @sub_method.nil?
138
+ i << prefix(indent+1) + "sub-method:\n"
139
+ i << @sub_method.display(indent+2)
140
+ end
141
+ i
142
+ end
143
+
144
+ def to_entry_string
145
+ "\"(number '#{@name}')\"".force_encoding("UTF-8")
146
+ end
147
+
148
+ end # class NumberExpression
149
+
150
+ # a Symbol Expression
151
+ class SymbolExpression < IdentifierExpression #:nodoc: all
152
+
153
+ def display(indent = 0)
154
+ i = prefix(indent)
155
+ i << "#{self.class.name} (#{line_no},#{char_no}): #{@name}\n"
156
+ unless @sub_method.nil?
157
+ i << prefix(indent+1) + "sub-method:\n"
158
+ i << @sub_method.display(indent+2)
159
+ end
160
+ i
161
+ end
162
+
163
+ def to_entry_string
164
+ "\"(symbol #{@name})\"".force_encoding("UTF-8")
165
+ end
166
+
167
+ end # class SymbolExpression
168
+
169
+ class RangeExpression < IdentifierExpression #:nodoc: all
170
+ attr_accessor :low
171
+ attr_accessor :high
172
+
173
+ def display(indent = 0)
174
+ p = prefix(indent)
175
+ i = p + "#{self.class.name} (#{line_no},#{char_no})\n"
176
+ i << p + "low:\n" + low.display(indent+1)
177
+ i << p + "high:\n" + high.display(indent+1)
178
+ i << "\n"
179
+ end
180
+
181
+ def to_entry_string
182
+ return "\"(range)\"".force_encoding("UTF-8")
183
+ end
184
+
185
+ end # class RangeExpression
186
+
187
+ # an Array declaration. It will hold its elements as a list of Expressions
188
+ class ArrayExpression < IdentifierExpression #:nodoc: all
189
+
190
+ attr_accessor :elements
191
+
192
+ def initialize(line_no,char_no)
193
+ super(line_no,char_no,'array')
194
+ @elements = []
195
+ end
196
+
197
+ def display(indent = 0)
198
+ i = super(indent)
199
+ @elements.each { |e| i << e.display(indent+1) }
200
+ i
201
+ end
202
+
203
+ def to_translation_entry(filename)
204
+ @elements.collect { |elem| elem.to_translation_entry(filename) }.flatten.compact
205
+ end
206
+
207
+ def to_entry_string
208
+ @elements.collect { |e| e.to_entry_string }
209
+ # '[' + @elements.collect { |e| e.to_entry_string }.join(', ') + ']'
210
+ end
211
+
212
+ end # class ArrayExpression
213
+
214
+ # a Hash declaration. Elements will be a Hash in which each element
215
+ # will itself be a HashElementExpression like this:
216
+ # { :key => Expression, :value => Expression }
217
+ class HashExpression < IdentifierExpression #:nodoc: all
218
+
219
+ attr_accessor :elements
220
+
221
+ def initialize(line_no,char_no)
222
+ super(line_no,char_no,'hash')
223
+ @elements = []
224
+ end
225
+
226
+ def display(indent = 0)
227
+ i = super(indent)
228
+ @elements.each { |e| i << e.display(indent) }
229
+ i
230
+ end
231
+
232
+ def to_translation_entry(filename)
233
+ @elements.collect { |elem| elem.to_translation_entry(filename) }.flatten.compact
234
+ end
235
+
236
+ def to_entry_string
237
+ '{ ' + @elements.collect { |e| e.to_entry_string }.compact.join(',') + ' }'.force_encoding("UTF-8")
238
+ end
239
+
240
+ end # class HashExpression
241
+
242
+ # a Hash Element. It holds the key and the value of the element as Expressions
243
+ class HashElementExpression < IdentifierExpression #:nodoc: all
244
+ attr_accessor :key
245
+ attr_accessor :value
246
+
247
+ def initialize(line_no,char_no)
248
+ super(line_no,char_no,'hashElement')
249
+ end
250
+
251
+ def display(indent = 0)
252
+ i = super(indent)
253
+ i << prefix(indent) + "key:\n" + @key.display(indent+1)
254
+ i << prefix(indent) + "value:\n" + @value.display(indent+1)
255
+ end
256
+
257
+ def to_translation_entry(filename)
258
+ r = [key.to_translation_entry(filename),value.to_translation_entry(filename)].flatten.compact
259
+ end
260
+
261
+ def to_entry_string
262
+ "#{key.to_entry_string} => #{value.to_entry_string}".force_encoding("UTF-8")
263
+ end
264
+
265
+ end # class HashElementExpression
266
+
267
+ # an Operator expression holds the operator itself, and the operands
268
+ class OperatorExpression < Expression #:nodoc: all
269
+
270
+ attr_accessor :operator
271
+ attr_accessor :operands
272
+
273
+ def initialize(line_no,char_no,op)
274
+ super(line_no,char_no,op)
275
+ @operands = []
276
+ @operator = op
277
+ end
278
+
279
+ def display(indent = 0)
280
+ p = prefix(indent)
281
+ i = p + "#{self.class.name} (#{line_no},#{char_no})\n"
282
+ @operands.each { |o| i << o.display(indent+1) }
283
+ i
284
+ end
285
+
286
+ def to_translation_entry(filename)
287
+ @operands.collect { |o| o.to_translation_entry(filename) }.flatten.compact
288
+ end
289
+
290
+ def to_entry_string
291
+ @operands.collect { |o| o.to_entry_string }.join(" #{@operator} ").force_encoding("UTF-8")
292
+ end
293
+
294
+ end # class OperatorExpression
295
+
296
+ # represents an 'inline-if' expression, as in:
297
+ # something ? when_true : when_false
298
+ class ConditionalExpression < Expression #:nodoc: all
299
+ attr_accessor :condition
300
+ attr_accessor :true_expr
301
+ attr_accessor :false_expr
302
+
303
+ def initialize(line_to,char_no)
304
+ super(line_to,char_no,'?')
305
+ end
306
+
307
+ def display(indent = 0)
308
+ i = prefix(indent) + "#{self.class.name} (#{line_no},#{char_no})\n"
309
+ i << prefix(indent+1) + "condition:\n"
310
+ i << @condition.display(indent+2)
311
+ i << prefix(indent+1) + "when true:\n"
312
+ i << @true_expr.display(indent+2)
313
+ i << prefix(indent+1) + "when false:\n"
314
+ i << @false_expr.display(indent+2)
315
+ i
316
+ end
317
+
318
+ def to_translation_entry(filename)
319
+ [condition.to_translation_entry(filename),
320
+ true_expr.to_translation_entry(filename),
321
+ false_expr.to_translation_entry(filename)].flatten.compact
322
+ end
323
+
324
+ def to_entry_string
325
+ "#{conditions.as_entry_string} ? #{true_expr.as_entry_string} : #{false_expr.as_entry_string}".force_encoding("UTF-8")
326
+ end
327
+
328
+ end # class ConditionalExpression
329
+
330
+ # any other Expression that we don't really care about
331
+ class DontCareExpression < Expression #:nodoc: all
332
+ end
333
+
334
+
335
+ OPERATOR_TOKENS = [RDoc::RubyToken::TkGT, RDoc::RubyToken::TkLT, RDoc::RubyToken::TkPLUS,
336
+ RDoc::RubyToken::TkMINUS, RDoc::RubyToken::TkMULT, RDoc::RubyToken::TkSTAR,
337
+ RDoc::RubyToken::TkDIV,
338
+ RDoc::RubyToken::TkMOD, RDoc::RubyToken::TkBITOR, RDoc::RubyToken::TkBITXOR,
339
+ RDoc::RubyToken::TkBITAND, RDoc::RubyToken::TkBITNOT, RDoc::RubyToken::TkNOTOP,
340
+ RDoc::RubyToken::TkPOW, RDoc::RubyToken::TkCMP, RDoc::RubyToken::TkEQ,
341
+ RDoc::RubyToken::TkEQQ, RDoc::RubyToken::TkNEQ, RDoc::RubyToken::TkGEQ,
342
+ RDoc::RubyToken::TkLEQ, RDoc::RubyToken::TkANDOP, RDoc::RubyToken::TkOROP,
343
+ RDoc::RubyToken::TkMATCH, RDoc::RubyToken::TkNMATCH, RDoc::RubyToken::TkLSHFT,
344
+ RDoc::RubyToken::TkRSHFT]
345
+
346
+ UNARY_OP_TOKENS = [RDoc::RubyToken::TkPLUS,RDoc::RubyToken::TkMINUS,RDoc::RubyToken::TkBITNOT,RDoc::RubyToken::TkNOTOP]
347
+
348
+
349
+ # Parses a Ruby (or ERB) file and generates a list of language tokens
350
+ class SourceParser
351
+
352
+ attr_accessor :translate_calls
353
+ attr_accessor :in_hash
354
+ attr_accessor :filename
355
+ attr_accessor :root
356
+
357
+ def initialize(filename, root, is_erb = false)
358
+ @in_hash = 0
359
+ @translate_calls = []
360
+ @root = File.expand_path(root).downcase
361
+ @filename = File.expand_path(filename).downcase
362
+ @relative_filename = @filename.gsub(@root,'')
363
+ content = File.open(filename, "r") { |f| f.read }
364
+ if is_erb
365
+ content = ERB.new(content).src
366
+ end
367
+
368
+ @lex = RDoc::RubyLex.new(content,RDoc::Options.new)
369
+ @token_list = []
370
+ while tk = @lex.token
371
+ @token_list << tk
372
+ end
373
+ @token_index = -1
374
+ @last_token_index = @token_list.size
375
+ end
376
+
377
+ def get_tk
378
+ @token_index += 1
379
+ if @token_index < @last_token_index
380
+ @tk = @token_list[@token_index]
381
+ else
382
+ @tk = nil
383
+ end
384
+ @tk
385
+ end
386
+
387
+ def peek_tk
388
+ peek_index = @token_index + 1
389
+ return peek_index < @last_token_index ? @token_list[peek_index] : nil
390
+ end
391
+
392
+ def rewind
393
+ @token_index = -1
394
+ end
395
+
396
+ def skip_white_space
397
+ while [RDoc::RubyToken::TkNL, RDoc::RubyToken::TkSPACE].include?(@tk.class)
398
+ get_tk
399
+ end
400
+ end
401
+
402
+ # simply list the tokens in the source file
403
+ def list_tokens
404
+ while get_tk
405
+ puts @tk.inspect
406
+ end
407
+ end
408
+
409
+ def parse_error(tk,msg)
410
+ raise(ParseError,"\n\nIn file #{@filename}, on line #{tk.line_no} at position #{tk.char_no}: " + msg + "\n\n")
411
+ end
412
+
413
+ # we simply detect calls to the ZLocalize methods and parse their parameter list
414
+ def parse
415
+ rewind
416
+ while get_tk
417
+ if @tk.is_a?(RDoc::RubyToken::TkCONSTANT) && @tk.text = 'ZLocalize'
418
+ get_tk # should be a dot
419
+ get_tk # either 'translate' or 'pluralize'
420
+ parse_translate_call if ['translate','pluralize'].include?(@tk.text)
421
+ elsif @tk.is_a?(RDoc::RubyToken::TkIDENTIFIER)
422
+ parse_translate_call if ['_','n_'].include?(@tk.text)
423
+ end
424
+ end
425
+ end
426
+
427
+ # parse a call to one of the translation methods <tt>_(...)</tt>, <tt>n_(...)</tt>
428
+ def parse_translate_call
429
+ m = IdentifierExpression.new(@tk.line_no,@tk.char_no,@tk.text)
430
+ get_tk
431
+ skip_white_space
432
+ expect_r_paren = @tk.is_a?(RDoc::RubyToken::TkLPAREN)
433
+ get_tk if expect_r_paren
434
+ m.parameters = parse_parameters
435
+ if expect_r_paren && !@tk.is_a?(RDoc::RubyToken::TkRPAREN)
436
+ parse_error(@tk,"')' expected but '#{@tk.text}' (#{@tk.class.name}) found")
437
+ end
438
+ @translate_calls << m
439
+ m
440
+ end
441
+
442
+
443
+ # parse the list of actual parameters to a method
444
+ def parse_parameters
445
+ parameters = []
446
+ skip_white_space
447
+ return if @tk.is_a?(RDoc::RubyToken::TkRPAREN)
448
+
449
+ parameters << parse_expression
450
+ skip_white_space
451
+ while @tk.is_a?(RDoc::RubyToken::TkCOMMA) do
452
+ get_tk
453
+ parameters << parse_expression
454
+ skip_white_space
455
+ end
456
+ parameters
457
+ end
458
+
459
+ # parse identifier(...), identifier[...] or identifier{...}
460
+ def parse_identifier
461
+ case @tk
462
+ when RDoc::RubyToken::TkLPAREN
463
+ ident_text = "(Method From Stack)"
464
+ when RDoc::RubyToken::TkfLBRACK
465
+ ident_text = "(Array Access)"
466
+ else
467
+ ident_text = @tk.text
468
+ line, char = @tk.line_no, @tk.char_no
469
+ get_tk
470
+ # check for a Ruby 2.0 Hash key symbol (identifier directly followed by a COLON)
471
+ if @tk.is_a?(RDoc::RubyToken::TkCOLON) || @tk.is_a?(RDoc::RubyToken::TkSYMBEG)
472
+ expr = SymbolExpression.new(line,char,'')
473
+ expr.set_text(ident_text + ':')
474
+ expr.name = ident_text + ':'
475
+ get_tk
476
+ return expr
477
+ end
478
+ end
479
+ ident = IdentifierExpression.new(@tk.line_no,@tk.char_no,ident_text)
480
+ while(@tk.is_a?(RDoc::RubyToken::TkCOLON2))
481
+ get_tk
482
+ ident.name << '::' + @tk.text
483
+ get_tk
484
+ end
485
+
486
+ case @tk
487
+ when RDoc::RubyToken::TkLPAREN
488
+ get_tk
489
+ ident.parameters = parse_parameters
490
+ unless @tk.is_a?(RDoc::RubyToken::TkRPAREN)
491
+ parse_error(@tk,"')' expected but '#{@tk.text}' found")
492
+ end
493
+ get_tk
494
+ when RDoc::RubyToken::TkfLBRACK
495
+ get_tk
496
+ ident.parameters = parse_parameters
497
+ unless @tk.is_a?(RDoc::RubyToken::TkRBRACK)
498
+ parse_error(@tk,"']' expected but '#{@tk.text}' found")
499
+ end
500
+ get_tk
501
+ end # case @tk
502
+ parse_identifier_method_call(ident)
503
+ end # parse_identifier
504
+
505
+ # parse the additional methods in an expression
506
+ # for example:
507
+ # self.method.sub_method(1).another_sub_method(2)
508
+ def parse_identifier_method_call(ident)
509
+ case @tk
510
+ when RDoc::RubyToken::TkDOT
511
+ get_tk
512
+ ident.sub_method = parse_identifier
513
+ when RDoc::RubyToken::TkLPAREN, RDoc::RubyToken::TkfLBRACK
514
+ ident.sub_method = parse_identifier
515
+ end
516
+ ident
517
+ end
518
+
519
+ # check if the expression currently being parsed as a range specifier
520
+ def check_for_range_expression(expr)
521
+ skip_white_space
522
+ if [RDoc::RubyToken::TkDOT2,RDoc::RubyToken::TkDOT3].include?(@tk.class)
523
+ range_expr = RangeExpression.new(expr.line_no,expr.char_no,'')
524
+ range_expr.low = expr
525
+ get_tk
526
+ range_expr.high = parse_expression
527
+ return range_expr
528
+ else
529
+ return expr
530
+ end
531
+ end
532
+
533
+ # check if the expression is a hash element declaration (i.e. is it followed by <tt>=></tt>)
534
+ def check_for_hash_element_expression(expr)
535
+ return expr if @in_hash > 0
536
+ if expr.is_a?(SymbolExpression) && expr.text =~ /:$/
537
+ he_expr = HashElementExpression.new(expr.line_no,expr.char_no)
538
+ he_expr.key = expr
539
+ skip_white_space
540
+ he_expr.value = parse_expression
541
+ # get_tk
542
+ return he_expr
543
+ else
544
+ skip_white_space
545
+ if @tk.is_a?(RDoc::RubyToken::TkASSIGN)
546
+ get_tk
547
+ if @tk.is_a?(RDoc::RubyToken::TkGT)
548
+ he_expr = HashElementExpression.new(expr.line_no,expr.char_no)
549
+ he_expr.key = expr
550
+ get_tk
551
+ he_expr.value = parse_expression
552
+ return he_expr
553
+ end
554
+ end
555
+ end
556
+ return expr
557
+ end
558
+
559
+ # parse an Expression, which can be either a simple identifier,
560
+ # a method call with parameters, an operator expression (as in 1 + 1)
561
+ # or an 'inline-if' expression
562
+ def parse_expression
563
+ skip_white_space
564
+ expr1 = parse_operand
565
+ skip_white_space
566
+ if OPERATOR_TOKENS.include?(@tk.class)
567
+ expr = OperatorExpression.new(expr1.line_no,expr1.char_no,'')
568
+ expr.operands << expr1
569
+ while OPERATOR_TOKENS.include?(@tk.class)
570
+ get_tk
571
+ expr.operands << parse_operand
572
+ end
573
+ else
574
+ expr = expr1
575
+ end
576
+ skip_white_space
577
+ if @tk.is_a?(RDoc::RubyToken::TkQUESTION)
578
+ get_tk
579
+ expr2 = ConditionalExpression.new(expr.line_no,expr.char_no)
580
+ expr2.condition = expr1
581
+ expr2.true_expr = parse_expression
582
+ skip_white_space
583
+ unless @tk.is_a?(RDoc::RubyToken::TkCOLON)
584
+ parse_error(@tk,"':' expected but #{@tk.text} found")
585
+ end
586
+ get_tk
587
+ expr2.false_expr = parse_expression
588
+ expr = expr2
589
+ end
590
+ expr
591
+ end
592
+
593
+ # parse and return an operand to an OperatorExpression
594
+ def parse_operand
595
+ skip_white_space
596
+ while UNARY_OP_TOKENS.include?(@tk.class)
597
+ get_tk
598
+ skip_white_space
599
+ end
600
+ case @tk
601
+ when RDoc::RubyToken::TkLPAREN, RDoc::RubyToken::TkfLPAREN
602
+ get_tk
603
+ expr = parse_expression
604
+ unless @tk.is_a?(RDoc::RubyToken::TkRPAREN)
605
+ parse_error(@tk,"')' expected but #{@tk.text} found (unmatched parenthesis)")
606
+ end
607
+ get_tk
608
+ expr = parse_identifier_method_call(expr) # ident.something
609
+ expr = check_for_range_expression(expr)
610
+ expr = check_for_hash_element_expression(expr)
611
+ when RDoc::RubyToken::TkSTRING, RDoc::RubyToken::TkDSTRING, RDoc::RubyToken::TkXSTRING, RDoc::RubyToken::TkREGEXP
612
+ expr = StringExpression.new(@tk.line_no,@tk.char_no, @tk.text)
613
+ get_tk
614
+ # check for Ruby 2.2 Hash symbol (string directly followed by a COLON)
615
+ if @tk.is_a?(RDoc::RubyToken::TkCOLON)
616
+ expr = SymbolExpression.new(expr.line_no,expr.char_no,expr.name + ':')
617
+ expr.text = expr.name
618
+ get_tk
619
+ end
620
+ expr = parse_identifier_method_call(expr)
621
+ expr = check_for_range_expression(expr)
622
+ expr = check_for_hash_element_expression(expr)
623
+ when RDoc::RubyToken::TkINTEGER, RDoc::RubyToken::TkFLOAT, RDoc::RubyToken::TkTRUE, RDoc::RubyToken::TkFALSE
624
+ expr = NumberExpression.new(@tk.line_no,@tk.char_no,@tk.text)
625
+ get_tk
626
+ expr = parse_identifier_method_call(expr)
627
+ expr = check_for_range_expression(expr)
628
+ expr = check_for_hash_element_expression(expr)
629
+ when RDoc::RubyToken::TkSYMBEG
630
+ expr = SymbolExpression.new(@tk.line_no,@tk.char_no,'')
631
+ get_tk
632
+ if @tk.text =~ /^[a-z]{1,}[a-z0-9\_]*?$/ #is_a?(RDoc::RubyToken::TkIDENTIFIER)
633
+ expr.set_text(':' + @tk.text)
634
+ expr.name = ':' + @tk.text
635
+ get_tk
636
+ expr = parse_identifier_method_call(expr)
637
+ expr = check_for_hash_element_expression(expr)
638
+ else
639
+ parse_error(@tk,"':' not followed by identifier or operator")
640
+ end
641
+ when RDoc::RubyToken::TkGVAR, RDoc::RubyToken::TkIVAR, RDoc::RubyToken::TkSELF, RDoc::RubyToken::TkNIL
642
+ expr = parse_identifier
643
+ when RDoc::RubyToken::TkCONSTANT, RDoc::RubyToken::TkIDENTIFIER
644
+ expr = parse_identifier
645
+ expr = check_for_hash_element_expression(expr)
646
+ when RDoc::RubyToken::TkLBRACK
647
+ expr = parse_array
648
+ expr = parse_identifier_method_call(expr)
649
+ when RDoc::RubyToken::TkLBRACE
650
+ expr = parse_hash
651
+ expr = parse_identifier_method_call(expr)
652
+ when RDoc::RubyToken::TkKW
653
+ # check for a Ruby 2.0 syntax hash key symbol (such as end: begin: class: case: etc...)
654
+ if peek_tk.is_a?(RDoc::RubyToken::TkCOLON)
655
+ ident_text, line, char = @tk.text, @tk.line_no, @tk.char_no
656
+ get_tk
657
+ expr = SymbolExpression.new(line,char,'')
658
+ expr.set_text(ident_text + ':')
659
+ expr.name = ident_text + ':'
660
+ get_tk
661
+ expr = check_for_hash_element_expression(expr)
662
+ end
663
+ end # case @tk
664
+ expr
665
+ end
666
+
667
+ # parse an Array declaration <tt>[...]</tt>
668
+ def parse_array
669
+ get_tk
670
+ array_expr = ArrayExpression.new(@tk.line_no,@tk.char_no)
671
+ skip_white_space
672
+ array_expr.elements << parse_expression
673
+ skip_white_space
674
+ while @tk.is_a?(RDoc::RubyToken::TkCOMMA) do
675
+ get_tk
676
+ array_expr.elements << parse_expression
677
+ skip_white_space
678
+ end
679
+ unless @tk.is_a?(RDoc::RubyToken::TkRBRACK)
680
+ parse_error(@tk,"']' expected but '#{@tk.text}' found")
681
+ end
682
+ get_tk
683
+ array_expr
684
+ end
685
+
686
+ # Parse a +Hash+ declaration <tt>{ ... }</tt>
687
+ def parse_hash
688
+ @in_hash += 1
689
+ get_tk
690
+ hash_expr = HashExpression.new(@tk.line_no,@tk.char_no)
691
+ skip_white_space
692
+ hash_expr.elements << parse_hash_element
693
+ skip_white_space
694
+ while @tk.is_a?(RDoc::RubyToken::TkCOMMA) do
695
+ get_tk
696
+ hash_expr.elements << parse_hash_element
697
+ skip_white_space
698
+ end
699
+ unless @tk.is_a?(RDoc::RubyToken::TkRBRACE)
700
+ parse_error(@tk,"'}' expected but '#{@tk.text}' found")
701
+ end
702
+ get_tk
703
+ @in_hash -= 1
704
+ hash_expr
705
+ end
706
+
707
+ # parse a Hash element within a +Hash+ declaration
708
+ def parse_hash_element
709
+ el = HashElementExpression.new(@tk.line_no,@tk.char_no)
710
+ el.key = parse_expression
711
+ if el.key.is_a?(SymbolExpression) && el.key.text =~ /:$/
712
+ skip_white_space
713
+ el.value = parse_expression
714
+ # get_tk
715
+ return el
716
+ else
717
+ skip_white_space
718
+ if @tk.is_a?(RDoc::RubyToken::TkASSIGN)
719
+ get_tk
720
+ if @tk.is_a?(RDoc::RubyToken::TkGT)
721
+ get_tk
722
+ skip_white_space
723
+ el.value = parse_expression
724
+ skip_white_space
725
+ return el
726
+ end
727
+ end
728
+ end
729
+ parse_error(@tk,"'=>' expected but '#{@tk.text}' found")
730
+ end
731
+
732
+ # return a Hash of all translation entries we collected
733
+ def translation_entries
734
+ entries = {}
735
+ self.translate_calls.each do |c|
736
+ e = c.to_translation_entry(@relative_filename)
737
+ if e.is_a?(Array)
738
+ e.each do |te|
739
+ if entries[te.source]
740
+ entries[te.source].references += te.references
741
+ else
742
+ entries[te.source] = te
743
+ end
744
+ end
745
+ elsif e.is_a?(TranslationEntry)
746
+ if entries[te.source]
747
+ entries[e.source].references += te.references
748
+ else
749
+ entries[e.source] = e
750
+ end
751
+ end
752
+ end
753
+ entries
754
+ end
755
+ end # class SourceParser
756
+ end # module ZLocalize