<?xml version="1.0"?>
<!-- ***** 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 Verbosio XML editor.
  -
  - The Initial Developer of the Original Code is
  - Alexander J. Vincent <ajvincent@gmail.com>.
  - Portions created by the Initial Developer are Copyright (C) 2007
  - 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 LGPL or the GPL. 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 ***** -->
<!DOCTYPE bindings [
  <!ENTITY sourceEditBindings
    "chrome://source-edit/content/bindings/source-editor.xml"
    >
]>
<bindings
   xmlns="http://www.mozilla.org/xbl"
   xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   xmlns:xbl="http://www.mozilla.org/xbl">

  <binding id="text-base">
    <implementation implements="xeIPrecondition">
      <field name="_transaction">null</field>
      <property name="transaction" readonly="true">
        <getter>return this._transaction;</getter>
      </property>

      <property name="wrapper" readonly="true">
        <getter>return this.getUserData("wrapper");</getter>
      </property>

      <field name="_invalidMessage">null</field>
      <property name="invalidMessage" readonly="true">
        <getter>return this._invalidMessage;</getter>
      </property>

      <field name="_helpURI">null</field>
      <property name="helpURI" readonly="true">
        <getter>return this._helpURI;</getter>
      </property>

      <field name="sourceMarkup" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "sourceMarkup");
      </field>
    </implementation>
  </binding>

  <binding id="xeIXMLDocumentWrapper"
           extends="&sourceEditBindings;#text-base">
    <content>
      <xul:vbox>
        <xul:description>Type in your XML markup here.</xul:description>
        <xul:hbox>
          <xul:textbox anonid="beforeDocType"
                       contents="code"
                       xulwidgets="true"
                       multiline="true"
                       rows="5"
                       cols="120"
                       wrap="off"
                      />
        </xul:hbox>
        <xul:hbox>
          <xul:textbox anonid="docType"
                       contents="code"
                       xulwidgets="true"
                       multiline="true"
                       rows="5"
                       cols="120"
                       readonly="true"
                       wrap="off"
                      />
        </xul:hbox>
        <xul:hbox>
          <xul:textbox anonid="sourceMarkup"
                       contents="code"
                       xulwidgets="true"
                       multiline="true"
                       rows="25"
                       cols="120"
                       wrap="off"
                      />
        </xul:hbox>
      </xul:vbox>
    </content>

    <implementation>
      <field name="beforeDocType" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "beforeDocType");
      </field>

      <field name="docType" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "docType");
      </field>

      <field name="sourceMarkup" readonly="true">
        document.getAnonymousElementByAttribute(this, "anonid", "sourceMarkup");
      </field>

      <field name="langPackSvc" readonly="true">
        Components.classes["@verbosio.mozdev.org/language-pack-service;1?"
          + "contentType=application/xml"]
                  .getService(Components.interfaces.xeIXMLLanguagePackService);
      </field>

      <property name="sourceFragment" readonly="true">
        <getter><![CDATA[
          return this.beforeDocType.value +
                 this.docType.value +
                 this.sourceMarkup.value;
        ]]></getter>
      </property>

      <method name="getMinIndexOfString">
        <parameter name="aString"/>
        <parameter name="aOffset"/>
        <parameter name="aChar"/>
        <body><![CDATA[
          var index = aString.indexOf(aChar, aOffset);
          return (index == -1) ? Infinity : index;
        ]]></body>
      </method>

      <method name="initialize">
        <body><![CDATA[
          var entityMgr = gDocumentPack.entityManager;
          var origSource = entityMgr.getSanitizedContent(this.wrapper);
          if (this.wrapper.wrappedDocument.doctype) {
            var doctype = this.wrapper.documentType;
            assert(origSource.indexOf(doctype) != -1, "Where is the doctype?");

            var docTypeStartIndex = origSource.indexOf(doctype);
            this.beforeDocType.value = origSource.substr(0, docTypeStartIndex);
            var docTypeEndIndex = docTypeStartIndex + doctype.length;

            this.docType.value = doctype;
            this.sourceMarkup.value = origSource.substr(docTypeEndIndex);
            this.beforeDocType.disabled = false;
          } else {
            this.beforeDocType.value = "";
            this.beforeDocType.disabled = true;
            this.docType.value = "";
            this.sourceMarkup.value = origSource;
          }

          var pre = this;
          this._transaction = new xeRecordedTransaction();
          this._transaction.addProperties({
           // xeITransaction
           preconditionLocalId: null,

           // xeITransaction
           description: "New source",

           // xeITransaction
           ownerWrapper: this.wrapper,

           // xeIRecordedTransaction
           doRecordedTransaction: function doRecordedTransaction() {
             const nsIDOMNode = Components.interfaces.nsIDOMNode;
             var source = pre.sourceFragment;
             var subdoc = parser.parseFromString(source, "application/xml");
             var subroot = subdoc.documentElement;
             var targetDoc = pre.wrapper.wrappedDocument;

/* We have to be very careful at the document level.  A Document node has at
   most one DocumentType child, and exactly one Element child.  So we can't
   just insert them; we have to know where they are, so we can replace them.

   Also, the DOM parser reads internal entities in a doctype's subset and
   delivers the context of them, where the original document doesn't have that.
   So we can't assert the doctypes are equal.
 */
             var iterator;

             if (targetDoc.doctype) {
               assert(subdoc.doctype, "No new doc type?");
               // Remove everything before the doctype.
               iterator = targetDoc.firstChild;
               while (iterator != targetDoc.doctype) {
                 targetDoc.removeChild(iterator);
                 iterator = targetDoc.firstChild;
               }
             }

             if (subdoc.doctype) {
               assert(targetDoc.doctype, "No old doc type?");
               iterator = subdoc.firstChild;
               while (iterator != subdoc.doctype) {
                 targetDoc.insertBefore(targetDoc.adoptNode(iterator), targetDoc.doctype);
                 iterator = subdoc.firstChild;
               }
             }

             // Remove everything before the document element.
             iterator = (!targetDoc.doctype) ?
                        targetDoc.firstChild :
                        targetDoc.doctype.nextSibling;
             var targetRoot = targetDoc.documentElement;
             while (iterator != targetRoot) {
               iterator = iterator.nextSibling;
               targetDoc.removeChild(iterator.previousSibling);
             }

             // Insert everything from before the new document element.
             iterator = (!subdoc.doctype) ?
                        subdoc.firstChild :
                        subdoc.doctype.nextSibling;
             while (iterator != subroot) {
               if (iterator.nodeType != nsIDOMNode.DOCUMENT_TYPE_NODE) {
                 targetDoc.insertBefore(targetDoc.adoptNode(iterator), targetRoot);
               }
               iterator = (!subdoc.doctype) ?
                          subdoc.firstChild :
                          subdoc.doctype.nextSibling;
             }

             // Replace the document element.
             targetDoc.replaceChild(
               targetDoc.adoptNode(
                 subroot.cloneNode(true)
               ), targetRoot
             );

             // Remove everything after the document element.
             iterator = targetDoc.lastChild;
             while (iterator != targetDoc.documentElement) {
               targetDoc.removeChild(iterator);
               iterator = targetDoc.lastChild;
             }

             // Insert everything from after the new document element.
             iterator = subroot.nextSibling;
             while (iterator) {
               targetDoc.appendChild(targetDoc.adoptNode(iterator));
               iterator = subroot.nextSibling;
             }

             // Reset the range.
             pre.wrapper.selectionRange.setStartBefore(targetDoc.firstChild);
             pre.wrapper.selectionRange.setEndBefore(targetDoc.firstChild);
           },

           // nsITransaction
           merge: function merge(aTransaction) {
             return false;
           },

           // nsISupports
           QueryInterface: function QueryInterface(aIID) {
             if (aIID.equals(Components.interfaces.xeIRecordedTransaction) ||
                 aIID.equals(Components.interfaces.xeITransaction) ||
                 aIID.equals(Components.interfaces.nsITransaction) ||
                 aIID.equals(Components.interfaces.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 xeIRecordedTransaction: " + prop + "\n");
                 break;
               }
             }
             if (!found) {
               dump("QUERYINTERFACE xeIRecordedTransaction: Unknown\n");
             }
             throw Components.results.NS_ERROR_NO_INTERFACE;
             return null;
           }
         }); // end this._transaction
        ]]></body>
      </method>

      <field name="saxReader" readonly="true">
        Components.classes["@mozilla.org/saxparser/xmlreader;1"]
                  .createInstance(Components.interfaces.nsISAXXMLReader);
      </field>

      <field name="saxLexicalHandler" readonly="true"><![CDATA[
        ({
           docTypeFound: false,
           comment: function comment() {},
           startDTD: function startDTD() {
             this.docTypeFound = true;
           },
           endDTD: function endDTD() {},
           startCDATA: function startCDATA() {},
           endCDATA: function endCDATA() {},
           startEntity: function startEntity() {},
           endEntity: function endEntity() {}
         });
      ]]></field>

      <constructor>
        this.saxReader.lexicalHandler = this.saxLexicalHandler;
      </constructor>

      <destructor>
        this.saxReader.lexicalHandler = null;
      </destructor>
    </implementation>

    <handlers>
      <handler event="input" phase="capturing"><![CDATA[
        // Parse the XML!
        var msg = this.langPackSvc.getFirstSyntaxError({
          wrappedSource: this.sourceFragment,
          publicURI: this.wrapper.publicURI
        });
        if (msg) {
          this._invalidMessage = msg;
          this.sourceMarkup.invalid = Boolean(msg);
        } else {
          // We can't have a doctype in the source editing.
          this.saxLexicalHandler.docTypeFound = false;
          this.saxReader.parseFromString(this.sourceMarkup.value,
                                         "application/xml");
          if (this.saxLexicalHandler.docTypeFound) {
            // XXX ajvincent Localize!
            this._invalidMessage = "You cannot change the document type.";
            this.sourceMarkup.invalid = true;
          } else {
            this._invalidMessage = null;
            this.sourceMarkup.invalid = false;
          }
        }
        this.parentNode.updateAcceptDisabled();
      ]]></handler>
    </handlers>
  </binding>

  <binding id="xeITextDocumentWrapper"
           extends="&sourceEditBindings;#text-base">
    <content>
      <xul:vbox>
        <xul:hbox>
          <xul:textbox anonid="sourceMarkup"
                       contents="code"
                       xulwidgets="true"
                       multiline="true"
                       rows="25"
                       cols="120"
                       wrap="off"
                      />
        </xul:hbox>
      </xul:vbox>
    </content>

    <implementation>
      <method name="initialize">
        <body><![CDATA[
          var pre = this;
          this._transaction = new xeTransactionCommon();
          this._transaction.addProperties({
            ownerWrapper: this.wrapper,
            originalSource: this.wrapper.wrappedSource,
            newSource: "",

            description: "Document-level edit",

            doTransaction: function doTransaction() {
              this.newSource = pre.sourceFragment;
              this.ownerWrapper.reparseSource(this.newSource);
            },

            undoTransaction: function undoTransaction() {
              this.ownerWrapper.reparseSource(this.originalSource);
            },

            redoTransaction: function redoTransaction() {
              this.ownerWrapper.reparseSource(this.newSource);
            },

            merge: function merge(aTxn) {
              return false;
            }
          });
        ]]></body>
      </method>

      <property name="sourceFragment" readonly="true">
        <getter><![CDATA[
          return this.sourceMarkup.value;
        ]]></getter>
      </property>
    </implementation>
  </binding>
</bindings>
