This May Not Be A Good Idea
A well-known software provider asked me to help implement the ATA iSpec 2200 aerospace standard [ATA iSpec 2200] for their client. I warned them that SGML is outdated and poorly supported by their own software—I have plenty of experience trying to implement both SGML and XML for it[1]. They insisted their SGML support was fixed, so I accepted the contract, though I recommended authoring in XML and only publishing to SGML.
Testing proved otherwise and I found numerous issues. Crucially, while the latest build had some functionality, the client’s older version failed entirely.
Then I got to meet the client.
It quickly emerged that ATA is only one of the many SGML and XML markup vocabularies they use. There is the SGML-based standard for technical manuals, MIL-STD-38784 [MIL-STD-38784], but also several flavours of the aerospace standard S1000D [S1000D], some SGML (issues 1.8, and 2.3) and some XML (the 4.x range , of which issue 4.1 for authoring was a must).
Note
The aerospace industry is very, very conservative. Keeping planes airborne is the priority, making system upgrades a secondary concern to data consistency[2]. Aerospace manufacturing is largely a collaborative process, involving hundreds or even thousands of organisations and, sometimes, multiple nations working together[3] to produce aircraft. To collaborate, the partners need to agree to use specific versions of the standards; the information is shared and reused by multiple organisations.
Being a mere contractor, enlisted to help out with implementing SGML, I'll admit that all this felt rather uncomfortable. SGML wouldn't work, but neither would proposing to move everything to XML. A middle ground was needed.
The Middle Ground
XML is the better choice today, of course. It's not because XML would somehow be better than SGML, whatever that means[4], but it's important to note that there's a lot of support for XML nowadays, be it tools, processes or related standards, but very, very little for SGML. XML may not be the hottest new thing anymore, but it's mature and literally all over the place. SGML, on the other hand, is literally what XML replaced. The infrastructure, the standardisation, the tools, it all happened for XML[5].
I've sold in XML in place of SGML for aerospace before, the idea always being to convert the SGML to XML, author everything in XML, and use XML technologies to publish to all required output formats, including SGML. For an ATA iSpec 2200 project I did in the past [Topic-based SGML? Really?], we had something like this:
Figure 1: ATA SGML to XML Conversion and Publishing

Of course, now I also needed to support MIL-STD-38784 and S1000D.
Looking closer at MIL-STD-38784, I found that the US Government had already done some
of my work for me. There was an XML version of the DTD from around 2005, so rather
than
spending time writing a new XML MIL-SPEC-like
DTD, I could use
theirs:
Figure 2: MIL-STD-38784 SGML to XML

The S1000D Issue 1.8 SGML DTDs (6 of them) evolved into XML Schemas from issue 3.0
and
onwards, so I resolved to use issue 4.1 XML, now 26 XML Schemas and a host of new
features. Importantly, issue 4.1 includes a way to define business
rules (the BREX
data module type) declaring how the
standard is to be used. In the case of converting 1.8 SGML to 4.1 XML, then, I could
define business rules that limit the use of 4.1 to features also available in
1.8.:
Figure 3: S1000D 1.8 SGML to 4.1 XML

