diff --git a/chrome/content/zotero/xpcom/date.js b/chrome/content/zotero/xpcom/date.js deleted file mode 100644 index 9f13c0fc0b..0000000000 --- a/chrome/content/zotero/xpcom/date.js +++ /dev/null @@ -1,962 +0,0 @@ -/* - ***** BEGIN LICENSE BLOCK ***** - - Copyright © 2009 Center for History and New Media - George Mason University, Fairfax, Virginia, USA - http://zotero.org - - This file is part of Zotero. - - Zotero is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zotero is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zotero. If not, see . - - ***** END LICENSE BLOCK ***** -*/ - -Zotero.Date = new function(){ - this.isSQLDate = isSQLDate; - this.isSQLDateTime = isSQLDateTime; - this.sqlHasYear = sqlHasYear; - this.sqlHasMonth = sqlHasMonth; - this.sqlHasDay = sqlHasDay; - this.getUnixTimestamp = getUnixTimestamp; - this.toUnixTimestamp = toUnixTimestamp; - this.getFileDateString = getFileDateString; - this.getFileTimeString = getFileTimeString; - this.getLocaleDateOrder = getLocaleDateOrder; - - var _localeDateOrder = null; - var _months; - var _monthsWithEnglish; - - this.init = function () { - if (!(Zotero.isFx || Zotero.isElectron) || Zotero.isBookmarklet) { - throw new Error("Unimplemented"); - } - - var json = JSON.parse(Zotero.File.getResource('resource://zotero/schema/dateFormats.json')); - var locale = Zotero.locale; - var english = locale.startsWith('en'); - // If no exact match, try first two characters ('de') - if (!json[locale]) { - locale = locale.substr(0, 2); - } - // Try first two characters repeated ('de-DE') - if (!json[locale]) { - locale = locale + "-" + locale.toUpperCase(); - } - // Look for another locale with same first two characters - if (!json[locale]) { - let sameLang = Object.keys(json).filter(l => l.startsWith(locale.substr(0, 2))); - if (sameLang.length) { - locale = sameLang[0]; - } - } - // If all else fails, use English - if (!json[locale]) { - locale = 'en-US'; - english = true; - } - _months = json[locale]; - - // Add English versions if not already added - if (english) { - _monthsWithEnglish = _months; - } - else { - _monthsWithEnglish = {}; - for (let key in _months) { - _monthsWithEnglish[key] = _months[key].concat(json['en-US'][key]); - } - } - }; - - - /** - * @param {Boolean} [withEnglish = false] - Include English months - * @return {Object} - Object with 'short' and 'long' arrays - */ - this.getMonths = function (withEnglish) { - if (withEnglish) { - if (_monthsWithEnglish) return _monthsWithEnglish; - } - else { - if (_months) return _months; - } - - if (Zotero.isFx && !Zotero.isBookmarklet) { - throw new Error("Months not cached"); - } - - // TODO: Use JSON file for connectors - return _months = _monthsWithEnglish = { - short: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], - long: ["January", "February", "March", "April", "May", "June", "July", "August", - "September", "October", "November", "December"]}; - } - - /** - * Convert an SQL date in the form '2006-06-13 11:03:05' into a JS Date object - * - * Can also accept just the date part (e.g. '2006-06-13') - **/ - this.sqlToDate = function (sqldate, isUTC) { - try { - if (!this.isSQLDate(sqldate) - && !this.isSQLDateTime(sqldate) - && !this.isSQLDateTimeWithoutSeconds(sqldate)) { - throw new Error("Invalid date"); - } - - var datetime = sqldate.split(' '); - var dateparts = datetime[0].split('-'); - if (datetime[1]){ - var timeparts = datetime[1].split(':'); - } - else { - timeparts = [false, false, false]; - } - - // Invalid date part - if (dateparts.length==1){ - throw new Error("Invalid date part"); - } - // Allow missing seconds - if (timeparts.length == 2) { - timeparts[2] = '00'; - } - - if (isUTC){ - return new Date(Date.UTC(dateparts[0], dateparts[1]-1, dateparts[2], - timeparts[0], timeparts[1], timeparts[2])); - } - - return new Date(dateparts[0], dateparts[1]-1, dateparts[2], - timeparts[0], timeparts[1], timeparts[2]); - } - catch (e){ - Zotero.debug(sqldate + ' is not a valid SQL date', 2) - return false; - } - } - - - /** - * Convert a JS Date object to an SQL date in the form '2006-06-13 11:03:05' - * - * If _toUTC_ is true, creates a UTC date - **/ - this.dateToSQL = function (date, toUTC) { - try { - if (toUTC){ - var year = date.getUTCFullYear(); - var month = date.getUTCMonth(); - var day = date.getUTCDate(); - var hours = date.getUTCHours(); - var minutes = date.getUTCMinutes(); - var seconds = date.getUTCSeconds(); - } - else { - var year = date.getFullYear(); - var month = date.getMonth(); - var day = date.getDate(); - var hours = date.getHours(); - var minutes = date.getMinutes(); - var seconds = date.getSeconds(); - } - - year = Zotero.Utilities.lpad(year, '0', 4); - month = Zotero.Utilities.lpad(month + 1, '0', 2); - day = Zotero.Utilities.lpad(day, '0', 2); - hours = Zotero.Utilities.lpad(hours, '0', 2); - minutes = Zotero.Utilities.lpad(minutes, '0', 2); - seconds = Zotero.Utilities.lpad(seconds, '0', 2); - - return year + '-' + month + '-' + day + ' ' - + hours + ':' + minutes + ':' + seconds; - } - catch (e){ - Zotero.debug(date + ' is not a valid JS date', 2); - return ''; - } - } - - - /** - * Convert a JS Date object to an ISO 8601 UTC date/time - * - * @param {Date} date JS Date object - * @return {String} ISO 8601 UTC date/time - * e.g. 2008-08-15T20:00:00Z - */ - this.dateToISO = function (date) { - var year = date.getUTCFullYear(); - var month = date.getUTCMonth(); - var day = date.getUTCDate(); - var hours = date.getUTCHours(); - var minutes = date.getUTCMinutes(); - var seconds = date.getUTCSeconds(); - - year = Zotero.Utilities.lpad(year, '0', 4); - month = Zotero.Utilities.lpad(month + 1, '0', 2); - day = Zotero.Utilities.lpad(day, '0', 2); - hours = Zotero.Utilities.lpad(hours, '0', 2); - minutes = Zotero.Utilities.lpad(minutes, '0', 2); - seconds = Zotero.Utilities.lpad(seconds, '0', 2); - - return year + '-' + month + '-' + day + 'T' - + hours + ':' + minutes + ':' + seconds + 'Z'; - } - - - var _re8601 = /^([0-9]{4})(-([0-9]{2})(-([0-9]{2})(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?$/; - - /** - * @return {Boolean} - True if string is an ISO 8601 date, false if not - */ - this.isISODate = function (str) { - return _re8601.test(str); - } - - /** - * Convert an ISO 8601–formatted date/time to a JS Date - * - * @param {String} isoDate ISO 8601 date - * @return {Date|False} - JS Date, or false if not a valid date - */ - this.isoToDate = function (isoDate) { - var d = isoDate.match(_re8601); - if (!d) return false; - return new Date(isoDate); - } - - - this.isoToSQL = function (isoDate) { - return Zotero.Date.dateToSQL(Zotero.Date.isoToDate(isoDate), true); // no 'this' for translator sandbox - } - - - /* - * converts a string to an object containing: - * day: integer form of the day - * month: integer form of the month (indexed from 0, not 1) - * year: 4 digit year (or, year + BC/AD/etc.) - * part: anything that does not fall under any of the above categories - * (e.g., "Summer," etc.) - * - * Note: the returned object is *not* a JS Date object - */ - var _slashRe = /^(.*?)\b([0-9]{1,4})(?:([\-\/\.\u5e74])([0-9]{1,2}))?(?:([\-\/\.\u6708])([0-9]{1,4}))?((?:\b|[^0-9]).*?)$/ - var _yearRe = /^(.*?)\b((?:circa |around |about |c\.? ?)?[0-9]{1,4}(?: ?B\.? ?C\.?(?: ?E\.?)?| ?C\.? ?E\.?| ?A\.? ?D\.?)|[0-9]{3,4})\b(.*?)$/i; - var _monthRe = null; - var _dayRe = null; - - this.strToDate = function (string) { - var date = { - order: '' - }; - - if (typeof string == 'string' || typeof string == 'number') { - string = Zotero.Utilities.trimInternal(string.toString()); - } - - // skip empty things - if(!string) { - return date; - } - - var parts = []; - - // first, directly inspect the string - var m = _slashRe.exec(string); - if(m && - ((!m[5] || !m[3]) || m[3] == m[5] || (m[3] == "\u5e74" && m[5] == "\u6708")) && // require sane separators - ((m[2] && m[4] && m[6]) || (!m[1] && !m[7]))) { // require that either all parts are found, - // or else this is the entire date field - // figure out date based on parts - if(m[2].length == 3 || m[2].length == 4 || m[3] == "\u5e74") { - // ISO 8601 style date (big endian) - date.year = m[2]; - date.month = m[4]; - date.day = m[6]; - date.order += m[2] ? 'y' : ''; - date.order += m[4] ? 'm' : ''; - date.order += m[6] ? 'd' : ''; - } else if(m[2] && !m[4] && m[6]) { - date.month = m[2]; - date.year = m[6]; - date.order += m[2] ? 'm' : ''; - date.order += m[6] ? 'y' : ''; - } else { - // local style date (middle or little endian) - var country = Zotero.locale ? Zotero.locale.substr(3) : "US"; - if(country == "US" || // The United States - country == "FM" || // The Federated States of Micronesia - country == "PW" || // Palau - country == "PH") { // The Philippines - date.month = m[2]; - date.day = m[4]; - date.order += m[2] ? 'm' : ''; - date.order += m[4] ? 'd' : ''; - } else { - date.month = m[4]; - date.day = m[2]; - date.order += m[2] ? 'd' : ''; - date.order += m[4] ? 'm' : ''; - } - date.year = m[6]; - if (m[6] !== undefined) { - date.order += 'y'; - } - } - - var longYear = date.year && date.year.toString().length > 2; - if(date.year) date.year = parseInt(date.year, 10); - if(date.day) date.day = parseInt(date.day, 10); - if(date.month) { - date.month = parseInt(date.month, 10); - - if(date.month > 12) { - // swap day and month - var tmp = date.day; - date.day = date.month - date.month = tmp; - date.order = date.order.replace('m', 'D') - .replace('d', 'M') - .replace('D', 'd') - .replace('M', 'm'); - } - } - - if((!date.month || date.month <= 12) && (!date.day || date.day <= 31)) { - // Parse pre-100 years with leading zeroes (001, 0001, 012, 0012, 0123, but not 08) - if (date.year && date.year < 100 && !longYear) { - var today = new Date(); - var year = today.getFullYear(); - var twoDigitYear = year % 100; - var century = year - twoDigitYear; - - if(date.year <= twoDigitYear) { - // assume this date is from our century - date.year = century + date.year; - } else { - // assume this date is from the previous century - date.year = century - 100 + date.year; - } - } - - if(date.month) date.month--; // subtract one for JS style - else delete date.month; - - //Zotero.debug("DATE: retrieved with algorithms: "+JSON.stringify(date)); - - parts.push( - { part: m[1], before: true }, - { part: m[7] } - ); - } else { - // give up; we failed the sanity check - Zotero.debug("DATE: algorithms failed sanity check"); - var date = { - order: '' - }; - parts.push({ part: string }); - } - } else { - //Zotero.debug("DATE: could not apply algorithms"); - parts.push({ part: string }); - } - - // couldn't find something with the algorithms; use regexp - // YEAR - if(!date.year) { - for (var i in parts) { - var m = _yearRe.exec(parts[i].part); - if (m) { - date.year = m[2]; - date.order = _insertDateOrderPart(date.order, 'y', parts[i]); - parts.splice( - i, 1, - { part: m[1], before: true }, - { part: m[3] } - ); - //Zotero.debug("DATE: got year (" + date.year + ", " + JSON.stringify(parts) + ")"); - break; - } - } - } - - // MONTH - if(date.month === undefined) { - // compile month regular expression - let months = Zotero.Date.getMonths(true); // no 'this' for translator sandbox - months = months.short.map(m => m.toLowerCase()) - .concat(months.long.map(m => m.toLowerCase())); - - if(!_monthRe) { - _monthRe = new RegExp("^(.*)\\b("+months.join("|")+")[^ ]*(?: (.*)$|$)", "i"); - } - - for (var i in parts) { - var m = _monthRe.exec(parts[i].part); - if (m) { - // Modulo 12 in case we have multiple languages - date.month = months.indexOf(m[2].toLowerCase()) % 12; - date.order = _insertDateOrderPart(date.order, 'm', parts[i]); - parts.splice( - i, 1, - { part: m[1], before: "m" }, - { part: m[3], after: "m" } - ); - //Zotero.debug("DATE: got month (" + date.month + ", " + JSON.stringify(parts) + ")"); - break; - } - } - } - - // DAY - if(!date.day) { - // compile day regular expression - if(!_dayRe) { - var daySuffixes = Zotero.isClient ? Zotero.getString("date.daySuffixes").replace(/, ?/g, "|") : ""; - _dayRe = new RegExp("\\b([0-9]{1,2})(?:"+daySuffixes+")?\\b(.*)", "i"); - } - - for (var i in parts) { - var m = _dayRe.exec(parts[i].part); - if (m) { - var day = parseInt(m[1], 10); - // Sanity check - if (day <= 31) { - date.day = day; - date.order = _insertDateOrderPart(date.order, 'd', parts[i]); - if(m.index > 0) { - var part = parts[i].part.substr(0, m.index); - if(m[2]) { - part += " " + m[2];; - } - } else { - var part = m[2]; - } - parts.splice( - i, 1, - { part: part } - ); - //Zotero.debug("DATE: got day (" + date.day + ", " + JSON.stringify(parts) + ")"); - break; - } - } - } - } - - // Concatenate date parts - date.part = ''; - for (var i in parts) { - date.part += parts[i].part + ' '; - } - - // clean up date part - if(date.part) { - date.part = date.part.replace(/^[^A-Za-z0-9]+|[^A-Za-z0-9]+$/g, ""); - } - - if(date.part === "" || date.part == undefined) { - delete date.part; - } - - //make sure year is always a string - if(date.year || date.year === 0) date.year += ''; - - return date; - } - - this.isHTTPDate = function(str) { - var dayNames = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; - var monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", - "Oct", "Nov", "Dec"]; - str = str.trim(); - var temp = str.split(','); - if (temp.length > 1) { - var dayOfWeek = temp[0]; - if(dayNames.indexOf(dayOfWeek) == -1) { - return false; - } - - str = temp[1].trim(); - } - temp = str.split(' '); - temp = temp.filter((t) => ! t.match(/^\s*$/)); - if (temp.length < 5) { - return false; - } - if (!temp[0].trim().match(/[0-3]\d/)) { - return false; - } - if (monthNames.indexOf(temp[1].trim()) == -1) { - return false; - } - if (!temp[2].trim().match(/\d\d\d\d/)) { - return false; - } - temp.splice(0, 3); - var time = temp[0].trim().split(':'); - if (time.length < 2) { - return false; - } - for (let t of time) { - if (!t.match(/\d\d/)) { - return false; - } - } - temp.splice(0, 1); - var zone = temp.join(' ').trim(); - return !!zone.match(/([+-]\d\d\d\d|UTC?|GMT|EST|EDT|CST|CDT|MST|MDT|PST|PDT)/) - }; - - - function _insertDateOrderPart(dateOrder, part, partOrder) { - if (!dateOrder) { - return part; - } - if (partOrder.before === true) { - return part + dateOrder; - } - if (partOrder.after === true) { - return dateOrder + part; - } - if (partOrder.before) { - var pos = dateOrder.indexOf(partOrder.before); - if (pos == -1) { - return dateOrder; - } - return dateOrder.replace(new RegExp("(" + partOrder.before + ")"), part + '$1'); - } - if (partOrder.after) { - var pos = dateOrder.indexOf(partOrder.after); - if (pos == -1) { - return dateOrder + part; - } - return dateOrder.replace(new RegExp("(" + partOrder.after + ")"), '$1' + part); - } - return dateOrder + part; - } - - - - /** - * does pretty formatting of a date object returned by strToDate() - * - * @param {Object} date A date object, as returned from strToDate() - * @param {Boolean} shortFormat Whether to return a short (12/1/95) date - * @return A formatted date string - * @type String - **/ - this.formatDate = function (date, shortFormat) { - if(shortFormat) { - var localeDateOrder = getLocaleDateOrder(); - var string = localeDateOrder[0]+"/"+localeDateOrder[1]+"/"+localeDateOrder[2]; - return string.replace("y", (date.year !== undefined ? date.year : "00")) - .replace("m", (date.month !== undefined ? 1+date.month : "0")) - .replace("d", (date.day !== undefined ? date.day : "0")); - } else { - var string = ""; - - if(date.part) { - string += date.part+" "; - } - - var months = Zotero.Date.getMonths().long; // no 'this' for translator sandbox - if(date.month != undefined && months[date.month]) { - // get short month strings from CSL interpreter - string += months[date.month]; - if(date.day) { - string += " "+date.day+", "; - } else { - string += " "; - } - } - - if(date.year) { - string += date.year; - } - } - - return string; - } - - this.strToISO = function (str) { - var date = this.strToDate(str); - - if(date.year) { - var dateString = Zotero.Utilities.lpad(date.year, "0", 4); - if (parseInt(date.month) == date.month) { - dateString += "-"+Zotero.Utilities.lpad(date.month+1, "0", 2); - if(date.day) { - dateString += "-"+Zotero.Utilities.lpad(date.day, "0", 2); - } - } - return dateString; - } - return false; - } - - - this.sqlToISO8601 = function (sqlDate) { - var date = sqlDate.substr(0, 10); - var matches = date.match(/^([0-9]{4})\-([0-9]{2})\-([0-9]{2})/); - if (!matches) { - return false; - } - date = matches[1]; - // Drop parts for reduced precision - if (matches[2] !== "00") { - date += "-" + matches[2]; - if (matches[3] !== "00") { - date += "-" + matches[3]; - } - } - var time = sqlDate.substr(11); - // TODO: validate times - if (time) { - date += "T" + time + "Z"; - } - return date; - } - - this.strToMultipart = function (str) { - if (!str){ - return ''; - } - - var parts = this.strToDate(str); - - // FIXME: Until we have a better BCE date solution, - // remove year value if not between 1 and 9999 - if (parts.year) { - var year = parts.year + ''; - if (!year.match(/^[0-9]{1,4}$/)) { - delete parts.year; - } - } - - parts.month = typeof parts.month != "undefined" ? parts.month + 1 : ''; - - var multi = (parts.year ? Zotero.Utilities.lpad(parts.year, '0', 4) : '0000') + '-' - + Zotero.Utilities.lpad(parts.month, '0', 2) + '-' - + (parts.day ? Zotero.Utilities.lpad(parts.day, '0', 2) : '00') - + ' ' - + str; - return multi; - } - - // Regexes for multipart and SQL dates - // Allow zeroes in multipart dates - // TODO: Allow negative multipart in DB and here with \-? - var _multipartRE = /^[0-9]{4}\-(0[0-9]|10|11|12)\-(0[0-9]|[1-2][0-9]|30|31) /; - var _sqldateRE = /^\-?[0-9]{4}\-(0[1-9]|10|11|12)\-(0[1-9]|[1-2][0-9]|30|31)$/; - var _sqldateWithZeroesRE = /^\-?[0-9]{4}\-(0[0-9]|10|11|12)\-(0[0-9]|[1-2][0-9]|30|31)$/; - var _sqldatetimeRE = /^\-?[0-9]{4}\-(0[1-9]|10|11|12)\-(0[1-9]|[1-2][0-9]|30|31) ([0-1][0-9]|[2][0-3]):([0-5][0-9]):([0-5][0-9])$/; - var _sqlDateTimeWithoutSecondsRE = /^\-?[0-9]{4}\-(0[1-9]|10|11|12)\-(0[1-9]|[1-2][0-9]|30|31) ([0-1][0-9]|[2][0-3]):([0-5][0-9])$/; - - /** - * Tests if a string is a multipart date string - * e.g. '2006-11-03 November 3rd, 2006' - */ - this.isMultipart = function (str) { - if (this.isSQLDateTime(str) || this.isSQLDateTimeWithoutSeconds(str)) { - return false; - } - return _multipartRE.test(str); - } - - - /** - * Returns the SQL part of a multipart date string - * (e.g. '2006-11-03 November 3rd, 2006' returns '2006-11-03') - */ - this.multipartToSQL = function (multi) { - if (!multi){ - return ''; - } - - if (!this.isMultipart(multi)) { - return '0000-00-00'; - } - - return multi.substr(0, 10); - } - - - /** - * Returns the user part of a multipart date string - * (e.g. '2006-11-03 November 3rd, 2006' returns 'November 3rd, 2006') - */ - this.multipartToStr = function (multi) { - if (!multi){ - return ''; - } - - if (!this.isMultipart(multi)) { - return multi; - } - - return multi.substr(11); - } - - - /** - * Convert 'yesterday'/'today'/'tomorrow' to SQL date, or else return original string - * - * @param {String} str - * @return {String} - */ - this.parseDescriptiveString = function (str) { - // Parse 'yesterday'/'today'/'tomorrow' and convert to dates, - // since it doesn't make sense for those to be actual metadata values - var lc = str.toLowerCase().trim(); - if (lc == 'yesterday' || lc == Zotero.getString('date.yesterday')) { - str = Zotero.Date.dateToSQL(new Date(new Date().getTime() - 86400000)).substr(0, 10); - } - else if (lc == 'today' || lc == Zotero.getString('date.today')) { - str = Zotero.Date.dateToSQL(new Date()).substr(0, 10); - } - else if (lc == 'tomorrow' || lc == Zotero.getString('date.tomorrow')) { - str = Zotero.Date.dateToSQL(new Date(new Date().getTime() + 86400000)).substr(0, 10); - } - return str; - }; - - - function isSQLDate(str, allowZeroes) { - if (allowZeroes) { - return _sqldateWithZeroesRE.test(str); - } - return _sqldateRE.test(str); - } - - - function isSQLDateTime(str){ - return _sqldatetimeRE.test(str); - } - - - this.isSQLDateTimeWithoutSeconds = function (str) { - return _sqlDateTimeWithoutSecondsRE.test(str); - } - - - function sqlHasYear(sqldate){ - return isSQLDate(sqldate, true) && sqldate.substr(0,4)!='0000'; - } - - - function sqlHasMonth(sqldate){ - return isSQLDate(sqldate, true) && sqldate.substr(5,2)!='00'; - } - - - function sqlHasDay(sqldate){ - return isSQLDate(sqldate, true) && sqldate.substr(8,2)!='00'; - } - - - function getUnixTimestamp() { - return Math.round(Date.now() / 1000); - } - - - function toUnixTimestamp(date) { - if (date === null || typeof date != 'object' || - date.constructor.name != 'Date') { - throw new Error(`'${date}' is not a valid date`); - } - return Math.round(date.getTime() / 1000); - } - - - /** - * Convert a JS Date to a relative date (e.g., "5 minutes ago") - * - * Adapted from http://snipplr.com/view/10290/javascript-parse-relative-date/ - * - * @param {Date} date - * @return {String} - */ - this.toRelativeDate = function (date) { - var str; - var now = new Date(); - var timeSince = now.getTime() - date; - var inSeconds = timeSince / 1000; - var inMinutes = timeSince / 1000 / 60; - var inHours = timeSince / 1000 / 60 / 60; - var inDays = timeSince / 1000 / 60 / 60 / 24; - var inYears = timeSince / 1000 / 60 / 60 / 24 / 365; - - var n; - - // in seconds - if (Math.round(inSeconds) == 1) { - var key = "secondsAgo"; - } - else if (inMinutes < 1.01) { - var key = "secondsAgo"; - n = Math.round(inSeconds); - } - - // in minutes - else if (Math.round(inMinutes) == 1) { - var key = "minutesAgo"; - } - else if (inHours < 1.01) { - var key = "minutesAgo"; - n = Math.round(inMinutes); - } - - // in hours - else if (Math.round(inHours) == 1) { - var key = "hoursAgo"; - } - else if (inDays < 1.01) { - var key = "hoursAgo"; - n = Math.round(inHours); - } - - // in days - else if (Math.round(inDays) == 1) { - var key = "daysAgo"; - } - else if (inYears < 1.01) { - var key = "daysAgo"; - n = Math.round(inDays); - } - - // in years - else if (Math.round(inYears) == 1) { - var key = "yearsAgo"; - } - else { - var key = "yearsAgo"; - var n = Math.round(inYears); - } - - return Zotero.getString("date.relative." + key + "." + (n ? "multiple" : "one"), n); - } - - // Initializing `toFriendlyDate` formatters, since - // `toLocaleDateString` is extremely slow (4500ms vs 200ms - // for 10k calls) - var _friendlyDateTodayFormatter = new Intl.DateTimeFormat( - false, { hour: 'numeric', minute: 'numeric' }); - - var _friendlyDateWeekFormatter = new Intl.DateTimeFormat( - false, { weekday: 'long' }); - - var _friendlyDateRegularFormatter = new Intl.DateTimeFormat( - false, { year: '2-digit', month: 'numeric', day: 'numeric' }); - - this.toFriendlyDate = function (date) { - // 6:14:36 PM - if (isToday(date)) { - return _friendlyDateTodayFormatter.format(date); - } - // 'Thursday' - if (isThisWeek(date)) { - return _friendlyDateWeekFormatter.format(date); - } - return _friendlyDateRegularFormatter.format(date); - }; - - - function isToday(date) { - var d = new Date(); - return d.getDate() == date.getDate() - && d.getMonth() == d.getMonth() - && d.getFullYear() == d.getFullYear(); - } - - - function isThisWeek(date) { - var d = new Date(); - return d.getFullYear() == date.getFullYear() && getWeekNumber(d) == getWeekNumber(date); - } - - - // https://stackoverflow.com/a/27125580 - function getWeekNumber(date) { - let onejan = new Date(date.getFullYear(), 0, 1); - return Math.ceil((((date.getTime() - onejan.getTime()) / 86400000) + onejan.getDay() + 1) / 7); - } - - - function getFileDateString(file){ - var date = new Date(); - date.setTime(file.lastModifiedTime); - return date.toLocaleDateString(); - } - - - function getFileTimeString(file){ - var date = new Date(); - date.setTime(file.lastModifiedTime); - return date.toLocaleTimeString(); - } - - /** - * Get the order of the date components based on the current locale - * - * Returns a string with y, m, and d (e.g. 'ymd', 'mdy') - */ - function getLocaleDateOrder(){ - if (!_localeDateOrder) { - switch (Zotero.locale ? Zotero.locale.substr(3) : "US") { - // middle-endian - case 'US': // The United States - case 'BZ': // Belize - case 'FM': // The Federated States of Micronesia - case 'PA': // Panama - case 'PH': // The Philippines - case 'PW': // Palau - case 'ZW': // Zimbabwe - _localeDateOrder = 'mdy'; - break; - - // big-endian - case 'fa': // Persian - case 'AL': // Albania - case 'CA': // Canada - case 'CN': // China - case 'HU': // Hungary - case 'JP': // Japan - case 'KE': // Kenya - case 'KR': // Korea - case 'LT': // Lithuania - case 'LV': // Latvia - case 'MN': // Mongolia - case 'SE': // Sweden - case 'TW': // Taiwan - case 'ZA': // South Africa - _localeDateOrder = 'ymd'; - break; - - // little-endian - default: - _localeDateOrder = 'dmy'; - } - } - return _localeDateOrder; - } -} - -if (typeof process === 'object' && process + '' === '[object process]'){ - module.exports = Zotero.Date; -} diff --git a/chrome/content/zotero/xpcom/openurl.js b/chrome/content/zotero/xpcom/openurl.js deleted file mode 100644 index 42f8441773..0000000000 --- a/chrome/content/zotero/xpcom/openurl.js +++ /dev/null @@ -1,511 +0,0 @@ -/* - ***** BEGIN LICENSE BLOCK ***** - - Copyright © 2009 Center for History and New Media - George Mason University, Fairfax, Virginia, USA - http://zotero.org - - This file is part of Zotero. - - Zotero is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zotero is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zotero. If not, see . - - ***** END LICENSE BLOCK ***** -*/ - -Zotero.OpenURL = new function() { - this.resolve = resolve; - this.discoverResolvers = discoverResolvers; - this.createContextObject = createContextObject; - this.parseContextObject = parseContextObject; - - /* - * Returns a URL to look up an item in the OpenURL resolver - */ - function resolve(itemObject) { - var co = createContextObject(itemObject, Zotero.Prefs.get("openURL.version")); - if(co) { - var base = Zotero.Prefs.get("openURL.resolver"); - // Add & if there's already a ? - var splice = base.indexOf("?") == -1 ? "?" : "&"; - return base + splice + co; - } - return false; - } - - /* - * Queries OCLC's OpenURL resolver registry and returns an address and version - */ - function discoverResolvers() { - var req = new XMLHttpRequest(); - req.open("GET", "http://worldcatlibraries.org/registry/lookup?IP=requestor", false); - req.send(null); - - if(!req.responseXML) { - throw new Error("Could not access resolver registry"); - } - - var resolverArray = new Array(); - var resolvers = req.responseXML.getElementsByTagName("resolver"); - for(var i=0; i 0) { - var version = "1.0"; - } else if(resolver.getElementsByTagName("OpenURL_0.1").length > 0) { - var version = "0.1"; - } else { - continue; - } - - resolverArray.push({name:name, url:url, version:version}); - } - - return resolverArray; - } - - /* - * Generates an OpenURL ContextObject from an item - */ - function createContextObject(item, version, asObj) { - var entries = (asObj ? {} : []); - - function _mapTag(data, tag, dontAddPrefix) { - if(!data) return; - - if(version === "1.0" && !dontAddPrefix) tag = "rft."+tag; - - if(asObj) { - if(!entries[tag]) entries[tag] = []; - entries[tag].push(data); - } else { - entries.push(tag+"="+encodeURIComponent(data)); - } - } - - if (item.toJSON) { - item = item.toJSON(); - } - - // find pmid - const pmidRe = /(?:\n|^)PMID:\s*(\d+)/g; - var pmid = pmidRe.exec(item.extra); - if(pmid) pmid = pmid[1]; - - // encode ctx_ver (if available) and encode identifiers - if(version == "0.1") { - _mapTag("Zotero:2", "sid", true); - if(item.DOI) _mapTag("doi:"+item.DOI, "id", true); - if(item.ISBN) _mapTag(item.ISBN, "isbn", true); - if(pmid) _mapTag("pmid:"+pmid, "id", true); - } else { - _mapTag("Z39.88-2004", "url_ver", true); - _mapTag("Z39.88-2004", "ctx_ver", true); - _mapTag("info:sid/zotero.org:2", "rfr_id", true); - if(item.DOI) _mapTag("info:doi/"+item.DOI, "rft_id", true); - if(item.ISBN) _mapTag("urn:isbn:"+item.ISBN, "rft_id", true); - if(pmid) _mapTag("info:pmid/"+pmid, "rft_id", true); - } - - // encode genre and item-specific data - if(item.itemType == "journalArticle") { - if(version === "1.0") { - _mapTag("info:ofi/fmt:kev:mtx:journal", "rft_val_fmt", true); - } - _mapTag("article", "genre"); - - _mapTag(item.title, "atitle"); - _mapTag(item.publicationTitle, (version == "0.1" ? "title" : "jtitle")); - _mapTag(item.journalAbbreviation, "stitle"); - _mapTag(item.volume, "volume"); - _mapTag(item.issue, "issue"); - } else if(item.itemType == "book" || item.itemType == "bookSection" || item.itemType == "conferencePaper" || item.itemType == "report") { - if(version === "1.0") { - _mapTag("info:ofi/fmt:kev:mtx:book", "rft_val_fmt", true); - } - - if(item.itemType == "book") { - _mapTag("book", "genre"); - _mapTag(item.title, (version == "0.1" ? "title" : "btitle")); - } else if (item.itemType == "conferencePaper") { - _mapTag("proceeding", "genre"); - _mapTag(item.title, "atitle"); - _mapTag(item.proceedingsTitle, (version == "0.1" ? "title" : "btitle")); - } else if (item.itemType == "report") { - _mapTag("report", "genre"); - _mapTag(item.seriesTitle, "series"); - _mapTag(item.title, (version == "0.1" ? "title" : "btitle")); - } else { - _mapTag("bookitem", "genre"); - _mapTag(item.title, "atitle"); - _mapTag(item.publicationTitle, (version == "0.1" ? "title" : "btitle")); - } - - _mapTag(item.place, "place"); - _mapTag(item.publisher, "publisher"); - _mapTag(item.edition, "edition"); - _mapTag(item.series, "series"); - } else if(item.itemType == "thesis" && version == "1.0") { - _mapTag("info:ofi/fmt:kev:mtx:dissertation", "rft_val_fmt", true); - - _mapTag(item.title, "title"); - _mapTag(item.publisher, "inst"); - _mapTag(item.type, "degree"); - } else if(item.itemType == "patent" && version == "1.0") { - _mapTag("info:ofi/fmt:kev:mtx:patent", "rft_val_fmt", true); - - _mapTag(item.title, "title"); - _mapTag(item.assignee, "assignee"); - _mapTag(item.patentNumber, "number"); - - if(item.issueDate) { - _mapTag(Zotero.Date.strToISO(item.issueDate), "date"); - } - } else { - //we map as much as possible to DC for all other types. This will export some info - //and work very nicely on roundtrip. All of these fields legal for mtx:dc according to - //http://alcme.oclc.org/openurl/servlet/OAIHandler/extension?verb=GetMetadata&metadataPrefix=mtx&identifier=info:ofi/fmt:kev:mtx:dc - _mapTag("info:ofi/fmt:kev:mtx:dc", "rft_val_fmt", true); - //lacking something better we use Zotero item types here; no clear alternative and this works for roundtrip - _mapTag(item.itemType, "type"); - _mapTag(item.title, "title"); - _mapTag(item.publicationTitle, "source"); - _mapTag(item.rights, "rights"); - _mapTag(item.publisher, "publisher"); - _mapTag(item.abstractNote, "description"); - if(item.DOI){ - _mapTag("urn:doi:" + item.DOI, "identifier"); - } - else if(item.url){ - _mapTag(item.url, "identifier"); - } - } - - if(item.creators && item.creators.length) { - // encode first author as first and last - let firstCreator = Zotero.Utilities.Internal.getFirstCreatorFromItemJSON(item); - if(item.itemType == "patent") { - _mapTag(firstCreator.firstName, "invfirst"); - _mapTag(firstCreator.lastName, "invlast"); - } else { - if (firstCreator.name) { - _mapTag(firstCreator.name, "aucorp"); - } else { - _mapTag(firstCreator.firstName, "aufirst"); - _mapTag(firstCreator.lastName, "aulast"); - } - } - - // encode subsequent creators as au - for(var i=0; i 1) { - _mapTag(pages[0], "spage"); - if(pages.length >= 2) _mapTag(pages[1], "epage"); - } - } - _mapTag(item.numPages, "tpages"); - _mapTag(item.ISBN, "isbn"); - _mapTag(item.ISSN, "issn"); - _mapTag(item.language, "language"); - if(asObj) return entries; - return entries.join("&"); - } - - function _cloneIfNecessary(obj1, obj2) { - if (Zotero.isFx && !Zotero.isBookmarklet) { - return Components.utils.cloneInto(obj1, obj2); - } - return obj1; - } - - /* - * Generates an item in the format returned by item.fromArray() given an - * OpenURL version 1.0 contextObject - * - * accepts an item array to fill, or creates and returns a new item array - */ - function parseContextObject(co, item) { - if(!item) { - var item = new Array(); - item.creators = new Array(); - } - - var coParts = co.split("&"); - - // get type - for(var i=0; i 8) { // we could check length separately for - // each type, but all of these identifiers - // must be > 8 characters - if(value.substr(0, 5) == "ISBN ") { - item.ISBN = value.substr(5); - } else if(value.substr(0, 5) == "ISSN ") { - item.ISSN = value.substr(5); - } else if(value.substr(0, 8) == "urn:doi:") { - item.DOI = value.substr(4); - } else if(value.substr(0, 7) == "http://" || value.substr(0, 8) == "https://") { - item.url = value; - } - } - } else if(key == "rft.description") { - item.abstractNote = value; - } else if(key == "rft.rights") { - item.rights = value; - } else if(key == "rft.language") { - item.language = value; - } else if(key == "rft.subject") { - item.tags.push(value); - } else if(key == "rft.type") { - if(Zotero.Utilities.itemTypeExists(value)) item.itemType = value; - } else if(key == "rft.source") { - item.publicationTitle = value; - } - } - } - - // To maintain author ordering when complex and simple authors are combined, - // we remember where they were and the correct offsets - var inserted = 0; - - // combine two lists of authors, eliminating duplicates - for(var i=0; i= complexAu[i].firstName.length && - item.creators[j].firstName.substr(0, complexAu[i].firstName.length) == complexAu[i].firstName))) { - pushMe = false; - break; - } - } - // Splice in the complex creator at the correct location, - // accounting for previous insertions - if(pushMe) { - item.creators.splice(offset + inserted, 0, complexAu[i]); - inserted++; - } - } - - return item; - } -} - -if (typeof process === 'object' && process + '' === '[object process]'){ - module.exports = Zotero.OpenURL; -} diff --git a/chrome/content/zotero/xpcom/rdf/identity.js b/chrome/content/zotero/xpcom/rdf/identity.js deleted file mode 100644 index 3795217882..0000000000 --- a/chrome/content/zotero/xpcom/rdf/identity.js +++ /dev/null @@ -1,495 +0,0 @@ -// Identity management and indexing for RDF -// -// This file provides IndexedFormula a formula (set of triples) which -// indexed by predicate, subject and object. -// -// It "smushes" (merges into a single node) things which are identical -// according to owl:sameAs or an owl:InverseFunctionalProperty -// or an owl:FunctionalProperty -// -// -// 2005-10 Written Tim Berners-Lee -// 2007 Changed so as not to munge statements from documents when smushing -// -// -/*jsl:option explicit*/ -// Turn on JavaScriptLint variable declaration checking -$rdf.IndexedFormula = function () { - - var owl_ns = "http://www.w3.org/2002/07/owl#"; - // var link_ns = "http://www.w3.org/2007/ont/link#"; - /* hashString functions are used as array indeces. This is done to avoid - ** conflict with existing properties of arrays such as length and map. - ** See issue 139. - */ - $rdf.Literal.prototype.hashString = $rdf.Literal.prototype.toNT; - $rdf.Symbol.prototype.hashString = $rdf.Symbol.prototype.toNT; - $rdf.BlankNode.prototype.hashString = $rdf.BlankNode.prototype.toNT; - $rdf.Collection.prototype.hashString = $rdf.Collection.prototype.toNT; - - - //Stores an associative array that maps URIs to functions - $rdf.IndexedFormula = function (features) { - this.statements = []; // As in Formula - this.optional = []; - this.propertyActions = []; // Array of functions to call when getting statement with {s X o} - //maps to [f(F,s,p,o),...] - this.classActions = []; // Array of functions to call when adding { s type X } - this.redirections = []; // redirect to lexically smaller equivalent symbol - this.aliases = []; // reverse mapping to redirection: aliases for this - this.HTTPRedirects = []; // redirections we got from HTTP - this.subjectIndex = []; // Array of statements with this X as subject - this.predicateIndex = []; // Array of statements with this X as subject - this.objectIndex = []; // Array of statements with this X as object - this.whyIndex = []; // Array of statements with X as provenance - this.index = [this.subjectIndex, this.predicateIndex, this.objectIndex, this.whyIndex]; - this.namespaces = {} // Dictionary of namespace prefixes - if(features === undefined) features = ["sameAs", - "InverseFunctionalProperty", "FunctionalProperty"]; - // this.features = features - // Callbackify? - function handleRDFType(formula, subj, pred, obj, why) { - if(formula.typeCallback != undefined) - formula.typeCallback(formula, obj, why); - - var x = formula.classActions[obj.hashString()]; - var done = false; - if(x) { - for(var i = 0; i < x.length; i++) { - done = done || x[i](formula, subj, pred, obj, why); - } - } - return done; // statement given is not needed if true - } //handleRDFType - //If the predicate is #type, use handleRDFType to create a typeCallback on the object - this.propertyActions[''] = [handleRDFType]; - - // Assumption: these terms are not redirected @@fixme - if($rdf.Util.ArrayIndexOf(features, "sameAs") >= 0) - this.propertyActions[''] = [ - function (formula, subj, pred, obj, why) { - // tabulator.log.warn("Equating "+subj.uri+" sameAs "+obj.uri); //@@ - formula.equate(subj, obj); - return true; // true if statement given is NOT needed in the store - }]; //sameAs -> equate & don't add to index - if($rdf.Util.ArrayIndexOf(features, "InverseFunctionalProperty") >= 0) - this.classActions["<" + owl_ns + "InverseFunctionalProperty>"] = [ - function (formula, subj, pred, obj, addFn) { - return formula.newPropertyAction(subj, handle_IFP); // yes subj not pred! - }]; //IFP -> handle_IFP, do add to index - if($rdf.Util.ArrayIndexOf(features, "FunctionalProperty") >= 0) - this.classActions["<" + owl_ns + "FunctionalProperty>"] = [ - function (formula, subj, proj, obj, addFn) { - return formula.newPropertyAction(subj, handle_FP); - } - ]; //FP => handleFP, do add to index - function handle_IFP(formula, subj, pred, obj) { - var s1 = formula.any(undefined, pred, obj); - if(s1 == undefined) return false; // First time with this value - // tabulator.log.warn("Equating "+s1.uri+" and "+subj.uri + " because IFP "+pred.uri); //@@ - formula.equate(s1, subj); - return true; - } //handle_IFP - function handle_FP(formula, subj, pred, obj) { - var o1 = formula.any(subj, pred, undefined); - if(o1 == undefined) return false; // First time with this value - // tabulator.log.warn("Equating "+o1.uri+" and "+obj.uri + " because FP "+pred.uri); //@@ - formula.equate(o1, obj); - return true; - } //handle_FP - } /* end IndexedFormula */ - - $rdf.IndexedFormula.prototype = new $rdf.Formula(); - $rdf.IndexedFormula.prototype.constructor = $rdf.IndexedFormula; - $rdf.IndexedFormula.SuperClass = $rdf.Formula; - - $rdf.IndexedFormula.prototype.newPropertyAction = function newPropertyAction(pred, action) { - //$rdf.log.debug("newPropertyAction: "+pred); - var hash = pred.hashString(); - if(this.propertyActions[hash] == undefined) - this.propertyActions[hash] = []; - this.propertyActions[hash].push(action); - // Now apply the function to to statements already in the store - var toBeFixed = this.statementsMatching(undefined, pred, undefined); - var done = false; - for(var i = 0; i < toBeFixed.length; i++) { // NOT optimized - sort toBeFixed etc - done = done || action(this, toBeFixed[i].subject, pred, toBeFixed[i].object); - } - return done; - } - - $rdf.IndexedFormula.prototype.setPrefixForURI = function (prefix, nsuri) { - //TODO:This is a hack for our own issues, which ought to be fixed post-release - //See http://dig.csail.mit.edu/cgi-bin/roundup.cgi/$rdf/issue227 - if(prefix == "tab" && this.namespaces["tab"]) { - return; - } - this.namespaces[prefix] = nsuri - } - - // Deprocated ... name too generic - $rdf.IndexedFormula.prototype.register = function (prefix, nsuri) { - this.namespaces[prefix] = nsuri - } - - - /** simplify graph in store when we realize two identifiers are equivalent - -We replace the bigger with the smaller. - -*/ - $rdf.IndexedFormula.prototype.equate = function (u1, u2) { - // tabulator.log.warn("Equating "+u1+" and "+u2); // @@ - //@@JAMBO Must canonicalize the uris to prevent errors from a=b=c - //03-21-2010 - u1 = this.canon(u1); - u2 = this.canon(u2); - var d = u1.compareTerm(u2); - if(!d) return true; // No information in {a = a} - var big, small; - if(d < 0) { // u1 less than u2 - return this.replaceWith(u2, u1); - } else { - return this.replaceWith(u1, u2); - } - } - - // Replace big with small, obsoleted with obsoleting. - // - $rdf.IndexedFormula.prototype.replaceWith = function (big, small) { - //$rdf.log.debug("Replacing "+big+" with "+small) // @@ - var oldhash = big.hashString(); - var newhash = small.hashString(); - - var moveIndex = function (ix) { - var oldlist = ix[oldhash]; - if(oldlist == undefined) return; // none to move - var newlist = ix[newhash]; - if(newlist == undefined) { - ix[newhash] = oldlist; - } else { - ix[newhash] = oldlist.concat(newlist); - } - delete ix[oldhash]; - } - - // the canonical one carries all the indexes - for(var i = 0; i < 4; i++) { - moveIndex(this.index[i]); - } - - this.redirections[oldhash] = small; - if(big.uri) { - //@@JAMBO: must update redirections,aliases from sub-items, too. - if(this.aliases[newhash] == undefined) - this.aliases[newhash] = []; - this.aliases[newhash].push(big); // Back link - if(this.aliases[oldhash]) { - for(var i = 0; i < this.aliases[oldhash].length; i++) { - this.redirections[this.aliases[oldhash][i].hashString()] = small; - this.aliases[newhash].push(this.aliases[oldhash][i]); - } - } - - //this.add(small, this.sym('http://www.w3.org/2007/ont/link#uri'), big.uri) - - // If two things are equal, and one is requested, we should request the other. - if(this.sf) { - this.sf.nowKnownAs(big, small) - } - } - - moveIndex(this.classActions); - moveIndex(this.propertyActions); - - $rdf.log.debug("Equate done. "+big+" now links to "+small) - return true; // true means the statement does not need to be put in - }; - - // Return the symbol with canonical URI as smushed - $rdf.IndexedFormula.prototype.canon = function (term) { - if(term == undefined) return term; - var y = this.redirections[term.hashString()]; - if(y == undefined) return term; - return y; - } - - // Compare by canonical URI as smushed - $rdf.IndexedFormula.prototype.sameThings = function (x, y) { - if(x.sameTerm(y)) return true; - var x1 = this.canon(x); - // alert('x1='+x1); - if(x1 == undefined) return false; - var y1 = this.canon(y); - // alert('y1='+y1); //@@ - if(y1 == undefined) return false; - return(x1.uri == y1.uri); - } - - // A list of all the URIs by which this thing is known - $rdf.IndexedFormula.prototype.uris = function (term) { - var cterm = this.canon(term) - var terms = this.aliases[cterm.hashString()]; - if(!cterm.uri) return [] - var res = [cterm.uri] - if(terms != undefined) { - for(var i = 0; i < terms.length; i++) { - res.push(terms[i].uri) - } - } - return res - } - - // On input parameters, convert constants to terms - // - function RDFMakeTerm(formula, val, canonicalize) { - if(typeof val != 'object') { - if(typeof val == 'string') - return new $rdf.Literal(val); - if(typeof val == 'number') - return new $rdf.Literal(val); // @@ differet types - if(typeof val == 'boolean') - return new $rdf.Literal(val ? "1" : "0", undefined, $rdf.Symbol.prototype.XSDboolean); - if(typeof val == 'undefined') - return undefined; - else // @@ add converting of dates and numbers - throw new Error("Can't make Term from " + val + " of type " + typeof val); - } - return val; - } - - // Add a triple to the store - // - // Returns the statement added - // (would it be better to return the original formula for chaining?) - // - $rdf.IndexedFormula.prototype.add = function (subj, pred, obj, why) { - var actions, st; - if(why == undefined) why = this.fetcher ? this.fetcher.appNode : this.sym("chrome:theSession"); //system generated - //defined in source.js, is this OK with identity.js only user? - subj = RDFMakeTerm(this, subj); - pred = RDFMakeTerm(this, pred); - obj = RDFMakeTerm(this, obj); - why = RDFMakeTerm(this, why); - - if(this.predicateCallback != undefined) - this.predicateCallback(this, pred, why); - - // Action return true if the statement does not need to be added - var actions = this.propertyActions[this.canon(pred).hashString()]; - var done = false; - if(actions) { - // alert('type: '+typeof actions +' @@ actions='+actions); - for(var i = 0; i < actions.length; i++) { - done = done || actions[i](this, subj, pred, obj, why); - } - } - - //If we are tracking provenanance, every thing should be loaded into the store - //if (done) return new Statement(subj, pred, obj, why); // Don't put it in the store - // still return this statement for owl:sameAs input - var hash = [this.canon(subj).hashString(), this.canon(pred).hashString(), - this.canon(obj).hashString(), this.canon(why).hashString()]; - var st = new $rdf.Statement(subj, pred, obj, why); - for(var i = 0; i < 4; i++) { - var ix = this.index[i]; - var h = hash[i]; - if(ix[h] == undefined) ix[h] = []; - ix[h].push(st); // Set of things with this as subject, etc - } - - //tabulator.log.debug("ADDING {"+subj+" "+pred+" "+obj+"} "+why); - this.statements.push(st); - return st; - }; //add - // Find out whether a given URI is used as symbol in the formula - $rdf.IndexedFormula.prototype.mentionsURI = function (uri) { - var hash = '<' + uri + '>'; - return (!!this.subjectIndex[hash] - || !!this.objectIndex[hash] - || !!this.predicateIndex[hash]); - } - - // Find an unused id for a file being edited: return a symbol - // (Note: Slow iff a lot of them -- could be O(log(k)) ) - $rdf.IndexedFormula.prototype.nextSymbol = function (doc) { - for(var i = 0;; i++) { - var uri = doc.uri + '#n' + i; - if(!this.mentionsURI(uri)) return this.sym(uri); - } - } - - - $rdf.IndexedFormula.prototype.anyStatementMatching = function (subj, pred, obj, why) { - var x = this.statementsMatching(subj, pred, obj, why, true); - if(!x || x == []) return undefined; - return x[0]; - }; - - - // Return statements matching a pattern - // ALL CONVENIENCE LOOKUP FUNCTIONS RELY ON THIS! - $rdf.IndexedFormula.prototype.statementsMatching = function (subj, pred, obj, why, justOne) { - //$rdf.log.debug("Matching {"+subj+" "+pred+" "+obj+"}"); - var pat = [subj, pred, obj, why]; - var pattern = []; - var hash = []; - var wild = []; // wildcards - var given = []; // Not wild - for(var p = 0; p < 4; p++) { - pattern[p] = this.canon(RDFMakeTerm(this, pat[p])); - if(pattern[p] == undefined) { - wild.push(p); - } else { - given.push(p); - hash[p] = pattern[p].hashString(); - } - } - if(given.length == 0) { - return this.statements; - } - if(given.length == 1) { // Easy too, we have an index for that - var p = given[0]; - var list = this.index[p][hash[p]]; - if(list && justOne) { - if(list.length > 1) - list = list.slice(0, 1); - } - return list == undefined ? [] : list; - } - - // Now given.length is 2, 3 or 4. - // We hope that the scale-free nature of the data will mean we tend to get - // a short index in there somewhere! - var best = 1e10; // really bad - var best_i; - for(var i = 0; i < given.length; i++) { - var p = given[i]; // Which part we are dealing with - var list = this.index[p][hash[p]]; - if(list == undefined) return []; // No occurrences - if(list.length < best) { - best = list.length; - best_i = i; // (not p!) - } - } - - // Ok, we have picked the shortest index but now we have to filter it - var best_p = given[best_i]; - var possibles = this.index[best_p][hash[best_p]]; - var check = given.slice(0, best_i).concat(given.slice(best_i + 1)) // remove best_i - var results = []; - var parts = ['subject', 'predicate', 'object', 'why']; - for(var j = 0; j < possibles.length; j++) { - var st = possibles[j]; - for(var i = 0; i < check.length; i++) { // for each position to be checked - var p = check[i]; - if(!this.canon(st[parts[p]]).sameTerm(pattern[p])) { - st = null; - break; - } - } - if(st != null) { - results.push(st); - if(justOne) - break; - } - } - return results; - }; // statementsMatching - /** remove a particular statement from the bank **/ - $rdf.IndexedFormula.prototype.remove = function (st) { - //$rdf.log.debug("entering remove w/ st=" + st); - var term = [st.subject, st.predicate, st.object, st.why]; - for(var p = 0; p < 4; p++) { - var c = this.canon(term[p]); - var h = c.hashString(); - if(this.index[p][h] == undefined) { - //$rdf.log.warn ("Statement removal: no index '+p+': "+st); - } else { - $rdf.Util.RDFArrayRemove(this.index[p][h], st); - } - } - $rdf.Util.RDFArrayRemove(this.statements, st); - }; //remove - /** remove all statements matching args (within limit) **/ - $rdf.IndexedFormula.prototype.removeMany = function (subj, pred, obj, why, limit) { - //$rdf.log.debug("entering removeMany w/ subj,pred,obj,why,limit = " + subj +", "+ pred+", " + obj+", " + why+", " + limit); - var sts = this.statementsMatching(subj, pred, obj, why, false); - //This is a subtle bug that occcured in updateCenter.js too. - //The fact is, this.statementsMatching returns this.whyIndex instead of a copy of it - //but for perfromance consideration, it's better to just do that - //so make a copy here. - var statements = []; - for(var i = 0; i < sts.length; i++) statements.push(sts[i]); - if(limit) statements = statements.slice(0, limit); - for(var i = 0; i < statements.length; i++) this.remove(statements[i]); - }; //removeMany - /** Utility**/ - - /* @method: copyTo - @description: replace @template with @target and add appropriate triples (no triple removed) - one-direction replication -*/ - $rdf.IndexedFormula.prototype.copyTo = function (template, target, flags) { - if(!flags) flags = []; - var statList = this.statementsMatching(template); - if($rdf.Util.ArrayIndexOf(flags, 'two-direction') != -1) - statList.concat(this.statementsMatching(undefined, undefined, template)); - for(var i = 0; i < statList.length; i++) { - var st = statList[i]; - switch(st.object.termType) { - case 'symbol': - this.add(target, st.predicate, st.object); - break; - case 'literal': - case 'bnode': - case 'collection': - this.add(target, st.predicate, st.object.copy(this)); - } - if($rdf.Util.ArrayIndexOf(flags, 'delete') != -1) this.remove(st); - } - }; - //for the case when you alter this.value (text modified in userinput.js) - $rdf.Literal.prototype.copy = function () { - return new $rdf.Literal(this.value, this.lang, this.datatype); - }; - $rdf.BlankNode.prototype.copy = function (formula) { //depends on the formula - var bnodeNew = new $rdf.BlankNode(); - formula.copyTo(this, bnodeNew); - return bnodeNew; - } - /** Full N3 bits -- placeholders only to allow parsing, no functionality! **/ - - $rdf.IndexedFormula.prototype.newUniversal = function (uri) { - var x = this.sym(uri); - if(!this._universalVariables) this._universalVariables = []; - this._universalVariables.push(x); - return x; - } - - $rdf.IndexedFormula.prototype.newExistential = function (uri) { - if(!uri) return this.bnode(); - var x = this.sym(uri); - return this.declareExistential(x); - } - - $rdf.IndexedFormula.prototype.declareExistential = function (x) { - if(!this._existentialVariables) this._existentialVariables = []; - this._existentialVariables.push(x); - return x; - } - - $rdf.IndexedFormula.prototype.formula = function (features) { - return new $rdf.IndexedFormula(features); - } - - $rdf.IndexedFormula.prototype.close = function () { - return this; - } - - $rdf.IndexedFormula.prototype.hashString = $rdf.IndexedFormula.prototype.toNT; - - return $rdf.IndexedFormula; - -}(); -// ends \ No newline at end of file diff --git a/chrome/content/zotero/xpcom/rdf/init.js b/chrome/content/zotero/xpcom/rdf/init.js deleted file mode 100644 index 506ef16254..0000000000 --- a/chrome/content/zotero/xpcom/rdf/init.js +++ /dev/null @@ -1,34 +0,0 @@ -/* Set up the environment before loading the rest of the files into Zotero */ -var $rdf = { - Util: { - ArrayIndexOf: function (arr, item, i) { - //supported in all browsers except IE<9 - return arr.indexOf(item, i); - }, - RDFArrayRemove: function (a, x) { //removes all statements equal to x from a - for (var i = 0; i < a.length; i++) { - //TODO: This used to be the following, which didnt always work..why - //if(a[i] == x) - if (a[i].subject.sameTerm(x.subject) && a[i].predicate.sameTerm(x.predicate) && a[i].object.sameTerm(x.object) && a[i].why.sameTerm(x.why)) { - a.splice(i, 1); - return; - } - } - throw new Error("RDFArrayRemove: Array did not contain " + x); - } - }, - log: { - debug: Zotero.debug, - warn: Zotero.debug, - error: Zotero.debug - } -}; - -if(Zotero.RDF) { - Zotero.RDF.AJAW = $rdf; -} else { - Zotero.RDF = {AJAW:$rdf}; -} - -var tabulator = {log: $rdf.log}; -var alert = $rdf.log.warn; diff --git a/chrome/content/zotero/xpcom/rdf/match.js b/chrome/content/zotero/xpcom/rdf/match.js deleted file mode 100644 index b904bca0f9..0000000000 --- a/chrome/content/zotero/xpcom/rdf/match.js +++ /dev/null @@ -1,144 +0,0 @@ -// Matching a statement against a formula -// -// -// W3C open source licence 2005. -// -// We retpresent a set as an associative array whose value for -// each member is set to true. -$rdf.Symbol.prototype.sameTerm = function (other) { - if(!other) { - return false - } - return((this.termType == other.termType) && (this.uri == other.uri)) -} - -$rdf.BlankNode.prototype.sameTerm = function (other) { - if(!other) { - return false - } - return((this.termType == other.termType) && (this.id == other.id)) -} - -$rdf.Literal.prototype.sameTerm = function (other) { - if(!other) { - return false - } - return((this.termType == other.termType) - && (this.value == other.value) - && (this.lang == other.lang) - && ((!this.datatype && !other.datatype) - || (this.datatype && this.datatype.sameTerm(other.datatype)))) -} - -$rdf.Variable.prototype.sameTerm = function (other) { - if(!other) { - return false - } - return((this.termType == other.termType) && (this.uri == other.uri)) -} - -$rdf.Collection.prototype.sameTerm = $rdf.BlankNode.prototype.sameTerm - -$rdf.Formula.prototype.sameTerm = function (other) { - return this.hashString() == other.hashString(); -} -// Comparison for ordering -// -// These compare with ANY term -// -// -// When we smush nodes we take the lowest value. This is not -// arbitrary: we want the value actually used to be the literal -// (or list or formula). -$rdf.Literal.prototype.classOrder = 1 -$rdf.Collection.prototype.classOrder = 3 -$rdf.Formula.prototype.classOrder = 4 -$rdf.Symbol.prototype.classOrder = 5 -$rdf.BlankNode.prototype.classOrder = 6 - -// Compaisons return sign(self - other) -// Literals must come out before terms for smushing -$rdf.Literal.prototype.compareTerm = function (other) { - if(this.classOrder < other.classOrder) return -1 - if(this.classOrder > other.classOrder) return +1 - if(this.value < other.value) return -1 - if(this.value > other.value) return +1 - return 0 -} - -$rdf.Symbol.prototype.compareTerm = function (other) { - if(this.classOrder < other.classOrder) return -1 - if(this.classOrder > other.classOrder) return +1 - if(this.uri < other.uri) return -1 - if(this.uri > other.uri) return +1 - return 0 -} - -$rdf.BlankNode.prototype.compareTerm = function (other) { - if(this.classOrder < other.classOrder) return -1 - if(this.classOrder > other.classOrder) return +1 - if(this.id < other.id) return -1 - if(this.id > other.id) return +1 - return 0 -} - -$rdf.Collection.prototype.compareTerm = $rdf.BlankNode.prototype.compareTerm - -// Convenience routines -// Only one of s p o can be undefined, and w is optional. -$rdf.Formula.prototype.each = function (s, p, o, w) { - var results = [] - var st, sts = this.statementsMatching(s, p, o, w, false) - var i, n = sts.length - if(typeof s == 'undefined') { - for(i = 0; i < n; i++) { - st = sts[i]; - results.push(st.subject) - } - } else if(typeof p == 'undefined') { - for(i = 0; i < n; i++) { - st = sts[i]; - results.push(st.predicate) - } - } else if(typeof o == 'undefined') { - for(i = 0; i < n; i++) { - st = sts[i]; - results.push(st.object) - } - } else if(typeof w == 'undefined') { - for(i = 0; i < n; i++) { - st = sts[i]; - results.push(st.why) - } - } - return results -} - -$rdf.Formula.prototype.any = function (s, p, o, w) { - var st = this.anyStatementMatching(s, p, o, w) - if(typeof st == 'undefined') return undefined; - - if(typeof s == 'undefined') return st.subject; - if(typeof p == 'undefined') return st.predicate; - if(typeof o == 'undefined') return st.object; - - return undefined -} - -$rdf.Formula.prototype.holds = function (s, p, o, w) { - var st = this.anyStatementMatching(s, p, o, w) - if(typeof st == 'undefined') return false; - return true; -} - -$rdf.Formula.prototype.the = function (s, p, o, w) { - // the() should contain a check there is only one - var x = this.any(s, p, o, w) - if(typeof x == 'undefined') - $rdf.log.error("No value found for the(){" + s + " " + p + " " + o + "}.") - return x -} - -$rdf.Formula.prototype.whether = function (s, p, o, w) { - return this.statementsMatching(s, p, o, w, false).length; -} \ No newline at end of file diff --git a/chrome/content/zotero/xpcom/rdf/n3parser.js b/chrome/content/zotero/xpcom/rdf/n3parser.js deleted file mode 100644 index c91b754eae..0000000000 --- a/chrome/content/zotero/xpcom/rdf/n3parser.js +++ /dev/null @@ -1,1384 +0,0 @@ -$rdf.N3Parser = function () { - - function hexify(str) { // also used in parser - return encodeURI(str); - } - - // Things we need to define to make converted pythn code work in js - // environment of $rdf - var RDFSink_forSomeSym = "http://www.w3.org/2000/10/swap/log#forSome"; - var RDFSink_forAllSym = "http://www.w3.org/2000/10/swap/log#forAll"; - var Logic_NS = "http://www.w3.org/2000/10/swap/log#"; - - // pyjs seems to reference runtime library which I didn't find - var pyjslib_Tuple = function (theList) { - return theList - }; - - var pyjslib_List = function (theList) { - return theList - }; - - var pyjslib_Dict = function (listOfPairs) { - if(listOfPairs.length > 0) - throw new Error("missing.js: oops nnonempty dict not imp"); - return []; - } - - var pyjslib_len = function (s) { - return s.length - } - - var pyjslib_slice = function (str, i, j) { - if(typeof str.slice == 'undefined') - throw '@@ mising.js: No .slice function for ' + str + ' of type ' + (typeof str) - if((typeof j == 'undefined') || (j == null)) return str.slice(i); - return str.slice(i, j) // @ exactly the same spec? - } - var StopIteration = Error('dummy error stop iteration'); - - var pyjslib_Iterator = function (theList) { - this.last = 0; - this.li = theList; - this.next = function () { - if(this.last == this.li.length) throw StopIteration; - return this.li[this.last++]; - } - return this; - }; - - var ord = function (str) { - return str.charCodeAt(0) - } - - var string_find = function (str, s) { - return str.indexOf(s) - } - - var assertFudge = function (condition, desc) { - if(condition) return; - if(desc) throw new Error("python Assertion failed: " + desc); - throw new Error("(python) Assertion failed."); - } - - - var stringFromCharCode = function (uesc) { - return String.fromCharCode(uesc); - } - - var uripath_join = function (base, given) { - return $rdf.Util.uri.join(given, base) // sad but true - } - - var becauseSubexpression = null; // No reason needed - var diag_tracking = 0; - var diag_chatty_flag = 0; - var diag_progress = function (str) { - /*$rdf.log.debug(str);*/ - } - - // why_BecauseOfData = function(doc, reason) { return doc }; - - var RDF_type_URI = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"; - var DAML_sameAs_URI = "http://www.w3.org/2002/07/owl#sameAs"; - - /* -function SyntaxError(details) { - return new __SyntaxError(details); -} -*/ - - function __SyntaxError(details) { - this.details = details - } - - /* - -$Id: n3parser.js 14561 2008-02-23 06:37:26Z kennyluck $ - -HAND EDITED FOR CONVERSION TO JAVASCRIPT - -This module implements a Nptation3 parser, and the final -part of a notation3 serializer. - -See also: - -Notation 3 -http://www.w3.org/DesignIssues/Notation3 - -Closed World Machine - and RDF Processor -http://www.w3.org/2000/10/swap/cwm - -To DO: See also "@@" in comments - -- Clean up interfaces -______________________________________________ - -Module originally by Dan Connolly, includeing notation3 -parser and RDF generator. TimBL added RDF stream model -and N3 generation, replaced stream model with use -of common store/formula API. Yosi Scharf developped -the module, including tests and test harness. - -*/ - - var ADDED_HASH = "#"; - var LOG_implies_URI = "http://www.w3.org/2000/10/swap/log#implies"; - var INTEGER_DATATYPE = "http://www.w3.org/2001/XMLSchema#integer"; - var FLOAT_DATATYPE = "http://www.w3.org/2001/XMLSchema#double"; - var DECIMAL_DATATYPE = "http://www.w3.org/2001/XMLSchema#decimal"; - var BOOLEAN_DATATYPE = "http://www.w3.org/2001/XMLSchema#boolean"; - var option_noregen = 0; - var _notQNameChars = "\t\r\n !\"#$%&'()*.,+/;<=>?@[\\]^`{|}~"; - var _notNameChars = (_notQNameChars + ":"); - var _rdfns = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; - var N3CommentCharacter = "#"; - var eol = new RegExp("^[ \\t]*(#[^\\n]*)?\\r?\\n", 'g'); - var eof = new RegExp("^[ \\t]*(#[^\\n]*)?$", 'g'); - var ws = new RegExp("^[ \\t]*", 'g'); - var signed_integer = new RegExp("^[-+]?[0-9]+", 'g'); - var number_syntax = new RegExp("^([-+]?[0-9]+)(\\.[0-9]+)?(e[-+]?[0-9]+)?", 'g'); - var digitstring = new RegExp("^[0-9]+", 'g'); - var interesting = new RegExp("[\\\\\\r\\n\\\"]", 'g'); - var langcode = new RegExp("^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)?", 'g'); - - function SinkParser(store, openFormula, thisDoc, baseURI, genPrefix, metaURI, flags, why) { - return new __SinkParser(store, openFormula, thisDoc, baseURI, genPrefix, metaURI, flags, why); - } - - function __SinkParser(store, openFormula, thisDoc, baseURI, genPrefix, metaURI, flags, why) { - if(typeof openFormula == 'undefined') openFormula = null; - if(typeof thisDoc == 'undefined') thisDoc = ""; - if(typeof baseURI == 'undefined') baseURI = null; - if(typeof genPrefix == 'undefined') genPrefix = ""; - if(typeof metaURI == 'undefined') metaURI = null; - if(typeof flags == 'undefined') flags = ""; - if(typeof why == 'undefined') why = null; - /* - note: namespace names should *not* end in #; - the # will get added during qname processing */ - - this._bindings = new pyjslib_Dict([]); - this._flags = flags; - if((thisDoc != "")) { - assertFudge((thisDoc.indexOf(":") >= 0), ("Document URI not absolute: " + thisDoc)); - this._bindings[""] = ((thisDoc + "#")); - } - this._store = store; - if(genPrefix) { - store.setGenPrefix(genPrefix); - } - this._thisDoc = thisDoc; - this.source = store.sym(thisDoc); - this.lines = 0; - this.statementCount = 0; - this.startOfLine = 0; - this.previousLine = 0; - this._genPrefix = genPrefix; - this.keywords = new pyjslib_List(["a", "this", "bind", "has", "is", "of", "true", "false"]); - this.keywordsSet = 0; - this._anonymousNodes = new pyjslib_Dict([]); - this._variables = new pyjslib_Dict([]); - this._parentVariables = new pyjslib_Dict([]); - this._reason = why; - this._reason2 = null; - if(diag_tracking) { - this._reason2 = why_BecauseOfData(store.sym(thisDoc), this._reason); - } - if(baseURI) { - this._baseURI = baseURI; - } else { - if(thisDoc) { - this._baseURI = thisDoc; - } else { - this._baseURI = null; - } - } - assertFudge(!(this._baseURI) || (this._baseURI.indexOf(":") >= 0)); - if(!(this._genPrefix)) { - if(this._thisDoc) { - this._genPrefix = (this._thisDoc + "#_g"); - } else { - this._genPrefix = RDFSink_uniqueURI(); - } - } - if((openFormula == null)) { - if(this._thisDoc) { - this._formula = store.formula((thisDoc + "#_formula")); - } else { - this._formula = store.formula(); - } - } else { - this._formula = openFormula; - } - this._context = this._formula; - this._parentContext = null; - } - __SinkParser.prototype.here = function (i) { - return((((this._genPrefix + "_L") + this.lines) + "C") + ((i - this.startOfLine) + 1)); - }; - __SinkParser.prototype.formula = function () { - return this._formula; - }; - __SinkParser.prototype.loadStream = function (stream) { - return this.loadBuf(stream.read()); - }; - __SinkParser.prototype.loadBuf = function (buf) { - /* - Parses a buffer and returns its top level formula*/ - - this.startDoc(); - this.feed(buf); - return this.endDoc(); - }; - __SinkParser.prototype.feed = function (octets) { - /* - Feed an octet stream tothe parser - - if BadSyntax is raised, the string - passed in the exception object is the - remainder after any statements have been parsed. - So if there is more data to feed to the - parser, it should be straightforward to recover.*/ - - var str = octets; - var i = 0; - while((i >= 0)) { - var j = this.skipSpace(str, i); - if((j < 0)) { - return; - } - var i = this.directiveOrStatement(str, j); - if((i < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, j, "expected directive or statement"); - } - } - }; - __SinkParser.prototype.directiveOrStatement = function (str, h) { - var i = this.skipSpace(str, h); - if((i < 0)) { - return i; - } - var j = this.directive(str, i); - if((j >= 0)) { - return this.checkDot(str, j); - } - var j = this.statement(str, i); - if((j >= 0)) { - return this.checkDot(str, j); - } - return j; - }; - __SinkParser.prototype.tok = function (tok, str, i) { - /* - Check for keyword. Space must have been stripped on entry and - we must not be at end of file.*/ - var whitespace = "\t\n\v\f\r "; - if((pyjslib_slice(str, i, (i + 1)) == "@")) { - var i = (i + 1); - } else { - if(($rdf.Util.ArrayIndexOf(this.keywords, tok) < 0)) { - return -1; - } - } - var k = (i + pyjslib_len(tok)); - if((pyjslib_slice(str, i, k) == tok) && (_notQNameChars.indexOf(str.charAt(k)) >= 0)) { - return k; - } else { - return -1; - } - }; - __SinkParser.prototype.directive = function (str, i) { - var j = this.skipSpace(str, i); - if((j < 0)) { - return j; - } - var res = new pyjslib_List([]); - var j = this.tok("bind", str, i); - if((j > 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, "keyword bind is obsolete: use @prefix"); - } - var j = this.tok("keywords", str, i); - if((j > 0)) { - var i = this.commaSeparatedList(str, j, res, false); - if((i < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, "'@keywords' needs comma separated list of words"); - } - this.setKeywords(pyjslib_slice(res, null, null)); - if((diag_chatty_flag > 80)) { - diag_progress("Keywords ", this.keywords); - } - return i; - } - var j = this.tok("forAll", str, i); - if((j > 0)) { - var i = this.commaSeparatedList(str, j, res, true); - if((i < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, "Bad variable list after @forAll"); - } - - var __x = new pyjslib_Iterator(res); - try { - while(true) { - var x = __x.next(); - - - if($rdf.Util.ArrayIndexOf(this._variables, x) < 0 || ($rdf.Util.ArrayIndexOf(this._parentVariables, x) >= 0)) { - this._variables[x] = (this._context.newUniversal(x)); - } - - } - } catch(e) { - if(e != StopIteration) { - throw e; - } - } - - return i; - } - var j = this.tok("forSome", str, i); - if((j > 0)) { - var i = this.commaSeparatedList(str, j, res, this.uri_ref2); - if((i < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, "Bad variable list after @forSome"); - } - - var __x = new pyjslib_Iterator(res); - try { - while(true) { - var x = __x.next(); - - - this._context.declareExistential(x); - - } - } catch(e) { - if(e != StopIteration) { - throw e; - } - } - - return i; - } - var j = this.tok("prefix", str, i); - if((j >= 0)) { - var t = new pyjslib_List([]); - var i = this.qname(str, j, t); - if((i < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, j, "expected qname after @prefix"); - } - var j = this.uri_ref2(str, i, t); - if((j < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, "expected after @prefix _qname_"); - } - var ns = t[1].uri; - if(this._baseURI) { - var ns = uripath_join(this._baseURI, ns); - } else { - assertFudge((ns.indexOf(":") >= 0), "With no base URI, cannot handle relative URI for NS"); - } - assertFudge((ns.indexOf(":") >= 0)); - this._bindings[t[0][0]] = (ns); - - this.bind(t[0][0], hexify(ns)); - return j; - } - var j = this.tok("base", str, i); - if((j >= 0)) { - var t = new pyjslib_List([]); - var i = this.uri_ref2(str, j, t); - if((i < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, j, "expected after @base "); - } - var ns = t[0].uri; - if(this._baseURI) { - var ns = uripath_join(this._baseURI, ns); - } else { - throw BadSyntax(this._thisDoc, this.lines, str, j, (("With no previous base URI, cannot use relative URI in @base <" + ns) + ">")); - } - assertFudge((ns.indexOf(":") >= 0)); - this._baseURI = ns; - return i; - } - return -1; - }; - __SinkParser.prototype.bind = function (qn, uri) { - if((qn == "")) { - } else { - this._store.setPrefixForURI(qn, uri); - } - }; - __SinkParser.prototype.setKeywords = function (k) { - /* - Takes a list of strings*/ - - if((k == null)) { - this.keywordsSet = 0; - } else { - this.keywords = k; - this.keywordsSet = 1; - } - }; - __SinkParser.prototype.startDoc = function () {}; - __SinkParser.prototype.endDoc = function () { - /* - Signal end of document and stop parsing. returns formula*/ - - return this._formula; - }; - __SinkParser.prototype.makeStatement = function (quad) { - quad[0].add(quad[2], quad[1], quad[3], this.source); - this.statementCount += 1; - }; - __SinkParser.prototype.statement = function (str, i) { - var r = new pyjslib_List([]); - var i = this.object(str, i, r); - if((i < 0)) { - return i; - } - var j = this.property_list(str, i, r[0]); - if((j < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, "expected propertylist"); - } - return j; - }; - __SinkParser.prototype.subject = function (str, i, res) { - return this.item(str, i, res); - }; - __SinkParser.prototype.verb = function (str, i, res) { - /* - has _prop_ - is _prop_ of - a - = - _prop_ - >- prop -> - <- prop -< - _operator_*/ - - var j = this.skipSpace(str, i); - if((j < 0)) { - return j; - } - var r = new pyjslib_List([]); - var j = this.tok("has", str, i); - if((j >= 0)) { - var i = this.prop(str, j, r); - if((i < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, j, "expected property after 'has'"); - } - res.push(new pyjslib_Tuple(["->", r[0]])); - return i; - } - var j = this.tok("is", str, i); - if((j >= 0)) { - var i = this.prop(str, j, r); - if((i < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, j, "expected after 'is'"); - } - var j = this.skipSpace(str, i); - if((j < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, "End of file found, expected property after 'is'"); - return j; - } - var i = j; - var j = this.tok("of", str, i); - if((j < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, "expected 'of' after 'is' "); - } - res.push(new pyjslib_Tuple(["<-", r[0]])); - return j; - } - var j = this.tok("a", str, i); - if((j >= 0)) { - res.push(new pyjslib_Tuple(["->", this._store.sym(RDF_type_URI)])); - return j; - } - if((pyjslib_slice(str, i, (i + 2)) == "<=")) { - res.push(new pyjslib_Tuple(["<-", this._store.sym((Logic_NS + "implies"))])); - return(i + 2); - } - if((pyjslib_slice(str, i, (i + 1)) == "=")) { - if((pyjslib_slice(str, (i + 1), (i + 2)) == ">")) { - res.push(new pyjslib_Tuple(["->", this._store.sym((Logic_NS + "implies"))])); - return(i + 2); - } - res.push(new pyjslib_Tuple(["->", this._store.sym(DAML_sameAs_URI)])); - return(i + 1); - } - if((pyjslib_slice(str, i, (i + 2)) == ":=")) { - res.push(new pyjslib_Tuple(["->", (Logic_NS + "becomes")])); - return(i + 2); - } - var j = this.prop(str, i, r); - if((j >= 0)) { - res.push(new pyjslib_Tuple(["->", r[0]])); - return j; - } - if((pyjslib_slice(str, i, (i + 2)) == ">-") || (pyjslib_slice(str, i, (i + 2)) == "<-")) { - throw BadSyntax(this._thisDoc, this.lines, str, j, ">- ... -> syntax is obsolete."); - } - return -1; - }; - __SinkParser.prototype.prop = function (str, i, res) { - return this.item(str, i, res); - }; - __SinkParser.prototype.item = function (str, i, res) { - return this.path(str, i, res); - }; - __SinkParser.prototype.blankNode = function (uri) { - return this._context.bnode(uri, this._reason2); - }; - __SinkParser.prototype.path = function (str, i, res) { - /* - Parse the path production. - */ - - var j = this.nodeOrLiteral(str, i, res); - if((j < 0)) { - return j; - } - while(("!^.".indexOf(pyjslib_slice(str, j, (j + 1))) >= 0)) { - var ch = pyjslib_slice(str, j, (j + 1)); - if((ch == ".")) { - var ahead = pyjslib_slice(str, (j + 1), (j + 2)); - if(!(ahead) || (_notNameChars.indexOf(ahead) >= 0) && (":?<[{(".indexOf(ahead) < 0)) { - break; - } - } - var subj = res.pop(); - var obj = this.blankNode(this.here(j)); - var j = this.node(str, (j + 1), res); - if((j < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, j, "EOF found in middle of path syntax"); - } - var pred = res.pop(); - if((ch == "^")) { - this.makeStatement(new pyjslib_Tuple([this._context, pred, obj, subj])); - } else { - this.makeStatement(new pyjslib_Tuple([this._context, pred, subj, obj])); - } - res.push(obj); - } - return j; - }; - __SinkParser.prototype.anonymousNode = function (ln) { - /* - Remember or generate a term for one of these _: anonymous nodes*/ - - var term = this._anonymousNodes[ln]; - if(term) { - return term; - } - var term = this._store.bnode(this._context, this._reason2); - this._anonymousNodes[ln] = (term); - return term; - }; - __SinkParser.prototype.node = function (str, i, res, subjectAlready) { - if(typeof subjectAlready == 'undefined') subjectAlready = null; - /* - Parse the production. - Space is now skipped once at the beginning - instead of in multipe calls to self.skipSpace(). - */ - - var subj = subjectAlready; - var j = this.skipSpace(str, i); - if((j < 0)) { - return j; - } - var i = j; - var ch = pyjslib_slice(str, i, (i + 1)); - if((ch == "[")) { - var bnodeID = this.here(i); - var j = this.skipSpace(str, (i + 1)); - if((j < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, "EOF after '['"); - } - if((pyjslib_slice(str, j, (j + 1)) == "=")) { - var i = (j + 1); - var objs = new pyjslib_List([]); - var j = this.objectList(str, i, objs); - - if((j >= 0)) { - var subj = objs[0]; - if((pyjslib_len(objs) > 1)) { - - var __obj = new pyjslib_Iterator(objs); - try { - while(true) { - var obj = __obj.next(); - - - this.makeStatement(new pyjslib_Tuple([this._context, this._store.sym(DAML_sameAs_URI), subj, obj])); - - } - } catch(e) { - if(e != StopIteration) { - throw e; - } - } - - } - var j = this.skipSpace(str, j); - if((j < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, "EOF when objectList expected after [ = "); - } - if((pyjslib_slice(str, j, (j + 1)) == ";")) { - var j = (j + 1); - } - } else { - throw BadSyntax(this._thisDoc, this.lines, str, i, "objectList expected after [= "); - } - } - if((subj == null)) { - var subj = this.blankNode(bnodeID); - } - var i = this.property_list(str, j, subj); - if((i < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, j, "property_list expected"); - } - var j = this.skipSpace(str, i); - if((j < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, "EOF when ']' expected after [ "); - } - if((pyjslib_slice(str, j, (j + 1)) != "]")) { - throw BadSyntax(this._thisDoc, this.lines, str, j, "']' expected"); - } - res.push(subj); - return(j + 1); - } - if((ch == "{")) { - var ch2 = pyjslib_slice(str, (i + 1), (i + 2)); - if((ch2 == "$")) { - i += 1; - var j = (i + 1); - var mylist = new pyjslib_List([]); - var first_run = true; - while(1) { - var i = this.skipSpace(str, j); - if((i < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, "needed '$}', found end."); - } - if((pyjslib_slice(str, i, (i + 2)) == "$}")) { - var j = (i + 2); - break; - } - if(!(first_run)) { - if((pyjslib_slice(str, i, (i + 1)) == ",")) { - i += 1; - } else { - throw BadSyntax(this._thisDoc, this.lines, str, i, "expected: ','"); - } - } else { - var first_run = false; - } - var item = new pyjslib_List([]); - var j = this.item(str, i, item); - if((j < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, "expected item in set or '$}'"); - } - mylist.push(item[0]); - } - res.push(this._store.newSet(mylist, this._context)); - return j; - } else { - var j = (i + 1); - var oldParentContext = this._parentContext; - this._parentContext = this._context; - var parentAnonymousNodes = this._anonymousNodes; - var grandParentVariables = this._parentVariables; - this._parentVariables = this._variables; - this._anonymousNodes = new pyjslib_Dict([]); - this._variables = this._variables.slice(); - var reason2 = this._reason2; - this._reason2 = becauseSubexpression; - if((subj == null)) { - var subj = this._store.formula(); - } - this._context = subj; - while(1) { - var i = this.skipSpace(str, j); - if((i < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, "needed '}', found end."); - } - if((pyjslib_slice(str, i, (i + 1)) == "}")) { - var j = (i + 1); - break; - } - var j = this.directiveOrStatement(str, i); - if((j < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, "expected statement or '}'"); - } - } - this._anonymousNodes = parentAnonymousNodes; - this._variables = this._parentVariables; - this._parentVariables = grandParentVariables; - this._context = this._parentContext; - this._reason2 = reason2; - this._parentContext = oldParentContext; - res.push(subj.close()); - return j; - } - } - if((ch == "(")) { - var thing_type = this._store.list; - var ch2 = pyjslib_slice(str, (i + 1), (i + 2)); - if((ch2 == "$")) { - var thing_type = this._store.newSet; - i += 1; - } - var j = (i + 1); - var mylist = new pyjslib_List([]); - while(1) { - var i = this.skipSpace(str, j); - if((i < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, "needed ')', found end."); - } - if((pyjslib_slice(str, i, (i + 1)) == ")")) { - var j = (i + 1); - break; - } - var item = new pyjslib_List([]); - var j = this.item(str, i, item); - if((j < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, "expected item in list or ')'"); - } - mylist.push(item[0]); - } - res.push(thing_type(mylist, this._context)); - return j; - } - var j = this.tok("this", str, i); - if((j >= 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, "Keyword 'this' was ancient N3. Now use @forSome and @forAll keywords."); - res.push(this._context); - return j; - } - var j = this.tok("true", str, i); - if((j >= 0)) { - res.push(true); - return j; - } - var j = this.tok("false", str, i); - if((j >= 0)) { - res.push(false); - return j; - } - if((subj == null)) { - var j = this.uri_ref2(str, i, res); - if((j >= 0)) { - return j; - } - } - return -1; - }; - __SinkParser.prototype.property_list = function (str, i, subj) { - /* - Parse property list - Leaves the terminating punctuation in the buffer - */ - - while(1) { - var j = this.skipSpace(str, i); - if((j < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, "EOF found when expected verb in property list"); - return j; - } - if((pyjslib_slice(str, j, (j + 2)) == ":-")) { - var i = (j + 2); - var res = new pyjslib_List([]); - var j = this.node(str, i, res, subj); - if((j < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, "bad {} or () or [] node after :- "); - } - var i = j; - continue; - } - var i = j; - var v = new pyjslib_List([]); - var j = this.verb(str, i, v); - if((j <= 0)) { - return i; - } - var objs = new pyjslib_List([]); - var i = this.objectList(str, j, objs); - if((i < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, j, "objectList expected"); - } - - var __obj = new pyjslib_Iterator(objs); - try { - while(true) { - var obj = __obj.next(); - - - var pairFudge = v[0]; - var dir = pairFudge[0]; - var sym = pairFudge[1]; - if((dir == "->")) { - this.makeStatement(new pyjslib_Tuple([this._context, sym, subj, obj])); - } else { - this.makeStatement(new pyjslib_Tuple([this._context, sym, obj, subj])); - } - - } - } catch(e) { - if(e != StopIteration) { - throw e; - } - } - - var j = this.skipSpace(str, i); - if((j < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, j, "EOF found in list of objects"); - return j; - } - if((pyjslib_slice(str, i, (i + 1)) != ";")) { - return i; - } - var i = (i + 1); - } - }; - __SinkParser.prototype.commaSeparatedList = function (str, j, res, ofUris) { - /* - return value: -1 bad syntax; >1 new position in str - res has things found appended - - Used to use a final value of the function to be called, e.g. this.bareWord - but passing the function didn't work fo js converion pyjs - */ - - var i = this.skipSpace(str, j); - if((i < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, "EOF found expecting comma sep list"); - return i; - } - if((str.charAt(i) == ".")) { - return j; - } - if(ofUris) { - var i = this.uri_ref2(str, i, res); - } else { - var i = this.bareWord(str, i, res); - } - if((i < 0)) { - return -1; - } - while(1) { - var j = this.skipSpace(str, i); - if((j < 0)) { - return j; - } - var ch = pyjslib_slice(str, j, (j + 1)); - if((ch != ",")) { - if((ch != ".")) { - return -1; - } - return j; - } - if(ofUris) { - var i = this.uri_ref2(str, (j + 1), res); - } else { - var i = this.bareWord(str, (j + 1), res); - } - if((i < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, "bad list content"); - return i; - } - } - }; - __SinkParser.prototype.objectList = function (str, i, res) { - var i = this.object(str, i, res); - if((i < 0)) { - return -1; - } - while(1) { - var j = this.skipSpace(str, i); - if((j < 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, j, "EOF found after object"); - return j; - } - if((pyjslib_slice(str, j, (j + 1)) != ",")) { - return j; - } - var i = this.object(str, (j + 1), res); - if((i < 0)) { - return i; - } - } - }; - __SinkParser.prototype.checkDot = function (str, i) { - var j = this.skipSpace(str, i); - if((j < 0)) { - return j; - } - if((pyjslib_slice(str, j, (j + 1)) == ".")) { - return(j + 1); - } - if((pyjslib_slice(str, j, (j + 1)) == "}")) { - return j; - } - if((pyjslib_slice(str, j, (j + 1)) == "]")) { - return j; - } - throw BadSyntax(this._thisDoc, this.lines, str, j, "expected '.' or '}' or ']' at end of statement"); - return i; - }; - __SinkParser.prototype.uri_ref2 = function (str, i, res) { - /* - Generate uri from n3 representation. - - Note that the RDF convention of directly concatenating - NS and local name is now used though I prefer inserting a '#' - to make the namesapces look more like what XML folks expect. - */ - - var qn = new pyjslib_List([]); - var j = this.qname(str, i, qn); - if((j >= 0)) { - var pairFudge = qn[0]; - var pfx = pairFudge[0]; - var ln = pairFudge[1]; - if((pfx == null)) { - assertFudge(0, "not used?"); - var ns = (this._baseURI + ADDED_HASH); - } else { - var ns = this._bindings[pfx]; - if(!(ns)) { - if((pfx == "_")) { - res.push(this.anonymousNode(ln)); - return j; - } - throw BadSyntax(this._thisDoc, this.lines, str, i, (("Prefix " + pfx) + " not bound.")); - } - } - var symb = this._store.sym((ns + ln)); - if(($rdf.Util.ArrayIndexOf(this._variables, symb) >= 0)) { - res.push(this._variables[symb]); - } else { - res.push(symb); - } - return j; - } - var i = this.skipSpace(str, i); - if((i < 0)) { - return -1; - } - if((str.charAt(i) == "?")) { - var v = new pyjslib_List([]); - var j = this.variable(str, i, v); - if((j > 0)) { - res.push(v[0]); - return j; - } - return -1; - } else if((str.charAt(i) == "<")) { - var i = (i + 1); - var st = i; - while((i < pyjslib_len(str))) { - if((str.charAt(i) == ">")) { - var uref = pyjslib_slice(str, st, i); - if(this._baseURI) { - var uref = uripath_join(this._baseURI, uref); - } else { - assertFudge((uref.indexOf(":") >= 0), "With no base URI, cannot deal with relative URIs"); - } - if((pyjslib_slice(str, (i - 1), i) == "#") && !((pyjslib_slice(uref, -1, null) == "#"))) { - var uref = (uref + "#"); - } - var symb = this._store.sym(uref); - if(($rdf.Util.ArrayIndexOf(this._variables, symb) >= 0)) { - res.push(this._variables[symb]); - } else { - res.push(symb); - } - return(i + 1); - } - var i = (i + 1); - } - throw BadSyntax(this._thisDoc, this.lines, str, j, "unterminated URI reference"); - } else if(this.keywordsSet) { - var v = new pyjslib_List([]); - var j = this.bareWord(str, i, v); - if((j < 0)) { - return -1; - } - if(($rdf.Util.ArrayIndexOf(this.keywords, v[0]) >= 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, (("Keyword \"" + v[0]) + "\" not allowed here.")); - } - res.push(this._store.sym((this._bindings[""] + v[0]))); - return j; - } else { - return -1; - } - }; - __SinkParser.prototype.skipSpace = function (str, i) { - /* - Skip white space, newlines and comments. - return -1 if EOF, else position of first non-ws character*/ - var tmp = str; - var whitespace = ' \n\r\t\f\x0b\xa0\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u3000'; - for(var j = (i ? i : 0); j < str.length; j++) { - if(whitespace.indexOf(str.charAt(j)) === -1) { - if(str.charAt(j) === '#') { - str = str.slice(i).replace(/^[^\n]*\n/, ""); - i = 0; - j = -1; - } else { - break; - } - } - } - var val = (tmp.length - str.length) + j; - if(val === tmp.length) { - return -1; - } - return val; - }; - __SinkParser.prototype.variable = function (str, i, res) { - /* - ?abc -> variable(:abc) - */ - - var j = this.skipSpace(str, i); - if((j < 0)) { - return -1; - } - if((pyjslib_slice(str, j, (j + 1)) != "?")) { - return -1; - } - var j = (j + 1); - var i = j; - if(("0123456789-".indexOf(str.charAt(j)) >= 0)) { - throw BadSyntax(this._thisDoc, this.lines, str, j, (("Varible name can't start with '" + str.charAt(j)) + "s'")); - return -1; - } - while((i < pyjslib_len(str)) && (_notNameChars.indexOf(str.charAt(i)) < 0)) { - var i = (i + 1); - } - if((this._parentContext == null)) { - throw BadSyntax(this._thisDoc, this.lines, str, j, ("Can't use ?xxx syntax for variable in outermost level: " + pyjslib_slice(str, (j - 1), i))); - } - res.push(this._store.variable(pyjslib_slice(str, j, i))); - return i; - }; - __SinkParser.prototype.bareWord = function (str, i, res) { - /* - abc -> :abc - */ - - var j = this.skipSpace(str, i); - if((j < 0)) { - return -1; - } - var ch = str.charAt(j); - if(("0123456789-".indexOf(ch) >= 0)) { - return -1; - } - if((_notNameChars.indexOf(ch) >= 0)) { - return -1; - } - var i = j; - while((i < pyjslib_len(str)) && (_notNameChars.indexOf(str.charAt(i)) < 0)) { - var i = (i + 1); - } - res.push(pyjslib_slice(str, j, i)); - return i; - }; - __SinkParser.prototype.qname = function (str, i, res) { - /* - - xyz:def -> ('xyz', 'def') - If not in keywords and keywordsSet: def -> ('', 'def') - :def -> ('', 'def') - */ - - var i = this.skipSpace(str, i); - if((i < 0)) { - return -1; - } - var c = str.charAt(i); - if(("0123456789-+".indexOf(c) >= 0)) { - return -1; - } - if((_notNameChars.indexOf(c) < 0)) { - var ln = c; - var i = (i + 1); - while((i < pyjslib_len(str))) { - var c = str.charAt(i); - if((_notNameChars.indexOf(c) < 0)) { - var ln = (ln + c); - var i = (i + 1); - } else { - break; - } - } - } else { - var ln = ""; - } - if((i < pyjslib_len(str)) && (str.charAt(i) == ":")) { - var pfx = ln; - var i = (i + 1); - var ln = ""; - while((i < pyjslib_len(str))) { - var c = str.charAt(i); - if((_notNameChars.indexOf(c) < 0)) { - var ln = (ln + c); - var i = (i + 1); - } else { - break; - } - } - res.push(new pyjslib_Tuple([pfx, ln])); - return i; - } else { - if(ln && this.keywordsSet && ($rdf.Util.ArrayIndexOf(this.keywords, ln) < 0)) { - res.push(new pyjslib_Tuple(["", ln])); - return i; - } - return -1; - } - }; - __SinkParser.prototype.object = function (str, i, res) { - var j = this.subject(str, i, res); - if((j >= 0)) { - return j; - } else { - var j = this.skipSpace(str, i); - if((j < 0)) { - return -1; - } else { - var i = j; - } - if((str.charAt(i) == "\"")) { - if((pyjslib_slice(str, i, (i + 3)) == "\"\"\"")) { - var delim = "\"\"\""; - } else { - var delim = "\""; - } - var i = (i + pyjslib_len(delim)); - var pairFudge = this.strconst(str, i, delim); - var j = pairFudge[0]; - var s = pairFudge[1]; - res.push(this._store.literal(s)); - diag_progress("New string const ", s, j); - return j; - } else { - return -1; - } - } - }; - __SinkParser.prototype.nodeOrLiteral = function (str, i, res) { - var j = this.node(str, i, res); - if((j >= 0)) { - return j; - } else { - var j = this.skipSpace(str, i); - if((j < 0)) { - return -1; - } else { - var i = j; - } - var ch = str.charAt(i); - if(("-+0987654321".indexOf(ch) >= 0)) { - number_syntax.lastIndex = 0; - var m = number_syntax.exec(str.slice(i)); - if((m == null)) { - throw BadSyntax(this._thisDoc, this.lines, str, i, "Bad number syntax"); - } - var j = (i + number_syntax.lastIndex); - var val = pyjslib_slice(str, i, j); - if((val.indexOf("e") >= 0)) { - res.push(this._store.literal(parseFloat(val), undefined, this._store.sym(FLOAT_DATATYPE))); - } else if((pyjslib_slice(str, i, j).indexOf(".") >= 0)) { - res.push(this._store.literal(parseFloat(val), undefined, this._store.sym(DECIMAL_DATATYPE))); - } else { - res.push(this._store.literal(parseInt(val), undefined, this._store.sym(INTEGER_DATATYPE))); - } - return j; - } - if((str.charAt(i) == "\"")) { - if((pyjslib_slice(str, i, (i + 3)) == "\"\"\"")) { - var delim = "\"\"\""; - } else { - var delim = "\""; - } - var i = (i + pyjslib_len(delim)); - var dt = null; - var pairFudge = this.strconst(str, i, delim); - var j = pairFudge[0]; - var s = pairFudge[1]; - var lang = null; - if((pyjslib_slice(str, j, (j + 1)) == "@")) { - langcode.lastIndex = 0; - - var m = langcode.exec(str.slice((j + 1))); - if((m == null)) { - throw BadSyntax(this._thisDoc, startline, str, i, "Bad language code syntax on string literal, after @"); - } - var i = ((langcode.lastIndex + j) + 1); - - var lang = pyjslib_slice(str, (j + 1), i); - var j = i; - } - if((pyjslib_slice(str, j, (j + 2)) == "^^")) { - var res2 = new pyjslib_List([]); - var j = this.uri_ref2(str, (j + 2), res2); - var dt = res2[0]; - } - res.push(this._store.literal(s, lang, dt)); - return j; - } else { - return -1; - } - } - }; - __SinkParser.prototype.strconst = function (str, i, delim) { - /* - parse an N3 string constant delimited by delim. - return index, val - */ - - var j = i; - var ustr = ""; - var startline = this.lines; - while((j < pyjslib_len(str))) { - var i = (j + pyjslib_len(delim)); - if((pyjslib_slice(str, j, i) == delim)) { - return new pyjslib_Tuple([i, ustr]); - } - if((str.charAt(j) == "\"")) { - var ustr = (ustr + "\""); - var j = (j + 1); - continue; - } - interesting.lastIndex = 0; - var m = interesting.exec(str.slice(j)); - if(!(m)) { - throw BadSyntax(this._thisDoc, startline, str, j, ((("Closing quote missing in string at ^ in " + pyjslib_slice(str, (j - 20), j)) + "^") + pyjslib_slice(str, j, (j + 20)))); - } - var i = ((j + interesting.lastIndex) - 1); - var ustr = (ustr + pyjslib_slice(str, j, i)); - var ch = str.charAt(i); - if((ch == "\"")) { - var j = i; - continue; - } else if((ch == "\r")) { - var j = (i + 1); - continue; - } else if((ch == "\n")) { - if((delim == "\"")) { - throw BadSyntax(this._thisDoc, startline, str, i, "newline found in string literal"); - } - this.lines = (this.lines + 1); - var ustr = (ustr + ch); - var j = (i + 1); - this.previousLine = this.startOfLine; - this.startOfLine = j; - } else if((ch == "\\")) { - var j = (i + 1); - var ch = pyjslib_slice(str, j, (j + 1)); - if(!(ch)) { - throw BadSyntax(this._thisDoc, startline, str, i, "unterminated string literal (2)"); - } - var k = string_find("abfrtvn\\\"", ch); - if((k >= 0)) { - var uch = "\a\b\f\r\t\v\n\\\"".charAt(k); - var ustr = (ustr + uch); - var j = (j + 1); - } else if((ch == "u")) { - var pairFudge = this.uEscape(str, (j + 1), startline); - var j = pairFudge[0]; - var ch = pairFudge[1]; - var ustr = (ustr + ch); - } else if((ch == "U")) { - var pairFudge = this.UEscape(str, (j + 1), startline); - var j = pairFudge[0]; - var ch = pairFudge[1]; - var ustr = (ustr + ch); - } else { - throw BadSyntax(this._thisDoc, this.lines, str, i, "bad escape"); - } - } - } - throw BadSyntax(this._thisDoc, this.lines, str, i, "unterminated string literal"); - }; - __SinkParser.prototype.uEscape = function (str, i, startline) { - var j = i; - var count = 0; - var value = 0; - while((count < 4)) { - var chFudge = pyjslib_slice(str, j, (j + 1)); - var ch = chFudge.toLowerCase(); - var j = (j + 1); - if((ch == "")) { - throw BadSyntax(this._thisDoc, startline, str, i, "unterminated string literal(3)"); - } - var k = string_find("0123456789abcdef", ch); - if((k < 0)) { - throw BadSyntax(this._thisDoc, startline, str, i, "bad string literal hex escape"); - } - var value = ((value * 16) + k); - var count = (count + 1); - } - var uch = String.fromCharCode(value); - return new pyjslib_Tuple([j, uch]); - }; - __SinkParser.prototype.UEscape = function (str, i, startline) { - var j = i; - var count = 0; - var value = "\\U"; - while((count < 8)) { - var chFudge = pyjslib_slice(str, j, (j + 1)); - var ch = chFudge.toLowerCase(); - var j = (j + 1); - if((ch == "")) { - throw BadSyntax(this._thisDoc, startline, str, i, "unterminated string literal(3)"); - } - var k = string_find("0123456789abcdef", ch); - if((k < 0)) { - throw BadSyntax(this._thisDoc, startline, str, i, "bad string literal hex escape"); - } - var value = (value + ch); - var count = (count + 1); - } - var uch = stringFromCharCode((("0x" + pyjslib_slice(value, 2, 10)) - 0)); - return new pyjslib_Tuple([j, uch]); - }; - - function BadSyntax(uri, lines, str, i, why) { - return(((((((("Line " + (lines + 1)) + " of <") + uri) + ">: Bad syntax: ") + why) + "\nat: \"") + pyjslib_slice(str, i, (i + 30))) + "\""); - } - - - function stripCR(str) { - var res = ""; - - var __ch = new pyjslib_Iterator(str); - try { - while(true) { - var ch = __ch.next(); - - - if((ch != "\r")) { - var res = (res + ch); - } - - } - } catch(e) { - if(e != StopIteration) { - throw e; - } - } - - return res; - } - - - function dummyWrite(x) { - } - - return SinkParser; - -}(); \ No newline at end of file diff --git a/chrome/content/zotero/xpcom/rdf/rdfparser.js b/chrome/content/zotero/xpcom/rdf/rdfparser.js deleted file mode 100644 index b88cd641ef..0000000000 --- a/chrome/content/zotero/xpcom/rdf/rdfparser.js +++ /dev/null @@ -1,569 +0,0 @@ -/** - * @fileoverview - * TABULATOR RDF PARSER - * - * Version 0.1 - * Parser believed to be in full positive RDF/XML parsing compliance - * with the possible exception of handling deprecated RDF attributes - * appropriately. Parser is believed to comply fully with other W3C - * and industry standards where appropriate (DOM, ECMAScript, &c.) - * - * Author: David Sheets - * SVN ID: $Id$ - * - * W3C® SOFTWARE NOTICE AND LICENSE - * http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 - * This work (and included software, documentation such as READMEs, or - * other related items) is being provided by the copyright holders under - * the following license. By obtaining, using and/or copying this work, - * you (the licensee) agree that you have read, understood, and will - * comply with the following terms and conditions. - * - * Permission to copy, modify, and distribute this software and its - * documentation, with or without modification, for any purpose and - * without fee or royalty is hereby granted, provided that you include - * the following on ALL copies of the software and documentation or - * portions thereof, including modifications: - * - * 1. The full text of this NOTICE in a location viewable to users of - * the redistributed or derivative work. - * 2. Any pre-existing intellectual property disclaimers, notices, or terms and - * conditions. If none exist, the W3C Software Short Notice should be - * included (hypertext is preferred, text is permitted) within the body - * of any redistributed or derivative code. - * 3. Notice of any changes or modifications to the files, including the - * date changes were made. (We recommend you provide URIs to the location - * from which the code is derived.) - * - * THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT - * HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, - * INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS - * FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR - * DOCUMENTATION WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, - * TRADEMARKS OR OTHER RIGHTS. - * - * COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL - * OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR - * DOCUMENTATION. - * - * The name and trademarks of copyright holders may NOT be used in - * advertising or publicity pertaining to the software without specific, - * written prior permission. Title to copyright in this software and any - * associated documentation will at all times remain with copyright - * holders. - */ -/** - * @class Class defining an RDFParser resource object tied to an RDFStore - * - * @author David Sheets - * @version 0.1 - * - * @constructor - * @param {RDFStore} store An RDFStore object - */ -$rdf.RDFParser = function (store) { - var RDFParser = {}; - - /** Standard namespaces that we know how to handle @final - * @member RDFParser - */ - RDFParser['ns'] = { - 'RDF': "http://www.w3.org/1999/02/22-rdf-syntax-ns#", - 'RDFS': "http://www.w3.org/2000/01/rdf-schema#" - } - /** DOM Level 2 node type magic numbers @final - * @member RDFParser - */ - RDFParser['nodeType'] = { - 'ELEMENT': 1, - 'ATTRIBUTE': 2, - 'TEXT': 3, - 'CDATA_SECTION': 4, - 'ENTITY_REFERENCE': 5, - 'ENTITY': 6, - 'PROCESSING_INSTRUCTION': 7, - 'COMMENT': 8, - 'DOCUMENT': 9, - 'DOCUMENT_TYPE': 10, - 'DOCUMENT_FRAGMENT': 11, - 'NOTATION': 12 - } - - /** - * Frame class for namespace and base URI lookups - * Base lookups will always resolve because the parser knows - * the default base. - * - * @private - */ - this['frameFactory'] = function (parser, parent, element) { - return { - 'NODE': 1, - 'ARC': 2, - 'parent': parent, - 'parser': parser, - 'store': parser['store'], - 'element': element, - 'lastChild': 0, - 'base': null, - 'lang': null, - 'node': null, - 'nodeType': null, - 'listIndex': 1, - 'rdfid': null, - 'datatype': null, - 'collection': false, - - /** Terminate the frame and notify the store that we're done */ - 'terminateFrame': function () { - if(this['collection']) { - this['node']['close']() - } - }, - - /** Add a symbol of a certain type to the this frame */ - 'addSymbol': function (type, uri) { - uri = $rdf.Util.uri.join(uri, this['base']) - this['node'] = this['store']['sym'](uri) - this['nodeType'] = type - }, - - /** Load any constructed triples into the store */ - 'loadTriple': function () { - if(this['parent']['parent']['collection']) { - this['parent']['parent']['node']['append'](this['node']) - } else { - this['store']['add'](this['parent']['parent']['node'], - this['parent']['node'], - this['node'], - this['parser']['why']) - } - if(this['parent']['rdfid'] != null) { // reify - var triple = this['store']['sym']( - $rdf.Util.uri.join("#" + this['parent']['rdfid'], this['base'])) - this['store']['add'](triple, - this['store']['sym'](RDFParser['ns']['RDF'] + "type"), - this['store']['sym'](RDFParser['ns']['RDF'] + "Statement"), - this['parser']['why']) - this['store']['add'](triple, - this['store']['sym'](RDFParser['ns']['RDF'] + "subject"), - this['parent']['parent']['node'], - this['parser']['why']) - this['store']['add'](triple, - this['store']['sym'](RDFParser['ns']['RDF'] + "predicate"), - this['parent']['node'], - this['parser']['why']) - this['store']['add'](triple, - this['store']['sym'](RDFParser['ns']['RDF'] + "object"), - this['node'], - this['parser']['why']) - } - }, - - /** Check if it's OK to load a triple */ - 'isTripleToLoad': function () { - return (this['parent'] != null - && this['parent']['parent'] != null - && this['nodeType'] == this['NODE'] - && this['parent']['nodeType'] == this['ARC'] - && this['parent']['parent']['nodeType'] == this['NODE']) - }, - - /** Add a symbolic node to this frame */ - 'addNode': function (uri) { - this['addSymbol'](this['NODE'], uri) - if(this['isTripleToLoad']()) { - this['loadTriple']() - } - }, - - /** Add a collection node to this frame */ - 'addCollection': function () { - this['nodeType'] = this['NODE'] - this['node'] = this['store']['collection']() - this['collection'] = true - if(this['isTripleToLoad']()) { - this['loadTriple']() - } - }, - - /** Add a collection arc to this frame */ - 'addCollectionArc': function () { - this['nodeType'] = this['ARC'] - }, - - /** Add a bnode to this frame */ - 'addBNode': function (id) { - if(id != null) { - if(this['parser']['bnodes'][id] != null) { - this['node'] = this['parser']['bnodes'][id] - } else { - this['node'] = this['parser']['bnodes'][id] = this['store']['bnode']() - } - } else { - this['node'] = this['store']['bnode']() - } - - this['nodeType'] = this['NODE'] - if(this['isTripleToLoad']()) { - this['loadTriple']() - } - }, - - /** Add an arc or property to this frame */ - 'addArc': function (uri) { - if(uri == RDFParser['ns']['RDF'] + "li") { - uri = RDFParser['ns']['RDF'] + "_" + this['parent']['listIndex']++ - } - this['addSymbol'](this['ARC'], uri) - }, - - /** Add a literal to this frame */ - 'addLiteral': function (value) { - if(this['parent']['datatype']) { - this['node'] = this['store']['literal']( - value, "", this['store']['sym']( - this['parent']['datatype'])) - } else { - this['node'] = this['store']['literal']( - value, this['lang']) - } - this['nodeType'] = this['NODE'] - if(this['isTripleToLoad']()) { - this['loadTriple']() - } - } - } - } - - //from the OpenLayers source .. needed to get around IE problems. - this['getAttributeNodeNS'] = function (node, uri, name) { - var attributeNode = null; - if(node.getAttributeNodeNS) { - attributeNode = node.getAttributeNodeNS(uri, name); - } else { - var attributes = node.attributes; - var potentialNode, fullName; - for(var i = 0; i < attributes.length; ++i) { - potentialNode = attributes[i]; - if(potentialNode.namespaceURI == uri) { - fullName = (potentialNode.prefix) ? (potentialNode.prefix + ":" + name) : name; - if(fullName == potentialNode.nodeName) { - attributeNode = potentialNode; - break; - } - } - } - } - return attributeNode; - } - - /** Our triple store reference @private */ - this['store'] = store - /** Our identified blank nodes @private */ - this['bnodes'] = {} - /** A context for context-aware stores @private */ - this['why'] = null - /** Reification flag */ - this['reify'] = false - - /** - * Build our initial scope frame and parse the DOM into triples - * @param {DOMTree} document The DOM to parse - * @param {String} base The base URL to use - * @param {Object} why The context to which this resource belongs - */ - this['parse'] = function (document, base, why) { - // alert('parse base:'+base); - var children = document['childNodes'] - - // clean up for the next run - this['cleanParser']() - - // figure out the root element - //var root = document.documentElement; //this is faster, I think, cross-browser issue? well, DOM 2 - if(document['nodeType'] == RDFParser['nodeType']['DOCUMENT']) { - for(var c = 0; c < children['length']; c++) { - if(children[c]['nodeType'] == RDFParser['nodeType']['ELEMENT']) { - var root = children[c] - break - } - } - } else if(document['nodeType'] == RDFParser['nodeType']['ELEMENT']) { - var root = document - } else { - throw new Error("RDFParser: can't find root in " + base + ". Halting. ") - return false - } - - this['why'] = why - - - // our topmost frame - var f = this['frameFactory'](this) - this['base'] = base - f['base'] = base - f['lang'] = '' - - this['parseDOM'](this['buildFrame'](f, root)) - return true - } - this['parseDOM'] = function (frame) { - // a DOM utility function used in parsing - var elementURI = function (el) { - var result = ""; - if(el['namespaceURI'] == null) { - throw new Error("RDF/XML syntax error: No namespace for " - + el['localName'] + " in " + this.base) - } - if(el['namespaceURI']) { - result = result + el['namespaceURI']; - } - if(el['localName']) { - result = result + el['localName']; - } else if(el['nodeName']) { - if(el['nodeName'].indexOf(":") >= 0) - result = result + el['nodeName'].split(":")[1]; - else - result = result + el['nodeName']; - } - return result; - } - var dig = true // if we'll dig down in the tree on the next iter - while(frame['parent']) { - var dom = frame['element'] - var attrs = dom['attributes'] - - if(dom['nodeType'] == RDFParser['nodeType']['TEXT'] - || dom['nodeType'] == RDFParser['nodeType']['CDATA_SECTION']) { - //we have a literal - if(frame['parent']['nodeType'] == frame['NODE']) { - //must have had attributes, store as rdf:value - frame['addArc'](RDFParser['ns']['RDF'] + 'value'); - frame = this['buildFrame'](frame); - } - frame['addLiteral'](dom['nodeValue']) - } else if(elementURI(dom) != RDFParser['ns']['RDF'] + "RDF") { - // not root - if(frame['parent'] && frame['parent']['collection']) { - // we're a collection element - frame['addCollectionArc']() - frame = this['buildFrame'](frame, frame['element']) - frame['parent']['element'] = null - } - if(!frame['parent'] || !frame['parent']['nodeType'] - || frame['parent']['nodeType'] == frame['ARC']) { - // we need a node - var about = this['getAttributeNodeNS'](dom, RDFParser['ns']['RDF'], "about") - var rdfid = this['getAttributeNodeNS'](dom, RDFParser['ns']['RDF'], "ID") - if(about && rdfid) { - throw new Error("RDFParser: " + dom['nodeName'] - + " has both rdf:id and rdf:about." + " Halting. Only one of these" - + " properties may be specified on a" + " node."); - } - if(about == null && rdfid) { - frame['addNode']("#" + rdfid['nodeValue']) - dom['removeAttributeNode'](rdfid) - } else if(about == null && rdfid == null) { - var bnid = this['getAttributeNodeNS'](dom, RDFParser['ns']['RDF'], "nodeID") - if(bnid) { - frame['addBNode'](bnid['nodeValue']) - dom['removeAttributeNode'](bnid) - } else { - frame['addBNode']() - } - } else { - frame['addNode'](about['nodeValue']) - dom['removeAttributeNode'](about) - } - - // Typed nodes - var rdftype = this['getAttributeNodeNS'](dom, RDFParser['ns']['RDF'], "type") - if(RDFParser['ns']['RDF'] + "Description" != elementURI(dom)) { - rdftype = { - 'nodeValue': elementURI(dom) - } - } - if(rdftype != null) { - this['store']['add'](frame['node'], - this['store']['sym'](RDFParser['ns']['RDF'] + "type"), - this['store']['sym']( - $rdf.Util.uri.join( - rdftype['nodeValue'], - frame['base'])), - this['why']) - if(rdftype['nodeName']) { - dom['removeAttributeNode'](rdftype) - } - } - - // Property Attributes - for(var x = attrs['length'] - 1; x >= 0; x--) { - this['store']['add'](frame['node'], - this['store']['sym'](elementURI(attrs[x])), - this['store']['literal']( - attrs[x]['nodeValue'], - frame['lang']), - this['why']) - } - } else { - // we should add an arc (or implicit bnode+arc) - frame['addArc'](elementURI(dom)) - - // save the arc's rdf:ID if it has one - if(this['reify']) { - var rdfid = this['getAttributeNodeNS'](dom, RDFParser['ns']['RDF'], "ID") - if(rdfid) { - frame['rdfid'] = rdfid['nodeValue'] - dom['removeAttributeNode'](rdfid) - } - } - - var parsetype = this['getAttributeNodeNS'](dom, RDFParser['ns']['RDF'], "parseType") - var datatype = this['getAttributeNodeNS'](dom, RDFParser['ns']['RDF'], "datatype") - if(datatype) { - frame['datatype'] = datatype['nodeValue'] - dom['removeAttributeNode'](datatype) - } - - if(parsetype) { - var nv = parsetype['nodeValue'] - if(nv == "Literal") { - frame['datatype'] = RDFParser['ns']['RDF'] + "XMLLiteral" - // (this.buildFrame(frame)).addLiteral(dom) - // should work but doesn't - frame = this['buildFrame'](frame) - frame['addLiteral'](dom) - dig = false - } else if(nv == "Resource") { - frame = this['buildFrame'](frame, frame['element']) - frame['parent']['element'] = null - frame['addBNode']() - } else if(nv == "Collection") { - frame = this['buildFrame'](frame, frame['element']) - frame['parent']['element'] = null - frame['addCollection']() - } - dom['removeAttributeNode'](parsetype) - } - - if(attrs['length'] != 0) { - var resource = this['getAttributeNodeNS'](dom, RDFParser['ns']['RDF'], "resource") - var bnid = this['getAttributeNodeNS'](dom, RDFParser['ns']['RDF'], "nodeID") - - frame = this['buildFrame'](frame) - if(resource) { - frame['addNode'](resource['nodeValue']) - dom['removeAttributeNode'](resource) - } else { - if(bnid) { - frame['addBNode'](bnid['nodeValue']) - dom['removeAttributeNode'](bnid) - } else { - frame['addBNode']() - } - } - - for(var x = attrs['length'] - 1; x >= 0; x--) { - var f = this['buildFrame'](frame) - f['addArc'](elementURI(attrs[x])) - if(elementURI(attrs[x]) == RDFParser['ns']['RDF'] + "type") { - (this['buildFrame'](f))['addNode']( - attrs[x]['nodeValue']) - } else { - (this['buildFrame'](f))['addLiteral']( - attrs[x]['nodeValue']) - } - } - } else if(dom['childNodes']['length'] == 0) { - (this['buildFrame'](frame))['addLiteral']("") - } - } - } // rdf:RDF - // dig dug - dom = frame['element'] - while(frame['parent']) { - var pframe = frame - while(dom == null) { - frame = frame['parent'] - dom = frame['element'] - } - var candidate = dom['childNodes'][frame['lastChild']] - if(candidate == null || !dig) { - frame['terminateFrame']() - if(!(frame = frame['parent'])) { - break - } // done - dom = frame['element'] - dig = true - } else if((candidate['nodeType'] != RDFParser['nodeType']['ELEMENT'] - && candidate['nodeType'] != RDFParser['nodeType']['TEXT'] - && candidate['nodeType'] != RDFParser['nodeType']['CDATA_SECTION']) - || ((candidate['nodeType'] == RDFParser['nodeType']['TEXT'] - || candidate['nodeType'] == RDFParser['nodeType']['CDATA_SECTION']) - && dom['childNodes']['length'] != 1)) { - frame['lastChild']++ - } else { - // not a leaf - frame['lastChild']++; - frame = this['buildFrame'](pframe, dom['childNodes'][frame['lastChild'] - 1]) - break - } - } - } // while - } - - /** - * Cleans out state from a previous parse run - * @private - */ - this['cleanParser'] = function () { - this['bnodes'] = {} - this['why'] = null - } - - /** - * Builds scope frame - * @private - */ - this['buildFrame'] = function (parent, element) { - var frame = this['frameFactory'](this, parent, element) - if(parent) { - frame['base'] = parent['base'] - frame['lang'] = parent['lang'] - } - if(element == null - || element['nodeType'] == RDFParser['nodeType']['TEXT'] - || element['nodeType'] == RDFParser['nodeType']['CDATA_SECTION']) { - return frame - } - - var attrs = element['attributes'] - - var base = element['getAttributeNode']("xml:base") - if(base != null) { - frame['base'] = base['nodeValue'] - element['removeAttribute']("xml:base") - } - var lang = element['getAttributeNode']("xml:lang") - if(lang != null) { - frame['lang'] = lang['nodeValue'] - element['removeAttribute']("xml:lang") - } - - // remove all extraneous xml and xmlns attributes - for(var x = attrs['length'] - 1; x >= 0; x--) { - if(attrs[x]['nodeName']['substr'](0, 3) == "xml") { - if(attrs[x].name.slice(0, 6) == 'xmlns:') { - var uri = attrs[x].nodeValue; - // alert('base for namespac attr:'+this.base); - if(this.base) uri = $rdf.Util.uri.join(uri, this.base); - this.store.setPrefixForURI(attrs[x].name.slice(6), uri); - } - // alert('rdfparser: xml atribute: '+attrs[x].name) //@@ - element['removeAttributeNode'](attrs[x]) - } - } - return frame - } -} \ No newline at end of file diff --git a/chrome/content/zotero/xpcom/rdf/serialize.js b/chrome/content/zotero/xpcom/rdf/serialize.js deleted file mode 100644 index 863a2f538b..0000000000 --- a/chrome/content/zotero/xpcom/rdf/serialize.js +++ /dev/null @@ -1,877 +0,0 @@ -/* Serialization of RDF Graphs - ** - ** Tim Berners-Lee 2006 - ** This is or was http://dig.csail.mit.edu/2005/ajar/ajaw/js/rdf/serialize.js - ** - ** Bug: can't serialize http://data.semanticweb.org/person/abraham-bernstein/rdf - ** in XML (from mhausenblas) - */ -// @@@ Check the whole toStr thing tosee whetehr it still makes sense -- tbl -// -$rdf.Serializer = function () { - - var __Serializer = function (store) { - this.flags = ""; - this.base = null; - this.prefixes = []; - this.keywords = ['a']; // The only one we generate at the moment - this.prefixchars = "abcdefghijklmnopqustuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - this.incoming = null; // Array not calculated yet - this.formulas = []; // remebering original formulae from hashes - this.store = store; - - /* pass */ - } - - var Serializer = function (store) { - return new __Serializer(store) - }; - - __Serializer.prototype.setBase = function (base) { - this.base = base - }; - - __Serializer.prototype.setFlags = function (flags) { - this.flags = flags ? flags : '' - }; - - - __Serializer.prototype.toStr = function (x) { - var s = x.toNT(); - if(x.termType == 'formula') { - this.formulas[s] = x; // remember as reverse does not work - } - return s; - }; - - __Serializer.prototype.fromStr = function (s) { - if(s[0] == '{') { - var x = this.formulas[s]; - if(!x) alert('No formula object for ' + s) - return x; - } - return this.store.fromNT(s); - }; - - - - - - /* Accumulate Namespaces - ** - ** These are only hints. If two overlap, only one gets used - ** There is therefore no guarantee in general. - */ - - __Serializer.prototype.suggestPrefix = function (prefix, uri) { - this.prefixes[uri] = prefix; - } - - // Takes a namespace -> prefix map - __Serializer.prototype.suggestNamespaces = function (namespaces) { - for(var px in namespaces) { - this.prefixes[namespaces[px]] = px; - } - } - - // Make up an unused prefix for a random namespace - __Serializer.prototype.makeUpPrefix = function (uri) { - var p = uri; - var namespaces = []; - var pok; - var sz = this; - - function canUse(pp) { - if(namespaces[pp]) return false; // already used - sz.prefixes[uri] = pp; - pok = pp; - return true - } - for(var ns in sz.prefixes) { - namespaces[sz.prefixes[ns]] = ns; // reverse index - } - // trim off illegal characters from the end - var i; - for(i = p.length - 1; i>=0; i--) { - if(sz._notNameChars.indexOf(p.charAt(i)) == -1) break; - } - p = p.substring(0, i+1); - if(p) { - // find shortest possible NCName to use as namespace name - for(i = p.length - 1; i>=0; i--) { - if(sz._notNameChars.indexOf(p.charAt(i)) != -1) break; - } - i++; - p = p.substr(i); - - if(p.length < 6 && canUse(p)) return pok; // exact is best - if(canUse(p.slice(0, 3))) return pok; - if(canUse(p.slice(0, 2))) return pok; - if(canUse(p.slice(0, 4))) return pok; - if(canUse(p.slice(0, 1))) return pok; - if(canUse(p.slice(0, 5))) return pok; - p = p.slice(0, 3); - } else { - // no suitable characters (weird), fall back to 'ns' - p = 'ns'; - if(canUse(p)) return pok; - } - for(var i = 0;; i++) if(canUse(p + i)) return pok; - } - - - - // Todo: - // - Sort the statements by subject, pred, object - // - do stuff about the docu first and then (or first) about its primary topic. - __Serializer.prototype.rootSubjects = function (sts) { - var incoming = {}; - var subjects = {}; - var sz = this; - var allBnodes = {}; - - /* This scan is to find out which nodes will have to be the roots of trees - ** in the serialized form. This will be any symbols, and any bnodes - ** which hve more or less than one incoming arc, and any bnodes which have - ** one incoming arc but it is an uninterrupted loop of such nodes back to itself. - ** This should be kept linear time with repect to the number of statements. - ** Note it does not use any indexing of the store. - */ - - - tabulator.log.debug('serialize.js Find bnodes with only one incoming arc\n') - for(var i = 0; i < sts.length; i++) { - var st = sts[i]; - [st.subject, st.predicate, st.object].map(function (y) { - if(y.termType == 'bnode') { - allBnodes[y.toNT()] = true - } - }); - var x = sts[i].object; - if(!incoming[x]) incoming[x] = []; - incoming[x].push(st.subject) // List of things which will cause this to be printed - var ss = subjects[sz.toStr(st.subject)]; // Statements with this as subject - if(!ss) ss = []; - ss.push(st); - subjects[this.toStr(st.subject)] = ss; // Make hash. @@ too slow for formula? - //$rdf.log.debug(' sz potential subject: '+sts[i].subject) - } - - var roots = []; - for(var xNT in subjects) { - var x = sz.fromStr(xNT); - if((x.termType != 'bnode') || !incoming[x] || (incoming[x].length != 1)) { - roots.push(x); - //$rdf.log.debug(' sz actual subject -: ' + x) - continue; - } - } - this.incoming = incoming; // Keep for serializing @@ Bug for nested formulas - //////////// New bit for CONNECTED bnode loops:frootshash - // This scans to see whether the serialization is gpoing to lead to a bnode loop - // and at the same time accumulates a list of all bnodes mentioned. - // This is in fact a cut down N3 serialization - /* - tabulator.log.debug('serialize.js Looking for connected bnode loops\n') - for (var i=0; i?@[\\]^`{|}~"; - __Serializer.prototype._notNameChars = (__Serializer.prototype._notQNameChars + ":"); - __Serializer.prototype._NCNameRegExp = (function() { - // escape characters that are unsafe inside RegExp character set - var reSafeChars = __Serializer.prototype._notNameChars.replace(/[-\]\\]/g, '\\$&'); - return new RegExp('[^0-9\\-.' + reSafeChars + '][^' + reSafeChars + ']*$'); - })(); - - - __Serializer.prototype.statementsToN3 = function (sts) { - var indent = 4; - var width = 80; - var sz = this; - - var namespaceCounts = []; // which have been used - var predMap = { - 'http://www.w3.org/2002/07/owl#sameAs': '=', - 'http://www.w3.org/2000/10/swap/log#implies': '=>', - 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type': 'a' - } - - - - - ////////////////////////// Arrange the bits of text - var spaces = function (n) { - var s = ''; - for(var i = 0; i < n; i++) s += ' '; - return s - } - - var treeToLine = function (tree) { - var str = ''; - for(var i = 0; i < tree.length; i++) { - var branch = tree[i]; - var s2 = (typeof branch == 'string') ? branch : treeToLine(branch); - if(i != 0 && s2 != ',' && s2 != ';' && s2 != '.') str += ' '; - str += s2; - } - return str; - } - - // Convert a nested tree of lists and strings to a string - var treeToString = function (tree, level) { - var str = ''; - var lastLength = 100000; - if(!level) level = 0; - for(var i = 0; i < tree.length; i++) { - var branch = tree[i]; - if(typeof branch != 'string') { - var substr = treeToString(branch, level + 1); - if(substr.length < 10 * (width - indent * level) - && substr.indexOf('"""') < 0) { - // Don't mess up multiline strings - var line = treeToLine(branch); - if(line.length < (width - indent * level)) { - branch = ' ' + line; // @@ Hack: treat as string below - substr = '' - } - } - if(substr) lastLength = 10000; - str += substr; - } - if(typeof branch == 'string') { - if(branch.length == '1' && str.slice(-1) == '\n') { - if(",.;".indexOf(branch) >= 0) { - str = str.slice(0, -1) + branch + '\n'; // slip punct'n on end - lastLength += 1; - continue; - } else if("])}".indexOf(branch) >= 0) { - str = str.slice(0, -1) + ' ' + branch + '\n'; - lastLength += 2; - continue; - } - } - if(lastLength < (indent * level + 4)) { // continue - str = str.slice(0, -1) + ' ' + branch + '\n'; - lastLength += branch.length + 1; - } else { - var line = spaces(indent * level) + branch; - str += line + '\n'; - lastLength = line.length; - } - - } else { // not string - } - } - return str; - }; - - ////////////////////////////////////////////// Structure for N3 - - // Convert a set of statements into a nested tree of lists and strings - function statementListToTree(statements) { - // print('Statement tree for '+statements.length); - var res = []; - var stats = sz.rootSubjects(statements); - var roots = stats.roots; - var results = [] - for(var i = 0; i < roots.length; i++) { - var root = roots[i]; - results.push(subjectTree(root, stats)) - } - return results; - } - - // The tree for a subject - function subjectTree(subject, stats) { - if(subject.termType == 'bnode' && !stats.incoming[subject]) - return objectTree(subject, stats, true).concat(["."]); // Anonymous bnode subject - return [termToN3(subject, stats)].concat([propertyTree(subject, stats)]).concat(["."]); - } - - - // The property tree for a single subject or anonymous node - function propertyTree(subject, stats) { - // print('Proprty tree for '+subject); - var results = [] - var lastPred = null; - var sts = stats.subjects[sz.toStr(subject)]; // relevant statements - if(typeof sts == 'undefined') { - throw('Cant find statements for ' + subject); - } - sts.sort(); - var objects = []; - for(var i = 0; i < sts.length; i++) { - var st = sts[i]; - if(st.predicate.uri == lastPred) { - objects.push(','); - } else { - if(lastPred) { - results = results.concat([objects]).concat([';']); - objects = []; - } - results.push(predMap[st.predicate.uri] ? - predMap[st.predicate.uri] : - termToN3(st.predicate, stats)); - } - lastPred = st.predicate.uri; - objects.push(objectTree(st.object, stats)); - } - results = results.concat([objects]); - return results; - } - - function objectTree(obj, stats, force) { - if(obj.termType == 'bnode' - && stats.subjects[sz.toStr(obj)] - // and there are statements - && (force || stats.rootsHash[obj.toNT()] == undefined)) // and not a root - return ['['].concat(propertyTree(obj, stats)).concat([']']); - return termToN3(obj, stats); - } - - function termToN3(expr, stats) { - switch(expr.termType) { - case 'bnode': - case 'variable': - return expr.toNT(); - case 'literal': - var str = stringToN3(expr.value); - if(expr.lang) str += '@' + expr.lang; - if(expr.datatype) str += '^^' + termToN3(expr.datatype, stats); - return str; - case 'symbol': - return symbolToN3(expr.uri); - case 'formula': - var res = ['{']; - res = res.concat(statementListToTree(expr.statements)); - return res.concat(['}']); - case 'collection': - var res = ['(']; - for(i = 0; i < expr.elements.length; i++) { - res.push([objectTree(expr.elements[i], stats)]); - } - res.push(')'); - return res; - - default: - throw new Error("Internal: termToN3 cannot handle " + expr + " of termType+" + expr.termType); - return '' + expr; - } - } - - ////////////////////////////////////////////// Atomic Terms - // Deal with term level things and nesting with no bnode structure - function symbolToN3(uri) { // c.f. symbolString() in notation3.py - var j = uri.indexOf('#'); - if(j < 0 && sz.flags.indexOf('/') < 0) { - j = uri.lastIndexOf('/'); - } - if(j >= 0 && sz.flags.indexOf('p') < 0) { // Can split at namespace - var canSplit = true; - for(var k = j + 1; k < uri.length; k++) { - if(__Serializer.prototype._notNameChars.indexOf(uri[k]) >= 0) { - canSplit = false; - break; - } - } - if(canSplit) { - var localid = uri.slice(j + 1); - var namesp = uri.slice(0, j + 1); - if(sz.defaultNamespace - && sz.defaultNamespace == namesp - && sz.flags.indexOf('d') < 0) { // d -> suppress default - if(sz.flags.indexOf('k') >= 0 - && sz.keyords.indexOf(localid) < 0) - return localid; - return ':' + localid; - } - var prefix = sz.prefixes[namesp]; - if(prefix) { - namespaceCounts[namesp] = true; - return prefix + ':' + localid; - } - if(uri.slice(0, j) == sz.base) - return '<#' + localid + '>'; - // Fall though if can't do qname - } - } - if(sz.flags.indexOf('r') < 0 && sz.base) - uri = $rdf.Util.uri.refTo(sz.base, uri); - else if(sz.flags.indexOf('u') >= 0) - uri = backslashUify(uri); - else uri = hexify(uri); - return '<' + uri + '>'; - } - - function prefixDirectives() { - var str = ''; - if(sz.defaultNamespace) - str += '@prefix : <' + sz.defaultNamespace + '>.\n'; - for(var ns in namespaceCounts) { - str += '@prefix ' + sz.prefixes[ns] + ': <' + ns + '>.\n'; - } - return str + '\n'; - } - - // stringToN3: String escaping for N3 - // - var forbidden1 = new RegExp(/[\\"\b\f\r\v\t\n\u0080-\uffff]/gm); - var forbidden3 = new RegExp(/[\\"\b\f\r\v\u0080-\uffff]/gm); - - function stringToN3(str, flags) { - if(!flags) flags = "e"; - var res = '', i = 0, j = 0; - var delim; - var forbidden; - if(str.length > 20 // Long enough to make sense - && str.slice(-1) != '"' // corner case' - && flags.indexOf('n') < 0 // Force single line - && (str.indexOf('\n') > 0 || str.indexOf('"') > 0)) { - delim = '"""'; - forbidden = forbidden3; - } else { - delim = '"'; - forbidden = forbidden1; - } - for(i = 0; i < str.length;) { - forbidden.lastIndex = 0; - var m = forbidden.exec(str.slice(i)); - if(m == null) break; - j = i + forbidden.lastIndex - 1; - res += str.slice(i, j); - var ch = str[j]; - if(ch == '"' && delim == '"""' && str.slice(j, j + 3) != '"""') { - res += ch; - } else { - var k = '\b\f\r\t\v\n\\"'.indexOf(ch); // No escaping of bell (7)? - if(k >= 0) { - res += "\\" + 'bfrtvn\\"' [k]; - } else { - if(flags.indexOf('e') >= 0) { - res += '\\u' + ('000' + ch.charCodeAt(0).toString(16).toLowerCase()).slice(-4) - } else { // no 'e' flag - res += ch; - } - } - } - i = j + 1; - } - return delim + res + str.slice(i) + delim - } - - // Body of toN3: - var tree = statementListToTree(sts); - return prefixDirectives() + treeToString(tree, -1); - - } - - // String ecaping utilities - function hexify(str) { // also used in parser - // var res = ''; - // for (var i=0; i126 || k<33) - // res += '%' + ('0'+n.toString(16)).slice(-2); // convert to upper? - // else - // res += str[i]; - // } - // return res; - return encodeURI(str); - } - - - function backslashUify(str) { - var res = '', k; - for(var i = 0; i < str.length; i++) { - k = str.charCodeAt(i); - if(k > 65535) - res += '\\U' + ('00000000' + n.toString(16)).slice(-8); // convert to upper? - else if(k > 126) - res += '\\u' + ('0000' + n.toString(16)).slice(-4); - else - res += str[i]; - } - return res; - } - - - - - - - //////////////////////////////////////////////// XML serialization - __Serializer.prototype.statementsToXML = function (sts) { - var indent = 4; - var width = 80; - var sz = this; - - var namespaceCounts = []; // which have been used - namespaceCounts['http://www.w3.org/1999/02/22-rdf-syntax-ns#'] = true; - - ////////////////////////// Arrange the bits of XML text - var spaces = function (n) { - var s = ''; - for(var i = 0; i < n; i++) s += ' '; - return s - } - - var XMLtreeToLine = function (tree) { - var str = ''; - for(var i = 0; i < tree.length; i++) { - var branch = tree[i]; - var s2 = (typeof branch == 'string') ? branch : XMLtreeToLine(branch); - str += s2; - } - return str; - } - - // Convert a nested tree of lists and strings to a string - var XMLtreeToString = function (tree, level) { - var str = ''; - var lastLength = 100000; - if(!level) level = 0; - for(var i = 0; i < tree.length; i++) { - var branch = tree[i]; - if(typeof branch != 'string') { - var substr = XMLtreeToString(branch, level + 1); - if(substr.length < 10 * (width - indent * level) - && substr.indexOf('"""') < 0) { - // Don't mess up multiline strings - var line = XMLtreeToLine(branch); - if(line.length < (width - indent * level)) { - branch = ' ' + line; // @@ Hack: treat as string below - substr = '' - } - } - if(substr) lastLength = 10000; - str += substr; - } - if(typeof branch == 'string') { - if(lastLength < (indent * level + 4)) { // continue - str = str.slice(0, -1) + ' ' + branch + '\n'; - lastLength += branch.length + 1; - } else { - var line = spaces(indent * level) + branch; - str += line + '\n'; - lastLength = line.length; - } - - } else { // not string - } - } - return str; - }; - - function statementListToXMLTree(statements) { - sz.suggestPrefix('rdf', 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'); - var stats = sz.rootSubjects(statements); - var roots = stats.roots; - var results = [], root; - for(var i = 0; i < roots.length; i++) { - root = roots[i]; - results.push(subjectXMLTree(root, stats)) - } - return results; - } - - function escapeForXML(str) { - if(typeof str == 'undefined') return '@@@undefined@@@@'; - return str.replace(/&/g, '&') - .replace(//g, '>') - .replace(/"/g, '"'); - } - - function relURI(term) { - return escapeForXML((sz.base) ? $rdf.Util.uri.refTo(this.base, term.uri) : term.uri); - } - - // The tree for a subject - function subjectXMLTree(subject, stats) { - const liPrefix = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#_'; - var results = []; - var type = null, t, st; - var sts = stats.subjects[sz.toStr(subject)]; // relevant statements - // Sort only on the predicate, leave the order at object - // level undisturbed. This leaves multilingual content in - // the order of entry (for partner literals), which helps - // readability. - // - // For the predicate sort, we attempt to split the uri - // as a hint to the sequence, as sequenced items seems - // to be of the form http://example.com#_1, http://example.com#_2, - // et cetera. Probably not the most optimal of fixes, but - // it does work. - sts.sort(function (a, b) { - var aa = a.predicate.uri.split('#_'); - var bb = a.predicate.uri.split('#_'); - if(aa[0] > bb[0]) { - return 1; - } else if(aa[0] < bb[0]) { - return -1; - } else if("undefined" !== typeof aa[1] && "undefined" !== typeof bb[1]) { - if(parseInt(aa[1], 10) > parseInt(bb[1], 10)) { - return 1; - } else if(parseInt(aa[1], 10) < parseInt(bb[1], 10)) { - return -1; - } - } - return 0; - }); - - for(var i = 0; i < sts.length; i++) { - st = sts[i]; - if(st.predicate.uri == 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' && !type && st.object.termType == "symbol") { - // look for a type - type = st.object; - } else { - // see whether predicate can be replaced with "li" - if(st.predicate.uri.substr(0, liPrefix.length) == liPrefix) { - var number = st.predicate.uri.substr(liPrefix.length); - // make sure these are actually numeric list items - var intNumber = parseInt(number); - if(number == intNumber.toString()) { - // was numeric; don't need to worry about ordering since we've already - // sorted the statements - st.predicate = $rdf.Symbol('http://www.w3.org/1999/02/22-rdf-syntax-ns#li'); - } - } - t = qname(st.predicate); - switch(st.object.termType) { - case 'bnode': - if(sz.incoming[st.object].length == 1) { - results = results.concat(['<' + t + '>', - subjectXMLTree(st.object, stats), - '']); - } else { - results = results.concat(['<' + t + ' rdf:nodeID="' - + st.object.toNT().slice(2) + '"/>']); - } - break; - case 'symbol': - results = results.concat(['<' + t + ' rdf:resource="' - + relURI(st.object) + '"/>']); - break; - case 'literal': - results = results.concat(['<' + t - + (st.object.dt ? ' rdf:datatype="' + escapeForXML(st.object.dt.uri) + '"' : '') - + (st.object.lang ? ' xml:lang="' + st.object.lang + '"' : '') - + '>' + escapeForXML(st.object.value) - + '']); - break; - case 'collection': - results = results.concat(['<' + t + ' rdf:parseType="Collection">', - collectionXMLTree(st.object, stats), - '']); - break; - default: - throw new Error("Can't serialize object of type " + st.object.termType + " into XML"); - } // switch - } - } - - var tag = type ? qname(type) : 'rdf:Description'; - - var attrs = ''; - if(subject.termType == 'bnode') { - if(!sz.incoming[subject] || sz.incoming[subject].length != 1) { // not an anonymous bnode - attrs = ' rdf:nodeID="' + subject.toNT().slice(2) + '"'; - } - } else { - attrs = ' rdf:about="' + relURI(subject) + '"'; - } - - return ['<' + tag + attrs + '>'].concat([results]).concat([""]); - } - - function collectionXMLTree(subject, stats) { - var res = [] - for(var i = 0; i < subject.elements.length; i++) { - res.push(subjectXMLTree(subject.elements[i], stats)); - } - return res; - } - - // The property tree for a single subject or anonymos node - function propertyXMLTree(subject, stats) { - var results = [] - var sts = stats.subjects[sz.toStr(subject)]; // relevant statements - if(sts == undefined) return results; // No relevant statements - sts.sort(); - for(var i = 0; i < sts.length; i++) { - var st = sts[i]; - switch(st.object.termType) { - case 'bnode': - if(stats.rootsHash[st.object.toNT()]) { // This bnode has been done as a root -- no content here @@ what bout first time - results = results.concat(['<' + qname(st.predicate) + ' rdf:nodeID="' + st.object.toNT().slice(2) + '">', - '']); - } else { - results = results.concat(['<' + qname(st.predicate) + ' rdf:parseType="Resource">', - propertyXMLTree(st.object, stats), - '']); - } - break; - case 'symbol': - results = results.concat(['<' + qname(st.predicate) + ' rdf:resource="' - + relURI(st.object) + '"/>']); - break; - case 'literal': - results = results.concat(['<' + qname(st.predicate) - + (st.object.datatype ? ' rdf:datatype="' + escapeForXML(st.object.datatype.uri) + '"' : '') - + (st.object.lang ? ' xml:lang="' + st.object.lang + '"' : '') - + '>' + escapeForXML(st.object.value) - + '']); - break; - case 'collection': - results = results.concat(['<' + qname(st.predicate) + ' rdf:parseType="Collection">', - collectionXMLTree(st.object, stats), - '']); - break; - default: - throw new Error("Can't serialize object of type " + st.object.termType + " into XML"); - - } // switch - } - return results; - } - - function qname(term) { - var uri = term.uri; - - var j = uri.search(sz._NCNameRegExp); - if(j < 0) throw("Cannot make qname out of <" + uri + ">") - - var localid = uri.substr(j); - var namesp = uri.substr(0, j); - if(sz.defaultNamespace - && sz.defaultNamespace == namesp - && sz.flags.indexOf('d') < 0) { // d -> suppress default - return localid; - } - var prefix = sz.prefixes[namesp]; - if(!prefix) prefix = sz.makeUpPrefix(namesp); - namespaceCounts[namesp] = true; - return prefix + ':' + localid; - // throw ('No prefix for namespace "'+namesp +'" for XML qname for '+uri+', namespaces: '+sz.prefixes+' sz='+sz); - } - - // Body of toXML: - var tree = statementListToXMLTree(sts); - var str = '']; //@@ namespace declrations - return XMLtreeToString(tree2, -1); - - - } // End @@ body - return Serializer; - -}(); \ No newline at end of file diff --git a/chrome/content/zotero/xpcom/rdf/term.js b/chrome/content/zotero/xpcom/rdf/term.js deleted file mode 100644 index 5d3ec66779..0000000000 --- a/chrome/content/zotero/xpcom/rdf/term.js +++ /dev/null @@ -1,346 +0,0 @@ -// These are the classes corresponding to the RDF and N3 data models -// -// Designed to look like rdflib and cwm designs. -// -// Issues: Should the names start with RDF to make them -// unique as program-wide symbols? -// -// W3C open source licence 2005. -// -// Symbol -$rdf.Empty = function () { - return this; -}; - -$rdf.Empty.prototype.termType = 'empty'; -$rdf.Empty.prototype.toString = function () { - return "()" -}; -$rdf.Empty.prototype.toNT = $rdf.Empty.prototype.toString; - -$rdf.Symbol = function (uri) { - this.uri = uri; - this.value = uri; // -- why? -tim - return this; -} - -$rdf.Symbol.prototype.termType = 'symbol'; -$rdf.Symbol.prototype.toString = function () { - return("<" + this.uri + ">"); -}; -$rdf.Symbol.prototype.toNT = $rdf.Symbol.prototype.toString; - -// Some precalculated symbols -$rdf.Symbol.prototype.XSDboolean = new $rdf.Symbol('http://www.w3.org/2001/XMLSchema#boolean'); -$rdf.Symbol.prototype.XSDdecimal = new $rdf.Symbol('http://www.w3.org/2001/XMLSchema#decimal'); -$rdf.Symbol.prototype.XSDfloat = new $rdf.Symbol('http://www.w3.org/2001/XMLSchema#float'); -$rdf.Symbol.prototype.XSDinteger = new $rdf.Symbol('http://www.w3.org/2001/XMLSchema#integer'); -$rdf.Symbol.prototype.XSDdateTime = new $rdf.Symbol('http://www.w3.org/2001/XMLSchema#dateTime'); -$rdf.Symbol.prototype.integer = new $rdf.Symbol('http://www.w3.org/2001/XMLSchema#integer'); // Used? -// Blank Node -if(typeof $rdf.NextId != 'undefined') { - $rdf.log.error('Attempt to re-zero existing blank node id counter at ' + $rdf.NextId); -} else { - $rdf.NextId = 0; // Global genid -} -$rdf.NTAnonymousNodePrefix = "_:n"; - -$rdf.BlankNode = function (id) { - /*if (id) - this.id = id; - else*/ - this.id = $rdf.NextId++; - this.value = id ? id : this.id.toString(); - return this -}; - -$rdf.BlankNode.prototype.termType = 'bnode'; -$rdf.BlankNode.prototype.toNT = function () { - return $rdf.NTAnonymousNodePrefix + this.id -}; -$rdf.BlankNode.prototype.toString = $rdf.BlankNode.prototype.toNT; - -// Literal -$rdf.Literal = function (value, lang, datatype) { - this.value = value - if(lang == "" || lang == null) this.lang = undefined; - else this.lang = lang; // string - if(datatype == null) this.datatype = undefined; - else this.datatype = datatype; // term - return this; -} - -$rdf.Literal.prototype.termType = 'literal' -$rdf.Literal.prototype.toString = function () { - return '' + this.value; -}; -$rdf.Literal.prototype.toNT = function () { - var str = this.value - if(typeof str != 'string') { - if(typeof str == 'number') return '' + str; - throw Error("Value of RDF literal is not string: " + str) - } - str = str.replace(/\\/g, '\\\\'); // escape backslashes - str = str.replace(/\"/g, '\\"'); // escape quotes - str = str.replace(/\n/g, '\\n'); // escape newlines - str = '"' + str + '"' //'; - if(this.datatype) { - str = str + '^^' + this.datatype.toNT() - } - if(this.lang) { - str = str + "@" + this.lang; - } - return str; -}; - -$rdf.Collection = function () { - this.id = $rdf.NextId++; // Why need an id? For hashstring. - this.elements = []; - this.closed = false; -}; - -$rdf.Collection.prototype.termType = 'collection'; - -$rdf.Collection.prototype.toNT = function () { - return $rdf.NTAnonymousNodePrefix + this.id -}; - -$rdf.Collection.prototype.toString = function () { - var str = '('; - for(var i = 0; i < this.elements.length; i++) - str += this.elements[i] + ' '; - return str + ')'; -}; - -$rdf.Collection.prototype.append = function (el) { - this.elements.push(el) -} -$rdf.Collection.prototype.unshift = function (el) { - this.elements.unshift(el); -} -$rdf.Collection.prototype.shift = function () { - return this.elements.shift(); -} - -$rdf.Collection.prototype.close = function () { - this.closed = true -} - - -// Convert Javascript representation to RDF term object -// -$rdf.term = function (val) { - if(typeof val == 'object') - if(val instanceof Date) { - var d2 = function (x) { - return('' + (100 + x)).slice(1, 3) - }; // format as just two digits - return new $rdf.Literal('' + val.getUTCFullYear() + '-' + d2(val.getUTCMonth() + 1) - + '-' + d2(val.getUTCDate()) + 'T' + d2(val.getUTCHours()) + ':' - + d2(val.getUTCMinutes()) + ':' + d2(val.getUTCSeconds()) + 'Z', - undefined, - $rdf.Symbol.prototype.XSDdateTime); - - } else if(val instanceof Array) { - var x = new $rdf.Collection(); - for(var i = 0; i < val.length; i++) - x.append($rdf.term(val[i])); - return x; - } else - return val; - if(typeof val == 'string') - return new $rdf.Literal(val); - if(typeof val == 'number') { - var dt; - if(('' + val).indexOf('e') >= 0) dt = $rdf.Symbol.prototype.XSDfloat; - else if(('' + val).indexOf('.') >= 0) dt = $rdf.Symbol.prototype.XSDdecimal; - else dt = $rdf.Symbol.prototype.XSDinteger; - return new $rdf.Literal(val, undefined, dt); - } - if(typeof val == 'boolean') - return new $rdf.Literal(val ? "1" : "0", undefined, $rdf.Symbol.prototype.XSDboolean); - if(typeof val == 'undefined') - return undefined; - throw("Can't make term from " + val + " of type " + typeof val); -} - -// Statement -// -// This is a triple with an optional reason. -// -// The reason can point to provenece or inference -// -$rdf.Statement = function (subject, predicate, object, why) { - this.subject = $rdf.term(subject) - this.predicate = $rdf.term(predicate) - this.object = $rdf.term(object) - if(typeof why != 'undefined') { - this.why = why; - } - return this; -} - -$rdf.st = function (subject, predicate, object, why) { - return new $rdf.Statement(subject, predicate, object, why); -}; - -$rdf.Statement.prototype.toNT = function () { - return (this.subject.toNT() + " " + this.predicate.toNT() + " " + this.object.toNT() + " ."); -}; - -$rdf.Statement.prototype.toString = $rdf.Statement.prototype.toNT; - -// Formula -// -// Set of statements. -$rdf.Formula = function () { - this.statements = [] - this.constraints = [] - this.initBindings = [] - this.optional = [] - return this; -}; - - -$rdf.Formula.prototype.termType = 'formula'; -$rdf.Formula.prototype.toNT = function () { - return "{" + this.statements.join('\n') + "}" -}; -$rdf.Formula.prototype.toString = $rdf.Formula.prototype.toNT; - -$rdf.Formula.prototype.add = function (subj, pred, obj, why) { - this.statements.push(new $rdf.Statement(subj, pred, obj, why)) -} - -// Convenience methods on a formula allow the creation of new RDF terms: -$rdf.Formula.prototype.sym = function (uri, name) { - if(name != null) { - throw new Error("This feature (kb.sym with 2 args) is removed. Do not assume prefix mappings."); - if(!$rdf.ns[uri]) throw 'The prefix "' + uri + '" is not set in the API'; - uri = $rdf.ns[uri] + name - } - return new $rdf.Symbol(uri) -} - -$rdf.sym = function (uri) { - return new $rdf.Symbol(uri); -}; - -$rdf.Formula.prototype.literal = function (val, lang, dt) { - return new $rdf.Literal(val.toString(), lang, dt) -} -$rdf.lit = $rdf.Formula.prototype.literal; - -$rdf.Formula.prototype.bnode = function (id) { - return new $rdf.BlankNode(id) -} - -$rdf.Formula.prototype.formula = function () { - return new $rdf.Formula() -} - -$rdf.Formula.prototype.collection = function () { // obsolete - return new $rdf.Collection() -} - -$rdf.Formula.prototype.list = function (values) { - var li = new $rdf.Collection(); - if(values) { - for(var i = 0; i < values.length; i++) { - li.append(values[i]); - } - } - return li; -} - -/* Variable - ** - ** Variables are placeholders used in patterns to be matched. - ** In cwm they are symbols which are the formula's list of quantified variables. - ** In sparl they are not visibily URIs. Here we compromise, by having - ** a common special base URI for variables. Their names are uris, - ** but the ? nottaion has an implicit base uri of 'varid:' - */ - -$rdf.Variable = function (rel) { - this.base = "varid:"; // We deem variabe x to be the symbol varid:x - this.uri = $rdf.Util.uri.join(rel, this.base); - return this; -} - -$rdf.Variable.prototype.termType = 'variable'; -$rdf.Variable.prototype.toNT = function () { - if(this.uri.slice(0, this.base.length) == this.base) { - return '?' + this.uri.slice(this.base.length); - } // @@ poor man's refTo - return '?' + this.uri; -}; - -$rdf.Variable.prototype.toString = $rdf.Variable.prototype.toNT; -$rdf.Variable.prototype.classOrder = 7; - -$rdf.variable = $rdf.Formula.prototype.variable = function (name) { - return new $rdf.Variable(name); -}; - -$rdf.Variable.prototype.hashString = $rdf.Variable.prototype.toNT; - - -// The namespace function generator -$rdf.Namespace = function (nsuri) { - return function (ln) { - return new $rdf.Symbol(nsuri + (ln === undefined ? '' : ln)) - } -} - -$rdf.Formula.prototype.ns = function (nsuri) { - return function (ln) { - return new $rdf.Symbol(nsuri + (ln === undefined ? '' : ln)) - } -} - - -// Parse a single token -// -// The bnode bit should not be used on program-external values; designed -// for internal work such as storing a bnode id in an HTML attribute. -// This will only parse the strings generated by the vaious toNT() methods. -$rdf.Formula.prototype.fromNT = function (str) { - var len = str.length - var ch = str.slice(0, 1) - if(ch == '<') return $rdf.sym(str.slice(1, len - 1)) - if(ch == '"') { - var lang = undefined; - var dt = undefined; - var k = str.lastIndexOf('"'); - if(k < len - 1) { - if(str[k + 1] == '@') lang = str.slice(k + 2, len); - else if(str.slice(k + 1, k + 3) == '^^') dt = $rdf.fromNT(str.slice(k + 3, len)); - else throw new Error("Can't convert string from NT: " + str); - } - var str = (str.slice(1, k)); - str = str.replace(/\\"/g, '"'); // unescape quotes ' - str = str.replace(/\\n/g, '\n'); // unescape newlines - str = str.replace(/\\\\/g, '\\'); // unescape backslashes - return $rdf.lit(str, lang, dt); - } - if(ch == '_') { - var x = new $rdf.BlankNode(); - x.id = parseInt(str.slice(3)); - $rdf.NextId-- - return x - } - if(ch == '?') { - var x = new $rdf.Variable(str.slice(1)); - return x; - } - throw new Error("Can't convert from NT: " + str); - -} -$rdf.fromNT = $rdf.Formula.prototype.fromNT; // Not for inexpert user -// Convenience - and more conventional name: -$rdf.graph = function () { - return new $rdf.IndexedFormula(); -}; - -// ends \ No newline at end of file diff --git a/chrome/content/zotero/xpcom/rdf/uri.js b/chrome/content/zotero/xpcom/rdf/uri.js deleted file mode 100644 index 642a1fc2bd..0000000000 --- a/chrome/content/zotero/xpcom/rdf/uri.js +++ /dev/null @@ -1,149 +0,0 @@ -// Implementing URI-specific functions -// -// See RFC 2386 -// -// This is or was http://www.w3.org/2005/10/ajaw/uri.js -// 2005 W3C open source licence -// -// -// Take a URI given in relative or absolute form and a base -// URI, and return an absolute URI -// -// See also http://www.w3.org/2000/10/swap/uripath.py -// -if(typeof $rdf.Util.uri == "undefined") { - $rdf.Util.uri = {}; -}; - -$rdf.Util.uri.join = function (given, base) { - // if (typeof $rdf.log.debug != 'undefined') $rdf.log.debug(" URI given="+given+" base="+base) - var baseHash = base.indexOf('#') - if(baseHash > 0) base = base.slice(0, baseHash) - if(given.length == 0) return base // before chopping its filename off - if(given.indexOf('#') == 0) return base + given - var colon = given.indexOf(':') - if(colon >= 0) return given // Absolute URI form overrides base URI - var baseColon = base.indexOf(':') - if(base == "") return given; - if(baseColon < 0) { - alert("Invalid base: " + base + ' in join with ' + given); - return given - } - var baseScheme = base.slice(0, baseColon + 1) // eg http: - if(given.indexOf("//") == 0) // Starts with // - return baseScheme + given; - if(base.indexOf('//', baseColon) == baseColon + 1) { // Any hostpart? - var baseSingle = base.indexOf("/", baseColon + 3) - if(baseSingle < 0) { - if(base.length - baseColon - 3 > 0) { - return base + "/" + given - } else { - return baseScheme + given - } - } - } else { - var baseSingle = base.indexOf("/", baseColon + 1) - if(baseSingle < 0) { - if(base.length - baseColon - 1 > 0) { - return base + "/" + given - } else { - return baseScheme + given - } - } - } - - if(given.indexOf('/') == 0) // starts with / but not // - return base.slice(0, baseSingle) + given - - var path = base.slice(baseSingle) - var lastSlash = path.lastIndexOf("/") - if(lastSlash < 0) return baseScheme + given - if((lastSlash >= 0) - && (lastSlash < (path.length - 1))) - path = path.slice(0, lastSlash + 1) // Chop trailing filename from base - path = path + given - while(path.match(/[^\/]*\/\.\.\//)) // must apply to result of prev - path = path.replace(/[^\/]*\/\.\.\//, '') // ECMAscript spec 7.8.5 - path = path.replace(/\.\//g, '') // spec vague on escaping - path = path.replace(/\/\.$/, '/') - return base.slice(0, baseSingle) + path -} - -if(typeof tabulator != 'undefined' && tabulator.isExtension) { - $rdf.Util.uri.join2 = function (given, base) { - var tIOService = Components.classes['@mozilla.org/network/io-service;1'] - .getService(Components.interfaces.nsIIOService); - - var baseURI = tIOService.newURI(base, null, null); - return tIOService.newURI(baseURI.resolve(given), null, null).spec; - } -} else - $rdf.Util.uri.join2 = $rdf.Util.uri.join; - -// refTo: Make a URI relative to a given base -// -// based on code in http://www.w3.org/2000/10/swap/uripath.py -// -$rdf.Util.uri.commonHost = new RegExp("^[-_a-zA-Z0-9.]+:(//[^/]*)?/[^/]*$"); - -$rdf.Util.uri.hostpart = function (u) { - var m = /[^\/]*\/\/([^\/]*)\//.exec(u); - return m ? m[1] : '' -}; - -$rdf.Util.uri.refTo = function (base, uri) { - if(!base) return uri; - if(base == uri) return ""; - var i = 0; // How much are they identical? - while(i < uri.length && i < base.length) - if(uri[i] == base[i]) i++; - else break; - if(base.slice(0, i).match($rdf.Util.uri.commonHost)) { - var k = uri.indexOf('//'); - if(k < 0) k = -2; // no host - var l = uri.indexOf('/', k + 2); // First *single* slash - if(uri.slice(l + 1, l + 2) != '/' - && base.slice(l + 1, l + 2) != '/' - && uri.slice(0, l) == base.slice(0, l)) - // common path to single slash - return uri.slice(l); // but no other common path segments - } - // fragment of base? - if(uri.slice(i, i + 1) == '#' && base.length == i) return uri.slice(i); - while(i > 0 && uri[i - 1] != '/') i--; - - if(i < 3) return uri; // No way - if((base.indexOf('//', i - 2) > 0) - || uri.indexOf('//', i - 2) > 0) - return uri; // an unshared '//' - if(base.indexOf(':', i) > 0) return uri; // unshared ':' - var n = 0; - for(var j = i; j < base.length; j++) if(base[j] == '/') n++; - if(n == 0 && i < uri.length && uri[i] == '#') return './' + uri.slice(i); - if(n == 0 && i == uri.length) return './'; - var str = ''; - for(var j = 0; j < n; j++) str += '../'; - return str + uri.slice(i); -} - - -/** returns URI without the frag **/ -$rdf.Util.uri.docpart = function (uri) { - var i = uri.indexOf("#") - if(i < 0) return uri - return uri.slice(0, i) -} - -/** The document in which something a thing defined **/ -$rdf.Util.uri.document = function (x) { - return $rdf.sym($rdf.Util.uri.docpart(x.uri)); -} - -/** return the protocol of a uri **/ -/** return null if there isn't one **/ -$rdf.Util.uri.protocol = function (uri) { - var index = uri.indexOf(':'); - if(index >= 0) return uri.slice(0, index); - else return null; -} //protocol -//ends \ No newline at end of file diff --git a/chrome/content/zotero/xpcom/translation/tlds.js b/chrome/content/zotero/xpcom/translation/tlds.js deleted file mode 100644 index 9b5405dd1e..0000000000 --- a/chrome/content/zotero/xpcom/translation/tlds.js +++ /dev/null @@ -1,274 +0,0 @@ -const TLDS = { - "ac":true, - "ad":true, - "ae":true, - "aero":true, - "af":true, - "ag":true, - "ai":true, - "al":true, - "am":true, - "an":true, - "ao":true, - "aq":true, - "ar":true, - "arpa":true, - "as":true, - "asia":true, - "at":true, - "au":true, - "aw":true, - "ax":true, - "az":true, - "ba":true, - "bb":true, - "bd":true, - "be":true, - "bf":true, - "bg":true, - "bh":true, - "bi":true, - "biz":true, - "bj":true, - "bm":true, - "bn":true, - "bo":true, - "br":true, - "bs":true, - "bt":true, - "bv":true, - "bw":true, - "by":true, - "bz":true, - "ca":true, - "cat":true, - "cc":true, - "cd":true, - "cf":true, - "cg":true, - "ch":true, - "ci":true, - "ck":true, - "cl":true, - "cm":true, - "cn":true, - "co":true, - "com":true, - "coop":true, - "cr":true, - "cu":true, - "cv":true, - "cx":true, - "cy":true, - "cz":true, - "de":true, - "dj":true, - "dk":true, - "dm":true, - "do":true, - "dz":true, - "ec":true, - "edu":true, - "ee":true, - "eg":true, - "er":true, - "es":true, - "et":true, - "eu":true, - "fi":true, - "fj":true, - "fk":true, - "fm":true, - "fo":true, - "fr":true, - "ga":true, - "gb":true, - "gd":true, - "ge":true, - "gf":true, - "gg":true, - "gh":true, - "gi":true, - "gl":true, - "gm":true, - "gn":true, - "gov":true, - "gp":true, - "gq":true, - "gr":true, - "gs":true, - "gt":true, - "gu":true, - "gw":true, - "gy":true, - "hk":true, - "hm":true, - "hn":true, - "hr":true, - "ht":true, - "hu":true, - "id":true, - "ie":true, - "il":true, - "im":true, - "in":true, - "info":true, - "int":true, - "io":true, - "iq":true, - "ir":true, - "is":true, - "it":true, - "je":true, - "jm":true, - "jo":true, - "jobs":true, - "jp":true, - "ke":true, - "kg":true, - "kh":true, - "ki":true, - "km":true, - "kn":true, - "kp":true, - "kr":true, - "kw":true, - "ky":true, - "kz":true, - "la":true, - "lb":true, - "lc":true, - "li":true, - "lk":true, - "lr":true, - "ls":true, - "lt":true, - "lu":true, - "lv":true, - "ly":true, - "ma":true, - "mc":true, - "md":true, - "me":true, - "mg":true, - "mh":true, - "mil":true, - "mk":true, - "ml":true, - "mm":true, - "mn":true, - "mo":true, - "mobi":true, - "mp":true, - "mq":true, - "mr":true, - "ms":true, - "mt":true, - "mu":true, - "museum":true, - "mv":true, - "mw":true, - "mx":true, - "my":true, - "mz":true, - "na":true, - "name":true, - "nc":true, - "ne":true, - "net":true, - "nf":true, - "ng":true, - "ni":true, - "nl":true, - "no":true, - "np":true, - "nr":true, - "nu":true, - "nz":true, - "om":true, - "org":true, - "pa":true, - "pe":true, - "pf":true, - "pg":true, - "ph":true, - "pk":true, - "pl":true, - "pm":true, - "pn":true, - "pr":true, - "pro":true, - "ps":true, - "pt":true, - "pw":true, - "py":true, - "qa":true, - "re":true, - "ro":true, - "rs":true, - "ru":true, - "rw":true, - "sa":true, - "sb":true, - "sc":true, - "sd":true, - "se":true, - "sg":true, - "sh":true, - "si":true, - "sj":true, - "sk":true, - "sl":true, - "sm":true, - "sn":true, - "so":true, - "sr":true, - "st":true, - "su":true, - "sv":true, - "sy":true, - "sz":true, - "tc":true, - "td":true, - "tel":true, - "tf":true, - "tg":true, - "th":true, - "tj":true, - "tk":true, - "tl":true, - "tm":true, - "tn":true, - "to":true, - "tp":true, - "tr":true, - "travel":true, - "tt":true, - "tv":true, - "tw":true, - "tz":true, - "ua":true, - "ug":true, - "uk":true, - "us":true, - "uy":true, - "uz":true, - "va":true, - "vc":true, - "ve":true, - "vg":true, - "vi":true, - "vn":true, - "vu":true, - "wf":true, - "ws":true, - "xxx":true, - "ye":true, - "yt":true, - "za":true, - "zm":true, - "zw":true -}; -if (typeof process === 'object' && process + '' === '[object process]'){ - module.exports = TLDS; -} \ No newline at end of file diff --git a/chrome/content/zotero/xpcom/translation/translate.js b/chrome/content/zotero/xpcom/translation/translate.js deleted file mode 100644 index 78a7d885ee..0000000000 --- a/chrome/content/zotero/xpcom/translation/translate.js +++ /dev/null @@ -1,3197 +0,0 @@ -/* - ***** BEGIN LICENSE BLOCK ***** - - Copyright © 2009 Center for History and New Media - George Mason University, Fairfax, Virginia, USA - http://zotero.org - - This file is part of Zotero. - - Zotero is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zotero is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zotero. If not, see . - - ***** END LICENSE BLOCK ***** -*/ - -/** - * @class - * Deprecated class for creating new Zotero.Translate instances
- *
- * New code should use Zotero.Translate.Web, Zotero.Translate.Import, Zotero.Translate.Export, or - * Zotero.Translate.Search - */ -Zotero.Translate = function(type) { - Zotero.debug("Translate: WARNING: new Zotero.Translate() is deprecated; please don't use this if you don't have to"); - // hack - var translate = Zotero.Translate.newInstance(type); - for(var i in translate) { - this[i] = translate[i]; - } - this.constructor = translate.constructor; - this.__proto__ = translate.__proto__; -} - -/** - * Create a new translator by a string type - */ -Zotero.Translate.newInstance = function(type) { - return new Zotero.Translate[type.substr(0, 1).toUpperCase()+type.substr(1).toLowerCase()]; -} - -/** - * Namespace for Zotero sandboxes - * @namespace - */ -Zotero.Translate.Sandbox = { - /** - * Combines a sandbox with the base sandbox - */ - "_inheritFromBase":function(sandboxToMerge) { - var newSandbox = {}; - - for(var method in Zotero.Translate.Sandbox.Base) { - newSandbox[method] = Zotero.Translate.Sandbox.Base[method]; - } - - for(var method in sandboxToMerge) { - newSandbox[method] = sandboxToMerge[method]; - } - - return newSandbox; - }, - - /** - * Base sandbox. These methods are available to all translators. - * @namespace - */ - "Base": { - /** - * Called as {@link Zotero.Item#complete} from translators to save items to the database. - * @param {Zotero.Translate} translate - * @param {SandboxItem} An item created using the Zotero.Item class from the sandbox - */ - _itemDone: function (translate, item) { - // https://github.com/zotero/translators/issues/1353 - var asyncTranslator = !(translate instanceof Zotero.Translate.Web) - && translate.translator[0].configOptions - && translate.translator[0].configOptions.async; - - var run = async function (async) { - Zotero.debug("Translate: Saving item"); - - // warn if itemDone called after translation completed - if(translate._complete) { - Zotero.debug("Translate: WARNING: Zotero.Item#complete() called after Zotero.done(); please fix your code", 2); - } - - const allowedObjects = [ - "complete", - "attachments", - "creators", - "tags", - "notes", - "relations", - // Is this still needed? - "seeAlso" - ]; - - // Create a new object here, so that we strip the "complete" property - var newItem = {}; - var oldItem = item; - for(var i in item) { - var val = item[i]; - if(i === "complete" || (!val && val !== 0)) continue; - - var type = typeof val; - var isObject = type === "object" || type === "xml" || type === "function", - shouldBeObject = allowedObjects.indexOf(i) !== -1; - if(isObject && !shouldBeObject) { - // Convert things that shouldn't be objects to objects - translate._debug("Translate: WARNING: typeof "+i+" is "+type+"; converting to string"); - newItem[i] = val.toString(); - } else if(shouldBeObject && !isObject) { - translate._debug("Translate: WARNING: typeof "+i+" is "+type+"; converting to array"); - newItem[i] = [val]; - } else if(type === "string") { - // trim strings - newItem[i] = val.trim(); - } else { - newItem[i] = val; - } - } - item = newItem; - - if (item.title) { - item.title = translate._cleanTitle(item.title, item.itemType); - // Short Title is added before this in the web itemDone, so if we removed - // a suffix (e.g., ": a novel") and the short title now matches the title, - // remove it - if (item.title == item.shortTitle) { - delete item.shortTitle; - } - } - - // Clean empty creators - if (item.creators) { - for (var i=0; i translate.decrementAsyncProcesses("Zotero.Translate#_saveItems()")); - } - }; - - return run(asyncTranslator); - }, - - /** - * Gets translator options that were defined in displayOptions in translator header - * - * @param {Zotero.Translate} translate - * @param {String} option Option to be retrieved - */ - "getOption":function(translate, option) { - if(typeof option !== "string") { - throw(new Error("getOption: option must be a string")); - return; - } - - return translate._displayOptions[option]; - }, - - /** - * Gets a hidden preference that can be defined by hiddenPrefs in translator header - * - * @param {Zotero.Translate} translate - * @param {String} pref Prefernce to be retrieved - */ - "getHiddenPref":function(translate, pref) { - if(typeof(pref) != "string") { - throw(new Error("getPref: preference must be a string")); - } - - var hp = translate._translatorInfo.hiddenPrefs || {}; - - var value; - try { - value = Zotero.Prefs.get('translators.' + pref); - } catch(e) {} - - return (value !== undefined ? value : hp[pref]); - }, - - /** - * For loading other translators and accessing their methods - * - * @param {Zotero.Translate} translate - * @param {String} type Translator type ("web", "import", "export", or "search") - * @returns {Object} A safeTranslator object, which operates mostly like Zotero.Translate - */ - "loadTranslator":function(translate, type) { - const setDefaultHandlers = function(translate, translation) { - if(type !== "export" - && (!translation._handlers['itemDone'] || !translation._handlers['itemDone'].length)) { - translation.setHandler("itemDone", function(obj, item) { - translate.Sandbox._itemDone(translate, item); - }); - } - if(!translation._handlers['selectItems'] || !translation._handlers['selectItems'].length) { - translation.setHandler("selectItems", translate._handlers["selectItems"]); - } - } - - if(typeof type !== "string") { - throw(new Error("loadTranslator: type must be a string")); - return; - } - - Zotero.debug("Translate: Creating translate instance of type "+type+" in sandbox"); - var translation = Zotero.Translate.newInstance(type); - translation._parentTranslator = translate; - translation.setTranslatorProvider(translate._translatorProvider); - - if(translation instanceof Zotero.Translate.Export && !(translation instanceof Zotero.Translate.Export)) { - throw(new Error("Only export translators may call other export translators")); - } - - /** - * @class Wrapper for {@link Zotero.Translate} for safely calling another translator - * from inside an existing translator - * @inner - */ - var safeTranslator = {}; - safeTranslator.setSearch = function(arg) { - if(!Zotero.isBookmarklet) arg = JSON.parse(JSON.stringify(arg)); - return translation.setSearch(arg); - }; - safeTranslator.setDocument = function(arg) { - if (Zotero.isFx && !Zotero.isBookmarklet) { - return translation.setDocument( - Zotero.Translate.DOMWrapper.wrap(arg, arg.SpecialPowers_wrapperOverrides) - ); - } else { - return translation.setDocument(arg); - } - }; - var errorHandlerSet = false; - safeTranslator.setHandler = function(arg1, arg2) { - if(arg1 === "error") errorHandlerSet = true; - translation.setHandler(arg1, - function(obj, item) { - try { - item = item.wrappedJSObject ? item.wrappedJSObject : item; - if(arg1 == "itemDone") { - item.complete = translate._sandboxZotero.Item.prototype.complete; - } - arg2(obj, item); - } catch(e) { - translate.complete(false, e); - } - } - ); - }; - safeTranslator.setString = function(arg) { translation.setString(arg) }; - safeTranslator.setTranslator = function(arg) { - var success = translation.setTranslator(arg); - if(!success) { - throw new Error("Translator "+translate.translator[0].translatorID+" attempted to call invalid translatorID "+arg); - } - }; - - var translatorsHandlerSet = false; - safeTranslator.getTranslators = function() { - if(!translation._handlers["translators"] || !translation._handlers["translators"].length) { - throw new Error('Translator must register a "translators" handler to '+ - 'call getTranslators() in this translation environment.'); - } - if(!translatorsHandlerSet) { - translation.setHandler("translators", function() { - translate.decrementAsyncProcesses("safeTranslator#getTranslators()"); - }); - } - translate.incrementAsyncProcesses("safeTranslator#getTranslators()"); - return translation.getTranslators(); - }; - - var doneHandlerSet = false; - safeTranslator.translate = function() { - translate.incrementAsyncProcesses("safeTranslator#translate()"); - setDefaultHandlers(translate, translation); - if(!doneHandlerSet) { - doneHandlerSet = true; - translation.setHandler("done", function() { translate.decrementAsyncProcesses("safeTranslator#translate()") }); - } - if(!errorHandlerSet) { - errorHandlerSet = true; - translation.setHandler("error", function(obj, error) { translate.complete(false, error) }); - } - translation.translate(false); - }; - - safeTranslator.getTranslatorObject = function(callback) { - if(callback) { - translate.incrementAsyncProcesses("safeTranslator#getTranslatorObject()"); - } else { - throw new Error("Translator must pass a callback to getTranslatorObject() to "+ - "operate in this translation environment."); - } - - var translator = translation.translator[0]; - translator = typeof translator === "object" - ? translator - : translation._translatorProvider.get(translator); - // Zotero.Translators.get returns a value in the client and a promise in connectors - // so we normalize the value to a promise here - Zotero.Promise.resolve(translator) - .then(function(translator) { - return translation._loadTranslator(translator) - }) - .then(function() { - return translation._prepareTranslation(); - }) - .then(function () { - setDefaultHandlers(translate, translation); - var sandbox = translation._sandboxManager.sandbox; - if(!Zotero.Utilities.isEmpty(sandbox.exports)) { - sandbox.exports.Zotero = sandbox.Zotero; - sandbox = sandbox.exports; - } else { - translate._debug("COMPAT WARNING: "+translation.translator[0].label+" does "+ - "not export any properties. Only detect"+translation._entryFunctionSuffix+ - " and do"+translation._entryFunctionSuffix+" will be available in "+ - "connectors."); - } - - callback(sandbox); - translate.decrementAsyncProcesses("safeTranslator#getTranslatorObject()"); - }).catch(function(e) { - translate.complete(false, e); - return; - }); - }; - - return safeTranslator; - }, - - /** - * Enables asynchronous detection or translation - * @param {Zotero.Translate} translate - * @deprecated - */ - "wait":function(translate) {}, - - /** - * Sets the return value for detection - * - * @param {Zotero.Translate} translate - */ - "done":function(translate, returnValue) { - if(translate._currentState === "detect") { - translate._returnValue = returnValue; - } - }, - - /** - * Proxy for translator _debug function - * - * @param {Zotero.Translate} translate - * @param {String} string String to write to console - * @param {String} [level] Level to log as (1 to 5) - */ - "debug":function(translate, string, level) { - translate._debug(string, level); - } - }, - - /** - * Web functions exposed to sandbox - * @namespace - */ - "Web":{ - /** - * Lets user pick which items s/he wants to put in his/her library - * @param {Zotero.Translate} translate - * @param {Object} items An set of id => name pairs in object format - */ - "selectItems":function(translate, items, callback) { - function transferObject(obj) { - return obj; - } - - if(Zotero.Utilities.isEmpty(items)) { - throw new Error("Translator called select items with no items"); - } - - // Some translators pass an array rather than an object to Zotero.selectItems. - // This will break messaging outside of Firefox, so we need to fix it. - if(Object.prototype.toString.call(items) === "[object Array]") { - translate._debug("WARNING: Zotero.selectItems should be called with an object, not an array"); - var itemsObj = {}; - for(var i in items) itemsObj[i] = items[i]; - items = itemsObj; - } - - if(translate._selectedItems) { - // if we have a set of selected items for this translation, use them - return transferObject(translate._selectedItems); - } else if(translate._handlers.select) { - // whether the translator supports asynchronous selectItems - var haveAsyncCallback = !!callback; - // whether the handler operates asynchronously - var haveAsyncHandler = false; - var returnedItems = null; - - var callbackExecuted = false; - if(haveAsyncCallback) { - // if this translator provides an async callback for selectItems, rig things - // up to pop off the async process - var newCallback = function(selectedItems) { - callbackExecuted = true; - try { - callback(transferObject(selectedItems)); - } catch (e) { - translate.complete(false, e); - return false; - } - if(haveAsyncHandler) translate.decrementAsyncProcesses("Zotero.selectItems()"); - }; - } else { - // if this translator doesn't provide an async callback for selectItems, set things - // up so that we can wait to see if the select handler returns synchronously. If it - // doesn't, we will need to restart translation. - var newCallback = function(selectedItems) { - callbackExecuted = true; - if(haveAsyncHandler) { - translate.translate({ - libraryID: translate._libraryID, - saveAttachments: translate._saveAttachments, - selectedItems - }); - } else { - returnedItems = transferObject(selectedItems); - } - }; - } - - if(Zotero.isFx && !Zotero.isBookmarklet) { - items = Components.utils.cloneInto(items, {}); - } - - var returnValue = translate._runHandler("select", items, newCallback); - if(returnValue !== undefined) { - // handler may have returned a value, which makes callback unnecessary - Zotero.debug("WARNING: Returning items from a select handler is deprecated. "+ - "Please pass items as to the callback provided as the third argument to "+ - "the handler."); - - returnedItems = transferObject(returnValue); - haveAsyncHandler = false; - } else { - // if we don't have returnedItems set already, the handler is asynchronous - haveAsyncHandler = !callbackExecuted; - } - - if(haveAsyncCallback) { - if(haveAsyncHandler) { - // we are running asynchronously, so increment async processes - translate.incrementAsyncProcesses("Zotero.selectItems()"); - } else if(!callbackExecuted) { - // callback didn't get called from handler, so call it here - callback(returnedItems); - } - return false; - } else { - translate._debug("COMPAT WARNING: No callback was provided for "+ - "Zotero.selectItems(). When executed outside of Firefox, a selectItems() call "+ - "will require this translator to be called multiple times.", 1); - - if(haveAsyncHandler) { - // The select handler is asynchronous, but this translator doesn't support - // asynchronous select. We return false to abort translation in this - // instance, and we will restart it later when the selectItems call is - // complete. - translate._aborted = true; - return false; - } else { - return returnedItems; - } - } - } else { // no handler defined; assume they want all of them - if(callback) callback(items); - return items; - } - }, - - /** - * Overloads {@link Zotero.Translate.Sandbox.Base._itemDone} to ensure that no standalone - * items are saved, that an item type is specified, and to add a libraryCatalog and - * shortTitle if relevant. - * @param {Zotero.Translate} translate - * @param {SandboxItem} An item created using the Zotero.Item class from the sandbox - */ - "_itemDone":function(translate, item) { - // Only apply checks if there is no parent translator - if(!translate._parentTranslator) { - if(!item.itemType) { - item.itemType = "webpage"; - translate._debug("WARNING: No item type specified"); - } - - if(item.type == "attachment" || item.type == "note") { - Zotero.debug("Translate: Discarding standalone "+item.type+" in non-import translator", 2); - return; - } - - // store library catalog if this item was captured from a website, and - // libraryCatalog is truly undefined (not false or "") - if(item.repository !== undefined) { - Zotero.debug("Translate: 'repository' field is now 'libraryCatalog'; please fix your code", 2); - item.libraryCatalog = item.repository; - delete item.repository; - } - - // automatically set library catalog - if(item.libraryCatalog === undefined && item.itemType != "webpage") { - item.libraryCatalog = translate.translator[0].label; - } - - // Remove library catalog if not valid for type, so it doesn't get saved to Extra - if (item.libraryCatalog) { - let itemTypeID = Zotero.ItemTypes.getID(item.itemType); - let fieldID = Zotero.ItemFields.getID('libraryCatalog'); - if (!Zotero.ItemFields.isValidForType(fieldID, itemTypeID)) { - delete item.libraryCatalog; - } - } - - // automatically set access date if URL is set - if(item.url && typeof item.accessDate == 'undefined') { - item.accessDate = Zotero.Date.dateToISO(new Date()); - } - - //consider type-specific "title" alternatives - var altTitle = Zotero.ItemFields.getName(Zotero.ItemFields.getFieldIDFromTypeAndBase(item.itemType, 'title')); - if(altTitle && item[altTitle]) item.title = item[altTitle]; - - if(!item.title) { - translate.complete(false, new Error("No title specified for item")); - return; - } - - // create short title - if(item.shortTitle === undefined && Zotero.Utilities.fieldIsValidForType("shortTitle", item.itemType)) { - // only set if changes have been made - var setShortTitle = false; - var title = item.title; - - // shorten to before first colon - var index = title.indexOf(":"); - if(index !== -1) { - title = title.substr(0, index); - setShortTitle = true; - } - // shorten to after first question mark - index = title.indexOf("?"); - if(index !== -1) { - index++; - if(index != title.length) { - title = title.substr(0, index); - setShortTitle = true; - } - } - - if(setShortTitle) item.shortTitle = title; - } - - /* Clean up ISBNs - * Allow multiple ISBNs, but... - * (1) validate all ISBNs - * (2) convert all ISBNs to ISBN-13 - * (3) remove any duplicates - * (4) separate them with space - */ - if (item.ISBN) { - // Match ISBNs with groups separated by various dashes or even spaces - var isbnRe = /\b(?:97[89][\s\x2D\xAD\u2010-\u2015\u2043\u2212]*)?(?:\d[\s\x2D\xAD\u2010-\u2015\u2043\u2212]*){9}[\dx](?![\x2D\xAD\u2010-\u2015\u2043\u2212])\b/gi, - validISBNs = [], - isbn; - while (isbn = isbnRe.exec(item.ISBN)) { - var validISBN = Zotero.Utilities.cleanISBN(isbn[0]); - if (!validISBN) { - // Back up and move up one character - isbnRe.lastIndex = isbn.index + 1; - continue; - } - - var isbn13 = Zotero.Utilities.toISBN13(validISBN); - if (validISBNs.indexOf(isbn13) == -1) validISBNs.push(isbn13); - } - item.ISBN = validISBNs.join(' '); - } - - // refuse to save very long tags - if(item.tags) { - for(var i=0; i 255) { - translate._debug("WARNING: Skipping unsynchable tag "+JSON.stringify(tagString)); - item.tags.splice(i--, 1); - } - } - } - - for(var i=0; i node"); - } - }, - - /** - * Import functions exposed to sandbox - * @namespace - */ - "Import":{ - /** - * Saves a collection to the DB - * Called as {@link Zotero.Collection#complete} from the sandbox - * @param {Zotero.Translate} translate - * @param {SandboxCollection} collection - */ - "_collectionDone":function(translate, collection) { - translate.newCollections.push(collection); - if(translate._libraryID == false) { - translate._runHandler("collectionDone", collection); - } - }, - - /** - * Sets the value of the progress indicator associated with export as a percentage - * @param {Zotero.Translate} translate - * @param {Number} value - */ - "setProgress":function(translate, value) { - if(typeof value !== "number") { - translate._progress = null; - } else { - translate._progress = value; - } - } - }, - - /** - * Export functions exposed to sandbox - * @namespace - */ - "Export":{ - /** - * Retrieves the next item to be exported - * @param {Zotero.Translate} translate - * @return {SandboxItem} - */ - "nextItem":function(translate) { - var item = translate._itemGetter.nextItem(); - - if(translate._displayOptions.hasOwnProperty("exportTags") && !translate._displayOptions["exportTags"]) { - item.tags = []; - } - - translate._runHandler("itemDone", item); - - return item; - }, - - /** - * Retrieves the next collection to be exported - * @param {Zotero.Translate} translate - * @return {SandboxCollection} - */ - "nextCollection":function(translate) { - if(!translate._translatorInfo.configOptions || !translate._translatorInfo.configOptions.getCollections) { - throw(new Error("getCollections configure option not set; cannot retrieve collection")); - } - - return translate._itemGetter.nextCollection(); - }, - - /** - * @borrows Zotero.Translate.Sandbox.Import.setProgress as this.setProgress - */ - "setProgress":function(translate, value) { - Zotero.Translate.Sandbox.Import.setProgress(translate, value); - } - }, - - /** - * Search functions exposed to sandbox - * @namespace - */ - "Search":{ - /** - * @borrows Zotero.Translate.Sandbox.Web._itemDone as this._itemDone - */ - "_itemDone":function(translate, item) { - // Always set library catalog, even if we have a parent translator - if(item.libraryCatalog === undefined) { - item.libraryCatalog = translate.translator[0].label; - } - - Zotero.Translate.Sandbox.Web._itemDone(translate, item); - } - } -} - -/** - * @class Base class for all translation types - * - * @property {String} type The type of translator. This is deprecated; use instanceof instead. - * @property {Zotero.Translator[]} translator The translator currently in use. Usually, only the - * first entry of the Zotero.Translator array is populated; subsequent entries represent - * translators to be used if the first fails. - * @property {String} path The path or URI string of the target - * @property {String} newItems Items created when translate() was called - * @property {String} newCollections Collections created when translate() was called - * @property {Number} runningAsyncProcesses The number of async processes that are running. These - * need to terminate before Zotero.done() is called. - */ -Zotero.Translate.Base = function() {} -Zotero.Translate.Base.prototype = { - /** - * Initializes a Zotero.Translate instance - */ - "init":function() { - this._handlers = []; - this._currentState = null; - this._translatorInfo = null; - this._translatorProvider = Zotero.Translators; - this.document = null; - this.location = null; - }, - - /** - * Sets the location to operate upon - * - * @param {String|nsIFile} location The URL to which the sandbox should be bound or path to local file - */ - "setLocation":function(location) { - this.location = location; - if(typeof this.location == "object") { // if a file - this.path = location.path; - } else { // if a url - this.path = location; - } - }, - - /** - * Sets the translator to be used for import/export - * - * @param {Array{Zotero.Translator}|Zotero.Translator|string} Translator object or ID - */ - "setTranslator":function(translator) { - // Accept an array of translators - if (Array.isArray(translator)) { - this.translator = translator; - return true; - } - if(!translator) { - throw new Error("No translator specified"); - } - - this.translator = null; - - if(typeof(translator) == "object") { // passed an object and not an ID - if(translator.translatorID) { - this.translator = [translator]; - } else { - throw(new Error("No translatorID specified")); - } - } else { - this.translator = [translator]; - } - - return !!this.translator; - }, - - /** - * Registers a handler function to be called when translation is complete - * - * @param {String} type Type of handler to register. Legal values are: - * 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.Translate.getTranslators() is - * complete - * passed: an array of appropriate translators - * returns: N/A - * pageModified - * valid: web - * called: when a web page has been modified - * passed: the document object for the modified page - * returns: N/A - * @param {Function} handler Callback function. All handlers will be passed the current - * translate instance as the first argument. The second argument is dependent on the handler. - */ - "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 - * @param {String} type See {@link Zotero.Translate.Base#setHandler} for valid values - */ - "clearHandlers":function(type) { - this._handlers[type] = new Array(); - }, - - /** - * Clears a single handler for a given function - * @param {String} type See {@link Zotero.Translate.Base#setHandler} for valid values - * @param {Function} handler Callback function to remove - */ - "removeHandler":function(type, handler) { - var handlerIndex = this._handlers[type].indexOf(handler); - if(handlerIndex !== -1) this._handlers[type].splice(handlerIndex, 1); - }, - - /** - * Set custom translator provider, as returned by Zotero.Translators.makeTranslatorProvider() - * - * Used by Scaffold to substitute external translator files - * - * @param {Object} translatorProvider - */ - setTranslatorProvider: function (translatorProvider) { - this._translatorProvider = translatorProvider; - }, - - /** - * Indicates that a new async process is running - */ - "incrementAsyncProcesses":function(f) { - this._runningAsyncProcesses++; - if(this._parentTranslator) { - this._parentTranslator.incrementAsyncProcesses(f+" from child translator"); - } else { - //Zotero.debug("Translate: Incremented asynchronous processes to "+this._runningAsyncProcesses+" for "+f, 4); - //Zotero.debug((new Error()).stack); - } - }, - - /** - * Indicates that a new async process is finished - */ - "decrementAsyncProcesses":function(f, by) { - this._runningAsyncProcesses -= (by ? by : 1); - if(!this._parentTranslator) { - //Zotero.debug("Translate: Decremented asynchronous processes to "+this._runningAsyncProcesses+" for "+f, 4); - //Zotero.debug((new Error()).stack); - } - if(this._runningAsyncProcesses === 0) { - this.complete(); - } - if(this._parentTranslator) this._parentTranslator.decrementAsyncProcesses(f+" from child translator", by); - }, - - /** - * Clears all handlers for a given function - * @param {String} type See {@link Zotero.Translate.Base#setHandler} for valid values - * @param {Any} argument Argument to be passed to handler - */ - "_runHandler":function(type) { - var returnValue = undefined; - if(this._handlers[type]) { - // compile list of arguments - if(this._parentTranslator) { - // if there is a parent translator, make sure we don't pass the Zotero.Translate - // object, since it could open a security hole - var args = [null]; - } else { - var args = [this]; - } - for(var i=1; i false).then(function (rpcTranslators) { - this._waitingForRPC = false; - - // if there are translators, add them to the list of found translators - if (rpcTranslators) { - for(var i=0, n=rpcTranslators.length; i this._translateTranslatorLoaded()) - .catch(e => deferred.reject(e)); - }.bind(this)); - } - else { - this._loadTranslator(this.translator[0]) - .then(() => this._translateTranslatorLoaded()) - .catch(e => deferred.reject(e)); - } - - return deferred.promise; - }), - - /** - * Called when translator has been retrieved and loaded - */ - "_translateTranslatorLoaded": Zotero.Promise.method(function() { - // set display options to default if they don't exist - if(!this._displayOptions) this._displayOptions = this._translatorInfo.displayOptions || {}; - - var loadPromise = this._prepareTranslation(); - if (this.noWait) { - if (!loadPromise.isResolved()) { - throw new Error("Load promise is not resolved in noWait mode"); - } - rest.apply(this, arguments); - } else { - return loadPromise.then(() => rest.apply(this, arguments)) - } - - function rest() { - Zotero.debug("Translate: Beginning translation with " + this.translator[0].label); - - this.incrementAsyncProcesses("Zotero.Translate#translate()"); - - // translate - try { - let maybePromise = Function.prototype.apply.call( - this._sandboxManager.sandbox["do" + this._entryFunctionSuffix], - null, - this._getParameters() - ); - // doImport can return a promise to allow for incremental saves (via promise-returning - // item.complete() calls) - if (maybePromise) { - maybePromise - .then(() => this.decrementAsyncProcesses("Zotero.Translate#translate()")) - .catch(e => this.complete(false, e)); - return; - } - } catch (e) { - this.complete(false, e); - return false; - } - - this.decrementAsyncProcesses("Zotero.Translate#translate()"); - } - }), - - /** - * Return the progress of the import operation, or null if progress cannot be determined - */ - "getProgress":function() { return null }, - - /** - * Translate a URL to a form that goes through the appropriate proxy, or - * convert a relative URL to an absolute one - * - * @param {String} url - * @param {Boolean} dontUseProxy If true, don't convert URLs to variants - * that use the proxy - * @type String - * @private - */ - "resolveURL":function(url, dontUseProxy) { - Zotero.debug("Translate: resolving URL " + url); - - const hostPortRe = /^([A-Z][-A-Z0-9+.]*):\/\/[^\/]+/i; - const allowedSchemes = ['http', 'https', 'ftp']; - - var m = url.match(hostPortRe), - resolved; - if (!m) { - if (this.location) { - if (Zotero.isFx) { - resolved = Services.io.newURI(this.location, "", null).resolve(url); - } - else { - resolved = new URL(url, this.location).toString(); - } - } - else if (url.startsWith('//')) { - // Use HTTPS by default for protocol-relative URL with no associated web page - resolved = 'https:' + url; - } - else { - throw new Error('Cannot resolve relative URL without an associated web page: ' + url); - } - } else if (allowedSchemes.indexOf(m[1].toLowerCase()) == -1) { - Zotero.debug("Translate: unsupported scheme " + m[1]); - return url; - } else { - resolved = url; - } - - Zotero.debug("Translate: resolved to " + resolved); - - // convert proxy to proper if applicable - if(!dontUseProxy && this.translator && this.translator[0] - && this._proxy) { - var proxiedURL = this._proxy.toProxy(resolved); - if (proxiedURL != resolved) { - Zotero.debug("Translate: proxified to " + proxiedURL); - } - resolved = proxiedURL; - } - - /*var m = hostPortRe.exec(resolved); - if(!m) { - throw new Error("Invalid URL supplied for HTTP request: "+url); - } else if(this._translate.document && this._translate.document.location) { - var loc = this._translate.document.location; - if(this._translate._currentState !== "translate" && loc - && (m[1].toLowerCase() !== loc.protocol.toLowerCase() - || m[2].toLowerCase() !== loc.host.toLowerCase())) { - throw new Error("Attempt to access "+m[1]+"//"+m[2]+" from "+loc.protocol+"//"+loc.host - +" blocked: Cross-site requests are only allowed during translation"); - } - }*/ - - return resolved; - }, - - /** - * 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). - * @param {Boolean|String} returnValue An item type or a boolean true or false - * @param {String|Exception} [error] An error that occurred during translation. - * @returm {String|NULL} The exception serialized to a string, or null if translation - * completed successfully. - */ - "complete":function(returnValue, error) { - // allow translation to be aborted for re-running after selecting items - if(this._aborted) return; - - // Make sure this isn't called twice - if(this._currentState === null) { - if(!returnValue) { - Zotero.debug("Translate: WARNING: Zotero.done() called after translator completion with error"); - Zotero.debug(error); - } else { - var e = new Error(); - Zotero.debug("Translate: WARNING: Zotero.done() called after translation completion. This should never happen. Please examine the stack below."); - Zotero.debug(e.stack); - } - return; - } - - // reset async processes and propagate them to parent - if(this._parentTranslator && this._runningAsyncProcesses) { - this._parentTranslator.decrementAsyncProcesses("Zotero.Translate#complete", this._runningAsyncProcesses); - } - this._runningAsyncProcesses = 0; - - if(!returnValue && this._returnValue) returnValue = this._returnValue; - - var errorString = null; - if(!returnValue && error) errorString = this._generateErrorString(error); - if(this._currentState === "detect") { - if(this._potentialTranslators.length) { - var lastTranslator = this._potentialTranslators.shift(); - var lastProxy = this._proxies ? this._proxies.shift() : null; - - if (returnValue) { - var dupeTranslator = {proxy: lastProxy ? new Zotero.Proxy(lastProxy) : null}; - - for (var i in lastTranslator) dupeTranslator[i] = lastTranslator[i]; - if (Zotero.isBookmarklet && returnValue === "server") { - // In the bookmarklet, the return value from detectWeb can be "server" to - // indicate the translator should be run on the Zotero server - dupeTranslator.runMode = Zotero.Translator.RUN_MODE_ZOTERO_SERVER; - } else { - // Usually the return value from detectWeb will be either an item type or - // the string "multiple" - dupeTranslator.itemType = returnValue; - } - - this._foundTranslators.push(dupeTranslator); - } else if(error) { - this._debug("Detect using "+lastTranslator.label+" failed: \n"+errorString, 2); - } - } - - if(this._potentialTranslators.length && (this._getAllTranslators || !returnValue)) { - // more translators to try; proceed to next translator - this._detect(); - } else { - this._currentState = null; - if(!this._waitingForRPC) this._detectTranslatorsCollected(); - } - } else { - // unset return value is equivalent to true - if(returnValue === undefined) returnValue = true; - - if(returnValue) { - if(this.saveQueue.length) { - this._waitingForSave = true; - this._saveItems(this.saveQueue) - .catch(e => this._runHandler("error", e)) - .then(() => this.saveQueue = []); - return; - } - this._debug("Translation successful"); - } else { - if(error) { - // report error to console - Zotero.logError(error); - - // report error to debug log - this._debug("Translation using "+(this.translator && this.translator[0] && this.translator[0].label ? this.translator[0].label : "no translator")+" failed: \n"+errorString, 2); - } - - this._runHandler("error", error); - } - - this._currentState = null; - - // call handlers - this._runHandler("itemsDone", returnValue); - if(returnValue) { - this._checkIfDone(); - } else { - this._runHandler("done", returnValue); - } - } - - return errorString; - }, - - _cleanTitle: function (title, itemType) { - if (itemType == 'book' || itemType == 'bookSection') { - return title.replace(/\s*:\s*a novel\s*$/i, ''); - } - return title; - }, - - /** - * Canonicalize an array of tags such that they are all objects with the tag stored in the - * "tag" property and a type (if specified) is stored in the "type" property - * @returns {Object[]} Array of new tag objects - */ - "_cleanTags":function(tags) { - var newTags = []; - if(!tags) return newTags; - for(var i=0; i x.id == attachment.id); - } - else { - var attachmentIndex = this._savingAttachments.indexOf(attachment); - } - if(progress === false || progress === 100) { - if(attachmentIndex !== -1) { - this._savingAttachments.splice(attachmentIndex, 1); - } - } else if(attachmentIndex === -1) { - this._savingAttachments.push(attachment); - } - - if(itemDoneEventsDispatched) { - // itemDone event has already fired, so we can fire attachmentProgress - // notifications - this._runHandler("attachmentProgress", attachment, progress, error); - this._checkIfDone(); - } else { - // Defer until after we fire the itemDone event - deferredProgress.push([attachment, progress, error]); - attachmentsWithProgress.push(attachment); - } - } - - return this._itemSaver.saveItems(items.slice(), attachmentCallback.bind(this), - function(newItems) { - this._runHandler("itemsDone", newItems); - // Remove attachments not being saved from item.attachments - for(var i=0; i newItems); - }.bind(this)) - .then(function (newItems) { - // Specify that itemDone event was dispatched, so that we don't defer - // attachmentProgress notifications anymore - itemDoneEventsDispatched = true; - - // Run deferred attachmentProgress notifications - for(var i=0; i { - this._savingItems -= items.length; - this.complete(false, e); - throw e; - }); - }), - - /** - * Checks if saving done, and if so, fires done event - */ - "_checkIfDone":function() { - if(!this._savingItems && !this._savingAttachments.length && (!this._currentState || this._waitingForSave)) { - if (this.newCollections - && this._libraryID !== false - && this._itemSaver.saveCollections) { - var me = this; - this._itemSaver.saveCollections(this.newCollections) - .then(function (newCollections) { - me.newCollections = newCollections; - me._runHandler("done", true); - }) - .catch(function (err) { - me._runHandler("error", err); - me._runHandler("done", false); - }); - } else { - this._runHandler("done", true); - } - } - }, - - /** - * Begins running detect code for a translator, first loading it - */ - "_detect":function() { - // there won't be any translators if we need an RPC call - if(!this._potentialTranslators.length) { - this.complete(true); - return; - } - - let lab = this._potentialTranslators[0].label; - this._loadTranslator(this._potentialTranslators[0]) - .then(function() { - return this._detectTranslatorLoaded(); - }.bind(this)) - .catch(function (e) { - this.complete(false, e); - }.bind(this)); - }, - - /** - * Runs detect code for a translator - */ - "_detectTranslatorLoaded":function() { - this._prepareDetection(); - - this.incrementAsyncProcesses("Zotero.Translate#getTranslators"); - - try { - var returnValue = Function.prototype.apply.call(this._sandboxManager.sandbox["detect"+this._entryFunctionSuffix], null, this._getParameters()); - } catch(e) { - this.complete(false, e); - return; - } - - if(returnValue !== undefined) this._returnValue = returnValue; - this.decrementAsyncProcesses("Zotero.Translate#getTranslators"); - }, - - /** - * Called when all translators have been collected for detection - */ - "_detectTranslatorsCollected":function() { - Zotero.debug("Translate: All translator detect calls and RPC calls complete:"); - this._foundTranslators.sort(function(a, b) { - // If priority is equal, prioritize translators that run in browser over the client - if (a.priority == b.priority) { - return a.runMode - b.runMode; - } - return a.priority-b.priority; - }); - if (this._foundTranslators.length) { - this._foundTranslators.forEach(function(t) { - Zotero.debug("\t" + t.label + ": " + t.priority); - }); - } else { - Zotero.debug("\tNo suitable translators found"); - } - this._runHandler("translators", this._foundTranslators); - }, - - /** - * Loads the translator into its sandbox - * @param {Zotero.Translator} translator - * @return {Promise} - */ - _loadTranslator: Zotero.Promise.method(function (translator) { - var sandboxLocation = this._getSandboxLocation(); - if(!this._sandboxLocation || sandboxLocation !== this._sandboxLocation) { - this._sandboxLocation = sandboxLocation; - this._generateSandbox(); - } - - this._currentTranslator = translator; - - // Pass on the proxy of the parent translate - if (this._parentTranslator) { - this._proxy = this._parentTranslator._proxy; - } else { - this._proxy = translator.proxy; - } - this._runningAsyncProcesses = 0; - this._returnValue = undefined; - this._aborted = false; - this.saveQueue = []; - - var parse = function(code) { - Zotero.debug("Translate: Parsing code for " + translator.label + " " - + "(" + translator.translatorID + ", " + translator.lastUpdated + ")", 4); - try { - this._sandboxManager.eval( - "var exports = {}, ZOTERO_TRANSLATOR_INFO = " + code, - [ - "detect" + this._entryFunctionSuffix, - "do" + this._entryFunctionSuffix, - "exports", - "ZOTERO_TRANSLATOR_INFO" - ], - (translator.file ? translator.file.path : translator.label) - ); - } - catch (e) { - Zotero.logError(e); - } - this._translatorInfo = this._sandboxManager.sandbox.ZOTERO_TRANSLATOR_INFO; - }.bind(this); - - if (this.noWait) { - let codePromise = translator.getCode(); - if (!codePromise.isResolved()) { - throw new Error("Code promise is not resolved in noWait mode"); - } - parse(codePromise.value()); - } - else { - return translator.getCode().then(parse); - } - }), - - /** - * Generates a sandbox for scraping/scraper detection - */ - "_generateSandbox":function() { - Zotero.debug("Translate: Binding sandbox to "+(typeof this._sandboxLocation == "object" ? this._sandboxLocation.document.location : this._sandboxLocation), 4); - this._sandboxManager = new Zotero.Translate.SandboxManager(this._sandboxLocation); - const createArrays = "['creators', 'notes', 'tags', 'seeAlso', 'attachments']"; - var src = ""; - src += "Zotero.Item = function (itemType) {"+ - "var createArrays = "+createArrays+";"+ - "this.itemType = itemType;"+ - "for(var i=0, n=createArrays.length; i ${this.path}`; - } - if (Zotero.Prefs.get("downloadAssociatedFiles")) { - errorString += "\ndownloadAssociatedFiles => true"; - } - if (Zotero.Prefs.get("automaticSnapshots")) { - errorString += "\nautomaticSnapshots => true"; - } - return errorString; - }, - - /** - * Determines the location where the sandbox should be bound - * @return {String|document} The location to which to bind the sandbox - */ - "_getSandboxLocation":function() { - return (this._parentTranslator ? this._parentTranslator._sandboxLocation : "http://www.example.com/"); - }, - - /** - * Gets parameters to be passed to detect* and do* functions - * @return {Array} A list of parameters - */ - "_getParameters":function() { return []; }, - - /** - * No-op for preparing detection - */ - "_prepareDetection":function() {}, - - /** - * No-op for preparing translation - */ - "_prepareTranslation": function () { return Zotero.Promise.resolve(); } -} - -/** - * @class Web translation - * - * @property {Document} document The document object to be used for web scraping (set with setDocument) - * @property {Zotero.CookieSandbox} cookieSandbox A CookieSandbox to manage cookies for - * this Translate instance. - */ -Zotero.Translate.Web = function() { - this._registeredDOMObservers = {} - this.init(); -} -Zotero.Translate.Web.prototype = new Zotero.Translate.Base(); -Zotero.Translate.Web.prototype.type = "web"; -Zotero.Translate.Web.prototype._entryFunctionSuffix = "Web"; -Zotero.Translate.Web.prototype.Sandbox = Zotero.Translate.Sandbox._inheritFromBase(Zotero.Translate.Sandbox.Web); - -/** - * Sets the browser to be used for web translation - * @param {Document} doc An HTML document - */ -Zotero.Translate.Web.prototype.setDocument = function(doc) { - this.document = doc; - try { - this.rootDocument = doc.defaultView.top.document; - } catch (e) { - // Cross-origin frames won't be able to access top.document and will throw an error - } - if (!this.rootDocument) { - this.rootDocument = doc; - } - this.setLocation(doc.location.href, this.rootDocument.location.href); -} - -/** - * Sets a Zotero.CookieSandbox to handle cookie management for XHRs initiated from this - * translate instance - * - * @param {Zotero.CookieSandbox} cookieSandbox - */ -Zotero.Translate.Web.prototype.setCookieSandbox = function(cookieSandbox) { - this.cookieSandbox = cookieSandbox; -} - -/** - * Sets headers to include in HTTP requests. Used by translation-server. - */ -Zotero.Translate.Web.prototype.setRequestHeaders = function (headers) { - this.requestHeaders = headers; -}; - -/** - * Sets the location to operate upon - * - * @param {String} location The URL of the page to translate - * @param {String} rootLocation The URL of the root page, within which `location` is embedded - */ -Zotero.Translate.Web.prototype.setLocation = function(location, rootLocation) { - this.location = location; - this.rootLocation = rootLocation || location; - this.path = this.location; -} - -/** - * Get potential web translators - */ -Zotero.Translate.Web.prototype._getTranslatorsGetPotentialTranslators = function() { - return this._translatorProvider.getWebTranslatorsForLocation(this.location, this.rootLocation); -} - -/** - * Bind sandbox to document being translated - */ -Zotero.Translate.Web.prototype._getSandboxLocation = function() { - if(this._parentTranslator) { - return this._parentTranslator._sandboxLocation; - } else if(this.document.defaultView - && (this.document.defaultView.toString().indexOf("Window") !== -1 - || this.document.defaultView.toString().indexOf("XrayWrapper") !== -1)) { - return this.document.defaultView; - } else { - return this.document.location.toString(); - } -} - -/** - * Pass document and location to detect* and do* functions - */ -Zotero.Translate.Web.prototype._getParameters = function() { - return [this.document, this.location]; -}; - -/** - * Prepare translation - */ -Zotero.Translate.Web.prototype._prepareTranslation = Zotero.Promise.method(function () { - this._itemSaver = new Zotero.Translate.ItemSaver({ - libraryID: this._libraryID, - collections: this._collections, - attachmentMode: Zotero.Translate.ItemSaver[(this._saveAttachments ? "ATTACHMENT_MODE_DOWNLOAD" : "ATTACHMENT_MODE_IGNORE")], - forceTagType: 1, - sessionID: this._sessionID, - cookieSandbox: this._cookieSandbox, - proxy: this._proxy, - baseURI: this.location - }); - this.newItems = []; -}); - -/** - * Overload translate to set selectedItems - */ -Zotero.Translate.Web.prototype.translate = function (options = {}, ...args) { - if (typeof options == 'number' || options === false) { - Zotero.debug("Translate: translate() now takes an object -- update your code", 2); - options = { - libraryID: options, - saveAttachments: args[0], - selectedItems: args[1] - }; - } - this._selectedItems = options.selectedItems; - return Zotero.Translate.Base.prototype.translate.call(this, options); -} - -/** - * Overload _translateTranslatorLoaded to send an RPC call if necessary - */ -Zotero.Translate.Web.prototype._translateTranslatorLoaded = async function() { - var runMode = this.translator[0].runMode; - if(runMode === Zotero.Translator.RUN_MODE_IN_BROWSER || this._parentTranslator) { - Zotero.Translate.Base.prototype._translateTranslatorLoaded.apply(this); - } else if(runMode === Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE || - (runMode === Zotero.Translator.RUN_MODE_ZOTERO_SERVER && await Zotero.Connector.checkIsOnline())) { - var me = this; - let html = this.document.documentElement.innerHTML; - html = html.replace(new RegExp(Zotero.Utilities.quotemeta(ZOTERO_CONFIG.BOOKMARKLET_URL), 'g'), "about:blank") - // Higher timeout since translation might take a while if additional HTTP requests are made - Zotero.Connector.callMethod({method: "savePage", timeout: 60*1000}, { - sessionID: this._sessionID, - uri: this.location.toString(), - translatorID: (typeof this.translator[0] === "object" - ? this.translator[0].translatorID : this.translator[0]), - cookie: this.document.cookie, - proxy: this._proxy ? this._proxy.toJSON() : null, - html - }).then(me._translateRPCComplete.bind(me), me._translateRPCComplete.bind(me, null)); - } else if(runMode === Zotero.Translator.RUN_MODE_ZOTERO_SERVER) { - var me = this; - Zotero.API.createItem({"url":this.document.location.href.toString()}).then(function(response) { - me._translateServerComplete(201, response); - }, function(error) { - me._translateServerComplete(error.status, error.responseText); - }); - } -} - -/** - * Called when an call to Zotero Standalone for translation completes - */ -Zotero.Translate.Web.prototype._translateRPCComplete = async function(obj, failureCode) { - if(!obj) return this.complete(false, failureCode); - - if(obj.selectItems) { - // if we have to select items, call the selectItems handler and do it - var me = this; - this._runHandler("select", obj.selectItems, - function(selectedItems) { - Zotero.Connector.callMethod("selectItems", - {"instanceID":obj.instanceID, "selectedItems":selectedItems}) - .then((obj) => me._translateRPCComplete(obj)) - } - ); - } else { - // if we don't have to select items, continue - for(var i=0, n=obj.items.length; i 0; - - var configOptions = this._translatorInfo.configOptions || {}, - getCollections = configOptions.getCollections || false; - var loadPromise = Zotero.Promise.resolve(); - switch (this._export.type) { - case 'collection': - this._itemGetter.setCollection(this._export.collection, getCollections); - break; - case 'items': - this._itemGetter.setItems(this._export.items); - break; - case 'library': - loadPromise = this._itemGetter.setAll(this._export.id, getCollections); - break; - default: - throw new Error('No export set up'); - break; - } - delete this._export; - - if (this.noWait) { - if (!loadPromise.isResolved()) { - throw new Error("Load promise is not resolved in noWait mode"); - } - rest.apply(this, arguments); - } else { - return loadPromise.then(() => rest.apply(this, arguments)) - } - - function rest() { - // export file data, if requested - if (this._displayOptions.exportFileData) { - this.location = this._itemGetter.exportFiles( - this.location, - this.translator[0].target, - { - includeAnnotations: this._displayOptions.includeAnnotations - } - ); - } - - // initialize IO - // this is currently hackish since we pass null callbacks to the init function (they have - // callbacks to be consistent with import, but they are synchronous, so we ignore them) - if(!this.location) { - this._io = new Zotero.Translate.IO.String(null, this.path ? this.path : "", this._sandboxManager); - this._io.init(configOptions["dataMode"], function() {}); - } else if(!Zotero.Translate.IO.Write) { - throw new Error("Writing to files is not supported in this build of Zotero."); - } else { - this._io = new Zotero.Translate.IO.Write(this.location); - this._io.init(configOptions["dataMode"], - this._displayOptions["exportCharset"] ? this._displayOptions["exportCharset"] : null, - function() {}); - } - - this._sandboxManager.importObject(this._io); - } -}); - -/** - * Overload Zotero.Translate.Base#translate to make sure that - * Zotero.Translate.Export#translate is not called without setting a - * translator first. Doesn't make sense to run detection for export. - */ -Zotero.Translate.Export.prototype.translate = function() { - if(!this.translator || !this.translator.length) { - this.complete(false, new Error("Export translation initiated without setting a translator")); - } else { - return Zotero.Translate.Base.prototype.translate.apply(this, arguments); - } -}; - -/** - * Return the progress of the import operation, or null if progress cannot be determined - */ -Zotero.Translate.Export.prototype.getProgress = function() { - if(this._progress !== undefined) return this._progress; - if(!this._itemGetter) { - return null; - } - return (1-this._itemGetter.numItemsRemaining/this._itemGetter.numItems)*100; -}; - -/** - * @class Search translation - * @property {Array[]} search Item (in {@link Zotero.Item#serialize} format) to extrapolate data - * (set with setSearch) - */ -Zotero.Translate.Search = function() { - this.init(); -}; -Zotero.Translate.Search.prototype = new Zotero.Translate.Base(); -Zotero.Translate.Search.prototype.type = "search"; -Zotero.Translate.Search.prototype._entryFunctionSuffix = "Search"; -Zotero.Translate.Search.prototype.Sandbox = Zotero.Translate.Sandbox._inheritFromBase(Zotero.Translate.Sandbox.Search); -Zotero.Translate.Search.prototype.ERROR_NO_RESULTS = "No items returned from any translator"; - -/** - * @borrows Zotero.Translate.Web#setCookieSandbox - */ -Zotero.Translate.Search.prototype.setCookieSandbox = Zotero.Translate.Web.prototype.setCookieSandbox; - -/** - * Sets the item to be used for searching - * @param {Object} item An item, with as many fields as desired, in the format returned by - * {@link Zotero.Item#serialize} - */ -Zotero.Translate.Search.prototype.setSearch = function(search) { - this.search = search; -} - -/** - * Set an identifier to use for searching - * - * @param {Object} identifier - An object with 'DOI', 'ISBN', or 'PMID' - */ -Zotero.Translate.Search.prototype.setIdentifier = function (identifier) { - var search; - if (identifier.DOI) { - search = { - itemType: "journalArticle", - DOI: identifier.DOI - }; - } - else if (identifier.ISBN) { - search = { - itemType: "book", - ISBN: identifier.ISBN - }; - } - else if (identifier.PMID) { - search = { - itemType: "journalArticle", - contextObject: "rft_id=info:pmid/" + identifier.PMID - }; - } - else if (identifier.arXiv) { - search = { - itemType: "journalArticle", - arXiv: identifier.arXiv - }; - } - else { - throw new Error("Unrecognized identifier"); - } - this.setSearch(search); -} - -/** - * Overloads {@link Zotero.Translate.Base#getTranslators} to always return all potential translators - */ -Zotero.Translate.Search.prototype.getTranslators = function() { - return Zotero.Translate.Base.prototype.getTranslators.call(this, true); -} - -/** - * Overload Zotero.Translate.Base#complete to move onto the next translator if - * translation fails - */ -Zotero.Translate.Search.prototype.complete = function(returnValue, error) { - if(this._currentState == "translate" - && (!this.newItems || !this.newItems.length) - && !this._savingItems - //length is 0 only when translate was called without translators - && this.translator.length) { - Zotero.debug("Translate: Could not find a result using " + this.translator[0].label - + (this.translator.length > 1 ? " -- trying next translator" : ""), 3); - if(error) Zotero.debug(this._generateErrorString(error), 3); - if(this.translator.length > 1) { - this.translator.shift(); - this.translate({ - libraryID: this._libraryID, - saveAttachments: this._saveAttachments, - collections: this._collections - }); - return; - } else { - Zotero.debug("No more translators to try"); - error = this.ERROR_NO_RESULTS; - returnValue = false; - } - } - - // call super - Zotero.Translate.Base.prototype.complete.apply(this, [returnValue, error]); -} - -/** - * Pass search item to detect* and do* functions - */ -Zotero.Translate.Search.prototype._getParameters = function() { - return [this.search]; -}; - -/** - * Extract sandbox location from translator target - */ -Zotero.Translate.Search.prototype._getSandboxLocation = function() { - // 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) return m[0]; - } - return Zotero.Translate.Base.prototype._getSandboxLocation.call(this); -} - -Zotero.Translate.Search.prototype._prepareTranslation = Zotero.Translate.Web.prototype._prepareTranslation; - -/** - * IO-related functions - * @namespace - */ -Zotero.Translate.IO = { - /** - * Parses XML using DOMParser - */ - "parseDOMXML":function(input, charset, size) { - try { - var dp = new DOMParser(); - } catch(e) { - try { - var dp = Components.classes["@mozilla.org/xmlextras/domparser;1"] - .createInstance(Components.interfaces.nsIDOMParser); - } catch(e) { - throw new Error("DOMParser not supported"); - } - } - - if(typeof input == "string") { - var nodes = dp.parseFromString(input, "text/xml"); - } else { - var nodes = dp.parseFromStream(input, charset, size, "text/xml"); - } - - if(nodes.getElementsByTagName("parsererror").length) { - throw new Error("DOMParser error: loading data into data store failed"); - } - - if("normalize" in nodes) nodes.normalize(); - - return nodes; - }, - - /** - * Names of RDF data modes - */ - "rdfDataModes":["rdf", "rdf/xml", "rdf/n3"] -}; - -/******* String support *******/ - -/** - * @class Translate backend for translating from a string - */ -Zotero.Translate.IO.String = function(string, uri, sandboxManager) { - if(string && typeof string === "string") { - this.string = string; - } else { - this.string = ""; - } - this.contentLength = this.string.length; - this.bytesRead = 0; - this._uri = uri; - this._sandboxManager = sandboxManager; -} - -Zotero.Translate.IO.String.prototype = { - "__exposedProps__":{ - "RDF":"r", - "read":"r", - "write":"r", - "setCharacterSet":"r", - "getXML":"r" - }, - - "_initRDF": function () { - Zotero.debug("Translate: Initializing RDF data store"); - this._dataStore = new Zotero.RDF.AJAW.IndexedFormula(); - this.RDF = new Zotero.Translate.IO._RDFSandbox(this._dataStore); - - if(this.contentLength) { - try { - var xml = Zotero.Translate.IO.parseDOMXML(this.string); - } catch(e) { - this._xmlInvalid = true; - throw e; - } - var parser = new Zotero.RDF.AJAW.RDFParser(this._dataStore); - parser.parse(xml, this._uri); - } - }, - - "setCharacterSet":function(charset) {}, - - "read":function(bytes) { - // if we are reading in RDF data mode and no string is set, serialize current RDF to the - // string - if(Zotero.Translate.IO.rdfDataModes.indexOf(this._mode) !== -1 && this.string === "") { - this.string = this.RDF.serialize(); - } - - // return false if string has been read - if(this.bytesRead >= this.contentLength) { - return false; - } - - if(bytes !== undefined) { - if(this.bytesRead >= this.contentLength) return false; - var oldPointer = this.bytesRead; - this.bytesRead += bytes; - return this.string.substr(oldPointer, bytes); - } else { - // bytes not specified; read a line - var oldPointer = this.bytesRead; - var lfIndex = this.string.indexOf("\n", this.bytesRead); - - if(lfIndex !== -1) { - // in case we have a CRLF - this.bytesRead = lfIndex+1; - if(this.contentLength > lfIndex && this.string.substr(lfIndex-1, 1) === "\r") { - lfIndex--; - } - return this.string.substr(oldPointer, lfIndex-oldPointer); - } - - if(!this._noCR) { - var crIndex = this.string.indexOf("\r", this.bytesRead); - if(crIndex === -1) { - this._noCR = true; - } else { - this.bytesRead = crIndex+1; - return this.string.substr(oldPointer, crIndex-oldPointer-1); - } - } - - this.bytesRead = this.contentLength; - return this.string.substr(oldPointer); - } - }, - - "write":function(data) { - this.string += data; - this.contentLength = this.string.length; - }, - - "getXML":function() { - try { - var xml = Zotero.Translate.IO.parseDOMXML(this.string); - } catch(e) { - this._xmlInvalid = true; - throw e; - } - return xml; - }, - - init: function (newMode) { - this.bytesRead = 0; - this._noCR = undefined; - - this._mode = newMode; - if(newMode === "xml/e4x") { - throw new Error("E4X is not supported"); - } else if(newMode && (Zotero.Translate.IO.rdfDataModes.indexOf(newMode) !== -1 - || newMode.substr(0, 3) === "xml/dom") && this._xmlInvalid) { - throw new Error("XML known invalid"); - } else if(Zotero.Translate.IO.rdfDataModes.indexOf(this._mode) !== -1) { - this._initRDF(); - } - }, - - "close":function(serialize) { - // if we are writing in RDF data mode and no string is set, serialize current RDF to the - // string - if(serialize && Zotero.Translate.IO.rdfDataModes.indexOf(this._mode) !== -1 && this.string === "") { - this.string = this.RDF.serialize(); - } - } -} - -/****** RDF DATA MODE ******/ - -/** - * @class An API for handling RDF from the sandbox. This is exposed to translators as Zotero.RDF. - * - * @property {Zotero.RDF.AJAW.IndexedFormula} _dataStore - * @property {Integer[]} _containerCounts - * @param {Zotero.RDF.AJAW.IndexedFormula} dataStore - */ -Zotero.Translate.IO._RDFSandbox = function(dataStore) { - this._dataStore = dataStore; -} - -Zotero.Translate.IO._RDFSandbox.prototype = { - "_containerCounts":[], - "__exposedProps__":{ - "addStatement":"r", - "newResource":"r", - "newContainer":"r", - "addContainerElement":"r", - "getContainerElements":"r", - "addNamespace":"r", - "getAllResources":"r", - "getResourceURI":"r", - "getArcsIn":"r", - "getArcsOut":"r", - "getSources":"r", - "getTargets":"r", - "getStatementsMatching":"r", - "serialize":"r" - }, - - /** - * Gets a resource as a Zotero.RDF.AJAW.Symbol, rather than a string - * @param {String|Zotero.RDF.AJAW.Symbol} about - * @return {Zotero.RDF.AJAW.Symbol} - */ - "_getResource":function(about) { - return (typeof about == "object" ? about : new Zotero.RDF.AJAW.Symbol(about)); - }, - - /** - * Runs a callback to initialize this RDF store - */ - "_init":function() { - if(this._prepFunction) { - this._dataStore = this._prepFunction(); - delete this._prepFunction; - } - }, - - /** - * Serializes the current RDF to a string - */ - "serialize":function(dataMode) { - var serializer = Zotero.RDF.AJAW.Serializer(this._dataStore); - - for(var prefix in this._dataStore.namespaces) { - serializer.suggestPrefix(prefix, this._dataStore.namespaces[prefix]); - } - - // serialize in appropriate format - if(dataMode == "rdf/n3") { - return serializer.statementsToN3(this._dataStore.statements); - } - - return serializer.statementsToXML(this._dataStore.statements); - }, - - /** - * Adds an RDF triple - * @param {String|Zotero.RDF.AJAW.Symbol} about - * @param {String|Zotero.RDF.AJAW.Symbol} relation - * @param {String|Zotero.RDF.AJAW.Symbol} value - * @param {Boolean} literal Whether value should be treated as a literal (true) or a resource - * (false) - */ - "addStatement":function(about, relation, value, literal) { - if(about === null || about === undefined) { - throw new Error("about must be defined in Zotero.RDF.addStatement"); - } - if(relation === null || relation === undefined) { - throw new Error("relation must be defined in Zotero.RDF.addStatement"); - } - if(value === null || value === undefined) { - throw new Error("value must be defined in Zotero.RDF.addStatement"); - } - - if(literal) { - // zap chars that Mozilla will mangle - value = value.toString().replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, ''); - } else { - value = this._getResource(value); - } - - this._dataStore.add(this._getResource(about), this._getResource(relation), value); - }, - - /** - * Creates a new anonymous resource - * @return {Zotero.RDF.AJAW.Symbol} - */ - "newResource":function() { - return new Zotero.RDF.AJAW.BlankNode(); - }, - - /** - * Creates a new container resource - * @param {String} type The type of the container ("bag", "seq", or "alt") - * @param {String|Zotero.RDF.AJAW.Symbol} about The URI of the resource - * @return {Zotero.Translate.RDF.prototype.newContainer - */ - "newContainer":function(type, about) { - const rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; - const containerTypes = {"bag":"Bag", "seq":"Seq", "alt":"Alt"}; - - type = type.toLowerCase(); - if(!containerTypes[type]) { - throw new Error("Invalid container type in Zotero.RDF.newContainer"); - } - - var about = this._getResource(about); - this.addStatement(about, rdf+"type", rdf+containerTypes[type], false); - this._containerCounts[about.toNT()] = 1; - - return about; - }, - - /** - * Adds a new element to a container - * @param {String|Zotero.RDF.AJAW.Symbol} about The container - * @param {String|Zotero.RDF.AJAW.Symbol} element The element to add to the container - * @param {Boolean} literal Whether element should be treated as a literal (true) or a resource - * (false) - */ - "addContainerElement":function(about, element, literal) { - const rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"; - - var about = this._getResource(about); - this._dataStore.add(about, new Zotero.RDF.AJAW.Symbol(rdf+"_"+(this._containerCounts[about.toNT()]++)), element, literal); - }, - - /** - * Gets all elements within a container - * @param {String|Zotero.RDF.AJAW.Symbol} about The container - * @return {Zotero.RDF.AJAW.Symbol[]} - */ - "getContainerElements":function(about) { - const liPrefix = "http://www.w3.org/1999/02/22-rdf-syntax-ns#_"; - - var about = this._getResource(about); - var statements = this._dataStore.statementsMatching(about); - var containerElements = []; - - // loop over arcs out looking for list items - for(var i=0; i. - - ***** END LICENSE BLOCK ***** -*/ - -// Enumeration of types of translators -var TRANSLATOR_TYPES = {"import":1, "export":2, "web":4, "search":8}; - -// Properties required for every translator -var TRANSLATOR_REQUIRED_PROPERTIES = ["translatorID", "translatorType", "label", "creator", - "target", "priority", "lastUpdated"]; -// Properties that are preserved if present -var TRANSLATOR_OPTIONAL_PROPERTIES = ["targetAll", "browserSupport", "minVersion", "maxVersion", - "inRepository", "configOptions", "displayOptions", - "hiddenPrefs", "itemType"]; -// Properties that are passed from background to inject page in connector -var TRANSLATOR_PASSING_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES. - concat(["targetAll", "browserSupport", "code", "runMode", "itemType", "inRepository"]); - -/** - * @class Represents an individual translator - * @constructor - * @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} creator Author(s) of the translator - * @property {String} target Location that the translator processes - * @property {String} minVersion Minimum Zotero version - * @property {String} maxVersion Minimum Zotero version - * @property {Integer} priority Lower-priority translators will be selected first - * @property {String} browserSupport String indicating browser supported by the translator - * g = Gecko (Firefox) - * c = Google Chrome (WebKit & V8) - * s = Safari (WebKit & Nitro/Squirrelfish Extreme) - * i = Internet Explorer - * b = Bookmarklet - * v = Server - * @property {Object} configOptions Configuration options for import/export - * @property {Object} displayOptions Display options for export - * @property {Object} hiddenPrefs Hidden preferences configurable through about:config - * @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 {Object} metadata - Metadata block as object - * @property {String} code The executable JavaScript for the translator - * @property {Boolean} cacheCode Whether to cache code for this session (non-connector only) - * @property {String} [path] File path corresponding to this translator (non-connector only) - * @property {String} [fileName] File name corresponding to this translator (non-connector only) - */ -Zotero.Translator = function(info) { - this.init(info); -} - -/** - * Initializes a translator from a set of info, clearing code if it is set - */ -Zotero.Translator.prototype.init = function(info) { - // make sure we have all the properties - for (let property of TRANSLATOR_REQUIRED_PROPERTIES) { - if (info[property] === undefined) { - this.logError(new Error('Missing property "'+property+'" in translator metadata JSON object in ' + info.label)); - break; - } else { - this[property] = info[property]; - } - } - for (let property of TRANSLATOR_OPTIONAL_PROPERTIES) { - if(info[property] !== undefined) { - this[property] = info[property]; - } - } - - this.browserSupport = info["browserSupport"] ? info["browserSupport"] : "g"; - - var supported = !Zotero.isBookmarklet || this.browserSupport.includes("b") || - /(?:^|; ?)bookmarklet-debug-mode=1(?:$|; ?)/.test(document.cookie); - - if (supported) { - this.runMode = Zotero.Translator.RUN_MODE_IN_BROWSER; - } else { - this.runMode = Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE; - } - - if (this.translatorType & TRANSLATOR_TYPES["import"]) { - // compile import regexp to match only file extension - this.importRegexp = this.target ? new RegExp("\\."+this.target+"$", "i") : null; - } else if (this.hasOwnProperty("importRegexp")) { - delete this.importRegexp; - } - - this.cacheCode = Zotero.isConnector || info.cacheCode; - if (this.translatorType & TRANSLATOR_TYPES["web"]) { - // compile web regexp - this.cacheCode |= !this.target; - this.webRegexp = { - root: this.target ? new RegExp(this.target, "i") : null, - all: this.targetAll ? new RegExp(this.targetAll, "i") : null - }; - } else if (this.hasOwnProperty("webRegexp")) { - delete this.webRegexp; - } - - if (info.path) { - this.path = info.path; - this.fileName = OS.Path.basename(info.path); - } - if (info.code && this.cacheCode) { - this.code = Zotero.Translator.replaceDeprecatedStatements(info.code); - } else if (this.hasOwnProperty("code")) { - delete this.code; - } - // Save a copy of the metadata block - delete info.path; - delete info.code; - this.metadata = info; - -} - -/** - * Load code for a translator - */ -Zotero.Translator.prototype.getCode = Zotero.Promise.method(function () { - if (this.code) return this.code; - - if (Zotero.isConnector) { - return Zotero.Repo.getTranslatorCode(this.translatorID) - .then(function (args) { - var code = args[0]; - var source = args[1]; - if(!code) { - throw new Error("Code for " + this.label + " could not be retrieved"); - } - // Cache any translators for session, since retrieving via - // HTTP may be expensive - this.code = code; - this.codeSource = source; - return code; - }.bind(this)); - } else { - var promise = Zotero.File.getContentsAsync(this.path); - if(this.cacheCode) { - // Cache target-less web translators for session, since we - // will use them a lot - return promise.then(function(code) { - this.code = code; - return code; - }.bind(this)); - } - return promise; - } -}); - -/** - * Get metadata block for a translator - */ -Zotero.Translator.prototype.serialize = function(properties) { - var info = {}; - for(var i in properties) { - var property = properties[i]; - info[property] = this[property]; - } - return info; -} - -/** - * 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) { - if (Zotero.isFx && this.path) { - Components.utils.import("resource://gre/modules/FileUtils.jsm"); - var file = new FileUtils.File(this.path); - var ios = Components.classes["@mozilla.org/network/io-service;1"]. - getService(Components.interfaces.nsIIOService); - Zotero.log(message, type ? type : "error", ios.newFileURI(file).spec); - Zotero.debug(message, 1); - } else { - Zotero.logError(message); - } -} - -/** - * Replace deprecated ES5 statements - */ -Zotero.Translator.replaceDeprecatedStatements = function(code) { - const foreach = /^(\s*)for each\s*\((var )?([^ ]+) in (.*?)\)(\s*){/gm; - code = code.replace(foreach, "$1var $3_zForEachSubject = $4; "+ - "for(var $3_zForEachIndex in $3_zForEachSubject)$5{ "+ - "$2$3 = $3_zForEachSubject[$3_zForEachIndex];"); - return code; -} - -Zotero.Translator.RUN_MODE_IN_BROWSER = 1; -Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE = 2; -Zotero.Translator.RUN_MODE_ZOTERO_SERVER = 4; -Zotero.Translator.TRANSLATOR_TYPES = TRANSLATOR_TYPES; -Zotero.Translator.TRANSLATOR_OPTIONAL_PROPERTIES = TRANSLATOR_OPTIONAL_PROPERTIES; -Zotero.Translator.TRANSLATOR_REQUIRED_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES; diff --git a/chrome/content/zotero/xpcom/utilities.js b/chrome/content/zotero/xpcom/utilities.js deleted file mode 100644 index fca0502b9d..0000000000 --- a/chrome/content/zotero/xpcom/utilities.js +++ /dev/null @@ -1,1968 +0,0 @@ -/* - ***** BEGIN LICENSE BLOCK ***** - - Copyright © 2009 Center for History and New Media - George Mason University, Fairfax, Virginia, USA - http://zotero.org - - This file is part of Zotero. - - Zotero is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Zotero is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with Zotero. If not, see . - - - Utilities based in part on code taken from Piggy Bank 2.1.1 (BSD-licensed) - - ***** END LICENSE BLOCK ***** -*/ - -(function() { -if (typeof module != 'undefined') { - require('./xregexp-all'); -} - -function movedToUtilitiesInternal(fnName) { - return function () { - if (Zotero.Utilities && Zotero.Utilitie.Internal) { - Zotero.debug(`Zotero.Utilities.${fnName}() is deprecated -- use Zotero.Utilities.Internal.${fnName}() instead`); - return Zotero.Utilities.Internal[fnName].apply(Zotero.Utilities.Internal, arguments); - } else { - throw new Error(`Zotero.Utilities.${fnName}() is only available in the zotero-client codebase`) - } - } -} - -/** - * @class Functions for text manipulation and other miscellaneous purposes - */ -var Utilities = { - /** - * Returns a function which will execute `fn` with provided arguments after `delay` milliseconds and not more - * than once, if called multiple times. See - * http://stackoverflow.com/questions/24004791/can-someone-explain-the-debounce-function-in-javascript - * @param fn {Function} function to debounce - * @param delay {Integer} number of miliseconds to delay the function execution - * @returns {Function} - */ - debounce: function(fn, delay=500) { - var timer = null; - return function () { - let args = arguments; - clearTimeout(timer); - timer = setTimeout(function () { - fn.apply(this, args); - }.bind(this), delay); - }; - }, - - /** - * Creates and returns a new, throttled version of the - * passed function, that, when invoked repeatedly, - * will only actually call the original function at most - * once per every wait milliseconds - * - * By default, throttle will execute the function as soon - * as you call it for the first time, and, if you call it - * again any number of times during the wait period, as soon - * as that period is over. If you'd like to disable the - * leading-edge call, pass {leading: false}, and if you'd - * like to disable the execution on the trailing-edge, - * pass {trailing: false}. See - * https://underscorejs.org/#throttle - * https://github.com/jashkenas/underscore/blob/master/underscore.js - * (c) 2009-2018 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - * Underscore may be freely distributed under the MIT license. - * - * @param {Function} func Function to throttle - * @param {Integer} wait Wait period in milliseconds - * @param {Boolean} [options.leading] Call at the beginning of the wait period - * @param {Boolean} [options.trailing] Call at the end of the wait period - */ - throttle: function (func, wait, options) { - var context, args, result; - var timeout = null; - var previous = 0; - if (!options) options = {}; - var later = function () { - previous = options.leading === false ? 0 : Date.now(); - timeout = null; - result = func.apply(context, args); - if (!timeout) context = args = null; - }; - return function () { - var now = Date.now(); - if (!previous && options.leading === false) previous = now; - var remaining = wait - (now - previous); - context = this; - args = arguments; - if (remaining <= 0 || remaining > wait) { - if (timeout) { - clearTimeout(timeout); - timeout = null; - } - previous = now; - result = func.apply(context, args); - if (!timeout) context = args = null; - } - else if (!timeout && options.trailing !== false) { - timeout = setTimeout(later, remaining); - } - return result; - }; - }, - - /** - * Fixes author name capitalization. - * Currently for all uppercase names only - * - * JOHN -> John - * GUTIÉRREZ-ALBILLA -> Gutiérrez-Albilla - * O'NEAL -> O'Neal - * - * @param {String} string Uppercase author name - * @return {String} Title-cased author name - */ - capitalizeName: function (string) { - if (typeof string === "string" && string.toUpperCase() === string) { - string = Utilities.XRegExp.replace( - string.toLowerCase(), - Utilities.XRegExp('(^|[^\\pL])\\pL', 'g'), - m => m.toUpperCase() - ); - } - return string; - }, - - /** - * Cleans extraneous punctuation off a creator name and parse into first and last name - * - * @param {String} author Creator string - * @param {String} type Creator type string (e.g., "author" or "editor") - * @param {Boolean} useComma Whether the creator string is in inverted (Last, First) format - * @return {Object} firstName, lastName, and creatorType - */ - cleanAuthor: function(author, type, useComma) { - var allCaps = 'A-Z' + - '\u0400-\u042f'; //cyrilic - - var allCapsRe = new RegExp('^[' + allCaps + ']+$'); - var initialRe = new RegExp('^-?[' + allCaps + ']$'); - - if(typeof(author) != "string") { - throw new Error("cleanAuthor: author must be a string"); - } - - author = author.replace(/^[\s\u00A0\.\,\/\[\]\:]+/, '') - .replace(/[\s\u00A0\.\,\/\[\]\:]+$/, '') - .replace(/[\s\u00A0]+/, ' '); - - if(useComma) { - // Add spaces between periods - author = author.replace(/\.([^ ])/, ". $1"); - - var splitNames = author.split(/, ?/); - if(splitNames.length > 1) { - var lastName = splitNames[0]; - var firstName = splitNames[1]; - } else { - var lastName = author; - } - } else { - // Don't parse "Firstname Lastname [Country]" as "[Country], Firstname Lastname" - var spaceIndex = author.length; - do { - spaceIndex = author.lastIndexOf(" ", spaceIndex-1); - var lastName = author.substring(spaceIndex + 1); - var firstName = author.substring(0, spaceIndex); - } while (!Utilities.XRegExp('\\pL').test(lastName[0]) && spaceIndex > 0) - } - - if(firstName && allCapsRe.test(firstName) && - firstName.length < 4 && - (firstName.length == 1 || lastName.toUpperCase() != lastName)) { - // first name is probably initials - var newFirstName = ""; - for(var i=0; i]*>/gi, "\n"); - x = x.replace(/<\/p>/gi, "\n\n"); - return x.replace(/<[^>]+>/g, ""); - }, - - /** - * Strip info:doi prefix and any suffixes from a DOI - * @type String - */ - cleanDOI: function(/**String**/ x) { - if(typeof(x) != "string") { - throw new Error("cleanDOI: argument must be a string"); - } - - var doi = x.match(/10(?:\.[0-9]{4,})?\/[^\s]*[^\s\.,]/); - return doi ? doi[0] : null; - }, - - /** - * Clean and validate ISBN. - * Return isbn if valid, otherwise return false - * @param {String} isbn - * @param {Boolean} [dontValidate=false] Do not validate check digit - * @return {String|Boolean} Valid ISBN or false - */ - cleanISBN: function(isbnStr, dontValidate) { - isbnStr = isbnStr.toUpperCase() - .replace(/[\x2D\xAD\u2010-\u2015\u2043\u2212]+/g, ''); // Ignore dashes - var isbnRE = /\b(?:97[89]\s*(?:\d\s*){9}\d|(?:\d\s*){9}[\dX])\b/g, - isbnMatch; - while(isbnMatch = isbnRE.exec(isbnStr)) { - var isbn = isbnMatch[0].replace(/\s+/g, ''); - - if (dontValidate) { - return isbn; - } - - if(isbn.length == 10) { - // Verify ISBN-10 checksum - var sum = 0; - for (var i = 0; i < 9; i++) { - sum += isbn[i] * (10-i); - } - //check digit might be 'X' - sum += (isbn[9] == 'X')? 10 : isbn[9]*1; - - if (sum % 11 == 0) return isbn; - } else { - // Verify ISBN 13 checksum - var sum = 0; - for (var i = 0; i < 12; i+=2) sum += isbn[i]*1; //to make sure it's int - for (var i = 1; i < 12; i+=2) sum += isbn[i]*3; - sum += isbn[12]*1; //add the check digit - - if (sum % 10 == 0 ) return isbn; - } - - isbnRE.lastIndex = isbnMatch.index + 1; // Retry the same spot + 1 - } - - return false; - }, - - /* - * Convert ISBN 10 to ISBN 13 - * @param {String} isbn ISBN 10 or ISBN 13 - * cleanISBN - * @return {String} ISBN-13 - */ - toISBN13: function(isbnStr) { - var isbn; - if (!(isbn = Utilities.cleanISBN(isbnStr, true))) { - throw new Error('ISBN not found in "' + isbnStr + '"'); - } - - if (isbn.length == 13) { - isbn = isbn.substr(0,12); // Strip off check digit and re-calculate it - } else { - isbn = '978' + isbn.substr(0,9); - } - - var sum = 0; - for (var i = 0; i < 12; i++) { - sum += isbn[i] * (i%2 ? 3 : 1); - } - - var checkDigit = 10 - (sum % 10); - if (checkDigit == 10) checkDigit = 0; - - return isbn + checkDigit; - }, - - /** - * Clean and validate ISSN. - * Return issn if valid, otherwise return false - */ - cleanISSN: function(/**String*/ issnStr) { - issnStr = issnStr.toUpperCase() - .replace(/[\x2D\xAD\u2010-\u2015\u2043\u2212]+/g, ''); // Ignore dashes - var issnRE = /\b(?:\d\s*){7}[\dX]\b/g, - issnMatch; - while (issnMatch = issnRE.exec(issnStr)) { - var issn = issnMatch[0].replace(/\s+/g, ''); - - // Verify ISSN checksum - var sum = 0; - for (var i = 0; i < 7; i++) { - sum += issn[i] * (8-i); - } - //check digit might be 'X' - sum += (issn[7] == 'X')? 10 : issn[7]*1; - - if (sum % 11 == 0) { - return issn.substring(0,4) + '-' + issn.substring(4); - } - - issnRE.lastIndex = issnMatch.index + 1; // Retry same spot + 1 - } - - return false; - }, - - /** - * Convert plain text to HTML by replacing special characters and replacing newlines with BRs or - * P tags - * @param {String} str Plain text string - * @param {Boolean} singleNewlineIsParagraph Whether single newlines should be considered as - * paragraphs. If true, each newline is replaced with a P tag. If false, double newlines - * are replaced with P tags, while single newlines are replaced with BR tags. - * @type String - */ - text2html: function (/**String**/ str, /**Boolean**/ singleNewlineIsParagraph) { - str = Utilities.htmlSpecialChars(str); - - // \n =>

- if (singleNewlineIsParagraph) { - str = '

' - + str.replace(/\n/g, '

') - .replace(/ /g, '  ') - + '

'; - } - // \n\n =>

, \n =>
- else { - str = '

' - + str.replace(/\n\n/g, '

') - .replace(/\n/g, '
') - .replace(/ /g, '  ') - + '

'; - } - return str.replace(/

\s*<\/p>/g, '

 

'); - }, - - /** - * Encode special XML/HTML characters - * Certain entities can be inserted manually: - * =>
- * => … - * - * @param {String} str - * @return {String} - */ - htmlSpecialChars: function(str) { - if (str && typeof str != 'string') { - Zotero.debug('#htmlSpecialChars: non-string arguments are deprecated. Update your code', - 1, undefined, true); - str = str.toString(); - } - - if (!str) return ''; - - return str - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/'/g, ''') - .replace(//g, '>') - .replace(/<ZOTERO([^\/]+)\/>/g, function (str, p1, offset, s) { - switch (p1) { - case 'BREAK': - return '
'; - case 'HELLIP': - return '…'; - default: - return p1; - } - }); - }, - - /** - * Decodes HTML entities within a string, returning plain text - * @type String - */ - "unescapeHTML":new function() { - var nsIScriptableUnescapeHTML, node; - - return function(/**String*/ str) { - // If no tags, no need to unescape - if(str.indexOf("<") === -1 && str.indexOf("&") === -1) return str; - - if(Zotero.isFx && !Zotero.isBookmarklet) { - // Create a node and use the textContent property to do unescaping where - // possible, because this approach preserves line endings in the HTML - if(node === undefined) { - node = Utilities.Internal.getDOMDocument().createElement("div"); - } - - node.innerHTML = str; - return node.textContent.replace(/ {2,}/g, " "); - } else if(Zotero.isNode) { - let {JSDOM} = require('jsdom'); - let document = (new JSDOM(str)).window.document; - return document.documentElement.textContent.replace(/ {2,}/g, " "); - } else { - if(!node) node = document.createElement("div"); - node.innerHTML = str; - return ("textContent" in node ? node.textContent : node.innerText).replace(/ {2,}/g, " "); - } - }; - }, - - /** - * Wrap URLs and DOIs in links in plain text - * - * Ignore URLs preceded by '>', just in case there are already links - * @type String - */ - autoLink: function (/**String**/ str) { - // "http://www.google.com." - // "http://www.google.com. " - // "" (and other characters, with or without a space after) - str = str.replace(/([^>])(https?:\/\/[^\s]+)([\."'>:\]\)](\s|$))/g, '$1$2$3'); - // "http://www.google.com" - // "http://www.google.com " - str = str.replace(/([^">])(https?:\/\/[^\s]+)(\s|$)/g, '$1$2$3'); - - // DOI - str = str.replace(/(doi:[ ]*)(10\.[^\s]+[0-9a-zA-Z])/g, '$1$2'); - return str; - }, - - /** - * Parses a text string for HTML/XUL markup and returns an array of parts. Currently only finds - * HTML links (<a> tags) - * - * @return {Array} An array of objects with the following form:
- *
   {
-	 *         type: 'text'|'link',
-	 *         text: "text content",
-	 *         [ attributes: { key1: val [ , key2: val, ...] }
-	 *    }
- */ - parseMarkup: function(/**String*/ str) { - var parts = []; - var splits = str.split(/(]+>[^<]*<\/a>)/); - - for(var i=0; i]+)>([^<]*)<\/a>/); - if (matches) { - // Attribute pairs - var attributes = {}; - var pairs = matches[1].match(/([^ =]+)="([^"]+")/g); - for(var j=0; j 3 ? j % 3 : 0; - - return s + (j ? i.substr(0, j) + t : "") + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + t) + (c ? d + Math.abs(n - i).toFixed(c).slice(2) : ""); - }, - - /** - * Cleans a title, converting it to title case and replacing " :" with ":" - * - * @param {String} string - * @param {Boolean} force Forces title case conversion, even if the capitalizeTitles pref is off - * @type String - */ - capitalizeTitle: function(string, force) { - const skipWords = ["but", "or", "yet", "so", "for", "and", "nor", "a", "an", - "the", "at", "by", "from", "in", "into", "of", "on", "to", "with", "up", - "down", "as"]; - - // this may only match a single character - const delimiterRegexp = /([ \/\u002D\u00AD\u2010-\u2015\u2212\u2E3A\u2E3B])/; - - string = this.trimInternal(string); - string = string.replace(/ : /g, ": "); - if (Zotero.Prefs && !Zotero.Prefs.get('capitalizeTitles') && !force) return string; - if (!string) return ""; - - // split words - var words = string.split(delimiterRegexp); - var isUpperCase = string.toUpperCase() == string; - - var newString = ""; - var delimiterOffset = words[0].length; - var lastWordIndex = words.length-1; - var previousWordIndex = -1; - for(var i=0; i<=lastWordIndex; i++) { - // only do manipulation if not a delimiter character - if(words[i].length != 0 && (words[i].length != 1 || !delimiterRegexp.test(words[i]))) { - var upperCaseVariant = words[i].toUpperCase(); - var lowerCaseVariant = words[i].toLowerCase(); - - // only use if word does not already possess some capitalization - if(isUpperCase || words[i] == lowerCaseVariant) { - if( - // a skip word - skipWords.indexOf(lowerCaseVariant.replace(/[^a-zA-Z]+/, "")) != -1 - // not first or last word - && i != 0 && i != lastWordIndex - // does not follow a colon - && (previousWordIndex == -1 || words[previousWordIndex][words[previousWordIndex].length-1].search(/[:\?!]/)==-1) - ) { - words[i] = lowerCaseVariant; - } else { - // this is not a skip word or comes after a colon; - // we must capitalize - // handle punctuation in the beginning, including multiple, as in "¿Qué pasa?" - var punct = words[i].match(/^[\'\"¡¿“‘„«\s]+/); - punct = punct ? punct[0].length+1 : 1; - words[i] = words[i].length ? words[i].substr(0, punct).toUpperCase() + - words[i].substr(punct).toLowerCase() : words[i]; - } - } - - previousWordIndex = i; - } - - newString += words[i]; - } - - return newString; - }, - - capitalize: function (str) { - if (typeof str != 'string') throw new Error("Argument must be a string"); - if (!str) return str; // Empty string - return str[0].toUpperCase() + str.substr(1); - }, - - /** - * Replaces accented characters in a string with ASCII equivalents - * - * @param {String} str - * @param {Boolean} [lowercaseOnly] Limit conversions to lowercase characters - * (for improved performance on lowercase input) - * @return {String} - * - * From http://lehelk.com/2011/05/06/script-to-remove-diacritics/ - */ - removeDiacritics: function (str, lowercaseOnly) { - // Short-circuit on the most basic input - if (/^[a-zA-Z0-9_-]*$/.test(str)) return str; - - var map = this._diacriticsRemovalMap.lowercase; - for (var i=0, len=map.length; i' + obj + '<=== (' + type + ')'; - } - else { - return '' + obj; - } - } - else if (type == 'string' || typeof obj.toJSON == 'function') { - return JSON.stringify(obj, false, ' '); - } - else if (type == 'function') { - var funcStr = ('' + obj).trim(); - if (!level) { - // Dump function contents as well if only dumping function - return funcStr; - } - - // Display [native code] label for native functions, but make it one line - if (/^[^{]+{\s*\[native code\]\s*}$/i.test(funcStr)) { - return funcStr.replace(/\s*(\[native code\])\s*/i, ' $1 '); - } - - // For non-native functions, display an elipsis - return ('' + obj).replace(/{[\s\S]*}/, '{...}'); - } - else if (type != 'object') { - return '<> ' + obj; - } - - // Don't descend into global object cache for data objects - if (Zotero.isClient && typeof obj == 'object' && obj instanceof Zotero.DataObject) { - maxLevel = 1; - } - - // More complex dump with indentation for objects - if (level === undefined) { - level = 0; - } - - if (maxLevel === undefined) { - maxLevel = 5; - } - - var objType = Object.prototype.toString.call(obj); - - if (level > maxLevel) { - return objType + " <>"; - } - - // The padding given at the beginning of the line. - var level_padding = ""; - for (var j=0; j'); - dumpedText += "<>"; - continue; - } - } - - try { - dumpedText += Utilities.varDump(value,level+1,maxLevel,parentObjects.concat([value]),path.concat([prop])); - } catch(e) { - dumpedText += "<>"; - } - } - - var lastChar = dumpedText.charAt(dumpedText.length - 1); - if (lastChar != '[' && lastChar != '{') { - dumpedText += '\n' + level_padding.substr(4); - } - dumpedText += isArray ? ']' : '}'; - - return dumpedText; - }, - - /** - * Converts Zotero.Item to a format expected by translators - * This is mostly the Zotero web API item JSON format, but with an attachments - * and notes arrays and optional compatibility mappings for older translators. - * - * @param {Zotero.Item} zoteroItem - * @param {Boolean} legacy Add mappings for legacy (pre-4.0.27) translators - * @return {Object} - */ - itemToExportFormat: function (zoteroItem, legacy, skipChildItems) { - function addCompatibilityMappings(item, zoteroItem) { - item.uniqueFields = {}; - - // Meaningless local item ID, but some older export translators depend on it - item.itemID = zoteroItem.id; - item.key = zoteroItem.key; // CSV translator exports this - - // "version" is expected to be a field for "computerProgram", which is now - // called "versionNumber" - delete item.version; - if (item.versionNumber) { - item.version = item.uniqueFields.version = item.versionNumber; - delete item.versionNumber; - } - - // SQL instead of ISO-8601 - item.dateAdded = zoteroItem.dateAdded; - item.dateModified = zoteroItem.dateModified; - if (item.accessDate) { - item.accessDate = zoteroItem.getField('accessDate'); - } - - // Map base fields - for (let field in item) { - let id = Zotero.ItemFields.getID(field); - if (!id || !Zotero.ItemFields.isValidForType(id, zoteroItem.itemTypeID)) { - continue; - } - - let baseField = Zotero.ItemFields.getName( - Zotero.ItemFields.getBaseIDFromTypeAndField(item.itemType, field) - ); - - if (!baseField || baseField == field) { - item.uniqueFields[field] = item[field]; - } else { - item[baseField] = item[field]; - item.uniqueFields[baseField] = item[field]; - } - } - - // Add various fields for compatibility with translators pre-4.0.27 - item.itemID = zoteroItem.id; - item.libraryID = zoteroItem.libraryID == 1 ? null : zoteroItem.libraryID; - - // Creators - if (item.creators) { - for (let i=0; i} A CSL item, or a promise for a CSL item if a Zotero.Item - * is passed - */ - itemToCSLJSON: function(zoteroItem) { - // If a Zotero.Item was passed, convert it to the proper format (skipping child items) and - // call this function again with that object - // - // (Zotero.Item won't be defined in translation-server) - if (typeof Zotero.Item !== 'undefined' && zoteroItem instanceof Zotero.Item) { - return this.itemToCSLJSON( - Utilities.Internal.itemToExportFormat(zoteroItem, false, true) - ); - } - - var cslType = Zotero.Schema.CSL_TYPE_MAPPINGS[zoteroItem.itemType]; - if (!cslType) { - throw new Error('Unexpected Zotero Item type "' + zoteroItem.itemType + '"'); - } - - var itemTypeID = Zotero.ItemTypes.getID(zoteroItem.itemType); - - var cslItem = { - 'id':zoteroItem.uri, - 'type':cslType - }; - - // get all text variables (there must be a better way) - for(var variable in Zotero.Schema.CSL_TEXT_MAPPINGS) { - if (variable === "shortTitle") continue; // read both title-short and shortTitle, but write only title-short - var fields = Zotero.Schema.CSL_TEXT_MAPPINGS[variable]; - for(var i=0, n=fields.length; i 1 - && nameObj.family.charAt(0) == '"' - && nameObj.family.charAt(nameObj.family.length - 1) == '"' - ) { - nameObj.family = nameObj.family.substr(1, nameObj.family.length - 2); - } else { - Zotero.CiteProc.CSL.parseParticles(nameObj, true); - } - } - } else if (creator.name) { - nameObj = {'literal': creator.name}; - } - - if(cslItem[creatorType]) { - cslItem[creatorType].push(nameObj); - } else { - cslItem[creatorType] = [nameObj]; - } - } - } - - // get date variables - for(var variable in Zotero.Schema.CSL_DATE_MAPPINGS) { - var date = zoteroItem[Zotero.Schema.CSL_DATE_MAPPINGS[variable]]; - if (!date) { - var typeSpecificFieldID = Zotero.ItemFields.getFieldIDFromTypeAndBase(itemTypeID, Zotero.Schema.CSL_DATE_MAPPINGS[variable]); - if (typeSpecificFieldID) { - date = zoteroItem[Zotero.ItemFields.getName(typeSpecificFieldID)]; - } - } - - if(date) { - // Convert UTC timestamp to local timestamp for access date - if (Zotero.Schema.CSL_DATE_MAPPINGS[variable] == 'accessDate' && !Zotero.Date.isSQLDate(date)) { - // Accept ISO date - if (Zotero.Date.isISODate(date)) { - let d = Zotero.Date.isoToDate(date); - date = Zotero.Date.dateToSQL(d, true); - } - let localDate = Zotero.Date.sqlToDate(date, true); - date = Zotero.Date.dateToSQL(localDate); - } - var dateObj = Zotero.Date.strToDate(date); - // otherwise, use date-parts - var dateParts = []; - if(dateObj.year) { - // add year, month, and day, if they exist - dateParts.push(dateObj.year); - if(dateObj.month !== undefined) { - // strToDate() returns a JS-style 0-indexed month, so we add 1 to it - dateParts.push(dateObj.month+1); - if(dateObj.day) { - dateParts.push(dateObj.day); - } - } - cslItem[variable] = {"date-parts":[dateParts]}; - - // if no month, use season as month - if(dateObj.part && dateObj.month === undefined) { - cslItem[variable].season = dateObj.part; - } - } else { - // if no year, pass date literally - cslItem[variable] = {"literal":date}; - } - } - } - - // Special mapping for note title - if (zoteroItem.itemType == 'note' && zoteroItem.note) { - cslItem.title = Zotero.Notes.noteToTitle(zoteroItem.note); - } - - //this._cache[zoteroItem.id] = cslItem; - return cslItem; - }, - - /** - * Converts an item in CSL JSON format to a Zotero item - * @param {Zotero.Item} item - * @param {Object} cslItem - */ - itemFromCSLJSON: function(item, cslItem) { - var isZoteroItem = !!item.setType, - zoteroType; - - if (!cslItem.type) { - Zotero.debug(cslItem, 1); - throw new Error("No 'type' provided in CSL-JSON"); - } - - // Some special cases to help us map item types correctly - // This ensures that we don't lose data on import. The fields - // we check are incompatible with the alternative item types - if (cslItem.type == 'bill' && (cslItem.publisher || cslItem['number-of-volumes'])) { - zoteroType = 'hearing'; - } - else if (cslItem.type == 'broadcast' - && (cslItem['archive'] - || cslItem['archive_location'] - || cslItem['container-title'] - || cslItem['event-place'] - || cslItem['publisher'] - || cslItem['publisher-place'] - || cslItem['source'])) { - zoteroType = 'tvBroadcast'; - } - else if (cslItem.type == 'book' && cslItem.version) { - zoteroType = 'computerProgram'; - } - else if (cslItem.type == 'song' && cslItem.number) { - zoteroType = 'podcast'; - } - else if (cslItem.type == 'motion_picture' - && (cslItem['collection-title'] || cslItem['publisher-place'] - || cslItem['event-place'] || cslItem.volume - || cslItem['number-of-volumes'] || cslItem.ISBN)) { - zoteroType = 'videoRecording'; - } - else if (Zotero.Schema.CSL_TYPE_MAPPINGS_REVERSE[cslItem.type]) { - zoteroType = Zotero.Schema.CSL_TYPE_MAPPINGS_REVERSE[cslItem.type][0]; - } - else { - Zotero.debug(`Unknown CSL type '${cslItem.type}' -- using 'document'`, 2); - zoteroType = "document" - } - - var itemTypeID = Zotero.ItemTypes.getID(zoteroType); - if(isZoteroItem) { - item.setType(itemTypeID); - } else { - item.itemID = cslItem.id; - item.itemType = zoteroType; - } - - // map text fields - for (let variable in Zotero.Schema.CSL_TEXT_MAPPINGS) { - if(variable in cslItem) { - let textMappings = Zotero.Schema.CSL_TEXT_MAPPINGS[variable]; - for(var i=0; i. - - - Utilities based in part on code taken from Piggy Bank 2.1.1 (BSD-licensed) - - ***** END LICENSE BLOCK ***** -*/ - -/** - * @class All functions accessible from within Zotero.Utilities namespace inside sandboxed - * translators - * - * @constructor - * @augments Zotero.Utilities - * @borrows Zotero.Date.formatDate as this.formatDate - * @borrows Zotero.Date.strToDate as this.strToDate - * @borrows Zotero.Date.strToISO as this.strToISO - * @borrows Zotero.OpenURL.createContextObject as this.createContextObject - * @borrows Zotero.OpenURL.parseContextObject as this.parseContextObject - * @borrows Zotero.HTTP.processDocuments as this.processDocuments - * @borrows Zotero.HTTP.doPost as this.doPost - * @param {Zotero.Translate} translate - */ -Zotero.Utilities.Translate = function(translate) { - this._translate = translate; -} - -var tmp = function() {}; -tmp.prototype = Zotero.Utilities; -Zotero.Utilities.Translate.prototype = new tmp(); - -Zotero.Utilities.Translate.prototype.formatDate = Zotero.Date.formatDate; -Zotero.Utilities.Translate.prototype.strToDate = Zotero.Date.strToDate; -Zotero.Utilities.Translate.prototype.strToISO = Zotero.Date.strToISO; -Zotero.Utilities.Translate.prototype.createContextObject = Zotero.OpenURL.createContextObject; -Zotero.Utilities.Translate.prototype.parseContextObject = Zotero.OpenURL.parseContextObject; - -/** - * Hack to overloads {@link Zotero.Utilities.capitalizeTitle} to allow overriding capitalizeTitles - * pref on a per-translate instance basis (for translator testing only) - */ -Zotero.Utilities.Translate.prototype.capitalizeTitle = function(string, force) { - if(force === undefined) { - var translate = this._translate; - do { - if(translate.capitalizeTitles !== undefined) { - force = translate.capitalizeTitles; - break; - } - } while(translate = translate._parentTranslator); - } - - return Zotero.Utilities.capitalizeTitle(string, force); -} - -/** - * Gets the current Zotero version - * - * @type String - */ -Zotero.Utilities.Translate.prototype.getVersion = function() { - return Zotero.version; -} - -/** - * Takes an XPath query and returns the results - * - * @deprecated Use {@link Zotero.Utilities.xpath} or doc.evaluate() directly - * @type Node[] - */ -Zotero.Utilities.Translate.prototype.gatherElementsOnXPath = function(doc, parentNode, xpath, nsResolver) { - var elmts = []; - - var iterator = doc.evaluate(xpath, parentNode, nsResolver, XPathResult.ANY_TYPE, null); - var elmt = iterator.iterateNext(); - var i = 0; - while (elmt) { - elmts[i++] = elmt; - elmt = iterator.iterateNext(); - } - return elmts; -} - -/** - * Gets a given node as a string containing all child nodes - * - * @deprecated Use doc.evaluate and the "nodeValue" or "textContent" property - * @type String - */ -Zotero.Utilities.Translate.prototype.getNodeString = function(doc, contextNode, xpath, nsResolver) { - var elmts = this.gatherElementsOnXPath(doc, contextNode, xpath, nsResolver); - var returnVar = ""; - for(var i=0; i textContent pairs, suitable for passing to - * Zotero.selectItems from within a translator - */ -Zotero.Utilities.Translate.prototype.getItemArray = function(doc, inHere, urlRe, rejectRe) { - var availableItems = new Object(); // Technically, associative arrays are objects - - // Require link to match this - if(urlRe) { - if(urlRe.exec) { - var urlRegexp = urlRe; - } else { - var urlRegexp = new RegExp(); - urlRegexp.compile(urlRe, "i"); - } - } - // Do not allow text to match this - if(rejectRe) { - if(rejectRe.exec) { - var rejectRegexp = rejectRe; - } else { - var rejectRegexp = new RegExp(); - rejectRegexp.compile(rejectRe, "i"); - } - } - - if(!inHere.length) { - inHere = new Array(inHere); - } - - for(var j=0; j
  • `astral` - * @returns {Boolean} Whether the feature is installed. - * @example - * - * XRegExp.isInstalled('natives'); - */ - self.isInstalled = function(feature) { - return !!(features[feature]); - }; - -/** - * Returns `true` if an object is a regex; `false` if it isn't. This works correctly for regexes - * created in another frame, when `instanceof` and `constructor` checks would fail. - * @memberOf XRegExp - * @param {*} value Object to check. - * @returns {Boolean} Whether the object is a `RegExp` object. - * @example - * - * XRegExp.isRegExp('string'); // -> false - * XRegExp.isRegExp(/regex/i); // -> true - * XRegExp.isRegExp(RegExp('^', 'm')); // -> true - * XRegExp.isRegExp(XRegExp('(?s).')); // -> true - */ - self.isRegExp = function(value) { - return toString.call(value) === '[object RegExp]'; - //return isType(value, 'RegExp'); - }; - -/** - * Returns the first matched string, or in global mode, an array containing all matched strings. - * This is essentially a more convenient re-implementation of `String.prototype.match` that gives - * the result types you actually want (string instead of `exec`-style array in match-first mode, - * and an empty array instead of `null` when no matches are found in match-all mode). It also lets - * you override flag g and ignore `lastIndex`, and fixes browser bugs. - * @memberOf XRegExp - * @param {String} str String to search. - * @param {RegExp} regex Regex to search with. - * @param {String} [scope='one'] Use 'one' to return the first match as a string. Use 'all' to - * return an array of all matched strings. If not explicitly specified and `regex` uses flag g, - * `scope` is 'all'. - * @returns {String|Array} In match-first mode: First match as a string, or `null`. In match-all - * mode: Array of all matched strings, or an empty array. - * @example - * - * // Match first - * XRegExp.match('abc', /\w/); // -> 'a' - * XRegExp.match('abc', /\w/g, 'one'); // -> 'a' - * XRegExp.match('abc', /x/g, 'one'); // -> null - * - * // Match all - * XRegExp.match('abc', /\w/g); // -> ['a', 'b', 'c'] - * XRegExp.match('abc', /\w/, 'all'); // -> ['a', 'b', 'c'] - * XRegExp.match('abc', /x/, 'all'); // -> [] - */ - self.match = function(str, regex, scope) { - var global = (regex.global && scope !== 'one') || scope === 'all', - cacheFlags = (global ? 'g' : '') + (regex.sticky ? 'y' : ''), - result, - r2; - - regex[REGEX_DATA] = regex[REGEX_DATA] || getBaseProps(); - - // Shares cached copies with `XRegExp.exec`/`replace` - r2 = regex[REGEX_DATA][cacheFlags || 'noGY'] || ( - regex[REGEX_DATA][cacheFlags || 'noGY'] = copy(regex, { - add: cacheFlags, - remove: scope === 'one' ? 'g' : '' - }) - ); - - result = nativ.match.call(toObject(str), r2); - - if (regex.global) { - regex.lastIndex = ( - (scope === 'one' && result) ? - // Can't use `r2.lastIndex` since `r2` is nonglobal in this case - (result.index + result[0].length) : 0 - ); - } - - return global ? (result || []) : (result && result[0]); - }; - -/** - * Retrieves the matches from searching a string using a chain of regexes that successively search - * within previous matches. The provided `chain` array can contain regexes and objects with `regex` - * and `backref` properties. When a backreference is specified, the named or numbered backreference - * is passed forward to the next regex or returned. - * @memberOf XRegExp - * @param {String} str String to search. - * @param {Array} chain Regexes that each search for matches within preceding results. - * @returns {Array} Matches by the last regex in the chain, or an empty array. - * @example - * - * // Basic usage; matches numbers within tags - * XRegExp.matchChain('1 2 3 4 a 56', [ - * XRegExp('(?is).*?'), - * /\d+/ - * ]); - * // -> ['2', '4', '56'] - * - * // Passing forward and returning specific backreferences - * html = 'XRegExp\ - * Google'; - * XRegExp.matchChain(html, [ - * {regex: //i, backref: 1}, - * {regex: XRegExp('(?i)^https?://(?[^/?#]+)'), backref: 'domain'} - * ]); - * // -> ['xregexp.com', 'www.google.com'] - */ - self.matchChain = function(str, chain) { - return (function recurseChain(values, level) { - var item = chain[level].regex ? chain[level] : {regex: chain[level]}, - matches = [], - addMatch = function(match) { - if (item.backref) { - /* Safari 4.0.5 (but not 5.0.5+) inappropriately uses sparse arrays to hold - * the `undefined`s for backreferences to nonparticipating capturing - * groups. In such cases, a `hasOwnProperty` or `in` check on its own would - * inappropriately throw the exception, so also check if the backreference - * is a number that is within the bounds of the array. - */ - if (!(match.hasOwnProperty(item.backref) || +item.backref < match.length)) { - throw new ReferenceError('Backreference to undefined group: ' + item.backref); - } - - matches.push(match[item.backref] || ''); - } else { - matches.push(match[0]); - } - }, - i; - - for (i = 0; i < values.length; ++i) { - self.forEach(values[i], item.regex, addMatch); - } - - return ((level === chain.length - 1) || !matches.length) ? - matches : - recurseChain(matches, level + 1); - }([str], 0)); - }; - -/** - * Returns a new string with one or all matches of a pattern replaced. The pattern can be a string - * or regex, and the replacement can be a string or a function to be called for each match. To - * perform a global search and replace, use the optional `scope` argument or include flag g if - * using a regex. Replacement strings can use `${n}` for named and numbered backreferences. - * Replacement functions can use named backreferences via `arguments[0].name`. Also fixes browser - * bugs compared to the native `String.prototype.replace` and can be used reliably cross-browser. - * @memberOf XRegExp - * @param {String} str String to search. - * @param {RegExp|String} search Search pattern to be replaced. - * @param {String|Function} replacement Replacement string or a function invoked to create it. - * Replacement strings can include special replacement syntax: - *
  • $$ - Inserts a literal $ character. - *
  • $&, $0 - Inserts the matched substring. - *
  • $` - Inserts the string that precedes the matched substring (left context). - *
  • $' - Inserts the string that follows the matched substring (right context). - *
  • $n, $nn - Where n/nn are digits referencing an existent capturing group, inserts - * backreference n/nn. - *
  • ${n} - Where n is a name or any number of digits that reference an existent capturing - * group, inserts backreference n. - * Replacement functions are invoked with three or more arguments: - *
  • The matched substring (corresponds to $& above). Named backreferences are accessible as - * properties of this first argument. - *
  • 0..n arguments, one for each backreference (corresponding to $1, $2, etc. above). - *
  • The zero-based index of the match within the total search string. - *
  • The total string being searched. - * @param {String} [scope='one'] Use 'one' to replace the first match only, or 'all'. If not - * explicitly specified and using a regex with flag g, `scope` is 'all'. - * @returns {String} New string with one or all matches replaced. - * @example - * - * // Regex search, using named backreferences in replacement string - * var name = XRegExp('(?\\w+) (?\\w+)'); - * XRegExp.replace('John Smith', name, '${last}, ${first}'); - * // -> 'Smith, John' - * - * // Regex search, using named backreferences in replacement function - * XRegExp.replace('John Smith', name, function(match) { - * return match.last + ', ' + match.first; - * }); - * // -> 'Smith, John' - * - * // String search, with replace-all - * XRegExp.replace('RegExp builds RegExps', 'RegExp', 'XRegExp', 'all'); - * // -> 'XRegExp builds XRegExps' - */ - self.replace = function(str, search, replacement, scope) { - var isRegex = self.isRegExp(search), - global = (search.global && scope !== 'one') || scope === 'all', - cacheFlags = (global ? 'g' : '') + (search.sticky ? 'y' : ''), - s2 = search, - result; - - if (isRegex) { - search[REGEX_DATA] = search[REGEX_DATA] || getBaseProps(); - - // Shares cached copies with `XRegExp.exec`/`match`. Since a copy is used, - // `search`'s `lastIndex` isn't updated *during* replacement iterations - s2 = search[REGEX_DATA][cacheFlags || 'noGY'] || ( - search[REGEX_DATA][cacheFlags || 'noGY'] = copy(search, { - add: cacheFlags, - remove: scope === 'one' ? 'g' : '' - }) - ); - } else if (global) { - s2 = new RegExp(self.escape(String(search)), 'g'); - } - - // Fixed `replace` required for named backreferences, etc. - result = fixed.replace.call(toObject(str), s2, replacement); - - if (isRegex && search.global) { - // Fixes IE, Safari bug (last tested IE 9, Safari 5.1) - search.lastIndex = 0; - } - - return result; - }; - -/** - * Performs batch processing of string replacements. Used like {@link #XRegExp.replace}, but - * accepts an array of replacement details. Later replacements operate on the output of earlier - * replacements. Replacement details are accepted as an array with a regex or string to search for, - * the replacement string or function, and an optional scope of 'one' or 'all'. Uses the XRegExp - * replacement text syntax, which supports named backreference properties via `${name}`. - * @memberOf XRegExp - * @param {String} str String to search. - * @param {Array} replacements Array of replacement detail arrays. - * @returns {String} New string with all replacements. - * @example - * - * str = XRegExp.replaceEach(str, [ - * [XRegExp('(?a)'), 'z${name}'], - * [/b/gi, 'y'], - * [/c/g, 'x', 'one'], // scope 'one' overrides /g - * [/d/, 'w', 'all'], // scope 'all' overrides lack of /g - * ['e', 'v', 'all'], // scope 'all' allows replace-all for strings - * [/f/g, function($0) { - * return $0.toUpperCase(); - * }] - * ]); - */ - self.replaceEach = function(str, replacements) { - var i, r; - - for (i = 0; i < replacements.length; ++i) { - r = replacements[i]; - str = self.replace(str, r[0], r[1], r[2]); - } - - return str; - }; - -/** - * Splits a string into an array of strings using a regex or string separator. Matches of the - * separator are not included in the result array. However, if `separator` is a regex that contains - * capturing groups, backreferences are spliced into the result each time `separator` is matched. - * Fixes browser bugs compared to the native `String.prototype.split` and can be used reliably - * cross-browser. - * @memberOf XRegExp - * @param {String} str String to split. - * @param {RegExp|String} separator Regex or string to use for separating the string. - * @param {Number} [limit] Maximum number of items to include in the result array. - * @returns {Array} Array of substrings. - * @example - * - * // Basic use - * XRegExp.split('a b c', ' '); - * // -> ['a', 'b', 'c'] - * - * // With limit - * XRegExp.split('a b c', ' ', 2); - * // -> ['a', 'b'] - * - * // Backreferences in result array - * XRegExp.split('..word1..', /([a-z]+)(\d+)/i); - * // -> ['..', 'word', '1', '..'] - */ - self.split = function(str, separator, limit) { - return fixed.split.call(toObject(str), separator, limit); - }; - -/** - * Executes a regex search in a specified string. Returns `true` or `false`. Optional `pos` and - * `sticky` arguments specify the search start position, and whether the match must start at the - * specified position only. The `lastIndex` property of the provided regex is not used, but is - * updated for compatibility. Also fixes browser bugs compared to the native - * `RegExp.prototype.test` and can be used reliably cross-browser. - * @memberOf XRegExp - * @param {String} str String to search. - * @param {RegExp} regex Regex to search with. - * @param {Number} [pos=0] Zero-based index at which to start the search. - * @param {Boolean|String} [sticky=false] Whether the match must start at the specified position - * only. The string `'sticky'` is accepted as an alternative to `true`. - * @returns {Boolean} Whether the regex matched the provided value. - * @example - * - * // Basic use - * XRegExp.test('abc', /c/); // -> true - * - * // With pos and sticky - * XRegExp.test('abc', /c/, 0, 'sticky'); // -> false - */ - self.test = function(str, regex, pos, sticky) { - // Do this the easy way :-) - return !!self.exec(str, regex, pos, sticky); - }; - -/** - * Uninstalls optional features according to the specified options. All optional features start out - * uninstalled, so this is used to undo the actions of {@link #XRegExp.install}. - * @memberOf XRegExp - * @param {Object|String} options Options object or string. - * @example - * - * // With an options object - * XRegExp.uninstall({ - * // Disables support for astral code points in Unicode addons - * astral: true, - * - * // Restores native regex methods - * natives: true - * }); - * - * // With an options string - * XRegExp.uninstall('astral natives'); - */ - self.uninstall = function(options) { - options = prepareOptions(options); - - if (features.astral && options.astral) { - setAstral(false); - } - - if (features.natives && options.natives) { - setNatives(false); - } - }; - -/** - * Returns an XRegExp object that is the union of the given patterns. Patterns can be provided as - * regex objects or strings. Metacharacters are escaped in patterns provided as strings. - * Backreferences in provided regex objects are automatically renumbered to work correctly within - * the larger combined pattern. Native flags used by provided regexes are ignored in favor of the - * `flags` argument. - * @memberOf XRegExp - * @param {Array} patterns Regexes and strings to combine. - * @param {String} [flags] Any combination of XRegExp flags. - * @returns {RegExp} Union of the provided regexes and strings. - * @example - * - * XRegExp.union(['a+b*c', /(dogs)\1/, /(cats)\1/], 'i'); - * // -> /a\+b\*c|(dogs)\1|(cats)\2/i - */ - self.union = function(patterns, flags) { - var parts = /(\()(?!\?)|\\([1-9]\d*)|\\[\s\S]|\[(?:[^\\\]]|\\[\s\S])*]/g, - output = [], - numCaptures = 0, - numPriorCaptures, - captureNames, - pattern, - rewrite = function(match, paren, backref) { - var name = captureNames[numCaptures - numPriorCaptures]; - - // Capturing group - if (paren) { - ++numCaptures; - // If the current capture has a name, preserve the name - if (name) { - return '(?<' + name + '>'; - } - // Backreference - } else if (backref) { - // Rewrite the backreference - return '\\' + (+backref + numPriorCaptures); - } - - return match; - }, - i; - - if (!(isType(patterns, 'Array') && patterns.length)) { - throw new TypeError('Must provide a nonempty array of patterns to merge'); - } - - for (i = 0; i < patterns.length; ++i) { - pattern = patterns[i]; - - if (self.isRegExp(pattern)) { - numPriorCaptures = numCaptures; - captureNames = (pattern[REGEX_DATA] && pattern[REGEX_DATA].captureNames) || []; - - // Rewrite backreferences. Passing to XRegExp dies on octals and ensures patterns - // are independently valid; helps keep this simple. Named captures are put back - output.push(nativ.replace.call(self(pattern.source).source, parts, rewrite)); - } else { - output.push(self.escape(pattern)); - } - } - - return self(output.join('|'), flags); - }; - -/* ============================== - * Fixed/extended native methods - * ============================== */ - -/** - * Adds named capture support (with backreferences returned as `result.name`), and fixes browser - * bugs in the native `RegExp.prototype.exec`. Calling `XRegExp.install('natives')` uses this to - * override the native method. Use via `XRegExp.exec` without overriding natives. - * @private - * @param {String} str String to search. - * @returns {Array} Match array with named backreference properties, or `null`. - */ - fixed.exec = function(str) { - var origLastIndex = this.lastIndex, - match = nativ.exec.apply(this, arguments), - name, - r2, - i; - - if (match) { - // Fix browsers whose `exec` methods don't return `undefined` for nonparticipating - // capturing groups. This fixes IE 5.5-8, but not IE 9's quirks mode or emulation of - // older IEs. IE 9 in standards mode follows the spec - if (!correctExecNpcg && match.length > 1 && indexOf(match, '') > -1) { - r2 = copy(this, {remove: 'g'}); - // Using `str.slice(match.index)` rather than `match[0]` in case lookahead allowed - // matching due to characters outside the match - nativ.replace.call(String(str).slice(match.index), r2, function() { - var len = arguments.length, i; - // Skip index 0 and the last 2 - for (i = 1; i < len - 2; ++i) { - if (arguments[i] === undefined) { - match[i] = undefined; - } - } - }); - } - - // Attach named capture properties - if (this[REGEX_DATA] && this[REGEX_DATA].captureNames) { - // Skip index 0 - for (i = 1; i < match.length; ++i) { - name = this[REGEX_DATA].captureNames[i - 1]; - if (name) { - match[name] = match[i]; - } - } - } - - // Fix browsers that increment `lastIndex` after zero-length matches - if (this.global && !match[0].length && (this.lastIndex > match.index)) { - this.lastIndex = match.index; - } - } - - if (!this.global) { - // Fixes IE, Opera bug (last tested IE 9, Opera 11.6) - this.lastIndex = origLastIndex; - } - - return match; - }; - -/** - * Fixes browser bugs in the native `RegExp.prototype.test`. Calling `XRegExp.install('natives')` - * uses this to override the native method. - * @private - * @param {String} str String to search. - * @returns {Boolean} Whether the regex matched the provided value. - */ - fixed.test = function(str) { - // Do this the easy way :-) - return !!fixed.exec.call(this, str); - }; - -/** - * Adds named capture support (with backreferences returned as `result.name`), and fixes browser - * bugs in the native `String.prototype.match`. Calling `XRegExp.install('natives')` uses this to - * override the native method. - * @private - * @param {RegExp|*} regex Regex to search with. If not a regex object, it is passed to `RegExp`. - * @returns {Array} If `regex` uses flag g, an array of match strings or `null`. Without flag g, - * the result of calling `regex.exec(this)`. - */ - fixed.match = function(regex) { - var result; - - if (!self.isRegExp(regex)) { - // Use the native `RegExp` rather than `XRegExp` - regex = new RegExp(regex); - } else if (regex.global) { - result = nativ.match.apply(this, arguments); - // Fixes IE bug - regex.lastIndex = 0; - - return result; - } - - return fixed.exec.call(regex, toObject(this)); - }; - -/** - * Adds support for `${n}` tokens for named and numbered backreferences in replacement text, and - * provides named backreferences to replacement functions as `arguments[0].name`. Also fixes - * browser bugs in replacement text syntax when performing a replacement using a nonregex search - * value, and the value of a replacement regex's `lastIndex` property during replacement iterations - * and upon completion. Note that this doesn't support SpiderMonkey's proprietary third (`flags`) - * argument. Calling `XRegExp.install('natives')` uses this to override the native method. Use via - * `XRegExp.replace` without overriding natives. - * @private - * @param {RegExp|String} search Search pattern to be replaced. - * @param {String|Function} replacement Replacement string or a function invoked to create it. - * @returns {String} New string with one or all matches replaced. - */ - fixed.replace = function(search, replacement) { - var isRegex = self.isRegExp(search), - origLastIndex, - captureNames, - result; - - if (isRegex) { - if (search[REGEX_DATA]) { - captureNames = search[REGEX_DATA].captureNames; - } - // Only needed if `search` is nonglobal - origLastIndex = search.lastIndex; - } else { - search += ''; // Type-convert - } - - // Don't use `typeof`; some older browsers return 'function' for regex objects - if (isType(replacement, 'Function')) { - // Stringifying `this` fixes a bug in IE < 9 where the last argument in replacement - // functions isn't type-converted to a string - result = nativ.replace.call(String(this), search, function() { - var args = arguments, i; - if (captureNames) { - // Change the `arguments[0]` string primitive to a `String` object that can - // store properties. This really does need to use `String` as a constructor - args[0] = new String(args[0]); - // Store named backreferences on the first argument - for (i = 0; i < captureNames.length; ++i) { - if (captureNames[i]) { - args[0][captureNames[i]] = args[i + 1]; - } - } - } - // Update `lastIndex` before calling `replacement`. Fixes IE, Chrome, Firefox, - // Safari bug (last tested IE 9, Chrome 17, Firefox 11, Safari 5.1) - if (isRegex && search.global) { - search.lastIndex = args[args.length - 2] + args[0].length; - } - // Should pass `undefined` as context; see - // - return replacement.apply(undefined, args); - }); - } else { - // Ensure that the last value of `args` will be a string when given nonstring `this`, - // while still throwing on `null` or `undefined` context - result = nativ.replace.call(this == null ? this : String(this), search, function() { - // Keep this function's `arguments` available through closure - var args = arguments; - return nativ.replace.call(String(replacement), replacementToken, function($0, $1, $2) { - var n; - // Named or numbered backreference with curly braces - if ($1) { - /* XRegExp behavior for `${n}`: - * 1. Backreference to numbered capture, if `n` is an integer. Use `0` for - * for the entire match. Any number of leading zeros may be used. - * 2. Backreference to named capture `n`, if it exists and is not an - * integer overridden by numbered capture. In practice, this does not - * overlap with numbered capture since XRegExp does not allow named - * capture to use a bare integer as the name. - * 3. If the name or number does not refer to an existing capturing group, - * it's an error. - */ - n = +$1; // Type-convert; drop leading zeros - if (n <= args.length - 3) { - return args[n] || ''; - } - // Groups with the same name is an error, else would need `lastIndexOf` - n = captureNames ? indexOf(captureNames, $1) : -1; - if (n < 0) { - throw new SyntaxError('Backreference to undefined group ' + $0); - } - return args[n + 1] || ''; - } - // Else, special variable or numbered backreference without curly braces - if ($2 === '$') { // $$ - return '$'; - } - if ($2 === '&' || +$2 === 0) { // $&, $0 (not followed by 1-9), $00 - return args[0]; - } - if ($2 === '`') { // $` (left context) - return args[args.length - 1].slice(0, args[args.length - 2]); - } - if ($2 === "'") { // $' (right context) - return args[args.length - 1].slice(args[args.length - 2] + args[0].length); - } - // Else, numbered backreference without curly braces - $2 = +$2; // Type-convert; drop leading zero - /* XRegExp behavior for `$n` and `$nn`: - * - Backrefs end after 1 or 2 digits. Use `${..}` for more digits. - * - `$1` is an error if no capturing groups. - * - `$10` is an error if less than 10 capturing groups. Use `${1}0` instead. - * - `$01` is `$1` if at least one capturing group, else it's an error. - * - `$0` (not followed by 1-9) and `$00` are the entire match. - * Native behavior, for comparison: - * - Backrefs end after 1 or 2 digits. Cannot reference capturing group 100+. - * - `$1` is a literal `$1` if no capturing groups. - * - `$10` is `$1` followed by a literal `0` if less than 10 capturing groups. - * - `$01` is `$1` if at least one capturing group, else it's a literal `$01`. - * - `$0` is a literal `$0`. - */ - if (!isNaN($2)) { - if ($2 > args.length - 3) { - throw new SyntaxError('Backreference to undefined group ' + $0); - } - return args[$2] || ''; - } - throw new SyntaxError('Invalid token ' + $0); - }); - }); - } - - if (isRegex) { - if (search.global) { - // Fixes IE, Safari bug (last tested IE 9, Safari 5.1) - search.lastIndex = 0; - } else { - // Fixes IE, Opera bug (last tested IE 9, Opera 11.6) - search.lastIndex = origLastIndex; - } - } - - return result; - }; - -/** - * Fixes browser bugs in the native `String.prototype.split`. Calling `XRegExp.install('natives')` - * uses this to override the native method. Use via `XRegExp.split` without overriding natives. - * @private - * @param {RegExp|String} separator Regex or string to use for separating the string. - * @param {Number} [limit] Maximum number of items to include in the result array. - * @returns {Array} Array of substrings. - */ - fixed.split = function(separator, limit) { - if (!self.isRegExp(separator)) { - // Browsers handle nonregex split correctly, so use the faster native method - return nativ.split.apply(this, arguments); - } - - var str = String(this), - output = [], - origLastIndex = separator.lastIndex, - lastLastIndex = 0, - lastLength; - - /* Values for `limit`, per the spec: - * If undefined: pow(2,32) - 1 - * If 0, Infinity, or NaN: 0 - * If positive number: limit = floor(limit); if (limit >= pow(2,32)) limit -= pow(2,32); - * If negative number: pow(2,32) - floor(abs(limit)) - * If other: Type-convert, then use the above rules - */ - // This line fails in very strange ways for some values of `limit` in Opera 10.5-10.63, - // unless Opera Dragonfly is open (go figure). It works in at least Opera 9.5-10.1 and 11+ - limit = (limit === undefined ? -1 : limit) >>> 0; - - self.forEach(str, separator, function(match) { - // This condition is not the same as `if (match[0].length)` - if ((match.index + match[0].length) > lastLastIndex) { - output.push(str.slice(lastLastIndex, match.index)); - if (match.length > 1 && match.index < str.length) { - Array.prototype.push.apply(output, match.slice(1)); - } - lastLength = match[0].length; - lastLastIndex = match.index + lastLength; - } - }); - - if (lastLastIndex === str.length) { - if (!nativ.test.call(separator, '') || lastLength) { - output.push(''); - } - } else { - output.push(str.slice(lastLastIndex)); - } - - separator.lastIndex = origLastIndex; - return output.length > limit ? output.slice(0, limit) : output; - }; - -/* ============================== - * Built-in syntax/flag tokens - * ============================== */ - - add = self.addToken; - -/* Letter identity escapes that natively match literal characters: `\a`, `\A`, etc. These should be - * SyntaxErrors but are allowed in web reality. XRegExp makes them errors for cross-browser - * consistency and to reserve their syntax, but lets them be superseded by addons. - */ - add( - /\\([ABCE-RTUVXYZaeg-mopqyz]|c(?![A-Za-z])|u(?![\dA-Fa-f]{4})|x(?![\dA-Fa-f]{2}))/, - function(match, scope) { - // \B is allowed in default scope only - if (match[1] === 'B' && scope === defaultScope) { - return match[0]; - } - throw new SyntaxError('Invalid escape ' + match[0]); - }, - {scope: 'all'} - ); - -/* Empty character class: `[]` or `[^]`. This fixes a critical cross-browser syntax inconsistency. - * Unless this is standardized (per the ES spec), regex syntax can't be accurately parsed because - * character class endings can't be determined. - */ - add( - /\[(\^?)]/, - function(match) { - // For cross-browser compatibility with ES3, convert [] to \b\B and [^] to [\s\S]. - // (?!) should work like \b\B, but is unreliable in some versions of Firefox - return match[1] ? '[\\s\\S]' : '\\b\\B'; - } - ); - -/* Comment pattern: `(?# )`. Inline comments are an alternative to the line comments allowed in - * free-spacing mode (flag x). - */ - add( - /\(\?#[^)]*\)/, - function(match, scope, flags) { - // Keep tokens separated unless the following token is a quantifier - return isQuantifierNext(match.input, match.index + match[0].length, flags) ? - '' : '(?:)'; - } - ); - -/* Whitespace and line comments, in free-spacing mode (aka extended mode, flag x) only. - */ - add( - /\s+|#.*/, - function(match, scope, flags) { - // Keep tokens separated unless the following token is a quantifier - return isQuantifierNext(match.input, match.index + match[0].length, flags) ? - '' : '(?:)'; - }, - {flag: 'x'} - ); - -/* Dot, in dotall mode (aka singleline mode, flag s) only. - */ - add( - /\./, - function() { - return '[\\s\\S]'; - }, - {flag: 's'} - ); - -/* Named backreference: `\k`. Backreference names can use the characters A-Z, a-z, 0-9, _, - * and $ only. Also allows numbered backreferences as `\k`. - */ - add( - /\\k<([\w$]+)>/, - function(match) { - // Groups with the same name is an error, else would need `lastIndexOf` - var index = isNaN(match[1]) ? (indexOf(this.captureNames, match[1]) + 1) : +match[1], - endIndex = match.index + match[0].length; - if (!index || index > this.captureNames.length) { - throw new SyntaxError('Backreference to undefined group ' + match[0]); - } - // Keep backreferences separate from subsequent literal numbers - return '\\' + index + ( - endIndex === match.input.length || isNaN(match.input.charAt(endIndex)) ? - '' : '(?:)' - ); - } - ); - -/* Numbered backreference or octal, plus any following digits: `\0`, `\11`, etc. Octals except `\0` - * not followed by 0-9 and backreferences to unopened capture groups throw an error. Other matches - * are returned unaltered. IE < 9 doesn't support backreferences above `\99` in regex syntax. - */ - add( - /\\(\d+)/, - function(match, scope) { - if ( - !( - scope === defaultScope && - /^[1-9]/.test(match[1]) && - +match[1] <= this.captureNames.length - ) && - match[1] !== '0' - ) { - throw new SyntaxError('Cannot use octal escape or backreference to undefined group ' + - match[0]); - } - return match[0]; - }, - {scope: 'all'} - ); - -/* Named capturing group; match the opening delimiter only: `(?`. Capture names can use the - * characters A-Z, a-z, 0-9, _, and $ only. Names can't be integers. Supports Python-style - * `(?P` as an alternate syntax to avoid issues in recent Opera (which natively supports the - * Python-style syntax). Otherwise, XRegExp might treat numbered backreferences to Python-style - * named capture as octals. - */ - add( - /\(\?P?<([\w$]+)>/, - function(match) { - // Disallow bare integers as names because named backreferences are added to match - // arrays and therefore numeric properties may lead to incorrect lookups - if (!isNaN(match[1])) { - throw new SyntaxError('Cannot use integer as capture name ' + match[0]); - } - if (match[1] === 'length' || match[1] === '__proto__') { - throw new SyntaxError('Cannot use reserved word as capture name ' + match[0]); - } - if (indexOf(this.captureNames, match[1]) > -1) { - throw new SyntaxError('Cannot use same name for multiple groups ' + match[0]); - } - this.captureNames.push(match[1]); - this.hasNamedCapture = true; - return '('; - } - ); - -/* Capturing group; match the opening parenthesis only. Required for support of named capturing - * groups. Also adds explicit capture mode (flag n). - */ - add( - /\((?!\?)/, - function(match, scope, flags) { - if (flags.indexOf('n') > -1) { - return '(?:'; - } - this.captureNames.push(null); - return '('; - }, - {optionalFlags: 'n'} - ); - -/* ============================== - * Expose XRegExp - * ============================== */ - - return self; - -}());