bsdata-parser 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +167 -0
- package/dist/index.d.ts +241 -0
- package/dist/index.js +302 -0
- package/dist/index.js.map +1 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Aodhan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
# bsdata-parser
|
|
2
|
+
|
|
3
|
+
A faithful, typed parser for [BSData](https://github.com/BSData) `.gst`/`.cat` XML game-data files into a typed Intermediate Representation (IR), plus a golden-diff harness for validating a downstream projection against a reference output.
|
|
4
|
+
|
|
5
|
+
[](https://github.com/aodhan-dev/bsdata-parser/actions/workflows/ci.yml)
|
|
6
|
+
[](https://www.npmjs.com/package/bsdata-parser)
|
|
7
|
+
|
|
8
|
+
## Why
|
|
9
|
+
|
|
10
|
+
Naive BSData parsers flatten the XML as they go, silently dropping modifiers and conditions they cannot statically resolve. The result is recurring "missing option" bugs whenever BSData's conditional logic kicks in. This library solves that by keeping the full modifier/condition tree in the IR and leaving resolution to the projection layer.
|
|
11
|
+
|
|
12
|
+
## Architecture
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
BSData (.gst/.cat) ──parse──▶ IR (faithful tree) ──project──▶ your output
|
|
16
|
+
│
|
|
17
|
+
reference output ◀───diff───────┘
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- **`src/parse`** - XML to IR. Mechanical transcription only: no interpretation, no flattening, no domain knowledge. Every modifier, condition, constraint, and repeat node is preserved verbatim.
|
|
21
|
+
- **`src/project`** - IR to output. All domain interpretation lives here. This layer is intentionally not included in the package - you supply it, shaped to your own output schema.
|
|
22
|
+
- **`harness/`** - golden-diff utilities. Load a reference output (oracle) and diff a candidate against it to find gaps. Auto-skips when no local data is present, so the test suite runs cleanly everywhere.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install bsdata-parser
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { parseToIr } from 'bsdata-parser'
|
|
34
|
+
import type { Ir } from 'bsdata-parser'
|
|
35
|
+
|
|
36
|
+
// 1. Load your .gst/.cat files however you like (fs, fetch, etc.)
|
|
37
|
+
const files: Record<string, string> = {
|
|
38
|
+
'my-game.gst': '<gameSystem ...>...</gameSystem>',
|
|
39
|
+
'faction-a.cat': '<catalogue ...>...</catalogue>',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 2. Parse to IR - faithful, lossless, typed
|
|
43
|
+
const ir: Ir = parseToIr(files)
|
|
44
|
+
|
|
45
|
+
// 3. Project to your output schema
|
|
46
|
+
const output = myProjector(ir)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`parseToIr` accepts a `filename -> XML string` map and returns an `Ir` object containing the fully parsed game system and all catalogues as typed trees.
|
|
50
|
+
|
|
51
|
+
## IR shape
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
interface Ir {
|
|
55
|
+
gameSystem: IrCatalogueFile // the .gst file
|
|
56
|
+
catalogues: IrCatalogueFile[] // all .cat files
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface IrCatalogueFile {
|
|
60
|
+
filename: string
|
|
61
|
+
kind: 'gameSystem' | 'catalogue'
|
|
62
|
+
id: string
|
|
63
|
+
name: string
|
|
64
|
+
gameSystemId?: string
|
|
65
|
+
catalogueLinks: IrCatalogueLink[]
|
|
66
|
+
root: IrCatalogueRoot
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
interface IrCatalogueRoot {
|
|
70
|
+
costTypes: IrCostType[]
|
|
71
|
+
profileTypes: IrProfileType[]
|
|
72
|
+
categoryEntries: IrCategoryEntry[]
|
|
73
|
+
rules: IrRule[]
|
|
74
|
+
sharedRules: IrRule[]
|
|
75
|
+
sharedProfiles: IrProfile[]
|
|
76
|
+
selectionEntries: IrSelectionEntry[]
|
|
77
|
+
sharedSelectionEntries: IrSelectionEntry[]
|
|
78
|
+
sharedSelectionEntryGroups: IrSelectionEntryGroup[]
|
|
79
|
+
entryLinks: IrEntryLink[]
|
|
80
|
+
forceEntries: IrForceEntry[]
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
All node types (`IrSelectionEntry`, `IrConstraint`, `IrModifier`, `IrCondition`, etc.) are exported from the package. See `src/ir/types.ts` for the complete set.
|
|
85
|
+
|
|
86
|
+
## Writing a projector
|
|
87
|
+
|
|
88
|
+
A projector is a function `(ir: Ir) => YourOutputType`. It lives in your own codebase and is never part of this package:
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { parseToIr } from 'bsdata-parser'
|
|
92
|
+
import type { Ir } from 'bsdata-parser'
|
|
93
|
+
|
|
94
|
+
function projectMyGame(ir: Ir): MyOutput {
|
|
95
|
+
const categories: Record<string, string> = {}
|
|
96
|
+
for (const entry of ir.gameSystem.root.categoryEntries) {
|
|
97
|
+
categories[entry.id] = entry.name
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const units = ir.catalogues.flatMap(cat =>
|
|
101
|
+
cat.root.sharedSelectionEntries
|
|
102
|
+
.filter(e => e.type === 'unit' && !e.hidden)
|
|
103
|
+
.map(e => ({ id: e.id, name: e.name, faction: cat.name }))
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return { categories, units }
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function buildOutput(files: Record<string, string>): MyOutput {
|
|
110
|
+
return projectMyGame(parseToIr(files))
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Golden-diff harness
|
|
115
|
+
|
|
116
|
+
The `harness/` directory contains utilities for validating that your projector reproduces a known-good reference output as a superset. Copy or adapt these into your own project:
|
|
117
|
+
|
|
118
|
+
- `harness/oracle.ts` - `loadBsdata()`, `loadOracle()`, `hasData()` (reads from `BSDATA_DIR`)
|
|
119
|
+
- `harness/diff.ts` - `diffCatalogues(oracle, candidate)` - counts-based diff, domain-agnostic
|
|
120
|
+
|
|
121
|
+
Set `BSDATA_DIR` to a local directory containing:
|
|
122
|
+
- `bsdata/` - the `.cat`/`.gst` source files
|
|
123
|
+
- `catalogue.json` - a reference output to diff against
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
// your-project/harness/golden-parity.test.ts
|
|
127
|
+
import { describe, it, expect } from 'vitest'
|
|
128
|
+
import { readFile, readdir } from 'node:fs/promises'
|
|
129
|
+
import { existsSync } from 'node:fs'
|
|
130
|
+
import { join } from 'node:path'
|
|
131
|
+
import { buildOutput } from './my-projector'
|
|
132
|
+
|
|
133
|
+
const dir = process.env.BSDATA_DIR ?? ''
|
|
134
|
+
const hasData = () => existsSync(join(dir, 'catalogue.json'))
|
|
135
|
+
|
|
136
|
+
describe.skipIf(!hasData())('golden parity', () => {
|
|
137
|
+
it('reproduces every oracle collection', async () => {
|
|
138
|
+
const oracle = JSON.parse(await readFile(join(dir, 'catalogue.json'), 'utf-8'))
|
|
139
|
+
const names = (await readdir(join(dir, 'bsdata'))).filter(f => f.endsWith('.cat') || f.endsWith('.gst'))
|
|
140
|
+
const files: Record<string, string> = {}
|
|
141
|
+
await Promise.all(names.map(async n => { files[n] = await readFile(join(dir, 'bsdata', n), 'utf-8') }))
|
|
142
|
+
const candidate = buildOutput(files)
|
|
143
|
+
// diff: for every key in oracle, compare collection size
|
|
144
|
+
const diffs = Object.keys(oracle).filter(k => {
|
|
145
|
+
const size = (v: unknown) => Array.isArray(v) ? v.length : v && typeof v === 'object' ? Object.keys(v as object).length : 0
|
|
146
|
+
return size(oracle[k]) !== size((candidate as Record<string, unknown>)[k])
|
|
147
|
+
})
|
|
148
|
+
expect(diffs).toEqual([])
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Data and domain code (never committed)
|
|
154
|
+
|
|
155
|
+
No game data is bundled here. BSData source files are third-party content you assemble yourself. Your projector is likewise kept local - nothing data- or domain-specific should be published.
|
|
156
|
+
|
|
157
|
+
## Commands
|
|
158
|
+
|
|
159
|
+
| Command | Description |
|
|
160
|
+
|---------|-------------|
|
|
161
|
+
| `npm test` | Unit suite + golden-parity test (auto-skips without `BSDATA_DIR`) |
|
|
162
|
+
| `npm run build` | Compile to `dist/` via tsup |
|
|
163
|
+
| `npm run typecheck` | `tsc --noEmit` |
|
|
164
|
+
|
|
165
|
+
## License
|
|
166
|
+
|
|
167
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BSData intermediate representation (IR).
|
|
3
|
+
*
|
|
4
|
+
* A FAITHFUL, lossless-by-intent tree mirror of the BSData .gst / .cat model.
|
|
5
|
+
* Nothing is resolved, flattened, or interpreted here. Scope strings are raw BSData
|
|
6
|
+
* values. Modifier/condition trees are opaque. All interpretation belongs in the
|
|
7
|
+
* projection (src/project).
|
|
8
|
+
*/
|
|
9
|
+
interface IrConstraint {
|
|
10
|
+
id: string;
|
|
11
|
+
type: 'min' | 'max';
|
|
12
|
+
value: number;
|
|
13
|
+
scope: string;
|
|
14
|
+
field: string;
|
|
15
|
+
shared?: boolean;
|
|
16
|
+
includeChildSelections?: boolean;
|
|
17
|
+
}
|
|
18
|
+
interface IrCharacteristic {
|
|
19
|
+
name: string;
|
|
20
|
+
typeId: string;
|
|
21
|
+
value: string;
|
|
22
|
+
}
|
|
23
|
+
interface IrProfile {
|
|
24
|
+
id: string;
|
|
25
|
+
name: string;
|
|
26
|
+
hidden: boolean;
|
|
27
|
+
typeId: string;
|
|
28
|
+
typeName: string;
|
|
29
|
+
characteristics: IrCharacteristic[];
|
|
30
|
+
}
|
|
31
|
+
interface IrCost {
|
|
32
|
+
name: string;
|
|
33
|
+
typeId: string;
|
|
34
|
+
value: number;
|
|
35
|
+
}
|
|
36
|
+
interface IrRule {
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
hidden: boolean;
|
|
40
|
+
description: string;
|
|
41
|
+
}
|
|
42
|
+
interface IrInfoLink {
|
|
43
|
+
id: string;
|
|
44
|
+
name: string;
|
|
45
|
+
hidden: boolean;
|
|
46
|
+
targetId: string;
|
|
47
|
+
type: string;
|
|
48
|
+
}
|
|
49
|
+
interface IrCategoryLink {
|
|
50
|
+
id: string;
|
|
51
|
+
targetId: string;
|
|
52
|
+
primary: boolean;
|
|
53
|
+
}
|
|
54
|
+
/** Verbatim condition node - not evaluated, retained for the modifier tree. */
|
|
55
|
+
interface IrCondition {
|
|
56
|
+
type: string;
|
|
57
|
+
value: number;
|
|
58
|
+
field: string;
|
|
59
|
+
scope: string;
|
|
60
|
+
childId?: string;
|
|
61
|
+
shared?: boolean;
|
|
62
|
+
includeChildSelections?: boolean;
|
|
63
|
+
percentValue?: boolean;
|
|
64
|
+
}
|
|
65
|
+
interface IrConditionGroup {
|
|
66
|
+
type: string;
|
|
67
|
+
conditions: IrCondition[];
|
|
68
|
+
conditionGroups: IrConditionGroup[];
|
|
69
|
+
}
|
|
70
|
+
/** Verbatim modifier node - not evaluated. */
|
|
71
|
+
interface IrModifier {
|
|
72
|
+
type: string;
|
|
73
|
+
field: string;
|
|
74
|
+
value: string | number;
|
|
75
|
+
conditions: IrCondition[];
|
|
76
|
+
conditionGroups: IrConditionGroup[];
|
|
77
|
+
repeats: IrRepeat[];
|
|
78
|
+
}
|
|
79
|
+
interface IrRepeat {
|
|
80
|
+
value: number;
|
|
81
|
+
repeats: number;
|
|
82
|
+
field: string;
|
|
83
|
+
scope: string;
|
|
84
|
+
childId?: string;
|
|
85
|
+
shared?: boolean;
|
|
86
|
+
includeChildSelections?: boolean;
|
|
87
|
+
roundUp?: boolean;
|
|
88
|
+
}
|
|
89
|
+
type IrEntryType = 'unit' | 'model' | 'upgrade' | 'mount';
|
|
90
|
+
interface IrSelectionEntry {
|
|
91
|
+
id: string;
|
|
92
|
+
name: string;
|
|
93
|
+
hidden: boolean;
|
|
94
|
+
collective: boolean;
|
|
95
|
+
import: boolean;
|
|
96
|
+
type: IrEntryType;
|
|
97
|
+
defaultAmount?: number;
|
|
98
|
+
constraints: IrConstraint[];
|
|
99
|
+
profiles: IrProfile[];
|
|
100
|
+
rules: IrRule[];
|
|
101
|
+
infoLinks: IrInfoLink[];
|
|
102
|
+
categoryLinks: IrCategoryLink[];
|
|
103
|
+
costs: IrCost[];
|
|
104
|
+
modifiers: IrModifier[];
|
|
105
|
+
selectionEntries: IrSelectionEntry[];
|
|
106
|
+
selectionEntryGroups: IrSelectionEntryGroup[];
|
|
107
|
+
entryLinks: IrEntryLink[];
|
|
108
|
+
}
|
|
109
|
+
interface IrSelectionEntryGroup {
|
|
110
|
+
id: string;
|
|
111
|
+
name: string;
|
|
112
|
+
hidden: boolean;
|
|
113
|
+
collective: boolean;
|
|
114
|
+
import: boolean;
|
|
115
|
+
defaultSelectionEntryId?: string;
|
|
116
|
+
constraints: IrConstraint[];
|
|
117
|
+
modifiers: IrModifier[];
|
|
118
|
+
selectionEntries: IrSelectionEntry[];
|
|
119
|
+
selectionEntryGroups: IrSelectionEntryGroup[];
|
|
120
|
+
entryLinks: IrEntryLink[];
|
|
121
|
+
}
|
|
122
|
+
interface IrEntryLink {
|
|
123
|
+
id: string;
|
|
124
|
+
name: string;
|
|
125
|
+
hidden: boolean;
|
|
126
|
+
collective: boolean;
|
|
127
|
+
import: boolean;
|
|
128
|
+
targetId: string;
|
|
129
|
+
type: string;
|
|
130
|
+
defaultAmount?: number;
|
|
131
|
+
comment?: string;
|
|
132
|
+
constraints: IrConstraint[];
|
|
133
|
+
costs: IrCost[];
|
|
134
|
+
modifiers: IrModifier[];
|
|
135
|
+
profiles: IrProfile[];
|
|
136
|
+
infoLinks: IrInfoLink[];
|
|
137
|
+
categoryLinks: IrCategoryLink[];
|
|
138
|
+
selectionEntries: IrSelectionEntry[];
|
|
139
|
+
selectionEntryGroups: IrSelectionEntryGroup[];
|
|
140
|
+
entryLinks: IrEntryLink[];
|
|
141
|
+
}
|
|
142
|
+
interface IrCategoryEntry {
|
|
143
|
+
id: string;
|
|
144
|
+
name: string;
|
|
145
|
+
hidden: boolean;
|
|
146
|
+
}
|
|
147
|
+
interface IrForceCategoryLink {
|
|
148
|
+
id: string;
|
|
149
|
+
targetId: string;
|
|
150
|
+
primary: boolean;
|
|
151
|
+
hidden: boolean;
|
|
152
|
+
constraints: IrConstraint[];
|
|
153
|
+
}
|
|
154
|
+
interface IrForceEntry {
|
|
155
|
+
id: string;
|
|
156
|
+
name: string;
|
|
157
|
+
hidden: boolean;
|
|
158
|
+
categoryLinks: IrForceCategoryLink[];
|
|
159
|
+
rules: IrRule[];
|
|
160
|
+
modifiers: IrModifier[];
|
|
161
|
+
forceEntries: IrForceEntry[];
|
|
162
|
+
}
|
|
163
|
+
interface IrCostType {
|
|
164
|
+
id: string;
|
|
165
|
+
name: string;
|
|
166
|
+
defaultCostLimit: number;
|
|
167
|
+
}
|
|
168
|
+
/** A BSData catalogueLink node: references another catalogue this one imports from. */
|
|
169
|
+
interface IrCatalogueLink {
|
|
170
|
+
id: string;
|
|
171
|
+
name: string;
|
|
172
|
+
targetId: string;
|
|
173
|
+
type: string;
|
|
174
|
+
importRootEntries: boolean;
|
|
175
|
+
}
|
|
176
|
+
interface IrProfileType {
|
|
177
|
+
id: string;
|
|
178
|
+
name: string;
|
|
179
|
+
characteristicTypes: Array<{
|
|
180
|
+
id: string;
|
|
181
|
+
name: string;
|
|
182
|
+
}>;
|
|
183
|
+
}
|
|
184
|
+
interface IrCatalogueRoot {
|
|
185
|
+
costTypes: IrCostType[];
|
|
186
|
+
profileTypes: IrProfileType[];
|
|
187
|
+
categoryEntries: IrCategoryEntry[];
|
|
188
|
+
rules: IrRule[];
|
|
189
|
+
/** Rules from the BSData <sharedRules> element: the canonical rule-definition pool. */
|
|
190
|
+
sharedRules: IrRule[];
|
|
191
|
+
/** Profiles from the BSData <sharedProfiles> element: abilities with Summary/Description characteristics. */
|
|
192
|
+
sharedProfiles: IrProfile[];
|
|
193
|
+
selectionEntries: IrSelectionEntry[];
|
|
194
|
+
sharedSelectionEntries: IrSelectionEntry[];
|
|
195
|
+
sharedSelectionEntryGroups: IrSelectionEntryGroup[];
|
|
196
|
+
entryLinks: IrEntryLink[];
|
|
197
|
+
forceEntries: IrForceEntry[];
|
|
198
|
+
}
|
|
199
|
+
/** A single parsed BSData file (game system or catalogue). */
|
|
200
|
+
interface IrCatalogueFile {
|
|
201
|
+
filename: string;
|
|
202
|
+
kind: 'gameSystem' | 'catalogue';
|
|
203
|
+
id: string;
|
|
204
|
+
name: string;
|
|
205
|
+
gameSystemId?: string;
|
|
206
|
+
catalogueLinks: IrCatalogueLink[];
|
|
207
|
+
root: IrCatalogueRoot;
|
|
208
|
+
}
|
|
209
|
+
/** The whole parsed source set: the game system plus every catalogue. */
|
|
210
|
+
interface Ir {
|
|
211
|
+
gameSystem: IrCatalogueFile;
|
|
212
|
+
catalogues: IrCatalogueFile[];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
declare function parseToIr(files: Record<string, string>): Ir;
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* IR -> downstream projection.
|
|
219
|
+
*
|
|
220
|
+
* Projects the faithful IR into the flattened, ready-to-consume shape the downstream application
|
|
221
|
+
* needs. ALL domain interpretation lives here; the IR and parser stay domain-agnostic. The concrete
|
|
222
|
+
* projector and its reference data are supplied locally and are never committed to this repo (see
|
|
223
|
+
* README) -- this repo ships the framework, not any specific data model.
|
|
224
|
+
*
|
|
225
|
+
* "Superset" is the contract: the projection MUST reproduce every field the reference output carries
|
|
226
|
+
* (so the existing consumer keeps working unchanged), and MAY add fields the current consumer lacks.
|
|
227
|
+
* The golden-diff harness asserts the reproduction half; new fields are additive and ignored.
|
|
228
|
+
*
|
|
229
|
+
* STUB. Supply a local implementation that imports your projector modules. The local file is
|
|
230
|
+
* gitignored so it never reaches this public repo. See CLAUDE.md for the hygiene rule.
|
|
231
|
+
*/
|
|
232
|
+
declare function projectCatalogue(_ir: Ir): Record<string, unknown>;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Public entry point. `filename -> XML` map in, projected output object out. The signature matches
|
|
236
|
+
* the downstream consumer's existing build step so this can be wired in incrementally (swap the
|
|
237
|
+
* import, keep the golden diff green) without touching the consumer.
|
|
238
|
+
*/
|
|
239
|
+
declare function buildCatalogue(files: Record<string, string>): Record<string, unknown>;
|
|
240
|
+
|
|
241
|
+
export { type Ir, type IrCatalogueFile, buildCatalogue, parseToIr, projectCatalogue };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
// src/parse/xml.ts
|
|
2
|
+
import { XMLParser } from "fast-xml-parser";
|
|
3
|
+
var ALWAYS_ARRAY = /* @__PURE__ */ new Set([
|
|
4
|
+
"selectionEntry",
|
|
5
|
+
"selectionEntryGroup",
|
|
6
|
+
"entryLink",
|
|
7
|
+
"constraint",
|
|
8
|
+
"profile",
|
|
9
|
+
"characteristic",
|
|
10
|
+
"characteristicType",
|
|
11
|
+
"cost",
|
|
12
|
+
"costType",
|
|
13
|
+
"profileType",
|
|
14
|
+
"rule",
|
|
15
|
+
"categoryLink",
|
|
16
|
+
"categoryEntry",
|
|
17
|
+
"infoLink",
|
|
18
|
+
"modifier",
|
|
19
|
+
"condition",
|
|
20
|
+
"conditionGroup",
|
|
21
|
+
"repeat",
|
|
22
|
+
"forceEntry",
|
|
23
|
+
"catalogueLink"
|
|
24
|
+
]);
|
|
25
|
+
var xmlParser = new XMLParser({
|
|
26
|
+
ignoreAttributes: false,
|
|
27
|
+
attributeNamePrefix: "@_",
|
|
28
|
+
textNodeName: "#text",
|
|
29
|
+
isArray: (name) => ALWAYS_ARRAY.has(name),
|
|
30
|
+
parseAttributeValue: true
|
|
31
|
+
});
|
|
32
|
+
function arr(v) {
|
|
33
|
+
if (!v) return [];
|
|
34
|
+
if (Array.isArray(v)) return v;
|
|
35
|
+
return [v];
|
|
36
|
+
}
|
|
37
|
+
function bool(v) {
|
|
38
|
+
return v === true || v === "true";
|
|
39
|
+
}
|
|
40
|
+
function parseConstraint(n) {
|
|
41
|
+
return {
|
|
42
|
+
id: String(n["@_id"] ?? ""),
|
|
43
|
+
type: n["@_type"] === "min" ? "min" : "max",
|
|
44
|
+
value: Number(n["@_value"] ?? 0),
|
|
45
|
+
scope: String(n["@_scope"] ?? ""),
|
|
46
|
+
field: String(n["@_field"] ?? "selections"),
|
|
47
|
+
shared: bool(n["@_shared"]),
|
|
48
|
+
includeChildSelections: bool(n["@_includeChildSelections"])
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function parseCost(n) {
|
|
52
|
+
return {
|
|
53
|
+
name: String(n["@_name"] ?? ""),
|
|
54
|
+
typeId: String(n["@_typeId"] ?? ""),
|
|
55
|
+
value: Number(n["@_value"] ?? 0)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function parseCharacteristic(n) {
|
|
59
|
+
const raw = n["#text"];
|
|
60
|
+
return {
|
|
61
|
+
name: String(n["@_name"] ?? ""),
|
|
62
|
+
typeId: String(n["@_typeId"] ?? ""),
|
|
63
|
+
value: raw != null ? String(raw) : ""
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function parseProfile(n) {
|
|
67
|
+
return {
|
|
68
|
+
id: String(n["@_id"] ?? ""),
|
|
69
|
+
name: String(n["@_name"] ?? ""),
|
|
70
|
+
hidden: bool(n["@_hidden"]),
|
|
71
|
+
typeId: String(n["@_typeId"] ?? ""),
|
|
72
|
+
typeName: String(n["@_typeName"] ?? ""),
|
|
73
|
+
characteristics: arr(n.characteristics?.characteristic).map(parseCharacteristic)
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function parseRule(n) {
|
|
77
|
+
return {
|
|
78
|
+
id: String(n["@_id"] ?? ""),
|
|
79
|
+
name: String(n["@_name"] ?? ""),
|
|
80
|
+
hidden: bool(n["@_hidden"]),
|
|
81
|
+
description: n.description != null ? String(n.description) : ""
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function parseInfoLink(n) {
|
|
85
|
+
return {
|
|
86
|
+
id: String(n["@_id"] ?? ""),
|
|
87
|
+
name: String(n["@_name"] ?? ""),
|
|
88
|
+
hidden: bool(n["@_hidden"]),
|
|
89
|
+
targetId: String(n["@_targetId"] ?? ""),
|
|
90
|
+
type: String(n["@_type"] ?? "")
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function parseCategoryLink(n) {
|
|
94
|
+
return {
|
|
95
|
+
id: String(n["@_id"] ?? ""),
|
|
96
|
+
targetId: String(n["@_targetId"] ?? ""),
|
|
97
|
+
primary: bool(n["@_primary"])
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function parseCondition(n) {
|
|
101
|
+
const out = {
|
|
102
|
+
type: String(n["@_type"] ?? ""),
|
|
103
|
+
value: Number(n["@_value"] ?? 0),
|
|
104
|
+
field: String(n["@_field"] ?? ""),
|
|
105
|
+
scope: String(n["@_scope"] ?? "")
|
|
106
|
+
};
|
|
107
|
+
if (n["@_childId"] != null) out.childId = String(n["@_childId"]);
|
|
108
|
+
if (n["@_shared"] != null) out.shared = bool(n["@_shared"]);
|
|
109
|
+
if (n["@_includeChildSelections"] != null) out.includeChildSelections = bool(n["@_includeChildSelections"]);
|
|
110
|
+
if (n["@_percentValue"] != null) out.percentValue = bool(n["@_percentValue"]);
|
|
111
|
+
return out;
|
|
112
|
+
}
|
|
113
|
+
function parseConditionGroup(n) {
|
|
114
|
+
return {
|
|
115
|
+
type: String(n["@_type"] ?? ""),
|
|
116
|
+
conditions: arr(n.conditions?.condition).map(parseCondition),
|
|
117
|
+
conditionGroups: arr(n.conditionGroups?.conditionGroup).map(parseConditionGroup)
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function parseRepeat(n) {
|
|
121
|
+
const out = {
|
|
122
|
+
value: Number(n["@_value"] ?? 0),
|
|
123
|
+
repeats: Number(n["@_repeats"] ?? 0),
|
|
124
|
+
field: String(n["@_field"] ?? ""),
|
|
125
|
+
scope: String(n["@_scope"] ?? "")
|
|
126
|
+
};
|
|
127
|
+
if (n["@_childId"] != null) out.childId = String(n["@_childId"]);
|
|
128
|
+
if (n["@_shared"] != null) out.shared = bool(n["@_shared"]);
|
|
129
|
+
if (n["@_includeChildSelections"] != null) out.includeChildSelections = bool(n["@_includeChildSelections"]);
|
|
130
|
+
if (n["@_roundUp"] != null) out.roundUp = bool(n["@_roundUp"]);
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
function parseModifier(n) {
|
|
134
|
+
return {
|
|
135
|
+
type: String(n["@_type"] ?? ""),
|
|
136
|
+
field: String(n["@_field"] ?? ""),
|
|
137
|
+
value: n["@_value"] ?? "",
|
|
138
|
+
conditions: arr(n.conditions?.condition).map(parseCondition),
|
|
139
|
+
conditionGroups: arr(n.conditionGroups?.conditionGroup).map(parseConditionGroup),
|
|
140
|
+
repeats: arr(n.repeats?.repeat).map(parseRepeat)
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function parseSelectionEntry(n) {
|
|
144
|
+
return {
|
|
145
|
+
id: String(n["@_id"] ?? ""),
|
|
146
|
+
name: String(n["@_name"] ?? ""),
|
|
147
|
+
hidden: bool(n["@_hidden"]),
|
|
148
|
+
collective: bool(n["@_collective"]),
|
|
149
|
+
import: bool(n["@_import"]),
|
|
150
|
+
type: n["@_type"] ?? "upgrade",
|
|
151
|
+
...n["@_defaultAmount"] != null ? { defaultAmount: Number(n["@_defaultAmount"]) } : {},
|
|
152
|
+
constraints: arr(n.constraints?.constraint).map(parseConstraint),
|
|
153
|
+
profiles: arr(n.profiles?.profile).map(parseProfile),
|
|
154
|
+
rules: arr(n.rules?.rule).map(parseRule),
|
|
155
|
+
infoLinks: arr(n.infoLinks?.infoLink).map(parseInfoLink),
|
|
156
|
+
categoryLinks: arr(n.categoryLinks?.categoryLink).map(parseCategoryLink),
|
|
157
|
+
costs: arr(n.costs?.cost).map(parseCost),
|
|
158
|
+
modifiers: arr(n.modifiers?.modifier).map(parseModifier),
|
|
159
|
+
selectionEntries: arr(n.selectionEntries?.selectionEntry).map(parseSelectionEntry),
|
|
160
|
+
selectionEntryGroups: arr(n.selectionEntryGroups?.selectionEntryGroup).map(parseSelectionEntryGroup),
|
|
161
|
+
entryLinks: arr(n.entryLinks?.entryLink).map(parseEntryLink)
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function parseSelectionEntryGroup(n) {
|
|
165
|
+
return {
|
|
166
|
+
id: String(n["@_id"] ?? ""),
|
|
167
|
+
name: String(n["@_name"] ?? ""),
|
|
168
|
+
hidden: bool(n["@_hidden"]),
|
|
169
|
+
collective: bool(n["@_collective"]),
|
|
170
|
+
import: bool(n["@_import"]),
|
|
171
|
+
...n["@_defaultSelectionEntryId"] != null ? { defaultSelectionEntryId: String(n["@_defaultSelectionEntryId"]) } : {},
|
|
172
|
+
constraints: arr(n.constraints?.constraint).map(parseConstraint),
|
|
173
|
+
modifiers: arr(n.modifiers?.modifier).map(parseModifier),
|
|
174
|
+
selectionEntries: arr(n.selectionEntries?.selectionEntry).map(parseSelectionEntry),
|
|
175
|
+
selectionEntryGroups: arr(n.selectionEntryGroups?.selectionEntryGroup).map(parseSelectionEntryGroup),
|
|
176
|
+
entryLinks: arr(n.entryLinks?.entryLink).map(parseEntryLink)
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
function parseEntryLink(n) {
|
|
180
|
+
return {
|
|
181
|
+
id: String(n["@_id"] ?? ""),
|
|
182
|
+
name: String(n["@_name"] ?? ""),
|
|
183
|
+
hidden: bool(n["@_hidden"]),
|
|
184
|
+
collective: bool(n["@_collective"]),
|
|
185
|
+
import: bool(n["@_import"]),
|
|
186
|
+
targetId: String(n["@_targetId"] ?? ""),
|
|
187
|
+
type: String(n["@_type"] ?? ""),
|
|
188
|
+
...n["@_defaultAmount"] != null ? { defaultAmount: Number(n["@_defaultAmount"]) } : {},
|
|
189
|
+
...n.comment != null ? { comment: String(n.comment).trim() } : {},
|
|
190
|
+
constraints: arr(n.constraints?.constraint).map(parseConstraint),
|
|
191
|
+
costs: arr(n.costs?.cost).map(parseCost),
|
|
192
|
+
modifiers: arr(n.modifiers?.modifier).map(parseModifier),
|
|
193
|
+
profiles: arr(n.profiles?.profile).map(parseProfile),
|
|
194
|
+
infoLinks: arr(n.infoLinks?.infoLink).map(parseInfoLink),
|
|
195
|
+
categoryLinks: arr(n.categoryLinks?.categoryLink).map(parseCategoryLink),
|
|
196
|
+
selectionEntries: arr(n.selectionEntries?.selectionEntry).map(parseSelectionEntry),
|
|
197
|
+
selectionEntryGroups: arr(n.selectionEntryGroups?.selectionEntryGroup).map(parseSelectionEntryGroup),
|
|
198
|
+
entryLinks: arr(n.entryLinks?.entryLink).map(parseEntryLink)
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function parseForceCategoryLink(n) {
|
|
202
|
+
return {
|
|
203
|
+
id: String(n["@_id"] ?? ""),
|
|
204
|
+
targetId: String(n["@_targetId"] ?? ""),
|
|
205
|
+
primary: bool(n["@_primary"]),
|
|
206
|
+
hidden: bool(n["@_hidden"]),
|
|
207
|
+
constraints: arr(n.constraints?.constraint).map(parseConstraint)
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function parseForceEntry(n) {
|
|
211
|
+
return {
|
|
212
|
+
id: String(n["@_id"] ?? ""),
|
|
213
|
+
name: String(n["@_name"] ?? ""),
|
|
214
|
+
hidden: bool(n["@_hidden"]),
|
|
215
|
+
categoryLinks: arr(n.categoryLinks?.categoryLink).map(parseForceCategoryLink),
|
|
216
|
+
rules: arr(n.rules?.rule).map(parseRule),
|
|
217
|
+
modifiers: arr(n.modifiers?.modifier).map(parseModifier),
|
|
218
|
+
forceEntries: arr(n.forceEntries?.forceEntry).map(parseForceEntry)
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function parseCatalogueLink(n) {
|
|
222
|
+
return {
|
|
223
|
+
id: String(n["@_id"] ?? ""),
|
|
224
|
+
name: String(n["@_name"] ?? ""),
|
|
225
|
+
targetId: String(n["@_targetId"] ?? ""),
|
|
226
|
+
type: String(n["@_type"] ?? ""),
|
|
227
|
+
importRootEntries: bool(n["@_importRootEntries"])
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
function parseCatalogueRoot(r) {
|
|
231
|
+
return {
|
|
232
|
+
costTypes: arr(r.costTypes?.costType).map((n) => ({
|
|
233
|
+
id: String(n["@_id"] ?? ""),
|
|
234
|
+
name: String(n["@_name"] ?? ""),
|
|
235
|
+
defaultCostLimit: Number(n["@_defaultCostLimit"] ?? -1)
|
|
236
|
+
})),
|
|
237
|
+
profileTypes: arr(r.profileTypes?.profileType).map((n) => ({
|
|
238
|
+
id: String(n["@_id"] ?? ""),
|
|
239
|
+
name: String(n["@_name"] ?? ""),
|
|
240
|
+
characteristicTypes: arr(n.characteristicTypes?.characteristicType).map((c) => ({
|
|
241
|
+
id: String(c["@_id"] ?? ""),
|
|
242
|
+
name: String(c["@_name"] ?? "")
|
|
243
|
+
}))
|
|
244
|
+
})),
|
|
245
|
+
categoryEntries: arr(r.categoryEntries?.categoryEntry).map((n) => ({
|
|
246
|
+
id: String(n["@_id"] ?? ""),
|
|
247
|
+
name: String(n["@_name"] ?? ""),
|
|
248
|
+
hidden: bool(n["@_hidden"])
|
|
249
|
+
})),
|
|
250
|
+
rules: arr(r.rules?.rule).map(parseRule),
|
|
251
|
+
sharedRules: arr(r.sharedRules?.rule).map(parseRule),
|
|
252
|
+
sharedProfiles: arr(r.sharedProfiles?.profile).map(parseProfile),
|
|
253
|
+
selectionEntries: arr(r.selectionEntries?.selectionEntry).map(parseSelectionEntry),
|
|
254
|
+
sharedSelectionEntries: arr(r.sharedSelectionEntries?.selectionEntry).map(parseSelectionEntry),
|
|
255
|
+
sharedSelectionEntryGroups: arr(r.sharedSelectionEntryGroups?.selectionEntryGroup).map(parseSelectionEntryGroup),
|
|
256
|
+
entryLinks: arr(r.entryLinks?.entryLink).map(parseEntryLink),
|
|
257
|
+
forceEntries: arr(r.forceEntries?.forceEntry).map(parseForceEntry)
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
function parseFile(filename, xml) {
|
|
261
|
+
const doc = xmlParser.parse(xml);
|
|
262
|
+
const isGst = "gameSystem" in doc;
|
|
263
|
+
const root = isGst ? doc.gameSystem : doc.catalogue;
|
|
264
|
+
const kind = isGst ? "gameSystem" : "catalogue";
|
|
265
|
+
return {
|
|
266
|
+
filename,
|
|
267
|
+
kind,
|
|
268
|
+
id: String(root["@_id"] ?? ""),
|
|
269
|
+
name: String(root["@_name"] ?? ""),
|
|
270
|
+
...root["@_gameSystemId"] != null ? { gameSystemId: String(root["@_gameSystemId"]) } : {},
|
|
271
|
+
catalogueLinks: arr(root.catalogueLinks?.catalogueLink).map(parseCatalogueLink),
|
|
272
|
+
root: parseCatalogueRoot(root)
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/parse/index.ts
|
|
277
|
+
function parseToIr(files) {
|
|
278
|
+
const parsed = Object.entries(files).map(([filename, xml]) => parseFile(filename, xml));
|
|
279
|
+
const gameSystem = parsed.find((f) => f.kind === "gameSystem");
|
|
280
|
+
if (!gameSystem) throw new Error("parseToIr: no .gst file in source set");
|
|
281
|
+
return {
|
|
282
|
+
gameSystem,
|
|
283
|
+
catalogues: parsed.filter((f) => f.kind === "catalogue")
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// src/project/catalogue.ts
|
|
288
|
+
function projectCatalogue(_ir) {
|
|
289
|
+
return {};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/index.ts
|
|
293
|
+
function buildCatalogue(files) {
|
|
294
|
+
const ir = parseToIr(files);
|
|
295
|
+
return projectCatalogue(ir);
|
|
296
|
+
}
|
|
297
|
+
export {
|
|
298
|
+
buildCatalogue,
|
|
299
|
+
parseToIr,
|
|
300
|
+
projectCatalogue
|
|
301
|
+
};
|
|
302
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/parse/xml.ts","../src/parse/index.ts","../src/project/catalogue.ts","../src/index.ts"],"sourcesContent":["import { XMLParser } from 'fast-xml-parser'\nimport type {\n IrCatalogueFile, IrCatalogueRoot,\n IrConstraint, IrCost, IrProfile, IrCharacteristic,\n IrRule, IrInfoLink, IrCategoryLink,\n IrModifier, IrCondition, IrConditionGroup, IrRepeat,\n IrSelectionEntry, IrSelectionEntryGroup, IrEntryLink,\n IrCostType, IrProfileType, IrCategoryEntry,\n IrEntryType,\n IrForceCategoryLink, IrForceEntry,\n IrCatalogueLink,\n} from '../ir/types.ts'\n\nconst ALWAYS_ARRAY = new Set([\n 'selectionEntry', 'selectionEntryGroup', 'entryLink',\n 'constraint', 'profile', 'characteristic', 'characteristicType',\n 'cost', 'costType', 'profileType',\n 'rule', 'categoryLink', 'categoryEntry',\n 'infoLink', 'modifier', 'condition', 'conditionGroup', 'repeat',\n 'forceEntry', 'catalogueLink',\n])\n\nconst xmlParser = new XMLParser({\n ignoreAttributes: false,\n attributeNamePrefix: '@_',\n textNodeName: '#text',\n isArray: (name) => ALWAYS_ARRAY.has(name),\n parseAttributeValue: true,\n})\n\n// ---- helpers ----\n\nfunction arr<T>(v: unknown): T[] {\n if (!v) return []\n if (Array.isArray(v)) return v as T[]\n return [v as T]\n}\n\nfunction bool(v: unknown): boolean {\n return v === true || v === 'true'\n}\n\n// ---- primitive node parsers ----\n\nfunction parseConstraint(n: any): IrConstraint {\n return {\n id: String(n['@_id'] ?? ''),\n type: n['@_type'] === 'min' ? 'min' : 'max',\n value: Number(n['@_value'] ?? 0),\n scope: String(n['@_scope'] ?? ''),\n field: String(n['@_field'] ?? 'selections'),\n shared: bool(n['@_shared']),\n includeChildSelections: bool(n['@_includeChildSelections']),\n }\n}\n\nfunction parseCost(n: any): IrCost {\n return {\n name: String(n['@_name'] ?? ''),\n typeId: String(n['@_typeId'] ?? ''),\n value: Number(n['@_value'] ?? 0),\n }\n}\n\nfunction parseCharacteristic(n: any): IrCharacteristic {\n const raw = n['#text']\n return {\n name: String(n['@_name'] ?? ''),\n typeId: String(n['@_typeId'] ?? ''),\n value: raw != null ? String(raw) : '',\n }\n}\n\nfunction parseProfile(n: any): IrProfile {\n return {\n id: String(n['@_id'] ?? ''),\n name: String(n['@_name'] ?? ''),\n hidden: bool(n['@_hidden']),\n typeId: String(n['@_typeId'] ?? ''),\n typeName: String(n['@_typeName'] ?? ''),\n characteristics: arr<any>(n.characteristics?.characteristic).map(parseCharacteristic),\n }\n}\n\nfunction parseRule(n: any): IrRule {\n return {\n id: String(n['@_id'] ?? ''),\n name: String(n['@_name'] ?? ''),\n hidden: bool(n['@_hidden']),\n description: n.description != null ? String(n.description) : '',\n }\n}\n\nfunction parseInfoLink(n: any): IrInfoLink {\n return {\n id: String(n['@_id'] ?? ''),\n name: String(n['@_name'] ?? ''),\n hidden: bool(n['@_hidden']),\n targetId: String(n['@_targetId'] ?? ''),\n type: String(n['@_type'] ?? ''),\n }\n}\n\nfunction parseCategoryLink(n: any): IrCategoryLink {\n return {\n id: String(n['@_id'] ?? ''),\n targetId: String(n['@_targetId'] ?? ''),\n primary: bool(n['@_primary']),\n }\n}\n\nfunction parseCondition(n: any): IrCondition {\n const out: IrCondition = {\n type: String(n['@_type'] ?? ''),\n value: Number(n['@_value'] ?? 0),\n field: String(n['@_field'] ?? ''),\n scope: String(n['@_scope'] ?? ''),\n }\n if (n['@_childId'] != null) out.childId = String(n['@_childId'])\n if (n['@_shared'] != null) out.shared = bool(n['@_shared'])\n if (n['@_includeChildSelections'] != null) out.includeChildSelections = bool(n['@_includeChildSelections'])\n if (n['@_percentValue'] != null) out.percentValue = bool(n['@_percentValue'])\n return out\n}\n\nfunction parseConditionGroup(n: any): IrConditionGroup {\n return {\n type: String(n['@_type'] ?? ''),\n conditions: arr<any>(n.conditions?.condition).map(parseCondition),\n conditionGroups: arr<any>(n.conditionGroups?.conditionGroup).map(parseConditionGroup),\n }\n}\n\nfunction parseRepeat(n: any): IrRepeat {\n const out: IrRepeat = {\n value: Number(n['@_value'] ?? 0),\n repeats: Number(n['@_repeats'] ?? 0),\n field: String(n['@_field'] ?? ''),\n scope: String(n['@_scope'] ?? ''),\n }\n if (n['@_childId'] != null) out.childId = String(n['@_childId'])\n if (n['@_shared'] != null) out.shared = bool(n['@_shared'])\n if (n['@_includeChildSelections'] != null) out.includeChildSelections = bool(n['@_includeChildSelections'])\n if (n['@_roundUp'] != null) out.roundUp = bool(n['@_roundUp'])\n return out\n}\n\nfunction parseModifier(n: any): IrModifier {\n return {\n type: String(n['@_type'] ?? ''),\n field: String(n['@_field'] ?? ''),\n value: n['@_value'] ?? '',\n conditions: arr<any>(n.conditions?.condition).map(parseCondition),\n conditionGroups: arr<any>(n.conditionGroups?.conditionGroup).map(parseConditionGroup),\n repeats: arr<any>(n.repeats?.repeat).map(parseRepeat),\n }\n}\n\n// ---- structural node parsers (mutually recursive) ----\n\nfunction parseSelectionEntry(n: any): IrSelectionEntry {\n return {\n id: String(n['@_id'] ?? ''),\n name: String(n['@_name'] ?? ''),\n hidden: bool(n['@_hidden']),\n collective: bool(n['@_collective']),\n import: bool(n['@_import']),\n type: (n['@_type'] ?? 'upgrade') as IrEntryType,\n ...(n['@_defaultAmount'] != null ? { defaultAmount: Number(n['@_defaultAmount']) } : {}),\n constraints: arr<any>(n.constraints?.constraint).map(parseConstraint),\n profiles: arr<any>(n.profiles?.profile).map(parseProfile),\n rules: arr<any>(n.rules?.rule).map(parseRule),\n infoLinks: arr<any>(n.infoLinks?.infoLink).map(parseInfoLink),\n categoryLinks: arr<any>(n.categoryLinks?.categoryLink).map(parseCategoryLink),\n costs: arr<any>(n.costs?.cost).map(parseCost),\n modifiers: arr<any>(n.modifiers?.modifier).map(parseModifier),\n selectionEntries: arr<any>(n.selectionEntries?.selectionEntry).map(parseSelectionEntry),\n selectionEntryGroups: arr<any>(n.selectionEntryGroups?.selectionEntryGroup).map(parseSelectionEntryGroup),\n entryLinks: arr<any>(n.entryLinks?.entryLink).map(parseEntryLink),\n }\n}\n\nfunction parseSelectionEntryGroup(n: any): IrSelectionEntryGroup {\n return {\n id: String(n['@_id'] ?? ''),\n name: String(n['@_name'] ?? ''),\n hidden: bool(n['@_hidden']),\n collective: bool(n['@_collective']),\n import: bool(n['@_import']),\n ...(n['@_defaultSelectionEntryId'] != null\n ? { defaultSelectionEntryId: String(n['@_defaultSelectionEntryId']) }\n : {}),\n constraints: arr<any>(n.constraints?.constraint).map(parseConstraint),\n modifiers: arr<any>(n.modifiers?.modifier).map(parseModifier),\n selectionEntries: arr<any>(n.selectionEntries?.selectionEntry).map(parseSelectionEntry),\n selectionEntryGroups: arr<any>(n.selectionEntryGroups?.selectionEntryGroup).map(parseSelectionEntryGroup),\n entryLinks: arr<any>(n.entryLinks?.entryLink).map(parseEntryLink),\n }\n}\n\nfunction parseEntryLink(n: any): IrEntryLink {\n return {\n id: String(n['@_id'] ?? ''),\n name: String(n['@_name'] ?? ''),\n hidden: bool(n['@_hidden']),\n collective: bool(n['@_collective']),\n import: bool(n['@_import']),\n targetId: String(n['@_targetId'] ?? ''),\n type: String(n['@_type'] ?? ''),\n ...(n['@_defaultAmount'] != null ? { defaultAmount: Number(n['@_defaultAmount']) } : {}),\n ...(n.comment != null ? { comment: String(n.comment).trim() } : {}),\n constraints: arr<any>(n.constraints?.constraint).map(parseConstraint),\n costs: arr<any>(n.costs?.cost).map(parseCost),\n modifiers: arr<any>(n.modifiers?.modifier).map(parseModifier),\n profiles: arr<any>(n.profiles?.profile).map(parseProfile),\n infoLinks: arr<any>(n.infoLinks?.infoLink).map(parseInfoLink),\n categoryLinks: arr<any>(n.categoryLinks?.categoryLink).map(parseCategoryLink),\n selectionEntries: arr<any>(n.selectionEntries?.selectionEntry).map(parseSelectionEntry),\n selectionEntryGroups: arr<any>(n.selectionEntryGroups?.selectionEntryGroup).map(parseSelectionEntryGroup),\n entryLinks: arr<any>(n.entryLinks?.entryLink).map(parseEntryLink),\n }\n}\n\n// ---- force entry parsers ----\n\nfunction parseForceCategoryLink(n: any): IrForceCategoryLink {\n return {\n id: String(n['@_id'] ?? ''),\n targetId: String(n['@_targetId'] ?? ''),\n primary: bool(n['@_primary']),\n hidden: bool(n['@_hidden']),\n constraints: arr<any>(n.constraints?.constraint).map(parseConstraint),\n }\n}\n\nfunction parseForceEntry(n: any): IrForceEntry {\n return {\n id: String(n['@_id'] ?? ''),\n name: String(n['@_name'] ?? ''),\n hidden: bool(n['@_hidden']),\n categoryLinks: arr<any>(n.categoryLinks?.categoryLink).map(parseForceCategoryLink),\n rules: arr<any>(n.rules?.rule).map(parseRule),\n modifiers: arr<any>(n.modifiers?.modifier).map(parseModifier),\n forceEntries: arr<any>(n.forceEntries?.forceEntry).map(parseForceEntry),\n }\n}\n\n// ---- catalogue link parser ----\n\nfunction parseCatalogueLink(n: any): IrCatalogueLink {\n return {\n id: String(n['@_id'] ?? ''),\n name: String(n['@_name'] ?? ''),\n targetId: String(n['@_targetId'] ?? ''),\n type: String(n['@_type'] ?? ''),\n importRootEntries: bool(n['@_importRootEntries']),\n }\n}\n\n// ---- catalogue root ----\n\nfunction parseCatalogueRoot(r: any): IrCatalogueRoot {\n return {\n costTypes: arr<any>(r.costTypes?.costType).map((n: any): IrCostType => ({\n id: String(n['@_id'] ?? ''),\n name: String(n['@_name'] ?? ''),\n defaultCostLimit: Number(n['@_defaultCostLimit'] ?? -1),\n })),\n profileTypes: arr<any>(r.profileTypes?.profileType).map((n: any): IrProfileType => ({\n id: String(n['@_id'] ?? ''),\n name: String(n['@_name'] ?? ''),\n characteristicTypes: arr<any>(n.characteristicTypes?.characteristicType).map((c: any) => ({\n id: String(c['@_id'] ?? ''),\n name: String(c['@_name'] ?? ''),\n })),\n })),\n categoryEntries: arr<any>(r.categoryEntries?.categoryEntry).map((n: any): IrCategoryEntry => ({\n id: String(n['@_id'] ?? ''),\n name: String(n['@_name'] ?? ''),\n hidden: bool(n['@_hidden']),\n })),\n rules: arr<any>(r.rules?.rule).map(parseRule),\n sharedRules: arr<any>(r.sharedRules?.rule).map(parseRule),\n sharedProfiles: arr<any>(r.sharedProfiles?.profile).map(parseProfile),\n selectionEntries: arr<any>(r.selectionEntries?.selectionEntry).map(parseSelectionEntry),\n sharedSelectionEntries: arr<any>(r.sharedSelectionEntries?.selectionEntry).map(parseSelectionEntry),\n sharedSelectionEntryGroups: arr<any>(r.sharedSelectionEntryGroups?.selectionEntryGroup).map(parseSelectionEntryGroup),\n entryLinks: arr<any>(r.entryLinks?.entryLink).map(parseEntryLink),\n forceEntries: arr<any>(r.forceEntries?.forceEntry).map(parseForceEntry),\n }\n}\n\n// ---- public entry ----\n\nexport function parseFile(filename: string, xml: string): IrCatalogueFile {\n const doc = xmlParser.parse(xml)\n const isGst = 'gameSystem' in doc\n const root = isGst ? doc.gameSystem : doc.catalogue\n const kind: 'gameSystem' | 'catalogue' = isGst ? 'gameSystem' : 'catalogue'\n return {\n filename,\n kind,\n id: String(root['@_id'] ?? ''),\n name: String(root['@_name'] ?? ''),\n ...(root['@_gameSystemId'] != null ? { gameSystemId: String(root['@_gameSystemId']) } : {}),\n catalogueLinks: arr<any>(root.catalogueLinks?.catalogueLink).map(parseCatalogueLink),\n root: parseCatalogueRoot(root),\n }\n}\n","import type { Ir } from '../ir/types.ts'\nimport { parseFile } from './xml.ts'\n\nexport function parseToIr(files: Record<string, string>): Ir {\n const parsed = Object.entries(files).map(([filename, xml]) => parseFile(filename, xml))\n const gameSystem = parsed.find(f => f.kind === 'gameSystem')\n if (!gameSystem) throw new Error('parseToIr: no .gst file in source set')\n return {\n gameSystem,\n catalogues: parsed.filter(f => f.kind === 'catalogue'),\n }\n}\n","import type { Ir } from '../ir/types.ts'\n\n/**\n * IR -> downstream projection.\n *\n * Projects the faithful IR into the flattened, ready-to-consume shape the downstream application\n * needs. ALL domain interpretation lives here; the IR and parser stay domain-agnostic. The concrete\n * projector and its reference data are supplied locally and are never committed to this repo (see\n * README) -- this repo ships the framework, not any specific data model.\n *\n * \"Superset\" is the contract: the projection MUST reproduce every field the reference output carries\n * (so the existing consumer keeps working unchanged), and MAY add fields the current consumer lacks.\n * The golden-diff harness asserts the reproduction half; new fields are additive and ignored.\n *\n * STUB. Supply a local implementation that imports your projector modules. The local file is\n * gitignored so it never reaches this public repo. See CLAUDE.md for the hygiene rule.\n */\nexport function projectCatalogue(_ir: Ir): Record<string, unknown> {\n return {}\n}\n","import { parseToIr } from './parse/index.ts'\nimport { projectCatalogue } from './project/catalogue.ts'\n\n/**\n * Public entry point. `filename -> XML` map in, projected output object out. The signature matches\n * the downstream consumer's existing build step so this can be wired in incrementally (swap the\n * import, keep the golden diff green) without touching the consumer.\n */\nexport function buildCatalogue(files: Record<string, string>): Record<string, unknown> {\n const ir = parseToIr(files)\n return projectCatalogue(ir)\n}\n\nexport { parseToIr } from './parse/index.ts'\nexport { projectCatalogue } from './project/catalogue.ts'\nexport type { Ir, IrCatalogueFile } from './ir/types.ts'\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "bsdata-parser",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Faithful BSData .gst/.cat XML parser: parses game-data source files into a typed IR and exposes a golden-diff harness for validating a downstream projection.",
|
|
7
|
+
"keywords": ["bsdata", "battlescribe", "parser", "xml", "game-data"],
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/aodhan-dev/bsdata-parser.git"
|
|
11
|
+
},
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"import": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"dist"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"prepublishOnly": "git diff --quiet HEAD -- src/project/catalogue.ts || (echo 'ERROR: catalogue.ts has local changes (skip-worktree). Publish via the CI publish workflow on a version tag, not locally.' && exit 1)",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"test:watch": "vitest",
|
|
28
|
+
"typecheck": "tsc --noEmit"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@types/node": "^20.19.41",
|
|
32
|
+
"tsup": "^8.5.1",
|
|
33
|
+
"tsx": "^4.0.0",
|
|
34
|
+
"typescript": "^5.3.0",
|
|
35
|
+
"vitest": "^1.6.1"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"fast-xml-parser": "^4.5.6"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=20"
|
|
42
|
+
}
|
|
43
|
+
}
|