/* | |
Copyright (c) 2006-2007, The Xooki project | |
http://xooki.sourceforge.net/ | |
Licensed under the Apache License, Version 2.0 (the "License"); | |
you may not use this file except in compliance with the License. | |
You may obtain a copy of the License at | |
http://www.apache.org/licenses/LICENSE-2.0 | |
Unless required by applicable law or agreed to in writing, software | |
distributed under the License is distributed on an "AS IS" BASIS, | |
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
See the License for the specific language governing permissions and | |
limitations under the License. | |
Some code is largely inspired by code found in the dojo toolkit, | |
see http://dojotoolkit.org/ for more information. | |
*/ | |
/* | |
This script can be either embedded in a xooki page for in browser processing, or used in batch using rhino or java 6 javascript tool: | |
jrunscript path/to/xooki.js inputFileFromXookiSite.html [path/to/dir/to/put/generated/file] | |
Be sure to be in the directory where the html input to process is when running this command. | |
*/ | |
var batchMode = (typeof arguments != 'undefined'); | |
var xooki = {}; | |
xooki.console = ""; // used for debugging purpose only, and only when the debug div is not yet created | |
xooki.config = {}; | |
xooki.c = xooki.config; | |
function t(msg) { | |
// returns the internationalized version of the message, or the message if no translation is available | |
// t stands for translate | |
// FIXME | |
// if (typeof xooki.c == "object" | |
// && typeof xooki.c.messages == "object" | |
// && typeof xooki.c.messages[msg] == "string") { | |
// msg = xooki.c.messages[msg]; | |
// } | |
var arr = []; | |
for (var i=1; i<arguments.length; i++) { | |
arr.push(arguments[i]); | |
} | |
return xooki.string.substituteParams(msg, arr); | |
} | |
function css(clss) { | |
// returns the css class or id configured, or the given class (or id) if no one is configured | |
if (typeof xooki.c.css[clss] != "undefined") { | |
return xooki.c.css[clss]; | |
} else { | |
return clss; | |
} | |
} | |
function u(path) { | |
// convert a path relative to the root to a full URL | |
// u stands for Url | |
if (batchMode) { | |
return xooki.c.relativeRoot+path; | |
} else { | |
return xooki.c.root + path; | |
} | |
} | |
function lu(path) { | |
// convert a path relative to the local root to a full URL | |
// l stands for local, u stands for Url | |
if (batchMode) { | |
return xooki.c.localRelativeRoot+path; | |
} else { | |
return xooki.c.localRoot + path; | |
} | |
} | |
function cu(urlCfgProp) { | |
// get a path from a configuration path and convert it to an URL | |
// cu stands for Configured Url | |
if (typeof xooki.c.path[urlCfgProp] == "undefined") { | |
xooki.warn(t("path not configured in xooki: '${0}'", urlCfgProp)); | |
return ""; | |
} | |
return u(xooki.c.path[urlCfgProp]); | |
} | |
function pu(id) { | |
// returns the url of the page identified by id | |
// pu stands for Page Url | |
return u(id+".html"); | |
} | |
xooki.p = function(path) { | |
// get a xooki full path from a xooki relative path | |
// p stands for path | |
return xooki.c.path.install+"/"+path; | |
} | |
xooki.u = function(path) { | |
// convert a path relative to the xooki installation dir to a full URL | |
return u(xooki.p(path)); | |
} | |
xooki.cu = function(urlCfgProp) { | |
// get a xooki path from a configuration path and convert it to an URL | |
if (typeof xooki.c.path[urlCfgProp] == "undefined") { | |
xooki.warn(t("path not configured in xooki: '${0}'", urlCfgProp)); | |
} | |
return xooki.u(xooki.c.path[urlCfgProp]); | |
} | |
xooki.util = { | |
isArray: function(it) { | |
return (it && it instanceof Array || typeof it == "array"); // Boolean | |
}, | |
mix: function(src, into, override) { | |
if (typeof override == "undefined") { | |
override = true; | |
} | |
if (typeof into == "undefined") { | |
into = {}; | |
} | |
for (var k in src) { | |
if (typeof src[k] == "object" && !xooki.util.isArray(src[k])) { | |
if (override || typeof into[k] == "object" || typeof into[k] == "undefined") { | |
if (typeof into[k] != "object") { | |
into[k] = {}; | |
} | |
xooki.util.mix(src[k], into[k], override); | |
} | |
} else if (override || typeof into[k] == "undefined") { | |
into[k] = src[k]; | |
} | |
} | |
return into; | |
}, | |
initArray: function(a) { | |
if (this.isArray(a)) return a; | |
else return {}; | |
} | |
} | |
xooki.url = { | |
newXmlHttpRequest: function() { | |
// we first try to use ActiveX, because IE7 has a direct support for XmlHttpRequest object, | |
// but which doesn't work with file urls | |
if(window.ActiveXObject) | |
{ | |
try { req = new ActiveXObject("Msxml2.XMLHTTP"); | |
} catch(e) { | |
try { req = new ActiveXObject("Microsoft.XMLHTTP"); | |
} catch(e) { req = false; } | |
} | |
} | |
else if(window.XMLHttpRequest) { | |
try { req = new XMLHttpRequest(); | |
} catch(e) { req = false; } | |
} | |
return req; | |
}, | |
loadURL: function( url, warnOnError ) { | |
req = this.newXmlHttpRequest(); | |
if(req) { | |
try { | |
req.open("GET", url, false); | |
req.send(""); | |
return req.responseText; | |
} catch (e) { | |
if (warnOnError != false) | |
xooki.error(e, t("problem while loading URL ${0}", url)); | |
else | |
xooki.debug(t("problem while loading URL ${0}: ${1}", url, e)); | |
} | |
} | |
return null; | |
}, | |
asyncLoadURL: function( url, callback, obj ) { | |
var req = this.newXmlHttpRequest(); | |
if(req) { | |
try { | |
req.open("GET", url, true); | |
req.onreadystatechange=function() { | |
if (req.readyState == 4) { | |
if (req.status == 200 || req.status == 0) { | |
callback(req.responseText, obj); | |
} | |
} | |
}; | |
req.send(""); | |
} catch (e) { | |
xooki.error(e, t("problem while loading URL ${0}", url)); | |
} | |
} | |
}, | |
include: function(script_filename) { | |
document.write('<' + 'script'); | |
document.write(' language="javascript"'); | |
document.write(' type="text/javascript"'); | |
document.write(' src="' + xooki.u(script_filename) + '">'); | |
document.write('</' + 'script' + '>'); | |
}, | |
evalURL: function( url, warnOnErrorUrl ) { | |
script = this.loadURL(url, warnOnErrorUrl); | |
if (script != null) { | |
try { | |
eval(script); | |
} catch (e) { | |
xooki.error(e, t("error while executing script from URL ${0}", url)); | |
} | |
} | |
}, | |
action: function(action) { | |
// returns the url for an action on the same page | |
loc = batchMode?'':xooki.pageURL; | |
if (loc.indexOf("#") != -1) { | |
loc = loc.substring(0, loc.indexOf("#")); | |
} | |
return loc+"?action="+action; | |
} | |
}; | |
xooki.string = { | |
substituteParams: function(/*string*/template, /* object - optional or ... */hash) { | |
// borrowed from dojo | |
// summary: | |
// Performs parameterized substitutions on a string. Throws an exception if any parameter is unmatched. | |
// | |
// description: | |
// For example, | |
// dojo.string.substituteParams("File '${0}' is not found in directory '${1}'.","foo.html","/temp"); | |
// returns | |
// "File 'foo.html' is not found in directory '/temp'." | |
// | |
// template: the original string template with ${values} to be replaced | |
// hash: name/value pairs (type object) to provide substitutions. Alternatively, substitutions may be | |
// included as an array | |
var map; | |
if (typeof hash == "object" && hash.length) { // array | |
map = {}; | |
for (var i in hash) { | |
map[i+""] = hash[i]; | |
} | |
} else { | |
map = hash; | |
} | |
return template.replace(/\$\{(\w+)\}/g, function(match, key){ | |
if(typeof(map[key]) != "undefined" && map[key] != null){ | |
return map[key]; | |
} | |
xooki.warn("Substitution not found: " + key); | |
return key; | |
}); // string | |
}, | |
processTemplate: function(/*string*/template, /* object */hash) { | |
if (typeof template.process == "function") { | |
return template.process(hash); | |
} else { | |
return this.substituteParams(template, hash); | |
} | |
}, | |
exceptionText: function(e, message) { | |
var s = e.description ? e.description : e.toString(); | |
return message ? message+":\n"+s : s; | |
}, | |
findXmlSection: function(str, element, from) { | |
return this.findSection(str, new RegExp('<'+element+'(\\s*\\w+="[\\w\\s]*")*>'), new RegExp('</'+element+'>'), from); | |
}, | |
find: function(/*string*/str, /*string or regexp*/exp, /*number, optional*/from) { | |
// find an expression (string or regexp) in a string, from an optional index | |
// the object returned has two properties: | |
// begin: the index in str of the matching find | |
// end: the index in str of the end of the matching find | |
// returns null if no match is found | |
if (typeof from != "number") { | |
from = 0; | |
} | |
if (typeof exp == "string") { | |
var result = {}; | |
result.begin = str.indexOf(exp,from); | |
if (result.begin >= 0) { | |
result.end = result.begin + exp.length; | |
return result; | |
} | |
} else { | |
var m; | |
if (from > 0) { | |
// I haven't found any other way to start from the given index | |
m = exp.exec(str.substring(from)); | |
} else { | |
m = exp.exec(str); | |
} | |
if (m != null) { | |
var result = {}; | |
result.begin = m.index + from; | |
result.end = result.begin + m[0].length; | |
return result; | |
} | |
} | |
return null; | |
}, | |
findSection: function(/*string*/str, /*string or regexp*/open, /*string or regexp*/close, /*number, optional*/from) { | |
// finds a section delimited by open and close tokens in the given string | |
// the algorithm looks for matching open and close tokens | |
// the returned object has the following properties: | |
// outerStart: the index in str where the first open token was found | |
// innerStart: the index in str just after the found open token | |
// innerEnd: the index in str where the matching close token was found | |
// outerEnd: the index in str just after the matching close token | |
// children: an array of similar objects if nested sections where found | |
// if no section is found (no open token, an open token with no matching | |
// close token, or a close token before an open token), null is returned | |
// | |
// for instance if open=='(' and close==')' then the section will find | |
// a section delimited by the first found open parenthesis and the matching | |
// close parentethis, taking into account other opening parentethis | |
// examples: | |
// findSection("a(test)b", "(", ")") == {outerStart: 1, innerStart:2, innerEnd:6, outerEnd:7, children:[]} | |
// findSection("a(te(s)(t))b", "(", ")") == {outerStart: 1, innerStart:2, innerEnd:10, outerEnd:11, | |
// children:[ | |
// {outerStart: 4, innerStart:5, innerEnd:6, outerEnd:7, children:[]}, | |
// {outerStart: 7, innerStart:8, innerEnd:9, outerEnd:10, children:[]} | |
// ]} | |
var openResult = this.find(str, open, from); | |
if (openResult == null) { | |
return null; | |
} | |
var closeResult = this.find(str, close, from); | |
if (closeResult == null || closeResult.begin < openResult.end) { | |
return null; | |
} | |
if (openResult.end <= openResult.begin || closeResult.end <= closeResult.begin) { | |
// empty match are not allowed | |
return null; | |
} | |
var children = []; | |
var child = this.findSection(str, open, close, openResult.end); | |
while (child != null) { | |
if (child.outerEnd > closeResult.begin) { | |
closeResult = this.find(str, close, child.outerEnd); | |
if (closeResult == null) { | |
// unmatched open token | |
return null; | |
} | |
} | |
children.push(child); | |
child = this.findSection(str, open, close, child.outerEnd); | |
} | |
return { | |
outerStart: openResult.begin, | |
innerStart: openResult.end, | |
innerEnd: closeResult.begin, | |
outerEnd: closeResult.end, | |
children: children | |
}; | |
}, | |
mul: function (/*string*/ s, /*int*/ n) { | |
r = ''; | |
for (var i=0; i < n; i++) { | |
r += s; | |
} | |
return r; | |
} | |
}; | |
xooki.json = { | |
evalJson: function (str) { | |
try { | |
return eval("("+str+")"); | |
} catch (e) { | |
return null; | |
} | |
}, | |
loadURL: function (url) { | |
return this.evalJson(xooki.url.loadURL(url)); | |
} | |
}; | |
// Displays an alert of an exception description with optional message | |
xooki.warn = function(e, message) { | |
xooki.display(xooki.string.exceptionText(e, message), "#eecccc"); | |
} | |
// Displays an alert of an exception description with optional message | |
xooki.error = function(e, message) { | |
xooki.display(xooki.string.exceptionText(e, message), "#ffdddd"); | |
} | |
xooki.info = function(message) { | |
xooki.display(message, "#ddddff"); | |
} | |
xooki.display = function(message, background) { | |
var messages = document.getElementById('xooki-messages'); | |
if (messages) { | |
messages.innerHTML = '<table width="100%" border="0"><tr><td align="center">'+message+'</td></tr></table>'; | |
messages.style.background = background; | |
messages.style.display = "inline"; | |
} else { | |
alert(message); | |
} | |
} | |
xooki.debug = function(message) { | |
var console = typeof document == 'undefined' ? false : document.getElementById('xooki-console'); | |
if (console) { | |
console.value += message + "\n"; | |
} else { | |
xooki.console += message + "\n"; | |
} | |
} | |
xooki.debugShowDetail = function (message) { | |
var detail = typeof document == 'undefined' ? false : document.getElementById('xooki-debug-detail'); | |
if (detail) { | |
detail.value=message; | |
} else { | |
alert(message); | |
} | |
} | |
xooki.html = { | |
hide: function(divid) { | |
document.getElementById(divid).style.display = 'none'; | |
}, | |
show: function (divid) { | |
document.getElementById(divid).style.display = ''; | |
}, | |
pageLink: function(page) { | |
if (page.isAbstract) { | |
return page.title; | |
} else { | |
return '<a href="'+(page.url != null ? page.url : pu(page.id))+'" '+(page.id == xooki.page.id?'class="current"':'')+'>'+page.title+'</a>'; | |
} | |
}, | |
// insert the given header in the html head | |
// can be used only when the browser is still in the head ! | |
addHeader: function(/* string */ head) { | |
document.write(head); | |
}, | |
setBody: function( /* string */ body) { | |
document.body.innerHTML = body; | |
} | |
}; | |
xooki.component = { | |
childrenList: function () { | |
if (xooki.page.children.length > 0) { | |
childrenList = '<ul class="'+css('childrenList')+'">'; | |
for (var i in xooki.page.children) { | |
childrenList+='<li>'+xooki.html.pageLink(xooki.page.children[i])+'</li>'; | |
} | |
childrenList += "</ul>"; | |
return childrenList; | |
} else { | |
return ""; | |
} | |
}, | |
menu: function () { | |
var menu = '<ul id="'+css("treemenu")+'" class="treeview">'; | |
menu += (function (page) { | |
var menu = ''; | |
for (var i in page.children) { | |
if (typeof page.children[i] == 'object') { | |
smenu = arguments.callee(page.children[i]); | |
if (smenu != '') { | |
menu += '<li id="xooki-'+page.children[i].id+'" class="submenu">'+xooki.html.pageLink(page.children[i]); | |
if (smenu.indexOf('id="xooki-'+xooki.page.id+'"') != -1 | |
|| page.children[i].id == xooki.page.id) { | |
// either a descendant or the node processed is the current page node | |
// we specify that the menu must be opened by default | |
menu += '<ul class="open"'; | |
menu += '>'+smenu+'</ul>'; | |
} else { | |
menu += '<ul class="closed"'; | |
menu += '>'+smenu+'</ul>'; | |
} | |
} else { | |
menu += '<li id="xooki-'+page.children[i].id+'">'+xooki.html.pageLink(page.children[i]); | |
} | |
menu += '</li>'; | |
} | |
} | |
return menu; | |
})(xooki.toc); | |
menu += '</ul>'; | |
return menu; | |
}, | |
messages: function () { | |
return '<div id="xooki-messages" onclick="xooki.html.hide(\'xooki-messages\')" style="zIndex:999;display:none;position:absolute;top:30px;padding:10px;border-style:solid;background:#eeeeee;"></div>'; | |
}, | |
debugPanel: function () { | |
return '<div id="xooki-debug" style="display:none;margin-top:15px;padding:10px;border-style:solid;background:#eeeeee;"><strong>Xooki Console</strong><br/><textarea cols="100" rows="15" id="xooki-console">'+xooki.console+'</textarea><hr/><a href="javascript:xooki.debugShowDetail(document.getElementById(\'xooki-body\').innerHTML)">content</a> <a href="javascript:xooki.debugShowDetail(xooki.c.body)">xooki body</a> <a href="javascript:xooki.debugShowDetail(document.body.innerHTML)">whole body</a> <a href="javascript:xooki.action.evaluate()">evaluate</a><br/><textarea cols="100" rows="15" id="xooki-debug-detail"></textarea></div>'; | |
}, | |
printerFriendlyLocation: function () { | |
return xooki.url.action("print"); | |
}, | |
printerFriendlyLink: function () { | |
return '<a href="'+this.printerFriendlyLocation()+'">'+t('Printer Friendly')+'</a>'; | |
}, | |
breadCrumb: function () { | |
var breadCrumb = '<span class="breadCrumb">'; | |
breadCrumb += (function (page) { | |
var breadCrumb = xooki.html.pageLink(page); | |
if (page.meta.level >= 1) { | |
breadCrumb = arguments.callee(page.meta.parent) + " > " + breadCrumb; | |
} | |
return breadCrumb; | |
})(xooki.page); | |
breadCrumb += '</span>'; | |
return breadCrumb; | |
} | |
}; | |
xooki.render = {}; | |
xooki.render.printerFriendlyAsyncLoader = function(source, arr) { | |
var root = arr[0]; | |
var page = arr[1]; | |
if (source == null) { | |
return; | |
} | |
var level = page.meta.level - root.meta.level + 1; | |
// compute printer friendly block | |
var beginIndex = source.indexOf('<textarea id="xooki-source">'); | |
beginIndex += '<textarea id="xooki-source">'.length; | |
var endIndex = source.lastIndexOf('</textarea>'); | |
source = source.substring(beginIndex, endIndex); | |
var printerFriendly = "<h"+level+">"+page.title+"</h"+level+">"; | |
printerFriendly += xooki.input.format.main(source, level) + "<hr/>"; | |
// inject block in page | |
var pf = document.getElementById('xooki-printerFriendly'); | |
pf.innerHTML += printerFriendly; | |
// continue recursive loading | |
var nextPage = xooki.toc.getNextPage(page, root); | |
if (nextPage != null) { | |
xooki.url.asyncLoadURL(pu(nextPage.id), xooki.render.printerFriendlyAsyncLoader, [root, nextPage]); | |
} | |
}; | |
xooki.render.printerFriendlyAsync = function() { | |
xooki.c.body = xooki.c.messages | |
+ "<div id='xooki-printerFriendly'></div>" // div where printer friendly content will be put | |
+ xooki.c.debugPanel; | |
document.body.innerHTML = xooki.string.processTemplate(xooki.template.body, xooki.c); | |
// start async loading of content | |
xooki.url.asyncLoadURL(pu(xooki.page.id), xooki.render.printerFriendlyAsyncLoader, [xooki.page, xooki.page]); | |
}; | |
xooki.render.printerFriendlySync = function() { | |
xooki.c.body = xooki.c.messages | |
+ (function (page, level) { | |
var source = xooki.url.loadURL(pu(page.id)); | |
if (source == null) { | |
return ""; | |
} | |
var beginIndex = source.indexOf('<textarea id="xooki-source">'); | |
beginIndex += '<textarea id="xooki-source">'.length; | |
var endIndex = source.lastIndexOf('</textarea>'); | |
source = source.substring(beginIndex, endIndex); | |
var printerFriendly = "<div class='toc-title toc-title-"+level+"'>"+page.title+"</div>"; | |
printerFriendly += xooki.input.format.main(source, level); | |
for (var i=0; i <page.children.length; i++) { | |
printerFriendly += "<hr/>"; | |
printerFriendly += arguments.callee(page.children[i], level+1); | |
} | |
return printerFriendly; | |
})(xooki.page, 1); | |
xooki.html.setBody(xooki.string.processTemplate(xooki.template.body, xooki.c)); | |
}; | |
xooki.render.printerFriendly = function() { | |
for (var k in xooki.component) { | |
xooki.c[k] = xooki.component[k](); | |
} | |
if (batchMode) { | |
xooki.render.printerFriendlySync(); | |
} else { | |
xooki.render.printerFriendlyAsync(); | |
} | |
}; | |
xooki.render.page = function() { | |
// realize all components available | |
for (var k in xooki.component) { | |
xooki.c[k] = xooki.component[k](); | |
} | |
xooki.input.source(); | |
if (xooki.c.allowEdit) { | |
xooki.c.body = xooki.c.messages | |
+ xooki.c.toolbar | |
+ '<div id="xooki-content">' | |
+ '<div id="xooki-body"></div>' | |
+ '</div>' | |
+ xooki.c.editZone | |
+ xooki.c.debugPanel; | |
} else { | |
xooki.c.body = xooki.c.messages | |
+ '<div id="xooki-content">' | |
+ '<div id="xooki-body"></div>' | |
+ '</div>' | |
+ xooki.c.debugPanel; | |
} | |
xooki.html.setBody(xooki.string.processTemplate(xooki.template.body, xooki.c)); | |
xooki.input.applyChanges(); | |
}; | |
xooki.render.main = function() { | |
if (xooki.c.action == "print") { | |
// render the printer friendly version of the page | |
this.printerFriendly(); | |
} else { | |
// render the page normally | |
this.page(); | |
} | |
}; | |
xooki.input = { | |
source: function() { | |
if (typeof document != 'undefined' && document.getElementById('xooki-source') != null) { | |
this._source = document.getElementById('xooki-source').value; | |
} | |
return this._source; | |
}, | |
processed: function() { | |
return this.format.main(this.source()); | |
}, | |
format: { | |
getInputFilters: function (inputFormat) { | |
return xooki.c[inputFormat+"InputFormat"]; | |
}, | |
define: function (inputFormat, filters) { | |
// define a new inputFormat | |
// inputFormat: the new input format name | |
// filters: an array of input filter names | |
xooki.c[inputFormat+"InputFormat"] = filters; | |
}, | |
main: function(source, level) { | |
// formats an input source | |
if (xooki.c.inputFormat && typeof this.getInputFilters(xooki.c.inputFormat) != "undefined") { | |
format = xooki.c.inputFormat; | |
} else { | |
format = xooki.c.defaultInputFormat; | |
} | |
filters = this.getInputFilters(format); | |
for (var i in filters) { | |
if (typeof filters[i] == 'string') { | |
xooki.debug('processing filter '+filters[i]); | |
f = xooki.input.filters[filters[i]]; | |
if (typeof f == "function") { | |
try { | |
source = f(source, level); // process filter | |
} catch (e) { | |
xooki.error(e, t("error occurred while processing filter ${0}", filters[i])); | |
} | |
} else { | |
xooki.error(t("unknown filter ${0} used in input format ${1}", filters[i], format)); | |
} | |
} | |
} | |
return source; | |
} | |
}, | |
filters: { | |
url: function (input) { | |
// handle urls | |
return input.replace(new RegExp("(?:file|http|https|mailto|ftp):[^\\s'\"]+(?:/|\\b)", "g"), function (str, offset, s) { | |
var before = s.substring(0,offset); | |
if (before.match(/(href|src)="$/)) { | |
return str; | |
} else { | |
return '<a href="'+str+'">'+str+'</a>'; | |
} | |
}); | |
}, | |
shortcuts: function (input) { | |
// handle shortcut links like this: | |
// [[svn:build.xml]] => <a href="https://xooki.svn.sourceforge.net/svnroot/xooki/trunk/build.xml">build.xml</a> | |
// [[svn:test/example.js a good example]] => <a href="https://xooki.svn.sourceforge.net/svnroot/xooki/trunk/test/example.js">a good example</a> | |
// needs to be configured in xooki config like this | |
// xooki.c.shortcuts.<any shortcut>.url = base url of the shortcut. | |
// ex: xooki.c.shortcuts.svn.url = https://xooki.svn.sourceforge.net/svnroot/xooki/trunk/ | |
return input.replace(new RegExp("\\[\\[([^:\n]+):([^\\]\n]+)\\]\\]", "g"), function (str, prefix, code, offset, s) { | |
if (typeof xooki.c.shortcuts == "undefined" || typeof xooki.c.shortcuts[prefix] == "undefined") { | |
xooki.debug('unknown shortcut '+prefix); | |
return str; | |
} | |
var index = code.indexOf(' '); | |
var path = index>0?code.substring(0,index):code; | |
var title = index>0?code.substring(index+1):path; | |
var pre = typeof xooki.c.shortcuts[prefix].pre == "undefined"?'':xooki.c.shortcuts[prefix].pre; | |
var post = typeof xooki.c.shortcuts[prefix].post == "undefined"?'':xooki.c.shortcuts[prefix].post; | |
return '<a href="'+pre+path+post+'">'+title+'</a>'; | |
}); | |
}, | |
xookiLinks: function (input) { | |
// handle xooki links like this: | |
// [[page/id]] | |
// [[page/id My Title]] | |
return input.replace(new RegExp("\\[\\[([^\\]]+)\\]\\]", "g"), function (str, code, offset, s) { | |
var index = code.indexOf(' '); | |
var id = (index>0?code.substring(0,index):code); | |
var title; | |
var url; | |
var invalid = false; | |
if (typeof xooki.toc.pages[xooki.toc.importRoot + id] != "undefined") { | |
title = xooki.toc.pages[xooki.toc.importRoot + id].title; | |
url = pu(xooki.toc.importRoot + id); | |
} else if (xooki.toc.importRoot.length > 0 && typeof xooki.toc.pages[id] != "undefined") { | |
title = xooki.toc.pages[id].title; | |
url = pu(id); | |
} else { | |
invalid = true; | |
title = code; | |
url = u(id); | |
} | |
if (index>0) { | |
title = code.substring(index+1); | |
} | |
if (invalid) { | |
if (batchMode) { | |
// do not output invalid links as links in batch mode | |
return title; | |
} else { | |
return title+'<a href="'+url+'">?</a>'; | |
} | |
} else { | |
return '<a href="'+url+'">'+title+'</a>'; | |
} | |
}); | |
}, | |
wikiMarkup: function (input) { | |
// handle bold | |
input = input.replace(new RegExp("\\*([^\n]+)\\*", "g"), "<b>$1</b>"); | |
// handle italic | |
input = input.replace(new RegExp("\\_([^\n]+)\\_", "g"), "<em>$1</em>"); | |
return input; | |
}, | |
jira: function (input) { | |
// auto replace jira issue ids (like IVY-12) by a link to the issue | |
// needs to be configured in xooki config like this | |
// xooki.c.jira.ids = an array of jira projects ids (ex: ["IVY", "ANT"]) | |
// xooki.c.jira.url = the url of the jira server (ex: "http://issues.apache.org/jira") | |
if (typeof xooki.c.jira != "object") { | |
return input; | |
} | |
input = input.replace(new RegExp("(("+xooki.c.jira.ids.join("|")+")-\\d+)([^\"\\d])", "g"), '<a href="'+xooki.c.jira.url+'/browse/$1">$1</a>$3'); | |
return input; | |
}, | |
code: function (input) { | |
codeSection = xooki.string.findXmlSection(input, "code"); | |
from = 0; | |
while (codeSection != null) { | |
processedSection = "<pre>" | |
+ input.substring(codeSection.innerStart, codeSection.innerEnd).replace(/</g, "<").replace(/>/g, ">") // .replace(/\n/g, "<br/>") | |
+ "</pre>"; | |
input = input.substring(0, codeSection.outerStart) | |
+ processedSection | |
+ input.substring(codeSection.outerEnd); | |
from = codeSection.outerStart + processedSection.length; | |
codeSection = xooki.string.findXmlSection(input, "code", from); | |
} | |
return input; | |
}, | |
lineBreak: function (input) { | |
return input.replace(new RegExp("\r?\n", "g"), function (str, offset, s) { | |
var before = s.substring(0,offset); | |
var after = s.substring(offset+str.length); | |
if (after.match(/^<\/?(ul|table|li|pre|div)(\s*\w+="[^"]+")*\s*>/i) || (before.match(/<\/?\w+(\s*\w+="[^"]+")*\s*\/?>\s*$/i) && !before.match(/<\/?(a|b|strong|em|i|big|br class="xooki-br")(\s*\w+="[^"]+")*\s*\/?>\s*$/i))) { | |
return '\n'; | |
} else { | |
return '<br class="xooki-br"/>'; // the class is not really necessary but allow to distinguish generated br from input one | |
} | |
}); | |
}, | |
includes: function (input) { | |
//[<url>] replaced by the content of the url | |
result = ""; | |
lastStart = 0; | |
nextPos = input.indexOf("[<" , lastStart); | |
while( nextPos > 0 ) { | |
result = result + input.slice(lastStart,nextPos); | |
lastStart = nextPos; | |
nextPos = input.indexOf(">]" , lastStart); | |
result = result + xooki.url.loadURL(lu(input.slice(lastStart+2,nextPos))); | |
lastStart = nextPos + 2; | |
nextPos = input.indexOf("[<" , lastStart); | |
} | |
return result + input.slice(lastStart); | |
}, | |
printFormatImgFix: function (input, level) { | |
if (level == undefined || level < 3) { | |
return input; | |
} | |
return input.replace(new RegExp('<img +src *= *\\"([^\\"]*)\\"', "g"), function (str, img, offset, s) { | |
l = level; | |
while (l > 2) { | |
if (img.indexOf("../") >= 0) { | |
img = img.substring(3); | |
} else { | |
break; | |
} | |
l--; | |
} | |
return '<img src="'+img+'"'; | |
}); | |
} | |
}, | |
applyChanges: function() { | |
document.getElementById('xooki-body').innerHTML = xooki.input.processed(); | |
} | |
}; | |
xooki.postProcess = function() { | |
xooki.render.main(); | |
window.onkeypress = keyCtrl; | |
}; | |
if (typeof xooki.io == "undefined") { | |
xooki.io = {}; | |
} | |
xooki.action = {} | |
xooki.action.toggleDebug = function() { | |
if (xooki.c.debug) { | |
if (document.getElementById('xooki-debug').style.display == 'none') { | |
xooki.html.show('xooki-debug'); | |
} else { | |
xooki.html.hide('xooki-debug'); | |
} | |
} | |
} | |
xooki.action.evaluate = function () { | |
var exp = prompt("Please enter javascript expression to evaluate"); | |
xooki.debugShowDetail(eval(exp)); | |
} | |
// TODO, review use registration | |
function keyCtrl(evt) { | |
var code = xooki.c.browser.NS ? evt.which : event.keyCode; | |
var ctrl = xooki.c.browser.NS ? evt.ctrlKey : event.ctrlKey; | |
var key = String.fromCharCode(code); | |
if (xooki.c.debug && ctrl && "d" == key) { | |
xooki.action.toggleDebug(); | |
return false; | |
} | |
if (xooki.c.allowEdit && ctrl && "s" == key) { | |
xooki.action.saveChanges(); | |
return false; | |
} | |
if (xooki.c.allowEdit && ctrl && "e" == key) { | |
xooki.action.toggleEdit(); | |
return false; | |
} | |
} | |
// xooki engine init function | |
xooki.init = function() { | |
//////////////////////////////////////////////////////////////////////////// | |
////////////////// config init | |
//////////////////////////////////////////////////////////////////////////// | |
initConfigProperty = function(prop, value, defaultValue) { | |
if (typeof this[prop] == "undefined") { | |
if (typeof value == "undefined") { | |
this[prop] = defaultValue; | |
} else if (typeof value == "function") { | |
this[prop] = value(); | |
} else { | |
this[prop] = value; | |
} | |
} | |
}; | |
if (typeof xookiConfig != "undefined") {xooki.util.mix(xookiConfig, xooki.config);} | |
xooki.c.initProperty = initConfigProperty; | |
xooki.c.computeRoot = function() { | |
root = xooki.pageURL; | |
// remove trailing parts of the URL to go the root depending on level | |
for (var i=0; i < xooki.c.level + 1; i++) { | |
root = root.substring(0, root.lastIndexOf('/')); | |
} | |
return root + '/'; | |
}; | |
xooki.c.computeRelativeRoot = function() { | |
return xooki.string.mul('../', xooki.c.level); | |
}; | |
xooki.c.setImportLevel = function(level) { | |
// compute roots with old level value, for paths relative to the local (non imported) root | |
this.localRoot = this.computeRoot(); | |
this.localRelativeRoot = this.computeRelativeRoot(); | |
// change level and update roots | |
this.level+=level; | |
this.root = this.computeRoot(); | |
this.relativeRoot = this.computeRelativeRoot(); | |
}; | |
xooki.c.initProperty("level", 0); | |
xooki.c.initProperty("root", xooki.c.computeRoot); | |
xooki.c.initProperty("relativeRoot", xooki.c.computeRelativeRoot); | |
xooki.c.initProperty("localRoot", xooki.c.root); | |
xooki.c.initProperty("localRelativeRoot", xooki.c.relativeRoot); | |
globalConfig = xooki.url.loadURL(u("config.json"), false); | |
if (globalConfig != null && globalConfig.length != 0) { | |
globalConfig = eval('('+globalConfig+')'); | |
xooki.util.mix(globalConfig, xooki.c, false); | |
} | |
xooki.url.evalURL(u("config.js"), false); | |
xooki.url.evalURL(u("config.extra.js"), false); | |
xooki.c.initProperty("defaultInputFormat", "xooki"); | |
xooki.c.initProperty("xookiInputFormat", ["xooki"]); | |
xooki.c.initProperty("allowEdit", !batchMode && xooki.pageURL.substr(0,5) == "file:"); | |
xooki.input.format.define("xooki", ["code", "shortcuts", "url", "xookiLinks", "jira", "lineBreak" , "includes", "printFormatImgFix"]); | |
xooki.c.path = (typeof xooki.c.path != "undefined")?xooki.c.path:{}; | |
xooki.c.path.initProperty = initConfigProperty; | |
xooki.c.path.initProperty("install", "xooki"); | |
xooki.c.path.initProperty("messages", xooki.p("messages.json")); | |
xooki.c.path.initProperty("template", "template.html"); | |
xooki.c.path.initProperty("printTemplate", "printTemplate.html"); | |
xooki.c.path.initProperty("toc", "toc.json"); | |
xooki.c.path.initProperty("blankPageTpl", xooki.p("blankPageTpl.html")); | |
xooki.c.css = (typeof xooki.c.css != "undefined")?xooki.c.css:{}; | |
xooki.c.messages = xooki.json.loadURL(cu("messages")); | |
if (!batchMode) { | |
xooki.c.browser = { | |
NS: (window.Event) ? 1 : 0 | |
}; | |
// action | |
if (! xooki.c.action) xooki.c.action = 'render'; | |
// TODO: better handle action extraction | |
xooki.c.action = window.location.search == '?action=print'?'print':xooki.c.action; | |
} | |
var match = new RegExp("^.*\\/((?:.*\\/){"+xooki.c.level+"}[^\\/]*)(?:\\.\\w+)(?:\\?.+)?$", "g").exec(xooki.pageURL); | |
if (match == null || match[1] == '') { | |
xooki.c.curPageId = "index"; | |
} else { | |
xooki.c.curPageId = match[1]; | |
} | |
//////////////////////////////////////////////////////////////////////////// | |
////////////////// TOC init | |
//////////////////////////////////////////////////////////////////////////// | |
xooki.toc = xooki.json.loadURL(cu("toc")); | |
xooki.toc.url = cu("toc"); | |
xooki.toc.pages = {}; // to store a by id map of pages objects | |
xooki.toc.importRoot = ''; | |
xooki.toc.actualRoot = xooki.toc; // this is the real root of the TOC, in case of a TOC imported, it will point to the root of the TOC on which import has been performed | |
// populate meta data | |
(function(page, parent, index, level, prefix) { | |
if (prefix.length > 0) { | |
page.meta = xooki.util.mix({id: page.id}, page.meta); | |
page.id = prefix + page.id; | |
} | |
xooki.toc.pages[page.id] = page; | |
page.meta = xooki.util.mix({ | |
index: index, | |
level: level, | |
getSerializeValue: function(o, k) { | |
if (k == 'id' && typeof this.id != 'undefined') { | |
return this.id; | |
} else { | |
return o[k]; | |
} | |
} | |
}, page.meta); | |
page.meta.parent = parent; | |
if (typeof page.importNode != 'undefined' && !page.isImported) { | |
// this node requires to import another xooki TOC | |
importedTocUrl = u(page.importRoot + '/toc.json'); | |
importedToc = xooki.json.loadURL(importedTocUrl); | |
// look for the imported node in the importedTOC and import it in main TOC | |
(function(page, parent, index, level, prefix, importedToc, node, id, populateFunction) { | |
if (node.id == id) { | |
xooki.util.mix(node, page, false); | |
page.id = id; | |
page.isImported = true; | |
page.meta = xooki.util.mix({ | |
isTransient: function(k) { | |
// only title, importRoot and importNode should be serialized | |
return k != 'title' && k != 'importRoot' && k != 'importNode'; | |
} | |
}, page.meta); | |
if (xooki.c.curPageId.indexOf(prefix) == 0) { | |
// the current page is in this imported TOC | |
xooki.toc.actualRoot = importedToc; | |
xooki.toc.url = u(page.importRoot + '/toc.json'); | |
xooki.toc.importRoot = prefix; | |
} | |
populateFunction(page, parent, index, level, prefix); | |
return true; | |
} else if (typeof node.children != 'undefined') { | |
for (var i=0; i<node.children.length; i++) { | |
if (arguments.callee(page, parent, index, level, prefix, importedToc, node.children[i], id, populateFunction)) { | |
return true; | |
} | |
} | |
} | |
return false; | |
})(page, parent, index, level, page.importRoot+'/', importedToc, importedToc, page.importNode, arguments.callee); | |
} | |
if (typeof page.children == 'undefined') { | |
page.children = []; | |
} else { | |
for (var i=0; i<page.children.length; i++) { | |
arguments.callee(page.children[i], page, i, level+1, prefix); // recurse | |
} | |
} | |
})(xooki.toc, null, 0, -1, ''); | |
xooki.toc.getNextPage = function(page, root) { | |
if (page.children.length > 0) { | |
return page.children[0]; | |
} else if (page.meta.parent != null) { | |
var cur = page; | |
var next = xooki.toc.getNextSibling(cur); | |
while (next == null) { | |
cur = cur.meta.parent; | |
if (cur == null || cur == root) { | |
return null; | |
} | |
next = xooki.toc.getNextSibling(cur); | |
} | |
return next; | |
} else { | |
return null; | |
} | |
}; | |
xooki.toc.getNextSibling = function(page) { | |
if (page.meta.parent == null) { | |
return null; | |
} | |
if (page.meta.parent.children.length > page.meta.index) { | |
return page.meta.parent.children[page.meta.index+1]; | |
} else { | |
return null; | |
} | |
}; | |
xooki.page = xooki.toc.pages[xooki.c.curPageId]; | |
if (xooki.page == null) { | |
xooki.warn(t('page id not found in TOC: ${0}',xooki.c.curPageId)); | |
xooki.page = xooki.toc.children[0]; | |
} | |
if (typeof xooki.config.title == 'undefined') { | |
xooki.config.title = xooki.page.title; | |
} | |
xooki.config.page = xooki.page; | |
//////////////////////////////////////////////////////////////////////////// | |
////////////////// main template loading + head output | |
//////////////////////////////////////////////////////////////////////////// | |
xooki.template = {}; | |
xooki.template.source = xooki.url.loadURL(xooki.c.action == "print"?cu("printTemplate"):cu("template")); | |
if(xooki.template.source != null) { | |
xooki.template.head = xooki.template.source.match(/<head>([^�]*)<\/head>/im)[1]; | |
var root = batchMode?xooki.c.relativeRoot:xooki.c.root; | |
var head = xooki.string.processTemplate(xooki.template.head, xooki.config); | |
head = head.replace(/href="([^\\$:"]+)"/g, 'href="'+root+'$1"'); | |
head = head.replace(/src="([^\\$:"]+)"/g, 'src="'+root+'$1"'); | |
xooki.html.addHeader(head); | |
var body = xooki.template.source.match(/<body>([^�]*)<\/body>/im)[1]; | |
body = body.replace(/href="([^\\$:"]+)"/g, 'href="'+root+'$1"'); | |
xooki.template.body = body.replace(/src="([^\\$:"]+)"/g, 'src="'+root+'$1"'); | |
} | |
//////////////////////////////////////////////////////////////////////////// | |
////////////////// includes | |
//////////////////////////////////////////////////////////////////////////// | |
if (batchMode) { | |
xooki.html.addHeader('<script language="javascript" type="text/javascript">xooki = {u: function(url) {return "'+xooki.c.relativeRoot+'xooki/"+url;}};</script>'); | |
} | |
if (xooki.c.allowEdit) { | |
xooki.url.include("xookiEdit.js"); | |
} | |
for (var k in xooki.c) { | |
if (typeof xooki.c[k] == "string" || typeof xooki.c[k] == "number" || typeof xooki.c[k] == "boolean") { | |
xooki.debug(k+": "+xooki.c[k]); | |
} | |
} | |
}; | |
if (batchMode) { | |
importPackage(java.io); | |
xooki.io.loadFile = function( url, warnOnError ) { | |
var str = ''; | |
try { | |
var r = new BufferedReader(new FileReader(url)); | |
line = r.readLine(); | |
while (line != null) { | |
str += line + '\n'; | |
line = r.readLine(); | |
} | |
r.close(); | |
} catch (e) { | |
if (warnOnError) { | |
throw e; | |
} else { | |
xooki.debug("error occurred while loading "+url); | |
} | |
} | |
return str; | |
}; | |
xooki.io.saveFile = function (fileUrl, content) { | |
p = new File(fileUrl).getParentFile(); | |
if (p != null) { | |
p.mkdirs(); | |
} | |
pw = new PrintWriter(new FileWriter(fileUrl)); | |
pw.write(content); | |
pw.close(); | |
return true; | |
} | |
xooki.url.loadURL = function( url, warnOnError ) { | |
return xooki.io.loadFile(url, warnOnError ); | |
}; | |
xooki.html.addHeader = function (head) { | |
xooki.pageContent = xooki.pageContent.replace(/<\/head>/, head+'\n</head>'); | |
}; | |
xooki.html.setBody = function(body) { | |
xooki.pageContent = xooki.pageContent.replace(/<body>(.|[^,])*<\/body>/gm, '<body>'+body+'</body>'); | |
} | |
xooki.url.include = function(script_filename) { | |
xooki.html.addHeader('<script language="javascript" type="text/javascript" src="'+xooki.c.relativeRoot+'xooki/'+script_filename+'"></script>'); | |
}; | |
xooki.input.source = function() { | |
if (typeof this._source == 'undefined') { | |
xooki.debug('searching source'); | |
var beg = xooki.pageContent.indexOf('<textarea id="xooki-source">'); | |
beg += '<textarea id="xooki-source">'.length; | |
var end = xooki.pageContent.lastIndexOf('</textarea>'); | |
this._source = xooki.pageContent.substring(beg, end); | |
xooki.debug('source found'); | |
} | |
return this._source; | |
} | |
xooki.render.page = function() { | |
// realize all components available | |
xooki.debug('realizing components'); | |
for (var k in xooki.component) { | |
xooki.c[k] = xooki.component[k](); | |
} | |
xooki.debug('processing body'); | |
xooki.c.body = xooki.input.processed(); | |
xooki.debug('updating body'); | |
var body = xooki.string.processTemplate(xooki.template.body, xooki.c); | |
xooki.html.setBody(body); | |
}; | |
xooki.display = function(message, background) { | |
print(message); | |
}; | |
xooki.debug = function (message) { | |
if (xooki.c.debug) { | |
print(message+'\n'); | |
} | |
}; | |
var i=0; | |
if (arguments.length > i && arguments[0] == '-debug') { | |
xooki.c.debug = true; | |
i++; | |
} else { | |
xooki.c.debug = false; | |
} | |
var file = 'index.html'; | |
if (arguments.length > i) { | |
file = arguments[i]; | |
i++; | |
} | |
var generateTo = "gen"; | |
if (arguments.length > i) { | |
generateTo = arguments[i]; | |
i++; | |
} | |
xooki.c.action = 'render'; | |
if (arguments.length > i) { | |
xooki.c.action = arguments[i]; | |
i++; | |
} | |
xooki.pageURL = new File(file).toURL().toExternalForm(); | |
print('processing '+new File(file).getAbsolutePath()+'...\n'); | |
xooki.pageContent = xooki.io.loadFile(file); | |
if (xooki.pageContent.match(/<textarea\s+id="xooki\-source">/) == null) { | |
print(file + ' is not a valid xooki source. ignored.'); | |
} else { | |
var m = /var\s+xookiConfig\s+=\s+{.*};/.exec(xooki.pageContent); | |
if (typeof m != 'undefined' && m != null) { | |
eval(m[0]); | |
} | |
xooki.init(); | |
xooki.pageContent = xooki.pageContent.replace(/<script type="text\/javascript" src="[^"]*xooki.js"><\/script>/g, ''); | |
xooki.render.main(); | |
var dest = generateTo.endsWith(".html") ? generateTo : generateTo+'/'+file; | |
print('generating to '+dest); | |
xooki.io.saveFile(dest, xooki.pageContent); | |
} | |
} else { | |
xooki.pageURL = window.location.toString(); | |
xooki.init(); | |
} | |