www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | Submodules | README | LICENSE

commit f06ce2705c8e2eeda203d7c1a4945587aa977571
parent f6b1d6e56e43ed731eae778a7ab26f447c787dcc
Author: Simon Kornblith <simon@simonster.com>
Date:   Thu, 11 Sep 2008 21:29:05 +0000

implements Zotero.Styles and Zotero.Style using flat files


Diffstat:
Mchrome/content/zotero/bibliography.js | 13+++++++------
Mchrome/content/zotero/browser.js | 2+-
Mchrome/content/zotero/fileInterface.js | 6+++---
Mchrome/content/zotero/preferences/preferences.js | 22++++++++++++++--------
Mchrome/content/zotero/xpcom/cite.js | 316+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Mchrome/content/zotero/xpcom/integration.js | 28++++++++++++++++++++++++----
Mchrome/content/zotero/xpcom/quickCopy.js | 8++++----
Mchrome/content/zotero/xpcom/zotero.js | 2+-
8 files changed, 217 insertions(+), 180 deletions(-)

diff --git a/chrome/content/zotero/bibliography.js b/chrome/content/zotero/bibliography.js @@ -51,7 +51,7 @@ var Zotero_File_Interface_Bibliography = new function() { } var listbox = document.getElementById("style-listbox"); - var styles = Zotero.Cite.getStyles(); + var styles = Zotero.Styles.getVisible(); // if no style is set, get the last style used if(!_io.style) { @@ -61,13 +61,14 @@ var Zotero_File_Interface_Bibliography = new function() { // add styles to list var index = 0; - for (var i in styles) { + var nStyles = styles.length; + for(var i=0; i<nStyles; i++) { var itemNode = document.createElement("listitem"); - itemNode.setAttribute("value", i); - itemNode.setAttribute("label", styles[i]); + itemNode.setAttribute("value", styles[i].styleID); + itemNode.setAttribute("label", styles[i].title); listbox.appendChild(itemNode); - if(i == _io.style) { + if(styles[i].styleID == _io.style) { var selectIndex = index; } index++; @@ -142,7 +143,7 @@ var Zotero_File_Interface_Bibliography = new function() { // update status of displayAs box based - var styleClass = Zotero.Cite.getStyleClass(selectedStyle); + var styleClass = Zotero.Styles.get(selectedStyle).class; document.getElementById("displayAs").disabled = styleClass != "note"; } diff --git a/chrome/content/zotero/browser.js b/chrome/content/zotero/browser.js @@ -629,7 +629,7 @@ Zotero_Browser.Tab.prototype._attemptLocalFileImport = function(doc) { var csl = Zotero.File.getContentsFromURL(doc.documentURI); if(csl.indexOf("http://purl.org/net/xbiblio/csl") != -1) { // looks like a CSL; try to import - Zotero.Cite.installStyle(csl, doc.documentURI); + Zotero.Styles.install(csl, doc.documentURI); } } else { // see if we can import this file diff --git a/chrome/content/zotero/fileInterface.js b/chrome/content/zotero/fileInterface.js @@ -320,7 +320,7 @@ var Zotero_File_Interface = new function() { createInstance(Components.interfaces.nsITransferable); var clipboardService = Components.classes["@mozilla.org/widget/clipboard;1"]. getService(Components.interfaces.nsIClipboard); - var csl = Zotero.Cite.getStyle(style); + var csl = Zotero.Styles.get(style).csl; var itemSet = csl.createItemSet(items); // add HTML @@ -357,7 +357,7 @@ var Zotero_File_Interface = new function() { var clipboardService = Components.classes["@mozilla.org/widget/clipboard;1"]. getService(Components.interfaces.nsIClipboard); - var csl = Zotero.Cite.getStyle(style); + var csl = Zotero.Styles.get(style).csl; var itemSet = csl.createItemSet(items); var itemIDs = []; for (var i=0; i<items.length; i++) { @@ -420,7 +420,7 @@ var Zotero_File_Interface = new function() { return; } else { - var csl = Zotero.Cite.getStyle(io.style); + var csl = Zotero.Styles.get(io.style).csl; var itemSet = csl.createItemSet(items); var bibliography = csl.formatBibliography(itemSet, format); } diff --git a/chrome/content/zotero/preferences/preferences.js b/chrome/content/zotero/preferences/preferences.js @@ -371,13 +371,13 @@ function buildQuickCopyFormatDropDown(menulist, contentType, currentFormat) { popup.appendChild(itemNode); // add styles to list - var styles = Zotero.Cite.getStyles(); - for (var i in styles) { + var styles = Zotero.Styles.getVisible(); + for each(var style in styles) { var baseVal = 'bibliography=' + i; - var val = 'bibliography' + (contentType == 'html' ? '/html' : '') + '=' + i; + var val = 'bibliography' + (contentType == 'html' ? '/html' : '') + '=' + style.styleID; var itemNode = document.createElement("menuitem"); itemNode.setAttribute("value", val); - itemNode.setAttribute("label", styles[i]); + itemNode.setAttribute("label", style.title); itemNode.setAttribute("oncommand", 'updateQuickCopyHTMLCheckbox()'); popup.appendChild(itemNode); @@ -1188,7 +1188,7 @@ function addStyle() { var name = file.leafName.replace(/\.ens$/i, ""); var date = new Date(file.lastModifiedTime); - var cslID = Zotero.Cite.installStyle(read, false, date, name); + var cslID = Zotero.Styles.install(read, false, date, name); } else { // This _should_ get the right charset for us automatically var fileURI = Components.classes["@mozilla.org/network/protocol;1?name=file"] @@ -1205,7 +1205,7 @@ function addStyle() { throw e; } - var cslID = Zotero.Cite.installStyle(req.responseText); + var cslID = Zotero.Styles.install(req.responseText); } } @@ -1221,8 +1221,14 @@ function deleteStyle() { var treeitem = tree.lastChild.childNodes[tree.currentIndex]; var cslID = treeitem.getAttribute('id').substr(11); - Zotero.Cite.deleteStyle(cslID); - this.refreshStylesList(); + var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] + .getService(Components.interfaces.nsIPromptService); + var text = Zotero.getString('styles.deleteStyle', [title]); + if(ps.confirm(null, '', text)) { + Zotero.Styles.get(cslID).delete(); + this.refreshStylesList(); + } + document.getElementById('styleManager-delete').disabled = true; } diff --git a/chrome/content/zotero/xpcom/cite.js b/chrome/content/zotero/xpcom/cite.js @@ -20,175 +20,185 @@ ***** END LICENSE BLOCK ***** */ -/* - * Zotero.Cite: a class for creating bibliographies from within Scholar - * this class handles pulling the CSL file and item data out of the database, - * while CSL, below, handles the actual generation of the bibliography +/** + * @property {Boolean} cacheTranslatorData Whether translator data should be cached or reloaded + * every time a translator is accessed + * @property {Zotero.CSL} lastCSL */ - -Zotero.Cite = new function() { - default xml namespace = "http://purl.org/net/xbiblio/csl"; - - var _lastCSL = null; - var _lastStyle = null; +Zotero.Styles = new function() { + var _initialized = false; + var _styles, _visibleStyles; - this.getStyles = getStyles; - this.getStyleClass = getStyleClass; - this.getStyle = getStyle; - this.installStyle = installStyle; - this.deleteStyle = deleteStyle; + this.ios = Components.classes["@mozilla.org/network/io-service;1"]. + getService(Components.interfaces.nsIIOService); - /* - * returns an associative array of cslID => styleName pairs + /** + * Initializes styles cache, loading metadata for styles into memory */ - function getStyles() { - // get styles - var sql = "SELECT cslID, title FROM csl ORDER BY title"; - var styles = Zotero.DB.query(sql); + this.init = function() { + _initialized = true; - // convert to associative array - var stylesObject = new Object(); - for each(var style in styles) { - stylesObject[style.cslID] = style.title; - } + var start = (new Date()).getTime() - return stylesObject; - } - - /* - * gets the class of a given style - */ - function getStyleClass(cslID) { - var csl = _getCSL(cslID); - var xml = new XML(Zotero.CSL.Global.cleanXML(csl)); - return xml["@class"].toString(); + _styles = {}; + _visibleStyles = []; + this.cacheTranslatorData = Zotero.Prefs.get("cacheTranslatorData"); + this.lastCSL = null; + + // main dir + var dir = Zotero.getStylesDirectory(); + var i = _readStylesFromDirectory(dir, true); + + // hidden dir + dir.append("hidden"); + if(dir.exists()) i += _readStylesFromDirectory(dir); + + Zotero.debug("Cached "+i+" styles in "+((new Date()).getTime() - start)+" ms"); } - /* - * gets CSL from the database, or, if it's the most recently used style, - * from the cache + /** + * Reads all styles from a given directory and caches their metadata */ - function getStyle(cslID) { - if(_lastStyle != cslID || Zotero.Prefs.get("cacheTranslatorData") == false) { - // create a CSL instance - var csl = _getCSL(cslID); + function _readStylesFromDirectory(dir, visible) { + var i = 0; + var contents = dir.directoryEntries; + while(contents.hasMoreElements()) { + var file = contents.getNext().QueryInterface(Components.interfaces.nsIFile); + if(!file.leafName || file.leafName[0] == "." || file.isDirectory()) continue; - // load CSL in EN mode if necessary - if(csl.substr(0, 6) == "\x00\x08\xFF\x00\x00\x00") { - // EN style - var enConverter = new Zotero.ENConverter(csl); - csl = enConverter.parse(); - } - _lastCSL = new Zotero.CSL(csl); + var style = new Zotero.Style(file); - _lastStyle = cslID; + if(style.styleID) { + if(_styles[style.styleID]) { + // same style is already cached + Zotero.log('Style with ID '+style.styleID+' already loaded from "'+ + _styles[style.styleID].file.leafName+'"', "error", + Zotero.Styles.ios.newFileURI(style.file).spec); + } else { + // add to cache + _styles[style.styleID] = style; + if(visible) _visibleStyles.push(style); + } + } + i++; } - return _lastCSL; + return i; } - /* - * get CSL for a given style from the database + /** + * Gets a style with a given ID + * @param {String} id */ - function _getCSL(cslID) { - var style = Zotero.DB.valueQuery("SELECT csl FROM csl WHERE cslID = ?", [cslID]); - if(!style) throw "Zotero.Cite: invalid CSL ID"; - return style; + this.get = function(id) { + if(!_initialized) this.init(); + return _styles[id]; } /** - * installs a style - **/ - function installStyle(cslString, loadURI, date, name) { - var error = false; - try { - if(cslString.substr(0, 6) == "\x00\x08\xFF\x00\x00\x00") { - // EN style - var enConverter = new Zotero.ENConverter(cslString, date, name); - var xml = enConverter.parse(); - } else { - // CSL - var xml = new XML(Zotero.CSL.Global.cleanXML(cslString)); - } - } - catch (e) { - error = true; - Components.utils.reportError(e); - } - - if (!xml || error) { - alert(Zotero.getString('styles.installError', (loadURI ? loadURI : "This"))); - return false; - } - - var uri = xml.info.id.toString(); - var title = xml.info.title.toString(); - var updated = xml.info.updated.toString().replace(/(.+)T([^\+]+)\+?.*/, "$1 $2"); - - var sql = "SELECT title FROM csl WHERE cslID=?"; - var existingTitle = Zotero.DB.valueQuery(sql, uri); - - var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] - .getService(Components.interfaces.nsIPromptService); - - var buttonFlags = (ps.BUTTON_POS_0) * (ps.BUTTON_TITLE_IS_STRING) - + (ps.BUTTON_POS_1) * (ps.BUTTON_TITLE_CANCEL); - - if (existingTitle) { - if(loadURI) { - var text = Zotero.getString('styles.updateStyleURI', [existingTitle, title, loadURI]); - } else { - var text = Zotero.getString('styles.updateStyle', [existingTitle, title]); - } - } - else { - if(loadURI) { - var text = Zotero.getString('styles.installStyleURI', [title, loadURI]); - } else { - var text = Zotero.getString('styles.installStyle', [title]); - } - } - - var acceptButton = Zotero.getString('general.install'); - - var index = ps.confirmEx(null, - '', - text, - buttonFlags, - acceptButton, null, null, null, {} - ); - - if (index == 0) { - var sql = "REPLACE INTO csl VALUES (?,?,?,?)"; - Zotero.DB.query(sql, [uri, updated, title, cslString]); - alert(Zotero.getString('styles.installed', title)); - return uri; - } + * Gets all visible styles + */ + this.getVisible = function() { + if(!_initialized || !this.cacheTranslatorData) this.init(); + return _visibleStyles; } /** - * deletes a style - **/ - function deleteStyle(uri) { - var sql = "SELECT title FROM csl WHERE cslID=?"; - var title = Zotero.DB.valueQuery(sql, uri); + * Gets all styles + */ + this.getAll = function() { + if(!_initialized || !this.cacheTranslatorData) this.init(); + return _styles; + } +} + +/** + * @class Represents a style file and its metadata + * @property {String} styleID + * @property {String} title + * @property {String} updated + * @property {String} class + * @property {Zotero.CSL} csl + */ +Zotero.Style = function(file) { + this.file = file; + + var extension = file.leafName.substr(-4).toLowerCase(); + if(extension == ".ens") { + this.type = "ens"; - if(!title) throw "Cite: style to delete does not exist!" + this.styleID = Zotero.Styles.ios.newFileURI(this.file).spec; + this.title = file.leafName.substr(0, file.leafName.length-4); + this.updated = Zotero.Date.dateToSQL(new Date(file.lastModifiedTime)); + } else if(extension == ".csl") { + // "with ({});" needed to fix default namespace scope issue + // See https://bugzilla.mozilla.org/show_bug.cgi?id=330572 + default xml namespace = "http://purl.org/net/xbiblio/csl"; with ({}); - var ps = Components.classes["@mozilla.org/embedcomp/prompt-service;1"] - .getService(Components.interfaces.nsIPromptService); + this.type = "csl"; - var text = Zotero.getString('styles.deleteStyle', [title]); + var xml = Zotero.CSL.Global.cleanXML(Zotero.File.getContents(file)); + xml = new XML(xml); - if(ps.confirm(null, '', text)) { - var sql = "DELETE FROM csl WHERE cslID=?"; - Zotero.DB.query(sql, uri); - } + this.styleID = xml.info.id.toString(); + this.title = xml.info.title.toString(); + this.updated = xml.info.updated.toString().replace(/(.+)T([^\+]+)\+?.*/, "$1 $2"); + this._class = xml.@class.toString(); } } +Zotero.Style.prototype.__defineGetter__("csl", +/** + * Retrieves the Zotero.CSL object for this style + * @type Zotero.CSL + */ +function() { + // cache last style + if(Zotero.Styles.cacheTranslatorData && Zotero.Styles.lastCSL.styleID == this.styleID) { + return Zotero.Styles.lastCSL; + } + + + if(this.type == "ens") { + // EN style + var iStream = Components.classes["@mozilla.org/network/file-input-stream;1"] + .createInstance(Components.interfaces.nsIFileInputStream); + iStream.init(this.file, 0x01, 0664, 0); + var bStream = Components.classes["@mozilla.org/binaryinputstream;1"] + .createInstance(Components.interfaces.nsIBinaryInputStream); + bStream.setInputStream(iStream); + var string = bStream.readBytes(this.file.fileSize); + iStream.close(); + + var enConverter = new Zotero.ENConverter(string, null, this.title); + var xml = enConverter.parse(); + } else { + var cslString = Zotero.File.getContents(this.file); + var xml = new XML(Zotero.CSL.Global.cleanXML(cslString)); + } + + return (Zotero.Styles.lastCSL = new Zotero.CSL(xml)); +}); + +Zotero.Style.prototype.__defineGetter__("class", +/** + * Retrieves the style class, either from the metadata that's already loaded or by loading the file + * @type String + */ +function() { + if(this._class) return this._class; + return (this._class = this.csl.class); +}); + +/** + * Deletes a style + */ +Zotero.Style.prototype.delete = function() { + this.file.remove(); + Zotero.Styles.init(); +} -Zotero.Cite.MIMEHandler = new function () { +Zotero.Styles.MIMEHandler = new function () { this.init = init; /* @@ -198,16 +208,16 @@ Zotero.Cite.MIMEHandler = new function () { Zotero.debug("Registering URIContentListener for text/x-csl"); var uriLoader = Components.classes["@mozilla.org/uriloader;1"] .getService(Components.interfaces.nsIURILoader); - uriLoader.registerContentListener(Zotero.Cite.MIMEHandler.URIContentListener); + uriLoader.registerContentListener(Zotero.Styles.MIMEHandler.URIContentListener); } } /* - * Zotero.Cite.MIMEHandler.URIContentListener: implements + * Zotero.Styles.MIMEHandler.URIContentListener: implements * nsIURIContentListener interface to grab MIME types */ -Zotero.Cite.MIMEHandler.URIContentListener = new function() { +Zotero.Styles.MIMEHandler.URIContentListener = new function() { // list of content types to capture // NOTE: must be from shortest to longest length this.desiredContentTypes = ["text/x-csl"]; @@ -236,7 +246,7 @@ Zotero.Cite.MIMEHandler.URIContentListener = new function() { function doContent(contentType, isContentPreferred, request, contentHandler) { Zotero.debug("Running doContent() for " + request.name); - contentHandler.value = new Zotero.Cite.MIMEHandler.StreamListener(request, contentType); + contentHandler.value = new Zotero.Styles.MIMEHandler.StreamListener(request, contentType); return false; } @@ -253,10 +263,10 @@ Zotero.Cite.MIMEHandler.URIContentListener = new function() { } /* - * Zotero.Cite.MIMEHandler.StreamListener: implements nsIStreamListener and + * Zotero.Styles.MIMEHandler.StreamListener: implements nsIStreamListener and * nsIRequestObserver interfaces to download MIME types we've grabbed */ -Zotero.Cite.MIMEHandler.StreamListener = function(request, contentType) { +Zotero.Styles.MIMEHandler.StreamListener = function(request, contentType) { this._request = request; this._contentType = contentType this._readString = ""; @@ -266,7 +276,7 @@ Zotero.Cite.MIMEHandler.StreamListener = function(request, contentType) { Zotero.debug("Prepared to grab content type " + contentType); } -Zotero.Cite.MIMEHandler.StreamListener.prototype.QueryInterface = function(iid) { +Zotero.Styles.MIMEHandler.StreamListener.prototype.QueryInterface = function(iid) { if (iid.equals(Components.interfaces.nsISupports) || iid.equals(Components.interfaces.nsIRequestObserver) || iid.equals(Components.interfaces.nsIStreamListener)) { @@ -275,12 +285,12 @@ Zotero.Cite.MIMEHandler.StreamListener.prototype.QueryInterface = function(iid) throw Components.results.NS_ERROR_NO_INTERFACE; } -Zotero.Cite.MIMEHandler.StreamListener.prototype.onStartRequest = function(channel, context) {} +Zotero.Styles.MIMEHandler.StreamListener.prototype.onStartRequest = function(channel, context) {} /* * Called when there's data available; basically, we just want to collect this data */ -Zotero.Cite.MIMEHandler.StreamListener.prototype.onDataAvailable = function(request, context, inputStream, offset, count) { +Zotero.Styles.MIMEHandler.StreamListener.prototype.onDataAvailable = function(request, context, inputStream, offset, count) { Zotero.debug(count + " bytes available"); if (inputStream != this._scriptableStreamInput) { @@ -295,7 +305,7 @@ Zotero.Cite.MIMEHandler.StreamListener.prototype.onDataAvailable = function(requ /* * Called when the request is done */ -Zotero.Cite.MIMEHandler.StreamListener.prototype.onStopRequest = function(channel, context, status) { +Zotero.Styles.MIMEHandler.StreamListener.prototype.onStopRequest = function(channel, context, status) { Zotero.debug("Request finished"); var externalHelperAppService = Components.classes["@mozilla.org/uriloader/external-helper-app-service;1"] .getService(Components.interfaces.nsIExternalHelperAppService); @@ -307,7 +317,7 @@ Zotero.Cite.MIMEHandler.StreamListener.prototype.onStopRequest = function(channe var loadURI = ''; } - Zotero.Cite.installStyle(this._readString, loadURI); + Zotero.Styles.install(this._readString, loadURI); } @@ -333,12 +343,12 @@ Zotero.CSL = function(csl) { // load localizations this._terms = Zotero.CSL.Global.parseLocales(this._csl.terms); - // load class - this.class = this._csl["@class"].toString(); + // load class and styleID + this.styleID = this._csl.info.id.toString(); + this.class = this._csl["@class"].toString(); Zotero.debug("CSL: style class is "+this.class); this.hasBibliography = (this._csl.bibliography.length() ? 1 : 0); - Zotero.debug("hasBibliography "+this.hasBibliography); } /* diff --git a/chrome/content/zotero/xpcom/integration.js b/chrome/content/zotero/xpcom/integration.js @@ -704,7 +704,7 @@ Zotero.Integration.SOAP_Compat = new function() { } try { - Zotero.Cite.getStyle(vars[2]); + Zotero.Styles.get(vars[2]); } catch(e) { return "ERROR:prefsNeedReset"; } @@ -809,7 +809,7 @@ Zotero.Integration.Session.prototype.setStyle = function(styleID, prefs) { if(styleID) { this.styleID = styleID; try { - this.style = Zotero.Cite.getStyle(styleID); + this.style = Zotero.Styles.get(styleID).csl; this.dateModified = new Object(); this.itemSet = this.style.createItemSet(); @@ -1482,22 +1482,36 @@ Zotero.Integration.Session.prototype.getBibliographyData = function() { } } -/* - * Interface for bibliography editor +/** + * @class Interface for bibliography editor to alter document bibliography + * @constructor + * Creates a new bibliography editor interface + * @param {Zotero.Integration.Session} session */ Zotero.Integration.Session.BibliographyEditInterface = function(session) { this.session = session; } +/** + * Gets the @link {Zotero.CSL.ItemSet} for the bibliography being edited + * The item set should not be modified, but may be used to determine what items are in the + * bibliography. + */ Zotero.Integration.Session.BibliographyEditInterface.prototype.getItemSet = function() { return this.session.itemSet; } +/** + * Checks whether an item is cited in the bibliography being edited + */ Zotero.Integration.Session.BibliographyEditInterface.prototype.isCited = function(item) { if(this.session.citationsByItemID[item.id]) return true; return false; } +/** + * Checks whether an item is cited in the bibliography being edited + */ Zotero.Integration.Session.BibliographyEditInterface.prototype.add = function(item) { // create new item this.session.itemSet.add([item]); @@ -1505,6 +1519,9 @@ Zotero.Integration.Session.BibliographyEditInterface.prototype.add = function(it this.session.sortItemSet(); } +/** + * Removes an item from the bibliography being edited + */ Zotero.Integration.Session.BibliographyEditInterface.prototype.remove = function(item) { // create new item this.session.itemSet.remove([item]); @@ -1524,6 +1541,9 @@ Zotero.Integration.Session.BibliographyEditInterface.prototype.remove = function if(this.session.uncitedItems[item.key]) this.session.uncitedItems[item.key] = undefined; } +/** + * Generates a preview of the bibliography entry for a given item + */ Zotero.Integration.Session.BibliographyEditInterface.prototype.preview = function(item) { var itemSet = this.session.style.createItemSet([item]); return this.session.style.formatBibliography(itemSet, "Integration"); diff --git a/chrome/content/zotero/xpcom/quickCopy.js b/chrome/content/zotero/xpcom/quickCopy.js @@ -190,7 +190,7 @@ Zotero.QuickCopy = new function() { return content; } - var csl = Zotero.Cite.getStyle(format); + var csl = Zotero.Styles.get(format).csl; var itemSet = csl.createItemSet(items); // Copy citations if shift key pressed @@ -223,9 +223,9 @@ Zotero.QuickCopy = new function() { var translators = translation.getTranslators(); // add styles to list - var styles = Zotero.Cite.getStyles(); - for (var i in styles) { - _formattedNames['bibliography=' + i] = styles[i]; + var styles = Zotero.Styles.getVisible(); + for each(var style in styles) { + _formattedNames['bibliography=' + style.styleID] = style.title; } for (var i=0; i<translators.length; i++) { diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js @@ -277,7 +277,7 @@ var Zotero = new function(){ Zotero.Proxies.init(); Zotero.Ingester.MIMEHandler.init(); - Zotero.Cite.MIMEHandler.init(); + Zotero.Styles.MIMEHandler.init(); this.initialized = true; Zotero.debug("Initialized in "+((new Date()).getTime() - start)+" ms");