Skip to content

Commit cbdb87a

Browse files
Merge pull request #7 from jacksierkstra/develop
Added a new XSD parser implementation.
2 parents 7696ff1 + 1450e39 commit cbdb87a

24 files changed

+1974
-227
lines changed

.npmignore

-19
This file was deleted.

jest.config.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ const config: Config = {
149149
// snapshotSerializers: [],
150150

151151
// The test environment that will be used for testing
152-
// testEnvironment: "jest-environment-node",
152+
testEnvironment: "jest-environment-jsdom",
153153

154154
// Options that will be passed to the testEnvironment
155155
// testEnvironmentOptions: {},

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@
77
"types": "dist/index.d.ts",
88
"scripts": {
99
"test": "jest",
10-
"build": "tsc",
10+
"build": "tsc -p tsconfig.build.json",
1111
"clean": "rimraf dist",
1212
"prepublishOnly": "yarn clean && yarn build && tsc-alias"
1313
},
1414
"files": [
1515
"dist/",
16+
"package.json",
1617
"LICENSE",
1718
"README.md"
1819
],
@@ -24,6 +25,7 @@
2425
"@types/jest": "^29.5.14",
2526
"@types/xmldom": "^0.1.34",
2627
"jest": "^29.7.0",
28+
"jest-environment-jsdom": "^29.7.0",
2729
"rimraf": "^6.0.1",
2830
"ts-jest": "^29.2.6",
2931
"ts-node": "^10.9.2",

src/core/main.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { ValidationResult } from "@lib/types/validation";
22
import { Validator, ValidatorImpl } from "@lib/validator/validator";
33
import { XMLParser, XMLParserImpl } from "@lib/xml/parser";
4-
import { XSDParser, XSDParserImpl } from "@lib/xsd/parser";
4+
import { XSDParser } from "@lib/xsd/parser";
5+
import { XSDStandardParserImpl } from "@lib/xsd/standard";
56

67
export class Checkr {
78

@@ -11,7 +12,7 @@ export class Checkr {
1112

1213
constructor() {
1314
this.xmlParser = new XMLParserImpl();
14-
this.xsdParser = new XSDParserImpl(this.xmlParser);
15+
this.xsdParser = new XSDStandardParserImpl(this.xmlParser);
1516
this.validator = new ValidatorImpl(this.xmlParser, this.xsdParser);
1617
}
1718

src/index.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ export { Checkr } from "@lib/core/main";
22
export { ValidationResult } from "@lib/types/validation";
33
export { Validator, ValidatorImpl } from "@lib/validator/validator";
44
export { XMLParser, XMLParserImpl } from "@lib/xml/parser";
5-
export { XSDParser, XSDParserImpl } from "@lib/xsd/parser";
5+
export { XSDParser } from "@lib/xsd/parser";
6+
export { XSDStandardParserImpl } from "@lib/xsd/standard";
7+
export { XSDPipelineParserImpl } from "@lib/xsd/pipeline/parser";

src/validator/validator.test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { Validator, ValidatorImpl } from "@lib/validator/validator";
22
import { XMLParserImpl } from "@lib/xml/parser";
3-
import { XSDParserImpl } from "@lib/xsd/parser";
3+
import { XSDStandardParserImpl } from "@lib/xsd/standard";
44

55
describe("Validator", () => {
66
let validator: Validator;
77

88
beforeAll(() => {
99
const xmlParser = new XMLParserImpl();
10-
const xsdParser = new XSDParserImpl(xmlParser);
10+
const xsdParser = new XSDStandardParserImpl(xmlParser);
1111
validator = new ValidatorImpl(xmlParser, xsdParser);
1212
});
1313

src/xsd/parser.test.ts

+243-5
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
import { XSDSchema } from "@lib/types/xsd";
22
import { XMLParserImpl } from "@lib/xml/parser";
3-
import { XSDParserImpl } from "@lib/xsd/parser";
3+
import { XSDParser } from "@lib/xsd/parser";
4+
import { XSDPipelineParserImpl } from "@lib/xsd/pipeline/parser";
5+
import { XSDStandardParserImpl } from "@lib/xsd/standard";
46

5-
describe("XSDParser", () => {
6-
let parser: XSDParserImpl;
7+
8+
9+
const runCommonTests = (xsdParser: XSDParser) => {
10+
let parser: XSDParser;
711

812
beforeAll(() => {
9-
const xmlParser = new XMLParserImpl();
10-
parser = new XSDParserImpl(xmlParser);
13+
parser = xsdParser;
1114
});
1215

1316
const parseAndExpect = async (xsd: string, expectations: (schema: XSDSchema) => void) => {
@@ -177,4 +180,239 @@ describe("XSDParser", () => {
177180
expect(schema.elements).toHaveLength(0);
178181
});
179182
});
183+
184+
it("should parse enumeration restrictions", async () => {
185+
const xsd = `
186+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
187+
<xs:element name="Status">
188+
<xs:simpleType>
189+
<xs:restriction base="xs:string">
190+
<xs:enumeration value="Pending"/>
191+
<xs:enumeration value="Approved"/>
192+
<xs:enumeration value="Rejected"/>
193+
</xs:restriction>
194+
</xs:simpleType>
195+
</xs:element>
196+
</xs:schema>
197+
`;
198+
await parseAndExpect(xsd, (schema) => {
199+
const statusElement = schema.elements.find((el) => el.name === "Status");
200+
expect(statusElement?.enumeration).toEqual(["Pending", "Approved", "Rejected"]);
201+
});
202+
});
203+
204+
it("should parse pattern restrictions", async () => {
205+
const xsd = `
206+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
207+
<xs:element name="Code">
208+
<xs:simpleType>
209+
<xs:restriction base="xs:string">
210+
<xs:pattern value="[A-Z]{3}[0-9]{2}"/>
211+
</xs:restriction>
212+
</xs:simpleType>
213+
</xs:element>
214+
</xs:schema>
215+
`;
216+
await parseAndExpect(xsd, (schema) => {
217+
const codeElement = schema.elements.find((el) => el.name === "Code");
218+
expect(codeElement?.pattern).toBe("[A-Z]{3}[0-9]{2}");
219+
});
220+
});
221+
222+
it("should parse minLength and maxLength restrictions", async () => {
223+
const xsd = `
224+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
225+
<xs:element name="Comment">
226+
<xs:simpleType>
227+
<xs:restriction base="xs:string">
228+
<xs:minLength value="10"/>
229+
<xs:maxLength value="100"/>
230+
</xs:restriction>
231+
</xs:simpleType>
232+
</xs:element>
233+
</xs:schema>
234+
`;
235+
await parseAndExpect(xsd, (schema) => {
236+
const commentElement = schema.elements.find((el) => el.name === "Comment");
237+
expect(commentElement?.minLength).toBe(10);
238+
expect(commentElement?.maxLength).toBe(100);
239+
});
240+
});
241+
242+
it("should parse all restrictions combined", async () => {
243+
const xsd = `
244+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
245+
<xs:element name="ComplexString">
246+
<xs:simpleType>
247+
<xs:restriction base="xs:string">
248+
<xs:enumeration value="Value1"/>
249+
<xs:enumeration value="Value2"/>
250+
<xs:pattern value="[A-Z]+"/>
251+
<xs:minLength value="3"/>
252+
<xs:maxLength value="10"/>
253+
</xs:restriction>
254+
</xs:simpleType>
255+
</xs:element>
256+
</xs:schema>
257+
`;
258+
await parseAndExpect(xsd, (schema) => {
259+
const complexStringElement = schema.elements.find((el) => el.name === "ComplexString");
260+
expect(complexStringElement?.enumeration).toEqual(["Value1", "Value2"]);
261+
expect(complexStringElement?.pattern).toBe("[A-Z]+");
262+
expect(complexStringElement?.minLength).toBe(3);
263+
expect(complexStringElement?.maxLength).toBe(10);
264+
});
265+
});
266+
267+
it("should handle whitespace in attribute values", async () => {
268+
const xsd = `
269+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
270+
<xs:element name="Item">
271+
<xs:attribute name=" attr1 " type=" xs:integer " fixed=" fixed value " />
272+
</xs:element>
273+
</xs:schema>
274+
`;
275+
await parseAndExpect(xsd, (schema) => {
276+
const itemElement = schema.elements.find((el) => el.name === "Item");
277+
const attr1 = itemElement?.attributes?.find((attr) => attr.name === " attr1 ");
278+
expect(attr1?.type).toBe(" xs:integer ");
279+
expect(attr1?.fixed).toBe(" fixed value ");
280+
});
281+
});
282+
283+
it("should handle whitespace in enumeration values", async () => {
284+
const xsd = `
285+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
286+
<xs:element name="Status">
287+
<xs:simpleType>
288+
<xs:restriction base="xs:string">
289+
<xs:enumeration value=" Value 1 "/>
290+
<xs:enumeration value="Value 2"/>
291+
</xs:restriction>
292+
</xs:simpleType>
293+
</xs:element>
294+
</xs:schema>
295+
`;
296+
await parseAndExpect(xsd, (schema) => {
297+
const statusElement = schema.elements.find((el) => el.name === "Status");
298+
expect(statusElement?.enumeration).toEqual([" Value 1 ", "Value 2"]);
299+
});
300+
});
301+
302+
it("should handle maxOccurs='unbounded' in nested elements", async () => {
303+
const xsd = `
304+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
305+
<xs:element name="Order">
306+
<xs:complexType>
307+
<xs:sequence>
308+
<xs:element name="Item" maxOccurs="unbounded"/>
309+
</xs:sequence>
310+
</xs:complexType>
311+
</xs:element>
312+
</xs:schema>
313+
`;
314+
await parseAndExpect(xsd, (schema) => {
315+
const orderElement = schema.elements.find((el) => el.name === "Order");
316+
const itemElement = orderElement?.children?.find((el) => el.name === "Item");
317+
expect(itemElement?.maxOccurs).toBeNaN();
318+
});
319+
});
320+
321+
it("should parse nested complex types", async () => {
322+
const xsd = `
323+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
324+
<xs:element name="Root">
325+
<xs:complexType>
326+
<xs:sequence>
327+
<xs:element name="Nested">
328+
<xs:complexType>
329+
<xs:sequence>
330+
<xs:element name="Inner"/>
331+
</xs:sequence>
332+
</xs:complexType>
333+
</xs:element>
334+
</xs:sequence>
335+
</xs:complexType>
336+
</xs:element>
337+
</xs:schema>
338+
`;
339+
await parseAndExpect(xsd, (schema) => {
340+
const rootElement = schema.elements.find((el) => el.name === "Root");
341+
const nestedElement = rootElement?.children?.find((el) => el.name === "Nested");
342+
const innerElement = nestedElement?.children?.find((el) => el.name === "Inner");
343+
expect(innerElement).toBeDefined();
344+
});
345+
});
346+
347+
it("should handle empty complex types", async () => {
348+
const xsd = `
349+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
350+
<xs:element name="Empty">
351+
<xs:complexType/>
352+
</xs:element>
353+
</xs:schema>
354+
`;
355+
await parseAndExpect(xsd, (schema) => {
356+
const emptyElement = schema.elements.find((el) => el.name === "Empty");
357+
expect(emptyElement?.children).toHaveLength(0);
358+
expect(emptyElement?.attributes).toHaveLength(0);
359+
});
360+
});
361+
362+
it("should handle empty enumeration values", async () => {
363+
const xsd = `
364+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
365+
<xs:element name="Status">
366+
<xs:simpleType>
367+
<xs:restriction base="xs:string">
368+
<xs:enumeration value="Value1"/>
369+
<xs:enumeration value=""/>
370+
<xs:enumeration value="Value3"/>
371+
</xs:restriction>
372+
</xs:simpleType>
373+
</xs:element>
374+
</xs:schema>
375+
`;
376+
await parseAndExpect(xsd, (schema) => {
377+
const statusElement = schema.elements.find((el) => el.name === "Status");
378+
expect(statusElement?.enumeration).toEqual(["Value1", "", "Value3"]);
379+
});
380+
});
381+
382+
it("should handle elements with namespaces", async () => {
383+
const xsd = `
384+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:ex="http://example.com">
385+
<xs:element name="ex:Data"/>
386+
</xs:schema>
387+
`;
388+
await parseAndExpect(xsd, (schema) => {
389+
const dataElement = schema.elements.find((el) => el.name === "ex:Data");
390+
expect(dataElement).toBeDefined();
391+
});
392+
});
393+
394+
it("should handle elements with empty names", async () => {
395+
const xsd = `
396+
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
397+
<xs:element name=""/>
398+
</xs:schema>
399+
`;
400+
await parseAndExpect(xsd, (schema) => {
401+
expect(schema.elements).toHaveLength(0);
402+
});
403+
});
404+
405+
};
406+
407+
describe('XSDParser Implementations', () => {
408+
const xmlParser = new XMLParserImpl();
409+
410+
describe('XSDStandardParserImpl', () => {
411+
runCommonTests(new XSDStandardParserImpl(xmlParser));
412+
});
413+
414+
describe('XSDPipelineParserImpl', () => {
415+
runCommonTests(new XSDPipelineParserImpl(xmlParser));
416+
});
417+
180418
});

0 commit comments

Comments
 (0)