-
-
Notifications
You must be signed in to change notification settings - Fork 151
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(examples): extend xml-converter, add CLI version, update docs
- Loading branch information
1 parent
2f01447
commit 95ba4f6
Showing
12 changed files
with
491 additions
and
194 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
digraph g { | ||
rankdir=LR; | ||
node[fontname="Inconsolata", fontsize="11"]; | ||
edge[arrowsize="0.75", fontname="Inconsolata", fontsize="9"]; | ||
|
||
rank=same { | ||
prettyPrint; | ||
doubleQuote; | ||
trailingComma; | ||
removeTags; | ||
removeAttribs; | ||
xml; | ||
} | ||
|
||
prettyPrint -> formatOpts; | ||
doubleQuote -> formatOpts; | ||
trailingComma -> formatOpts; | ||
|
||
removeTags -> opts; | ||
removeAttribs -> opts; | ||
formatOpts -> opts; | ||
|
||
xml -> main; | ||
opts -> main; | ||
|
||
main -> app -> ui; | ||
|
||
ui -> xml[label="user", style=dashed]; | ||
ui -> prettyPrint[label="user", style=dashed]; | ||
ui -> doubleQuote[label="user", style=dashed]; | ||
ui -> trailingComma[label="user", style=dashed]; | ||
ui -> removeTags[label="user", style=dashed]; | ||
ui -> removeAttribs[label="user", style=dashed]; | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
#!/usr/bin/env node | ||
|
||
const fs = require("fs"); | ||
const path = require("path"); | ||
const program = require("commander"); | ||
const package = require("../package.json"); | ||
const convert = require("./convert"); | ||
const format = require("./format"); | ||
const utils = require("./utils"); | ||
|
||
program | ||
.version(package.version) | ||
.usage("[options] <file>") | ||
.option("-t, --tags <items>", "remove tags from tree", utils.asSet) | ||
.option("-a, --attribs <items>", "remove attribs from tree", utils.asSet) | ||
.option("-v, --var <name>", "generate TS export var decl") | ||
.option("-s, --single-quote", "use single quotes") | ||
.option("-p, --no-pretty", "disable pretty printing") | ||
.parse(process.argv); | ||
|
||
if (program.args.length == 0) { | ||
console.log("Please run: hiccup --help"); | ||
process.exit(1); | ||
} | ||
|
||
const xmlFile = path.resolve(program.args[0]); | ||
const quote = program.singleQuote ? `'` : `"`; | ||
const copts = { | ||
format: program.pretty ? | ||
{ ...format.DEFAULT_FORMAT, quote, indent: 4 } : | ||
{ ...format.COMPACT_FORMAT, quote }, | ||
removeAttribs: program.attribs || new Set(), | ||
removeTags: program.tags || new Set(), | ||
}; | ||
|
||
const xml = fs.readFileSync(xmlFile).toString(); | ||
const hiccup = convert.convertXML(xml, copts); | ||
|
||
console.log( | ||
program.var ? | ||
`export const ${program.var} =\n${hiccup};` : | ||
hiccup | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
import { isString } from "@thi.ng/checks/is-string"; | ||
import { | ||
parse, | ||
ParseElement, | ||
ParseEvent, | ||
Type | ||
} from "@thi.ng/sax"; | ||
import { comp } from "@thi.ng/transducers/func/comp"; | ||
import { pairs } from "@thi.ng/transducers/iter/pairs"; | ||
import { assocObj } from "@thi.ng/transducers/rfn/assoc-obj"; | ||
import { last } from "@thi.ng/transducers/rfn/last"; | ||
import { push } from "@thi.ng/transducers/rfn/push"; | ||
import { transduce } from "@thi.ng/transducers/transduce"; | ||
import { filter } from "@thi.ng/transducers/xform/filter"; | ||
import { map } from "@thi.ng/transducers/xform/map"; | ||
import { DEFAULT_FORMAT, format, FormatOpts } from "./format"; | ||
|
||
export interface ConversionOpts { | ||
format: FormatOpts; | ||
removeTags: Set<string>; | ||
removeAttribs: Set<string>; | ||
} | ||
|
||
export const DEFAULT_OPTS: ConversionOpts = { | ||
format: DEFAULT_FORMAT, | ||
removeAttribs: new Set(), | ||
removeTags: new Set(), | ||
}; | ||
|
||
// converts given XMLish string into formatted hiccup | ||
export const convertXML = (src: string, opts: Partial<ConversionOpts> = {}) => { | ||
let tree = transformTree( | ||
parseXML(src), | ||
<ConversionOpts>{ ...DEFAULT_OPTS, ...opts } | ||
); | ||
return format({ ...DEFAULT_FORMAT, ...opts.format }, "", tree); | ||
}; | ||
|
||
// parses given XMLish string using @thi.ng/sax transducer into a | ||
// sequence of parse events. we only care about the final (or error) | ||
// event, which will be related to the final close tag and contains the | ||
// entire tree | ||
const parseXML = (src: string) => | ||
transduce( | ||
comp( | ||
parse({ trim: true, boolean: true, entities: true }), | ||
filter((e) => e.type === Type.ELEM_END || e.type === Type.ERROR) | ||
), | ||
last(), | ||
src | ||
); | ||
|
||
// transforms string of CSS properties into a plain object | ||
const transformCSS = (css: string) => | ||
css.split(";").reduce( | ||
(acc, p) => { | ||
const [k, v] = p.split(":"); | ||
(v != null) && (acc[k.trim()] = parseAttrib([k, v.trim()])[1]); | ||
return acc; | ||
}, | ||
{} | ||
); | ||
|
||
// takes attrib key-value pair and attempts to coerce / transform its | ||
// value. returns updated pair. | ||
const parseAttrib = (attrib: string[]) => { | ||
let [k, v] = attrib; | ||
if (isString(v)) { | ||
v = v.replace(/[\n\r]+\s*/g, " "); | ||
return k === "style" ? | ||
[k, transformCSS(v)] : | ||
v === "true" ? | ||
[k, true] : | ||
v === "false" ? | ||
[k, false] : | ||
[k, /^[0-9.e+-]+$/.test(v) ? | ||
parseFloat(v) : | ||
v]; | ||
} | ||
return attrib; | ||
}; | ||
|
||
// transforms an entire object of attributes | ||
const transformAttribs = (attribs: any, remove: Set<string> = new Set()) => | ||
transduce( | ||
comp( | ||
filter((a) => !remove.has(a[0])), | ||
map(parseAttrib) | ||
), | ||
assocObj(), | ||
{}, | ||
pairs<string>(attribs) | ||
); | ||
|
||
// transforms element name by attempting to form Emmet-like tags | ||
const transformTag = (tag: string, attribs: any) => { | ||
if (attribs.id) { | ||
tag += "#" + attribs.id; | ||
delete attribs.id; | ||
} | ||
if (isString(attribs.class)) { | ||
const classes = attribs.class.replace(/\s+/g, "."); | ||
classes.length && (tag += "." + classes); | ||
delete attribs.class; | ||
} | ||
return tag; | ||
}; | ||
|
||
// recursively transforms entire parse tree | ||
const transformTree = (tree: ParseEvent | ParseElement, opts: ConversionOpts) => { | ||
if ((<ParseEvent>tree).type === Type.ERROR) { | ||
return ["error", tree.body]; | ||
} | ||
if (opts.removeTags.has(tree.tag)) { | ||
return; | ||
} | ||
const attribs = transformAttribs(tree.attribs, opts.removeAttribs); | ||
const res: any[] = [transformTag(tree.tag, attribs)]; | ||
if (Object.keys(attribs).length) { | ||
res.push(attribs); | ||
} | ||
if (tree.body) { | ||
res.push(tree.body); | ||
} | ||
if (tree.children && tree.children.length) { | ||
transduce( | ||
comp( | ||
map((t: any) => transformTree(t, opts)), | ||
filter((t) => !!t) | ||
), | ||
push(), | ||
res, | ||
tree.children | ||
) | ||
} | ||
return res; | ||
}; |
Oops, something went wrong.