text: "use strict";
  
  // Copyright 2012 United States Government, as represented by the Secretary of Defense, Under
  // Secretary of Defense (Personnel & Readiness).
  // 
  // 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.
  
  /// XPath resolution functions.
  /// 
  /// @module vwf/utility/xpath
  
  define( [ "module" ], function( module ) {
  
      var exports = {
  
          // -- resolve --------------------------------------------------------------------------
  
          /// Resolve an XPath expression, using a callback function to interpret each step.
          /// 
          /// @param {String|String[]|Object[]} xpath
          /// @param {ID} rootID
          /// @param {ID|ID[]} contextIDs
          /// @param {Function} callback
          /// @param {Object} [thisArg]
          /// 
          /// @returns {ID[]|undefined}
  
          resolve: function( xpath, rootID, contextIDs, callback /* ( step, id, resolveAttributes ) */, thisArg ) {
  
              // Accept contextIDs as either a single id or an array of ids.
  
              if ( ! ( contextIDs instanceof Array ) ) {
                  contextIDs = [ contextIDs ];
              }
  
              // Parse the expression.
  
              var steps = this.parse( xpath );
  
              if ( steps ) {
  
                  // Reset the context if it's an absolute path.
  
                  if ( steps.absolute ) {
                      contextIDs = rootID ? [ rootID ] : [];
                  }
  
                  // Resolve each step.
  
                  steps.forEach( function( step ) {
  
                      contextIDs = Array.prototype.concat.apply( [], contextIDs.map( function( id ) {
  
                          var stepIDs = callback.call( thisArg, step, id );
  
                          step.predicates && step.predicates.forEach( function( predicate ) {
  
                              stepIDs = stepIDs.filter( function( stepID ) {
                                  return this.resolve( predicate, rootID, stepID, function( step, id ) {
                                      return callback.call( this, step, id, true );
                                  }, thisArg ).length;
                              }, this );
  
                          }, this  );
  
                          return stepIDs;
  
                      }, this ) );
  
                  }, this );
  
                  return contextIDs;
              }
  
          },
  
          // -- parse ----------------------------------------------------------------------------
  
          /// Parse an XPath expression into a series of steps.
          /// 
          /// @param {String|String[]|Object[]} xpath
          /// 
          /// @returns {Object[]|undefined}
  
          parse: function( xpath ) {
  
              var steps = [], step;
  
              if ( typeof xpath == "string" || xpath instanceof String ) {
  
                  if ( xpath[0] == "/" ) {
                      steps.absolute = true;
                      xpath = { string: xpath, index: 1 };
                  } else {
                      xpath = { string: xpath, index: 0 };
                  }
  
                  while ( xpath.index < xpath.string.length &&
                          ( step = /* assignment! */ this.parseStep( xpath ) ) ) {
                      steps.push( step );
                  }
  
                  if ( xpath.index < xpath.string.length ) {
                      return undefined;
                  }
  
              } else if ( typeof xpath[0] == "string" || xpath[0] instanceof String ) {
  
                  var valid = true;
  
                  steps = xpath.map( function( step ) {
  
                      step = this.parseStep( step );
                      valid = valid && step;
  
                      return step;
  
                  }, this );
  
                  if ( ! valid ) {
                      return undefined;
                  }
  
              } else {
  
                  steps = xpath;
              }
  
              return steps;
          },
  
          // -- parseStep ------------------------------------------------------------------------
  
          /// Parse an XPath step expression.
          /// 
          /// @param {String|Object} xpath
          /// 
          /// @returns {Object|undefined}
  
          parseStep: function( xpath ) {
  
              if ( typeof xpath == "string" || xpath instanceof String ) {
                  xpath = { string: xpath, index: 0 };
              }
  
              if ( xpath.index < xpath.string.length && ! this.regex.separator.test( xpath.string.slice( xpath.index ) ) ) {
                  var step_match = this.regex.step.exec( xpath.string.slice( xpath.index ) );
              } else {
                  var step_match = [].concat( "", new Array( 11 ), "" /* abbreviated_step */ ); // special case for "//"
              }
  
              if ( step_match ) {
  
                  xpath.index += step_match[0].length;
  
                  var axis_name = step_match[1],
                      abbreviated_axis_specifier = step_match[2],
                      node_kind = step_match[3],
                      node_name = step_match[4],
                      node_name_quoted = step_match[5],
                      node_name_wildcard = step_match[6],
                      type_name = step_match[7],
                      type_name_quoted = step_match[8],
                      name_test = step_match[9],
                      name_test_quoted = step_match[10],
                      name_test_wildcard = step_match[11],
                      abbreviated_step = step_match[12];
  
                  if ( name_test || name_test_quoted || name_test_wildcard ) {
                    node_name = name_test;
                    node_name_quoted = name_test_quoted;
                    node_name_wildcard = name_test_wildcard;
                  }
  
                  if ( node_name_quoted ) {
                    node_name = this.unquoteName( node_name_quoted );
                  }
  
                  if ( type_name_quoted ) {
                    type_name = this.unquoteName( type_name_quoted );
                  }
  
                  switch ( abbreviated_step ) {
  
                      case "": // "" == "descendant-or-self:node()"
                          axis_name = "descendant-or-self";
                          node_kind = "node";
                          break;
  
                      case ".": // "." == "self::node()"
                          axis_name = "self";
                          node_kind = "node";
                          break;
  
                      case "..": // ".." == "parent::node()"
                          axis_name = "parent";
                          node_kind = "node";
                          break;
  
                  }
  
                  switch ( abbreviated_axis_specifier ) {
  
                      case "": // "name" == "child::name"
                          axis_name = "child";
                          break;
  
                      case "@": // "@name" == "attribute::name"
                          axis_name = "attribute";
                          break;
  
                  }
  
                  // // * == element()
  
                  // "preceding::"
                  // "preceding-sibling::"
  
                  // "ancestor-or-self::"
                  // "ancestor::"
                  // "parent::"
                  // "self::"
                  // "child::"
                  // "descendant::"
                  // "descendant-or-self::"
  
                  // "following-sibling::"
                  // "following::"
  
                  // // * == attribute()
  
                  // "attribute::"
  
                  // // * == namespace()
  
                  // "namespace::"
  
                  if ( node_name_wildcard && ! node_kind ) {
  
                      switch ( axis_name ) {
  
                          default:
                              node_kind = "element";
                              break;
  
                          case "attribute":
                              node_kind = "attribute";
                              break;
  
                          case "namespace":
                              node_kind = "namespace";
                              break;
  
                      }
  
                  }
  
                  // Parse the predicates.
  
                  var predicates = [], predicate;
  
                  while ( predicate = /* assignment! */ this.parsePredicate( xpath ) ) {
                      predicates.push( predicate );
                  }
  
                  // Absorb the separator.
  
                  this.parseSeparator( xpath );
  
                  // Now have: axis_name and name_test | node_kind(node_name,type_name)
  
                  var step = {
                      axis: axis_name,
                      kind: node_kind,
                      name: node_name,
                      type: type_name,
                  };
  
                  if ( predicates.length ) {
                      step.predicates = predicates;
                  }
  
                  return step;
              }
  
          },
  
          // -- parsePredicate -------------------------------------------------------------------
  
          /// Parse an XPath step predicate.
          /// 
          /// @param {String|Object} xpath
          /// 
          /// @returns {Object[]|undefined}
  
          parsePredicate: function( xpath ) {
  
              if ( typeof xpath == "string" || xpath instanceof String ) {
                  xpath = { string: xpath, index: 0 };
              }
  
              var predicate_match = this.regex.predicate.exec( xpath.string.slice( xpath.index ) );
  
              if ( predicate_match ) {
                  xpath.index += predicate_match[0].length;
                  return this.parse( predicate_match[1] );
              }
  
          },
  
          // -- parseSeparator -------------------------------------------------------------------
  
          /// Parse an XPath step separator.
          /// 
          /// @param {String|Object} xpath
          /// 
          /// @returns {Boolean|undefined}
  
          parseSeparator: function( xpath ) {
  
              if ( typeof xpath == "string" || xpath instanceof String ) {
                  xpath = { string: xpath, index: 0 };
              }
  
              var separator_match = this.regex.separator.exec( xpath.string.slice( xpath.index ) );
  
              if ( separator_match ) {
                  xpath.index += separator_match[0].length;
                  return true;
              }
  
          },
  
          // -- quoteName --------------------------------------------------------------------------
  
          /// Apply quotation marks around a name and escape internal quotation marks and escape
          /// characters.
          /// 
          /// @param {String} unquoted_name
          /// 
          /// @returns {String}
  
          quoteName: function( unquoted_name ) {
              return '"' + unquoted_name.replace( /(["\\])/g, "\\$1" ) + '"';
          },
  
          // -- unquoteName ------------------------------------------------------------------------
  
          /// Remove the enclosing quotation marks and unescape internal quotation marks and escape
          /// characters of a quoted name.
          /// 
          /// @param {String} quoted_name
          /// 
          /// @returns {String}
  
          unquoteName: function( quoted_name ) {
              if ( quoted_name[0] == "'" ) {
                return quoted_name.slice( 1, -1 ).replace( /\\(['\\])/g, "$1" );
              } else if ( quoted_name[0] == '"' ) {
                return quoted_name.slice( 1, -1 ).replace( /\\(["\\])/g, "$1" );
              }
          },
  
          // -- regex ----------------------------------------------------------------------------
  
          /// Regexes to crack the XPath string.
  
          regex: ( function() {
  
              var name = "[A-Za-z_][A-Za-z_0-9.-]*",              // XPath QName: http://www.w3.org/TR/xpath20/#prod-xpath-QName
                  singleQuotedName = "'(?:[^'\\\\]|\\'|\\\\)+'",  // Single-quoted QName (VWF extension)
                  doubleQuotedName = '"(?:[^"\\\\]|\\"|\\\\)+"',  // Double-quoted QName (VWF extension)
                  wildcard = "\\*";                               // XPath Wildcard: http://www.w3.org/TR/xpath20/#prod-xpath-Wildcard
  
              /// @field
  
              var step =                                          // XPath StepExpr: http://www.w3.org/TR/xpath20/#prod-xpath-StepExpr
  
                  "(?:" +
  
                    "(?:" +
  
                      "(?:" +
                        "(?:(" + name + ")::)" +                  // "axis", as in "axis::"" (axis_name)
                      "|" +
                        "(@|)" +                                  // "@", "" (abbreviated_axis_specifier)
                      ")" +
  
                      "(?:" +
  
                        "(?:" +
                          "(" + name + ")" +                      // "kind" (node_kind)
                          "\\(" +                                 // "("
                            "(?:" +
                              "(?:" +
                                "(" + name + ")" +                // "node" (node_name)
                              "|" +
                                "(" +
                                  "(?:" + singleQuotedName + ")" + // "'node'" (node_name_quoted, quoted and with internal escapes)
                                "|" +
                                  "(?:" + doubleQuotedName + ")" + // "\"node\"" (node_name_quoted, quoted and with internal escapes)
                                ")" +
                              "|" +
                                "(" + wildcard + ")" +            // "*" (node_name_wildcard)
                              ")" +
                              "(?:" +
                                "," +                             // ","
                                "(?:" +
                                  "(" + name + ")" +              // "type" (type_name)
                                "|" +
                                  "(" +
                                    "(?:" + singleQuotedName + ")" + // "'type'" (type_name_quoted, quoted and with internal escapes)
                                  "|" +
                                    "(?:" + doubleQuotedName + ")" + // '"type"' (type_name_quoted, quoted and with internal escapes)
                                  ")" +
                                ")" +
                              ")?" +
                            ")?" +
                          "\\)" +                                 // ")"
                        ")" +
  
                      "|" +
  
                        "(?:" +
                          "(" + name + ")" +                      // "name" (name_test)
                        "|" +
                          "(" +
                            "(?:" + singleQuotedName + ")" +      // "'name'" (name_test_quoted, quoted and with internal escapes)
                          "|" +
                            "(?:" + doubleQuotedName + ")" +      // '"name"' (name_test_quoted, quoted and with internal escapes)
                          ")" +
                        "|" +
                          "(" + wildcard + ")" +                  // "*" (name_test_wildcard)
                        ")" +
  
                      ")" +
  
                    ")" +
  
                  "|" +
  
                    "(\\.\\.|\\.)" +                             // "..", "." (abbreviated_step)
  
                  ")";
  
              /// @field
  
              var predicate =                                     // XPath Predicate: http://www.w3.org/TR/xpath20/#prod-xpath-Predicate
  
                  "\\[" +
                      "(" +
                          step + // "[^\\]]*" +
                      ")" +
                  "\\]";
  
              /// @field
  
              var separator = "/";
  
              var regexes = {
  
                  /// @field
  
                  step: new RegExp( "^" + step ),
  
                  /// @field
  
                  predicate: new RegExp( "^" + predicate ),
  
                  /// @field
  
                  separator: new RegExp( "^" + separator ),
  
              };
  
              return regexes;
  
          } )(),
  
      };
  
      return exports;
  
  } );
  