Commit ddb34831 authored by Shaun McCance's avatar Shaun McCance
Browse files

Why am I doing this?

parents
# What the heck is this?
This is an attempt at doing documentation transforms using a template
engine that people know. I appear to be the last XSLT programmer in the
world, and sometimes people get very cross with me when I say those four
letters. So I had an idea.
What if we did it in JavaScript using Mustache templates?
Wild.
So I grabbed Handlebars, which I like a bit more than plain ol Mustache.
And then I put together this transform context that helps us track things
while we recursively process documents.
But wait. I don't have access to an XML parser. DomParser isn't in GJS.
GXml doesn't seem to be introspectable. Some day maybe I'll release Axing,
the GIO-based XML framework I've worked on. But today is not that day.
So I wrote some Python to turn XML into JSON. Like putting a skin suit
on a yak and calling it shaved.
Peace y'all.
-- Shaun
{
"html-inline": {
"http://projectmallard.org/1.0/": {
},
"text()": true
},
"html-block": {
"http://projectmallard.org/1.0/": {
"desc": "mal2html-block-desc",
"div": "mal2html-block-div",
"example": "mal2html-block-div",
"p": "html-block-p",
"list": "mal2html-block-list",
"listing": "mal2html-block-div",
"note": "mal2html-block-note",
"steps": "mal2html-block-list",
"synopsis": "mal2html-block-div",
"title": "mal2html-block-title"
},
"text()": false
},
"html-page-body": {
"http://projectmallard.org/1.0/": {
"page": "mal2html-page-body"
}
},
"html-css-content": {
"http://projectmallard.org/1.0/": {
"page": ["html-css-content-core",
"html-css-content-elements",
"html-css-content-syntax",
"html-css-content-mallard"]
}
},
"html-lang-attrs": {
"http://projectmallard.org/1.0/": {
"*": "mal2html-lang-attrs"
}
},
"html-title": {
"http://projectmallard.org/1.0/": {
"page": "mal2html-title"
}
}
}
This diff is collapsed.
const ByteArray = imports.byteArray;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const Handlebars = imports.handlebars;
var Transform = class {
constructor() {
this.calls = {};
this.templates = {};
this.hbenv = Handlebars.Handlebars.create();
this.hbenv.registerHelper('call', function (template, ...args) {
let options = args[args.length-1];
let transform = options.data.root.transform;
let mapto = [];
let retval = '';
if (args.length > 1) {
mapto = args[0];
}
else if (this === options.data.root) {
if (this.element)
mapto = [this.element];
else if (this.text)
mapto = [this.text];
else
throw 'Context is empty somehow'
}
else {
mapto = [this];
}
for (let node of mapto) {
let tocall = [];
let element, text;
if (typeof(node) == 'string')
text = node;
else
element = node;
if (template in transform.calls) {
let calldef = transform.calls[template];
if (Array.isArray(calldef) || typeof(calldef) == 'string') {
tocall = calldef;
}
else if (text) {
if ('text()' in calldef) {
tocall = calldef['text()']
}
}
else {
if (element && element.namespace in calldef) {
if (element.localname in calldef[element.namespace])
tocall = calldef[element.namespace][element.localname];
else if ('*' in calldef[element.namespace])
tocall = calldef[element.namespace]['*'];
}
}
}
if (!Array.isArray(tocall)) {
tocall = [tocall];
}
if (tocall.length == 0) {
if (template in transform.templates) {
tocall = [template];
}
else {
if (element && element.localname)
transform.warn(`No definition found for ${template} on ${element.localname}`);
else
transform.warn(`No definition found for ${template} on text()`);
return '';
}
}
for (let tmpl of tocall) {
if (text && tmpl == true) {
retval += text;
}
else if (text && tmpl == false) {
/* do nothing */
}
else if (tmpl in transform.templates) {
let ctxt = {'transform': transform,
'element': element,
'text': text,
'vars': options.data.root.vars};
retval += transform.templates[tmpl](ctxt);
}
else {
transform.warn(`No template found for ${tmpl}`);
}
}
}
return new Handlebars.Handlebars.SafeString(retval);
});
this.hbenv.registerHelper('var', (name, ...args) => {
let options = args[args.length-1];
if (args.length > 1) {
options.data.root.vars[name] = args[0];
}
else if (options.fn) {
options.data.root.vars[name] = options.fn(options.data.root);
}
if (options.hash.normalize)
options.data.root.vars[name] = _utils.normalize(options.data.root.vars[name]);
});
this.hbenv.registerHelper('param', (name, ...args) => {
let options = args[args.length-1];
if (options.data.root._params === undefined) {
options.data.root._params = {}
}
if (args.length > 1) {
options.data.root._params[name] = args[0];
}
else if (options.fn) {
options.data.root._params[name] = options.fn(options.data.root);
}
if (options.hash.normalize)
options.data.root._params[name] = _utils.normalize(options.data.root._params[name]);
});
this.hbenv.registerHelper('get_param', (name, options) => {
return options.data.root._params[name];
});
this.hbenv.registerHelper('get_section_depth', _utils.get_section_depth);
this.hbenv.registerHelper('add', (a, b) => a + b);
this.hbenv.registerHelper('mul', (a, b) => a * b);
this.hbenv.registerHelper('div', (a, b) => a / b);
this.hbenv.registerHelper('clamp', (val, min, max) => {
if (val < min)
return min;
if (val > max)
return max;
return val;
});
this.hbenv.registerHelper('is_text', node => { return typeof(node) == 'string'; });
this.hbenv.registerHelper('is_element', _utils.is_element);
this.hbenv.registerHelper('is_title', _utils.is_title);
this.hbenv.registerHelper('is_subtitle', _utils.is_subtitle);
this.hbenv.registerHelper('is_desc', _utils.is_desc);
this.hbenv.registerHelper('is_info', _utils.is_info);
this.hbenv.registerHelper('is_content', _utils.is_content);
this.hbenv.registerHelper('get_title', _utils.get_title);
this.hbenv.registerHelper('get_subtitle', _utils.get_subtitle);
this.hbenv.registerHelper('get_desc', _utils.get_desc);
this.hbenv.registerHelper('get_contents', _utils.get_contents);
this.hbenv.registerHelper('equal', (a, b) => a == b);
this.hbenv.registerHelper('contains', function (strlist, token, options) {
if (typeof(strlist) != 'string') {
return false;
}
for (let stritem of strlist.split(' ')) {
if (token == stritem) {
return true;
}
}
return false;
});
this.hbenv.registerHelper('if_test', function (options) {
/* FIXME */
return true;
});
this.hbenv.registerHelper('if_class', function (options) {
/* FIXME */
return '';
});
this.hbenv.registerHelper('ui_class', function (options) {
/* FIXME */
return '';
});
}
get_template(filename) {
let tmplfile = Gio.File.new_for_path(filename);
let [ok, contents] = tmplfile.load_contents(null);
return this.hbenv.compile(ByteArray.toString(contents));
}
register_calls(filename) {
let callsfile = Gio.File.new_for_path(filename);
let [ok, contents] = callsfile.load_contents(null);
/* FIXME: either merge or do a list of dicts so we can stack these */
this.calls = JSON.parse(ByteArray.toString(contents));
}
register_template(filename) {
let tmpl = this.get_template(filename);
let basename = GLib.path_get_basename(filename);
if (basename.slice(-3) == '.hb') {
basename = basename.slice(0, -3)
}
this.templates[basename] = tmpl;
this.hbenv.registerPartial(basename, tmpl);
}
register_templates(dirname) {
let dir = Gio.File.new_for_path(dirname);
let children = dir.enumerate_children('standard::name,standard::type', 0, null);
let info;
while ((info = children.next_file(null)) != null) {
if (info.get_file_type() == Gio.FileType.REGULAR) {
if (info.get_name().slice(-3) == '.hb') {
this.register_template(dir.get_child(info.get_name()).get_path());
}
}
}
}
transform(template, element) {
_utils.parentify(element);
return this.templates[template]({'transform': this, 'element': element, 'vars': {}});
}
warn(message) {
if (true)
print(message);
}
}
class _utils {
static
parentify(element) {
for (let child of element.children) {
if (typeof(child) != 'string') {
child.parent = element;
_utils.parentify(child);
}
}
}
static
normalize(str) {
return str.replace(/\s+/g, ' ').replace(/^\s+/, '').replace(/\s$/, '');
}
static
get_section_depth(element) {
let retval = 0;
let parent = element;
while(parent) {
if (parent.namespace == 'http://projectmallard.org/1.0/')
if (element.localname == 'section')
retval++;
parent = parent.parent;
}
return retval;
}
static is_element(element, localname, namespace) {
if (typeof(element) != 'object')
return false;
if (localname != element.localname)
return false;
if (namespace == element.namespace)
return true;
if (namespace == 'mallard' && element.namespace == 'http://projectmallard.org/1.0/')
return true;
return false;
}
static
is_title(element) {
if (element.namespace == 'http://projectmallard.org/1.0/')
return element.localname == 'title';
return false;
}
static
is_subtitle(element) {
if (element.namespace == 'http://projectmallard.org/1.0/')
return element.localname == 'subtitle';
return false;
}
static
is_desc(element) {
if (element.namespace == 'http://projectmallard.org/1.0/')
return element.localname == 'desc';
return false;
}
static
is_info(element) {
if (element.namespace == 'http://projectmallard.org/1.0/')
return element.localname == 'info';
return false;
}
static
is_section(element) {
if (element.namespace == 'http://projectmallard.org/1.0/')
return element.localname == 'section';
return false;
}
static
is_content(element) {
if (_utils.is_title(element) || _utils.is_subtitle(element) ||
_utils.is_desc(element) || _utils.is_info(element) ||
_utils.is_section(element))
return false;
return true;
}
static
get_title(element) {
for (let child of element.children) {
if (_utils.is_title(child))
return [child];
}
return []
}
static
get_subtitle(element) {
for (let child of element.children) {
if (_utils.is_subtitle(child))
return [child];
}
return []
}
static
get_desc(element) {
for (let child of element.children) {
if (_utils.is_desc(child))
return [child];
}
return []
}
static
get_contents(element) {
let retval = [];
for (let child of element.children) {
if (_utils.is_content(child))
retval.push(child);
}
return retval;
}
static
get_sections(element) {
let retval = [];
for (let child of element.children) {
if (_utils.is_section(child))
retval.push(child);
}
return retval;
}
}
function get_xml(filename) {
let xmlfile = Gio.File.new_for_uri(filename);
}
/*
hb = Handlebars.Handlebars.create()
for each tmpl:
t = hb.compile(tmpl)
or
t = hb.template(tmpl)
hb.registerPartial(tmpl, t)
hb.registerPartial('name', 'stringcontent')
*/
/*
{
namespace: 'http://projectmallard.org/1.0/',
localname: 'page',
mallard: true,
docbook: false,
dita: false,
attrs: {
'style': 'foo bar',
'{http://www.w3.org/1999/xlink}href': 'http://example.com',
},
info: { },
title: { },
desc: { },
contents: [ ],
}
*/
{
"namespace": "http://projectmallard.org/1.0/",
"localname": "page",
"attrs": {
"type": "topic",
"style": "concept",
"id": "credit1"
},
"children": [
"\n ",
{
"namespace": "http://projectmallard.org/1.0/",
"localname": "info",
"attrs": {},
"children": [
"\n ",
{
"namespace": "http://projectmallard.org/1.0/",
"localname": "credit",
"attrs": {
"type": "author copyright"
},
"children": [
"\n ",
{
"namespace": "http://projectmallard.org/1.0/",
"localname": "name",
"attrs": {},
"children": [
"Shaun McCance"
]
},
"\n ",
{
"namespace": "http://projectmallard.org/1.0/",
"localname": "email",
"attrs": {},
"children": [
"shaunm@redhat.com"
]
},
"\n ",
{
"namespace": "http://projectmallard.org/1.0/",
"localname": "years",
"attrs": {},
"children": [
"2020"
]
},
"\n "
]
},
"\n\n ",
{
"namespace": "http://projectmallard.org/1.0/",
"localname": "credit",
"attrs": {
"type": "editor"
},
"children": [
"\n ",
{
"namespace": "http://projectmallard.org/1.0/",
"localname": "name",
"attrs": {},
"children": [
"Rupert Monkey"
]
},
"\n ",
{
"namespace": "http://projectmallard.org/1.0/",
"localname": "years",
"attrs": {},
"children": [
"2020"
]
},
"\n "
]
},
"\n\n ",
{
"namespace": "http://projectmallard.org/1.0/",
"localname": "credit",
"attrs": {
"type": "illustrator"
},
"children": [
"\n ",
{
"namespace": "http://projectmallard.org/1.0/",
"localname": "name",
"attrs": {},
"children": [
"Wanda the Fish"
]
},
"\n ",
{
"namespace": "http://projectmallard.org/1.0/",
"localname": "years",
"attrs": {},
"children": [
"2020"
]
},
"\n "
]
},
"\n\n ",
{
"namespace": "http://projectmallard.org/1.0/",
"localname": "credit",
"attrs": {
"type": "translator"
},
"children": [
"\n ",
{
"namespace": "http://projectmallard.org/1.0/",
"localname": "name",
"attrs": {},
"children": [
"Wilbur"
]
},
"\n ",
{
"namespace": "http://projectmallard.org/1.0/",
"localname": "years",
"attrs": {},