Skip to content

Instantly share code, notes, and snippets.

@noobymatze
Created January 5, 2018 13:10
Show Gist options
  • Save noobymatze/faf408872cd3aaee2073f468adf8f302 to your computer and use it in GitHub Desktop.
Save noobymatze/faf408872cd3aaee2073f468adf8f302 to your computer and use it in GitHub Desktop.
Add some types to allow rendering FOP Xml
/**
* `Xml` describes a simple XML document, specific to FOP.
*
* This type is used as the result for TSX, which allows writing
* Xml nodes in TypeScript. Here is an example for valid TypeScript
* code, using this library (provided you saved it in a file ending
* with .tsx, for example person.tsx):
*
* ```
* // person.tsx
* import { Xml, node } from 'fop-xml';
*
* interface Person {
* firstName: string;
* lastName: string;
* }
*
* const render = (person: Person): Xml => (
* <root>
* <layout-master-set master-name="standard" page-width=">
* `${person.firstName} ${person.lastName}`
* </layout-master-set>
* </root>
* )
*
* render({firstName: 'Matthias', lastName: 'Metzger'});
* ```
*
* Now compile with:
*
* ```
* $ tsc --jsx react --jsxFactory "node" person.tsx
* ```
*
* In a bigger project, it is probably valuable to add a
* `tsconfig.json`.
*/
export type Xml
= { type: 'node', name: string, attributes: {}, children: Array<Xml | string>}
| { type: 'text', value: string }
| { type: 'raw', value: string };
/**
* Create a new node, with the given name, attributes and children.
*
* *Note:* This function should be declared in the tsconfig.json as
* `jsxFactory` to render FOP and will seldomly be used directly.
*
* @param name a name of a tag
* @param attributes all attributes of this tag
* @param children potential children of this tag
*/
export function node(name: string, attributes: {}, ...children: Array<Xml | string>) {
return {
type: 'node',
name,
attributes,
children
};
}
/**
* A simple single text, which will be escaped.
*
* @param value the actual value
*/
export function text(value: string): Xml {
return {
type: 'text',
value
};
}
/**
* A simple single text, which will *not* be escaped.
*
* *Note:* You should only use this, if you know, what you
* are doing. The lengthy name exists for a reason.
*
* @param value the actual value
* @return an `Xml` structure
*/
export function dangerouslyUnescapedText(value: string): Xml {
return {
type: 'raw',
value
};
}
// RENDER
/**
* Render the given `Xml` structure to a string.
*
* @param xml an `Xml` value
* @return a string
*/
export function renderToString(xml: Xml | string): string {
if (typeof xml === 'string') {
return escape(xml)
}
switch (xml.type) {
case 'text':
return escape(xml.value);
case 'raw':
return xml.value;
case 'node':
return xml.name === 'root' ?
`<?xml version="1.0" encoding="UTF-8" ?>${renderNode(
xml.name,
{ ...xml.attributes,
// We don't want the user to be able to specify the
// following attributes, since we would need to make
// the `fo:` prefix in the resulting XML configurable.
// Therefore override, if they exist, add otherwise.
"xml:fo": "http://www.w3.org/1999/XSL/Format",
"xmlns:xs": "http://www.w3.org/2001/XMLSchema",
"xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance",
},
xml.children
)}` :
renderNode(xml.name, xml.attributes, xml.children)
}
}
function renderNode(name: string, attributes: {}, children: Array<Xml | string>): string {
const attrs = Object
.keys(attributes)
.map(a => ` ${a}="${escape(attributes[a])}"`)
.join('');
const kids = children
.map(node => renderToString(node))
.join('');
return `<fo:${name}${attrs}>${kids}</fo:${name}>`;
}
/**
* Escape the given value.
*
* There are some sensitive characters in any XML
* document. Therefore, those will be escaped here.
*
* @param value any unescaped string
* @return an escaped string
*/
function escape(value: string): string {
return value
.replace('"', '&quot;')
.replace("'", '&apos;')
.replace('<', '&lt;')
.replace('>', '&gt;')
.replace('&', '&amp;');
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment