Class: Pubid::Parsers::MrString
- Defined in:
- lib/pubid/parsers/mr_string.rb
Overview
Parses a machine-readable (MR) identifier string back into an Identifier.
The MR format produced by Renderers::MrString mirrors the human form,
lowercased and filename-safe:
iso.9001.2015 → ISO 9001:2015
iso-iec.17031-1.2020 → ISO/IEC 17031-1:2020
iso.1234-1-2-3.2020 → ISO 1234-1-2-3:2020
iso.tr.14627.2017 → ISO/TR 14627:2017
iso.16634.-- → ISO 16634:--
iso.9001.2015_amd.1.2020 → ISO 9001:2015/Amd 1:2020
iso-iec.13818-1.2015_amd.3.2016_cor.1.2017 → ISO/IEC 13818-1:2015/Amd 3:2016/Cor 1:2017
Copublishers join with - (iso-iec), supplements append with _
(iso.9001.2015_amd.1.2020). Both round-trip back to / here. The
flavor's own parser is case-insensitive on the type/publisher tokens
we emit, so the lowercased slug re-parses cleanly.
Note: NIST, CSA, IEEE, JIS, and SAE emit their own MR shapes that this
parser does not attempt to reverse (their to_s already round-trips
through the flavor's own parser, so callers should use that path for
those flavors).
Constant Summary collapse
- FLAVOR_MAP =
{ "ISO" => :iso, "IEC" => :iec, "IEEE" => :ieee, "NIST" => :nist, "NBS" => :nist, "BS" => :bsi, "CEN" => :cen_cenelec, "JIS" => :jis, "ETSI" => :etsi, "ITU" => :itu, "ANSI" => :ansi, "OIML" => :oiml, "CIE" => :cie, "ASHRAE" => :ashrae, "AMCA" => :amca, "IDF" => :idf, "IHO" => :iho, "JCGM" => :jcgm, "SAE" => :sae, "ASME" => :asme, "CSA" => :csa, "API" => :api, "ASTM" => :astm, "PLATEAU" => :plateau, "CCSDS" => :ccsds, }.freeze
- FLAVOR_CONSTANT_MAP =
Map flavor symbols to Pubid autoload constants
{ iso: :Iso, iec: :Iec, ieee: :Ieee, nist: :Nist, bsi: :Bsi, cen_cenelec: :CenCenelec, jis: :Jis, etsi: :Etsi, itu: :Itu, ansi: :Ansi, oiml: :Oiml, cie: :Cie, ashrae: :Ashrae, amca: :Amca, idf: :Idf, iho: :Iho, jcgm: :Jcgm, sae: :Sae, asme: :Asme, csa: :Csa, api: :Api, astm: :Astm, plateau: :Plateau, ccsds: :Ccsds, }.freeze
- TYPE_CODES =
Lower-case type codes the renderer emits as a separate MR segment (e.g.
iso.tr.14627). When the parser sees one of these in the second position, it splits it back out as/TYPE. %w[ tr ts pas is amd cor suppl add ext guide spec standard corrigendum amendment ].freeze
Class Method Summary collapse
-
.convert_head(head) ⇒ Object
Converts a head segment like
iso-iec.13818-1.2015toISO/IEC 13818-1:2015. -
.convert_supplement(supp) ⇒ Object
Converts a supplement segment like
amd.1.2020toAmd 1:2020. -
.convert_to_human_readable(mr_string) ⇒ Object
Convert the MR slug back to a human-readable identifier string the flavor's own parser will accept.
- .detect_flavor(mr_string) ⇒ Object
- .parse(mr_string) ⇒ Object
Class Method Details
.convert_head(head) ⇒ Object
Converts a head segment like iso-iec.13818-1.2015 to
ISO/IEC 13818-1:2015.
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/pubid/parsers/mr_string.rb', line 129 def self.convert_head(head) parts = head.split(".") return parts.join(" ").upcase if parts.length <= 1 publisher, *rest = parts # Optional type code segment (iso.tr.14627.2017 → ISO/TR 14627:2017). type_segment = nil if rest.first && TYPE_CODES.include?(rest.first.downcase) type_segment = rest.shift.upcase end # Trailing language segment — bare language code(s) with no parens. # Detect by length <= 5 and all-alpha (en, fr, en-fr, ru). language_segment = nil if rest.last&.match?(/\A[a-z]{2}(-[a-z]{2})*\z/) language_segment = rest.pop end # Trailing year segment (may be `--` for undated, or YYYY[-MM[-DD]]). year_segment = nil if rest.any? && rest.last.match?(/\A(?:\d{4}(?:-\d{2}){0,2}|--)\z/) year_segment = rest.pop end # Copublishers in the first segment: `iso-iec` → `ISO/IEC`. Multi-word # publisher bodies (e.g. `cen-cenelec`) round-trip the same way. publisher_part = publisher.split("-").map(&:upcase).join("/") publisher_part = "#{publisher_part}/#{type_segment}" if type_segment number_part = rest.join(" ") result = publisher_part result += " #{number_part}" unless number_part.empty? result += ":#{year_segment}" if year_segment if language_segment langs = language_segment.split("-").join(",") result += "(#{langs})" end result end |
.convert_supplement(supp) ⇒ Object
Converts a supplement segment like amd.1.2020 to Amd 1:2020.
The first token is the lowercase type code; remaining tokens are
number and optional year.
173 174 175 176 177 178 179 180 181 182 183 |
# File 'lib/pubid/parsers/mr_string.rb', line 173 def self.convert_supplement(supp) tokens = supp.split(".") type = tokens.shift&.upcase year = tokens.last&.match?(/\A\d{4}\z/) ? tokens.pop : nil number = tokens.join(" ") result = type.to_s result += " #{number}" unless number.empty? result += ":#{year}" if year result end |
.convert_to_human_readable(mr_string) ⇒ Object
Convert the MR slug back to a human-readable identifier string the
flavor's own parser will accept. Splits on _ first so each segment
(head + each supplement) is converted independently, then re-joined
with /. Within the head, the copublisher separator - (first
segment only) is restored to /.
117 118 119 120 121 122 123 124 125 |
# File 'lib/pubid/parsers/mr_string.rb', line 117 def self.convert_to_human_readable(mr_string) head, *supplements = mr_string.split("_") human = convert_head(head) return human if supplements.empty? supplements.inject(human) do |acc, supp| "#{acc}/#{convert_supplement(supp)}" end end |
.detect_flavor(mr_string) ⇒ Object
107 108 109 110 |
# File 'lib/pubid/parsers/mr_string.rb', line 107 def self.detect_flavor(mr_string) publisher = mr_string.split(/[._-]/).first.to_s.upcase FLAVOR_MAP[publisher] || :iso end |
.parse(mr_string) ⇒ Object
92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/pubid/parsers/mr_string.rb', line 92 def self.parse(mr_string) flavor = detect_flavor(mr_string) # Trigger autoload to register the flavor if (const_name = FLAVOR_CONSTANT_MAP[flavor]) Pubid.const_get(const_name) end flavor_module = Pubid::Registry.get(flavor) raise ArgumentError, "Unknown flavor: #{flavor}" unless flavor_module identifier_string = convert_to_human_readable(mr_string) flavor_module.parse(identifier_string) end |