Class: AsciiChem::Formatter::Text
- Defined in:
- lib/asciichem/formatter/text.rb
Overview
Renders a Model tree as canonical AsciiChem text. Round-trip
conformance: AsciiChem.parse(s).to_text == s for any conformant
s. The formatter is the canonicaliser — equivalent inputs map
to the same output.
Canonicalisation rules (v1):
- Explicit subscript marker:
H_2, notH2. - Coefficient before molecule:
2H_2O. - Isotope binds to atom:
^14C(no{}carrier). - Charge is number-then-sign per IUPAC:
Ca^2+. - Oxidation state in roman numerals with parens:
Fe^(III). - Group brackets preserved from input.
- Reaction arrows use the canonical ASCII spelling (
->,<-,<=>,<->).
Instance Method Summary collapse
- #atom_annotation(atom) ⇒ Object
- #format_coord(value) ⇒ Object
- #molecule_annotations(molecule) ⇒ Object
- #render_arrow_with_conditions(reaction) ⇒ Object
- #render_node(node) ⇒ Object
- #render_terms(terms) ⇒ Object
- #visit_atom(atom) ⇒ Object
- #visit_bond(bond) ⇒ Object
- #visit_calculation(calc) ⇒ Object
- #visit_crystal(crystal) ⇒ Object
- #visit_electron_configuration(ec) ⇒ Object
- #visit_embedded_math(em) ⇒ Object
- #visit_formula(formula) ⇒ Object
- #visit_group(group) ⇒ Object
- #visit_mechanism(mech) ⇒ Object
- #visit_molecule(molecule) ⇒ Object
- #visit_reaction(reaction) ⇒ Object
- #visit_reaction_cascade(cascade) ⇒ Object
- #visit_spectrum(spectrum) ⇒ Object
- #visit_text(text) ⇒ Object
- #visit_z_matrix(zm) ⇒ Object
Methods inherited from Base
Instance Method Details
#atom_annotation(atom) ⇒ Object
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/asciichem/formatter/text.rb', line 59 def atom_annotation(atom) parts = [] if atom.x2 && atom.y2 coord = "@(#{format_coord(atom.x2)},#{format_coord(atom.y2)}" coord << ",#{format_coord(atom.z2)}" if atom.z2 parts << "#{coord})" end parts << "@#{atom.atom_parity}" if atom.atom_parity parts << "@m(#{atom.spin_multiplicity})" if atom.spin_multiplicity parts << %(@t("#{atom.atom_title}")) if atom.atom_title if atom.x_fract && atom.y_fract && atom.z_fract parts << "@f(#{format_coord(atom.x_fract)},#{format_coord(atom.y_fract)},#{format_coord(atom.z_fract)})" end parts.join end |
#format_coord(value) ⇒ Object
75 76 77 |
# File 'lib/asciichem/formatter/text.rb', line 75 def format_coord(value) value == value.to_i ? value.to_i.to_s : value.to_s end |
#molecule_annotations(molecule) ⇒ Object
32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/asciichem/formatter/text.rb', line 32 def molecule_annotations(molecule) parts = [] molecule.names.each { |n| parts << %(@name("#{n.content}")) } molecule.identifiers.each { |i| parts << %(@#{i.convention}("#{i.value}")) } parts << %(@title("#{molecule.title}")) if molecule.title molecule.formulas.each { |f| parts << %(@formula("#{f[:concise]}")) if f[:concise] } molecule.labels.each { |l| parts << %(@label("#{l[:value]}")) if l[:value] } molecule.properties.each { |p| parts << %(@#{p[:title]}("#{p[:value]}")) if p[:title] && p[:value] } molecule..each { |m| parts << %(@meta("#{m[:name]}","#{m[:content]}")) } parts.empty? ? "" : " #{parts.join}" end |
#render_arrow_with_conditions(reaction) ⇒ Object
211 212 213 214 215 216 217 218 219 |
# File 'lib/asciichem/formatter/text.rb', line 211 def render_arrow_with_conditions(reaction) arrow = reaction.arrow_ascii conds = reaction.conditions return arrow unless conds above = conds.above ? "[#{conds.above}]" : "" below = conds.below ? "[#{conds.below}]" : "" "#{arrow}#{above}#{below}" end |
#render_node(node) ⇒ Object
203 204 205 |
# File 'lib/asciichem/formatter/text.rb', line 203 def render_node(node) node.accept(self) end |
#render_terms(terms) ⇒ Object
207 208 209 |
# File 'lib/asciichem/formatter/text.rb', line 207 def render_terms(terms) terms.map { |n| render_node(n) }.join(" + ") end |
#visit_atom(atom) ⇒ Object
44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
# File 'lib/asciichem/formatter/text.rb', line 44 def visit_atom(atom) parts = [] parts << (":" * atom.lone_pairs) if atom.lone_pairs parts << "^#{atom.isotope}" if atom.isotope parts << atom.element parts << "_#{atom.subscript}" if atom.subscript parts << "^#{atom.superscript}" if atom.superscript parts << "^#{atom.charge}" if atom.charge parts << "^(#{atom.oxidation_state})" if atom.oxidation_state parts << ("." * atom.radical_electrons) if atom.radical_electrons parts << atom.ring_closures.to_s if atom.ring_closures parts << atom_annotation(atom) parts.join end |
#visit_bond(bond) ⇒ Object
85 86 87 |
# File 'lib/asciichem/formatter/text.rb', line 85 def visit_bond(bond) bond.ascii end |
#visit_calculation(calc) ⇒ Object
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/asciichem/formatter/text.rb', line 161 def visit_calculation(calc) parts = ["calc"] params = [] params << calc.method if calc.method params << calc.basis if calc.basis parts << "(#{params.join('/')})" unless params.empty? unless calc.properties.empty? lines = calc.properties.map do |p| line = "#{p[:title]}: #{p[:value]}" line += " #{p[:units]}" if p[:units] line end parts << "{\n #{lines.join("\n ")}\n}" end parts.join end |
#visit_crystal(crystal) ⇒ Object
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/asciichem/formatter/text.rb', line 126 def visit_crystal(crystal) parts = ["crystal"] parts << "[#{crystal.name}]" if crystal.name params = [] params << "a=#{crystal.a}" if crystal.a params << "b=#{crystal.b}" if crystal.b params << "c=#{crystal.c}" if crystal.c params << "alpha=#{crystal.alpha}" if crystal.alpha params << "beta=#{crystal.beta}" if crystal.beta params << "gamma=#{crystal.gamma}" if crystal.gamma params << "sg=#{crystal.spacegroup}" if crystal.spacegroup parts << "(#{params.join(',')})" unless params.empty? atom_strs = crystal.atoms.map { |a| render_node(a) } parts << "{#{atom_strs.join(' ')}}" unless atom_strs.empty? parts.join end |
#visit_electron_configuration(ec) ⇒ Object
112 113 114 115 116 |
# File 'lib/asciichem/formatter/text.rb', line 112 def visit_electron_configuration(ec) parts = ec.orbitals.map { |orb, occ| "#{orb}^#{occ}" } parts << ec.term_symbol.to_s if ec.term_symbol parts.join(" ") end |
#visit_embedded_math(em) ⇒ Object
118 119 120 |
# File 'lib/asciichem/formatter/text.rb', line 118 def (em) "`#{em.source}`" end |
#visit_formula(formula) ⇒ Object
20 21 22 |
# File 'lib/asciichem/formatter/text.rb', line 20 def visit_formula(formula) formula.nodes.map { |n| render_node(n) }.join(" ") end |
#visit_group(group) ⇒ Object
79 80 81 82 83 |
# File 'lib/asciichem/formatter/text.rb', line 79 def visit_group(group) body = group.nodes.map { |n| render_node(n) }.join suffix = group.multiplicity ? "_#{group.multiplicity}" : "" "#{group.open_char}#{body}#{group.close_char}#{suffix}" end |
#visit_mechanism(mech) ⇒ Object
193 194 195 196 197 198 199 200 201 |
# File 'lib/asciichem/formatter/text.rb', line 193 def visit_mechanism(mech) parts = ["mechanism"] unless mech.steps.empty? && mech.spectators.empty? lines = mech.steps.map { |s| "#{s[:label]}: #{s[:reaction]}" } mech.spectators.each { |sp| lines << "spectator: #{sp}" } parts << "{\n #{lines.join("\n ")}\n}" end parts.join end |
#visit_molecule(molecule) ⇒ Object
24 25 26 27 28 29 30 |
# File 'lib/asciichem/formatter/text.rb', line 24 def visit_molecule(molecule) prefix = molecule.coefficient.nil? || molecule.coefficient.empty? ? "" : molecule.coefficient.to_s stereo = molecule.stereo ? "(#{molecule.stereo_letter})-" : "" body = molecule.nodes.map { |n| render_node(n) }.join annotations = molecule_annotations(molecule) "#{stereo}#{prefix}#{body}#{annotations}" end |
#visit_reaction(reaction) ⇒ Object
89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/asciichem/formatter/text.rb', line 89 def visit_reaction(reaction) left = reaction.reactants.map { |n| render_node(n) }.join(" + ") right = reaction.products.map { |n| render_node(n) }.join(" + ") arrow = reaction.arrow_ascii conds = reaction.conditions return "#{left} #{arrow} #{right}" unless conds above = conds.above ? "[#{conds.above}]" : "" below = conds.below ? "[#{conds.below}]" : "" "#{left} #{arrow}#{above}#{below} #{right}" end |
#visit_reaction_cascade(cascade) ⇒ Object
101 102 103 104 105 106 107 108 109 110 |
# File 'lib/asciichem/formatter/text.rb', line 101 def visit_reaction_cascade(cascade) return "" if cascade.steps.empty? head = cascade.steps.first out = "#{render_terms(head.reactants)} #{render_arrow_with_conditions(head)} #{render_terms(head.products)}" cascade.steps.drop(1).each do |step| out += " #{render_arrow_with_conditions(step)} #{render_terms(step.products)}" end out end |
#visit_spectrum(spectrum) ⇒ Object
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/asciichem/formatter/text.rb', line 143 def visit_spectrum(spectrum) parts = ["spectrum"] parts << "[#{spectrum.type}]" if spectrum.type params = spectrum.params.map { |k, v| "#{k}=#{v}" }.join(',') parts << "(#{params})" unless params.empty? peak_lines = spectrum.peaks.map do |peak| line = "#{peak[:position]}: #{peak[:intensity]}" line += " #{peak[:multiplicity]}" if peak[:multiplicity] line += %( "#{peak[:assignment]}") if peak[:assignment] line end unless peak_lines.empty? body = peak_lines.join("\n ") parts << "{\n #{body}\n}" end parts.join end |
#visit_text(text) ⇒ Object
122 123 124 |
# File 'lib/asciichem/formatter/text.rb', line 122 def visit_text(text) %("#{text.content}") end |
#visit_z_matrix(zm) ⇒ Object
178 179 180 181 182 183 184 185 186 187 188 189 190 191 |
# File 'lib/asciichem/formatter/text.rb', line 178 def visit_z_matrix(zm) parts = ["zmatrix"] unless zm.rows.empty? lines = zm.rows.map do |row| tokens = [row.atom] tokens << row.ref1 << row.distance if row.ref1 tokens << row.ref2 << row.angle if row.ref2 tokens << row.ref3 << row.dihedral if row.ref3 tokens.compact.join(" ") end parts << "{\n #{lines.join("\n ")}\n}" end parts.join end |