diff --git a/js/Recipe.js b/js/Recipe.js index d0c2e1368..17a03b295 100644 --- a/js/Recipe.js +++ b/js/Recipe.js @@ -28,7 +28,9 @@ this.ingredients = { */ var tiddler = require("./Tiddler.js"), - tiddlerUtils = require("./TiddlerUtils.js"), + tiddlerInput = require("./TiddlerInput.js"), + tiddlerOutput = require("./TiddlerOutput.js"), + utils = require("./TiddlerUtils.js"), tiddlywiki = require("./TiddlyWiki.js"), fs = require("fs"), path = require("path"), @@ -102,11 +104,11 @@ Recipe.prototype.readIngredient = function(dirname,filepath) { title: basename }; // Read the tiddler file - fields = tiddlerUtils.parseTiddler(fs.readFileSync(fullpath,"utf8"),extname,fields); + fields = tiddlerInput.parseTiddler(fs.readFileSync(fullpath,"utf8"),extname,fields); // Check for the .meta file var metafile = fullpath + ".meta"; if(path.existsSync(metafile)) { - fields = tiddlerUtils.parseMetaDataBlock(fs.readFileSync(metafile,"utf8"),fields); + fields = tiddlerInput.parseMetaDataBlock(fs.readFileSync(metafile,"utf8"),fields); } return fields; } @@ -164,7 +166,7 @@ Recipe.ingredientOutputter = { // Ordinary tiddlers are output as a <DIV> for(var t=0; t<ingredient.length; t++) { var tid = ingredient[t]; - tiddlerUtils.outputTiddlerDiv(out,tid); + tiddlerOutput.outputTiddlerDiv(out,tid); } }, javascript: function(out,ingredient) { @@ -196,7 +198,7 @@ Recipe.ingredientOutputter = { } else { tweakedTiddler = tid; } - tiddlerUtils.outputTiddlerDiv(out,tweakedTiddler,{omitPrecedingLineFeed: true}); + tiddlerOutput.outputTiddlerDiv(out,tweakedTiddler,{omitPrecedingLineFeed: true}); } } }; diff --git a/js/TiddlerInput.js b/js/TiddlerInput.js new file mode 100644 index 000000000..ddba0a371 --- /dev/null +++ b/js/TiddlerInput.js @@ -0,0 +1,154 @@ +/* +Functions concerned with parsing representations of tiddlers +*/ + +var argParser = require("./ArgParser.js"), + utils = require("./Utils.js"); + +var tiddlerInput = exports; + +/* +Parse a tiddler given its mimetype, and merge the results into a hashmap of tiddler fields. + +A file extension can be passed as a shortcut for the mimetype, as shown in tiddlerUtils.fileExtensionMappings. +For example ".txt" file extension is mapped to the "text/plain" mimetype. + +Special processing to extract embedded metadata is applied to some mimetypes. +*/ + +tiddlerInput.parseTiddler = function(text,type,fields) { + if(fields === undefined) { + var fields = {}; + } + // Map extensions to mimetpyes + var fileExtensionMapping = tiddlerInput.fileExtensionMappings[type]; + if(fileExtensionMapping) + type = fileExtensionMapping; + // Invoke the parser for the specified mimetype + var parser = tiddlerInput.parseTiddlerByMimeType[type]; + if(parser) { + return parser(text,fields); + } + return fields; +} + +tiddlerInput.fileExtensionMappings = { + ".txt": "text/plain", + ".html": "text/html", + ".tiddler": "application/x-tiddler-html-div", + ".tid": "application/x-tiddler", + ".js": "application/javascript" +} + +tiddlerInput.parseTiddlerByMimeType = { + "text/plain": function(text,fields) { + fields.text = text; + return fields; + }, + "text/html": function(text,fields) { + fields.text = text; + return fields; + }, + "application/x-tiddler-html-div": function(text,fields) { + fields = tiddlerInput.parseTiddlerDiv(text,fields); + return fields; + }, + "application/x-tiddler": function(text,fields) { + var split = text.indexOf("\n\n"); + if(split === -1) { + split = text.length; + } + fields = tiddlerInput.parseMetaDataBlock(text.substr(0,split),fields); + fields.text = text.substr(split + 2); + return fields; + }, + "application/javascript": function(text,fields) { + fields.text = text; + return fields; + } +} + +/* +Parse a block of metadata and merge the results into a hashmap of tiddler fields. + +The block consists of newline delimited lines consisting of the field name, a colon, and then the value. For example: + +title: Safari +modifier: blaine +created: 20110211110700 +modified: 20110211131020 +tags: browsers issues +creator: psd +*/ +tiddlerInput.parseMetaDataBlock = function(metaData,fields) { + if(fields === undefined) { + var fields = {}; + } + metaData.split("\n").forEach(function(line) { + var p = line.indexOf(":"); + if(p !== -1) { + var field = line.substr(0, p).trim(); + var value = line.substr(p+1).trim(); + fields[field] = tiddlerInput.parseMetaDataItem(field,value); + } + }); + return fields; +} + +/* +Parse an old-style tiddler DIV. It looks like this: + +<div title="Title" creator="JoeBloggs" modifier="JoeBloggs" created="201102111106" modified="201102111310" tags="myTag [[my long tag]]"> +<pre>The text of the tiddler (without the expected HTML encoding). +</pre> +</div> + +Note that the field attributes are HTML encoded, but that the body of the <PRE> tag is not. +*/ +tiddlerInput.parseTiddlerDiv = function(text,fields) { + if(fields === undefined) { + var fields = {}; + } + var divRegExp = /^\s*<div\s+([^>]*)>((?:.|\n)*)<\/div>\s*$/gi; + var subDivRegExp = /^(?:\s*<pre>)((?:.|\n)*)(?:<\/pre>\s*)$/gi; + var attrRegExp = /\s*([^=\s]+)\s*=\s*"([^"]*)"/gi; + var match = divRegExp.exec(text); + if(match) { + var subMatch = subDivRegExp.exec(match[2]); // Body of the <DIV> tag + if(subMatch) { + fields.text = subMatch[1]; + } else { + fields.text = match[2]; + } + do { + var attrMatch = attrRegExp.exec(match[1]); + if(attrMatch) { + var name = attrMatch[1]; + var value = attrMatch[2]; + fields[name] = tiddlerInput.parseMetaDataItem(name,value); + } + } while(attrMatch); + } + return fields; +} + +/* +Parse a single metadata field/value pair and return the value as the appropriate data type +*/ +tiddlerInput.parseMetaDataItem = function(field,value) { + var result; + switch(field) { + case "modified": + case "created": + result = utils.convertFromYYYYMMDDHHMMSS(value); + break; + case "tags": + var parser = new argParser.ArgParser(value,{noNames: true}); + result = parser.getValuesByName("",""); + break; + default: + result = value; + break; + } + return result; +} diff --git a/js/TiddlerOutput.js b/js/TiddlerOutput.js new file mode 100644 index 000000000..eb7d87f1b --- /dev/null +++ b/js/TiddlerOutput.js @@ -0,0 +1,63 @@ +/* +Functions concerned with parsing representations of tiddlers +*/ + +var argParser = require("./ArgParser.js"), + utils = require("./Utils.js"); + +var tiddlerOutput = exports; + +/* +Output a tiddler as an HTML <DIV> +out - array to push the output strings +tid - the tiddler to be output +options - options: + omitPrecedingLineFeed - determines if a linefeed is inserted between the <PRE> tag and the text +*/ +tiddlerOutput.outputTiddlerDiv = function(out,tid,options) { + var result = []; + var outputAttribute = function(name,value) { + result.push(" " + name + "=\"" + value + "\""); + }; + result.push("<div"); + for(var t in tid.fields) { + switch(t) { + case "text": + // Ignore the text field + break; + case "tags": + // Output tags as a list + outputAttribute(t,tiddlerOutput.stringifyTags(tid.fields.tags)); + break; + case "modified": + case "created": + // Output dates in YYYYMMDDHHMMSS + outputAttribute(t,utils.convertToYYYYMMDDHHMM(tid.fields[t])); + break; + default: + // Output other attributes raw + outputAttribute(t,tid.fields[t]); + break; + } + } + result.push(">\n<pre>"); + if(!(options && options.omitPrecedingLineFeed)) + result.push("\n"); + result.push(utils.htmlEncode(tid.fields.text)); + result.push("</pre>\n</div>"); + out.push(result.join("")); +} + +tiddlerOutput.stringifyTags = function(tags) { + var results = []; + for(var t=0; t<tags.length; t++) { + if(tags[t].indexOf(" ") !== -1) { + results.push("[[" + tags[t] + "]]"); + } else { + results.push(tags[t]); + } + } + return results.join(" "); +} + + diff --git a/js/TiddlerUtils.js b/js/TiddlerUtils.js deleted file mode 100644 index eea9bdb37..000000000 --- a/js/TiddlerUtils.js +++ /dev/null @@ -1,274 +0,0 @@ -/* -Various static utility functions concerned with parsing and generating representations of tiddlers and -other objects. - -This file is a bit of a dumping ground; the expectation is that most of these functions will be refactored. -*/ - -var argParser = require("./ArgParser.js"); - -var tiddlerUtils = exports; - -/* -Parse a tiddler given its mimetype, and merge the results into a hashmap of tiddler fields. - -A file extension can be passed as a shortcut for the mimetype, as shown in tiddlerUtils.fileExtensionMappings. -For example ".txt" file extension is mapped to the "text/plain" mimetype. - -Special processing to extract embedded metadata is applied to some mimetypes. -*/ - -tiddlerUtils.parseTiddler = function(text,type,fields) { - if(fields === undefined) { - var fields = {}; - } - // Map extensions to mimetpyes - var fileExtensionMapping = tiddlerUtils.fileExtensionMappings[type]; - if(fileExtensionMapping) - type = fileExtensionMapping; - // Invoke the parser for the specified mimetype - var parser = tiddlerUtils.parseTiddlerByMimeType[type]; - if(parser) { - return parser(text,fields); - } - return fields; -} - -tiddlerUtils.fileExtensionMappings = { - ".txt": "text/plain", - ".html": "text/html", - ".tiddler": "application/x-tiddler-html-div", - ".tid": "application/x-tiddler", - ".js": "application/javascript" -} - -tiddlerUtils.parseTiddlerByMimeType = { - "text/plain": function(text,fields) { - fields.text = text; - return fields; - }, - "text/html": function(text,fields) { - fields.text = text; - return fields; - }, - "application/x-tiddler-html-div": function(text,fields) { - fields = tiddlerUtils.parseTiddlerDiv(text,fields); - return fields; - }, - "application/x-tiddler": function(text,fields) { - var split = text.indexOf("\n\n"); - if(split === -1) { - split = text.length; - } - fields = tiddlerUtils.parseMetaDataBlock(text.substr(0,split),fields); - fields.text = text.substr(split + 2); - return fields; - }, - "application/javascript": function(text,fields) { - fields.text = text; - return fields; - } -} - -/* -Parse a block of metadata and merge the results into a hashmap of tiddler fields. - -The block consists of newline delimited lines consisting of the field name, a colon, and then the value. For example: - -title: Safari -modifier: blaine -created: 20110211110700 -modified: 20110211131020 -tags: browsers issues -creator: psd -*/ -tiddlerUtils.parseMetaDataBlock = function(metaData,fields) { - if(fields === undefined) { - var fields = {}; - } - metaData.split("\n").forEach(function(line) { - var p = line.indexOf(":"); - if(p !== -1) { - var field = line.substr(0, p).trim(); - var value = line.substr(p+1).trim(); - fields[field] = tiddlerUtils.parseMetaDataItem(field,value); - } - }); - return fields; -} - -/* -Parse an old-style tiddler DIV. It looks like this: - -<div title="Title" creator="JoeBloggs" modifier="JoeBloggs" created="201102111106" modified="201102111310" tags="myTag [[my long tag]]"> -<pre>The text of the tiddler (without the expected HTML encoding). -</pre> -</div> - -Note that the field attributes are HTML encoded, but that the body of the <PRE> tag is not. -*/ -tiddlerUtils.parseTiddlerDiv = function(text,fields) { - if(fields === undefined) { - var fields = {}; - } - var divRegExp = /^\s*<div\s+([^>]*)>((?:.|\n)*)<\/div>\s*$/gi; - var subDivRegExp = /^(?:\s*<pre>)((?:.|\n)*)(?:<\/pre>\s*)$/gi; - var attrRegExp = /\s*([^=\s]+)\s*=\s*"([^"]*)"/gi; - var match = divRegExp.exec(text); - if(match) { - var subMatch = subDivRegExp.exec(match[2]); // Body of the <DIV> tag - if(subMatch) { - fields.text = subMatch[1]; - } else { - fields.text = match[2]; - } - do { - var attrMatch = attrRegExp.exec(match[1]); - if(attrMatch) { - var name = attrMatch[1]; - var value = attrMatch[2]; - fields[name] = tiddlerUtils.parseMetaDataItem(name,value); - } - } while(attrMatch); - } - return fields; -} - -// Output a tiddler as an HTML <DIV> -// out - array to push the output strings -// tid - the tiddler to be output -// options - options: -// omitPrecedingLineFeed - determines if a linefeed is inserted between the <PRE> tag and the text -tiddlerUtils.outputTiddlerDiv = function(out,tid,options) { - var result = []; - var outputAttribute = function(name,value) { - result.push(" " + name + "=\"" + value + "\""); - }; - result.push("<div"); - for(var t in tid.fields) { - switch(t) { - case "text": - // Ignore the text field - break; - case "tags": - // Output tags as a list - outputAttribute(t,tiddlerUtils.stringifyTags(tid.fields.tags)); - break; - case "modified": - case "created": - // Output dates in YYYYMMDDHHMMSS - outputAttribute(t,tiddlerUtils.convertToYYYYMMDDHHMM(tid.fields[t])); - break; - default: - // Output other attributes raw - outputAttribute(t,tid.fields[t]); - break; - } - } - result.push(">\n<pre>"); - if(!(options && options.omitPrecedingLineFeed)) - result.push("\n"); - result.push(tiddlerUtils.htmlEncode(tid.fields.text)); - result.push("</pre>\n</div>"); - out.push(result.join("")); -} - -tiddlerUtils.stringifyTags = function(tags) { - var results = []; - for(var t=0; t<tags.length; t++) { - if(tags[t].indexOf(" ") !== -1) { - results.push("[[" + tags[t] + "]]"); - } else { - results.push(tags[t]); - } - } - return results.join(" "); -} - -/* -Parse a single metadata field/value pair and return the value as the appropriate data type -*/ -tiddlerUtils.parseMetaDataItem = function(field,value) { - var result; - switch(field) { - case "modified": - case "created": - result = tiddlerUtils.convertFromYYYYMMDDHHMMSS(value); - break; - case "tags": - var parser = new argParser.ArgParser(value,{noNames: true}); - result = parser.getValuesByName("",""); - break; - default: - result = value; - break; - } - return result; -} - -// Pad a string to a certain length with zeros -tiddlerUtils.zeroPad = function(n,d) -{ - var s = n.toString(); - if(s.length < d) - s = "000000000000000000000000000".substr(0,d-s.length) + s; - return s; -}; - -// Convert a date to local YYYYMMDDHHMM string format -tiddlerUtils.convertToLocalYYYYMMDDHHMM = function(date) -{ - return date.getFullYear() + tiddlerUtils.zeroPad(date.getMonth()+1,2) + tiddlerUtils.zeroPad(date.getDate(),2) + tiddlerUtils.zeroPad(date.getHours(),2) + tiddlerUtils.zeroPad(date.getMinutes(),2); -}; - -// Convert a date to UTC YYYYMMDDHHMM string format -tiddlerUtils.convertToYYYYMMDDHHMM = function(date) -{ - return date.getUTCFullYear() + tiddlerUtils.zeroPad(date.getUTCMonth()+1,2) + tiddlerUtils.zeroPad(date.getUTCDate(),2) + tiddlerUtils.zeroPad(date.getUTCHours(),2) + tiddlerUtils.zeroPad(date.getUTCMinutes(),2); -}; - -// Convert a date to UTC YYYYMMDD.HHMMSSMMM string format -tiddlerUtils.convertToYYYYMMDDHHMMSSMMM = function(date) -{ - return date.getUTCFullYear() + tiddlerUtils.zeroPad(date.getUTCMonth()+1,2) + tiddlerUtils.zeroPad(date.getUTCDate(),2) + "." + tiddlerUtils.zeroPad(date.getUTCHours(),2) + tiddlerUtils.zeroPad(date.getUTCMinutes(),2) + tiddlerUtils.zeroPad(date.getUTCSeconds(),2) + tiddlerUtils.zeroPad(date.getUTCMilliseconds(),3) +"0"; -}; - -// Create a date from a UTC YYYYMMDDHHMM format string -tiddlerUtils.convertFromYYYYMMDDHHMM = function(d) -{ - d = d?d.replace(/[^0-9]/g, ""):""; - return tiddlerUtils.convertFromYYYYMMDDHHMMSSMMM(d.substr(0,12)); -}; - -// Create a date from a UTC YYYYMMDDHHMMSS format string -tiddlerUtils.convertFromYYYYMMDDHHMMSS = function(d) -{ - d = d?d.replace(/[^0-9]/g, ""):""; - return tiddlerUtils.convertFromYYYYMMDDHHMMSSMMM(d.substr(0,14)); -}; - -// Create a date from a UTC YYYYMMDDHHMMSSMMM format string -tiddlerUtils.convertFromYYYYMMDDHHMMSSMMM = function(d) -{ - d = d ? d.replace(/[^0-9]/g, "") : ""; - return new Date(Date.UTC(parseInt(d.substr(0,4),10), - parseInt(d.substr(4,2),10)-1, - parseInt(d.substr(6,2),10), - parseInt(d.substr(8,2)||"00",10), - parseInt(d.substr(10,2)||"00",10), - parseInt(d.substr(12,2)||"00",10), - parseInt(d.substr(14,3)||"000",10))); -}; - -// Convert & to "&", < to "<", > to ">" and " to """ -tiddlerUtils.htmlEncode = function(s) -{ - return s.replace(/&/mg,"&").replace(/</mg,"<").replace(/>/mg,">").replace(/\"/mg,"""); -}; - -// Convert "&" to &, "<" to <, ">" to > and """ to " -tiddlerUtils.htmlDecode = function(s) -{ - return s.replace(/</mg,"<").replace(/>/mg,">").replace(/"/mg,"\"").replace(/&/mg,"&"); -}; - diff --git a/js/Utils.js b/js/Utils.js new file mode 100644 index 000000000..c3c39b7ab --- /dev/null +++ b/js/Utils.js @@ -0,0 +1,74 @@ +/* +Various static utility functions. + +This file is a bit of a dumping ground; the expectation is that most of these functions will be refactored. +*/ + +var utils = exports; + +// Pad a string to a certain length with zeros +utils.zeroPad = function(n,d) +{ + var s = n.toString(); + if(s.length < d) + s = "000000000000000000000000000".substr(0,d-s.length) + s; + return s; +}; + +// Convert a date to local YYYYMMDDHHMM string format +utils.convertToLocalYYYYMMDDHHMM = function(date) +{ + return date.getFullYear() + utils.zeroPad(date.getMonth()+1,2) + utils.zeroPad(date.getDate(),2) + utils.zeroPad(date.getHours(),2) + utils.zeroPad(date.getMinutes(),2); +}; + +// Convert a date to UTC YYYYMMDDHHMM string format +utils.convertToYYYYMMDDHHMM = function(date) +{ + return date.getUTCFullYear() + utils.zeroPad(date.getUTCMonth()+1,2) + utils.zeroPad(date.getUTCDate(),2) + utils.zeroPad(date.getUTCHours(),2) + utils.zeroPad(date.getUTCMinutes(),2); +}; + +// Convert a date to UTC YYYYMMDD.HHMMSSMMM string format +utils.convertToYYYYMMDDHHMMSSMMM = function(date) +{ + return date.getUTCFullYear() + utils.zeroPad(date.getUTCMonth()+1,2) + utils.zeroPad(date.getUTCDate(),2) + "." + utils.zeroPad(date.getUTCHours(),2) + utils.zeroPad(date.getUTCMinutes(),2) + utils.zeroPad(date.getUTCSeconds(),2) + utils.zeroPad(date.getUTCMilliseconds(),3) +"0"; +}; + +// Create a date from a UTC YYYYMMDDHHMM format string +utils.convertFromYYYYMMDDHHMM = function(d) +{ + d = d?d.replace(/[^0-9]/g, ""):""; + return utils.convertFromYYYYMMDDHHMMSSMMM(d.substr(0,12)); +}; + +// Create a date from a UTC YYYYMMDDHHMMSS format string +utils.convertFromYYYYMMDDHHMMSS = function(d) +{ + d = d?d.replace(/[^0-9]/g, ""):""; + return utils.convertFromYYYYMMDDHHMMSSMMM(d.substr(0,14)); +}; + +// Create a date from a UTC YYYYMMDDHHMMSSMMM format string +utils.convertFromYYYYMMDDHHMMSSMMM = function(d) +{ + d = d ? d.replace(/[^0-9]/g, "") : ""; + return new Date(Date.UTC(parseInt(d.substr(0,4),10), + parseInt(d.substr(4,2),10)-1, + parseInt(d.substr(6,2),10), + parseInt(d.substr(8,2)||"00",10), + parseInt(d.substr(10,2)||"00",10), + parseInt(d.substr(12,2)||"00",10), + parseInt(d.substr(14,3)||"000",10))); +}; + +// Convert & to "&", < to "<", > to ">" and " to """ +utils.htmlEncode = function(s) +{ + return s.replace(/&/mg,"&").replace(/</mg,"<").replace(/>/mg,">").replace(/\"/mg,"""); +}; + +// Convert "&" to &, "<" to <, ">" to > and """ to " +utils.htmlDecode = function(s) +{ + return s.replace(/</mg,"<").replace(/>/mg,">").replace(/"/mg,"\"").replace(/&/mg,"&"); +}; +