Skip to content

Commit 22e0e08

Browse files
cinderblockclaude
andcommitted
Decode NI .prj / .usr (Jet) databases via ewd
ewd now handles two kinds of container, with the right decoder picked by reading the file's magic bytes: - compressed-xml (.ewprj, .ms10..ms19): existing PKWare path, writes `<file>.xml`. - mdb (.prj, .usr): Jet 3 / Jet 4 Access database read via mdb-reader, writes `<file>.json` containing every table as `{ columns, rows }` with rows as objects. Dates become ISO 8601 strings; OLE/binary columns become `{ "_bytes": "base64", "value": "..." }` envelopes so the data survives a round-trip. Mechanism: - `src/formats.ts` gained a `kind: 'compressed-xml' | 'mdb'` field on each entry, plus a `detectFileFormat(filename)` that opens the file briefly and reads the magic bytes. - The `mdb` entry's header is `\x00\x01\x00\x00Standard Jet DB` so `detectFormatByHeader` matches by content; non-printable bytes are escaped in `knownFormatsList()` output. - `src/ewd.ts` now dispatches on `format.kind` after detection. - `src/decode.ts` adds a defensive kind check (rejects non-cxml). - `src/encode.ts` throws a clear "not implemented yet" error when asked to encode an mdb format. In-place-edit-only encoding is the planned follow-up. Tests: - +18 new tests (61 total) covering Jet magic detection (including both Jet 3 and Jet 4 version bytes), on-disk `detectFileFormat`, `.prj`/`.usr` extension mapping, the new `kind` discriminator, and `normalizeValue` for Date / Uint8Array / Buffer / Buffer-like / bigint inputs. Verified end-to-end on a real `Stocked.prj` (NI Cpcomp_s.prj derivative, 241 components, 37 tables) — decode succeeds and writes a 7 MB JSON. Adds `mdb-reader` (~106 KB unpacked). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent ec3e317 commit 22e0e08

10 files changed

Lines changed: 487 additions & 37 deletions

File tree

README.md

Lines changed: 74 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,78 @@
11
# Electronics Workbench Decoder and Encoder
22

3-
Two CLIs:
3+
Interop tools for Electronics Workbench / Multisim / Ultiboard binary
4+
formats. The goal: get designs and part databases out of EW into
5+
mainstream representations (and back, where the format permits).
46

5-
- **`ewd`** — decode an EWB project into its underlying XML.
6-
- **`ewe`** — encode XML back into a compressed EWB file the original software can re-open.
7+
- **`ewd`** — decode an EW file into JSON or XML, picking the right
8+
decoder by reading the file's magic bytes.
9+
- **`ewe`** — encode the text-form back into an EW binary the original
10+
software can re-open. Currently supports compressed-XML; `.prj` Jet
11+
database encode is deferred (see below).
712

813
## Supported formats
914

10-
| Key | Extension(s) | Header |
11-
| ---------- | --------------------- | --------------------------------------- |
12-
| `ewprj` | `.ewprj` | `CompressedElectronicsWorkbenchXML` |
13-
| `multisim` | `.ms10``.ms19` | `MSMCompressedElectronicsWorkbenchXML` |
15+
| Key | Extension(s) | Kind | Decode output |
16+
| ---------- | ------------------ | ---------------- | ---------------- |
17+
| `ewprj` | `.ewprj` | `compressed-xml` | `<file>.xml` |
18+
| `multisim` | `.ms10``.ms19` | `compressed-xml` | `<file>.xml` |
19+
| `mdb` | `.prj`, `.usr` | `mdb` | `<file>.json` |
1420

