http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/cssom/iframe/style.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/cssom/iframe/style.js b/externs/GCL/externs/goog/cssom/iframe/style.js new file mode 100644 index 0000000..24c8c05 --- /dev/null +++ b/externs/GCL/externs/goog/cssom/iframe/style.js @@ -0,0 +1,1016 @@ +// Copyright 2007 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// All Rights Reserved. + +/** + * @fileoverview Provides utility routines for copying modified + * {@code CSSRule} objects from the parent document into iframes so that any + * content in the iframe will be styled as if it was inline in the parent + * document. + * + * <p> + * For example, you might have this CSS rule: + * + * #content .highlighted { background-color: yellow; } + * + * And this DOM structure: + * + * <div id="content"> + * <iframe /> + * </div> + * + * Then inside the iframe you have: + * + * <body> + * <div class="highlighted"> + * </body> + * + * If you copied the CSS rule directly into the iframe, it wouldn't match the + * .highlighted div. So we rewrite the original stylesheets based on the + * context where the iframe is going to be inserted. In this case the CSS + * selector would be rewritten to: + * + * body .highlighted { background-color: yellow; } + * </p> + * + */ + + +goog.provide('goog.cssom.iframe.style'); + +goog.require('goog.asserts'); +goog.require('goog.cssom'); +goog.require('goog.dom'); +goog.require('goog.dom.NodeType'); +goog.require('goog.dom.TagName'); +goog.require('goog.dom.classlist'); +goog.require('goog.string'); +goog.require('goog.style'); +goog.require('goog.userAgent'); + + +/** + * Regexp that matches "a", "a:link", "a:visited", etc. + * @type {RegExp} + * @private + */ +goog.cssom.iframe.style.selectorPartAnchorRegex_ = + /a(:(link|visited|active|hover))?/; + + +/** + * Delimiter between selectors (h1, h2) + * @type {string} + * @private + */ +goog.cssom.iframe.style.SELECTOR_DELIMITER_ = ','; + + +/** + * Delimiter between selector parts (.main h1) + * @type {string} + * @private + */ +goog.cssom.iframe.style.SELECTOR_PART_DELIMITER_ = ' '; + + +/** + * Delimiter marking the start of a css rules section ( h1 { ) + * @type {string} + * @private + */ +goog.cssom.iframe.style.DECLARATION_START_DELIMITER_ = '{'; + + +/** + * Delimiter marking the end of a css rules section ( } ) + * @type {string} + * @private + */ +goog.cssom.iframe.style.DECLARATION_END_DELIMITER_ = '}\n'; + + + +/** + * Class representing a CSS rule set. A rule set is something like this: + * h1, h2 { font-family: Arial; color: red; } + * @constructor + * @private + */ +goog.cssom.iframe.style.CssRuleSet_ = function() { + /** + * Text of the declarations inside the rule set. + * For example: 'font-family: Arial; color: red;' + * @type {string} + */ + this.declarationText = ''; + + /** + * Array of CssSelector objects, one for each selector. + * Example: [h1, h2] + * @type {Array<goog.cssom.iframe.style.CssSelector_>} + */ + this.selectors = []; +}; + + +/** + * Initializes the rule set from a {@code CSSRule}. + * + * @param {CSSRule} cssRule The {@code CSSRule} to initialize from. + * @return {boolean} True if initialization succeeded. We only support + * {@code CSSStyleRule} and {@code CSSFontFaceRule} objects. + */ +goog.cssom.iframe.style.CssRuleSet_.prototype.initializeFromCssRule = + function(cssRule) { + var ruleStyle = cssRule.style; // Cache object for performance. + if (!ruleStyle) { + return false; + } + var selector; + var declarations = ''; + if (ruleStyle && + (selector = cssRule.selectorText) && + (declarations = ruleStyle.cssText)) { + // IE get confused about cssText context if a stylesheet uses the + // mid-pass hack, and it ends up with an open comment (/*) but no + // closing comment. This will effectively comment out large parts + // of generated stylesheets later. This errs on the safe side by + // always tacking on an empty comment to force comments to be closed + // We used to check for a troublesome open comment using a regular + // expression, but it's faster not to check and always do this. + if (goog.userAgent.IE) { + declarations += '/* */'; + } + } else if (cssRule.cssText) { + var cssSelectorMatch = /([^\{]+)\{/; + var endTagMatch = /\}[^\}]*$/g; + // cssRule.cssText contains both selector and declarations: + // parse them out. + selector = cssSelectorMatch.exec(cssRule.cssText)[1]; + // Remove selector, {, and trailing }. + declarations = cssRule.cssText.replace(cssSelectorMatch, '').replace( + endTagMatch, ''); + } + if (selector) { + this.setSelectorsFromString(selector); + this.declarationText = declarations; + return true; + } + return false; +}; + + +/** + * Parses a selectors string (which may contain multiple comma-delimited + * selectors) and loads the results into this.selectors. + * @param {string} selectorsString String containing selectors. + */ +goog.cssom.iframe.style.CssRuleSet_.prototype.setSelectorsFromString = + function(selectorsString) { + this.selectors = []; + var selectors = selectorsString.split(/,\s*/gm); + for (var i = 0; i < selectors.length; i++) { + var selector = selectors[i]; + if (selector.length > 0) { + this.selectors.push(new goog.cssom.iframe.style.CssSelector_(selector)); + } + } +}; + + +/** + * Make a copy of this ruleset. + * @return {!goog.cssom.iframe.style.CssRuleSet_} A new CssRuleSet containing + * the same data as this one. + */ +goog.cssom.iframe.style.CssRuleSet_.prototype.clone = function() { + var newRuleSet = new goog.cssom.iframe.style.CssRuleSet_(); + newRuleSet.selectors = this.selectors.concat(); + newRuleSet.declarationText = this.declarationText; + return newRuleSet; +}; + + +/** + * Set the declaration text with properties from a given object. + * @param {Object} sourceObject Object whose properties and values should + * be used to generate the declaration text. + * @param {boolean=} opt_important Whether !important should be added to each + * declaration. + */ +goog.cssom.iframe.style.CssRuleSet_.prototype.setDeclarationTextFromObject = + function(sourceObject, opt_important) { + var stringParts = []; + // TODO(user): for ... in is costly in IE6 (extra garbage collection). + for (var prop in sourceObject) { + var value = sourceObject[prop]; + if (value) { + stringParts.push(prop, + ':', + value, (opt_important ? ' !important' : ''), + ';'); + } + } + this.declarationText = stringParts.join(''); +}; + + +/** + * Serializes this CssRuleSet_ into an array as a series of strings. + * The array can then be join()-ed to get a string representation + * of this ruleset. + * @param {Array<string>} array The array to which to append strings. + */ +goog.cssom.iframe.style.CssRuleSet_.prototype.writeToArray = function(array) { + var selectorCount = this.selectors.length; + var matchesAnchorTag = false; + for (var i = 0; i < selectorCount; i++) { + var selectorParts = this.selectors[i].parts; + var partCount = selectorParts.length; + for (var j = 0; j < partCount; j++) { + array.push(selectorParts[j].inputString_, + goog.cssom.iframe.style.SELECTOR_PART_DELIMITER_); + } + if (i < (selectorCount - 1)) { + array.push(goog.cssom.iframe.style.SELECTOR_DELIMITER_); + } + if (goog.userAgent.GECKO && + !goog.userAgent.isVersionOrHigher('1.9a')) { + // In Gecko pre-1.9 (Firefox 2 and lower) we need to add !important + // to rulesets that match "A" tags, otherwise Gecko's built-in + // stylesheet will take precedence when designMode is on. + matchesAnchorTag = matchesAnchorTag || + goog.cssom.iframe.style.selectorPartAnchorRegex_.test( + selectorParts[partCount - 1].inputString_); + } + } + var declarationText = this.declarationText; + if (matchesAnchorTag) { + declarationText = goog.cssom.iframe.style.makeColorRuleImportant_( + declarationText); + } + array.push(goog.cssom.iframe.style.DECLARATION_START_DELIMITER_, + declarationText, + goog.cssom.iframe.style.DECLARATION_END_DELIMITER_); +}; + + +/** + * Regexp that matches "color: value;". + * @type {RegExp} + * @private + */ +goog.cssom.iframe.style.colorImportantReplaceRegex_ = + /(^|;|{)\s*color:([^;]+);/g; + + +/** + * Adds !important to a css color: rule + * @param {string} cssText Text of the CSS rule(s) to modify. + * @return {string} Text with !important added to the color: rule if found. + * @private + */ +goog.cssom.iframe.style.makeColorRuleImportant_ = function(cssText) { + // Replace to insert a "! important" string. + return cssText.replace(goog.cssom.iframe.style.colorImportantReplaceRegex_, + '$1 color: $2 ! important; '); +}; + + + +/** + * Represents a single CSS selector, as described in + * http://www.w3.org/TR/REC-CSS2/selector.html + * Currently UNSUPPORTED are the following selector features: + * <ul> + * <li>pseudo-classes (:hover) + * <li>child selectors (div > h1) + * <li>adjacent sibling selectors (div + h1) + * <li>attribute selectors (input[type=submit]) + * </ul> + * @param {string=} opt_selectorString String containing selectors to parse. + * @constructor + * @private + */ +goog.cssom.iframe.style.CssSelector_ = function(opt_selectorString) { + + /** + * Object to track ancestry matches to speed up repeatedly testing this + * CssSelector against the same NodeAncestry object. + * @type {Object} + * @private + */ + this.ancestryMatchCache_ = {}; + if (opt_selectorString) { + this.setPartsFromString_(opt_selectorString); + } +}; + + +/** + * Parses a selector string into individual parts. + * @param {string} selectorString A string containing a CSS selector. + * @private + */ +goog.cssom.iframe.style.CssSelector_.prototype.setPartsFromString_ = + function(selectorString) { + var parts = []; + var selectorPartStrings = selectorString.split(/\s+/gm); + for (var i = 0; i < selectorPartStrings.length; i++) { + if (!selectorPartStrings[i]) { + continue; // Skip empty strings. + } + var part = new goog.cssom.iframe.style.CssSelectorPart_( + selectorPartStrings[i]); + parts.push(part); + } + this.parts = parts; +}; + + +/** + * Tests to see what part of a DOM element hierarchy would be matched by + * this selector, and returns the indexes of the matching element and matching + * selector part. + * <p> + * For example, given this hierarchy: + * document > html > body > div.content > div.sidebar > p + * and this CSS selector: + * body div.sidebar h1 + * This would return {elementIndex: 4, selectorPartIndex: 1}, + * indicating that the element at index 4 matched + * the css selector at index 1. + * </p> + * @param {goog.cssom.iframe.style.NodeAncestry_} elementAncestry Object + * representing an element and its ancestors. + * @return {Object} Object with the properties elementIndex and + * selectorPartIndex, or null if there was no match. + */ +goog.cssom.iframe.style.CssSelector_.prototype.matchElementAncestry = + function(elementAncestry) { + + var ancestryUid = elementAncestry.uid; + if (this.ancestryMatchCache_[ancestryUid]) { + return this.ancestryMatchCache_[ancestryUid]; + } + + // Walk through the selector parts and see how far down the element hierarchy + // we can go while matching the selector parts. + var elementIndex = 0; + var match = null; + var selectorPart = null; + var lastSelectorPart = null; + var ancestorNodes = elementAncestry.nodes; + var ancestorNodeCount = ancestorNodes.length; + + for (var i = 0; i <= this.parts.length; i++) { + selectorPart = this.parts[i]; + while (elementIndex < ancestorNodeCount) { + var currentElementInfo = ancestorNodes[elementIndex]; + if (selectorPart && + selectorPart.testElement(currentElementInfo)) { + match = { + elementIndex: elementIndex, + selectorPartIndex: i + }; + elementIndex++; + break; + } else if (lastSelectorPart && + lastSelectorPart.testElement(currentElementInfo)) { + match = { + elementIndex: elementIndex, + selectorPartIndex: i - 1 + }; + } + elementIndex++; + } + lastSelectorPart = selectorPart; + } + this.ancestryMatchCache_[ancestryUid] = match; + return match; +}; + + + +/** + * Represents one part of a CSS Selector. For example in the selector + * 'body #foo .bar', body, #foo, and .bar would be considered selector parts. + * In the official CSS spec these are called "simple selectors". + * @param {string} selectorPartString A string containing the selector part + * in css format. + * @constructor + * @private + */ +goog.cssom.iframe.style.CssSelectorPart_ = function(selectorPartString) { + // Only one CssSelectorPart instance should exist for a given string. + var cacheEntry = goog.cssom.iframe.style.CssSelectorPart_.instances_[ + selectorPartString]; + if (cacheEntry) { + return cacheEntry; + } + + // Optimization to avoid the more-expensive lookahead. + var identifiers; + if (selectorPartString.match(/[#\.]/)) { + // Lookahead regexp, won't work on IE 5.0. + identifiers = selectorPartString.split(/(?=[#\.])/); + } else { + identifiers = [selectorPartString]; + } + var properties = {}; + for (var i = 0; i < identifiers.length; i++) { + var identifier = identifiers[i]; + if (identifier.charAt(0) == '.') { + properties.className = identifier.substring(1, identifier.length); + } else if (identifier.charAt(0) == '#') { + properties.id = identifier.substring(1, identifier.length); + } else { + properties.tagName = identifier.toUpperCase(); + } + } + this.inputString_ = selectorPartString; + this.matchProperties_ = properties; + this.testedElements_ = {}; + goog.cssom.iframe.style.CssSelectorPart_.instances_[selectorPartString] = + this; +}; + + +/** + * Cache of existing CssSelectorPart_ instances. + * @type {Object} + * @private + */ +goog.cssom.iframe.style.CssSelectorPart_.instances_ = {}; + + +/** + * Test whether an element matches this selector part, considered in isolation. + * @param {Object} elementInfo Element properties to test. + * @return {boolean} Whether the element matched. + */ +goog.cssom.iframe.style.CssSelectorPart_.prototype.testElement = + function(elementInfo) { + + var elementUid = elementInfo.uid; + var cachedMatch = this.testedElements_[elementUid]; + if (typeof cachedMatch != 'undefined') { + return cachedMatch; + } + + var matchProperties = this.matchProperties_; + var testTag = matchProperties.tagName; + var testClass = matchProperties.className; + var testId = matchProperties.id; + + var matched = true; + if (testTag && testTag != '*' && testTag != elementInfo.nodeName) { + matched = false; + } else if (testId && testId != elementInfo.id) { + matched = false; + } else if (testClass && + !elementInfo.classNames[testClass]) { + matched = false; + } + + this.testedElements_[elementUid] = matched; + return matched; +}; + + + +/** + * Represents an element and all its parent/ancestor nodes. + * This class exists as an optimization so we run tests on an element + * hierarchy multiple times without walking the dom each time. + * @param {Element} el The DOM element whose ancestry should be stored. + * @constructor + * @private + */ +goog.cssom.iframe.style.NodeAncestry_ = function(el) { + var node = el; + var nodeUid = goog.getUid(node); + + // Return an existing object from the cache if one exits for this node. + var ancestry = goog.cssom.iframe.style.NodeAncestry_.instances_[nodeUid]; + if (ancestry) { + return ancestry; + } + + var nodes = []; + do { + var nodeInfo = { + id: node.id, + nodeName: node.nodeName + }; + nodeInfo.uid = goog.getUid(nodeInfo); + var className = node.className; + var classNamesLookup = {}; + if (className) { + var classNames = goog.dom.classlist.get(goog.asserts.assertElement(node)); + for (var i = 0; i < classNames.length; i++) { + classNamesLookup[classNames[i]] = 1; + } + } + nodeInfo.classNames = classNamesLookup; + nodes.unshift(nodeInfo); + } while (node = node.parentNode); + + /** + * Array of nodes in order of hierarchy from the top of the document + * to the node passed to the constructor + * @type {Array<Node>} + */ + this.nodes = nodes; + + this.uid = goog.getUid(this); + goog.cssom.iframe.style.NodeAncestry_.instances_[nodeUid] = this; +}; + + +/** + * Object for caching existing NodeAncestry instances. + * @private + */ +goog.cssom.iframe.style.NodeAncestry_.instances_ = {}; + + +/** + * Throw away all cached dom information. Call this if you've modified + * the structure or class/id attributes of your document and you want + * to recalculate the currently applied CSS rules. + */ +goog.cssom.iframe.style.resetDomCache = function() { + goog.cssom.iframe.style.NodeAncestry_.instances_ = {}; +}; + + +/** + * Inspects a document and returns all active rule sets + * @param {Document} doc The document from which to read CSS rules. + * @return {!Array<goog.cssom.iframe.style.CssRuleSet_>} An array of CssRuleSet + * objects representing all the active rule sets in the document. + * @private + */ +goog.cssom.iframe.style.getRuleSetsFromDocument_ = function(doc) { + var ruleSets = []; + var styleSheets = goog.cssom.getAllCssStyleSheets(doc.styleSheets); + for (var i = 0, styleSheet; styleSheet = styleSheets[i]; i++) { + var domRuleSets = goog.cssom.getCssRulesFromStyleSheet(styleSheet); + if (domRuleSets && domRuleSets.length) { + for (var j = 0, n = domRuleSets.length; j < n; j++) { + var ruleSet = new goog.cssom.iframe.style.CssRuleSet_(); + if (ruleSet.initializeFromCssRule(domRuleSets[j])) { + ruleSets.push(ruleSet); + } + } + } + } + return ruleSets; +}; + + +/** + * Static object to cache rulesets read from documents. Inspecting all + * active css rules is an expensive operation, so its best to only do + * it once and then cache the results. + * @type {Object} + * @private + */ +goog.cssom.iframe.style.ruleSetCache_ = {}; + + +/** + * Cache of ruleset objects keyed by document unique ID. + * @type {Object} + * @private + */ +goog.cssom.iframe.style.ruleSetCache_.ruleSetCache_ = {}; + + +/** + * Loads ruleset definitions from a document. If the cache already + * has rulesets for this document the cached version will be replaced. + * @param {Document} doc The document from which to load rulesets. + */ +goog.cssom.iframe.style.ruleSetCache_.loadRuleSetsForDocument = function(doc) { + var docUid = goog.getUid(doc); + goog.cssom.iframe.style.ruleSetCache_.ruleSetCache_[docUid] = + goog.cssom.iframe.style.getRuleSetsFromDocument_(doc); +}; + + +/** + * Retrieves the array of css rulesets for this document. A cached + * version will be used when possible. + * @param {Document} doc The document for which to get rulesets. + * @return {!Array<goog.cssom.iframe.style.CssRuleSet_>} An array of CssRuleSet + * objects representing the css rule sets in the supplied document. + */ +goog.cssom.iframe.style.ruleSetCache_.getRuleSetsForDocument = function(doc) { + var docUid = goog.getUid(doc); + var cache = goog.cssom.iframe.style.ruleSetCache_.ruleSetCache_; + if (!cache[docUid]) { + goog.cssom.iframe.style.ruleSetCache_.loadRuleSetsForDocument(doc); + } + // Build a cloned copy of rulesets array, so if object in the returned array + // get modified future calls will still return the original unmodified + // versions. + var ruleSets = cache[docUid]; + var ruleSetsCopy = []; + for (var i = 0; i < ruleSets.length; i++) { + ruleSetsCopy.push(ruleSets[i].clone()); + } + return ruleSetsCopy; +}; + + +/** + * Array of CSS properties that are inherited by child nodes, according to + * the CSS 2.1 spec. Properties that may be set to relative values, such + * as font-size, and line-height, are omitted. + * @type {Array<string>} + * @private + */ +goog.cssom.iframe.style.inheritedProperties_ = [ + 'color', + 'visibility', + 'quotes', + 'list-style-type', + 'list-style-image', + 'list-style-position', + 'list-style', + 'page-break-inside', + 'orphans', + 'widows', + 'font-family', + 'font-style', + 'font-variant', + 'font-weight', + 'text-indent', + 'text-align', + 'text-transform', + 'white-space', + 'caption-side', + 'border-collapse', + 'border-spacing', + 'empty-cells', + 'cursor' +]; + + +/** + * Array of CSS 2.1 properties that directly effect text nodes. + * @type {Array<string>} + * @private + */ +goog.cssom.iframe.style.textProperties_ = [ + 'font-family', + 'font-size', + 'font-weight', + 'font-variant', + 'font-style', + 'color', + 'text-align', + 'text-decoration', + 'text-indent', + 'text-transform', + 'letter-spacing', + 'white-space', + 'word-spacing' +]; + + +/** + * Reads the current css rules from element's document, and returns them + * rewriting selectors so that any rules that formerly applied to element will + * be applied to doc.body. This makes it possible to replace a block in a page + * with an iframe and preserve the css styling of the contents. + * + * @param {Element} element The element for which context should be calculated. + * @param {boolean=} opt_forceRuleSetCacheUpdate Flag to force the internal + * cache of rulesets to refresh itself before we read the same. + * @param {boolean=} opt_copyBackgroundContext Flag indicating that if the + * {@code element} has a transparent background, background rules + * from the nearest ancestor element(s) that have background-color + * and/or background-image set should be copied. + * @return {string} String containing all CSS rules present in the original + * document, with modified selectors. + * @see goog.cssom.iframe.style.getBackgroundContext. + */ +goog.cssom.iframe.style.getElementContext = function( + element, + opt_forceRuleSetCacheUpdate, + opt_copyBackgroundContext) { + var sourceDocument = element.ownerDocument; + if (opt_forceRuleSetCacheUpdate) { + goog.cssom.iframe.style.ruleSetCache_.loadRuleSetsForDocument( + sourceDocument); + } + var ruleSets = goog.cssom.iframe.style.ruleSetCache_. + getRuleSetsForDocument(sourceDocument); + + var elementAncestry = new goog.cssom.iframe.style.NodeAncestry_(element); + var bodySelectorPart = new goog.cssom.iframe.style.CssSelectorPart_('body'); + + for (var i = 0; i < ruleSets.length; i++) { + var ruleSet = ruleSets[i]; + var selectors = ruleSet.selectors; + // Cache selectors.length since we may be adding rules in the loop. + var ruleCount = selectors.length; + for (var j = 0; j < ruleCount; j++) { + var selector = selectors[j]; + // Test whether all or part of this selector would match + // this element or one of its ancestors + var match = selector.matchElementAncestry(elementAncestry); + if (match) { + var ruleIndex = match.selectorPartIndex; + var selectorParts = selector.parts; + var lastSelectorPartIndex = selectorParts.length - 1; + var selectorCopy; + if (match.elementIndex == elementAncestry.nodes.length - 1 || + ruleIndex < lastSelectorPartIndex) { + // Either the first part(s) of the selector matched this element, + // or the first part(s) of the selector matched a parent element + // and there are more parts of the selector that could target + // children of this element. + // So we inject a new selector, replacing the part that matched this + // element with 'body' so it will continue to match. + var selectorPartsCopy = selectorParts.concat(); + selectorPartsCopy.splice(0, + ruleIndex + 1, + bodySelectorPart); + selectorCopy = new goog.cssom.iframe.style.CssSelector_(); + selectorCopy.parts = selectorPartsCopy; + selectors.push(selectorCopy); + } else if (ruleIndex > 0 && ruleIndex == lastSelectorPartIndex) { + // The rule didn't match this element, but the entire rule did + // match an ancestor element. In this case we want to copy + // just the last part of the rule, to give it a chance to be applied + // to additional matching elements inside this element. + // Example DOM structure: body > div.funky > ul > li#editme + // Example CSS selector: .funky ul + // New CSS selector: body ul + selectorCopy = new goog.cssom.iframe.style.CssSelector_(); + selectorCopy.parts = [ + bodySelectorPart, + selectorParts[lastSelectorPartIndex] + ]; + selectors.push(selectorCopy); + } + } + } + } + + // Insert a new ruleset, setting the current inheritable styles of this + // element as the defaults for everything under in the frame. + var defaultPropertiesRuleSet = new goog.cssom.iframe.style.CssRuleSet_(); + var computedStyle = goog.cssom.iframe.style.getComputedStyleObject_(element); + + // Copy inheritable styles so they are applied to everything under HTML. + var htmlSelector = new goog.cssom.iframe.style.CssSelector_(); + htmlSelector.parts = [new goog.cssom.iframe.style.CssSelectorPart_('html')]; + defaultPropertiesRuleSet.selectors = [htmlSelector]; + var defaultProperties = {}; + for (var i = 0, prop; + prop = goog.cssom.iframe.style.inheritedProperties_[i]; + i++) { + defaultProperties[prop] = computedStyle[goog.string.toCamelCase(prop)]; + } + defaultPropertiesRuleSet.setDeclarationTextFromObject(defaultProperties); + ruleSets.push(defaultPropertiesRuleSet); + + var bodyRuleSet = new goog.cssom.iframe.style.CssRuleSet_(); + var bodySelector = new goog.cssom.iframe.style.CssSelector_(); + bodySelector.parts = [new goog.cssom.iframe.style.CssSelectorPart_('body')]; + // Core set of sane property values for BODY, to prevent copied + // styles from completely breaking the display. + var bodyProperties = { + position: 'relative', + top: '0', + left: '0', + right: 'auto', // Override any existing right value so 'left' works. + display: 'block', + visibility: 'visible' + }; + // Text formatting property values, to keep text nodes directly under BODY + // looking right. + for (i = 0; prop = goog.cssom.iframe.style.textProperties_[i]; i++) { + bodyProperties[prop] = computedStyle[goog.string.toCamelCase(prop)]; + } + if (opt_copyBackgroundContext && + goog.cssom.iframe.style.isTransparentValue_( + computedStyle['backgroundColor'])) { + // opt_useAncestorBackgroundRules means that, if the original element + // has a transparent backgorund, background properties rules should be + // added to explicitly make the body have the same background appearance + // as in the original element, even if its positioned somewhere else + // in the DOM. + var bgProperties = + goog.cssom.iframe.style.getBackgroundContext(element); + bodyProperties['background-color'] = bgProperties['backgroundColor']; + var elementBgImage = computedStyle['backgroundImage']; + if (!elementBgImage || elementBgImage == 'none') { + bodyProperties['background-image'] = bgProperties['backgroundImage']; + bodyProperties['background-repeat'] = bgProperties['backgroundRepeat']; + bodyProperties['background-position'] = + bgProperties['backgroundPosition']; + } + } + + bodyRuleSet.setDeclarationTextFromObject(bodyProperties, true); + bodyRuleSet.selectors = [bodySelector]; + ruleSets.push(bodyRuleSet); + + // Write outputTextParts to doc. + var ruleSetStrings = []; + ruleCount = ruleSets.length; + for (i = 0; i < ruleCount; i++) { + ruleSets[i].writeToArray(ruleSetStrings); + } + return ruleSetStrings.join(''); +}; + + +/** + * Tests whether a value is equivalent to 'transparent'. + * @param {string} colorValue The value to test. + * @return {boolean} Whether the value is transparent. + * @private + */ +goog.cssom.iframe.style.isTransparentValue_ = function(colorValue) { + return colorValue == 'transparent' || colorValue == 'rgba(0, 0, 0, 0)'; +}; + + +/** + * Returns an object containing the set of computedStyle/currentStyle + * values for the given element. Note that this should be used with + * caution as it ignores the fact that currentStyle and computedStyle + * are not the same for certain properties. + * + * @param {Element} element The element whose computed style to return. + * @return {Object} Object containing style properties and values. + * @private + */ +goog.cssom.iframe.style.getComputedStyleObject_ = function(element) { + // Return an object containing the element's computedStyle/currentStyle. + // The resulting object can be re-used to read multiple properties, which + // is faster than calling goog.style.getComputedStyle every time. + return element.currentStyle || + goog.dom.getOwnerDocument(element).defaultView.getComputedStyle( + element, '') || {}; +}; + + +/** + * RegExp that splits a value like "10px" or "-1em" into parts. + * @private + * @type {RegExp} + */ +goog.cssom.iframe.style.valueWithUnitsRegEx_ = /^(-?)([0-9]+)([a-z]*|%)/; + + +/** + * Given an object containing a set of styles, returns a two-element array + * containing the values of background-position-x and background-position-y. + * @param {Object} styleObject Object from which to read style properties. + * @return {Array<string>} The background-position values in the order [x, y]. + * @private + */ +goog.cssom.iframe.style.getBackgroundXYValues_ = function(styleObject) { + // Gecko only has backgroundPosition, containing both values. + // IE has only backgroundPositionX/backgroundPositionY. + // WebKit has both. + if (styleObject['backgroundPositionY']) { + return [styleObject['backgroundPositionX'], + styleObject['backgroundPositionY']]; + } else { + return (styleObject['backgroundPosition'] || '0 0').split(' '); + } +}; + + +/** + * Generates a set of CSS properties that can be used to make another + * element's background look like the background of a given element. + * This is useful when you want to copy the CSS context of an element, + * but the element's background is transparent. In the original context + * you would see the ancestor's backround color/image showing through, + * but in the new context there might be a something different underneath. + * Note that this assumes the element you're copying context from has a + * fairly standard positioning/layout - it assumes that when the element + * has a transparent background what you're going to see through it is its + * ancestors. + * @param {Element} element The element from which to copy background styles. + * @return {!Object} Object containing background* properties. + */ +goog.cssom.iframe.style.getBackgroundContext = function(element) { + var propertyValues = { + 'backgroundImage': 'none' + }; + var ancestor = element; + var currentIframeWindow; + // Walk up the DOM tree to find the ancestor nodes whose backgrounds + // may be visible underneath this element. Background-image and + // background-color don't have to come from the same node, but as soon + // an element with background-color is found there's no need to continue + // because backgrounds farther up the chain won't be visible. + // (This implementation is not sophisticated enough to handle opacity, + // or multple layered partially-transparent background images.) + while ((ancestor = ancestor.parentNode) && + ancestor.nodeType == goog.dom.NodeType.ELEMENT) { + var computedStyle = goog.cssom.iframe.style.getComputedStyleObject_( + /** @type {!Element} */ (ancestor)); + // Copy background color if a non-transparent value is found. + var backgroundColorValue = computedStyle['backgroundColor']; + if (!goog.cssom.iframe.style.isTransparentValue_(backgroundColorValue)) { + propertyValues['backgroundColor'] = backgroundColorValue; + } + // If a background image value is found, copy background-image, + // background-repeat, and background-position. + if (computedStyle['backgroundImage'] && + computedStyle['backgroundImage'] != 'none') { + propertyValues['backgroundImage'] = computedStyle['backgroundImage']; + propertyValues['backgroundRepeat'] = computedStyle['backgroundRepeat']; + // Calculate the offset between the original element and the element + // providing the background image, so the background position can be + // adjusted. + var relativePosition; + if (currentIframeWindow) { + relativePosition = goog.style.getFramedPageOffset( + element, currentIframeWindow); + var frameElement = currentIframeWindow.frameElement; + var iframeRelativePosition = goog.style.getRelativePosition( + /** @type {!Element} */ (frameElement), + /** @type {!Element} */ (ancestor)); + var iframeBorders = goog.style.getBorderBox(frameElement); + relativePosition.x += iframeRelativePosition.x + iframeBorders.left; + relativePosition.y += iframeRelativePosition.y + iframeBorders.top; + } else { + relativePosition = goog.style.getRelativePosition( + element, /** @type {Element} */ (ancestor)); + } + var backgroundXYValues = goog.cssom.iframe.style.getBackgroundXYValues_( + computedStyle); + // Parse background-repeat-* values in the form "10px", and adjust them. + for (var i = 0; i < 2; i++) { + var positionValue = backgroundXYValues[i]; + var coordinate = i == 0 ? 'X' : 'Y'; + var positionProperty = 'backgroundPosition' + coordinate; + // relative position to its ancestor. + var positionValueParts = + goog.cssom.iframe.style.valueWithUnitsRegEx_.exec(positionValue); + if (positionValueParts) { + var value = parseInt( + positionValueParts[1] + positionValueParts[2], 10); + var units = positionValueParts[3]; + // This only attempts to handle pixel values for now (plus + // '0anything', which is equivalent to 0px). + // TODO(user) Convert non-pixel values to pixels when possible. + if (value == 0 || units == 'px') { + value -= (coordinate == 'X' ? + relativePosition.x : relativePosition.y); + } + positionValue = value + units; + } + propertyValues[positionProperty] = positionValue; + } + propertyValues['backgroundPosition'] = + propertyValues['backgroundPositionX'] + ' ' + + propertyValues['backgroundPositionY']; + } + if (propertyValues['backgroundColor']) { + break; + } + if (ancestor.tagName == goog.dom.TagName.HTML) { + try { + currentIframeWindow = goog.dom.getWindow( + /** @type {Document} */ (ancestor.parentNode)); + // This could theoretically throw a security exception if the parent + // iframe is in a different domain. + ancestor = currentIframeWindow.frameElement; + if (!ancestor) { + // Loop has reached the top level window. + break; + } + } catch (e) { + // We don't have permission to go up to the parent window, stop here. + break; + } + } + } + return propertyValues; +};
http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/cssom/iframe/style_test_import.css ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/cssom/iframe/style_test_import.css b/externs/GCL/externs/goog/cssom/iframe/style_test_import.css new file mode 100644 index 0000000..d2c603f --- /dev/null +++ b/externs/GCL/externs/goog/cssom/iframe/style_test_import.css @@ -0,0 +1,10 @@ +/* + * Copyright 2010 The Closure Library Authors. All Rights Reserved. + * + * Use of this source code is governed by the Apache License, Version 2.0. + * See the COPYING file for details. + */ + +div div strong { + font-style: italic; +} http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/datasource/datamanager.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/datasource/datamanager.js b/externs/GCL/externs/goog/datasource/datamanager.js new file mode 100644 index 0000000..5eccd20 --- /dev/null +++ b/externs/GCL/externs/goog/datasource/datamanager.js @@ -0,0 +1,561 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview + * Central class for registering and accessing data sources + * Also handles processing of data events. + * + * There is a shared global instance that most client code should access via + * goog.ds.DataManager.getInstance(). However you can also create your own + * DataManager using new + * + * Implements DataNode to provide the top element in a data registry + * Prepends '$' to top level data names in path to denote they are root object + * + */ +goog.provide('goog.ds.DataManager'); + +goog.require('goog.ds.BasicNodeList'); +goog.require('goog.ds.DataNode'); +goog.require('goog.ds.Expr'); +goog.require('goog.object'); +goog.require('goog.string'); +goog.require('goog.structs'); +goog.require('goog.structs.Map'); + + + +/** + * Create a DataManger + * @extends {goog.ds.DataNode} + * @constructor + * @final + */ +goog.ds.DataManager = function() { + this.dataSources_ = new goog.ds.BasicNodeList(); + this.autoloads_ = new goog.structs.Map(); + this.listenerMap_ = {}; + this.listenersByFunction_ = {}; + this.aliases_ = {}; + this.eventCount_ = 0; + this.indexedListenersByFunction_ = {}; +}; + + +/** + * Global instance + * @private + */ +goog.ds.DataManager.instance_ = null; +goog.inherits(goog.ds.DataManager, goog.ds.DataNode); + + +/** + * Get the global instance + * @return {!goog.ds.DataManager} The data manager singleton. + */ +goog.ds.DataManager.getInstance = function() { + if (!goog.ds.DataManager.instance_) { + goog.ds.DataManager.instance_ = new goog.ds.DataManager(); + } + return goog.ds.DataManager.instance_; +}; + + +/** + * Clears the global instance (for unit tests to reset state). + */ +goog.ds.DataManager.clearInstance = function() { + goog.ds.DataManager.instance_ = null; +}; + + +/** + * Add a data source + * @param {goog.ds.DataNode} ds The data source. + * @param {boolean=} opt_autoload Whether to automatically load the data, + * defaults to false. + * @param {string=} opt_name Optional name, can also get name + * from the datasource. + */ +goog.ds.DataManager.prototype.addDataSource = function(ds, opt_autoload, + opt_name) { + var autoload = !!opt_autoload; + var name = opt_name || ds.getDataName(); + if (!goog.string.startsWith(name, '$')) { + name = '$' + name; + } + ds.setDataName(name); + this.dataSources_.add(ds); + this.autoloads_.set(name, autoload); +}; + + +/** + * Create an alias for a data path, very similar to assigning a variable. + * For example, you can set $CurrentContact -> $Request/Contacts[5], and all + * references to $CurrentContact will be procesed on $Request/Contacts[5]. + * + * Aliases will hide datasources of the same name. + * + * @param {string} name Alias name, must be a top level path ($Foo). + * @param {string} dataPath Data path being aliased. + */ +goog.ds.DataManager.prototype.aliasDataSource = function(name, dataPath) { + if (!this.aliasListener_) { + this.aliasListener_ = goog.bind(this.listenForAlias_, this); + } + if (this.aliases_[name]) { + var oldPath = this.aliases_[name].getSource(); + this.removeListeners(this.aliasListener_, oldPath + '/...', name); + } + this.aliases_[name] = goog.ds.Expr.create(dataPath); + this.addListener(this.aliasListener_, dataPath + '/...', name); + this.fireDataChange(name); +}; + + +/** + * Listener function for matches of paths that have been aliased. + * Fires a data change on the alias as well. + * + * @param {string} dataPath Path of data event fired. + * @param {string} name Name of the alias. + * @private + */ +goog.ds.DataManager.prototype.listenForAlias_ = function(dataPath, name) { + var aliasedExpr = this.aliases_[name]; + + if (aliasedExpr) { + // If it's a subpath, appends the subpath to the alias name + // otherwise just fires on the top level alias + var aliasedPath = aliasedExpr.getSource(); + if (dataPath.indexOf(aliasedPath) == 0) { + this.fireDataChange(name + dataPath.substring(aliasedPath.length)); + } else { + this.fireDataChange(name); + } + } +}; + + +/** + * Gets a named child node of the current node. + * + * @param {string} name The node name. + * @return {goog.ds.DataNode} The child node, + * or null if no node of this name exists. + */ +goog.ds.DataManager.prototype.getDataSource = function(name) { + if (this.aliases_[name]) { + return this.aliases_[name].getNode(); + } else { + return this.dataSources_.get(name); + } +}; + + +/** + * Get the value of the node + * @return {!Object} The value of the node. + * @override + */ +goog.ds.DataManager.prototype.get = function() { + return this.dataSources_; +}; + + +/** @override */ +goog.ds.DataManager.prototype.set = function(value) { + throw Error('Can\'t set on DataManager'); +}; + + +/** @override */ +goog.ds.DataManager.prototype.getChildNodes = function(opt_selector) { + if (opt_selector) { + return new goog.ds.BasicNodeList( + [this.getChildNode(/** @type {string} */(opt_selector))]); + } else { + return this.dataSources_; + } +}; + + +/** + * Gets a named child node of the current node + * @param {string} name The node name. + * @return {goog.ds.DataNode} The child node, + * or null if no node of this name exists. + * @override + */ +goog.ds.DataManager.prototype.getChildNode = function(name) { + return this.getDataSource(name); +}; + + +/** @override */ +goog.ds.DataManager.prototype.getChildNodeValue = function(name) { + var ds = this.getDataSource(name); + return ds ? ds.get() : null; +}; + + +/** + * Get the name of the node relative to the parent node + * @return {string} The name of the node. + * @override + */ +goog.ds.DataManager.prototype.getDataName = function() { + return ''; +}; + + +/** + * Gets the a qualified data path to this node + * @return {string} The data path. + * @override + */ +goog.ds.DataManager.prototype.getDataPath = function() { + return ''; +}; + + +/** + * Load or reload the backing data for this node + * only loads datasources flagged with autoload + * @override + */ +goog.ds.DataManager.prototype.load = function() { + var len = this.dataSources_.getCount(); + for (var i = 0; i < len; i++) { + var ds = this.dataSources_.getByIndex(i); + var autoload = this.autoloads_.get(ds.getDataName()); + if (autoload) { + ds.load(); + } + } +}; + + +/** + * Gets the state of the backing data for this node + * @return {goog.ds.LoadState} The state. + * @override + */ +goog.ds.DataManager.prototype.getLoadState = goog.abstractMethod; + + +/** + * Whether the value of this node is a homogeneous list of data + * @return {boolean} True if a list. + * @override + */ +goog.ds.DataManager.prototype.isList = function() { + return false; +}; + + +/** + * Get the total count of events fired (mostly for debugging) + * @return {number} Count of events. + */ +goog.ds.DataManager.prototype.getEventCount = function() { + return this.eventCount_; +}; + + +/** + * Adds a listener + * Listeners should fire when any data with path that has dataPath as substring + * is changed. + * TODO(user) Look into better listener handling + * + * @param {Function} fn Callback function, signature function(dataPath, id). + * @param {string} dataPath Fully qualified data path. + * @param {string=} opt_id A value passed back to the listener when the dataPath + * is matched. + */ +goog.ds.DataManager.prototype.addListener = function(fn, dataPath, opt_id) { + // maxAncestor sets how distant an ancestor you can be of the fired event + // and still fire (you always fire if you are a descendant). + // 0 means you don't fire if you are an ancestor + // 1 means you only fire if you are parent + // 1000 means you will fire if you are ancestor (effectively infinite) + var maxAncestors = 0; + if (goog.string.endsWith(dataPath, '/...')) { + maxAncestors = 1000; + dataPath = dataPath.substring(0, dataPath.length - 4); + } else if (goog.string.endsWith(dataPath, '/*')) { + maxAncestors = 1; + dataPath = dataPath.substring(0, dataPath.length - 2); + } + + opt_id = opt_id || ''; + var key = dataPath + ':' + opt_id + ':' + goog.getUid(fn); + var listener = {dataPath: dataPath, id: opt_id, fn: fn}; + var expr = goog.ds.Expr.create(dataPath); + + var fnUid = goog.getUid(fn); + if (!this.listenersByFunction_[fnUid]) { + this.listenersByFunction_[fnUid] = {}; + } + this.listenersByFunction_[fnUid][key] = {listener: listener, items: []}; + + while (expr) { + var listenerSpec = {listener: listener, maxAncestors: maxAncestors}; + var matchingListeners = this.listenerMap_[expr.getSource()]; + if (matchingListeners == null) { + matchingListeners = {}; + this.listenerMap_[expr.getSource()] = matchingListeners; + } + matchingListeners[key] = listenerSpec; + maxAncestors = 0; + expr = expr.getParent(); + this.listenersByFunction_[fnUid][key].items.push( + {key: key, obj: matchingListeners}); + } +}; + + +/** + * Adds an indexed listener. + * + * Indexed listeners allow for '*' in data paths. If a * exists, will match + * all values and return the matched values in an array to the callback. + * + * Currently uses a promiscuous match algorithm: Matches everything before the + * first '*', and then does a regex match for all of the returned events. + * Although this isn't optimized, it is still an improvement as you can collapse + * 100's of listeners into a single regex match + * + * @param {Function} fn Callback function, signature (dataPath, id, indexes). + * @param {string} dataPath Fully qualified data path. + * @param {string=} opt_id A value passed back to the listener when the dataPath + * is matched. + */ +goog.ds.DataManager.prototype.addIndexedListener = function(fn, dataPath, + opt_id) { + var firstStarPos = dataPath.indexOf('*'); + // Just need a regular listener + if (firstStarPos == -1) { + this.addListener(fn, dataPath, opt_id); + return; + } + + var listenPath = dataPath.substring(0, firstStarPos) + '...'; + + // Create regex that matches * to any non '\' character + var ext = '$'; + if (goog.string.endsWith(dataPath, '/...')) { + dataPath = dataPath.substring(0, dataPath.length - 4); + ext = ''; + } + var regExpPath = goog.string.regExpEscape(dataPath); + var matchRegExp = regExpPath.replace(/\\\*/g, '([^\\\/]+)') + ext; + + // Matcher function applies the regex and calls back the original function + // if the regex matches, passing in an array of the matched values + var matchRegExpRe = new RegExp(matchRegExp); + var matcher = function(path, id) { + var match = matchRegExpRe.exec(path); + if (match) { + match.shift(); + fn(path, opt_id, match); + } + }; + this.addListener(matcher, listenPath, opt_id); + + // Add the indexed listener to the map so that we can remove it later. + var fnUid = goog.getUid(fn); + if (!this.indexedListenersByFunction_[fnUid]) { + this.indexedListenersByFunction_[fnUid] = {}; + } + var key = dataPath + ':' + opt_id; + this.indexedListenersByFunction_[fnUid][key] = { + listener: {dataPath: listenPath, fn: matcher, id: opt_id} + }; +}; + + +/** + * Removes indexed listeners with a given callback function, and optional + * matching datapath and matching id. + * + * @param {Function} fn Callback function, signature function(dataPath, id). + * @param {string=} opt_dataPath Fully qualified data path. + * @param {string=} opt_id A value passed back to the listener when the dataPath + * is matched. + */ +goog.ds.DataManager.prototype.removeIndexedListeners = function( + fn, opt_dataPath, opt_id) { + this.removeListenersByFunction_( + this.indexedListenersByFunction_, true, fn, opt_dataPath, opt_id); +}; + + +/** + * Removes listeners with a given callback function, and optional + * matching dataPath and matching id + * + * @param {Function} fn Callback function, signature function(dataPath, id). + * @param {string=} opt_dataPath Fully qualified data path. + * @param {string=} opt_id A value passed back to the listener when the dataPath + * is matched. + */ +goog.ds.DataManager.prototype.removeListeners = function(fn, opt_dataPath, + opt_id) { + + // Normalize data path root + if (opt_dataPath && goog.string.endsWith(opt_dataPath, '/...')) { + opt_dataPath = opt_dataPath.substring(0, opt_dataPath.length - 4); + } else if (opt_dataPath && goog.string.endsWith(opt_dataPath, '/*')) { + opt_dataPath = opt_dataPath.substring(0, opt_dataPath.length - 2); + } + + this.removeListenersByFunction_( + this.listenersByFunction_, false, fn, opt_dataPath, opt_id); +}; + + +/** + * Removes listeners with a given callback function, and optional + * matching dataPath and matching id from the given listenersByFunction + * data structure. + * + * @param {Object} listenersByFunction The listeners by function. + * @param {boolean} indexed Indicates whether the listenersByFunction are + * indexed or not. + * @param {Function} fn Callback function, signature function(dataPath, id). + * @param {string=} opt_dataPath Fully qualified data path. + * @param {string=} opt_id A value passed back to the listener when the dataPath + * is matched. + * @private + */ +goog.ds.DataManager.prototype.removeListenersByFunction_ = function( + listenersByFunction, indexed, fn, opt_dataPath, opt_id) { + var fnUid = goog.getUid(fn); + var functionMatches = listenersByFunction[fnUid]; + if (functionMatches != null) { + for (var key in functionMatches) { + var functionMatch = functionMatches[key]; + var listener = functionMatch.listener; + if ((!opt_dataPath || opt_dataPath == listener.dataPath) && + (!opt_id || opt_id == listener.id)) { + if (indexed) { + this.removeListeners( + listener.fn, listener.dataPath, listener.id); + } + if (functionMatch.items) { + for (var i = 0; i < functionMatch.items.length; i++) { + var item = functionMatch.items[i]; + delete item.obj[item.key]; + } + } + delete functionMatches[key]; + } + } + } +}; + + +/** + * Get the total number of listeners (per expression listened to, so may be + * more than number of times addListener() has been called + * @return {number} Number of listeners. + */ +goog.ds.DataManager.prototype.getListenerCount = function() { + var count = 0; + goog.object.forEach(this.listenerMap_, function(matchingListeners) { + count += goog.structs.getCount(matchingListeners); + }); + return count; +}; + + +/** + * Disables the sending of all data events during the execution of the given + * callback. This provides a way to avoid useless notifications of small changes + * when you will eventually send a data event manually that encompasses them + * all. + * + * Note that this function can not be called reentrantly. + * + * @param {Function} callback Zero-arg function to execute. + */ +goog.ds.DataManager.prototype.runWithoutFiringDataChanges = function(callback) { + if (this.disableFiring_) { + throw Error('Can not nest calls to runWithoutFiringDataChanges'); + } + + this.disableFiring_ = true; + try { + callback(); + } finally { + this.disableFiring_ = false; + } +}; + + +/** + * Fire a data change event to all listeners + * + * If the path matches the path of a listener, the listener will fire + * + * If your path is the parent of a listener, the listener will fire. I.e. + * if $Contacts/[email protected] changes, then we will fire listener for + * $Contacts/[email protected]/Name as well, as the assumption is that when + * a parent changes, all children are invalidated. + * + * If your path is the child of a listener, the listener may fire, depending + * on the ancestor depth. + * + * A listener for $Contacts might only be interested if the contact name changes + * (i.e. $Contacts doesn't fire on $Contacts/[email protected]/Name), + * while a listener for a specific contact might + * (i.e. $Contacts/[email protected] would fire on $Contacts/[email protected]/Name). + * Adding "/..." to a lisetener path listens to all children, and adding "/*" to + * a listener path listens only to direct children + * + * @param {string} dataPath Fully qualified data path. + */ +goog.ds.DataManager.prototype.fireDataChange = function(dataPath) { + if (this.disableFiring_) { + return; + } + + var expr = goog.ds.Expr.create(dataPath); + var ancestorDepth = 0; + + // Look for listeners for expression and all its parents. + // Parents of listener expressions are all added to the listenerMap as well, + // so this will evaluate inner loop every time the dataPath is a child or + // an ancestor of the original listener path + while (expr) { + var matchingListeners = this.listenerMap_[expr.getSource()]; + if (matchingListeners) { + for (var id in matchingListeners) { + var match = matchingListeners[id]; + var listener = match.listener; + if (ancestorDepth <= match.maxAncestors) { + listener.fn(dataPath, listener.id); + } + } + } + ancestorDepth++; + expr = expr.getParent(); + } + this.eventCount_++; +}; http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/datasource/datasource.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/datasource/datasource.js b/externs/GCL/externs/goog/datasource/datasource.js new file mode 100644 index 0000000..4cb0db6 --- /dev/null +++ b/externs/GCL/externs/goog/datasource/datasource.js @@ -0,0 +1,658 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview Generic rich data access API. + * + * Abstraction for data sources that allows listening for changes at different + * levels of the data tree and updating the data via XHR requests + * + */ + + +goog.provide('goog.ds.BaseDataNode'); +goog.provide('goog.ds.BasicNodeList'); +goog.provide('goog.ds.DataNode'); +goog.provide('goog.ds.DataNodeList'); +goog.provide('goog.ds.EmptyNodeList'); +goog.provide('goog.ds.LoadState'); +goog.provide('goog.ds.SortedNodeList'); +goog.provide('goog.ds.Util'); +goog.provide('goog.ds.logger'); + +goog.require('goog.array'); +goog.require('goog.log'); + + + +/** + * Interface for node in rich data tree. + * + * Names that are reserved for system use and shouldn't be used for data node + * names: eval, toSource, toString, unwatch, valueOf, watch. Behavior is + * undefined if these names are used. + * + * @constructor + */ +goog.ds.DataNode = function() {}; + + +/** + * Get the value of the node + * @param {...?} var_args Do not check arity of arguments, because + * some subclasses require args. + * @return {*} The value of the node, or null if no value. + */ +goog.ds.DataNode.prototype.get = goog.abstractMethod; + + +/** + * Set the value of the node + * @param {*} value The new value of the node. + */ +goog.ds.DataNode.prototype.set = goog.abstractMethod; + + +/** + * Gets all of the child nodes of the current node. + * Should return an empty DataNode list if no child nodes. + * @param {string=} opt_selector String selector to choose child nodes. + * @return {!goog.ds.DataNodeList} The child nodes. + */ +goog.ds.DataNode.prototype.getChildNodes = goog.abstractMethod; + + +/** + * Gets a named child node of the current node + * @param {string} name The node name. + * @param {boolean=} opt_canCreate Whether to create a child node if it does not + * exist. + * @return {goog.ds.DataNode} The child node, or null + * if no node of this name exists. + */ +goog.ds.DataNode.prototype.getChildNode = goog.abstractMethod; + + +/** + * Gets the value of a child node + * @param {string} name The node name. + * @return {*} The value of the node, or null if no value or the child node + * doesn't exist. + */ +goog.ds.DataNode.prototype.getChildNodeValue = goog.abstractMethod; + + +/** + * Sets a named child node of the current node. + * + * @param {string} name The node name. + * @param {Object} value The value to set, can be DataNode, object, property, + * or null. If value is null, removes the child node. + * @return {Object} The child node, if the node was set. + */ +goog.ds.DataNode.prototype.setChildNode = goog.abstractMethod; + + +/** + * Get the name of the node relative to the parent node + * @return {string} The name of the node. + */ +goog.ds.DataNode.prototype.getDataName = goog.abstractMethod; + + +/** + * Set the name of the node relative to the parent node + * @param {string} name The name of the node. + */ +goog.ds.DataNode.prototype.setDataName = goog.abstractMethod; + + +/** + * Gets the a qualified data path to this node + * @return {string} The data path. + */ +goog.ds.DataNode.prototype.getDataPath = goog.abstractMethod; + + +/** + * Load or reload the backing data for this node + */ +goog.ds.DataNode.prototype.load = goog.abstractMethod; + + +/** + * Gets the state of the backing data for this node + * @return {goog.ds.LoadState} The state. + */ +goog.ds.DataNode.prototype.getLoadState = goog.abstractMethod; + + +/** + * Whether the value of this node is a homogeneous list of data + * @return {boolean} True if a list. + */ +goog.ds.DataNode.prototype.isList = goog.abstractMethod; + + +/** + * Enum for load state of a DataNode. + * @enum {string} + */ +goog.ds.LoadState = { + LOADED: 'LOADED', + LOADING: 'LOADING', + FAILED: 'FAILED', + NOT_LOADED: 'NOT_LOADED' +}; + + + +/** + * Base class for data node functionality, has default implementations for + * many of the functions. + * + * implements {goog.ds.DataNode} + * @constructor + */ +goog.ds.BaseDataNode = function() {}; + + +/** + * Set the value of the node + * @param {Object} value The new value of the node. + */ +goog.ds.BaseDataNode.prototype.set = goog.abstractMethod; + + +/** + * Gets all of the child nodes of the current node. + * Should return an empty DataNode list if no child nodes. + * @param {string=} opt_selector String selector to choose child nodes. + * @return {!goog.ds.DataNodeList} The child nodes. + */ +goog.ds.BaseDataNode.prototype.getChildNodes = function(opt_selector) { + return new goog.ds.EmptyNodeList(); +}; + + +/** + * Gets a named child node of the current node + * @param {string} name The node name. + * @param {boolean=} opt_canCreate Whether you can create the child node if + * it doesn't exist already. + * @return {goog.ds.DataNode} The child node, or null if no node of + * this name exists and opt_create is false. + */ +goog.ds.BaseDataNode.prototype.getChildNode = function(name, opt_canCreate) { + return null; +}; + + +/** + * Gets the value of a child node + * @param {string} name The node name. + * @return {Object} The value of the node, or null if no value or the + * child node doesn't exist. + */ +goog.ds.BaseDataNode.prototype.getChildNodeValue = function(name) { + return null; +}; + + +/** + * Get the name of the node relative to the parent node + * @return {string} The name of the node. + */ +goog.ds.BaseDataNode.prototype.getDataName = goog.abstractMethod; + + +/** + * Gets the a qualified data path to this node + * @return {string} The data path. + */ +goog.ds.BaseDataNode.prototype.getDataPath = function() { + var parentPath = ''; + var myName = this.getDataName(); + if (this.getParent && this.getParent()) { + parentPath = this.getParent().getDataPath() + + (myName.indexOf(goog.ds.STR_ARRAY_START) != -1 ? '' : + goog.ds.STR_PATH_SEPARATOR); + } + + return parentPath + myName; +}; + + +/** + * Load or reload the backing data for this node + */ +goog.ds.BaseDataNode.prototype.load = goog.nullFunction; + + +/** + * Gets the state of the backing data for this node + * @return {goog.ds.LoadState} The state. + */ +goog.ds.BaseDataNode.prototype.getLoadState = function() { + return goog.ds.LoadState.LOADED; +}; + + +/** + * Gets the parent node. Subclasses implement this function + * @type {Function} + * @protected + */ +goog.ds.BaseDataNode.prototype.getParent = null; + + +/** + * Interface for node list in rich data tree. + * + * Has both map and list-style accessors + * + * @constructor + * @extends {goog.ds.DataNode} + */ +// TODO(arv): Use interfaces when available. +goog.ds.DataNodeList = function() {}; + + +/** + * Add a node to the node list. + * If the node has a dataName, uses this for the key in the map. + * + * @param {goog.ds.DataNode} node The node to add. + */ +goog.ds.DataNodeList.prototype.add = goog.abstractMethod; + + +/** + * Get a node by string key. + * Returns null if node doesn't exist. + * + * @param {string} key String lookup key. + * @return {*} The node, or null if doesn't exist. + * @override + */ +goog.ds.DataNodeList.prototype.get = goog.abstractMethod; + + +/** + * Get a node by index + * Returns null if the index is out of range + * + * @param {number} index The index of the node. + * @return {goog.ds.DataNode} The node, or null if doesn't exist. + */ +goog.ds.DataNodeList.prototype.getByIndex = goog.abstractMethod; + + +/** + * Gets the size of the node list + * + * @return {number} The size of the list. + */ +goog.ds.DataNodeList.prototype.getCount = goog.abstractMethod; + + +/** + * Sets a node in the list of a given name + * @param {string} name Name of the node. + * @param {goog.ds.DataNode} node The node. + */ +goog.ds.DataNodeList.prototype.setNode = goog.abstractMethod; + + +/** + * Removes a node in the list of a given name + * @param {string} name Name of the node. + * @return {boolean} True if node existed and was deleted. + */ +goog.ds.DataNodeList.prototype.removeNode = goog.abstractMethod; + + +/** + * Simple node list implementation with underlying array and map + * implements goog.ds.DataNodeList. + * + * Names that are reserved for system use and shouldn't be used for data node + * names: eval, toSource, toString, unwatch, valueOf, watch. Behavior is + * undefined if these names are used. + * + * @param {Array<goog.ds.DataNode>=} opt_nodes optional nodes to add to list. + * @constructor + * @extends {goog.ds.DataNodeList} + */ +// TODO(arv): Use interfaces when available. +goog.ds.BasicNodeList = function(opt_nodes) { + this.map_ = {}; + this.list_ = []; + this.indexMap_ = {}; + if (opt_nodes) { + for (var i = 0, node; node = opt_nodes[i]; i++) { + this.add(node); + } + } +}; + + +/** + * Add a node to the node list. + * If the node has a dataName, uses this for the key in the map. + * TODO(user) Remove function as well + * + * @param {goog.ds.DataNode} node The node to add. + * @override + */ +goog.ds.BasicNodeList.prototype.add = function(node) { + this.list_.push(node); + var dataName = node.getDataName(); + if (dataName) { + this.map_[dataName] = node; + this.indexMap_[dataName] = this.list_.length - 1; + } +}; + + +/** + * Get a node by string key. + * Returns null if node doesn't exist. + * + * @param {string} key String lookup key. + * @return {goog.ds.DataNode} The node, or null if doesn't exist. + * @override + */ +goog.ds.BasicNodeList.prototype.get = function(key) { + return this.map_[key] || null; +}; + + +/** + * Get a node by index + * Returns null if the index is out of range + * + * @param {number} index The index of the node. + * @return {goog.ds.DataNode} The node, or null if doesn't exist. + * @override + */ +goog.ds.BasicNodeList.prototype.getByIndex = function(index) { + return this.list_[index] || null; +}; + + +/** + * Gets the size of the node list + * + * @return {number} The size of the list. + * @override + */ +goog.ds.BasicNodeList.prototype.getCount = function() { + return this.list_.length; +}; + + +/** + * Sets a node in the list of a given name + * @param {string} name Name of the node. + * @param {goog.ds.DataNode} node The node. + * @override + */ +goog.ds.BasicNodeList.prototype.setNode = function(name, node) { + if (node == null) { + this.removeNode(name); + } else { + var existingNode = this.indexMap_[name]; + if (existingNode != null) { + this.map_[name] = node; + this.list_[existingNode] = node; + } else { + this.add(node); + } + } +}; + + +/** + * Removes a node in the list of a given name + * @param {string} name Name of the node. + * @return {boolean} True if node existed and was deleted. + * @override + */ +goog.ds.BasicNodeList.prototype.removeNode = function(name) { + var existingNode = this.indexMap_[name]; + if (existingNode != null) { + this.list_.splice(existingNode, 1); + delete this.map_[name]; + delete this.indexMap_[name]; + for (var index in this.indexMap_) { + if (this.indexMap_[index] > existingNode) { + this.indexMap_[index]--; + } + } + } + return existingNode != null; +}; + + +/** + * Get the index of a named node + * @param {string} name The name of the node to get the index of. + * @return {number|undefined} The index. + */ +goog.ds.BasicNodeList.prototype.indexOf = function(name) { + return this.indexMap_[name]; +}; + + +/** + * Immulatable empty node list + * @extends {goog.ds.BasicNodeList} + * @constructor + * @final + */ + +goog.ds.EmptyNodeList = function() { + goog.ds.BasicNodeList.call(this); +}; +goog.inherits(goog.ds.EmptyNodeList, goog.ds.BasicNodeList); + + +/** + * Add a node to the node list. + * If the node has a dataName, uses this for the key in the map. + * + * @param {goog.ds.DataNode} node The node to add. + * @override + */ +goog.ds.EmptyNodeList.prototype.add = function(node) { + throw Error('Can\'t add to EmptyNodeList'); +}; + + + +/** + * Node list implementation which maintains sort order during insertion and + * modification operations based on a comparison function. + * + * The SortedNodeList does not guarantee sort order will be maintained if + * the underlying data nodes are modified externally. + * + * Names that are reserved for system use and shouldn't be used for data node + * names: eval, toSource, toString, unwatch, valueOf, watch. Behavior is + * undefined if these names are used. + * + * @param {Function} compareFn Comparison function by which the + * node list is sorted. Should take 2 arguments to compare, and return a + * negative integer, zero, or a positive integer depending on whether the + * first argument is less than, equal to, or greater than the second. + * @param {Array<goog.ds.DataNode>=} opt_nodes optional nodes to add to list; + * these are assumed to be in sorted order. + * @extends {goog.ds.BasicNodeList} + * @constructor + */ +goog.ds.SortedNodeList = function(compareFn, opt_nodes) { + this.compareFn_ = compareFn; + goog.ds.BasicNodeList.call(this, opt_nodes); +}; +goog.inherits(goog.ds.SortedNodeList, goog.ds.BasicNodeList); + + +/** + * Add a node to the node list, maintaining sort order. + * If the node has a dataName, uses this for the key in the map. + * + * @param {goog.ds.DataNode} node The node to add. + * @override + */ +goog.ds.SortedNodeList.prototype.add = function(node) { + if (!this.compareFn_) { + this.append(node); + return; + } + + var searchLoc = goog.array.binarySearch(this.list_, node, this.compareFn_); + + // if there is another node that is "equal" according to the comparison + // function, insert before that one; otherwise insert at the location + // goog.array.binarySearch indicated + if (searchLoc < 0) { + searchLoc = -(searchLoc + 1); + } + + // update any indexes that are after the insertion point + for (var index in this.indexMap_) { + if (this.indexMap_[index] >= searchLoc) { + this.indexMap_[index]++; + } + } + + goog.array.insertAt(this.list_, node, searchLoc); + var dataName = node.getDataName(); + if (dataName) { + this.map_[dataName] = node; + this.indexMap_[dataName] = searchLoc; + } +}; + + +/** + * Adds the given node to the end of the SortedNodeList. This should + * only be used when the caller can guarantee that the sort order will + * be maintained according to this SortedNodeList's compareFn (e.g. + * when initializing a new SortedNodeList from a list of nodes that has + * already been sorted). + * @param {goog.ds.DataNode} node The node to append. + */ +goog.ds.SortedNodeList.prototype.append = function(node) { + goog.ds.SortedNodeList.superClass_.add.call(this, node); +}; + + +/** + * Sets a node in the list of a given name, maintaining sort order. + * @param {string} name Name of the node. + * @param {goog.ds.DataNode} node The node. + * @override + */ +goog.ds.SortedNodeList.prototype.setNode = function(name, node) { + if (node == null) { + this.removeNode(name); + } else { + var existingNode = this.indexMap_[name]; + if (existingNode != null) { + if (this.compareFn_) { + var compareResult = this.compareFn_(this.list_[existingNode], node); + if (compareResult == 0) { + // the new node can just replace the old one + this.map_[name] = node; + this.list_[existingNode] = node; + } else { + // remove the old node, then add the new one + this.removeNode(name); + this.add(node); + } + } + } else { + this.add(node); + } + } +}; + + +/** + * The character denoting an attribute. + * @type {string} + */ +goog.ds.STR_ATTRIBUTE_START = '@'; + + +/** + * The character denoting all children. + * @type {string} + */ +goog.ds.STR_ALL_CHILDREN_SELECTOR = '*'; + + +/** + * The wildcard character. + * @type {string} + */ +goog.ds.STR_WILDCARD = '*'; + + +/** + * The character denoting path separation. + * @type {string} + */ +goog.ds.STR_PATH_SEPARATOR = '/'; + + +/** + * The character denoting the start of an array. + * @type {string} + */ +goog.ds.STR_ARRAY_START = '['; + + +/** + * Shared logger instance for data package + * @type {goog.log.Logger} + */ +goog.ds.logger = goog.log.getLogger('goog.ds'); + + +/** + * Create a data node that references another data node, + * useful for pointer-like functionality. + * All functions will return same values as the original node except for + * getDataName() + * @param {!goog.ds.DataNode} node The original node. + * @param {string} name The new name. + * @return {!goog.ds.DataNode} The new data node. + */ +goog.ds.Util.makeReferenceNode = function(node, name) { + /** + * @constructor + * @extends {goog.ds.DataNode} + * @final + */ + var nodeCreator = function() {}; + nodeCreator.prototype = node; + var newNode = new nodeCreator(); + newNode.getDataName = function() { + return name; + }; + return newNode; +}; http://git-wip-us.apache.org/repos/asf/flex-falcon/blob/e2cad6e6/externs/GCL/externs/goog/datasource/expr.js ---------------------------------------------------------------------- diff --git a/externs/GCL/externs/goog/datasource/expr.js b/externs/GCL/externs/goog/datasource/expr.js new file mode 100644 index 0000000..c384fd1 --- /dev/null +++ b/externs/GCL/externs/goog/datasource/expr.js @@ -0,0 +1,545 @@ +// Copyright 2006 The Closure Library Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS-IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @fileoverview + * Expression evaluation utilities. Expression format is very similar to XPath. + * + * Expression details: + * - Of format A/B/C, which will evaluate getChildNode('A').getChildNode('B'). + * getChildNodes('C')|getChildNodeValue('C')|getChildNode('C') depending on + * call + * - If expression ends with '/name()', will get the name() of the node + * referenced by the preceding path. + * - If expression ends with '/count()', will get the count() of the nodes that + * match the expression referenced by the preceding path. + * - If expression ends with '?', the value is OK to evaluate to null. This is + * not enforced by the expression evaluation functions, instead it is + * provided as a flag for client code which may ignore depending on usage + * - If expression has [INDEX], will use getChildNodes().getByIndex(INDEX) + * + */ + + +goog.provide('goog.ds.Expr'); + +goog.require('goog.ds.BasicNodeList'); +goog.require('goog.ds.EmptyNodeList'); +goog.require('goog.string'); + + + +/** + * Create a new expression. An expression uses a string expression language, and + * from this string and a passed in DataNode can evaluate to a value, DataNode, + * or a DataNodeList. + * + * @param {string=} opt_expr The string expression. + * @constructor + * @final + */ +goog.ds.Expr = function(opt_expr) { + if (opt_expr) { + this.setSource_(opt_expr); + } +}; + + +/** + * Set the source expression text & parse + * + * @param {string} expr The string expression source. + * @param {Array=} opt_parts Array of the parts of an expression. + * @param {goog.ds.Expr=} opt_childExpr Optional child of this expression, + * passed in as a hint for processing. + * @param {goog.ds.Expr=} opt_prevExpr Optional preceding expression + * (i.e. $A/B/C is previous expression to B/C) passed in as a hint for + * processing. + * @private + */ +goog.ds.Expr.prototype.setSource_ = function(expr, opt_parts, + opt_childExpr, opt_prevExpr) { + this.src_ = expr; + + if (!opt_childExpr && !opt_prevExpr) { + // Check whether it can be empty + if (goog.string.endsWith(expr, goog.ds.Expr.String_.CAN_BE_EMPTY)) { + this.canBeEmpty_ = true; + expr = expr.substring(0, expr.length - 1); + } + + // Check whether this is an node function + if (goog.string.endsWith(expr, '()')) { + if (goog.string.endsWith(expr, goog.ds.Expr.String_.NAME_EXPR) || + goog.string.endsWith(expr, goog.ds.Expr.String_.COUNT_EXPR) || + goog.string.endsWith(expr, goog.ds.Expr.String_.POSITION_EXPR)) { + var lastPos = expr.lastIndexOf(goog.ds.Expr.String_.SEPARATOR); + if (lastPos != -1) { + this.exprFn_ = expr.substring(lastPos + 1); + expr = expr.substring(0, lastPos); + } else { + this.exprFn_ = expr; + expr = goog.ds.Expr.String_.CURRENT_NODE_EXPR; + } + if (this.exprFn_ == goog.ds.Expr.String_.COUNT_EXPR) { + this.isCount_ = true; + } + } + } + } + + // Split into component parts + this.parts_ = opt_parts || expr.split('/'); + this.size_ = this.parts_.length; + this.last_ = this.parts_[this.size_ - 1]; + this.root_ = this.parts_[0]; + + if (this.size_ == 1) { + this.rootExpr_ = this; + this.isAbsolute_ = goog.string.startsWith(expr, '$'); + } else { + this.rootExpr_ = goog.ds.Expr.createInternal_(this.root_, null, + this, null); + this.isAbsolute_ = this.rootExpr_.isAbsolute_; + this.root_ = this.rootExpr_.root_; + } + + if (this.size_ == 1 && !this.isAbsolute_) { + // Check whether expression maps to current node, for convenience + this.isCurrent_ = (expr == goog.ds.Expr.String_.CURRENT_NODE_EXPR || + expr == goog.ds.Expr.String_.EMPTY_EXPR); + + // Whether this expression is just an attribute (i.e. '@foo') + this.isJustAttribute_ = + goog.string.startsWith(expr, goog.ds.Expr.String_.ATTRIBUTE_START); + + // Check whether this is a common node expression + this.isAllChildNodes_ = expr == goog.ds.Expr.String_.ALL_CHILD_NODES_EXPR; + this.isAllAttributes_ = expr == goog.ds.Expr.String_.ALL_ATTRIBUTES_EXPR; + this.isAllElements_ = expr == goog.ds.Expr.String_.ALL_ELEMENTS_EXPR; + } +}; + + +/** + * Get the source data path for the expression + * @return {string} The path. + */ +goog.ds.Expr.prototype.getSource = function() { + return this.src_; +}; + + +/** + * Gets the last part of the expression. + * @return {?string} Last part of the expression. + */ +goog.ds.Expr.prototype.getLast = function() { + return this.last_; +}; + + +/** + * Gets the parent expression of this expression, or null if this is top level + * @return {goog.ds.Expr} The parent. + */ +goog.ds.Expr.prototype.getParent = function() { + if (!this.parentExprSet_) { + if (this.size_ > 1) { + this.parentExpr_ = goog.ds.Expr.createInternal_(null, + this.parts_.slice(0, this.parts_.length - 1), this, null); + } + this.parentExprSet_ = true; + } + return this.parentExpr_; +}; + + +/** + * Gets the parent expression of this expression, or null if this is top level + * @return {goog.ds.Expr} The parent. + */ +goog.ds.Expr.prototype.getNext = function() { + if (!this.nextExprSet_) { + if (this.size_ > 1) { + this.nextExpr_ = goog.ds.Expr.createInternal_(null, this.parts_.slice(1), + null, this); + } + this.nextExprSet_ = true; + } + return this.nextExpr_; +}; + + +/** + * Evaluate an expression on a data node, and return a value + * Recursively walks through child nodes to evaluate + * TODO(user) Support other expression functions + * + * @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against. + * If not provided, evaluates against DataManager global root. + * @return {*} Value of the node, or null if doesn't exist. + */ +goog.ds.Expr.prototype.getValue = function(opt_ds) { + if (opt_ds == null) { + opt_ds = goog.ds.DataManager.getInstance(); + } else if (this.isAbsolute_) { + opt_ds = opt_ds.getDataRoot ? opt_ds.getDataRoot() : + goog.ds.DataManager.getInstance(); + } + + if (this.isCount_) { + var nodes = this.getNodes(opt_ds); + return nodes.getCount(); + } + + if (this.size_ == 1) { + return opt_ds.getChildNodeValue(this.root_); + } else if (this.size_ == 0) { + return opt_ds.get(); + } + + var nextDs = opt_ds.getChildNode(this.root_); + + if (nextDs == null) { + return null; + } else { + return this.getNext().getValue(nextDs); + } +}; + + +/** + * Evaluate an expression on a data node, and return matching nodes + * Recursively walks through child nodes to evaluate + * + * @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against. + * If not provided, evaluates against data root. + * @param {boolean=} opt_canCreate If true, will try to create new nodes. + * @return {goog.ds.DataNodeList} Matching nodes. + */ +goog.ds.Expr.prototype.getNodes = function(opt_ds, opt_canCreate) { + return /** @type {goog.ds.DataNodeList} */(this.getNodes_(opt_ds, + false, opt_canCreate)); +}; + + +/** + * Evaluate an expression on a data node, and return the first matching node + * Recursively walks through child nodes to evaluate + * + * @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against. + * If not provided, evaluates against DataManager global root. + * @param {boolean=} opt_canCreate If true, will try to create new nodes. + * @return {goog.ds.DataNode} Matching nodes, or null if doesn't exist. + */ +goog.ds.Expr.prototype.getNode = function(opt_ds, opt_canCreate) { + return /** @type {goog.ds.DataNode} */(this.getNodes_(opt_ds, + true, opt_canCreate)); +}; + + +/** + * Evaluate an expression on a data node, and return the first matching node + * Recursively walks through child nodes to evaluate + * + * @param {goog.ds.DataNode=} opt_ds Optional datasource to evaluate against. + * If not provided, evaluates against DataManager global root. + * @param {boolean=} opt_selectOne Whether to return single matching DataNode + * or matching nodes in DataNodeList. + * @param {boolean=} opt_canCreate If true, will try to create new nodes. + * @return {goog.ds.DataNode|goog.ds.DataNodeList} Matching node or nodes, + * depending on value of opt_selectOne. + * @private + */ +goog.ds.Expr.prototype.getNodes_ = function(opt_ds, opt_selectOne, + opt_canCreate) { + if (opt_ds == null) { + opt_ds = goog.ds.DataManager.getInstance(); + } else if (this.isAbsolute_) { + opt_ds = opt_ds.getDataRoot ? opt_ds.getDataRoot() : + goog.ds.DataManager.getInstance(); + } + + if (this.size_ == 0 && opt_selectOne) { + return opt_ds; + } else if (this.size_ == 0 && !opt_selectOne) { + return new goog.ds.BasicNodeList([opt_ds]); + } else if (this.size_ == 1) { + if (opt_selectOne) { + return opt_ds.getChildNode(this.root_, opt_canCreate); + } + else { + var possibleListChild = opt_ds.getChildNode(this.root_); + if (possibleListChild && possibleListChild.isList()) { + return possibleListChild.getChildNodes(); + } else { + return opt_ds.getChildNodes(this.root_); + } + } + } else { + var nextDs = opt_ds.getChildNode(this.root_, opt_canCreate); + if (nextDs == null && opt_selectOne) { + return null; + } else if (nextDs == null && !opt_selectOne) { + return new goog.ds.EmptyNodeList(); + } + return this.getNext().getNodes_(nextDs, opt_selectOne, opt_canCreate); + } +}; + + +/** + * Whether the expression can be null. + * + * @type {boolean} + * @private + */ +goog.ds.Expr.prototype.canBeEmpty_ = false; + + +/** + * The parsed paths in the expression + * + * @type {Array<string>} + * @private + */ +goog.ds.Expr.prototype.parts_ = []; + + +/** + * Number of paths in the expression + * + * @type {?number} + * @private + */ +goog.ds.Expr.prototype.size_ = null; + + +/** + * The root node path in the expression + * + * @type {string} + * @private + */ +goog.ds.Expr.prototype.root_; + + +/** + * The last path in the expression + * + * @type {?string} + * @private + */ +goog.ds.Expr.prototype.last_ = null; + + +/** + * Whether the expression evaluates to current node + * + * @type {boolean} + * @private + */ +goog.ds.Expr.prototype.isCurrent_ = false; + + +/** + * Whether the expression is just an attribute + * + * @type {boolean} + * @private + */ +goog.ds.Expr.prototype.isJustAttribute_ = false; + + +/** + * Does this expression select all DOM-style child nodes (element and text) + * + * @type {boolean} + * @private + */ +goog.ds.Expr.prototype.isAllChildNodes_ = false; + + +/** + * Does this expression select all DOM-style attribute nodes (starts with '@') + * + * @type {boolean} + * @private + */ +goog.ds.Expr.prototype.isAllAttributes_ = false; + + +/** + * Does this expression select all DOM-style element child nodes + * + * @type {boolean} + * @private + */ +goog.ds.Expr.prototype.isAllElements_ = false; + + +/** + * The function used by this expression + * + * @type {?string} + * @private + */ +goog.ds.Expr.prototype.exprFn_ = null; + + +/** + * Cached value for the parent expression. + * @type {goog.ds.Expr?} + * @private + */ +goog.ds.Expr.prototype.parentExpr_ = null; + + +/** + * Cached value for the next expression. + * @type {goog.ds.Expr?} + * @private + */ +goog.ds.Expr.prototype.nextExpr_ = null; + + +/** + * Create an expression from a string, can use cached values + * + * @param {string} expr The expression string. + * @return {goog.ds.Expr} The expression object. + */ +goog.ds.Expr.create = function(expr) { + var result = goog.ds.Expr.cache_[expr]; + + if (result == null) { + result = new goog.ds.Expr(expr); + goog.ds.Expr.cache_[expr] = result; + } + return result; +}; + + +/** + * Create an expression from a string, can use cached values + * Uses hints from related expressions to help in creation + * + * @param {?string=} opt_expr The string expression source. + * @param {Array=} opt_parts Array of the parts of an expression. + * @param {goog.ds.Expr=} opt_childExpr Optional child of this expression, + * passed in as a hint for processing. + * @param {goog.ds.Expr=} opt_prevExpr Optional preceding expression + * (i.e. $A/B/C is previous expression to B/C) passed in as a hint for + * processing. + * @return {goog.ds.Expr} The expression object. + * @private + */ +goog.ds.Expr.createInternal_ = function(opt_expr, opt_parts, opt_childExpr, + opt_prevExpr) { + var expr = opt_expr || opt_parts.join('/'); + var result = goog.ds.Expr.cache_[expr]; + + if (result == null) { + result = new goog.ds.Expr(); + result.setSource_(expr, opt_parts, opt_childExpr, opt_prevExpr); + goog.ds.Expr.cache_[expr] = result; + } + return result; +}; + + +/** + * Cache of pre-parsed expressions + * @private + */ +goog.ds.Expr.cache_ = {}; + + +/** + * Commonly used strings in expressions. + * @enum {string} + * @private + */ +goog.ds.Expr.String_ = { + SEPARATOR: '/', + CURRENT_NODE_EXPR: '.', + EMPTY_EXPR: '', + ATTRIBUTE_START: '@', + ALL_CHILD_NODES_EXPR: '*|text()', + ALL_ATTRIBUTES_EXPR: '@*', + ALL_ELEMENTS_EXPR: '*', + NAME_EXPR: 'name()', + COUNT_EXPR: 'count()', + POSITION_EXPR: 'position()', + INDEX_START: '[', + INDEX_END: ']', + CAN_BE_EMPTY: '?' +}; + + +/** + * Standard expressions + */ + + +/** + * The current node + */ +goog.ds.Expr.CURRENT = goog.ds.Expr.create( + goog.ds.Expr.String_.CURRENT_NODE_EXPR); + + +/** + * For DOM interop - all DOM child nodes (text + element). + * Text nodes have dataName #text + */ +goog.ds.Expr.ALL_CHILD_NODES = + goog.ds.Expr.create(goog.ds.Expr.String_.ALL_CHILD_NODES_EXPR); + + +/** + * For DOM interop - all DOM element child nodes + */ +goog.ds.Expr.ALL_ELEMENTS = + goog.ds.Expr.create(goog.ds.Expr.String_.ALL_ELEMENTS_EXPR); + + +/** + * For DOM interop - all DOM attribute nodes + * Attribute nodes have dataName starting with "@" + */ +goog.ds.Expr.ALL_ATTRIBUTES = + goog.ds.Expr.create(goog.ds.Expr.String_.ALL_ATTRIBUTES_EXPR); + + +/** + * Get the dataName of a node + */ +goog.ds.Expr.NAME = goog.ds.Expr.create(goog.ds.Expr.String_.NAME_EXPR); + + +/** + * Get the count of nodes matching an expression + */ +goog.ds.Expr.COUNT = goog.ds.Expr.create(goog.ds.Expr.String_.COUNT_EXPR); + + +/** + * Get the position of the "current" node in the current node list + * This will only apply for datasources that support the concept of a current + * node (none exist yet). This is similar to XPath position() and concept of + * current node + */ +goog.ds.Expr.POSITION = goog.ds.Expr.create(goog.ds.Expr.String_.POSITION_EXPR);
