diff --git a/adapters/.gitignore b/adapters/.gitignore new file mode 100644 index 00000000..607eaf6e --- /dev/null +++ b/adapters/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +*.log +node_modules +_bak +package-lock.json \ No newline at end of file diff --git a/adapters/README.md b/adapters/README.md new file mode 100644 index 00000000..e09af797 --- /dev/null +++ b/adapters/README.md @@ -0,0 +1 @@ +# adapters \ No newline at end of file diff --git a/adapters/common/cache-manager.js b/adapters/common/cache-manager.js new file mode 100644 index 00000000..86699a0a --- /dev/null +++ b/adapters/common/cache-manager.js @@ -0,0 +1,288 @@ +/**************************************************************************** + Copyright (c) 2019 Xiamen Yaji Software Co., Ltd. + + https://www.cocos.com/ + + Permission is hereby granted, free of charge, to any person obtaining a copy + of cache-manager software and associated engine source code (the "Software"), a limited, + worldwide, royalty-free, non-assignable, revocable and non-exclusive license + to use Cocos Creator solely to develop games on your target platforms. You shall + not use Cocos Creator software for developing other software or tools that's + used for developing games. You are not granted to publish, distribute, + sublicense, and/or sell copies of Cocos Creator. + + The software or tools in cache-manager License Agreement are licensed, not sold. + Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + ****************************************************************************/ +const { getUserDataPath, readJsonSync, makeDirSync, writeFileSync, copyFile, downloadFile, writeFile, deleteFile, rmdirSync, unzip, isOutOfStorage } = window.fsUtils; + +var checkNextPeriod = false; +var writeCacheFileList = null; +var startWrite = false; +var nextCallbacks = []; +var callbacks = []; +var cleaning = false; +var suffix = 0; +const REGEX = /^https?:\/\/.*/; + +var cacheManager = { + + cacheDir: 'gamecaches', + + cachedFileName: 'cacheList.json', + + // whether or not cache asset into user's storage space + cacheEnabled: true, + + // whether or not auto clear cache when storage ran out + autoClear: true, + + // cache one per cycle + cacheInterval: 500, + + deleteInterval: 500, + + writeFileInterval: 2000, + + // whether or not storage space has run out + outOfStorage: false, + + tempFiles: null, + + cachedFiles: null, + + cacheQueue: {}, + + version: '1.0', + + getCache (url) { + return this.cachedFiles.has(url) ? this.cachedFiles.get(url).url : ''; + }, + + getTemp (url) { + return this.tempFiles.has(url) ? this.tempFiles.get(url) : ''; + }, + + init () { + this.cacheDir = getUserDataPath() + '/' + this.cacheDir; + var cacheFilePath = this.cacheDir + '/' + this.cachedFileName; + var result = readJsonSync(cacheFilePath); + if (result instanceof Error || !result.version) { + if (!(result instanceof Error)) rmdirSync(this.cacheDir, true); + this.cachedFiles = new cc.AssetManager.Cache(); + makeDirSync(this.cacheDir, true); + writeFileSync(cacheFilePath, JSON.stringify({ files: this.cachedFiles._map, version: this.version }), 'utf8'); + } + else { + this.cachedFiles = new cc.AssetManager.Cache(result.files); + } + this.tempFiles = new cc.AssetManager.Cache(); + }, + + updateLastTime (url) { + if (this.cachedFiles.has(url)) { + var cache = this.cachedFiles.get(url); + cache.lastTime = Date.now(); + } + }, + + _write () { + writeCacheFileList = null; + startWrite = true; + writeFile(this.cacheDir + '/' + this.cachedFileName, JSON.stringify({ files: this.cachedFiles._map, version: this.version }), 'utf8', function () { + startWrite = false; + for (let i = 0, j = callbacks.length; i < j; i++) { + callbacks[i](); + } + callbacks.length = 0; + callbacks.push.apply(callbacks, nextCallbacks); + nextCallbacks.length = 0; + }); + }, + + writeCacheFile (cb) { + if (!writeCacheFileList) { + writeCacheFileList = setTimeout(this._write.bind(this), this.writeFileInterval); + if (startWrite === true) { + cb && nextCallbacks.push(cb); + } + else { + cb && callbacks.push(cb); + } + } else { + cb && callbacks.push(cb); + } + }, + + _cache () { + var self = this; + for (var id in this.cacheQueue) { + var { srcUrl, isCopy, cacheBundleRoot } = this.cacheQueue[id]; + var time = Date.now().toString(); + + var localPath = ''; + + if (cacheBundleRoot) { + localPath = `${this.cacheDir}/${cacheBundleRoot}/${time}${suffix++}${cc.path.extname(id)}`; + } + else { + localPath = `${this.cacheDir}/${time}${suffix++}${cc.path.extname(id)}`; + } + + function callback (err) { + checkNextPeriod = false; + if (err) { + if (isOutOfStorage(err.message)) { + self.outOfStorage = true; + self.autoClear && self.clearLRU(); + return; + } + } else { + self.cachedFiles.add(id, { bundle: cacheBundleRoot, url: localPath, lastTime: time }); + delete self.cacheQueue[id]; + self.writeCacheFile(); + } + if (!cc.js.isEmptyObject(self.cacheQueue)) { + checkNextPeriod = true; + setTimeout(self._cache.bind(self), self.cacheInterval); + } + } + if (!isCopy) { + downloadFile(srcUrl, localPath, null, callback); + } + else { + copyFile(srcUrl, localPath, callback); + } + return; + } + checkNextPeriod = false; + }, + + cacheFile (id, srcUrl, cacheEnabled, cacheBundleRoot, isCopy) { + cacheEnabled = cacheEnabled !== undefined ? cacheEnabled : this.cacheEnabled; + if (!cacheEnabled || this.cacheQueue[id] || this.cachedFiles.has(id)) return; + + this.cacheQueue[id] = { srcUrl, cacheBundleRoot, isCopy }; + if (!checkNextPeriod) { + checkNextPeriod = true; + if (!this.outOfStorage) { + setTimeout(this._cache.bind(this), this.cacheInterval); + } + else { + checkNextPeriod = false; + } + } + }, + + clearCache () { + rmdirSync(this.cacheDir, true); + this.cachedFiles = new cc.AssetManager.Cache(); + makeDirSync(this.cacheDir, true); + var cacheFilePath = this.cacheDir + '/' + this.cachedFileName; + this.outOfStorage = false; + writeFileSync(cacheFilePath, JSON.stringify({ files: this.cachedFiles._map, version: this.version }), 'utf8'); + cc.assetManager.bundles.forEach(bundle => { + if (REGEX.test(bundle.base)) this.makeBundleFolder(bundle.name); + }); + }, + + clearLRU () { + if (cleaning) return; + cleaning = true; + var caches = []; + var self = this; + this.cachedFiles.forEach(function (val, key) { + if (val.bundle === 'internal') return; + if (self._isZipFile(key) && cc.assetManager.bundles.find(bundle => bundle.base.indexOf(val.url) !== -1)) return; + caches.push({ originUrl: key, url: val.url, lastTime: val.lastTime }); + }); + caches.sort(function (a, b) { + return a.lastTime - b.lastTime; + }); + caches.length = Math.floor(caches.length / 3); + if (caches.length === 0) return; + for (var i = 0, l = caches.length; i < l; i++) { + this.cachedFiles.remove(caches[i].originUrl); + } + + this.writeCacheFile(function () { + function deferredDelete () { + var item = caches.pop(); + if (self._isZipFile(item.originUrl)) { + rmdirSync(item.url, true); + self._deleteFileCB(); + } + else { + deleteFile(item.url, self._deleteFileCB.bind(self)); + } + if (caches.length > 0) { + setTimeout(deferredDelete, self.deleteInterval); + } + else { + cleaning = false; + } + } + setTimeout(deferredDelete, self.deleteInterval); + }); + + }, + + removeCache (url) { + if (this.cachedFiles.has(url)) { + var self = this; + var path = this.cachedFiles.remove(url).url; + this.writeCacheFile(function () { + if (self._isZipFile(url)) { + rmdirSync(path, true); + self._deleteFileCB(); + } + else { + deleteFile(path, self._deleteFileCB.bind(self)); + } + }); + } + }, + + _deleteFileCB (err) { + if (!err) this.outOfStorage = false; + }, + + makeBundleFolder (bundleName) { + makeDirSync(this.cacheDir + '/' + bundleName, true); + }, + + unzipAndCacheBundle (id, zipFilePath, cacheBundleRoot, onComplete) { + let time = Date.now().toString(); + let targetPath = `${this.cacheDir}/${cacheBundleRoot}/${time}${suffix++}`; + let self = this; + makeDirSync(targetPath, true); + unzip(zipFilePath, targetPath, function (err) { + if (err) { + rmdirSync(targetPath, true); + if (isOutOfStorage(err.message)) { + self.outOfStorage = true; + self.autoClear && self.clearLRU(); + } + onComplete && onComplete(err); + return; + } + self.cachedFiles.add(id, { bundle: cacheBundleRoot, url: targetPath, lastTime: time }); + self.writeCacheFile(); + onComplete && onComplete(null, targetPath); + }); + }, + + _isZipFile (url) { + return url.slice(-4) === '.zip'; + }, +}; + +cc.assetManager.cacheManager = module.exports = cacheManager; \ No newline at end of file diff --git a/adapters/common/engine/AssetManager.js b/adapters/common/engine/AssetManager.js new file mode 100644 index 00000000..f849c900 --- /dev/null +++ b/adapters/common/engine/AssetManager.js @@ -0,0 +1,471 @@ +const cacheManager = require('../cache-manager'); +const { fs, downloadFile, readText, readArrayBuffer, readJson, loadSubpackage, getUserDataPath, exists } = window.fsUtils; + +const REGEX = /^https?:\/\/.*/; +const cachedSubpackageList = {}; +const downloader = cc.assetManager.downloader; +const parser = cc.assetManager.parser; +const presets = cc.assetManager.presets; +const isSubDomain = __globalAdapter.isSubContext; +downloader.maxConcurrency = 8; +downloader.maxRequestsPerFrame = 64; +presets['scene'].maxConcurrency = 10; +presets['scene'].maxRequestsPerFrame = 64; + +let SUBCONTEXT_ROOT, REMOTE_SERVER_ROOT; +let subpackages = {}, remoteBundles = {}; + +function downloadScript (url, options, onComplete) { + if (typeof options === 'function') { + onComplete = options; + options = null; + } + if (REGEX.test(url)) { + onComplete && onComplete(new Error('Can not load remote scripts')); + } + else { + __cocos_require__(url); + onComplete && onComplete(null); + } +} + +function handleZip (url, options, onComplete) { + let cachedUnzip = cacheManager.cachedFiles.get(url); + if (cachedUnzip) { + cacheManager.updateLastTime(url); + onComplete && onComplete(null, cachedUnzip.url); + } + else if (REGEX.test(url)) { + downloadFile(url, null, options.header, options.onFileProgress, function (err, downloadedZipPath) { + if (err) { + onComplete && onComplete(err); + return; + } + cacheManager.unzipAndCacheBundle(url, downloadedZipPath, options.__cacheBundleRoot__, onComplete); + }); + } + else { + cacheManager.unzipAndCacheBundle(url, url, options.__cacheBundleRoot__, onComplete); + } +} + +function downloadDomAudio (url, options, onComplete) { + if (typeof options === 'function') { + onComplete = options; + options = null; + } + + let dom; + let sys = cc.sys; + if (sys.platform === sys.TAOBAO || sys.platform === sys.TAOBAO_MINIGAME) { + dom = window.document.createElement('audio'); + } else { + dom = document.createElement('audio'); + } + dom.src = url; + + // HACK: wechat does not callback when load large number of assets + onComplete && onComplete(null, dom); +} + +function download (url, func, options, onFileProgress, onComplete) { + var result = transformUrl(url, options); + if (result.inLocal) { + func(result.url, options, onComplete); + } + else if (result.inCache) { + cacheManager.updateLastTime(url); + func(result.url, options, function (err, data) { + if (err) { + cacheManager.removeCache(url); + } + onComplete(err, data); + }); + } + else { + downloadFile(url, null, options.header, onFileProgress, function (err, path) { + if (err) { + onComplete(err, null); + return; + } + func(path, options, function (err, data) { + if (!err) { + cacheManager.tempFiles.add(url, path); + cacheManager.cacheFile(url, path, options.cacheEnabled, options.__cacheBundleRoot__, true); + } + onComplete(err, data); + }); + }); + } +} + +function parseArrayBuffer (url, options, onComplete) { + readArrayBuffer(url, onComplete); +} + +function parseText (url, options, onComplete) { + readText(url, onComplete); +} + +function parseJson (url, options, onComplete) { + readJson(url, onComplete); +} + +function downloadText (url, options, onComplete) { + download(url, parseText, options, options.onFileProgress, onComplete); +} + +var downloadJson = !isSubDomain ? function (url, options, onComplete) { + download(url, parseJson, options, options.onFileProgress, onComplete); +} : function (url, options, onComplete) { + var { url } = transformUrl(url, options); + url = url.slice(SUBCONTEXT_ROOT.length + 1); // remove subcontext root in url + var content = __cocos_require__(cc.path.changeExtname(url, '.js')); + onComplete && onComplete(null, content); +} + +var loadFont = !isSubDomain ? function (url, options, onComplete) { + var fontFamily = __globalAdapter.loadFont(url); + onComplete(null, fontFamily || 'Arial'); +} : function (url, options, onComplete) { + onComplete(null, 'Arial'); +} + +function doNothing (content, options, onComplete) { + exists(content, (existence) => { + if (existence) { + onComplete(null, content); + } else { + onComplete(new Error(`file ${content} does not exist!`)); + } + }); +} + +function downloadAsset (url, options, onComplete) { + download(url, doNothing, options, options.onFileProgress, onComplete); +} + +function subdomainTransformUrl (url, options, onComplete) { + var { url } = transformUrl(url, options); + onComplete(null, url); +} + +function downloadBundle (nameOrUrl, options, onComplete) { + let bundleName = cc.path.basename(nameOrUrl); + var version = options.version || cc.assetManager.downloader.bundleVers[bundleName]; + const suffix = version ? `${version}.` : ''; + + function getConfigPathForSubPackage () { + let sys = cc.sys; + if (sys.platform === sys.TAOBAO_MINIGAME) { + return `${bundleName}/config.${suffix}json`; + } + return `subpackages/${bundleName}/config.${suffix}json`; + } + + function appendBaseToJsonData (data) { + if (!data) return; + + let sys = cc.sys; + if (sys.platform === sys.TAOBAO_MINIGAME) { + data.base = `${bundleName}/`; + } else { + data.base = `subpackages/${bundleName}/`; + } + } + + if (subpackages[bundleName]) { + var config = getConfigPathForSubPackage(); + let loadedCb = function () { + downloadJson(config, options, function (err, data) { + appendBaseToJsonData(data); + onComplete(err, data); + }); + }; + if (cachedSubpackageList[bundleName]) { + return loadedCb(); + } + loadSubpackage(bundleName, options.onFileProgress, function (err) { + if (err) { + onComplete(err, null); + return; + } + cachedSubpackageList[bundleName] = true; + loadedCb(); + }); + } + else { + let js, url; + if (REGEX.test(nameOrUrl) || (!isSubDomain && nameOrUrl.startsWith(getUserDataPath()))) { + url = nameOrUrl; + js = `src/scripts/${bundleName}/index.js`; + cacheManager.makeBundleFolder(bundleName); + } + else { + if (remoteBundles[bundleName]) { + url = `${REMOTE_SERVER_ROOT}remote/${bundleName}`; + js = `src/scripts/${bundleName}/index.js`; + cacheManager.makeBundleFolder(bundleName); + } + else { + url = `assets/${bundleName}`; + js = `assets/${bundleName}/index.js`; + } + } + __cocos_require__(js); + options.__cacheBundleRoot__ = bundleName; + var config = `${url}/config.${version ? version + '.' : ''}json`; + downloadJson(config, options, function (err, data) { + if (err) { + onComplete && onComplete(err); + return; + } + if (data.isZip) { + let zipVersion = data.zipVersion; + let zipUrl = `${url}/res.${zipVersion ? zipVersion + '.' : ''}zip`; + handleZip(zipUrl, options, function (err, unzipPath) { + if (err) { + onComplete && onComplete(err); + return; + } + data.base = unzipPath + '/res/'; + // PATCH: for android alipay version before v10.1.95 (v10.1.95 included) + // to remove in the future + let sys = cc.sys; + if (sys.platform === sys.ALIPAY_GAME && sys.os === sys.OS_ANDROID) { + let resPath = unzipPath + 'res/'; + if (fs.accessSync({path: resPath}).success) { + data.base = resPath; + } + } + onComplete && onComplete(null, data); + }); + } + else { + data.base = url + '/'; + onComplete && onComplete(null, data); + } + }); + } +}; + +const originParsePVRTex = parser.parsePVRTex; +let parsePVRTex = function (file, options, onComplete) { + readArrayBuffer(file, function (err, data) { + if (err) return onComplete(err); + originParsePVRTex(data, options, onComplete); + }); +}; + +const originParseASTCTex = parser.parseASTCTex; +let parseASTCTex = function (file, options, onComplete) { + readArrayBuffer(file, function (err, data) { + if (err) return onComplete(err); + originParseASTCTex(data, options, onComplete); + }); +}; + +const originParsePKMTex = parser.parsePKMTex; +let parsePKMTex = function (file, options, onComplete) { + readArrayBuffer(file, function (err, data) { + if (err) return onComplete(err); + originParsePKMTex(data, options, onComplete); + }); +}; + +function parsePlist (url, options, onComplete) { + readText(url, function (err, file) { + var result = null; + if (!err) { + result = cc.plistParser.parse(file); + if (!result) err = new Error('parse failed'); + } + onComplete && onComplete(err, result); + }); +} + +let downloadImage = isSubDomain ? subdomainTransformUrl : downloadAsset; +downloader.downloadDomAudio = downloadDomAudio; +downloader.downloadScript = downloadScript; +parser.parsePVRTex = parsePVRTex; +parser.parsePKMTex = parsePKMTex; +parser.parseASTCTex = parseASTCTex; + +downloader.register({ + '.js' : downloadScript, + + // Audio + '.mp3' : downloadAsset, + '.ogg' : downloadAsset, + '.wav' : downloadAsset, + '.m4a' : downloadAsset, + + // Image + '.png' : downloadImage, + '.jpg' : downloadImage, + '.bmp' : downloadImage, + '.jpeg' : downloadImage, + '.gif' : downloadImage, + '.ico' : downloadImage, + '.tiff' : downloadImage, + '.image' : downloadImage, + '.webp' : downloadImage, + '.pvr': downloadAsset, + '.pkm': downloadAsset, + '.astc': downloadAsset, + + '.font': downloadAsset, + '.eot': downloadAsset, + '.ttf': downloadAsset, + '.woff': downloadAsset, + '.svg': downloadAsset, + '.ttc': downloadAsset, + + // Txt + '.txt' : downloadAsset, + '.xml' : downloadAsset, + '.vsh' : downloadAsset, + '.fsh' : downloadAsset, + '.atlas' : downloadAsset, + + '.tmx' : downloadAsset, + '.tsx' : downloadAsset, + '.plist' : downloadAsset, + '.fnt' : downloadAsset, + + '.json' : downloadJson, + '.ExportJson' : downloadAsset, + + '.binary' : downloadAsset, + '.bin': downloadAsset, + '.dbbin': downloadAsset, + '.skel': downloadAsset, + + '.mp4': downloadAsset, + '.avi': downloadAsset, + '.mov': downloadAsset, + '.mpg': downloadAsset, + '.mpeg': downloadAsset, + '.rm': downloadAsset, + '.rmvb': downloadAsset, + + 'bundle': downloadBundle, + + 'default': downloadText, +}); + +parser.register({ + '.png' : downloader.downloadDomImage, + '.jpg' : downloader.downloadDomImage, + '.bmp' : downloader.downloadDomImage, + '.jpeg' : downloader.downloadDomImage, + '.gif' : downloader.downloadDomImage, + '.ico' : downloader.downloadDomImage, + '.tiff' : downloader.downloadDomImage, + '.image' : downloader.downloadDomImage, + '.webp' : downloader.downloadDomImage, + '.pvr': parsePVRTex, + '.pkm': parsePKMTex, + '.astc': parseASTCTex, + + '.font': loadFont, + '.eot': loadFont, + '.ttf': loadFont, + '.woff': loadFont, + '.svg': loadFont, + '.ttc': loadFont, + + // Audio + '.mp3' : downloadDomAudio, + '.ogg' : downloadDomAudio, + '.wav' : downloadDomAudio, + '.m4a' : downloadDomAudio, + + // Txt + '.txt' : parseText, + '.xml' : parseText, + '.vsh' : parseText, + '.fsh' : parseText, + '.atlas' : parseText, + + '.tmx' : parseText, + '.tsx' : parseText, + '.fnt' : parseText, + '.plist' : parsePlist, + + '.binary' : parseArrayBuffer, + '.bin': parseArrayBuffer, + '.dbbin': parseArrayBuffer, + '.skel': parseArrayBuffer, + + '.ExportJson' : parseJson, +}); + +var transformUrl = !isSubDomain ? function (url, options) { + var inLocal = false; + var inCache = false; + var isInUserDataPath = url.startsWith(getUserDataPath()); + if (isInUserDataPath) { + inLocal = true; + } + else if (REGEX.test(url)) { + if (!options.reload) { + var cache = cacheManager.cachedFiles.get(url); + if (cache) { + inCache = true; + url = cache.url; + } + else { + var tempUrl = cacheManager.tempFiles.get(url); + if (tempUrl) { + inLocal = true; + url = tempUrl; + } + } + } + } + else { + inLocal = true; + } + return { url, inLocal, inCache }; +} : function (url, options) { + if (!REGEX.test(url)) { + url = SUBCONTEXT_ROOT + '/' + url; + } + return { url }; +} + +if (!isSubDomain) { + cc.assetManager.transformPipeline.append(function (task) { + var input = task.output = task.input; + for (var i = 0, l = input.length; i < l; i++) { + var item = input[i]; + var options = item.options; + if (!item.config) { + if (item.ext === 'bundle') continue; + options.cacheEnabled = options.cacheEnabled !== undefined ? options.cacheEnabled : false; + } + else { + options.__cacheBundleRoot__ = item.config.name; + } + } + }); + + var originInit = cc.assetManager.init; + cc.assetManager.init = function (options) { + originInit.call(cc.assetManager, options); + options.subpackages && options.subpackages.forEach(x => subpackages[x] = 'subpackages/' + x); + options.remoteBundles && options.remoteBundles.forEach(x => remoteBundles[x] = true); + REMOTE_SERVER_ROOT = options.server || ''; + if (REMOTE_SERVER_ROOT && !REMOTE_SERVER_ROOT.endsWith('/')) REMOTE_SERVER_ROOT += '/'; + cacheManager.init(); + }; +} +else { + var originInit = cc.assetManager.init; + cc.assetManager.init = function (options) { + originInit.call(cc.assetManager, options); + SUBCONTEXT_ROOT = options.subContextRoot || ''; + }; +} + diff --git a/adapters/common/engine/Audio.js b/adapters/common/engine/Audio.js new file mode 100644 index 00000000..60f22698 --- /dev/null +++ b/adapters/common/engine/Audio.js @@ -0,0 +1,69 @@ +const Audio = cc._Audio; + +if (Audio) { + let originGetDuration = Audio.prototype.getDuration; + Object.assign(Audio.prototype, { + _createElement () { + let elem = this._src._nativeAsset; + // Reuse dom audio element + if (!this._element) { + this._element = __globalAdapter.createInnerAudioContext(); + } + this._element.src = elem.src; + }, + + destroy () { + if (this._element) { + this._element.destroy(); + this._element = null; + } + }, + + setCurrentTime (num) { + let self = this; + this._src && this._src._ensureLoaded(function () { + self._element.seek(num); + }); + }, + + stop () { + let self = this; + this._src && this._src._ensureLoaded(function () { + // HACK: some platforms won't set currentTime to 0 when stop audio + self._element.seek(0); + self._element.stop(); + self._unbindEnded(); + self.emit('stop'); + self._state = Audio.State.STOPPED; + }); + }, + + _bindEnded () { + let elem = this._element; + if (elem && elem.onEnded && !this._onended._binded) { + this._onended._binded = true; + elem.onEnded(this._onended); + } + }, + + _unbindEnded () { + let elem = this._element; + if (elem && elem.offEnded && this._onended._binded) { + this._onended._binded = false; + elem.offEnded && elem.offEnded(this._onended); + } + }, + + getDuration () { + let duration = originGetDuration.call(this); + // HACK: in mini game, if dynamicly load audio, can't get duration from audioClip + // because duration is not coming from audio deserialization + duration = duration || (this._element ? this._element.duration : 0); + return duration; + }, + + // adapt some special operations on web platform + _touchToPlay () { }, + _forceUpdatingState () { }, + }); +} diff --git a/adapters/common/engine/AudioEngine.js b/adapters/common/engine/AudioEngine.js new file mode 100644 index 00000000..60ff52c6 --- /dev/null +++ b/adapters/common/engine/AudioEngine.js @@ -0,0 +1,3 @@ +if (cc && cc.audioEngine) { + cc.audioEngine._maxAudioInstance = 10; +} \ No newline at end of file diff --git a/adapters/common/engine/DeviceMotionEvent.js b/adapters/common/engine/DeviceMotionEvent.js new file mode 100644 index 00000000..2e4750d5 --- /dev/null +++ b/adapters/common/engine/DeviceMotionEvent.js @@ -0,0 +1,37 @@ + +const inputManager = cc.internal.inputManager; +const globalAdapter = window.__globalAdapter; + +Object.assign(inputManager, { + setAccelerometerEnabled (isEnable) { + let scheduler = cc.director.getScheduler(); + scheduler.enableForTarget(this); + if (isEnable) { + this._registerAccelerometerEvent(); + scheduler.scheduleUpdate(this); + } + else { + this._unregisterAccelerometerEvent(); + scheduler.unscheduleUpdate(this); + } + }, + + // No need to adapt + // setAccelerometerInterval (interval) { }, + + _registerAccelerometerEvent () { + this._accelCurTime = 0; + let self = this; + this._acceleration = new cc.Acceleration(); + globalAdapter.startAccelerometer(function (res) { + self._acceleration.x = res.x; + self._acceleration.y = res.y; + self._acceleration.z = res.y; + }); + }, + + _unregisterAccelerometerEvent () { + this._accelCurTime = 0; + globalAdapter.stopAccelerometer(); + }, +}); diff --git a/adapters/common/engine/Editbox.js b/adapters/common/engine/Editbox.js new file mode 100644 index 00000000..55f9017e --- /dev/null +++ b/adapters/common/engine/Editbox.js @@ -0,0 +1,177 @@ +(function () { + if (!(cc && cc.EditBox)) { + return; + } + + const EditBox = cc.EditBox; + const js = cc.js; + const KeyboardReturnType = EditBox.KeyboardReturnType; + const MAX_VALUE = 65535; + const KEYBOARD_HIDE_TIME = 600; + let _hideKeyboardTimeout = null; + let _currentEditBoxImpl = null; + + function getKeyboardReturnType (type) { + switch (type) { + case KeyboardReturnType.DEFAULT: + case KeyboardReturnType.DONE: + return 'done'; + case KeyboardReturnType.SEND: + return 'send'; + case KeyboardReturnType.SEARCH: + return 'search'; + case KeyboardReturnType.GO: + return 'go'; + case KeyboardReturnType.NEXT: + return 'next'; + } + return 'done'; + } + + const BaseClass = EditBox._ImplClass; + function MiniGameEditBoxImpl () { + BaseClass.call(this); + + this._eventListeners = { + onKeyboardInput: null, + onKeyboardConfirm: null, + onKeyboardComplete: null, + }; + } + + js.extend(MiniGameEditBoxImpl, BaseClass); + EditBox._ImplClass = MiniGameEditBoxImpl; + + Object.assign(MiniGameEditBoxImpl.prototype, { + init (delegate) { + if (!delegate) { + cc.error('EditBox init failed'); + return; + } + this._delegate = delegate; + }, + + beginEditing () { + // In case multiply register events + if (this._editing) { + return; + } + this._ensureKeyboardHide(() => { + let delegate = this._delegate; + this._showKeyboard(); + this._registerKeyboardEvent(); + this._editing = true; + _currentEditBoxImpl = this; + delegate.editBoxEditingDidBegan(); + }); + }, + + endEditing () { + this._hideKeyboard(); + let cbs = this._eventListeners; + cbs.onKeyboardComplete && cbs.onKeyboardComplete(); + }, + + _registerKeyboardEvent () { + let self = this; + let delegate = this._delegate; + let cbs = this._eventListeners; + + cbs.onKeyboardInput = function (res) { + if (delegate._string !== res.value) { + delegate.editBoxTextChanged(res.value); + } + } + + cbs.onKeyboardConfirm = function (res) { + delegate.editBoxEditingReturn(); + let cbs = self._eventListeners; + cbs.onKeyboardComplete && cbs.onKeyboardComplete(res); + } + + cbs.onKeyboardComplete = function (res) { + self._editing = false; + _currentEditBoxImpl = null; + self._unregisterKeyboardEvent(); + if (res && res.value && delegate._string !== res.value) { + delegate.editBoxTextChanged(res.value); + } + delegate.editBoxEditingDidEnded(); + } + + __globalAdapter.onKeyboardInput(cbs.onKeyboardInput); + __globalAdapter.onKeyboardConfirm(cbs.onKeyboardConfirm); + __globalAdapter.onKeyboardComplete(cbs.onKeyboardComplete); + }, + + _unregisterKeyboardEvent () { + let cbs = this._eventListeners; + + if (cbs.onKeyboardInput) { + __globalAdapter.offKeyboardInput(cbs.onKeyboardInput); + cbs.onKeyboardInput = null; + } + if (cbs.onKeyboardConfirm) { + __globalAdapter.offKeyboardConfirm(cbs.onKeyboardConfirm); + cbs.onKeyboardConfirm = null; + } + if (cbs.onKeyboardComplete) { + __globalAdapter.offKeyboardComplete(cbs.onKeyboardComplete); + cbs.onKeyboardComplete = null; + } + }, + + _otherEditing () { + return !!_currentEditBoxImpl && _currentEditBoxImpl !== this && _currentEditBoxImpl._editing; + }, + + _ensureKeyboardHide (cb) { + let otherEditing = this._otherEditing(); + if (!otherEditing && !_hideKeyboardTimeout) { + return cb(); + } + if (_hideKeyboardTimeout) { + clearTimeout(_hideKeyboardTimeout); + } + if (otherEditing) { + _currentEditBoxImpl.endEditing(); + } + _hideKeyboardTimeout = setTimeout(() => { + _hideKeyboardTimeout = null; + cb(); + }, KEYBOARD_HIDE_TIME); + }, + + _showKeyboard () { + let delegate = this._delegate; + let multiline = (delegate.inputMode === EditBox.InputMode.ANY); + let maxLength = (delegate.maxLength < 0 ? MAX_VALUE : delegate.maxLength); + + __globalAdapter.showKeyboard({ + defaultValue: delegate._string, + maxLength: maxLength, + multiple: multiline, + confirmHold: false, + confirmType: getKeyboardReturnType(delegate.returnType), + success (res) { + + }, + fail (res) { + cc.warn(res.errMsg); + } + }); + }, + + _hideKeyboard () { + __globalAdapter.hideKeyboard({ + success (res) { + + }, + fail (res) { + cc.warn(res.errMsg); + }, + }); + }, + }); +})(); + diff --git a/adapters/common/engine/Game.js b/adapters/common/engine/Game.js new file mode 100644 index 00000000..e4ff3369 --- /dev/null +++ b/adapters/common/engine/Game.js @@ -0,0 +1,165 @@ +const inputManager = cc.internal.inputManager; +const renderer = cc.renderer; +const game = cc.game; +const dynamicAtlasManager = cc.dynamicAtlasManager; + +let originRun = game.run; +Object.assign(game, { + _banRunningMainLoop: __globalAdapter.isSubContext, + _firstSceneLaunched: false, + + run () { + cc.director.once(cc.Director.EVENT_AFTER_SCENE_LAUNCH, () => { + this._firstSceneLaunched = true; + }); + originRun.apply(this, arguments); + }, + + setFrameRate (frameRate) { + this.config.frameRate = frameRate; + if (__globalAdapter.setPreferredFramesPerSecond) { + __globalAdapter.setPreferredFramesPerSecond(frameRate); + } + else { + if (this._intervalId) { + window.cancelAnimFrame(this._intervalId); + } + this._intervalId = 0; + this._paused = true; + this._setAnimFrame(); + this._runMainLoop(); + } + }, + + _runMainLoop () { + if (this._banRunningMainLoop) { + return; + } + var self = this, callback, config = self.config, + director = cc.director, + skip = true, frameRate = config.frameRate; + + cc.debug.setDisplayStats(config.showFPS); + + callback = function () { + if (!self._paused) { + self._intervalId = window.requestAnimFrame(callback); + if (frameRate === 30 && !__globalAdapter.setPreferredFramesPerSecond) { + skip = !skip; + if (skip) { + return; + } + } + director.mainLoop(); + } + }; + + self._intervalId = window.requestAnimFrame(callback); + self._paused = false; + }, + + _initRenderer () { + // Avoid setup to be called twice. + if (this._rendererInitialized) return; + + // frame and container are useless on minigame platform + let sys = cc.sys; + if (sys.platform === sys.TAOBAO || sys.platform === sys.TAOBAO_MINIGAME) { + this.frame = this.container = window.document.createElement("DIV"); + } else { + this.frame = this.container = document.createElement("DIV"); + } + + let localCanvas; + if (__globalAdapter.isSubContext) { + localCanvas = window.sharedCanvas || __globalAdapter.getSharedCanvas(); + } + else if (sys.platform === sys.TAOBAO || sys.platform === sys.TAOBAO_MINIGAME) { + localCanvas = window.canvas; + } + else { + localCanvas = canvas; + } + this.canvas = localCanvas; + + this._determineRenderType(); + // WebGL context created successfully + if (this.renderType === this.RENDER_TYPE_WEBGL) { + var opts = { + 'stencil': true, + // MSAA is causing serious performance dropdown on some browsers. + 'antialias': cc.macro.ENABLE_WEBGL_ANTIALIAS, + 'alpha': cc.macro.ENABLE_TRANSPARENT_CANVAS, + 'preserveDrawingBuffer': false, + }; + renderer.initWebGL(localCanvas, opts); + this._renderContext = renderer.device._gl; + + // Enable dynamic atlas manager by default + if (!cc.macro.CLEANUP_IMAGE_CACHE && dynamicAtlasManager) { + dynamicAtlasManager.enabled = true; + } + } + if (!this._renderContext) { + this.renderType = this.RENDER_TYPE_CANVAS; + // Could be ignored by module settings + renderer.initCanvas(localCanvas); + this._renderContext = renderer.device._ctx; + } + + this._rendererInitialized = true; + }, + + _initEvents () { + let sys = cc.sys; + // register system events + if (this.config.registerSystemEvent) { + inputManager.registerSystemEvent(this.canvas); + } + + var hidden = false; + + function onHidden() { + if (!hidden) { + hidden = true; + game.emit(game.EVENT_HIDE); + } + } + + function onShown(res) { + if (hidden) { + hidden = false; + if (game.renderType === game.RENDER_TYPE_WEBGL) { + game._renderContext.finish(); + } + game.emit(game.EVENT_SHOW, res); + } + } + + // NOTE: onAudioInterruptionEnd and onAudioInterruptionBegin on ByteDance platform is not designed to behave the same as the ones on WeChat platform, + // the callback is invoked on game show or hide on ByteDance platform, while is not invoked on WeChat platform. + // See the docs on WeChat: https://developers.weixin.qq.com/minigame/dev/api/base/app/app-event/wx.onAudioInterruptionBegin.html + if (sys.platform !== sys.BYTEDANCE_GAME) { + __globalAdapter.onAudioInterruptionEnd && __globalAdapter.onAudioInterruptionEnd(function () { + if (cc.audioEngine) cc.audioEngine._restore(); + + }); + __globalAdapter.onAudioInterruptionBegin && __globalAdapter.onAudioInterruptionBegin(function () { + if (cc.audioEngine) cc.audioEngine._break(); + }); + } + + // Maybe not support in open data context + __globalAdapter.onShow && __globalAdapter.onShow(onShown); + __globalAdapter.onHide && __globalAdapter.onHide(onHidden); + + this.on(game.EVENT_HIDE, function () { + game.pause(); + }); + this.on(game.EVENT_SHOW, function () { + game.resume(); + }); + }, + + end () { }, // mini game platform not support this api +}); diff --git a/adapters/common/engine/InputManager.js b/adapters/common/engine/InputManager.js new file mode 100644 index 00000000..3480d337 --- /dev/null +++ b/adapters/common/engine/InputManager.js @@ -0,0 +1,42 @@ +const mgr = cc.internal.inputManager; +const canvasPosition = { + left: 0, + top: 0, + width: window.innerWidth, + height: window.innerHeight +}; + +if (mgr) { + Object.assign(mgr, { + _updateCanvasBoundingRect () {}, + + registerSystemEvent (element) { + if(this._isRegisterEvent) return; + + this._glView = cc.view; + let self = this; + + //register touch event + let _touchEventsMap = { + onTouchStart: this.handleTouchesBegin, + onTouchMove: this.handleTouchesMove, + onTouchEnd: this.handleTouchesEnd, + onTouchCancel: this.handleTouchesCancel, + }; + + let registerTouchEvent = function (eventName) { + let handler = _touchEventsMap[eventName]; + __globalAdapter[eventName](function (event) { + if (!event.changedTouches) return; + handler.call(self, self.getTouchesByEvent(event, canvasPosition)); + }); + }; + + for (let eventName in _touchEventsMap) { + registerTouchEvent(eventName); + } + + this._isRegisterEvent = true; + }, + }); +} \ No newline at end of file diff --git a/adapters/common/engine/Screen.js b/adapters/common/engine/Screen.js new file mode 100644 index 00000000..02abd858 --- /dev/null +++ b/adapters/common/engine/Screen.js @@ -0,0 +1,5 @@ +Object.assign(cc.screen, { + autoFullScreen: function (element, onFullScreenChange) { + // Not support on mini game + } +}); \ No newline at end of file diff --git a/adapters/common/engine/Texture2D.js b/adapters/common/engine/Texture2D.js new file mode 100644 index 00000000..e5a1d3b4 --- /dev/null +++ b/adapters/common/engine/Texture2D.js @@ -0,0 +1,12 @@ +const Texture2D = cc.Texture2D; + +if (Texture2D) { + Object.assign(Texture2D.prototype, { + initWithElement (element) { + if (!element) + return; + this._image = element; + this.handleLoadedTexture(); + }, + }); +} diff --git a/adapters/common/engine/globalAdapter/BaseSystemInfo.js b/adapters/common/engine/globalAdapter/BaseSystemInfo.js new file mode 100644 index 00000000..73a8e683 --- /dev/null +++ b/adapters/common/engine/globalAdapter/BaseSystemInfo.js @@ -0,0 +1,67 @@ +function adaptSys (sys, env) { + if (!env) { + env = __globalAdapter.getSystemInfoSync(); + } + + var language = env.language || ''; + var system = env.system || 'iOS'; + var platform = env.platform || 'iOS'; + + sys.isNative = false; + sys.isBrowser = false; + sys.isMobile = true; + sys.language = language.substr(0, 2); + sys.languageCode = language.toLowerCase(); + + platform = platform.toLowerCase(); + if (platform === "android") { + sys.os = sys.OS_ANDROID; + } + else if (platform === "ios") { + sys.os = sys.OS_IOS; + } + + system = system.toLowerCase(); + // Adaptation to Android P + if (system === 'android p') { + system = 'android p 9.0'; + } + + var version = /[\d\.]+/.exec(system); + sys.osVersion = version ? version[0] : system; + sys.osMainVersion = parseInt(sys.osVersion); + sys.browserType = null; + sys.browserVersion = null; + + var w = env.windowWidth; + var h = env.windowHeight; + var ratio = env.pixelRatio || 1; + sys.windowPixelResolution = { + width: ratio * w, + height: ratio * h + }; + + sys.localStorage = window.localStorage; + + var _supportWebGL = __globalAdapter.isSubContext ? false : true;; + var _supportWebp = false; + try { + var _canvas = document.createElement("canvas"); + _supportWebp = _canvas.toDataURL('image/webp').startsWith('data:image/webp'); + } + catch (err) { } + + sys.capabilities = { + "canvas": true, + "opengl": !!_supportWebGL, + "webp": _supportWebp + }; + sys.__audioSupport = { + ONLY_ONE: false, + WEB_AUDIO: false, + DELAY_CREATE_CTX: false, + format: ['.mp3'] + }; +} + +module.exports = adaptSys; diff --git a/adapters/common/engine/globalAdapter/ContainerStrategy.js b/adapters/common/engine/globalAdapter/ContainerStrategy.js new file mode 100644 index 00000000..365b09f4 --- /dev/null +++ b/adapters/common/engine/globalAdapter/ContainerStrategy.js @@ -0,0 +1,25 @@ +function adaptContainerStrategy (containerStrategyProto) { + containerStrategyProto._setupContainer = function (view, width, height) { + // Setup pixel ratio for retina display + var devicePixelRatio = view._devicePixelRatio = 1; + if (view.isRetinaEnabled()) { + devicePixelRatio = view._devicePixelRatio = Math.min(view._maxPixelRatio, window.devicePixelRatio || 1); + } + // size of sharedCanvas is readonly in subContext + if (__globalAdapter.isSubContext) { + return; + } + let locCanvas = cc.game.canvas; + // Setup canvas + width *= devicePixelRatio; + height *= devicePixelRatio; + // FIX: black screen on Baidu platform + // reset canvas size may call gl.clear(), especially when you call cc.director.loadScene() + if (locCanvas.width !== width || locCanvas.height !== height) { + locCanvas.width = width; + locCanvas.height = height; + } + }; +} + +module.exports = adaptContainerStrategy; \ No newline at end of file diff --git a/adapters/common/engine/globalAdapter/View.js b/adapters/common/engine/globalAdapter/View.js new file mode 100644 index 00000000..bdfca342 --- /dev/null +++ b/adapters/common/engine/globalAdapter/View.js @@ -0,0 +1,43 @@ +function adaptView (viewProto) { + Object.assign(viewProto, { + _adjustViewportMeta () { + // minigame not support + }, + + setRealPixelResolution (width, height, resolutionPolicy) { + // Reset the resolution size and policy + this.setDesignResolutionSize(width, height, resolutionPolicy); + }, + + enableAutoFullScreen (enabled) { + cc.warn('cc.view.enableAutoFullScreen() is not supported on minigame platform.'); + }, + + isAutoFullScreenEnabled () { + return false; + }, + + setCanvasSize () { + cc.warn('cc.view.setCanvasSize() is not supported on minigame platform.'); + }, + + setFrameSize () { + cc.warn('frame size is readonly on minigame platform.'); + }, + + _initFrameSize () { + let locFrameSize = this._frameSize; + if (__globalAdapter.isSubContext) { + let sharedCanvas = window.sharedCanvas || __globalAdapter.getSharedCanvas(); + locFrameSize.width = sharedCanvas.width; + locFrameSize.height = sharedCanvas.height; + } + else { + locFrameSize.width = window.innerWidth; + locFrameSize.height = window.innerHeight; + } + }, + }); +} + +module.exports = adaptView; \ No newline at end of file diff --git a/adapters/common/engine/globalAdapter/index.js b/adapters/common/engine/globalAdapter/index.js new file mode 100644 index 00000000..3bfae829 --- /dev/null +++ b/adapters/common/engine/globalAdapter/index.js @@ -0,0 +1,9 @@ +const adapter = window.__globalAdapter; + +Object.assign(adapter, { + adaptSys: require('./BaseSystemInfo'), + + adaptView: require('./View'), + + adaptContainerStrategy: require('./ContainerStrategy'), +}); \ No newline at end of file diff --git a/adapters/common/engine/index.js b/adapters/common/engine/index.js new file mode 100644 index 00000000..f4eb9d9d --- /dev/null +++ b/adapters/common/engine/index.js @@ -0,0 +1,10 @@ +require('./Audio'); +require('./AudioEngine'); +require('./DeviceMotionEvent'); +require('./Editbox'); +require('./Game'); +require('./InputManager'); +require('./AssetManager'); +require('./Screen'); +require('./Texture2D'); +require('./misc'); \ No newline at end of file diff --git a/adapters/common/engine/misc.js b/adapters/common/engine/misc.js new file mode 100644 index 00000000..15843d4e --- /dev/null +++ b/adapters/common/engine/misc.js @@ -0,0 +1 @@ +cc.macro.DOWNLOAD_MAX_CONCURRENT = 10; \ No newline at end of file diff --git a/adapters/common/utils.js b/adapters/common/utils.js new file mode 100644 index 00000000..3ead0b65 --- /dev/null +++ b/adapters/common/utils.js @@ -0,0 +1,111 @@ +const utils = { + /** + * @param {Object} target + * @param {Object} origin + * @param {String} methodName + * @param {String} targetMethodName + */ + cloneMethod (target, origin, methodName, targetMethodName) { + if (origin[methodName]) { + targetMethodName = targetMethodName || methodName; + target[targetMethodName] = origin[methodName].bind(origin); + } + }, + + /** + * + * @param {String} str + * @returns + */ + encode (str) { + let encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + const string = String(str); + let result = ''; + let currentIndex = 0; + let sum = void 0; + while (string.charAt(0 | currentIndex) || (encodings = '=', currentIndex % 1)) { + currentIndex += 0.75; + const currentCode = string.charCodeAt(currentIndex); + if (currentCode > 255) { + // Cannot handle when it is greater than 255 + throw new Error('"btoa" failed'); + } + sum = sum << 8 | currentCode; + const encodeIndex = 63 & sum >> 8 - currentIndex % 1 * 8; + result += encodings.charAt(encodeIndex); + } + + return result; + }, + + /** + * + * @param {String} str + */ + decode (str) { + const encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + let res = ''; + const string = String(str).replace(/[=]+$/, ''); + let o; + let r; + let i = 0; + let currentIndex = 0; + while (r = string.charAt(currentIndex)) { + currentIndex += 1; + r = encodings.indexOf(r); + if (~r) { + o = i % 4 ? 64 * o + r : r; + if (i++ % 4) { + res += String.fromCharCode(255 & o >> (-2 * i & 6)); + } + } + } + + return res; + }, + + /** + * + * @param {ArrayBuffer} buffer + */ + arrayBufferToBase64 (buffer) { + return utils.encode(utils.arrayBufferToString(buffer)); + }, + + /** + * + * @param {String} base64 + */ + base64ToArrayBuffer (base64) { + return utils.stringToArrayBuffer(utils.decode(base64)); + }, + + /** + * + * @param {ArrayBuffer} buffer + */ + arrayBufferToString (buffer) { + let result = ''; + const uintArray = new Uint8Array(buffer); + const byteLength = uintArray.byteLength; + for (let i = 0; i < byteLength; i++) { + result += String.fromCharCode(uintArray[i]); + } + return result; + }, + + /** + * + * @param {String} string + */ + stringToArrayBuffer (string) { + const length = string.length; + const uintArray = new Uint8Array(length); + for (let i = 0; i < length; i++) { + uintArray[i] = string.charCodeAt(i); + } + return uintArray.buffer; + }, +}; + +module.exports = utils; diff --git a/adapters/common/xmldom/dom-parser.js b/adapters/common/xmldom/dom-parser.js new file mode 100644 index 00000000..e45dd476 --- /dev/null +++ b/adapters/common/xmldom/dom-parser.js @@ -0,0 +1,252 @@ +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){ + 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 htmlEntity = require('./entities'); +var XMLReader = require('./sax').XMLReader; +var DOMImplementation = exports.DOMImplementation = require('./dom').DOMImplementation; +exports.XMLSerializer = require('./dom').XMLSerializer ; +exports.DOMParser = DOMParser; +//} diff --git a/adapters/common/xmldom/dom.js b/adapters/common/xmldom/dom.js new file mode 100755 index 00000000..935531b3 --- /dev/null +++ b/adapters/common/xmldom/dom.js @@ -0,0 +1,1246 @@ +/* + * 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 + */ + +function copy(src,dest){ + for(var p in src){ + dest[p] = src[p]; + } +} +/** +^\w+\.prototype\.([_\w]+)\s*=\s*((?:.*\{\s*?[\r\n][\s\S]*?^})|\S.*?(?=[;\r\n]));? +^\w+\.prototype\.([_\w]+)\s*=\s*(\S.*?(?=[;\r\n]));? + */ +function _extends(Class,Super){ + var pt = Class.prototype; + if(!(pt instanceof Super)){ + function t(){}; + t.prototype = Super.prototype; + t = new t(); + // copy(pt,t); + for(var p in pt){ + t[p] = pt[p]; + } + Class.prototype = pt = t; + } + if(pt.constructor != Class){ + if(typeof Class != 'function'){ + console.error("unknow Class:"+Class) + } + pt.constructor = Class + } +} +var htmlns = 'http://www.w3.org/1999/xhtml' ; +// Node Types +var NodeType = {} +var ELEMENT_NODE = NodeType.ELEMENT_NODE = 1; +var ATTRIBUTE_NODE = NodeType.ATTRIBUTE_NODE = 2; +var TEXT_NODE = NodeType.TEXT_NODE = 3; +var CDATA_SECTION_NODE = NodeType.CDATA_SECTION_NODE = 4; +var ENTITY_REFERENCE_NODE = NodeType.ENTITY_REFERENCE_NODE = 5; +var ENTITY_NODE = NodeType.ENTITY_NODE = 6; +var PROCESSING_INSTRUCTION_NODE = NodeType.PROCESSING_INSTRUCTION_NODE = 7; +var COMMENT_NODE = NodeType.COMMENT_NODE = 8; +var DOCUMENT_NODE = NodeType.DOCUMENT_NODE = 9; +var DOCUMENT_TYPE_NODE = NodeType.DOCUMENT_TYPE_NODE = 10; +var DOCUMENT_FRAGMENT_NODE = NodeType.DOCUMENT_FRAGMENT_NODE = 11; +var NOTATION_NODE = NodeType.NOTATION_NODE = 12; + +// ExceptionCode +var ExceptionCode = {} +var ExceptionMessage = {}; +var INDEX_SIZE_ERR = ExceptionCode.INDEX_SIZE_ERR = ((ExceptionMessage[1]="Index size error"),1); +var DOMSTRING_SIZE_ERR = ExceptionCode.DOMSTRING_SIZE_ERR = ((ExceptionMessage[2]="DOMString size error"),2); +var HIERARCHY_REQUEST_ERR = ExceptionCode.HIERARCHY_REQUEST_ERR = ((ExceptionMessage[3]="Hierarchy request error"),3); +var WRONG_DOCUMENT_ERR = ExceptionCode.WRONG_DOCUMENT_ERR = ((ExceptionMessage[4]="Wrong document"),4); +var INVALID_CHARACTER_ERR = ExceptionCode.INVALID_CHARACTER_ERR = ((ExceptionMessage[5]="Invalid character"),5); +var NO_DATA_ALLOWED_ERR = ExceptionCode.NO_DATA_ALLOWED_ERR = ((ExceptionMessage[6]="No data allowed"),6); +var NO_MODIFICATION_ALLOWED_ERR = ExceptionCode.NO_MODIFICATION_ALLOWED_ERR = ((ExceptionMessage[7]="No modification allowed"),7); +var NOT_FOUND_ERR = ExceptionCode.NOT_FOUND_ERR = ((ExceptionMessage[8]="Not found"),8); +var NOT_SUPPORTED_ERR = ExceptionCode.NOT_SUPPORTED_ERR = ((ExceptionMessage[9]="Not supported"),9); +var INUSE_ATTRIBUTE_ERR = ExceptionCode.INUSE_ATTRIBUTE_ERR = ((ExceptionMessage[10]="Attribute in use"),10); +//level2 +var INVALID_STATE_ERR = ExceptionCode.INVALID_STATE_ERR = ((ExceptionMessage[11]="Invalid state"),11); +var SYNTAX_ERR = ExceptionCode.SYNTAX_ERR = ((ExceptionMessage[12]="Syntax error"),12); +var INVALID_MODIFICATION_ERR = ExceptionCode.INVALID_MODIFICATION_ERR = ((ExceptionMessage[13]="Invalid modification"),13); +var NAMESPACE_ERR = ExceptionCode.NAMESPACE_ERR = ((ExceptionMessage[14]="Invalid namespace"),14); +var INVALID_ACCESS_ERR = ExceptionCode.INVALID_ACCESS_ERR = ((ExceptionMessage[15]="Invalid access"),15); + + +function DOMException(code, message) { + if(message instanceof Error){ + var error = message; + }else{ + error = this; + Error.call(this, ExceptionMessage[code]); + this.message = ExceptionMessage[code]; + if(Error.captureStackTrace) Error.captureStackTrace(this, DOMException); + } + error.code = code; + if(message) this.message = this.message + ": " + message; + return error; +}; +DOMException.prototype = Error.prototype; +copy(ExceptionCode,DOMException) +/** + * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-536297177 + * The NodeList interface provides the abstraction of an ordered collection of nodes, without defining or constraining how this collection is implemented. NodeList objects in the DOM are live. + * The items in the NodeList are accessible via an integral index, starting from 0. + */ +function NodeList() { +}; +NodeList.prototype = { + /** + * The number of nodes in the list. The range of valid child node indices is 0 to length-1 inclusive. + * @standard level1 + */ + length:0, + /** + * Returns the indexth item in the collection. If index is greater than or equal to the number of nodes in the list, this returns null. + * @standard level1 + * @param index unsigned long + * Index into the collection. + * @return Node + * The node at the indexth position in the NodeList, or null if that is not a valid index. + */ + item: function(index) { + return this[index] || null; + }, + toString:function(isHTML,nodeFilter){ + for(var buf = [], i = 0;i=0){ + var lastIndex = list.length-1 + while(i0 || key == 'xmlns'){ +// return null; +// } + //console.log() + var i = this.length; + while(i--){ + var attr = this[i]; + //console.log(attr.nodeName,key) + if(attr.nodeName == key){ + return attr; + } + } + }, + setNamedItem: function(attr) { + var el = attr.ownerElement; + if(el && el!=this._ownerElement){ + throw new DOMException(INUSE_ATTRIBUTE_ERR); + } + var oldAttr = this.getNamedItem(attr.nodeName); + _addNamedNode(this._ownerElement,this,attr,oldAttr); + return oldAttr; + }, + /* returns Node */ + setNamedItemNS: function(attr) {// raises: WRONG_DOCUMENT_ERR,NO_MODIFICATION_ALLOWED_ERR,INUSE_ATTRIBUTE_ERR + var el = attr.ownerElement, oldAttr; + if(el && el!=this._ownerElement){ + throw new DOMException(INUSE_ATTRIBUTE_ERR); + } + oldAttr = this.getNamedItemNS(attr.namespaceURI,attr.localName); + _addNamedNode(this._ownerElement,this,attr,oldAttr); + return oldAttr; + }, + + /* returns Node */ + removeNamedItem: function(key) { + var attr = this.getNamedItem(key); + _removeNamedNode(this._ownerElement,this,attr); + return attr; + + + },// raises: NOT_FOUND_ERR,NO_MODIFICATION_ALLOWED_ERR + + //for level2 + removeNamedItemNS:function(namespaceURI,localName){ + var attr = this.getNamedItemNS(namespaceURI,localName); + _removeNamedNode(this._ownerElement,this,attr); + return attr; + }, + getNamedItemNS: function(namespaceURI, localName) { + var i = this.length; + while(i--){ + var node = this[i]; + if(node.localName == localName && node.namespaceURI == namespaceURI){ + return node; + } + } + return null; + } +}; +/** + * @see http://www.w3.org/TR/REC-DOM-Level-1/level-one-core.html#ID-102161490 + */ +function DOMImplementation(/* Object */ features) { + this._features = {}; + if (features) { + for (var feature in features) { + this._features = features[feature]; + } + } +}; + +DOMImplementation.prototype = { + hasFeature: function(/* string */ feature, /* string */ version) { + var versions = this._features[feature.toLowerCase()]; + if (versions && (!version || version in versions)) { + return true; + } else { + return false; + } + }, + // Introduced in DOM Level 2: + createDocument:function(namespaceURI, qualifiedName, doctype){// raises:INVALID_CHARACTER_ERR,NAMESPACE_ERR,WRONG_DOCUMENT_ERR + var doc = new Document(); + doc.implementation = this; + doc.childNodes = new NodeList(); + doc.doctype = doctype; + if(doctype){ + doc.appendChild(doctype); + } + if(qualifiedName){ + var root = doc.createElementNS(namespaceURI,qualifiedName); + doc.appendChild(root); + } + return doc; + }, + // Introduced in DOM Level 2: + createDocumentType:function(qualifiedName, publicId, systemId){// raises:INVALID_CHARACTER_ERR,NAMESPACE_ERR + var node = new DocumentType(); + node.name = qualifiedName; + node.nodeName = qualifiedName; + node.publicId = publicId; + node.systemId = systemId; + // Introduced in DOM Level 2: + //readonly attribute DOMString internalSubset; + + //TODO:.. + // readonly attribute NamedNodeMap entities; + // readonly attribute NamedNodeMap notations; + return node; + } +}; + + +/** + * @see http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/core.html#ID-1950641247 + */ + +function Node() { +}; + +Node.prototype = { + firstChild : null, + lastChild : null, + previousSibling : null, + nextSibling : null, + attributes : null, + parentNode : null, + childNodes : null, + ownerDocument : null, + nodeValue : null, + namespaceURI : null, + prefix : null, + localName : null, + // Modified in DOM Level 2: + insertBefore:function(newChild, refChild){//raises + return _insertBefore(this,newChild,refChild); + }, + replaceChild:function(newChild, oldChild){//raises + this.insertBefore(newChild,oldChild); + if(oldChild){ + this.removeChild(oldChild); + } + }, + removeChild:function(oldChild){ + return _removeChild(this,oldChild); + }, + appendChild:function(newChild){ + return this.insertBefore(newChild,null); + }, + hasChildNodes:function(){ + return this.firstChild != null; + }, + cloneNode:function(deep){ + return cloneNode(this.ownerDocument||this,this,deep); + }, + // Modified in DOM Level 2: + normalize:function(){ + var child = this.firstChild; + while(child){ + var next = child.nextSibling; + if(next && next.nodeType == TEXT_NODE && child.nodeType == TEXT_NODE){ + this.removeChild(next); + child.appendData(next.data); + }else{ + child.normalize(); + child = next; + } + } + }, + // Introduced in DOM Level 2: + isSupported:function(feature, version){ + return this.ownerDocument.implementation.hasFeature(feature,version); + }, + // Introduced in DOM Level 2: + hasAttributes:function(){ + return this.attributes.length>0; + }, + lookupPrefix:function(namespaceURI){ + var el = this; + while(el){ + var map = el._nsMap; + //console.dir(map) + if(map){ + for(var n in map){ + if(map[n] == namespaceURI){ + return n; + } + } + } + el = el.nodeType == ATTRIBUTE_NODE?el.ownerDocument : el.parentNode; + } + return null; + }, + // Introduced in DOM Level 3: + lookupNamespaceURI:function(prefix){ + var el = this; + while(el){ + var map = el._nsMap; + //console.dir(map) + if(map){ + if(prefix in map){ + return map[prefix] ; + } + } + el = el.nodeType == ATTRIBUTE_NODE?el.ownerDocument : el.parentNode; + } + return null; + }, + // Introduced in DOM Level 3: + isDefaultNamespace:function(namespaceURI){ + var prefix = this.lookupPrefix(namespaceURI); + return prefix == null; + } +}; + + +function _xmlEncoder(c){ + return c == '<' && '<' || + c == '>' && '>' || + c == '&' && '&' || + c == '"' && '"' || + '&#'+c.charCodeAt()+';' +} + + +copy(NodeType,Node); +copy(NodeType,Node.prototype); + +/** + * @param callback return true for continue,false for break + * @return boolean true: break visit; + */ +function _visitNode(node,callback){ + if(callback(node)){ + return true; + } + if(node = node.firstChild){ + do{ + if(_visitNode(node,callback)){return true} + }while(node=node.nextSibling) + } +} + + + +function Document(){ +} +function _onAddAttribute(doc,el,newAttr){ + doc && doc._inc++; + var ns = newAttr.namespaceURI ; + if(ns == 'http://www.w3.org/2000/xmlns/'){ + //update namespace + el._nsMap[newAttr.prefix?newAttr.localName:''] = newAttr.value + } +} +function _onRemoveAttribute(doc,el,newAttr,remove){ + doc && doc._inc++; + var ns = newAttr.namespaceURI ; + if(ns == 'http://www.w3.org/2000/xmlns/'){ + //update namespace + delete el._nsMap[newAttr.prefix?newAttr.localName:''] + } +} +function _onUpdateChild(doc,el,newChild){ + if(doc && doc._inc){ + doc._inc++; + //update childNodes + var cs = el.childNodes; + if(newChild){ + cs[cs.length++] = newChild; + }else{ + //console.log(1) + var child = el.firstChild; + var i = 0; + while(child){ + cs[i++] = child; + child =child.nextSibling; + } + cs.length = i; + } + } +} + +/** + * attributes; + * children; + * + * writeable properties: + * nodeValue,Attr:value,CharacterData:data + * prefix + */ +function _removeChild(parentNode,child){ + var previous = child.previousSibling; + var next = child.nextSibling; + if(previous){ + previous.nextSibling = next; + }else{ + parentNode.firstChild = next + } + if(next){ + next.previousSibling = previous; + }else{ + parentNode.lastChild = previous; + } + _onUpdateChild(parentNode.ownerDocument,parentNode); + return child; +} +/** + * preformance key(refChild == null) + */ +function _insertBefore(parentNode,newChild,nextChild){ + var cp = newChild.parentNode; + if(cp){ + cp.removeChild(newChild);//remove and update + } + if(newChild.nodeType === DOCUMENT_FRAGMENT_NODE){ + var newFirst = newChild.firstChild; + if (newFirst == null) { + return newChild; + } + var newLast = newChild.lastChild; + }else{ + newFirst = newLast = newChild; + } + var pre = nextChild ? nextChild.previousSibling : parentNode.lastChild; + + newFirst.previousSibling = pre; + newLast.nextSibling = nextChild; + + + if(pre){ + pre.nextSibling = newFirst; + }else{ + parentNode.firstChild = newFirst; + } + if(nextChild == null){ + parentNode.lastChild = newLast; + }else{ + nextChild.previousSibling = newLast; + } + do{ + newFirst.parentNode = parentNode; + }while(newFirst !== newLast && (newFirst= newFirst.nextSibling)) + _onUpdateChild(parentNode.ownerDocument||parentNode,parentNode); + //console.log(parentNode.lastChild.nextSibling == null) + if (newChild.nodeType == DOCUMENT_FRAGMENT_NODE) { + newChild.firstChild = newChild.lastChild = null; + } + return newChild; +} +function _appendSingleChild(parentNode,newChild){ + var cp = newChild.parentNode; + if(cp){ + var pre = parentNode.lastChild; + cp.removeChild(newChild);//remove and update + var pre = parentNode.lastChild; + } + var pre = parentNode.lastChild; + newChild.parentNode = parentNode; + newChild.previousSibling = pre; + newChild.nextSibling = null; + if(pre){ + pre.nextSibling = newChild; + }else{ + parentNode.firstChild = newChild; + } + parentNode.lastChild = newChild; + _onUpdateChild(parentNode.ownerDocument,parentNode,newChild); + return newChild; + //console.log("__aa",parentNode.lastChild.nextSibling == null) +} +Document.prototype = { + //implementation : null, + nodeName : '#document', + nodeType : DOCUMENT_NODE, + doctype : null, + documentElement : null, + _inc : 1, + + insertBefore : function(newChild, refChild){//raises + if(newChild.nodeType == DOCUMENT_FRAGMENT_NODE){ + var child = newChild.firstChild; + while(child){ + var next = child.nextSibling; + this.insertBefore(child,refChild); + child = next; + } + return newChild; + } + if(this.documentElement == null && newChild.nodeType == ELEMENT_NODE){ + this.documentElement = newChild; + } + + return _insertBefore(this,newChild,refChild),(newChild.ownerDocument = this),newChild; + }, + removeChild : function(oldChild){ + if(this.documentElement == oldChild){ + this.documentElement = null; + } + return _removeChild(this,oldChild); + }, + // Introduced in DOM Level 2: + importNode : function(importedNode,deep){ + return importNode(this,importedNode,deep); + }, + // Introduced in DOM Level 2: + getElementById : function(id){ + var rtv = null; + _visitNode(this.documentElement,function(node){ + if(node.nodeType == ELEMENT_NODE){ + if(node.getAttribute('id') == id){ + rtv = node; + return true; + } + } + }) + return rtv; + }, + + //document factory method: + createElement : function(tagName){ + var node = new Element(); + node.ownerDocument = this; + node.nodeName = tagName; + node.tagName = tagName; + node.childNodes = new NodeList(); + var attrs = node.attributes = new NamedNodeMap(); + attrs._ownerElement = node; + return node; + }, + createDocumentFragment : function(){ + var node = new DocumentFragment(); + node.ownerDocument = this; + node.childNodes = new NodeList(); + return node; + }, + createTextNode : function(data){ + var node = new Text(); + node.ownerDocument = this; + node.appendData(data) + return node; + }, + createComment : function(data){ + var node = new Comment(); + node.ownerDocument = this; + node.appendData(data) + return node; + }, + createCDATASection : function(data){ + var node = new CDATASection(); + node.ownerDocument = this; + node.appendData(data) + return node; + }, + createProcessingInstruction : function(target,data){ + var node = new ProcessingInstruction(); + node.ownerDocument = this; + node.tagName = node.target = target; + node.nodeValue= node.data = data; + return node; + }, + createAttribute : function(name){ + var node = new Attr(); + node.ownerDocument = this; + node.name = name; + node.nodeName = name; + node.localName = name; + node.specified = true; + return node; + }, + createEntityReference : function(name){ + var node = new EntityReference(); + node.ownerDocument = this; + node.nodeName = name; + return node; + }, + // Introduced in DOM Level 2: + createElementNS : function(namespaceURI,qualifiedName){ + var node = new Element(); + var pl = qualifiedName.split(':'); + var attrs = node.attributes = new NamedNodeMap(); + node.childNodes = new NodeList(); + node.ownerDocument = this; + node.nodeName = qualifiedName; + node.tagName = qualifiedName; + node.namespaceURI = namespaceURI; + if(pl.length == 2){ + node.prefix = pl[0]; + node.localName = pl[1]; + }else{ + //el.prefix = null; + node.localName = qualifiedName; + } + attrs._ownerElement = node; + return node; + }, + // Introduced in DOM Level 2: + createAttributeNS : function(namespaceURI,qualifiedName){ + var node = new Attr(); + var pl = qualifiedName.split(':'); + node.ownerDocument = this; + node.nodeName = qualifiedName; + node.name = qualifiedName; + node.namespaceURI = namespaceURI; + node.specified = true; + if(pl.length == 2){ + node.prefix = pl[0]; + node.localName = pl[1]; + }else{ + //el.prefix = null; + node.localName = qualifiedName; + } + return node; + } +}; +_extends(Document,Node); + + +function Element() { + this._nsMap = {}; +}; +Element.prototype = { + nodeType : ELEMENT_NODE, + hasAttribute : function(name){ + return this.getAttributeNode(name)!=null; + }, + getAttribute : function(name){ + var attr = this.getAttributeNode(name); + return attr && attr.value || ''; + }, + getAttributeNode : function(name){ + return this.attributes.getNamedItem(name); + }, + setAttribute : function(name, value){ + var attr = this.ownerDocument.createAttribute(name); + attr.value = attr.nodeValue = "" + value; + this.setAttributeNode(attr) + }, + removeAttribute : function(name){ + var attr = this.getAttributeNode(name) + attr && this.removeAttributeNode(attr); + }, + + //four real opeartion method + appendChild:function(newChild){ + if(newChild.nodeType === DOCUMENT_FRAGMENT_NODE){ + return this.insertBefore(newChild,null); + }else{ + return _appendSingleChild(this,newChild); + } + }, + setAttributeNode : function(newAttr){ + return this.attributes.setNamedItem(newAttr); + }, + setAttributeNodeNS : function(newAttr){ + return this.attributes.setNamedItemNS(newAttr); + }, + removeAttributeNode : function(oldAttr){ + //console.log(this == oldAttr.ownerElement) + return this.attributes.removeNamedItem(oldAttr.nodeName); + }, + //get real attribute name,and remove it by removeAttributeNode + removeAttributeNS : function(namespaceURI, localName){ + var old = this.getAttributeNodeNS(namespaceURI, localName); + old && this.removeAttributeNode(old); + }, + + hasAttributeNS : function(namespaceURI, localName){ + return this.getAttributeNodeNS(namespaceURI, localName)!=null; + }, + getAttributeNS : function(namespaceURI, localName){ + var attr = this.getAttributeNodeNS(namespaceURI, localName); + return attr && attr.value || ''; + }, + setAttributeNS : function(namespaceURI, qualifiedName, value){ + var attr = this.ownerDocument.createAttributeNS(namespaceURI, qualifiedName); + attr.value = attr.nodeValue = "" + value; + this.setAttributeNode(attr) + }, + getAttributeNodeNS : function(namespaceURI, localName){ + return this.attributes.getNamedItemNS(namespaceURI, localName); + }, + + getElementsByTagName : function(tagName){ + return new LiveNodeList(this,function(base){ + var ls = []; + _visitNode(base,function(node){ + if(node !== base && node.nodeType == ELEMENT_NODE && (tagName === '*' || node.tagName == tagName)){ + ls.push(node); + } + }); + return ls; + }); + }, + getElementsByTagNameNS : function(namespaceURI, localName){ + return new LiveNodeList(this,function(base){ + var ls = []; + _visitNode(base,function(node){ + if(node !== base && node.nodeType === ELEMENT_NODE && (namespaceURI === '*' || node.namespaceURI === namespaceURI) && (localName === '*' || node.localName == localName)){ + ls.push(node); + } + }); + return ls; + + }); + } +}; +Document.prototype.getElementsByTagName = Element.prototype.getElementsByTagName; +Document.prototype.getElementsByTagNameNS = Element.prototype.getElementsByTagNameNS; + + +_extends(Element,Node); +function Attr() { +}; +Attr.prototype.nodeType = ATTRIBUTE_NODE; +_extends(Attr,Node); + + +function CharacterData() { +}; +CharacterData.prototype = { + data : '', + substringData : function(offset, count) { + return this.data.substring(offset, offset+count); + }, + appendData: function(text) { + text = this.data+text; + this.nodeValue = this.data = text; + this.length = text.length; + }, + insertData: function(offset,text) { + this.replaceData(offset,0,text); + + }, + appendChild:function(newChild){ + throw new Error(ExceptionMessage[HIERARCHY_REQUEST_ERR]) + }, + deleteData: function(offset, count) { + this.replaceData(offset,count,""); + }, + replaceData: function(offset, count, text) { + var start = this.data.substring(0,offset); + var end = this.data.substring(offset+count); + text = start + text + end; + this.nodeValue = this.data = text; + this.length = text.length; + } +} +_extends(CharacterData,Node); +function Text() { +}; +Text.prototype = { + nodeName : "#text", + nodeType : TEXT_NODE, + splitText : function(offset) { + var text = this.data; + var newText = text.substring(offset); + text = text.substring(0, offset); + this.data = this.nodeValue = text; + this.length = text.length; + var newNode = this.ownerDocument.createTextNode(newText); + if(this.parentNode){ + this.parentNode.insertBefore(newNode, this.nextSibling); + } + return newNode; + } +} +_extends(Text,CharacterData); +function Comment() { +}; +Comment.prototype = { + nodeName : "#comment", + nodeType : COMMENT_NODE +} +_extends(Comment,CharacterData); + +function CDATASection() { +}; +CDATASection.prototype = { + nodeName : "#cdata-section", + nodeType : CDATA_SECTION_NODE +} +_extends(CDATASection,CharacterData); + + +function DocumentType() { +}; +DocumentType.prototype.nodeType = DOCUMENT_TYPE_NODE; +_extends(DocumentType,Node); + +function Notation() { +}; +Notation.prototype.nodeType = NOTATION_NODE; +_extends(Notation,Node); + +function Entity() { +}; +Entity.prototype.nodeType = ENTITY_NODE; +_extends(Entity,Node); + +function EntityReference() { +}; +EntityReference.prototype.nodeType = ENTITY_REFERENCE_NODE; +_extends(EntityReference,Node); + +function DocumentFragment() { +}; +DocumentFragment.prototype.nodeName = "#document-fragment"; +DocumentFragment.prototype.nodeType = DOCUMENT_FRAGMENT_NODE; +_extends(DocumentFragment,Node); + + +function ProcessingInstruction() { +} +ProcessingInstruction.prototype.nodeType = PROCESSING_INSTRUCTION_NODE; +_extends(ProcessingInstruction,Node); +function XMLSerializer(){} +XMLSerializer.prototype.serializeToString = function(node,isHtml,nodeFilter){ + return nodeSerializeToString.call(node,isHtml,nodeFilter); +} +Node.prototype.toString = nodeSerializeToString; +function nodeSerializeToString(isHtml,nodeFilter){ + var buf = []; + var refNode = this.nodeType == 9 && this.documentElement || this; + var prefix = refNode.prefix; + var uri = refNode.namespaceURI; + + if(uri && prefix == null){ + //console.log(prefix) + var prefix = refNode.lookupPrefix(uri); + if(prefix == null){ + //isHTML = true; + var visibleNamespaces=[ + {namespace:uri,prefix:null} + //{namespace:uri,prefix:''} + ] + } + } + serializeToString(this,buf,isHtml,nodeFilter,visibleNamespaces); + //console.log('###',this.nodeType,uri,prefix,buf.join('')) + return buf.join(''); +} +function needNamespaceDefine(node,isHTML, visibleNamespaces) { + var prefix = node.prefix||''; + var uri = node.namespaceURI; + if (!prefix && !uri){ + return false; + } + if (prefix === "xml" && uri === "http://www.w3.org/XML/1998/namespace" + || uri == 'http://www.w3.org/2000/xmlns/'){ + return false; + } + + var i = visibleNamespaces.length + //console.log('@@@@',node.tagName,prefix,uri,visibleNamespaces) + while (i--) { + var ns = visibleNamespaces[i]; + // get namespace prefix + //console.log(node.nodeType,node.tagName,ns.prefix,prefix) + if (ns.prefix == prefix){ + return ns.namespace != uri; + } + } + //console.log(isHTML,uri,prefix=='') + //if(isHTML && prefix ==null && uri == 'http://www.w3.org/1999/xhtml'){ + // return false; + //} + //node.flag = '11111' + //console.error(3,true,node.flag,node.prefix,node.namespaceURI) + return true; +} +function serializeToString(node,buf,isHTML,nodeFilter,visibleNamespaces){ + if(nodeFilter){ + node = nodeFilter(node); + if(node){ + if(typeof node == 'string'){ + buf.push(node); + return; + } + }else{ + return; + } + //buf.sort.apply(attrs, attributeSorter); + } + switch(node.nodeType){ + case ELEMENT_NODE: + if (!visibleNamespaces) visibleNamespaces = []; + var startVisibleNamespaces = visibleNamespaces.length; + var attrs = node.attributes; + var len = attrs.length; + var child = node.firstChild; + var nodeName = node.tagName; + + isHTML = (htmlns === node.namespaceURI) ||isHTML + buf.push('<',nodeName); + + + + for(var i=0;i'); + //if is cdata child node + if(isHTML && /^script$/i.test(nodeName)){ + while(child){ + if(child.data){ + buf.push(child.data); + }else{ + serializeToString(child,buf,isHTML,nodeFilter,visibleNamespaces); + } + child = child.nextSibling; + } + }else + { + while(child){ + serializeToString(child,buf,isHTML,nodeFilter,visibleNamespaces); + child = child.nextSibling; + } + } + buf.push(''); + }else{ + buf.push('/>'); + } + // remove added visible namespaces + //visibleNamespaces.length = startVisibleNamespaces; + return; + case DOCUMENT_NODE: + case DOCUMENT_FRAGMENT_NODE: + var child = node.firstChild; + while(child){ + serializeToString(child,buf,isHTML,nodeFilter,visibleNamespaces); + child = child.nextSibling; + } + return; + case ATTRIBUTE_NODE: + return buf.push(' ',node.name,'="',node.value.replace(/[<&"]/g,_xmlEncoder),'"'); + case TEXT_NODE: + return buf.push(node.data.replace(/[<&]/g,_xmlEncoder)); + case CDATA_SECTION_NODE: + return buf.push( ''); + case COMMENT_NODE: + return buf.push( ""); + case DOCUMENT_TYPE_NODE: + var pubid = node.publicId; + var sysid = node.systemId; + buf.push(''); + }else if(sysid && sysid!='.'){ + buf.push(' SYSTEM "',sysid,'">'); + }else{ + var sub = node.internalSubset; + if(sub){ + buf.push(" [",sub,"]"); + } + buf.push(">"); + } + return; + case PROCESSING_INSTRUCTION_NODE: + return buf.push( ""); + case ENTITY_REFERENCE_NODE: + return buf.push( '&',node.nodeName,';'); + //case ENTITY_NODE: + //case NOTATION_NODE: + default: + buf.push('??',node.nodeName); + } +} +function importNode(doc,node,deep){ + var node2; + switch (node.nodeType) { + case ELEMENT_NODE: + node2 = node.cloneNode(false); + node2.ownerDocument = doc; + //var attrs = node2.attributes; + //var len = attrs.length; + //for(var i=0;i', + amp: '&', + quot: '"', + apos: "'", + Agrave: "À", + Aacute: "Á", + Acirc: "Â", + Atilde: "Ã", + Auml: "Ä", + Aring: "Å", + AElig: "Æ", + Ccedil: "Ç", + Egrave: "È", + Eacute: "É", + Ecirc: "Ê", + Euml: "Ë", + Igrave: "Ì", + Iacute: "Í", + Icirc: "Î", + Iuml: "Ï", + ETH: "Ð", + Ntilde: "Ñ", + Ograve: "Ò", + Oacute: "Ó", + Ocirc: "Ô", + Otilde: "Õ", + Ouml: "Ö", + Oslash: "Ø", + Ugrave: "Ù", + Uacute: "Ú", + Ucirc: "Û", + Uuml: "Ü", + Yacute: "Ý", + THORN: "Þ", + szlig: "ß", + agrave: "à", + aacute: "á", + acirc: "â", + atilde: "ã", + auml: "ä", + aring: "å", + aelig: "æ", + ccedil: "ç", + egrave: "è", + eacute: "é", + ecirc: "ê", + euml: "ë", + igrave: "ì", + iacute: "í", + icirc: "î", + iuml: "ï", + eth: "ð", + ntilde: "ñ", + ograve: "ò", + oacute: "ó", + ocirc: "ô", + otilde: "õ", + ouml: "ö", + oslash: "ø", + ugrave: "ù", + uacute: "ú", + ucirc: "û", + uuml: "ü", + yacute: "ý", + thorn: "þ", + yuml: "ÿ", + nbsp: " ", + iexcl: "¡", + cent: "¢", + pound: "£", + curren: "¤", + yen: "¥", + brvbar: "¦", + sect: "§", + uml: "¨", + copy: "©", + ordf: "ª", + laquo: "«", + not: "¬", + shy: "­­", + reg: "®", + macr: "¯", + deg: "°", + plusmn: "±", + sup2: "²", + sup3: "³", + acute: "´", + micro: "µ", + para: "¶", + middot: "·", + cedil: "¸", + sup1: "¹", + ordm: "º", + raquo: "»", + frac14: "¼", + frac12: "½", + frac34: "¾", + iquest: "¿", + times: "×", + divide: "÷", + forall: "∀", + part: "∂", + exist: "∃", + empty: "∅", + nabla: "∇", + isin: "∈", + notin: "∉", + ni: "∋", + prod: "∏", + sum: "∑", + minus: "−", + lowast: "∗", + radic: "√", + prop: "∝", + infin: "∞", + ang: "∠", + and: "∧", + or: "∨", + cap: "∩", + cup: "∪", + 'int': "∫", + there4: "∴", + sim: "∼", + cong: "≅", + asymp: "≈", + ne: "≠", + equiv: "≡", + le: "≤", + ge: "≥", + sub: "⊂", + sup: "⊃", + nsub: "⊄", + sube: "⊆", + supe: "⊇", + oplus: "⊕", + otimes: "⊗", + perp: "⊥", + sdot: "⋅", + Alpha: "Α", + Beta: "Β", + Gamma: "Γ", + Delta: "Δ", + Epsilon: "Ε", + Zeta: "Ζ", + Eta: "Η", + Theta: "Θ", + Iota: "Ι", + Kappa: "Κ", + Lambda: "Λ", + Mu: "Μ", + Nu: "Ν", + Xi: "Ξ", + Omicron: "Ο", + Pi: "Π", + Rho: "Ρ", + Sigma: "Σ", + Tau: "Τ", + Upsilon: "Υ", + Phi: "Φ", + Chi: "Χ", + Psi: "Ψ", + Omega: "Ω", + alpha: "α", + beta: "β", + gamma: "γ", + delta: "δ", + epsilon: "ε", + zeta: "ζ", + eta: "η", + theta: "θ", + iota: "ι", + kappa: "κ", + lambda: "λ", + mu: "μ", + nu: "ν", + xi: "ξ", + omicron: "ο", + pi: "π", + rho: "ρ", + sigmaf: "ς", + sigma: "σ", + tau: "τ", + upsilon: "υ", + phi: "φ", + chi: "χ", + psi: "ψ", + omega: "ω", + thetasym: "ϑ", + upsih: "ϒ", + piv: "ϖ", + OElig: "Œ", + oelig: "œ", + Scaron: "Š", + scaron: "š", + Yuml: "Ÿ", + fnof: "ƒ", + circ: "ˆ", + tilde: "˜", + ensp: " ", + emsp: " ", + thinsp: " ", + zwnj: "‌", + zwj: "‍", + lrm: "‎", + rlm: "‏", + ndash: "–", + mdash: "—", + lsquo: "‘", + rsquo: "’", + sbquo: "‚", + ldquo: "“", + rdquo: "”", + bdquo: "„", + dagger: "†", + Dagger: "‡", + bull: "•", + hellip: "…", + permil: "‰", + prime: "′", + Prime: "″", + lsaquo: "‹", + rsaquo: "›", + oline: "‾", + euro: "€", + trade: "™", + larr: "←", + uarr: "↑", + rarr: "→", + darr: "↓", + harr: "↔", + crarr: "↵", + lceil: "⌈", + rceil: "⌉", + lfloor: "⌊", + rfloor: "⌋", + loz: "◊", + spades: "♠", + clubs: "♣", + hearts: "♥", + diams: "♦" +}; +//for(var n in exports.entityMap){console.log(exports.entityMap[n].charCodeAt())} \ No newline at end of file diff --git a/adapters/common/xmldom/sax.js b/adapters/common/xmldom/sax.js new file mode 100644 index 00000000..f8b494e0 --- /dev/null +++ b/adapters/common/xmldom/sax.js @@ -0,0 +1,616 @@ +//[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)//