Skip to content

NaturalIntelligence/detailed-xml-validator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

detailed-xml-validator

A comprehensive XML validator that validates against custom rule schemas and reports all failures at once, not just the first error encountered.

Unlike XSD validators, this module uses an intuitive XML-based rule format that mirrors your data structure. It performs syntax checking via fast-xml-parser, then validates frequency, type, range, length, pattern matching, ordering, uniqueness, and null constraints.

Features

  • Complete error reporting — Reports all validation failures in one pass
  • Intuitive rule syntax — XML-based rules that mirror your data structure
  • Rich validation types — String patterns, numeric ranges, custom validators
  • Flexible constraints — Required fields, repeatable elements, occurrence limits
  • Custom validators — Register your own validation functions
  • Type safety — Built-in types: integers, decimals, dates, booleans, maps
  • Ordering constraints — Enforce before/after positional ordering between sibling tags
  • Date boundsmin, max, and range on type="date" fields
  • Relational constraintssameAs, notSameAs, lessThan, moreThan across sibling fields
  • Uniqueness constraintsunique="true" (sibling scope) and unique="global" (document scope)
  • Range shorthandrange="min..max" as readable sugar for min + max
  • TypeScript types — Bundled .d.ts declarations
  • ES Modules — Full import/export syntax; bundle with your own webpack/rollup config

Installation

npm install detailed-xml-validator

Quick Start

import Validator from "detailed-xml-validator";

const rules = `<?xml version="1.0"?>
<students nillable="false">
    <student repeatable minOccurs="1">
        <firstname minLength="3" maxLength="10" nillable="false"></firstname>
        <age type="positiveInteger" range="9..19"></age>
    </student>
</students>`;

const xmlData = `<?xml version="1.0"?>
<students>
    <student>
        <firstname>Jo</firstname>
        <age>25</age>
    </student>
</students>`;

const validator = new Validator(rules);
const failures = validator.validate(xmlData);

if (failures.length > 0) {
    console.log(`Found ${failures.length} validation issues:`);
    failures.forEach(f => console.log(f));
} else {
    console.log("Validation passed!");
    const data = validator.data; // Access parsed XML as JS object
}

Rule Syntax

Basic Structure

Rules are written in XML format that mirrors your expected data structure:

<?xml version="1.0"?>
<root>
    <element attribute="constraint" anotherAttribute="value">
        <nestedElement></nestedElement>
    </element>
</root>

Attribute Rules

Use the special <:a> tag to define validation rules for XML attributes:

<student repeatable>
    <:a>
        <id length="6"></id>
        <status pattern="active|inactive"></status>
    </:a>
    <n></n>
</student>

Required vs Optional Elements

By default, all elements are optional (nillable).

<!-- Optional -->
<nickname></nickname>

<!-- Required -->
<email nillable="false"></email>

Repeatable Elements (Lists)

<students>
    <student repeatable minOccurs="1" maxOccurs="100">
        <n></n>
    </student>
</students>
  • repeatable — Marks this as a list element
  • minOccurs — Minimum occurrences (default: 0)
  • maxOccurs — Maximum occurrences (default: unlimited)

Validation Types

Type Constraints

<age type="positiveInteger"></age>
<price type="positiveDecimal"></price>
<temperature type="integer"></temperature>
<rating type="decimal"></rating>
<count type="number"></count>
<birthdate type="date"></birthdate>
<name type="string"></name>
<metadata type="map"></metadata>

Supported types: positiveInteger, positiveDecimal, integer, decimal, number, date, string (default), map.

Numeric Constraints

<!-- Explicit min/max -->
<age type="integer" min="18" max="65"></age>

<!-- Range shorthand — equivalent to the above -->
<age type="integer" range="18..65"></age>

<!-- Works with decimals too -->
<price type="number" range="0.01..999.99"></price>

range="min..max" is syntactic sugar for min + max. If you specify all three, the explicit min/max attributes win.

String Constraints

<username minLength="3" maxLength="20"></username>
<zipcode length="5"></zipcode>
<email pattern="[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}"></email>
<phone pattern_i="^\d{3}-\d{3}-\d{4}$"></phone>
<status in="active,inactive,pending"></status>
<country fixed="USA"></country>

Pattern modifiers: pattern (default), pattern_i (case-insensitive), pattern_m (multiline), pattern_im / pattern_mi.

Ordering Constraints (before / after)

