/*=:project parseSelector 2.0 =:description Provides an extensible way of parsing CSS selectors against a DOM in JavaScript. =:file Copyright: 2006 Mark Wubben. Author: Mark Wubben, =:license * This software is licensed and provided under the CC-GNU LGPL * See =:notes * The parsing of CSS selectors as streams has been based on Dean Edwards excellent work with cssQuery. See for more info. */ var parseSelector = (function() { var SEPERATOR = /\s*,\s*/ function parseSelector(selector, node) { node = node || document.documentElement; var selectors = selector.split(SEPERATOR); var result = []; for(var i = 0; i < selectors.length; i++) { var nodes = [node]; var stream = toStream(selectors[i]); for(var j = 0;j < stream.length;) { var token = stream[j++]; var filter = stream[j++]; var args = ''; if(stream[j] == '(') { while(stream[j++] != ')' && j < stream.length) args += stream[j]; args = args.slice(0, -1); } nodes = select(nodes, token, filter, args); } result = result.concat(nodes); } return result; } var WHITESPACE = /\s*([\s>+~(),]|^|$)\s*/g; var IMPLIED_ALL = /([\s>+~,]|[^(]\+|^)([#.:@])/g; var STANDARD_SELECT = /^[^\s>+~]/; var STREAM = /[\s#.:>+~()@]|[^\s#.:>+~()@]+/g; function toStream(selector) { var stream = selector.replace(WHITESPACE, '$1') .replace(IMPLIED_ALL, '$1*$2'); if(STANDARD_SELECT.test(stream)) stream = ' ' + stream; return stream.match(STREAM) || []; } function select(nodes, token, filter, args) { /*alert('"'+token+'"\n'+filter+'\n'+args+'\n'+nodes);*/ return (selectors[token]) ? selectors[token](nodes, filter, args) : []; } var util = { toArray: function(enumerable) { var a = []; for(var i = 0; i < enumerable.length; i++) util.push(a, enumerable[i]); return a; }, push: function(arr) { for(var i = 1; i < arguments.length; i++) arr[arr.length] = arguments[i]; return arr.length; } }; var dom = { isTag: function(node, tag) { return (tag == '*') || ( tag.toLowerCase() == node.nodeName.toLowerCase().replace(':html', '') ); }, previousSiblingElement: function(node) { do node = node.previousSibling; while(node && node.nodeType != 1); return node; }, nextSiblingElement: function(node) { do node = node.nextSibling; while(node && node.nodeType != 1); return node; }, hasClass: function(name, node) { return (node.className || '').match('(^|\\s)'+name+'(\\s|$)'); }, getByTag: function(tag, node) { /* IE5.x does not support document.getElementsByTagName("*") therefore we're falling back to element.all */ if(tag == '*') { var nodes = node.getElementsByTagName(tag); if(nodes.length == 0 && node.all != null) return node.all return nodes; } return node.getElementsByTagName(tag); } }; var selectors = { '#': function(nodes, filter) { for(var i = 0; i < nodes.length; i++) { if(nodes[i].getAttribute('id') + '' == filter) return [nodes[i]]; } return []; }, ' ': function(nodes, filter) { var result = []; for(var i = 0; i < nodes.length; i++) { result = result.concat(util.toArray(dom.getByTag(filter, nodes[i]))); } return result; }, '>': function(nodes, filter) { var result = []; for(var i = 0, node; i < nodes.length; i++) { node = nodes[i]; for(var j = 0, child; j < node.childNodes.length; j++) { child = node.childNodes[j]; if(child.nodeType == 1 && dom.isTag(child, filter)) { util.push(result, child); } } } return result; }, '.': function(nodes, filter) { var result = []; for(var i = 0, node; i < nodes.length; i++) { node = nodes[i]; if(dom.hasClass([filter], node)) util.push(result, node); } return result; }, ':': function(nodes, filter, args) { return (pseudoClasses[filter]) ? pseudoClasses[filter](nodes, args) : []; } }; parseSelector.selectors = selectors; parseSelector.pseudoClasses = {}; parseSelector.util = util; parseSelector.dom = dom; return parseSelector; })(); /*=:project scalable Inman Flash Replacement (sIFR) version 3, revision 106 =:file Copyright: 2006 Mark Wubben. Author: Mark Wubben, =:history * IFR: Shaun Inman * sIFR 1: Mike Davidson, Shaun Inman and Tomas Jogin * sIFR 2: Mike Davidson, Shaun Inman, Tomas Jogin and Mark Wubben =:license * This software is licensed and provided under the CC-GNU LGPL * See */ var sIFR = new function() { //=:private Constant reference to the Singleton instance var SIFR = this; var CSS_HASFLASH = 'sIFR-hasFlash'; var CSS_REPLACED = 'sIFR-replaced'; var CSS_FLASH = 'sIFR-flash'; var CSS_IGNORE = 'sIFR-ignore'; var CSS_ALTERNATE = 'sIFR-alternate'; var CSS_CLASS = 'sIFR-class'; var XHTML_NS = 'http://www.w3.org/1999/xhtml'; var MIN_FONT_SIZE = 6; var MAX_FONT_SIZE = 126; var MIN_FLASH_VERSION = 7; var ADVANCED_FLASH_VERSION = 8; var PREFETCH_COOKIE = 'SIFR-PREFETCHED'; this.compatMode = false; this.isActive = false; this.isEnabled = true; this.hideElements = true; this.replaceNonDisplayed = false; this.registerEvents = true; this.waitForPrefetch = true; this.cookiePath = '/'; this.domains = []; // In Gecko, preceding the sIFR element with a floated element, inside a fixed-width wrapper, // gives wrong dimensions. Set this property to `true` to force the temporary clearing of the // sIFR element, which works around the problem. this.forceClear = false; var elementCount = 0; var hasPrefetched = false; var advancedModeOnly = false; // Advanced mode requires Flash 8 or above. var ua = new function() { var ua = navigator.userAgent.toLowerCase(); var product = (navigator.product || '').toLowerCase(); this.macintosh = ua.indexOf('mac') > -1; this.windows = ua.indexOf('windows') > -1; this.opera = ua.indexOf('opera') > -1; this.konqueror = product.indexOf('konqueror') > -1; this.ie = ua.indexOf('ie') > -1 && !this.opera; this.ieWin = this.ie && this.windows; this.safari = ua.indexOf('safari') > -1; this.webkit = ua.indexOf('applewebkit') > -1 && !this.konqueror; this.khtml = this.webkit || this.konqueror; this.gecko = !this.webkit && product == 'gecko'; var $; this.operaVersion = this.webkitVersion = this.geckoBuildDate = this.konquerorVersion = 0; if(this.opera) { $ = ua.match(/.*opera(\s|\/)(\d+\.\d+)/); this.operaVersion = $.length > 1 ? parseInt($[2]) : 0; } if(this.webkit) { $ = ua.match(/.*applewebkit\/(\d+).*/); this.webkitVersion = $.length > 0 ? parseInt($[1]) : 0; } if(this.gecko) { $ = ua.match(/.*gecko\/(\d{8}).*/); this.geckoBuildDate = $.length > 0 ? parseInt($[1]) : 0; } if(this.konqueror) { $ = ua.match(/.*konqueror\/(\d\.\d).*/); this.konquerorVersion = $.length > 0 ? parseInt($[1]) : 0; } this.flashVersion = 0; if(this.ieWin) { try { this.flashVersion = parseFloat( /([\d,?]+)/.exec( new ActiveXObject('ShockwaveFlash.ShockwaveFlash.7').GetVariable('$version') )[1].replace(/,/g, '.') ); } catch(e) {} } else if(navigator.plugins && navigator.plugins['Shockwave Flash']) { var flashPlugin = navigator.plugins['Shockwave Flash']; this.flashVersion = parseFloat(/(\d+\.?\d*)/.exec(flashPlugin.description)[1]); // Watch out for QuickTime, which could be stealing the Flash handling! var i = 0; while(this.flashVersion >= MIN_FLASH_VERSION && i < navigator.mimeTypes.length) { var mime = navigator.mimeTypes[i]; if(mime.type == 'application/x-shockwave-flash' && mime.enabledPlugin.description.toLowerCase().indexOf('quicktime') > -1) { this.flashVersion = 0; } i++; } } this.flash = this.flashVersion >= MIN_FLASH_VERSION; this.transparencySupport = true; if(!this.macintosh && !this.windows || this.opera && this.operaVersion < 7.6 || this.webkit && this.webkitVersion < 312 || this.gecko && this.geckoBuildDate < 20020523) { this.transparencySupport = false; } this.computedStyleSupport = true; if(!this.ie && !this.gecko && (!document.defaultView || !document.defaultView.getComputedStyle) //: Older Geckos have trouble with `getComputedStyle()` || this.gecko && this.geckoBuildDate < 20030624) { this.computedStyleSupport = false; } this.zoomSupport = !!(this.opera && document.documentElement); this.geckoXml = this.gecko && (document.contentType || '').indexOf("xml") > -1; this.innerHtmlHack = this.konqueror || (this.webkit && this.webkitVersion < 312) || this.ie; this.requiresPrefetch = this.ieWin || this.safari; this.supported = (!this.ie || this.ieWin) && (!this.macintosh || !this.opera || this.operaVersion >= 8) && (!ua.webkit || ua.webkitVersion >= 100) && !!(Array.prototype.push && Array.prototype.pop && Array.prototype.splice); }; this.ua = ua; var dom = new function() { this.getBody = function() { var nodes = document.getElementsByTagName('body'); if(nodes.length == 1) return nodes[0]; }; this.addClass = function(name, node) { node.className = ((node.className || '') == '' ? '' : node.className + ' ') + name; }; this.hasClass = function(name, node) { return new RegExp('(^|\\s)'+name+'(\\s|$)').test(node.className); }; this.create = function(name) { if(document.createElementNS) return document.createElementNS(XHTML_NS, name); return document.createElement(name); } var useInnerHtml; try { var n = this.create('span'); n.innerHTML = 'x'; useInnerHtml = n.innerHTML == 'x'; } catch (e) { useInnerHtml = false; } this.setInnerHtml = function(node, html) { if(useInnerHtml) node.innerHTML = html; else { html = ['', html, ''].join(''); var xml = (new DOMParser()).parseFromString(html, 'text/xml'); xml = document.importNode(xml.documentElement, true); while(node.firstChild) node.removeChild(node.firstChild); while(xml.firstChild) node.appendChild(xml.firstChild); } } this.appendNode = function(to, node) { to.appendChild(node); if(this.innerHtmlHack) to.innerHTML += ''; } this.getStyleAsInt = function(node, property) { if(!ua.computedStyleSupport && !SIFR.compatMode) return null; var value; if(document.defaultView && document.defaultView.getComputedStyle) value = document.defaultView.getComputedStyle(node, null)[property]; else if(node.currentStyle) value = node.currentStyle[property]; value = parseInt(value); return isNaN(value) ? 0 : value; } this.getZoom = function() { return hacks.zoom.getLatest(); } }; this.dom = dom; var util = { normalize: function(str) { return str.replace(/(\s)\s+/g, '$1'); }, toJson: function(obj) { var json = ''; switch(typeof(obj)) { case 'string': json = '"' + obj + '"'; break; case 'number': case 'boolean': json = obj.toString(); break; case 'object': json = []; for(var prop in obj) { if(obj[prop] == Object.prototype[prop]) continue; json.push(prop + ':', util.toJson(obj[prop])); } json = '{' + json.join(',') + '}'; break; } return json; } }; this.util = util; var hacks = {}; hacks.fragmentIdentifier = new function() { this.fix = true; var cachedTitle; this.cache = function() { cachedTitle = document.title; }; function doFix() { document.title = cachedTitle; } this.restore = function() { if(this.fix) setTimeout(doFix, 0); }; }; // The zoom hack needs to be run before replace(). The synchronizer can be // used to ensure this. hacks.synchronizer = new function() { this.isBlocked = false; this.block = function() { this.isBlocked = true; }; this.unblock = function() { this.isBlocked = false; blockedReplaceKwargsStore.replaceAll(); }; }; // Detect the page zoom in Opera. Adapted from . hacks.zoom = new function() { // Latest zoom, assume 100 var latestZoom = 100; this.getLatest = function() { return latestZoom; } if(ua.zoomSupport && ua.opera) { // Create the DOM element used to calculate the zoom. var node = document.createElement('div'); node.style.position = 'fixed'; node.style.left = '-65536px'; node.style.top = '0'; node.style.height = '100%'; node.style.width = '1px'; node.style.zIndex = '-32'; document.documentElement.appendChild(node); function updateZoom() { if(!node) return; var zoom = window.innerHeight / node.offsetHeight; var correction = Math.round(zoom * 100) % 10; if(correction > 5) zoom = Math.round(zoom * 100) + 10 - correction; else zoom = Math.round(zoom * 100) - correction; latestZoom = isNaN(zoom) ? 100 : zoom; hacks.synchronizer.unblock(); document.documentElement.removeChild(node); node = null; } hacks.synchronizer.block(); // We need to wait a few ms before Opera the offsetHeight of the node // becomes available. setTimeout(updateZoom, 54); } }; this.hacks = hacks; var replaceKwargsStore = { kwargs: [], replaceAll: function() { for(var i = 0; i < this.kwargs.length; i++) SIFR.replace(this.kwargs[i]); this.kwargs = []; } }; var blockedReplaceKwargsStore = { kwargs: [], replaceAll: replaceKwargsStore.replaceAll }; this.useAdvancedModeOnly = function(version) { if(version && version < MIN_FLASH_VERSION) return; advancedModeOnly = true; ua.flash = ua.flashVersion >= (version || ADVANCED_FLASH_VERSION); } function isValidDomain() { var domain = ''; try { // When trying to access document.domain on a Google-translated page with Firebug, I got an exception. domain = document.domain; } catch(e) {}; // The goal here is not to prevent usage of the Flash movie, but running sIFR on possibly translated pages if(domain == 'localhost' || SIFR.domains.length == 0) return true; for(var i = 0; i < SIFR.domains.length; i++) { if(new RegExp(SIFR.domains[i] + '$').test(domain)) return true; } return false; } this.activate = function() { if(!ua.flash || !this.isEnabled || this.isActive || !isValidDomain() || !ua.supported || !this.compatMode && !ua.computedStyleSupport) { return; } this.isActive = true; if(this.hideElements) this.setFlashClass(); if(ua.ieWin && hacks.fragmentIdentifier.fix && window.location.hash != '') { hacks.fragmentIdentifier.cache(); } else hacks.fragmentIdentifier.fix = false; if(!this.registerEvents) return; function handler(evt) { SIFR.initialize(true); // Remove handlers to prevent memory leak in Firefox 1.5 if(document.removeEventListener) document.removeEventListener('load', handler, false); if(window.removeEventListener) window.removeEventListener('load', handler, false); } if(window.attachEvent) { window.attachEvent('onload', handler); } else if((!ua.konqueror || ua.konquerorVersion < 3.5) && (document.addEventListener || window.addEventListener)) { if(document.addEventListener) document.addEventListener('load', handler, false); if(window.addEventListener) window.addEventListener('load', handler, false); } else { if(typeof window.onload == 'function'){ var fOld = window.onload; window.onload = function(evt){ fOld(evt); handler(evt); }; } else window.onload = handler; } }; this.hasFlashClass = false; this.setFlashClass = function() { if(this.hasFlashClass) return; dom.addClass(CSS_HASFLASH, dom.getBody() || document.documentElement); this.hasFlashClass = true; }; this.isInitialized = false; this.initialize = function(evt) { if(this.isInitialized || !this.isActive || !this.isEnabled || !evt && (ua.requiresPrefetch && hasPrefetched && this.waitForPrefetch || !this.uaCompletedRendering())) { return; } this.isInitialized = true; replaceKwargsStore.replaceAll(); clearPrefetch(); }; this.uaCompletedRendering = function() { return (this.isInitialized || !ua.khtml && !ua.geckoXml && !!dom.getBody()); }; function getSource(src) { if(typeof(src) == 'string') return src; var versions = []; for(var version in src) if(src[version] != Object.prototype.version) versions.push(version); versions.sort().reverse(); for(var i = 0; i < versions.length; i++) { if(parseFloat(versions[i]) <= ua.flashVersion) return src[versions[i]]; } throw new Error("sIFR: Could not determine appropriate source"); } this.prefetch = function() { if(!ua.requiresPrefetch || !ua.flash || !this.isEnabled || !isValidDomain()) return; if(this.waitForPrefetch && new RegExp(';?' + PREFETCH_COOKIE + '=true;?').test(document.cookie)) return; try { // We don't know which DOM actions the user agent will allow hasPrefetched = true; if(ua.ieWin) prefetchIexplore(arguments); else prefetchLight(arguments); if(this.waitForPrefetch) document.cookie = PREFETCH_COOKIE + '=true;path=' + this.cookiePath; } catch(e) {} }; function prefetchIexplore(args) { var head = document.getElementsByTagName('head')[0]; for(var i = 0; i < args.length; i++) { var node = dom.create('embed'); head.appendChild(node); node.setAttribute('src', getSource(args[i])); node.setAttribute('sIFR-prefetch', 'true'); } } // Lightweight prefetch method function prefetchLight(args) { for(var i = 0; i < args.length; i++) new Image().src = getSource(args[i]); } function clearPrefetch() { if(!ua.ieWin || !hasPrefetched) return; try { var head = document.getElementsByTagName('head')[0]; var nodes = head.getElementsByTagName('embed'); for(var i = nodes.length - 1; i >= 0; i--) { if(node.getAttribute('sIFR-prefetch') == 'true') head.removeChild(node); } } catch(e) {} } // Gives a font-size to required vertical space ratio // Tested with Verdana function getRatio(size) { if(size <= 10) return 1.55; if(size <= 19) return 1.45; if(size <= 32) return 1.35; if(size <= 71) return 1.30; return 1.25; } function convertCssArg(arg) { if(!arg) return ''; var css = []; for(var selector in arg) { var rule = arg[selector]; if(rule == Object.prototype[selector]) continue; css.push(selector, '{'); for(var property in rule) { if(rule[property] == Object.prototype[property]) continue; css.push(property, ':', rule[property], ';'); } css.push('}'); } return escape(css.join('')); } function extractFromCss(css, selector, property, remove) { var value = null; if(css && css[selector] && css[selector][property]) { value = css[selector][property]; if(remove) delete css[selector][property]; } return value; } function getFilters(obj) { var filters = []; for(var filter in obj) { if(obj[filter] == Object.prototype[filter]) continue; var properties = obj[filter]; filter = [filter.replace(/filter/i, '') + 'Filter']; for(var property in properties) { if(properties[property] == Object.prototype[property]) continue; filter.push(property + ':' + escape(util.toJson(properties[property]))); } filters.push(filter.join(',')); } return filters.join(';'); } this.replace = function(kwargs) { if(!ua.supported) return; if(!this.isInitialized) return replaceKwargsStore.kwargs.push(kwargs); if(hacks.synchronizer.isBlocked) return blockedReplaceKwargsStore.kwargs.push(kwargs); var nodes = parseSelector(kwargs.selector); if(nodes.length == 0) return; this.setFlashClass(); var src = getSource(kwargs.src); var css = convertCssArg(kwargs.css); var filters = ua.flashVersion >= ADVANCED_FLASH_VERSION ? getFilters(kwargs.filters) : ''; var leading = parseInt(extractFromCss(kwargs.css, '.sIFR-root', 'leading')) || 0; var backgroundColor = extractFromCss(kwargs.css, '.sIFR-root', 'background-color', true) || '#FFFFFF'; var gridFitType = kwargs.gridFitType || extractFromCss(kwargs.css, '.sIFR-root', 'text-align') != 'left' ? 'subpixel' : 'pixel'; var wmode = kwargs.wmode || ''; if(wmode == 'transparent') { if(!ua.transparencySupport) wmode = 'opaque'; else backgroundColor = 'transparent'; } // Some IE installs refuse to show the Flash unless it gets the really absolute // URI of the file. I haven't been able to reproduce this behavior but let's // ensure a full URI none the less. if(ua.ie && src.charAt(0) == '/') { src = location.toString().replace(/([^:]+)(:\/?\/?)([^\/]+).*/, '$1$2$3') + src; } for(var i = 0; i < nodes.length; i++) { var node = nodes[i]; if(dom.hasClass(CSS_REPLACED, node) || dom.hasClass(CSS_IGNORE, node)) { continue; } // Elements with no height (`0` in IE, `undefined` in Safari) are usually `display: none`. // Let's attempt to display them and replace them anyway. var resetDisplay = false; if(!node.offsetHeight) { if(!SIFR.replaceNonDisplayed) continue; node.style.display = 'block'; if(!node.offsetHeight) { // If they are still without height, don't replace them. node.style.display = ''; continue; } resetDisplay = true; } if(SIFR.forceClear && ua.gecko) node.style.clear = 'both'; // Determine lineHeight (the font-size used in the Flash) and the number // of lines. We also need the dimensions in order to calculate word // wrapping. var paddingTop = dom.getStyleAsInt(node, 'paddingTop') || 0; var paddingRight = dom.getStyleAsInt(node, 'paddingRight') || 0; var paddingBottom = dom.getStyleAsInt(node, 'paddingBottom') || 0; var paddingLeft = dom.getStyleAsInt(node, 'paddingLeft') || 0; var borderTop = dom.getStyleAsInt(node, 'borderTopWidth') || 0; var borderRight = dom.getStyleAsInt(node, 'borderRightWidth') || 0; var borderBottom = dom.getStyleAsInt(node, 'borderBottomWidth') || 0; var borderLeft = dom.getStyleAsInt(node, 'borderLeftWidth') || 0; var height = node.offsetHeight - paddingTop - paddingBottom - borderTop - borderBottom; if(!ua.computedStyleSupport) height -= kwargs.verticalSpacing || 0; var width = node.offsetWidth - paddingLeft - paddingRight - borderLeft - borderRight; if(!ua.computedStyleSupport) width -= kwargs.horizontalSpacing || 0; var lineHeight, lines; if(!ua.ie) { //:=todo Only do once for each selector? if(!ua.computedStyleSupport && SIFR.compatMode) { var html = node.innerHTML; dom.setInnerHtml(node, 'X'); lineHeight = node.offsetHeight - paddingTop - paddingBottom - borderTop - borderBottom; dom.setInnerHtml(node, html); } else lineHeight = dom.getStyleAsInt(node, 'lineHeight'); lines = Math.floor(height / lineHeight); } else if(ua.ie) { // IE returs computed style in the original units, which is quite useless. var html = node.innerHTML; dom.setInnerHtml(node, 'X
X
X'); // Without these settings, we won't be able to get the rects properly. node.style.visibility = 'visible'; node.style.width = 'auto'; node.style.styleFloat = 'none'; var rects = node.getClientRects(); lineHeight = rects[1].bottom - rects[1].top; // In IE, the lineHeight is about 1.25 times the height in other browsers. lineHeight = Math.ceil(lineHeight * 0.8); dom.setInnerHtml(node, html); rects = node.getClientRects(); lines = rects.length; // By setting an empty string, the values will fall back to those in the (non-inline) CSS. // When that CSS changes, the changes are reflected here. Setting explicit values would break // that behaviour. node.style.styleFloat = ''; node.style.width = ''; node.style.visibility = ''; } // We have all the info we need, reset the display setting now. if(resetDisplay) node.style.display = ''; if(SIFR.forceClear && ua.gecko) node.style.clear = ''; lineHeight = Math.max(MIN_FONT_SIZE, lineHeight); lineHeight = Math.min(MAX_FONT_SIZE, lineHeight); if(isNaN(lines) || !isFinite(lines)) lines = 1; height = Math.round(lines * lineHeight); if(lines > 1 && leading) height += Math.round((lines - 1) * leading); // I wanted to use `noembed` here, but unfortunately FlashBlock only works with `span.sIFR-alternate` var alternate = dom.create('span'); alternate.className = CSS_ALTERNATE; // Clone the original content to the alternate element. var contentNode = node.cloneNode(true); for(var j = 0, l = contentNode.childNodes.length; j < l; j++) { alternate.appendChild(contentNode.childNodes[j].cloneNode(true)); } // Allow the sIFR content to be modified if(kwargs.modifyContent) kwargs.modifyContent(contentNode); var contentObj = handleContent(contentNode); var $ = contentObj.content.match(/
/g); var extraLines = $ ? $.length : 0; var vars = ['content=' + contentObj.content, 'chars=' + contentObj.chars, 'links=' + contentObj.links, 'targets=' + contentObj.targets, 'w=' + width, 'h=' + height, 'thickness=' + kwargs.thickness || '', 'sharpness=' + kwargs.sharpness || '', 'kerning=' + kwargs.kerning || '', 'gridfittype=' + gridFitType, 'zoomsupport=' + ua.zoomSupport, 'lines=' + lines, 'extralines=' + extraLines, 'filters=' + filters, 'size=' + lineHeight, 'zoom=' + dom.getZoom(), 'css=' + css]; var callbackName = 'sIFR_callback_' + elementCount++; window[callbackName + '_DoFSCommand'] = (function(node) { return function(info, arg) { //if(info == 'FSCommand:log') console.log(arg); if(/(FSCommand\:)?resize/.test(info)) { var $ = arg.split(':'); node.firstChild.setAttribute($[0], $[1]); // Here comes another story! // // Good old Safari (saw this in 2.0.3) will *not* repaint the // Flash movie with the new dimensions *until* the document // receives a UIEvent. I haven't tested this throroughly, so it // might respond to other events as well. // // The solution is to trick Safari into thinking that the `embed` // element has changed, this is done by adding an empty string // to it's `innerHTML`. Be aware though that adding this string // to `node` (the parent of the `embed` element) will immediately // crash Safari. // // Just to be sure this hack is applied to all browsers which // implement the KHTML engine. // /*:=todo Test this bug in older browsers to see if it occurs there as well. */ if(ua.khtml) node.firstChild.innerHTML += ''; } } })(node); // Approach the final height to avoid annoying movements of the page height = Math.round(lines * getRatio(lineHeight) * lineHeight); var flash; if(ua.ie) { flash = [ '', '', '', '', '', '', '', '', // Load in the callback code. Keep the ' ].join(''); } else { flash = [ '' ].join(''); } dom.setInnerHtml(node, flash); dom.appendNode(node, alternate); dom.addClass(CSS_REPLACED, node); } hacks.fragmentIdentifier.restore(); }; /*=:private Walks through the childNodes of `source`. Generates a text representation of these childNodes. Returns: * string: the text representation. Notes: * A number of items are still to do. See the individual comments for that. * This method does not recursion because it'll be necessary to keep a count of all links and their URIs. This is easier without recursion. */ function handleContent(source) { //:=todo add filters var stack = []; var nodes = source.childNodes; //:=todo dojo string builder for speed? // We start the arrays with default values. This stops us from having to // check if the array is empty when parsing the content. Don't mess with // this, it works. var content = ['']; var chars = [',']; var links = []; var targets = []; var i = 0; while(i < nodes.length) { var node = nodes[i]; if(node.nodeType == 3) { var text = util.normalize(node.nodeValue); if(chars[chars.length - 1] != ',' && /^\s/.test(text)) { content.push(','); chars.push(','); } var words = text.replace(/\s$/, '').split(/\s/); var j = 0; while(j < words.length) { if(words[j].length == 0) words.splice(j, 1); else { if(chars[chars.length - 1] != ',') chars[chars.length - 1] += words[j].length; else chars.push(words[j].length); // Escape reserved characters content.push(words[j].replace(/\%/g, '%25').replace(/\&/g, '%26').replace(/\,/g, '%2C')); if(j < words.length - 1) { content.push(','); chars.push(','); } j++; } } if(/\s$/.test(text) && chars[chars.length - 1] != ',') { content.push(','); chars.push(','); } } if(node.nodeType == 1) { var attributes = []; var nodeName = node.nodeName.toLowerCase(); var className = node.className || ''; // If there are multiple classes, look for the specified sIFR class if(/\s+/.test(className)) { if(className.indexOf(CSS_CLASS)) className = className.match('(\\s|^)' + CSS_CLASS + '-([^\\s$]*)(\\s|$)')[2]; // or use the first class else className = className.match(/^([^\s]+)/)[1]; } if(className != '') attributes.push('class="' + className + '"'); switch(nodeName) { case 'a': var href = node.getAttribute('href') || ''; var target = node.getAttribute('target') || ''; if(href != '') { links.push(escape(href)); targets.push(escape(target)); attributes.push('href="asfunction:sIFR.followLink,' + (links.length - 1) + '"'); } break; case 'br': if(/^
\s*$/.test(content[content.length - 1])) chars.push(0); if(chars[chars.length - 1] != ',') { content.push(','); chars.push(','); } break; } // The tag needs to be one string in order to detect sequenced
tags content.push( '<' + nodeName + (attributes.length > 0 ? ' ' : '') + escape(attributes.join(' ')) + '>' ); //:=todo pass to tag filters to get attributes (this.filters.filterNode) if(node.hasChildNodes()) { // Push the current index to the stack and prepare to iterate // over the childNodes. stack.push(i); i = 0; nodes = node.childNodes; continue; } } if(stack.length > 0 && !node.nextSibling) { // Iterating the childNodes has been completed. Go back to the position // before we started the iteration. If that position was the last child, // go back even further. do { i = stack.pop(); nodes = node.parentNode.parentNode.childNodes; node = nodes[i]; if(node) content.push(''); } while(i < nodes.length && stack.length > 0); } i++; } // Force the `content` and `chars` arrays into a known shape, depending // on the DOM the arrays might be different and carry extraneous // information, this will be sliced out when the function returns. if(content[content.length - 1] != ',') content.push(','); if(content[1] != ',') content.splice(1, 0, ','); if(chars[chars.length - 1] != ',') chars.push(','); if(chars[1] != ',') chars.splice(1, 0, ','); return { content: content.slice(2, content.length - 1).join(''), chars: chars.slice(2, chars.length - 1).join(''), links: links.join(','), targets: targets.join(',') }; } };