latexmath 0.1.4 → 1.0.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +50 -0
- data/LICENSE.md +33 -0
- data/README.adoc +133 -14
- data/artifacts/latex.bindings.python.json +1 -0
- data/artifacts/latex.bindings.rs.json +1 -0
- data/artifacts/latex.bindings.ruby.json +1 -0
- data/artifacts/latex.bindings.ts.json +1 -0
- data/artifacts/latex.checksums.txt +19 -0
- data/artifacts/latex.json +1 -0
- data/data/latex-symbols.json +3301 -0
- data/docs/model-contract.adoc +125 -0
- data/docs/parser-contract.adoc +349 -0
- data/docs/testsuite-pin.adoc +76 -0
- data/grammars/latex.wrappers.json +17 -0
- data/lib/latexmath/model/nodes.rb +918 -0
- data/lib/latexmath/model.rb +44 -0
- data/lib/latexmath/parser/artifact.rb +80 -0
- data/lib/latexmath/parser/configuration.rb +35 -0
- data/lib/latexmath/parser/engine.rb +161 -0
- data/lib/latexmath/parser/errors.rb +51 -0
- data/lib/latexmath/parser/native_recursion_guard.rb +43 -0
- data/lib/latexmath/parser/preprocessor.rb +77 -0
- data/lib/latexmath/parser/raw_parser.rb +84 -0
- data/lib/latexmath/parser/source_map.rb +121 -0
- data/lib/latexmath/parser/unwrap.rb +91 -0
- data/lib/latexmath/parser.rb +97 -0
- data/lib/latexmath/symbols.rb +74 -0
- data/lib/latexmath/version.rb +1 -1
- data/lib/latexmath.rb +5 -87
- data/model/latex.lml.checksums.txt +36 -0
- data/model/latex.lml.json +3350 -0
- metadata +60 -71
- data/.editorconfig +0 -14
- data/.github/workflows/test.yml +0 -80
- data/.gitignore +0 -22
- data/.rspec +0 -3
- data/.rubocop.yml +0 -129
- data/CODE_OF_CONDUCT.md +0 -74
- data/Gemfile +0 -10
- data/Gemfile.lock +0 -79
- data/Rakefile +0 -95
- data/bin/console +0 -14
- data/bin/setup +0 -8
- data/exe/latexmath +0 -8
- data/latexmath.gemspec +0 -34
- data/lib/latexmath/aggregator.rb +0 -351
- data/lib/latexmath/converter.rb +0 -424
- data/lib/latexmath/equation.rb +0 -13
- data/lib/latexmath/ext.rb +0 -9
- data/lib/latexmath/symbol.rb +0 -21
- data/lib/latexmath/tokenizer.rb +0 -82
- data/lib/latexmath/xml/builder.rb +0 -4
- data/lib/latexmath/xml/element.rb +0 -25
- data/lib/unimathsymbols.js.erb +0 -4
- data/lib/unimathsymbols.txt +0 -2864
- data/opal/latexmath-opal.rb +0 -40
- data/opal/ox.rb +0 -64
- data/opal/pseudoenumerator.rb +0 -19
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "htmlentities"
|
|
4
|
+
|
|
5
|
+
module Latexmath
|
|
6
|
+
# Maps positions in the preprocessed text back to the caller's text.
|
|
7
|
+
#
|
|
8
|
+
# It runs the steps of Preprocessor.call again, each as a scan that
|
|
9
|
+
# records, for every character it writes, the caller character it came
|
|
10
|
+
# from: a character that passes through keeps its origin; the characters
|
|
11
|
+
# of a replacement (a decoded or encoded entity, "\\ " for an encoded
|
|
12
|
+
# newline, a restored \text group) start at the origin of the text they
|
|
13
|
+
# replace, and a restored group's characters map one to one onto the
|
|
14
|
+
# caller's group. Removed text leaves no characters behind, so a position
|
|
15
|
+
# after it maps to the caller character that follows it.
|
|
16
|
+
#
|
|
17
|
+
# Only used to report refusals; the grammar input itself always comes
|
|
18
|
+
# from Preprocessor.call. ParseError positions are mapped only when this
|
|
19
|
+
# scan writes exactly the bytes Preprocessor.call wrote.
|
|
20
|
+
#
|
|
21
|
+
# Internal.
|
|
22
|
+
module SourceMap
|
|
23
|
+
FLAVOR = "xhtml1" # HTMLEntities.new's default, as Preprocessor::ENTITIES
|
|
24
|
+
DECODE = HTMLEntities::Decoder.new(FLAVOR).instance_variable_get(:@entity_regexp)
|
|
25
|
+
NAMES = HTMLEntities::MAPPINGS.fetch(FLAVOR)
|
|
26
|
+
# HTMLEntities' :hexadecimal encoding: the basic characters and every
|
|
27
|
+
# character outside U+0020..U+007E.
|
|
28
|
+
ENCODE = /[<>'"&]|[^\u{20}-\u{7E}]/
|
|
29
|
+
|
|
30
|
+
# Preprocessor.gsub_space_and_unicodes, as [pattern, literal replacement].
|
|
31
|
+
STEPS = [
|
|
32
|
+
[/((?<!\\) )|\n+/, ""],
|
|
33
|
+
[/\\\\ /, "\\\\"],
|
|
34
|
+
[/&/, "&"],
|
|
35
|
+
[/"/, "\""],
|
|
36
|
+
[/(?<!\\\\)\\
/, "\\ "],
|
|
37
|
+
[/
/, ""],
|
|
38
|
+
].freeze
|
|
39
|
+
|
|
40
|
+
module_function
|
|
41
|
+
|
|
42
|
+
# [preprocessed text, origins] for the normalized UTF-8 caller +input+:
|
|
43
|
+
# origins[i] is the caller character offset of preprocessed character i,
|
|
44
|
+
# and origins[text.length] is input.length.
|
|
45
|
+
def call(input)
|
|
46
|
+
originals = Preprocessor.text_groups(input).map { |range| [input[range], range.begin] }
|
|
47
|
+
text = input
|
|
48
|
+
map = (0..input.length).to_a
|
|
49
|
+
text, map = scan(text, map, DECODE) { |m| decode(m) }
|
|
50
|
+
text, map = scan(text, map, ENCODE) { |m| encode(m[0]) }
|
|
51
|
+
STEPS.each { |pattern, replacement| text, map = scan(text, map, pattern) { replacement } }
|
|
52
|
+
restore_groups(text, map, originals)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# The caller character offset of character +char+ of +text+ (the
|
|
56
|
+
# preprocessed text of +input+), or nil when the mapping does not
|
|
57
|
+
# reproduce +text+.
|
|
58
|
+
def caller_char(input, text, char)
|
|
59
|
+
mapped, map = call(input)
|
|
60
|
+
return nil unless mapped.b == text.b && char && char <= mapped.length
|
|
61
|
+
|
|
62
|
+
map[char]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def scan(text, map, pattern)
|
|
66
|
+
out = +""
|
|
67
|
+
origins = []
|
|
68
|
+
pos = 0
|
|
69
|
+
while (m = pattern.match(text, pos))
|
|
70
|
+
start = m.begin(0)
|
|
71
|
+
out << text[pos...start]
|
|
72
|
+
origins.concat(map[pos...start])
|
|
73
|
+
replacement = yield(m)
|
|
74
|
+
out << replacement
|
|
75
|
+
origins.concat([map[start]] * replacement.length)
|
|
76
|
+
pos = m.end(0)
|
|
77
|
+
end
|
|
78
|
+
out << text[pos..]
|
|
79
|
+
origins.concat(map[pos..])
|
|
80
|
+
[out, origins]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# HTMLEntities::Decoder#decode for one match.
|
|
84
|
+
def decode(match)
|
|
85
|
+
if match[1] && (codepoint = NAMES[match[1]])
|
|
86
|
+
codepoint.chr(Encoding::UTF_8)
|
|
87
|
+
elsif match[2]
|
|
88
|
+
match[2].to_i(10).chr(Encoding::UTF_8)
|
|
89
|
+
elsif match[3]
|
|
90
|
+
match[3].to_i(16).chr(Encoding::UTF_8)
|
|
91
|
+
else
|
|
92
|
+
match[0]
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def encode(char)
|
|
97
|
+
"&#x#{char.codepoints.first.to_s(16)};"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Preprocessor.replace_text_groups: the n-th group found in +text+ gives
|
|
101
|
+
# way to the n-th group of the caller's input, character for character.
|
|
102
|
+
def restore_groups(text, map, originals)
|
|
103
|
+
out = +""
|
|
104
|
+
origins = []
|
|
105
|
+
pos = 0
|
|
106
|
+
Preprocessor.text_groups(text).each do |range|
|
|
107
|
+
out << text[pos...range.begin]
|
|
108
|
+
origins.concat(map[pos...range.begin])
|
|
109
|
+
group, start = originals.shift
|
|
110
|
+
if group
|
|
111
|
+
out << group
|
|
112
|
+
origins.concat((start...(start + group.length)).to_a)
|
|
113
|
+
end
|
|
114
|
+
pos = range.end + 1
|
|
115
|
+
end
|
|
116
|
+
out << text[pos..]
|
|
117
|
+
origins.concat(map[pos..])
|
|
118
|
+
[out, origins]
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Latexmath
|
|
6
|
+
# The inverse of the grammar's shape normalization (option F), for the
|
|
7
|
+
# raw-tree boundary and parity replay only.
|
|
8
|
+
#
|
|
9
|
+
# The generator only ADDS captures, listed in grammars/latex.wrappers.json
|
|
10
|
+
# as {name => kind}. Unwrap replaces each wrapper by the value legacy
|
|
11
|
+
# produced there and re-runs Parslet's own flattening algebra
|
|
12
|
+
# (Parsanol::Atoms::CanFlatten#merge_fold / #flatten_repetition) over the
|
|
13
|
+
# parts, bottom-up:
|
|
14
|
+
#
|
|
15
|
+
# hash entries are split, in order, into runs of ordinary keys (one hash
|
|
16
|
+
# part each) and wrapper keys (one part each, by kind):
|
|
17
|
+
# text -> the wrapped value (nil / [] -> "", legacy unnamed)
|
|
18
|
+
# empty -> "" (String)
|
|
19
|
+
# list -> the list ([] -> "")
|
|
20
|
+
# nil -> nil
|
|
21
|
+
# text_list -> the text; "" (or []) -> [] (legacy named, empty)
|
|
22
|
+
# items -> the list, unchanged
|
|
23
|
+
# then folded with merge_fold, as legacy folded the sequence.
|
|
24
|
+
# A hash of nil-kind wrappers only is nil.
|
|
25
|
+
# array elements are unwrapped, then flatten_repetition (named).
|
|
26
|
+
module Unwrap
|
|
27
|
+
extend Parsanol::Atoms::CanFlatten
|
|
28
|
+
|
|
29
|
+
PATH = File.expand_path("../../../grammars/latex.wrappers.json", __dir__)
|
|
30
|
+
|
|
31
|
+
@wrappers = nil
|
|
32
|
+
@lock = Mutex.new
|
|
33
|
+
|
|
34
|
+
module_function
|
|
35
|
+
|
|
36
|
+
# The wrapper table of the bundled grammar.
|
|
37
|
+
def wrappers
|
|
38
|
+
@lock.synchronize { @wrappers ||= load(PATH).freeze }
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def load(path)
|
|
42
|
+
JSON.parse(File.read(path, encoding: "UTF-8")).fetch("wrappers")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def call(value, wrappers = self.wrappers)
|
|
46
|
+
case value
|
|
47
|
+
when Hash then unwrap_hash(value, wrappers)
|
|
48
|
+
when Array then Unwrap.send(:flatten_repetition, value.map { |v| call(v, wrappers) }, true)
|
|
49
|
+
else value
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def unwrap_hash(value, wrappers)
|
|
54
|
+
return value.to_h { |k, v| [k, call(v, wrappers)] } if value.keys.none? { |k| wrappers.key?(k.to_s) }
|
|
55
|
+
|
|
56
|
+
parts = []
|
|
57
|
+
run = nil
|
|
58
|
+
only_nil = true
|
|
59
|
+
value.each do |k, v|
|
|
60
|
+
kind = wrappers[k.to_s]
|
|
61
|
+
if kind
|
|
62
|
+
parts << run if run
|
|
63
|
+
run = nil
|
|
64
|
+
only_nil &&= kind == "nil"
|
|
65
|
+
parts << part(kind, call(v, wrappers))
|
|
66
|
+
else
|
|
67
|
+
only_nil = false
|
|
68
|
+
(run ||= {})[k] = call(v, wrappers)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
parts << run if run
|
|
72
|
+
return nil if only_nil
|
|
73
|
+
|
|
74
|
+
Unwrap.send(:foldl, parts) { |l, r| Unwrap.send(:merge_fold, l, r) }
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def part(kind, v)
|
|
78
|
+
case kind
|
|
79
|
+
when "text" then v.nil? || v == [] ? +"" : v
|
|
80
|
+
when "empty" then +""
|
|
81
|
+
when "list" then v == [] ? +"" : v
|
|
82
|
+
when "nil" then nil
|
|
83
|
+
when "text_list" then v == [] || v == "" ? [] : v
|
|
84
|
+
when "items" then v
|
|
85
|
+
else raise ArgumentError, "unknown wrapper kind #{kind}"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
private_class_method :unwrap_hash, :part
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "parsanol"
|
|
4
|
+
require_relative "version"
|
|
5
|
+
|
|
6
|
+
# The LaTeX stage parser, backed by a compiled Parsanol (PARG) grammar
|
|
7
|
+
# artifact generated from the pinned legacy Plurimath grammar.
|
|
8
|
+
#
|
|
9
|
+
# Latexmath.parse(input, decimal:, engine:) returns the typed LaTeX model, a
|
|
10
|
+
# Latexmath::Model::Document. Underneath are the grammar artifact, its
|
|
11
|
+
# declared syntax bindings and the internal raw-tree boundary
|
|
12
|
+
# (Latexmath::RawParser) that parity replay compares with the legacy
|
|
13
|
+
# Plurimath parser. See docs/parser-contract.adoc and
|
|
14
|
+
# docs/model-contract.adoc.
|
|
15
|
+
#
|
|
16
|
+
# lib/latexmath.rb requires this file.
|
|
17
|
+
module Latexmath
|
|
18
|
+
autoload :Artifact, "latexmath/parser/artifact"
|
|
19
|
+
autoload :Configuration, "latexmath/parser/configuration"
|
|
20
|
+
autoload :Engine, "latexmath/parser/engine"
|
|
21
|
+
autoload :Model, "latexmath/model"
|
|
22
|
+
autoload :Preprocessor, "latexmath/parser/preprocessor"
|
|
23
|
+
autoload :RawParser, "latexmath/parser/raw_parser"
|
|
24
|
+
autoload :SourceMap, "latexmath/parser/source_map"
|
|
25
|
+
autoload :Symbols, "latexmath/symbols"
|
|
26
|
+
autoload :Unwrap, "latexmath/parser/unwrap"
|
|
27
|
+
|
|
28
|
+
@artifacts = nil
|
|
29
|
+
@artifacts_lock = Mutex.new
|
|
30
|
+
|
|
31
|
+
class << self
|
|
32
|
+
# Parses LaTeX math +input+ (a String, valid UTF-8 or convertible to it)
|
|
33
|
+
# into the typed model: a Model::Document whose +expression+ is the
|
|
34
|
+
# formula's tree and +decimal+ the decimal marker it was parsed with
|
|
35
|
+
# ("." , "," or "\u066B"). Input with no formula (empty, or only
|
|
36
|
+
# spaces and newlines, which the preprocessing removes) gives a
|
|
37
|
+
# Document whose expression is nil.
|
|
38
|
+
#
|
|
39
|
+
# +engine+: :auto (native when available, else Ruby), :native or :ruby.
|
|
40
|
+
# Raises ParseError (offsets into +input+), NestingTooDeep,
|
|
41
|
+
# ArgumentError, ArtifactError or EngineUnavailable.
|
|
42
|
+
def parse(input, decimal: ".", engine: :auto)
|
|
43
|
+
text = RawParser.normalize_input(input)
|
|
44
|
+
RawParser.entry_for(decimal)
|
|
45
|
+
return Model::Document.new(decimal: decimal) if Preprocessor.call(text).empty?
|
|
46
|
+
|
|
47
|
+
raw = RawParser.parse(text, decimal: decimal, engine: engine)
|
|
48
|
+
build(raw, decimal, text)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# The verified, paired parse and Ruby bindings artifacts (internal).
|
|
52
|
+
def artifacts
|
|
53
|
+
artifact_store.fetch
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Points Latexmath at other artifacts (for tests). The next parse loads
|
|
57
|
+
# and verifies them.
|
|
58
|
+
def use_artifacts(path, bindings_path)
|
|
59
|
+
artifact_store.use(path, bindings_path)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def artifact_paths
|
|
63
|
+
[artifact_store.path, artifact_store.bindings_path]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The engine (:native or :ruby) of the last parser run on this thread,
|
|
67
|
+
# or nil before the first one.
|
|
68
|
+
def last_engine
|
|
69
|
+
Engine.last_engine
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def native_available?
|
|
73
|
+
Engine.native_available?
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def build(raw, decimal, text)
|
|
79
|
+
Model::Document.new(expression: raw.model, decimal: decimal)
|
|
80
|
+
rescue SystemStackError
|
|
81
|
+
raise NestingTooDeep.new("LaTeX input nests too deeply to build its model (Ruby stack exhausted)",
|
|
82
|
+
input: text, byte_offset: nil, char_offset: nil, grammar_input: raw.text)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def artifact_store
|
|
86
|
+
@artifacts_lock.synchronize do
|
|
87
|
+
@artifacts ||= Artifact.new(Configuration::DEFAULT_ARTIFACT_PATH,
|
|
88
|
+
Configuration::DEFAULT_BINDINGS_PATH)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Installs a hook into Parsanol at load time, so these are required, not
|
|
95
|
+
# autoloaded.
|
|
96
|
+
require_relative "parser/errors"
|
|
97
|
+
require_relative "parser/native_recursion_guard"
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Latexmath
|
|
6
|
+
# The legacy LaTeX symbol table, exported from the pinned Plurimath by
|
|
7
|
+
# scripts/export_symbols.rb into data/latex-symbols.json: for every
|
|
8
|
+
# spelling a symbol capture can have, its Unicode value, the MathML
|
|
9
|
+
# element the legacy writer uses and its kind. Loading it needs no
|
|
10
|
+
# Plurimath.
|
|
11
|
+
#
|
|
12
|
+
# The legacy transform looks a captured symbol up by its text, stripped;
|
|
13
|
+
# text the table lacks stays a plain symbol of that text (#resolve).
|
|
14
|
+
module Symbols
|
|
15
|
+
DATA_PATH = File.expand_path("../../data/latex-symbols.json", __dir__)
|
|
16
|
+
FORMAT = "latexmath-symbols/v1"
|
|
17
|
+
|
|
18
|
+
# +spelling+: the captured text ("alpha", "α", "("); +value+: its
|
|
19
|
+
# Unicode text; +mathml+: "mi" or "mo", nil for text the table lacks;
|
|
20
|
+
# +kind+: "symbol", "open", "close" or "paren"; +plurimath_class+: the
|
|
21
|
+
# legacy class relative to Plurimath::Math::Symbols.
|
|
22
|
+
Entry = Struct.new(:spelling, :value, :mathml, :kind, :plurimath_class, keyword_init: true)
|
|
23
|
+
|
|
24
|
+
@table = nil
|
|
25
|
+
@lock = Mutex.new
|
|
26
|
+
|
|
27
|
+
module_function
|
|
28
|
+
|
|
29
|
+
# The Entry for the captured +text+, or nil when the table lacks it.
|
|
30
|
+
def lookup(text)
|
|
31
|
+
table[:entries][text.to_s.strip]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# The Entry for the captured +text+; text the table lacks becomes a
|
|
35
|
+
# plain symbol of its own (stripped) text, as in the legacy transform.
|
|
36
|
+
def resolve(text)
|
|
37
|
+
key = text.to_s.strip
|
|
38
|
+
table[:entries][key] ||
|
|
39
|
+
Entry.new(spelling: key, value: key, mathml: nil, kind: "symbol", plurimath_class: "Symbol").freeze
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def size
|
|
43
|
+
table[:entries].size
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Where the table comes from: the oracle repository, commit, lib tree and
|
|
47
|
+
# Ruby of the export.
|
|
48
|
+
def provenance
|
|
49
|
+
table[:oracle]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def table
|
|
53
|
+
@lock.synchronize { @table ||= load_table(DATA_PATH) }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def load_table(path)
|
|
57
|
+
data = JSON.parse(File.read(path, encoding: "UTF-8"))
|
|
58
|
+
raise ArtifactError, "#{path} is #{data['format'].inspect}, not #{FORMAT}" unless data["format"] == FORMAT
|
|
59
|
+
|
|
60
|
+
entries = data.fetch("symbols").to_h do |r|
|
|
61
|
+
entry = Entry.new(spelling: r.fetch("spelling"), value: r.fetch("value"), mathml: r.fetch("mathml"),
|
|
62
|
+
kind: r.fetch("kind"), plurimath_class: r.fetch("class")).freeze
|
|
63
|
+
[entry.spelling, entry]
|
|
64
|
+
end
|
|
65
|
+
raise ArtifactError, "#{path} repeats a spelling" unless entries.size == data["symbols"].size
|
|
66
|
+
|
|
67
|
+
{ oracle: data.fetch("oracle").freeze, entries: entries.freeze }.freeze
|
|
68
|
+
rescue JSON::ParserError, KeyError, SystemCallError => e
|
|
69
|
+
raise ArtifactError, "symbol table #{path} cannot be loaded: #{e.class}: #{e.message}"
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private_class_method :table, :load_table
|
|
73
|
+
end
|
|
74
|
+
end
|
data/lib/latexmath/version.rb
CHANGED
data/lib/latexmath.rb
CHANGED
|
@@ -1,88 +1,6 @@
|
|
|
1
|
-
#
|
|
2
|
-
require 'json'
|
|
3
|
-
require 'htmlentities'
|
|
4
|
-
require 'ox'
|
|
5
|
-
require_relative 'latexmath/ext'
|
|
6
|
-
require_relative 'latexmath/version'
|
|
7
|
-
require_relative 'latexmath/constants/symbols'
|
|
8
|
-
require_relative 'latexmath/aggregator'
|
|
9
|
-
require_relative 'latexmath/converter'
|
|
10
|
-
require_relative 'latexmath/symbol'
|
|
11
|
-
require_relative 'latexmath/tokenizer'
|
|
12
|
-
require_relative 'latexmath/xml/element'
|
|
13
|
-
require_relative 'latexmath/equation'
|
|
1
|
+
# frozen_string_literal: true
|
|
14
2
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
'\\pmatrix',
|
|
20
|
-
'\\pmatrix*',
|
|
21
|
-
'\\bmatrix',
|
|
22
|
-
'\\bmatrix*',
|
|
23
|
-
'\\Bmatrix',
|
|
24
|
-
'\\Bmatrix*',
|
|
25
|
-
'\\vmatrix',
|
|
26
|
-
'\\vmatrix*',
|
|
27
|
-
'\\Vmatrix',
|
|
28
|
-
'\\Vmatrix*',
|
|
29
|
-
'\\array',
|
|
30
|
-
'\\split',
|
|
31
|
-
'\\substack'
|
|
32
|
-
].freeze
|
|
33
|
-
|
|
34
|
-
SPACES = ['\\,', '\\:', '\\;', '\\\\'].freeze
|
|
35
|
-
STYLES = {
|
|
36
|
-
'\\bf' => 'mathbf'
|
|
37
|
-
}.freeze
|
|
38
|
-
|
|
39
|
-
LIMITS = ['\\lim', '\\sup', '\\inf', '\\max', '\\min'].freeze
|
|
40
|
-
COMMANDS = {
|
|
41
|
-
# command: [params_count, mathml_equivalent, attributes]
|
|
42
|
-
'_' => [2, 'msub', {}],
|
|
43
|
-
'^' => [2, 'msup', {}],
|
|
44
|
-
'_^' => [3, 'msubsup', {}],
|
|
45
|
-
'\\frac' => [2, 'mfrac', {}],
|
|
46
|
-
'\\sqrt' => [1, 'msqrt', {}],
|
|
47
|
-
'\\root' => [2, 'mroot', {}],
|
|
48
|
-
'\\binom' => [2, 'mfrac', { "linethickness": '0' }],
|
|
49
|
-
'\\left' => [
|
|
50
|
-
1,
|
|
51
|
-
'mo',
|
|
52
|
-
[%w[stretchy true], %w[fence true], %w[form prefix]]
|
|
53
|
-
],
|
|
54
|
-
'\\right' => [
|
|
55
|
-
1,
|
|
56
|
-
'mo',
|
|
57
|
-
[%w[stretchy true], %w[fence true], %w[form postfix]]
|
|
58
|
-
],
|
|
59
|
-
'\\overline' => [1, 'mover', {}],
|
|
60
|
-
'\\bar' => [1, 'mover', {}],
|
|
61
|
-
'\\underline' => [1, 'munder', {}],
|
|
62
|
-
'\\limits' => [3, 'munderover', {}],
|
|
63
|
-
'\\overrightarrow' => [1, 'mover', {}]
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
COMMANDS['\\quad'] = [0, 'mo', { "mathvariant": 'italic', separator: 'true' }]
|
|
67
|
-
COMMANDS['\\qquad'] = [0, 'mo', { "mathvariant": 'italic', separator: 'true' }]
|
|
68
|
-
SPACES.each do |space|
|
|
69
|
-
COMMANDS[space] = [0, 'mspace', { "width": '0.167em' }]
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
MATRICES.each do |matrix|
|
|
73
|
-
COMMANDS[matrix] = [1, 'mtable', {}]
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
LIMITS.each do |limit|
|
|
77
|
-
COMMANDS[limit] = [1, 'munder', {}]
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def self.parse(string)
|
|
81
|
-
lxm_input = HTMLEntities.new.decode(string)
|
|
82
|
-
|
|
83
|
-
# parse
|
|
84
|
-
Equation.new(lxm_input)
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
class Error < StandardError; end
|
|
88
|
-
end
|
|
3
|
+
# LaTeX math parser for Plurimath. Latexmath.parse returns the typed LaTeX
|
|
4
|
+
# model; see lib/latexmath/parser.rb, docs/parser-contract.adoc and
|
|
5
|
+
# docs/model-contract.adoc.
|
|
6
|
+
require_relative "latexmath/parser"
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Written by scripts/verify_lml.rb: the LML checksum of every model
|
|
2
|
+
# document, then the sha256 of the documents file.
|
|
3
|
+
Latexmath::Model::Args sha256:5462960711eec1726badec07cd0a0697463c8fc35df4769e10bd23323bfabaa6
|
|
4
|
+
Latexmath::Model::Base sha256:3ce2dcae82ec2dc51287195a3cee13977f9325889a8523da2643f56c9d3ef1f4
|
|
5
|
+
Latexmath::Model::Binary sha256:10ab6af22512dc6e3b794d02d715b4b45e23c7a448766233d09e12cc96e5fb1f
|
|
6
|
+
Latexmath::Model::BinaryFunctions sha256:91faa02cd9931ba27287f393594d7b1312d8324f99fe7f07a64e3bbc15b2ef17
|
|
7
|
+
Latexmath::Model::Dividend sha256:b3e9b1d3d03d9640d83b3268ced1204f7d8ccde89453115823df34bf43aa0dd8
|
|
8
|
+
Latexmath::Model::Divisor sha256:8830d0ca59e31ba6645cde667705156f73d02fdc60d87745f8d8beac81725f0a
|
|
9
|
+
Latexmath::Model::Document sha256:c770d3c85caedc6dd289102e426cc8cf5cc7327e54f021c36ec9d277c5fbe77a
|
|
10
|
+
Latexmath::Model::Ending sha256:4e6b05b7cc3e0cf383082b5b91eab692d4fcbe7aa9f3709b015b80c8eedaec8b
|
|
11
|
+
Latexmath::Model::Environment sha256:4ca449f28a047f5c233571f18c73a53ad40fc283aedb7ab7876a25866f3fce10
|
|
12
|
+
Latexmath::Model::Expression sha256:e5cb9286cf37e399d9fe11058e517d5dd4a11e3076fb7d08141ca4689252ec07
|
|
13
|
+
Latexmath::Model::FirstValue sha256:f23a8ce39e0e1a4800e8f46ccef2cc39b1f0f50333aab254c41cee80ad423638
|
|
14
|
+
Latexmath::Model::IntermediateExp sha256:124f5a4368fc210f21361fbc54629c6da4c44c4e9185c354c78a4aac97b3cee7
|
|
15
|
+
Latexmath::Model::ItemsItem sha256:28625103e5527d530dec8971463a4186aaa1fa31a4960be1e44103e1199cf601
|
|
16
|
+
Latexmath::Model::LatexTree sha256:ab699f312f98a337d0443fb0e46e901ec3a5f02d3b1dcc1a1e41edf2db8a8c96
|
|
17
|
+
Latexmath::Model::LeftParen sha256:9948524e67fa426033187ebc1c9e015e2c5e3a4ace59b81ca3d223de9e849484
|
|
18
|
+
Latexmath::Model::LeftRight sha256:d219a07366ebf63b556957d75d0a2ef9a7b0b4d69db9d5a47cef3ba198673072
|
|
19
|
+
Latexmath::Model::Limits sha256:04d4af34438ccab20272798c0d23877dd1a14cc95c01290ffc37ba156d1a8a0c
|
|
20
|
+
Latexmath::Model::MathSymbol sha256:655eefa4badd24c9960f2ba8728f4a1981b808c9fbe076bfdc7875b8703cea3a
|
|
21
|
+
Latexmath::Model::Options sha256:09363eec7dc62f50a06ea6a1065280fbeaef1385e2e84f1da92f7df4e9a01e6c
|
|
22
|
+
Latexmath::Model::Over sha256:5ec5c3ccd41a5c65149887cbed15632ac8dd2a05bd66ba337cde4b6cb1cee7c1
|
|
23
|
+
Latexmath::Model::Power sha256:6bf73b69d65980865f2c1d8922e0a2ab6c43c76d96f9135b94f9ffb2936da81a
|
|
24
|
+
Latexmath::Model::PowerBase sha256:31dae32f1de56ab2203d7b649c760a1e25ab170b6847dbec3286d5c9cd24ecae
|
|
25
|
+
Latexmath::Model::RightParen sha256:b6f7510f3a8bf4cf8b61e15074e483f921c1ec51c512a333a7eb68cc53e24569
|
|
26
|
+
Latexmath::Model::SecondValue sha256:94bef28cac697a6d37d1e84bf8441202effabfb540ae017790faba5c83c59a3c
|
|
27
|
+
Latexmath::Model::Sequence sha256:3e7caee1c9af131b51beb87a8d666b03961c0885bba79f9db865c2c5eb9cca61
|
|
28
|
+
Latexmath::Model::Subscript sha256:ad5250291647cc6ec2caaecf3571a2a0244d77e2a3320b2513f00050a747c08b
|
|
29
|
+
Latexmath::Model::Supscript sha256:2143627f3829bcf53abfdebbdbab2824de4ddfcbdd77530075d0edbcfa07d48f
|
|
30
|
+
Latexmath::Model::SymbolNode sha256:e01f443c419fdcca1b2c3c70e21532d9aea1be13a4304558e01bb08644ae5fa1
|
|
31
|
+
Latexmath::Model::TableData sha256:b2e2b1c329b4017897fde5d61958487c3e88da33581c414480e15d8746fb7572
|
|
32
|
+
Latexmath::Model::TernaryClass sha256:bd741ce478aa270bd90cd24c9e2e8a073afc750e9796113a79e79284c5ae4f8b
|
|
33
|
+
Latexmath::Model::ThirdValue sha256:2ce323686afb2f7ad053ac49a62eb1dc092cbddd126f94f5ac2bc39cf043c4ac
|
|
34
|
+
Latexmath::Model::UnaryFunctions sha256:df5b690a991e6963faecbfdd2bebb06deb319bd0485d20c48379a3d844a67326
|
|
35
|
+
Latexmath::Model::UnderOver sha256:a6e7aa8d2ae55610b8473eed4fa39481c75bbfc09e1a44f4d14fd2df63ce8713
|
|
36
|
+
model/latex.lml.json sha256:312b1de981c4aa6435fba996c4fc34d34f401e36742a6cdc1c4e9f1cf1f20d60
|