diff --git a/hamill/hamill.mjs b/hamill/hamill.mjs index ae0e749..1a6ae3f 100644 --- a/hamill/hamill.mjs +++ b/hamill/hamill.mjs @@ -28,21 +28,18 @@ // Imports //------------------------------------------------------------------------------- +import { LANGUAGES, LEXERS } from "./weyland.mjs"; + let fs = null; -if (typeof process !== 'undefined' && process !== null && typeof process.version !== 'undefined' && process.version !== null && typeof process.version === "string") // ==="node" -{ +if ( + typeof process !== "undefined" && + process !== null && + typeof process?.version !== "undefined" && + typeof process?.version === "string" +) { + // Node code only //import fs from 'fs'; - fs = await import('fs'); -} - -//----------------------------------------------------------------------------- -// Functions -//----------------------------------------------------------------------------- - -function pp(o) -{ - o.document = 'redacted'; - return o; + fs = await import("fs"); } //----------------------------------------------------------------------------- @@ -51,50 +48,46 @@ function pp(o) // Tagged lines -class Line -{ - constructor(value, type) - { +class Line { + constructor(value, type, param = null) { this.value = value; this.type = type; + this.param = param; } - toString() - { - return `${this.type} |${this.value}|`; + toString() { + if (this.param === null) { + return `${this.type} |${this.value}|`; + } else { + return `${this.type} |${this.value}| (${this.param})`; + } } } // Document nodes -class EmptyNode -{ - constructor(document) - { +class EmptyNode { + constructor(document, ids = null, cls = null) { this.document = document; - if (this.document === undefined || this.document === null) - { + if (this.document === undefined || this.document === null) { throw new Error("Undefined or null document"); } + this.ids = ids; + this.cls = cls; } - toString() - { + toString() { return this.constructor.name; } } -class Node extends EmptyNode -{ - constructor(document, content=null) - { - super(document); +class Node extends EmptyNode { + constructor(document, content = null, ids = null, cls = null) { + super(document, ids, cls); this.content = content; } - toString() - { - if (this.content === null) - { + toString() { + if (this.content === null) { return this.constructor.name; } else { return this.constructor.name + " { content: " + this.content + " }"; @@ -102,278 +95,215 @@ class Node extends EmptyNode } } -class Text extends Node -{ - to_html() - { +class Text extends Node { + to_html() { return this.content; } } -class Start extends Node -{ - to_html() - { +class Start extends Node { + to_html() { let markups = { - 'bold': 'b', - 'italic': 'i', - 'stroke': 's', - 'underline': 'u', - 'sup': 'sup', - 'sub': 'sub', - // 'code': 'code' + bold: "b", + italic: "i", + stroke: "s", + underline: "u", + sup: "sup", + sub: "sub", + strong: "strong", + em: "em", + // 'code': 'code' + }; + if (!(this.content in markups)) { + throw new Error(`Unknown text style:${this.content}`); } return `<${markups[this.content]}>`; } } -class Stop extends Node -{ - to_html() - { +class Stop extends Node { + to_html() { let markups = { - 'bold': 'b', - 'italic': 'i', - 'stroke': 's', - 'underline': 'u', - 'sup': 'sup', - 'sub': 'sub', - // 'code': 'code' + bold: "b", + italic: "i", + stroke: "s", + underline: "u", + sup: "sup", + sub: "sub", + strong: "strong", + em: "em", + // 'code': 'code' + }; + if (!(this.content in markups)) { + throw new Error(`Unknown text style:${this.content}`); } - return ``; + return ``; } } -class Picture extends Node -{ - constructor(document, url, text=null, cls=null, ids=null) - { - super(document, url); +class Picture extends Node { + constructor(document, url, text = null, cls = null, ids = null) { + super(document, url, ids, cls); this.text = text; - this.cls = cls; - this.ids = ids; } - to_html() - { - let cls = ''; - if (this.cls !== null) - { - cls = ` class="${this.cls}"`; - } - let ids = ''; - if (this.ids !== null) - { - ids = ` id="${this.ids}"`; - } - if (this.text !== null) - { - return `
${this.text}
${this.text}
`; - } - else - { - return ``; + to_html() { + let cls = (this.cls === null) ? '' : ` class="${this.cls}"`; + let ids = (this.ids === null) ? '' : ` id="${this.ids}"`; + if (this.text !== null) { + return `
${this.text}
`; + } else { + return ``; } } } -class HR extends EmptyNode -{ - to_html() - { +class HR extends EmptyNode { + to_html() { return "
\n"; } } -class BR extends EmptyNode -{ - to_html() - { - return '
'; +class BR extends EmptyNode { + to_html() { + return "
"; } } -class Span extends EmptyNode -{ - constructor(document, ids, cls, text) - { - super(document); - this.ids = ids; - this.cls = cls; - this.text = text; - } - - to_html() - { - let r = "${this.text}`; - return r; +class Span extends Node { + to_html() { + let cls = (this.cls === null) ? '' : ` class="${this.cls}"`; + let ids = (this.ids === null) ? '' : ` id="${this.ids}"`; + return `${this.content}`; } } -class ParagraphIndicator extends EmptyNode -{ - constructor(document, ids, cls) - { - super(document); - this.ids = ids; - this.cls = cls; - } +class ParagraphIndicator extends EmptyNode { - to_html() - { - let r = "`; } } -class Comment extends Node {} +class Comment extends Node { } -class Row extends EmptyNode -{ - constructor(document, node_list_list) - { +class Row extends EmptyNode { + constructor(document, node_list_list) { super(document); this.node_list_list = node_list_list; this.is_header = false; } } -class RawHTML extends Node -{ - to_html() - { +class RawHTML extends Node { + to_html() { return this.content + "\n"; } } -class Include extends Node {} +class Include extends Node { } -class Title extends Node -{ - constructor(document, content, level) - { +class Title extends Node { + constructor(document, content, level) { super(document, content); this.level = level; } } -class StartDiv extends EmptyNode -{ - constructor(document, id=null, cls=null) - { - super(document); - this.id = id; - this.cls = cls; +class StartDetail extends Node { + + to_html() { + let cls = (this.cls === null) ? '' : ` class="${this.cls}"`; + let ids = (this.ids === null) ? '' : ` id="${this.ids}"`; + return `${this.content}\n`; } +} - to_html() - { - if (this.id !== null && this.cls !== null) - { - return `
\n`; - } - else if (this.id !== null) - { - return `
\n`; - } - else if (this.cls !== null) - { - return `
\n`; - } - else - { - return '
\n'; - } +class Detail extends Node { + constructor(document, content, data, ids = null, cls = null) { + super(document, content, ids, cls); + this.data = data; + } + + to_html() { + let cls = (this.cls === null) ? '' : ` class="${this.cls}"`; + let ids = (this.ids === null) ? '' : ` id="${this.ids}"`; + return `${this.content}${this.data}\n`; + } +} + +class EndDetail extends EmptyNode { + to_html() { + return "\n"; } } -class EndDiv extends EmptyNode -{ - to_html() - { +class StartDiv extends EmptyNode { + constructor(document, ids = null, cls = null) { + super(document, ids, cls); + } + + to_html() { + let cls = (this.cls === null) ? '' : ` class="${this.cls}"`; + let ids = (this.ids === null) ? '' : ` id="${this.ids}"`; + return `\n`; + } +} + +class EndDiv extends EmptyNode { + to_html() { return "
\n"; } } -class Composite extends EmptyNode -{ - constructor(document, parent=null) - { +class Composite extends EmptyNode { + constructor(document, parent = null) { super(document); this.children = []; this.parent = parent; } - add_child(o) - { - if (! o instanceof EmptyNode) - { - throw new Error("A composite can only be made of EmptyNode and subclasses"); + add_child(o) { + if (!(o instanceof EmptyNode)) { + throw new Error( + "A composite can only be made of EmptyNode and subclasses" + ); } this.children.push(o); - if (o instanceof Composite) - { + if (o instanceof Composite) { o.parent = this; } return o; } - add_children(ls) - { - //this.children = this.children.concat(ls); - for (let e of ls) - { + add_children(ls) { + for (let e of ls) { this.add_child(e); } } - last() - { - return this.children[this.children.length-1]; + last() { + return this.children[this.children.length - 1]; } - parent() - { + get_parent() { return this.parent; } - root() - { - if (this.parent === null) - { + root() { + if (this.parent === null) { return this; } else { return this.parent.root(); } } - toString() - { + toString() { return this.constructor.name + ` (${this.children.length})`; } - pop() - { + pop() { return this.children.pop(); } - to_html(level=0) - { + to_html(level = 0) { let s = ""; - for (const child of this.children) - { - if (child instanceof List) - { + for (const child of this.children) { + if (child instanceof List) { s += "\n" + child.to_html(level); } else { s += child.to_html(); @@ -383,23 +313,25 @@ class Composite extends EmptyNode } } -class TextLine extends Composite -{ - constructor(document, children=[]) - { +class TextLine extends Composite { + constructor(document, children = []) { super(document); this.add_children(children); } - to_html() - { - return this.document.string_to_html('', this.children); + to_html() { + return this.document.string_to_html("", this.children); } } -class List extends Composite -{ - constructor(document, parent, ordered=false, reverse=false, level=0, children=[]) - { +class List extends Composite { + constructor( + document, + parent, + ordered = false, + reverse = false, + level = 0, + children = [] + ) { super(document, parent); this.add_children(children); this.level = level; @@ -407,27 +339,30 @@ class List extends Composite this.reverse = reverse; } - to_html(level=0) - { + to_html(level = 0) { let start = " ".repeat(level); let end = " ".repeat(level); - if (this.ordered) - { - start += "
    "; + if (this.ordered) { + if (this.reverse) { + start += "
      "; + } else { + start += "
        "; + } end += "
      "; } else { start += "
        "; end += "
      "; } let s = start + "\n"; - for (const child of this.children) - { - s += " ".repeat(level) + "
    1. "; - if (child instanceof List) - { - s += "\n" + child.to_html(level+1) + "
    2. \n"; - } else if (child instanceof Composite && !(child instanceof TextLine)) { - s += child.to_html(level+1) + " \n"; + for (const child of this.children) { + s += " ".repeat(level) + "
    3. "; + if (child instanceof List) { + s += "\n" + child.to_html(level + 1) + "
    4. \n"; + } else if ( + child instanceof Composite && + !(child instanceof TextLine) + ) { + s += child.to_html(level + 1) + " \n"; } else { s += child.to_html() + "\n"; } @@ -441,89 +376,106 @@ class List extends Composite // [[https://...]] display = url // [[display->label]] (you must define somewhere ::label:: https://) // [[display->https://...]] -class Link extends EmptyNode -{ - constructor(document, url, display=null) - { +// http[s] can be omitted, but in this case the url should start by www. +class Link extends EmptyNode { + constructor(document, url, display = null) { super(document); this.url = url; this.display = display; // list of nodes } - toString() - { + toString() { return this.constructor.name + ` ${this.display} -> ${this.url}`; } - to_html() - { + to_html() { let url = this.url; let display = null; - if (this.display !== null) - { - display = this.document.string_to_html('', this.display); - } - if (!url.startsWith('https://') && !url.startsWith('http://')) - { - if (url === '#') - { - url = this.document.get_label( this.document.make_anchor(display)); - } - else - { + if (this.display !== null) { + display = this.document.string_to_html("", this.display); + } + if ( + !url.startsWith("https://") && + !url.startsWith("http://") && + !url.startsWith("www.") + ) { + if (url === "#") { + url = this.document.get_label( + this.document.make_anchor(display) + ); + } else { url = this.document.get_label(url); } } - if (display === undefined || display === null) - { + if (display === undefined || display === null) { display = url; } return `${display}`; } } -class Definition extends Node -{ - constructor(document, header, content) - { + +class Definition extends Node { + constructor(document, header, content) { super(document, content); this.header = header; } } -class Quote extends Node {} -class Code extends Node -{ - constructor(document, content, inline=false) - { + +class Quote extends Node { + constructor(document, content, cls = null, ids = null) { super(document, content); + this.cls = cls; + this.ids = ids; + } + toString() { + return `Quote { content: ${this.content.replace(/\n/g, "\\n")}}`; + } + to_html() { + let cls = (this.cls === null) ? '' : ` class="${this.cls}"`; + let ids = (this.ids === null) ? '' : ` id="${this.ids}"`; + return `\n` + this.document.safe(this.content).replace(/\n/g, "
      \n") + "\n"; + } +} + +class Code extends Node { + constructor(document, content, ids = null, cls = null, lang = null, inline = false) { + super(document, content, ids, cls); this.inline = inline; + this.lang = lang; + } + + toString() { + let lang = (this.lang === null) ? "" : `:${this.lang}`; + let inline = (this.inline) ? " inline" : ""; + return `Code${lang} { content: ${this.content}}${inline}`; } - to_html() - { - // appelé uniquement par string_to_html pour le code inline - //return '' + this.content + ''; - if (this.inline) - { - return '' + this.content + ''; + + to_html() { + let output = ""; + if (this.lang !== null && this.lang in LANGUAGES) { + output = LEXERS[this.lang].to_html(this.content, null, [ + "blank", + ]); } else { - throw new Error("It's done elsewhere."); + output = this.content; + } + if (this.inline) { + return "" + this.document.safe(output) + ""; + } else { + return "
      \n" + output + "
      \n"; } } } -class GetVar extends Node -{ - constructor(document, content) - { +class GetVar extends Node { + constructor(document, content) { super(document, content); - if (content === null || content === undefined) - { + if (content === null || content === undefined) { throw new Error("A GetVar node must have a content"); } } } -class SetVar extends EmptyNode -{ - constructor(document, id, value, type, constant) - { +class SetVar extends EmptyNode { + constructor(document, id, value, type, constant) { super(document); this.id = id; this.value = value; @@ -531,18 +483,15 @@ class SetVar extends EmptyNode this.constant = constant; } } -class Markup extends Node {} +class Markup extends Node { } // Variable & document -class Variable -{ - constructor(document, name, type, constant=false, value=null) - { +class Variable { + constructor(document, name, type, constant = false, value = null) { this.document = document; this.name = name; - if (type !== 'number' && type !== 'string' && type !== 'boolean') - { + if (type !== "number" && type !== "string" && type !== "boolean") { throw new Error(`Unknown type ${type} for variable ${name}`); } this.type = type; @@ -550,43 +499,73 @@ class Variable this.value = value; } - set_variable(value) - { - if (this.value !== null && this.constant) - { - throw new Error(`Can't set the value of the already defined constant: ${this.name} of type ${this.type}`); + set_variable(value) { + if (this.value !== null && this.constant) { + throw new Error( + `Can't set the value of the already defined constant: ${this.name} of type ${this.type}` + ); } - if ((isNaN(value) && this.type === 'number') || - (typeof value === 'string' && this.type !== 'string') || - (typeof value === 'boolean' && this.type !== 'boolean')) - { - throw new Error(`Cant't set the value to ${value} for variable ${this.name} of type ${this.type}`); + if ( + (isNaN(value) && this.type === "number") || + (typeof value === "string" && this.type !== "string") || + (typeof value === "boolean" && this.type !== "boolean") + ) { + throw new Error( + `Cant't set the value to ${value} for variable ${this.name} of type ${this.type}` + ); } this.value = value; } - get_value() - { - if (this.name === 'NOW') - { - return new Date().toLocaleDateString(undefined, { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }); - } - else - return this.value; + get_value() { + if (this.name === "NOW") { + return new Date().toLocaleDateString(undefined, { + weekday: "long", + year: "numeric", + month: "long", + day: "numeric", + }); + } else return this.value; } } -class Document -{ - constructor(name=null) - { +class Document { + constructor(name = null) { + this.predefined_constants = [ + "TITLE", + "ICON", + "LANG", + "ENCODING", + "BODY_CLASS", + "BODY_ID", + "VERSION", + "NOW", + ]; this.name = name; this.variables = { - 'VERSION': new Variable(this, 'VERSION', 'string', 'true', 'Hamill 2.00'), - 'NOW': new Variable(this, 'NOW', 'string', 'true', ''), - 'PARAGRAPH_DEFINITION': new Variable(this, 'PARAGRAPH_DEFINITION', 'boolean', false, false), - 'EXPORT_COMMENT': new Variable(this, 'EXPORT_COMMENT', 'boolean', false, false), - 'DEFAULT_CODE': new Variable(this, 'DEFAULT_CODE', 'string', 'false') + VERSION: new Variable( + this, + "VERSION", + "string", + "true", + "Hamill 2.00" + ), + NOW: new Variable(this, "NOW", "string", "true", ""), + PARAGRAPH_DEFINITION: new Variable( + this, + "PARAGRAPH_DEFINITION", + "boolean", + false, + false + ), + EXPORT_COMMENT: new Variable( + this, + "EXPORT_COMMENT", + "boolean", + false, + false + ), + DEFAULT_CODE: new Variable(this, "DEFAULT_CODE", "string", "false"), }; this.required = []; this.css = []; @@ -594,91 +573,74 @@ class Document this.nodes = []; } - set_name(name) - { + set_name(name) { this.name = name; } - to_html_file(output_directory) - { - let parts = this.name.split('/'); + to_html_file(output_directory) { + let parts = this.name.split("/"); let outfilename = parts[parts.length - 1]; - outfilename = outfilename.substring(0, outfilename.lastIndexOf('.hml')) + '.html'; - let sep = output_directory[output_directory.length - 1] === '/' ? '' : '/'; + outfilename = + outfilename.substring(0, outfilename.lastIndexOf(".hml")) + ".html"; + let sep = + output_directory[output_directory.length - 1] === "/" ? "" : "/"; let target = output_directory + sep + outfilename; fs.writeFileSync(target, this.to_html(true)); // with header - console.log('Outputting in:', target); + console.log("Outputting in:", target); + } + + has_variable(k) { + return k in this.variables && this.variables[k] !== null; } - set_variable(k, v, t='string', c=false) - { - //console.log(`Setting ${k} to ${v}`); - if (k in this.variables) - { + set_variable(k, v, t = "string", c = false) { + if (k in this.variables) { this.variables[k].set_variable(v); - } - else - { + } else { this.variables[k] = new Variable(this, k, t, c, v); } } - get_variable(k, default_value=null) - { - if (k in this.variables) - { + get_variable(k, default_value = null) { + if (k in this.variables) { return this.variables[k].get_value(); - } - else if (default_value !== null) - { + } else if (default_value !== null) { return default_value; - } - else - { - console.log('Dumping variables:'); - for (const [k, v] of Object.entries(this.variables)) - { - console.log(' ', v.name, '=', v.value); + } else { + console.log("Dumping variables:"); + for (const v of Object.values(this.variables)) { + console.log(" ", v.name, "=", v.value); } throw new Error(`Unknown variable: ${k}`); } } - add_required(r) - { + add_required(r) { this.required.push(r); } - add_css(c) - { + add_css(c) { this.css.push(c); } - add_label(l, v) - { + add_label(l, v) { this.labels[l] = v; } - add_node(n) - { - if (n === undefined || n === null) - { + add_node(n) { + if (n === undefined || n === null) { throw new Error("Trying to add an undefined or null node"); } this.nodes.push(n); } - get_node(i) - { + get_node(i) { return this.nodes[i]; } - get_label(target) - { - if (! (target in this.labels)) - { - for (const label in this.labels) - { + get_label(target) { + if (!(target in this.labels)) { + for (const label in this.labels) { console.log(label); } throw new Error("Label not found : " + target); @@ -686,114 +648,123 @@ class Document return this.labels[target]; } - make_anchor(text) - { - return text.toLocaleLowerCase().replace(/ /g, '-'); + make_anchor(text) { + let step1 = text.replace(/ /g, "-").toLocaleLowerCase(); + let result = ""; + let in_html = false; + for (let c of step1) { + if (c === "<") { + in_html = true; + } else if (c === ">") { + in_html = false; + } else if (!in_html) { + result += c; + } + } + return result; } - string_to_html(content, nodes) - { - if (nodes === undefined || nodes === null) - { + string_to_html(content, nodes) { + if (nodes === undefined || nodes === null) { throw new Error("No nodes to process"); } - if (typeof content !== 'string') throw new Error('Parameter content should be of type string'); - if (!Array.isArray(nodes) || (!(nodes[0] instanceof Start) - && !(nodes[0] instanceof Stop) && !(nodes[0] instanceof Text) - && !(nodes[0] instanceof Link) && !(nodes[0] instanceof GetVar)) - && !(nodes[0] instanceof ParagraphIndicator) - && !(nodes[0] instanceof Picture) - && !(nodes[0] instanceof Code) - && (nodes[0] instanceof Code && !nodes[0].inline)) - { - throw new Error(`Parameter nodes should be an array of Start|Stop|Text|Link|GetVar|Code(inline) and is: ${typeof nodes[0]}`); - } - for (let node of nodes) - { - if (node instanceof Start - || node instanceof Stop - || node instanceof Span - || node instanceof Picture - || node instanceof BR - || node instanceof Text) - { + if (typeof content !== "string") + throw new Error("Parameter content should be of type string"); + if ( + !Array.isArray(nodes) || + (!(nodes[0] instanceof Start) && + !(nodes[0] instanceof Stop) && + !(nodes[0] instanceof Text) && + !(nodes[0] instanceof Link) && + !(nodes[0] instanceof GetVar) && + !(nodes[0] instanceof ParagraphIndicator) && + !(nodes[0] instanceof Picture) && + !(nodes[0] instanceof Code) && + nodes[0] instanceof Code && + !nodes[0].inline) + ) { + throw new Error( + `Parameter nodes should be an array of Start|Stop|Text|Link|GetVar|Code(inline) and is: ${typeof nodes[0]}` + ); + } + for (let node of nodes) { + if ( + node instanceof Start || + node instanceof Stop || + node instanceof Span || + node instanceof Picture || + node instanceof BR || + node instanceof Text || + node instanceof Code || + node instanceof ParagraphIndicator + ) { content += node.to_html(); - } - else if (node instanceof Link) - { + } else if (node instanceof Link) { content += node.to_html(this); - } - else if (node instanceof GetVar) - { + } else if (node instanceof GetVar) { content += this.get_variable(node.content); - } - else if (node instanceof ParagraphIndicator) - { - let ret = content.lastIndexOf("

      "); - content = content.substring(0, ret) + node.to_html() + content.substring(ret + 3); - } - else if (node instanceof Code) - { - content += node.to_html(); - } - else - { - throw new Error("Impossible to handle this type of node: " + node.constructor.name); + } else { + throw new Error( + "Impossible to handle this type of node: " + + node.constructor.name + ); } } return content; } - to_html(header=false) - { + safe(str) { + return str.replace(//g, ">"); + } + + to_html(header = false, skip_error = false) { let start_time = new Date(); - let content = ''; - if (header) - { - content = ` + let content = ""; + if (header) { + content = ` - + - ${this.get_variable('TITLE', 'Undefined title')} - + ${this.get_variable("TITLE", "Undefined title")} + \n`; // For CSS - if (this.required.length > 0) - { - for (let req of this.required) - { - if (req.endsWith('.css')) - { + if (this.required.length > 0) { + for (let req of this.required) { + if (req.endsWith(".css")) { content += ` \n`; } } } - if (this.css.length > 0) - { + if (this.css.length > 0) { content += ' \n'; + content += " \n"; } // For javascript - if (this.required.length > 0) - { - for (let req of this.required) - { - if (req.endsWith('.js')) - { + if (this.required.length > 0) { + for (let req of this.required) { + if (req.endsWith(".js")) { content += ` \n`; } } } - if (header) - { - content += "\n"; - content += "\n"; + content += "\n"; + let bclass = ""; + let bid = ""; + if (this.has_variable("BODY_ID")) { + bid = ' id="' + this.get_variable("BODY_ID") + '"'; } + if (this.has_variable("BODY_CLASS")) { + bclass = ' class="' + this.get_variable("BODY_CLASS") + '"'; + } + content += `\n`; } let first_text = true; let not_processed = 0; @@ -807,169 +778,119 @@ class Document let in_def_list = false; let in_code_block = false; let in_quote_block = false; - for (const [index, node] of this.nodes.entries()) - { - //console.log(content.substring(content.indexOf(''))); - //console.log(index, node); - + for (const node of this.nodes) { // Consistency - if (!(node instanceof TextLine) && in_paragraph) - { + if (!(node instanceof TextLine) && in_paragraph) { content += "

      \n"; in_paragraph = false; } - if (!(node instanceof Definition) && in_def_list) - { + if (!(node instanceof Definition) && in_def_list) { content += "\n"; in_def_list = false; } - if (!(node instanceof Row) && in_table) - { + if (!(node instanceof Row) && in_table) { content += "\n"; in_table = false; } - if (!(node instanceof Quote) && in_quote_block) - { - content += "\n"; - in_quote_block = false; - } - if (!(node instanceof Code) && in_code_block) - { - content += "\n"; - in_code_block = false; - } + // Handling of nodes - if (node.constructor.name === 'EmptyNode') - { + if (node.constructor.name === "EmptyNode") { // Nothing, it is just too close the paragraph, done above. - } - else if (node instanceof Include) - { + } else if (node instanceof Include) { let file = fs.readFileSync(node.content); content += file + "\n"; - } - else if (node instanceof Title) - { - content += `${node.content}\n`; - } - else if (node instanceof Comment) - { - if (this.get_variable('EXPORT_COMMENT')) - { - content += '\n'; + } else if (node instanceof Title) { + let contentAsString = this.string_to_html("", node.content); + content += `${contentAsString}\n`; + } else if (node instanceof Comment) { + if (this.get_variable("EXPORT_COMMENT")) { + content += "\n"; } - } - else if (node instanceof SetVar) - { - this.set_variable(node.id, node.value, node.type, node.constant); - } - else if (node instanceof HR - || node instanceof StartDiv - || node instanceof EndDiv - || node instanceof RawHTML - || node instanceof List) - { + } else if (node instanceof SetVar) { + if (!node.constant) { + if (this.predefined_constants.includes(node.id)) { + throw new Error( + `You cannot use ${node.id} for a variable because it is a predefined constant.` + ); + } + } + this.set_variable( + node.id, + node.value, + node.type, + node.constant + ); + } else if ( + node instanceof HR || + node instanceof StartDiv || + node instanceof EndDiv || + node instanceof StartDetail || + node instanceof EndDetail || + node instanceof Detail || + node instanceof RawHTML || + node instanceof List || + node instanceof Quote || + node instanceof Code + ) { content += node.to_html(); - } - else if (node instanceof TextLine) - { - if (!in_paragraph) - { + } else if (node instanceof TextLine) { + // Check that ParagraphIndicator must be only at 0 + for (let nc = 0; nc < node.children.length; nc++) { + if ( + node.children[nc] instanceof ParagraphIndicator && + nc > 0 + ) { + throw new Error( + "A paragraph indicator must always be at the start of a text line/" + ); + } + } + if (!in_paragraph) { in_paragraph = true; - content += "

      "; + // If the first child is a pragraph indicator, don't start the paragraph ! + if ( + node.children.length > 0 && + !(node.children[0] instanceof ParagraphIndicator) + ) { + content += "

      "; + } } else { content += "
      \n"; } content += node.to_html(); - } - else if (node instanceof Definition) - { - if (!in_def_list) - { + } else if (node instanceof Definition) { + if (!in_def_list) { in_def_list = true; content += "

      \n"; } - content += '
      '; + content += "
      "; content = this.string_to_html(content, node.header) + "
      \n"; - content += '
      ' - if (this.get_variable('PARAGRAPH_DEFINITION') === true) content += '

      '; + content += "

      "; + if (this.get_variable("PARAGRAPH_DEFINITION") === true) + content += "

      "; content = this.string_to_html(content, node.content); - if (this.get_variable('PARAGRAPH_DEFINITION') === true) content += '

      '; - content += '
      \n'; - } - else if (node instanceof Quote) - { - if (!in_quote_block) - { - in_quote_block = true; - content += '
      \n'; - if (node.content.startsWith('>>>')) - { - content += node.content.substring(3) + "
      \n"; - } - else - { - content += node.content.substring(2) + "
      \n"; - } - } - else - { - if (node.content.startsWith('>>')) - { - content += node.content.substring(2) + "
      \n"; - } - else - { - content += node.content + "
      \n"; - } - } - } - else if (node instanceof Code) - { - if (!in_code_block) - { - in_code_block = true; - content += '
      \n';
      -                    if (node.content.startsWith('@@@'))
      -                    {
      -                        content += node.content.substring(3) + "\n";
      -                    }
      -                    else
      -                    {
      -                        content += node.content.substring(2) + "\n";
      -                    }
      -                }
      -                else
      -                {
      -                    if (node.content.startsWith('@@'))
      -                    {
      -                        content += node.content.substring(2) + "\n";
      -                    }
      -                    else
      -                    {
      -                        content += node.content + "\n";
      -                    }
      -                }
      -            }
      -            else if (node instanceof Row)
      -            {
      -                if (!in_table)
      -                {
      +                if (this.get_variable("PARAGRAPH_DEFINITION") === true)
      +                    content += "

      \n"; + content += "\n"; + } else if (node instanceof Row) { + if (!in_table) { in_table = true; content += "\n"; } content += ""; - let delim = node.is_header ? 'th' : 'td'; - for (let node_list of node.node_list_list) - { - let center = ''; - //console.log(node_list[0]); - if (node_list.length > 0 - && node_list[0] instanceof Node // for content - && node_list[0].content.length > 0 - && node_list[0].content[0] === '=') - { - node_list[0].content = node_list[0].content.substring(1); + let delim = node.is_header ? "th" : "td"; + for (let node_list of node.node_list_list) { + let center = ""; + if ( + node_list.length > 0 && + node_list[0] instanceof Node && // for content + node_list[0].content.length > 0 && + node_list[0].content[0] === "=" + ) { + node_list[0].content = + node_list[0].content.substring(1); center = ' class="text-center"'; } content += `<${delim}${center}>`; @@ -977,932 +898,1324 @@ class Document content += ``; } content += "\n"; - } - else - { - //console.log(index, node); + } else if (skip_error) { not_processed += 1; - if (!(node.constructor.name in types_not_processed)) - { + if (!(types_not_processed.includes(node.constructor.name))) { types_not_processed[node.constructor.name] = 0; } types_not_processed[node.constructor.name] += 1; + } else { + throw new Error(`Unknown node: ${node.constructor.name}`); } } - if (in_paragraph) - { + if (in_paragraph) { content += "

      \n"; - in_paragraph = false; } - if (stack.length > 0) - { - content = this.assure_list_consistency(content, stack, 0, null, null); + if (stack.length > 0) { + content = this.assure_list_consistency( + content, + stack, + 0, + null, + null + ); } - if (in_table) - { + if (in_table) { content += "
      \n"; } - if (in_quote_block) - { + if (in_quote_block) { content += "
      \n"; } - if (in_code_block) - { + if (in_code_block) { content += "\n"; } - if (!first_text) - { + if (!first_text) { content += "

      \n"; } - if (header) - { + if (header) { content += "\n \n"; } - console.log('\nNodes processed:', this.nodes.length - not_processed, '/', this.nodes.length); - if (not_processed > 0) - { + console.log( + "\nRoot nodes processed:", + this.nodes.length - not_processed, + "/", + this.nodes.length + ); + if (not_processed > 0) { console.log(`Nodes not processed ${not_processed}:`); - for (let [k, v] of Object.entries(types_not_processed)) - { - console.log(' -', k, v); + for (let [k, v] of Object.entries(types_not_processed)) { + console.log(" -", k, v); } } let end_time = new Date(); - let elapsed = (end_time - start_time)/1000; - console.log('Processed in: %ds', elapsed, '\n'); + let elapsed = (end_time - start_time) / 1000; + console.log("Processed in: %ds", elapsed, "\n"); return content; } - to_s(level=0, node=null) - { + to_s(level = 0, node = null, header = false) { let out = ""; - if (node === null || node === undefined) - { - out += '\n------------------------------------------------------------------------\n'; - out += 'Liste des nodes du document\n'; - out += '------------------------------------------------------------------------\n\n'; - for (const n of this.nodes) - { + if (node === null || node === undefined) { + if (header === true) { + out += + "\n------------------------------------------------------------------------\n"; + out += "Liste des nodes du document\n"; + out += + "------------------------------------------------------------------------\n\n"; + } + for (const n of this.nodes) { out += this.to_s(level, n); } } else { - let info = " " + node.toString(); - out += " ".repeat(level) + info + '\n'; - if (node instanceof Composite) - { - for (const n of node.children) - { + let info = " " + node.toString(); + out += " ".repeat(level) + info + "\n"; + if (node instanceof Composite) { + for (const n of node.children) { + out += this.to_s(level + 1, n); + } + } + if (node instanceof Row) { + for (const n of node.node_list_list) { out += this.to_s(level + 1, n); } } } return out; } - } -class Hamill -{ - // Read a file and produce a big string - static read_file(filename, encoding='utf8') - { - let data; - try - { - data = fs.readFileSync(filename, encoding); +class Hamill { + static process(string_or_filename) { + // Try to read as a file name, if it fails, take it as a string + let data = null; + let name = null; + if (fs !== null) { + try { + data = fs.readFileSync(string_or_filename, "utf-8"); + console.log(`Data read from file: ${string_or_filename}`); + name = string_or_filename; + } catch { + // Nothing + } + } + if (data === null) { + data = string_or_filename; + console.log(`Data read from string:`); + } + // Check authorized characters + let filtered = ""; + const authorized = [ + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h", + "i", + "j", + "k", + "l", + "m", + "n", + "o", + "p", + "q", + "r", + "s", + "t", + "u", + "v", + "w", + "x", + "y", + "z", + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z", + "á", + "à", + "â", + "ä", + "é", + "è", + "ê", + "ë", + "í", + "ì", + "î", + "ï", + "ó", + "ò", + "ô", + "ö", + "ú", + "ù", + "û", + "ü", + "ý", + "ÿ", + "Á", + "À", + "Â", + "Ä", + "É", + "È", + "Ê", + "Ë", + "Í", + "Ì", + "Î", + "Ï", + "Ó", + "Ò", + "Ô", + "Ö", + "Ú", + "Ù", + "Û", + "Ü", + "Ý", + "ã", + "Ã", + "õ", + "Õ", + "œ", + "Œ", + "ß", + "ẞ", + "ñ", + "Ñ", + "ç", + "Ç", + " ", + "0", + "1", + "2", + "3", + "4", + "5", + "6", + "7", + "8", + "9", + "½", + "¾", + "$", + "€", + "£", + "¥", + "₹", + "₽", // Common currency : dollar, euro, pound, yen, rupee, ruble + "+", + "-", + "*", + "/", + "%", + "^", // Common mathematical operators + ">", + "<", + "=", + "!", + "~", // Common comparison operators + "&", + "|", + "#", // Hamill images & titles, comment + '"', + "'", + "°", + "@", + "–", // Common various + "{", + "}", + "(", + ")", + "[", + "]", // Common opening/closing + ".", + ",", + ";", + ":", + "?", + "!", + "«", + "»", + "’", + "‘", + "“", + "”", + "…", // Common ponctuations + "\n", + "\t", // Common whitespaces \r is NOT AUTHORIZED + "❤", // Some love + "'", + "-", + "_", + "^", + "%", + "@", + "!", + "/", // Hamill text modifiers + "+", + "-", + "|", // Hamill lists + "{", + ".", // Hamill structure tags (div, p and span) + "\\", // Hamill escape + ">", // Hamill blocks + "$", // Hamill definition lists and display vars/consts + "/", // Hamill comments + "|", + "-", // Hamill tables + "[", + "-", + ">", + "]", + ":", // Hamill links and labels + "(", + "-", + ">", + ")", + ".", + "=", // Hamill define vars/consts + "§", // Hamill comments + "•", // Hamill list + ]; + data = data.replace(/\r\n/g, "\n"); + data = data.replace(/\r/g, "\n"); + for (let char of data) { + // And removes multiple new lines + if (authorized.includes(char)) { + filtered += char; + } else { + throw new Error(`Unauthorized char: ${char}`); + } } - catch (err) - { - throw new Error(err); + // Display raw lines + let lines = filtered.split("\n"); + for (let [index, line] of lines.entries()) { + console.log(` ${index + 1}. ${line.replace("\n", "")}`); } - return data; - } - - static split_lines(data) - { - let lines = data.replace(/\r\n/g, "\n").replace(/\n\r/g, "\n").replace(/\r/g, "\n").split("\n"); - return lines; + // Tag lines + let tagged = Hamill.tag_lines(filtered.split("\n")); + console.log("\nTagged Lines:"); + for (const [index, line] of tagged.entries()) { + console.log(` ${index + 1}. ${line}`); + } + // Make a document + let doc = Hamill.parse_tagged_lines(tagged); + doc.set_name(name); + console.log("\nDocument:"); + console.log(doc.to_s()); + return doc; } // First pass: we tag all the lines - static tag_lines(raw) - { + static tag_lines(raw) { let lines = []; let next_is_def = false; - let in_code_block = false; - let in_quote_block = false; - for (const [index, value] of raw.entries()) - { + let in_code_block = false; // only the first and the last line start with @@@ + let in_code_block_prefixed = false; // each line must start with @@ + let in_quote_block = false; // only the first and the last line start with >>> + for (const value of raw) { let trimmed = value.trim(); - if (in_code_block) - { - lines.push(new Line(value, 'code')) - } - else if (in_quote_block) - { - lines.push(new Line(value, 'quote')) - } - else if (trimmed.length === 0) - { - lines.push(new Line('', 'empty')); + // Check states + // End of prefixed block + if (in_code_block_prefixed && !trimmed.startsWith('@@') && !trimmed.startsWith('@@@')) { + in_code_block_prefixed = false; + // Final line @@@ of a not prefixed block + } else if (in_code_block && trimmed === '@@@') { + in_code_block = false; + continue; + } else if (in_quote_block && trimmed === '>>>') { + in_quote_block = false; + continue; } - // Titles : - else if (trimmed[0] === '#') - { - lines.push(new Line(trimmed, 'title')); + + // States handling + if (in_code_block || in_code_block_prefixed) { + lines.push(new Line(value, "code")); + } else if (in_quote_block) { + lines.push(new Line(value, "quote")); + } else if (trimmed.length === 0) { + lines.push(new Line("", "empty")); + // Titles : + } else if (trimmed[0] === "#") { + lines.push(new Line(trimmed, "title")); } // HR : - else if ((trimmed.match(/-/g)||[]).length === trimmed.length) - { - lines.push(new Line('', 'separator')); + else if ((trimmed.match(/-/g) || []).length === trimmed.length) { + lines.push(new Line("", "separator")); } // Lists, line with the first non empty character is "* " or "+ " or "- " : - else if (trimmed.substring(0, 2) === '* ') - { - lines.push(new Line(value, 'unordered_list')); - } - else if (trimmed.substring(0, 2) === '+ ') - { - lines.push(new Line(value, 'ordered_list')); - } - else if (trimmed.substring(0, 2) === '- ') - { - lines.push(new Line(value, 'reverse_list')); + else if (trimmed.substring(0, 2) === "* ") { + let start = value.indexOf("* "); + let level = Math.trunc(start / 2); + if (level * 2 !== start) { + throw new Error( + "Level list must be indented by a multiple of two" + ); + } + lines.push(new Line(value, "unordered_list", level + 1)); + } else if (trimmed.substring(0, 2) === "+ ") { + let start = value.indexOf("+ "); + let level = Math.trunc(start / 2); + if (level * 2 !== start) { + throw new Error( + "Level list must be indented by a multiple of two" + ); + } + lines.push(new Line(value, "ordered_list", level + 1)); + } else if (trimmed.substring(0, 2) === "- ") { + let start = value.indexOf("- "); + let level = Math.trunc(start / 2); + if (level * 2 !== start) { + throw new Error( + "Level list must be indented by a multiple of two" + ); + } + lines.push(new Line(value, "reverse_list", level + 1)); } // Keywords, line with the first non empty character is "!" : // var, const, include, require, css, html, comment - else if (trimmed.startsWith('!var ')) - { - lines.push(new Line(trimmed, 'var')); - } - else if (trimmed.startsWith('!const ')) - { - lines.push(new Line(trimmed, 'const')); - } - else if (trimmed.startsWith('!include ')) - { - lines.push(new Line(trimmed, 'include')); - } - else if (trimmed.startsWith('!require ')) - { - lines.push(new Line(trimmed, 'require')); - } - else if (trimmed.startsWith('!css ')) - { - lines.push(new Line(trimmed, 'css')); - } - else if (trimmed.startsWith('!html')) - { - lines.push(new Line(trimmed, 'html')); - } - else if (trimmed.substring(0, 2) === '//') - { - lines.push(new Line(trimmed, 'comment')); + else if (trimmed.startsWith("!var ")) { + lines.push(new Line(trimmed, "var")); + } else if (trimmed.startsWith("!const ")) { + lines.push(new Line(trimmed, "const")); + } else if (trimmed.startsWith("!include ")) { + lines.push(new Line(trimmed, "include")); + } else if (trimmed.startsWith("!require ")) { + lines.push(new Line(trimmed, "require")); + } else if (trimmed.startsWith("!css ")) { + lines.push(new Line(trimmed, "css")); + } else if (trimmed.startsWith("!html")) { + lines.push(new Line(trimmed, "html")); + } else if ( + trimmed.startsWith("!rem") || + trimmed.substring(0, 2) === "§§" + ) { + lines.push(new Line(trimmed, "comment")); } // Block of code - else if (trimmed.substring(0, 2) === '@@@') - { - in_code_block = !in_code_block; - lines.push(new Line(value, 'code')) - } - else if (trimmed.substring(0, 2) === '@@' && trimmed.substring(trimmed.length-2, trimmed.length) !== '@@') // :TODO: Escaping @@ in code for Ruby. @@code@@ should be a

      not a

      !
      -            {
      -                lines.push(new Line(value, 'code'));
      +            else if (trimmed.substring(0, 3) === "@@@") {
      +                in_code_block = true;
      +                lines.push(new Line(value, "code"));
      +            } else if (
      +                trimmed.substring(0, 2) === "@@" &&
      +                !trimmed.substring(2).includes("@@")
      +            ) {
      +                in_code_block_prefixed = true;
      +                lines.push(new Line(value, "code"));
                   }
                   // Block of quote
      -            else if (trimmed.substring(0, 2) === '>>>')
      -            {
      -                in_quote_block = !in_quote_block;
      -                lines.push(new Line(value, 'quote'))
      -            }
      -            else if (trimmed.substring(0, 2) === '>>')
      -            {
      -                lines.push(new Line(value, 'quote'));
      +            else if (trimmed.substring(0, 3) === ">>>") {
      +                in_quote_block = true; // will be desactivate in Check states
      +                lines.push(new Line(value, "quote"));
      +            } else if (trimmed.substring(0, 2) === ">>") {
      +                lines.push(new Line(value, "quote"));
                   }
                   // Labels
      -            else if (trimmed.substring(0, 2) === '::')
      -            {
      -                lines.push(new Line(trimmed, 'label'));
      +            else if (trimmed.substring(0, 2) === "::") {
      +                lines.push(new Line(trimmed, "label"));
                   }
                   // Div (Si la ligne entière est {{ }}, c'est une div. On ne fait pas de span d'une ligne)
      -            else if (trimmed.substring(0, 2) === '{{' && trimmed.substring(trimmed.length - 2) === '}}')
      -            {
      -                lines.push(new Line(trimmed, 'div'));
      -            }
      -            // Tables
      -            else if (trimmed[0] === '|' && trimmed[trimmed.length - 1] === '|')
      -            {
      -                lines.push(new Line(trimmed, 'row'));
      +            else if (
      +                trimmed.substring(0, 2) === "{{" &&
      +                trimmed.substring(trimmed.length - 2) === "}}" &&
      +                trimmed.lastIndexOf("{{") == 0
      +            ) {
      +                // span au début et à la fin = erreur
      +                lines.push(new Line(trimmed, "div"));
      +                // Detail
      +            } else if (
      +                trimmed.substring(0, 2) === "<<" &&
      +                trimmed.substring(trimmed.length - 2) === ">>" &&
      +                trimmed.lastIndexOf("<<") == 0
      +            ) {
      +                lines.push(new Line(trimmed, "detail"))
      +                // Tables
      +            } else if (
      +                trimmed[0] === "|" &&
      +                trimmed[trimmed.length - 1] === "|"
      +            ) {
      +                lines.push(new Line(trimmed, "row"));
                   }
                   // Definition lists
      -            else if (trimmed.substring(0, 2) === '$ ')
      -            {
      -                lines.push(new Line(trimmed.substring(2), 'definition-header'));
      +            else if (trimmed.substring(0, 2) === "$ ") {
      +                lines.push(new Line(trimmed.substring(2), "definition-header"));
                       next_is_def = true;
      -            }
      -            else
      -            {
      -                if (!next_is_def)
      -                {
      -                    lines.push(new Line(trimmed, 'text'));
      -                }
      -                else
      -                {
      -                    lines.push(new Line(trimmed, 'definition-content'));
      -                    next_is_def = false;
      -                }
      +            } else if (!next_is_def) {
      +                lines.push(new Line(trimmed, "text"));
      +            } else {
      +                lines.push(new Line(trimmed, "definition-content"));
      +                next_is_def = false;
                   }
               }
               return lines;
           }
       
      -    static process_string(data)
      -    {
      -        let raw = Hamill.split_lines(data);
      -        let lines = Hamill.tag_lines(raw);
      -        if (DEBUG)
      -        {
      -            console.log('Lines:');
      -            for (const [index, line] of lines.entries())
      -            {
      -                console.log(`${index}: ${line}`);
      -            }
      -            console.log();
      -        }
      -        let doc = Hamill.process_lines(lines);
      -        return doc;
      -    }
      -
      -    // Take a filename, return a list of tagged lines, output the result in a file
      -    static process_file(filename)
      -    {
      -        if (fs === null)
      -        {
      -            throw new Error("Not in node.js : module fs not defined. Aborting.");
      -        }
      -        if (DEBUG)
      -        {
      -            console.log('Processing file:', filename);
      -            console.log('--------------------------------------------------------------------------');
      -        }
      -        let data = Hamill.read_file(filename);
      -        let doc = this.process_string(data);
      -        doc.set_name(filename);
      -        return doc;
      -    }
      -
           // Take a list of tagged lines return a valid Hamill document
      -    static process_lines(lines)
      -    {
      -        if (DEBUG) console.log(`Processing ${lines.length} lines`);
      +    static parse_tagged_lines(lines) {
      +        if (DEBUG) console.log(`\nProcessing ${lines.length} lines`);
               let doc = new Document();
               let definition = null;
               // Lists
               let actual_list = null;
               let actual_level = 0;
      +        let starting_level = 0;
      +        // On pourrait avoir un root aussi
               // Main loop
      -        for (const [index, line] of lines.entries())
      -        {
      -            let text = undefined;
      -            let id = undefined;
      -            let value = undefined;
      +        let count = 0;
      +        while (count < lines.length) {
      +            let line = lines[count];
      +            let text = null;
      +            let id = null;
      +            let value = null;
                   // List
      -            if (actual_list !== null && line.type !== 'unordered_list'
      -                                     && line.type !== 'ordered_list'
      -                                     && line.type !== 'reverse_list')
      -            {
      +            if (
      +                actual_list !== null &&
      +                line.type !== "unordered_list" &&
      +                line.type !== "ordered_list" &&
      +                line.type !== "reverse_list"
      +            ) {
                       doc.add_node(actual_list.root());
                       actual_list = null;
                       actual_level = 0;
                   }
      +            // Titles
      +            let lvl = 0;
      +            // Quotes
      +            let nodeContent = "";
      +            let free = false;
      +            // Lists
      +            let delimiters = {
      +                unordered_list: "* ",
      +                ordered_list: "+ ",
      +                reverse_list: "- ",
      +            };
      +            let delimiter = "";
      +            let list_level = 0;
                   let elem_is_unordered = false;
                   let elem_is_ordered = false;
                   let elem_is_reverse = false;
      -            switch (line.type)
      -            {
      -                case 'title':
      -                    let lvl = 0;
      -                    for (const char of line.value)
      -                    {
      -                        if (char === '#')
      -                        {
      +            let item_text = "";
      +            let item_nodes = [];
      +            // Includes
      +            let include = "";
      +            // Rows
      +            let content = "";
      +            // Divs
      +            let res = null;
      +            switch (line.type) {
      +                case "title":
      +                    for (const char of line.value) {
      +                        if (char === "#") {
                                   lvl += 1;
      -                        }
      -                        else
      -                        {
      +                        } else {
                                   break;
                               }
                           }
                           text = line.value.substring(lvl).trim();
      -                    doc.add_node(new Title(doc, text, lvl));
      -                    doc.add_label(doc.make_anchor(text), '#' + doc.make_anchor(text));
      +                    try {
      +                        let interpreted = Hamill.parse_inner_string(doc, text);
      +                        doc.add_node(new Title(doc, interpreted, lvl));
      +                        doc.add_label(
      +                            doc.make_anchor(text),
      +                            "#" + doc.make_anchor(text)
      +                        );
      +                    } catch (e) {
      +                        console.log(`Error at line ${count} on title: ${line}`);
      +                        throw e;
      +                    }
                           break;
      -                case 'separator':
      +                case "separator":
                           doc.add_node(new HR(doc));
                           break;
      -                case 'text':
      -                    if (line.value.trim().startsWith('\\* ')) line.value = line.value.trim().substring(1);
      -                    let n = Hamill.process_inner_string(doc, line.value);
      -                    doc.add_node(new TextLine(doc, n));
      +                case "text":
      +                    if (
      +                        line.value.trim().startsWith("\\* ") ||
      +                        line.value.trim().startsWith("\\!html") ||
      +                        line.value.trim().startsWith("\\!var") ||
      +                        line.value.trim().startsWith("\\!const") ||
      +                        line.value.trim().startsWith("\\!include") ||
      +                        line.value.trim().startsWith("\\!require")
      +                    ) {
      +                        line.value = line.value.trim().substring(1);
      +                    }
      +                    try {
      +                        let n = Hamill.parse_inner_string(doc, line.value);
      +                        doc.add_node(new TextLine(doc, n));
      +                    } catch (e) {
      +                        console.log(`Error at line ${count} on text: ${line}`);
      +                        throw e;
      +                    }
                           break;
      -                case 'unordered_list':
      +                case "unordered_list":
                           elem_is_unordered = true;
      -                    if (actual_list === null)
      -                    {
      +                    if (actual_list === null) {
                               actual_list = new List(doc, null, false, false);
                               actual_level = 1;
      +                        starting_level = line.param;
                           }
      -                    // next
      -                case 'ordered_list':
      -                    if (line.type === 'ordered_list') elem_is_ordered = true;
      -                    if (actual_list === null)
      -                    {
      +                // next
      +                case "ordered_list":
      +                    if (line.type === "ordered_list") elem_is_ordered = true;
      +                    if (actual_list === null) {
                               actual_list = new List(doc, null, true, false);
                               actual_level = 1;
      +                        starting_level = line.param;
                           }
      -                    // next
      -                case 'reverse_list':
      -                    if (line.type === 'reverse_list') elem_is_reverse = true;
      -                    if (actual_list === null)
      -                    {
      +                // next
      +                case "reverse_list":
      +                    if (line.type === "reverse_list") elem_is_reverse = true;
      +                    if (actual_list === null) {
                               actual_list = new List(doc, null, true, true);
                               actual_level = 1;
      +                        starting_level = line.param;
                           }
                           // common code
                           // compute item level
      -                    let delimiters = {'unordered_list': '* ', 'ordered_list': '+ ', 'reverse_list': '- '};
      -                    let delimiter = delimiters[line.type];
      -                    let list_level = Math.floor(line.value.indexOf(delimiter) / 2) + 1;
      +                    delimiter = delimiters[line.type];
      +                    list_level = line.param; //Math.floor(line.value.indexOf(delimiter) / 2) + 1;
      +                    // check coherency with the starting level
      +                    if (list_level < starting_level) {
      +                        throw new Error(
      +                            "Coherency error: a following item of list has a lesser level than its starting level."
      +                        );
      +                    } else {
      +                        list_level = list_level - (starting_level - 1);
      +                    }
                           // coherency
      -                    if (list_level === actual_level)
      -                    {
      -                        if ((elem_is_unordered && (actual_list.ordered || actual_list.reverse))
      -                            || (elem_is_ordered && !actual_list.ordered)
      -                            || (elem_is_reverse && !actual_list.reverse))
      -                        {
      -                            throw new Error(`Incoherency with previous item ${actual_level} at this level ${list_level}: ul:${elem_is_unordered} ol:${elem_is_unordered} r:${elem_is_reverse} vs o:${actual_list.ordered} r:${actual_list.reverse}`);
      +                    if (list_level === actual_level) {
      +                        if (
      +                            (elem_is_unordered &&
      +                                (actual_list.ordered || actual_list.reverse)) ||
      +                            (elem_is_ordered && !actual_list.ordered) ||
      +                            (elem_is_reverse && !actual_list.reverse)
      +                        ) {
      +                            throw new Error(
      +                                `Incoherency with previous item ${actual_level} at this level ${list_level}: ul:${elem_is_unordered} ol:${elem_is_unordered} r:${elem_is_reverse} vs o:${actual_list.ordered} r:${actual_list.reverse}`
      +                            );
                               }
                           }
      -                    while (list_level > actual_level)
      -                    {
      +                    while (list_level > actual_level) {
                               let last = actual_list.pop(); // get and remove the last item
                               let c = new Composite(doc, actual_list); // create a new composite
                               c.add_child(last); // put the old last item in it
                               actual_list = actual_list.add_child(c); // link the new composite to the list
      -                        let sub = new List(doc, c, elem_is_ordered, elem_is_reverse); // create a new list
      +                        let sub = new List(
      +                            doc,
      +                            c,
      +                            elem_is_ordered,
      +                            elem_is_reverse
      +                        ); // create a new list
                               actual_list = actual_list.add_child(sub);
                               actual_level += 1;
                           }
      -                    while (list_level < actual_level)
      -                    {
      -                        actual_list = actual_list.parent();
      +                    while (list_level < actual_level) {
      +                        actual_list = actual_list.get_parent();
      +                        if (actual_list.constructor.name === "Composite") {
      +                            // L'item était un composite, il faut remonter à la liste mère !
      +                            actual_list = actual_list.get_parent();
      +                        }
                               actual_level -= 1;
      -                        if (! actual_list instanceof List)
      -                        {
      -                            throw new Error("List incoherency: last element is not a list.");
      +                        if (actual_list.constructor.name !== "List") {
      +                            throw new Error(
      +                                `List incoherency: last element is not a list but a ${actual_list.constructor.name}`
      +                            );
                               }
                           }
                           // creation
      -                    let item_text = line.value.substring(line.value.indexOf(delimiter) + 2).trim();
      -                    let item_nodes = Hamill.process_inner_string(doc, item_text);
      +                    item_text = line.value
      +                        .substring(line.value.indexOf(delimiter) + 2)
      +                        .trim();
      +                    item_nodes = Hamill.parse_inner_string(doc, item_text);
                           actual_list.add_child(new TextLine(doc, item_nodes));
                           break;
      -                case 'html':
      -                    doc.add_node(new RawHTML(doc, line.value.replace('!html ', '').trim()));
      +                case "html":
      +                    doc.add_node(
      +                        new RawHTML(
      +                            doc,
      +                            line.value.replace("!html ", "").trim()
      +                        )
      +                    );
                           break;
      -                case 'css':
      -                    text = line.value.replace('!css ', '').trim();
      +                case "css":
      +                    text = line.value.replace("!css ", "").trim();
                           doc.add_css(text);
                           break;
      -                case 'include':
      -                    let include = line.value.replace('!include ', '').trim();
      +                case "include":
      +                    include = line.value.replace("!include ", "").trim();
                           doc.add_node(new Include(doc, include));
                           break;
      -                case 'require':
      -                    text = line.value.replace('!require ', '').trim();
      +                case "require":
      +                    text = line.value.replace("!require ", "").trim();
                           doc.add_required(text);
                           break;
      -                case 'const':
      -                    text = line.value.replace('!const ', '').split('=');
      +                case "const":
      +                    text = line.value.replace("!const ", "").split("=");
                           id = text[0].trim();
                           value = text[1].trim();
      -                    doc.set_variable(id, value, 'string', true);
      +                    doc.set_variable(id, value, "string", true);
                           break;
      -                case 'var':
      -                    text = line.value.replace('!var ', '').split('=');
      +                case "var":
      +                    text = line.value.replace("!var ", "").split("=");
                           id = text[0].trim();
                           value = text[1].trim();
      -                    if (value === 'true') value = true;
      -                    if (value === 'TRUE') value = true;
      -                    if (value === 'false') value = false;
      -                    if (value === 'FALSE') value = false;
      -                    let type = 'string';
      -                    if (typeof value === 'boolean')
      -                    {
      -                        type = 'boolean';
      -                    }
      -                    doc.add_node(new SetVar(doc, id, value, type, false));
      +                    if (value === "true") value = true;
      +                    if (value === "TRUE") value = true;
      +                    if (value === "false") value = false;
      +                    if (value === "FALSE") value = false;
      +                    doc.add_node(new SetVar(doc, id, value, typeof value === "boolean" ? "boolean" : "string", false));
                           break;
      -                case 'label':
      -                    value = line.value.replace(/::/, '').trim();
      -                    text = value.split('::');
      -                    let label = text[0].trim();
      -                    let url = text[1].trim();
      -                    doc.add_label(label, url);
      +                case "label":
      +                    value = line.value.replace(/::/, "").trim();
      +                    text = value.split("::");
      +                    doc.add_label(text[0].trim(), text[1].trim()); // label, url
                           break;
      -                case 'div':
      -                    value = line.value.substring(2, line.value.length - 2).trim();
      -                    let res = Hamill.process_inner_markup(value);
      -                    if (res['has_only_text'] && res['text'] === 'end')
      -                    {
      -                        doc.add_node(new EndDiv(doc));
      -                    }
      -                    else if (res['has_only_text'] && res['text'] === 'begin')
      -                    {
      -                        doc.add_node(new StartDiv(doc));
      +                case "detail":
      +                    value = line.value
      +                        .substring(2, line.value.length - 2)
      +                        .trim();
      +                    if (value === "end") {
      +                        doc.add_node(new EndDetail(doc));
      +                    } else {
      +                        let parts = value.split("->");
      +                        let res = Hamill.parse_inner_markup(parts[0]);
      +                        if (
      +                            parts.length === 1 ||
      +                            parts[1].trim().length === 0
      +                        ) {
      +                            doc.add_node(
      +                                new StartDetail(
      +                                    doc,
      +                                    res["text"].trim(),
      +                                    res["id"],
      +                                    res["class"]
      +                                )
      +                            );
      +                        } else {
      +                            // Detail simple < content>>
      +                            doc.add_node(
      +                                new Detail(
      +                                    doc,
      +                                    res["text"].trim(),
      +                                    parts[1].trim(),
      +                                    res["id"],
      +                                    res["class"]
      +                                )
      +                            );
      +                        }
                           }
      -                    else if (res['has_only_text'])
      -                    {
      +                    break;
      +                case "div":
      +                    value = line.value
      +                        .substring(2, line.value.length - 2)
      +                        .trim();
      +                    res = Hamill.parse_inner_markup(value);
      +                    if (res["text"] === "end") {
      +                        doc.add_node(new EndDiv(doc)); // We can put {{end .myclass #myid}} but it has no meaning except to code reading
      +                    } else if (
      +                        res["has_only_text"] &&
      +                        res["text"] !== "begin"
      +                    ) {
                               console.log(res);
      -                        throw new Error(`Unknown quick markup: ${res['text']} in ${line}`);
      -                    }
      -                    else
      -                    {
      -                        doc.add_node(new StartDiv(doc, res['id'], res['class']));
      +                        throw new Error(
      +                            `Unknown quick markup: ${res["text"]} in ${line}`
      +                        );
      +                    } else if (
      +                        res["text"] === "begin" ||
      +                        res["text"] === null
      +                    ) {
      +                        // begin can be omitted if there is no class nor id
      +                        doc.add_node(
      +                            new StartDiv(doc, res["id"], res["class"])
      +                        );
                           }
                           break;
      -                case 'comment':
      -                    doc.add_node(new Comment(doc, line.value.substring(2)));
      +                case "comment":
      +                    if (line.value.startsWith("!rem ")) {
      +                        doc.add_node(new Comment(doc, line.value.substring(4)));
      +                    } else {
      +                        doc.add_node(new Comment(doc, line.value.substring(2)));
      +                    }
                           break;
      -                case 'row':
      -                    let content = line.value.substring(1, line.value.length - 1);
      -                    if (content.length === (content.match(/(-|\|)/g) || []).length)
      -                    {
      +                case "row":
      +                    content = line.value.substring(
      +                        1,
      +                        line.value.length - 1
      +                    );
      +                    if (
      +                        content.length ===
      +                        (content.match(/[-|]/g) || []).length
      +                    ) {
                               let i = doc.nodes.length - 1;
      -                        while (doc.get_node(i) instanceof Row)
      -                        {
      +                        while (doc.get_node(i) instanceof Row) {
                                   doc.get_node(i).is_header = true;
                                   i -= 1;
                               }
      -                    }
      -                    else
      -                    {
      -                        let parts = content.split('|'); // Handle escape
      +                    } else {
      +                        let parts = content.split("|"); // Handle escape
                               let all_nodes = [];
      -                        for (let p of parts)
      -                        {
      -                            let nodes = Hamill.process_inner_string(doc, p);
      +                        for (let p of parts) {
      +                            let nodes = Hamill.parse_inner_string(doc, p);
                                   all_nodes.push(nodes);
                               }
                               doc.add_node(new Row(doc, all_nodes));
                           }
                           break;
      -                case 'empty':
      -                    doc.add_node(new EmptyNode(doc));
      +                case "empty":
      +                    // Prevent multiple empty nodes
      +                    if (
      +                        doc.nodes.length === 0 ||
      +                        doc.nodes[doc.nodes.length - 1].constructor.name !==
      +                        "EmptyNode"
      +                    ) {
      +                        doc.add_node(new EmptyNode(doc));
      +                    }
                           break;
      -                case 'definition-header':
      -                    definition = Hamill.process_inner_string(doc, line.value);
      +                case "definition-header":
      +                    definition = Hamill.parse_inner_string(doc, line.value);
                           break;
      -                case 'definition-content':
      -                    if (definition === null)
      -                    {
      -                        throw new Error('Definition content without header: ' + line.value);
      +                case "definition-content":
      +                    if (definition === null) {
      +                        throw new Error(
      +                            "Definition content without header: " + line.value
      +                        );
                           }
      -                    doc.add_node(new Definition(doc, definition, Hamill.process_inner_string(doc, line.value)));
      +                    doc.add_node(
      +                        new Definition(
      +                            doc,
      +                            definition,
      +                            Hamill.process_inner_string(doc, line.value)
      +                        )
      +                    );
                           definition = null;
                           break;
      -                case 'quote':
      -                    doc.add_node(new Quote(doc, line.value));
      +                case "quote":
      +                    res = {};
      +                    res['class'] = null;
      +                    res['id'] = null;
      +                    if (line.value === ">>>") {
      +                        free = true;
      +                        count += 1;
      +                    } else if (line.value.startsWith(">>>")) {
      +                        free = true;
      +                        res = this.parse_inner_markup(line.value.substring(3));
      +                        if (res["has_text"]) {
      +                            throw new Error("A line starting a blockquote should only have a class or id indication not text");
      +                        }
      +                        count += 1;
      +                    }
      +                    while (count < lines.length && lines[count].type === "quote") {
      +                        line = lines[count];
      +                        if (!free && !line.value.startsWith(">>")) {
      +                            break;
      +                        } else if (free && line.value === ">>>") {
      +                            break;
      +                        } else if (!free) {
      +                            nodeContent += line.value.substring(2) + "\n";
      +                        } else {
      +                            nodeContent += line.value + "\n";
      +                        }
      +                        count += 1;
      +                    }
      +                    doc.add_node(new Quote(doc, nodeContent, res['class'], res['id']));
      +                    if (count < lines.length && lines[count].type !== "quote") {
      +                        count -= 1;
      +                    }
                           break;
      -                case 'code':
      -                    doc.add_node(new Code(doc, line.value));
      +                case "code":
      +                    res = {};
      +                    res['class'] = null;
      +                    res['id'] = null;
      +                    if (line.value === "@@@") {
      +                        free = true;
      +                        count += 1;
      +                        res['text'] = null;
      +                    } else if (line.value.startsWith("@@@")) {
      +                        free = true;
      +                        res = this.parse_inner_markup(line.value.substring(3));
      +                        count += 1;
      +                    } else if (line.value.startsWith("@@")) {
      +                        res = this.parse_inner_markup(line.value.substring(2));
      +                        if (res['text'] in LANGUAGES) {
      +                            count += 1; // skip
      +                        }
      +                    }
      +                    while (count < lines.length && lines[count].type === "code") {
      +                        line = lines[count];
      +                        if (!free && !line.value.startsWith("@@")) {
      +                            break;
      +                        } else if (free && line.value === "@@@") {
      +                            break;
      +                        } else if (!free) {
      +                            nodeContent += line.value.substring(2) + "\n";
      +                        } else {
      +                            nodeContent += line.value + "\n";
      +                        }
      +                        count += 1;
      +                    }
      +                    doc.add_node(new Code(doc, nodeContent, res['class'], res['id'], res['text'], false)); // text is the language
      +                    if (count < lines.length && lines[count].type !== "code") {
      +                        count -= 1;
      +                    }
                           break;
                       default:
                           throw new Error(`Unknown ${line.type}`);
                   }
      +            count += 1;
               }
               // List
      -        if (actual_list !== null)
      -        {
      +        if (actual_list !== null) {
                   doc.add_node(actual_list.root());
               }
               return doc;
           }
       
      -    static process_inner_string(doc, str)
      -    {
      -        let in_sup = false;
      -        let in_sub = false;
      -        let in_bold = false;
      -        let in_italic = false;
      -        let in_underline = false;
      -        let in_stroke = false;
      -        let in_link = false; // hum check this :TODO:
      +    // Find a pattern in a string. Pattern can be any character wide. Won't find any escaped pattern \pattern but will accept double escaped \\pattern
      +    static find(str, start, pattern) {
      +        // String not big enough to have the motif
      +        if (pattern.length > str.slice(start).length) {
      +            return -1;
      +        }
      +        for (let i = start; i < str.length; i++) {
      +            if (str.slice(i, i + pattern.length) === pattern) {
      +                if (
      +                    (i - 1 < start)
      +                    || (i - 1 >= start && str[i - 1] !== "\\")
      +                    || (i - 2 < start)
      +                    || (i - 2 >= start && str[i - 1] === "\\" && str[i - 2] === "\\")) {
      +                    return i;
      +                }
      +            }
      +        }
      +        return -1;
      +    }
      +
      +    static unescape_code(str) {
      +        let res = "";
      +        for (let i = 0; i < str.length; i++) {
      +            const char = str[i];
      +            const next = i + 1 < str.length ? str[i + 1] : "";
      +            const next_next = i + 2 < str.length ? str[i + 2] : "";
      +            if (char === "\\" && next === '@' && next_next === '@') {
      +                // do nothing because we don't add the '\' char but we will add @@ after
      +            } else {
      +                res += char;
      +            }
      +        }
      +        return res;
      +    }
      +
      +    static parse_inner_string(doc, str) {
               let index = 0;
      -        let word = '';
      +        let word = "";
               let nodes = [];
      -        while (index < str.length)
      -        {
      +        let matches = [
      +            ["@", "@", "code"],
      +            ["(", "(", "picture"],
      +            ["[", "[", "link"],
      +            ["{", "{", "markup"],
      +            ["$", "$", "echo"],
      +            ["*", "*", "bold"],
      +            ["!", "!", "strong"],
      +            ["'", "'", "italic"],
      +            ["/", "/", "em"],
      +            ["_", "_", "underline"],
      +            ["^", "^", "sup"],
      +            ["%", "%", "sub"],
      +            ["-", "-", "stroke"],
      +        ];
      +        let specials = ["@", "(", "[", "{", "$", "*", "!", "'", "/", "_", "^", "%", "-", "#", "\\", "•"];
      +        let modes = {
      +            bold: false,
      +            strong: false,
      +            italic: false,
      +            em: false,
      +            underline: false,
      +            sup: false,
      +            sub: false,
      +            stroke: false,
      +        };
      +
      +        while (index < str.length) {
                   let char = str[index];
                   let next = index + 1 < str.length ? str[index + 1] : null;
                   let next_next = index + 2 < str.length ? str[index + 2] : null;
                   let prev = index - 1 >= 0 ? str[index - 1] : null;
      -            // Glyphs
      -            // Glyphs - Solo
      -            if (char === '&')
      -            {
      -                word += '&';
      -            } else if (char === '<')
      -            {
      -                word += '<';
      -            } else if (char === '>')
      -            {
      -                word += '>';
      +            // Remplacement des glyphes
                   // Glyphs - Quatuor
      -            } else if (char === '!' && next === '!' && next_next === ' ' && prev !== "  ") {
      -                if (word.length > 0)
      -                {
      -                    nodes.push(new Text(doc, word.substring(0, word.length - 1))); // remove the last space
      -                    word = '';
      +            if (
      +                char === "#" &&
      +                next === "#" &&
      +                next_next === " " &&
      +                prev !== "\\"
      +            ) {
      +                if (word.length > 0) {
      +                    nodes.push(
      +                        new Text(doc, word.substring(0, word.length - 1))
      +                    ); // remove the last space
      +                    word = "";
                       }
                       nodes.push(new BR(doc));
                       index += 2;
      -            } else if (char === '\\' && str.substring(index + 1, index + 5) === ' !! ') { // escape it
      -                word += ' !! ';
      +            } else if (char === "\\" && next === "\\" && next_next === "\\") {
      +                // escape it
      +                word += "\\\\";
                       index += 4;
      -            // Glyphs - Trio
      -            } else if (char === '.' && next === '.' && next_next === '.' && prev !== "\\") {
      -                word += '…';
      +                // Glyphs - Trio
      +            } else if (
      +                char === "." &&
      +                next === "." &&
      +                next_next === "." &&
      +                prev !== "\\"
      +            ) {
      +                word += "…";
                       index += 2;
      -            } else if (char === '=' && next === '=' && next_next === '>' && prev !== "\\") {
      -                word += '⇒'; // ==>
      +            } else if (
      +                char === "=" &&
      +                next === "=" &&
      +                next_next === ">" &&
      +                prev !== "\\"
      +            ) {
      +                word += "⇒"; // ==>
                       index += 2;
      -            } else if (char === '<' && next === '=' && next_next === '=' && prev !== "\\") {
      -                word += '⇐';  // <==
      +            } else if (
      +                char === "<" &&
      +                next === "=" &&
      +                next_next === "=" &&
      +                prev !== "\\"
      +            ) {
      +                word += "⇐"; // <==
                       index += 2;
      -            // Glyphs - Duo
      -            } else if (char === '-' && next === '>' && prev !== "\\" && !in_link) {
      -                word += '→';  // ->
      +                // Glyphs - Duo
      +            } else if (char === "-" && next === ">" && prev !== "\\") {
      +                word += "→"; // ->
                       index += 1;
      -            } else if (char === '<' && next === '-' && prev !== "\\") {
      -                word += '←';   // <-
      +            } else if (char === "<" && next === "-" && prev !== "\\") {
      +                word += "←"; // <-
                       index += 1;
      -            } else if (char === 'o' && next === 'e' && prev !== "\\") {
      -                word += 'œ';            // oe
      +            } else if (char === "o" && next === "e" && prev !== "\\") {
      +                word += "œ"; // oe
                       index += 1;
      -            } else if (char === 'O' && next === 'E' && prev !== "\\") {
      -                word += 'Œ';            // OE
      +            } else if (char === "O" && next === "E" && prev !== "\\") {
      +                word += "Œ"; // OE
                       index += 1;
      -            } else if (char === '=' && next === '=' && prev !== "\\") {
      -                word += '⩵';            // ==
      +            } else if (char === "=" && next === "=" && prev !== "\\") {
      +                word += "⩵"; // ==
                       index += 1;
      -            } else if (char === '!' && next === '=' && prev !== "\\") {
      -                word += '≠';         // !=
      +            } else if (char === "!" && next === "=" && prev !== "\\") {
      +                word += "≠"; // !=
                       index += 1;
      -            } else if (char === '>' && next === '=' && prev !== "\\") {
      -                word += '⩾';// >=
      +            } else if (char === ">" && next === "=" && prev !== "\\") {
      +                word += "⩾"; // >=
                       index += 1;
      -            } else if (char === '<' && next === '=' && prev !== "\\") {
      -                word += '⩽';   // <=
      +            } else if (char === "<" && next === "=" && prev !== "\\") {
      +                word += "⩽"; // <=
                       index += 1;
      -            }
      -            // Styles
      -            else if (char === '@' && next === '@' && prev !== '\\')
      -            {
      -                if (word.length > 0)
      -                {
      -                    nodes.push(new Text(doc, word));
      -                    word = '';
      +                // Glyphs sur un caractère
      +            } else if (char === "&") {
      +                word += "&";
      +            } else if (char === "<") {
      +                word += "<";
      +            } else if (char === ">") {
      +                word += ">";
      +                // Escaping
      +            } else if (char === "\\" && specials.includes(next)) {
      +                // Do nothing, this is an escaping slash
      +                if (next === "\\") {
      +                    word += "\\";
      +                    index += 1;
                       }
      -                let is_code_ok = -1;
      -                for (let subindex = index  + 2; subindex < str.length; subindex++)
      -                {
      -                    let subchar = str[subindex];
      -                    let subnext = (subindex + 1) < str.length ? str[subindex + 1] : null;
      -                    let subprev = (subindex - 1) > 0 ? str[subindex - 1] : null;
      -                    // Ignore all formatting in a inline code bloc
      -                    if (subchar === '@' && subnext === '@' && subprev !== '\\')
      -                    {
      -                        nodes.push(new Code(doc, str.substring(index + 2, subindex), true));
      -                        is_code_ok = subindex + 1;
      +            }
      +            // Text Styles
      +            else {
      +                let match = null;
      +                for (let pattern of matches) {
      +                    if (
      +                        char === pattern[0] &&
      +                        next === pattern[1] &&
      +                        prev !== "\\"
      +                    ) {
      +                        match = pattern[2];
                               break;
                           }
                       }
      -                if (is_code_ok === -1)
      -                {
      -                    throw new Error("Unfinished inline code sequence: " + str);
      -                }
      -                index = is_code_ok; // will inc by 1 at the end of the loop
      -            }
      -            else if (char === '*' && next === '*' && prev !== '\\')
      -            {
      -                if (word.length > 0)
      -                {
      -                    nodes.push(new Text(doc, word));
      -                    word = '';
      -                }
      -                if (!in_bold)
      -                {
      -                    in_bold = true;
      -                    nodes.push(new Start(doc, 'bold'))
      -                }
      -                else
      -                {
      -                    in_bold = false;
      -                    nodes.push(new Stop(doc, 'bold'));
      -                }
      -                index += 1;
      -            }
      -            else if (char === "'" && next === "'" && prev !== '\\')
      -            {
      -                if (word.length > 0)
      -                {
      -                    nodes.push(new Text(doc, word));
      -                    word = '';
      -                }
      -                if (!in_italic)
      -                {
      -                    in_italic = true;
      -                    nodes.push(new Start(doc, 'italic'))
      -                }
      -                else
      -                {
      -                    in_italic = false;
      -                    nodes.push(new Stop(doc, 'italic'));
      -                }
      -                index += 1;
      -            }
      -            else if (char === '_' && next === '_' && prev !== '\\')
      -            {
      -                if (word.length > 0)
      -                {
      -                    nodes.push(new Text(doc, word));
      -                    word = '';
      -                }
      -                if (!in_underline)
      -                {
      -                    in_underline = true;
      -                    nodes.push(new Start(doc, 'underline'))
      -                }
      -                else
      -                {
      -                    in_underline = false;
      -                    nodes.push(new Stop(doc, 'underline'));
      -                }
      -                index += 1;
      -            }
      -            else if (char === '-' && next === '-' && prev !== '\\')
      -            {
      -                if (word.length > 0)
      -                {
      -                    nodes.push(new Text(doc, word));
      -                    word = '';
      -                }
      -                if (!in_stroke)
      -                {
      -                    in_stroke = true;
      -                    nodes.push(new Start(doc, 'stroke'))
      -                }
      -                else
      -                {
      -                    in_stroke = false;
      -                    nodes.push(new Stop(doc, 'stroke'));
      -                }
      -                index += 1;
      -            }
      -            else if (char === '^' && next === '^' && prev !== '\\')
      -            {
      -                if (word.length > 0)
      -                {
      -                    nodes.push(new Text(doc, word));
      -                    word = '';
      -                }
      -                if (!in_sup)
      -                {
      -                    in_sup = true;
      -                    nodes.push(new Start(doc, 'sup'));
      -                }
      -                else
      -                {
      -                    in_sup = false;
      -                    nodes.push(new Stop(doc, 'sup'));
      -                }
      -                index += 1;
      -            }
      -            else if (char === '%' && next === '%' && prev !== '\\')
      -            {
      -                if (word.length > 0)
      -                {
      -                    nodes.push(new Text(doc, word));
      -                    word = '';
      -                }
      -                if (!in_sub)
      -                {
      -                    in_sub = true;
      -                    nodes.push(new Start(doc, 'sub'));
      -                }
      -                else
      -                {
      -                    in_sub = false;
      -                    nodes.push(new Stop(doc, 'sub'));
      -                }
      -                index += 1;
      -            }
      -            else if (char === '{' && next === '{' && prev !== '\\')
      -            {
      -                if (word.length > 0)
      -                {
      -                    nodes.push(new Text(doc, word));
      -                    word = '';
      -                }
      -                let end = str.indexOf('}}', index);
      -                let content = str.substring(index+2, end);
      -                let res = Hamill.process_inner_markup(content);
      -                if (res['has_text'])
      -                {
      -                    nodes.push(new Span(doc, res['id'], res['class'], res['text']));
      -                }
      -                else
      -                {
      -                    nodes.push(new ParagraphIndicator(doc, res['id'], res['class']));
      -                }
      -                index = end + 1;
      -            }
      -            else if (char === '[' && next === '[' && prev !== '\\')
      -            {
      -                if (word.length > 0)
      -                {
      -                    nodes.push(new Text(doc, word));
      -                    word = '';
      -                }
      -                let end = str.indexOf(']]', index);
      -                let content = str.substring(index+2, end);
      -                let parts = content.split('->');
      -                let display = undefined;
      -                let url = undefined;
      -                if (parts.length === 1)
      -                {
      -                    url = parts[0].trim();
      -                }
      -                else if (parts.length === 2)
      -                {
      -                    display = Hamill.process_inner_string(doc, parts[0].trim());
      -                    url = parts[1].trim();
      -                }
      -                nodes.push(new Link(doc, url, display));
      -                index = end + 1;
      -            }
      -            else if (char === '(' && next === '(' && prev !== '\\')
      -            {
      -                if (word.length > 0)
      -                {
      -                    nodes.push(new Text(doc, word));
      -                    word = '';
      -                }
      -                let end = str.indexOf('))', index);
      -                let content = str.substring(index+2, end);
      -                let res = Hamill.process_inner_picture(content);
      -                nodes.push(new Picture(doc, res["url"], res["text"], res["class"], res["id"]));
      -                index = end + 1;
      -            }
      -            else if (char === '$' && next === '$' && prev !== '\\')
      -            {
      -                if (word.length > 0)
      -                {
      -                    nodes.push(new Text(doc, word));
      -                    word = '';
      +                if (match !== null) {
      +                    if (word.length > 0) {
      +                        nodes.push(new Text(doc, word));
      +                        word = "";
      +                    }
      +                    if (match === "picture") {
      +                        let end = str.indexOf("))", index);
      +                        if (end === -1) {
      +                            throw new Error(`Unclosed image in ${str}`);
      +                        }
      +                        let content = str.substring(index + 2, end);
      +                        let res = Hamill.parse_inner_picture(content);
      +                        nodes.push(
      +                            new Picture(
      +                                doc,
      +                                res["url"],
      +                                res["text"],
      +                                res["class"],
      +                                res["id"]
      +                            )
      +                        );
      +                        index = end + 1;
      +                    } else if (match === "link") {
      +                        let end = str.indexOf("]]", index);
      +                        if (end === -1) {
      +                            throw new Error(`Unclosed link in ${str}`);
      +                        }
      +                        let content = str.substring(index + 2, end);
      +                        let parts = content.split("->");
      +                        let display = null;
      +                        let url = null;
      +                        if (parts.length === 1) {
      +                            url = parts[0].trim();
      +                        } else if (parts.length === 2) {
      +                            display = Hamill.parse_inner_string(
      +                                doc,
      +                                parts[0].trim()
      +                            );
      +                            url = parts[1].trim();
      +                        }
      +                        nodes.push(new Link(doc, url, display));
      +                        index = end + 1;
      +                    } else if (match === "markup") {
      +                        let end = str.indexOf("}}", index);
      +                        if (end === -1) {
      +                            throw new Error(`Unclosed markup in ${str}`);
      +                        }
      +                        let content = str.substring(index + 2, end);
      +                        let res = Hamill.parse_inner_markup(content);
      +                        if (res["has_text"]) {
      +                            nodes.push(
      +                                new Span(
      +                                    doc,
      +                                    res["text"],
      +                                    res["id"],
      +                                    res["class"]
      +                                )
      +                            );
      +                        } else {
      +                            nodes.push(
      +                                new ParagraphIndicator(
      +                                    doc,
      +                                    res["id"],
      +                                    res["class"]
      +                                )
      +                            );
      +                        }
      +                        index = end + 1;
      +                    } else if (match === "echo") {
      +                        let end = str.indexOf("$$", index + 2);
      +                        if (end === -1) {
      +                            throw new Error(`Unclosed display in ${str}`);
      +                        }
      +                        let content = str.substring(index + 2, end);
      +                        nodes.push(new GetVar(doc, content));
      +                        index = end + 1;
      +                    } else if (match === "code") {
      +                        let is_code_ok = Hamill.find(str, index + 2, "@@");
      +                        if (is_code_ok === -1) {
      +                            throw new Error(
      +                                "Unfinished inline code sequence: " + str
      +                            );
      +                        }
      +                        let code_str = str.slice(index + 2, is_code_ok);
      +                        let lang = null;
      +                        let language = code_str.split(" ")[0];
      +                        if (language in LANGUAGES) {
      +                            lang = language;
      +                            code_str = code_str.substring(language.length+1); // remove the language and one space
      +                        }
      +                        nodes.push(new Code(doc, Hamill.unescape_code(code_str), null, null, lang, true)); // unescape only @@ !
      +                        index = is_code_ok + 1; // will inc by 1 at the end of the loop
      +                    } else {
      +                        if (!modes[match]) {
      +                            modes[match] = true;
      +                            nodes.push(new Start(doc, match));
      +                        } else {
      +                            modes[match] = false;
      +                            nodes.push(new Stop(doc, match));
      +                        }
      +                        index += 1;
      +                    }
      +                } // no match
      +                else {
      +                    word += char;
                       }
      -                let end = str.indexOf('$$', index+2);
      -                let content = str.substring(index+2, end);
      -                nodes.push(new GetVar(doc, content));
      -                index = end + 1;
      -            }
      -            else if (char === '\\'
      -                     && ['*', "'", '-', '_', '^', '%', '@', '$', '(', '[', '{'].includes(next)
      -                     && ['*', "'", '-', '_', '^', '%', '@', '$', '(', '[', '{'].includes(next_next)
      -                     && next === next_next)
      -            {
      -                // Do nothing, this an escaping slash
      -            }
      -            else
      -            {
      -                word += char;
                   }
                   index += 1;
               }
      -        if (word.length > 0)
      -        {
      +        if (word.length > 0) {
                   nodes.push(new Text(doc, word));
      -            word = '';
               }
               return nodes;
           }
       
      -    static process_inner_picture(content)
      -    {
      +    static parse_inner_picture(content) {
               let res = null;
      -        let parts = content.split('->');
      -        if (parts.length === 1)
      -        {
      -            return {'has_text': false, 'has_only_text': false,
      -                    'class': null, 'id': null, 'text': null,
      -                    'url': parts[0]};
      -        }
      -        else
      -        {
      +        let parts = content.split("->");
      +        if (parts.length === 1) {
      +            return {
      +                has_text: false,
      +                has_only_text: false,
      +                class: null,
      +                id: null,
      +                text: null,
      +                url: parts[0],
      +            };
      +        } else {
                   content = parts[0];
      -            res = Hamill.process_inner_markup(content);
      -            res['url'] = parts[1].trim();
      +            res = Hamill.parse_inner_markup(content);
      +            res["url"] = parts[1].trim();
               }
               return res;
           }
       
      -    static process_inner_markup(content)
      -    {
      +    static parse_inner_markup(content) {
               let cls = null;
               let in_class = false;
               let ids = null;
               let in_ids = false;
               let text = null;
               let in_text = false;
      -        for (let c of content)
      -        {
      -            if (c === '.' && in_class === false && in_ids === false && in_text === false && cls === null && text === null)
      -            {
      +        for (let c of content) {
      +            if (
      +                c === "." &&
      +                in_class === false &&
      +                in_ids === false &&
      +                in_text === false &&
      +                cls === null &&
      +                text === null
      +            ) {
                       in_class = true;
      -                cls = '';
      +                cls = "";
                       continue;
      -            }
      -            else if (c === '.')
      -            {
      -                throw new Error(`Class or text already defined for this markup: ${content}`);
      -            }
      -
      -            if (c === '#' && in_class === false && in_ids === false && in_text === false && ids === null && text === null)
      -            {
      +            } else if (c === ".") {
      +                throw new Error(
      +                    `Class or text already defined for this markup: ${content}`
      +                );
      +            }
      +
      +            if (
      +                c === "#" &&
      +                in_class === false &&
      +                in_ids === false &&
      +                in_text === false &&
      +                ids === null &&
      +                text === null
      +            ) {
                       in_ids = true;
      -                ids = '';
      +                ids = "";
                       continue;
      -            }
      -            else if (c === '#')
      -            {
      -                throw new Error(`ID or text alreay defined for this markup: ${content}`);
      +            } else if (c === "#") {
      +                throw new Error(
      +                    `ID or text alreay defined for this markup: ${content}`
      +                );
                   }
       
      -            if (c === ' ' && in_class)
      -            {
      +            if (c === " " && in_class) {
                       in_class = false;
                   }
       
      -            if (c === ' ' && in_ids)
      -            {
      +            if (c === " " && in_ids) {
                       in_ids = false;
                   }
       
      -            if (c !== ' ' && in_class === false && in_ids === false && in_text === false && text === null)
      -            {
      +            if (
      +                c !== " " &&
      +                in_class === false &&
      +                in_ids === false &&
      +                in_text === false &&
      +                text === null
      +            ) {
                       in_text = true;
      -                text = '';
      +                text = "";
                   }
       
      -            if (in_class)
      -            {
      +            if (in_class) {
                       cls += c;
      -            }
      -            else if (in_ids)
      -            {
      +            } else if (in_ids) {
                       ids += c;
      -            }
      -            else if (in_text)
      -            {
      +            } else if (in_text) {
                       text += c;
                   }
               }
      -        let has_text = (text !== null) ? true : false;
      -        let has_only_text = (has_text && cls === null && ids === null) ? true : false;
      -        return {'has_text': has_text, 'has_only_text': has_only_text,  'class': cls, 'id': ids, 'text': text};
      +        let has_text = text !== null;
      +        let has_only_text =
      +            has_text && cls === null && ids === null;
      +        return {
      +            has_text: has_text,
      +            has_only_text: has_only_text,
      +            class: cls,
      +            id: ids,
      +            text: text,
      +        };
           }
      -
       }
       
       //-------------------------------------------------------------------------------
       // Functions
       //-------------------------------------------------------------------------------
       
      -function tests(stop_on_first_error=false)
      -{
      -    console.log("\n------------------------------------------------------------------------");
      -    console.log("Test de process_string");
      -    console.log("------------------------------------------------------------------------\n");
      +function tests(stop_on_first_error = false, stop_at = null) {
      +    console.log(
      +        "\n========================================================================"
      +    );
      +    console.log("Starting tests");
      +    console.log(
      +        "========================================================================"
      +    );
           let test_suite = [
               // Comments, HR and BR
      -        ["// This is a comment", ""],
      -        ["!var EXPORT_COMMENT=true\n// This is a comment", "\n"],
      +        ["!rem This is a comment", ""],
      +        ["§§ This is another comment", ""],
      +        [
      +            "!var EXPORT_COMMENT=true\n!rem This is a comment",
      +            "\n",
      +        ],
      +        [
      +            "!var EXPORT_COMMENT=true\n§§ This is a comment",
      +            "\n",
      +        ],
               ["---", "
      \n"], - ["a !! b", "

      a
      b

      \n"], + ["a ## b", "

      a
      b

      \n"], // Titles ["### Title 3", '

      Title 3

      \n'], ["#Title 1", '

      Title 1

      \n'], + // Paragraph + ["a", "

      a

      \n"], + ["a\n\n\n", "

      a

      \n"], + ["a\nb\n\n", "

      a
      \nb

      \n"], // Text modifications ["**bonjour**", "

      bonjour

      \n"], ["''italic''", "

      italic

      \n"], @@ -1911,64 +2224,253 @@ function tests(stop_on_first_error=false) ["^^superscript^^", "

      superscript

      \n"], ["%%subscript%%", "

      subscript

      \n"], ["@@code@@", "

      code

      \n"], + ["!!ceci est strong!!", "

      ceci est strong

      \n"], + ["//ceci est emphase//", "

      ceci est emphase

      \n"], + // Escaping + ["\\**bonjour\\**", "

      **bonjour**

      \n"], + [ + "@@code \\@@variable = '\\n' end@@", + "

      code @@variable = '\\n' end

      \n", + ], + // Div, p and span + ["{{#myid .myclass}}", '
      \n'], + ["{{#myid}}", '
      \n'], + ["{{.myclass}}", '
      \n'], + ["{{begin}}", "
      \n"], + ["{{end}}", "
      \n"], + [ + "{{#myid .myclass}}content", + '

      content

      \n', + ], + [ + "{{#myid}}content", + '

      content

      \n'], + [ + "{{.myclass}}content", + '

      content

      \n'], + [ + "je suis {{#myid .myclass rouge}} et oui !", + '

      je suis rouge et oui !

      \n', + ], + [ + "je suis {{#myid rouge}} et oui !", + '

      je suis rouge et oui !

      \n', + ], + [ + "je suis {{.myclass rouge}} et oui !", + '

      je suis rouge et oui !

      \n', + ], + // Details + [ + "< petit>>", + "
      smallpetit
      \n", + ], + [ + "<<.reddetail small -> petit>>", + `
      smallpetit
      \n`, + ], + [ + "<<#mydetail small -> petit>>", + `
      smallpetit
      \n`, + ], + [ + "<<.reddetail #mydetail small -> petit>>", + `
      smallpetit
      \n`, + ], + [ + "<>\n* This is very big!\n* Indeed\n<>", + "
      big\n
        \n
      • This is very big!
      • \n
      • Indeed
      • \n
      \n
      \n", + ], + [ + "<<.mydetail big>>\n* This is very big!\n* Indeed\n<>", + `
      big\n
        \n
      • This is very big!
      • \n
      • Indeed
      • \n
      \n
      \n`, + ], + [ + "<<#reddetail big>>\n* This is very big!\n* Indeed\n<>", + `
      big\n
        \n
      • This is very big!
      • \n
      • Indeed
      • \n
      \n
      \n`, + ], + [ + "<<#reddetail .mydetail big>>\n* This is very big!\n* Indeed\n<>", + `
      big\n
        \n
      • This is very big!
      • \n
      • Indeed
      • \n
      \n
      \n`, + ], + // Code + [ + "Voici du code : @@if a == 5 then puts('hello 5') end@@", + "

      Voici du code : if a == 5 then puts('hello 5') end

      \n", + ], + [ + "Voici du code Ruby : @@ruby if a == 5 then puts('hello 5') end@@", + `

      Voici du code Ruby : if a == 5 then puts('hello 5') end

      \n`, + ], + [ + "@@ruby\n@@if a == 5 then\n@@ puts('hello 5')\n@@end\n", + "
      \nif a == 5 then\n    puts('hello 5')\nend\n
      \n" + ], + [ + "@@@ruby\nif a == 5 then\n puts('hello 5')\nend\n@@@\n", + "
      \nif a == 5 then\n    puts('hello 5')\nend\n
      \n" + ], + // Quotes + [ + ">>ceci est une quote\n>>qui s'étend sur une autre ligne\nText normal", + "
      \nceci est une quote
      \nqui s'étend sur une autre ligne
      \n
      \n

      Text normal

      \n" + ], + [ + ">>>\nceci est une quote libre\nqui s'étend sur une autre ligne aussi\n>>>\nText normal", + "
      \nceci est une quote libre
      \nqui s'étend sur une autre ligne aussi
      \n
      \n

      Text normal

      \n" + ], + [ + ">>>.redquote\nceci est une quote libre\nqui s'étend sur une autre ligne aussi\n>>>\nText normal", + `
      \nceci est une quote libre
      \nqui s'étend sur une autre ligne aussi
      \n
      \n

      Text normal

      \n` + ], + [ + ">>>#myquote\nceci est une quote libre\nqui s'étend sur une autre ligne aussi\n>>>\nText normal", + `
      \nceci est une quote libre
      \nqui s'étend sur une autre ligne aussi
      \n
      \n

      Text normal

      \n` + ], + [ + ">>>.redquote #myquote\nceci est une quote libre\nqui s'étend sur une autre ligne aussi\n>>>\nText normal", + `
      \nceci est une quote libre
      \nqui s'étend sur une autre ligne aussi
      \n
      \n

      Text normal

      \n` + ], + [ + ">>>.redquote #myquote OH NON DU TEXTE !\nceci est une quote libre\nqui s'étend sur une autre ligne aussi\n>>>\nText normal", + `
      \nceci est une quote libre
      \nqui s'étend sur une autre ligne aussi
      \n
      \n

      Text normal

      \n`, + "A line starting a blockquote should only have a class or id indication not text" + ], + // Lists + [ + "* Bloc1\n * A\n * B\n* Bloc2\n * C", + "
        \n
      • Bloc1\n
          \n
        • A
        • \n
        • B
        • \n
        \n
      • \n
      • Bloc2\n
          \n
        • C
        • \n
        \n
      • \n
      \n", + ], + [" * A", "
        \n
      • A
      • \n
      \n"], + // Definition lists + // Tables + // Links + [ + "[[Ceci est un mauvais lien->", + "", + "Unclosed link in [[Ceci est un mauvais lien->", + ], + // Images + [ + "((https://fr.wikipedia.org/wiki/%C3%89douard_Detaille#/media/Fichier:Carabinier_de_la_Garde_imp%C3%A9riale.jpg))", + `

      \n` + ], + // Constants + ["!const NUMCONST = 25\n$$NUMCONST$$", "

      25

      \n"], + ["\\!const NOT A CONST", "

      !const NOT A CONST

      \n"], + [ + "!const ALPHACONST = abcd\n!const ALPHACONST = efgh", + "", + "Can't set the value of the already defined constant: ALPHACONST of type string", + ], + // Variables + ["!var NUMBER=5\n$$NUMBER$$", "

      5

      \n"], + [ + "!var ALPHA=je suis un poulpe\n$$ALPHA$$", + "

      je suis un poulpe

      \n", + ], + ["!var BOOLEAN=true\n$$BOOLEAN$$", "

      true

      \n"], + [ + "!var NUM=1\n$$NUM$$\n!var NUM=25\n$$NUM$$\n", + "

      1

      \n

      25

      \n", + ], + ["\\!var I AM NOT A VAR", "

      !var I AM NOT A VAR

      \n"], + ["$$UNKNOWNVAR$$", "", "Unknown variable: UNKNOWNVAR"], + [ + "!var TITLE=ERROR", + "", + "You cannot use TITLE for a variable because it is a predefined constant.", + ], + // Inclusion of HTML files + ["!include include_test.html", "

      Hello World!

      \n"], + [ + "\\!include I AM NOT AN INCLUDE", + "

      !include I AM NOT AN INCLUDE

      \n", + ], + // Links to CSS and JavaScript files + ["!require pipo.css", ""], + [ + "\\!require I AM NOT A REQUIRE", + "

      !require I AM NOT A REQUIRE

      \n", + ], + // Raw HTML and CSS + ["!html
      Hello
      ", "
      Hello
      \n"], + [ + "\\!html
      Hello
      ", + "

      !html <div>Hello</div>

      \n", + ], // Error, the \ should be removed! + ["!css p { color: pink;}", ""], ]; let nb_ok = 0; - for (let t of test_suite) - { - if (test(t[0], t[1])) - { + for (let [index, t] of test_suite.entries()) { + if ( + t === undefined || + t === null || + !Array.isArray(t) || + (t.length !== 2 && t.length !== 3) + ) { + throw new Error("Test not well defined:", t); + } + console.log( + "\n-------------------------------------------------------------------------" + ); + console.log(`Test ${index + 1}`); + console.log( + "-------------------------------------------------------------------------\n" + ); + if (test(t[0], t[1], t.length === 3 ? t[2] : null)) { nb_ok += 1; - } - else if (stop_on_first_error) - { + } else if (stop_on_first_error) { throw new Error("Stopping on first error"); } + if (stop_at !== null && stop_at === index + 1) { + console.log(`Stopped at ${stop_at}`); + break; + } } - console.log(`Tests ok : ${nb_ok} / ${test_suite.length}`); + console.log(`\nTests ok : ${nb_ok} / ${test_suite.length}\n`); //let doc = Hamill.process_string("* A\n* B [[http://www.gogol.com]]\n + D\n + E"); //let doc = Hamill.process_string("+ Été @@2006@@ Mac, Intel, Mac OS X"); //let doc = Hamill.process_string("@@Code@@"); //let doc = Hamill.process_string("Bonjour $$VERSION$$"); - - /* - console.log("------------------------------------------------------------------------"); - console.log("Test de process_file (hamill)"); - console.log("------------------------------------------------------------------------\n"); - - Hamill.process_file('../../dgx/static/input/informatique/tools_langs.hml').to_html_file('../../dgx/informatique/'); - Hamill.process_file('../../dgx/static/input/index.hml').to_html_file('../../dgx/'); - Hamill.process_file('../../dgx/static/input/tests.hml').to_html_file('../../dgx/'); - */ } -function test(s, r) -{ - let doc = Hamill.process_string(s); - console.log(doc.to_s()); - let output = doc.to_html(); - console.log("RESULT:"); - if (output === "") - { - console.log("EMPTY"); - } - else - { - console.log(output); - } - if (output === r) - { - console.log("Test Validated"); - return true; - } - else - { - if (r === "") - { - r = "EMPTY"; +function test(text, result, error = null) { + try { + let doc = Hamill.process(text); + let output = doc.to_html(); + console.log("RESULT:"); + if (output === "") { + console.log("EMPTY"); + } else { + console.log(output); + } + if (output === result) { + console.log("Test Validated"); + return true; + } else { + if (result === "") { + result = "EMPTY"; + } + console.log(`Error, expected:\n${result}`); + return false; + } + } catch (e) { + console.log("RESULT:"); + if (error !== null && e.message === error) { + console.log("Error expected:", e.message); + console.log("Test Validated"); + return true; + } else if (error !== null) { + console.log(e.message); + console.log(`Error, expected:\n${error}`); + return false; + } else { + console.log("Unexpected error:", e.message, e.stack); + console.log(`No error expected, expected:\n${result}`); + return false; } - console.log("Error, expected:", r); - return false; } } @@ -1976,20 +2478,65 @@ function test(s, r) // Main //------------------------------------------------------------------------------- -var DEBUG = false; -if (/*DEBUG &&*/ fs !== null) -{ +const DEBUG = true; +if (fs !== null) { const do_test = true; - //const do_test = false; - if (do_test) - { - tests(true); - } - else - { - Hamill.process_file('../../dgx/static/input/index.hml').to_html_file('../../dgx/'); - Hamill.process_file('../../dgx/static/input/passetemps/pres_jeuxvideo.hml').to_html_file('../../dgx/passetemps/'); + if (do_test) { + tests(true); //, 5); + Hamill.process("../../dgx/static/input/tests.hml").to_html_file( + "../../dgx/" + ); + } else { + console.log( + "------------------------------------------------------------------------" + ); + console.log("Test de process_file (hamill)"); + console.log( + "------------------------------------------------------------------------\n" + ); + + // Pages racines + Hamill.process("../../dgx/static/input/index.hml").to_html_file( + "../../dgx/" + ); + Hamill.process("../../dgx/static/input/blog.hml").to_html_file( + "../../dgx/" + ); + Hamill.process("../../dgx/static/input/plan.hml").to_html_file( + "../../dgx/" + ); + Hamill.process("../../dgx/static/input/liens.hml").to_html_file( + "../../dgx/" + ); + Hamill.process("../../dgx/static/input/tests.hml").to_html_file( + "../../dgx/" + ); + // Passetemps + Hamill.process( + "../../dgx/static/input/passetemps/pres_jeuxvideo.hml" + ).to_html_file("../../dgx/passetemps/"); + /* This is not code + //- RTS --------------------------------------------------------------- + Hamill.process( + "../../dgx/static/input/rts/index.hml" + ).to_html_file("../../dgx/rts/"); + //- Ash --------------------------------------------------------------- + Hamill.process( + "../../dgx/static/input/ash/ash_guide.hml" + ).to_html_file("../../dgx/ash/"); + //- Hamill ------------------------------------------------------------ + Hamill.process( + "../../dgx/static/input/hamill/index.hml" + ).to_html_file("../../dgx/hamill/"); + */ + Hamill.process( + "../../dgx/static/input/hamill/hamill.hml" + ).to_html_file("../../dgx/hamill/"); } } -export {Hamill, Document}; \ No newline at end of file +//------------------------------------------------------------------------------- +// Exports +//------------------------------------------------------------------------------- + +export { Hamill, Document }; diff --git a/hamill/index.html b/hamill/index.html index dc0b390..80fe9e4 100644 --- a/hamill/index.html +++ b/hamill/index.html @@ -1,217 +1,78 @@ - - - - - - Hamill - - - - - - - - - - -
      - - -
      -

      Hamill

      -

      Entrer du code hamill à gauche pour produire de l'HTML à droite en cliquant sur Parse.

      -
      -
      - - - - - -
      -
      -
      -
      -
      -
      - -
      -
      - - + + + + Hamill Live + + + + + + +

      Live Hamill

      +

      Entrer du code Hamill à gauche pour produire de l'HTML à droite en cliquant sur Parse.

      +
      +
      + + + + + +
      +
      +
      +
      +
      + \ No newline at end of file