Enforce positional ordering between sibling tags in the XML document. Both attributes reference the name of a sibling element. This is a structural check — it validates tag order, not field values. Works on any tag type, not just dates.

<event>
    <title></title>
    <!-- startDate must appear after title in the XML -->
    <startDate after="title"></startDate>
    <!-- endDate must appear after startDate -->
    <endDate after="startDate"></endDate>
</event>

<order>
    <orderDate></orderDate>
    <!-- shipDate must come after orderDate AND before deliveryDate -->
    <shipDate after="orderDate" before="deliveryDate"></shipDate>
    <deliveryDate></deliveryDate>
</order>

Failure shape:

{ code: "after",  path: "order.shipDate", actual: "shipDate", expected: "orderDate" }
{ code: "before", path: "order.shipDate", actual: "shipDate", expected: "deliveryDate" }
  • The check is skipped silently when the referenced sibling is absent from the data.
  • To enforce that a reference field must be present, mark it nillable="false".

Date Bounds (min / max / range on dates)

Constrain type="date" fields to a specific date range. Accepts ISO 8601 date strings.

<!-- Explicit min/max -->
<birthDate type="date" min="1900-01-01" max="2010-12-31"></birthDate>

<!-- Range shorthand — equivalent to the above -->
<eventDate type="date" range="2024-01-01..2024-12-31"></eventDate>

<!-- Only a lower bound -->
<startDate type="date" min="2020-01-01"></startDate>

range="min..max" works the same as for numeric fields. Explicit min/max take precedence over range when all three are set. Bounds are inclusive.

Failure shape:

{ code: "min", path: "event.startDate", actual: "2019-06-01", expected: "2020-01-01" }
{ code: "max", path: "event.birthDate", actual: "2025-01-01", expected: "2010-12-31" }

The bounds check is skipped when the date value is itself invalid (a type error is reported instead).

Relational Constraints (sameAs / notSameAs / lessThan / moreThan)

Compare a field's value against another sibling field's value. All four attributes take the name of a sibling element as their value.

<order>
    <originalPrice type="number"></originalPrice>
    <!-- discountedPrice must be strictly less than originalPrice -->
    <discountedPrice type="number" lessThan="originalPrice"></discountedPrice>
    <!-- tax must be strictly less than discountedPrice -->
    <tax type="number" lessThan="discountedPrice"></tax>
</order>

<event>
    <!-- startDate value must be before endDate value -->
    <startDate type="date" lessThan="endDate"></startDate>
    <endDate type="date"></endDate>
</event>

<form>
    <password></password>
    <!-- confirmPassword must equal password -->
    <confirmPassword sameAs="password"></confirmPassword>
</form>

<user>
    <username></username>
    <!-- password must not equal username -->
    <password notSameAs="username"></password>
</user>
Attribute Passes when
lessThan="ref" this value < ref value (strictly)
moreThan="ref" this value > ref value (strictly)
sameAs="ref" this value == ref value
notSameAs="ref" this value != ref value

Comparison is type-aware based on the field's declared type:

  • type="date"Date.parse() comparison
  • numeric types (integer, number, etc.) → Number() comparison
  • string / no type → lexicographic comparison

Failure shape:

{ code: "lessThan", path: "order.discountedPrice", actual: "120", expected: "originalPrice" }
{ code: "sameAs",   path: "form.confirmPassword",  actual: "wrong", expected: "password" }

Skipped silently when:

  • The referenced sibling is absent from the data (use nillable="false" on the ref field if you want that enforced separately)
  • Either value is a map/object rather than a primitive
  • The ref value cannot be coerced to the expected type

Uniqueness Constraints (unique)

unique="true" — Sibling-scoped uniqueness

Values must be unique within the repeatable collection they belong to.

<students>
    <student repeatable>
        <email unique="true"></email>
        <studentId unique="true"></studentId>
    </student>
</students>

The same value is allowed in a different collection (a separate <students> block elsewhere in the document).

unique="global" — Document-scoped uniqueness

Values must be unique across the entire document, regardless of where the field appears.

<root>
    <groupA>
        <transactionId unique="global"></transactionId>
    </groupA>
    <groupB>
        <transactionId unique="global"></transactionId>
    </groupB>
</root>

Failure shape:

{ code: "unique", path: "students.student[2].email", value: "dup@example.com" }

Only the second and subsequent occurrences of a duplicate produce a failure. The first occurrence is always accepted.

Options

