#encoding: utf-8 require 'rspec' require 'asciimath' require_relative 'ast' require 'nokogiri' module Xml def self.mathml2_xsd @schema ||= File.open(File.expand_path('../schema/mathml2/mathml2.xsd', __FILE__)) { |io| Nokogiri::XML::Schema(io) } end def self.mathml3_xsd @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) 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 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 => 'A^_=A^expjϑ0', :mathml_word => 'A^_=A^expjϑ0', :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 => 'x+b2a<±b24a2ca', :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 => 'a2+b2=c2', :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 => 'x=b±b24ac2a', :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 => 'm=y2y1x2x1=ΔyΔx', :html => 'm=y2y1x2x1=Δ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 => 'f(x)=limΔx0f(x+Δx)f(x)Δx', :html => 'f(x)=limΔx0f(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 => 'ddx[xn]=nxn1', :html => 'ddx[xn]=nxn1', :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 => 'abf(x)dx=[F(x)]ab=F(b)F(a)', :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 => 'abf(x)dx=f(c)(ba)', :html => 'baf(x)dx=f(c)(ba)', :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 => 'ax2+bx+c=0', :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 => 'average value=1baabf(x)dx', :html => 'average value=1babaf(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 => 'ddx[axf(t)dt]=f(x)', :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 => 'ab^xy¯A_v', :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 => 'z1234', :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 => 'limxcf(x)f(c)xc', :html => 'limxcf(x)f(c)xc', :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 => '0π2g(x)dx', :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 => 'n=0an', :html => 'n=0an', :latex => '\\sum_{n = 0}^\\infty a_n', )) example('((1),(42))', &should_generate( :ast => matrix([%w[1], %w[42]]), :mathml => '(142)', :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 => '(123456789)', :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 => '|abcd|=adbc', :mathml_word => 'abcd=adbc', :html => '|abcd|=adbc', :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 => '(a11a1nam1amn)', :html => '(a11a1nam1amn)', :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 => 'k=1nk=1+2++n=n(n+1)2', :html => 'nk=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 => 'a+b+c=dn', :latex => '\\mathbf{a + b} + \\mathscr{c} = \\mathfrak{d^n}', )) example('max()', &should_generate( :ast => seq(symbol('max'), paren(nil)), :mathml => 'max()', :html => 'max()', :latex => '\\max ( )', )) example('text("foo")', &should_generate( :ast => text('"foo"'), :mathml => '"foo"', :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 => '1+23+4', :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 => 'si={1ifsi>si+1+1ifsisi+1', :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 => 'si={ifsi>si+1+1', :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 => 'abcd', :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 => 'c+da+b', :html => 'a+bc+d', :latex => '\\overset{a + b}{c + d}', )) example('underset a b', &should_generate( :ast => binary( symbol('underset'), 'a', 'b' ), :mathml => 'ba', :html => 'ba', :latex => '\\underset{a}{b}', )) example('sin a_c^b', &should_generate( :ast => seq( symbol('sin'), subsup('a', 'c', 'b') ), :mathml => 'sinacb', :html => 'sinabc', :latex => '\\sin a_c^b', )) example('max a_c^b', &should_generate( :ast => seq( symbol('max'), subsup('a', 'c', 'b') ), :mathml => 'maxacb', :html => 'maxabc', :latex => '\\max a_c^b', )) example('norm a_c^b', &should_generate( :ast => subsup(unary(symbol('norm'), 'a'), 'c', 'b'), :mathml => 'acb', :html => 'abc', :latex => '{\\lVert a \\rVert}_c^b', )) example('overarc a_b^c', &should_generate( :ast => subsup(unary(symbol('overarc'), 'a'), 'b', 'c'), :mathml => 'abc', :latex => '{\\overset{\\frown}{a}}_b^c' )) example('frown a_b^c', &should_generate( :ast => seq(symbol('frown'), subsup('a', 'b', 'c')), :mathml => 'abc', :latex => '\\frown a_b^c', )) example('sin(a_c^b)', &should_generate( :ast => seq(symbol('sin'), paren(subsup('a', 'c', 'b'))), :mathml => 'sin(acb)', :latex => '\\sin ( a_c^b )', )) example('text(a)a2)', &should_generate( :ast => seq(text('a'), identifier('a'), number('2'), symbol(')')), :mathml => 'aa2)', :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 => 'abcabc', :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 => 'xyzabc', :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 => '{x : xAxB}', :latex => '\\left \\{ x \\; : \\; x \\in A \\wedge x \\in B \\right \\}', )) 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