cpp-xer-parser 0.6.1__py3-none-any.whl

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.
@@ -0,0 +1,287 @@
1
+ Metadata-Version: 2.4
2
+ Name: cpp-xer-parser
3
+ Version: 0.6.1
4
+ Summary: Parse and write Primavera P6 XER files in pure Python: every table, calendars, cross-reference maps, half-step XERs and DCMA-14 profiles.
5
+ Author: Dana Fitkowski
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/danafitkowski/cpp-xer-parser
8
+ Project-URL: Changelog, https://github.com/danafitkowski/cpp-xer-parser/blob/main/CHANGELOG.md
9
+ Project-URL: Issues, https://github.com/danafitkowski/cpp-xer-parser/issues
10
+ Keywords: primavera,p6,xer,schedule,cpm,construction,forensic,dcma
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Other Audience
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Programming Language :: Python :: 3.15
23
+ Classifier: Topic :: Office/Business :: Scheduling
24
+ Classifier: Topic :: File Formats
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Dynamic: license-file
29
+
30
+ # cpp-xer-parser
31
+
32
+ [![license: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/danafitkowski/cpp-xer-parser/blob/main/LICENSE)
33
+ [![python: 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
34
+ [![tests](https://github.com/danafitkowski/cpp-xer-parser/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/danafitkowski/cpp-xer-parser/actions/workflows/test.yml)
35
+ [![version: 0.6.1](https://img.shields.io/badge/version-0.6.1-blue.svg)](https://github.com/danafitkowski/cpp-xer-parser/blob/main/CHANGELOG.md)
36
+
37
+ A standalone parser and generator for Primavera P6 XER files. Pure Python 3.10+, MIT-licensed, no third-party runtime dependencies.
38
+
39
+ Maintained by [Critical Path Partners](https://criticalpathpartners.ca) — a forensic-scheduling consultancy.
40
+
41
+ Companion to [`cpp-cpm-engine`](https://github.com/danafitkowski/cpp-cpm-engine).
42
+
43
+ ---
44
+
45
+ ## Scope: what this is, and what it is not
46
+
47
+ **What this is.** A complete XER reader and writer. It parses every table in an export into structured Python data, builds the cross-reference maps (WBS, resources, logic, activity codes, user-defined fields), decodes calendars, and writes XER files back out in P6 24.12 table order. Every function in the feature table below is implemented in this repository. None of them is a shim that calls out to something private. Vendor it, fork it, or import it.
48
+
49
+ **What this is not.** It is not the whole toolchain Critical Path Partners runs. CPP's forensic deliverables are produced with additional validation that is kept private and is not part of this package. Named in capability terms, this package does not include:
50
+
51
+ - **A date-field validation gate.** Date fields are parsed as they appear. There is no pass that flags unparseable or implausible date values before downstream math consumes them.
52
+ - **A calendar-decode warning channel.** `parse_calendar_data` decodes the standard P6 encoding, with time slots in either of the two field orders P6 writes, and then falls through two legacy patterns. When all of them fail it returns an empty work week rather than raising or warning, and the working-day helpers then count that calendar as Mon-Fri. Code that needs that signal has to inspect the returned structure itself. A corrupt calendar record is not reported to the caller either: a record P6 cannot bind (finish-first time slots under an illegal `clndr_type`) is decoded here as the week it declares, while P6 schedules its activities on its default Standard calendar, which is what `cpp-cpm-engine`'s calendar decoder does too.
53
+ - **Working-day arithmetic beyond `get_work_days_between` and `duration_hours_to_days`.** The additional working-day delta helper in CPP's internal parser is not included here.
54
+
55
+ None of that makes this parser wrong. It makes it smaller. If you are comparing output from this package against a report CPP produced, the difference in validation surface is the thing to ask about, and the answer is in this section rather than in a footnote.
56
+
57
+ ---
58
+
59
+ ## Why this parser
60
+
61
+ The XER format is fragile. Field counts shift between P6 versions. Calendar data ships inside a nested-paren mini-language. BOMs come and go without warning. Real-world exports from large programs use formats that simple split-on-tab parsers silently mangle.
62
+
63
+ It comes out of forensic schedule analysis, where a silently mangled field becomes a wrong number in a report that someone has to defend. The design goals follow from that: fields round-trip through parse and generate, the calendar encodings real exports actually use are decoded rather than guessed at, and generated files carry the field counts P6 expects on import. `tests/` is what backs those goals. Read it before relying on them.
64
+
65
+ ---
66
+
67
+ ## Install
68
+
69
+ ```bash
70
+ pip install cpp-xer-parser
71
+ ```
72
+
73
+ Installed, the modules are the package `cpp_xer_parser`. The command `cpp-xer-parser` runs the same command line as `python scripts/xer_parser.py`.
74
+
75
+ Or use a clone with no install: no external dependencies, just put `scripts/` on your `sys.path`.
76
+
77
+ ```bash
78
+ git clone https://github.com/danafitkowski/cpp-xer-parser
79
+ cd cpp-xer-parser
80
+ ```
81
+
82
+ The parser is pure Python 3.10+ with no third-party runtime dependencies.
83
+
84
+ ---
85
+
86
+ ## Quick start
87
+
88
+ ```python
89
+ from cpp_xer_parser import parse_xer, print_summary, get_table, build_wbs_map
90
+ # From a clone instead: sys.path.insert(0, 'scripts'), then
91
+ # from xer_parser import parse_xer, print_summary, get_table, build_wbs_map
92
+
93
+ # Parse
94
+ data = parse_xer('path/to/your.xer')
95
+
96
+ # Print a one-shot summary
97
+ print_summary(data)
98
+
99
+ # Drill into tables
100
+ tasks = get_table(data, 'TASK')
101
+ print(f'{len(tasks)} activities')
102
+
103
+ # Cross-reference maps
104
+ wbs = build_wbs_map(data)
105
+ for t in tasks[:3]:
106
+ print(t['task_code'], '→', wbs[t['wbs_id']]['_full_path'])
107
+ ```
108
+
109
+ ---
110
+
111
+ ## What it does
112
+
113
+ | Feature | Function |
114
+ |--------------------------------------|-----------------------------------------------------------------------|
115
+ | Parse XER to structured dict | `parse_xer(path)` |
116
+ | Generate XER from dict | `generate_xer(data, path, p6_version='24.12', currency='CAD', ...)` |
117
+ | Table accessor | `get_table(data, 'TASK')` |
118
+ | Calendar map (with holidays) | `get_calendar_map(data)` |
119
+ | Activity calendars, blank id = project calendar | `resolve_task_calendars(data)`, `with_resolved_calendars(tasks, resolution)` |
120
+ | Calendar-resolution disclosure block | `calendar_resolution_block(resolution, keep=None)` |
121
+ | Calendars converted on their shifts | `calendar_shift_hours(cal_map)` |
122
+ | Corrupt calendar records, P6's fallback | `calendar_corrupt_fallbacks(cal_map)` |
123
+ | WBS hierarchy with full paths | `build_wbs_map(data)` |
124
+ | Resource assignments per task | `build_resource_map(data)` |
125
+ | Assignments as P6 schedules resource dependent work | `resource_dependent_assignments(data)` |
126
+ | Predecessor / successor maps | `build_predecessor_map(data)` |
127
+ | Activity code map | `build_activity_code_map(data)` |
128
+ | User-defined field map | `build_udf_map(data)` |
129
+ | Work-day count between two dates | `get_work_days_between(start, end, calendar)` |
130
+ | Duration hours-to-days conversion | `duration_hours_to_days(hours, calendar)` |
131
+ | Structural validation report | `validate_schedule(data, profile='commercial')` |
132
+ | Schedule structure score | `aace_31r_compliance(data, profile='commercial')` |
133
+ | Schedule integrity manifest † | `generate_xer_manifest(data, xer_path=None, **manifest_kwargs)` |
134
+ | Schema drift between two XERs | `schema_diff(data_a, data_b)` |
135
+ | Half-step XER generator (SmartPM-equivalent) | `compute_half_step_xer(base_path, updated_path, output_path)` |
136
+
137
+ † `validate_schedule` and `aace_31r_compliance` run from a plain clone: they use the bundled `scripts/validation.py` and `scripts/config_profiles.py`. `generate_xer_manifest` also needs CPP's internal audit-trail module, which is **not** bundled here. Without it the call raises `RuntimeError` rather than returning a manifest with missing provenance. Everything else in the table needs nothing but the standard library.
138
+
139
+ ---
140
+
141
+ ## The data date P6 schedules from
142
+
143
+ `parse_xer` reads `PROJECT.last_recalc_date` as P6 schedules from it, and lists every project it changed in `data['data_date_filled']`, so a report can say the date was not the file's own.
144
+
145
+ - A blank data date is the project's planned start: P6 schedules "from the project data date, using the project start date as the initial data date". The entry reads `{'proj_id', 'last_recalc_date', 'filled_from': 'plan_start_date'}`. A blank with no planned start stays blank.
146
+ - A filed data date on an earlier day than the planned start is lifted to the planned start, as P6 lifts it when it opens the project (data date 28 August, planned start 1 September: P6 shows and schedules from 1 September). The entry also carries the filed date as `filed_data_date`. A planned start later the same day is left as filed.
147
+
148
+ ---
149
+
150
+ ## Blank activity calendar ids
151
+
152
+ A blank `TASK.clndr_id` means the project calendar, not "no calendar". MPXJ writes it blank for every task of a converted MS Project file that has no task-level calendar. Looked up as it stands, `cal_map.get(task['clndr_id'])` returns `None`, and whatever runs next falls to its last resort: 8 h/day in `duration_hours_to_days`, without a word, and in `cpp-cpm-engine` a continuous seven-day week, with an ALERT, unless the caller hands it the project calendar. Take the TASK rows through the resolver before looking a calendar up:
153
+
154
+ ```python
155
+ from cpp_xer_parser.xer_parser import (
156
+ get_table, get_calendar_map, duration_hours_to_days,
157
+ resolve_task_calendars, with_resolved_calendars)
158
+
159
+ resolution = resolve_task_calendars(data)
160
+ tasks = with_resolved_calendars(get_table(data, 'TASK'), resolution)
161
+ cal_map = get_calendar_map(data)
162
+ for t in tasks:
163
+ float_days = duration_hours_to_days(t.get('total_float_hr_cnt'),
164
+ cal_map.get(t.get('clndr_id', '')))
165
+ ```
166
+
167
+ - `resolve_task_calendars` applies the chain `TASK.clndr_id` → `PROJECT.clndr_id` for the row's `proj_id` → the calendar flagged `default_flag = 'Y'`.
168
+ - `with_resolved_calendars` fills only blank ids. A filled row is a copy and every other row is passed through as it is, so the parsed data stays as received.
169
+ - `resolution['unresolved']` lists every row left with no usable calendar: a blank id with nothing to fall back on, or an id the CALENDAR table does not declare, which is reported and never replaced.
170
+ - `validate_schedule` raises a BLOCK `XER-TASK-CALENDAR-UNRESOLVED` for those rows, and an INFO `XER-TASK-CALENDAR-FALLBACK` when blank ids resolved. `generate_summary` converts float on the resolved calendar.
171
+ - A consumer that works on only some of the rows (one project, no LOE or WBS summaries) builds what it discloses with `calendar_resolution_block(resolution, keep=...)`.
172
+ - `get_calendar_map` entries carry the row's `default_flag`.
173
+ - A calendar's `hours_per_day` is the hours its shifts work when every working day of its week works the same number of hours and `day_hr_cnt` says otherwise. P6 schedules a duration against the shifts; `day_hr_cnt` ("Hours per Day") only converts hours to days for display. A Monday-Saturday 07:00-17:00 calendar declaring 8 ran a 40 h activity over four days on P6's own F9, and `duration_hours_to_days` now gives 4. The file's figure is kept as `declared_hours_per_day`, `calendar_shift_hours(cal_map)` lists every such calendar, and a `CalendarShiftHoursWarning` names each. A week whose working days work different hours keeps `day_hr_cnt`.
174
+ - An empty calendar record (no `clndr_data`) is P6's internal Standard: Monday to Friday, 08:00-16:00, eight hours a day, no holidays, whatever `day_hr_cnt` declares. Records declaring 10, 6 and 24 hours all ran the same 08:00-16:00 days on P6's own F9. The entry carries `empty_record: True` and keeps the declared figure as `declared_hours_per_day`; `calendar_shift_hours` does not list it, since it has no shifts.
175
+ - A corrupt calendar record is P6's internal Standard too: Monday to Friday, eight hours a day, no holidays. A record is corrupt when its week's shift slots are written finish-first, `(f|HH:MM|s|HH:MM)`, and its `clndr_type` is not a legal P6 token (`CA_Base`, `CA_Rsrc`, `CA_Project`); the one seen in real exports says `CT_Project`. P6 cannot bind such a record and schedules its activities on that Standard, as its own stored dates show. The entry carries `p6_fallback: True` and the `calendar-corrupt-p6-fallback` ALERT under `corrupt_record_alert`; `calendar_corrupt_fallbacks(cal_map)` lists the alerts, and a `CalendarCorruptRecordWarning` names each calendar. Finish-first slots with a legal type are an ordinary export variant and decode as declared. The screen is `cpp-cpm-engine`'s own `decode_calendar_record`, copied into the parser.
176
+
177
+ ---
178
+
179
+ ## Half-step XER (vendor-equivalent: SmartPM/Plannex)
180
+
181
+ The most analytically interesting feature in the repo. The `compute_half_step_xer` function implements the bifurcation procedure of AACE 29R-03 §2.3.D.2 ("Bifurcation: Creating a Progress-Only Half-Step Update" — the RP itself names the procedure "half-stepping or two-stepping"), which is how MIP 3.4 (Observational / Dynamic / Contemporaneous Split) splits each update. Vendor half-step tools (SmartPM, Plannex) are equivalents of this procedure. It takes a period-start schedule plus the next period's update, and produces a schedule that has **only the progress fields** copied from the updated schedule onto the base structure.
182
+
183
+ The forensic value: anything that moves in the half-step moved because work didn't happen as planned. Anything that moves between the half-step and the full update moved because the contractor revised logic or scope.
184
+
185
+ ```python
186
+ from cpp_xer_parser import compute_half_step_xer
187
+
188
+ result = compute_half_step_xer(
189
+ base_xer_path='period_start.xer',
190
+ updated_xer_path='period_end.xer',
191
+ output_xer_path='half_step.xer',
192
+ )
193
+
194
+ print(f"Matched: {result['matched_count']}")
195
+ print(f"Progressed: {result['progressed_count']}")
196
+ print(f"Activities added in updated (NOT copied): {result['unmatched_in_updated']}")
197
+ print(f"Activities removed in updated (preserved in output): {result['unmatched_in_base']}")
198
+ ```
199
+
200
+ `unmatched_in_updated` is forensically significant: these are activities the contractor added between period boundaries, and they belong to the logic-revision layer, not the progress layer.
201
+
202
+ ---
203
+
204
+ ## XER generation rules (P6 24.12)
205
+
206
+ The generator emits P6-importable XER files following these rules:
207
+
208
+ 1. **Table order**: CURRTYPE, FINTMPL, OBS, PROJECT, CALENDAR, SCHEDOPTIONS, PROJWBS, TASK, TASKPRED, then remaining tables. See the `TABLE_ORDER` constant. This is a generation order that P6 imports cleanly, not an order real exports obey: the 166 exports measured for `references/table-reference.md` produced 38 distinct `%T` sequences (see its Export order section). When parsing, never assume an order.
209
+ 2. **ERMHDR line**: nine tab-separated fields, `ERMHDR\t24.12\t<export_date>\t<export_scope>\t<user>\t<user_full_name>\t<database>\tProject Management\t<currency>`. A header read from a parsed file is written back as it was read; otherwise it is built from the `generate_xer` keyword arguments.
210
+ 3. **Field counts**: every `%R` row must have exactly the same number of fields as the `%F` definition that precedes it. The generator enforces this.
211
+ 4. **Known field counts for P6 23.10, 23.12 and 24.12**, measured across the 163 genuine exports at those versions behind `references/table-reference.md`, and held in `TABLE_FIELD_COUNTS_BY_VERSION`:
212
+ - PROJECT: 71 fields
213
+ - SCHEDOPTIONS: 25 fields
214
+ - PROJWBS: 26 fields
215
+ - TASK: 61 fields (`crt_path_num` is the 61st, at 24.12 as at 23.x)
216
+ - TASKPRED: 11 fields
217
+
218
+ No export gave a different count for a table it carried, and the 50 genuine 24.12 exports match 23.12 on all five. Earlier versions of this README gave 72, 26, 27, 62 and 12 and called the difference from the constant an open question; the measurement settles it in the constant's favour. 19.12 is a different layout (PROJECT 82, TASK 66, TASKPRED 10). Counts are a sanity check, not a parsing contract: index fields by name from the `%F` line, never by position.
219
+ 5. **Encoding**: UTF-8 with CRLF line endings by default. Pass `encoding='cp1252'` for strict legacy P6 compatibility with non-ASCII text.
220
+ 6. **Calendar data**: when generating from scratch, copy the `clndr_data` field verbatim from a working reference XER. Re-encoding is brittle.
221
+ 7. **End-of-file marker**: the file ends with a `%E` line, as genuine P6 exports do. The parser skips it, so it never becomes a table and does not multiply on a round trip.
222
+ 8. **Cell values**: a tab, CR or LF inside a value would add a column or split a row, so each run of them is written as one space. Genuine P6 data never carries them.
223
+
224
+ See `references/table-reference.md` for the table reference: 40 XER tables, 27 of them with a field-by-field list, and 13 less common ones (documents, roles, shifts, resource codes, risk) described at table level rather than field level.
225
+
226
+ ---
227
+
228
+ ## Running the tests
229
+
230
+ ```bash
231
+ python tests/test_xer_parser.py # core parser + generator smoke test
232
+ python tests/test_half_step.py # half-step generator (SmartPM-equivalent)
233
+ python tests/test_bundled_validation_runs.py # bundled validation stubs bind on a plain clone
234
+ ```
235
+
236
+ Or with pytest:
237
+
238
+ ```bash
239
+ pip install pytest
240
+ pytest tests/
241
+ ```
242
+
243
+ `pytest tests/` runs 142 tests across 14 files. 140 pass and two skip: parity checks against `cpp-cpm-engine`, of `add_work_days` and `subtract_work_days` and of the corrupt-calendar screen, which run when that engine's `python_reference` directory is on `sys.path` (for example `PYTHONPATH=../cpp-cpm-engine/python_reference pytest tests/`), and then all 142 pass. CI runs the same suite on Linux, macOS and Windows against Python 3.10, 3.11 and 3.12.
244
+
245
+ All tests build their XER fixtures synthetically in memory; no real client XER files ship with the repo.
246
+
247
+ ---
248
+
249
+ ## Relationship to CPP's internal toolchain
250
+
251
+ Critical Path Partners keeps a larger internal parser that this package was cut from. What is published here is the parsing and generation core. What is not published is the extra validation surface listed under [Scope](#scope-what-this-is-and-what-it-is-not): the date-field gate, the calendar-decode warning channel, and the additional working-day arithmetic. Reports CPP issues are produced with the internal toolchain, not with this package on its own.
252
+
253
+ Two consequences worth being explicit about:
254
+
255
+ 1. **Nothing here is a stub.** The functions in the feature table are the working implementations, and the test suite exercises them. This is a smaller parser, not a demonstration copy.
256
+ 2. **If you are auditing a CPP deliverable**, this repository shows you the parsing and generation logic behind it, and the Scope section tells you what else ran that you cannot see here. Ask about the difference rather than inferring it.
257
+
258
+ `scripts/validation.py` and `scripts/config_profiles.py` are minimal standalone subsets of the same-named modules in the larger internal suite: enough for `validate_schedule` and `aace_31r_compliance` to run end-to-end from a plain clone. A report's `to_dict()` carries the internal version's `summary` block, whose `worst_severity` names the worst finding; it also keeps this subset's `counts` key, and lists findings in the order they were added rather than worst first. `generate_xer_manifest` additionally needs the internal audit-trail module, which is not bundled; see the note under the feature table.
259
+
260
+ ---
261
+
262
+ ## License
263
+
264
+ MIT — see [LICENSE](https://github.com/danafitkowski/cpp-xer-parser/blob/main/LICENSE).
265
+
266
+ You may use this parser in commercial forensic consulting, in academic research, in your own scheduling product, in court-filed expert reports. Just keep the copyright notice.
267
+
268
+ ---
269
+
270
+ ## Contributing
271
+
272
+ See [CONTRIBUTING.md](https://github.com/danafitkowski/cpp-xer-parser/blob/main/CONTRIBUTING.md). Bug reports and pull requests are welcome.
273
+
274
+ ---
275
+
276
+ ## Companion repositories
277
+
278
+ - **[cpp-cpm-engine](https://github.com/danafitkowski/cpp-cpm-engine)** — The forensically-defensible CPM engine.
279
+ - **[cpp-critical-path-validator](https://github.com/danafitkowski/cpp-critical-path-validator)** — Critical path validation, DCMA-14 assessment, and logic health review.
280
+
281
+ ---
282
+
283
+ ## Strategic note
284
+
285
+ Critical Path Partners is a forensic-scheduling consultancy. We open-source the foundational tooling because every academic, every solo forensic, every contractor's internal scheduler now has a reason to install CPP and a citation pathway. The math is a commodity; the workflow and discipline are not.
286
+
287
+ If you ship something built on this parser, we'd like to hear about it: [criticalpathpartners.ca](https://criticalpathpartners.ca).
@@ -0,0 +1,10 @@
1
+ cpp_xer_parser/__init__.py,sha256=rTD7fItUts3aKhYQplUrbtIh4xNiR9VLyAFb-NrBymo,1174
2
+ cpp_xer_parser/config_profiles.py,sha256=9pWj4Nbipnf5-TowqZUV33qJRCDzegLs4EQDcGB75YU,5544
3
+ cpp_xer_parser/validation.py,sha256=70BrabJ9lpHji1z-gFbT5jSDKEKZIORjXqAGadVijdU,6869
4
+ cpp_xer_parser/xer_parser.py,sha256=OCNCjjOAh8Rx-atQ_RhYPkN7IWJdX1fOmlNEcUUgxj0,148724
5
+ cpp_xer_parser-0.6.1.dist-info/licenses/LICENSE,sha256=dKM0qKyTaVirl2mSnq2ucd5g29wKedXcMv5FZN3QnoE,1096
6
+ cpp_xer_parser-0.6.1.dist-info/METADATA,sha256=GGAT4G256-YPlF1SEoMRUrcnnAdGG24SoNlGa3Kmukc,21979
7
+ cpp_xer_parser-0.6.1.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
8
+ cpp_xer_parser-0.6.1.dist-info/entry_points.txt,sha256=qvkKzbXjXbj4TXq8QQMkxd-WunVEkRazOi37zq4TsY8,66
9
+ cpp_xer_parser-0.6.1.dist-info/top_level.txt,sha256=YOvbMkMRFXw2NP-0_LOfQsuEdavU1bnEBy50gpC2beU,15
10
+ cpp_xer_parser-0.6.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.2.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ cpp-xer-parser = cpp_xer_parser.xer_parser:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dana Fitkowski / Critical Path Partners
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.
@@ -0,0 +1 @@
1
+ cpp_xer_parser