mathemagical 0.0.1
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.
- data/LICENSE +674 -0
- data/README.md +21 -0
- data/Rakefile +136 -0
- data/bin/mathemagical +9 -0
- data/lib/mathemagical.rb +24 -0
- data/lib/mathemagical/element.rb +304 -0
- data/lib/mathemagical/latex.rb +1123 -0
- data/lib/mathemagical/latex/builtin.rb +5 -0
- data/lib/mathemagical/latex/builtin/symbol.rb +549 -0
- data/lib/mathemagical/string.rb +32 -0
- data/lib/mathemagical/symbol/character_reference.rb +2107 -0
- data/lib/mathemagical/symbol/entity_reference.rb +2106 -0
- data/lib/mathemagical/symbol/utf8.rb +2108 -0
- data/lib/mathemagical/util.rb +351 -0
- data/mathemagical.gemspec +86 -0
- data/spec/math_ml/element_spec.rb +32 -0
- data/spec/math_ml/latex/macro_spec.rb +122 -0
- data/spec/math_ml/latex/parser_spec.rb +574 -0
- data/spec/math_ml/latex/scanner_spec.rb +202 -0
- data/spec/math_ml/string_spec.rb +29 -0
- data/spec/math_ml/util_spec.rb +700 -0
- data/spec/math_ml_spec.rb +13 -0
- data/spec/util.rb +44 -0
- metadata +118 -0
@@ -0,0 +1,351 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
#
|
3
|
+
# Utility for MathML Library
|
4
|
+
#
|
5
|
+
# Copyright (C) 2006, KURODA Hiraku <hiraku@hinet.mydns.jp>
|
6
|
+
# You can redistribute it and/or modify it under GPL2.
|
7
|
+
#
|
8
|
+
|
9
|
+
require "mathemagical"
|
10
|
+
|
11
|
+
module Mathemagical::Util
|
12
|
+
ESCAPES = {"<"=>"lt",
|
13
|
+
">"=>"gt",
|
14
|
+
"&"=>"amp",
|
15
|
+
"\""=>"quot",
|
16
|
+
"'"=>"apos"
|
17
|
+
}
|
18
|
+
INVALID_RE = /(?!)/
|
19
|
+
EQNARRAY_RE = /\\begin\s*\{eqnarray\}(#{Mathemagical::LaTeX::MBEC}*?)\\end\s*\{eqnarray\}/
|
20
|
+
SINGLE_COMMAND_RE = /(\\([a-zA-Z]+))[ \t]?/
|
21
|
+
|
22
|
+
def self.escapeXML(s, br=false)
|
23
|
+
r = s.gsub(/[<>&"']/){|m| "&#{ESCAPES[m]};"}
|
24
|
+
br ? r.gsub(/\n/, "<br />\n") : r
|
25
|
+
end
|
26
|
+
|
27
|
+
def escapeXML(s, br=false)
|
28
|
+
Mathemagical::Util.escapeXML(s, br)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.collect_regexp(a)
|
32
|
+
if a
|
33
|
+
a = [a].flatten
|
34
|
+
a.size>0 ? Regexp.new(a.inject(""){|r, i| i.is_a?(Regexp) ? "#{r}#{i.to_s}|" : r}.chop) : INVALID_RE
|
35
|
+
else
|
36
|
+
INVALID_RE
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def collect_regexp(a)
|
41
|
+
Mathemagical::Util.collect_regexp(a)
|
42
|
+
end
|
43
|
+
|
44
|
+
class MathData
|
45
|
+
attr_reader :math_list, :msrc_list, :dmath_list, :dsrc_list, :escape_list, :esrc_list, :user_list, :usrc_list
|
46
|
+
def initialize
|
47
|
+
@math_list = []
|
48
|
+
@msrc_list = []
|
49
|
+
@dmath_list = []
|
50
|
+
@dsrc_list = []
|
51
|
+
@escape_list = []
|
52
|
+
@esrc_list = []
|
53
|
+
@user_list = []
|
54
|
+
@usrc_list = []
|
55
|
+
end
|
56
|
+
|
57
|
+
def update(s)
|
58
|
+
@math_list.concat(s.math_list)
|
59
|
+
@msrc_list.concat(s.msrc_list)
|
60
|
+
@dmath_list.concat(s.dmath_list)
|
61
|
+
@dsrc_list.concat(s.dsrc_list)
|
62
|
+
@escape_list.concat(s.escape_list)
|
63
|
+
@esrc_list.concat(s.esrc_list)
|
64
|
+
@user_list.concat(s.user_list)
|
65
|
+
@usrc_list.concat(s.usrc_list)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class SimpleLaTeX
|
70
|
+
include Mathemagical::Util
|
71
|
+
|
72
|
+
@@default_latex = nil
|
73
|
+
DEFAULT = {
|
74
|
+
:delimiter=>"\001",
|
75
|
+
:math_env_list=>[
|
76
|
+
/\$((?:\\.|[^\\\$])#{Mathemagical::LaTeX::MBEC}*?)\$/m,
|
77
|
+
/\\\((#{Mathemagical::LaTeX::MBEC}*?)\\\)/m
|
78
|
+
],
|
79
|
+
:dmath_env_list=>[
|
80
|
+
/\$\$(#{Mathemagical::LaTeX::MBEC}*?)\$\$/m,
|
81
|
+
/\\\[(#{Mathemagical::LaTeX::MBEC}*?)\\\]/m
|
82
|
+
],
|
83
|
+
:escape_list=>[
|
84
|
+
/\\(.)/m
|
85
|
+
],
|
86
|
+
:through_list=>[
|
87
|
+
],
|
88
|
+
:escape_any=> false,
|
89
|
+
:without_parse=>false
|
90
|
+
}
|
91
|
+
|
92
|
+
def initialize(options = {})
|
93
|
+
@params = DEFAULT.merge(options)
|
94
|
+
@params[:parser] = Mathemagical::LaTeX::Parser.new unless @params[:parser] || @params[:without_parse]
|
95
|
+
|
96
|
+
@params[:math_envs] = collect_regexp(@params[:math_env_list])
|
97
|
+
@params[:dmath_envs] = collect_regexp(@params[:dmath_env_list])
|
98
|
+
@params[:escapes] = collect_regexp(@params[:escape_list])
|
99
|
+
@params[:throughs] = collect_regexp(@params[:through_list])
|
100
|
+
reset_encode_proc
|
101
|
+
reset_rescue_proc
|
102
|
+
reset_decode_proc
|
103
|
+
reset_unencode_proc
|
104
|
+
end
|
105
|
+
|
106
|
+
def reset_encode_proc
|
107
|
+
@encode_proc_re = INVALID_RE
|
108
|
+
@encode_proc = nil
|
109
|
+
end
|
110
|
+
|
111
|
+
def set_encode_proc(*re, &proc)
|
112
|
+
@encode_proc_re = collect_regexp(re)
|
113
|
+
@encode_proc = proc
|
114
|
+
end
|
115
|
+
|
116
|
+
def reset_rescue_proc
|
117
|
+
@rescue_proc = nil
|
118
|
+
end
|
119
|
+
|
120
|
+
def set_rescue_proc(&proc)
|
121
|
+
@rescue_proc = proc
|
122
|
+
end
|
123
|
+
|
124
|
+
def reset_decode_proc
|
125
|
+
@decode_proc = nil
|
126
|
+
end
|
127
|
+
|
128
|
+
def set_decode_proc(&proc)
|
129
|
+
@decode_proc = proc
|
130
|
+
end
|
131
|
+
|
132
|
+
def set_unencode_proc(&proc)
|
133
|
+
@unencode_proc = proc
|
134
|
+
end
|
135
|
+
|
136
|
+
def reset_unencode_proc
|
137
|
+
@unencode_proc = nil
|
138
|
+
end
|
139
|
+
|
140
|
+
def encode(src, *proc_re, &proc)
|
141
|
+
if proc_re.size>0 && proc_re[0].is_a?(MathData)
|
142
|
+
data = proc_re.shift
|
143
|
+
else
|
144
|
+
data = MathData.new
|
145
|
+
end
|
146
|
+
|
147
|
+
proc_re = proc_re.size==0 ? @encode_proc_re : collect_regexp(proc_re)
|
148
|
+
proc = @encode_proc unless proc
|
149
|
+
|
150
|
+
s = StringScanner.new(src)
|
151
|
+
encoded = ""
|
152
|
+
|
153
|
+
until s.eos?
|
154
|
+
if s.scan(/(.*?)(((((#{@params[:throughs]})|#{@params[:dmath_envs]})|#{@params[:math_envs]})|#{proc_re})|#{@params[:escapes]})/m)
|
155
|
+
encoded << s[1]
|
156
|
+
case
|
157
|
+
when s[6]
|
158
|
+
encoded << s[6]
|
159
|
+
when s[5], s[4]
|
160
|
+
env_src = s[5] || s[4]
|
161
|
+
if @params[:dmath_envs]=~env_src
|
162
|
+
encoded << "#{@params[:delimiter]}d#{data.dsrc_list.size}#{@params[:delimiter]}"
|
163
|
+
data.dsrc_list << env_src
|
164
|
+
else
|
165
|
+
encoded << "#{@params[:delimiter]}m#{data.msrc_list.size}#{@params[:delimiter]}"
|
166
|
+
data.msrc_list << env_src
|
167
|
+
end
|
168
|
+
when s[3]
|
169
|
+
size = s[3].size
|
170
|
+
s.pos = left = s.pos-size
|
171
|
+
if r=proc.call(s)
|
172
|
+
right = s.pos
|
173
|
+
encoded << "#{@params[:delimiter]}u#{data.user_list.size}#{@params[:delimiter]}"
|
174
|
+
data.user_list << r
|
175
|
+
data.usrc_list << s.string[left...right]
|
176
|
+
else
|
177
|
+
encoded << s.peek(size)
|
178
|
+
s.pos = s.pos+size
|
179
|
+
end
|
180
|
+
when s[2]
|
181
|
+
encoded << "#{@params[:delimiter]}e#{data.escape_list.size}#{@params[:delimiter]}"
|
182
|
+
@params[:escapes]=~s[2]
|
183
|
+
data.esrc_list << s[2]
|
184
|
+
data.escape_list << escapeXML($+, true)
|
185
|
+
end
|
186
|
+
else
|
187
|
+
encoded << s.rest
|
188
|
+
s.terminate
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
parse(data, @params[:parser]) unless @params[:without_parse]
|
193
|
+
|
194
|
+
return encoded, data
|
195
|
+
end
|
196
|
+
|
197
|
+
def error_to_html(e)
|
198
|
+
"<br />\n#{escapeXML(e.message)}<br />\n<code>#{escapeXML(e.done).gsub(/\n/, "<br />\n")}<strong>#{escapeXML(e.rest).gsub(/\n/, "<br />\n")}</strong></code><br />"
|
199
|
+
end
|
200
|
+
|
201
|
+
def latex_parser
|
202
|
+
@params[:parser] = Mathemagical::LaTeX::Parser.new unless @params[:parser]
|
203
|
+
@params[:parser]
|
204
|
+
end
|
205
|
+
|
206
|
+
def parse(data, parser=nil)
|
207
|
+
parser = latex_parser unless parser
|
208
|
+
(data.math_list.size...data.msrc_list.size).each do |i|
|
209
|
+
begin
|
210
|
+
@params[:math_envs]=~data.msrc_list[i]
|
211
|
+
data.math_list[i] = parser.parse($+)
|
212
|
+
rescue Mathemagical::LaTeX::ParseError => e
|
213
|
+
if @rescue_proc
|
214
|
+
data.math_list[i] = @rescue_proc.call(e)
|
215
|
+
else
|
216
|
+
data.math_list[i] = error_to_html(e)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
(data.dmath_list.size...data.dsrc_list.size).each do |i|
|
221
|
+
begin
|
222
|
+
@params[:dmath_envs]=~data.dsrc_list[i]
|
223
|
+
data.dmath_list[i] = parser.parse($+, true)
|
224
|
+
rescue Mathemagical::LaTeX::ParseError => e
|
225
|
+
if @rescue_proc
|
226
|
+
data.dmath_list[i] = @rescue_proc.call(e)
|
227
|
+
else
|
228
|
+
data.dmath_list[i] = error_to_html(e)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def decode(encoded, data, without_parsed = false, &proc)
|
235
|
+
return nil if encoded==nil
|
236
|
+
proc = @decode_proc unless proc
|
237
|
+
encoded.gsub(/#{Regexp.escape(@params[:delimiter])}([demu])(\d+)#{Regexp.escape(@params[:delimiter])}/) do
|
238
|
+
i = $2.to_i
|
239
|
+
t, d, s =
|
240
|
+
case $1
|
241
|
+
when "d"
|
242
|
+
[:dmath, without_parsed ? escapeXML(data.dsrc_list[i], true) : data.dmath_list[i], data.dsrc_list[i]]
|
243
|
+
when "e"
|
244
|
+
[:escape, data.escape_list[i], data.esrc_list[i]]
|
245
|
+
when "m"
|
246
|
+
[:math, without_parsed ? escapeXML(data.msrc_list[i], true) : data.math_list[i], data.msrc_list[i]]
|
247
|
+
when "u"
|
248
|
+
[:user, data.user_list[i], data.usrc_list[i]]
|
249
|
+
end
|
250
|
+
if proc
|
251
|
+
proc.call(d, :type=>t, :index=>i, :src=>s) || d
|
252
|
+
else
|
253
|
+
d
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def decode_partial(type, encoded, data, &proc)
|
259
|
+
return nil if encoded==nil
|
260
|
+
head =
|
261
|
+
case type
|
262
|
+
when :math
|
263
|
+
"m"
|
264
|
+
when :dmath
|
265
|
+
"d"
|
266
|
+
when :escape
|
267
|
+
"e"
|
268
|
+
when :user
|
269
|
+
"u"
|
270
|
+
else
|
271
|
+
return
|
272
|
+
end
|
273
|
+
encoded.gsub(/#{Regexp.escape(@params[:delimiter])}#{head}(\d+)#{Regexp.escape(@params[:delimiter])}/) do
|
274
|
+
i = $1.to_i
|
275
|
+
t, d, s =
|
276
|
+
case head
|
277
|
+
when "d"
|
278
|
+
[:dmath, data.dmath_list[i], data.dsrc_list[i]]
|
279
|
+
when "e"
|
280
|
+
[:escape, data.escape_list[i], data.esrc_list[i]]
|
281
|
+
when "m"
|
282
|
+
[:math, data.math_list[i], data.msrc_list[i]]
|
283
|
+
when "u"
|
284
|
+
[:user, data.user_list[i], data.usrc_list[i]]
|
285
|
+
end
|
286
|
+
if proc
|
287
|
+
proc.call(d, :type=>t, :index=>i, :src=>s) || "#{@params[:delimiter]}#{head}#{i}#{@params[:delimiter]}"
|
288
|
+
else
|
289
|
+
d
|
290
|
+
end
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def unencode(encoded, data, without_escape=false, &proc)
|
295
|
+
return nil if encoded==nil
|
296
|
+
proc = @unencode_proc unless proc
|
297
|
+
encoded.gsub(/#{Regexp.escape(@params[:delimiter])}([demu])(\d+)#{Regexp.escape(@params[:delimiter])}/) do
|
298
|
+
i = $2.to_i
|
299
|
+
t, s =
|
300
|
+
case $1
|
301
|
+
when "d"
|
302
|
+
[:dmath, data.dsrc_list[i]]
|
303
|
+
when "e"
|
304
|
+
[:escape, data.esrc_list[i]]
|
305
|
+
when "m"
|
306
|
+
[:math, data.msrc_list[i]]
|
307
|
+
when "u"
|
308
|
+
[:user, data.usrc_list[i]]
|
309
|
+
end
|
310
|
+
s = escapeXML(s, true) unless without_escape
|
311
|
+
if proc
|
312
|
+
proc.call(s, :type=>t, :index=>i) || s
|
313
|
+
else
|
314
|
+
s
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def self.encode(src)
|
320
|
+
@@default_latex = self.new unless @@default_latex
|
321
|
+
@@default_latex.encode(src)
|
322
|
+
end
|
323
|
+
|
324
|
+
def self.decode(src, data)
|
325
|
+
@@default_latex.decode(src, data)
|
326
|
+
end
|
327
|
+
|
328
|
+
def parse_eqnarray(src, parser=nil)
|
329
|
+
src = "\\begin{array}{ccc}#{src}\\end{array}"
|
330
|
+
parser = latex_parser unless parser
|
331
|
+
begin
|
332
|
+
parser.parse(src, true)
|
333
|
+
rescue Mathemagical::LaTeX::ParseError => e
|
334
|
+
e = Mathemagical::LaTeX::ParseError.new(e.message,
|
335
|
+
e.rest.sub(/\\end\{array\}\z/, '\end{eqnarray}'),
|
336
|
+
e.done.sub(/\A\\begin\{array\}\{ccc\}/, '\begin{eqnarray}'))
|
337
|
+
@rescue_proc ? @rescue_proc.call(e) : error_to_html(e)
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
def parse_single_command(src, parser=nil)
|
342
|
+
s = src[SINGLE_COMMAND_RE, 1]
|
343
|
+
parser = latex_parser unless parser
|
344
|
+
begin
|
345
|
+
parser.parse(s)
|
346
|
+
rescue Mathemagical::LaTeX::ParseError => e
|
347
|
+
src[SINGLE_COMMAND_RE, 2]
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
## This is the rakegem gemspec template. Make sure you read and understand
|
2
|
+
## all of the comments. Some sections require modification, and others can
|
3
|
+
## be deleted if you don't need them. Once you understand the contents of
|
4
|
+
## this file, feel free to delete any comments that begin with two hash marks.
|
5
|
+
## You can find comprehensive Gem::Specification documentation, at
|
6
|
+
## http://docs.rubygems.org/read/chapter/20
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.specification_version = 2 if s.respond_to? :specification_version=
|
9
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
10
|
+
s.rubygems_version = '1.3.5'
|
11
|
+
|
12
|
+
## Leave these as is they will be modified for you by the rake gemspec task.
|
13
|
+
## If your rubyforge_project name is different, then edit it and comment out
|
14
|
+
## the sub! line in the Rakefile
|
15
|
+
s.name = 'mathemagical'
|
16
|
+
s.version = '0.0.1'
|
17
|
+
s.date = '2013-02-16'
|
18
|
+
s.rubyforge_project = 'mathemagical'
|
19
|
+
|
20
|
+
## Make sure your summary is short. The description may be as long
|
21
|
+
## as you like.
|
22
|
+
s.summary = "MathML parsing library based on the math_ml gem."
|
23
|
+
s.description = "MathML parsing library based on the math_ml gem with modification."
|
24
|
+
|
25
|
+
## List the primary authors. If there are a bunch of authors, it's probably
|
26
|
+
## better to set the email to an email list or something. If you don't have
|
27
|
+
## a custom homepage, consider using your GitHub URL or the like.
|
28
|
+
s.authors = ["Jeremy McAnally", "KURODA Hiraku"]
|
29
|
+
s.email = 'jeremy@github.com'
|
30
|
+
s.homepage = 'http://github.com/jm/mathmagical'
|
31
|
+
|
32
|
+
## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
|
33
|
+
## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
|
34
|
+
s.require_paths = %w[lib]
|
35
|
+
|
36
|
+
## If your gem includes any executables, list them here.
|
37
|
+
s.executables = ["mathemagical"]
|
38
|
+
|
39
|
+
## Specify any RDoc options here. You'll want to add your README and
|
40
|
+
## LICENSE files to the extra_rdoc_files list.
|
41
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
42
|
+
s.extra_rdoc_files = %w[README.md LICENSE]
|
43
|
+
|
44
|
+
## List your runtime dependencies here. Runtime dependencies are those
|
45
|
+
## that are needed for an end user to actually USE your code.
|
46
|
+
s.add_dependency('htmlentities')
|
47
|
+
|
48
|
+
## List your development dependencies here. Development dependencies are
|
49
|
+
## those that are only needed during development
|
50
|
+
s.add_development_dependency('eim_xml')
|
51
|
+
|
52
|
+
## Leave this section as-is. It will be automatically generated from the
|
53
|
+
## contents of your Git repository via the gemspec task. DO NOT REMOVE
|
54
|
+
## THE MANIFEST COMMENTS, they are used as delimiters by the task.
|
55
|
+
# = MANIFEST =
|
56
|
+
s.files = %w[
|
57
|
+
LICENSE
|
58
|
+
README.md
|
59
|
+
Rakefile
|
60
|
+
bin/mathemagical
|
61
|
+
lib/mathemagical.rb
|
62
|
+
lib/mathemagical/element.rb
|
63
|
+
lib/mathemagical/latex.rb
|
64
|
+
lib/mathemagical/latex/builtin.rb
|
65
|
+
lib/mathemagical/latex/builtin/symbol.rb
|
66
|
+
lib/mathemagical/string.rb
|
67
|
+
lib/mathemagical/symbol/character_reference.rb
|
68
|
+
lib/mathemagical/symbol/entity_reference.rb
|
69
|
+
lib/mathemagical/symbol/utf8.rb
|
70
|
+
lib/mathemagical/util.rb
|
71
|
+
mathemagical.gemspec
|
72
|
+
spec/math_ml/element_spec.rb
|
73
|
+
spec/math_ml/latex/macro_spec.rb
|
74
|
+
spec/math_ml/latex/parser_spec.rb
|
75
|
+
spec/math_ml/latex/scanner_spec.rb
|
76
|
+
spec/math_ml/string_spec.rb
|
77
|
+
spec/math_ml/util_spec.rb
|
78
|
+
spec/math_ml_spec.rb
|
79
|
+
spec/util.rb
|
80
|
+
]
|
81
|
+
# = MANIFEST =
|
82
|
+
|
83
|
+
## Test files will be grabbed from the file list. Make sure the path glob
|
84
|
+
## matches what you actually use.
|
85
|
+
s.test_files = s.files.select { |path| path =~ /^test\/test_.*\.rb/ }
|
86
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "mathemagical"
|
2
|
+
|
3
|
+
describe Mathemagical::Element do
|
4
|
+
it "#display_style and #as_display_style" do
|
5
|
+
Mathemagical::Element.new("test").display_style.should == nil
|
6
|
+
e = Mathemagical::Element.new("test")
|
7
|
+
r = e.as_display_style
|
8
|
+
r.should equal(e)
|
9
|
+
e.display_style.should be_true
|
10
|
+
end
|
11
|
+
|
12
|
+
it "#pop" do
|
13
|
+
e = Mathemagical::Element.new("super")
|
14
|
+
s = Mathemagical::Element.new("sub")
|
15
|
+
|
16
|
+
e.pop.should be_nil
|
17
|
+
|
18
|
+
e << s
|
19
|
+
e.pop.should equal(s)
|
20
|
+
e.pop.should be_nil
|
21
|
+
|
22
|
+
e << "text"
|
23
|
+
e.pop.should == "text"
|
24
|
+
e.pop.should be_nil
|
25
|
+
end
|
26
|
+
|
27
|
+
it "#to_s" do
|
28
|
+
e = Mathemagical::Element.new("e")
|
29
|
+
e << "test<"
|
30
|
+
e.to_s.should == "<e>test<</e>"
|
31
|
+
end
|
32
|
+
end
|