# cruftlesser

> Yet another simple way to parse and generate XML

Latest version **1.0.5** (published 2024-07-01) · MIT license · 0 weekly downloads

## Install

```sh
npm install cruftlesser
pnpm add cruftlesser
yarn add cruftlesser
bun add cruftlesser
```

## Health

**Score 25/100 (F)** — status: abandoned.

Positive: has types; no vulnerabilities; high quality score.

Warnings: low downloads; no esm support.

Negative: abandoned; low maintenance score.

## Facts

| | |
|---|---|
| Version | 1.0.5 |
| Published | 2024-07-01 |
| First published | 2022-10-18 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | CommonJS |
| Dependencies | 2 |
| Unpacked size | 72.5 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 0 |
| Author | Wilfred Springer |

## Links

- npm: https://www.npmjs.com/package/cruftlesser
- Repository: https://github.com/brakowski/cruftlesser
- npm.io page: https://npm.io/package/cruftlesser

## Dependencies (2)

- [lodash](https://npm.io/package/lodash.md) ^4.17.21
- [@xmldom/xmldom](https://npm.io/package/@xmldom/xmldom.md) ^0.8.4

## Recent versions

- 1.0.5 (latest) — 2024-07-01
- 1.0.4 — 2024-06-28
- 1.0.3 — 2023-11-13
- 1.0.2 — 2022-11-02
- 1.0.1 — 2022-10-18
- 1.0.0 — 2022-10-18

## README

<!--
  -- This file is auto-generated from ./README.js.md. Changes should be made there.
  -->
# ! This is a fork of the great cruftless library !

This fork fixed a few vulnerabilities, since the original maintainer of the library does not seem to be active anymore. The original can still be found under:

https://github.com/wspringer/cruftless

# README

An XML builder / parser that tries to ease the common cases, allowing you to quickly build a model from your document structure and get a builder / parser for free.


## Yet another XML binding framework?

I hate to say this, but: 'yes'. Or, perhaps: 'no'. Because Cruftless is not really an XML binding framework as you know it. It's almost more like Handlebars. But where Handlebars allows you to only _generate_ documents, Cruftless also allows you to _extract_ data from documents.

## Building XML documents

Cruftless builds a simplified metamodel of your XML document, and it's not based on a DOM API. So, if this is the XML document:

```xml
<person>
  <name>John Doe</name>
  <age>16</age>
</person>
```

Then, using the builder API, Cruftless allows you to _build_ a model of your document like this:

```javascript
const { element, attr, text } = require('cruftlesser')();

let el = element("person").content(
  element("name").content(text().value("John Doe")),
  element("age").content(text().value(16))
);
```

… and then to turn it back into XML, you'd use the `toXML()` operation:

```javascript
el.toXML(); // ⇨ '<person>\r\n  <name>John Doe</name>\r\n  <age>16</age>\r\n</person>'
```

… or the `toDOM()` operation instead to return a DOM representation of the document:

```javascript
el.toDOM();
```

## Binding

Now, this itself doesn't seem all that useful. Where it gets useful is when you start adding references to your document model:

```javascript
el = element("person").content(
  element("name").content(text().bind("name")),
  element("age").content(text().bind("age"))
);
```

Now, if you want to generate different versions of your XML document for different persons, you can simply pass in an object with `name` and `age` properties:

```javascript
let xml = el.toXML({ name: "John Doe", age: "16" }); // ⇨ '<person>\r\n  <name>John Doe</name>\r\n  <age>16</age>\r\n</person>'
```

But the beauty is, it also works the other way around. If you have your model with binding expressions, then you're able to _extract_ data from XML like this:

```javascript
el.fromXML(xml); // ⇨ { name: 'John Doe', age: '16' }
```

## Less tedious, please

I hope you can see how this is useful. However, I also hope you can see that this is perhaps not ideal. I mean, it's nice that you're able to build a model of XML, but in many cases, you already have the snippets of XML that you need to populate with data. So, the question is if there is an easier way to achieve the same, if you already have snippets of XML. Perhaps not surprisingly, there is:

```javascript
let template = `<person>
  <name>{{name}}</name>
  <age>{{age}}</age>
</person>`;

let { parse } = require('cruftlesser')();

el = parse(template);
console.log(el.toXML({ name: "Jane Doe", age: "18" }));
⇒ <person>
⇒   <name>Jane Doe</name>
⇒   <age>18</age>
⇒ </person>
```

## Additional metadata

The example above is rather simple. However, Cruftless allows you also deal with
more complex cases. And not only that, it also allows you to set additional
metadata on binding expressions, using the pipe symbol. In the template below,
we're binding `<person/>` elements inside a `<persons/>` element to a property
`persons`, and we're inserting every occurence of it into the `persons` array.
The processing instruction annotation might be feel a little awkward at first. There are
other ways to define the binding, including one that requires using attributes
of particular namespace. Check the test files for examples.

```javascript
template = parse(`<persons>
  <person><?bind persons|array?>
    <name>{{name|required}}</name>
    <age>{{age|integer|required}}</age>
  </person>
</persons>`);

// Note that because of the 'integer' modifier, integer values are
// now automatically getting transfered from and to strings.

console.log(
  template.toXML({
    persons: [
      { name: "John Doe", age: 16 },
      { name: "Jane Doe", age: 18 },
    ],
  })
);
⇒ <persons>
⇒   <person>
⇒     <name>John Doe</name>
⇒     <age>16</age>
⇒   </person>
⇒   <person>
⇒     <name>Jane Doe</name>
⇒     <age>18</age>
⇒   </person>
⇒ </persons>
```

You can add your own value types to convert from and to the string literals
included in the XML representation.

```javascript
const { element, attr, text, parse } = require('cruftlesser')({
  types: {
    zeroOrOne: {
      type: "boolean",
      from: (str) => str == "1",
      to: (value) => (value ? "1" : "0"),
    },
  },
});

template = parse(`<foo>{{value|zeroOrOne}})</foo>`);
console.log(template.toXML({ value: true }));
console.log(template.toXML({ value: false }));
⇒ <foo>1</foo>
⇒ <foo>0</foo>
```

The same works with attributes as well:

```javascript
template = parse(`<foo bar="{{value|zeroOrOne}}"/>`);
console.log(template.toXML({ value: true }));
console.log(template.toXML({ value: false }));
⇒ <foo bar="1"/>
⇒ <foo bar="0"/>
```

Sometimes, it's still useful to be able to access the raw field values, ignoring
the type annotations.

To get the actual data:

```javascript
// The second argument defaults to false, so might as well leave it out
console.log(template.fromXML("<foo bar='1'/>", false));
⇒ { value: true }
```

To get the raw data:

```javascript
console.log(template.fromXML("<foo bar='1'/>", true));
⇒ { value: '1' }
```

## Alternative notation

The `<!--persons|array-->` way of annotating an element is not the only way you are able to add metadata. Another way to add metadata to elements is by using one of the reserved attributes prefixed with `c-`.

```javascript
template = parse(`<persons>
  <person c-bind="persons|array">
    <name>{{name|required}}</name>
    <age>{{age|integer|required}}</age>
  </person>
</persons>`);

console.log(
  template.toXML({
    persons: [
      { name: "John Doe", age: 16 },
      { name: "Jane Doe", age: 18 },
    ],
  })
);
⇒ <persons>
⇒   <person>
⇒     <name>John Doe</name>
⇒     <age>16</age>
⇒   </person>
⇒   <person>
⇒     <name>Jane Doe</name>
⇒     <age>18</age>
⇒   </person>
⇒ </persons>
```

If you hate the magic `c-` prefixed attributes, then you can also a slightly
less readable but admittedly more correct XML namespace:

```javascript
template = parse(`<persons>
  <person xmlns:c="https://github.com/wspringer/cruftless" c:bind="persons|array">
    <name>{{name|required}}</name>
    <age>{{age|integer|required}}</age>
  </person>
</persons>`);

console.log(
  template.toXML({
    persons: [
      { name: "John Doe", age: 16 },
      { name: "Jane Doe", age: 18 },
    ],
  })
);
⇒ <persons>
⇒   <person>
⇒     <name>John Doe</name>
⇒     <age>16</age>
⇒   </person>
⇒   <person>
⇒     <name>Jane Doe</name>
⇒     <age>18</age>
⇒   </person>
⇒ </persons>
```

## Conditionals

There may be times when you want to exclude entire sections of an XML structure
if a particular condition is met. Cruftless has some basic support for that,
albeit limited. You can set conditions on elements, using the `c-if` attribute.
In that case, the element will only be included in case the expression of the
`c-if` attribute is evaluating to something else than `undefined` or `null`.

```javascript
template = parse(`<foo><bar c-if="a">text</bar></foo>`);

template.toXML({}); // ⇨ '<foo/>'
template.toXML({ a: null }); // ⇨ '<foo/>'
template.toXML({ a: void 0 }); // ⇨ '<foo/>'
template.toXML({ a: 3 }); // ⇨ '<foo>\r\n  <bar>text</bar>\r\n</foo>'
```

If your template contains variable references, and the data structure you are
passing in does not contain these references, then — instead of generating the
value `undefined`, Cruftless will drop the entire element. In fact, if a deeply
nested element contains references to variable, and that variable is not
defined, then it will not only drop _that_ element, but all elements that
included that element referring to a non-existing variable.

```javascript
template = parse(`<level1>
  <level2 b="{{b}}">
    <level3>{{a}}</level3>
  </level2>
</level1>`);

console.log(template.toXML({ b: 2 }));
⇒ <level1>
⇒   <level2 b="2"/>
⇒ </level1>
```

```javascript
console.log(template.toXML({ b: 2, a: 3 }));
⇒ <level1>
⇒   <level2 b="2">
⇒     <level3>3</level3>
⇒   </level2>
⇒ </level1>
```

```javascript
console.log(template.toXML({ a: 3 }));
⇒ <level1>
⇒   <level2>
⇒     <level3>3</level3>
⇒   </level2>
⇒ </level1>
```

## CDATA

Your XML documents might contain CDATA sections. Cruftless will treat those like
ordinary text nodes. That is, if you have an element that has a text node bound
to a variable, then it will resolve those values regardless of the fact if the
incoming XML document has a text node or a CDATA node.

```javascript
template = parse(`<person>{{name}}</person>`);
console.log(template.fromXML(`<person>Alice</person>`));
⇒ { name: 'Alice' }
```

```javascript
console.log(template.fromXML(`<person><![CDATA[Alice]]></person>`));
⇒ { name: 'Alice' }
```

However, if you would _produce_ XML, then — by default — it will always produce
a text node:

```javascript
console.log(template.toXML({ name: "Alice" }));
⇒ <person>Alice</person>
```

That is, unless you specifiy a `cdata` option in your binding:

```javascript
template = parse(`<person>{{name|cdata}}</person>`);
console.log(template.toXML({ name: "Alice" }));
⇒ <person>
⇒   <![CDATA[Alice]]>
⇒ </person>
```


## JSON-ish Schema (incomplete, subject to change)

Since Cruftless has all of the metadata of your XML document and how it binds to
your data structures at its disposal, it also allows you to generate a 'schema'
of the data structure it expects.

```javascript
let schema = template.descriptor();
console.log(JSON.stringify(schema, null, 2));
⇒ {
⇒   "type": "object",
⇒   "keys": {
⇒     "name": {
⇒       "type": "string"
⇒     }
⇒   }
⇒ }
```

The schema will include additional metadata you attached to expressions:

```javascript
template = parse(`<person>
  <name>{{name|sample:Wilfred}}</name>
  <age>{{age|integer|sample:45}}</age>
</person>`);

schema = template.descriptor();
console.log(JSON.stringify(schema, null, 2));
⇒ {
⇒   "type": "object",
⇒   "keys": {
⇒     "name": {
⇒       "type": "string",
⇒       "sample": "Wilfred"
⇒     },
⇒     "age": {
⇒       "type": "integer",
⇒       "sample": 45
⇒     }
⇒   }
⇒ }
```

## RelaxNG Schema

Since Cruftless captures the structure of the XML document, it's also able to
generate an XML Schema representation of the document structure. Only, it's not
relying on XML Schema. It's using RelaxNG instead. If you never heard of
RelaxNG before: think of it as a more readable better version of XML Schema,
without the craziness.

So based on the template above, this would give you the RelaxNG schema:

```javascript
const { relaxng } = require('cruftlesser')();

console.log(relaxng(template));
⇒ <grammar datatypeLibrary="http://www.w3.org/2001/XMLSchema-datatypes" xmlns="http://relaxng.org/ns/structure/1.0">
⇒   <start>
⇒     <element name="person">
⇒       <optional>
⇒         <element name="name">
⇒           <data type="string"/>
⇒         </element>
⇒       </optional>
⇒       <optional>
⇒         <element name="age">
⇒           <data type="integer"/>
⇒         </element>
⇒       </optional>
⇒     </element>
⇒   </start>
⇒ </grammar>
```


----
Markdown generated from [./README.js.md](./README.js.md) by [![RunMD Logo](https://i.imgur.com/h0FVyzU.png)](https://github.com/broofa/runmd)

---
_Source: https://npm.io/package/cruftlesser · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