const validator = new Validator(rules, {
    unknownAllow: true,                          // default: true
    boolean: ["true", "false", "yes", "no"],     // default: ["true", "false"]
});
  • unknownAllow — When false, reports an "unknown" failure for any element not defined in rules.
  • boolean — Array of strings considered valid for type="boolean" fields.

Custom Validators

validator.register("isEmail", (value, path) => {
    if (!value.includes("@")) {
        return { code: "invalid-email", path, value };
    }
});

Reference by name in rules:

<email checkBy="isEmail" nillable="false"></email>

Return any object to push a failure, or nothing (/ undefined / null) to pass.

Complete Example

Rules

<?xml version="1.0"?>
<students nillable="false">
    <student repeatable minOccurs="1">
        <:a>
            <id length="6"></id>
        </:a>
        <firstname minLength="3" maxLength="10" nillable="false"></firstname>
        <email pattern="[a-z0-9]+@school\.org" nillable="false" unique="true"></email>
        <age type="positiveInteger" range="9..19"></age>
        <enrolledOn type="date" min="2000-01-01"></enrolledOn>
        <!-- graduatesOn must appear after enrolledOn in XML, and its value must be later -->
        <graduatesOn type="date" after="enrolledOn" moreThan="enrolledOn"></graduatesOn>
        <marks>
            <subject repeatable minOccurs="5" maxOccurs="6">
                <name pattern="math|hindi|english|science|history"></name>
                <score type="positiveDecimal" range="0..100"></score>
            </subject>
        </marks>
    </student>
</students>

Code

import Validator from "detailed-xml-validator";
import { readFileSync } from "fs";

const rules = readFileSync("rules.xml", "utf8");
const xmlData = readFileSync("data.xml", "utf8");

const validator = new Validator(rules, { unknownAllow: false });
const failures = validator.validate(xmlData);

if (failures.length > 0) {
    failures.forEach(f => console.error(`[${f.code}] ${f.path}`));
    process.exit(1);
}

// validator.data contains the parsed XML as a plain JS object

Error Response Reference

code Meaning Extra fields
missing Required element absent
unknown Element not in rules (when unknownAllow: false)
unexpected sequence Array where scalar expected
unexpected value in a map Scalar where map expected value
not a <type> Value fails type check value
min / max Numeric or date out of range actual, expected
minOccurs / maxOccurs Occurrence count out of range actual, expected
minLength / maxLength / length String length violation actual, expected
pattern Regex mismatch actual, expected
fixed / in Value not in allowed set actual, expected
after Tag does not appear after referenced sibling in XML actual (tag name), expected (ref tag name)
before Tag does not appear before referenced sibling in XML actual (tag name), expected (ref tag name)
lessThan Field value is not strictly less than sibling value actual, expected (ref field name)
moreThan Field value is not strictly greater than sibling value actual, expected (ref field name)
sameAs Field value does not equal sibling value actual, expected (ref field name)
notSameAs Field value equals sibling value (should differ) actual, expected (ref field name)
unique Duplicate value violates uniqueness constraint value

TypeScript

Type declarations are bundled at src/index.d.ts. Add to your tsconfig.json:

{
  "compilerOptions": {
    "typeRoots": ["./node_modules/detailed-xml-validator/src"]
  }
}

Or import the types directly:

import Validator, { ValidationFailure, OrderingFailure, RelationalFailure, DateBoundsFailure, UniqueFailure } from "detailed-xml-validator";

API Reference

new Validator(rules, options?)

  • rules — XML string containing validation rules. Throws if empty, non-string, or malformed.
  • options — Optional ValidatorOptions object.

validator.validate(xmlData)

Returns ValidationFailure[]. Empty array means the document is valid. Throws if xmlData is empty, non-string, or malformed.

validator.register(name, fn)

Registers a custom validator. fn(value, path) should return a failure object or falsy.

validator.data

The parsed XML as a plain JS object after the last validate() call. null before first call.

Why Not XSD?

  1. Simpler syntax — Rules look like your data, not a separate schema language
  2. All errors at once — No stopping at the first failure
  3. Business-logic validatorscheckBy for custom JS validation
  4. Ordering & uniqueness — Constraints XSD cannot express cleanly
  5. JavaScript-native — No external tools, works in Node.js directly

License

MIT — see LICENSE

About

Validate for XML schema and returns all the possible failures

Resources

License

Stars

Watchers

Forks

Packages