@mitre/hdf-utilities 2.0.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.md ADDED
@@ -0,0 +1,55 @@
1
+ # License
2
+
3
+ Copyright © 2025 The MITRE Corporation.
4
+
5
+ Approved for Public Release; Distribution Unlimited. Case Number 18-3678.
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License"); you may
8
+ not use this file except in compliance with the License. You may obtain a
9
+ copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16
+ License for the specific language governing permissions and limitations
17
+ under the License.
18
+
19
+ ## Redistribution Terms
20
+
21
+ Redistribution and use in source and binary forms, with or without
22
+ modification, are permitted provided that the following conditions are
23
+ met:
24
+
25
+ - Redistributions of source code must retain the above copyright/digital
26
+ rights legend, this list of conditions and the following Notice.
27
+ - Redistributions in binary form must reproduce the above
28
+ copyright/digital rights legend, this list of conditions and the
29
+ following Notice in the documentation and/or other materials provided
30
+ with the distribution.
31
+ - Neither the name of The MITRE Corporation nor the names of its contributors
32
+ may be used to endorse or promote products derived from this software
33
+ without specific prior written permission.
34
+
35
+ ## Notice
36
+
37
+ The MITRE Corporation grants permission to reproduce, distribute, modify, and
38
+ otherwise use this software to the extent permitted by the licensed terms
39
+ provided in the LICENSE file included with this project.
40
+
41
+ This software was produced by The MITRE Corporation for the U.S. Government
42
+ under contract. As such the U.S. Government has certain use and data
43
+ rights in this software. No use other than those granted to the U.S.
44
+ Government, or to those acting on behalf of the U.S. Government, under
45
+ these contract arrangements is authorized without the express written
46
+ permission of The MITRE Corporation.
47
+
48
+ Some files in this codebase were generated by generative AI, under the
49
+ direction and review of The MITRE Corporation employees, for the purpose of
50
+ development efficiency. All AI-generated code functionality was validated
51
+ by standard quality and assurance testing.
52
+
53
+ For further information, please contact The MITRE Corporation,
54
+ Contracts Management Office, 7515 Colshire Drive, McLean, VA 22102-7539,
55
+ (703) 983-6000.
package/README.md ADDED
@@ -0,0 +1,403 @@
1
+ # @mitre/hdf-utilities
2
+
3
+ Generic utility functions for working with common data formats (JSON, XML, CSV) and cryptographic hashing. These utilities provide safe parsing, building, and validation operations used throughout the HDF library ecosystem.
4
+
5
+ ## Scope and Responsibilities
6
+
7
+ **hdf-utilities** provides low-level format handling utilities:
8
+ - Parse and build JSON, XML, CSV documents
9
+ - Validate format syntax (well-formed XML, parseable JSON, etc.)
10
+ - Generate cryptographic hashes for data integrity
11
+ - Sanitize values for safe output
12
+
13
+ **What this library does NOT do:**
14
+ - Validate HDF document semantics (use `@mitre/hdf-validators` for schema validation)
15
+ - Parse HDF-specific structures (use `@mitre/hdf-parsers` for HDF traversal)
16
+ - Convert between security tool formats (use `@mitre/hdf-converters`)
17
+
18
+ ### hdf-utilities vs. hdf-validators
19
+
20
+ | hdf-utilities | hdf-validators |
21
+ |---------------|----------------|
22
+ | Format syntax validation | Schema conformance validation |
23
+ | "Is this valid JSON?" | "Is this valid HDF?" |
24
+ | "Can this XML be parsed?" | "Does this HDF have required fields?" |
25
+ | Generic data format handling | HDF-specific semantic rules |
26
+ | No knowledge of HDF schema | Validates against JSON schemas |
27
+
28
+ **Example:**
29
+ - `isValidJSON('{"foo": "bar"}')` → `true` (hdf-utilities - syntax check)
30
+ - `validateHdfResults({baselines: []})` → validates against HDF schema (hdf-validators)
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ npm install @mitre/hdf-utilities
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ ### JSON Utilities
41
+
42
+ Safe JSON parsing and stringification with better error handling:
43
+
44
+ ```typescript
45
+ import { parseJSON, stringifyJSON, isValidJSON } from '@mitre/hdf-utilities';
46
+
47
+ // Parse JSON with clear error messages
48
+ try {
49
+ const data = parseJSON<HdfResults>(jsonString);
50
+ console.log(data.baselines);
51
+ } catch (error) {
52
+ console.error('Invalid JSON:', error.message);
53
+ }
54
+
55
+ // Stringify with pretty printing
56
+ const hdf = { baselines: [], targets: [] };
57
+ const json = stringifyJSON(hdf, { pretty: true, indent: 2 });
58
+
59
+ // Check if string is valid JSON before parsing
60
+ if (isValidJSON(userInput)) {
61
+ const data = parseJSON(userInput);
62
+ }
63
+ ```
64
+
65
+ ### Hash Utilities
66
+
67
+ Generate and verify cryptographic hashes for data integrity:
68
+
69
+ ```typescript
70
+ import { sha256, sha512, generateHash, hashObject, verifyHash } from '@mitre/hdf-utilities';
71
+
72
+ // Quick SHA-256 hash
73
+ const hash = sha256('content to hash');
74
+ console.log(hash); // hex-encoded hash
75
+
76
+ // Hash with custom options
77
+ const customHash = generateHash('data', {
78
+ algorithm: 'sha512',
79
+ encoding: 'base64'
80
+ });
81
+
82
+ // Hash JavaScript objects (deterministic JSON serialization)
83
+ const baseline = { name: 'My Baseline', version: '1.0' };
84
+ const objectHash = hashObject(baseline, { algorithm: 'sha256' });
85
+
86
+ // Verify data integrity
87
+ const isValid = verifyHash('original data', hash, { algorithm: 'sha256' });
88
+ if (!isValid) {
89
+ console.error('Data integrity check failed!');
90
+ }
91
+ ```
92
+
93
+ ### XML Utilities
94
+
95
+ Parse and build XML documents with array handling:
96
+
97
+ ```typescript
98
+ import { parseXml, buildXml, parseXmlWithArrays, isValidXml, extractTextFromXml } from '@mitre/hdf-utilities';
99
+
100
+ // Parse XML to JavaScript object
101
+ const xml = '<root><item>value</item></root>';
102
+ const obj = parseXml(xml);
103
+ console.log(obj); // { root: { item: 'value' } }
104
+
105
+ // Build XML from object
106
+ const data = {
107
+ HdfResults: {
108
+ baselines: {
109
+ baseline: [
110
+ { name: 'Baseline 1' },
111
+ { name: 'Baseline 2' }
112
+ ]
113
+ }
114
+ }
115
+ };
116
+ const xmlOutput = buildXml(data);
117
+ // <?xml version="1.0"?>
118
+ // <HdfResults>
119
+ // <baselines>
120
+ // <baseline><name>Baseline 1</name></baseline>
121
+ // <baseline><name>Baseline 2</name></baseline>
122
+ // </baselines>
123
+ // </HdfResults>
124
+
125
+ // Parse XML with automatic array detection
126
+ const nessusXml = '<Report><ReportHost>...</ReportHost><ReportHost>...</ReportHost></Report>';
127
+ const report = parseXmlWithArrays(nessusXml, {
128
+ arrayPaths: ['Report.ReportHost'] // Force these to always be arrays
129
+ });
130
+
131
+ // Check if string is well-formed XML
132
+ if (isValidXml(userInput)) {
133
+ const parsed = parseXml(userInput);
134
+ }
135
+
136
+ // Extract plain text from XML (strips all tags)
137
+ const plainText = extractTextFromXml('<p>Hello <b>world</b></p>');
138
+ console.log(plainText); // "Hello world"
139
+ ```
140
+
141
+ ### CSV Utilities
142
+
143
+ Parse and build CSV documents with sanitization:
144
+
145
+ ```typescript
146
+ import {
147
+ parseCsv,
148
+ buildCsv,
149
+ isValidCsv,
150
+ sanitizeCsvValue,
151
+ sanitizeCsvArray,
152
+ sanitizeCsvObject
153
+ } from '@mitre/hdf-utilities';
154
+
155
+ // Parse CSV string to array of objects
156
+ const csv = 'name,age\nAlice,30\nBob,25';
157
+ const rows = parseCsv(csv);
158
+ console.log(rows);
159
+ // [
160
+ // { name: 'Alice', age: '30' },
161
+ // { name: 'Bob', age: '25' }
162
+ // ]
163
+
164
+ // Build CSV from array of objects
165
+ const data = [
166
+ { id: 'REQ-001', status: 'passed', impact: 0.7 },
167
+ { id: 'REQ-002', status: 'failed', impact: 0.9 }
168
+ ];
169
+ const csvOutput = buildCsv(data);
170
+ // id,status,impact
171
+ // REQ-001,passed,0.7
172
+ // REQ-002,failed,0.9
173
+
174
+ // Validate CSV syntax
175
+ if (isValidCsv(fileContent)) {
176
+ const parsed = parseCsv(fileContent);
177
+ }
178
+
179
+ // Sanitize individual values (escape special characters)
180
+ const safe = sanitizeCsvValue('Value with "quotes" and, commas');
181
+ console.log(safe); // "Value with ""quotes"" and, commas"
182
+
183
+ // Sanitize arrays
184
+ const tags = ['AC-1', 'AC-2', null, undefined];
185
+ const safeTags = sanitizeCsvArray(tags);
186
+ console.log(safeTags); // ['AC-1', 'AC-2', '', '']
187
+
188
+ // Sanitize entire objects
189
+ const requirement = {
190
+ id: 'REQ-001',
191
+ title: 'Title with "quotes"',
192
+ tags: ['AC-1', null]
193
+ };
194
+ const safeReq = sanitizeCsvObject(requirement);
195
+ // All string values escaped, nulls converted to empty strings
196
+ ```
197
+
198
+ ## API Reference
199
+
200
+ ### JSON Utilities
201
+
202
+ #### `parseJSON<T>(input: string): T`
203
+
204
+ Parse JSON string with clear error messages.
205
+
206
+ - **Parameters:**
207
+ - `input` - JSON string to parse
208
+ - **Returns:** Parsed JSON value with inferred type
209
+ - **Throws:** Error with descriptive message if JSON is invalid or empty
210
+
211
+ #### `stringifyJSON(value: unknown, options?: StringifyOptions): string`
212
+
213
+ Convert JavaScript value to JSON string.
214
+
215
+ - **Parameters:**
216
+ - `value` - Value to stringify
217
+ - `options.pretty` - Enable pretty printing (default: `false`)
218
+ - `options.indent` - Spaces for indentation (default: `2`)
219
+ - **Returns:** JSON string
220
+ - **Throws:** Error if value contains circular references
221
+
222
+ #### `isValidJSON(input: unknown): boolean`
223
+
224
+ Check if string can be parsed as JSON.
225
+
226
+ - **Parameters:**
227
+ - `input` - Value to check (must be string)
228
+ - **Returns:** `true` if valid JSON syntax, `false` otherwise
229
+
230
+ ### Hash Utilities
231
+
232
+ #### `sha256(data: string): string`
233
+
234
+ Generate SHA-256 hash (hex-encoded).
235
+
236
+ - **Parameters:**
237
+ - `data` - String to hash
238
+ - **Returns:** Hex-encoded hash string
239
+
240
+ #### `sha512(data: string): string`
241
+
242
+ Generate SHA-512 hash (hex-encoded).
243
+
244
+ - **Parameters:**
245
+ - `data` - String to hash
246
+ - **Returns:** Hex-encoded hash string
247
+
248
+ #### `generateHash(data: string, options?: HashOptions): string`
249
+
250
+ Generate hash with custom algorithm and encoding.
251
+
252
+ - **Parameters:**
253
+ - `data` - String to hash
254
+ - `options.algorithm` - Hash algorithm: `'sha256'` | `'sha512'` (default: `'sha256'`)
255
+ - `options.encoding` - Output encoding: `'hex'` | `'base64'` | `'base64url'` (default: `'hex'`)
256
+ - **Returns:** Encoded hash string
257
+
258
+ #### `hashObject(obj: unknown, options?: HashOptions): string`
259
+
260
+ Generate deterministic hash of JavaScript object.
261
+
262
+ - **Parameters:**
263
+ - `obj` - Object to hash (will be JSON-stringified)
264
+ - `options` - Same as `generateHash`
265
+ - **Returns:** Hash of serialized object
266
+
267
+ #### `verifyHash(data: string, expectedHash: string, options?: HashOptions): boolean`
268
+
269
+ Verify data matches expected hash.
270
+
271
+ - **Parameters:**
272
+ - `data` - Original data to verify
273
+ - `expectedHash` - Expected hash value
274
+ - `options` - Same hash options used during generation
275
+ - **Returns:** `true` if hash matches, `false` otherwise
276
+
277
+ ### XML Utilities
278
+
279
+ #### `parseXml(xml: string, options?: object): object`
280
+
281
+ Parse XML string to JavaScript object.
282
+
283
+ - **Parameters:**
284
+ - `xml` - XML string to parse
285
+ - `options` - fast-xml-parser options (optional)
286
+ - **Returns:** Parsed object
287
+ - **Throws:** Error if XML is malformed
288
+
289
+ #### `buildXml(obj: object, options?: object): string`
290
+
291
+ Build XML string from JavaScript object.
292
+
293
+ - **Parameters:**
294
+ - `obj` - Object to convert to XML
295
+ - `options` - fast-xml-parser builder options (optional)
296
+ - **Returns:** XML string with header
297
+ - **Throws:** Error if conversion fails
298
+
299
+ #### `parseXmlWithArrays(xml: string, options: { arrayPaths: string[] }): object`
300
+
301
+ Parse XML with specified paths forced as arrays.
302
+
303
+ - **Parameters:**
304
+ - `xml` - XML string to parse
305
+ - `options.arrayPaths` - Dot-notation paths to treat as arrays (e.g., `'Report.ReportHost'`)
306
+ - **Returns:** Parsed object with consistent arrays
307
+ - **Throws:** Error if XML is malformed
308
+
309
+ #### `isValidXml(xml: string): boolean`
310
+
311
+ Check if string is well-formed XML.
312
+
313
+ - **Parameters:**
314
+ - `xml` - String to validate
315
+ - **Returns:** `true` if valid XML syntax, `false` otherwise
316
+
317
+ #### `extractTextFromXml(xml: string): string`
318
+
319
+ Extract plain text content from XML (strips all tags).
320
+
321
+ - **Parameters:**
322
+ - `xml` - XML string
323
+ - **Returns:** Plain text content with whitespace normalized
324
+
325
+ ### CSV Utilities
326
+
327
+ #### `parseCsv<T>(csv: string, options?: object): T[]`
328
+
329
+ Parse CSV string to array of objects.
330
+
331
+ - **Parameters:**
332
+ - `csv` - CSV string to parse
333
+ - `options` - PapaParse options (optional)
334
+ - **Returns:** Array of objects (headers become keys)
335
+ - **Throws:** Error if CSV is malformed
336
+
337
+ #### `buildCsv<T>(data: T[], options?: object): string`
338
+
339
+ Build CSV string from array of objects.
340
+
341
+ - **Parameters:**
342
+ - `data` - Array of objects to convert
343
+ - `options` - PapaParse unparse options (optional)
344
+ - **Returns:** CSV string with headers
345
+
346
+ #### `isValidCsv(csv: string): boolean`
347
+
348
+ Check if string can be parsed as CSV.
349
+
350
+ - **Parameters:**
351
+ - `csv` - String to validate
352
+ - **Returns:** `true` if parseable CSV, `false` otherwise
353
+
354
+ #### `sanitizeCsvValue(value: unknown): string`
355
+
356
+ Escape value for safe CSV output.
357
+
358
+ - **Parameters:**
359
+ - `value` - Value to sanitize
360
+ - **Returns:** Escaped string (quotes escaped, nulls → empty string)
361
+
362
+ #### `sanitizeCsvArray(values: unknown[]): string[]`
363
+
364
+ Sanitize array of values for CSV.
365
+
366
+ - **Parameters:**
367
+ - `values` - Array to sanitize
368
+ - **Returns:** Array of sanitized strings
369
+
370
+ #### `sanitizeCsvObject<T>(obj: T): T`
371
+
372
+ Recursively sanitize all string values in object.
373
+
374
+ - **Parameters:**
375
+ - `obj` - Object to sanitize
376
+ - **Returns:** New object with sanitized values
377
+
378
+ ## Development
379
+
380
+ ```bash
381
+ # Install dependencies
382
+ pnpm install
383
+
384
+ # Run tests
385
+ pnpm test
386
+
387
+ # Run tests with coverage
388
+ pnpm test:coverage
389
+
390
+ # Build the package
391
+ pnpm build
392
+
393
+ # Lint code
394
+ pnpm lint
395
+ ```
396
+
397
+ ## Test Coverage
398
+
399
+ All utilities maintain **>95% test coverage**. Run `pnpm test:coverage` to view current coverage report.
400
+
401
+ ## License
402
+
403
+ Apache-2.0 © MITRE Corporation
@@ -0,0 +1,100 @@
1
+ /**
2
+ * CSV parsing and generation utilities using d3-dsv
3
+ *
4
+ * SECURITY WARNING: CSV files opened in Excel/LibreOffice may execute formulas.
5
+ * Use sanitization options when exporting data that may contain user-controlled content.
6
+ */
7
+ export { extractColumn as extractCsvColumn, findRows as findCsvRows, } from '../object/index.js';
8
+ /**
9
+ * Options for parsing CSV
10
+ */
11
+ export interface CsvParseOptions {
12
+ header?: boolean;
13
+ skipEmptyLines?: boolean;
14
+ transformHeader?: (header: string, index: number) => string;
15
+ dynamicTyping?: boolean;
16
+ delimiter?: string;
17
+ maxSize?: number;
18
+ }
19
+ /**
20
+ * Options for building CSV
21
+ */
22
+ export interface CsvBuildOptions {
23
+ header?: boolean;
24
+ newline?: string;
25
+ skipEmptyLines?: boolean;
26
+ delimiter?: string;
27
+ }
28
+ /**
29
+ * Parse CSV string into array of objects with headers as keys
30
+ * @param csv - CSV string to parse
31
+ * @param options - Optional parse configuration
32
+ * @returns Array of objects representing rows
33
+ * @throws Error if parsing fails
34
+ */
35
+ export declare function parseCsv<T = Record<string, unknown>>(csv: string, options?: Partial<CsvParseOptions>): T[];
36
+ /**
37
+ * Parse CSV string into array of arrays without treating first row as headers
38
+ * @param csv - CSV string to parse
39
+ * @param options - Optional parse configuration
40
+ * @returns Array of arrays representing rows
41
+ * @throws Error if parsing fails
42
+ */
43
+ export declare function parseCsvArray(csv: string, options?: Partial<CsvParseOptions>): string[][];
44
+ /**
45
+ * Build CSV string from array of objects
46
+ *
47
+ * SECURITY: Set sanitize=true when data may contain user-controlled content
48
+ * to prevent CSV formula injection attacks in Excel/LibreOffice.
49
+ *
50
+ * @param data - Array of objects to convert to CSV
51
+ * @param options - Optional build configuration and sanitize flag
52
+ * @returns CSV string
53
+ */
54
+ export declare function buildCsv<T = Record<string, unknown>>(data: T[], options?: Partial<CsvBuildOptions> & {
55
+ sanitize?: boolean;
56
+ }): string;
57
+ /**
58
+ * Build CSV string from array of arrays
59
+ *
60
+ * SECURITY: Set sanitize=true when data may contain user-controlled content
61
+ * to prevent CSV formula injection attacks in Excel/LibreOffice.
62
+ *
63
+ * @param data - Array of arrays to convert to CSV
64
+ * @param options - Optional build configuration and sanitize flag
65
+ * @returns CSV string
66
+ */
67
+ export declare function buildCsvArray(data: string[][], options?: Partial<CsvBuildOptions> & {
68
+ sanitize?: boolean;
69
+ }): string;
70
+ /**
71
+ * Validate CSV format
72
+ * @param csv - CSV string to validate
73
+ * @returns True if valid CSV, false otherwise
74
+ */
75
+ export declare function isValidCsv(csv: string): boolean;
76
+ /**
77
+ * Sanitize a single CSV value to prevent formula injection
78
+ *
79
+ * Prefixes values starting with =, +, -, @, |, or % with a single quote
80
+ * to prevent Excel/LibreOffice from interpreting them as formulas.
81
+ *
82
+ * @param value - Value to sanitize
83
+ * @returns Sanitized value
84
+ */
85
+ export declare function sanitizeCsvValue(value: unknown): string;
86
+ /**
87
+ * Sanitize all values in an array to prevent formula injection
88
+ *
89
+ * @param values - Array of values to sanitize
90
+ * @returns Array with sanitized values
91
+ */
92
+ export declare function sanitizeCsvArray(values: unknown[]): string[];
93
+ /**
94
+ * Sanitize all values in an object to prevent formula injection
95
+ *
96
+ * @param obj - Object with values to sanitize
97
+ * @returns New object with sanitized values
98
+ */
99
+ export declare function sanitizeCsvObject<T extends Record<string, unknown>>(obj: T): Record<string, string>;
100
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/csv/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,EACL,aAAa,IAAI,gBAAgB,EACjC,QAAQ,IAAI,WAAW,GACxB,MAAM,oBAAoB,CAAC;AAE5B;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,eAAe,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5D,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAgED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClD,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GACjC,CAAC,EAAE,CA2CL;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,EACX,OAAO,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GACjC,MAAM,EAAE,EAAE,CAoBZ;AAED;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClD,IAAI,EAAE,CAAC,EAAE,EACT,OAAO,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,GAC1D,MAAM,CA8BR;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,MAAM,EAAE,EAAE,EAChB,OAAO,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG;IAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAE,GAC1D,MAAM,CAmBR;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAe/C;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAOvD;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE,CAE5D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjE,GAAG,EAAE,CAAC,GACL,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAMxB"}