Passed first set of builder tests.

This commit is contained in:
Michael Griebling
2023-01-07 07:53:46 -05:00
parent a5e6d37d0c
commit 9afc6970d4
6 changed files with 1354 additions and 475 deletions

View File

@@ -273,56 +273,56 @@ public class MTMathAtomFactory {
"amalg" : MTMathAtom(type: .binaryOperator, value: "\u{2A3F}"),
// No limit operators
"log" : MTMathAtomFactory.getOperator(withName: "log", limits: false),
"lg" : MTMathAtomFactory.getOperator(withName: "lg", limits: false),
"ln" : MTMathAtomFactory.getOperator(withName: "ln", limits: false),
"sin" : MTMathAtomFactory.getOperator(withName: "sin", limits: false),
"arcsin" : MTMathAtomFactory.getOperator(withName: "arcsin", limits: false),
"sinh" : MTMathAtomFactory.getOperator(withName: "sinh", limits: false),
"cos" : MTMathAtomFactory.getOperator(withName: "cos", limits: false),
"arccos" : MTMathAtomFactory.getOperator(withName: "arccos", limits: false),
"cosh" : MTMathAtomFactory.getOperator(withName: "cosh", limits: false),
"tan" : MTMathAtomFactory.getOperator(withName: "tan", limits: false),
"arctan" : MTMathAtomFactory.getOperator(withName: "arctan", limits: false),
"tanh" : MTMathAtomFactory.getOperator(withName: "tanh", limits: false),
"cot" : MTMathAtomFactory.getOperator(withName: "cot", limits: false),
"coth" : MTMathAtomFactory.getOperator(withName: "coth", limits: false),
"sec" : MTMathAtomFactory.getOperator(withName: "sec", limits: false),
"csc" : MTMathAtomFactory.getOperator(withName: "csc", limits: false),
"arg" : MTMathAtomFactory.getOperator(withName: "arg", limits: false),
"ker" : MTMathAtomFactory.getOperator(withName: "ker", limits: false),
"dim" : MTMathAtomFactory.getOperator(withName: "dim", limits: false),
"hom" : MTMathAtomFactory.getOperator(withName: "hom", limits: false),
"exp" : MTMathAtomFactory.getOperator(withName: "exp", limits: false),
"deg" : MTMathAtomFactory.getOperator(withName: "deg", limits: false),
"log" : MTMathAtomFactory.operatorWithName( "log", limits: false),
"lg" : MTMathAtomFactory.operatorWithName( "lg", limits: false),
"ln" : MTMathAtomFactory.operatorWithName( "ln", limits: false),
"sin" : MTMathAtomFactory.operatorWithName( "sin", limits: false),
"arcsin" : MTMathAtomFactory.operatorWithName( "arcsin", limits: false),
"sinh" : MTMathAtomFactory.operatorWithName( "sinh", limits: false),
"cos" : MTMathAtomFactory.operatorWithName( "cos", limits: false),
"arccos" : MTMathAtomFactory.operatorWithName( "arccos", limits: false),
"cosh" : MTMathAtomFactory.operatorWithName( "cosh", limits: false),
"tan" : MTMathAtomFactory.operatorWithName( "tan", limits: false),
"arctan" : MTMathAtomFactory.operatorWithName( "arctan", limits: false),
"tanh" : MTMathAtomFactory.operatorWithName( "tanh", limits: false),
"cot" : MTMathAtomFactory.operatorWithName( "cot", limits: false),
"coth" : MTMathAtomFactory.operatorWithName( "coth", limits: false),
"sec" : MTMathAtomFactory.operatorWithName( "sec", limits: false),
"csc" : MTMathAtomFactory.operatorWithName( "csc", limits: false),
"arg" : MTMathAtomFactory.operatorWithName( "arg", limits: false),
"ker" : MTMathAtomFactory.operatorWithName( "ker", limits: false),
"dim" : MTMathAtomFactory.operatorWithName( "dim", limits: false),
"hom" : MTMathAtomFactory.operatorWithName( "hom", limits: false),
"exp" : MTMathAtomFactory.operatorWithName( "exp", limits: false),
"deg" : MTMathAtomFactory.operatorWithName( "deg", limits: false),
// Limit operators
"lim" : MTMathAtomFactory.getOperator(withName: "lim", limits: true),
"limsup" : MTMathAtomFactory.getOperator(withName: "lim sup", limits: true),
"liminf" : MTMathAtomFactory.getOperator(withName: "lim inf", limits: true),
"max" : MTMathAtomFactory.getOperator(withName: "max", limits: true),
"min" : MTMathAtomFactory.getOperator(withName: "min", limits: true),
"sup" : MTMathAtomFactory.getOperator(withName: "sup", limits: true),
"inf" : MTMathAtomFactory.getOperator(withName: "inf", limits: true),
"det" : MTMathAtomFactory.getOperator(withName: "det", limits: true),
"Pr" : MTMathAtomFactory.getOperator(withName: "Pr", limits: true),
"gcd" : MTMathAtomFactory.getOperator(withName: "gcd", limits: true),
"lim" : MTMathAtomFactory.operatorWithName( "lim", limits: true),
"limsup" : MTMathAtomFactory.operatorWithName( "lim sup", limits: true),
"liminf" : MTMathAtomFactory.operatorWithName( "lim inf", limits: true),
"max" : MTMathAtomFactory.operatorWithName( "max", limits: true),
"min" : MTMathAtomFactory.operatorWithName( "min", limits: true),
"sup" : MTMathAtomFactory.operatorWithName( "sup", limits: true),
"inf" : MTMathAtomFactory.operatorWithName( "inf", limits: true),
"det" : MTMathAtomFactory.operatorWithName( "det", limits: true),
"Pr" : MTMathAtomFactory.operatorWithName( "Pr", limits: true),
"gcd" : MTMathAtomFactory.operatorWithName( "gcd", limits: true),
// Large operators
"prod" : MTMathAtomFactory.getOperator(withName: "\u{220F}", limits: true),
"coprod" : MTMathAtomFactory.getOperator(withName: "\u{2210}", limits: true),
"sum" : MTMathAtomFactory.getOperator(withName: "\u{2211}", limits: true),
"int" : MTMathAtomFactory.getOperator(withName: "\u{222B}", limits: false),
"oint" : MTMathAtomFactory.getOperator(withName: "\u{222E}", limits: false),
"bigwedge" : MTMathAtomFactory.getOperator(withName: "\u{22C0}", limits: true),
"bigvee" : MTMathAtomFactory.getOperator(withName: "\u{22C1}", limits: true),
"bigcap" : MTMathAtomFactory.getOperator(withName: "\u{22C2}", limits: true),
"bigcup" : MTMathAtomFactory.getOperator(withName: "\u{22C3}", limits: true),
"bigodot" : MTMathAtomFactory.getOperator(withName: "\u{2A00}", limits: true),
"bigoplus" : MTMathAtomFactory.getOperator(withName: "\u{2A01}", limits: true),
"bigotimes" : MTMathAtomFactory.getOperator(withName: "\u{2A02}", limits: true),
"biguplus" : MTMathAtomFactory.getOperator(withName: "\u{2A04}", limits: true),
"bigsqcup" : MTMathAtomFactory.getOperator(withName: "\u{2A06}", limits: true),
"prod" : MTMathAtomFactory.operatorWithName( "\u{220F}", limits: true),
"coprod" : MTMathAtomFactory.operatorWithName( "\u{2210}", limits: true),
"sum" : MTMathAtomFactory.operatorWithName( "\u{2211}", limits: true),
"int" : MTMathAtomFactory.operatorWithName( "\u{222B}", limits: false),
"oint" : MTMathAtomFactory.operatorWithName( "\u{222E}", limits: false),
"bigwedge" : MTMathAtomFactory.operatorWithName( "\u{22C0}", limits: true),
"bigvee" : MTMathAtomFactory.operatorWithName( "\u{22C1}", limits: true),
"bigcap" : MTMathAtomFactory.operatorWithName( "\u{22C2}", limits: true),
"bigcup" : MTMathAtomFactory.operatorWithName( "\u{22C3}", limits: true),
"bigodot" : MTMathAtomFactory.operatorWithName( "\u{2A00}", limits: true),
"bigoplus" : MTMathAtomFactory.operatorWithName( "\u{2A01}", limits: true),
"bigotimes" : MTMathAtomFactory.operatorWithName( "\u{2A02}", limits: true),
"biguplus" : MTMathAtomFactory.operatorWithName( "\u{2A04}", limits: true),
"bigsqcup" : MTMathAtomFactory.operatorWithName( "\u{2A06}", limits: true),
// Latex command characters
"{" : MTMathAtom(type: .open, value: "{"),
@@ -454,6 +454,40 @@ public class MTMathAtomFactory {
return nil
}
public static func fontNameForStyle(_ fontStyle:MTFontStyle) -> String {
switch fontStyle {
case .defaultStyle:
return "mathnormal";
case .roman:
return "mathrm";
case .bold:
return "mathbf";
case .fraktur:
return "mathfrak";
case .caligraphic:
return "mathcal";
case .italic:
return "mathit";
case .sansSerif:
return "mathsf";
case .blackboard:
return "mathbb";
case .typewriter:
return "mathtt";
case .boldItalic:
return "bm";
}
}
// Return an atom for times sign \times or *
public static func times() -> MTMathAtom {
return MTMathAtom(type: .binaryOperator, value: UnicodeSymbol.multiplication)
@@ -511,43 +545,41 @@ public class MTMathAtomFactory {
- Chars with special meaning in latex: ^ _ { } \
All other characters will have a non-nil atom returned.
*/
public static func atom(for char: String) -> MTMathAtom? {
let atomCharacterSet = CharacterSet(charactersIn: UnicodeScalar(0x21)!...UnicodeScalar(0x7E)!)
if char.rangeOfCharacter(from: atomCharacterSet) != nil {
switch char {
public static func atom(forCharacter ch: Character) -> MTMathAtom? {
let chStr = String(ch)
switch chStr {
case "\\u{0410}"..."\\u{044F}":
return MTMathAtom(type: .ordinary, value: chStr)
case _ where ch.utf32Char < 0x0021 || ch.utf32Char > 0x007E:
return nil
case "$", "%", "#", "&", "~", "\'", "^", "_", "{", "}", "\\":
return nil
case "(", "[":
return MTMathAtom(type: .open, value: char)
return MTMathAtom(type: .open, value: chStr)
case ")", "]", "!", "?":
return MTMathAtom(type: .close, value: char)
return MTMathAtom(type: .close, value: chStr)
case ",", ";":
return MTMathAtom(type: .punctuation, value: char)
return MTMathAtom(type: .punctuation, value: chStr)
case "=", ">", "<":
return MTMathAtom(type: .relation, value: char)
return MTMathAtom(type: .relation, value: chStr)
case ":":
// Math colon is ratio. Regular colon is \colon
return MTMathAtom(type: .relation, value: "\u{2236}")
case "-":
return MTMathAtom(type: .binaryOperator, value: "\u{2212}")
case "+", "*":
return MTMathAtom(type: .binaryOperator, value: char)
case ".", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9":
return MTMathAtom(type: .number, value: char)
case _ where
(char.rangeOfCharacter(from: CharacterSet.lowercaseLetters) != nil) ||
(char.rangeOfCharacter(from: CharacterSet.lowercaseLetters) != nil) :
return MTMathAtom(type: .variable, value: char)
return MTMathAtom(type: .binaryOperator, value: chStr)
case ".", "0"..."9":
return MTMathAtom(type: .number, value: chStr)
case "a"..."z", "A"..."Z":
return MTMathAtom(type: .variable, value: chStr)
case "\"", "/", "@", "`", "|":
return MTMathAtom(type: .ordinary, value: char)
return MTMathAtom(type: .ordinary, value: chStr)
default:
assert(false, "Unknown Character: \(char)")
print("Unknown characters: \(char)")
assertionFailure("Unknown ASCII character '\(ch)'. Should have been handled earlier.")
return nil
}
}
return nil
}
/** Returns a `MTMathList` with one atom per character in the given string. This function
does not do any LaTeX conversion or interpretation. It simply uses `atomForCharacter` to
@@ -556,7 +588,7 @@ public class MTMathAtomFactory {
let list = MTMathList()
for character in string {
if let newAtom = atom(for: "\(character)") {
if let newAtom = atom(forCharacter: character) {
list.add(newAtom)
}
}
@@ -602,14 +634,14 @@ public class MTMathAtomFactory {
e.g. to define a symbol for "lcm" one can call:
`[MTMathAtomFactory addLatexSymbol:@"lcm" value:[MTMathAtomFactory operatorWithName:@"lcm" limits: false)]` */
public static func define(latexSymbol name: String, value: MTMathAtom) {
public static func add(latexSymbol name: String, value: MTMathAtom) {
Self.sharedInstance.supportedLatexSymbols[name] = value
Self.sharedInstance.textToLatexSymbolName[value.nucleus] = name
}
/** Returns a large opertor for the given name. If limits is true, limits are set up on
the operator and displyed differently. */
public static func getOperator(withName name: String, limits: Bool) -> MTLargeOperator {
public static func operatorWithName(_ name: String, limits: Bool) -> MTLargeOperator {
return MTLargeOperator(value: name, limits: limits)
}
@@ -617,7 +649,7 @@ public class MTMathAtomFactory {
such as `grave`, `hat` etc. If the name is not a recognized accent name, this
returns nil. The `innerList` of the returned `MTAccent` is nil.
*/
public static func getAccent(withName name: String) -> MTAccent? {
public static func accent(withName name: String) -> MTAccent? {
if let accentValue = Self.accents[name] {
return MTAccent(value: accentValue)
}
@@ -626,7 +658,7 @@ public class MTMathAtomFactory {
/** Returns the accent name for the given accent. This is the reverse of the above
function. */
public static func getName(of accent: MTAccent) -> String? {
public static func accentName(_ accent: MTAccent) -> String? {
return Self.sharedInstance.accentValueToName[accent.nucleus]
}
@@ -681,7 +713,7 @@ public class MTMathAtomFactory {
@note The reason this function returns a `MTMathAtom` and not a `MTMathTable` is because some
matrix environments are have builtin delimiters added to the table and hence are returned as inner atoms.
*/
public static func table(withEnvironment env: String?, rows: [[MTMathList]]) -> MTMathAtom? {
public static func table(withEnvironment env: String?, rows: [[MTMathList]], error:inout NSError?) -> MTMathAtom? {
let table = MTMathTable(environment: env)
for i in 0..<rows.count {
@@ -703,12 +735,12 @@ public class MTMathAtomFactory {
if env == nil {
table.interColumnSpacing = 0
table.interRowAdditionalSpacing = 1
for i in 0..<table.numberOfCols() {
table.set(alignment: .left, forCol: i)
for i in 0..<table.numColumns {
table.set(alignment: .left, forColumn: i)
}
return table
} else {
if let delims = matrixEnvs[env!] {
} else if let env = env {
if let delims = matrixEnvs[env] {
table.environment = "matrix"
table.interRowAdditionalSpacing = 0
table.interColumnSpacing = 18
@@ -731,8 +763,11 @@ public class MTMathAtomFactory {
return table
}
} else if env == "eqalign" || env == "split" || env == "aligned" {
if table.numberOfCols() != 2 {
print("\(env!) environment can only have 2 columns")
if table.numColumns != 2 {
let message = "\(env) environment can only have 2 columns"
if error != nil {
error = NSError(domain: MTParseError, code: MTParseErrors.invalidNumColumns.rawValue, userInfo: [NSLocalizedDescriptionKey:message])
}
return nil
}
@@ -747,47 +782,56 @@ public class MTMathAtomFactory {
table.interRowAdditionalSpacing = 1
table.interColumnSpacing = 0
table.set(alignment: .right, forCol: 0)
table.set(alignment: .left, forCol: 1)
table.set(alignment: .right, forColumn: 0)
table.set(alignment: .left, forColumn: 1)
return table
} else if env == "displaylines" || env == "gather" {
if table.numberOfCols() != 1 {
print("\(env!) environment can only have 1 columns")
if table.numColumns != 1 {
let message = "\(env) environment can only have 1 column"
if error != nil {
error = NSError(domain: MTParseError, code: MTParseErrors.invalidNumColumns.rawValue, userInfo: [NSLocalizedDescriptionKey:message])
}
return nil
}
table.interRowAdditionalSpacing = 1
table.interColumnSpacing = 0
table.set(alignment: .center, forCol: 0)
table.set(alignment: .center, forColumn: 0)
return table
} else if env == "eqnarray" {
if table.numberOfCols() != 3 {
print("\(env!) environment can only have 3 columns")
if table.numColumns != 3 {
let message = "\(env) environment can only have 3 columns"
if error != nil {
error = NSError(domain: MTParseError, code: MTParseErrors.invalidNumColumns.rawValue, userInfo: [NSLocalizedDescriptionKey:message])
}
return nil
}
table.interRowAdditionalSpacing = 1
table.interColumnSpacing = 18
table.set(alignment: .right, forCol: 0)
table.set(alignment: .center, forCol: 1)
table.set(alignment: .left, forCol: 2)
table.set(alignment: .right, forColumn: 0)
table.set(alignment: .center, forColumn: 1)
table.set(alignment: .left, forColumn: 2)
return table
} else if env == "cases" {
if table.numberOfCols() != 2 {
print("\(env!) environment can only have 2 columns")
if table.numColumns != 2 {
let message = "cases environment can only have 2 columns"
if error != nil {
error = NSError(domain: MTParseError, code: MTParseErrors.invalidNumColumns.rawValue, userInfo: [NSLocalizedDescriptionKey:message])
}
return nil
}
table.interRowAdditionalSpacing = 0
table.interColumnSpacing = 18
table.set(alignment: .left, forCol: 0)
table.set(alignment: .left, forCol: 1)
table.set(alignment: .left, forColumn: 0)
table.set(alignment: .left, forColumn: 1)
let style = MTMathStyle(style: .text)
@@ -807,9 +851,13 @@ public class MTMathAtomFactory {
return inner
} else {
print("Unknown table environment")
let message = "Unknown environment \(env)"
if error != nil {
error = NSError(domain: MTParseError, code: MTParseErrors.invalidNumColumns.rawValue, userInfo: [NSLocalizedDescriptionKey:message])
}
return nil
}
}
return nil
}
}

View File

@@ -78,7 +78,7 @@ public enum MTFontStyle:Int {
boldItalic
}
public class MTMathAtom: Any, CustomStringConvertible {
public class MTMathAtom: CustomStringConvertible {
public var type: MTMathAtomType
public var subScript: MTMathList?
public var superScript: MTMathList?
@@ -271,10 +271,10 @@ public class MTFraction: MTMathAtom {
public class MTRadical: MTMathAtom {
// Under the roof
var radicand: MTMathList? = MTMathList()
public var radicand: MTMathList? = MTMathList()
// Value on radical sign
var degree: MTMathList?
public var degree: MTMathList?
convenience init() {
self.init(type: .radical, value: "")
@@ -312,7 +312,7 @@ public class MTRadical: MTMathAtom {
}
public class MTLargeOperator: MTMathAtom {
var limits: Bool = false
public var limits: Bool = false
convenience init(value: String, limits: Bool = false) {
self.init(type: .largeOperator, value: value)
@@ -323,15 +323,15 @@ public class MTLargeOperator: MTMathAtom {
// MARK: - MTInner
public class MTInner: MTMathAtom {
var innerList: MTMathList?
var leftBoundary: MTMathAtom? {
public var innerList: MTMathList?
public var leftBoundary: MTMathAtom? {
didSet {
if leftBoundary != nil && leftBoundary!.type != .boundary {
assertionFailure("Left boundary must be of type .boundary")
}
}
}
var rightBoundary: MTMathAtom? {
public var rightBoundary: MTMathAtom? {
didSet {
if rightBoundary != nil && rightBoundary!.type != .boundary {
assertionFailure("Right boundary must be of type .boundary")
@@ -383,7 +383,7 @@ public class MTInner: MTMathAtom {
}
public class MTOverLine: MTMathAtom {
var innerList: MTMathList?
public var innerList: MTMathList?
override public var finalized: MTMathAtom {
let finalized: MTOverLine = super.finalized as! MTOverLine
@@ -399,7 +399,7 @@ public class MTOverLine: MTMathAtom {
}
public class MTUnderLine: MTMathAtom {
var innerList: MTMathList?
public var innerList: MTMathList?
override public var finalized: MTMathAtom {
let finalized: MTUnderLine = super.finalized as! MTUnderLine
@@ -415,7 +415,7 @@ public class MTUnderLine: MTMathAtom {
}
public class MTAccent: MTMathAtom {
var innerList: MTMathList?
public var innerList: MTMathList?
override public var finalized: MTMathAtom {
let finalized: MTAccent = super.finalized as! MTAccent
@@ -431,7 +431,7 @@ public class MTAccent: MTMathAtom {
}
public class MTMathSpace: MTMathAtom {
var space: CGFloat = 0
public var space: CGFloat = 0
convenience init(space: CGFloat) {
self.init(type: .space, value: "")
@@ -460,10 +460,10 @@ public enum MTLineStyle {
}
public class MTMathStyle: MTMathAtom {
var style: MTLineStyle = .display
public var style: MTLineStyle = .display
convenience init(style: MTLineStyle = .display) {
self.init(type: .space, value: "")
self.init(type: .style, value: "")
self.style = style
}
}
@@ -517,14 +517,14 @@ public enum MTColumnAlignment {
}
public class MTMathTable: MTMathAtom {
var alignments = [MTColumnAlignment]()
var cells = [[MTMathList]]()
public var alignments = [MTColumnAlignment]()
public var cells = [[MTMathList]]()
var environment: String?
var interColumnSpacing: CGFloat = 0
var interRowAdditionalSpacing: CGFloat = 0
var numColumns = 0
var numRows = 0
public var environment: String?
public var interColumnSpacing: CGFloat = 0
public var interRowAdditionalSpacing: CGFloat = 0
// public var numColumns = 0
// public var numRows = 0
override public var finalized: MTMathAtom {
let finalized: MTMathTable = super.finalized as! MTMathTable
@@ -543,22 +543,22 @@ public class MTMathTable: MTMathAtom {
self.environment = environment
}
func set(cell list: MTMathList, forRow row:Int, column:Int) {
public func set(cell list: MTMathList, forRow row:Int, column:Int) {
if self.cells.count <= row {
for _ in self.cells.count...row {
self.cells.append([])
}
}
var rowArray = self.cells[row]
if rowArray.count <= column {
for _ in rowArray.count...column {
rowArray.append(MTMathList())
let rows = self.cells[row].count
if rows <= column {
for _ in rows...column {
self.cells[row].append(MTMathList())
}
}
rowArray[column] = list
self.cells[row][column] = list
}
func set(alignment: MTColumnAlignment, forCol col: Int) {
public func set(alignment: MTColumnAlignment, forColumn col: Int) {
if self.alignments.count <= col {
for _ in self.alignments.count...col {
self.alignments.append(MTColumnAlignment.center)
@@ -568,7 +568,7 @@ public class MTMathTable: MTMathAtom {
self.alignments[col] = alignment
}
func getAlignmentOf(col: Int) -> MTColumnAlignment {
public func get(alignmentForColumn col: Int) -> MTColumnAlignment {
if self.alignments.count <= col {
return MTColumnAlignment.center
} else {
@@ -576,7 +576,7 @@ public class MTMathTable: MTMathAtom {
}
}
func numberOfCols() -> Int {
public var numColumns: Int {
var numberOfCols = 0
for row in self.cells {
@@ -586,7 +586,7 @@ public class MTMathTable: MTMathAtom {
return numberOfCols
}
func numberOfRows() -> Int {
public var numRows: Int {
return self.cells.count
}
}

View File

@@ -92,6 +92,7 @@ public class MTMathListBuilder {
]
init(string: String) {
self.error = nil
self.string = string
self.currentCharIndex = string.startIndex
self.currentFontStyle = .defaultStyle
@@ -115,31 +116,39 @@ public class MTMathListBuilder {
return builder.build()
}
public static func build(fromString string: String, error:inout NSError?) -> MTMathList? {
public static func build(fromString string: String, error:inout NSError) -> MTMathList? {
let builder = MTMathListBuilder(string: string)
let output = builder.build()
if builder.error != nil {
if error != nil {
error = builder.error
}
error = builder.error!
return nil
} else {
error = NSError()
}
return output
}
public static func mathListToString(_ ml: MTMathList?) -> String {
var output = ""
var str = ""
var currentfontStyle = MTFontStyle.defaultStyle
if let atomList = ml {
for atom in atomList.atoms {
switch atom.type {
case .fraction:
if currentfontStyle != atom.fontStyle {
if currentfontStyle != .defaultStyle {
str += "}"
}
if atom.fontStyle != .defaultStyle {
let fontStyleName = MTMathAtomFactory.fontNameForStyle(atom.fontStyle)
str += "\\\(fontStyleName){"
}
currentfontStyle = atom.fontStyle
}
if atom.type == .fraction {
if let frac = atom as? MTFraction {
if frac.hasRule {
output += "\\frac{\(mathListToString(frac.numerator!))}{\(mathListToString(frac.denominator!))}"
str += "\\frac{\(mathListToString(frac.numerator!))}{\(mathListToString(frac.denominator!))}"
} else {
var command: String? = nil
if frac.leftDelimiter == nil && frac.rightDelimiter == nil {
command = "atop"
} else if frac.leftDelimiter == "(" && frac.rightDelimiter == ")" {
@@ -151,51 +160,47 @@ public class MTMathListBuilder {
} else {
command = "atopwithdelims\(frac.leftDelimiter!)\(frac.rightDelimiter!)"
}
output += "{\(mathListToString(frac.numerator!)) \\\(command!) \(mathListToString(frac.denominator!))}"
str += "{\(mathListToString(frac.numerator!)) \\\(command!) \(mathListToString(frac.denominator!))}"
}
}
break
case .radical:
output += "\\sqrt"
} else if atom.type == .radical {
str += "\\sqrt"
if let rad = atom as? MTRadical {
if rad.degree != nil {
output += "[\(mathListToString(rad.degree!))]"
str += "[\(mathListToString(rad.degree!))]"
}
output += "{\(mathListToString(rad.radicand!))}"
str += "{\(mathListToString(rad.radicand!))}"
}
break
case .inner:
} else if atom.type == .inner {
if let inner = atom as? MTInner {
if inner.leftBoundary != nil || inner.rightBoundary != nil {
if inner.leftBoundary != nil {
output += "\\left\(delimToString(delim: inner.leftBoundary!))"
str += "\\left\(delimToString(delim: inner.leftBoundary!)) "
} else {
output += "\\left. "
str += "\\left. "
}
output += mathListToString(inner.innerList!)
str += mathListToString(inner.innerList!)
if inner.rightBoundary != nil {
output += "\\right\(delimToString(delim: inner.rightBoundary!))"
str += "\\right\(delimToString(delim: inner.rightBoundary!)) "
} else {
output += "\\right. "
str += "\\right. "
}
} else {
output += "{\(mathListToString(inner.innerList!))}"
str += "{\(mathListToString(inner.innerList!))}"
}
}
break
case .table:
} else if atom.type == .table {
if let table = atom as? MTMathTable {
if table.environment != nil {
output += "\\begin{\(table.environment!)}"
str += "\\begin{\(table.environment!)}"
}
for i in 0..<table.numberOfRows() {
for j in 0..<table.cells[i].count {
let cell = table.cells[i][j]
for i in 0..<table.numRows {
let row = table.cells[i]
for j in 0..<row.count {
let cell = row[j]
if table.environment == "matrix" {
if cell.atoms.count >= 1 && cell.atoms[0].type == .style {
// remove first atom
@@ -209,82 +214,90 @@ public class MTMathListBuilder {
}
}
output += mathListToString(cell)
str += mathListToString(cell)
if j < table.cells[i].count {
output += "&"
if j < row.count - 1 {
str += "&"
}
}
if i < table.numberOfRows() - 1 {
output += "\\\\ "
if i < table.numRows - 1 {
str += "\\\\ "
}
}
if table.environment != nil {
output += "\\end{\(table.environment!)}"
str += "\\end{\(table.environment!)}"
}
}
break
case .overline:
} else if atom.type == .overline {
if let overline = atom as? MTOverLine {
output += "\\overline"
output += "{\(mathListToString(overline.innerList!))}"
str += "\\overline"
str += "{\(mathListToString(overline.innerList!))}"
}
break
case .underline:
} else if atom.type == .underline {
if let underline = atom as? MTUnderLine {
output += "\\underline"
output += "{\(mathListToString(underline.innerList!))}"
str += "\\underline"
str += "{\(mathListToString(underline.innerList!))}"
}
break
case .accent:
} else if atom.type == .accent {
if let accent = atom as? MTAccent {
output += "\\\(MTMathAtomFactory.getName(of: accent)!){\(mathListToString(accent.innerList!))}"
str += "\\\(MTMathAtomFactory.accentName(accent)!){\(mathListToString(accent.innerList!))}"
}
break
case .space:
} else if atom.type == .largeOperator {
let op = atom as! MTLargeOperator
let command = MTMathAtomFactory.latexSymbolName(for: atom)
let originalOp = MTMathAtomFactory.atom(forLatexSymbol: command!) as! MTLargeOperator
str += "\\\(command!) "
if originalOp.limits != op.limits {
if op.limits {
str += "\\limits "
} else {
str += "\\nolimits "
}
}
} else if atom.type == .space {
if let space = atom as? MTMathSpace {
if let command = MTMathListBuilder.spaceToCommands[space.space] {
output += "\\\(command)"
str += "\\\(command) "
} else {
output += String.init(format: "\\mkern%.1fmu", space.space)
str += String(format: "\\mkern%.1fmu", space.space)
}
}
break
case .style:
} else if atom.type == .style {
if let style = atom as? MTMathStyle {
if let command = MTMathListBuilder.styleToCommands[style.style] {
output += "\\\(command)"
str += "\\\(command) "
}
}
break
default:
if atom.nucleus.count == 0 {
output += "{}"
} else if atom.nucleus.isEmpty {
str += "{}"
} else if atom.nucleus == "\u{2236}" {
output += ":"
// math colon
str += ":"
} else if atom.nucleus == "\u{2212}" {
output += "-"
// math minus
str += "-"
} else {
if let command = MTMathAtomFactory.latexSymbolName(for: atom) {
output += "\\\(command)"
str += "\\\(command) "
} else {
output += "\(atom.nucleus)"
str += "\(atom.nucleus)"
}
}
break
}
if atom.superScript != nil {
output += "^{\(mathListToString(atom.superScript!))}"
str += "^{\(mathListToString(atom.superScript!))}"
}
if atom.subScript != nil {
output += "_{\(mathListToString(atom.subScript!))}"
str += "_{\(mathListToString(atom.subScript!))}"
}
}
}
return output
if currentfontStyle != .defaultStyle {
str += "}"
}
return str
}
public static func delimToString(delim: MTMathAtom) -> String {
@@ -302,15 +315,12 @@ public class MTMathListBuilder {
return ""
}
func getNextCharacter() -> Character? {
func getNextCharacter() -> Character {
assert(self.hasCharacters, "Retrieving character at index \(self.currentCharIndex) beyond length \(self.string.count)")
if self.hasCharacters {
let ch = string[currentCharIndex]
currentCharIndex = string.index(after: currentCharIndex)
return ch
}
return nil
}
func unlookCharacter() {
assert(currentCharIndex > string.startIndex, "Unlooking when at the first character.")
@@ -322,10 +332,10 @@ public class MTMathListBuilder {
}
func buildInternal(_ oneCharOnly: Bool) -> MTMathList? {
return self.buildInternal(oneCharOnly, stop: nil)
return self.buildInternal(oneCharOnly, stopChar: nil)
}
func buildInternal(_ oneCharOnly: Bool, stop: Character?) -> MTMathList? {
func buildInternal(_ oneCharOnly: Bool, stopChar stop: Character?) -> MTMathList? {
let list = MTMathList()
assert(!(oneCharOnly && stop != nil), "Cannot set both oneCharOnly and stopChar.")
var prevAtom: MTMathAtom? = nil
@@ -333,7 +343,7 @@ public class MTMathListBuilder {
if error != nil { return nil }
var atom: MTMathAtom? = nil
let char = self.getNextCharacter()!
let char = self.getNextCharacter()
if oneCharOnly {
if char == "^" || char == "}" || char == "_" || char == "&" {
@@ -342,7 +352,7 @@ public class MTMathListBuilder {
}
}
if stop != nil && char == stop {
if stop != nil && char == stop! {
return list
}
@@ -355,7 +365,7 @@ public class MTMathListBuilder {
list.add(prevAtom!)
}
prevAtom!.setSuperScript(self.buildInternal(true))
prevAtom!.superScript = self.buildInternal(true)
continue
} else if char == "_" {
assert(!oneCharOnly, "This should have been handled before")
@@ -365,46 +375,43 @@ public class MTMathListBuilder {
prevAtom = MTMathAtom(type: .ordinary, value: "")
list.add(prevAtom!)
}
prevAtom!.setSubScript(self.buildInternal(true))
prevAtom!.subScript = self.buildInternal(true)
continue
} else if char == "{" {
// this puts us in a recursive routine, and sets oneCharOnly to false and no stop character
if let subList = self.buildInternal(false, stop: "}") {
prevAtom = subList.atoms.last
list.append(subList)
let subList = self.buildInternal(false, stopChar: "}")
prevAtom = subList!.atoms.last
list.append(subList!)
if oneCharOnly {
return list
}
continue
} else {
print("open brackets but inner...")
return nil
}
} else if char == "}" {
assert(!oneCharOnly, "This should have been handled before")
assert(stop == nil, "This should have been handled before")
// We encountered a closing brace when there is no stop set, that means there was no
// corresponding opening brace.
print("Mismatched braces")
let errorMessage = "Mismatched braces."
self.setError(.mismatchBraces, message:errorMessage)
return nil
} else if char == "\\" {
let command = readCommand()
let done = stopCommand(command, list:list, stop:stop)
let done = stopCommand(command, list:list, stopChar:stop)
if done != nil {
return done
} else if error != nil {
return nil
}
if self.applyModifier(command, atom:prevAtom) {
continue;
continue
}
let fontStyle = MTMathAtomFactory.fontStyleWithName(command)
if fontStyle != nil {
if let fontStyle = MTMathAtomFactory.fontStyleWithName(command) {
let oldSpacesAllowed = spacesAllowed
// Text has special consideration where it allows spaces without escaping.
spacesAllowed = command == "text"
let oldFontStyle = currentFontStyle
currentFontStyle = fontStyle!
currentFontStyle = fontStyle
let sublist = self.buildInternal(true)!
// Restore the font style.
currentFontStyle = oldFontStyle
@@ -431,27 +438,20 @@ public class MTMathListBuilder {
if self.currentEnv != nil {
return list
} else {
if let table = self.buildTable(env: nil, firstList: list, isRow: false) {
return MTMathList(atom: table)
}
let table = self.buildTable(env: nil, firstList: list, isRow: false)
return MTMathList(atom: table!)
}
} else if spacesAllowed && char == " " {
atom = MTMathAtomFactory.atom(forLatexSymbol: " ")
} else {
atom = MTMathAtomFactory.atom(for: String(char))
atom = MTMathAtomFactory.atom(forCharacter: char)
if atom == nil {
continue
}
}
assert(atom != nil, "Atom shouldn't be nil")
if atom == nil {
print("wtf atom why nil?")
return nil
}
atom?.fontStyle = currentFontStyle
list.add(atom!)
prevAtom = atom
@@ -462,12 +462,14 @@ public class MTMathListBuilder {
if stop != nil {
if stop == "}" {
print("Missing Closing Brace")
// We did not find a corresponding closing brace.
self.setError(.mismatchBraces, message:"Missing closing brace")
} else {
print("Expected Character not found: \(stop!)")
// we never found our stop character
let errorMessage = "Expected character not found: \(stop!)"
self.setError(.characterNotFound, message:errorMessage)
}
}
return list
}
@@ -475,7 +477,7 @@ public class MTMathListBuilder {
if let atom = MTMathAtomFactory.atom(forLatexSymbol: command) {
return atom
}
if let accent = MTMathAtomFactory.getAccent(withName: command) {
if let accent = MTMathAtomFactory.accent(withName: command) {
// The command is an accent
accent.innerList = self.buildInternal(true)
return accent;
@@ -499,7 +501,7 @@ public class MTMathListBuilder {
let ch = self.getNextCharacter()
if (ch == "[") {
// special handling for sqrt[degree]{radicand}
rad.degree = self.buildInternal(false, stop:"]")
rad.degree = self.buildInternal(false, stopChar:"]")
rad.radicand = self.buildInternal(true)
} else {
self.unlookCharacter()
@@ -574,8 +576,8 @@ public class MTMathListBuilder {
// a string of all upper and lower case characters.
var mutable = ""
while self.hasCharacters {
let ch = self.getNextCharacter()!
if (ch == "#" || (ch >= "A" && ch <= "F") || (ch >= "a" && ch <= "f") || (ch >= "0" && ch <= "9")) {
let ch = self.getNextCharacter()
if ch == "#" || (ch >= "A" && ch <= "F") || (ch >= "a" && ch <= "f") || (ch >= "0" && ch <= "9") {
mutable.append(ch) // appendString:[NSString stringWithCharacters:&ch length:1]];
} else {
// we went too far
@@ -594,7 +596,7 @@ public class MTMathListBuilder {
func skipSpaces() {
while self.hasCharacters {
let ch = self.getNextCharacter()?.asciiValue ?? 0
let ch = self.getNextCharacter().utf32Char
if ch < 0x21 || ch > 0x7E {
// skip non ascii characters and spaces
continue;
@@ -605,7 +607,7 @@ public class MTMathListBuilder {
}
}
func stopCommand(_ command: String, list:MTMathList, stop:Character?) -> MTMathList? {
func stopCommand(_ command: String, list:MTMathList, stopChar:Character?) -> MTMathList? {
var fractionCommands: [String:[Character]] {
[
"over": [],
@@ -627,20 +629,19 @@ public class MTMathListBuilder {
}
// return the list read so far.
return list
} else if let _ = fractionCommands[command] {
} else if let delims = fractionCommands[command] {
var frac:MTFraction! = nil;
if command == "over" {
frac = MTFraction()
} else {
frac = MTFraction(hasRule: false)
}
let delims = fractionCommands[command]!
if delims.count == 2 {
frac.leftDelimiter = String(delims[0])
frac.rightDelimiter = String(delims[1])
}
frac.numerator = list;
frac.denominator = self.buildInternal(false, stop: stop)
frac.denominator = self.buildInternal(false, stopChar: stopChar)
if error != nil {
return nil;
}
@@ -658,7 +659,7 @@ public class MTMathListBuilder {
return MTMathList(atom: table!)
}
} else if command == "end" {
if currentEnv != nil {
if currentEnv == nil {
let errorMessage = "Missing \\begin";
self.setError(.missingBegin, message:errorMessage)
return nil;
@@ -691,7 +692,7 @@ public class MTMathListBuilder {
}
return true
} else if modifier == "nolimits" {
if atom!.type != .largeOperator {
if atom?.type != .largeOperator {
let errorMessage = "No limits can only be applied to an operator."
self.setError(.invalidLimits, message:errorMessage)
return true
@@ -715,8 +716,7 @@ public class MTMathListBuilder {
if let atom = MTMathAtomFactory.atom(forLatexSymbol: command) {
return atom
}
if let accent = MTMathAtomFactory.getAccent(withName: command) {
if let accent = MTMathAtomFactory.accent(withName: command) {
accent.innerList = self.buildInternal(true)
return accent
} else if command == "frac" {
@@ -734,9 +734,8 @@ public class MTMathListBuilder {
} else if command == "sqrt" {
let rad = MTRadical()
let char = self.getNextCharacter()
if char == "[" {
rad.degree = self.buildInternal(false, stop: "]")
rad.degree = self.buildInternal(false, stopChar: "]")
rad.radicand = self.buildInternal(true)
} else {
self.unlookCharacter()
@@ -752,7 +751,7 @@ public class MTMathListBuilder {
}
self.currentInnerAtom!.innerList = self.buildInternal(false)
if self.currentInnerAtom?.rightBoundary == nil {
print("Missing \\right")
self.setError(.missingRight, message: "Missing \\right")
return nil
}
let newInner = self.currentInnerAtom
@@ -775,86 +774,24 @@ public class MTMathListBuilder {
} else {
return nil
}
} else if command == "color" {
// A color command has 2 arguments
let mathColor = MTMathColor()
mathColor.colorString = self.readColor()!
mathColor.innerList = self.buildInternal(true)
return mathColor
} else if command == "colorbox" {
// A color command has 2 arguments
let mathColorbox = MTMathColorbox()
mathColorbox.colorString = self.readColor()!
mathColorbox.innerList = self.buildInternal(true)
return mathColorbox
} else {
print("Invalid Command")
self.setError(.invalidCommand, message: "Invalid command \\\(command)")
return nil
}
}
// func stop(command: String, list: MTMathList, stopChar: Character) -> MTMathList? {
// let fractionCommands = [
// "over": [],
// "atop": [],
// "choose": ["(", ")"],
// "brack": ["[", "]"],
// "brace": ["{", "}"]
// ]
//
// if command == "right" {
// if self.currentInnerAtom == nil {
// print("missing left")
// return nil
// }
//
// if let rightBoundary = self.getBoundaryAtom("right") {
// self.currentInnerAtom!.rightBoundary = rightBoundary
//
// return list
// } else {
// return nil
// }
// } else if let delims = fractionCommands[command] {
// let frac: MTFraction
// if command == "over" {
// frac = MTFraction()
// } else {
// frac = MTFraction(hasRule: false)
// }
//
// if delims.count == 2 {
// frac.leftDelimiter = delims[0]
// frac.rightDelimiter = delims[1]
// }
//
// frac.numerator = list
// frac.denominator = self.buildInternal(false, stopChar: stopChar)
//
// let fracList = MTMathList()
// fracList.add(frac)
// return fracList
// } else if command == "\\" || command == "cr" {
// if currentEnv != nil {
// currentEnv!.numRows += 1
// return list
// } else {
// if let table = self.buildTable(env: nil, firstList: list, isRow: true) {
// return MTMathList(atoms: [table])
// } else {
// return nil
// }
// }
// } else if command == "end" {
// if self.currentEnv == nil {
// print("Missing \\begin")
// return nil
// }
//
// if let env = self.readEnvironment() {
// if env != self.currentEnv?.envName {
// print("Begin environment name \(currentEnv!.envName!) does not match end name: \(env)")
// return nil
// }
//
// currentEnv?.ended = true
//
// return list
// } else {
// return nil
// }
// }
// return nil
// }
func readEnvironment() -> String? {
if !self.expectCharacter("{") {
// We didn't find an opening brace, so no env found.
@@ -862,7 +799,7 @@ public class MTMathListBuilder {
return nil
}
self.skipSpace()
self.skipSpaces()
let env = self.readString()
if !self.expectCharacter("}") {
@@ -873,13 +810,18 @@ public class MTMathListBuilder {
return env
}
func expectCharacter(_ char: Character) -> Bool {
self.skipSpace()
func MTAssertNotSpace(_ ch: Character) {
assert(ch >= "\u{21}" && ch <= "\u{7E}", "Expected non-space character \(ch)")
}
func expectCharacter(_ ch: Character) -> Bool {
MTAssertNotSpace(ch)
self.skipSpaces()
if self.hasCharacters {
let nextChar = self.getNextCharacter()!
if nextChar == char {
let nextChar = self.getNextCharacter()
MTAssertNotSpace(nextChar)
if nextChar == ch {
return true
} else {
self.unlookCharacter()
@@ -899,64 +841,68 @@ public class MTMathListBuilder {
var currentCol = 0
var rows = [[MTMathList]]()
rows.append([MTMathList]())
if firstList != nil {
rows[currentRow][currentCol] = firstList!
rows[currentRow].append(firstList!)
if isRow {
currentEnv?.numRows += 1
currentEnv!.numRows+=1
currentRow+=1
rows.append([MTMathList]())
} else {
currentCol+=1
}
}
while !currentEnv!.ended && self.hasCharacters {
if let list = self.buildInternal(false) {
rows[currentRow][currentCol] = list
let list = self.buildInternal(false)
if list == nil {
// If there is an error building the list, bail out early.
return nil
}
rows[currentRow].append(list!)
currentCol+=1
if self.currentEnv!.numRows > currentRow {
currentRow = self.currentEnv!.numRows
if currentEnv!.numRows > currentRow {
currentRow = currentEnv!.numRows
rows.append([MTMathList]())
currentCol = 0
}
} else {
return nil
}
}
if !currentEnv!.ended && currentEnv?.envName == nil {
print("Missing \\end")
if !currentEnv!.ended && currentEnv!.envName != nil {
self.setError(.missingEnd, message: "Missing \\end")
return nil
}
if let table = MTMathAtomFactory.table(withEnvironment: currentEnv?.envName, rows: rows) {
var error:NSError?
let table = MTMathAtomFactory.table(withEnvironment: currentEnv?.envName, rows: rows, error: &error)
if table == nil && self.error == nil {
self.error = error
return nil
}
self.currentEnv = oldEnv
return table
} else {
return nil
}
}
func getBoundaryAtom(_ delimiterType: String) -> MTMathAtom? {
let delim = self.readDelimiter()
if delim == nil {
assertionFailure("Missing delimiter for \(delimiterType)")
let errorMessage = "Missing delimiter for \\\(delimiterType)"
self.setError(.missingDelimiter, message:errorMessage)
return nil
}
let boundary = MTMathAtomFactory.boundary(forDelimiter: delim!)
if boundary == nil {
assertionFailure("Invalid delimiter for \(delimiterType): \(delim!)")
let errorMessage = "Invalid delimiter for \(delimiterType): \(delim!)"
self.setError(.missingDelimiter, message:errorMessage)
return nil
}
return boundary
}
func readDelimiter() -> String? {
self.skipSpace()
self.skipSpaces()
while self.hasCharacters {
let char = self.getNextCharacter()!
let char = self.getNextCharacter()
MTAssertNotSpace(char)
if char == "\\" {
let command = self.readCommand()
if command == "|" {
@@ -970,31 +916,16 @@ public class MTMathListBuilder {
return nil
}
func skipSpace() {
while self.hasCharacters {
let char = self.getNextCharacter()
let asciiCharacterSet = CharacterSet(charactersIn: UnicodeScalar(0x21)...UnicodeScalar(0x7e))
if String(char!).rangeOfCharacter(from: asciiCharacterSet) != nil {
self.unlookCharacter()
return
} else {
continue
}
}
}
func readCommand() -> String {
let singleChars = Array("{}$#%_| ,>;!\\")
let singleChars = "{}$#%_| ,>;!\\"
if self.hasCharacters {
if let char = self.getNextCharacter() {
let char = self.getNextCharacter()
if let _ = singleChars.firstIndex(of: char) {
return String(char)
} else {
self.unlookCharacter()
}
}
}
return self.readString()
}
@@ -1002,7 +933,7 @@ public class MTMathListBuilder {
// a string of all upper and lower case characters.
var output = ""
while self.hasCharacters {
if let char = self.getNextCharacter() {
let char = self.getNextCharacter()
if char.isLowercase || char.isUppercase {
output.append(char)
} else {
@@ -1010,7 +941,6 @@ public class MTMathListBuilder {
break
}
}
}
return output
}
}

View File

@@ -71,12 +71,12 @@ class MTMathUILabel : MTView {
var latex = "" {
didSet {
self.error = nil
var error: NSError? = nil
var error = NSError()
self.mathList = MTMathListBuilder.build(fromString: latex, error: &error)
if error != nil {
if error != NSError() {
self.mathList = nil
self.error = error
self.errorLabel?.text = error?.localizedDescription
self.errorLabel?.text = error.localizedDescription
self.errorLabel?.frame = self.bounds
self.errorLabel?.isHidden = !self.displayErrorInline
} else {

View File

@@ -1637,7 +1637,7 @@ class MTTypesetter {
for i in 0..<cols.count {
let col = cols[i]
let colWidth = columnWidths[i]
let alignment = table?.getAlignmentOf(col: i)
let alignment = table?.get(alignmentForColumn: i)
var cellPos = columnStart;
switch alignment {
case .right:

View File

@@ -47,6 +47,34 @@ final class SwiftMathRenderTests: XCTestCase {
}
}
func getTestData() -> [TestRecord] {
[
TestRecord(build: "x", atomType: [.variable ], types: [], result: "x"),
TestRecord(build: "1", atomType: [.number ] , types: [], result: "1"),
TestRecord(build: "*", atomType: [.binaryOperator ] ,types: [], result:"*"),
TestRecord(build: "+", atomType: [.binaryOperator ], types: [], result:"+"),
TestRecord(build: ".", atomType: [.number ], types: [], result:"."),
TestRecord(build: "(", atomType: [.open ], types: [], result:"(" ),
TestRecord(build: ")", atomType: [.close ], types: [], result:")"),
TestRecord(build: ",", atomType: [.punctuation], types: [], result:","),
TestRecord(build: "!", atomType: [.close], types: [], result:"!"),
TestRecord(build: "=", atomType: [.relation], types: [], result:"="),
TestRecord(build: "x+2", atomType: [.variable, .binaryOperator, .number ], types: [], result:"x+2"),
// spaces are ignored
TestRecord(build: "(2.3 * 8)", atomType: [.open, .number, .number, .number, .binaryOperator, .number , .close ], types: [], result:"(2.3*8)"),
// braces are just for grouping
TestRecord(build: "5{3+4}", atomType: [.number, .number, .binaryOperator, .number], types: [], result:"53+4"),
// commands
TestRecord(build: "\\pi+\\theta\\geq 3",atomType: [.variable, .binaryOperator, .variable, .relation, .number], types: [], result:"\\pi +\\theta \\geq 3"),
// aliases
TestRecord(build: "\\pi\\ne 5 \\land 3", atomType: [.variable, .relation, .number, .binaryOperator, .number], types: [], result:"\\pi \\neq 5\\wedge 3"),
// control space
TestRecord(build: "x \\ y", atomType: [ .variable, .ordinary, .variable], types: [], result:"x\\ y"),
// spacing
TestRecord(build: "x \\quad y \\; z \\! q", atomType: [ .variable, .space, .variable,.space, .variable, .space, .variable], types: [], result:"x\\quad y\\; z\\! q")
]
}
func getTestDataSuperScript() -> [TestRecord] {
[
TestRecord(build: "x^2", atomType: [.variable], types: [.number], result: "x^{2}"),
@@ -125,13 +153,68 @@ final class SwiftMathRenderTests: XCTestCase {
]
}
func getTestDataParseErrors() -> [(String, MTParseErrors)] {
return [
("}a", .mismatchBraces),
("\\notacommand", .invalidCommand),
("\\sqrt[5+3", .characterNotFound),
("{5+3", .mismatchBraces),
("5+3}", .mismatchBraces),
("{1+\\frac{3+2", .mismatchBraces),
("1+\\left", .missingDelimiter),
("\\left(\\frac12\\right", .missingDelimiter),
("\\left 5 + 3 \\right)", .invalidDelimiter),
("\\left(\\frac12\\right + 3", .invalidDelimiter),
("\\left\\lmoustache 5 + 3 \\right)", .invalidDelimiter),
("\\left(\\frac12\\right\\rmoustache + 3", .invalidDelimiter),
("5 + 3 \\right)", .missingLeft),
("\\left(\\frac12", .missingRight),
("\\left(5 + \\left| \\frac12 \\right)", .missingRight),
("5+ \\left|\\frac12\\right| \\right)", .missingLeft),
("\\begin matrix \\end matrix", .characterNotFound), // missing {
("\\begin", .characterNotFound), // missing {
("\\begin{", .characterNotFound), // missing }
("\\begin{matrix parens}", .characterNotFound), // missing } (no spaces in env)
("\\begin{matrix} x", .missingEnd),
("\\begin{matrix} x \\end", .characterNotFound), // missing {
("\\begin{matrix} x \\end + 3", .characterNotFound), // missing {
("\\begin{matrix} x \\end{", .characterNotFound), // missing }
("\\begin{matrix} x \\end{matrix + 3", .characterNotFound), // missing }
("\\begin{matrix} x \\end{pmatrix}", .invalidEnv),
("x \\end{matrix}", .missingBegin),
("\\begin{notanenv} x \\end{notanenv}", .invalidEnv),
("\\begin{matrix} \\notacommand \\end{matrix}", .invalidCommand),
("\\begin{displaylines} x & y \\end{displaylines}", .invalidNumColumns),
("\\begin{eqalign} x \\end{eqalign}", .invalidNumColumns),
("\\nolimits", .invalidLimits),
("\\frac\\limits{1}{2}", .invalidLimits)
]
}
func testBuilder() throws {
let data = getTestData()
for testCase in data {
let str = testCase.build
var error = NSError()
let list = MTMathListBuilder.build(fromString: str, error: &error)
XCTAssert(error.code == 0)
let desc = "Error for string:\(str)"
let atomTypes = testCase.atomType
self.checkAtomTypes(list, types:atomTypes, desc:desc)
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, testCase.result, desc)
}
}
func testSuperScript() throws {
let data = getTestDataSuperScript()
for testCase in data {
let str = testCase.build
var error:NSError?
var error = NSError()
let list = MTMathListBuilder.build(fromString: str, error:&error)
XCTAssertNil(error)
XCTAssert(error.code == NSNotFound)
let desc = "Error for string:\(str)"
let atomTypes = testCase.atomType
checkAtomTypes(list, types:atomTypes, desc:desc)
@@ -163,9 +246,9 @@ final class SwiftMathRenderTests: XCTestCase {
let data = getTestDataSubScript()
for testCase in data {
let str = testCase.build
var error:NSError?
var error = NSError()
let list = MTMathListBuilder.build(fromString: str, error:&error)
XCTAssertNil(error)
XCTAssert(error.code == NSNotFound)
let desc = "Error for string:\(str)"
let atomTypes = testCase.atomType
checkAtomTypes(list, types:atomTypes, desc:desc)
@@ -197,9 +280,9 @@ final class SwiftMathRenderTests: XCTestCase {
let data = getTestDataSuperSubScript()
for testCase in data {
let str = testCase.build
var error:NSError?
var error = NSError()
let list = MTMathListBuilder.build(fromString: str, error:&error)
XCTAssertNil(error)
XCTAssert(error.code == NSNotFound)
let desc = "Error for string:\(str)"
let atomTypes = testCase.atomType
checkAtomTypes(list, types:atomTypes, desc:desc)
@@ -425,7 +508,7 @@ final class SwiftMathRenderTests: XCTestCase {
for testCase in data {
let str = testCase.build
var error:NSError?
var error = NSError()
let list = MTMathListBuilder.build(fromString: str, error: &error)!
XCTAssertNotNil(list, str);
@@ -456,6 +539,824 @@ final class SwiftMathRenderTests: XCTestCase {
}
}
func testOver() throws {
let str = "1 \\over c";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 1, desc);
let frac = list.atoms[0] as! MTFraction
XCTAssertEqual(frac.type, .fraction, desc);
XCTAssertEqual(frac.nucleus, "", desc);
XCTAssertTrue(frac.hasRule);
XCTAssertNil(frac.rightDelimiter);
XCTAssertNil(frac.leftDelimiter);
var subList = frac.numerator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
var atom = subList.atoms[0];
XCTAssertEqual(atom.type, .number, desc);
XCTAssertEqual(atom.nucleus, "1", desc);
atom = list.atoms[0];
subList = frac.denominator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "c", desc);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\frac{1}{c}", desc);
}
func testOverInParens() throws {
let str = "5 + {1 \\over c} + 8";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 5, desc);
let types = [MTMathAtomType.number, .binaryOperator, .fraction, .binaryOperator, .number]
self.checkAtomTypes(list, types:types, desc:desc)
let frac = list.atoms[2] as! MTFraction
XCTAssertEqual(frac.type, .fraction, desc);
XCTAssertEqual(frac.nucleus, "", desc);
XCTAssertTrue(frac.hasRule);
XCTAssertNil(frac.rightDelimiter);
XCTAssertNil(frac.leftDelimiter);
var subList = frac.numerator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
var atom = subList.atoms[0];
XCTAssertEqual(atom.type, .number, desc);
XCTAssertEqual(atom.nucleus, "1", desc);
atom = list.atoms[0];
subList = frac.denominator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "c", desc);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "5+\\frac{1}{c}+8", desc);
}
func testAtop() throws {
let str = "1 \\atop c";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 1, desc);
let frac = list.atoms[0] as! MTFraction
XCTAssertEqual(frac.type, .fraction, desc);
XCTAssertEqual(frac.nucleus, "", desc);
XCTAssertFalse(frac.hasRule);
XCTAssertNil(frac.rightDelimiter);
XCTAssertNil(frac.leftDelimiter);
var subList = frac.numerator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
var atom = subList.atoms[0];
XCTAssertEqual(atom.type, .number, desc);
XCTAssertEqual(atom.nucleus, "1", desc);
atom = list.atoms[0];
subList = frac.denominator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "c", desc);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "{1 \\atop c}", desc);
}
func testAtopInParens() throws {
let str = "5 + {1 \\atop c} + 8";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 5, desc);
let types = [MTMathAtomType.number, .binaryOperator, .fraction, .binaryOperator, .number]
self.checkAtomTypes(list, types:types, desc:desc)
let frac = list.atoms[2] as! MTFraction
XCTAssertEqual(frac.type, .fraction, desc);
XCTAssertEqual(frac.nucleus, "", desc);
XCTAssertFalse(frac.hasRule);
XCTAssertNil(frac.rightDelimiter);
XCTAssertNil(frac.leftDelimiter);
var subList = frac.numerator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
var atom = subList.atoms[0];
XCTAssertEqual(atom.type, .number, desc);
XCTAssertEqual(atom.nucleus, "1", desc);
atom = list.atoms[0];
subList = frac.denominator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "c", desc);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "5+{1 \\atop c}+8", desc);
}
func testChoose() throws {
let str = "n \\choose k";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 1, desc);
let frac = list.atoms[0] as! MTFraction
XCTAssertEqual(frac.type, .fraction, desc);
XCTAssertEqual(frac.nucleus, "", desc);
XCTAssertFalse(frac.hasRule);
XCTAssertEqual(frac.rightDelimiter, ")");
XCTAssertEqual(frac.leftDelimiter, "(");
var subList = frac.numerator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
var atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "n", desc);
atom = list.atoms[0];
subList = frac.denominator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "k", desc);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "{n \\choose k}", desc);
}
func testBrack() throws {
let str = "n \\brack k";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 1, desc);
let frac = list.atoms[0] as! MTFraction
XCTAssertEqual(frac.type, .fraction, desc);
XCTAssertEqual(frac.nucleus, "", desc);
XCTAssertFalse(frac.hasRule);
XCTAssertEqual(frac.rightDelimiter, "]");
XCTAssertEqual(frac.leftDelimiter, "[");
var subList = frac.numerator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
var atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "n", desc);
atom = list.atoms[0];
subList = frac.denominator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "k", desc);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "{n \\brack k}", desc);
}
func testBrace() throws {
let str = "n \\brace k";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 1, desc);
let frac = list.atoms[0] as! MTFraction
XCTAssertEqual(frac.type, .fraction, desc);
XCTAssertEqual(frac.nucleus, "", desc);
XCTAssertFalse(frac.hasRule);
XCTAssertEqual(frac.rightDelimiter, "}");
XCTAssertEqual(frac.leftDelimiter, "{");
var subList = frac.numerator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
var atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "n", desc);
atom = list.atoms[0];
subList = frac.denominator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "k", desc);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "{n \\brace k}", desc);
}
func testBinom() throws {
let str = "\\binom{n}{k}";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 1, desc);
let frac = list.atoms[0] as! MTFraction
XCTAssertEqual(frac.type, .fraction, desc);
XCTAssertEqual(frac.nucleus, "", desc);
XCTAssertFalse(frac.hasRule);
XCTAssertEqual(frac.rightDelimiter, ")");
XCTAssertEqual(frac.leftDelimiter, "(");
var subList = frac.numerator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
var atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "n", desc);
atom = list.atoms[0];
subList = frac.denominator!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "k", desc);
// convert it back to latex (binom converts to choose)
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "{n \\choose k}", desc);
}
func testOverLine() throws {
let str = "\\overline 2";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 1, desc);
let over = list.atoms[0] as! MTOverLine
XCTAssertEqual(over.type, .overline, desc);
XCTAssertEqual(over.nucleus, "", desc);
let subList = over.innerList!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
let atom = subList.atoms[0];
XCTAssertEqual(atom.type, .number, desc);
XCTAssertEqual(atom.nucleus, "2", desc);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\overline{2}", desc);
}
func testUnderline() throws {
let str = "\\underline 2";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 1, desc);
let under = list.atoms[0] as! MTUnderLine
XCTAssertEqual(under.type, .underline, desc);
XCTAssertEqual(under.nucleus, "", desc);
let subList = under.innerList!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
let atom = subList.atoms[0];
XCTAssertEqual(atom.type, .number, desc);
XCTAssertEqual(atom.nucleus, "2", desc);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\underline{2}", desc);
}
func testAccent() throws {
let str = "\\bar x";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 1, desc);
let accent = list.atoms[0] as! MTAccent
XCTAssertEqual(accent.type, .accent, desc);
XCTAssertEqual(accent.nucleus, "\u{0304}", desc);
let subList = accent.innerList!
XCTAssertNotNil(subList, desc);
XCTAssertEqual((subList.atoms.count), 1, desc);
let atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc);
XCTAssertEqual(atom.nucleus, "x", desc);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\bar{x}", desc);
}
func testMathSpace() throws {
let str = "\\!";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 1, desc);
let space = list.atoms[0] as! MTMathSpace
XCTAssertEqual(space.type, .space, desc);
XCTAssertEqual(space.nucleus, "", desc);
XCTAssertEqual(space.space, -3);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\! ", desc);
}
func testMathStyle() throws {
let str = "\\textstyle y \\scriptstyle x";
let list = MTMathListBuilder.build(fromString:str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc);
XCTAssertEqual((list.atoms.count), 4, desc);
let style = list.atoms[0] as! MTMathStyle
XCTAssertEqual(style.type, .style, desc);
XCTAssertEqual(style.nucleus, "", desc);
XCTAssertEqual(style.style, .text);
let style2 = list.atoms[2] as! MTMathStyle
XCTAssertEqual(style2.type, .style, desc);
XCTAssertEqual(style2.nucleus, "", desc);
XCTAssertEqual(style2.style, .script);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\textstyle y\\scriptstyle x", desc);
}
func testMatrix() throws {
let str = "\\begin{matrix} x & y \\\\ z & w \\end{matrix}";
let list = MTMathListBuilder.build(fromString:str)!
XCTAssertNotNil(list);
XCTAssertEqual((list.atoms.count), 1);
let table = list.atoms[0] as! MTMathTable
XCTAssertEqual(table.type, .table);
XCTAssertEqual(table.nucleus, "");
XCTAssertEqual(table.environment, "matrix");
XCTAssertEqual(table.interRowAdditionalSpacing, 0);
XCTAssertEqual(table.interColumnSpacing, 18);
XCTAssertEqual(table.numRows, 2);
XCTAssertEqual(table.numColumns, 2);
for i in 0..<2 {
let alignment = table.get(alignmentForColumn:i)
XCTAssertEqual(alignment, .center);
for j in 0..<2 {
let cell = table.cells[j][i];
XCTAssertEqual(cell.atoms.count, 2);
let style = cell.atoms[0] as! MTMathStyle
XCTAssertEqual(style.type, .style);
XCTAssertEqual(style.style, .text);
let atom = cell.atoms[1];
XCTAssertEqual(atom.type, .variable);
}
}
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\begin{matrix}x&y\\\\ z&w\\end{matrix}");
}
func testPMatrix() throws {
let str = "\\begin{pmatrix} x & y \\\\ z & w \\end{pmatrix}";
let list = MTMathListBuilder.build(fromString:str)!
XCTAssertNotNil(list);
XCTAssertEqual((list.atoms.count), 1);
let inner = list.atoms[0] as! MTInner
XCTAssertEqual(inner.type, .inner, str);
XCTAssertEqual(inner.nucleus, "", str);
let innerList = inner.innerList!
XCTAssertNotNil(innerList, str);
XCTAssertNotNil(inner.leftBoundary, str);
XCTAssertEqual(inner.leftBoundary!.type, .boundary, str);
XCTAssertEqual(inner.leftBoundary!.nucleus, "(", str);
XCTAssertNotNil(inner.rightBoundary, str);
XCTAssertEqual(inner.rightBoundary!.type, .boundary, str);
XCTAssertEqual(inner.rightBoundary!.nucleus, ")", str);
XCTAssertEqual((innerList.atoms.count), 1);
let table = innerList.atoms[0] as! MTMathTable
XCTAssertEqual(table.type, .table);
XCTAssertEqual(table.nucleus, "");
XCTAssertEqual(table.environment, "matrix");
XCTAssertEqual(table.interRowAdditionalSpacing, 0);
XCTAssertEqual(table.interColumnSpacing, 18);
XCTAssertEqual(table.numRows, 2);
XCTAssertEqual(table.numColumns, 2);
for i in 0..<2 {
let alignment = table.get(alignmentForColumn:i)
XCTAssertEqual(alignment, .center);
for j in 0..<2 {
let cell = table.cells[j][i];
XCTAssertEqual(cell.atoms.count, 2);
let style = cell.atoms[0] as! MTMathStyle
XCTAssertEqual(style.type, .style);
XCTAssertEqual(style.style, .text);
let atom = cell.atoms[1];
XCTAssertEqual(atom.type, .variable);
}
}
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\left( \\begin{matrix}x&y\\\\ z&w\\end{matrix}\\right) ");
}
func testDefaultTable() throws {
let str = "x \\\\ y";
let list = MTMathListBuilder.build(fromString:str)!
XCTAssertNotNil(list);
XCTAssertEqual(list.atoms.count, 1);
let table = list.atoms[0] as! MTMathTable
XCTAssertEqual(table.type, .table);
XCTAssertEqual(table.nucleus, "");
XCTAssertNil(table.environment);
XCTAssertEqual(table.interRowAdditionalSpacing, 1);
XCTAssertEqual(table.interColumnSpacing, 0);
XCTAssertEqual(table.numRows, 2);
XCTAssertEqual(table.numColumns, 1);
for i in 0..<1 {
let alignment = table.get(alignmentForColumn: i)
XCTAssertEqual(alignment, .left);
for j in 0..<2 {
let cell = table.cells[j][i];
XCTAssertEqual(cell.atoms.count, 1);
let atom = cell.atoms[0];
XCTAssertEqual(atom.type, .variable);
}
}
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "x\\\\ y");
}
func testDefaultTableWithCols() throws {
let str = "x & y \\\\ z & w";
let list = MTMathListBuilder.build(fromString:str)!
XCTAssertNotNil(list);
XCTAssertEqual((list.atoms.count), 1);
let table = list.atoms[0] as! MTMathTable
XCTAssertEqual(table.type, .table);
XCTAssertEqual(table.nucleus, "");
XCTAssertNil(table.environment);
XCTAssertEqual(table.interRowAdditionalSpacing, 1);
XCTAssertEqual(table.interColumnSpacing, 0);
XCTAssertEqual(table.numRows, 2);
XCTAssertEqual(table.numColumns, 2);
for i in 0..<2 {
let alignment = table.get(alignmentForColumn:i)
XCTAssertEqual(alignment, .left);
for j in 0..<2 {
let cell = table.cells[j][i];
XCTAssertEqual(cell.atoms.count, 1);
let atom = cell.atoms[0];
XCTAssertEqual(atom.type, .variable);
}
}
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "x&y\\\\ z&w");
}
func testEqalign() throws {
let str1 = "\\begin{eqalign}x&y\\\\ z&w\\end{eqalign}";
let str2 = "\\begin{split}x&y\\\\ z&w\\end{split}";
let str3 = "\\begin{aligned}x&y\\\\ z&w\\end{aligned}";
for str in [str1, str2, str3] {
let list = MTMathListBuilder.build(fromString:str)!
XCTAssertNotNil(list);
XCTAssertEqual((list.atoms.count), 1);
let table = list.atoms[0] as! MTMathTable
XCTAssertEqual(table.type, .table);
XCTAssertEqual(table.nucleus, "");
XCTAssertEqual(table.interRowAdditionalSpacing, 1);
XCTAssertEqual(table.interColumnSpacing, 0);
XCTAssertEqual(table.numRows, 2);
XCTAssertEqual(table.numColumns, 2);
for i in 0..<2 {
let alignment = table.get(alignmentForColumn:i)
XCTAssertEqual(alignment, (i == 0) ? .right: .left);
for j in 0..<2 {
let cell = table.cells[j][i];
if (i == 0) {
XCTAssertEqual(cell.atoms.count, 1);
let atom = cell.atoms[0];
XCTAssertEqual(atom.type, .variable);
} else {
XCTAssertEqual(cell.atoms.count, 2);
self.checkAtomTypes(cell, types:[.ordinary, .variable], desc:str)
}
}
}
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, str);
}
}
func testDisplayLines() throws {
let str1 = "\\begin{displaylines}x\\\\ y\\end{displaylines}";
let str2 = "\\begin{gather}x\\\\ y\\end{gather}";
for str in [str1, str2] {
let list = MTMathListBuilder.build(fromString:str)
XCTAssertNotNil(list)
XCTAssertEqual(list?.atoms.count, 1);
let table = list?.atoms[0] as! MTMathTable
XCTAssertEqual(table.type, .table);
XCTAssertEqual(table.nucleus, "");
XCTAssertEqual(table.interRowAdditionalSpacing, 1);
XCTAssertEqual(table.interColumnSpacing, 0);
XCTAssertEqual(table.numRows, 2);
XCTAssertEqual(table.numColumns, 1);
for i in 0..<1 {
let alignment = table.get(alignmentForColumn:i)
XCTAssertEqual(alignment, .center);
for j in 0..<2 {
let cell = table.cells[j][i];
XCTAssertEqual(cell.atoms.count, 1);
let atom = cell.atoms[0];
XCTAssertEqual(atom.type, .variable);
}
}
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, str);
}
}
func testErrors() throws {
let data = getTestDataParseErrors()
for testCase in data {
let str = testCase.0
var error = NSError()
let list = MTMathListBuilder.build(fromString: str, error:&error)
let desc = "Error for string:\(str)"
XCTAssertNil(list, desc)
XCTAssertNotNil(error, desc)
XCTAssertEqual(error.domain, MTParseError, desc)
let num = testCase.1
let code = num.rawValue
XCTAssertEqual(error.code, code, desc)
}
}
func testCustom() throws {
let str = "\\lcm(a,b)";
var error = NSError()
var list = MTMathListBuilder.build(fromString: str, error:&error)
XCTAssertNil(list);
XCTAssert(error.code == NSNotFound)
MTMathAtomFactory.add(latexSymbol: "lcm", value: MTMathAtomFactory.operatorWithName("lcm", limits:false))
list = MTMathListBuilder.build(fromString: str, error:&error)
let atomTypes = [MTMathAtomType.largeOperator, .open, .variable, .punctuation, .variable, .close]
self.checkAtomTypes(list, types:atomTypes, desc:"Error for lcm")
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\lcm (a,b)");
}
func testFontSingle() throws {
let str = "\\mathbf x";
let list = MTMathListBuilder.build(fromString: str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc)
XCTAssertEqual(list.atoms.count, 1, desc)
let atom = list.atoms[0];
XCTAssertEqual(atom.type, .variable, desc)
XCTAssertEqual(atom.nucleus, "x", desc)
XCTAssertEqual(atom.fontStyle, .bold)
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\mathbf{x}", desc)
}
func testFontOneChar() throws {
let str = "\\cal xy";
let list = MTMathListBuilder.build(fromString: str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc)
XCTAssertEqual((list.atoms.count), 2, desc)
var atom = list.atoms[0];
XCTAssertEqual(atom.type, .variable, desc)
XCTAssertEqual(atom.nucleus, "x", desc)
XCTAssertEqual(atom.fontStyle, .caligraphic);
atom = list.atoms[1];
XCTAssertEqual(atom.type, .variable, desc)
XCTAssertEqual(atom.nucleus, "y", desc)
XCTAssertEqual(atom.fontStyle, .defaultStyle);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\mathcal{x}y", desc)
}
func testFontMultipleChars() throws {
let str = "\\frak{xy}";
let list = MTMathListBuilder.build(fromString: str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc)
XCTAssertEqual((list.atoms.count), 2, desc)
var atom = list.atoms[0];
XCTAssertEqual(atom.type, .variable, desc)
XCTAssertEqual(atom.nucleus, "x", desc)
XCTAssertEqual(atom.fontStyle, .fraktur);
atom = list.atoms[1];
XCTAssertEqual(atom.type, .variable, desc)
XCTAssertEqual(atom.nucleus, "y", desc)
XCTAssertEqual(atom.fontStyle, .fraktur);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\mathfrak{xy}", desc)
}
func testFontOneCharInside() throws {
let str = "\\sqrt \\mathrm x y";
let list = MTMathListBuilder.build(fromString: str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc)
XCTAssertEqual((list.atoms.count), 2, desc)
let rad = list.atoms[0] as! MTRadical
XCTAssertEqual(rad.type, .radical, desc)
XCTAssertEqual(rad.nucleus, "", desc)
let subList = rad.radicand!
var atom = subList.atoms[0];
XCTAssertEqual(atom.type, .variable, desc)
XCTAssertEqual(atom.nucleus, "x", desc)
XCTAssertEqual(atom.fontStyle, .roman);
atom = list.atoms[1];
XCTAssertEqual(atom.type, .variable, desc)
XCTAssertEqual(atom.nucleus, "y", desc)
XCTAssertEqual(atom.fontStyle, .defaultStyle)
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\sqrt{\\mathrm{x}}y", desc)
}
func testText() throws {
let str = "\\text{x y}";
let list = MTMathListBuilder.build(fromString: str)!
let desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc)
XCTAssertEqual((list.atoms.count), 3, desc)
var atom = list.atoms[0];
XCTAssertEqual(atom.type, .variable, desc)
XCTAssertEqual(atom.nucleus, "x", desc)
XCTAssertEqual(atom.fontStyle, .roman);
atom = list.atoms[1];
XCTAssertEqual(atom.type, .ordinary, desc)
XCTAssertEqual(atom.nucleus, " ", desc)
atom = list.atoms[2];
XCTAssertEqual(atom.type, .variable, desc)
XCTAssertEqual(atom.nucleus, "y", desc)
XCTAssertEqual(atom.fontStyle, .roman);
// convert it back to latex
let latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\mathrm{x\\ y}", desc)
}
func testLimits() throws {
// Int with no limits (default)
var str = "\\int";
var list = MTMathListBuilder.build(fromString: str)!
var desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc)
XCTAssertEqual((list.atoms.count), 1, desc)
var op = list.atoms[0] as! MTLargeOperator
XCTAssertEqual(op.type, .largeOperator, desc)
XCTAssertFalse(op.limits);
// convert it back to latex
var latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\int ", desc)
// Int with limits
str = "\\int\\limits"
list = MTMathListBuilder.build(fromString: str)!
desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc)
XCTAssertEqual((list.atoms.count), 1, desc)
op = list.atoms[0] as! MTLargeOperator
XCTAssertEqual(op.type, .largeOperator, desc)
XCTAssertTrue(op.limits);
// convert it back to latex
latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\int \\limits ", desc)
}
func testNoLimits() throws {
// Sum with limits (default)
var str = "\\sum";
var list = MTMathListBuilder.build(fromString: str)!
var desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc)
XCTAssertEqual((list.atoms.count), 1, desc)
var op = list.atoms[0] as! MTLargeOperator
XCTAssertEqual(op.type, .largeOperator, desc)
XCTAssertTrue(op.limits);
// convert it back to latex
var latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\sum ", desc)
// Int with limits
str = "\\sum\\nolimits";
list = MTMathListBuilder.build(fromString: str)!
desc = "Error for string:\(str)"
XCTAssertNotNil(list, desc)
XCTAssertEqual(list.atoms.count, 1, desc)
op = list.atoms[0] as! MTLargeOperator
XCTAssertEqual(op.type, .largeOperator, desc)
XCTAssertFalse(op.limits);
// convert it back to latex
latex = MTMathListBuilder.mathListToString(list)
XCTAssertEqual(latex, "\\sum \\nolimits ", desc)
}
// func testPerformanceExample() throws {
// // This is an example of a performance test case.