/*=: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('', node.nodeName.toLowerCase(), '>');
} 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(',')
};
}
};