15-
`ewd` identifies the format by reading the magic bytes at the start of the
16-
file, so the filename extension can be anything (renamed files, no extension,
17-
`.dat`, etc. all decode correctly).
21+
### compressed-xml (`ewprj`, `multisim`)
22+
23+
PKWare DCL Implode (ASCII literal mode, large dictionary) wrapped in a
24+
small container: a magic-string header, a 64-bit LE total decompressed
25+
length, then a sequence of `(decompressed_length: u32,
26+
compressed_length: u32, pkware_implode_block)` sections. Multisim chunks
27+
big files into 900 000-decompressed-byte sections; `ewe` mirrors that.
28+
29+
Decode and encode are both supported. A decode/encode/decode round-trip
30+
on every sample tested produces byte-identical XML, and the resulting
31+
files are accepted by `ewd`.
32+
33+
### mdb (`.prj`, `.usr`)
34+
35+
NI ships part libraries and project component databases as Microsoft
36+
Access Jet 3 / Jet 4 files (despite the `.prj` / `.usr` extensions).
37+
`ewd` reads them via [`mdb-reader`](https://www.npmjs.com/package/mdb-reader)
38+
and emits a single JSON document with every table:
39+
40+
```json
41+
{
42+
"format": "mdb",
43+
"source": "Stocked.prj",
44+
"tables": {
45+
"SYS_COMPONENT": {
46+
"columns": [{ "name": "Component_ID", "type": "long" }, ...],
47+
"rows": [{ "Component_ID": 17, "Component_Name": "BD9763FVM", ... }, ...]
48+
},
49+
...
50+
}
51+
}
52+
```
53+
54+
Dates become ISO 8601 strings; binary blob fields (`ole` columns) become
55+
`{ "_bytes": "base64", "value": "..." }` envelopes so the data survives a
56+
JSON round-trip.
57+
58+
**Encode (write-back) is not implemented yet.** Writing a valid Jet 3
59+
file from scratch would mean reimplementing Access's page/B-tree storage
60+
engine, which is a multi-month undertaking. The planned path is in-place
61+
edits only: read an existing `.prj`, change cell values that fit in the
62+
existing page layout, write back. That's tractable; a full Jet writer
63+
is not.
64+
65+
## Format detection
66+
67+
`ewd` identifies the format by reading the magic bytes at the start of
68+
the file, so the filename extension can be anything (renamed files, no
69+
extension, `.dat`, etc.). It then dispatches to the right decoder.
1870

1971
`ewe` infers the format from the **output** filename's extension; pass
2072
`--format <key>` to override for non-standard extensions. Out-of-range
2173
Multisim versions (e.g. `.ms9` or future `.ms20+`) need an explicit
2274
`--format multisim`.
2375

24-
The container is a small header, then a 64-bit little-endian total
25-
decompressed length, then a sequence of `(decompressed_length: u32,
26-
compressed_length: u32, pkware_implode_block)` sections. Compression is
27-
PKWare DCL Implode (ASCII literal mode, large dictionary). Multisim chunks
28-
big files into 900 000-decompressed-byte sections; `ewe` mirrors that.
29-
30-
> Note: the `*.prj` / `*.usr` / `*.ldb` files that ship as part of NI's
31-
> "Electronics Workbench Database" are Microsoft Access (Jet) databases,
32-
> not the compressed-XML container described here. They aren't handled by
33-
> `ewd` / `ewe`.
34-
3576
## Install
3677

3778
Requires [bun](https://bun.sh) (≥ 1.0).
@@ -44,9 +85,11 @@ bun install
4485

4586
```bash
4687
bun run ewd --verbose ./samples/Temp.ewprj ./samples/Design1.ms14
88+
bun run ewd --verbose ./samples/Stocked.prj # writes Stocked.prj.json
4789
```
4890

49-
For each input, writes `<filename>.xml` next to it.
91+
For each input, writes `<filename>.xml` (compressed-xml) or
92+
`<filename>.json` (mdb) next to it.
5093

5194
Options:
5295

@@ -75,6 +118,9 @@ Options:
75118
- `-f`, `--format <key>` — force a format (`ewprj` or `multisim`)
76119
- positional args — XML files to encode
77120

121+
Trying to encode an `mdb` target fails with a clear error until
122+
in-place-edit support lands.
123+
78124
### Compression ratio caveat
79125

80126
`ewe` uses [`node-pkware`](https://github.com/cinderblock/node-pkware)'s
@@ -91,10 +137,12 @@ produces byte-identical XML, and the resulting files are accepted by
91137
bun test
92138
```
93139

94-
Covers the format registry (extension matching, header detection),
95-
encoder filename helpers, and round-trip integration tests (tiny
96-
`.ewprj`, tiny `.ms14`, multi-block payload that spans more than one
97-
PKWare section, and an empty payload).
140+
Covers the format registry (extension matching, header detection
141+
including the Jet magic prefix with leading NUL bytes, on-disk
142+
detection), encoder filename helpers, `mdb` value normalization
143+
(`Date` → ISO, OLE blobs → base64), and compressed-xml round-trip
144+
integration (tiny `.ewprj`, tiny `.ms14`, multi-block payload that
145+
spans more than one PKWare section, and an empty payload).
98146

99147
## Development
100148

0 commit comments

Comments
 (0)