mathemagical 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/README.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Mathemagical
|
2
|
+
============
|
3
|
+
|
4
|
+
## Usage and such
|
5
|
+
|
6
|
+
Things will go here. Oh, yes. They will.
|
7
|
+
|
8
|
+
## Origins
|
9
|
+
|
10
|
+
The `math_ml` gem is a perfectly fine MathML parser, but for my needs, it had a few issues:
|
11
|
+
|
12
|
+
* It requires `eim_xml`. I'd prefer not to introduce an XML generation library into production if I don't have to. This library uses a simple homegrown XML generation system that covers all our needs without an extra, unverified gem floating about.
|
13
|
+
* It lacks a binscript. Not a huge deal, but I'd have to write one anyhow since it's GPL2.
|
14
|
+
* It's crufty and had a few bugs. I'll continually be improving the quality of the code as I work on this.
|
15
|
+
* There was no place to contribute to the code that I could find.
|
16
|
+
|
17
|
+
So I decided to fork it. Since most interpretations of the GPL say that if you fork, you must rename, I decided to rename my fork to Mathemagical.
|
18
|
+
|
19
|
+
## License
|
20
|
+
|
21
|
+
The original `math_ml` gem is license under the GPL Version 2. My modifications are licensed under the BSD License or whatever is most liberal, but since the original code is all GPL2, it doesn't really matter much.
|
data/Rakefile
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'date'
|
4
|
+
|
5
|
+
#############################################################################
|
6
|
+
#
|
7
|
+
# Helper functions
|
8
|
+
#
|
9
|
+
#############################################################################
|
10
|
+
|
11
|
+
def name
|
12
|
+
@name ||= Dir['*.gemspec'].first.split('.').first
|
13
|
+
end
|
14
|
+
|
15
|
+
def version
|
16
|
+
line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
|
17
|
+
line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
|
18
|
+
end
|
19
|
+
|
20
|
+
def date
|
21
|
+
Date.today.to_s
|
22
|
+
end
|
23
|
+
|
24
|
+
def rubyforge_project
|
25
|
+
name
|
26
|
+
end
|
27
|
+
|
28
|
+
def gemspec_file
|
29
|
+
"#{name}.gemspec"
|
30
|
+
end
|
31
|
+
|
32
|
+
def gem_file
|
33
|
+
"#{name}-#{version}.gem"
|
34
|
+
end
|
35
|
+
|
36
|
+
def replace_header(head, header_name)
|
37
|
+
head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
|
38
|
+
end
|
39
|
+
|
40
|
+
#############################################################################
|
41
|
+
#
|
42
|
+
# Standard tasks
|
43
|
+
#
|
44
|
+
#############################################################################
|
45
|
+
|
46
|
+
require 'rdoc/task'
|
47
|
+
Rake::RDocTask.new do |rdoc|
|
48
|
+
rdoc.rdoc_dir = 'rdoc'
|
49
|
+
rdoc.title = "#{name} #{version}"
|
50
|
+
rdoc.rdoc_files.include('README*')
|
51
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
52
|
+
end
|
53
|
+
|
54
|
+
desc "Open an irb session preloaded with this library"
|
55
|
+
task :console do
|
56
|
+
sh "irb -rubygems -r ./lib/#{name}.rb"
|
57
|
+
end
|
58
|
+
|
59
|
+
#############################################################################
|
60
|
+
#
|
61
|
+
# Custom tasks (add your own tasks here)
|
62
|
+
#
|
63
|
+
#############################################################################
|
64
|
+
|
65
|
+
require 'rspec/core/rake_task'
|
66
|
+
RSpec::Core::RakeTask.new(:spec)
|
67
|
+
|
68
|
+
task :default => :spec
|
69
|
+
|
70
|
+
#############################################################################
|
71
|
+
#
|
72
|
+
# Packaging tasks
|
73
|
+
#
|
74
|
+
#############################################################################
|
75
|
+
|
76
|
+
desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
|
77
|
+
task :release => :build do
|
78
|
+
unless `git branch` =~ /^\* master$/
|
79
|
+
puts "You must be on the master branch to release!"
|
80
|
+
exit!
|
81
|
+
end
|
82
|
+
sh "git commit --allow-empty -a -m 'Release #{version}'"
|
83
|
+
sh "git tag v#{version}"
|
84
|
+
sh "git push origin master"
|
85
|
+
sh "git push origin v#{version}"
|
86
|
+
sh "gem push pkg/#{name}-#{version}.gem"
|
87
|
+
end
|
88
|
+
|
89
|
+
desc "Build #{gem_file} into the pkg directory"
|
90
|
+
task :build => :gemspec do
|
91
|
+
sh "mkdir -p pkg"
|
92
|
+
sh "gem build #{gemspec_file}"
|
93
|
+
sh "mv #{gem_file} pkg"
|
94
|
+
end
|
95
|
+
|
96
|
+
desc "Generate #{gemspec_file}"
|
97
|
+
task :gemspec => :validate do
|
98
|
+
# read spec file and split out manifest section
|
99
|
+
spec = File.read(gemspec_file)
|
100
|
+
head, manifest, tail = spec.split(" # = MANIFEST =\n")
|
101
|
+
|
102
|
+
# replace name version and date
|
103
|
+
replace_header(head, :name)
|
104
|
+
replace_header(head, :version)
|
105
|
+
replace_header(head, :date)
|
106
|
+
#comment this out if your rubyforge_project has a different name
|
107
|
+
replace_header(head, :rubyforge_project)
|
108
|
+
|
109
|
+
# determine file list from git ls-files
|
110
|
+
files = `git ls-files`.
|
111
|
+
split("\n").
|
112
|
+
sort.
|
113
|
+
reject { |file| file =~ /^\./ }.
|
114
|
+
reject { |file| file =~ /^(rdoc|pkg)/ }.
|
115
|
+
map { |file| " #{file}" }.
|
116
|
+
join("\n")
|
117
|
+
|
118
|
+
# piece file back together and write
|
119
|
+
manifest = " s.files = %w[\n#{files}\n ]\n"
|
120
|
+
spec = [head, manifest, tail].join(" # = MANIFEST =\n")
|
121
|
+
File.open(gemspec_file, 'w') { |io| io.write(spec) }
|
122
|
+
puts "Updated #{gemspec_file}"
|
123
|
+
end
|
124
|
+
|
125
|
+
desc "Validate #{gemspec_file}"
|
126
|
+
task :validate do
|
127
|
+
libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
|
128
|
+
unless libfiles.empty?
|
129
|
+
puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
|
130
|
+
exit!
|
131
|
+
end
|
132
|
+
unless Dir['VERSION*'].empty?
|
133
|
+
puts "A `VERSION` file at root level violates Gem best practices."
|
134
|
+
exit!
|
135
|
+
end
|
136
|
+
end
|
data/bin/mathemagical
ADDED
data/lib/mathemagical.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# MathML Library
|
2
|
+
#
|
3
|
+
# Copyright (C) 2005, KURODA Hiraku <hiraku@hinet.mydns.jp>
|
4
|
+
# You can redistribute it and/or modify it under GPL2.
|
5
|
+
|
6
|
+
require "strscan"
|
7
|
+
require "htmlentities"
|
8
|
+
|
9
|
+
module Mathemagical
|
10
|
+
VERSION = '0.0.1' unless defined?(:VERSION)
|
11
|
+
|
12
|
+
class Error < StandardError; end
|
13
|
+
|
14
|
+
def self.encode(s)
|
15
|
+
@entities ||= HTMLEntities.new
|
16
|
+
|
17
|
+
# TODO: HTMLEntities Y U DOUBLE ENCODE??
|
18
|
+
@entities.encode(s).gsub(/&([#a-zA-Z0-9]{2,24});/, '&\1;')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
require "mathemagical/element"
|
23
|
+
require "mathemagical/symbol/entity_reference"
|
24
|
+
require "mathemagical/latex"
|
@@ -0,0 +1,304 @@
|
|
1
|
+
module Mathemagical
|
2
|
+
class Element
|
3
|
+
attr_reader :display_style, :attributes, :children
|
4
|
+
attr_accessor :name
|
5
|
+
|
6
|
+
def initialize(name, options = {})
|
7
|
+
@name = name
|
8
|
+
@attributes = options
|
9
|
+
@children = []
|
10
|
+
end
|
11
|
+
|
12
|
+
def as_display_style
|
13
|
+
@display_style = true
|
14
|
+
self
|
15
|
+
end
|
16
|
+
|
17
|
+
def []=(key, value)
|
18
|
+
@attributes[key.to_sym] = value
|
19
|
+
end
|
20
|
+
|
21
|
+
def [](key)
|
22
|
+
@attributes[key.to_sym]
|
23
|
+
end
|
24
|
+
|
25
|
+
def <<(*obj)
|
26
|
+
@children += obj.flatten.map {|o| o.is_a?(String) ? Mathemagical.encode(o) : o }
|
27
|
+
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def to_s
|
32
|
+
children.empty? ? self_closing_xml : xml
|
33
|
+
end
|
34
|
+
|
35
|
+
def self_closing_xml
|
36
|
+
"<#{@name}#{build_attributes_string} />"
|
37
|
+
end
|
38
|
+
|
39
|
+
def xml
|
40
|
+
"<#{@name}#{build_attributes_string}>
|
41
|
+
#{child_ml}
|
42
|
+
</#{@name}>".gsub(/[\n\t]/, '')
|
43
|
+
end
|
44
|
+
|
45
|
+
def child_ml
|
46
|
+
@children.map(&:to_s).join("\n")
|
47
|
+
end
|
48
|
+
|
49
|
+
def build_attributes_string
|
50
|
+
return if @attributes.empty?
|
51
|
+
|
52
|
+
" " + @attributes.map do |key, value|
|
53
|
+
"#{key}='#{value}'"
|
54
|
+
end.join(" ")
|
55
|
+
end
|
56
|
+
|
57
|
+
def pop
|
58
|
+
@children.pop
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
module Variant
|
63
|
+
NORMAL = "normal"
|
64
|
+
BOLD = "bold"
|
65
|
+
BOLD_ITALIC = "bold-italic"
|
66
|
+
def variant=(v)
|
67
|
+
self["mathvariant"] = v
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
module Align
|
72
|
+
CENTER = "center"
|
73
|
+
LEFT = "left"
|
74
|
+
RIGHT = "right"
|
75
|
+
end
|
76
|
+
|
77
|
+
module Line
|
78
|
+
SOLID = "solid"
|
79
|
+
NONE = "none"
|
80
|
+
end
|
81
|
+
|
82
|
+
class Math < Element
|
83
|
+
def initialize(display_style)
|
84
|
+
super("math", "xmlns"=>"http://www.w3.org/1998/Math/MathML")
|
85
|
+
self[:display] = display_style ? "block" : "inline"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class Row < Element
|
90
|
+
def initialize
|
91
|
+
super("mrow")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class Break < Element
|
96
|
+
def initialize
|
97
|
+
super("br", :xmlns => 'http://www.w3.org/1999/xhtml')
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class None < Element
|
102
|
+
def initialize
|
103
|
+
super("none")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
class Space < Element
|
108
|
+
def initialize(width)
|
109
|
+
super("mspace", "width"=>width)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class Fenced < Element
|
114
|
+
attr_reader :open, :close
|
115
|
+
|
116
|
+
def initialize
|
117
|
+
super("mfenced")
|
118
|
+
end
|
119
|
+
|
120
|
+
def open=(o)
|
121
|
+
o = "" if o.to_s=="." || !o
|
122
|
+
o = "{" if o.to_s=="\\{"
|
123
|
+
self[:open] = Mathemagical.encode(o)
|
124
|
+
end
|
125
|
+
|
126
|
+
def close=(c)
|
127
|
+
c = "" if c.to_s=="." || !c
|
128
|
+
c = "}" if c.to_s=="\\}"
|
129
|
+
self[:close] = Mathemagical.encode(c)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
class Frac < Element
|
134
|
+
def initialize(numerator, denominator)
|
135
|
+
super("mfrac")
|
136
|
+
self << numerator
|
137
|
+
self << denominator
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class SubSup < Element
|
142
|
+
attr_reader :sub, :sup, :body
|
143
|
+
|
144
|
+
def initialize(display_style, body)
|
145
|
+
super("mrow")
|
146
|
+
as_display_style if display_style
|
147
|
+
@body = body
|
148
|
+
end
|
149
|
+
|
150
|
+
def update_name
|
151
|
+
if @sub || @sup
|
152
|
+
name = "m"
|
153
|
+
name << (@sub ? (@display_style ? "under" : "sub") : "")
|
154
|
+
name << (@sup ? (@display_style ? "over" : "sup") : "")
|
155
|
+
else
|
156
|
+
name = "mrow"
|
157
|
+
end
|
158
|
+
self.name = name
|
159
|
+
end
|
160
|
+
private :update_name
|
161
|
+
|
162
|
+
def update_contents
|
163
|
+
children.clear
|
164
|
+
children << @body
|
165
|
+
children << @sub if @sub
|
166
|
+
children << @sup if @sup
|
167
|
+
end
|
168
|
+
private :update_contents
|
169
|
+
|
170
|
+
def update
|
171
|
+
update_name
|
172
|
+
update_contents
|
173
|
+
end
|
174
|
+
private :update
|
175
|
+
|
176
|
+
def sub=(sub)
|
177
|
+
@sub = sub
|
178
|
+
update
|
179
|
+
end
|
180
|
+
|
181
|
+
def sup=(sup)
|
182
|
+
@sup = sup
|
183
|
+
update
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
class Over < Element
|
188
|
+
def initialize(base, over)
|
189
|
+
super("mover")
|
190
|
+
self << base << over
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
class Under < Element
|
195
|
+
def initialize(base, under)
|
196
|
+
super("munder")
|
197
|
+
self << base << under
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
class Number < Element
|
202
|
+
def initialize
|
203
|
+
super("mn")
|
204
|
+
end
|
205
|
+
|
206
|
+
def <<(o)
|
207
|
+
@children << o
|
208
|
+
self
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
class Identifier < Element
|
213
|
+
def initialize
|
214
|
+
super("mi")
|
215
|
+
end
|
216
|
+
|
217
|
+
def <<(o)
|
218
|
+
@children << Mathemagical.encode(o)
|
219
|
+
self
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
class Operator < Element
|
224
|
+
def initialize
|
225
|
+
super("mo")
|
226
|
+
end
|
227
|
+
|
228
|
+
def <<(o)
|
229
|
+
@children << Mathemagical.encode(o)
|
230
|
+
self
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
class Text < Element
|
235
|
+
def initialize
|
236
|
+
super("mtext")
|
237
|
+
end
|
238
|
+
|
239
|
+
def <<(o)
|
240
|
+
@children << Mathemagical.encode(o)
|
241
|
+
self
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
class Sqrt < Element
|
246
|
+
def initialize
|
247
|
+
super("msqrt")
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
class Root < Element
|
252
|
+
def initialize(index, base)
|
253
|
+
super("mroot")
|
254
|
+
self << base
|
255
|
+
self << index
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
class Table < Element
|
260
|
+
def initialize
|
261
|
+
super("mtable")
|
262
|
+
end
|
263
|
+
|
264
|
+
def set_align_attribute(name, a, default)
|
265
|
+
if a.is_a?(Array) && a.size>0
|
266
|
+
value = ""
|
267
|
+
a.each do |i|
|
268
|
+
value << " "+i
|
269
|
+
end
|
270
|
+
if value =~ /^( #{default})*$/
|
271
|
+
@attributes.delete(name)
|
272
|
+
else
|
273
|
+
@attributes[name] = value.strip
|
274
|
+
end
|
275
|
+
else
|
276
|
+
@attributes.delete(name)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def aligns=(a)
|
281
|
+
set_align_attribute("columnalign", a, Align::CENTER)
|
282
|
+
end
|
283
|
+
|
284
|
+
def vlines=(a)
|
285
|
+
set_align_attribute("columnlines", a, Line::NONE)
|
286
|
+
end
|
287
|
+
|
288
|
+
def hlines=(a)
|
289
|
+
set_align_attribute("rowlines", a, Line::NONE)
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
class Tr < Element
|
294
|
+
def initialize
|
295
|
+
super("mtr")
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
class Td < Element
|
300
|
+
def initialize
|
301
|
+
super("mtd")
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|