From f26d2d904de41b2f1bfa92221cdc061c2814a1ca Mon Sep 17 00:00:00 2001 From: Ants-Aare Date: Fri, 28 Mar 2025 15:24:33 +0100 Subject: [PATCH 1/4] Added display of Intermediate Representation --- src/display-intermediate-representation.typ | 86 ++++++++ src/utils.typ | 73 ++++++- .../.gitignore | 4 + .../ref/1.png | Bin 0 -> 2031 bytes .../test.typ | 90 ++++++++ .../.gitignore | 4 + .../ref/1.png | Bin 0 -> 5901 bytes .../test.typ | 196 ++++++++++++++++++ 8 files changed, 452 insertions(+), 1 deletion(-) create mode 100644 src/display-intermediate-representation.typ create mode 100644 tests/intermediate-representation-molecules/.gitignore create mode 100644 tests/intermediate-representation-molecules/ref/1.png create mode 100644 tests/intermediate-representation-molecules/test.typ create mode 100644 tests/intermediate-representation-reactions/.gitignore create mode 100644 tests/intermediate-representation-reactions/ref/1.png create mode 100644 tests/intermediate-representation-reactions/test.typ diff --git a/src/display-intermediate-representation.typ b/src/display-intermediate-representation.typ new file mode 100644 index 0000000..63200ac --- /dev/null +++ b/src/display-intermediate-representation.typ @@ -0,0 +1,86 @@ +#import "utils.typ": try-at, count-to-content, charge-to-content, get-bracket, get-arrow, phase-to-content + +#let display-element(data) = { + let isotope = data.at("isotope", default: none) + math.attach( + data.symbol, + t: data.at("oxidation-number", default: none), + tr: charge-to-content(data.at("charge", default: none)), + br: count-to-content(data.at("count", default: none)), + tl: try-at(isotope, "mass-number"), + bl: try-at(isotope, "atomic-number"), + ) +} + +#let display-group(data) = { + let children = data.at("children", default:()) + math.attach( + math.lr({ + get-bracket(data.kind, open:true) + for child in children { + if child.type == "element"{ + display-element(child) + } else if child.type == "group" { + display-group(child) + } + } + get-bracket(data.kind, open:false) + }), + tr: charge-to-content(data.at("charge", default: none)), + br: count-to-content(data.at("count", default: none)), + ) +} + +#let display-ir(data) = { + if data== none{ + none + } else if type(data) == array{ + for value in data { + display-ir(value) + } + } else if data.type == "molecule" { + count-to-content(data.at("count", default: none)) + math.attach( + [ + #let children = data.at("children", default:()) + #for child in children { + if child.type == "element"{ + display-element(child) + } else if child.type == "group" { + display-group(child) + } + } + ], + tr: charge-to-content(data.at("charge", default: none)), + // br: phase-to-content(data.at("phase", default:none)), + ) + context { + text(phase-to-content(data.at("phase", default:none)), size: text.size * 0.75) + } + } else if data.type == "+"{ + math.plus + } else if data.type == "group"{ + display-group(data) + } else if data.type == "element"{ + display-element(data) + } else if data.type == "arrow"{ + if data.at("align", default: 0) == 1{ + $&$ + } + let top = display-ir(data.at("top", default:none)) + let bottom = display-ir(data.at("bottom", default:none)) + math.attach( + math.stretch( + get-arrow(data.at("kind", default:0)), + size: 100% + 2em + ), + t: top, + b: bottom, + ) + if data.at("align", default: 0) == 2{ + $&$ + } + } else if data.type == "content" { + data.body + } +} \ No newline at end of file diff --git a/src/utils.typ b/src/utils.typ index 9a74644..2e1b8ac 100644 --- a/src/utils.typ +++ b/src/utils.typ @@ -67,6 +67,70 @@ "7p": 6, ) +#let brackets = ( + math.paren.l, + math.bracket.l, + math.brace.l, + math.bar.v, + math.paren.r, + math.bracket.r, + math.brace.r, + math.bar.v, +) +#let arrows = ( + sym.arrow.r.l, + sym.arrow.r, + sym.arrow.l, + sym.arrow.r.double, + sym.arrow.l.double, + sym.arrow.r.not, + sym.arrow.l.not, + sym.harpoons.rtlb +) + +#let get-bracket(kind, open: true) = { + if not open{ + kind += 4 + } + brackets.at(kind, default:none) +} +#let get-arrow(kind) = { + arrows.at(kind, default:sym.arrow.r) +} + +#let phase-to-content(phase) = { + if phase == none{ + none + } else if type(phase) == str{ + "(" + phase + ")" + } +} + +#let count-to-content(factor) = { + if factor == none{ + none + } else if type(factor) == int{ + if factor > 1{ + str(factor) + } + } +} +#let charge-to-content(charge) = { + if charge == none{ + none + } else if type(charge) == int{ + if charge < 0{ + str(calc.abs(charge)) + "-" + } + else if charge > 0{ + str(calc.abs(charge)) + "+" + } + else { + none + } + } +} + #let parser-config = ( arrow: (arrow_size: 120%, reversible_size: 120%), conditions: ( @@ -107,7 +171,7 @@ type(it) == content and it.func() == heading and it.depth <= depth } -// Following utility methods are from: +// Following utility method is from: // https://github.com/typst-community/linguify/blob/b220a5993c7926b1d2edcc155cda00d2050da9ba/lib/utils.typ#L3 #let if-auto-then(val,ret) = { if (val == auto){ @@ -117,6 +181,13 @@ } } +#let try-at(value, field, default:none) = { + if (value == none){ + none + } else { + value.at(field, default: default) + } +} // own utils diff --git a/tests/intermediate-representation-molecules/.gitignore b/tests/intermediate-representation-molecules/.gitignore new file mode 100644 index 0000000..40223be --- /dev/null +++ b/tests/intermediate-representation-molecules/.gitignore @@ -0,0 +1,4 @@ +# generated by tytanic, do not edit + +diff/** +out/** diff --git a/tests/intermediate-representation-molecules/ref/1.png b/tests/intermediate-representation-molecules/ref/1.png new file mode 100644 index 0000000000000000000000000000000000000000..59bf7b8cb6381d04889d94e6ab73c7626c282b07 GIT binary patch literal 2031 zcmVJT{3yNhmjzPyHtOiWTjv8GxxYl5cB?edpqiaFjScolAP-3#`3MybD%DTFa zfCelpDiAfg7Qlifh*-diV1WpcVgYy=+zhVU8ia2JAin)2(YCyI9zu zq&f&5(r}b)rHz!9Zx#tp8(-p*SqiXDT&;C%vSg75aLIV72$Nys4JAV4su-Dw}QW>i`g8mS6kW z49hk@qbsX-A#mQzP6NObtL7QN??XY|WGbAJb^w2w@3kZgV7U#okLCQEZLa{N^uGYW z<^~g=1KA%p{|~^hb-=sBLHqW%kktj`#yQmmmiuFxj$Z$rOaEl#j&=WF5WXrXL8G_WA--Wrl;${44QM_rAm^JCr(@T?v7ei^ zTGKo!d^JV|z{Rc12?SVlh=oy@B=*Zia2SBdhqF4VA#n;p_V5~0 z$EIv}VjS!=d;5V&)Ib6D_Vy9zny)bu*3Q{))gx-46MlZy8vqEtf)2aE*VlLQCohJT zh5suyh#IS(4x2sV`|mf2)u)M%j*j+oe=(fEWU+;DadA7v?pu1>z*MnviQ+1?gN;2) zm4&8Ph7EIdnzX}M*xX~KorAKSan+*h?xIn>U4u*57EfGwc=&Aq4jedgC@O;-$IJ?` z`kf8>&DiVigM|2W#te6-_r%+j?a=@g0L3%S$3*{fVovv?O(O{P>LHq32UyrwYBMzn z(uPzE3kG6KPA>e+FJB)ExkBUuXjhsQQ6~4j0H_9-tp?!K>rw=;x|`Ia1b~il0L5F?`j7v0JEgI$OX#S@UD^%03)*jxNq~k2C&_F0kcJX zS*#E;7VjhIH**C5@sdS4a+;PpY~w>C{?yo4R+=J2|V4HVHph?EGP zgXC9BQpn3#0wvo*6fBJ*4Zv<`4QO{Q3Azn&7HJh@K{ZUQDQ6p8kg+|a$3VOYhKVld zY9%cz2g@L7Rw=g4zYM@m84S2D$Z__DXVUV@uq2Ak{uywlL6SCa{$}*+=~O+ji?lhWkYp2*mMBiGAjW&_fL_5qV#{v#8eb4 z!BV?{3bsX<0odjNz~38xkqKZwp;GL9sTVI{l}3nEfRb$RwL@3&hhi^s&;Lj%76(a^ zv8bd|24EY1tyV)+`xd7G>k7BQwI{%Ov9V(8Idgvi|8Ur4h1~5H@7_U|$1Z@sZADk6 zrGiZp=>}kXIH}c{M^S(L85EdBRfK)t>INw-N1=x;NZKaE=7bcGTlu>~aC{8#sNnbrI@-Ve^2r&ol%HMz6B%P#b>m&Urf zxw%f5gHp`s0WOP+0fuh{xySzPt%h{zaSXtBtn0)vA?WfeLf4e#0BHX0IsZ!4SpJZz zemZ*kR8KXma52)y_s&04TH&2yDoWip4IqDne_>)@=i_2z~57Y)imj+&2O?AS)Xq<~_6! z&;D8{&dTb!0hC6y*BSvUgowq61v|SgRRL~7h*E4!qiB9RHa#UJjhd(+B_-tv$R)iW z^U7TByV~a_Nl#x5>}8i6aLa!suvc1HSzBA#@Je8RSnz9`{x7ej{ujO23m+Ml({ca+ N002ovPDHLkV1f>N)I9(I literal 0 HcmV?d00001 diff --git a/tests/intermediate-representation-molecules/test.typ b/tests/intermediate-representation-molecules/test.typ new file mode 100644 index 0000000..c8fb9d2 --- /dev/null +++ b/tests/intermediate-representation-molecules/test.typ @@ -0,0 +1,90 @@ +#import "../../src/display-intermediate-representation.typ" : display-ir +#set page(width: auto, height: auto, margin: 0.5em) + +#let co2 = ( + type:"molecule", + count:1, + phase:"g", + charge:0, + align:none, + arrow:none, + children:( + ( + type:"element", + count:1, + symbol:"C", + charge:0, + oxidation-number:none, + isotope:none, + align:none, + ), + ( + type:"element", + count:2, + symbol:"O", + charge:0, + oxidation-number:none, + isotope:none, + align:none, + ) + ) +) + +#let hexacyanidoferrat = ( + type:"molecule", + count:3, + phase:"s", + charge:0, + align:none, + arrow:none, + children:( + ( + type:"group", + count:2, + kind:1, + charge:4, + align:none, + children:( + ( + type:"element", + count:1, + symbol:"Fe", + charge:0, + oxidation-number:none, + isotope:none, + align:none, + ), + ( + type:"group", + count:6, + kind:0, + charge:0, + align:none, + children:( + ( + type:"element", + count:1, + symbol:"C", + charge:0, + oxidation-number:none, + isotope:none, + align:none, + ), + ( + type:"element", + count:1, + symbol:"N", + charge:0, + oxidation-number:none, + isotope:none, + align:none, + ), + ) + ), + ) + ), + ) +) + +#display-ir(co2)\ +#display-ir(hexacyanidoferrat) \ No newline at end of file diff --git a/tests/intermediate-representation-reactions/.gitignore b/tests/intermediate-representation-reactions/.gitignore new file mode 100644 index 0000000..40223be --- /dev/null +++ b/tests/intermediate-representation-reactions/.gitignore @@ -0,0 +1,4 @@ +# generated by tytanic, do not edit + +diff/** +out/** diff --git a/tests/intermediate-representation-reactions/ref/1.png b/tests/intermediate-representation-reactions/ref/1.png new file mode 100644 index 0000000000000000000000000000000000000000..9e63fb3c4fc5b56440d69d158b8a48f4bea5d78a GIT binary patch literal 5901 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 literal 0 HcmV?d00001 diff --git a/tests/intermediate-representation-reactions/test.typ b/tests/intermediate-representation-reactions/test.typ new file mode 100644 index 0000000..61414c5 --- /dev/null +++ b/tests/intermediate-representation-reactions/test.typ @@ -0,0 +1,196 @@ +#import "../../src/display-intermediate-representation.typ" : display-ir +#set page(width: auto, height: auto, margin: 0.5em) + +#let reaction1 = ( + ( + type: "molecule", + charge:2, + children:( + ( + type:"group", + kind:1, + children:( + ( + type:"element", + symbol:"Cu", + ), + ( + type:"group", + kind:0, + count:4, + children:( + ( + type:"element", + count:2, + symbol:"H", + ), + ( + type:"element", + symbol:"O", + ), + ) + ), + ) + ), + ) + ), + ( + type: "arrow", + kind: 1, + top: none, + bottom: none, + align: 1 + ), + ( + type: "molecule", + charge:2, + children:( + ( + type:"group", + kind:1, + children:( + ( + type:"element", + symbol:"Cu", + ), + ( + type:"group", + kind:0, + count:4, + children:( + ( + type:"element", + symbol:"N", + ), + ( + type:"element", + count:3, + symbol:"H", + ), + ) + ), + ) + ), + ) + ), + (type: "+"), + ( + type: "molecule", + count:4, + children:( + ( + type:"element", + count:2, + symbol:"H", + ), + ( + type:"element", + symbol:"O", + ), + ) + ) +) + +#let reaction2 = ( + ( + type: "molecule", + charge:2, + children:( + ( + type:"group", + kind:1, + children:( + ( + type:"element", + symbol:"Cu", + ), + ( + type:"group", + kind:0, + count:4, + children:( + ( + type:"element", + count:2, + symbol:"H", + ), + ( + type:"element", + symbol:"O", + ), + ) + ), + ) + ), + ) + ), + ( + type:"+" + ), + ( + type: "molecule", + count:4, + children:( + ( + type:"element", + symbol:"N", + ), + ( + type:"element", + count:3, + symbol:"H", + ), + ) + ), + ( + type: "arrow", + kind: 1, + top: ( + ( + type:"content", + body:[dissolve in ] + ), + ( + type: "molecule", + children:( + ( + type:"element", + count:2, + symbol:"H", + ), + ( + type:"element", + symbol:"O", + ), + ) + ), + ), + bottom: ( + ( + type:"content", + body:$Delta H^0$ + ), + ), + align: 1 + ), + ( + type: "molecule", + count:4, + children:( + ( + type:"element", + count:2, + symbol:"H", + ), + ( + type:"element", + symbol:"O", + ), + ) + ), +) + +$ + #display-ir(reaction1)\ + #display-ir(reaction2)\ +$ \ No newline at end of file From b9fe9cbfdbf57698a5fffa8eafa10cd475fb07d6 Mon Sep 17 00:00:00 2001 From: Ants-Aare Date: Sun, 30 Mar 2025 12:55:19 +0200 Subject: [PATCH 2/4] added unit tests for shell configuration and cleaned up files --- src/data-model.typ | 160 +++++++-------------------- src/display-shell-configuration.typ | 76 +++++++++++++ src/lib.typ | 3 +- tests/shell-configuration/.gitignore | 4 + tests/shell-configuration/ref/1.png | Bin 0 -> 2860 bytes tests/shell-configuration/test.typ | 8 ++ 6 files changed, 131 insertions(+), 120 deletions(-) create mode 100644 src/display-shell-configuration.typ create mode 100644 tests/shell-configuration/.gitignore create mode 100644 tests/shell-configuration/ref/1.png create mode 100644 tests/shell-configuration/test.typ diff --git a/src/data-model.typ b/src/data-model.typ index 4f23ee2..d0eec88 100644 --- a/src/data-model.typ +++ b/src/data-model.typ @@ -1,21 +1,53 @@ -#import "utils.typ": is-sequence, is-kind, is-heading, is-metadata, padright, get-all-children, hydrates, elements, shell-capacities, orbital-capacities, get-element-dict, get-molecule-dict, to-string +#import "utils.typ": is-sequence, is-kind, is-heading, is-metadata, padright, get-all-children, hydrates, elements, get-element-dict, get-molecule-dict, to-string #import "regex.typ": patterns -#import "formula-parser.typ": ce +#let get-element( + symbol: auto, + atomic-number:auto, + common-name:auto, + cas:auto, +)={ + let element = if symbol != auto { + elements.find(x=> x.symbol == symbol) + } else if atomic-number != auto{ + elements.find(x=> x.atomic-number == atomic-number) + } else if common-name != auto{ + elements.find(x=> x.common-name == common-name) + } else if cas != auto{ + elements.find(x=> x.cas == cas) + } + return metadata(element) +} + +#let validate-element(element)={ + let type = type(element) + if type == str{ + if element.len() > 2{ + return get-element(common-name:element) + } else { + return get-element(symbol:element) + } + } else if type == int{ + return get-element(atomic-number:element) + } else if type == content{ + return get-element-dict(element) + } else if type == dictionary{ + return element + } +} //TODO: properly parse bracket contents // maybe recursively with a bracket regex, passing in the bracket content and multiplier(?) //TODO: Properly apply stochiometry #let get-element-counts(molecule)={ - let found-elements = (:) let remaining = molecule.trim() while remaining.len() > 0 { - let match = remaining.match(regex_patterns.at("element")) + let match = remaining.match(patterns.element) if match != none { remaining = remaining.slice(match.end) let element = match.captures.at(0) - let count = int(if match.captures.at(1, default: "") == "" {1} else{match.captures.at(1)}) + let count = 1 //int(if match.captures.at(1, default: "") == "" {1} else{match.captures.at(1)}) let current = found-elements.at(element, default: 0) found-elements.insert(element, count) } @@ -43,116 +75,6 @@ return weight } -#let get-shell-configuration(element)={ - element = get-element-dict(element) - let charge = element.at("charge", default:0) - let electron-amount = element.atomic-number - charge - - let result = () - for value in shell-capacities { - if electron-amount <= 0{ - break - } - - if electron-amount >= value.at(1){ - result.push(value) - electron-amount -= value.at(1) - } else { - result.push((value.at(0), electron-amount)) - electron-amount = 0 - } - } - return result -} - -//TODO: fix Cr and Mo -#let get-electron-configuration(element)={ - element = get-element-dict(element) - let charge = element.at("charge", default:0) - let electron-amount = element.atomic-number - charge - - let result = () - for value in orbital-capacities { - if electron-amount <= 0{ - break - } - if electron-amount >= value.at(1){ - result.push(value) - electron-amount -= value.at(1) - } else { - result.push((value.at(0), electron-amount)) - electron-amount = 0 - } - } - return result -} - -#let display-electron-configuration(element, short: false)={ - let configuration = get-electron-configuration(element) - - if short{ - let prefix = "" - if configuration.at(14, default: (0,0)).at(1) == 6{ - configuration = configuration.slice(15) - prefix = "[Rn]" - } else if configuration.at(10, default: (0,0)).at(1) == 6{ - configuration = configuration.slice(11) - prefix = "[Xe]" - } else if configuration.at(7, default: (0,0)).at(1) == 6{ - configuration = configuration.slice(8) - prefix = "[Kr]" - } else if configuration.at(4, default: (0,0)).at(1) == 6{ - configuration = configuration.slice(5) - prefix = "[Ar]" - } else if configuration.at(2, default: (0,0)).at(1) == 6{ - configuration = configuration.slice(3) - prefix = "[Ne]" - } else if configuration.at(0, default: (0,0)).at(1) == 2{ - configuration = configuration.slice(1) - prefix = "[He]" - } - - return prefix + configuration.map(x=> $#x.at(0)^#str(x.at(1))$).sum() - } else{ - return configuration.map(x=> $#x.at(0)^#str(x.at(1))$).sum() - } -} - -#let get-element( - symbol: auto, - atomic-number:auto, - common-name:auto, - cas:auto, -)={ - let element = if symbol != auto { - elements.find(x=> x.symbol == symbol) - } else if atomic-number != auto{ - elements.find(x=> x.atomic-number == atomic-number) - } else if common-name != auto{ - elements.find(x=> x.common-name == common-name) - } else if cas != auto{ - elements.find(x=> x.cas == cas) - } - return metadata(element) -} - -#let validate-element(element)={ - let type = type(element) - if type == str{ - if element.len() > 2{ - return get-element(common-name:element) - } else { - return get-element(symbol:element) - } - } else if type == int{ - return get-element(atomic-number:element) - } else if type == content{ - return get-element-dict(element) - } else if type == dictionary{ - return element - } -} - #let define-ion( element, charge: 0, @@ -241,18 +163,18 @@ formula = smiles } - if CAS == ""{ - CAS = none + if cas == ""{ + cas = none } found-elements = get-element-counts(formula) - if InChI != ""{ + if inchi != ""{ // TODO: create InChI keys from provided InChI: // https://typst.app/universe/package/jumble // https://www.inchi-trust.org/download/104/InChI_TechMan.pdf }else{ - InChI = none + inchi = none } } diff --git a/src/display-shell-configuration.typ b/src/display-shell-configuration.typ new file mode 100644 index 0000000..f4d25a5 --- /dev/null +++ b/src/display-shell-configuration.typ @@ -0,0 +1,76 @@ +#import "utils.typ": get-element-dict, shell-capacities, orbital-capacities + +#let get-shell-configuration(element)={ + element = get-element-dict(element) + let charge = element.at("charge", default:0) + let electron-amount = element.atomic-number - charge + + let result = () + for value in shell-capacities { + if electron-amount <= 0{ + break + } + + if electron-amount >= value.at(1){ + result.push(value) + electron-amount -= value.at(1) + } else { + result.push((value.at(0), electron-amount)) + electron-amount = 0 + } + } + return result +} + +//TODO: fix Cr and Mo +#let get-electron-configuration(element)={ + element = get-element-dict(element) + let charge = element.at("charge", default:0) + let electron-amount = element.atomic-number - charge + + let result = () + for value in orbital-capacities { + if electron-amount <= 0{ + break + } + if electron-amount >= value.at(1){ + result.push(value) + electron-amount -= value.at(1) + } else { + result.push((value.at(0), electron-amount)) + electron-amount = 0 + } + } + return result +} + +#let display-electron-configuration(element, short: false)={ + let configuration = get-electron-configuration(element) + + if short{ + let prefix = "" + if configuration.at(14, default: (0,0)).at(1) == 6{ + configuration = configuration.slice(15) + prefix = "[Rn]" + } else if configuration.at(10, default: (0,0)).at(1) == 6{ + configuration = configuration.slice(11) + prefix = "[Xe]" + } else if configuration.at(7, default: (0,0)).at(1) == 6{ + configuration = configuration.slice(8) + prefix = "[Kr]" + } else if configuration.at(4, default: (0,0)).at(1) == 6{ + configuration = configuration.slice(5) + prefix = "[Ar]" + } else if configuration.at(2, default: (0,0)).at(1) == 6{ + configuration = configuration.slice(3) + prefix = "[Ne]" + } else if configuration.at(0, default: (0,0)).at(1) == 2{ + configuration = configuration.slice(1) + prefix = "[He]" + } + + return prefix + configuration.map(x=> $#x.at(0)^#str(x.at(1))$).sum() + } else{ + return configuration.map(x=> $#x.at(0)^#str(x.at(1))$).sum() + } +} \ No newline at end of file diff --git a/src/lib.typ b/src/lib.typ index e2a741e..d596e17 100644 --- a/src/lib.typ +++ b/src/lib.typ @@ -1,2 +1,3 @@ -#import "data-model.typ": get-element-counts, get-element, get-weight, define-molecule, define-hydrate, reaction, get-shell-configuration, get-electron-configuration, display-electron-configuration +#import "data-model.typ": get-element-counts, get-element, get-weight, define-molecule, define-hydrate, reaction +#import "display-shell-configuration.typ": get-electron-configuration, get-shell-configuration, display-electron-configuration #import "formula-parser.typ": ce \ No newline at end of file diff --git a/tests/shell-configuration/.gitignore b/tests/shell-configuration/.gitignore new file mode 100644 index 0000000..40223be --- /dev/null +++ b/tests/shell-configuration/.gitignore @@ -0,0 +1,4 @@ +# generated by tytanic, do not edit + +diff/** +out/** diff --git a/tests/shell-configuration/ref/1.png b/tests/shell-configuration/ref/1.png new file mode 100644 index 0000000000000000000000000000000000000000..4e897ee11ae422899df7cc0ebbe18609cb0a9762 GIT binary patch literal 2860 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 Date: Sun, 30 Mar 2025 13:09:22 +0200 Subject: [PATCH 3/4] Refactored existing parser from ground up --- ...se-formula-intermediate-representation.typ | 164 ++++++++++++++++++ tests/parse-ir-elements/.gitignore | 5 + tests/parse-ir-elements/test.typ | 66 +++++++ 3 files changed, 235 insertions(+) create mode 100644 src/parse-formula-intermediate-representation.typ create mode 100644 tests/parse-ir-elements/.gitignore create mode 100644 tests/parse-ir-elements/test.typ diff --git a/src/parse-formula-intermediate-representation.typ b/src/parse-formula-intermediate-representation.typ new file mode 100644 index 0000000..2dcef4b --- /dev/null +++ b/src/parse-formula-intermediate-representation.typ @@ -0,0 +1,164 @@ +#let patterns = ( + // element: regex("^(?P[A-Z][a-z]?)(?:(?P_?\d+)|(?P\^?[+-]?\d*\.?-?))?(?:(?P_?\d+)|(?P\^?[+-]?\d*\.?-?))?"), + element: regex("^(?P[A-Z][a-z]?)(?:(?P_?\d+)|(?P(?:\^[+-]?[IV]+|\^?[+-]?\d?)\.?-?))?(?:(?P_?\d+)|(?P(?:\^[+-]?[IV]+|\^?[+-]?\d?)\.?-?))?"), + // group: regex("^(\((?:[^()]|(?R))*\)|\{(?:[^{}]|(?R))*\}|\[(?:[^\[\]]|(?R))*\])"), + group: regex("^(?P\((?:[^()]|(?R))*\)|\{(?:[^{}]|(?R))*\}|\[(?:[^\[\]]|(?R))*\])(?:(?P_?\d+)|(?P(?:\^[+-]?[IV]+|\^?[+-]?\d?)\.?-?))?(?:(?P_?\d+)|(?P(?:\^[+-]?[IV]+|\^?[+-]?\d?)\.?-?))?"), + + // Match physical states (s/l/g/aq) + state: regex("^\((s|l|g|aq|solid|liquid|gas|aqueous)\)"), +) + +#let get-count-and-charge(count1, count2, charge1, charge2) = { + let radical = false + let count = if count1 != none and count1 != ""{ + int(count1.replace("_", "")) + } else if count2 != none and count2 != ""{ + int(count2.replace("_", "")) + } else { + none + } + + let charge = if charge1 != none and charge1 != ""{ + charge1.replace("^", "") + } else if charge2 != none and charge2 != ""{ + charge2.replace("^", "") + } else{ + none + } + + if charge != none and charge != ""{ + if charge.contains("."){ + charge = charge.replace(".", "") + radical = true + } + if charge == "-"{ + charge = -1 + } else if charge.contains("-"){ + charge = -int(charge.replace("-", "")) + } else if charge == "+" { + charge = 1 + } else if charge.replace("+", "").contains(regex("^[0-9]+$")){ + charge = int(charge.replace("+", "")) + } + } + + return (count, charge, radical) +} + +#let element-string-to-ir(formula) = { + let element-match = formula.match(patterns.element) + + if element-match != none{ + let element = ( + type: "element", + symbol: element-match.captures.at(0), + ) + let x = get-count-and-charge(element-match.captures.at(1), + element-match.captures.at(3), + element-match.captures.at(2), + element-match.captures.at(4), + ) + if x.at(0) != none{ + element.count = x.at(0) + } + if x.at(1) != none{ + element.charge = x.at(1) + } + if x.at(2) { + element.radical = x.at(2) + } + return (true, element, element-match.end) + } + return (false,) +} + +#let group-string-to-ir(formula) = { + let group-match = formula.match(patterns.group) + if group-match != none{ + + let group-content = group-match.captures.at(0) + let kind = if group-content.at(0) == "("{ + group-content = group-content.trim(regex("[()]"), repeat:false) + 0 + } else if group-content.at(0) == "["{ + group-content = group-content.trim(regex("[\[\]]"), repeat:false) + 1 + }else if group-content.at(0) == "{"{ + group-content = group-content.trim(regex("[{}]"), repeat:false) + 2 + } + let x = get-count-and-charge(group-match.captures.at(1), + group-match.captures.at(3), + group-match.captures.at(2), + group-match.captures.at(4), + ) + + let group = ( + type: "group", + kind:kind, + children:() + ) + if x.at(0) != none{ + group.count = x.at(0) + } + if x.at(1) != none{ + group.charge = x.at(1) + } + if x.at(2) { + group.radical = x.at(2) + } + + let remaining = group-content + while remaining.len() > 0 { + let element = element-string-to-ir(remaining) + if element.at(0) { + group.children.push(element.at(1)) + remaining = remaining.slice(element.at(2)) + continue + } + + let result = group-string-to-ir(remaining) + if result.at(0) { + group.children.push(result.at(1)) + remaining = remaining.slice(result.at(2)) + continue + } + + remaining = remaining.slice(remaining.codepoints().at(0).len()) + } + + return (true, group, group-match.end) + } + return (false,) +} + +//this will assume that the string is a molecule for performance reasons +#let molecule-string-to-ir(formula) = { + let remaining = formula.trim() + if remaining.len() == 0 { + return none + } + let molecule = ( + type: "molecule", + children:() + ) + + while remaining.len() > 0 { + let element = element-string-to-ir(remaining) + if element.at(0) { + molecule.children.push(element.at(1)) + remaining = remaining.slice(element.at(2)) + continue + } + + let group = group-string-to-ir(remaining) + if group.at(0) { + molecule.children.push(group.at(1)) + remaining = remaining.slice(group.at(2)) + continue + } + + remaining = remaining.slice(remaining.codepoints().at(0).len()) + } + return molecule +} \ No newline at end of file diff --git a/tests/parse-ir-elements/.gitignore b/tests/parse-ir-elements/.gitignore new file mode 100644 index 0000000..03681f9 --- /dev/null +++ b/tests/parse-ir-elements/.gitignore @@ -0,0 +1,5 @@ +# generated by tytanic, do not edit + +diff/** +out/** +ref/** diff --git a/tests/parse-ir-elements/test.typ b/tests/parse-ir-elements/test.typ new file mode 100644 index 0000000..8c0d2fd --- /dev/null +++ b/tests/parse-ir-elements/test.typ @@ -0,0 +1,66 @@ +#import "../../src/parse-formula-intermediate-representation.typ" : molecule-string-to-ir + +#let co2 = ( + type: "molecule", + children: ( + (type: "element", symbol: "C"), + (type: "element", symbol: "O", count: 2), + ), + ) +#let ir-co2 = molecule-string-to-ir("CO2") + +#let no = ( + type: "molecule", + children: ( + (type: "element", symbol: "N"), + ( + type: "element", + symbol: "O", + charge: -2, + radical: true, + ), + ), + ) +#let ir-no = molecule-string-to-ir("NO^2.-") + +#let na = ( + type: "molecule", + children: ( + (type: "element", symbol: "Na", count: 3, charge: 1), + ), + ) +#let ir-na1 = molecule-string-to-ir("Na_3^+") +#let ir-na2 = molecule-string-to-ir("Na_3^+") + +#let cl = ( + type: "molecule", + children: ( + ( + type: "element", + symbol: "Cl", + count: 2, + charge: -1, + ), + ), + ) +#let ir-cl = molecule-string-to-ir("Cl2-1") + +#let fe = ( + type: "molecule", + children: ( + ( + type: "element", + symbol: "Fe", + count: 2, + charge: "III", + ), + ), + ) +#let ir-fe = molecule-string-to-ir("Fe2^III)") + +#assert(co2 == ir-co2) +#assert(no == ir-no) +#assert(na == ir-na1) +#assert(na == ir-na2) +#assert(cl == ir-cl) +#assert(fe == ir-fe) \ No newline at end of file From b1120aa833de0ecb9d1e6c7d6a558f2ee038f8ab Mon Sep 17 00:00:00 2001 From: Ants-Aare Date: Sun, 30 Mar 2025 22:08:20 +0200 Subject: [PATCH 4/4] improvements --- src/display-intermediate-representation.typ | 96 ++++--- src/formula-parser.typ | 139 --------- src/lib.typ | 7 +- ...se-formula-intermediate-representation.typ | 265 +++++++++++++++-- src/utils.typ | 40 ++- tests/README-graphic1/ref/1.png | Bin 3005 -> 3167 bytes tests/README-graphic1/ref/formula-parser.svg | 268 ------------------ tests/README-graphic1/test.typ | 2 +- tests/arrow-align/ref/1.png | Bin 2910 -> 1536 bytes tests/brackets/ref/1.png | Bin 5266 -> 5229 bytes tests/charges/ref/1.png | Bin 2664 -> 3254 bytes tests/charges/test.typ | 6 +- .../ref/1.png | Bin 5901 -> 6020 bytes .../test.typ | 4 +- tests/parse-ir-elements/test.typ | 15 +- tests/parse-ir-groups/.gitignore | 5 + tests/parse-ir-groups/test.typ | 43 +++ tests/simple-formulas/ref/1.png | Bin 3142 -> 3184 bytes tests/simple-formulas/test.typ | 8 +- 19 files changed, 405 insertions(+), 493 deletions(-) delete mode 100644 src/formula-parser.typ delete mode 100644 tests/README-graphic1/ref/formula-parser.svg create mode 100644 tests/parse-ir-groups/.gitignore create mode 100644 tests/parse-ir-groups/test.typ diff --git a/src/display-intermediate-representation.typ b/src/display-intermediate-representation.typ index 63200ac..6c50db1 100644 --- a/src/display-intermediate-representation.typ +++ b/src/display-intermediate-representation.typ @@ -5,7 +5,7 @@ math.attach( data.symbol, t: data.at("oxidation-number", default: none), - tr: charge-to-content(data.at("charge", default: none)), + tr: charge-to-content(data.at("charge", default: none), radical: data.at("radical", default: false)), br: count-to-content(data.at("count", default: none)), tl: try-at(isotope, "mass-number"), bl: try-at(isotope, "atomic-number"), @@ -14,37 +14,39 @@ #let display-group(data) = { let children = data.at("children", default:()) + let kind = data.at("kind", default: 1) math.attach( math.lr({ - get-bracket(data.kind, open:true) + get-bracket(kind, open:true) for child in children { - if child.type == "element"{ + if child.type == "content" { + child.body + } else if child.type == "element" { display-element(child) + }else if data.type == "align"{ + $&$ } else if child.type == "group" { display-group(child) } } - get-bracket(data.kind, open:false) + get-bracket(kind, open:false) }), tr: charge-to-content(data.at("charge", default: none)), br: count-to-content(data.at("count", default: none)), ) } -#let display-ir(data) = { - if data== none{ - none - } else if type(data) == array{ - for value in data { - display-ir(value) - } - } else if data.type == "molecule" { - count-to-content(data.at("count", default: none)) +#let display-molecule(data) = { + count-to-content(data.at("count", default: none)) math.attach( [ #let children = data.at("children", default:()) #for child in children { - if child.type == "element"{ + if child.type == "content"{ + child.body + } else if data.type == "align"{ + $&$ + } else if child.type == "element"{ display-element(child) } else if child.type == "group" { display-group(child) @@ -55,32 +57,52 @@ // br: phase-to-content(data.at("phase", default:none)), ) context { - text(phase-to-content(data.at("phase", default:none)), size: text.size * 0.75) + text(phase-to-content(data.at("phase", default:none)), size: text.size * 0.75) } - } else if data.type == "+"{ - math.plus - } else if data.type == "group"{ - display-group(data) - } else if data.type == "element"{ - display-element(data) - } else if data.type == "arrow"{ - if data.at("align", default: 0) == 1{ - $&$ +} + +#let display-ir(data) = { + if data== none{ + none + } else if type(data) == array{ + for value in data { + display-ir(value) + //this removes spacing for groups that have long charges (looks better) + if value.type == "molecule" { + let last = value.children.last() + if last.type == "group" and (last.at("charge", default: none) != none or last.at("count", default: none) != none){ + h(-0.4em) + } + } } - let top = display-ir(data.at("top", default:none)) - let bottom = display-ir(data.at("bottom", default:none)) - math.attach( - math.stretch( - get-arrow(data.at("kind", default:0)), - size: 100% + 2em - ), - t: top, - b: bottom, - ) - if data.at("align", default: 0) == 2{ + }else{ + if data.type == "molecule" { + display-molecule(data) + } else if data.type == "+"{ + h(0.4em, weak: true) + math.plus + h(0.4em, weak: true) + } else if data.type == "group"{ + display-group(data) + } else if data.type == "element"{ + display-element(data) + } else if data.type == "content"{ + data.body + }else if data.type == "align"{ $&$ + } else if data.type == "arrow"{ + h(0.4em, weak: true) + let top = display-ir(data.at("top", default:none)) + let bottom = display-ir(data.at("bottom", default:none)) + math.attach( + math.stretch( + get-arrow(data.at("kind", default:0)), + size: 100% + 2em + ), + t: top, + b: bottom, + ) + h(0.4em, weak: true) } - } else if data.type == "content" { - data.body } } \ No newline at end of file diff --git a/src/formula-parser.typ b/src/formula-parser.typ deleted file mode 100644 index e22321c..0000000 --- a/src/formula-parser.typ +++ /dev/null @@ -1,139 +0,0 @@ -// Import required modules -#import "utils.typ": parser-config -#import "regex.typ": patterns - -// [CHANGE] Replaced direct pattern access with cached patterns for better performance -#let PATTERNS = { - let cache = (:) - for pattern in patterns.keys() { - cache.insert(pattern, patterns.at(pattern)) - } - cache -} - -// [CHANGE] Added symbol map for consistent rendering and better maintainability -#let SYMBOL_MAP = ( - arrows: ( - "<-": (sym.harpoons.rtlb, parser-config.arrow.reversible_size), - "=": ($=$, parser-config.arrow.arrow_size), - "->": ($->$, parser-config.arrow.arrow_size) - ), - charges: ( - "+": (math.plus, 0.8em), - "-": (math.minus, 0.75em) - ) -) - -// [CHANGE] Simplified charge processing with explicit symbol hiding -#let process_charge = (input, charge) => { - let first = charge.first() - show "-": text(size: 0.75em, baseline: -0.15em)[#math.minus] - show "+": text(size: 0.75em, baseline: -0.15em)[#math.plus] - show "^": none - context {$#block(height: measure(input).height)^#charge$} -} - -// Process reaction conditions (temperature, pressure, catalyst, etc.) -#let process_condition(cond) = { - let cond = cond.trim() - if cond.match(patterns.heating) != none { - return (none, { sym.Delta }) - } - - let is_bottom = ( - parser-config.conditions.bottom.identifiers.any(ids => ids.any(id => cond.starts-with(id))) - or parser-config.conditions.bottom.units.any(unit => cond.ends-with(unit)) - ) - - return if is_bottom { (none, cond) } else { (parse_formula(cond), none) } -} - -// Process reaction arrows with top and bottom conditions -#let process_arrow(arrow_text, condition: none) = { - let (arrow_match, bracket_content) = if arrow_text.contains("[") { - let match = arrow_text.match(patterns.arrow) - (match.captures.at(0), match.captures.at(1)) - } else { - (arrow_text, none) - } - - let arrow = if arrow_match.contains("<-") { - $stretch(#sym.harpoons.rtlb, size: #parser-config.arrow.reversible_size)$ - } else if arrow_match.contains("=") { - $stretch(=, size: #parser-config.arrow.arrow_size)$ - } else { - $stretch(->, size: #parser-config.arrow.arrow_size)$ - } - - let top = () - let bottom = () - - if bracket_content != none { - top.push(bracket_content) - } - - if condition != none { - for cond in condition.split(",") { - let (t, b) = process_condition(cond) - if t != none { top.push(t) } - if b != none { bottom.push(b) } - } - } - - $arrow^#top.join(",")_#bottom.join(",")$ -} - -// [CHANGE] Added pattern handlers for better code organization and reusability -#let PATTERN_HANDLERS = ( - charge: (r,c) => process_charge(r, c), - arrow: (t) => process_arrow(t), -) - -// [CHANGE] Optimized main parser with single-pass matching and improved error handling -#let ce = formula => { - let remaining = formula.trim() - if remaining.len() == 0 { return [] } - - let result = none - let pattern_group = parser-config.match_order.full - - while remaining.len() > 0 { - let best = (pattern: none, match: none) - - // [CHANGE] Single pass scan replaces multiple pattern attempts - for pattern in pattern_group { - let match = remaining.match(PATTERNS.at(pattern)) - if match != none { - best = (pattern: pattern, match: match) - break - } - } - - // [CHANGE] Simplified pattern handling using direct math mode - if best.match != none { - let pattern = best.pattern - let match = best.match - - result += if pattern == "plus" { $+$ } - else if pattern == "element" { $#match.captures.at(0) _#if match.captures.at(1) != none [#match.captures.at(1)]$ } - else if pattern == "bracket" { $#match.captures.at(0) _#if match.captures.at(1) != none [#match.captures.at(1)]$ } - else if pattern == "charge" { (PATTERN_HANDLERS.charge)(result, match.captures.at(0)) } - else { (PATTERN_HANDLERS.arrow)(match.text) } - - remaining = remaining.slice(match.end) - } else { - // [CHANGE] Better Unicode support with codepoints handling - result += text(remaining.at(0)) - remaining = remaining.slice(remaining.codepoints().at(0).len()) - } - } - - $upright(display(result))$ -} - -// Test section for previewing the output -#import "@preview/shadowed:0.2.0": shadowed - -#set page(margin: 0.3em, width: auto, height: auto) - -#shadowed(inset: 0.7em, radius: 6pt)[#ce("Cu^2+ +")] \ No newline at end of file diff --git a/src/lib.typ b/src/lib.typ index d596e17..c54a58d 100644 --- a/src/lib.typ +++ b/src/lib.typ @@ -1,3 +1,8 @@ #import "data-model.typ": get-element-counts, get-element, get-weight, define-molecule, define-hydrate, reaction #import "display-shell-configuration.typ": get-electron-configuration, get-shell-configuration, display-electron-configuration -#import "formula-parser.typ": ce \ No newline at end of file +#import "display-intermediate-representation.typ": display-ir +#import "parse-formula-intermediate-representation.typ": string-to-ir + +#let ce(formula) = { + display-ir(string-to-ir(formula)) +} \ No newline at end of file diff --git a/src/parse-formula-intermediate-representation.typ b/src/parse-formula-intermediate-representation.typ index 2dcef4b..cceb3a7 100644 --- a/src/parse-formula-intermediate-representation.typ +++ b/src/parse-formula-intermediate-representation.typ @@ -1,8 +1,13 @@ +#import "utils.typ": arrow-string-to-kind #let patterns = ( // element: regex("^(?P[A-Z][a-z]?)(?:(?P_?\d+)|(?P\^?[+-]?\d*\.?-?))?(?:(?P_?\d+)|(?P\^?[+-]?\d*\.?-?))?"), element: regex("^(?P[A-Z][a-z]?)(?:(?P_?\d+)|(?P(?:\^[+-]?[IV]+|\^?[+-]?\d?)\.?-?))?(?:(?P_?\d+)|(?P(?:\^[+-]?[IV]+|\^?[+-]?\d?)\.?-?))?"), // group: regex("^(\((?:[^()]|(?R))*\)|\{(?:[^{}]|(?R))*\}|\[(?:[^\[\]]|(?R))*\])"), group: regex("^(?P\((?:[^()]|(?R))*\)|\{(?:[^{}]|(?R))*\}|\[(?:[^\[\]]|(?R))*\])(?:(?P_?\d+)|(?P(?:\^[+-]?[IV]+|\^?[+-]?\d?)\.?-?))?(?:(?P_?\d+)|(?P(?:\^[+-]?[IV]+|\^?[+-]?\d?)\.?-?))?"), + + reaction-plus: regex("^(\s?\+\s?)"), + reaction-arrow: regex("^\s?(<->|<=>|->|<-|=>|<=|-\/>|<\/-)(?:\[([^\[\]]*(?:\[[^\[\]]*\][^\[\]]*)*)\])?(?:\[([^\[\]]*(?:\[[^\[\]]*\][^\[\]]*)*)\])?\s?"), + math: regex("^(\$[^$]*\$)"), // Match physical states (s/l/g/aq) state: regex("^\((s|l|g|aq|solid|liquid|gas|aqueous)\)"), @@ -45,34 +50,47 @@ return (count, charge, radical) } -#let element-string-to-ir(formula) = { +#let string-to-element(formula) = { let element-match = formula.match(patterns.element) - if element-match != none{ - let element = ( - type: "element", - symbol: element-match.captures.at(0), - ) - let x = get-count-and-charge(element-match.captures.at(1), - element-match.captures.at(3), - element-match.captures.at(2), - element-match.captures.at(4), - ) - if x.at(0) != none{ - element.count = x.at(0) - } - if x.at(1) != none{ - element.charge = x.at(1) - } - if x.at(2) { - element.radical = x.at(2) - } - return (true, element, element-match.end) - } - return (false,) + if element-match != none{ + let element = ( + type: "element", + symbol: element-match.captures.at(0), + ) + let x = get-count-and-charge(element-match.captures.at(1), + element-match.captures.at(3), + element-match.captures.at(2), + element-match.captures.at(4), + ) + if x.at(0) != none{ + element.count = x.at(0) + } + if x.at(1) != none{ + element.charge = x.at(1) + } + if x.at(2) { + element.radical = x.at(2) + } + return (true, element, element-match.end) + } + return (false,) +} + +#let string-to-math(formula) = { + let match = formula.match(patterns.math) + + if match != none{ + let math-content = ( + type: "content", + body: eval(match.text), + ) + return (true, math-content, match.end) + } + return (false,) } -#let group-string-to-ir(formula) = { +#let string-to-group(formula) = { let group-match = formula.match(patterns.group) if group-match != none{ @@ -107,58 +125,243 @@ if x.at(2) { group.radical = x.at(2) } + + let random-content = "" let remaining = group-content while remaining.len() > 0 { - let element = element-string-to-ir(remaining) + + if remaining.at(0) == "&"{ + group.children.push((type:"align",)) + remaining = remaining.slice(1) + continue + } + let math-result = string-to-math(remaining) + if math-result.at(0) { + if random-content != none and random-content != ""{ + group.children.push((type:"content", body:[#random-content])) + } + random-content = "" + group.children.push(math-result.at(1)) + remaining = remaining.slice(math-result.at(2)) + continue + } + + let element = string-to-element(remaining) if element.at(0) { + if random-content != none and random-content != ""{ + group.children.push((type:"content", body:[#random-content])) + } + random-content = "" group.children.push(element.at(1)) remaining = remaining.slice(element.at(2)) continue } - let result = group-string-to-ir(remaining) + let result = string-to-group(remaining) if result.at(0) { + if random-content != none and random-content != ""{ + group.children.push((type:"content", body:[#random-content])) + } + random-content = "" group.children.push(result.at(1)) remaining = remaining.slice(result.at(2)) continue } + random-content += remaining.codepoints().at(0) remaining = remaining.slice(remaining.codepoints().at(0).len()) } - + + if random-content != none and random-content != ""{ + group.children.push((type:"content", body:[#random-content])) + } return (true, group, group-match.end) } return (false,) } + //this will assume that the string is a molecule for performance reasons #let molecule-string-to-ir(formula) = { let remaining = formula.trim() if remaining.len() == 0 { return none } + let molecule = ( type: "molecule", children:() ) - + + let random-content = "" + while remaining.len() > 0 { - let element = element-string-to-ir(remaining) + + if remaining.at(0) == "&"{ + molecule.children.push((type:"align",)) + remaining = remaining.slice(1) + continue + } + + let math-result = string-to-math(remaining) + if math-result.at(0) { + if random-content != none and random-content != ""{ + molecule.children.push((type:"content", body:[#random-content])) + } + random-content = "" + molecule.children.push(math-result.at(1)) + remaining = remaining.slice(math-result.at(2)) + continue + } + + let element = string-to-element(remaining) + if element.at(0) { + if random-content != none and random-content != ""{ + molecule.children.push((type:"content", body:[#random-content])) + } + random-content = "" molecule.children.push(element.at(1)) remaining = remaining.slice(element.at(2)) continue } - let group = group-string-to-ir(remaining) + let group = string-to-group(remaining) if group.at(0) { + if random-content != none and random-content != ""{ + molecule.children.push((type:"content", body:[#random-content])) + } + random-content = "" molecule.children.push(group.at(1)) remaining = remaining.slice(group.at(2)) continue } - + + random-content += remaining.codepoints().at(0) remaining = remaining.slice(remaining.codepoints().at(0).len()) } + + if random-content != none and random-content != ""{ + molecule.children.push((type:"content", body:[#random-content])) + } return molecule +} + +#let string-to-ir(reaction) = { + let remaining = reaction.trim() + if remaining.len() == 0 { + return none + } + let full-reaction = () + + let current-molecule = ( + type: "molecule", + children:() + ) + + let random-content = "" + while remaining.len() > 0 { + + if remaining.at(0) == "&"{ + if current-molecule.children.len() > 0{ + full-reaction.push(current-molecule) + current-molecule = (type:"molecule", children:()) + } + full-reaction.push((type:"align",)) + remaining = remaining.slice(1) + continue + } + let math-result = string-to-math(remaining) + if math-result.at(0) { + if random-content != none and random-content != ""{ + full-reaction.push((type:"content", body:[#random-content])) + } + random-content = "" + full-reaction.push(math-result.at(1)) + remaining = remaining.slice(math-result.at(2)) + continue + } + + let element = string-to-element(remaining) + if element.at(0) { + if random-content != none and random-content != ""{ + if current-molecule.children.len() == 0{ + full-reaction.push((type:"content", body:[#random-content])) + } else{ + current-molecule.children.push((type:"content", body:[#random-content])) + } + } + random-content = "" + current-molecule.children.push(element.at(1)) + remaining = remaining.slice(element.at(2)) + continue + } + + let group = string-to-group(remaining) + if group.at(0) { + if random-content != none and random-content != ""{ + if current-molecule.children.len() == 0{ + full-reaction.push((type:"content", body:[#random-content])) + } else{ + current-molecule.children.push((type:"content", body:[#random-content])) + } + } + random-content = "" + current-molecule.children.push(group.at(1)) + remaining = remaining.slice(group.at(2)) + continue + } + + let plus-match = remaining.match(patterns.reaction-plus) + if plus-match != none{ + if current-molecule.children.len() > 0{ + full-reaction.push(current-molecule) + current-molecule = (type:"molecule", children:()) + } + if random-content != none and random-content != ""{ + full-reaction.push((type:"content", body:[#random-content])) + } + random-content = "" + full-reaction.push((type:"+")) + remaining = remaining.slice(plus-match.end) + continue + } + + let arrow-match = remaining.match(patterns.reaction-arrow) + if arrow-match != none{ + if current-molecule.children.len() > 0{ + full-reaction.push(current-molecule) + current-molecule = (type:"molecule", children:()) + } + if random-content != none and random-content != ""{ + full-reaction.push((type:"content", body:[#random-content])) + } + random-content = "" + let arrow = ( + type:"arrow", + kind:arrow-string-to-kind(arrow-match.captures.at(0)), + ) + if arrow-match.captures.at(1) != none{ + arrow.top = string-to-ir(arrow-match.captures.at(1)) + } + if arrow-match.captures.at(2) != none{ + arrow.bottom = string-to-ir(arrow-match.captures.at(2)) + } + full-reaction.push(arrow) + remaining = remaining.slice(arrow-match.end) + continue + } + + random-content += remaining.codepoints().at(0) + remaining = remaining.slice(remaining.codepoints().at(0).len()) + } + if current-molecule.children.len() != 0{ + full-reaction.push(current-molecule) + } + if random-content != none and random-content != ""{ + full-reaction.push((type:"content", body:[#random-content])) + } + + return full-reaction } \ No newline at end of file diff --git a/src/utils.typ b/src/utils.typ index 2e1b8ac..d986251 100644 --- a/src/utils.typ +++ b/src/utils.typ @@ -77,6 +77,7 @@ math.brace.r, math.bar.v, ) + #let arrows = ( sym.arrow.r.l, sym.arrow.r, @@ -87,6 +88,16 @@ sym.arrow.l.not, sym.harpoons.rtlb ) +#let arrow-kinds = ( + "<->":0, + "->":1, + "<-":2, + "=>":3, + "<+":4, + "-/>":5, + "":7, +) #let get-bracket(kind, open: true) = { if not open{ @@ -115,19 +126,36 @@ } } } -#let charge-to-content(charge) = { +#let arrow-string-to-kind(arrow) = { + arrow = arrow.trim() + arrow-kinds.at(arrow, default: 1) +} +#let charge-to-content(charge, radical: false) = { if charge == none{ none - } else if type(charge) == int{ - if charge < 0{ - str(calc.abs(charge)) + "-" + } else if type(charge) == int { + if radical{ + sym.bullet + } + if charge < 0 { + if calc.abs(charge) > 1{ + str(calc.abs(charge)) + } + math.minus } - else if charge > 0{ - str(calc.abs(charge)) + "+" + else if charge > 0 { + if charge > 1{ + str(charge) + } + math.plus } else { none } + } else if type(charge) == str{ + charge.replace(".", sym.bullet) + .replace("-", math.minus) + .replace("+", math.plus) } } diff --git a/tests/README-graphic1/ref/1.png b/tests/README-graphic1/ref/1.png index 8978ab0636273c9367cdd5b2150d72c314643753..88ec8da57cf1cc8b8114785f9c19482e1d3375e4 100644 GIT binary patch literal 3167 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 FV1mg})p!5^ literal 3005 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 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/README-graphic1/test.typ b/tests/README-graphic1/test.typ index f1feff5..2c7e2a8 100644 --- a/tests/README-graphic1/test.typ +++ b/tests/README-graphic1/test.typ @@ -1,4 +1,4 @@ #import "../../src/lib.typ" : ce #set page(width: auto, height: auto, margin: 0.5em) -#ce("[Cu(H2O)4]^2+ + 4NH3 -> [Cu(NH3)4]^2+ + 4H2O") +$#ce("[Cu(H2O)4]^2+ + 4NH3 -> [Cu(NH3)4]^2+ + 4H2O")$ diff --git a/tests/arrow-align/ref/1.png b/tests/arrow-align/ref/1.png index f857144d34ebbe5356a797ba2723865f200f7409..b63e8a02602187a0a4bebf5aff4950e40433c189 100644 GIT binary patch delta 1531 zcmVD+sFXAaC|o!MoYJL~!Uaelw=-rqlb=6{~Ohdb*xoA zAJ@nAakxINkL%<5I9wmsN6Gk^3?O%^Bu-rKdMxz)4mo51sd;ZaKwd$?g4iJ|J01gl zMWDV<29g#C66+Rt>4E3#OVGmsGLYPZC#7aMeB<%@e$CwqS#I5Im-^E;9aea}zJenS z_(Usloh!P2^nY1`q0-a!S>w9N3$Ty4!PxWi`c}alo2Tomnn%cPm`L29bWB$teM3r1 zOV_1nKQ|Lk*Eg?{klQNnX5t1*rgrt#7g$BvmN@knPuJHSWomD4PlpmB!_`U$I}X$3 zVDWys?kql5S$U|ql(xr;E5xhElM*XrHt)S>{7-tgzJDVTV<~ZvOk`M)>L7R&UG$of zbPdrYr=})ow6smuJTG02fE0_&llLna?cw_7oDu(EKddLNb3NjK?7^i5u->EfJ&2d; zOe`iYQx{o5Wc6JDRfk9G+q+z*0(ToM%Lsu+RT|copzUrOPtLoEaxWJ$y`#!UzhUN zrCcA^$Km?8KCX}J<8XakAJ@nAakxINPwDzwDCWHoE!Lr666+}VR4C;*SB$4n-QiqcUb)Yp zMt>&PPF#lXC}O}3%=cyWQ8KZR)mIB}i`BOjUSai_{jkcZzRq}CCVb3%uxZ~#4C+zD zfa{!^*jFFg^~GQrtFHn=%&fjFSj+0GL+>W1`pTD+G8n^rkkm?|@TejNT*o@%A9;P> zqrQXDXPE|D+0A>^FRX#pw}fs2r}}37Mt|DX_!n_dlS#;0%vHpI8?4Lit1rZ8G@eSd300<#x}FxeJf$LU7txPE!xm(cJuDT*%oK|GGbFw zQbq$@vFD3}Qz&6LLrDWHY2d(1T)e!*m2Djim-~g)HkiihBav{7(MOJ846Bb!#u6v` z?k1W9@?bTQSoXD$YtXk%2?Mf65`PD`@6n~LrB!HK>@9`10?kQpH4zzo=fUTWpbzc( z=0YB;uNCnkt8W92bE0q0Cb1j`t(0wXonlSJ{fZcHna=e0<_NPa1qNPOE+Wyv3(ps7mb{`3A{QseJ-W!oE@a`UmLGDPe%P z#F;e0aw&!N9J#v`OA8)kL%-beOw>c h$Mtc29Io$w=0Dxc$SJn6Wqkku002ovPDHLkV1mD21aklY literal 2910 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 IM6N<$f<_0DJpcdz diff --git a/tests/brackets/ref/1.png b/tests/brackets/ref/1.png index 5883d0390dccb242ff3fa4897f1c066fb847a995..b6da07b5b18b60dec87282e7fbdb51d7417082ff 100644 GIT binary patch literal 5229 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 Y2U>#(wu(y6Hvj+t07*qoM6N<$f_k3&<^TWy diff --git a/tests/charges/ref/1.png b/tests/charges/ref/1.png index 4bbcda0ccc5955bd9d1dc1d5f7ebf4e85cb76b52..3d34e73312cc84d3c3da2381c0217db922d34609 100644 GIT binary patch literal 3254 zcmV;n3`z5eP)P%v@4S9@NYP%Mxrtk{=}u-k+^aM; z;if`L%NZ*M@3zeq)Rq0_%r%2;QWaK;*~zG*z``FWG999*(r94gR@8CO^dx|ecBrFL z#-&w50c{C@-=9mWhlG$+HyOz-wiyE#s7D2^Ioe+bibrZ7NbkypA2ka=9evZ~ zSDMQSZdnQngeq zRZG=UwNx!tOVv`fRBojeIpu~>TJde4m%RG+TBijkcao3a6kpE-eCp*p#m_rfNu@W3 z+9mbrh-cm zN3TQVMO&ccQcPv5!P`!55LnS4;Jr^1l~sxl&3sPc+#J(6Isn%f`7-7dmWFNfyO8zV z&lOdQybs2{JZAm{@NWIQH#RONDjP|AV&Y<>W7RVUwNx!tOVv`fR4r9Y)l#)oEo}mA zsiBl^T=xi-US0B*)0<(Jz$@7m6Bi%5I~&Q-vGH+xm)`9| zSrA zZmjgdcJCj3np&!B%??5DWA<8urJvBtkhoBAvr27K|9_=$NN-~4ImU9Kw}m~`V5vL3 z1xWyn5KAWn^9J3ov|Bx;y@PrF4_>;yzqU?k%V5FNbxQXzs({{pMw*67o$1XQr7w!5 z>*9Hn|4Henc;3$H(wHCd&&(XDtzBvvFL0<+`W>UP>Qa{7P-zGafn*$cGZ?pT3-%-%Z~k*lyQ-S5tOx8e>Y6yD_02`(-!=0jXu-oD!0(m)qU`(fM|VDln3J3PUSfi^F2bEJ)ns56akv{VB@ zsS!(o-U`+i4V7N8ppy_48k=O*DsA5GZM?Mketr6PG*bG7%j9RK-UeqRTMWGsY)ONq z_=rLw`kZFs(J4Lp^l>y&x^p2=(0LpX`0=yi{PPZPkbTRoo ztkSXTQA-AUyk@Dd4S-;iJK%)sS5i01I2H|;qTnU#y#SIs^uEN0XT}(@u;UtBJUW8e zANm`O2)>3|&L43ihr?%3lJO`Rv;_6E%Q8 z-jg!$01`ezO>rLF)p$?L$0RTA@;oEJyWL?bM8CUqetTrcfU{`0REoW*h5UHGss$vu z8s(lN1y89oj1#lq&w$Tv89?bY-{?I*zXIi#h7-@x07^AicpCL&TP;;f4XyNA`c1?{g!@O06L!*KY@3rC5r_wh`kQpcf;S*DWNpK^^htEMw_0h z|8%8tx??#s=OmRfE%l%Thz`)J^_Su-Z9vo~k_+-I&7d|F(Cb18_m+m(-9<44%76EM zN&O)iPVe7aI&D0V##FO3@OCt{Q{DjhL6SwVsaW>>%I*ME&38Fq?>05rh zenKhm$L5Tc4!v`4|EaHUNU#gQYFTzbj8 zQweyL4yJ!9x)iakMqCF_;%c@7ygMzb|L#>*DKbWOS`@j=?&TD4&f9lxWA#Mu?U>rj z%BoF|k%}q>PJO>}Tc&!Bq?W3sYN=YPma3&{samR*8fs}J?yph_rSlF$PjkKs4LCec z7NviBRSQKOVVfbyozu<5>)D~v;6+aGnC$89rn&3t?m5|G!X{vIm>f!rlh-n~F*P;$ z#}6zmQCo@^1gUMu74nH@epLW8S@W6kzQ3w)GO0C9$-D-@D@7K&w@msRsJ~5bpq7Cr z!OJoq4MBpj4|s>DBLw{^3cQm8W%h|zla8ULons*Qw{b2+V<=fH?F7L9eJRH5lu>CR zux1x(a^48u59A2FIC?=>Z|O#78I=wL&SwF*6&)T`435S$2Rt_lgI*!gnR-ieOf|A8 zJ@EdWOxgKT2JIXG`HkymK3E;P`H`pgx6mS$7+$d?xE-Itj7OMD_E zclIn^^A~u5;gF=y9PQ+>UJKrtSnd(~qP5tQkhuSsxGZqO1HrlG<2q<)k__*}GwpR~ z8l8P6I4);^CFU9OD}{d~nkLd8oC5k9C^DKa!_tCbMsf~tl-g~)k3=E?5(y%OO*BL-*&cI!%$!k_9zqGt7 z6V=4t_GaFV0PLT0F z@S@B-!7I!D=DD+qD1`~rf@jCR0q_fjKs1Mbg2;IwuKM}vt&(vmiKVa6B%rbvoq_1Q zahE%|IW`%XPDQ9D|Iz_84~QgNNP5#|oHE@Cyz@AeRC={l%VWS&a)M+O%>n#l$`UPq zR#2%nX+WD80OM&qB);?-pxgD{N2j5b(wBz~8|?N;k@#dC2Z;~81d(D&^8m>#a)jg! z@=synEuP{D-~FZWwZdIPiZ~`XIsrwN@;F=Z$Eg}!nPE= z%83J%P+EFq3(@klB0y3&$SN8@qD|j?2o@a~u7uJv-T{Gu0X~1AT17;kUgMv0_(}`G zffbN-Y2&K$Dxr5{vQD9;bp_*?07|5;`TrODx| oR(_nXzP3UwRZG=Uwe*4f5A=@?*1KanLsk-Ee+lQ6=o=uNG>28wH5uPCKxE-WgW3v?94#6(-%Q!}A5GSS=@ zLKa~Lw>q*&E+~SEHx*HE8wC+z7(hOkXFDu9UX*#B=bU<8e>{hCfb+c1@B7DmXXebj z0EX;dP%@MZCBu*z*Q+3Z#?nRKc(@hHosnssi|Us>z{>hTu8e{Y3xGF$!Ac4fHzVD_ z#@gD}(b3^0YwNes)Depl(qsS^2`K=eJvGp@7LBOJSsj5?89G8r!FXs>h{hhNthNDp z$BrFa?Ok^{AxQ>ir=h7p^KyI^7#KLLN8M#csrVU*K=d{=FwF(P=j6DgL#c5Ev}FK( zo95|v7y!H{0C2a_N|ixrDez-IuvYx-B=T+604Q=_9XrF}IHb#Pc+;G68R_&V0AIa$ z0|2t)GHbv|o-fI1XjgF=!|7E3hiMENtG+8q2C7`aXx}O*I5^mgMxZHaGO!&COJi+i z9UbJ!5WGjY)0a8pJF*7=4(*aW;~J$`A*k)|bQdTcdQakvjBf2;G`H^J2A=NYe}v8* zeqN>}uoe*LvhtOcSjkW_lnf=~&zSKjd(=t!GtQ5kP1RClR2As(G-VQGRBi0hXL7Hu z-)g}MoZ=tgH#rh?pJC(3=)h+@-1$pdSp|lg*h!ax#iqXJae0pMoLVqe&atG9^J9=2?o8?QMGvLQz>Uv7 z>^m|nADo25WK~i^CN9J!sgn2qQmO2sN`{i5WGER*hLZ7o8MnR|HSmM*Yv3gASE-W| zf5(ORWVI?Owomyyn=^=7f{fg4E4G}*Q{}Gc;N!xdP8TNp;lGky-ri~WQabL@94V_45kb9WR#L*aWiI6Hwb*_Xg*`+ zQN}txLo>0z`CH@D#mpE+$3PHFd+-^~{tP!N**lMcbRYy1sC(m~NS6^oogf%YKb0%v zI5pCMQ)NjL9u9)Q?;)tVTmtl=!}fCy`QyhizZ5(AAzO&$TkkZeb@G9 zz$iG?kMug3kFwl#gls)m0RW@UwIBni6h14RXIzZNX8r`u7?V?ARP<|m8LUr95EwTJ z;ZxIxwkSg&t-4p(kg=b*p2>TsHmx3=I$wi$<3DfmQ8D zj)C_URqM-G?PGw`7zA9&0q`JkMO-OZ|J)AN31R|)FZI`#k<~`q;*7KXz&dXKMNXt$ z8UPg0e6Tjt4iGG)ee`A2nm3-xl|AEWWNdZwGyG?adzs6)*(L-)kbNb9G+GJPT-qIi zZ%G?I!^)A-sXhapHlU$PvHMjCF;&gah(EN)jmromIRN$%HGpka;CxPAhhPP<)pr`` z6Arz0H*Y9+xs1-O5>rxAekAJvtp7VWdx-@Ep=5~Oj6pM7{^bTiV3->3WnBmGgDp6D zgkAw}C=Jn@F>n?DT-g|X{&_P5PUIm3`^>-_PQL}G+|=w3y%{~10odTH`rc+%3o||@ z=>SlW3LuN#2j>q{$4ao4l10#)(SAEnYJU@mC-KjmQA$WL1jV*v0Kn@kfCnb0!HaAa z$lmOznTr;z*~AZwd_u~>DQ}$xT%plGF$sUhj7wo35;8R8Yw*r?^*WjwxB-k2Ya!TW zJ?L0xCN-&~kwXBDiS`A zI0x4D5rCj9!gIjL&|Gj*)dvqBRR4w>Np=5yV7~^8x#8fA>|JBPC9uN25Y4CSln>6* zzV~3j4E!95=Gzzs&I)f`?L9+gV8}HzSD9-WSj!g!Im-;1al^d?%~L)8G+2vYn>cZN zuQ-Edpx|dTPs;CL6}htVm8VNe#$Pz2B4x?$+p=dA4@fVLGE0&>BavnT9X=2vL$igm zBcl`$l&$p`ICxqnnkbL0hSuU6F)|9j^dux;$&y(EEkYiG6aSLOMHKIO#k~wol?1qz ziZhgIBU%mso5`R`Ft)Y+0*nhCdNsXeoUh~BYH>2qnrOl3Onv}>TWwux!HT6JXzGE3 zFR(IHy z1N|HT1kvlLXZVa;mM>-jI803-SWNq%f$%v%iM+or7J`k`NC!Pc$hd)eF|;)VD`{uQl#xeiA$T*W2RIj{&Un;~ zMuYbT-3r#%y`|2;uXF%7X~Y$*Gq1cWMMe=C@+Lolp>;CP;eMDfPwb5H6I&?#>Uat2 z)%Y1L18}*gO(s}#E-Vr|2 H2O") #linebreak() -#ce("OH 1-") +#ce("OH-3") #linebreak() -#ce("Fe(OH)2 ^0") +#ce("Fe(OH)2^0") #linebreak() -#ce("PO4 3-") +#ce("PO4-3") diff --git a/tests/intermediate-representation-reactions/ref/1.png b/tests/intermediate-representation-reactions/ref/1.png index 9e63fb3c4fc5b56440d69d158b8a48f4bea5d78a..c3fbed2a0693d5f714dbeaae41a0bd3a6478c63e 100644 GIT binary patch literal 6020 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 diff --git a/tests/intermediate-representation-reactions/test.typ b/tests/intermediate-representation-reactions/test.typ index 61414c5..c7e294d 100644 --- a/tests/intermediate-representation-reactions/test.typ +++ b/tests/intermediate-representation-reactions/test.typ @@ -34,12 +34,12 @@ ), ) ), + (type: "align"), ( type: "arrow", kind: 1, top: none, bottom: none, - align: 1 ), ( type: "molecule", @@ -142,6 +142,7 @@ ), ) ), + (type: "align"), ( type: "arrow", kind: 1, @@ -171,7 +172,6 @@ body:$Delta H^0$ ), ), - align: 1 ), ( type: "molecule", diff --git a/tests/parse-ir-elements/test.typ b/tests/parse-ir-elements/test.typ index 8c0d2fd..0b48ce8 100644 --- a/tests/parse-ir-elements/test.typ +++ b/tests/parse-ir-elements/test.typ @@ -1,4 +1,6 @@ #import "../../src/parse-formula-intermediate-representation.typ" : molecule-string-to-ir +#import "../../src/lib.typ" : display-ir +#set page(width: auto, height: auto, margin: 0.5em) #let co2 = ( type: "molecule", @@ -56,8 +58,19 @@ ), ), ) -#let ir-fe = molecule-string-to-ir("Fe2^III)") +#let ir-fe = molecule-string-to-ir("Fe2^III") +#display-ir(ir-co2) +#display-ir(ir-no) +#display-ir(ir-cl) +#display-ir(ir-fe) +#display-ir(ir-na1) +#display-ir(ir-na2)\ +#display-ir(co2) +#display-ir(no) +#display-ir(cl) +#display-ir(fe) +#display-ir(na) #assert(co2 == ir-co2) #assert(no == ir-no) #assert(na == ir-na1) diff --git a/tests/parse-ir-groups/.gitignore b/tests/parse-ir-groups/.gitignore new file mode 100644 index 0000000..03681f9 --- /dev/null +++ b/tests/parse-ir-groups/.gitignore @@ -0,0 +1,5 @@ +# generated by tytanic, do not edit + +diff/** +out/** +ref/** diff --git a/tests/parse-ir-groups/test.typ b/tests/parse-ir-groups/test.typ new file mode 100644 index 0000000..b171a3d --- /dev/null +++ b/tests/parse-ir-groups/test.typ @@ -0,0 +1,43 @@ +#import "../../src/parse-formula-intermediate-representation.typ" : molecule-string-to-ir + +#let trisethylendiamin = ( + type: "molecule", + children: ( + ( + type: "group", + kind: 1, + children: ( + (type: "element", symbol: "Co"), + ( + type: "group", + kind: 0, + children: ((type: "content", body: [en]),), + count: 3, + ), + ), + ), + (type: "element", symbol: "Cl", count: 3), + ), + ) +#let ir-trisethylendiamin = molecule-string-to-ir("[Co(en)3]Cl3") + +#let fenh3 = ( + type: "molecule", + children: ( + (type: "element", symbol: "Fe"), + ( + type: "group", + kind: 1, + children: ( + (type: "element", symbol: "N"), + (type: "element", symbol: "H", count: 3), + ), + count: 2, + charge: 1, + ), + ), + ) +#let ir-fenh3 = molecule-string-to-ir("Fe[NH3]2+") + +#assert(trisethylendiamin == ir-trisethylendiamin) +#assert(fenh3 == ir-fenh3) \ No newline at end of file diff --git a/tests/simple-formulas/ref/1.png b/tests/simple-formulas/ref/1.png index a9d934e0ceb18028d3627970e5b8bb006c745086..a7eccaf162b8e4256129344b04aa0265f810367c 100644 GIT binary patch literal 3184 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 diff --git a/tests/simple-formulas/test.typ b/tests/simple-formulas/test.typ index bfcb174..10ef719 100644 --- a/tests/simple-formulas/test.typ +++ b/tests/simple-formulas/test.typ @@ -3,11 +3,11 @@ #set page(width: auto, height: auto, margin: 0.5em) -#ce("H2SO4") +$#ce("H2SO4")$ #linebreak() -#ce("12Fe2 (SO4)3") +$#ce("12Fe2(SO4)3")$ #linebreak() -#ce("514H2O") +$#ce("514H2O")$ #linebreak() -#ce("9Fe(OH)3") +$#ce("9Fe(OH)3")$