#encoding: utf-8
require 'rspec'
require 'asciimath'
require_relative 'ast'
require 'nokogiri'
module Xml
def self.mathml2_xsd
@mathml2_schema ||= File.open(File.expand_path('../schema/mathml2/mathml2.xsd', __FILE__)) { |io| Nokogiri::XML::Schema(io) }
end
def self.mathml3_xsd
@mathml3_schema ||= File.open(File.expand_path('../schema/mathml3/mathml3.xsd', __FILE__)) { |io| Nokogiri::XML::Schema(io) }
end
def self.parse(content)
Nokogiri::XML(content)
end
end
def should_generate(expected_output)
Proc.new { |example|
parsed = AsciiMath.parse(example.description)
variant = example.metadata[:variant]
if variant
if expected_output.key?(variant)
expected = expected_output[variant]
case variant
when :ast
expect(parsed.ast).to eq(expected)
when :mathml
expect(parsed.to_mathml).to eq(expected)
if defined?(RUBY_ENGINE) && RUBY_ENGINE != 'jruby'
xml_dom = Xml.parse(parsed.to_mathml(:xmlns => 'http://www.w3.org/1998/Math/MathML'))
Xml.mathml2_xsd.validate(xml_dom).each do |error|
fail(error.message)
end
Xml.mathml3_xsd.validate(xml_dom).each do |error|
fail(error.message)
end
end
when :mathml_word
expect(::AsciiMath::MathMLBuilder.new(:msword => true).append_expression(parsed.ast).to_s).to eq(expected)
when :html
expect(parsed.to_html).to eq(expected)
when :latex
expect(parsed.to_latex).to eq(expected)
else
skip("Unsupported output variant '#{variant}'")
end
else
skip("No example output provided for '#{variant}'")
end
else
skip('No known output variant found')
end
}
end
RSpec.shared_examples 'AsciiMath Examples' do
class << self
include ::AsciiMath::ASTHelper
end
example('underset(_)(hat A) = hat A exp j vartheta_0', &should_generate(
:ast => seq(
binary(
symbol('underset'),
group(symbol('_')),
group(unary(symbol('hat'), 'A'))
),
symbol('='),
unary(symbol('hat'), 'A'),
symbol('exp'),
'j',
sub(symbol('vartheta'), '0')
),
:mathml => '',
:mathml_word => '',
:latex => '\\underset{\\text{–}}{\\hat{A}} = \\hat{A} \\exp j \\vartheta_0'
))
example('x+b/(2a)<+-sqrt((b^2)/(4a^2)-c/a)', &should_generate(
:ast => seq(
'x',
symbol('+'),
infix('b', symbol('/'), grseq('2', 'a')),
symbol('<'),
symbol('+-'),
unary(
symbol('sqrt'),
grseq(
infix(
group(sup('b', '2')),
symbol('/'),
grseq('4', sup('a', '2'))
),
symbol('-'),
infix('c', symbol('/'), 'a')
)
)
),
:mathml => '',
:latex => 'x + \\frac{b}{2 a} < \\pm \\sqrt{\\frac{b^2}{4 a^2} - \\frac{c}{a}}',
))
example('a^2 + b^2 = c^2', &should_generate(
:ast => seq(
sup('a', '2'),
symbol('+'),
sup('b', '2'),
symbol('='),
sup('c', '2')
),
:mathml => '',
:html => 'a2+b2=c2',
:latex => 'a^2 + b^2 = c^2',
))
example('x = (-b+-sqrt(b^2-4ac))/(2a)', &should_generate(
:ast => seq(
'x',
symbol('='),
infix(
grseq(
symbol('-'), 'b',
symbol('+-'),
unary(symbol('sqrt'), grseq(sup('b', '2'), symbol('-'), '4', 'a', 'c'))
),
symbol('/'),
grseq('2', 'a'),
)
),
:mathml => '',
:latex => 'x = \\frac{- b \\pm \\sqrt{b^2 - 4 a c}}{2 a}',
))
example('m = (y_2 - y_1)/(x_2 - x_1) = (Deltay)/(Deltax)', &should_generate(
:ast => seq(
'm',
symbol('='),
infix(grseq(sub('y', '2'), symbol('-'), sub('y', '1')), symbol('/'), grseq(sub('x', '2'), symbol('-'), sub('x', '1'))),
symbol('='),
infix(grseq(symbol('Delta'), 'y'), symbol('/'), grseq(symbol('Delta'), 'x')),
),
:mathml => '',
:html => 'm=y2−y1x2−x1=ΔyΔx',
:latex => 'm = \\frac{y_2 - y_1}{x_2 - x_1} = \\frac{\\Delta y}{\\Delta x}',
))
example('f\'(x) = lim_(Deltax->0)(f(x+Deltax)-f(x))/(Deltax)', &should_generate(
:ast => seq(
symbol('f'),
symbol('\''),
paren('x'),
symbol('='),
sub(
symbol('lim'),
grseq(symbol('Delta'), 'x', symbol('->'), '0')
),
infix(
grseq(symbol('f'), paren(seq('x', symbol('+'), symbol('Delta'), 'x')), symbol('-'), symbol('f'), paren('x')),
symbol('/'),
grseq(symbol('Delta'), 'x')
)
),
:mathml => '',
:html => 'f′(x)=limΔx→0f(x+Δx)−f(x)Δx',
:latex => 'f \' ( x ) = \\lim_{\\Delta x \\rightarrow 0} \\frac{f \\left ( x + \\Delta x \\right ) - f ( x )}{\\Delta x}',
))
example('d/dx [x^n] = nx^(n - 1)', &should_generate(
:ast => seq(
infix('d', symbol('/'), symbol('dx')),
paren(symbol('['), sup('x', 'n'), symbol(']')),
symbol('='),
'n',
sup('x', grseq('n', symbol('-'), '1'))
),
:mathml => '',
:html => 'ddx[xn]=nxn−1',
:latex => '\\frac{d}{dx} [ x^n ] = n x^{n - 1}',
))
example('int_a^b f(x) dx = [F(x)]_a^b = F(b) - F(a)', &should_generate(
:ast => seq(
subsup(symbol('int'), 'a', 'b'),
symbol('f'),
paren('x'),
symbol('dx'),
symbol('='),
subsup(paren(symbol('['), seq('F', paren('x')), symbol(']')), 'a', 'b'),
symbol('='),
'F', paren('b'),
symbol('-'),
'F', paren('a')
),
:mathml => '',
:html => '∫baf(x)dx=[F(x)]ba=F(b)−F(a)',
:latex => '\\int_a^b f ( x ) dx = {\\left [ F ( x ) \\right ]}_a^b = F ( b ) - F ( a )',
))
example('int_a^b f(x) dx = f(c)(b - a)', &should_generate(
:ast => seq(
subsup(symbol('int'), 'a', 'b'),
symbol('f'),
paren('x'),
symbol('dx'),
symbol('='),
symbol('f'),
paren('c'),
paren(seq('b', symbol('-'), 'a')),
),
:mathml => '',
:html => '∫baf(x)dx=f(c)(b−a)',
:latex => '\\int_a^b f ( x ) dx = f ( c ) ( b - a )',
))
example('ax^2 + bx + c = 0', &should_generate(
:ast => seq(
'a',
sup('x', '2'),
symbol('+'),
'b',
'x',
symbol('+'),
'c',
symbol('='),
'0'
),
:mathml => '',
:html => 'ax2+bx+c=0',
:latex => 'a x^2 + b x + c = 0',
))
example('"average value"=1/(b-a) int_a^b f(x) dx', &should_generate(
:ast => seq(
'average value',
symbol('='),
infix('1', symbol('/'), grseq('b', symbol('-'), 'a')),
subsup(symbol('int'), 'a', 'b'),
symbol('f'),
paren('x'),
symbol('dx')
),
:mathml => '',
:html => 'average value=1b−a∫baf(x)dx',
:latex => '\\text{average value} = \\frac{1}{b - a} \\int_a^b f ( x ) dx',
))
example('d/dx[int_a^x f(t) dt] = f(x)', &should_generate(
:ast => seq(
infix('d', symbol('/'), symbol('dx')),
paren(
symbol('['),
seq(subsup(symbol('int'), 'a', 'x'), symbol('f'), paren('t'), symbol('dt')),
symbol(']')
),
symbol('='),
symbol('f'),
paren('x'),
),
:mathml => '',
:html => 'ddx[∫xaf(t)dt]=f(x)',
:latex => '\\frac{d}{dx} \\left [ \\int_a^x f ( t ) dt \\right ] = f ( x )',
))
example('hat(ab) bar(xy) ul(A) vec(v)', &should_generate(
:ast => seq(
unary(symbol('hat'), grseq('a', 'b')),
unary(symbol('bar'), grseq('x', 'y')),
unary(symbol('ul'), group('A')),
unary(symbol('vec'), group('v')),
),
:mathml => '',
:html => '^ab¯xyA_→v',
:latex => '\\hat{a b} \\overline{x y} \\underline{A} \\vec{v}',
))
example('z_12^34', &should_generate(
:ast => subsup('z', '12', '34'),
:mathml => '',
:html => 'z3412',
:latex => 'z_{12}^{34}',
))
example('lim_(x->c)(f(x)-f(c))/(x-c)', &should_generate(
:ast => seq(
sub(symbol('lim'), grseq('x', symbol('->'), 'c')),
infix(
grseq(symbol('f'), paren('x'), symbol('-'), symbol('f'), paren('c')),
symbol('/'),
grseq('x', symbol('-'), 'c')
)
),
:mathml => '',
:html => 'limx→cf(x)−f(c)x−c',
:latex => '\\lim_{x \\rightarrow c} \\frac{f ( x ) - f ( c )}{x - c}',
))
example('int_0^(pi/2) g(x) dx', &should_generate(
:ast => seq(
subsup(symbol('int'), '0', group(infix(symbol('pi'), symbol('/'), '2'))),
symbol('g'), paren('x'),
symbol('dx')
),
:mathml => '',
:html => '∫π20g(x)dx',
:latex => '\\int_0^{\\frac{\\pi}{2}} g ( x ) dx',
))
example('sum_(n=0)^oo a_n', &should_generate(
:ast => seq(
subsup(symbol('sum'), grseq('n', symbol('='), '0'), symbol('oo'),),
sub('a', 'n')
),
:mathml => '',
:html => '∞∑n=0an',
:latex => '\\sum_{n = 0}^\\infty a_n',
))
example('((1),(42))', &should_generate(
:ast => matrix([%w[1], %w[42]]),
:mathml => '',
:html => '(142)',
:latex => '\\left ( \\begin{matrix} 1 \\\\ 42 \\end{matrix} \\right )',
))
example('((1,42))', &should_generate(
:ast => matrix([%w[1 42]]),
:mathml => '',
:html => '(142)',
:latex => '\\left ( \\begin{matrix} 1 & 42 \\end{matrix} \\right )',
))
example('((1,2,3),(4,5,6),(7,8,9))', &should_generate(
:ast => matrix([%w[1 2 3], %w[4 5 6], %w[7 8 9]]),
:mathml => '',
:html => '(123456789)',
:latex => '\\left ( \\begin{matrix} 1 & 2 & 3 \\\\ 4 & 5 & 6 \\\\ 7 & 8 & 9 \\end{matrix} \\right )',
))
example('|(a,b),(c,d)|=ad-bc', &should_generate(
:ast => seq(
matrix(symbol('|'), [%w(a b), %w(c d)], symbol('|')),
symbol('='),
'a', 'd',
symbol('-'),
'b', 'c'
),
:mathml => '',
:mathml_word => '',
:html => '|abcd|=ad−bc',
:latex => '\\left | \\begin{matrix} a & b \\\\ c & d \\end{matrix} \\right | = a d - b c',
))
example('((a_(11), cdots , a_(1n)),(vdots, ddots, vdots),(a_(m1), cdots , a_(mn)))', &should_generate(
:ast => matrix([
[sub('a', group('11')), symbol('cdots'), sub('a', grseq('1', 'n'))],
[symbol('vdots'), symbol('ddots'), symbol('vdots')],
[sub('a', grseq('m', '1')), symbol('cdots'), sub('a', grseq('m', 'n'))]
]),
:mathml => '',
:html => '(a11⋯a1n⋮⋱⋮am1⋯amn)',
:latex => '\\left ( \\begin{matrix} a_{11} & \\cdots & a_{1 n} \\\\ \\vdots & \\ddots & \\vdots \\\\ a_{m 1} & \\cdots & a_{m n} \\end{matrix} \\right )',
))
example('sum_(k=1)^n k = 1+2+ cdots +n=(n(n+1))/2', &should_generate(
:ast => seq(
subsup(symbol('sum'), grseq('k', symbol('='), '1'), 'n'),
'k',
symbol('='),
'1', symbol('+'), '2', symbol('+'), symbol('cdots'), symbol('+'), 'n',
symbol('='),
infix(
grseq('n', paren(seq('n', symbol('+'), '1'))),
symbol('/'),
'2'
)
),
:mathml => '',
:html => 'n∑k=1k=1+2+⋯+n=n(n+1)2',
:latex => '\\sum_{k = 1}^n k = 1 + 2 + \\cdots + n = \\frac{n ( n + 1 )}{2}',
))
example('"Скорость"=("Расстояние")/("Время")', &should_generate(
:ast => seq(
'Скорость',
symbol('='),
infix(group('Расстояние'), symbol('/'), group('Время'))
),
:mathml => '',
:html => 'Скорость=РасстояниеВремя',
:latex => '\\text{Скорость} = \\frac{\\text{Расстояние}}{\\text{Время}}',
))
example('bb (a + b) + cc c = fr (d^n)', &should_generate(
:ast => seq(
unary(symbol('bb'), grseq('a', symbol('+'), 'b')),
symbol('+'),
unary(symbol('cc'), 'c'),
symbol('='),
unary(symbol('fr'), group(sup('d', 'n')))
),
:mathml => '',
:latex => '\\mathbf{a + b} + \\mathscr{c} = \\mathfrak{d^n}',
))
example('max()', &should_generate(
:ast => seq(symbol('max'), paren(nil)),
:mathml => '',
:html => 'max()',
:latex => '\\max ( )',
))
example('text("foo")', &should_generate(
:ast => text('"foo"'),
:mathml => '',
:html => '"foo"',
:latex => '\\text{"foo"}',
))
example('ubrace(1 + 2) obrace(3 + 4', &should_generate(
:ast => seq(
unary(symbol('ubrace'), grseq('1', symbol('+'), '2')),
unary(symbol('obrace'), group(symbol('('), seq('3', symbol('+'), '4'), nil))
),
:mathml => '',
:latex => '\\underbrace{1 + 2} \\overbrace{3 + 4}',
))
example('s\'_i = {(- 1, if s_i > s_(i + 1)),( + 1, if s_i <= s_(i + 1)):}', &should_generate(
:ast => seq(
's',
sub(symbol('\''), 'i'),
symbol('='),
matrix(
symbol('{'),
[
[seq(symbol('-'), '1'), seq(symbol('if'), sub('s', 'i'), symbol('>'), sub('s', grseq('i', symbol('+'), '1')))],
[seq(symbol('+'), '1'), seq(symbol('if'), sub('s', 'i'), symbol('<='), sub('s', grseq('i', symbol('+'), '1')))]
],
symbol(':}'),
)
),
:mathml => '',
:latex => 's \'_i = \\left \\{ \\begin{matrix} - 1 & \\operatorname{if} s_i > s_{i + 1} \\\\ + 1 & \\operatorname{if} s_i \\le s_{i + 1} \\end{matrix} \\right .',
))
example('s\'_i = {(, if s_i > s_(i + 1)),( + 1,):}', &should_generate(
:ast => seq(
's',
sub(symbol('\''), 'i'),
symbol('='),
matrix(
symbol('{'),
[
[[], [symbol('if'), sub('s', 'i'), symbol('>'), sub('s', grseq('i', symbol('+'), '1'))]],
[[symbol('+'), '1'], []]
],
symbol(':}')
)
),
:mathml => '',
:latex => 's \'_i = \\left \\{ \\begin{matrix} & \\operatorname{if} s_i > s_{i + 1} \\\\ + 1 & \\end{matrix} \\right .',
))
example('{:(a,b),(c,d):}', &should_generate(
:ast => matrix(
symbol('{:'),
[%w(a b), %w(c d)],
symbol(':}')
),
:mathml => '',
:latex => '\\begin{matrix} a & b \\\\ c & d \\end{matrix}',
))
example('overset (a + b) (c + d)', &should_generate(
:ast => binary(
symbol('overset'),
grseq('a', symbol('+'), 'b'),
grseq('c', symbol('+'), 'd')
),
:mathml => '',
:html => 'a+bc+d',
:latex => '\\overset{a + b}{c + d}',
))
example('underset a b', &should_generate(
:ast => binary(
symbol('underset'),
'a',
'b'
),
:mathml => '',
:html => 'ba',
:latex => '\\underset{a}{b}',
))
example('sin a_c^b', &should_generate(
:ast => seq(
symbol('sin'),
subsup('a', 'c', 'b')
),
:mathml => '',
:html => 'sinabc',
:latex => '\\sin a_c^b',
))
example('max a_c^b', &should_generate(
:ast => seq(
symbol('max'),
subsup('a', 'c', 'b')
),
:mathml => '',
:html => 'maxabc',
:latex => '\\max a_c^b',
))
example('norm a_c^b', &should_generate(
:ast => subsup(unary(symbol('norm'), 'a'), 'c', 'b'),
:mathml => '',
:html => '∥a∥bc',
:latex => '{\\lVert a \\rVert}_c^b',
))
example('overarc a_b^c', &should_generate(
:ast => subsup(unary(symbol('overarc'), 'a'), 'b', 'c'),
:mathml => '',
:latex => '{\\overset{\\frown}{a}}_b^c'
))
example('frown a_b^c', &should_generate(
:ast => seq(symbol('frown'), subsup('a', 'b', 'c')),
:mathml => '',
:latex => '\\frown a_b^c',
))
example('sin(a_c^b)', &should_generate(
:ast => seq(symbol('sin'), paren(subsup('a', 'c', 'b'))),
:mathml => '',
:latex => '\\sin ( a_c^b )',
))
example('text(a)a2)', &should_generate(
:ast => seq(text('a'), identifier('a'), number('2'), symbol(')')),
:mathml => '',
:html => 'aa2)',
:latex => '\\text{a} a 2 )',
))
example('cancel(a_b^c) cancel a_b^c', &should_generate(
:ast => seq(
unary(symbol('cancel'), group(subsup('a', 'b', 'c'))),
subsup(unary(symbol('cancel'), 'a'), 'b', 'c')
),
:mathml => '',
:latex => '\\cancel{a_b^c} {\\cancel{a}}_b^c',
))
example('color(red)(x) color(#123)(y) color(#1234ab)(z) colortext(blue)(a_b^c)', &should_generate(
:ast => seq(
binary(symbol('color'), color(255, 0, 0, 'red'), group('x')),
binary(symbol('color'), color(17, 34, 51, '#123'), group('y')),
binary(symbol('color'), color(18, 52, 171, '#1234ab'), group('z')),
binary(symbol('color'), color(0, 0, 255, 'blue'), group(subsup('a', 'b', 'c')))
),
:mathml => '',
:latex => '{\\color{red} x} {\\color[RGB]{17,34,51} y} {\\color[RGB]{18,52,171} z} {\\color{blue} a_b^c}',
))
example('{ x\ : \ x in A ^^ x in B }', &should_generate(
:ast => paren(
symbol('{'),
seq('x', symbol('\ '), ':', symbol('\ '), 'x', symbol('in'), 'A', symbol('^^'), 'x', symbol('in'), 'B'),
symbol('}')
),
:mathml => '',
:latex => '\\left \\{ x \\; : \\; x \\in A \\wedge x \\in B \\right \\}',
))
example('ii', &should_generate(
:ast => unary(symbol('ii'), identifier('')),
:mathml => ''
))
example('rm(ms)', &should_generate(
:ast => unary(symbol('rm'), grseq(identifier('m'), identifier('s'))),
:mathml => ''
))
example('hat', &should_generate(
:ast => unary(symbol('hat'), identifier('')),
:mathml => ''
))
example('40% * 3!', &should_generate(
:ast => seq('40', '%', symbol('*'), '3', '!'),
:mathml => '',
:html => '40%⋅3!',
:latex => '40 \% \cdot 3 !'
))
example('R(alpha_(K+1)|x)', &should_generate(
:ast => seq('R', paren(symbol('('), seq(sub('alpha', grseq('K', symbol('+'), '1')), symbol('|'), 'x'), symbol(')'))),
:mathml => '',
:latex => 'R \\left ( \\alpha_{K + 1} | x \\right )'
))
example('|(a),(b)|', &should_generate(
:ast => matrix(symbol('|'), [%w(a), %w(b)], symbol('|')),
))
example('|a+b|', &should_generate(
:ast => paren(symbol('|'), seq('a', '+', 'b'), symbol('|')),
))
example('|a+b|/c', &should_generate(
:ast => infix(paren(symbol('|'), seq('a', '+', 'b'), symbol('|')), '/', 'c'),
))
example('[[a,b,|,c],[d,e,|,f]]', &should_generate(
:ast => matrix(symbol('['), [['a', 'b', symbol('|'), 'c'], ['d', 'e', symbol('|'), 'f']], symbol(']')),
))
example('~a mlt b mgt -+c', &should_generate(
:ast => seq(symbol('~'), 'a', symbol('mlt'), 'b', symbol('mgt'), symbol('-+'), 'c'),
:latex => '\\sim a \\ll b \\gg \\mp c',
:mathml => ''
))
example('a+b+...+c', &should_generate(
:ast => seq('a', symbol('+'), 'b', symbol('+'), symbol('...'), symbol('+'), 'c'),
:latex => 'a + b + \ldots + c',
:mathml => ''
))
example('frac{a}{b}', &should_generate(
:ast => binary('frac', group(symbol('{'), 'a', symbol('}')), group(symbol('{'), 'b', symbol('}'))),
:latex => '\frac{a}{b}',
:mathml => ''
))
example('ubrace(((1, 0),(0, 1)))_("Adjustment to texture space")', &should_generate(
:ast => subsup(unary(symbol('ubrace'), group(matrix([%w[1 0], %w[0 1]]))), group("Adjustment to texture space"), []),
:latex => '\underbrace{\left ( \begin{matrix} 1 & 0 \\\\ 0 & 1 \end{matrix} \right )}_{\text{Adjustment to texture space}}',
:mathml => ''
))
version = RUBY_VERSION.split('.').map { |s| s.to_i }
if version[0] > 1 || version[1] > 8
example('Скорость=(Расстояние)/(Время)', &should_generate(
:ast => seq(
'С', 'к', 'о', 'р', 'о', 'с', 'т', 'ь',
symbol('='),
infix(
grseq('Р', 'а', 'с', 'с', 'т', 'о', 'я', 'н', 'и', 'е'),
symbol('/'),
grseq('В', 'р', 'е', 'м', 'я')
)
),
:mathml => '',
:html => 'Скорость=РасстояниеВремя',
:latex => 'С к о р о с т ь = \\frac{Р а с с т о я н и е}{В р е м я}'
))
end
end
describe 'AsciiMath::Parser', :variant => :ast do
include_examples 'AsciiMath Examples'
end
describe 'AsciiMath::MathMLBuilder', :variant => :mathml do
include_examples 'AsciiMath Examples'
end
describe 'AsciiMath::MathMLBuilder Microsoft Office', :variant => :mathml_word do
include_examples 'AsciiMath Examples'
end
describe 'AsciiMath::HTMLBuilder', :variant => :html do
include_examples 'AsciiMath Examples', :html
end
describe 'AsciiMath::LatexBuilder', :variant => :latex do
include_examples 'AsciiMath Examples'
end