Of course, the 4.1 XML would be published to any required format, including issue 1.8 SGML.
Rather than creating yet another one-off, I resolved to write a more generic library, extensible to other SGML vocabularies, and make it freely available to anyone still needing to convert SGML to XML. The results are on GitHub [SGML to XML TNG].
SGML to XML
Common to all three is that they all need to convert SGML to well-formed XML; we always need to handle the following:
-
DTDs; we'll need to know about things like tag omission[6], empty elements, default attribute values, etc.
Note
Tag omission, as indicated in the DTD, might frequently leave out end tags.
-
Entities (graphic, reusable text fragments, character sets, etc)
-
Syntax
There's no such thing as a well-formed SGML file; they must all reference a DTD[7] using a DOCTYPE declaration. The DOCTYPE will
usually have an internal subset
that contains entity declarations for
graphics and reusable text, but it can also include part of, or the entire DTD.
This is important, because when we process SGML, we'll need to use James Clark's SP
package [SP - An SGML System Conforming to International Standard ISO 8879 -- Standard Generalized
Markup Language]—SP is
the only game in town, really, but includes everything we need, from parsing
(nsgmls) and normalisation (spam) to SGML to XML syntax
conversion (sx). SP is all about parsing and processing instances, however,
so we need to normalise the SGML—pull the DTD and everything else into a single
file—before we can convert it to XML syntax.
In addition, an SGML document will use an SGML declaration[8], a text file that configures the SGML as used by the instances. It'll commonly define things like character sets to be used, attribute literal lengths, and whether or not we care about case for tag names, but there's a lot more. For example, we may redefine the tag limiters to be used.
That's pretty much where I drew the line; whilst case sensitivity (or the lack of it) is important, I don't intend to support curly-bracket tag limiters just yet. And there are other features that give me pause.
SGML Processing
Let's assume we have this rather trivial SGML DTD:
<!-- NOTATIONS -->
<!NOTATION cgm PUBLIC
"-//USA-DOD//NOTATION Computer Graphics Metafile//EN" >
<!NOTATION ccitt4 PUBLIC
"-//USA-DOD//NOTATION CCITT Group4 Facsimile//EN" >
<!NOTATION gcf PUBLIC
"-//ATA-TEXT//NOTATION Graphic Companion File//EN" >
<!NOTATION pdf PUBLIC
"-//Adobe Inc.//NOTATION Portable Document Format//EN" >
<!NOTATION png PUBLIC
"-//W3C//NOTATION Portable Network Graphics//EN" >
<!NOTATION jpeg PUBLIC
"+//ISBN 0-7923-9432-1::Graphic Notation//NOTATION Joint
Photographic Experts Group Raster//EN" >
<!NOTATION vrml PUBLIC
"-//ATA-TEXT//NOTATION Virtual Reality Modeling Language//EN" >
<!NOTATION mpeg PUBLIC
"-//ATA-TEXT//NOTATION Motion Pictures Expert Group//EN" >
<!NOTATION mp3 PUBLIC
"-//ATA-TEXT//NOTATION Motion Pictures Expert Group, Audio
Layer 3//EN" >
<!-- ENTITIES -->
<!ENTITY % id.att "id ID #IMPLIED" >
<!ELEMENT doc - - (title, chapter+) >
<!ATTLIST doc %id.att; >
<!ELEMENT title - - (#PCDATA) >
<!ELEMENT chapter - - (title, (para | graphic)+, section*) >
<!ATTLIST chapter %id.att; >
<!ELEMENT section - - (title, (para | graphic)+, section*) >
<!ATTLIST section %id.att; >
<!ELEMENT para - - (#PCDATA) >
<!ELEMENT graphic - O EMPTY >
<!ATTLIST graphic href CDATA #IMPLIED
name ENTITY #IMPLIED
%id.att; >
Note
The graphic element has two ways of referencing a graphic, an
ENTITY-based using @name and a direct link using
@href. Before you protest, remember that this DTD is just for
testing (it's part of my repo for that purpose—see SGML to XML TNG).
A valid SGML instance might look like this:
<!DOCTYPE doc PUBLIC "-//CWORD//DTD DOC TEST//EN" "doc.dtd" [
<!ENTITY bill SYSTEM "Bill_Murray.jpg" NDATA jpeg >
]>
<doc>
<title>My Test SGML Document</title>
<chapter>
<title>Chapter 1 Title</title>
<para>Some content.</para>
<graphic href="Bill_Murray.jpg">
</chapter>
<chapter>
<title>Chapter 2 Title</title>
<para>More content.</para>
<section>
<title>Subsection 1 Title</title>
<para>Subsection content.</para>
<graphic name="bill">
</section>
</chapter>
</doc>
This is assumed to use Unicode, but most real-life SGML DTDs will reference external ISO character entity sets since the DTDs were usually conceived before Unicode. For example, ATA DTDs include something along these lines[9]:
<!ENTITY % ISOtech PUBLIC "ISO 8879-1986//ENTITIES General Technical//EN" > <!ENTITY % ISOpub PUBLIC "ISO 8879-1986//ENTITIES Publishing//EN" > <!ENTITY % ISOnum PUBLIC "ISO 8879-1986//ENTITIES Numeric and Special Graphic//EN" > <!ENTITY % ISOgrk1 PUBLIC "ISO 8879-1986//ENTITIES Greek Letters//EN" > <!ENTITY % ISOgrk2 PUBLIC "ISO 8879-1986//ENTITIES Monotoniko Greek//EN" > <!ENTITY % ISOgrk3 PUBLIC "ISO 8879-1986//ENTITIES Greek Symbols//EN" > <!ENTITY % ISOgrk4 PUBLIC "ISO 8879-1986//ENTITIES Alternative Greek Symbols//EN" > <!ENTITY % ISOlat1 PUBLIC "ISO 8879-1986//ENTITIES Added Latin 1//EN" > <!ENTITY % ISObox PUBLIC "ISO 8879-1986//ENTITIES Box and Line Drawing//EN" > <!ENTITY % ISOamso PUBLIC "ISO 8879-1986//ENTITIES Added Math Symbols: Ordinary//EN" > <!ENTITY % ISOamsc PUBLIC "ISO 8879-1986//ENTITIES Added Math Symbols: Delimiters//EN" > %ISOtech; %ISOpub; %ISOnum; %ISOgrk1; %ISOgrk2; %ISOgrk3; %ISOgrk4; %ISOlat1; %ISObox; %ISOamso; %ISOamsc;
An entity like — would have a corresponding entity
declaration:
<!ENTITY mdash SDATA "[mdash ]"--=em dash-->
The SGML processing, all of which is in an Ant build file, involves the following:
-
Preprocessing specific to the SGML at hand. When I need to do this, it's usually about removing weirdly placed newlines that cause havoc later on if left alone.
-
Normalisation with James Clark's
spamapplication from the SP package [SP - An SGML System Conforming to International Standard ISO 8879 -- Standard Generalized Markup Language]. This is all about bringing in the entire DTD into the internal subset, warts and all[10], because otherwisesxwill have trouble with some markup constructs in the next step. -
Convert the normalised SGML into XML syntax using
sx[SP - An SGML System Conforming to International Standard ISO 8879 -- Standard Generalized Markup Language]. Unfortunately,sxgets rid of the DTD after processing, leaving behind the bare minimum required to resolve graphic entities, e.g.:<!DOCTYPE doc [ <!NOTATION jpeg SYSTEM ""> <!ENTITY bill SYSTEM "/home/ari/Documents/projects/tng-sgml2xml/doc/data/Bill_Murray.jpg" NDATA jpeg> ]>
For Unicode content, this is fine, but anything with character entities will have problems[11].
-
For anything NOT Unicode, we need to insert the missing entity declarations and references to the internal subset, but rather than using SGML-only
SDATAentities, they need to reference the Unicode characters:<!ENTITY mdash "—"> <!-- EM DASH -->
Since they won't harm anything if not used, I choose to always insert a standard set. We get this:
<!DOCTYPE doc [<!-- This maps SGML character entities to their Unicode equivalents --> <!-- Based on DocBook 4.1.2 XML entities --><!-- ISO 8879 official entity sets --> <!ENTITY % iso-amsa PUBLIC "ISO 8879:1986//ENTITIES Added Math Symbols: Arrow Relations//EN" "/home/ari/Documents/repos/sgml2xml-tng/modules/common/schemas/sgml/xml-entities/iso-amsa.ent"> %iso-amsa; <!ENTITY % iso-amsb PUBLIC "ISO 8879:1986//ENTITIES Added Math Symbols: Binary Operators//EN" "/home/ari/Documents/repos/sgml2xml-tng/modules/common/schemas/sgml/xml-entities/iso-amsb.ent"> %iso-amsb; <!– (etc) --> <!ENTITY bill SYSTEM "/home/ari/Documents/projects/tng-sgml2xml/doc/data/Bill_Murray.jpg" NDATA jpeg> ]>
We should now have well-formed XML.
XML Processing
The well-formed XML needs to be converted to whatever the end vocabulary (DTD, schema...) requires. Whilst the specifics will differ—certainly for the three formats, above—the principle is the same: tweak the XML as necessary to produce valid XML. This involves running a series of XSLT stylesheets, different for each vocabulary, but the pipeline mechanism is the same.
A first step is usually to encode the XML in UTF-8 and get rid of the entity declarations. Another is often to convert any entity-based graphics to direct references; XML processing can do a lot, but entity queries and manipulation remain an absolute pain[12].
The same Ant build file that converts the SGML to XML calls an XProc pipeline that runs a manifest file listing the required XSLT stylesheets in the order in which they run.
The sgml2xml-tng Repo
The SGML-to-XML conversion repository [SGML to XML TNG] is organised like this:
Figure 4: SGML to XML TNG

-
modules/contains the resources for each input SGML vocabulary.The modules here include
ata/,s1000d/, andmilspec/. Each of these contain the SGML and XML DTDs (and schemas), plus any pipelines, XSLT, and other resources required. There's also an example moduledoc/to provide a runnable example.And finally,
common/contains resources common to the modules, includingSDATAentities (SGML), Unicode entities (XML), common XSLT, etc. -
lib/contains the SP package for all things SGML, plus the MorganaXProc-IIIse XProc 3.x engine [MorganaXProc-III] for XML conversions and the SchXslt 2 Schematron to XSLT transpiler [SchXslt2] for Schematron validation. There's also the XProc Batch library [XProc Batch] to run pipelined XSLT transforms. -
catalogs/contains catalog files. -
ant/contains Ant macrodefs for the main Ant script. -
Ant build files (
build_sgml2xml.xml,build_sgml2xml.properties.xml, and a local properties file not shown here) to run the main conversion. The build file contains the main logic.
Here's the idea:
-
build_sgml2xml.xmlcontains all the steps required to process the SGML (see section “SGML Processing”) and the resulting (well-formed) XML (see section “XML Processing”). As a reminder, we do preprocess, normalise, convert to XML syntax, insert character entity declarations to the internal subset, encode to UTF-8, and run the XML pipeline.The build is configured in an Ant local property file (a tweak of
build_sgml2xml.properties.xml, namedbuild_sgml2xml.properties.local.xml). -
The SGML vocabularies are all modules. Each requires source DTDs, target DTDs/schemas, and any processing, including XSLT and XProc.
The repo [SGML to XML TNG] contains a rather trivial example, in the
doc/folder.If you want to add an SGML vocabulary (i.e., another module, check out the included examples. You may be able to use the common entity sets, but then again, you may not. The code makes the rather optimistic assumption that you can, in fact, reuse what is there now but will not force you to do so.
-
The initial well-formed XML is intended to go through a series of XSLT-based steps, transforming the XML into some valid vocabulary. If this is all that is needed, a new module will only require you to write the XSLT steps and list them in an XML manifest file similar to the existing ones.
If you need more, you can copy and edit, or fully replace, the default XProc pipelines with your own to do whatever might be missing.
The step outputs can all be saved to help debugging:
doc/
├── data
│ └── (SGML and graphics input)
└── tmp
└── 20260311133633
├── debug
│ └── (XProc XSLT pipeline debug output)
├── out
│ └── (valid XML and graphics)
├── pre
│ └── (preprocessed SGML)
├── reports
│ └── (reports)
├── spam
│ └── (spam output)
├── sx
│ └── (sx output)
└── xml
└── (well-formed XML)
The Ant build also provides an optional nsgmls validation
step.
Libraries and Tools
The SGML processing exists thanks to James Clark's fabulous SP toolkit [SP - An SGML System Conforming to International Standard ISO 8879 -- Standard Generalized Markup Language]. Without it, this paper would not exist.
The XML processing was always going to be a series of XSLT transforms. I much prefer the pipelined approach [Pipelined XSLT Transformations], and the default XML processing assumes a series of XSLTs listed in a manifest.
The pipelines are XProc 3.0 run with Achim Berndzen's MorganaXProc-IIIse [MorganaXProc-III]. The default pipelines use XProc Batch [XProc Batch], but this doesn't have to be the case.
There's also David Maus's SchXslt2 Schematron to XSLT 3.0 transpiler [SchXslt2] to run Schematron validation where needed.
Modelling XML DTDs and Schemas
While XML is the modern standard, SGML’s modeling capabilities were, in some respects, more robust. For example, SGML featured a logical AND group—allowing elements to appear in any order but exactly once. In XML, achieving this level of validation typically requires moving beyond DTDs to Schematron or XSD.
SGML also permitted exclusions
and
inclusions
—convenient shortcuts that often complicate conversion.
Exclusions allowed a designer to prohibit specific elements (like a table) from all
descendants of an element. Inclusions did the opposite, allowing a specific element
to appear anywhere within a hierarchy, bypassing standard model rules.
The inclusion
mechanism is a particular challenge for XML DTDs. In ATA iSpec 2200, for example,
revision markers (REVST and REVEND) were inclusions. Because they could legally appear at different hierarchy levels,
they are impossible to model in an XML DTD without resorting to messy, repeatable
OR groups.
Two primary strategies exist for resolving these models during conversion:
-
Processing Instructions (PIs): Since PIs can occur anywhere, they mimic SGML inclusions well. However, they require authoring environments that can protect them from accidental deletion.
-
Structural Remodelling: This involves a significant rewrite of content models, often replacing markers with attributes and external validation logic. While more labour-intensive, remodelling is usually the superior long-term solution as it avoids relying on PIs to carry semantic meaning.
XML to SGML
Publishing from XML to SGML is akin to the SGML to XML conversion process, but in reverse:
-
The
DOCTYPEdeclaration is removed; we are moving from validating to well-formed XML. -
An SGML
DOCTYPEis generated, including, for example, graphicENTITYdeclarations based on@hrefattributes,NOTATIONdeclarations, etc. -
Various XML-only constructs are removed and any SGML-only content models are generated. For example, we may want to remove the
@hrefgraphic references and insertENTITY-based references instead. -
All
EMPTYXML elements are expanded to explicitly include an end tag (so<graphic></graphic>in place of<graphic/>, which is required for normalisation byspam[SP - An SGML System Conforming to International Standard ISO 8879 -- Standard Generalized Markup Language] in a later step. -
The XML is converted from UTF-8 to ISO character encoding.
-
The generated
DOCTYPEdeclaration is inserted into the XML document, replacing the XML declaration. This, by the way, is where we leave XML-based processing behind and rely on Ant's text-based capabilities and SP. -
The XML is normalised to SGML syntax using
spam[SP - An SGML System Conforming to International Standard ISO 8879 -- Standard Generalized Markup Language]. This includesEMPTYelement syntax as per the SGML DTD, but also tag minimisation as indicated by the DTD. -
The SGML
DOCTYPEis reinserted into the SGML, as it is not retained byspam.
End Notes
Firstly, I confess to using AI (Gemini) to help generate the images. If you think they are ugly, then ping me and I'll silence you with my original art created in draw.io. Just don't say I didn't warn you.
A couple of notes regarding sgml2xml-tng:
-
The -tng in the name means that this is the next generation. The first was always meant only for a single set of SGML DTDs, and it was very difficult to extend it to support any other DTD.
There was a separate XML to SGML pipeline, equally difficult to extend.
-
The XML to SGML conversion is a tricky thing because there are so many ways to produce valid SGML. Most of my code uses XML and XML technologies, which limits what I can do, and then relies on Ant and James Clark's SP package to do anything I can't. SGML empty elements were miserably complicated, yet they are just one problem among many.
-
Which means that I drew the line at some of the more esoteric ways to produce valid SGML. Curly rather than angled brackets will need to wait, I'm afraid[13].
-
A reviewer asked about any SP limitations or issues I encountered. The overall answer would be
not that many
, but I'd certainly highlight its difficulties in outputting SGMLEMPTYelements (i.e., start tags only) without me first using a trick to expand those while still in XML syntax. I have not seen the kinds of ambiguous constructs the reviewer says SP knows nothing about, but it's difficult to comment further without seeing examples[14]. -
You'll notice that the ATA and S1000D DTDs and XSDs are missing from the repo. The ATA DTDs are proprietary and cost money to obtain, so I cannot legally include them. For S1000D, you'll need to approve the terms of use before downloading any DTD or schema.
-
Some will (rightly) ask why I use Ant. The answer nowadays is historical[15]; it's what I used for the first-generation implementations. I do want to move everything to XProc 3.1, but it's going to be a huge job and one that I currently hesitate to start[16], partly because of my current implementation relies on the free version of MorganaXProc-III; XML Calabash wasn't ready when much of the initial work was done.
I'd also like to thank some folks:
-
Henrik Mårtensson, who left the markup business long ago, but not before we did a lot of SGML together
-
Sheila Thomson, who is awesome at writing Ant and who substantially helped improve mine
-
Chris Pedoe, who not only believed me when I pointed out the built-in problems in the software we had to work with, but who also encouraged me to go ahead with my alternate solution
-
Nic Gibson, who not only convinced me of the virtues of pipelined XSLT, but who also gave me the means to follow through
-
James Clark, who's been at the centre of everything I've done in SGML and XML; for the longest time, I wanted him to sign my copy of SP
References
[ATA iSpec 2200] iSpec 2200: Information
Standards for Aviation Maintenance, Revision 2025.1
[online, fetched on 1
April 2026]. https://publications.airlines.org/products/ispec-2200-information-standards-for-aviation-maintenance-revision-2025-1
[MIL-STD-38784] MIL-STD-38784
[online, fetched on 1 April 2026]. https://www.navsea.navy.mil/Home/Warfare-Centers/NSWC-Carderock/Resources/Technical-Information-Systems/Navy-XML-SGML-Repository/DTDs-Schemas/MIL-STD-38784/
[S1000D] S1000D
[online, fetched on 30
March, 2026]. https://s1000d.org/
[SP - An SGML System Conforming to International Standard ISO 8879 -- Standard Generalized
Markup Language] SP
by James Clark [online, fetched on 30 March, 2026]. http://www.jclark.com/sp/
[Topic-based SGML? Really?] Topic-based SGML? Really?
by Ari Nordström [fetched on 4 April 2026].
doi:https://doi.org/10.4242/BalisageVol26.Nordstrom01
[SGML to XML TNG] sgml2xml-tng
by Ari Nordström [online, fetched on 30 March, 2026]. https://github.com/sgmlguru/sgml2xml-tng
[XProc Batch] XProc Batch
by Ari
Nordström and Nic Gibson [online, fetched on 3 April, 2026]. https://github.com/sgmlguru/xproc-batch
[Pipelined XSLT Transformations] Pipelined XSLT Transformations
by Ari
Nordström [online, fetched on 3 April 2026]. doi:https://doi.org/10.4242/BalisageVol25.Nordstrom01
[MorganaXProc-III] MorganaXProc-III
by Achim Berndzen
[online, fetched on 3 April 2026]. https://www.xml-project.com/morganaxproc-iii.html
[SchXslt2] SchXslt2 Schematron to XSLT 3.0 transpiler
by
David Maus [online, fetched on 5 April 2026]. https://codeberg.org/SchXslt/schxslt2
[1] Which, I guess, is why they contacted me in the first place.
[2] S1000D Change 1.8, for example, is more than 25 years old.
[3] Witness, for example, the Eurofighter Typhoon, a wholly European collaboration to produce a multirole fighter aircraft. The Eurofighter is a collaboration between several European nations and thousands of European companies.
[4] In fact, I'd argue that it isn't. I love SGML and its flexibility, and I really miss some of the constructs SGML allowed but XML does not.
[5] And keeps on happening. Just the other day, oXygen announced S1000D 4.x support.
[6] SGML would allow you to, for example, leave out end tags if the parser could infer its position.
[7] Otherwise known as the DTD external subset
.
[8] Referenced via a catalog file, included directly in the instance, or implied by the SGML application.
[9] The pretty formatting always makes me nostalgic.
[10] Meaning anything and everything referenced by the DTD internal subset or the DTD external subset.
[11] A single — without its matching entity
declaration will cause the XML to not be well-formed.
[12] Even though S1000D XML documents frequently do use entities and thus
internal DOCTYPE subsets.
[13] Although it's a problem I'd quite like to solve at some point.
[14] Thankfully, there is a Q&A session at the end of every presentation.
[15] And laziness on my part.
[16] MorganaXProc-IIIse (standard edition) does not support
p:run—you'll need the extended edition for that. Yet my
current thinking is that I'd rely on p:run for quite a
lot.