Project

sts

0.0
The project is in a healthy, maintained state
Library to work with NISO STS and ISOSTS.
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
 Dependencies

Runtime

~> 0.8.18
~> 2.4.0
 Project Readme

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

<article>, <book>

<standard>

Metadata

article-meta, journal-meta

iso-meta, reg-meta, nat-meta

MathML version

3+ (via mml gem)

2 (custom Ruby implementation)

Math elements

mml:math + tex-math

mml:math only

Terminology

Inline TBX elements

term-sec with tbx:termEntry

Standards references

Mixed in ref-list

Dedicated std/std-ref elements

Module tree

Sts::NisoSts::*

Sts::IsoSts::*

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

NISO 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-tripped

ISOSTS 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 (includes term-sec for 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"
end

TBX 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)

sec

sec_

sec-

term-sec

sec_

con-

fig

fig_

fig-

table-wrap

tab_

tab-

disp-formula

formula_

for-

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.