rutils 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG CHANGED
@@ -1,3 +1,6 @@
1
+ Версия 0.2.1 - 02.04.2007
2
+ * Восстановлен в gemspec gilenson.rb (Сергей Барабаш)
3
+
1
4
  Версия 0.2.0 - 02.04.2007
2
5
  * Фикс squeeze в dirify (Сергей Барабаш)
3
6
  * Формат даты по умолчанию - %F (Сергей Барабаш)
@@ -0,0 +1,617 @@
1
+ module RuTils
2
+ module Gilenson
3
+ # Позволяет возвращать класс форматтера при вызове
4
+ # RuTils::Gilenson.new
5
+ def self.new(*args) #:nodoc:
6
+ RuTils::Gilenson::Formatter.new(*args)
7
+ end
8
+
9
+ # Загружаем "старый" Гиленсон если он будет нужен
10
+ def self.const_missing(const) #:nodoc:
11
+ super(const) unless const == :Obsolete
12
+ require File.dirname(__FILE__) + '/gilenson_port'
13
+ return RuTils::Gilenson::Obsolete
14
+ end
15
+ end
16
+ end
17
+
18
+ # ==Что такое Gilenson
19
+ # Обработчик типографских символов в HTML согласно общепринятым правилам.
20
+ # Посвящается П.Г.Гиленсону[http://www.rudtp.ru/lib.php?book=172], благодаря которому русские правила тех.
21
+ # редактуры еще как минимум 20 лет останутся столь-же бессмысленно старомодными и строгими.
22
+ #
23
+ # Gilenson расставит в тексте "умные" правильные кавычки (русские - для кириллицы, английские - для латиницы),
24
+ # заменит "хитрые" пунктуационные символы на entities и отформатирует знаки типа (c), (tm), телефоны и адреса.
25
+ #
26
+ # Gilenson базируется на коде Typografica[http://pixel-apes.com/typografica] от PixelApes,
27
+ # который был приведен к положенному в Ruby стандарту. Основные отличия Gilenson от Typografica на PHP:
28
+ # * работа только и полностью в UTF-8 (включая entities, применимые в XML)
29
+ # * поддержка "raw"-вывода (символов вместо entities) - текст выводимый GIlenson можно верстать на бумаге
30
+ #
31
+ # Если вам нужно получать идентичный Typografica вывод, пользуйтесь RuTils::Gilenson::Obsolete
32
+ # вместо RuTils::Gilenson::Formatter.
33
+ #
34
+ # ==Использование
35
+ # Быстрее всего - через метод ++gilensize++ для любой строковой переменной
36
+ # %{ И вот они таки "приехали"}.gilensize => 'И вот они таки «приехали»'
37
+ # Все дополнительные настройки в таком случае передаются форматтеру
38
+ # %{ И вот они таки "приехали"}.gilensize(:laquo=>false) => 'И вот они таки "приехали"'
39
+ #
40
+ # Если форматтер надо настроить более тонко, можно использовать его и так:
41
+ # typ = RuTils::Gilenson.new('Эти "так называемые" великие деятели')
42
+ # typ.to_html => 'Эти «так называемые» великие деятели'
43
+ #
44
+ # или как фильтр
45
+ # formatter = RuTils::Gilenson.new
46
+ # formatter.configure(:dash=>true)
47
+ # for string in strings
48
+ # puts formatter.process(string)
49
+ # end
50
+ #
51
+ # ==Настройки
52
+ # Настройки регулируются через методы
53
+ # formatter.dashglue = true
54
+ # или ассоциированным хешем
55
+ # formatter.configure!(:dash=>true, :quotes=>false)
56
+ #
57
+ # Хеш также можно передавать как последний аргумент методам process и to_html,
58
+ # в таком случае настройки будут применены только при этом вызове
59
+ #
60
+ # beautified = formatter.process(my_text, :dash=>true)
61
+ #
62
+ # В параметры можно подставить также ключ :all чтобы временно включить или выключить все фильтры
63
+ #
64
+ # beautified = formatter.process(my_text, :all=>true)
65
+ #
66
+ # Помимо этого можно пользоваться каждым фильтром по отдельности используя метод +apply+
67
+ #
68
+ # Можно менять глифы, которые форматтер использует для подстановок. К примеру,
69
+ # formatter.glyph[:nbsp] = ' '
70
+ # заставит форматтер расставлять "традиционные" неразрывные пробелы. Именно это - большая глупость,
71
+ # но другие глифы заменить может быть нужно.
72
+ #
73
+ # ==Настройки форматтера
74
+ # "inches" - преобразовывать дюймы в знак дюйма;
75
+ # "laquo" - кавычки-ёлочки
76
+ # "quotes" - кавычки-английские лапки
77
+ # "dash" - проставлять короткое тире (150)
78
+ # "emdash" - длинное тире двумя минусами (151)
79
+ # "initials" - проставлять тонкие шпации в инициалах
80
+ # "copypaste" - замена непечатных и "специальных" юникодных символов на entities
81
+ # "(c)" - обрабатывать знак копирайта
82
+ # "(r)", "(tm)", "(p)", "+-" - спецсимволы, какие - понятно
83
+ # "degrees" - знак градуса
84
+ # "dashglue", "wordglue" - приклеивание предлогов и дефисов
85
+ # "spacing" - запятые и пробелы, перестановка
86
+ # "phones" - обработка телефонов
87
+ # "html" - при false - запрет использования тагов html
88
+ # "de_nobr" - при true все <nobr/> заменяются на <span class="nobr"/>
89
+ # "raw_output" - (по умолчанию false) - при true вместо entities выводятся UTF-символы
90
+ # "skip_attr" - (по умолчанию false) - при true не отрабатывать типографику в атрибутах тегов (title, alt)
91
+ # "skip_code" - (по умолчанию true) - при true не отрабатывать типографику внутри <code/>, <tt/>, CDATA
92
+
93
+ class RuTils::Gilenson::Formatter
94
+ attr_accessor :glyph
95
+
96
+ SETTINGS = {
97
+ "inches" => true, # преобразовывать дюймы в знак дюйма;
98
+ "laquo" => true, # кавычки-ёлочки
99
+ "quotes" => true, # кавычки-английские лапки
100
+ "dash" => true, # короткое тире (150)
101
+ "emdash" => true, # длинное тире двумя минусами (151)
102
+ "initials" => true, # тонкие шпации в инициалах
103
+ "copypaste" => false, # замена непечатных и "специальных" юникодных символов на entities
104
+ "(c)" => true, # обрабатывать знак копирайта
105
+ "(r)" => true,
106
+ "(tm)" => true,
107
+ "(p)" => true,
108
+ "+-" => true, # спецсимволы, какие - понятно
109
+ "degrees" => true, # знак градуса
110
+ "dashglue" => true, "wordglue" => true, # приклеивание предлогов и дефисов
111
+ "spacing" => true, # запятые и пробелы, перестановка
112
+ "phones" => true, # обработка телефонов
113
+ "html" => true, # разрешение использования тагов html
114
+ "de_nobr" => false, # при true все <nobr/> заменяются на <span class="nobr"/>
115
+ "raw_output" => false, # выводить UTF-8 вместо entities
116
+ "skip_attr" => false, # при true не отрабатывать типографику в атрибутах тегов
117
+ "skip_code" => true, # при true не отрабатывать типографику внутри <code/>, <tt/>, CDATA
118
+ } #:nodoc:
119
+
120
+ # Глифы, использующиеся в подстановках по-умолчанию
121
+ GLYPHS = {
122
+ :quot => "&#34;", # quotation mark
123
+ :amp => "&#38;", # ampersand
124
+ :apos => "&#39;", # apos
125
+ :gt => "&#62;", # greater-than sign
126
+ :lt => "&#60;", # less-than sign
127
+ :nbsp => "&#160;", # non-breaking space
128
+ :sect => "&#167;", # section sign
129
+ :copy => "&#169;", # copyright sign
130
+ :laquo => "&#171;", # left-pointing double angle quotation mark = left pointing guillemet
131
+ :reg => "&#174;", # registered sign = registered trade mark sign
132
+ :deg => "&#176;", # degree sign
133
+ :plusmn => "&#177;", # plus-minus sign = plus-or-minus sign
134
+ :para => "&#182;", # pilcrow sign = paragraph sign
135
+ :middot => "&#183;", # middle dot = Georgian comma = Greek middle dot
136
+ :raquo => "&#187;", # right-pointing double angle quotation mark = right pointing guillemet
137
+ :ndash => "&#8211;", # en dash
138
+ :mdash => "&#8212;", # em dash
139
+ :lsquo => "&#8216;", # left single quotation mark
140
+ :rsquo => "&#8217;", # right single quotation mark
141
+ :ldquo => "&#8220;", # left double quotation mark
142
+ :rdquo => "&#8221;", # right double quotation mark
143
+ :bdquo => "&#8222;", # double low-9 quotation mark
144
+ :bull => "&#8226;", # bullet = black small circle
145
+ :hellip => "&#8230;", # horizontal ellipsis = three dot leader
146
+ :numero => "&#8470;", # numero
147
+ :trade => "&#8482;", # trade mark sign
148
+ :minus => "&#8722;", # minus sign
149
+ :inch => "&#8243;", # inch/second sign (u0x2033) (не путать с кавычками!)
150
+ :thinsp => "&#8201;", # полукруглая шпация (тонкий пробел)
151
+ :nob_open => '<nobr>', # открывающий блок без переноса слов
152
+ :nob_close => '</nobr>', # открывающий блок без переноса слов
153
+ } #:nodoc:
154
+
155
+ # Нормальные "типографские" символы в UTF-виде. Браузерами обрабатываются плохонько, поэтому
156
+ # лучше заменять их на entities.
157
+ VERBATIM_GLYPHS = {
158
+ ' ' => :nbsp,# alt+0160 (NBSP here)
159
+ '«' => :laquo,
160
+ '»' => :raquo,
161
+ '§' => :sect,
162
+ '©' => :copy,
163
+ '®' => :reg,
164
+ '°' => :deg,
165
+ '±' => :plusmn,
166
+ '¶' => :para,
167
+ '·' => :middot,
168
+ '–' => :ndash,
169
+ '—' => :mdash,
170
+ '‘' => :lsquo,
171
+ '’' => :rsquo,
172
+ '“' => :ldquo,
173
+ '”' => :rdquo,
174
+ '„' => :bdquo,
175
+ '•' => :bull,
176
+ '…' => :hellip,
177
+ '№' => :numero,
178
+ '™' => :trade,
179
+ '−' => :minus,
180
+ ' ' => :thinsp,
181
+ '″' => :inch,
182
+ } #:nodoc:
183
+
184
+ # Для маркера мы применяем invalid UTF-sequence чтобы его НЕЛЬЗЯ было перепутать с частью
185
+ # любого другого мультибайтного глифа. Thanks to huNter.
186
+ REPLACEMENT_MARKER = RuTils::SUBSTITUTION_MARKER #:nodoc:
187
+
188
+ # Кто придумал &#147;? Не учите людей плохому...
189
+ # Привет А.Лебедеву http://www.artlebedev.ru/kovodstvo/62/
190
+ # Используем символы, потом берем по символам из glyphs форматтера.
191
+ # Молодец mash!
192
+ FORBIDDEN_NUMERIC_ENTITIES = {
193
+ '132' => :bdquo,
194
+ '133' => :hellip,
195
+ '146' => :apos,
196
+ '147' => :ldquo,
197
+ '148' => :rdquo,
198
+ '149' => :bull,
199
+ '150' => :ndash,
200
+ '151' => :mdash,
201
+ '153' => :trade,
202
+ } #:nodoc:
203
+
204
+ PROTECTED_SETTINGS = [ :raw_output ] #:nodoc:
205
+
206
+ def initialize(*args)
207
+ @_text = args[0].is_a?(String) ? args[0] : ''
208
+ setup_default_settings!
209
+ accept_configuration_arguments!(args.last) if args.last.is_a?(Hash)
210
+ end
211
+
212
+ # Настраивает форматтер ассоциированным хешем
213
+ # formatter.configure!(:dash=>true, :wordglue=>false)
214
+ def configure!(*config)
215
+ accept_configuration_arguments!(config.last) if config.last.is_a?(Hash)
216
+ end
217
+
218
+ alias :configure :configure! #Дружественный API
219
+
220
+ # Неизвестные методы - настройки. С = - установка ключа, без - получение значения
221
+ def method_missing(meth, *args) #:nodoc:
222
+ setting = meth.to_s.gsub(/=$/, '')
223
+ super(meth, *args) unless @settings.has_key?(setting) #this will pop the exception if we have no such setting
224
+
225
+ return (@settings[setting] = args[0])
226
+ end
227
+
228
+ # Обрабатывает text_to_process с сохранением настроек, присвоенных обьекту-форматтеру
229
+ # Дополнительные аргументы передаются как параметры форматтера и не сохраняются после прогона.
230
+ def process(text_to_process, *args)
231
+ @_text = text_to_process
232
+ if args.last.is_a?(Hash)
233
+ with_configuration(args.last) { self.to_html }
234
+ else
235
+ self.to_html
236
+ end
237
+ end
238
+
239
+ # Обрабатывает текст, присвоенный форматтеру при создании и возвращает результат обработки.
240
+ def to_html()
241
+ return '' unless @_text
242
+
243
+ text = @_text.strip
244
+
245
+ # -4. запрет тагов html
246
+ process_escape_html(text) unless @settings["html"]
247
+
248
+ # -3. Никогда (вы слышите?!) не пущать лабуду &#not_correct_number;
249
+ FORBIDDEN_NUMERIC_ENTITIES.dup.each_pair do | key, rep |
250
+ text.gsub!(/&##{key};/, glyph[rep])
251
+ end
252
+
253
+ # -2. Чистим copy&paste
254
+ process_copy_paste_clearing(text) if @settings['copypaste']
255
+
256
+ # -1. Замена &entity_name; на входе ('&nbsp;' => '&#160;' и т.д.)
257
+ process_html_entities(text)
258
+
259
+ # 0. Вырезаем таги
260
+ tags = lift_ignored_elements(text) if @skip_tags
261
+
262
+ # 1. Запятые и пробелы
263
+ process_spacing(text) if @settings["spacing"]
264
+
265
+ # 3. Спецсимволы
266
+ # 0. дюймы с цифрами
267
+ # заменено на инчи
268
+ process_inches(text) if @settings["inches"]
269
+
270
+ # 1. лапки
271
+ process_quotes(text) if @settings["quotes"]
272
+
273
+ # 2. ёлочки
274
+ process_laquo(text) if @settings["laquo"]
275
+
276
+ # 2b. одновременно ёлочки и лапки
277
+ process_compound_quotes(text) if (@settings["quotes"] && @settings["laquo"])
278
+
279
+ # 3. тире
280
+ process_dash(text) if @settings["dash"]
281
+
282
+ # 3a. тире длинное
283
+ process_emdash(text) if @settings["emdash"]
284
+
285
+ # 5. +/-
286
+ process_plusmin(text) if @settings["+-"]
287
+
288
+ # 5a. 12^C
289
+ process_degrees(text) if @settings["degrees"]
290
+
291
+ # 6. телефоны
292
+ process_phones(text) if @settings["phones"]
293
+
294
+ # 7. Короткие слова и &nbsp;
295
+ process_wordglue(text) if @settings["wordglue"]
296
+
297
+ # 8. Склейка ласт. Тьфу! дефисов.
298
+ process_dashglue(text) if @settings["dashglue"]
299
+
300
+ # 8a. Инициалы
301
+ process_initials(text) if @settings['initials']
302
+
303
+ # 8b. Троеточия
304
+ process_ellipsises(text) if @settings["wordglue"]
305
+
306
+ # БЕСКОНЕЧНОСТЬ. Вставляем таги обратно.
307
+ reinsert_fragments(text, tags) if @skip_tags
308
+
309
+ # фуф, закончили.
310
+ process_span_instead_of_nobr(text) if @settings["de_nobr"]
311
+
312
+ # заменяем entities на истинные символы
313
+ process_raw_output(text) if @settings["raw_output"]
314
+
315
+ text.strip
316
+ end
317
+
318
+
319
+ # Применяет отдельный фильтр к text и возвращает результат. Например:
320
+ # formatter.apply(:wordglue, "Вот так") => "Вот&#160;так"
321
+ # Удобно применять когда вам нужно задействовать отдельный фильтр Гиленсона, но не нужна остальная механика
322
+ # Последний аргумент определяет, нужно ли при применении фильтра сохранить в неприкосновенности таги и другие
323
+ # игнорируемые фрагменты текста (по умолчанию они сохраняются).
324
+ def apply(filter, text, lift_ignored_elements = true)
325
+ copy = text.dup
326
+ unless lift_ignored_elements
327
+ self.send("process_#{filter}".to_sym, copy)
328
+ else
329
+ lifting_fragments(copy) { self.send("process_#{filter}".to_sym, copy) }
330
+ end
331
+ copy
332
+ end
333
+
334
+ private
335
+
336
+ def setup_default_settings!
337
+ @skip_tags = true;
338
+ @ignore = /notypo/ # regex, который игнорируется. Этим надо воспользоваться для обработки pre и code
339
+
340
+ @glueleft = ['рис.', 'табл.', 'см.', 'им.', 'ул.', 'пер.', 'кв.', 'офис', 'оф.', 'г.']
341
+ @glueright = ['руб.', 'коп.', 'у.е.', 'мин.']
342
+
343
+ # Установки можно менять в каждом экземпляре
344
+ @settings = SETTINGS.dup
345
+
346
+ @mark_tag = REPLACEMENT_MARKER
347
+ # Глифы можено подменять в экземпляре форматтера поэтому копируем их из константы
348
+ @glyph = GLYPHS.dup
349
+
350
+ @phonemasks = [[ /([0-9]{4})\-([0-9]{2})\-([0-9]{2}) ([0-9]{2}):([0-9]{2}):([0-9]{2})/,
351
+ /([0-9]{4})\-([0-9]{2})\-([0-9]{2})/,
352
+ /(\([0-9\+\-]+\)) ?([0-9]{3})\-([0-9]{2})\-([0-9]{2})/,
353
+ /(\([0-9\+\-]+\)) ?([0-9]{2})\-([0-9]{2})\-([0-9]{2})/,
354
+ /(\([0-9\+\-]+\)) ?([0-9]{3})\-([0-9]{2})/,
355
+ /(\([0-9\+\-]+\)) ?([0-9]{2})\-([0-9]{3})/,
356
+ /([0-9]{3})\-([0-9]{2})\-([0-9]{2})/,
357
+ /([0-9]{2})\-([0-9]{2})\-([0-9]{2})/,
358
+ /([0-9]{1})\-([0-9]{2})\-([0-9]{2})/,
359
+ /([0-9]{2})\-([0-9]{3})/,
360
+ /([0-9]+)\-([0-9]+)/,
361
+ ],[
362
+ ':nob_open\1:ndash\2:ndash\3:nbsp\4:\5:\6:nob_close',
363
+ ':nob_open\1:ndash\2:ndash\3:nob_close',
364
+ ':nob_open\1:nbsp\2:ndash\3:ndash\4:nob_close',
365
+ ':nob_open\1:nbsp\2:ndash\3:ndash\4:nob_close',
366
+ ':nob_open\1:nbsp\2:ndash\3:nob_close',
367
+ ':nob_open\1:nbsp\2:ndash\3:nob_close',
368
+ ':nob_open\1:ndash\2:ndash\3:nob_close',
369
+ ':nob_open\1:ndash\2:ndash\3:nob_close',
370
+ ':nob_open\1:ndash\2:ndash\3:nob_close',
371
+ ':nob_open\1:ndash\2:nob_close',
372
+ ':nob_open\1:ndash\2:nob_close'
373
+ ]]
374
+ end
375
+
376
+ # Позволяет получить процедуру, при вызове возвращающую значение глифа
377
+ def lookup(glyph_to_lookup)
378
+ return Proc.new { self.glyph[glyph_to_lookup] }
379
+ end
380
+
381
+ # Подставляет "символы" (двоеточие + имя глифа) на нужное значение глифа заданное в данном форматтере
382
+ def substitute_glyphs_in_string(str)
383
+ re = str.dup
384
+ @glyph.each_pair do | key, subst |
385
+ re.gsub!(":#{key.to_s}", subst)
386
+ end
387
+ re
388
+ end
389
+
390
+ # Выполняет блок, временно включая настройки переданные в +hash+
391
+ def with_configuration(hash, &block)
392
+ old_settings, old_glyphs = @settings.dup, @glyph.dup
393
+ accept_configuration_arguments!(hash)
394
+ txt = yield
395
+ @settings, @glyph = old_settings, old_glyphs
396
+
397
+ return txt
398
+ end
399
+
400
+ def accept_configuration_arguments!(args_hash)
401
+
402
+ # Специальный случай - :all=>true|false
403
+ if args_hash.has_key?(:all)
404
+ if args_hash[:all]
405
+ @settings.each_pair {|k, v| @settings[k] = true unless PROTECTED_SETTINGS.include?(k.to_sym)}
406
+ else
407
+ @settings.each_pair {|k, v| @settings[k] = false unless PROTECTED_SETTINGS.include?(k.to_sym)}
408
+ end
409
+ else
410
+
411
+ # Кинуть ошибку если настройка нам неизвестна
412
+ unknown_settings = args_hash.keys.collect{|k|k.to_s} - @settings.keys.collect { |k| k.to_s }
413
+ raise RuTils::Gilenson::UnknownSetting, unknown_settings if unknown_settings.any?
414
+
415
+ args_hash.each_pair do | key, value |
416
+ @settings[key.to_s] = (value ? true : false)
417
+ end
418
+ end
419
+ end
420
+
421
+ # Вынимает игнорируемые фрагменты и заменяет их маркером, выполняет переданный блок и вставляет вынутое на место
422
+ def lifting_fragments(text, &block)
423
+ lifted = lift_ignored_elements(text)
424
+ yield
425
+ reinsert_fragments(text, lifted)
426
+ end
427
+
428
+ #Вынимает фрагменты из текста и возвращает массив с фрагментами
429
+ def lift_ignored_elements(text)
430
+ # re = /<\/?[a-z0-9]+("+ # имя тага
431
+ # "\s+("+ # повторяющая конструкция: хотя бы один разделитель и тельце
432
+ # "[a-z]+("+ # атрибут из букв, за которым может стоять знак равенства и потом
433
+ # "=((\'[^\']*\')|(\"[^\"]*\")|([0-9@\-_a-z:\/?&=\.]+))"+ #
434
+ # ")?"+
435
+ # ")?"+
436
+ # ")*\/?>|\xA2\xA2[^\n]*?==/i;
437
+
438
+ re_skipcode = '((<(code|tt)[ >](.*?)<\/(code|tt)>)|(<!\[CDATA\[(.*?)\]\]>))|' if @settings['skip_code']
439
+ re = /(#{re_skipcode}<\/?[a-z0-9]+(\s+([a-z]+(=((\'[^\']*\')|(\"[^\"]*\")|([0-9@\-_a-z:\/?&=\.]+)))?)?)*\/?>)/uim
440
+ tags = text.scan(re).map{ |tag| tag[0] } # первая группа!
441
+ text.gsub!(re, @mark_tag) #маркер тега, мы используем Invalid UTF-sequence для него
442
+ return tags
443
+ end
444
+
445
+ def reinsert_fragments(text, fragments)
446
+ fragments.each do |fragment|
447
+ fragment.gsub!(/ (href|src|data)=((?:(\')([^\']*)(\'))|(?:(\")([^\"]*)(\")))/uim) do
448
+ " #{$1}=" + $2.gsub(/&(?!(#0*38)|(amp);)/, self.glyph[:amp])
449
+ end # unless @settings['raw_output'] -- делать это надо всегда (mash)
450
+
451
+ unless @settings['skip_attr']
452
+ fragment.gsub!(/ (title|alt)=((?:(\')([^\']*)(\'))|(?:(\")([^\"]*)(\")))/uim) do
453
+ " #{$1}=#{$3}" + self.process($4.to_s) + "#{$5}#{$6}" + self.process($7.to_s) + "#{$8}"
454
+ end
455
+ end
456
+ text.sub!(@mark_tag, fragment)
457
+ end
458
+ end
459
+
460
+ ### Имплементации фильтров
461
+ def process_html_entities(text)
462
+ self.glyph.each { |key, value| text.gsub!(/&#{key};/, value)}
463
+ end
464
+
465
+ def process_initials(text)
466
+ initials = /([А-Я])[\.]{1,2}[\s]*?([А-Я])[\.]*[\s]*?([А-Я])([а-я])/u
467
+ replacement = substitute_glyphs_in_string('\1.\2.:thinsp\3\4')
468
+ text.gsub!(initials, replacement)
469
+ end
470
+
471
+ def process_copy_paste_clearing(text)
472
+ VERBATIM_GLYPHS.each {|key,value| text.gsub!(/#{key}/, glyph[value]) }
473
+ end
474
+
475
+ def process_spacing(text)
476
+ text.gsub!( /(\s*)([,]*)/sui, '\2\1');
477
+ text.gsub!( /(\s*)([\.?!]*)(\s*[ЁА-ЯA-Z])/su, '\2\1\3');
478
+ end
479
+
480
+ def process_dashglue(text)
481
+ text.gsub!( /([a-zа-яА-Я0-9]+(\-[a-zа-яА-Я0-9]+)+)/ui, glyph[:nob_open]+'\1'+glyph[:nob_close])
482
+ end
483
+
484
+ def process_escape_html(text)
485
+ text.gsub!(/&/, self.glyph[:amp])
486
+ text.gsub!(/</, self.glyph[:lt])
487
+ text.gsub!(/>/, self.glyph[:gt])
488
+ end
489
+
490
+ def process_span_instead_of_nobr(text)
491
+ text.gsub!(/<nobr>/, '<span class="nobr">')
492
+ text.gsub!(/<\/nobr>/, '</span>')
493
+ end
494
+
495
+ def process_dash(text)
496
+ text.gsub!( /(\s|;)\-(\s)/ui, '\1'+self.glyph[:ndash]+'\2')
497
+ end
498
+
499
+ def process_emdash(text)
500
+ text.gsub!( /(\s|;)\-\-(\s)/ui, '\1'+self.glyph[:mdash]+'\2')
501
+ # 4. (с)
502
+ text.gsub!(/\([сСcC]\)((?=\w)|(?=\s[0-9]+))/u, self.glyph[:copy]) if @settings["(c)"]
503
+ # 4a. (r)
504
+ text.gsub!( /\(r\)/ui, '<sup>'+self.glyph[:reg]+'</sup>') if @settings["(r)"]
505
+
506
+ # 4b. (tm)
507
+ text.gsub!( /\(tm\)|\(тм\)/ui, self.glyph[:trade]) if @settings["(tm)"]
508
+ # 4c. (p)
509
+ text.gsub!( /\(p\)/ui, self.glyph[:sect]) if @settings["(p)"]
510
+ end
511
+
512
+ def process_ellipsises(text)
513
+ text.gsub!( '...', self.glyph[:hellip])
514
+ end
515
+
516
+ def process_laquo(text)
517
+ text.gsub!( /\"\"/ui, self.glyph[:quot]*2);
518
+ text.gsub!( /(^|\s|#{@mark_tag}|>|\()\"((#{@mark_tag})*[~0-9ёЁA-Za-zА-Яа-я\-:\/\.])/ui, '\1'+self.glyph[:laquo]+'\2');
519
+ _text = '""';
520
+ until _text == text do
521
+ _text = text;
522
+ text.gsub!( /(#{self.glyph[:laquo]}([^\"]*)[ёЁA-Za-zА-Яа-я0-9\.\-:\/\?\!](#{@mark_tag})*)\"/sui,
523
+ '\1'+self.glyph[:raquo])
524
+ end
525
+ end
526
+
527
+ def process_quotes(text)
528
+ text.gsub!( /\"\"/ui, self.glyph[:quot]*2)
529
+ text.gsub!( /\"\.\"/ui, self.glyph[:quot]+"."+self.glyph[:quot])
530
+ _text = '""';
531
+ until _text == text do
532
+ _text = text.dup
533
+ text.gsub!( /(^|\s|#{@mark_tag}|>)\"([0-9A-Za-z\'\!\s\.\?\,\-\&\;\:\_\#{@mark_tag}]+(\"|#{self.glyph[:rdquo]}))/ui, '\1'+self.glyph[:ldquo]+'\2')
534
+ #this doesnt work in-place. somehow.
535
+ text.gsub!( /(#{self.glyph[:ldquo]}([A-Za-z0-9\'\!\s\.\?\,\-\&\;\:\#{@mark_tag}\_]*).*[A-Za-z0-9][\#{@mark_tag}\?\.\!\,]*)\"/ui, '\1'+self.glyph[:rdquo])
536
+ end
537
+ end
538
+
539
+ def process_compound_quotes(text)
540
+ text.gsub!(/(#{self.glyph[:ldquo]}(([A-Za-z0-9'!\.?,\-&;:]|\s|#{@mark_tag})*)#{self.glyph[:laquo]}(.*)#{self.glyph[:raquo]})#{self.glyph[:raquo]}/ui,'\1'+self.glyph[:rdquo]);
541
+ end
542
+
543
+ def process_degrees(text)
544
+ text.gsub!( /-([0-9])+\^([FCС])/, self.glyph[:ndash]+'\1'+self.glyph[:deg]+'\2') #deg
545
+ text.gsub!( /\+([0-9])+\^([FCС])/, '+\1'+self.glyph[:deg]+'\2')
546
+ text.gsub!( /\^([FCС])/, self.glyph[:deg]+'\1')
547
+ end
548
+
549
+ def process_wordglue(text)
550
+ text.replace(" " + text + " ")
551
+ _text = " " + text + " "
552
+
553
+ until _text == text
554
+ _text = text
555
+ text.gsub!( /(\s+)([a-zа-яА-Я]{1,2})(\s+)([^\\s$])/ui, '\1\2'+self.glyph[:nbsp]+'\4')
556
+ text.gsub!( /(\s+)([a-zа-яА-Я]{3})(\s+)([^\\s$])/ui, '\1\2'+self.glyph[:nbsp]+'\4')
557
+ end
558
+
559
+ text.gsub!(/(\s+)([a-zа-яА-Я]{1,2}[\)\]\!\?,\.;]{0,3}\s$)/ui, self.glyph[:nbsp]+'\2')
560
+
561
+ @glueleft.each { | i | text.gsub!( /(\s)(#{i})(\s+)/sui, '\1\2' + self.glyph[:nbsp]) }
562
+
563
+ @glueright.each { | i | text.gsub!( /(\s)(#{i})(\s+)/sui, self.glyph[:nbsp]+'\2\3') }
564
+
565
+ text.strip!
566
+ end
567
+
568
+ def process_phones(text)
569
+ @phonemasks[0].each_with_index do |pattern, i|
570
+ replacement = substitute_glyphs_in_string(@phonemasks[1][i])
571
+ text.gsub!(pattern, replacement)
572
+ end
573
+ end
574
+
575
+ def process_inches(text)
576
+ text.gsub!(/\s([0-9]{1,2}([\.,][0-9]{1,2})?)\"/ui, ' \1'+self.glyph[:inch])
577
+ end
578
+
579
+ def process_plusmin(text)
580
+ text.gsub!(/[^+]\+\-/ui, self.glyph[:plusmn])
581
+ end
582
+
583
+ # Подменяет все юникодные entities в тексте на истинные UTF-8-символы
584
+ def process_raw_output(text)
585
+ # Все глифы
586
+ @glyph.values.each do | entity |
587
+ next unless entity =~ /^&#(\d+);/
588
+ text.gsub!(/#{entity}/, entity_to_raw_utf8(entity))
589
+ end
590
+ end
591
+
592
+ # Конвертирует юникодные entities в UTF-8-codepoints
593
+ def entity_to_raw_utf8(entity)
594
+ entity =~ /^&#(\d+);/
595
+ $1 ? [$1.to_i].pack("U") : entity
596
+ end
597
+ end #end Gilenson
598
+
599
+ # Выбрасывается если форматтеру задается неизвестная настройка
600
+ class RuTils::Gilenson::UnknownSetting < RuntimeError
601
+ end
602
+
603
+ module RuTils::Gilenson::StringFormatting
604
+ # Форматирует строку с помощью Gilenson::Formatter. Все дополнительные опции передаются форматтеру.
605
+ def gilensize(*args)
606
+ opts = args.last.is_a?(Hash) ? args.last : {}
607
+ RuTils::Gilenson::Formatter.new(self, *opts).to_html
608
+ end
609
+
610
+ # Форматирует строку с помощью Gilenson::Obsolete. Всe дополнительные опции передаются форматтеру.
611
+ def o_gilensize(*args)
612
+ opts = args.last.is_a?(Hash) ? args.last : {}
613
+ RuTils::Gilenson::Obsolete.new(self, *opts).to_html
614
+ end
615
+ end
616
+
617
+ Object::String.send(:include, RuTils::Gilenson::StringFormatting)
data/lib/rutils.rb CHANGED
@@ -6,7 +6,7 @@ module RuTils
6
6
  INSTALLATION_DIRECTORY = File.expand_path(File.dirname(__FILE__) + '/../') #:nodoc:
7
7
  MAJOR = 0
8
8
  MINOR = 2
9
- TINY = 0
9
+ TINY = 1
10
10
 
11
11
  # Версия RuTils
12
12
  VERSION = [MAJOR, MINOR ,TINY].join('.') #:nodoc:
@@ -0,0 +1,324 @@
1
+ $KCODE = 'u'
2
+ require 'test/unit'
3
+ require File.dirname(__FILE__) + '/../lib/rutils'
4
+
5
+
6
+ # Cюда идут наши тесты типографа. Мы содержим их отдельно поскольку набор тестов Типографицы нами не контролируется.
7
+ # Когда у рутилей появятся собственные баги под каждый баг следует завести тест
8
+ class GilensonOwnTest < Test::Unit::TestCase
9
+
10
+ def setup
11
+ @gilenson = RuTils::Gilenson::Formatter.new
12
+ end
13
+
14
+ def test_tag_lift
15
+ assert_equal "Вот&#160;такие<tag some='foo>' /> <tagmore></tagmore> дела", "Вот такие<tag some='foo>' /> <tagmore></tagmore> дела".gilensize
16
+ end
17
+
18
+ def test_byte_pass
19
+ assert_equal '<p>Теперь добираться до офиса Студии автортранспортом стало удобнее. ' +
20
+ 'Для этого мы разместили в разделе <a href="#">«Контакт»</a> окно вебкамеры, ' +
21
+ 'которая непрерывно транслирует дорожную обстановку на Садовом кольце по адресу Земляной Вал, 23. <br>' +
22
+ 'Удачной дороги! </p>',
23
+ '<p>Теперь добираться до офиса Студии автортранспортом стало удобнее. ' +
24
+ 'Для этого мы разместили в разделе <a href="#">«Контакт»</a> окно вебкамеры, ' +
25
+ 'которая непрерывно транслирует дорожную обстановку на Садовом кольце по адресу Земляной Вал, 23. <br>' +
26
+ 'Удачной дороги! </p>'.gilensize
27
+ end
28
+
29
+ def test_phones
30
+ assert_equal '<nobr>3&#8211;12&#8211;30</nobr>', '3-12-30'.gilensize
31
+ assert_equal '<nobr>12&#8211;34&#8211;56</nobr>', '12-34-56'.gilensize
32
+ assert_equal '<nobr>88&#8211;25&#8211;04</nobr>', '88-25-04'.gilensize
33
+ assert_equal '+7 <nobr>(99284)&#160;65&#8211;818</nobr>', '+7 (99284) 65-818'.gilensize
34
+ assert_equal '<nobr>725&#8211;01&#8211;10</nobr>', '725-01-10'.gilensize
35
+ end
36
+
37
+ def test_address
38
+ assert_equal 'табл.&#160;2, рис.&#160;2.10', 'табл. 2, рис. 2.10'.gilensize
39
+ assert_equal 'офис&#160;415, оф.340, д.5, ул.&#160;Народной Воли, пл. Малышева', 'офис 415, оф.340, д.5, ул. Народной Воли, пл. Малышева'.gilensize
40
+ end
41
+
42
+ def test_html_entities_replace
43
+ assert_equal '&#34; &#38; &#39; &#62; &#60; &#160; &#167; &#169; &#171; &#174; &#176; &#177; &#183; &#187; &#8211; &#8212; &#8216; &#8217; &#8220; &#8221; &#8222; &#8226; &#8230; &#8482; &#8722;', '&quot; &amp; &apos; &gt; &lt; &nbsp; &sect; &copy; &laquo; &reg; &deg; &plusmn; &middot; &raquo; &ndash; &mdash; &lsquo; &rsquo; &ldquo; &rdquo; &bdquo; &bull; &hellip; &trade; &minus;'.gilensize
44
+ end
45
+
46
+ def test_ugly_entities_replace1 # not_correct_number
47
+ assert_equal '&#8222; &#8230; &#39; &#8220; &#8221; &#8226; &#8211; &#8212; &#8482;', '&#132; &#133; &#146; &#147; &#148; &#149; &#150; &#151; &#153;'.gilensize
48
+ end
49
+
50
+ def test_specials
51
+ assert_equal '&#169; 2002, &#169; 2003, &#169; 2004, &#169; 2005 &#8212; тоже без&#160;пробелов: &#169;2002, &#169;Кукуц. однако: варианты (а) и&#160;(с)', '(с) 2002, (С) 2003, (c) 2004, (C) 2005 -- тоже без пробелов: (с)2002, (c)Кукуц. однако: варианты (а) и (с)'.gilensize
52
+ assert_equal '+5&#176;С, +7&#176;C, &#8211;5&#176;F', '+5^С, +17^C, -275^F'.gilensize
53
+ assert_equal 'об&#160;этом подробнее &#8212; читай &#167;25', 'об этом подробнее -- читай (p)25'.gilensize
54
+ assert_equal 'один же&#160;минус &#8211; краткое тире', 'один же минус - краткое тире'.gilensize
55
+ assert_equal 'Sharpdesign&#8482;, Microsoft<sup>&#174;</sup>', 'Sharpdesign(tm), Microsoft(r)'.gilensize
56
+ end
57
+
58
+ def test_breaking
59
+ assert_equal 'скажи, мне, ведь не&#160;даром! Москва, клеймённая пожаром. Французу отдана', 'скажи ,мне, ведь не даром !Москва, клеймённая пожаром .Французу отдана'.gilensize
60
+ assert_equal 'so&#160;be it, my&#160;liege. Tiny dwellers roam thru midnight! Hell raised, the&#160;Balrog is&#160;hiding in&#160;your backyard!', 'so be it ,my liege .Tiny dwellers roam thru midnight !Hell raised, the Balrog is hiding in your backyard!'.gilensize
61
+ assert_equal 'при&#160;установке командой строки в&#160;?page=help <nobr>бла-бла-бла-бла</nobr>', 'при установке командой строки в ?page=help бла-бла-бла-бла'.gilensize
62
+ assert_equal 'как&#160;интересно будет переноситься со&#160;строки на&#160;строку <nobr>что-то</nobr> разделённое дефисом, ведь дефис тот&#160;тоже ведь из&#160;наших. <nobr>Какие-то</nobr> браузеры думают, что&#160;следует переносить и&#160;его&#8230;', 'как интересно будет переноситься со строки на строку что-то разделённое дефисом, ведь дефис тот тоже ведь из наших. Какие-то браузеры думают, что следует переносить и его...'.gilensize
63
+ end
64
+
65
+ def test_quotes
66
+ assert_equal 'english &#8220;quotes&#8221; should be&#160;quite like this', 'english "quotes" should be quite like this'.gilensize
67
+ assert_equal 'русские же&#160;&#171;оформляются&#187; подобным образом', 'русские же "оформляются" подобным образом'.gilensize
68
+ assert_equal 'кавычки &#171;расставлены&#187; &#8220;in a&#160;chaotic order&#8221;', 'кавычки "расставлены" "in a chaotic order"'.gilensize
69
+ assert_equal 'диагональ моего монитора &#8212; 17&#8243;, а&#160;размер пениса &#8212; 1,5&#8243;', 'диагональ моего монитора -- 17", а размер пениса -- 1,5"'.gilensize
70
+ assert_equal 'в&#160;толщину, &#171;вложенные &#8220;quotes&#8221; вот&#160;так&#187;, &#8220;or it&#160;&#171;будет вложено&#187; elsewhere&#8221;', 'в толщину, "вложенные "quotes" вот так", "or it "будет вложено" elsewhere"'.gilensize
71
+ assert_equal '&#8220;complicated &#171;кавычки&#187;, &#171;странные &#8220;includements&#8221; кавычек&#187;', '"complicated "кавычки", "странные "includements" кавычек"'.gilensize
72
+ assert_equal '&#8220;double &#8220;quotes&#8221;', '"double "quotes"'.gilensize
73
+ assert_equal '&#171;дважды вложенные &#171;кавычки&#187;', '"дважды вложенные "кавычки"'.gilensize
74
+ assert_equal '&#171;01/02/03&#187;, дискеты в&#160;5.25&#8243;', '"01/02/03", дискеты в 5.25"'.gilensize
75
+ assert_equal 'после троеточия правая кавычка &#8212; &#171;Вот&#8230;&#187;', 'после троеточия правая кавычка -- "Вот..."'.gilensize
76
+ assert_equal 'setlocale(LC_ALL, &#8220;ru_RU.UTF8&#8221;);', 'setlocale(LC_ALL, "ru_RU.UTF8");'.gilensize
77
+ assert_equal '&#8220;read, write, delete&#8221; с&#160;флагом &#8220;only_mine&#8221;', '"read, write, delete" с флагом "only_mine"'.gilensize
78
+ assert_equal '&#171;Двоеточие:&#187;, &#171;такую умную тему должен писать чувак умеющий скрипты скриптить.&#187;', '"Двоеточие:", "такую умную тему должен писать чувак умеющий скрипты скриптить."'.gilensize
79
+ assert_equal '(&#171;Вики != HTML&#187; &#8212; &#171;Вики != HTML&#187; &#8212; (&#171;всякая чушь&#187;))', '("Вики != HTML" -- "Вики != HTML" -- ("всякая чушь"))'.gilensize
80
+ assert_equal '&#171;фигня123&#187;, &#8220;fignya123&#8221;', '"фигня123", "fignya123"'.gilensize
81
+ # assert_equal '&#171;сбалансированные &#171;кавычки<!--notypo--><!--/notypo--> (четыре в&#160;конце) &#8212; связано с&#160;синтаксисом ваки', '"сбалансированные "кавычки"""" (четыре в конце) -- связано с синтаксисом ваки'.gilensize
82
+ assert_equal '&#171;несбалансированные &#171;кавычки&#34;&#34;" (три в&#160;конце) &#8212; связано с&#160;синтаксисом ваки', '"несбалансированные "кавычки""" (три в конце) -- связано с синтаксисом ваки'.gilensize
83
+ assert_equal '&#171;разноязыкие quotes&#187;', '"разноязыкие quotes"'.gilensize
84
+ assert_equal '&#171;multilanguage кавычки&#187;', '"multilanguage кавычки"'.gilensize
85
+ end
86
+
87
+ def test_additional_quote_cases
88
+ assert_equal "&#171;И это&#160;называется языком?&#187;, &#8212; таков был&#160;его вопрос",
89
+ %q{ "И это называется языком?", -- таков был его вопрос }.gilensize
90
+
91
+ assert_equal "&#171;Он &#8212; сволочь!&#187;, сказал&#160;я",
92
+ %q{ "Он -- сволочь!", сказал я }.gilensize
93
+ end
94
+
95
+ def test_initials
96
+ assert_equal 'Это&#160;нам сказал П.И.&#8201;Петров', 'Это нам сказал П. И. Петров'.gilensize
97
+
98
+ assert_equal "А&#160;Ефимов&#8230;",
99
+ @gilenson.process("А Ефимов...")
100
+
101
+ assert_equal "Обратился за&#160;ПО. К&#160;негодяям.",
102
+ @gilenson.process("Обратился за ПО. К негодяям.")
103
+
104
+ assert_equal "ГО&#160;Самарской обл.",
105
+ @gilenson.process("ГО Самарской обл.")
106
+
107
+ assert_equal "ГОР&#160;Самарской обл.",
108
+ @gilenson.process("ГОР Самарской обл.")
109
+
110
+ assert_equal "КОШМАР Самарской обл.",
111
+ @gilenson.process("КОШМАР Самарской обл.")
112
+
113
+ assert_equal "УФПС Самарской обл.",
114
+ @gilenson.process("УФПС Самарской обл.")
115
+
116
+ end
117
+
118
+ def test_nbsp_last_letters
119
+ assert_equal "сказал&#160;я", "сказал я".gilensize
120
+ assert_equal "сказал&#160;я!", "сказал я!".gilensize
121
+ assert_equal "сказал&#160;я?", "сказал я?".gilensize
122
+ assert_equal "сказал&#160;я&#8230;", "сказал я...".gilensize
123
+ assert_equal "сказал&#160;он&#8230;", "сказал он...".gilensize
124
+ assert_equal "сказали&#160;мы?..", "сказали мы?..".gilensize
125
+ assert_equal "сказали&#160;мы?!", "сказали мы?!".gilensize
126
+ assert_equal "сказали мы?!!!", "сказали мы?!!!".gilensize
127
+ assert_equal "сказали нам", "сказали нам".gilensize
128
+ assert_equal "(сказали&#160;им)", "(сказали им)".gilensize
129
+ end
130
+
131
+ def test_marker_bypass
132
+ assert_equal "<p><nobr>МИЭЛЬ-Недвижимость</nobr></p>", "<p>МИЭЛЬ-Недвижимость</p>".gilensize
133
+ end
134
+
135
+ def test_skip_code
136
+ @gilenson.configure!(:all => true, :skip_code => true)
137
+
138
+ assert_equal "<code>Скип -- скип!</code>",
139
+ @gilenson.process("<code>Скип -- скип!</code>")
140
+
141
+ assert_equal '<code attr="test -- attr">Скип -- скип!</code>',
142
+ @gilenson.process('<code attr="test -- attr">Скип -- скип!</code>')
143
+
144
+ assert_equal "<tt>Скип -- скип!</tt> test &#8212; test <tt attr='test -- attr'>Скип -- скип!</tt>",
145
+ @gilenson.process("<tt>Скип -- скип!</tt> test -- test <tt attr='test -- attr'>Скип -- скип!</tt>")
146
+
147
+ assert_equal "<tt>Скип -- скип!</tt><tt>Скип -- скип!</tt> &#8212; <code attr='test -- attr'>Скип -- скип!</code>",
148
+ @gilenson.process("<tt>Скип -- скип!</tt><tt>Скип -- скип!</tt> -- <code attr='test -- attr'>Скип -- скип!</code>")
149
+
150
+ assert_equal "<ttt>Скип &#8212; скип!</tt>",
151
+ @gilenson.process("<ttt>Скип -- скип!</tt>")
152
+
153
+ assert_equal "<tt>Скип &#8212; скип!</ttt>",
154
+ @gilenson.process("<tt>Скип -- скип!</ttt>")
155
+
156
+ assert_equal "Ах, &#8212; <code>var x = j // -- тест</code>",
157
+ @gilenson.process("Ах, -- <code>var x = j // -- тест</code>")
158
+
159
+ assert_equal "<![CDATA[ CDATA -- ]]> &#8212; CDATA",
160
+ @gilenson.process("<![CDATA[ CDATA -- ]]> -- CDATA")
161
+
162
+ assert_equal "<![CDATA[ CDATA -- >] -- CDATA ]]> &#8212; <![CDATA[ CDATA ]> -- CDATA ]]>",
163
+ @gilenson.process("<![CDATA[ CDATA -- >] -- CDATA ]]> -- <![CDATA[ CDATA ]> -- CDATA ]]>")
164
+
165
+ assert_equal "<![CDATA[ CDATA -- >] -- CDATA ]]> &#8212; <![CDATA[ CDATA ]> -- CDATA ]]> &#8212; CDATA ]]>",
166
+ @gilenson.process("<![CDATA[ CDATA -- >] -- CDATA ]]> -- <![CDATA[ CDATA ]> -- CDATA ]]> -- CDATA ]]>")
167
+
168
+ @gilenson.configure!(:skip_code => false)
169
+
170
+ assert_equal "Ах, &#8212; <code>var x&#160;= j&#160;// &#8212; тест</code>",
171
+ @gilenson.process("Ах, -- <code>var x = j // -- тест</code>")
172
+ end
173
+
174
+ def test_skip_attr
175
+ @gilenson.configure!(:skip_attr => true)
176
+
177
+ assert_equal "<a href='#' attr='смотри -- смотри' title='test -- me' alt=\"смотри -- смотри\">just &#8212; test</a>",
178
+ @gilenson.process("<a href='#' attr='смотри -- смотри' title='test -- me' alt=\"смотри -- смотри\">just -- test</a>")
179
+
180
+ assert_equal 'мы&#160;напишем title="test &#8212; me" и&#160;alt=\'test &#8212; me\', вот',
181
+ @gilenson.process('мы напишем title="test -- me" и alt=\'test -- me\', вот')
182
+
183
+ @gilenson.configure!(:skip_attr => false)
184
+
185
+ assert_equal "<a href='#' attr='смотри -- смотри' title='test &#8212;&#160;me' alt=\"смотри &#8212; смотри\">just &#8212; test</a>",
186
+ @gilenson.process("<a href='#' attr='смотри -- смотри' title='test -- me' alt=\"смотри -- смотри\">just -- test</a>")
187
+
188
+ assert_equal 'мы&#160;напишем title="test &#8212; me" и&#160;alt=\'test &#8212; me\', вот',
189
+ @gilenson.process('мы напишем title="test -- me" и alt=\'test -- me\', вот')
190
+ end
191
+
192
+ def test_escape_html
193
+ assert_equal "Используйте &#38; вместо &#38;amp;",
194
+ @gilenson.process("Используйте &#38; вместо &#38;amp;")
195
+
196
+ @gilenson.configure!(:html => false)
197
+
198
+ assert_equal "&#38;#38; &#8212; &#38;amp; &#60;code/&#62; &#60;some_tag&#62;таги не&#160;пройдут!&#60;/some_tag&#62;. Ну&#160;и?..",
199
+ @gilenson.process("&#38; -- &amp; <code/> <some_tag>таги не пройдут!</some_tag>. Ну и?..")
200
+
201
+ assert_equal "Используйте &#38;#38; вместо &#38;amp;",
202
+ @gilenson.process("Используйте &#38; вместо &amp;")
203
+
204
+ end
205
+
206
+ def test_ampersand_in_urls
207
+
208
+ @gilenson.configure!(:html=>false)
209
+
210
+ assert_equal "&#60;a href='test?test5=5&#38;#38;test6=6'&#62;test&#38;#38;&#60;/a&#62;",
211
+ @gilenson.process("<a href='test?test5=5&#38;test6=6'>test&#38;</a>")
212
+
213
+ @gilenson.configure!(:html=>true)
214
+
215
+ assert_equal "<a href='test?test7=7&#38;test8=8'>test&#38;</a>",
216
+ @gilenson.process("<a href='test?test7=7&#38;test8=8'>test&#38;</a>")
217
+
218
+ assert_equal "<a href='test?test9=9&#038;test10=10'>test&#038;</a>",
219
+ @gilenson.process("<a href='test?test9=9&#038;test10=10'>test&#038;</a>")
220
+
221
+ assert_equal "<a href='test?test11=11&#38;test12=12'>test&</a>",
222
+ @gilenson.process("<a href='test?test11=11&test12=12'>test&</a>")
223
+
224
+ assert_equal "<a href='test?test12=12&#38;'>test</a>",
225
+ @gilenson.process("<a href='test?test12=12&amp;'>test</a>")
226
+
227
+ assert_equal "<a href='test?x=1&#38;y=2' title='&#38;-amp, &#8230;-hellip'>test</a>",
228
+ @gilenson.process("<a href='test?x=1&y=2' title='&#38;-amp, &#8230;-hellip'>test</a>")
229
+
230
+ assert_equal "<a href='test?x=3&#38;#039;y=4'>test</a>",
231
+ @gilenson.process("<a href='test?x=3&#039;y=4'>test</a>")
232
+
233
+
234
+ @gilenson.glyph[:amp] = '&amp;'
235
+
236
+ assert_equal "<a href='test?test11=11&amp;test12=12'>test&</a>",
237
+ @gilenson.process("<a href='test?test11=11&test12=12'>test&</a>")
238
+
239
+ assert_equal "<a href='test?test13=13&amp;test14=14'>test&</a>",
240
+ @gilenson.process("<a href='test?test13=13&amp;test14=14'>test&</a>")
241
+
242
+ assert_equal "<a href='test?test15=15&amp;amppp;test16=16'>test&</a>",
243
+ @gilenson.process("<a href='test?test15=15&amppp;test16=16'>test&</a>")
244
+
245
+ end
246
+ end
247
+
248
+
249
+ class GilensonConfigurationTest < Test::Unit::TestCase
250
+ def setup
251
+ @gilenson = RuTils::Gilenson::Formatter.new
252
+ end
253
+
254
+ def test_settings_as_tail_arguments
255
+
256
+ assert_equal "Ну&#160;и куда вот&#160;&#8212; да&#160;туда!",
257
+ @gilenson.process("Ну и куда вот -- да туда!")
258
+
259
+ assert_equal "Ну и куда вот &#8212; да туда!",
260
+ @gilenson.process("Ну и куда вот -- да туда!", :dash => false, :dashglue => false, :wordglue => false)
261
+
262
+ assert_equal "Ну&#160;и куда вот&#160;&#8212; да&#160;туда!",
263
+ @gilenson.process("Ну и куда вот -- да туда!")
264
+
265
+ @gilenson.configure!(:dash => false, :dashglue => false, :wordglue => false)
266
+
267
+ assert_equal "Ну и куда вот &#8212; да туда!",
268
+ @gilenson.process("Ну и куда вот -- да туда!")
269
+
270
+ @gilenson.configure!(:all => true)
271
+
272
+ assert_equal "Ну&#160;и куда вот&#160;&#8212; да&#160;туда!",
273
+ @gilenson.process("Ну и куда вот -- да туда!")
274
+
275
+ @gilenson.configure!(:all => false)
276
+
277
+ assert_equal "Ну и куда вот -- да туда!",
278
+ @gilenson.process("Ну и куда вот -- да туда!")
279
+ end
280
+
281
+ def test_glyph_override
282
+ assert_equal 'скажи, мне, ведь не&#160;даром! Москва, клеймённая пожаром. Французу отдана',
283
+ @gilenson.process('скажи ,мне, ведь не даром !Москва, клеймённая пожаром .Французу отдана')
284
+
285
+ @gilenson.glyph[:nbsp] = '&nbsp;'
286
+ assert_equal 'скажи, мне, ведь не&nbsp;даром! Москва, клеймённая пожаром. Французу отдана',
287
+ @gilenson.process('скажи ,мне, ведь не даром !Москва, клеймённая пожаром .Французу отдана')
288
+ end
289
+
290
+ def test_ugly_entities_replace2 # copy&paste
291
+ @gilenson.configure!(:copypaste => true)
292
+ assert_equal '&#160; &#171; &#187; &#167; &#169; &#174; &#176; &#177; &#182; &#183; &#8211; &#8212; &#8216; &#8217; &#8220; &#8221; &#8222; &#8226; &#8230; &#8470; &#8482; &#8722; &#8201; &#8243;', @gilenson.process('  « » § © ® ° ± ¶ · – — ‘ ’ “ ” „ • … № ™ −   ″')
293
+ end
294
+
295
+ def test_raise_on_unknown_setting
296
+ assert_raise(RuTils::Gilenson::UnknownSetting) { @gilenson.configure!(:bararara => true) }
297
+ end
298
+
299
+ def test_raw_utf8_output
300
+ @gilenson.configure!(:raw_output=>true)
301
+ assert_equal '&#38442; Это просто «кавычки»',
302
+ @gilenson.process('&#38442; Это просто "кавычки"')
303
+ end
304
+
305
+ def test_configure_alternate_names
306
+ assert @gilenson.configure(:raw_output=>true)
307
+ assert @gilenson.configure!(:raw_output=>true)
308
+ end
309
+
310
+ end
311
+
312
+ # class TypograficaTrakoEntries < Test::Unit::TestCase
313
+ # def test_cpp
314
+ # assert_equal "C++-API", "C++-API".gilensize
315
+ # end
316
+ #
317
+ # def test_symmetricity # http://pixel-apes.com/typografica/trako/12
318
+ # assert_equal "&#171;Справка&#160;09&#187;", '"Справка 09"'.gilensize
319
+ # end
320
+ #
321
+ # def test_paths # http://pixel-apes.com/typografica/trako/13
322
+ # assert_equal '&#171;c:\www\sites\&#187;', '"c:\www\sites\"'.gilensize
323
+ # end
324
+ # end
metadata CHANGED
@@ -3,7 +3,7 @@ rubygems_version: 0.9.2
3
3
  specification_version: 1
4
4
  name: rutils
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.2.0
6
+ version: 0.2.1
7
7
  date: 2007-04-03 00:00:00 +02:00
8
8
  summary: Simple processing of russian strings
9
9
  require_paths:
@@ -33,6 +33,7 @@ files:
33
33
  - bin/rutilize
34
34
  - test/run_tests.rb
35
35
  - test/t_datetime.rb
36
+ - test/t_gilenson.rb
36
37
  - test/t_integration.rb
37
38
  - test/t_pluralize.rb
38
39
  - test/t_rutils_base.rb
@@ -47,6 +48,7 @@ files:
47
48
  - lib/transliteration
48
49
  - lib/countries/countries.rb
49
50
  - lib/datetime/datetime.rb
51
+ - lib/gilenson/gilenson.rb
50
52
  - lib/gilenson/gilenson_port.rb
51
53
  - lib/integration/blue_cloth_override.rb
52
54
  - lib/integration/integration.rb