module xlstgen; import std.file; import std.string; import std.stdio; class xlstgen { int ptr =0; char[] script; char[] ret; int indent; this(char[] filename) { script = cast(char[]) std.file.read(filename); ret = "\n"; ret ~= "\n\n"; indent = 1; ptr = 0; try { parseScript(); } catch (Exception e) { throw new Exception("Error: "~ e.toString() ~ "\nSo far: " ~ ret); } ret ~= "\n\n"; } void dump() { writefln("%s", ret); } void parseScript() { while(ptr < script.length) { switch(script[ptr]) { case ' ', '\n', '\t': // whitespace ptr++; continue; case '/': ptr++; if (script[ptr] == '/') { ptr++; while (script[ptr] != '\n') { ptr++; } continue; } throw new Exception("only // style comments currently supported"); continue; case '}': ptr++; indent--; return; case '<': ptr++; ret ~= parseRaw(); continue; default: parseDef(); continue; } } } bool showIndent = false; char[] indentStr() { if (!showIndent) { return ""; } return std.string.repeat(" " , indent* 2); } char[] indentCr() { return showIndent ? "\n" : ""; } void parseDef() { int start = ptr; char[][char[]] args; char[] methodname = ""; while(ptr < script.length) { switch(script[ptr]) { case '(': methodname = std.string.strip(script[start..ptr]); ptr++; args = parseArgs(methodname); continue; case ';': if (!methodname.length) { methodname = std.string.strip(script[start..ptr]); } ptr++; ret ~= indentStr() ~ "<" ~ toXsltName(methodname) ~ toXsltArgs(args) ~ " />" ~ indentCr(); return; case '{': if (!methodname.length) { methodname = std.string.strip(script[start..ptr]); } ptr++; ret ~= indentStr() ~ "<" ~ toXsltName(methodname) ~ toXsltArgs(args) ~ ">" ~ indentCr(); indent++; parseScript(); ret ~= indentStr() ~ "\n"; return; case '<': ret ~= indentStr() ~ "<" ~ toXsltName(methodname) ~ toXsltArgs(args) ~ ">"; ptr++; ret ~= parseRaw(); ret ~= "" ~ indentCr(); return; case ' ', '\t': //if (!methodname.length) { // methodname = std.string.strip(script[start..ptr]); // } default: // white space? ptr++; continue; } } } char[][char[]] parseArgs(char[] methodname) { char[][char[]] args; char[] argname = ""; int noargs = 0; bool hasName; int argstart = ptr; while(ptr < script.length) { switch(script[ptr]) { case ')': ptr++; return args; case ' ', '\n', '\t': // whitespace ptr++; continue; case '=': argname = std.string.strip(script[argstart..ptr]); hasName = true; ptr++; continue; case '"': ptr++; char[] argvalue = parseArgValue(); if ((hasName) && (!argname.length)) { throw new Exception("can not mix named, and unnamed args for " ~ methodname); } if (!argname.length) { argname = getDefaultArgName(methodname, noargs); //throw new Exception("auto argnames not supported yet. in " ~ methodname); } args[argname] = argvalue; noargs++; argstart = ptr; argname = ""; continue; case ',': // do nothing.. ptr++; argstart = ptr; continue; default: // skip name elements.. ptr++; continue; } } throw new Exception("could not find closing )"); } char[] parseArgValue() { int startpos = ptr;; while(ptr < script.length) { switch(script[ptr]) { case '"': ptr++; return script[startpos..ptr-1]; case '\\': if (script[ptr+1] == '\\') { ptr+=2; continue; } if (script[ptr+1] == '"') { ptr+=2; continue; } ptr++; continue; default: ptr++; continue; } } throw new Exception("could not find closing quote"); } char[] parseRaw() { int nobrace = 0; int startpos = ptr; while(ptr < script.length) { switch(script[ptr]) { case '<': nobrace++; //writefln("got open %d", nobrace); ptr++; continue; case '>': nobrace--; //writefln("got close %d", nobrace); ptr++; if (nobrace < 0) { return script[startpos..ptr-1]; } continue; default: ptr++; continue; } } throw new Exception("could not find closing brace : " ~ script[startpos..ptr] ); } char[] toXsltName(char[] n, bool withprefix = true) { char[] r = withprefix ? "xsl:" : "";; foreach (c;n) { if (c >= 'A' && c <= 'Z') { c += 32; r ~= "-" ~ c; continue; } r ~= c; } return r; } char[] toXsltArgs(char[][char[]] ar) { if (!ar.length) { return ""; } char[] r = ""; foreach (key,value;ar) { r ~= " " ~ toXsltName(key,false) ~ "=\"" ~ value ~ "\""; } return r; } char[] getDefaultArgName(char[] nm, int offset) { switch(nm) { case "applyImports": throw new Exception("Args not expected for : " ~ nm); case "applyTemplates": switch(offset) { case 0: return "select"; case 1: return "mode"; default: throw new Exception("to many args for: " ~ nm); } case "attribute": switch(offset) { case 0: return "name"; case 1: return "namespace"; default: throw new Exception("to many args for: " ~ nm); } case "attributeSet": switch(offset) { case 0: return "name"; case 1: return "useAttributeSets"; default: throw new Exception("to many args for: " ~ nm); } case "callTemplate": return "name"; case "choose": throw new Exception("Args not expected for : " ~ nm); case "comment": throw new Exception("Args not expected for : " ~ nm); case "copy": return "useAttributeSets"; case "copyOf": return "select"; case "decimalFormat": throw new Exception("No default arg for : " ~ nm); case "element": switch(offset) { case 0: return "name"; case 1: return "namespace"; // this order is a bit debatable.. case 2: return "useAttributeSets"; default: throw new Exception("to many args for: " ~ nm); } case "fallback": throw new Exception("Args not expected for : " ~ nm); case "forEach": return "sort"; case "if": return "test"; case "import": return "href"; case "include": return "href"; case "key": switch(offset) { case 0: return "name"; case 1: return "match"; case 2: return "use"; default: throw new Exception("to many args for: " ~ nm); } case "message": return "terminate"; case "namespaceAlias": switch(offset) { case 0: return "stylesheetPrefix"; case 1: return "resultPrefix"; default: throw new Exception("to many args for: " ~ nm); } case "number": throw new Exception("No default arg for : " ~ nm); case "otherwise": throw new Exception("Args not expected for : " ~ nm); case "output": switch(offset) { case 0: return "method"; case 1: return "indent"; default: throw new Exception("only method and indent are default args for : " ~ nm); } case "param": switch(offset) { case 0: return "name"; case 1: return "select"; default: throw new Exception("to many args for: " ~ nm); } case "preserveSpace": return "elements"; case "processingInstruction": return "name"; case "sort": throw new Exception("No default arg for : " ~ nm); //select, lang, data-type, order, case-order case "stripSpace": return "elements"; case "stylesheet": throw new Exception("No default arg for : " ~ nm); //exclude-result-prefixes, extension-element-prefixes, id, version case "template": switch(offset) { case 0: return "match"; default: throw new Exception("only match is the default arg for : " ~ nm); } case "text": return "disableOutputEscaping"; case "valueOf": switch(offset) { case 0: return "select"; case 1: return "disableOutputEscaping"; default: throw new Exception("to many args for: " ~ nm); } case "variable": switch(offset) { case 0: return "name"; case 1: return "select"; default: throw new Exception("to many args for: " ~ nm); } case "when": return "test"; case "withParam": switch(offset) { case 0: return "name"; case 1: return "select"; default: throw new Exception("to many args for: " ~ nm); } default: throw new Exception("Unknown tag name: " ~ nm); } } } void main(char[][] args) { //writefln("str %s", args[1]); auto a = new xlstgen(args[1]); a.dump(); }