zotero/chrome/content/zotero/xpcom/translate.js
Dan Stillman 86678aba27 Install/upgrade support for flat-file translators and styles
Files are copied from translators.zip and styles.zip (or, for SVN installs, 'translators' and (for now) 'csl' directories) in the installation directory to 'translators' and 'styles' directories in the data directory. A build_zip file is provided for testing translators.zip (which will take precedence over a 'translators' directory) but isn't required.

The timestamp stored in repotime.txt is stored in the database and is sent to the server for updates since that time.

Updating a file in [install-dir]/translators or [install-dir]/styles automatically copies all files in that directory to the data directory.
2008-09-12 22:09:54 +00:00

2686 lines
81 KiB
JavaScript

/*
***** BEGIN LICENSE BLOCK *****
Copyright (c) 2006 Center for History and New Media
George Mason University, Fairfax, Virginia, USA
http://chnm.gmu.edu
Licensed under the Educational Community License, Version 1.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.opensource.org/licenses/ecl1.php
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.
***** END LICENSE BLOCK *****
*/
// Enumeration of types of translators
const TRANSLATOR_TYPES = {"import":1, "export":2, "web":4, "search":8};
// Byte order marks for various character sets
const BOMs = {
"UTF-8":"\xEF\xBB\xBF",
"UTF-16BE":"\xFE\xFF",
"UTF-16LE":"\xFF\xFE",
"UTF-32BE":"\x00\x00\xFE\xFF",
"UTF-32LE":"\xFF\xFE\x00\x00"
}
/**
* Singleton to handle loading and caching of translators
* @namespace
*/
Zotero.Translators = new function() {
var _cache, _translators;
var _initialized = false;
/**
* Initializes translator cache, loading all relevant translators into memory
*/
this.init = function() {
_initialized = true;
var start = (new Date()).getTime()
_cache = {"import":[], "export":[], "web":[], "search":[]};
_translators = {};
var i = 0;
var contents = Zotero.getTranslatorsDirectory().directoryEntries;
while(contents.hasMoreElements()) {
var file = contents.getNext().QueryInterface(Components.interfaces.nsIFile);
if(!file.leafName || file.leafName[0] == ".") continue;
var translator = new Zotero.Translator(file);
if(translator.translatorID) {
if(_translators[translator.translatorID]) {
// same translator is already cached
translator.logError('Translator with ID '+
translator.translatorID+' already loaded from "'+
_translators[translator.translatorID].file.leafName+'"');
} else {
// add to cache
_translators[translator.translatorID] = translator;
for(var type in TRANSLATOR_TYPES) {
if(translator.translatorType & TRANSLATOR_TYPES[type]) {
_cache[type].push(translator);
}
}
}
}
i++;
}
Zotero.debug("Cached "+i+" translators in "+((new Date()).getTime() - start)+" ms");
}
/**
* Gets the translator that corresponds to a given ID
*/
this.get = function(id) {
if(!_initialized) this.init();
return _translators[id] ? _translators[id] : false;
}
/**
* Gets all translators for a specific type of translation
*/
this.getAllForType = function(type) {
if(!_initialized) this.init();
return _cache[type].slice(0);
}
/**
* @param {String} label
* @return {String}
*/
this.getFileNameFromLabel = function(label) {
return Zotero.File.getValidFileName(label) + ".js";
}
}
/**
* @class Represents an individual translator
* @constructor
* @param {nsIFile} file File from which to generate a translator object
* @property {String} translatorID Unique GUID of the translator
* @property {Integer} translatorType Type of the translator (use bitwise & with TRANSLATOR_TYPES to read)
* @property {String} label Human-readable name of the translator
* @property {String} target Location that the translator processes
* @property {Boolean} inRepository Whether the translator may be found in the repository
* @property {String} lastUpdated SQL-style date and time of translator's last update
* @property {String} code The executable JavaScript for the translator
*/
Zotero.Translator = function(file) {
// Maximum length for the info JSON in a translator
const MAX_INFO_LENGTH = 4096;
const infoRe = /{(?:(?:"(?:[^"\r\n]*(?:\\")?)*")*[^}"]*)*}/;
this.file = file;
var fStream = Components.classes["@mozilla.org/network/file-input-stream;1"].
createInstance(Components.interfaces.nsIFileInputStream);
var cStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"].
createInstance(Components.interfaces.nsIConverterInputStream);
fStream.init(file, -1, -1, 0);
cStream.init(fStream, "UTF-8", 4096,
Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
var str = {};
var infoString = cStream.readString(MAX_INFO_LENGTH, str);
var m = infoRe.exec(str.value);
if(!m) {
this.logError("Invalid or missing translator metadata JSON object");
} else {
try {
var info = Zotero.JSON.unserialize(m[0]);
} catch(e) {
this.logError("Invalid or missing translator metadata JSON object");
}
if(info) {
var haveMetadata = true;
// make sure we have all the properties
for each(var property in ["translatorID", "translatorType", "label", "target", "lastUpdated"]) {
if(info[property] === undefined) {
this.logError('Missing property "'+property+'" in translator metadata JSON object');
haveMetadata = false;
break;
} else {
this[property] = info[property];
}
}
if(haveMetadata) {
if(this.translatorType & TRANSLATOR_TYPES["import"]) {
// compile import regexp to match only file extension
this.importRegexp = this.target ? new RegExp("\\."+this.target+"$", "i") : null;
}
if(this.translatorType & TRANSLATOR_TYPES["web"]) {
// compile web regexp
this.webRegexp = this.target ? new RegExp(this.target, "i") : null;
if(!this.target) {
// for translators used on every page, cache code in memory
var strs = [str.value];
var amountRead;
while(amountRead = cStream.readString(4096, str)) strs.push(str.value);
this._code = strs.join("");
}
}
}
}
}
fStream.close();
}
Zotero.Translator.prototype.__defineGetter__("code",
/**
* Getter for "code" property
* @return {String} Code of translator
* @inner
*/
function() {
if(this._code) return this._code;
return Zotero.File.getContents(this.file);
});
/**
* Log a translator-related error
* @param {String} message The error message
* @param {String} [type] The error type ("error", "warning", "exception", or "strict")
* @param {String} [line] The text of the line on which the error occurred
* @param {Integer} lineNumber
* @param {Integer} colNumber
*/
Zotero.Translator.prototype.logError = function(message, type, line, lineNumber, colNumber) {
var ios = Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService);
Zotero.log(message, type ? type : "error", ios.newFileURI(this.file).spec);
}
/*
* Zotero.Translate: a class for translation of Zotero metadata from and to
* other formats
*
* eventually, Zotero.Ingester may be rolled in here (i.e., after we get rid
* of RDF)
*
* type can be:
* export
* import
* web
* search
*
* a typical export process:
* var translatorObj = new Zotero.Translate();
* var possibleTranslators = translatorObj.getTranslators();
* // do something involving nsIFilePicker; remember, each possibleTranslator
* // object has properties translatorID, label, and targetID
* translatorObj.setLocation(myNsILocalFile);
* translatorObj.setTranslator(possibleTranslators[x]); // also accepts only an ID
* translatorObj.setHandler("done", _translationDone);
* translatorObj.translate();
*
*
* PUBLIC PROPERTIES:
*
* type - the text type of translator (set by constructor, should be read only)
* document - the document object to be used for web scraping (read-only; set
* with setDocument)
* translator - the translator currently in use (read-only; set with
* setTranslator)
* location - the location of the target (read-only; set with setLocation)
* for import/export - this is an instance of nsILocalFile
* for web - this is a URL
* search - item (in toArray() format) to extrapolate data for (read-only; set
* with setSearch).
* items - items (in Zotero.Item format) to be exported. if this is empty,
* Zotero will export all items in the library (read-only; set with
* setItems). setting items disables export of collections.
* path - the path to the target; for web, this is the same as location
* string - the string content to be used as a file.
* saveItem - whether new items should be saved to the database. defaults to
* true; set using second argument of constructor.
* newItems - items created when translate() was called
* newCollections - collections created when translate() was called
*
* PSEUDO-PRIVATE PROPERTIES (used only by other objects in this file):
*
* waitForCompletion - whether to wait for asynchronous completion, or return
* immediately when script has finished executing
* configOptions - options set by translator modifying behavior of
* Zotero.Translate
* displayOptions - options available to user for this specific translator
*
* PRIVATE PROPERTIES:
*
* _charset - character set
* _numericTypes - possible numeric types as a comma-delimited string
* _handlers - handlers for various events (see setHandler)
* _sandbox - sandbox in which translators will be executed
* _streams - streams that need to be closed when execution is complete
* _IDMap - a map from IDs as specified in Zotero.Item() to IDs of actual items
* _parentTranslator - set when a translator is called from another translator.
* among other things, disables passing of the translate
* object to handlers and modifies complete() function on
* returned items
* _storage - the stored string to be treated as input
* _storageLength - the length of the stored string
* _exportFileDirectory - the directory to which files will be exported
*
* WEB-ONLY PROPERTIES:
*
* locationIsProxied - whether the URL being scraped is going through
* an EZProxy
* _downloadAssociatedFiles - whether to download content, according to
* preferences
*
* EXPORT-ONLY PROPERTIES:
*
* output - export output (if no location has been specified)
*/
Zotero.Translate = function(type, saveItem, saveAttachments) {
this.type = type;
// import = 0001 = 1
// export = 0010 = 2
// web = 0100 = 4
// search = 1000 = 8
// combination types determined by addition or bitwise AND
// i.e., import+export = 1+2 = 3
this._numericTypes = "";
for(var i=0; i<=1; i++) {
for(var j=0; j<=1; j++) {
for(var k=0; k<=1; k++) {
if(type == "import") {
this._numericTypes += ","+parseInt(i.toString()+j.toString()+k.toString()+"1", 2);
} else if(type == "export") {
this._numericTypes += ","+parseInt(i.toString()+j.toString()+"1"+k.toString(), 2);
} else if(type == "web") {
this._numericTypes += ","+parseInt(i.toString()+"1"+j.toString()+k.toString(), 2);
} else if(type == "search") {
this._numericTypes += ","+parseInt("1"+i.toString()+j.toString()+k.toString(), 2);
} else {
throw("invalid import type");
}
}
}
}
this._numericTypes = this._numericTypes.substr(1);
this.saveItem = !(saveItem === false);
this.saveAttachments = !(saveAttachments === false);
this._handlers = new Array();
this._streams = new Array();
}
/*
* sets the browser to be used for web translation; also sets the location
*/
Zotero.Translate.prototype.setDocument = function(doc) {
this.document = doc;
this.setLocation(doc.location.href);
}
/*
* sets the item to be used for searching
*/
Zotero.Translate.prototype.setSearch = function(search) {
this.search = search;
}
/*
* sets the items to be used for export
*/
Zotero.Translate.prototype.setItems = function(items) {
this.items = items;
}
/*
* sets the collection to be used for export (overrides setItems)
*/
Zotero.Translate.prototype.setCollection = function(collection) {
this.collection = collection;
}
/*
* sets the location to operate upon (file should be an nsILocalFile object or
* web address)
*/
Zotero.Translate.prototype.setLocation = function(location) {
if(this.type == "web") {
// account for proxies
this.location = Zotero.Proxies.proxyToProper(location);
if(this.location != location) {
// figure out if this URL is being proxies
this.locationIsProxied = true;
}
this.path = this.location;
} else {
this.location = location;
if(this.location instanceof Components.interfaces.nsIFile) { // if a file
this.path = location.path;
} else { // if a url
this.path = location;
}
}
}
/*
* sets the string to be used as a file
*/
Zotero.Translate.prototype.setString = function(string) {
this._storage = string;
this._storageLength = string.length;
this._storagePointer = 0;
}
/*
* sets translator display options. you can also pass a translator (not ID) to
* setTranslator that includes a displayOptions argument
*/
Zotero.Translate.prototype.setDisplayOptions = function(displayOptions) {
this._setDisplayOptions = displayOptions;
}
/*
* sets the translator to be used for import/export
*
* accepts either the object from getTranslators() or an ID
*/
Zotero.Translate.prototype.setTranslator = function(translator) {
if(!translator) {
throw("cannot set translator: invalid value");
}
this.translator = null;
this._setDisplayOptions = null;
if(typeof(translator) == "object") { // passed an object and not an ID
if(translator.translatorID) {
if(translator.displayOptions) {
this._setDisplayOptions = translator.displayOptions;
}
this.translator = [translator];
} else {
// we have an array of translators
if(this.type != "search") {
throw("cannot set translator: a single translator must be specified when doing "+this.type+" translation");
}
// accept a list of objects
this.translator = [];
for(var i in translator) {
if(typeof(translator[i]) == "object") {
this.translator.push([translator[i]]);
} else {
this.translator.push([Zotero.Translators.get(translator[i])]);
}
}
}
} else {
this.translator = [Zotero.Translators.get(translator)];
}
return !!this.translator;
}
/*
* registers a handler function to be called when translation is complete
*
* as the first argument, all handlers will be passed the current function. the
* second argument is dependent on the handler.
*
* select
* valid: web
* called: when the user needs to select from a list of available items
* passed: an associative array in the form id => text
* returns: a numerically indexed array of ids, as extracted from the passed
* string
*
* itemDone
* valid: import, web, search
* called: when an item has been processed; may be called asynchronously
* passed: an item object (see Zotero.Item)
* returns: N/A
*
* collectionDone
* valid: import
* called: when a collection has been processed, after all items have been
* added; may be called asynchronously
* passed: a collection object (see Zotero.Collection)
* returns: N/A
*
* done
* valid: all
* called: when all processing is finished
* passed: true if successful, false if an error occurred
* returns: N/A
*
* debug
* valid: all
* called: when Zotero.debug() is called
* passed: string debug message
* returns: true if message should be logged to the console, false if not
*
* error
* valid: all
* called: when a fatal error occurs
* passed: error object (or string)
* returns: N/A
*
* translators
* valid: all
* called: when a translator search initiated with Zotero.getTranslators() is
* complete
* passed: an array of appropriate translators
* returns: N/A
*/
Zotero.Translate.prototype.setHandler = function(type, handler) {
if(!this._handlers[type]) {
this._handlers[type] = new Array();
}
this._handlers[type].push(handler);
}
/*
* clears all handlers for a given function
*/
Zotero.Translate.prototype.clearHandlers = function(type) {
this._handlers[type] = new Array();
}
/*
* calls a handler (see setHandler above)
*/
Zotero.Translate.prototype.runHandler = function(type, argument) {
var returnValue = undefined;
if(this._handlers[type]) {
for(var i in this._handlers[type]) {
Zotero.debug("Translate: running handler "+i+" for "+type, 5);
try {
if(this._parentTranslator) {
returnValue = this._handlers[type][i](null, argument);
} else {
returnValue = this._handlers[type][i](this, argument);
}
} catch(e) {
if(this._parentTranslator) {
// throw handler errors if they occur when a translator is
// called from another translator, so that the
// "Could Not Translate" dialog will appear if necessary
throw(e);
} else {
// otherwise, fail silently, so as not to interfere with
// interface cleanup
Zotero.debug("Translate: "+e+' in handler '+i+' for '+type, 5);
}
}
}
}
return returnValue;
}
/*
* gets all applicable translators
*
* for import, you should call this after setLocation; otherwise, you'll just get
* a list of all import filters, not filters equipped to handle a specific file
*
* this returns a list of translator objects, of which the following fields
* are useful:
*
* translatorID - the GUID of the translator
* label - the name of the translator
* itemType - the type of item this scraper says it will scrape
*/
Zotero.Translate.prototype.getTranslators = function() {
// do not allow simultaneous instances of getTranslators
if(this._translatorSearch) this._translatorSearch.running = false;
var translators = Zotero.Translators.getAllForType(this.type);
// create a new sandbox
this._generateSandbox();
this._setSandboxMode("detect");
var possibleTranslators = new Array();
Zotero.debug("Translate: Searching for translators for "+(this.path ? this.path : "an undisclosed location"), 3);
// see which translators can translate
this._translatorSearch = new Zotero.Translate.TranslatorSearch(this, translators);
// erroring should call complete
this.error = function(value, error) { this._translatorSearch.complete(value, error) };
// return translators if asynchronous
if(!this._translatorSearch.asyncMode) return this._translatorSearch.foundTranslators;
}
/*
* loads a translator into a sandbox
*/
Zotero.Translate.prototype._loadTranslator = function() {
if(!this._sandbox || this.type == "search") {
// create a new sandbox if none exists, or for searching (so that it's
// bound to the correct url)
this._generateSandbox();
}
this._setSandboxMode("translate");
// parse detect code for the translator
if(!this._parseCode(this.translator[0])) {
this._translationComplete(false);
return false;
}
return true;
}
/*
* does the actual translation
*/
Zotero.Translate.prototype.translate = function() {
/*
* initialize properties
*/
this.newItems = new Array();
this.newCollections = new Array();
this._IDMap = new Array();
this._complete = false;
this._itemsDone = false;
if(!this.translator || !this.translator.length) {
throw("cannot translate: no translator specified");
}
if(!this.location && this.type != "search" && this.type != "export" && !this._storage) {
// searches operate differently, because we could have an array of
// translators and have to go through each
throw("cannot translate: no location specified");
}
// erroring should end
this.error = this._translationComplete;
if(!this._loadTranslator()) {
return;
}
if(this._setDisplayOptions) {
this.displayOptions = this._setDisplayOptions;
}
if(this._storage) {
// enable reading from storage, which we can't do until the translator
// is loaded
this._storageFunctions(true);
}
var returnValue;
if(this.type == "web") {
returnValue = this._web();
} else if(this.type == "import") {
returnValue = this._import();
} else if(this.type == "export") {
returnValue = this._export();
} else if(this.type == "search") {
returnValue = this._search();
}
if(returnValue && !this.waitForCompletion) {
// if synchronous, call _translationComplete();
this._translationComplete(true);
}
}
/*
* parses translator detect code
*/
Zotero.Translate.prototype._parseCode = function(translator) {
this.configOptions = {};
this.displayOptions = {};
Zotero.debug("Translate: Parsing code for "+translator.label, 4);
try {
Components.utils.evalInSandbox("var translatorInfo = "+translator.code, this._sandbox);
return true;
} catch(e) {
translator.logError(e.toString());
this._debug(e+' in parsing code for '+translator.label, 3);
return false;
}
}
/*
* generates a sandbox for scraping/scraper detection
*/
Zotero.Translate.prototype._generateSandbox = function() {
var me = this;
// get sandbox URL
var sandboxLocation = "http://www.example.com/";
if(this.type == "web") {
// use real URL, not proxied version, to create sandbox
sandboxLocation = this.document.defaultView;
Zotero.debug("Translate: Binding sandbox to "+this.document.location.href, 4);
} else {
if (this.type == "search") {
// generate sandbox for search by extracting domain from translator target
if(this.translator && this.translator[0] && this.translator[0].target) {
// so that web translators work too
const searchSandboxRe = /^http:\/\/[\w.]+\//;
var tempURL = this.translator[0].target.replace(/\\/g, "").replace(/\^/g, "");
var m = searchSandboxRe.exec(tempURL);
if(m) sandboxLocation = m[0];
}
}
Zotero.debug("Translate: Binding sandbox to "+sandboxLocation, 4);
}
// set up sandbox
this._sandbox = new Components.utils.Sandbox(sandboxLocation);
this._sandbox.Zotero = new Object();
// add utilities
this._sandbox.Zotero.Utilities = new Zotero.Utilities.Translate(this);
this._sandbox.Zotero.Utilities.HTTP = this._sandbox.Zotero.Utilities;
if(this.type == "export") {
// add routines to retrieve items and collections
this._sandbox.Zotero.nextItem = function() { return me._exportGetItem() };
this._sandbox.Zotero.nextCollection = function() { return me._exportGetCollection() }
} else {
// copy routines to add new items
this._sandbox.Zotero.Item = Zotero.Translate.GenerateZoteroItemClass();
this._sandbox.Zotero.Item.prototype.complete = function() {me._itemDone(this)};
if(this.type == "import") {
// add routines to add new collections
this._sandbox.Zotero.Collection = Zotero.Translate.GenerateZoteroCollectionClass();
// attach the function to be run when a collection is done
this._sandbox.Zotero.Collection.prototype.complete = function() {me._collectionDone(this)};
} else if(this.type == "web" || this.type == "search") {
// set up selectItems handler
this._sandbox.Zotero.selectItems = function(options) { return me._selectItems(options) };
}
}
this._sandbox.XPathResult = Components.interfaces.nsIDOMXPathResult;
// for debug messages
this._sandbox.Zotero.debug = function(string) {me._debug(string, 4)};
// for adding configuration options
this._sandbox.Zotero.configure = function(option, value) {me._configure(option, value) };
// for adding displayed options
this._sandbox.Zotero.addOption = function(option, value) {me._addOption(option, value) };
// for getting the value of displayed options
this._sandbox.Zotero.getOption = function(option) { return me._getOption(option) };
// for loading other translators and accessing their methods
this._sandbox.Zotero.loadTranslator = function(type) {
var translation = new Zotero.Translate(type, false);
translation._parentTranslator = me;
if(type == "export" && (this.type == "web" || this.type == "search")) {
throw("for security reasons, web and search translators may not call export translators");
}
// for security reasons, safeTranslator wraps the translator object.
// note that setLocation() is not allowed
var safeTranslator = new Object();
safeTranslator.setSearch = function(arg) { return translation.setSearch(arg) };
safeTranslator.setDocument = function(arg) { return translation.setDocument(arg) };
safeTranslator.setHandler = function(arg1, arg2) { translation.setHandler(arg1, arg2) };
safeTranslator.setString = function(arg) { translation.setString(arg) };
safeTranslator.setTranslator = function(arg) { return translation.setTranslator(arg) };
safeTranslator.getTranslators = function() { return translation.getTranslators() };
safeTranslator.translate = function() {
var noHandlers = true;
for(var i in translation._handlers) {
noHandlers = false;
break;
}
if(noHandlers) {
if(type != "export") {
translation.setHandler("itemDone", function(obj, item) { item.complete() });
}
if(type == "web") {
translation.setHandler("selectItems", me._handlers["selectItems"]);
}
}
return translation.translate()
};
safeTranslator.getTranslatorObject = function() {
// load the translator into our sandbox
translation._loadTranslator();
// initialize internal IO
translation._initializeInternalIO();
var noHandlers = true;
for(var i in translation._handlers) {
noHandlers = false;
break;
}
if(noHandlers) {
if(type != "export") {
translation.setHandler("itemDone", function(obj, item) { item.complete() });
}
if(type == "web") {
translation.setHandler("selectItems", me._handlers["selectItems"]);
}
}
// return sandbox
return translation._sandbox;
};
return safeTranslator;
}
}
/*
* Adds appropriate methods for detect/translate modes
*/
Zotero.Translate.prototype._setSandboxMode = function(mode) {
var me = this;
// erase waitForCompletion status and done function
this.waitForCompletion = false;
this._sandbox.Zotero.done = undefined;
if(mode == "detect") {
// for asynchronous operation, use wait()
// done() is implemented after wait() is called
this._sandbox.Zotero.wait = function() { me._enableAsynchronousDetect() };
} else {
// for asynchronous operation, use wait()
// done() is implemented after wait() is called
this._sandbox.Zotero.wait = function() { me._enableAsynchronousTranslate() };
}
}
/*
* sets an option that modifies the way the translator is executed
*
* called as configure() in translator detectCode
*
* current options:
*
* dataMode
* valid: import, export
* options: rdf, block, line
* purpose: selects whether write/read behave as standard text functions or
* using Mozilla's built-in support for RDF data sources
*
* getCollections
* valid: export
* options: true, false
* purpose: selects whether export translator will receive an array of
* collections and children in addition to the array of items and
* children
*/
Zotero.Translate.prototype._configure = function(option, value) {
this.configOptions[option] = value;
Zotero.debug("Translate: Setting configure option "+option+" to "+value, 4);
}
/*
* adds translator options to be displayed in a dialog
*
* called as addOption() in detect code
*
* current options are exportNotes and exportFileData
*/
Zotero.Translate.prototype._addOption = function(option, value) {
this.displayOptions[option] = value;
Zotero.debug("Translate: Setting display option "+option+" to "+value, 4);
}
/*
* gets translator options that were displayed in a dialog
*
* called as getOption() in detect code
*
*/
Zotero.Translate.prototype._getOption = function(option) {
return this.displayOptions[option];
}
/*
* makes translation API wait until done() has been called from the translator
* before executing _translationComplete
*
* called as wait() in translator code
*/
Zotero.Translate.prototype._enableAsynchronousDetect = function() {
var me = this;
this.waitForCompletion = true;
this._sandbox.Zotero.done = function(arg) { me._translatorSearch.complete(arg) };
}
Zotero.Translate.prototype._enableAsynchronousTranslate = function() {
var me = this;
this.waitForCompletion = true;
this._sandbox.Zotero.done = function(val) { me._translationComplete(val) };
}
/*
* lets user pick which items s/he wants to put in his/her library
*
* called as selectItems() in translator code
*/
Zotero.Translate.prototype._selectItems = function(options) {
// hack to see if there are options
var haveOptions = false;
for(var i in options) {
haveOptions = true;
break;
}
if(!haveOptions) {
throw "translator called select items with no items";
}
if(this._handlers.select) {
return this.runHandler("select", options);
} else { // no handler defined; assume they want all of them
return options;
}
}
/*
* executed on translator completion, either automatically from a synchronous
* scraper or as done() from an asynchronous scraper
*
* finishes things up and calls callback function(s)
*/
Zotero.Translate.prototype._translationComplete = function(returnValue, error) {
// to make sure this isn't called twice
if(returnValue === undefined) {
returnValue = this._itemsDone;
}
if(!this._complete) {
this._complete = true;
if(this.type == "search" && !this._itemsDone) {
// if we're performing a search and didn't get any results, go on
// to the next translator
Zotero.debug("Translate: Could not find a result using "+this.translator[0].label+": \n"
+this._generateErrorString(error), 3);
if(this.translator.length > 1) {
this.translator.shift();
this.translate();
return;
}
} else {
// close open streams
this._closeStreams();
if(!returnValue) {
// report error to console
if(this.translator[0]) {
this.translator[0].logError(error.toString(), "exception");
} else {
Components.utils.reportError(error);
}
// report error to debug log
var errorString = this._generateErrorString(error);
this._debug("Translation using "+(this.translator && this.translator[0] && this.translator[0].label ? this.translator[0].label : "no translator")+" failed: \n"+errorString, 2);
// report translation error for webpages to mothership
if(this.type == "web") this._reportTranslationFailure(errorString);
this.runHandler("error", error);
} else {
this._debug("Translation successful");
}
}
// call handlers
this.runHandler("done", returnValue);
}
}
/*
* generates a useful error string, for submitting and debugging purposes
*/
Zotero.Translate.prototype._generateErrorString = function(error) {
var errorString = "";
if(typeof(error) == "string") {
errorString = "\nthrown exception => "+error;
} else {
for(var i in error) {
if(typeof(error[i]) != "object") {
errorString += "\n"+i+' => '+error[i];
}
}
}
errorString += "\nurl => "+this.path
+ "\ndownloadAssociatedFiles => "+Zotero.Prefs.get("downloadAssociatedFiles")
+ "\nautomaticSnapshots => "+Zotero.Prefs.get("automaticSnapshots");
return errorString.substr(1);
}
/*
* runs an HTTP request to report a translation error
*/
Zotero.Translate.prototype._reportTranslationFailure = function(errorData) {
if(this.translator[0].inRepository && Zotero.Prefs.get("reportTranslationFailure")) {
var postBody = "id=" + encodeURIComponent(this.translator[0].translatorID) +
"&lastUpdated=" + encodeURIComponent(this.translator[0].lastUpdated) +
"&diagnostic=" + encodeURIComponent(Zotero.getSystemInfo()) +
"&errorData=" + encodeURIComponent(errorData);
Zotero.Utilities.HTTP.doPost("http://www.zotero.org/repo/report", postBody);
}
}
/*
* closes open file streams, if any exist
*/
Zotero.Translate.prototype._closeStreams = function() {
// serialize RDF and unregister dataSource
if(this._rdf) {
if(this._rdf.serializer) {
this._rdf.serializer.Serialize(this._streams[0]);
}
try {
if(this._rdf.dataSource) {
var rdfService = Components.classes["@mozilla.org/rdf/rdf-service;1"].
getService(Components.interfaces.nsIRDFService);
rdfService.UnregisterDataSource(this._rdf.dataSource);
}
} catch(e) {}
delete this._rdf.dataSource;
}
if(this._streams.length) {
for(var i in this._streams) {
var stream = this._streams[i];
// stream could be either an input stream or an output stream
try {
stream.QueryInterface(Components.interfaces.nsIFileInputStream);
} catch(e) {
try {
stream.QueryInterface(Components.interfaces.nsIFileOutputStream);
} catch(e) {
}
}
// encase close in try block, because it's possible it's already
// closed
try {
stream.close();
} catch(e) {
}
}
}
delete this._streams;
this._streams = new Array();
this._inputStream = null;
}
/*
* handles tags and see also data for notes and attachments
*/
Zotero.Translate.prototype._itemTagsAndSeeAlso = function(item, newItem) {
// add to ID map
if(item.itemID) {
this._IDMap[item.itemID] = newItem.id;
}
// add see alsos
if(item.seeAlso) {
for each(var seeAlso in item.seeAlso) {
if(this._IDMap[seeAlso]) {
newItem.addRelatedItem(this._IDMap[seeAlso]);
}
}
newItem.save();
}
if(item.tags) {
var tagsToAdd = {};
tagsToAdd[0] = []; // user tags
tagsToAdd[1] = []; // automatic tags
for each(var tag in item.tags) {
if(typeof(tag) == "string") {
// accept strings in tag array as automatic tags, or, if
// importing, as non-automatic tags
if (this.type == 'import') {
tagsToAdd[0].push(tag);
}
else {
tagsToAdd[1].push(tag);
}
} else if(typeof(tag) == "object") {
// also accept objects
if(tag.tag) {
if (!tagsToAdd[tag.type]) {
tagsToAdd[tag.type] = [];
}
tagsToAdd[tag.type].push(tag.tag);
}
}
}
for (var type in tagsToAdd) {
if (tagsToAdd[type].length) {
newItem.addTags(tagsToAdd[type], type);
}
}
}
}
/*
* executed when an item is done and ready to be loaded into the database
*/
Zotero.Translate._urlRe = /(([A-Za-z]+):\/\/[^\s]*)/i;
Zotero.Translate.prototype._itemDone = function(item, attachedTo) {
if(this.type == "web") {
// store repository if this item was captured from a website, and
// repository is truly undefined (not false or "")
if(!item.repository && item.repository !== false && item.repository !== "") {
item.repository = this.translator[0].label;
}
}
this._itemsDone = true;
if(!this.saveItem) { // if we're not supposed to save the item, just
// return the item array
// if a parent sandbox exists, use complete() function from that sandbox
if(this._parentTranslator) {
var pt = this._parentTranslator;
item.complete = function() { pt._itemDone(this) };
Zotero.debug("Translate: Calling done from parent sandbox", 4);
}
this.runHandler("itemDone", item);
return;
}
// Get typeID, defaulting to "webpage"
var type = (item.itemType ? item.itemType : "webpage");
if(type == "note") { // handle notes differently
var item = new Zotero.Item(false, 'note');
item.setNote(item.note);
var myID = item.save();
// re-retrieve the item
var newItem = Zotero.Items.get(myID);
} else {
if(!item.title && this.type == "web") {
throw("item has no title");
}
// if item was accessed through a proxy, ensure that the proxy
// address remains in the accessed version
if(this.locationIsProxied && item.url) {
item.url = Zotero.Proxies.properToProxy(item.url);
}
// create new item
if(type == "attachment") {
if(this.type != "import") {
Zotero.debug("Translate: Discarding standalone attachment", 2);
return;
}
Zotero.debug("Translate: Adding attachment", 4);
if(!item.url && !item.path) {
Zotero.debug("Translate: Ignoring attachment: no path or URL specified", 2);
return;
}
if(!item.path) {
// see if this is actually a file URL
var m = Zotero.Translate._urlRe.exec(item.url);
var protocol = m ? m[2].toLowerCase() : "";
if(protocol == "file") {
item.path = item.url;
item.url = false;
} else if(protocol != "http" && protocol != "https") {
Zotero.debug("Translate: Unrecognized protocol "+protocol, 2);
return;
}
}
if(!item.path) {
// create from URL
try {
var myID = Zotero.Attachments.linkFromURL(item.url, attachedTo,
(item.mimeType ? item.mimeType : undefined),
(item.title ? item.title : undefined));
} catch(e) {
Zotero.debug("Translate: Error adding attachment "+item.url, 2);
return;
}
Zotero.debug("Translate: Created attachment; id is "+myID, 4);
var newItem = Zotero.Items.get(myID);
} else {
// generate nsIFile
var IOService = Components.classes["@mozilla.org/network/io-service;1"].
getService(Components.interfaces.nsIIOService);
var uri = IOService.newURI(item.path, "", null);
var file = uri.QueryInterface(Components.interfaces.nsIFileURL).file;
if (!file.exists()) {
// use item title if possible, or else file leaf name
var title = item.title;
if(!title) title = file.leafName;
var myID = Zotero.Attachments.createMissingAttachment(
item.url ? Zotero.Attachments.LINK_MODE_IMPORTED_URL
: Zotero.Attachments.LINK_MODE_IMPORTED_FILE,
file, item.url ? item.url : null, title,
item.mimeType, item.charset, attachedTo);
}
else if (item.url) {
var myID = Zotero.Attachments.importSnapshotFromFile(file,
item.url, item.title, item.mimeType, item.charset,
attachedTo);
}
else {
var myID = Zotero.Attachments.importFromFile(file, attachedTo);
}
}
var typeID = Zotero.ItemTypes.getID("attachment");
var newItem = Zotero.Items.get(myID);
// add note if necessary
if(item.note) {
newItem.setNote(item.note);
}
} else {
var typeID = Zotero.ItemTypes.getID(type);
var newItem = new Zotero.Item(false, typeID);
}
// makes looping through easier
item.itemType = item.complete = undefined;
// automatically set access date if URL is set
if(item.url && !item.accessDate && this.type == "web") {
item.accessDate = "CURRENT_TIMESTAMP";
}
var fieldID, data;
for(var field in item) {
// loop through item fields
data = item[field];
if(data) { // if field has content
if(field == "creators") { // creators are a special case
for(var j in data) {
// try to assign correct creator type
if(data[j].creatorType) {
try {
var creatorType = Zotero.CreatorTypes.getID(data[j].creatorType);
} catch(e) {
Zotero.debug("Translate: Invalid creator type "+data[j].creatorType+" for creator index "+j, 2);
}
}
if(!creatorTypeID) {
var creatorTypeID = 1;
}
var fields = {
firstName: data[j].firstName,
lastName: data[j].lastName
};
var creatorDataID = Zotero.Creators.getDataID(fields);
if(creatorDataID) {
var linkedCreators = Zotero.Creators.getCreatorsWithData(creatorDataID);
// TODO: support identical creators via popup? ugh...
var creatorID = linkedCreators[0];
var creator = Zotero.Creators.get(creatorID);
} else {
var creator = new Zotero.Creator;
creator.setFields(fields);
var creatorID = creator.save();
}
newItem.setCreator(j, creator, creatorTypeID);
}
} else if(field == "seeAlso") {
newItem.translateSeeAlso = data;
} else if(field != "note" && field != "notes" && field != "itemID" &&
field != "attachments" && field != "tags" &&
(fieldID = Zotero.ItemFields.getID(field))) {
// if field is in db
// try to map from base field
if(Zotero.ItemFields.isBaseField(fieldID)) {
var fieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(typeID, fieldID);
if(fieldID) Zotero.debug("Translate: Mapping "+field+" to "+Zotero.ItemFields.getName(fieldID), 5);
}
// if field is valid for this type, set field
if(fieldID && Zotero.ItemFields.isValidForType(fieldID, typeID)) {
newItem.setField(fieldID, data);
} else {
Zotero.debug("Translate: Discarded field "+field+" for item: field not valid for type "+type, 3);
}
}
}
}
// create short title
if(this.type == "web" &&
item.shortTitle === undefined &&
Zotero.ItemFields.isValidForType("shortTitle", typeID)) {
// get field id
var fieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(typeID, "title");
// get title
var title = newItem.getField(fieldID);
if(title) {
// only set if changes have been made
var set = false;
// shorten to before first colon
var index = title.indexOf(":");
if(index !== -1) {
title = title.substr(0, index);
set = true;
}
// shorten to after first question mark
index = title.indexOf("?");
if(index !== -1) {
index++;
if(index != title.length) {
title = title.substr(0, index);
set = true;
}
}
if(set) newItem.setField("shortTitle", title);
}
}
// save item
if(myID) {
newItem.save();
} else {
var myID = newItem.save();
newItem = Zotero.Items.get(myID);
}
// handle notes
if(item.notes) {
for each(var note in item.notes) {
var item = new Zotero.Item(false, 'note');
item.setNote(note.note);
if (myID) {
item.setSource(myID);
}
var noteID = item.save();
// handle see also
var myNote = Zotero.Items.get(noteID);
this._itemTagsAndSeeAlso(note, myNote);
}
}
var automaticSnapshots = Zotero.Prefs.get("automaticSnapshots");
var downloadAssociatedFiles = Zotero.Prefs.get("downloadAssociatedFiles");
// handle attachments
if(item.attachments && this.saveAttachments &&
// DEBUG: is "this.type == 'import'" still necessary with this.saveAttachments?
(this.type == 'import' || automaticSnapshots || downloadAssociatedFiles)) {
for each(var attachment in item.attachments) {
if(this.type == "web") {
if(!attachment.url && !attachment.document) {
Zotero.debug("Translate: Not adding attachment: no URL specified", 2);
} else {
if(attachment.snapshot === false) {
if(!automaticSnapshots) {
continue;
}
// if snapshot is explicitly set to false, attach as link
if(attachment.document) {
Zotero.Attachments.linkFromURL(attachment.document.location.href, myID,
(attachment.mimeType ? attachment.mimeType : attachment.document.contentType),
(attachment.title ? attachment.title : attachment.document.title));
} else {
if(!attachment.mimeType || !attachment.title) {
Zotero.debug("Translate: Either mimeType or title is missing; attaching file will be slower", 3);
}
try {
Zotero.Attachments.linkFromURL(attachment.url, myID,
(attachment.mimeType ? attachment.mimeType : undefined),
(attachment.title ? attachment.title : undefined));
} catch(e) {
Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
}
}
} else if(attachment.document
|| (attachment.mimeType && attachment.mimeType == "text/html")
|| downloadAssociatedFiles) {
// if snapshot is not explicitly set to false, retrieve snapshot
if(attachment.document) {
try {
Zotero.Attachments.importFromDocument(attachment.document, myID, attachment.title);
} catch(e) {
Zotero.debug("Translate: Error attaching document", 2);
}
// Save attachment if snapshot pref enabled or not HTML
// (in which case downloadAssociatedFiles applies)
} else if(automaticSnapshots || !attachment.mimeType
|| attachment.mimeType != "text/html") {
var mimeType = null;
var title = null;
if(attachment.mimeType) {
// first, try to extract mime type from mimeType attribute
mimeType = attachment.mimeType;
} else if(attachment.document && attachment.document.contentType) {
// if that fails, use document if possible
mimeType = attachment.document.contentType
}
// same procedure for title as mime type
if(attachment.title) {
title = attachment.title;
} else if(attachment.document && attachment.document.title) {
title = attachment.document.title;
}
if(this.locationIsProxied) {
attachment.url = Zotero.Proxies.properToProxy(attachment.url);
}
var fileBaseName = Zotero.Attachments.getFileBaseNameFromItem(myID);
try {
Zotero.Attachments.importFromURL(attachment.url, myID, title, fileBaseName);
} catch(e) {
Zotero.debug("Translate: Error adding attachment "+attachment.url, 2);
}
}
}
}
} else if(this.type == "import") {
// create new attachments attached here
attachment.itemType = 'attachment';
this._itemDone(attachment, myID);
}
}
}
}
if(item.itemID) {
this._IDMap[item.itemID] = myID;
}
if(!attachedTo) {
this.newItems.push(myID);
}
// handle see also
if(item.seeAlso) {
for each(var seeAlso in item.seeAlso) {
if(this._IDMap[seeAlso]) {
newItem.addRelatedItem(this._IDMap[seeAlso]);
}
}
newItem.save();
}
// handle tags, if this is an import translation or automatic tagging is
// enabled in the preferences (as it is by default)
if(item.tags &&
(this.type == "import" || Zotero.Prefs.get("automaticTags"))) {
var tagsToAdd = {};
tagsToAdd[0] = []; // user tags
tagsToAdd[1] = []; // automatic tags
for each(var tag in item.tags) {
if(typeof(tag) == "string") {
// accept strings in tag array as automatic tags, or, if
// importing, as non-automatic tags
if (this.type == 'import') {
tagsToAdd[0].push(tag);
}
else {
tagsToAdd[1].push(tag);
}
} else if(typeof(tag) == "object") {
// also accept objects
if(tag.tag) {
if (this.type == "import") {
if (!tagsToAdd[tag.type]) {
tagsToAdd[tag.type] = [];
}
tagsToAdd[tag.type].push(tag.tag);
}
// force web imports to automatic
else {
tagsToAdd[1].push(tag.tag);
}
}
}
}
for (var type in tagsToAdd) {
if (tagsToAdd[type].length) {
newItem.addTags(tagsToAdd[type], type);
}
}
}
if(!attachedTo) {
// Re-retrieve item before passing to handler
newItem = Zotero.Items.get(newItem.id);
this.runHandler("itemDone", newItem);
}
delete item;
}
/*
* executed when a collection is done and ready to be loaded into the database
*/
Zotero.Translate.prototype._collectionDone = function(collection) {
var newCollection = this._processCollection(collection, null);
this.runHandler("collectionDone", newCollection);
}
/*
* recursively processes collections
*/
Zotero.Translate.prototype._processCollection = function(collection, parentID) {
var newCollection = Zotero.Collections.add(collection.name, parentID);
var myID = newCollection.id;
this.newCollections.push(myID);
for each(child in collection.children) {
if(child.type == "collection") {
// do recursive processing of collections
this._processCollection(child, myID);
} else {
// add mapped items to collection
if(this._IDMap[child.id]) {
Zotero.debug("Translate: Adding "+this._IDMap[child.id], 5);
newCollection.addItem(this._IDMap[child.id]);
} else {
Zotero.debug("Translate: Could not map "+child.id+" to an imported item", 2);
}
}
}
return newCollection;
}
/*
* logs a debugging message
*/
Zotero.Translate.prototype._debug = function(string, level) {
// if handler does not return anything explicitly false, show debug
// message in console
if(this.runHandler("debug", string) !== false) {
if(typeof string == "string") string = "Translate: "+string;
Zotero.debug(string, level);
}
}
/*
* does the actual web translation
*/
Zotero.Translate.prototype._web = function() {
try {
this._sandbox.doWeb(this.document, this.location);
} catch(e) {
if(this._parentTranslator) {
throw(e);
} else {
this._translationComplete(false, e);
return false;
}
}
return true;
}
/**
* Does the actual search translation
**/
Zotero.Translate.prototype._search = function() {
try {
this._sandbox.doSearch(this.search);
} catch(e) {
this._translationComplete(false, e);
return false;
}
return true;
}
/**
* Does the actual import translation
**/
Zotero.Translate.prototype._import = function() {
this.waitForCompletion = true;
this._importSniffCharacterSet();
}
/**
* Sniff file for its character set, then proceed with the rest of translation
*/
Zotero.Translate.prototype._importSniffCharacterSet = function(callback) {
if(!this._storage) {
if(this._charset) {
// have charset already; just go on
this._importDoneSniffing(this._charset);
} else {
// need to check charset
importCharset = Zotero.Prefs.get("import.charset");
if(importCharset == "auto") {
// look for charset
var me = this;
Zotero.File.getCharsetFromFile(this.location, "text/plain",
function(charset) {
me._charset = charset;
me._importDoneSniffing(charset);
});
} else {
this._importDoneSniffing(importCharset);
}
}
} else {
this._importDoneSniffing();
}
}
/**
* Complete import (used as callback after sniffing)
**/
Zotero.Translate.prototype._importDoneSniffing = function(charset) {
this._importConfigureIO(charset);
try {
this._sandbox.doImport();
} catch(e) {
if(this._parentTranslator) {
throw(e);
} else {
this._translationComplete(false, e);
return false;
}
}
this._translationComplete(true);
}
/*
* set up import for IO
*/
Zotero.Translate.prototype._importConfigureIO = function(charset) {
if(this._storage) {
if(this.configOptions.dataMode && this.configOptions.dataMode == "rdf") {
this._rdf = new Object();
// read string out of storage stream
var IOService = Components.classes['@mozilla.org/network/io-service;1']
.getService(Components.interfaces.nsIIOService);
this._rdf.dataSource = Components.classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].
createInstance(Components.interfaces.nsIRDFDataSource);
var parser = Components.classes["@mozilla.org/rdf/xml-parser;1"].
createInstance(Components.interfaces.nsIRDFXMLParser);
// get URI and parse
var baseURI = (this.location ? IOService.newURI(this.location, "utf-8", null) : null);
parser.parseString(this._rdf.dataSource, baseURI, this._storage);
// make an instance of the RDF handler
this._sandbox.Zotero.RDF = new Zotero.Translate.RDF(this._rdf.dataSource);
} else {
this._storageFunctions(true);
this._storagePointer = 0;
}
} else {
var me = this;
if(this.configOptions.dataMode && this.configOptions.dataMode == "rdf") {
if(!this._rdf) {
this._rdf = new Object()
var IOService = Components.classes['@mozilla.org/network/io-service;1']
.getService(Components.interfaces.nsIIOService);
var fileHandler = IOService.getProtocolHandler("file")
.QueryInterface(Components.interfaces.nsIFileProtocolHandler);
var URL = fileHandler.getURLSpecFromFile(this.location);
var RDFService = Components.classes['@mozilla.org/rdf/rdf-service;1']
.getService(Components.interfaces.nsIRDFService);
this._rdf.dataSource = RDFService.GetDataSourceBlocking(URL);
// make an instance of the RDF handler
this._sandbox.Zotero.RDF = new Zotero.Translate.RDF(this._rdf.dataSource);
}
} else {
// open file and set read methods
if(this._inputStream) {
this._inputStream.QueryInterface(Components.interfaces.nsISeekableStream)
.seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, 0);
this._inputStream.QueryInterface(Components.interfaces.nsIFileInputStream);
} else {
this._inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
.createInstance(Components.interfaces.nsIFileInputStream);
this._inputStream.init(this.location, 0x01, 0664, 0);
this._streams.push(this._inputStream);
}
var bomLength = 0;
if(charset === undefined || (charset && charset.length > 3 && charset.substr(0, 3) == "UTF")) {
// seek past BOM
var bomCharset = this._importGetBOM();
var bomLength = (bomCharset ? BOMs[bomCharset].length : 0);
this._inputStream.QueryInterface(Components.interfaces.nsISeekableStream)
.seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, bomLength);
if(bomCharset) charset = this._charset = bomCharset;
}
var intlStream = null;
if(charset) {
// if have detected charset
Zotero.debug("Translate: Using detected character set "+charset, 3);
// convert from detected charset
intlStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
.createInstance(Components.interfaces.nsIConverterInputStream);
intlStream.init(this._inputStream, charset, 65535,
Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
me._streams.push(intlStream);
}
// allow translator to set charset
this._sandbox.Zotero.setCharacterSet = function(charset) {
// seek back to the beginning
me._inputStream.QueryInterface(Components.interfaces.nsISeekableStream)
.seek(Components.interfaces.nsISeekableStream.NS_SEEK_SET, bomLength);
intlStream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
.createInstance(Components.interfaces.nsIConverterInputStream);
try {
intlStream.init(me._inputStream, charset, 65535,
Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
} catch(e) {
throw "Text encoding not supported";
}
me._streams.push(intlStream);
}
var str = new Object();
if(this.configOptions.dataMode && this.configOptions.dataMode == "line") { // line by line reading
this._inputStream.QueryInterface(Components.interfaces.nsILineInputStream);
this._sandbox.Zotero.read = function() {
if(intlStream && intlStream instanceof Components.interfaces.nsIUnicharLineInputStream) {
var amountRead = intlStream.readLine(str);
} else {
var amountRead = me._inputStream.readLine(str);
}
if(amountRead) {
return str.value;
} else {
return false;
}
}
} else { // block reading
var sStream;
this._sandbox.Zotero.read = function(amount) {
if(intlStream) {
// read from international stream, if one is available
var amountRead = intlStream.readString(amount, str);
if(amountRead) {
return str.value;
} else {
return false;
}
} else {
// allocate sStream on the fly
if(!sStream) {
sStream = Components.classes["@mozilla.org/scriptableinputstream;1"]
.createInstance(Components.interfaces.nsIScriptableInputStream);
sStream.init(me._inputStream);
}
// read from the scriptable input stream
var string = sStream.read(amount);
return string;
}
}
// attach sStream to stack of streams to close
this._streams.push(sStream);
}
}
}
}
/**
* Searches for a UTF BOM at the beginning of the input stream.
*
* @return The length of the UTF BOM.
*/
Zotero.Translate.prototype._importGetBOM = function() {
// if not checked for a BOM, open a binary input stream and read
var binStream = Components.classes["@mozilla.org/binaryinputstream;1"].
createInstance(Components.interfaces.nsIBinaryInputStream);
binStream.setInputStream(this._inputStream);
var possibleBOMs = BOMs;
var couldHaveBOM = true;
var newBOMs, readByte;
while(couldHaveBOM) {
newBOMs = {};
couldHaveBOM = false;
readByte = binStream.read8();
readChar = String.fromCharCode(readByte)
for(var charset in possibleBOMs) {
if(possibleBOMs[charset][0] == readChar) {
if(possibleBOMs[charset].length == 1) {
// have checked entire BOM
return charset;
} else {
// keep checking
newBOMs[charset] = possibleBOMs[charset].substr(1);
couldHaveBOM = true;
}
}
}
possibleBOMs = newBOMs;
}
return null;
}
/**
* Does the actual export, after code has been loaded and parsed
*/
Zotero.Translate.prototype._export = function() {
if(this.items) {
// if just items, get them and don't worry about collection logic
this._itemsLeft = this.items;
} else if(this.collection) {
// get items in this collection
this._itemsLeft = this.collection.getChildItems();
if(!this._itemsLeft) {
this._itemsLeft = [];
}
if(this.configOptions.getCollections) {
// get child collections
this._collectionsLeft = Zotero.getCollections(this.collection.id, true);
if(this._collectionsLeft.length) {
// only include parent collection if there are actually children
this._collectionsLeft.unshift(this.collection);
}
// get items in child collections
for each(var collection in this._collectionsLeft) {
var childItems = collection.getChildItems();
if(childItems) {
this._itemsLeft = this._itemsLeft.concat(childItems);
}
}
}
} else {
// get all top-level items
this._itemsLeft = Zotero.Items.getAll(true);
if(this.configOptions.getCollections) {
// get all collections
this._collectionsLeft = Zotero.getCollections();
}
}
// export file data, if requested
if(this.displayOptions["exportFileData"]) {
// generate directory
var directory = Components.classes["@mozilla.org/file/local;1"].
createInstance(Components.interfaces.nsILocalFile);
directory.initWithFile(this.location.parent);
// delete this file if it exists
if(this.location.exists()) {
this.location.remove(false);
}
// get name
var name = this.location.leafName;
directory.append(name);
// create directory
directory.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0700);
// generate a new location for the exported file, with the appropriate
// extension
this.location = Components.classes["@mozilla.org/file/local;1"].
createInstance(Components.interfaces.nsILocalFile);
this.location.initWithFile(directory);
this.location.append(name+"."+this.translator[0].target);
// create files directory
this._exportFileDirectory = Components.classes["@mozilla.org/file/local;1"].
createInstance(Components.interfaces.nsILocalFile);
this._exportFileDirectory.initWithFile(directory);
this._exportFileDirectory.append("files");
this._exportFileDirectory.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0700);
}
// configure IO
this._exportConfigureIO();
try {
this._sandbox.doExport();
} catch(e) {
this._translationComplete(false, e);
return false;
}
return true;
}
/**
* Configures the output stream for export and adds writing functions to the
* sandbox
*/
Zotero.Translate.prototype._exportConfigureIO = function() {
if(this.location) {
// open file
var fStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
.createInstance(Components.interfaces.nsIFileOutputStream);
fStream.init(this.location, 0x02 | 0x08 | 0x20, 0664, 0); // write, create, truncate
// attach to stack of streams to close at the end
this._streams.push(fStream);
if(this.configOptions.dataMode && this.configOptions.dataMode == "rdf") { // rdf io
this._rdf = new Object();
// create data source
this._rdf.dataSource = Components.classes["@mozilla.org/rdf/datasource;1?name=xml-datasource"].
createInstance(Components.interfaces.nsIRDFDataSource);
// create serializer
this._rdf.serializer = Components.classes["@mozilla.org/rdf/xml-serializer;1"].
createInstance(Components.interfaces.nsIRDFXMLSerializer);
this._rdf.serializer.init(this._rdf.dataSource);
this._rdf.serializer.QueryInterface(Components.interfaces.nsIRDFXMLSource);
// make an instance of the RDF handler
this._sandbox.Zotero.RDF = new Zotero.Translate.RDF(this._rdf.dataSource, this._rdf.serializer);
} else {
// regular io; write just writes to file
var intlStream = null;
var writtenToStream = false;
var streamCharset = null;
// allow setting of character sets
this._sandbox.Zotero.setCharacterSet = function(charset) {
streamCharset = charset.toUpperCase();
intlStream = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
.createInstance(Components.interfaces.nsIConverterOutputStream);
if(charset == "UTF-8xBOM") charset = "UTF-8";
intlStream.init(fStream, charset, 1024, "?".charCodeAt(0));
};
// if exportCharset option was presented to user, use the result
if(this.displayOptions.exportCharset) {
this._sandbox.Zotero.setCharacterSet(this.displayOptions.exportCharset);
}
this._sandbox.Zotero.write = function(data) {
if(streamCharset) {
if(!writtenToStream && BOMs[streamCharset]) {
// If stream has not yet been written to, and a UTF type
// has been selected, write the BOM
fStream.write(BOMs[streamCharset], BOMs[streamCharset].length);
}
if(streamCharset == "MACINTOSH") {
// fix buggy Mozilla MacRoman
splitData = data.split(/([\r\n]+)/);
for(var i=0; i<splitData.length; i+=2) {
// write raw newlines straight to the string
intlStream.writeString(splitData[i]);
if(splitData[i+1]) {
fStream.write(splitData[i+1], splitData[i+1].length);
}
}
return;
} else {
intlStream.writeString(data);
}
} else {
fStream.write(data, data.length);
}
writtenToStream = true;
};
}
} else {
var me = this;
this.output = "";
// dummy setCharacterSet function
this._sandbox.Zotero.setCharacterSet = function() {};
// write to string
this._sandbox.Zotero.write = function(data) {
me.output += data;
};
}
}
/*
* copies attachment and returns data, given an attachment object
*/
Zotero.Translate.prototype._exportGetAttachment = function(attachment) {
var attachmentArray = this._exportToArray(attachment);
var linkMode = attachment.getAttachmentLinkMode();
// get mime type
attachmentArray.mimeType = attachmentArray.uniqueFields.mimeType = attachment.getAttachmentMIMEType();
// get charset
attachmentArray.charset = attachmentArray.uniqueFields.charset = attachment.getAttachmentCharset();
if(linkMode != Zotero.Attachments.LINK_MODE_LINKED_URL &&
this.displayOptions["exportFileData"]) {
// add path and filename if not an internet link
var file = attachment.getFile();
attachmentArray.path = "files/"+attachmentArray.itemID+"/"+file.leafName;
if(linkMode == Zotero.Attachments.LINK_MODE_LINKED_FILE) {
// create a new directory
var directory = Components.classes["@mozilla.org/file/local;1"].
createInstance(Components.interfaces.nsILocalFile);
directory.initWithFile(this._exportFileDirectory);
directory.append(attachmentArray.itemID);
directory.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0700);
// copy file
try {
file.copyTo(directory, attachmentArray.filename);
} catch(e) {
attachmentArray.path = undefined;
}
} else {
// copy imported files from the Zotero directory
var directory = Zotero.getStorageDirectory();
directory.append(attachmentArray.itemID);
try {
directory.copyTo(this._exportFileDirectory, attachmentArray.itemID);
} catch(e) {
attachmentArray.path = undefined;
}
}
}
attachmentArray.itemType = "attachment";
return attachmentArray;
}
/*
* gets the next item to process (called as Zotero.nextItem() from code)
*/
Zotero.Translate.prototype._exportGetItem = function() {
if(this._itemsLeft.length != 0) {
var returnItem = this._itemsLeft.shift();
// export file data for single files
if(returnItem.isAttachment()) { // an independent attachment
var returnItemArray = this._exportGetAttachment(returnItem);
if(returnItemArray) {
return returnItemArray;
} else {
return this._exportGetItem();
}
} else {
returnItemArray = this._exportToArray(returnItem);
// get attachments, although only urls will be passed if exportFileData
// is off
returnItemArray.attachments = new Array();
var attachments = returnItem.getAttachments();
for each(var attachmentID in attachments) {
var attachment = Zotero.Items.get(attachmentID);
var attachmentInfo = this._exportGetAttachment(attachment);
if(attachmentInfo) {
returnItemArray.attachments.push(attachmentInfo);
}
}
}
this.runHandler("itemDone", returnItem);
return returnItemArray;
}
return false;
}
Zotero.Translate.prototype._exportToArray = function(returnItem) {
var returnItemArray = returnItem.toArray();
// Remove SQL date from multipart dates
if (returnItemArray.date) {
returnItemArray.date = Zotero.Date.multipartToStr(returnItemArray.date);
}
returnItemArray.uniqueFields = new Object();
// get base fields, not just the type-specific ones
var itemTypeID = returnItem.itemTypeID;
var allFields = Zotero.ItemFields.getItemTypeFields(itemTypeID);
for each(var field in allFields) {
var fieldName = Zotero.ItemFields.getName(field);
if(returnItemArray[fieldName] !== undefined) {
var baseField = Zotero.ItemFields.getBaseIDFromTypeAndField(itemTypeID, field);
if(baseField && baseField != field) {
var baseName = Zotero.ItemFields.getName(baseField);
if(baseName) {
returnItemArray[baseName] = returnItemArray[fieldName];
returnItemArray.uniqueFields[baseName] = returnItemArray[fieldName];
} else {
returnItemArray.uniqueFields[fieldName] = returnItemArray[fieldName];
}
} else {
returnItemArray.uniqueFields[fieldName] = returnItemArray[fieldName];
}
}
}
// preserve notes
if(returnItemArray.note) returnItemArray.uniqueFields.note = returnItemArray.note;
// TODO: Change tag.tag references in scrapers.sql to tag.name
// once translators are 1.5-only
if (returnItemArray.tags) {
for (var i in returnItemArray.tags) {
returnItemArray.tags[i].tag = returnItemArray.tags[i].name;
}
}
return returnItemArray;
}
/*
* gets the next item to collection (called as Zotero.nextCollection() from code)
*/
Zotero.Translate.prototype._exportGetCollection = function() {
if(!this.configOptions.getCollections) {
throw("getCollections configure option not set; cannot retrieve collection");
}
if(this._collectionsLeft && this._collectionsLeft.length != 0) {
var returnItem = this._collectionsLeft.shift();
return returnItem.toArray();
}
}
/*
* sets up internal IO in such a way that both reading and writing are possible
* (for inter-scraper communications)
*/
Zotero.Translate.prototype._initializeInternalIO = function() {
if(this.type == "import" || this.type == "export") {
if(this.configOptions.dataMode && this.configOptions.dataMode == "rdf") {
this._rdf = new Object();
// use an in-memory data source for internal IO
this._rdf.dataSource = Components.classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"].
createInstance(Components.interfaces.nsIRDFDataSource);
// make an instance of the RDF handler
this._sandbox.Zotero.RDF = new Zotero.Translate.RDF(this._rdf.dataSource);
} else {
this._storage = "";
this._storageLength = 0;
this._storagePointer = 0;
this._storageFunctions(true, true);
}
}
}
/*
* sets up functions for reading/writing to a storage stream
*/
Zotero.Translate.prototype._storageFunctions = function(read, write) {
var me = this;
// add setCharacterSet method that does nothing
this._sandbox.Zotero.setCharacterSet = function() {}
if(write) {
// set up write() method
this._sandbox.Zotero.write = function(data) {
me._storage += data;
me._storageLength += data.length;
};
}
if(read) {
// set up read methods
if(this.configOptions.dataMode && this.configOptions.dataMode == "line") { // line by line reading
var lastCharacter;
this._sandbox.Zotero.read = function() {
if(me._storagePointer >= me._storageLength) {
return false;
}
var oldPointer = me._storagePointer;
var lfIndex = me._storage.indexOf("\n", me._storagePointer);
if(lfIndex != -1) {
// in case we have a CRLF
me._storagePointer = lfIndex+1;
if(me._storageLength > lfIndex && me._storage[lfIndex-1] == "\r") {
lfIndex--;
}
return me._storage.substr(oldPointer, lfIndex-oldPointer);
}
var crIndex = me._storage.indexOf("\r", me._storagePointer);
if(crIndex != -1) {
me._storagePointer = crIndex+1;
return me._storage.substr(oldPointer, crIndex-oldPointer-1);
}
me._storagePointer = me._storageLength;
return me._storage.substr(oldPointer);
}
} else { // block reading
this._sandbox.Zotero.read = function(amount) {
if(me._storagePointer >= me._storageLength) {
return false;
}
if((me._storagePointer+amount) > me._storageLength) {
var oldPointer = me._storagePointer;
me._storagePointer = me._storageLength+1;
return me._storage.substr(oldPointer);
}
var oldPointer = me._storagePointer;
me._storagePointer += amount;
return me._storage.substr(oldPointer, amount);
}
}
}
}
/* Zotero.Translate.ZoteroItem: a class to perform recursive translator searches
* by waiting for completion of each translator
*/
Zotero.Translate.TranslatorSearch = function(translate, translators) {
// generate a copy of the translator search array
this.translate = translate;
this.allTranslators = translators;
this.translators = this.allTranslators.slice(0);
this.foundTranslators = new Array();
this.ignoreExtensions = false;
this.asyncMode = false;
this.running = true;
this.execute();
}
/*
* Check to see if a list of translators can scrape the page passed to the
* translate() instance
*/
Zotero.Translate.TranslatorSearch.prototype.execute = function() {
if(!this.running) return;
if(this.checkDone()) return;
// get next translator
var translator = this.translators.shift();
if((this.translate.type == "import" || this.translate.type == "web") && !this.translate.location && !this.translate._storage) {
// if no location yet (e.g., getting list of possible web translators),
// just return true
this.addTranslator(translator);
this.execute();
return;
}
// Test location with regular expression
var checkDetectCode = true;
if(translator.target && (this.translate.type == "import" || this.translate.type == "web")) {
var checkDetectCode = false;
if(this.translate.type == "web") {
var regularExpression = translator.webRegexp;
} else {
var regularExpression = translator.importRegexp;
}
if(regularExpression.test(this.translate.path)) {
checkDetectCode = true;
}
if(this.ignoreExtensions) {
// if we're ignoring extensions, that means we already tried
// everything without ignoring extensions and it didn't work
checkDetectCode = !checkDetectCode;
}
}
// Test with JavaScript if available and didn't have a regular expression or
// passed regular expression test
if(checkDetectCode) {
// parse the detect code and execute
this.translate._parseCode(translator);
if(this.translate.type == "import") {
var me = this;
try {
this.translate._importConfigureIO(); // so it can read
} catch(e) {
Zotero.debug("Translate: "+e+' in opening IO for '+translator.label);
this.execute();
return;
}
}
this.runDetectCode(translator);
} else {
this.execute();
}
}
/**
* Sets options and runs detectCode
**/
Zotero.Translate.TranslatorSearch.prototype.runDetectCode = function(translator) {
translator.configOptions = this.translate.configOptions;
translator.displayOptions = this.translate.displayOptions;
if((this.translate.type == "web" && this.translate._sandbox.detectWeb) ||
(this.translate.type == "search" && this.translate._sandbox.detectSearch) ||
(this.translate.type == "import" && this.translate._sandbox.detectImport)) {
var returnValue;
this.currentTranslator = translator;
try {
if(this.translate.type == "web") {
returnValue = this.translate._sandbox.detectWeb(this.translate.document, this.translate.location);
} else if(this.translate.type == "search") {
returnValue = this.translate._sandbox.detectSearch(this.translate.search);
} else if(this.translate.type == "import") {
returnValue = this.translate._sandbox.detectImport();
}
} catch(e) {
this.complete(returnValue, e);
return;
}
Zotero.debug("Translate: Executed detectCode for "+translator.label, 4);
if(this.translate.type == "web" && this.translate.waitForCompletion) {
this.asyncMode = true;
// don't immediately execute next
return;
} else if(returnValue) {
this.processReturnValue(translator, returnValue);
}
} else {
// add translator even though it has no proper detectCode (usually
// export translators, which have options but do nothing with them)
this.addTranslator(translator);
}
this.execute();
}
/*
* Determines whether all translators have been processed
*/
Zotero.Translate.TranslatorSearch.prototype.checkDone = function() {
if(this.translators.length == 0) {
// if we've gone through all of the translators, trigger the handler
if(this.foundTranslators.length) {
this.translate.runHandler("translators", this.foundTranslators);
return true;
} else if(this.translate.type == "import" && !this.ignoreExtensions) {
// if we fail the first time finding an import translator, search
// again, but ignore extensions
this.ignoreExtensions = true;
this.translators = this.allTranslators.slice(0);
} else {
this.translate.runHandler("translators", false);
return true;
}
}
return false;
}
/*
* Processes the return value from a translator
*/
Zotero.Translate.TranslatorSearch.prototype.processReturnValue = function(translator, returnValue) {
Zotero.debug("Translate: Found translator "+translator.label, 3);
if(typeof(returnValue) == "string") {
translator.itemType = returnValue;
}
this.addTranslator(translator);
}
/*
* Called upon completion of asynchronous translator search
*/
Zotero.Translate.TranslatorSearch.prototype.complete = function(returnValue, error) {
// reset done function
this.translate._sandbox.Zotero.done = undefined;
this.translate.waitForCompletion = false;
if(returnValue) {
this.processReturnValue(this.currentTranslator, returnValue);
} else if(error) {
var errorString = this.translate._generateErrorString(error);
this.currentTranslator.logError(error, "warning");
this.translate._debug("detectCode for "+(this.currentTranslator ? this.currentTranslator.label : "no translator")+" failed: \n"+errorString, 4);
}
this.currentTranslator = undefined;
this.asyncMode = false;
// resume execution
this.execute();
}
/*
* Copies a translator to the foundTranslators list
*/
Zotero.Translate.TranslatorSearch.prototype.addTranslator = function(translator) {
var newTranslator = new Object();
for(var i in translator) {
newTranslator[i] = translator[i];
}
this.foundTranslators.push(newTranslator);
}
/* Zotero.Translate.ZoteroItem: a class for generating a new item from
* inside scraper code
*/
Zotero.Translate.GenerateZoteroItemClass = function() {
var ZoteroItem = function(itemType) {
// assign item type
this.itemType = itemType;
// generate creators array
this.creators = new Array();
// generate notes array
this.notes = new Array();
// generate tags array
this.tags = new Array();
// generate see also array
this.seeAlso = new Array();
// generate file array
this.attachments = new Array();
};
return ZoteroItem;
}
/* Zotero.Translate.Collection: a class for generating a new top-level
* collection from inside scraper code
*/
Zotero.Translate.GenerateZoteroCollectionClass = function() {
var ZoteroCollection = function() {};
return ZoteroCollection;
}
/* Zotero.Translate.RDF: a class for handling RDF IO
*
* If an import/export translator specifies dataMode RDF, this is the interface,
* accessible from model.
*
* In order to simplify things, all classes take in their resource/container
* as either the Mozilla native type or a string, but all
* return resource/containers as Mozilla native types (use model.toString to
* convert)
*/
Zotero.Translate.RDF = function(dataSource, serializer) {
this._RDFService = Components.classes['@mozilla.org/rdf/rdf-service;1']
.getService(Components.interfaces.nsIRDFService);
this._AtomService = Components.classes["@mozilla.org/atom-service;1"]
.getService(Components.interfaces.nsIAtomService);
this._RDFContainerUtils = Components.classes["@mozilla.org/rdf/container-utils;1"]
.getService(Components.interfaces.nsIRDFContainerUtils);
this._dataSource = dataSource;
this._serializer = serializer;
}
// turn an nsISimpleEnumerator into an array
Zotero.Translate.RDF.prototype._deEnumerate = function(enumerator) {
if(!(enumerator instanceof Components.interfaces.nsISimpleEnumerator)) {
return false;
}
var resources = new Array();
while(enumerator.hasMoreElements()) {
var resource = enumerator.getNext();
try {
resource.QueryInterface(Components.interfaces.nsIRDFLiteral);
resources.push(resource.Value);
} catch(e) {
resource.QueryInterface(Components.interfaces.nsIRDFResource);
resources.push(resource);
}
}
if(resources.length) {
return resources;
} else {
return false;
}
}
// get a resource as an nsIRDFResource, instead of a string
Zotero.Translate.RDF.prototype._getResource = function(about) {
try {
if(!(about instanceof Components.interfaces.nsIRDFResource)) {
about = this._RDFService.GetResource(about);
}
} catch(e) {
throw("Zotero.Translate.RDF: Invalid RDF resource: "+about);
}
return about;
}
// USED FOR OUTPUT
// writes an RDF triple
Zotero.Translate.RDF.prototype.addStatement = function(about, relation, value, literal) {
about = this._getResource(about);
if(!(value instanceof Components.interfaces.nsIRDFResource)) {
if(literal) {
// zap chars that Mozilla will mangle
if(typeof(value) == "string") {
value = value.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
}
try {
value = this._RDFService.GetLiteral(value);
} catch(e) {
throw "Zotero.Translate.RDF.addStatement: Could not convert to literal";
}
} else {
try {
value = this._RDFService.GetResource(value);
} catch(e) {
throw "Zotero.Translate.RDF.addStatement: Could not convert to resource";
}
}
}
this._dataSource.Assert(about, this._RDFService.GetResource(relation), value, true);
}
// creates an anonymous resource
Zotero.Translate.RDF.prototype.newResource = function() {
return this._RDFService.GetAnonymousResource()
};
// creates a new container
Zotero.Translate.RDF.prototype.newContainer = function(type, about) {
about = this._getResource(about);
type = type.toLowerCase();
if(type == "bag") {
return this._RDFContainerUtils.MakeBag(this._dataSource, about);
} else if(type == "seq") {
return this._RDFContainerUtils.MakeSeq(this._dataSource, about);
} else if(type == "alt") {
return this._RDFContainerUtils.MakeAlt(this._dataSource, about);
} else {
throw "Invalid container type in model.newContainer";
}
}
// adds a new container element (index optional)
Zotero.Translate.RDF.prototype.addContainerElement = function(about, element, literal, index) {
if(!(about instanceof Components.interfaces.nsIRDFContainer)) {
about = this._getResource(about);
var container = Components.classes["@mozilla.org/rdf/container;1"].
createInstance(Components.interfaces.nsIRDFContainer);
container.Init(this._dataSource, about);
about = container;
}
if(!(element instanceof Components.interfaces.nsIRDFResource)) {
if(literal) {
element = this._RDFService.GetLiteral(element);
} else {
element = this._RDFService.GetResource(element);
}
}
if(index) {
about.InsertElementAt(element, index, true);
} else {
about.AppendElement(element);
}
}
// gets container elements as an array
Zotero.Translate.RDF.prototype.getContainerElements = function(about) {
if(!(about instanceof Components.interfaces.nsIRDFContainer)) {
about = this._getResource(about);
var container = Components.classes["@mozilla.org/rdf/container;1"].
createInstance(Components.interfaces.nsIRDFContainer);
container.Init(this._dataSource, about);
about = container;
}
return this._deEnumerate(about.GetElements());
}
// sets a namespace
Zotero.Translate.RDF.prototype.addNamespace = function(prefix, uri) {
if(this._serializer) { // silently fail, in case the reason the scraper
// is failing is that we're using internal IO
this._serializer.addNameSpace(this._AtomService.getAtom(prefix), uri);
}
}
// gets a resource's URI
Zotero.Translate.RDF.prototype.getResourceURI = function(resource) {
if(typeof(resource) == "string") {
return resource;
}
resource.QueryInterface(Components.interfaces.nsIRDFResource);
return resource.ValueUTF8;
}
// USED FOR INPUT
// gets all RDF resources
Zotero.Translate.RDF.prototype.getAllResources = function() {
var resourceEnumerator = this._dataSource.GetAllResources();
return this._deEnumerate(resourceEnumerator);
}
// gets arcs going in
Zotero.Translate.RDF.prototype.getArcsIn = function(resource) {
resource = this._getResource(resource);
var arcEnumerator = this._dataSource.ArcLabelsIn(resource);
return this._deEnumerate(arcEnumerator);
}
// gets arcs going out
Zotero.Translate.RDF.prototype.getArcsOut = function(resource) {
resource = this._getResource(resource);
var arcEnumerator = this._dataSource.ArcLabelsOut(resource);
return this._deEnumerate(arcEnumerator);
}
// gets source resources
Zotero.Translate.RDF.prototype.getSources = function(resource, property) {
property = this._getResource(property);
resource = this._getResource(resource);
var enumerator = this._dataSource.GetSources(property, resource, true);
return this._deEnumerate(enumerator);
}
// gets target resources
Zotero.Translate.RDF.prototype.getTargets = function(resource, property) {
property = this._getResource(property);
resource = this._getResource(resource);
var enumerator = this._dataSource.GetTargets(resource, property, true);
return this._deEnumerate(enumerator);
}