diff --git a/docs/_coverpage.md b/docs/_coverpage.md index 1b143cf..9f10d8e 100644 --- a/docs/_coverpage.md +++ b/docs/_coverpage.md @@ -1,4 +1,4 @@ -# Barchart Market Data SDK JavaScript 5.9.0 +# Barchart Market Data SDK JavaScript 5.10.0 > Inject real-time market data into your JavaScript applications diff --git a/example/browser/example.js b/example/browser/example.js index dd36cfd..48ea5ca 100644 --- a/example/browser/example.js +++ b/example/browser/example.js @@ -183,6 +183,7 @@ module.exports = (() => { connection = new Connection(); connection.setExtendedProfileMode(true); + connection.setExtendedQuoteMode(true); connection.on('events', handleEvents); connection.connect(hostname, username, password); }; @@ -504,7 +505,7 @@ module.exports = (() => { }; const SP_500 = ['ABT', 'ABBV', 'ACN', 'ATVI', 'AYI', 'ADBE', 'AMD', 'AAP', 'AES', 'AET', 'AMG', 'AFL', 'A', 'APD', 'AKAM', 'ALK', 'ALB', 'ARE', 'ALXN', 'ALGN', 'ALLE', 'AGN', 'ADS', 'LNT', 'ALL', 'GOOGL', 'GOOG', 'MO', 'AMZN', 'AEE', 'AAL', 'AEP', 'AXP', 'AIG', 'AMT', 'AWK', 'AMP', 'ABC', 'AME', 'AMGN', 'APH', 'APC', 'ADI', 'ANDV', 'ANSS', 'ANTM', 'AON', 'AOS', 'APA', 'AIV', 'AAPL', 'AMAT', 'ADM', 'ARNC', 'AJG', 'AIZ', 'T', 'ADSK', 'ADP', 'AZO', 'AVB', 'AVY', 'BHGE', 'BLL', 'BAC', 'BK', 'BCR', 'BAX', 'BBT', 'BDX', 'BRK.B', 'BBY', 'BIIB', 'BLK', 'HRB', 'BA', 'BWA', 'BXP', 'BSX', 'BHF', 'BMY', 'AVGO', 'BF.B', 'CHRW', 'CA', 'COG', 'CDNS', 'CPB', 'COF', 'CAH', 'CBOE', 'KMX', 'CCL', 'CAT', 'CBG', 'CBS', 'CELG', 'CNC', 'CNP', 'CTL', 'CERN', 'CF', 'SCHW', 'CHTR', 'CHK', 'CVX', 'CMG', 'CB', 'CHD', 'CI', 'XEC', 'CINF', 'CTAS', 'CSCO', 'C', 'CFG', 'CTXS', 'CLX', 'CME', 'CMS', 'COH', 'KO', 'CTSH', 'CL', 'CMCSA', 'CMA', 'CAG', 'CXO', 'COP', 'ED', 'STZ', 'COO', 'GLW', 'COST', 'COTY', 'CCI', 'CSRA', 'CSX', 'CMI', 'CVS', 'DHI', 'DHR', 'DRI', 'DVA', 'DE', 'DLPH', 'DAL', 'XRAY', 'DVN', 'DLR', 'DFS', 'DISCA', 'DISCK', 'DISH', 'DG', 'DLTR', 'D', 'DOV', 'DWDP', 'DPS', 'DTE', 'DRE', 'DUK', 'DXC', 'ETFC', 'EMN', 'ETN', 'EBAY', 'ECL', 'EIX', 'EW', 'EA', 'EMR', 'ETR', 'EVHC', 'EOG', 'EQT', 'EFX', 'EQIX', 'EQR', 'ESS', 'EL', 'ES', 'RE', 'EXC', 'EXPE', 'EXPD', 'ESRX', 'EXR', 'XOM', 'FFIV', 'FB', 'FAST', 'FRT', 'FDX', 'FIS', 'FITB', 'FE', 'FISV', 'FLIR', 'FLS', 'FLR', 'FMC', 'FL', 'F', 'FTV', 'FBHS', 'BEN', 'FCX', 'GPS', 'GRMN', 'IT', 'GD', 'GE', 'GGP', 'GIS', 'GM', 'GPC', 'GILD', 'GPN', 'GS', 'GT', 'GWW', 'HAL', 'HBI', 'HOG', 'HRS', 'HIG', 'HAS', 'HCA', 'HCP', 'HP', 'HSIC', 'HSY', 'HES', 'HPE', 'HLT', 'HOLX', 'HD', 'HON', 'HRL', 'HST', 'HPQ', 'HUM', 'HBAN', 'IDXX', 'INFO', 'ITW', 'ILMN', 'IR', 'INTC', 'ICE', 'IBM', 'INCY', 'IP', 'IPG', 'IFF', 'INTU', 'ISRG', 'IVZ', 'IRM', 'JEC', 'JBHT', 'SJM', 'JNJ', 'JCI', 'JPM', 'JNPR', 'KSU', 'K', 'KEY', 'KMB', 'KIM', 'KMI', 'KLAC', 'KSS', 'KHC', 'KR', 'LB', 'LLL', 'LH', 'LRCX', 'LEG', 'LEN', 'LUK', 'LLY', 'LNC', 'LKQ', 'LMT', 'L', 'LOW', 'LYB', 'MTB', 'MAC', 'M', 'MRO', 'MPC', 'MAR', 'MMC', 'MLM', 'MAS', 'MA', 'MAT', 'MKC', 'MCD', 'MCK', 'MDT', 'MRK', 'MET', 'MTD', 'MGM', 'KORS', 'MCHP', 'MU', 'MSFT', 'MAA', 'MHK', 'TAP', 'MDLZ', 'MON', 'MNST', 'MCO', 'MS', 'MOS', 'MSI', 'MYL', 'NDAQ', 'NOV', 'NAVI', 'NTAP', 'NFLX', 'NWL', 'NFX', 'NEM', 'NWSA', 'NWS', 'NEE', 'NLSN', 'NKE', 'NI', 'NBL', 'JWN', 'NSC', 'NTRS', 'NOC', 'NCLH', 'NRG', 'NUE', 'NVDA', 'ORLY', 'OXY', 'OMC', 'OKE', 'ORCL', 'PCAR', 'PKG', 'PH', 'PDCO', 'PAYX', 'PYPL', 'PNR', 'PBCT', 'PEP', 'PKI', 'PRGO', 'PFE', 'PCG', 'PM', 'PSX', 'PNW', 'PXD', 'PNC', 'RL', 'PPG', 'PPL', 'PX', 'PCLN', 'PFG', 'PG', 'PGR', 'PLD', 'PRU', 'PEG', 'PSA', 'PHM', 'PVH', 'QRVO', 'PWR', 'QCOM', 'DGX', 'Q', 'RRC', 'RJF', 'RTN', 'O', 'RHT', 'REG', 'REGN', 'RF', 'RSG', 'RMD', 'RHI', 'ROK', 'COL', 'ROP', 'ROST', 'RCL', 'CRM', 'SBAC', 'SCG', 'SLB', 'SNI', 'STX', 'SEE', 'SRE', 'SHW', 'SIG', 'SPG', 'SWKS', 'SLG', 'SNA', 'SO', 'LUV', 'SPGI', 'SWK', 'SBUX', 'STT', 'SRCL', 'SYK', 'STI', 'SYMC', 'SYF', 'SNPS', 'SYY', 'TROW', 'TGT', 'TEL', 'FTI', 'TXN', 'TXT', 'TMO', 'TIF', 'TWX', 'TJX', 'TMK', 'TSS', 'TSCO', 'TDG', 'TRV', 'TRIP', 'FOXA', 'FOX', 'TSN', 'UDR', 'ULTA', 'USB', 'UA', 'UAA', 'UNP', 'UAL', 'UNH', 'UPS', 'URI', 'UTX', 'UHS', 'UNM', 'VFC', 'VLO', 'VAR', 'VTR', 'VRSN', 'VRSK', 'VZ', 'VRTX', 'VIAB', 'V', 'VNO', 'VMC', 'WMT', 'WBA', 'DIS', 'WM', 'WAT', 'WEC', 'WFC', 'HCN', 'WDC', 'WU', 'WRK', 'WY', 'WHR', 'WMB', 'WLTW', 'WYN', 'WYNN', 'XEL', 'XRX', 'XLNX', 'XL', 'XYL', 'YUM', 'ZBH', 'ZION', 'ZTS']; - const C3 = ['AL79MRM1.C3', 'BSP9WGQ1.C3', 'RA10BGM1.C3']; + const C3 = ['AL79MRM1.C3', 'BSP9WGQ1.C3', 'BUT9USM1.C3', 'RA10BGM1.C3']; const C3_OLD = ['C3:AL79MRM1', 'C3:BSP9WGQ1', 'C3:RA10BGM1']; const CMDTY = ['SCB001.CP', 'MER001.CP', 'ZCBAUS.CM', 'HOPAW001009.CM', 'AE030UBX.CS', 'UDZZ303N.CS']; const PLATTS = ['PLATTS:AAWAB00', 'AAWAB00.PT', 'PLATTS:AAXVA00', 'AAXVA00.PT', 'PLATTS:CBAAF00', 'CBAAF00.PT']; @@ -516,7 +517,7 @@ module.exports = (() => { }); })(); -},{"./../../../lib/connection/Connection":2,"./../../../lib/meta":19,"./../../../lib/utilities/data/timezones":25,"./../../../lib/utilities/format/decimal":27,"./../../../lib/utilities/format/price":29,"./../../../lib/utilities/format/quote":30}],2:[function(require,module,exports){ +},{"./../../../lib/connection/Connection":2,"./../../../lib/meta":20,"./../../../lib/utilities/data/timezones":26,"./../../../lib/utilities/format/decimal":28,"./../../../lib/utilities/format/price":30,"./../../../lib/utilities/format/quote":31}],2:[function(require,module,exports){ const array = require('@barchart/common-js/lang/array'), object = require('@barchart/common-js/lang/object'); @@ -524,8 +525,9 @@ const ConnectionBase = require('./ConnectionBase'), parseMessage = require('./../utilities/parse/ddf/message'); const retrieveExchanges = require('./snapshots/exchanges/retrieveExchanges'), - retrieveSnapshots = require('./snapshots/quotes/retrieveSnapshots'), - retrieveProfileExtensions = require('./snapshots/profiles/retrieveProfileExtensions'), + retrieveProfileExtensions = require('./snapshots/profiles/retrieveExtensions'), + retrieveQuoteSnapshots = require('./snapshots/quotes/retrieveSnapshots'), + retrieveQuoteExtensions = require('./snapshots/quotes/retrieveExtensions'), SymbolParser = require('./../utilities/parsers/SymbolParser'); const WebSocketAdapterFactory = require('./adapter/WebSocketAdapterFactory'), @@ -585,6 +587,7 @@ module.exports = (() => { let __reconnectAllowed = false; let __pollingFrequency = null; let __extendedProfile = false; + let __extendedQuote = false; let __watchdogToken = null; let __watchdogAwake = false; let __exchangeMetadataPromise = null; @@ -594,6 +597,7 @@ module.exports = (() => { let __outboundMessages = []; let __knownConsumerSymbols = {}; let __pendingProfileSymbols = {}; + let __completedProfileExtensions = []; const __listeners = { marketDepth: {}, marketUpdate: {}, @@ -640,6 +644,12 @@ module.exports = (() => { if (__extendedProfile !== mode) { __extendedProfile = mode; } + } + + function setExtendedQuoteMode(mode) { + if (__extendedQuote !== mode) { + __extendedQuote = mode; + } } // // Functions for connecting to and disconnecting from JERQ, monitoring // established connections, and handling involuntary disconnects. @@ -1529,9 +1539,6 @@ module.exports = (() => { if (!s) { done = true; } else { - //if (s.startsWith('%')) { - // s = s + '\n'; - //} let skip = false; let msgType = 1; // Assume DDF message containing \x03 @@ -1748,13 +1755,22 @@ module.exports = (() => { } if (__extendedProfile && (task.id === 'MU_GO' || task.id === 'P_SNAPSHOT')) { - const symbolsExtended = symbolsUnique.filter(getIsExtendedSymbol); + const symbolsExtended = symbolsUnique.filter(getIsExtendedProfileSymbol); while (symbolsExtended.length > 0) { const batch = symbolsExtended.splice(0, batchSize); processExtendedProfiles(batch); } } + + if (__extendedQuote && task.id === 'MU_GO') { + const symbolsExtended = symbolsUnique.filter(getIsExtendedQuoteSymbol); + + while (symbolsExtended.length > 0) { + const batch = symbolsExtended.splice(0, batchSize); + processExtendedQuotes(batch); + } + } } } }; @@ -1856,7 +1872,7 @@ module.exports = (() => { return; } - retrieveSnapshots(symbols, __loginInfo.username, __loginInfo.password).then(quotes => { + retrieveQuoteSnapshots(symbols, __loginInfo.username, __loginInfo.password).then(quotes => { if (__connectionState !== state.authenticated) { return; } @@ -1890,12 +1906,12 @@ module.exports = (() => { setTimeout(pumpSnapshotRefresh, 3600000); } // - // Functions to acquire "extended" profile data not provided through DDF (via JERQ). + // Functions to acquire "extended" data not provided through DDF (via JERQ). // /** * Makes requests for "extended" profile data for a batch of symbols to an out-of-band - * service (i.e. extras). + * web service (i.e. extras). * * @private * @param {String[]} symbols @@ -1907,7 +1923,7 @@ module.exports = (() => { return; } - retrieveProfileExtensions(symbols, __loginInfo.username, __loginInfo.password).then(extensions => { + retrieveProfileExtensions(array.difference(symbols, __completedProfileExtensions), __loginInfo.username, __loginInfo.password).then(extensions => { if (__connectionState !== state.authenticated) { return; } @@ -1921,11 +1937,47 @@ module.exports = (() => { message.symbol = symbol; message.type = 'PROFILE_EXTENSION'; updateMarketState(message); + + __completedProfileExtensions.push(symbol); }); }); }).catch(e => { __logger.log(`Profiles [ ${__instance} ]: Out-of-band profile extension request failed for [ ${symbols.join()} ].`, e); }); + } + /** + * Makes requests for "extended" quote data for a batch of symbols to an out-of-band + * web service (e.g. extras). + * + * @private + * @param {String[]} symbols + */ + + + function processExtendedQuotes(symbols) { + if (__connectionState !== state.authenticated) { + return; + } + + retrieveQuoteExtensions(symbols, __loginInfo.username, __loginInfo.password).then(extensions => { + if (__connectionState !== state.authenticated) { + return; + } + + extensions.forEach(extension => { + const producerSymbol = extension.symbol; + const consumerSymbols = __knownConsumerSymbols[producerSymbol] || []; + const compositeSymbols = array.unique([producerSymbol].concat(consumerSymbols)); + compositeSymbols.forEach(symbol => { + const message = Object.assign({}, extension); + message.symbol = symbol; + message.type = 'QUOTE_EXTENSION'; + updateMarketState(message); + }); + }); + }).catch(e => { + __logger.log(`Profiles [ ${__instance} ]: Out-of-band quote extension request failed for [ ${symbols.join()} ].`, e); + }); } // // Internal utility functions for querying symbol subscriptions. // @@ -2157,9 +2209,23 @@ module.exports = (() => { */ - function getIsExtendedSymbol(symbol) { + function getIsExtendedProfileSymbol(symbol) { return SymbolParser.getIsFuture(symbol) || SymbolParser.getIsC3(symbol) || SymbolParser.getIsCmdty(symbol); } + /** + * Indicates if some quote information cannot be extracted from JERQ via + * DDF messages (which requires an out-of-band effort to get the complete + * profile). + * + * @private + * @param {String} symbol + * @returns {Boolean} + */ + + + function getIsExtendedQuoteSymbol(symbol) { + return SymbolParser.getIsFuture(symbol); + } /** * Breaks an array of symbols into multiple array, each containing no more * than 250 symbols. Also, symbols are filtered according to a predicate. @@ -2200,6 +2266,7 @@ module.exports = (() => { getProducerSymbolCount: getProducerSymbolCount, setPollingFrequency: setPollingFrequency, setExtendedProfileMode: setExtendedProfileMode, + setExtendedQuoteMode: setExtendedQuoteMode, handleProfileRequest: handleProfileRequest, getDiagnosticsController: getDiagnosticsController }; @@ -2280,6 +2347,10 @@ module.exports = (() => { return this._internal.setExtendedProfileMode(mode); } + _onExtendedQuoteModeChanged(mode) { + return this._internal.setExtendedQuoteMode(mode); + } + _handleProfileRequest(symbol) { this._internal.handleProfileRequest(symbol); } @@ -2297,7 +2368,7 @@ module.exports = (() => { return Connection; })(); -},{"./../logging/LoggerFactory":12,"./../meta":19,"./../utilities/parse/ddf/message":32,"./../utilities/parsers/SymbolParser":35,"./ConnectionBase":3,"./adapter/WebSocketAdapterFactory":5,"./adapter/WebSocketAdapterFactoryForBrowsers":6,"./diagnostics/DiagnosticsControllerBase":7,"./snapshots/exchanges/retrieveExchanges":8,"./snapshots/profiles/retrieveProfileExtensions":9,"./snapshots/quotes/retrieveSnapshots":10,"@barchart/common-js/lang/array":41,"@barchart/common-js/lang/object":44}],3:[function(require,module,exports){ +},{"./../logging/LoggerFactory":13,"./../meta":20,"./../utilities/parse/ddf/message":33,"./../utilities/parsers/SymbolParser":36,"./ConnectionBase":3,"./adapter/WebSocketAdapterFactory":5,"./adapter/WebSocketAdapterFactoryForBrowsers":6,"./diagnostics/DiagnosticsControllerBase":7,"./snapshots/exchanges/retrieveExchanges":8,"./snapshots/profiles/retrieveExtensions":9,"./snapshots/quotes/retrieveExtensions":10,"./snapshots/quotes/retrieveSnapshots":11,"@barchart/common-js/lang/array":42,"@barchart/common-js/lang/object":45}],3:[function(require,module,exports){ const is = require('@barchart/common-js/lang/is'); const MarketState = require('./../marketState/MarketState'); @@ -2323,6 +2394,7 @@ module.exports = (() => { this._marketState = new MarketState(symbol => this._handleProfileRequest(symbol)); this._pollingFrequency = null; this._extendedProfileMode = false; + this._extendedQuoteMode = false; this._instance = ++instanceCounter; } /** @@ -2518,10 +2590,9 @@ module.exports = (() => { return this._pollingFrequency; } /** - * When set to true, additional properties properties become - * available on {@link Profile} instances (e.g. future contract - * expiration date). This is accomplished by making additional - * out-of-band queries to Barchart services. + * When set to true, additional properties become available on {@link Profile} + * instances (e.g. the "first notice dates" for futures contracts). This is accomplished + * by making additional out-of-band queries to web Barchart services. * * @public * @param {Boolean} @@ -2545,17 +2616,55 @@ module.exports = (() => { return; } /** - * Indicates if additional {@link Profile} data (e.g. future contract - * expiration dates) should be loaded (via out-of-band queries). + * Indicates if additional {@link Profile} data (e.g. the "first notice dates" for + * futures contracts) should be loaded (via out-of-band queries). * * @public - * @returns {boolean} + * @returns {Boolean} */ getExtendedProfileMode() { return this._extendedProfileMode; } + /** + * When set to true, additional properties become available on {@link Quote} instances + * (e.g. "record high price" for futures contracts). This is accomplished by making + * additional out-of-band queries to Barchart web services. + * + * @public + * @param {Boolean} + */ + + + setExtendedQuoteMode(mode) { + if (is.boolean(mode) && this._extendedQuoteMode !== mode) { + this._extendedQuoteMode = mode; + + this._onExtendedQuoteModeChanged(this._extendedQuoteMode); + } + } + /** + * @protected + * @ignore + */ + + + _onExtendedQuoteModeChanged(mode) { + return; + } + /** + * Indicates if additional {@link Quote} data (e.g. "record high price" for futures + * contracts) should be loaded (via out-of-band queries). + * + * @public + * @returns {Boolean} + */ + + + getExtendedQuoteMode() { + return this._extendedQuoteMode; + } /** * @protected * @ignore @@ -2668,7 +2777,7 @@ module.exports = (() => { return ConnectionBase; })(); -},{"./../marketState/MarketState":16,"@barchart/common-js/lang/is":43}],4:[function(require,module,exports){ +},{"./../marketState/MarketState":17,"@barchart/common-js/lang/is":44}],4:[function(require,module,exports){ module.exports = (() => { 'use strict'; /** @@ -2955,7 +3064,7 @@ module.exports = (() => { return WebSocketAdapterFactoryForBrowsers; })(); -},{"./../../logging/LoggerFactory":12,"./WebSocketAdapter":4,"./WebSocketAdapterFactory":5}],7:[function(require,module,exports){ +},{"./../../logging/LoggerFactory":13,"./WebSocketAdapter":4,"./WebSocketAdapterFactory":5}],7:[function(require,module,exports){ const assert = require('@barchart/common-js/lang/assert'); const is = require('@barchart/common-js/lang/is'); @@ -3035,7 +3144,7 @@ module.exports = (() => { return DiagnosticsControllerBase; })(); -},{"@barchart/common-js/lang/assert":42,"@barchart/common-js/lang/is":43}],8:[function(require,module,exports){ +},{"@barchart/common-js/lang/assert":43,"@barchart/common-js/lang/is":44}],8:[function(require,module,exports){ const axios = require('axios'); module.exports = (() => { @@ -3083,7 +3192,7 @@ module.exports = (() => { return retrieveExchanges; })(); -},{"axios":46}],9:[function(require,module,exports){ +},{"axios":48}],9:[function(require,module,exports){ const axios = require('axios'); const array = require('@barchart/common-js/lang/array'), @@ -3109,7 +3218,7 @@ module.exports = (() => { * @returns {Promise} */ - function retrieveProfileExtensions(symbols, username, password) { + function retrieveExtensions(symbols, username, password) { return Promise.resolve().then(() => { if (logger === null) { logger = LoggerFactory.getLogger('@barchart/marketdata-api-js'); @@ -3118,13 +3227,13 @@ module.exports = (() => { assert.argumentIsArray(symbols, 'symbols', String); const symbolsForOnDemand = symbols.filter(SymbolParser.getIsC3); const symbolsForExtras = symbols.filter(SymbolParser.getIsFuture); - return Promise.all([retrieveProfileExtensionsFromExtras(symbolsForExtras), retrieveProfileExtensionsFromOnDemand(symbolsForOnDemand, username, password)]).then(results => { + return Promise.all([retrieveExtensionsFromExtras(symbolsForExtras), retrieveExtensionsFromOnDemand(symbolsForOnDemand, username, password)]).then(results => { return array.flatten(results); }); }); } - function retrieveProfileExtensionsFromExtras(symbols) { + function retrieveExtensionsFromExtras(symbols) { return Promise.resolve().then(() => { if (symbols.length === 0) { return Promise.resolve([]); @@ -3164,7 +3273,7 @@ module.exports = (() => { }); } - function retrieveProfileExtensionsFromOnDemand(symbols, username, password) { + function retrieveExtensionsFromOnDemand(symbols, username, password) { return Promise.resolve().then(() => { if (symbols.length === 0) { return Promise.resolve([]); @@ -3199,7 +3308,7 @@ module.exports = (() => { accumulator.push(extension); } catch (e) { - logger.warn(`Snapshot: Failed to process symbol`); + logger.warn(`Snapshot: Failed to process symbol [ ${JSON.stringify(result)} ]`); } return accumulator; @@ -3244,10 +3353,155 @@ module.exports = (() => { */ - return retrieveProfileExtensions; + return retrieveExtensions; +})(); + +},{"./../../../logging/LoggerFactory":13,"./../../../utilities/parsers/SymbolParser":36,"@barchart/common-js/lang/Day":39,"@barchart/common-js/lang/array":42,"@barchart/common-js/lang/assert":43,"@barchart/common-js/lang/is":44,"axios":48}],10:[function(require,module,exports){ +const axios = require('axios'); + +const array = require('@barchart/common-js/lang/array'), + Day = require('@barchart/common-js/lang/Day'), + is = require('@barchart/common-js/lang/is'); + +const SymbolParser = require('./../../../utilities/parsers/SymbolParser'); + +module.exports = (() => { + 'use strict'; + + let logger = null; + /** + * Executes an HTTP request for a quote snapshot extension(s). A quote + * extension contains supplemental quote-related data that is not available + * though normal sources (i.e. some data points are not available through + * a stream from JERQ). + * + * An array of quote refresh messages (suitable for processing by + * the {@link MarketState#processMessage} function) are returned. + * + * @function + * @ignore + * @param {String|Array} symbols + * @param {String} username + * @param {String} password + * @returns {Promise} + */ + + function retrieveExtensions(symbols, username, password) { + return Promise.resolve().then(() => { + let symbolsToUse; + + if (is.string(symbols)) { + symbolsToUse = [symbols]; + } else if (Array.isArray(symbols)) { + symbolsToUse = symbols; + } else { + throw new Error('The "symbols" argument must be a string or an array of strings.'); + } + + if (symbolsToUse.some(s => !is.string(s))) { + throw new Error('The "symbols" can only contain strings.'); + } + + if (!is.string(username)) { + throw new Error('The "username" argument must be a string.'); + } + + if (!is.string(password)) { + throw new Error('The "password" argument must be a string.'); + } + + const extensions = []; + + const getOrCreateExtension = symbol => { + let extension = extensions.find(e => e.symbol === symbol); + + if (!extension) { + extension = { + symbol + }; + extensions.push(extension); + } + + return extension; + }; + + const promises = []; + const futuresSymbols = array.unique(symbolsToUse.filter(s => SymbolParser.getIsFuture(s) && SymbolParser.getIsConcrete(s)).sort()); + + if (futuresSymbols.length !== 0) { + promises.push(retrieveFuturesHiLo(futuresSymbols, username, password).then(results => { + results.forEach(result => { + if (result.hilo) { + const extension = getOrCreateExtension(result.symbol); + const hilo = {}; + hilo.highPrice = result.hilo.highPrice; + hilo.highDate = result.hilo.highDate ? Day.parse(result.hilo.highDate) : null; + hilo.lowPrice = result.hilo.lowPrice; + hilo.lowDate = result.hilo.lowDate ? Day.parse(result.hilo.lowDate) : null; + extension.hilo = hilo; + } + }); + })); + } + + if (promises.length === 0) { + return Promise.resolve([]); + } + + return Promise.all(promises).then(() => { + return extensions; + }); + }); + } + /** + * Retrieves all-time highs and lows for specific futures contracts. + * + * @private + * @param symbols + * @param username + * @param password + * @returns {Promise<>} + */ + + + function retrieveFuturesHiLo(symbols, username, password) { + return Promise.resolve().then(() => { + const options = { + url: `https://instrument-extensions.aws.barchart.com/v1/futures/hilo?&symbols=${encodeURIComponent(symbols.join())}`, + method: 'GET' + }; + return Promise.resolve(axios(options)).then(response => { + return response.data || []; + }); + }); + } + /** + * Extended quote information. + * + * @typedef QuoteExtension + * @type {Object} + * @ignore + * @property {String} symbol + * @property {QuoteExtensionHiLo=} hilo + */ + + /** + * Extended quote information (for all-time highs and lows). + * + * @typedef QuoteExtensionHiLo + * @type {Object} + * @ignore + * @property {Number=} highPrice + * @property {Day=} highDate + * @property {Number=} lowPrice + * @property {Day=} lowDate + */ + + + return retrieveExtensions; })(); -},{"./../../../logging/LoggerFactory":12,"./../../../utilities/parsers/SymbolParser":35,"@barchart/common-js/lang/Day":38,"@barchart/common-js/lang/array":41,"@barchart/common-js/lang/assert":42,"@barchart/common-js/lang/is":43,"axios":46}],10:[function(require,module,exports){ +},{"./../../../utilities/parsers/SymbolParser":36,"@barchart/common-js/lang/Day":39,"@barchart/common-js/lang/array":42,"@barchart/common-js/lang/is":44,"axios":48}],11:[function(require,module,exports){ const axios = require('axios'); const array = require('@barchart/common-js/lang/array'), @@ -3450,7 +3704,7 @@ module.exports = (() => { return retrieveSnapshots; })(); -},{"./../../../logging/LoggerFactory":12,"./../../../utilities/convert/baseCodeToUnitCode":20,"./../../../utilities/convert/dateToDayCode":21,"./../../../utilities/convert/dayCodeToNumber":22,"./../../../utilities/parsers/SymbolParser":35,"@barchart/common-js/lang/array":41,"@barchart/common-js/lang/is":43,"axios":46}],11:[function(require,module,exports){ +},{"./../../../logging/LoggerFactory":13,"./../../../utilities/convert/baseCodeToUnitCode":21,"./../../../utilities/convert/dateToDayCode":22,"./../../../utilities/convert/dayCodeToNumber":23,"./../../../utilities/parsers/SymbolParser":36,"@barchart/common-js/lang/array":42,"@barchart/common-js/lang/is":44,"axios":48}],12:[function(require,module,exports){ module.exports = (() => { 'use strict'; /** @@ -3546,7 +3800,7 @@ module.exports = (() => { return Logger; })(); -},{}],12:[function(require,module,exports){ +},{}],13:[function(require,module,exports){ const Logger = require('./Logger'), LoggerProvider = require('./LoggerProvider'); @@ -3751,7 +4005,7 @@ module.exports = (() => { return LoggerFactory; })(); -},{"./Logger":11,"./LoggerProvider":13}],13:[function(require,module,exports){ +},{"./Logger":12,"./LoggerProvider":14}],14:[function(require,module,exports){ module.exports = (() => { 'use strict'; /** @@ -3788,7 +4042,7 @@ module.exports = (() => { return LoggerProvider; })(); -},{}],14:[function(require,module,exports){ +},{}],15:[function(require,module,exports){ const object = require('@barchart/common-js/lang//object'); const LoggerFactory = require('./../logging/LoggerFactory'); @@ -4051,7 +4305,7 @@ module.exports = (() => { return CumulativeVolume; })(); -},{"./../logging/LoggerFactory":12,"@barchart/common-js/lang//object":44}],15:[function(require,module,exports){ +},{"./../logging/LoggerFactory":13,"@barchart/common-js/lang//object":45}],16:[function(require,module,exports){ const Timezones = require('@barchart/common-js/lang/Timezones'); module.exports = (() => { @@ -4129,7 +4383,7 @@ module.exports = (() => { return Exchange; })(); -},{"@barchart/common-js/lang/Timezones":40}],16:[function(require,module,exports){ +},{"@barchart/common-js/lang/Timezones":41}],17:[function(require,module,exports){ const is = require('@barchart/common-js/lang/is'), object = require('@barchart/common-js/lang/object'), timezone = require('@barchart/common-js/lang/timezone'), @@ -4175,6 +4429,7 @@ module.exports = (() => { const _cvol = {}; const _profileCallbacks = {}; const _profileExtensions = {}; + const _quoteExtensions = {}; let _timestamp; @@ -4222,6 +4477,20 @@ module.exports = (() => { return cv; }; + const _processQuoteExtension = (quote, extension) => { + if (extension.hilo) { + const hilo = extension.hilo; + + if (is.number(hilo.highPrice)) { + quote.recordHighPrice = Math.max(hilo.highPrice, is.number(quote.highPrice) ? quote.highPrice : Number.MIN_SAFE_INTEGER); + } + + if (is.number(hilo.lowPrice)) { + quote.recordLowPrice = Math.min(hilo.lowPrice, is.number(quote.lowPrice) ? quote.lowPrice : Number.MAX_SAFE_INTEGER); + } + } + }; + const _getOrCreateQuote = symbol => { let quote = _quote[symbol]; @@ -4233,6 +4502,13 @@ module.exports = (() => { quote = Quote.clone(symbol, producerQuote); } else { quote = new Quote(symbol); + const extension = _quoteExtensions[symbol]; + + if (extension) { + _processQuoteExtension(quote, extension); + + delete _quoteExtensions[symbol]; + } } _quote[symbol] = quote; @@ -4320,6 +4596,18 @@ module.exports = (() => { return profile; }; + const _deriveRecordHighPrice = quote => { + if (is.number(quote.highPrice) && is.number(quote.recordHighPrice) && quote.highPrice > quote.recordHighPrice) { + quote.recordHighPrice = quote.highPrice; + } + }; + + const _deriveRecordLowPrice = quote => { + if (is.number(quote.lowPrice) && is.number(quote.recordLowPrice) && quote.lowPrice < quote.recordLowPrice) { + quote.recordLowPrice = quote.lowPrice; + } + }; + const _processMessage = message => { const symbol = message.symbol; @@ -4370,6 +4658,18 @@ module.exports = (() => { return; } + if (message.type === 'QUOTE_EXTENSION') { + const q = _quote[symbol]; + + if (p && q) { + _processQuoteExtension(q, message); + } else { + _quoteExtensions[symbol] = message; + } + + return; + } + if (!p && message.type !== 'REFRESH_QUOTE') { _logger.warn(`MarketState [ ${_instance} ]: No profile found for [ ${symbol} ]`); @@ -4426,10 +4726,16 @@ module.exports = (() => { switch (message.type) { case 'HIGH': q.highPrice = message.value; + + _deriveRecordHighPrice(q); + break; case 'LOW': q.lowPrice = message.value; + + _deriveRecordLowPrice(q); + break; case 'OPEN': @@ -4439,6 +4745,10 @@ module.exports = (() => { q.lowPrice = message.value; q.lastPrice = message.value; + _deriveRecordHighPrice(q); + + _deriveRecordLowPrice(q); + if (cv && cv.container) { cv.container.reset(); } @@ -4466,12 +4776,16 @@ module.exports = (() => { q.highPrice = undefined; } else if (message.highPrice) { q.highPrice = message.highPrice; + + _deriveRecordHighPrice(q); } if (message.lowPrice === null) { q.lowPrice = undefined; } else if (message.lowPrice) { q.lowPrice = message.lowPrice; + + _deriveRecordLowPrice(q); } if (message.lastPrice === null) { @@ -4565,6 +4879,11 @@ module.exports = (() => { q.openPrice = message.openPrice; q.highPrice = message.highPrice; q.lowPrice = message.lowPrice; + + _deriveRecordHighPrice(q); + + _deriveRecordLowPrice(q); + q.volume = message.volume; q.openInterest = message.openInterest; @@ -4902,7 +5221,7 @@ module.exports = (() => { return MarketState; })(); -},{"../utilities/parsers/SymbolParser":35,"./../logging/LoggerFactory":12,"./../meta":19,"./../utilities/convert/dayCodeToNumber":22,"./CumulativeVolume":14,"./Exchange":15,"./Profile":17,"./Quote":18,"@barchart/common-js/lang/Timezones":40,"@barchart/common-js/lang/is":43,"@barchart/common-js/lang/object":44,"@barchart/common-js/lang/timezone":45}],17:[function(require,module,exports){ +},{"../utilities/parsers/SymbolParser":36,"./../logging/LoggerFactory":13,"./../meta":20,"./../utilities/convert/dayCodeToNumber":23,"./CumulativeVolume":15,"./Exchange":16,"./Profile":18,"./Quote":19,"@barchart/common-js/lang/Timezones":41,"@barchart/common-js/lang/is":44,"@barchart/common-js/lang/object":45,"@barchart/common-js/lang/timezone":47}],18:[function(require,module,exports){ const SymbolParser = require('./../utilities/parsers/SymbolParser'), buildPriceFormatter = require('../utilities/format/factories/price'); @@ -5067,7 +5386,7 @@ module.exports = (() => { return Profile; })(); -},{"../utilities/format/factories/price":28,"./../utilities/parsers/SymbolParser":35}],18:[function(require,module,exports){ +},{"../utilities/format/factories/price":29,"./../utilities/parsers/SymbolParser":36}],19:[function(require,module,exports){ module.exports = (() => { 'use strict'; /** @@ -5245,6 +5564,20 @@ module.exports = (() => { */ this.lowPrice = null; + /** + * @property {number|null} recordHighPrice - The all-time highest trade price from current or previous trading sessions. + * @public + * @readonly + */ + + this.recordHighPrice = null; + /** + * @property {number|null} recordLowPrice - The all-time lowest trade price from current or previous trading sessions. + * @public + * @readonly + */ + + this.recordLowPrice = null; /** * @property {number|null} volume - The quantity traded during the current trading session. * @public @@ -5298,16 +5631,16 @@ module.exports = (() => { return Quote; })(); -},{}],19:[function(require,module,exports){ +},{}],20:[function(require,module,exports){ module.exports = (() => { 'use strict'; return { - version: '5.9.0' + version: '5.10.0' }; })(); -},{}],20:[function(require,module,exports){ +},{}],21:[function(require,module,exports){ const is = require('@barchart/common-js/lang/is'); const UnitCode = require('./../data/UnitCode'); @@ -5338,7 +5671,7 @@ module.exports = (() => { return convertBaseCodeToUnitCode; })(); -},{"./../data/UnitCode":24,"@barchart/common-js/lang/is":43}],21:[function(require,module,exports){ +},{"./../data/UnitCode":25,"@barchart/common-js/lang/is":44}],22:[function(require,module,exports){ const convertNumberToDayCode = require('./numberToDayCode'); module.exports = (() => { @@ -5365,7 +5698,7 @@ module.exports = (() => { return convertDateToDayCode; })(); -},{"./numberToDayCode":23}],22:[function(require,module,exports){ +},{"./numberToDayCode":24}],23:[function(require,module,exports){ const is = require('@barchart/common-js/lang/is'); module.exports = (() => { @@ -5399,7 +5732,7 @@ module.exports = (() => { return convertDayCodeToNumber; })(); -},{"@barchart/common-js/lang/is":43}],23:[function(require,module,exports){ +},{"@barchart/common-js/lang/is":44}],24:[function(require,module,exports){ const is = require('@barchart/common-js/lang/is'); module.exports = (() => { @@ -5436,7 +5769,7 @@ module.exports = (() => { return convertNumberToDayCode; })(); -},{"@barchart/common-js/lang/is":43}],24:[function(require,module,exports){ +},{"@barchart/common-js/lang/is":44}],25:[function(require,module,exports){ const Enum = require('@barchart/common-js/lang/Enum'); module.exports = (() => { @@ -5651,7 +5984,7 @@ module.exports = (() => { return UnitCode; })(); -},{"@barchart/common-js/lang/Enum":39}],25:[function(require,module,exports){ +},{"@barchart/common-js/lang/Enum":40}],26:[function(require,module,exports){ const timezone = require('@barchart/common-js/lang/timezone'); module.exports = (() => { @@ -5686,7 +6019,7 @@ module.exports = (() => { }; })(); -},{"@barchart/common-js/lang/timezone":45}],26:[function(require,module,exports){ +},{"@barchart/common-js/lang/timezone":47}],27:[function(require,module,exports){ module.exports = (() => { 'use strict'; @@ -5720,7 +6053,7 @@ module.exports = (() => { return formatDate; })(); -},{}],27:[function(require,module,exports){ +},{}],28:[function(require,module,exports){ const is = require('@barchart/common-js/lang/is'); module.exports = (() => { @@ -5790,7 +6123,7 @@ module.exports = (() => { return formatDecimal; })(); -},{"@barchart/common-js/lang/is":43}],28:[function(require,module,exports){ +},{"@barchart/common-js/lang/is":44}],29:[function(require,module,exports){ const formatPrice = require('./../price'); module.exports = (() => { @@ -5826,7 +6159,7 @@ module.exports = (() => { return buildPriceFormatter; })(); -},{"./../price":29}],29:[function(require,module,exports){ +},{"./../price":30}],30:[function(require,module,exports){ const is = require('@barchart/common-js/lang/is'); const formatDecimal = require('./decimal'); @@ -5927,7 +6260,7 @@ module.exports = (() => { return formatPrice; })(); -},{"./../data/UnitCode":24,"./decimal":27,"@barchart/common-js/lang/is":43}],30:[function(require,module,exports){ +},{"./../data/UnitCode":25,"./decimal":28,"@barchart/common-js/lang/is":44}],31:[function(require,module,exports){ const is = require('@barchart/common-js/lang/is'); const formatDate = require('./date'), @@ -6012,7 +6345,7 @@ module.exports = (() => { return formatQuoteDateTime; })(); -},{"./date":26,"./time":31,"@barchart/common-js/lang/Timezones":40,"@barchart/common-js/lang/is":43}],31:[function(require,module,exports){ +},{"./date":27,"./time":32,"@barchart/common-js/lang/Timezones":41,"@barchart/common-js/lang/is":44}],32:[function(require,module,exports){ module.exports = (() => { 'use strict'; @@ -6165,7 +6498,7 @@ module.exports = (() => { return formatTime; })(); -},{}],32:[function(require,module,exports){ +},{}],33:[function(require,module,exports){ const xmlDom = require('xmldom'); const parseValue = require('./value'), @@ -6718,7 +7051,7 @@ module.exports = (() => { return parseMessage; })(); -},{"./timestamp":33,"./value":34,"xmldom":76}],33:[function(require,module,exports){ +},{"./timestamp":34,"./value":35,"xmldom":78}],34:[function(require,module,exports){ module.exports = (() => { 'use strict'; /** @@ -6772,7 +7105,7 @@ module.exports = (() => { return parseTimestamp; })(); -},{}],34:[function(require,module,exports){ +},{}],35:[function(require,module,exports){ module.exports = (() => { 'use strict'; @@ -6872,8 +7205,9 @@ module.exports = (() => { return parseValue; })(); -},{}],35:[function(require,module,exports){ -const is = require('@barchart/common-js/lang/is'); +},{}],36:[function(require,module,exports){ +const is = require('@barchart/common-js/lang/is'), + string = require('@barchart/common-js/lang/string'); module.exports = (() => { 'use strict'; @@ -7209,6 +7543,28 @@ module.exports = (() => { return formatted; } + /** + * Converts an abbreviated futures symbol (with a single digit year) into + * a futures symbol with a two digit year. If the symbol is not a futures + * contract, a null value is returned. + * + * @static + * @public + * @param {String} symbol + * @returns {String|null} + */ + + + static getFuturesExplicitFormat(symbol) { + let explicit = null; + + if (SymbolParser.getIsFuture(symbol) && SymbolParser.getIsConcrete(symbol)) { + const parsed = SymbolParser.parseInstrumentType(symbol); + explicit = `${parsed.root}${parsed.month}${string.padLeft(Math.floor(parsed.year % 100).toString(), 2, '0')}`; + } + + return explicit; + } /** * Returns true if prices for the symbol should be represented as a percentage; false * otherwise. @@ -7538,7 +7894,7 @@ module.exports = (() => { return SymbolParser; })(); -},{"@barchart/common-js/lang/is":43}],36:[function(require,module,exports){ +},{"@barchart/common-js/lang/is":44,"@barchart/common-js/lang/string":46}],37:[function(require,module,exports){ const assert = require('./../../lang/assert'), comparators = require('./comparators'); @@ -7657,7 +8013,7 @@ module.exports = (() => { return ComparatorBuilder; })(); -},{"./../../lang/assert":42,"./comparators":37}],37:[function(require,module,exports){ +},{"./../../lang/assert":43,"./comparators":38}],38:[function(require,module,exports){ const assert = require('./../../lang/assert'); module.exports = (() => { @@ -7752,7 +8108,7 @@ module.exports = (() => { }; })(); -},{"./../../lang/assert":42}],38:[function(require,module,exports){ +},{"./../../lang/assert":43}],39:[function(require,module,exports){ const assert = require('./assert'), ComparatorBuilder = require('./../collections/sorting/ComparatorBuilder'), comparators = require('./../collections/sorting/comparators'), @@ -8271,7 +8627,7 @@ module.exports = (() => { return Day; })(); -},{"./../collections/sorting/ComparatorBuilder":36,"./../collections/sorting/comparators":37,"./assert":42,"./is":43}],39:[function(require,module,exports){ +},{"./../collections/sorting/ComparatorBuilder":37,"./../collections/sorting/comparators":38,"./assert":43,"./is":44}],40:[function(require,module,exports){ const assert = require('./assert'); module.exports = (() => { @@ -8391,7 +8747,7 @@ module.exports = (() => { return Enum; })(); -},{"./assert":42}],40:[function(require,module,exports){ +},{"./assert":43}],41:[function(require,module,exports){ const moment = require('moment-timezone/builds/moment-timezone-with-data-2012-2022'); const Enum = require('./Enum'), @@ -8533,7 +8889,7 @@ module.exports = (() => { return Timezones; })(); -},{"./Enum":39,"./is":43,"./timezone":45,"moment-timezone/builds/moment-timezone-with-data-2012-2022":74}],41:[function(require,module,exports){ +},{"./Enum":40,"./is":44,"./timezone":47,"moment-timezone/builds/moment-timezone-with-data-2012-2022":76}],42:[function(require,module,exports){ const assert = require('./assert'), is = require('./is'); @@ -9037,7 +9393,7 @@ module.exports = (() => { } })(); -},{"./assert":42,"./is":43}],42:[function(require,module,exports){ +},{"./assert":43,"./is":44}],43:[function(require,module,exports){ const is = require('./is'); module.exports = (() => { @@ -9182,7 +9538,7 @@ module.exports = (() => { }; })(); -},{"./is":43}],43:[function(require,module,exports){ +},{"./is":44}],44:[function(require,module,exports){ module.exports = (() => { 'use strict'; /** @@ -9401,7 +9757,7 @@ module.exports = (() => { }; })(); -},{}],44:[function(require,module,exports){ +},{}],45:[function(require,module,exports){ const array = require('./array'), is = require('./is'); @@ -9561,7 +9917,133 @@ module.exports = (() => { return object; })(); -},{"./array":41,"./is":43}],45:[function(require,module,exports){ +},{"./array":42,"./is":44}],46:[function(require,module,exports){ +const assert = require('./assert'), + is = require('./is'); + +module.exports = (() => { + 'use strict'; + + const regex = {}; + regex.camel = {}; + regex.camel.violations = /\b[A-Z]/g; + /** + * Utility functions for strings. + * + * @public + * @module lang/string + */ + + return { + /** + * Adjusts a string, replacing the first character of each word with an uppercase + * character and all subsequent characters in the word with lowercase characters. + * + * @public + * @static + * @param {String} s + * @returns {String} + */ + startCase(s) { + return s.split(' ').reduce((phrase, word) => { + if (word.length !== 0) { + phrase.push(word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()); + } + + return phrase; + }, []).join(' '); + }, + + /** + * Adjust a string to use camel case, where the first letter of each word is replaced + * with a lower case character. + * + * @public + * @static + * @param {String} s + * @returns {String} + */ + camelCase(s) { + assert.argumentIsRequired(s, 's', String); + return s.replace(regex.camel.violations, m => m.toLocaleLowerCase()); + }, + + /** + * If a string exceeds a desired length, it is truncated and a poor man's + * ellipsis (i.e. three periods) is appended. Otherwise, the original + * string is returned. + * + * @public + * @static + * @param {String} s + * @param {Number} length + * @returns {String} + */ + truncate(s, length) { + if (is.string(s) && s.length > length) { + return s.substring(0, length) + ' ...'; + } else { + return s; + } + }, + + /** + * Adds leading characters to a string, until the string length is a desired size. + * + * @public + * @static + * @param {String} s - The string to pad. + * @param {Number} length - The desired overall length of the string. + * @param {String} character - The character to use for padding. + * @returns {String} + */ + padLeft(s, length, character) { + assert.argumentIsRequired(s, 's', String); + assert.argumentIsRequired(length, 'length', Number); + assert.argumentIsRequired(character, 'character', String); + + if (character.length !== 1) { + throw new Error('The "character" argument must be one character in length.'); + } + + return character.repeat(length - s.length) + s; + }, + + /** + * Performs a simple token replacement on a string; where the tokens + * are braced numbers (e.g. {0}, {1}, {2}). + * + * @public + * @static + * @param {String} s - The string to format (e.g. 'my first name is {0} and my last name is {1}') + * @param {Array} data - The replacement data + * @returns {String} + */ + format(s, ...data) { + assert.argumentIsRequired(s, 's', String); + return s.replace(/{(\d+)}/g, (match, i) => { + let replacement; + + if (i < data.length) { + const item = data[i]; + + if (!is.undefined(item) && !is.null(item)) { + replacement = item.toString(); + } else { + replacement = match; + } + } else { + replacement = match; + } + + return replacement; + }); + } + + }; +})(); + +},{"./assert":43,"./is":44}],47:[function(require,module,exports){ const moment = require('moment-timezone/builds/moment-timezone-with-data-2012-2022'), assert = require('./assert'); @@ -9616,9 +10098,9 @@ module.exports = (() => { }; })(); -},{"./assert":42,"moment-timezone/builds/moment-timezone-with-data-2012-2022":74}],46:[function(require,module,exports){ +},{"./assert":43,"moment-timezone/builds/moment-timezone-with-data-2012-2022":76}],48:[function(require,module,exports){ module.exports = require('./lib/axios'); -},{"./lib/axios":48}],47:[function(require,module,exports){ +},{"./lib/axios":50}],49:[function(require,module,exports){ 'use strict'; var utils = require('./../utils'); @@ -9799,7 +10281,7 @@ module.exports = function xhrAdapter(config) { }); }; -},{"../core/buildFullPath":54,"../core/createError":55,"./../core/settle":59,"./../helpers/buildURL":63,"./../helpers/cookies":65,"./../helpers/isURLSameOrigin":68,"./../helpers/parseHeaders":70,"./../utils":72}],48:[function(require,module,exports){ +},{"../core/buildFullPath":56,"../core/createError":57,"./../core/settle":61,"./../helpers/buildURL":65,"./../helpers/cookies":67,"./../helpers/isURLSameOrigin":70,"./../helpers/parseHeaders":72,"./../utils":74}],50:[function(require,module,exports){ 'use strict'; var utils = require('./utils'); @@ -9857,7 +10339,7 @@ module.exports = axios; // Allow use of default import syntax in TypeScript module.exports.default = axios; -},{"./cancel/Cancel":49,"./cancel/CancelToken":50,"./cancel/isCancel":51,"./core/Axios":52,"./core/mergeConfig":58,"./defaults":61,"./helpers/bind":62,"./helpers/isAxiosError":67,"./helpers/spread":71,"./utils":72}],49:[function(require,module,exports){ +},{"./cancel/Cancel":51,"./cancel/CancelToken":52,"./cancel/isCancel":53,"./core/Axios":54,"./core/mergeConfig":60,"./defaults":63,"./helpers/bind":64,"./helpers/isAxiosError":69,"./helpers/spread":73,"./utils":74}],51:[function(require,module,exports){ 'use strict'; /** @@ -9878,7 +10360,7 @@ Cancel.prototype.__CANCEL__ = true; module.exports = Cancel; -},{}],50:[function(require,module,exports){ +},{}],52:[function(require,module,exports){ 'use strict'; var Cancel = require('./Cancel'); @@ -9937,14 +10419,14 @@ CancelToken.source = function source() { module.exports = CancelToken; -},{"./Cancel":49}],51:[function(require,module,exports){ +},{"./Cancel":51}],53:[function(require,module,exports){ 'use strict'; module.exports = function isCancel(value) { return !!(value && value.__CANCEL__); }; -},{}],52:[function(require,module,exports){ +},{}],54:[function(require,module,exports){ 'use strict'; var utils = require('./../utils'); @@ -10041,7 +10523,7 @@ utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { module.exports = Axios; -},{"../helpers/buildURL":63,"./../utils":72,"./InterceptorManager":53,"./dispatchRequest":56,"./mergeConfig":58}],53:[function(require,module,exports){ +},{"../helpers/buildURL":65,"./../utils":74,"./InterceptorManager":55,"./dispatchRequest":58,"./mergeConfig":60}],55:[function(require,module,exports){ 'use strict'; var utils = require('./../utils'); @@ -10095,7 +10577,7 @@ InterceptorManager.prototype.forEach = function forEach(fn) { module.exports = InterceptorManager; -},{"./../utils":72}],54:[function(require,module,exports){ +},{"./../utils":74}],56:[function(require,module,exports){ 'use strict'; var isAbsoluteURL = require('../helpers/isAbsoluteURL'); @@ -10117,7 +10599,7 @@ module.exports = function buildFullPath(baseURL, requestedURL) { return requestedURL; }; -},{"../helpers/combineURLs":64,"../helpers/isAbsoluteURL":66}],55:[function(require,module,exports){ +},{"../helpers/combineURLs":66,"../helpers/isAbsoluteURL":68}],57:[function(require,module,exports){ 'use strict'; var enhanceError = require('./enhanceError'); @@ -10137,7 +10619,7 @@ module.exports = function createError(message, config, code, request, response) return enhanceError(error, config, code, request, response); }; -},{"./enhanceError":57}],56:[function(require,module,exports){ +},{"./enhanceError":59}],58:[function(require,module,exports){ 'use strict'; var utils = require('./../utils'); @@ -10218,7 +10700,7 @@ module.exports = function dispatchRequest(config) { }); }; -},{"../cancel/isCancel":51,"../defaults":61,"./../utils":72,"./transformData":60}],57:[function(require,module,exports){ +},{"../cancel/isCancel":53,"../defaults":63,"./../utils":74,"./transformData":62}],59:[function(require,module,exports){ 'use strict'; /** @@ -10262,7 +10744,7 @@ module.exports = function enhanceError(error, config, code, request, response) { return error; }; -},{}],58:[function(require,module,exports){ +},{}],60:[function(require,module,exports){ 'use strict'; var utils = require('../utils'); @@ -10351,7 +10833,7 @@ module.exports = function mergeConfig(config1, config2) { return config; }; -},{"../utils":72}],59:[function(require,module,exports){ +},{"../utils":74}],61:[function(require,module,exports){ 'use strict'; var createError = require('./createError'); @@ -10378,7 +10860,7 @@ module.exports = function settle(resolve, reject, response) { } }; -},{"./createError":55}],60:[function(require,module,exports){ +},{"./createError":57}],62:[function(require,module,exports){ 'use strict'; var utils = require('./../utils'); @@ -10400,7 +10882,7 @@ module.exports = function transformData(data, headers, fns) { return data; }; -},{"./../utils":72}],61:[function(require,module,exports){ +},{"./../utils":74}],63:[function(require,module,exports){ (function (process){(function (){ 'use strict'; @@ -10502,7 +10984,7 @@ utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { module.exports = defaults; }).call(this)}).call(this,require('_process')) -},{"./adapters/http":47,"./adapters/xhr":47,"./helpers/normalizeHeaderName":69,"./utils":72,"_process":73}],62:[function(require,module,exports){ +},{"./adapters/http":49,"./adapters/xhr":49,"./helpers/normalizeHeaderName":71,"./utils":74,"_process":75}],64:[function(require,module,exports){ 'use strict'; module.exports = function bind(fn, thisArg) { @@ -10515,7 +10997,7 @@ module.exports = function bind(fn, thisArg) { }; }; -},{}],63:[function(require,module,exports){ +},{}],65:[function(require,module,exports){ 'use strict'; var utils = require('./../utils'); @@ -10587,7 +11069,7 @@ module.exports = function buildURL(url, params, paramsSerializer) { return url; }; -},{"./../utils":72}],64:[function(require,module,exports){ +},{"./../utils":74}],66:[function(require,module,exports){ 'use strict'; /** @@ -10603,7 +11085,7 @@ module.exports = function combineURLs(baseURL, relativeURL) { : baseURL; }; -},{}],65:[function(require,module,exports){ +},{}],67:[function(require,module,exports){ 'use strict'; var utils = require('./../utils'); @@ -10658,7 +11140,7 @@ module.exports = ( })() ); -},{"./../utils":72}],66:[function(require,module,exports){ +},{"./../utils":74}],68:[function(require,module,exports){ 'use strict'; /** @@ -10674,7 +11156,7 @@ module.exports = function isAbsoluteURL(url) { return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url); }; -},{}],67:[function(require,module,exports){ +},{}],69:[function(require,module,exports){ 'use strict'; /** @@ -10687,7 +11169,7 @@ module.exports = function isAxiosError(payload) { return (typeof payload === 'object') && (payload.isAxiosError === true); }; -},{}],68:[function(require,module,exports){ +},{}],70:[function(require,module,exports){ 'use strict'; var utils = require('./../utils'); @@ -10757,7 +11239,7 @@ module.exports = ( })() ); -},{"./../utils":72}],69:[function(require,module,exports){ +},{"./../utils":74}],71:[function(require,module,exports){ 'use strict'; var utils = require('../utils'); @@ -10771,7 +11253,7 @@ module.exports = function normalizeHeaderName(headers, normalizedName) { }); }; -},{"../utils":72}],70:[function(require,module,exports){ +},{"../utils":74}],72:[function(require,module,exports){ 'use strict'; var utils = require('./../utils'); @@ -10826,7 +11308,7 @@ module.exports = function parseHeaders(headers) { return parsed; }; -},{"./../utils":72}],71:[function(require,module,exports){ +},{"./../utils":74}],73:[function(require,module,exports){ 'use strict'; /** @@ -10855,7 +11337,7 @@ module.exports = function spread(callback) { }; }; -},{}],72:[function(require,module,exports){ +},{}],74:[function(require,module,exports){ 'use strict'; var bind = require('./helpers/bind'); @@ -11208,7 +11690,7 @@ module.exports = { stripBOM: stripBOM }; -},{"./helpers/bind":62}],73:[function(require,module,exports){ +},{"./helpers/bind":64}],75:[function(require,module,exports){ // shim for using process in browser var process = module.exports = {}; @@ -11394,7 +11876,7 @@ process.chdir = function (dir) { }; process.umask = function() { return 0; }; -},{}],74:[function(require,module,exports){ +},{}],76:[function(require,module,exports){ //! moment-timezone.js //! version : 0.5.26 //! Copyright (c) JS Foundation and other contributors @@ -12620,7 +13102,7 @@ process.umask = function() { return 0; }; return moment; })); -},{"moment":75}],75:[function(require,module,exports){ +},{"moment":77}],77:[function(require,module,exports){ //! moment.js ;(function (global, factory) { @@ -17224,7 +17706,7 @@ process.umask = function() { return 0; }; }))); -},{}],76:[function(require,module,exports){ +},{}],78:[function(require,module,exports){ function DOMParser(options){ this.options = options ||{locator:{}}; } @@ -17479,7 +17961,7 @@ exports.DOMParser = DOMParser; exports.__DOMHandler = DOMHandler; //} -},{"./dom":77,"./entities":78,"./sax":79}],77:[function(require,module,exports){ +},{"./dom":79,"./entities":80,"./sax":81}],79:[function(require,module,exports){ function copy(src,dest){ for(var p in src){ dest[p] = src[p]; @@ -18764,7 +19246,7 @@ try{ exports.XMLSerializer = XMLSerializer; //} -},{}],78:[function(require,module,exports){ +},{}],80:[function(require,module,exports){ exports.entityMap = { lt: '<', gt: '>', @@ -19009,7 +19491,7 @@ exports.entityMap = { diams: "♦" }; -},{}],79:[function(require,module,exports){ +},{}],81:[function(require,module,exports){ //[4] NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF] //[4a] NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040] //[5] Name ::= NameStartChar (NameChar)* diff --git a/lib/meta.js b/lib/meta.js index 213cb3b..20f0b0a 100644 --- a/lib/meta.js +++ b/lib/meta.js @@ -2,6 +2,6 @@ module.exports = (() => { 'use strict'; return { - version: '5.9.0' + version: '5.10.0' }; })(); \ No newline at end of file diff --git a/package.json b/package.json index 72d9e05..1d712d4 100644 --- a/package.json +++ b/package.json @@ -1,77 +1,77 @@ { - "name": "@barchart/marketdata-api-js", - "version": "5.9.0", - "description": "SDK for streaming market data from Barchart.com", - "author": { - "name": "Eero Pikat", - "email": "eero.pikat@barchart.com", - "url": "https://www.barchart.com" - }, - "contributors": [ - { - "name": "Mike Ehrenberg", - "email": "mike.ehrenberg@barchart.com", - "url": "https://www.barchart.com" + "name": "@barchart/marketdata-api-js", + "version": "5.10.0", + "description": "SDK for streaming market data from Barchart.com", + "author": { + "name": "Eero Pikat", + "email": "eero.pikat@barchart.com", + "url": "https://www.barchart.com" }, - { - "name": "Bryan Ingle", - "email": "bryan.ingle@barchart.com", - "url": "https://www.barchart.com" - } - ], - "scripts": { - "test": "gulp test", - "watch": "gulp watch", - "docs": "barchart-documentation serve" - }, - "repository": { - "type": "git", - "url": "git+ssh://github.com/barchart/marketdata-api-js.git" - }, - "keywords": [ - "Barchart", - "Market", - "Data", - "Realtime", - "Stocks", - "Futures", - "Forex" - ], - "dependencies": { - "@barchart/common-js": "^4.10.0", - "axios": "^0.21.1", - "ws": "7.4.6", - "xmldom": "0.6.0" - }, - "devDependencies": { - "@babel/core": "^7.6.2", - "aws-sdk": "^2.546.0", - "babelify": "^10.0.0", - "browserify": "^16.5.0", - "git-get-status": "^1.0.5", - "glob": "^6.0.1", - "gulp": "^4.0.2", - "gulp-awspublish": "^4.0.0", - "gulp-git": "^2.5.1", - "gulp-jasmine": "^2.2.1", - "gulp-jshint": "^2.1.0", - "gulp-prompt": "^1.2.0", - "gulp-rename": "^1.4.0", - "gulp-replace": "^0.5.4", - "jshint": "^2.10.3", - "log4js": "^4.4.0", - "merge-stream": "^2.0.0", - "uuid": "^8.3.2", - "vinyl-buffer": "^1.0.1", - "vinyl-source-stream": "^2.0.0" - }, - "browserify": { - "transform": [ - [ - "babelify" - ] - ] - }, - "license": "MIT", - "private": false + "contributors": [ + { + "name": "Mike Ehrenberg", + "email": "mike.ehrenberg@barchart.com", + "url": "https://www.barchart.com" + }, + { + "name": "Bryan Ingle", + "email": "bryan.ingle@barchart.com", + "url": "https://www.barchart.com" + } + ], + "scripts": { + "test": "gulp test", + "watch": "gulp watch", + "docs": "barchart-documentation serve" + }, + "repository": { + "type": "git", + "url": "git+ssh://github.com/barchart/marketdata-api-js.git" + }, + "keywords": [ + "Barchart", + "Market", + "Data", + "Realtime", + "Stocks", + "Futures", + "Forex" + ], + "dependencies": { + "@barchart/common-js": "^4.10.0", + "axios": "^0.21.1", + "ws": "7.4.6", + "xmldom": "0.6.0" + }, + "devDependencies": { + "@babel/core": "^7.6.2", + "aws-sdk": "^2.546.0", + "babelify": "^10.0.0", + "browserify": "^16.5.0", + "git-get-status": "^1.0.5", + "glob": "^6.0.1", + "gulp": "^4.0.2", + "gulp-awspublish": "^4.0.0", + "gulp-git": "^2.5.1", + "gulp-jasmine": "^2.2.1", + "gulp-jshint": "^2.1.0", + "gulp-prompt": "^1.2.0", + "gulp-rename": "^1.4.0", + "gulp-replace": "^0.5.4", + "jshint": "^2.10.3", + "log4js": "^4.4.0", + "merge-stream": "^2.0.0", + "uuid": "^8.3.2", + "vinyl-buffer": "^1.0.1", + "vinyl-source-stream": "^2.0.0" + }, + "browserify": { + "transform": [ + [ + "babelify" + ] + ] + }, + "license": "MIT", + "private": false } diff --git a/test/dist/barchart-marketdata-api-tests-5.js b/test/dist/barchart-marketdata-api-tests-5.js index c820dba..e366d3f 100644 --- a/test/dist/barchart-marketdata-api-tests-5.js +++ b/test/dist/barchart-marketdata-api-tests-5.js @@ -2324,7 +2324,7 @@ module.exports = (() => { return parseMessage; })(); -},{"./timestamp":24,"./value":25,"xmldom":37}],24:[function(require,module,exports){ +},{"./timestamp":24,"./value":25,"xmldom":38}],24:[function(require,module,exports){ module.exports = (() => { 'use strict'; /** @@ -2611,7 +2611,8 @@ module.exports = (() => { })(); },{"./../data/UnitCode":13,"@barchart/common-js/lang/is":32}],27:[function(require,module,exports){ -const is = require('@barchart/common-js/lang/is'); +const is = require('@barchart/common-js/lang/is'), + string = require('@barchart/common-js/lang/string'); module.exports = (() => { 'use strict'; @@ -2947,6 +2948,28 @@ module.exports = (() => { return formatted; } + /** + * Converts an abbreviated futures symbol (with a single digit year) into + * a futures symbol with a two digit year. If the symbol is not a futures + * contract, a null value is returned. + * + * @static + * @public + * @param {String} symbol + * @returns {String|null} + */ + + + static getFuturesExplicitFormat(symbol) { + let explicit = null; + + if (SymbolParser.getIsFuture(symbol) && SymbolParser.getIsConcrete(symbol)) { + const parsed = SymbolParser.parseInstrumentType(symbol); + explicit = `${parsed.root}${parsed.month}${string.padLeft(Math.floor(parsed.year % 100).toString(), 2, '0')}`; + } + + return explicit; + } /** * Returns true if prices for the symbol should be represented as a percentage; false * otherwise. @@ -3276,7 +3299,7 @@ module.exports = (() => { return SymbolParser; })(); -},{"@barchart/common-js/lang/is":32}],28:[function(require,module,exports){ +},{"@barchart/common-js/lang/is":32,"@barchart/common-js/lang/string":34}],28:[function(require,module,exports){ const assert = require('./assert'); module.exports = (() => { @@ -3538,7 +3561,7 @@ module.exports = (() => { return Timezones; })(); -},{"./Enum":28,"./is":32,"./timezone":34,"moment-timezone/builds/moment-timezone-with-data-2012-2022":35}],30:[function(require,module,exports){ +},{"./Enum":28,"./is":32,"./timezone":35,"moment-timezone/builds/moment-timezone-with-data-2012-2022":36}],30:[function(require,module,exports){ const assert = require('./assert'), is = require('./is'); @@ -3961,25 +3984,72 @@ module.exports = (() => { } else if (comparator(item, a[0]) < 0) { a.unshift(item); } else { - a.splice(binarySearch(a, item, comparator, 0, a.length - 1), 0, item); + a.splice(binarySearchForInsert(a, item, comparator, 0, a.length - 1), 0, item); } return a; + }, + + /** + * Performs a binary search to locate an item within an array. + * + * @param {*[]} a + * @param {*} key + * @param {Function} comparator + * @param {Number=} start + * @param {Number=} end + * @returns {*|null} + */ + binarySearch(a, key, comparator, start, end) { + assert.argumentIsArray(a, 'a'); + assert.argumentIsRequired(comparator, 'comparator', Function); + assert.argumentIsOptional(start, 'start', Number); + assert.argumentIsOptional(end, 'end', Number); + + if (a.length === 0) { + return null; + } + + return binarySearchForMatch(a, key, comparator, start || 0, end || a.length - 1); } }; - function binarySearch(array, item, comparator, start, end) { + function binarySearchForMatch(a, key, comparator, start, end) { + const size = end - start; + const midpointIndex = start + Math.floor(size / 2); + const midpointItem = a[midpointIndex]; + const comparison = comparator(key, midpointItem); + + if (comparison === 0) { + return midpointItem; + } else if (size < 2) { + const finalIndex = a.length - 1; + const finalItem = a[finalIndex]; + + if (end === finalIndex && comparator(key, finalItem) === 0) { + return finalItem; + } else { + return null; + } + } else if (comparison > 0) { + return binarySearchForMatch(a, key, comparator, midpointIndex, end); + } else { + return binarySearchForMatch(a, key, comparator, start, midpointIndex); + } + } + + function binarySearchForInsert(a, item, comparator, start, end) { const size = end - start; const midpointIndex = start + Math.floor(size / 2); - const midpointItem = array[midpointIndex]; + const midpointItem = a[midpointIndex]; const comparison = comparator(item, midpointItem) > 0; if (size < 2) { if (comparison > 0) { - const finalIndex = array.length - 1; + const finalIndex = a.length - 1; - if (end === finalIndex && comparator(item, array[finalIndex]) > 0) { + if (end === finalIndex && comparator(item, a[finalIndex]) > 0) { return end + 1; } else { return end; @@ -3988,9 +4058,9 @@ module.exports = (() => { return start; } } else if (comparison > 0) { - return binarySearch(array, item, comparator, midpointIndex, end); + return binarySearchForInsert(a, item, comparator, midpointIndex, end); } else { - return binarySearch(array, item, comparator, start, midpointIndex); + return binarySearchForInsert(a, item, comparator, start, midpointIndex); } } })(); @@ -4520,6 +4590,132 @@ module.exports = (() => { })(); },{"./array":30,"./is":32}],34:[function(require,module,exports){ +const assert = require('./assert'), + is = require('./is'); + +module.exports = (() => { + 'use strict'; + + const regex = {}; + regex.camel = {}; + regex.camel.violations = /\b[A-Z]/g; + /** + * Utility functions for strings. + * + * @public + * @module lang/string + */ + + return { + /** + * Adjusts a string, replacing the first character of each word with an uppercase + * character and all subsequent characters in the word with lowercase characters. + * + * @public + * @static + * @param {String} s + * @returns {String} + */ + startCase(s) { + return s.split(' ').reduce((phrase, word) => { + if (word.length !== 0) { + phrase.push(word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()); + } + + return phrase; + }, []).join(' '); + }, + + /** + * Adjust a string to use camel case, where the first letter of each word is replaced + * with a lower case character. + * + * @public + * @static + * @param {String} s + * @returns {String} + */ + camelCase(s) { + assert.argumentIsRequired(s, 's', String); + return s.replace(regex.camel.violations, m => m.toLocaleLowerCase()); + }, + + /** + * If a string exceeds a desired length, it is truncated and a poor man's + * ellipsis (i.e. three periods) is appended. Otherwise, the original + * string is returned. + * + * @public + * @static + * @param {String} s + * @param {Number} length + * @returns {String} + */ + truncate(s, length) { + if (is.string(s) && s.length > length) { + return s.substring(0, length) + ' ...'; + } else { + return s; + } + }, + + /** + * Adds leading characters to a string, until the string length is a desired size. + * + * @public + * @static + * @param {String} s - The string to pad. + * @param {Number} length - The desired overall length of the string. + * @param {String} character - The character to use for padding. + * @returns {String} + */ + padLeft(s, length, character) { + assert.argumentIsRequired(s, 's', String); + assert.argumentIsRequired(length, 'length', Number); + assert.argumentIsRequired(character, 'character', String); + + if (character.length !== 1) { + throw new Error('The "character" argument must be one character in length.'); + } + + return character.repeat(length - s.length) + s; + }, + + /** + * Performs a simple token replacement on a string; where the tokens + * are braced numbers (e.g. {0}, {1}, {2}). + * + * @public + * @static + * @param {String} s - The string to format (e.g. 'my first name is {0} and my last name is {1}') + * @param {Array} data - The replacement data + * @returns {String} + */ + format(s, ...data) { + assert.argumentIsRequired(s, 's', String); + return s.replace(/{(\d+)}/g, (match, i) => { + let replacement; + + if (i < data.length) { + const item = data[i]; + + if (!is.undefined(item) && !is.null(item)) { + replacement = item.toString(); + } else { + replacement = match; + } + } else { + replacement = match; + } + + return replacement; + }); + } + + }; +})(); + +},{"./assert":31,"./is":32}],35:[function(require,module,exports){ const moment = require('moment-timezone/builds/moment-timezone-with-data-2012-2022'), assert = require('./assert'); @@ -4574,7 +4770,7 @@ module.exports = (() => { }; })(); -},{"./assert":31,"moment-timezone/builds/moment-timezone-with-data-2012-2022":35}],35:[function(require,module,exports){ +},{"./assert":31,"moment-timezone/builds/moment-timezone-with-data-2012-2022":36}],36:[function(require,module,exports){ //! moment-timezone.js //! version : 0.5.26 //! Copyright (c) JS Foundation and other contributors @@ -5800,7 +5996,7 @@ module.exports = (() => { return moment; })); -},{"moment":36}],36:[function(require,module,exports){ +},{"moment":37}],37:[function(require,module,exports){ //! moment.js ;(function (global, factory) { @@ -10404,267 +10600,262 @@ module.exports = (() => { }))); -},{}],37:[function(require,module,exports){ -function DOMParser(options){ - this.options = options ||{locator:{}}; - -} -DOMParser.prototype.parseFromString = function(source,mimeType){ - var options = this.options; - var sax = new XMLReader(); - var domBuilder = options.domBuilder || new DOMHandler();//contentHandler and LexicalHandler - var errorHandler = options.errorHandler; - var locator = options.locator; - var defaultNSMap = options.xmlns||{}; - var entityMap = {'lt':'<','gt':'>','amp':'&','quot':'"','apos':"'"} - if(locator){ - domBuilder.setDocumentLocator(locator) - } - - sax.errorHandler = buildErrorHandler(errorHandler,domBuilder,locator); - sax.domBuilder = options.domBuilder || domBuilder; - if(/\/x?html?$/.test(mimeType)){ - entityMap.nbsp = '\xa0'; - entityMap.copy = '\xa9'; - defaultNSMap['']= 'http://www.w3.org/1999/xhtml'; - } - defaultNSMap.xml = defaultNSMap.xml || 'http://www.w3.org/XML/1998/namespace'; - if(source){ - sax.parse(source,defaultNSMap,entityMap); - }else{ - sax.errorHandler.error("invalid doc source"); - } - return domBuilder.doc; -} -function buildErrorHandler(errorImpl,domBuilder,locator){ - if(!errorImpl){ - if(domBuilder instanceof DOMHandler){ - return domBuilder; - } - errorImpl = domBuilder ; - } - var errorHandler = {} - var isCallback = errorImpl instanceof Function; - locator = locator||{} - function build(key){ - var fn = errorImpl[key]; - if(!fn && isCallback){ - fn = errorImpl.length == 2?function(msg){errorImpl(key,msg)}:errorImpl; - } - errorHandler[key] = fn && function(msg){ - fn('[xmldom '+key+']\t'+msg+_locator(locator)); - }||function(){}; - } - build('warning'); - build('error'); - build('fatalError'); - return errorHandler; -} - -//console.log('#\n\n\n\n\n\n\n####') -/** - * +ContentHandler+ErrorHandler - * +LexicalHandler+EntityResolver2 - * -DeclHandler-DTDHandler - * - * DefaultHandler:EntityResolver, DTDHandler, ContentHandler, ErrorHandler - * DefaultHandler2:DefaultHandler,LexicalHandler, DeclHandler, EntityResolver2 - * @link http://www.saxproject.org/apidoc/org/xml/sax/helpers/DefaultHandler.html - */ -function DOMHandler() { - this.cdata = false; -} -function position(locator,node){ - node.lineNumber = locator.lineNumber; - node.columnNumber = locator.columnNumber; -} -/** - * @see org.xml.sax.ContentHandler#startDocument - * @link http://www.saxproject.org/apidoc/org/xml/sax/ContentHandler.html - */ -DOMHandler.prototype = { - startDocument : function() { - this.doc = new DOMImplementation().createDocument(null, null, null); - if (this.locator) { - this.doc.documentURI = this.locator.systemId; - } - }, - startElement:function(namespaceURI, localName, qName, attrs) { - var doc = this.doc; - var el = doc.createElementNS(namespaceURI, qName||localName); - var len = attrs.length; - appendElement(this, el); - this.currentElement = el; - - this.locator && position(this.locator,el) - for (var i = 0 ; i < len; i++) { - var namespaceURI = attrs.getURI(i); - var value = attrs.getValue(i); - var qName = attrs.getQName(i); - var attr = doc.createAttributeNS(namespaceURI, qName); - this.locator &&position(attrs.getLocator(i),attr); - attr.value = attr.nodeValue = value; - el.setAttributeNode(attr) - } - }, - endElement:function(namespaceURI, localName, qName) { - var current = this.currentElement - var tagName = current.tagName; - this.currentElement = current.parentNode; - }, - startPrefixMapping:function(prefix, uri) { - }, - endPrefixMapping:function(prefix) { - }, - processingInstruction:function(target, data) { - var ins = this.doc.createProcessingInstruction(target, data); - this.locator && position(this.locator,ins) - appendElement(this, ins); - }, - ignorableWhitespace:function(ch, start, length) { - }, - characters:function(chars, start, length) { - chars = _toString.apply(this,arguments) - //console.log(chars) - if(chars){ - if (this.cdata) { - var charNode = this.doc.createCDATASection(chars); - } else { - var charNode = this.doc.createTextNode(chars); - } - if(this.currentElement){ - this.currentElement.appendChild(charNode); - }else if(/^\s*$/.test(chars)){ - this.doc.appendChild(charNode); - //process xml - } - this.locator && position(this.locator,charNode) - } - }, - skippedEntity:function(name) { - }, - endDocument:function() { - this.doc.normalize(); - }, - setDocumentLocator:function (locator) { - if(this.locator = locator){// && !('lineNumber' in locator)){ - locator.lineNumber = 0; - } - }, - //LexicalHandler - comment:function(chars, start, length) { - chars = _toString.apply(this,arguments) - var comm = this.doc.createComment(chars); - this.locator && position(this.locator,comm) - appendElement(this, comm); - }, - - startCDATA:function() { - //used in characters() methods - this.cdata = true; - }, - endCDATA:function() { - this.cdata = false; - }, - - startDTD:function(name, publicId, systemId) { - var impl = this.doc.implementation; - if (impl && impl.createDocumentType) { - var dt = impl.createDocumentType(name, publicId, systemId); - this.locator && position(this.locator,dt) - appendElement(this, dt); - } - }, - /** - * @see org.xml.sax.ErrorHandler - * @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html - */ - warning:function(error) { - console.warn('[xmldom warning]\t'+error,_locator(this.locator)); - }, - error:function(error) { - console.error('[xmldom error]\t'+error,_locator(this.locator)); - }, - fatalError:function(error) { - console.error('[xmldom fatalError]\t'+error,_locator(this.locator)); - throw error; - } -} -function _locator(l){ - if(l){ - return '\n@'+(l.systemId ||'')+'#[line:'+l.lineNumber+',col:'+l.columnNumber+']' - } -} -function _toString(chars,start,length){ - if(typeof chars == 'string'){ - return chars.substr(start,length) - }else{//java sax connect width xmldom on rhino(what about: "? && !(chars instanceof String)") - if(chars.length >= start+length || start){ - return new java.lang.String(chars,start,length)+''; - } - return chars; - } -} - -/* - * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/LexicalHandler.html - * used method of org.xml.sax.ext.LexicalHandler: - * #comment(chars, start, length) - * #startCDATA() - * #endCDATA() - * #startDTD(name, publicId, systemId) - * - * - * IGNORED method of org.xml.sax.ext.LexicalHandler: - * #endDTD() - * #startEntity(name) - * #endEntity(name) - * - * - * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/DeclHandler.html - * IGNORED method of org.xml.sax.ext.DeclHandler - * #attributeDecl(eName, aName, type, mode, value) - * #elementDecl(name, model) - * #externalEntityDecl(name, publicId, systemId) - * #internalEntityDecl(name, value) - * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/EntityResolver2.html - * IGNORED method of org.xml.sax.EntityResolver2 - * #resolveEntity(String name,String publicId,String baseURI,String systemId) - * #resolveEntity(publicId, systemId) - * #getExternalSubset(name, baseURI) - * @link http://www.saxproject.org/apidoc/org/xml/sax/DTDHandler.html - * IGNORED method of org.xml.sax.DTDHandler - * #notationDecl(name, publicId, systemId) {}; - * #unparsedEntityDecl(name, publicId, systemId, notationName) {}; - */ -"endDTD,startEntity,endEntity,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,resolveEntity,getExternalSubset,notationDecl,unparsedEntityDecl".replace(/\w+/g,function(key){ - DOMHandler.prototype[key] = function(){return null} -}) - -/* Private static helpers treated below as private instance methods, so don't need to add these to the public API; we might use a Relator to also get rid of non-standard public properties */ -function appendElement (hander,node) { - if (!hander.currentElement) { - hander.doc.appendChild(node); - } else { - hander.currentElement.appendChild(node); - } -}//appendChild and setAttributeNS are preformance key - -//if(typeof require == 'function'){ - var XMLReader = require('./sax').XMLReader; - var DOMImplementation = exports.DOMImplementation = require('./dom').DOMImplementation; - exports.XMLSerializer = require('./dom').XMLSerializer ; - exports.DOMParser = DOMParser; -//} - -},{"./dom":38,"./sax":39}],38:[function(require,module,exports){ +},{}],38:[function(require,module,exports){ +function DOMParser(options){ + this.options = options ||{locator:{}}; +} + +DOMParser.prototype.parseFromString = function(source,mimeType){ + var options = this.options; + var sax = new XMLReader(); + var domBuilder = options.domBuilder || new DOMHandler();//contentHandler and LexicalHandler + var errorHandler = options.errorHandler; + var locator = options.locator; + var defaultNSMap = options.xmlns||{}; + var isHTML = /\/x?html?$/.test(mimeType);//mimeType.toLowerCase().indexOf('html') > -1; + var entityMap = isHTML?htmlEntity.entityMap:{'lt':'<','gt':'>','amp':'&','quot':'"','apos':"'"}; + if(locator){ + domBuilder.setDocumentLocator(locator) + } + + sax.errorHandler = buildErrorHandler(errorHandler,domBuilder,locator); + sax.domBuilder = options.domBuilder || domBuilder; + if(isHTML){ + defaultNSMap['']= 'http://www.w3.org/1999/xhtml'; + } + defaultNSMap.xml = defaultNSMap.xml || 'http://www.w3.org/XML/1998/namespace'; + if(source && typeof source === 'string'){ + sax.parse(source,defaultNSMap,entityMap); + }else{ + sax.errorHandler.error("invalid doc source"); + } + return domBuilder.doc; +} +function buildErrorHandler(errorImpl,domBuilder,locator){ + if(!errorImpl){ + if(domBuilder instanceof DOMHandler){ + return domBuilder; + } + errorImpl = domBuilder ; + } + var errorHandler = {} + var isCallback = errorImpl instanceof Function; + locator = locator||{} + function build(key){ + var fn = errorImpl[key]; + if(!fn && isCallback){ + fn = errorImpl.length == 2?function(msg){errorImpl(key,msg)}:errorImpl; + } + errorHandler[key] = fn && function(msg){ + fn('[xmldom '+key+']\t'+msg+_locator(locator)); + }||function(){}; + } + build('warning'); + build('error'); + build('fatalError'); + return errorHandler; +} + +//console.log('#\n\n\n\n\n\n\n####') +/** + * +ContentHandler+ErrorHandler + * +LexicalHandler+EntityResolver2 + * -DeclHandler-DTDHandler + * + * DefaultHandler:EntityResolver, DTDHandler, ContentHandler, ErrorHandler + * DefaultHandler2:DefaultHandler,LexicalHandler, DeclHandler, EntityResolver2 + * @link http://www.saxproject.org/apidoc/org/xml/sax/helpers/DefaultHandler.html + */ +function DOMHandler() { + this.cdata = false; +} +function position(locator,node){ + node.lineNumber = locator.lineNumber; + node.columnNumber = locator.columnNumber; +} +/** + * @see org.xml.sax.ContentHandler#startDocument + * @link http://www.saxproject.org/apidoc/org/xml/sax/ContentHandler.html + */ +DOMHandler.prototype = { + startDocument : function() { + this.doc = new DOMImplementation().createDocument(null, null, null); + if (this.locator) { + this.doc.documentURI = this.locator.systemId; + } + }, + startElement:function(namespaceURI, localName, qName, attrs) { + var doc = this.doc; + var el = doc.createElementNS(namespaceURI, qName||localName); + var len = attrs.length; + appendElement(this, el); + this.currentElement = el; + + this.locator && position(this.locator,el) + for (var i = 0 ; i < len; i++) { + var namespaceURI = attrs.getURI(i); + var value = attrs.getValue(i); + var qName = attrs.getQName(i); + var attr = doc.createAttributeNS(namespaceURI, qName); + this.locator &&position(attrs.getLocator(i),attr); + attr.value = attr.nodeValue = value; + el.setAttributeNode(attr) + } + }, + endElement:function(namespaceURI, localName, qName) { + var current = this.currentElement + var tagName = current.tagName; + this.currentElement = current.parentNode; + }, + startPrefixMapping:function(prefix, uri) { + }, + endPrefixMapping:function(prefix) { + }, + processingInstruction:function(target, data) { + var ins = this.doc.createProcessingInstruction(target, data); + this.locator && position(this.locator,ins) + appendElement(this, ins); + }, + ignorableWhitespace:function(ch, start, length) { + }, + characters:function(chars, start, length) { + chars = _toString.apply(this,arguments) + //console.log(chars) + if(chars){ + if (this.cdata) { + var charNode = this.doc.createCDATASection(chars); + } else { + var charNode = this.doc.createTextNode(chars); + } + if(this.currentElement){ + this.currentElement.appendChild(charNode); + }else if(/^\s*$/.test(chars)){ + this.doc.appendChild(charNode); + //process xml + } + this.locator && position(this.locator,charNode) + } + }, + skippedEntity:function(name) { + }, + endDocument:function() { + this.doc.normalize(); + }, + setDocumentLocator:function (locator) { + if(this.locator = locator){// && !('lineNumber' in locator)){ + locator.lineNumber = 0; + } + }, + //LexicalHandler + comment:function(chars, start, length) { + chars = _toString.apply(this,arguments) + var comm = this.doc.createComment(chars); + this.locator && position(this.locator,comm) + appendElement(this, comm); + }, + + startCDATA:function() { + //used in characters() methods + this.cdata = true; + }, + endCDATA:function() { + this.cdata = false; + }, + + startDTD:function(name, publicId, systemId) { + var impl = this.doc.implementation; + if (impl && impl.createDocumentType) { + var dt = impl.createDocumentType(name, publicId, systemId); + this.locator && position(this.locator,dt) + appendElement(this, dt); + } + }, + /** + * @see org.xml.sax.ErrorHandler + * @link http://www.saxproject.org/apidoc/org/xml/sax/ErrorHandler.html + */ + warning:function(error) { + console.warn('[xmldom warning]\t'+error,_locator(this.locator)); + }, + error:function(error) { + console.error('[xmldom error]\t'+error,_locator(this.locator)); + }, + fatalError:function(error) { + throw new ParseError(error, this.locator); + } +} +function _locator(l){ + if(l){ + return '\n@'+(l.systemId ||'')+'#[line:'+l.lineNumber+',col:'+l.columnNumber+']' + } +} +function _toString(chars,start,length){ + if(typeof chars == 'string'){ + return chars.substr(start,length) + }else{//java sax connect width xmldom on rhino(what about: "? && !(chars instanceof String)") + if(chars.length >= start+length || start){ + return new java.lang.String(chars,start,length)+''; + } + return chars; + } +} + /* - * DOM Level 2 - * Object DOMException - * @see http://www.w3.org/TR/REC-DOM-Level-1/ecma-script-language-binding.html - * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ecma-script-binding.html + * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/LexicalHandler.html + * used method of org.xml.sax.ext.LexicalHandler: + * #comment(chars, start, length) + * #startCDATA() + * #endCDATA() + * #startDTD(name, publicId, systemId) + * + * + * IGNORED method of org.xml.sax.ext.LexicalHandler: + * #endDTD() + * #startEntity(name) + * #endEntity(name) + * + * + * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/DeclHandler.html + * IGNORED method of org.xml.sax.ext.DeclHandler + * #attributeDecl(eName, aName, type, mode, value) + * #elementDecl(name, model) + * #externalEntityDecl(name, publicId, systemId) + * #internalEntityDecl(name, value) + * @link http://www.saxproject.org/apidoc/org/xml/sax/ext/EntityResolver2.html + * IGNORED method of org.xml.sax.EntityResolver2 + * #resolveEntity(String name,String publicId,String baseURI,String systemId) + * #resolveEntity(publicId, systemId) + * #getExternalSubset(name, baseURI) + * @link http://www.saxproject.org/apidoc/org/xml/sax/DTDHandler.html + * IGNORED method of org.xml.sax.DTDHandler + * #notationDecl(name, publicId, systemId) {}; + * #unparsedEntityDecl(name, publicId, systemId, notationName) {}; */ +"endDTD,startEntity,endEntity,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,resolveEntity,getExternalSubset,notationDecl,unparsedEntityDecl".replace(/\w+/g,function(key){ + DOMHandler.prototype[key] = function(){return null} +}) + +/* Private static helpers treated below as private instance methods, so don't need to add these to the public API; we might use a Relator to also get rid of non-standard public properties */ +function appendElement (hander,node) { + if (!hander.currentElement) { + hander.doc.appendChild(node); + } else { + hander.currentElement.appendChild(node); + } +}//appendChild and setAttributeNS are preformance key + +//if(typeof require == 'function'){ +var htmlEntity = require('./entities'); +var sax = require('./sax'); +var XMLReader = sax.XMLReader; +var ParseError = sax.ParseError; +var DOMImplementation = exports.DOMImplementation = require('./dom').DOMImplementation; +exports.XMLSerializer = require('./dom').XMLSerializer ; +exports.DOMParser = DOMParser; +exports.__DOMHandler = DOMHandler; +//} +},{"./dom":39,"./entities":40,"./sax":41}],39:[function(require,module,exports){ function copy(src,dest){ for(var p in src){ dest[p] = src[p]; @@ -10676,10 +10867,6 @@ function copy(src,dest){ */ function _extends(Class,Super){ var pt = Class.prototype; - if(Object.create){ - var ppt = Object.create(Super.prototype) - pt.__proto__ = ppt; - } if(!(pt instanceof Super)){ function t(){}; t.prototype = Super.prototype; @@ -10730,7 +10917,12 @@ var INVALID_MODIFICATION_ERR = ExceptionCode.INVALID_MODIFICATION_ERR = ((Exce var NAMESPACE_ERR = ExceptionCode.NAMESPACE_ERR = ((ExceptionMessage[14]="Invalid namespace"),14); var INVALID_ACCESS_ERR = ExceptionCode.INVALID_ACCESS_ERR = ((ExceptionMessage[15]="Invalid access"),15); - +/** + * DOM Level 2 + * Object DOMException + * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/ecma-script-binding.html + * @see http://www.w3.org/TR/REC-DOM-Level-1/ecma-script-language-binding.html + */ function DOMException(code, message) { if(message instanceof Error){ var error = message; @@ -11272,6 +11464,21 @@ Document.prototype = { return rtv; }, + getElementsByClassName: function(className) { + var pattern = new RegExp("(^|\\s)" + className + "(\\s|$)"); + return new LiveNodeList(this, function(base) { + var ls = []; + _visitNode(base.documentElement, function(node) { + if(node !== base && node.nodeType == ELEMENT_NODE) { + if(pattern.test(node.getAttribute('class'))) { + ls.push(node); + } + } + }); + return ls; + }); + }, + //document factory method: createElement : function(tagName){ var node = new Element(); @@ -11576,7 +11783,7 @@ XMLSerializer.prototype.serializeToString = function(node,isHtml,nodeFilter){ Node.prototype.toString = nodeSerializeToString; function nodeSerializeToString(isHtml,nodeFilter){ var buf = []; - var refNode = this.nodeType == 9?this.documentElement:this; + var refNode = this.nodeType == 9 && this.documentElement || this; var prefix = refNode.prefix; var uri = refNode.namespaceURI; @@ -11675,9 +11882,13 @@ function serializeToString(node,buf,isHTML,nodeFilter,visibleNamespaces){ if (needNamespaceDefine(node,isHTML, visibleNamespaces)) { var prefix = node.prefix||''; var uri = node.namespaceURI; - var ns = prefix ? ' xmlns:' + prefix : " xmlns"; - buf.push(ns, '="' , uri , '"'); - visibleNamespaces.push({ prefix: prefix, namespace:uri }); + if (uri) { + // Avoid empty namespace value like xmlns:ds="" + // Empty namespace URL will we produce an invalid XML document + var ns = prefix ? ' xmlns:' + prefix : " xmlns"; + buf.push(ns, '="' , uri , '"'); + visibleNamespaces.push({ prefix: prefix, namespace:uri }); + } } if(child || isHTML && !/^(?:meta|link|img|br|hr|input)$/i.test(nodeName)){ @@ -11715,9 +11926,33 @@ function serializeToString(node,buf,isHTML,nodeFilter,visibleNamespaces){ } return; case ATTRIBUTE_NODE: - return buf.push(' ',node.name,'="',node.value.replace(/[<&"]/g,_xmlEncoder),'"'); + /** + * Well-formedness constraint: No < in Attribute Values + * The replacement text of any entity referred to directly or indirectly in an attribute value must not contain a <. + * @see https://www.w3.org/TR/xml/#CleanAttrVals + * @see https://www.w3.org/TR/xml/#NT-AttValue + */ + return buf.push(' ', node.name, '="', node.value.replace(/[<&"]/g,_xmlEncoder), '"'); case TEXT_NODE: - return buf.push(node.data.replace(/[<&]/g,_xmlEncoder)); + /** + * The ampersand character (&) and the left angle bracket (<) must not appear in their literal form, + * except when used as markup delimiters, or within a comment, a processing instruction, or a CDATA section. + * If they are needed elsewhere, they must be escaped using either numeric character references or the strings + * `&` and `<` respectively. + * The right angle bracket (>) may be represented using the string " > ", and must, for compatibility, + * be escaped using either `>` or a character reference when it appears in the string `]]>` in content, + * when that string is not marking the end of a CDATA section. + * + * In the content of elements, character data is any string of characters + * which does not contain the start-delimiter of any markup + * and does not include the CDATA-section-close delimiter, `]]>`. + * + * @see https://www.w3.org/TR/xml/#NT-CharData + */ + return buf.push(node.data + .replace(/[<&]/g,_xmlEncoder) + .replace(/]]>/g, ']]>') + ); case CDATA_SECTION_NODE: return buf.push( ''); case COMMENT_NODE: @@ -11727,13 +11962,13 @@ function serializeToString(node,buf,isHTML,nodeFilter,visibleNamespaces){ var sysid = node.systemId; buf.push(''); + buf.push('>'); }else if(sysid && sysid!='.'){ - buf.push(' SYSTEM "',sysid,'">'); + buf.push(' SYSTEM ', sysid, '>'); }else{ var sub = node.internalSubset; if(sub){ @@ -11899,646 +12134,902 @@ try{ } //if(typeof require == 'function'){ + exports.Node = Node; + exports.DOMException = DOMException; exports.DOMImplementation = DOMImplementation; exports.XMLSerializer = XMLSerializer; //} -},{}],39:[function(require,module,exports){ -//[4] NameStartChar ::= ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6] | [#xF8-#x2FF] | [#x370-#x37D] | [#x37F-#x1FFF] | [#x200C-#x200D] | [#x2070-#x218F] | [#x2C00-#x2FEF] | [#x3001-#xD7FF] | [#xF900-#xFDCF] | [#xFDF0-#xFFFD] | [#x10000-#xEFFFF] -//[4a] NameChar ::= NameStartChar | "-" | "." | [0-9] | #xB7 | [#x0300-#x036F] | [#x203F-#x2040] -//[5] Name ::= NameStartChar (NameChar)* -var nameStartChar = /[A-Z_a-z\xC0-\xD6\xD8-\xF6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD]///\u10000-\uEFFFF -var nameChar = new RegExp("[\\-\\.0-9"+nameStartChar.source.slice(1,-1)+"\\u00B7\\u0300-\\u036F\\u203F-\\u2040]"); -var tagNamePattern = new RegExp('^'+nameStartChar.source+nameChar.source+'*(?:\:'+nameStartChar.source+nameChar.source+'*)?$'); -//var tagNamePattern = /^[a-zA-Z_][\w\-\.]*(?:\:[a-zA-Z_][\w\-\.]*)?$/ -//var handlers = 'resolveEntity,getExternalSubset,characters,endDocument,endElement,endPrefixMapping,ignorableWhitespace,processingInstruction,setDocumentLocator,skippedEntity,startDocument,startElement,startPrefixMapping,notationDecl,unparsedEntityDecl,error,fatalError,warning,attributeDecl,elementDecl,externalEntityDecl,internalEntityDecl,comment,endCDATA,endDTD,endEntity,startCDATA,startDTD,startEntity'.split(',') - -//S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE -//S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE -var S_TAG = 0;//tag name offerring -var S_ATTR = 1;//attr name offerring -var S_ATTR_SPACE=2;//attr name end and space offer -var S_EQ = 3;//=space? -var S_ATTR_NOQUOT_VALUE = 4;//attr value(no quot value only) -var S_ATTR_END = 5;//attr value end and no space(quot end) -var S_TAG_SPACE = 6;//(attr value end || tag end ) && (space offer) -var S_TAG_CLOSE = 7;//closed el - -function XMLReader(){ - -} - -XMLReader.prototype = { - parse:function(source,defaultNSMap,entityMap){ - var domBuilder = this.domBuilder; - domBuilder.startDocument(); - _copy(defaultNSMap ,defaultNSMap = {}) - parse(source,defaultNSMap,entityMap, - domBuilder,this.errorHandler); - domBuilder.endDocument(); - } -} -function parse(source,defaultNSMapCopy,entityMap,domBuilder,errorHandler){ - function fixedFromCharCode(code) { - // String.prototype.fromCharCode does not supports - // > 2 bytes unicode chars directly - if (code > 0xffff) { - code -= 0x10000; - var surrogate1 = 0xd800 + (code >> 10) - , surrogate2 = 0xdc00 + (code & 0x3ff); - - return String.fromCharCode(surrogate1, surrogate2); - } else { - return String.fromCharCode(code); - } - } - function entityReplacer(a){ - var k = a.slice(1,-1); - if(k in entityMap){ - return entityMap[k]; - }else if(k.charAt(0) === '#'){ - return fixedFromCharCode(parseInt(k.substr(1).replace('x','0x'))) - }else{ - errorHandler.error('entity not found:'+a); - return a; - } - } - function appendText(end){//has some bugs - if(end>start){ - var xt = source.substring(start,end).replace(/&#?\w+;/g,entityReplacer); - locator&&position(start); - domBuilder.characters(xt,0,end-start); - start = end - } - } - function position(p,m){ - while(p>=lineEnd && (m = linePattern.exec(source))){ - lineStart = m.index; - lineEnd = lineStart + m[0].length; - locator.lineNumber++; - //console.log('line++:',locator,startPos,endPos) - } - locator.columnNumber = p-lineStart+1; - } - var lineStart = 0; - var lineEnd = 0; - var linePattern = /.*(?:\r\n?|\n)|.*$/g - var locator = domBuilder.locator; - - var parseStack = [{currentNSMap:defaultNSMapCopy}] - var closeMap = {}; - var start = 0; - while(true){ - try{ - var tagStart = source.indexOf('<',start); - if(tagStart<0){ - if(!source.substr(start).match(/^\s*$/)){ - var doc = domBuilder.doc; - var text = doc.createTextNode(source.substr(start)); - doc.appendChild(text); - domBuilder.currentElement = text; - } - return; - } - if(tagStart>start){ - appendText(tagStart); - } - switch(source.charAt(tagStart+1)){ - case '/': - var end = source.indexOf('>',tagStart+3); - var tagName = source.substring(tagStart+2,end); - var config = parseStack.pop(); - if(end<0){ - - tagName = source.substring(tagStart+2).replace(/[\s<].*/,''); - //console.error('#@@@@@@'+tagName) - errorHandler.error("end tag name: "+tagName+' is not complete:'+config.tagName); - end = tagStart+1+tagName.length; - }else if(tagName.match(/\s - locator&&position(tagStart); - end = parseInstruction(source,tagStart,domBuilder); - break; - case '!':// start){ - start = end; - }else{ - //TODO: 这里有可能sax回退,有位置错误风险 - appendText(Math.max(tagStart,start)+1); - } - } -} -function copyLocator(f,t){ - t.lineNumber = f.lineNumber; - t.columnNumber = f.columnNumber; - return t; -} - -/** - * @see #appendElement(source,elStartEnd,el,selfClosed,entityReplacer,domBuilder,parseStack); - * @return end of the elementStartPart(end of elementEndPart for selfClosed el) - */ -function parseElementStartPart(source,start,el,currentNSMap,entityReplacer,errorHandler){ - var attrName; - var value; - var p = ++start; - var s = S_TAG;//status - while(true){ - var c = source.charAt(p); - switch(c){ - case '=': - if(s === S_ATTR){//attrName - attrName = source.slice(start,p); - s = S_EQ; - }else if(s === S_ATTR_SPACE){ - s = S_EQ; - }else{ - //fatalError: equal must after attrName or space after attrName - throw new Error('attribute equal must after attrName'); - } - break; - case '\'': - case '"': - if(s === S_EQ || s === S_ATTR //|| s == S_ATTR_SPACE - ){//equal - if(s === S_ATTR){ - errorHandler.warning('attribute value must after "="') - attrName = source.slice(start,p) - } - start = p+1; - p = source.indexOf(c,start) - if(p>0){ - value = source.slice(start,p).replace(/&#?\w+;/g,entityReplacer); - el.add(attrName,value,start-1); - s = S_ATTR_END; - }else{ - //fatalError: no end quot match - throw new Error('attribute value no end \''+c+'\' match'); - } - }else if(s == S_ATTR_NOQUOT_VALUE){ - value = source.slice(start,p).replace(/&#?\w+;/g,entityReplacer); - //console.log(attrName,value,start,p) - el.add(attrName,value,start); - //console.dir(el) - errorHandler.warning('attribute "'+attrName+'" missed start quot('+c+')!!'); - start = p+1; - s = S_ATTR_END - }else{ - //fatalError: no equal before - throw new Error('attribute value must after "="'); - } - break; - case '/': - switch(s){ - case S_TAG: - el.setTagName(source.slice(start,p)); - case S_ATTR_END: - case S_TAG_SPACE: - case S_TAG_CLOSE: - s =S_TAG_CLOSE; - el.closed = true; - case S_ATTR_NOQUOT_VALUE: - case S_ATTR: - case S_ATTR_SPACE: - break; - //case S_EQ: - default: - throw new Error("attribute invalid close char('/')") - } - break; - case ''://end document - //throw new Error('unexpected end of input') - errorHandler.error('unexpected end of input'); - if(s == S_TAG){ - el.setTagName(source.slice(start,p)); - } - return p; - case '>': - switch(s){ - case S_TAG: - el.setTagName(source.slice(start,p)); - case S_ATTR_END: - case S_TAG_SPACE: - case S_TAG_CLOSE: - break;//normal - case S_ATTR_NOQUOT_VALUE://Compatible state - case S_ATTR: - value = source.slice(start,p); - if(value.slice(-1) === '/'){ - el.closed = true; - value = value.slice(0,-1) - } - case S_ATTR_SPACE: - if(s === S_ATTR_SPACE){ - value = attrName; - } - if(s == S_ATTR_NOQUOT_VALUE){ - errorHandler.warning('attribute "'+value+'" missed quot(")!!'); - el.add(attrName,value.replace(/&#?\w+;/g,entityReplacer),start) - }else{ - if(currentNSMap[''] !== 'http://www.w3.org/1999/xhtml' || !value.match(/^(?:disabled|checked|selected)$/i)){ - errorHandler.warning('attribute "'+value+'" missed value!! "'+value+'" instead!!') - } - el.add(value,value,start) - } - break; - case S_EQ: - throw new Error('attribute value missed!!'); - } -// console.log(tagName,tagNamePattern,tagNamePattern.test(tagName)) - return p; - /*xml space '\x20' | #x9 | #xD | #xA; */ - case '\u0080': - c = ' '; - default: - if(c<= ' '){//space - switch(s){ - case S_TAG: - el.setTagName(source.slice(start,p));//tagName - s = S_TAG_SPACE; - break; - case S_ATTR: - attrName = source.slice(start,p) - s = S_ATTR_SPACE; - break; - case S_ATTR_NOQUOT_VALUE: - var value = source.slice(start,p).replace(/&#?\w+;/g,entityReplacer); - errorHandler.warning('attribute "'+value+'" missed quot(")!!'); - el.add(attrName,value,start) - case S_ATTR_END: - s = S_TAG_SPACE; - break; - //case S_TAG_SPACE: - //case S_EQ: - //case S_ATTR_SPACE: - // void();break; - //case S_TAG_CLOSE: - //ignore warning - } - }else{//not space -//S_TAG, S_ATTR, S_EQ, S_ATTR_NOQUOT_VALUE -//S_ATTR_SPACE, S_ATTR_END, S_TAG_SPACE, S_TAG_CLOSE - switch(s){ - //case S_TAG:void();break; - //case S_ATTR:void();break; - //case S_ATTR_NOQUOT_VALUE:void();break; - case S_ATTR_SPACE: - var tagName = el.tagName; - if(currentNSMap[''] !== 'http://www.w3.org/1999/xhtml' || !attrName.match(/^(?:disabled|checked|selected)$/i)){ - errorHandler.warning('attribute "'+attrName+'" missed value!! "'+attrName+'" instead2!!') - } - el.add(attrName,attrName,start); - start = p; - s = S_ATTR; - break; - case S_ATTR_END: - errorHandler.warning('attribute space is required"'+attrName+'"!!') - case S_TAG_SPACE: - s = S_ATTR; - start = p; - break; - case S_EQ: - s = S_ATTR_NOQUOT_VALUE; - start = p; - break; - case S_TAG_CLOSE: - throw new Error("elements closed character '/' and '>' must be connected to"); - } - } - }//end outer switch - //console.log('p++',p) - p++; - } -} -/** - * @return true if has new namespace define - */ -function appendElement(el,domBuilder,currentNSMap){ - var tagName = el.tagName; - var localNSMap = null; - //var currentNSMap = parseStack[parseStack.length-1].currentNSMap; - var i = el.length; - while(i--){ - var a = el[i]; - var qName = a.qName; - var value = a.value; - var nsp = qName.indexOf(':'); - if(nsp>0){ - var prefix = a.prefix = qName.slice(0,nsp); - var localName = qName.slice(nsp+1); - var nsPrefix = prefix === 'xmlns' && localName - }else{ - localName = qName; - prefix = null - nsPrefix = qName === 'xmlns' && '' - } - //can not set prefix,because prefix !== '' - a.localName = localName ; - //prefix == null for no ns prefix attribute - if(nsPrefix !== false){//hack!! - if(localNSMap == null){ - localNSMap = {} - //console.log(currentNSMap,0) - _copy(currentNSMap,currentNSMap={}) - //console.log(currentNSMap,1) - } - currentNSMap[nsPrefix] = localNSMap[nsPrefix] = value; - a.uri = 'http://www.w3.org/2000/xmlns/' - domBuilder.startPrefixMapping(nsPrefix, value) - } - } - var i = el.length; - while(i--){ - a = el[i]; - var prefix = a.prefix; - if(prefix){//no prefix attribute has no namespace - if(prefix === 'xml'){ - a.uri = 'http://www.w3.org/XML/1998/namespace'; - }if(prefix !== 'xmlns'){ - a.uri = currentNSMap[prefix || ''] - - //{console.log('###'+a.qName,domBuilder.locator.systemId+'',currentNSMap,a.uri)} - } - } - } - var nsp = tagName.indexOf(':'); - if(nsp>0){ - prefix = el.prefix = tagName.slice(0,nsp); - localName = el.localName = tagName.slice(nsp+1); - }else{ - prefix = null;//important!! - localName = el.localName = tagName; - } - //no prefix element has default namespace - var ns = el.uri = currentNSMap[prefix || '']; - domBuilder.startElement(ns,localName,tagName,el); - //endPrefixMapping and startPrefixMapping have not any help for dom builder - //localNSMap = null - if(el.closed){ - domBuilder.endElement(ns,localName,tagName); - if(localNSMap){ - for(prefix in localNSMap){ - domBuilder.endPrefixMapping(prefix) - } - } - }else{ - el.currentNSMap = currentNSMap; - el.localNSMap = localNSMap; - //parseStack.push(el); - return true; - } -} -function parseHtmlSpecialContent(source,elStartEnd,tagName,entityReplacer,domBuilder){ - if(/^(?:script|textarea)$/i.test(tagName)){ - var elEndStart = source.indexOf('',elStartEnd); - var text = source.substring(elStartEnd+1,elEndStart); - if(/[&<]/.test(text)){ - if(/^script$/i.test(tagName)){ - //if(!/\]\]>/.test(text)){ - //lexHandler.startCDATA(); - domBuilder.characters(text,0,text.length); - //lexHandler.endCDATA(); - return elEndStart; - //} - }//}else{//text area - text = text.replace(/&#?\w+;/g,entityReplacer); - domBuilder.characters(text,0,text.length); - return elEndStart; - //} - - } - } - return elStartEnd+1; -} -function fixSelfClosed(source,elStartEnd,tagName,closeMap){ - //if(tagName in closeMap){ - var pos = closeMap[tagName]; - if(pos == null){ - //console.log(tagName) - pos = source.lastIndexOf('') - if(pos',start+4); - //append comment source.substring(4,end)//',start+4); + //append comment source.substring(4,end)//