Class: AsciiChem::Formatter::Text

Inherits:
Base
  • Object
show all
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, not H2.
  • 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

Methods inherited from Base

#render

Instance Method Details

#atom_annotation(atom) ⇒ Object



59
60
61
62
63
64
65
66
67
68
69
# File 'lib/asciichem/formatter/text.rb', line 59

def atom_annotation(atom)
  annotation = +""
  if atom.x2 && atom.y2
    annotation << "@(#{format_coord(atom.x2)},#{format_coord(atom.y2)}"
    annotation << ",#{format_coord(atom.z2)}" if atom.z2
    annotation << ")"
  elsif atom.atom_parity
    annotation << "@#{atom.atom_parity}"
  end
  annotation
end

#format_coord(value) ⇒ Object



71
72
73
# File 'lib/asciichem/formatter/text.rb', line 71

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

#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



81
82
83
# File 'lib/asciichem/formatter/text.rb', line 81

def visit_bond(bond)
  bond.ascii
end

#visit_electron_configuration(ec) ⇒ Object



108
109
110
111
112
# File 'lib/asciichem/formatter/text.rb', line 108

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



114
115
116
# File 'lib/asciichem/formatter/text.rb', line 114

def visit_embedded_math(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



75
76
77
78
79
# File 'lib/asciichem/formatter/text.rb', line 75

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_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



85
86
87
88
89
90
91
92
93
94
95
# File 'lib/asciichem/formatter/text.rb', line 85

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



97
98
99
100
101
102
103
104
105
106
# File 'lib/asciichem/formatter/text.rb', line 97

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_text(text) ⇒ Object



118
119
120
# File 'lib/asciichem/formatter/text.rb', line 118

def visit_text(text)
  %("#{text.content}")
end