Ruby library for NISO STS and ISOSTS
Purpose
The sts Ruby gem allows you to work with NISO STS and ISOSTS documents — two related XML formats for technical standards documents.
-
NISO STS (NISO Z39.96) — The standardized NISO format for journal and article publishing
-
ISOSTS — The internal precursor format to NISO STS, used by ISO before NISO standardization. ISOSTS adopts TBX (ISO 30042) for terminology markup.
|
Note
|
This is a work-in-progress. |
Formats Compared
| Aspect | NISO STS | ISOSTS |
|---|---|---|
Standard |
NISO Z39.96 (NISO standard) |
Internal format (pre-NISO standardization) |
History |
Standardized version |
Original internal format, predates NISO STS |
Root element |
|
|
Metadata |
|
|
MathML version |
3+ (via mml gem) |
2 (custom Ruby implementation) |
Math elements |
|
|
Terminology |
Inline TBX elements |
|
Standards references |
Mixed in |
Dedicated |
Module tree |
|
|
Despite sharing the same JATS ancestry, the two formats have incompatible content models. Elements with the same XML name (e.g., p, sec, disp-formula) may have different permitted children and attributes. They are implemented as completely separate Ruby class trees.
Library
Usage: NISO STS
The following code parses a NISO STS XML document.
require 'sts'
doc = IO.read('spec/fixtures/tbx-nisosts-0.2.xml')
sts = Sts::NisoSts::Standard.from_xml(doc)
puts sts.to_xml(pretty: true)
# => NISO STS file round-trippedNISO STS API
# Parse
sts = Sts::NisoSts::Standard.from_xml(xml_string)
# Access elements
sts.front.article_meta.title_wrap.main
sts.body.sec.first.label
# Serialize back
sts.to_xml
sts.to_xml(pretty: true)
# Check document type
sts.class.name # => "Sts::NisoSts::Standard"NISO STS root classes
-
Sts::NisoSts::Standard— Book or article root -
Sts::NisoSts::Front— Front matter (article-meta,journal-meta) -
Sts::NisoSts::Body— Document body -
Sts::NisoSts::Back— Back matter (references, footnotes)
Usage: ISOSTS
ISOSTS uses <standard> as the root element and includes MathML 2 for formulae. It adopts TBX (ISO 30042) for terminology sections.
require 'sts'
doc = IO.read('spec/fixtures/iso_sts/feature_doc.xml')
sts = Sts::IsoSts::Standard.from_xml(doc)
puts sts.to_xml(pretty: true)
# => ISOSTS file round-trippedISOSTS API
# Parse
sts = Sts::IsoSts::Standard.from_xml(xml_string)
# Access elements
sts.front.iso_meta.title_wrap.main
sts.front.iso_meta.std_ident.doc_number
sts.body.term_sec.first.term_entry.lang_set.first.term
# Serialize back
sts.to_xml
sts.to_xml(pretty: true)
# Check document type
sts.class.name # => "Sts::IsoSts::Standard"ISOSTS root classes
-
Sts::IsoSts::Standard— Standard document root (<standard>) -
Sts::IsoSts::Front— Front matter (iso-meta,reg-meta,nat-meta) -
Sts::IsoSts::Body— Document body (includesterm-secfor terminology) -
Sts::IsoSts::Back— Back matter (annexes, references, footnotes)
ISOSTS MathML 2
ISOSTS uses MathML 2 (not MathML 3 like NISO STS). All MathML is provided by the mml gem, bound per host:
-
IsoSts::*math attributes →Mml::V2::Math -
TbxIsoTml::*math attributes →Mml::V2::Math(TBX-in-ISOSTS only) -
NisoSts::*math attributes →Mml::V3::Math
disp_formula = sts.body.sec.first.disp_formula
math = disp_formula.math # => Mml::V2::Math
# Round-trip preserves mml: prefix
disp_formula.to_xml # => <disp-formula><mml:math ...>...</mml:math></disp-formula>MathML 2 and 3 share the same XML namespace (http://www.w3.org/1998/Math/MathML),
so Mml::Namespace (the mml gem’s top-level namespace class) is used
directly in both IsoSts::Standard#namespace_scope and
NisoSts::Standard#namespace_scope.
ISOSTS TBX Terminology
ISOSTS uses TBX (TermBase eXchange, ISO 30042) for terminology sections via term-sec:
# Access terminology entries
term_sec = sts.body.term_sec.first
term_entry = term_sec.term_entry
term_entry.lang_set.each do |lang_set|
puts lang_set.xml_lang # => "en"
puts lang_set.tig.first.term # => "medical device"
endTBX elements reuse Sts::TbxIsoTml::* classes.
TBX Integration
Both NISO STS and ISOSTS support TBX (TermBase eXchange, ISO 30042) for terminology markup. The TBX elements are in the Sts::TbxIsoTml module:
require 'sts'
# NISO STS with TBX
sts = Sts::NisoSts::Standard.from_xml(xml_with_tbx)
# ISOSTS with TBX
sts = Sts::IsoSts::Standard.from_xml(xml_with_tbx)ISO/IEC usage profile
ISO/IEC NISO STS is not a separate schema. It is a usage profile — a constraint layer on top of NISO STS covering which elements are required, which attribute values are allowed, and the conventions ISO and IEC follow.
So profiles do not change parsing. Parse the document first, then validate it against a profile:
require 'sts'
doc = Sts::NisoSts::Standard.from_xml(xml)
errors = Sts::Profiles.validator(:iso_iec).validate(doc)
# => ["ISO/IEC profile requires <body>"]validate returns an array of message strings, empty when the document satisfies the profile. It checks document structure and <std-meta>/<std-ident> metadata. The validator targets NISO STS documents (Sts::NisoSts::Standard).
Checking the semantic ID scheme
Annex F of the NISO STS 1.2 IEC/ISO Coding Guidelines tabulates two ID schemes, one for ISO and one for IEC. Pass id_scheme to check ids against one of them:
Sts::Profiles.validator(:iso_iec, id_scheme: :iso).validate(doc)
# => ["<fig> id 'fig-1' does not follow the iso ID scheme (expected prefix 'fig_')"]| Element | ISO (Annex F.2) | IEC (Annex F.1) |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
This check is off by default, and deliberately so. Annex F warns that "IDs generated in OSD are arbitrary" and that it is "not recommended to base any exploitation of ISO or IEC XML on semantic IDs". The schemes describe legacy content and the traditional publication chain, not a rule every document follows. Real documents bear this out — ISO_13849-1_2008-12_en_TBX.xml matches the IEC scheme for figures, tables and formulae but uses a sub- prefix for sections. Turn the check on only when you know your documents come from a chain that uses semantic ids.
Elements outside the table are never checked. Passing an unknown profile raises Sts::Profiles::UnknownProfileError; an unknown id_scheme raises ArgumentError.
Architecture principles
Namespace independence
IsoSts, NisoSts, and TbxIsoTml are independent schemas that
genuinely diverge. Each Sts::<Namespace>::<Class> is modelled from its
own schema source — never aliased across namespaces, never sharing base
classes. The schemas disagree on most elements (e.g., IsoSts::Fig has
title/alternatives that NisoSts::Figure lacks), so sharing types
would fight the schemas and violate the Open/Closed principle.
Anti-pattern enforcement
spec/anti_patterns_spec.rb runs 9 per-file checks across lib/ to
forbid: method_missing, respond_to_missing?, Object.const_get,
.send(, instance_variable_set/get, respond_to? type-checks,
hand-rolled serialization methods on Serializable subclasses
(to_h, to_xml, from_xml, etc.), require_relative, and
internal require. See CLAUDE.md for the rationale.
Schema validation
spec/schema_validation_spec.rb validates IsoSts output against the
canonical reference-docs/isosts-v1/xsd/ISOSTS.xsd via
Nokogiri::XML::Schema. Round-tripping alone cannot prove schema
conformance (a model that invents or drops an attribute still
round-trips symmetrically); the XSD is the only authoritative check.
Autoload convention
lib/sts.rb autoloads top-level namespaces. Each namespace’s
lib/sts/<namespace>.rb autoloads every class in alphabetical order
within category groups. External gems (lutaml/model, mml) are
eager-required at the top of lib/sts.rb; internal code uses autoload
only — no require_relative, no require "sts/…".
Credits
This gem is developed, maintained and funded by Ribose Inc.
License
The gem is available as open source under the terms of the 2-Clause BSD License.