/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (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.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Language Checker Service, XML.
 *
 * The Initial Developer of the Original Code is
 * Alexander J. Vincent <ajvincent@gmail.com>.
 * Portions created by the Initial Developer are Copyright (C) 2006
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

// Component metadata.  Please change all the values in this section.

var componentName = "Language Pack Service, XML";
var contractId    = "@verbosio.mozdev.org/language-pack-service;1?namespaceURI=http://www.mozilla.org/xbl";
var componentId   = Components.ID("{7f1b63dd-39f1-476b-9407-4879322f9594}");

// Utility function.
function ERROR_FAILURE() {
  throw Components.results.NS_ERROR_FAILURE;
}

// Utility function.
function NOT_IMPLEMENTED() {
  throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
}

var xmlLanguageChecker  = null;
var jsLanguageChecker   = null;
var nodePositionService = null;
var jsSourceMap         = null;

var C_i = Components.interfaces;

const xeIXMLLanguagePackService   = C_i.xeIXMLLanguagePackService;
const xeILanguagePackService      = C_i.xeILanguagePackService;
const nsITransactionListener      = C_i.nsITransactionListener;
const xeINodePositionService      = C_i.xeINodePositionService;
const xeISourceMapCreator         = C_i.xeISourceMapCreator;
const xeISourceMap                = C_i.xeISourceMap;
const nsIDOMNode                  = C_i.nsIDOMNode;
const nsIDOMNodeFilter            = C_i.nsIDOMNodeFilter;
const nsIDOMCharacterData         = C_i.nsIDOMCharacterData;
const nsIDOMParser                = C_i.nsIDOMParser;
const nsIScriptError              = C_i.nsIScriptError;
const nsIProgrammingLanguage      = C_i.nsIProgrammingLanguage;
const nsISecurityCheckedComponent = C_i.nsISecurityCheckedComponent;
const nsIClassInfo                = C_i.nsIClassInfo;
const nsIComponentRegistrar       = C_i.nsIComponentRegistrar;
const nsIFactory                  = C_i.nsIFactory;
const nsIModule                   = C_i.nsIModule;
const nsISupports                 = C_i.nsISupports;

C_i = null;

const parser     = Components.classes["@mozilla.org/xmlextras/domparser;1"]
                             .createInstance(nsIDOMParser);

const XBL_NS = "http://www.mozilla.org/xbl";

var ecmaDebuggingLoaded = false;
function loadDebugging() {
  if (!ecmaDebuggingLoaded) {
    var jsLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
                             .getService(Components.interfaces.mozIJSSubScriptLoader);
    jsLoader.loadSubScript("resource://app/res/ecma-debug.js", this);
    ecmaDebuggingLoaded = true;
  }
}

// Language pack service constructor.
function languagePack()
{
  loadDebugging();
  this.sandbox = null;
}
languagePack.prototype = {
  // xeIXMLLanguagePackService
  namespaceURI: XBL_NS,

  // xeIXMLLanguagePackService
  prefix: "xbl",

  // xeIXMLLanguagePackService
  getDTDUriArray: function getDTDUriArray(aCount) {
    NOT_IMPLEMENTED();
  },

  // xeIXMLLanguagePackService
  getXMLSchemaArray: function getXMLSchemaArray(aCount) {
    NOT_IMPLEMENTED();
  },

  // xeIXMLLanguagePackService
  get attributeWrapperName() {
    NOT_IMPLEMENTED();
  },

  // xeIXMLLanguagePackService
  handleWhitespace: function handleWhitespace(aLocalName) {
    return xeIXMLLanguagePackService.WHITESPACE_IGNORABLE;
  },

  uriNodeMap: {
    binding: [
      {
        namespaceURI: null,
        localName: "extends"
      }
    ],

    image: [
      {
        namespaceURI: null,
        localName: "src"
      }
    ],

    stylesheet: [
      {
        namespaceURI: null,
        localName: "src"
      }
    ]
  },

  // xeIXMLLanguagePackService
  isURINode: function isURINode(aNode) {
    loadDebugging();

    var ownerNode = null;
    if (aNode.nodeType == nsIDOMNode.ATTRIBUTE_NODE) {
      ownerNode = aNode.ownerElement;
    }
    if (aNode instanceof nsIDOMCharacterData) {
      ownerNode = aNode.parentNode;
    }

    assert(ownerNode, "No owner element for this node?");
    assert(ownerNode.nodeType == nsIDOMNode.ELEMENT_NODE, "No owner element for this node?");
    assert(ownerNode.namespaceURI == XBL_NS, "Called isURINode on wrong language pack.");

    if (ownerNode.localName in this.uriNodeMap) {
      var attrMap = this.uriNodeMap[ownerNode.localName];
      for (var i = 0; i < attrMap.length; i++) {
        var attrObj = attrMap[i];
        if ((attrObj.namespaceURI == aNode.namespaceURI) &&
            (attrObj.localName == aNode.localName)) {
          return true;
        }
      }
    }

    if (!xmlLanguageChecker) {
      const langPackPrefix = "@verbosio.mozdev.org/language-pack-service;1";
      const xmlContractId = langPackPrefix + "?contentType=application/xml";
      xmlLanguageChecker = Components.classes[xmlContractId]
                                     .getService(xeILanguagePackService);
      assert(xmlLanguageChecker, "No XML language pack service?");
    }

    return xmlLanguageChecker.isURINode(aNode);
  },

  /**
   * Get the first element child of a node.
   */
  getFirstElementChild: function getFirstElementChild(aNode) {
    const nsIDOMElement = Components.interfaces.nsIDOMElement;
    var iterator = aNode.firstChild;
    while (iterator) {
      if (iterator instanceof nsIDOMElement) {
        return iterator;
      }
      iterator = iterator.nextSibling;
    }
    return null;
  },

  /**
   * Get the following sibling which is an element.
   */
  getNextSiblingElement: function getNextSiblingElement(aElement) {
    const nsIDOMElement = Components.interfaces.nsIDOMElement;
    var iterator = aElement.nextSibling;
    while (iterator) {
      if (iterator instanceof nsIDOMElement) {
        return iterator;
      }
      iterator = iterator.nextSibling;
    }
    return null;
  },

  checkRequiredAttributes:
  function checkRequiredAttributes(aElement, aAttrArray, wrapper) {
    for (var i = 0; i < aAttrArray; i++) {
      attrCheck = aAttrArray[i];
      var childAlternate = null;
      var scriptNode = null;
      var err = null;
      childAlternate = (typeof attrCheck.childAlternateName == "string") ?
                        attrCheck.childAlternateName : null;

      if (childAlternate) {
        var iterator = this.getFirstElementChild(aElement);
        while (iterator) {
          if (iterator.namespaceURI != XBL_NS) {
            err = this.getScriptError({
              message: "Trying to validate an element not in the XBL namespace.",
              lineNumber: 0,
              columnNumber: 0
            }, wrapper);
            return err;
          }

          if (iterator.localName == childAlternate) {
            scriptNode = iterator;
            break;
          }
          iterator = this.getNextElementChild(iterator);
        }
      }

      if (aElement.hasAttributeNS(attrCheck.namespaceURI, attrCheck.localName)) {
        if (scriptNode) {
          err = this.getScriptError({
            message: "A " + aElement.localName  + " element has both a " + childAlternate
                   + " child element and a " + attrCheck.localName + " attribute.",
            lineNumber: 0,
            columnNumber: 0
          }, wrapper);
          return err;
        }
        scriptNode = aElement.getAttributeNodeNS(attrCheck.namespaceURI, attrCheck.localName);
      } // end aElement.hasAttributeNS(...)

      if (!scriptNode) {
        err = this.getScriptError({
          message: "Required node " + attrCheck.localName + " not found.",
          lineNumber: 0,
          columnNumber: 0
        }, wrapper);
        return err;
      }

      if ((typeof attrCheck.checkScript == "boolean") && attrCheck.checkScript) {
        err = this.checkNodeForScript(scriptNode, wrapper);
      }

      if (err) {
        return err;
      }
    } // end for (var i = 0; i < aAttrArray; i++)

    return null;
  },

  /**
   * Check a node for script-only content.
   */
  checkNodeForScript: function checkNodeForScript(aElement, wrapper) {
    var iterator = aElement.firstChild;
    while (iterator) {
      if (!(iterator instanceof Components.interfaces.nsIDOMCharacterData)) {
        err = this.getScriptError({
          message: "Node must have character data children.",
          lineNumber: 0,
          columnNumber: 0
        }, wrapper);
        return err;
      }

      iterator = iterator.nextSibling;

      // XXX ajvincent Script syntax checking?
    }

    return null;
  },

  // xeIXMLLanguagePackService
  checkValidity: function checkValidity(aElement, wrapper) {
    if (aElement.namespaceURI != XBL_NS) {
      err = this.getScriptError({
        message: "Trying to validate an element not in the XBL namespace.",
        lineNumber: 0,
        columnNumber: 0
      }, wrapper);
      return err;
    }

    var err;
    var iterator = this.getFirstChildElement(aElement);

    switch (aElement.localName) {
      case "bindings":
        while (iterator) {
          if (iterator.localName != "binding") {
            err = this.getScriptError({
              message: "XBL bindings elements must have only binding elements as children.",
              lineNumber: 0,
              columnNumber: 0
            }, wrapper);
            return err;
          }

          err = this.checkValidity(iterator, wrapper);
          if (err) {
            return err;
          }

          iterator = this.getNextSiblingElement(iterator);
        }
        return null; // end case "bindings"

      case "binding":
        err = this.checkRequiredAttributes(aElement,
                [{
                  namespaceURI: null,
                  localName: "id"
                 }],
                wrapper);
        if (err) {
          return err;
        }

        while (iterator) {
          var childCount = {
            resources: 0,
            content: 0,
            implementation: 0,
            handlers: 0
          }

          if (!(iterator.localName in childCount)) {
            err = this.getScriptError({
              message: "Unexpected element in XBL namespace:" + aElement.nodeName,
              lineNumber: 0,
              columnNumber: 0
            }, wrapper);
            return err;
          }

          childCount[iterator.localName]++;
          if (childCount[iterator.localName] > 1) {
            err = this.getScriptError({
              message: "Too many XBL elements of name " + aElement.localName + " in binding.",
              lineNumber: 0,
              columnNumber: 0
            }, wrapper);
            return err;
          }

          err = this.checkValidity(iterator, wrapper);
          if (err) {
            return err;
          }

          iterator = this.getNextSiblingElement(iterator);
        }
        return null; // end case "binding"

      case "resources":
        while (iterator) {
          if ((iterator.localName == "stylesheet") ||
              (iterator.localName == "image")) {
            err = this.checkValidity(iterator, wrapper);
          } else {
            err = this.getScriptError({
              message: "xbl:resources element can only have script and stylesheet children.",
              lineNumber: 0,
              columnNumber: 0
            }, wrapper);
          }
          if (err) {
            return err;
          }

          iterator = this.getNextSiblingElement(iterator);
        }
        return null; // end case "resources"

      case "stylesheet":
        if (aElement.firstChild) {
          err = this.getScriptError({
            message: "XBL stylesheet elements should not have children.",
            lineNumber: 0,
            columnNumber: 0
          }, wrapper);
          return err;
        }

        err = this.checkRequiredAttributes(aElement,
                [{
                  namespaceURI: null,
                  localName: "src"
                 }],
                wrapper);
        if (err) {
          return err;
        }

        return null; // end case "stylesheet"

      case "image":
        if (aElement.firstChild) {
          err = this.getScriptError({
            message: "XBL image elements should not have children.",
            lineNumber: 0,
            columnNumber: 0
          }, wrapper);
          return err;
        }
        err = this.checkRequiredAttributes(aElement,
                [{
                  namespaceURI: null,
                  localName: "src"
                 }],
                wrapper);
        if (err) {
          return err;
        }

        return null; // end case "image"

      case "content":
/* These elements can have any children, but for validation purposes we want to
   exclude <xbl:children/> elements.  So we do our iterating on a clone of the
   original element, and remove <xbl:children/>.
 */
        var elementCopy = aElement.cloneNode(true);
        var kids = elementCopy.getElementsByTagNameNS(XBL_NS, "children");
        for (var i = 0; i < kids.length; i++) {
          kids[i].parentNode.removeChild(kids[i]);
        }

        iterator = this.getFirstChildElement(elementCopy);

        const xeIXMLLanguagePack = Components.interfaces.xeIXMLLanguagePack;
        while (iterator) {
          if (aElement.namespaceURI == XBL_NS) {
            err = this.getScriptError({
              message: "xbl:content element shouldn't have XBL children, except for <xbl:children/>.",
              lineNumber: 0,
              columnNumber: 0
            }, wrapper);
            return err;
          }

          var prefix = "@verbosio.mozdev.org/language-pack-service;1?namespaceURI="
          var contract = prefix + iterator.namespaceURI;
          if (contract in Components.classes) {
            var langService = Components.classes[contract]
                                        .getService(xeIXMLLanguagePack);
            err = langService.checkValidity(iterator, wrapper);
            if (err) {
              return err;
            }
          }

          iterator = this.getNextSiblingElement(iterator);
        }
        return null; // end case "content"

      case "implementation":
        var constructorFound = false;
        var destructorFound = false;

        while (iterator) {
          if (iterator.localName == "constructor") {
            if (constructorFound) {
              err = this.getScriptError({
                message: "XBL constructor element already found for XBL implementation element.",
                lineNumber: 0,
                columnNumber: 0
              }, wrapper);
              return err;
            } else {
              constructorFound = true;
            }
          }

          if (iterator.localName == "destructor") {
            if (destructorFound) {
              err = this.getScriptError({
                message: "XBL destructor element already found for XBL implementation element.",
                lineNumber: 0,
                columnNumber: 0
              }, wrapper);
              return err;
            } else {
              destructorFound = true;
            }
          }

          if ((iterator.localName == "field") ||
              (iterator.localName == "property") ||
              (iterator.localName == "method") ||
              (iterator.localName == "constructor") ||
              (iterator.localName == "destructor")) {
            err = this.checkValidity(iterator, wrapper);
          } else {
            err = this.getScriptError({
              message: "XBL implementation elements take fields, properties, methods,"
                     + " an optional constructor and an optional destructor.",
              lineNumber: 0,
              columnNumber: 0
            }, wrapper);
          }
          if (err) {
            return err;
          }

          iterator = this.getNextSiblingElement(iterator);
        }
        return null; // end case "implementation"

      case "field":
        iterator = aElement.firstChild;
        if (!iterator) {
          err = this.getScriptError({
            message: "XBL field elements must have a script text content.",
            lineNumber: 0,
            columnNumber: 0
          }, wrapper);
          return err;
        }

        err = this.checkRequiredAttributes(aElement,
                [{
                  namespaceURI: null,
                  localName: "name"
                 }],
                wrapper);
        if (err) {
          return err;
        }

        err = this.checkNodeForScript(aElement, wrapper);
        return err; // end case "field"

      case "property":
        err = this.checkRequiredAttributes(aElement,
                [{
                  namespaceURI: null,
                  localName: "name"
                 },
                 {
                  namespaceURI: null,
                  localName: "onget",
                  childAlternateName: "getter",
                  checkScript: true
                 },
                 {
                  namespaceURI: null,
                  localName: "onset",
                  childAlternateName: "setter",
                  checkScript: true
                 }],
                wrapper);
        if (err) {
          return err;
        }

        err = this.checkNodeForScript(aElement, wrapper);
        return err; // end case "property"

      case "method":
        err = this.checkRequiredAttributes(aElement,
                [{
                  namespaceURI: null,
                  localName: "name"
                 }],
                wrapper);
        if (err) {
          return err;
        }

        var bodyFound = false;
        while (iterator) {
          if (bodyFound) {
            err = this.getScriptError({
              message: "XBL body element already found for XBL method element.",
              lineNumber: 0,
              columnNumber: 0
            }, wrapper);
          } else if (iterator.localName == "body") {
            bodyFound = true;
            err = this.checkValidity(iterator, wrapper);
          }
          else if (iterator.localName == "iterator") {
            err = this.checkValidity(iterator, wrapper);
          } else {
            err = this.getScriptError({
              message: "XBL method element can only have parameter and body elements.",
              lineNumber: 0,
              columnNumber: 0
            }, wrapper);
          }

          if (err) {
            return err;
          }

          iterator = this.getNextSiblingElement(iterator);
        }
        return null; // end case "method"

      case "parameter":
        if (aElement.firstChild) {
          err = this.getScriptError({
            message: "XBL parameter elements should not have children.",
            lineNumber: 0,
            columnNumber: 0
          }, wrapper);
          return err;
        }

        err = this.checkRequiredAttributes(aElement,
                [{
                  namespaceURI: null,
                  localName: "name"
                 }],
                wrapper);
        return err; // end case "parameter"

      case "body":
      case "constructor":
      case "destructor":
        err = this.checkNodeForScript(aElement, wrapper);
        return err; // end case "body", "constructor", "destructor"

      // XXX ajvincent getter, setter elements are handled by the property element check.

      case "handlers":
        while (iterator) {
          if (iterator.localName != "handler") {
            err = this.getScriptError({
              message: "XBL handler elements must be direct children of the handlers element.",
              lineNumber: 0,
              columnNumber: 0
            }, wrapper);
            return err;
          }

          err = this.checkValidity(iterator, wrapper);
          if (err) {
            return err;
          }

          iterator = this.getNextSiblingElement(iterator);
        }
        return null; // end case "handlers"

      case "handler":
        iterator = aElement.firstChild;
        if (Boolean(iterator) == aElement.hasAttribute("action")) {
          err = this.getScriptError({
            message: "XBL handler elements must have an action attribute or text content, not both.",
            lineNumber: 0,
            columnNumber: 0
          }, wrapper);
          return err;
        }

        err = this.checkRequiredAttributes(aElement,
                [{
                  namespaceURI: null,
                  localName: "event"
                 }],
                wrapper);
        if (err) {
          return err;
        }

        err = this.checkNodeForScript(aElement, wrapper);
        return err; // end case "handler"
    } // end switch (aElement.localName)

    err = this.getScriptError({
      message: "Unexpected element in XBL namespace:" + aElement.nodeName,
      lineNumber: 0,
      columnNumber: 0
      }, wrapper);
    return err;
  },

  // xeILanguagePackService
  contentType: "application/xml",

  // xeILanguagePackService
  specificationURI: "http://www.mozilla.org/projects/xbl/xbl.html",

  // xeILanguagePackService
  description: "eXtensible Binding Language 1.0",

  // xeILanguagePackService
  id: "XBL",

  // xeILanguagePackService
  version: "1.0",

  // xeILanguagePackService
  getNewTransaction: function getNewTransaction(transactionName) {
    NOT_IMPLEMENTED();
  },

  // xeILanguagePackService
  getFirstSyntaxError: function getFirstSyntaxError(wrapper) {
    var i;

    var source = wrapper.wrappedSource;
    const langPackPrefix = "@verbosio.mozdev.org/language-pack-service;1";
    if (!xmlLanguageChecker) {
      var xmlContractId = langPackPrefix + "?contentType=application/xml";
      xmlLanguageChecker = Components.classes[xmlContractId]
                                     .getService(xeILanguagePackService);
      assert(xmlLanguageChecker, "No XML language pack service?");
    }

    if (!jsLanguageChecker) {
      var jsContractId = langPackPrefix + "?contentType=application/javascript";
      jsLanguageChecker = Components.classes[jsContractId]
                                    .getService(xeILanguagePackService);
      assert(jsLanguageChecker, "No JavaScript language pack service?");
    }

    if (!jsSourceMap) {
      jsSourceMap = jsLanguageChecker.createSourceMap();
      assert(jsSourceMap, "No JavaScript source map?");
    }

    if (!nodePositionService) {
      var nodePositionCID = "@verbosio.mozdev.org/node-position-service;1";
      nodePositionService = Components.classes[nodePositionCID]
                                      .getService(xeINodePositionService);
      assert(nodePositionService, "No XML node positioning service?");
    }

    var err = xmlLanguageChecker.getFirstSyntaxError(wrapper);
    if (err) {
      return err;
    }

    var doc = parser.parseFromString(source, "application/xml");
    if ((doc.documentElement.namespaceURI != XBL_NS) ||
        (doc.documentElement.localName != "bindings")) {
      err = this.getScriptError({
        message: "XBL documents must have a root element in the XBL namespace named 'bindings'.",
        lineNumber: 0,
        columnNumber: 0
      }, wrapper);

      return err;
    }

    err = this.checkValidity(doc.documentElement, wrapper);
    if (err) {
      return err;
    }

    // Get coordinates of JavaScript inline source code.
    var filterTypesMatch = nsIDOMNodeFilter.SHOW_ATTRIBUTE |
                           nsIDOMNodeFilter.SHOW_TEXT |
                           nsIDOMNodeFilter.SHOW_SHOW_CDATA_SECTION;

    const filter = {
      ensureXBLOwner: function ensureXBLOwner(aNode) {
        return (aNode && aNode.namespaceURI == XBL_NS);
      },

      acceptNode: function acceptNode(aNode) {
        var rv = nsIDOMNodeFilter.FILTER_SKIP;
        switch (aNode.nodeType) {
          case nsIDOMNode.ELEMENT_NODE:
            for (var i = 0; i < aNode.attributes.length; i++) {
              var attr = aNode.attributes[i];
              if (this.acceptNode(attr) == nsIDOMNodeFilter.FILTER_ACCEPT) {
                rv = nsIDOMNode.FILTER_ACCEPT;
                break;
              }
            }
            break;

          /* Tree walkers usually don't deal with attribute nodes.  But the
           * node position service and the JavaScript source map do!
           */
          case nsIDOMNode.ATTRIBUTE_NODE:
            var owner = aNode.ownerElement;
            if (!this.ensureXBLOwner(owner)) {
              break;
            }
            if ((owner.localName == "property") &&
                ((aNode.nodeName == "onget") ||
                 (aNode.nodeName == "onset"))) {
              rv = nsIDOMNodeFilter.FILTER_ACCEPT;
              break;
            }
            if ((owner.localName == "handler") &&
                (aNode.nodeName == "action")) {
              rv = nsIDOMNodeFilter.FILTER_ACCEPT;
              break;
            }
            break;

          case nsIDOMNode.TEXT_NODE:
          case nsIDOMNode.CDATA_SECTION_NODE:
            var owner = aNode.parentNode;
            if (!this.ensureXBLOwner(owner)) {
              break;
            }
            switch (owner.localName) {
              case "getter":
              case "setter":
              case "field":
              case "body":
              case "handler":
              case "constructor":
              case "destructor":
              rv = nsIDOMNodeFilter.FILTER_ACCEPT;
              break;
            }
        } // end switch (aNode.nodeType)

        return rv;
      }
    }; // end const filter

    nodePositionService.updateNodePositions(wrapper, filterTypesMatch, filter);

    // Initialize the JavaScript source map.
    jsSourceMap.clear();
    filterTypesMatch = nsIDOMNodeFilter.SHOW_ELEMENT |
                       nsIDOMNodeFilter.SHOW_TEXT |
                       nsIDOMNodeFilter.SHOW_SHOW_CDATA_SECTION;
    var walker = doc.createTreeWalker(doc, filterTypesMatch, filter, true);
    var positionMapping = null;

    while (walker.nextNode()) {
      var node = walker.currentNode;
      if (node.nodeType == nsIDOMNode.ELEMENT_NODE) {

        // Any attribute which matches should be mapped.
        for (i = 0; i < node.attributes.length; i++) {
          var attr = node.attributes[i];
          if (filter.acceptNode(attr) == nsIDOMNodeFilter.FILTER_ACCEPT) {
            positionMapping = attr.getUserData("sourceCodeMapping");
            jsSourceMap.startFragment();
            jsSourceMap.addSource(attr.nodeValue,
                                  positionMapping.valueStart.lineNumber,
                                  positionMapping.valueStart.columnNumber);
            jsSourceMap.endFragment();
          }
        }

      } else if (node == node.parentNode.firstChild) {
        // We start work with the first data node only.  Others we ignore.
        // Make sure it's a contiguous stream of character data nodes!
        var iterator = node.nextSibling;
        while (iterator) {
          if (iterator instanceof nsIDOMCharacterData) {
            iterator = iterator.nextSibling;
          } else {
            iterator = null;
            node = null;
          }
        }

        if (!node) {
          continue;
        }

        // Map all the data nodes at once.
        jsSourceMap.startFragment();
        iterator = node;
        while (iterator) {
          positionMapping = iterator.getUserData("sourceCodeMapping");
          var lineStart = positionMapping.singleTagSet.startLineNumber;

          // 9 for <![CDATA[
          var columnStart = (iterator instanceof nsIDOMText) ?
                            positionMapping.singleTagSet.startColumnNumber :
                            positionMapping.singleTagSet.startColumnNumber + 9;

          jsSourceMap.addSource(iterator.nodeValue, lineStart, columnStart);
        }
        jsSourceMap.endFragment();
      }
    } // end while (walker.nextNode())
    walker = null;

    /* Get the syntax error from the JavaScript source map.  This is the last
     * step in xeILanguageCheckerService.getFirstSyntaxError.  If the map
     * returns null, we return null.
     */
    err = jsSourceMap.getFirstSyntaxError();
    return err;
  }, // end xeILanguageCheckerService.getFirstSyntaxError

  // nsITransactionListener
  willDo: function willDo(aManager, aTransaction) {
    // Do nothing
  },

  // nsITransactionListener
  didDo: function didDo(aManager, aTransaction, aResult) {
    // Do nothing
  },

  // nsITransactionListener
  willUndo: function willUndo(aManager, aTransaction) {
    // Do nothing
  },

  // nsITransactionListener
  didUndo: function didUndo(aManager, aTransaction, aResult) {
    // Do nothing
  },

  // nsITransactionListener
  willRedo: function willRedo(aManager, aTransaction) {
    // Do nothing
  },

  // nsITransactionListener
  didRedo: function didRedo(aManager, aTransaction, aResult) {
    // Do nothing
  },

  // nsITransactionListener
  willBeginBatch: function willBeginBatch(aManager) {
    NOT_IMPLEMENTED();
  },

  // nsITransactionListener
  didBeginBatch: function didBeginBatch(aManager, aResult) {
    NOT_IMPLEMENTED();
  },

  // nsITransactionListener
  willEndBatch: function willEndBatch(aManager) {
    NOT_IMPLEMENTED();
  },

  // nsITransactionListener
  didEndBatch: function didEndBatch(aManager, aResult) {
    NOT_IMPLEMENTED();
  },

  // nsITransactionListener
  willMerge: function willMerge(aManager, aTopTxn, aTxnToMerge) {
    NOT_IMPLEMENTED();
  },

  // nsITransactionListener
  didMerge: function didMerge(aManager, aTopTxn, aTxnToMerge) {
    NOT_IMPLEMENTED();
  },


  // nsISecurityCheckedComponent
  canCreateWrapper: function canCreateWrapper(iid) {
    return "AllAccess";
  },

  // nsISecurityCheckedComponent
  canCallMethod: function canCallMethod(iid, methodName) {
    return "AllAccess";
  },

  // nsISecurityCheckedComponent
  canGetProperty: function canGetProperty(iid, propertyName) {
    return "AllAccess";
  },

  // nsISecurityCheckedComponent
  canSetProperty: function canSetProperty(iid, propertyName) {
    return "NoAccess";
  },

  // nsIClassInfo
  getInterfaces: function getInterfaces(aCount) {
    var interfaces = [xeIXMLLanguagePackService,
                      xeILanguagePackService,
                      nsITransactionListener,
                      nsISecurityCheckedComponent,
                      nsIClassInfo];
    aCount.value = interfaces.length;
    return interfaces;
  },

  // nsIClassInfo
  getHelperForLanguage: function getHelperForLanguage(aLanguage) {
    return null;
  },

  // nsIClassInfo
  classDescription: componentName,

  // nsIClassInfo
  classID: componentId,

  // nsIClassInfo
  implementationLanguage: nsIProgrammingLanguage.JAVASCRIPT,

  // nsIClassInfo
  flags: nsIClassInfo.SINGLETON,

  // nsISupports
  QueryInterface: function QueryInterface(aIID) {
    if (aIID.equals(xeIXMLLanguagePackService) ||
        aIID.equals(xeILanguagePackService) ||
        aIID.equals(nsITransactionListener) ||
        aIID.equals(nsISecurityCheckedComponent) ||
        aIID.equals(nsIClassInfo) ||
        aIID.equals(nsISupports))
      return this;

    // Discover what interfaces we need
    var i = Components.interfaces;
    var found = false;
    for (var prop in i) {
      if (aIID.equals(i[prop])) {
        found = true;
        dump("QUERYINTERFACE XBLLanguagePack: " + prop + "\n");
        break;
      }
    }
    if (!found) {
      dump("QUERYINTERFACE XBLLanguagePack: Unknown\n");
    }

    throw Components.results.NS_ERROR_NO_INTERFACE;
    return null;
  }
};

/* The rest of this code is largely borrowed from "Creating Applications With
 * Mozilla", http://books.mozdev.org, Chapter 8.
 */
var Module = {
  registerSelf: function registerSelf(compMgr, fileSpec, location, type) {
    compReg = compMgr.QueryInterface(nsIComponentRegistrar);

    compReg.registerFactoryLocation(componentId,
                                    componentName,
                                    contractId,
                                    fileSpec,
                                    location,
                                    type);
  },

  getClassObject: function getClassObject(compMgr, aCID, aIID) {
    if (!aCID.equals(componentId)) {
      throw Components.results.NS_ERROR_NO_INTERFACE;
    }
    if (!aIID.equals(nsIFactory)) {
      throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
    }
    return this.factory;
  },

  factory: {
    createInstance: function createInstance(outer, iid) {
      if (outer != null)
        throw Components.results.NS_ERROR_NO_AGGREGATION;

      return (new languagePack()).QueryInterface(iid);
    }
  },

  canUnload: function canUnload() {
    return true;
  },

  QueryInterface: function QueryInterface(aIID) {
    if (aIID.equals(nsIModule) ||
       (aIID.equals(nsISupports)))
      return this;

    throw Components.results.NS_ERROR_NO_INTERFACE;
    return null;
  }
};

function NSGetModule(compMgr, fileSpec) {
  return Module;
}
