Tediparse is a library for parsing, generating, and validating ASC X12 EDI documents. You bring the X12 transaction-set grammar; the library walks it. Very roughly, it's jQuery for EDI — once you've supplied the schema.
For those unfamiliar with ASC X12 EDI, it is a data format used to encode common business documents like purchase orders, delivery notices, and health care claims. It is similar to XML in some ways, but precedes it by about 15 years; so if you think XML sucks, you will love to hate EDI.
Tediparse ships the engine, not the grammars. The library does not bundle
any X12 transaction-set definitions, code lists, envelopes, or ack/editor
content — that material is X12 IP and is not ours to redistribute. To parse
or generate real X12 documents you must supply (or license) your own grammar
and register it against Stupidedi::Config before calling the parser.
If you reach for one of the per-era namespaces that the upstream stupidedi
gem shipped — Stupidedi::Versions::FiftyTen, Stupidedi::TransactionSets::FortyTen,
Stupidedi::Interchanges::FiveOhOne, and so on — tediparse raises a typed
Stupidedi::Exceptions::MissingGrammarError with guidance, rather than
NameError. Parser-driven lookups against an empty config produce a
FailureState whose reason carries the same message.
Engine scope. Tediparse provides parsing and generation as a library. The
upstream gem's editor / acknowledgement subsystem (TA1 / 999 / 277CA) and
the bin/edi-pp / edi-ed / edi-obfuscate command-line tools are not
part of tediparse.
The canonical reference for how to wire up your own grammar against the engine lives in the spec harness:
spec/support/synthetic/demo.rb— a small adversarial transaction set built withStupidedi::TransactionSets::Builder. Exercises composites, code lists, interleaved segments and child loops, qualifier-discriminated sibling loops, and thePsyntax note.spec/support/synthetic/interchange_def.rb— minimal ISA envelope.spec/support/synthetic/functional_group_def.rb— minimal GS envelope.spec/support/synthetic/config.rb— wires the three pieces into a usableStupidedi::Config.
Mirror that shape in your own application and register against
Stupidedi::Config.new (the legacy Config.default / hipaa / contrib
factories are preserved for source compatibility but now return empty
configs).
If you have private grammars and need to keep them tested against tediparse, the upstream X12 fixture corpus and grammar tree remain recoverable from the pre-removal commit:
git fetch --tags
git worktree add ../conformance pre-x12-removal
Build the conformance suite against that worktree's fixtures, layer your licensed grammar on top, and run it out-of-tree.
This product includes software from stupidedi by Kyle Putnam, available at
https://github.com/kputnam/stupidedi.
Tediparse is a fork of stupidedi,
maintained by Adrian Duyzer at
Tediware. The fork was created to accelerate
development and support a broader set of X12 documents. The internal Ruby
module name remains Stupidedi for backward compatibility; require "tediparse" and require "stupidedi" both work.
- Original author: Kyle Putnam
- Tediparse maintainer: Adrian Duyzer
Transaction set specifications can be enormous, boring, and vague. Trading partners can demand strict adherence (often to their own unique interpretation of the specification) of the documents you generate. However, documents they generate themselves are often non-standard and require flexibility to parse them.
Tediparse enables you to encode these transaction set specifications directly in Ruby. From these specifications, it will generate a parser to read incoming messages and a DSL to generate outgoing messages. This approach has a huge advantage over writing a parser from scratch, which can be error-prone and difficult to change.
Delimiters, line breaks, and out-of-band data between interchanges are handled correctly. While many trading partners follow common conventions, it only takes one unexpected deviation, like swapping the ":" and "~" delimiters, to render a hand-written parser broken.
Tediparse handles many edge cases that can only be anticipated by reading carefully between the lines of the X12 documentation.
When generating EDI documents, validation is performed incrementally on each segment. This means the instant your client code violates the specification, an exception is thrown with a meaningful stack trace. Other libraries only perform validation after the entire document has been generated, while some don't perform validation at all.
Unlike other libraries, generating documents doesn't involve naming obscure identifiers from the specification (like C001, DE522 or LOOP2000), for elements of the grammar that don't actually appear in the output.
Like HAML or Builder::XmlMarkup, the DSL for generating documents closely matches terminology from the problem domain. You can see in the example below that code looks very similar to an EDI document.
The parser is designed using immutable data structures, making it thread-safe for runtimes that can utilize multiple cores. In some cases, immutability places higher demand on garbage collection; this has been somewhat mitigated with careful optimization.
The examples below assume config is a Stupidedi::Config you've populated
with your own grammar — see spec/support/synthetic/config.rb for the
authoring pattern.
require "tediparse"
# You bring the grammar. See spec/support/synthetic for the authoring shape.
config = MyApp::EDI.config
b = Stupidedi::Parser::BuilderDsl.build(config)
# These methods perform error checking: number of elements, element types, min/max
# length requirements, conditionally required elements, valid segments, number of
# segment occurrences, number of loop occurrences, etc.
b.ISA "00", nil, "00", nil,
"ZZ", "SUBMITTER ID",
"ZZ", "RECEIVER ID",
"990531", "1230", nil, "00501", "123456789", "1", "T", nil
# The API tracks the current position in the specification (e.g., the current loop,
# table, etc) to ensure well-formedness as each segment is generated.
b.GS "HC", "SENDER ID", "RECEIVER ID", "19990531", "1230", "1", "X", "005010X222"
b.ST "837", "1234", b.default
b.BHT "0019", "00", "X"*30, "19990531", Time.now.utc, "CH"
b.NM1 b.default, "1", "PREMIER BILLING SERVICE", nil, nil, nil, nil, "46", "12EEER000TY"
# ...
b.machine.zipper.tap do |z|
separators =
Stupidedi::Reader::Separators.build :segment => "~\n",
:element => "*",
:component => ":",
:repetition => "^"
w = Stupidedi::Writer::Default.new(z.root, separators)
print w.write()
endStupidedi::Writer::Default outputs plain X12; Stupidedi::Writer::Claredi
outputs a formatted HTML string.
b.machine.zipper.tap do |z|
w = Stupidedi::Writer::Claredi.new(z.root)
File.open('output.html', 'w') { |f| f.write w.write }
endrequire "tediparse"
config = MyApp::EDI.config
parser = Stupidedi::Parser.build(config)
input = File.open("path/to/your.edi", :encoding => "ISO-8859-1")
# Reader.build accepts IO (File), String, and DelegateInput
parser, result = parser.read(Stupidedi::Reader.build(input))
# Report fatal tokenizer failures
if result.fatal?
result.explain{|reason| raise reason + " at #{result.position.inspect}" }
end
# Helper function: fetch an element from the current segment
def el(m, *ns, &block)
if Stupidedi::Either === m
m.tap{|m| el(m, *ns, &block) }
else
yield(*ns.map{|n| m.elementn(n).map(&:value).fetch })
end
end
parser.first
.flatmap{|m| m.find(:GS) }
.flatmap{|m| m.find(:ST) }
.tap do |m|
el(m.find(:N1, "PR"), 2){|e| puts "Payer: #{e}" }
el(m.find(:N1, "PE"), 2){|e| puts "Payee: #{e}" }
end
.flatmap{|m| m.find(:LX) }
.flatmap{|m| m.find(:CLP) }
.flatmap{|m| m.find(:NM1, "QC") }
.tap{|m| el(m, 3, 4){|l,f| puts "Patient: #{l}, #{f}" }}bundle exec rake spec
It isn't a translator. It doesn't have bells and whistles, like the commercial EDI translators have, so it...
- Doesn't convert to/from XML, CSV, etc
- Doesn't transmit or receive files
- Doesn't do encryption
- Doesn't connect to your database
- Doesn't queue messages for delivery or receipt
- Doesn't generate acknowledgements
- Doesn't have a graphical interface
These features are orthogonal to the problem tediparse aims to solve, but they can certainly be implemented with other code taking advantage of tediparse.