From 7cf1a50fffe4f5ab96e4f08417a13fa14f4c174f Mon Sep 17 00:00:00 2001 From: Erlend Halvorsen Date: Wed, 6 Jul 2011 14:57:07 +0200 Subject: [PATCH] Added support for exporting html --- node/db.js | 3 +- node/exporters/exporthtml.js | 416 +++++++++++++++++++++++++++++++++++ node/server.js | 10 + 3 files changed, 427 insertions(+), 2 deletions(-) create mode 100644 node/exporters/exporthtml.js diff --git a/node/db.js b/node/db.js index c586e1c4c..67d115e64 100644 --- a/node/db.js +++ b/node/db.js @@ -28,7 +28,7 @@ var db = new ueberDB.database(settings.dbType, settings.dbSettings); /** * The UeberDB Object that provides the database functions */ -exports.db = null; +exports.db = db; /** * Initalizes the database with the settings provided by the settings module @@ -49,7 +49,6 @@ exports.init = function(callback) //everything ok else { - exports.db = db; callback(null); } }); diff --git a/node/exporters/exporthtml.js b/node/exporters/exporthtml.js new file mode 100644 index 000000000..7b412d147 --- /dev/null +++ b/node/exporters/exporthtml.js @@ -0,0 +1,416 @@ +/** + * Copyright 2009 Google Inc. + * + * 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. + */ +var async = require("async"); +var Changeset = require("../Changeset"); + + +function getPadPlainText(pad, revNum) { + var atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : + pad.atext()); + var textLines = atext.text.slice(0,-1).split('\n'); + var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text); + var apool = pad.pool(); + + var pieces = []; + for(var i=0;i= 0) { + anumMap[propTrueNum] = i; + } + }); + + function getLineHTML(text, attribs) { + var propVals = [false, false, false]; + var ENTER = 1; + var STAY = 2; + var LEAVE = 0; + + // Use order of tags (b/i/u) as order of nesting, for simplicity + // and decent nesting. For example, + // Just bold Bold and italics Just italics + // becomes + // Just bold Bold and italics Just italics + + var taker = Changeset.stringIterator(text); + var assem = Changeset.stringAssembler(); + + function emitOpenTag(i) { + assem.append('<'); + assem.append(tags[i]); + assem.append('>'); + } + function emitCloseTag(i) { + assem.append(''); + } + + var urls = _findURLs(text); + + var idx = 0; + function processNextChars(numChars) { + if (numChars <= 0) { + return; + } + + var iter = Changeset.opIterator(Changeset.subattribution(attribs, + idx, idx+numChars)); + idx += numChars; + + while (iter.hasNext()) { + var o = iter.next(); + var propChanged = false; + Changeset.eachAttribNumber(o.attribs, function(a) { + if (a in anumMap) { + var i = anumMap[a]; // i = 0 => bold, etc. + if (! propVals[i]) { + propVals[i] = ENTER; + propChanged = true; + } + else { + propVals[i] = STAY; + } + } + }); + for(var i=0;i=0; i--) { + if (propVals[i] === LEAVE) { + emitCloseTag(i); + propVals[i] = false; + } + else if (propVals[i] === STAY) { + emitCloseTag(i); + } + } + for(var i=0; i=0; i--) { + if (propVals[i]) { + emitCloseTag(i); + propVals[i] = false; + } + } + } // end processNextChars + + if (urls) { + urls.forEach(function(urlData) { + var startIndex = urlData[0]; + var url = urlData[1]; + var urlLength = url.length; + processNextChars(startIndex - idx); + assem.append(''); + processNextChars(urlLength); + assem.append(''); + }); + } + processNextChars(text.length - idx); + + return _processSpaces(assem.toString()); + } // end getLineHTML + + var pieces = []; + + // Need to deal with constraints imposed on HTML lists; can + // only gain one level of nesting at once, can't change type + // mid-list, etc. + // People might use weird indenting, e.g. skip a level, + // so we want to do something reasonable there. We also + // want to deal gracefully with blank lines. + var lists = []; // e.g. [[1,'bullet'], [3,'bullet'], ...] + for(var i=0;i 0) { + // do list stuff + var whichList = -1; // index into lists or -1 + if (line.listLevel) { + whichList = lists.length; + for(var j=lists.length-1;j>=0;j--) { + if (line.listLevel <= lists[j][0]) { + whichList = j; + } + } + } + + if (whichList >= lists.length) { + lists.push([line.listLevel, line.listTypeName]); + pieces.push('
  • ', lineContent || '
    '); + } + else if (whichList == -1) { + if (line.text) { + // non-blank line, end all lists + pieces.push(new Array(lists.length+1).join('
  • ')); + lists.length = 0; + pieces.push(lineContent, ''); + } + else { + pieces.push('
    '); + } + } + else { + while (whichList < lists.length-1) { + pieces.push(''); + lists.length--; + } + pieces.push('
  • ', lineContent || '
    '); + } + } + else { + pieces.push(lineContent, ''); + } + } + pieces.push(new Array(lists.length+1).join('
  • ')); + + return pieces.join(''); +} + +function _analyzeLine(text, aline, apool) { + var line = {}; + + // identify list + var lineMarker = 0; + line.listLevel = 0; + if (aline) { + var opIter = Changeset.opIterator(aline); + if (opIter.hasNext()) { + var listType = Changeset.opAttributeValue(opIter.next(), 'list', apool); + if (listType) { + lineMarker = 1; + listType = /([a-z]+)([12345678])/.exec(listType); + if (listType) { + line.listTypeName = listType[1]; + line.listLevel = Number(listType[2]); + } + } + } + } + if (lineMarker) { + line.text = text.substring(1); + line.aline = Changeset.subattribution(aline, 1); + } + else { + line.text = text; + line.aline = aline; + } + + return line; +} + +function getPadHTMLDocument(pad, revNum, noDocType, callback) { + var head = (noDocType?'':'\n')+ + '\n'+ + (noDocType?'': + '\n'+ + '\n'+ + '\n'+ + ''+'/'+pad.getId()+'\n'+ + '\n')+ + ''; + + var foot = '\n\n'; + + getPadHTML(pad, revNum, function (err, html) { + callback(err, head + html + foot); + }); +} + +function _escapeHTML(s) { + var re = /[&<>]/g; + if (! re.MAP) { + // persisted across function calls! + re.MAP = { + '&': '&', + '<': '<', + '>': '>', + }; + } + return s.replace(re, function(c) { return re.MAP[c]; }); +} + +// copied from ACE +function _processSpaces(s) { + var doesWrap = true; + if (s.indexOf("<") < 0 && ! doesWrap) { + // short-cut + return s.replace(/ /g, ' '); + } + var parts = []; + s.replace(/<[^>]*>?| |[^ <]+/g, function(m) { parts.push(m); }); + if (doesWrap) { + var endOfLine = true; + var beforeSpace = false; + // last space in a run is normal, others are nbsp, + // end of line is nbsp + for(var i=parts.length-1;i>=0;i--) { + var p = parts[i]; + if (p == " ") { + if (endOfLine || beforeSpace) + parts[i] = ' '; + endOfLine = false; + beforeSpace = true; + } + else if (p.charAt(0) != "<") { + endOfLine = false; + beforeSpace = false; + } + } + // beginning of line is nbsp + for(var i=0;i