All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.xmlbeans.impl.store.Cur Maven / Gradle / Ivy

Go to download

The Apache Commons Codec package contains simple encoder and decoders for various formats such as Base64 and Hexadecimal. In addition to these widely used encoders and decoders, the codec package also maintains a collection of phonetic encoding utilities.

The newest version!
/*   Copyright 2004 The Apache Software Foundation
 *
 *   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.
 */

package org.apache.xmlbeans.impl.store;

import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.DocumentType;
import org.w3c.dom.DOMException;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Element;
import org.w3c.dom.EntityReference;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;
import org.w3c.dom.DOMImplementation;

// DOM Level 3
import org.w3c.dom.UserDataHandler;
import org.w3c.dom.DOMConfiguration;
import org.w3c.dom.TypeInfo;


import javax.xml.transform.Source;

import java.io.PrintStream;

import java.util.Iterator;
import java.util.Map;
import java.util.ArrayList;
import java.util.List;

import org.apache.xmlbeans.impl.soap.Detail;
import org.apache.xmlbeans.impl.soap.DetailEntry;
import org.apache.xmlbeans.impl.soap.MimeHeaders;
import org.apache.xmlbeans.impl.soap.Name;
import org.apache.xmlbeans.impl.soap.SOAPBody;
import org.apache.xmlbeans.impl.soap.SOAPBodyElement;
import org.apache.xmlbeans.impl.soap.SOAPElement;
import org.apache.xmlbeans.impl.soap.SOAPEnvelope;
import org.apache.xmlbeans.impl.soap.SOAPException;
import org.apache.xmlbeans.impl.soap.SOAPFactory;
import org.apache.xmlbeans.impl.soap.SOAPFault;
import org.apache.xmlbeans.impl.soap.SOAPFaultElement;
import org.apache.xmlbeans.impl.soap.SOAPHeader;
import org.apache.xmlbeans.impl.soap.SOAPHeaderElement;
import org.apache.xmlbeans.impl.soap.SOAPPart;
import org.apache.xmlbeans.impl.store.Xobj.Bookmark;

import org.apache.xmlbeans.impl.store.Locale.LoadContext;

import org.apache.xmlbeans.impl.store.DomImpl.Dom;
import org.apache.xmlbeans.impl.store.DomImpl.CharNode;
import org.apache.xmlbeans.impl.store.DomImpl.TextNode;
import org.apache.xmlbeans.impl.store.DomImpl.CdataNode;
import org.apache.xmlbeans.impl.store.DomImpl.SaajTextNode;
import org.apache.xmlbeans.impl.store.DomImpl.SaajCdataNode;

import org.apache.xmlbeans.CDataBookmark;
import org.apache.xmlbeans.XmlBeans;
import org.apache.xmlbeans.XmlLineNumber;
import org.apache.xmlbeans.SchemaField;
import org.apache.xmlbeans.SchemaType;
import org.apache.xmlbeans.SchemaTypeLoader;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject;
import org.apache.xmlbeans.XmlOptions;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.QNameSet;
import org.apache.xmlbeans.XmlDocumentProperties;
import org.apache.xmlbeans.XmlCursor.XmlBookmark;

import org.apache.xmlbeans.impl.values.TypeStore;
import org.apache.xmlbeans.impl.values.TypeStoreUser;
import org.apache.xmlbeans.impl.values.TypeStoreVisitor;
import org.apache.xmlbeans.impl.values.TypeStoreUserFactory;

import javax.xml.namespace.QName;

import org.apache.xmlbeans.impl.common.ValidatorListener;
import org.apache.xmlbeans.impl.common.XmlLocale;
import org.apache.xmlbeans.impl.common.QNameHelper;


final class Cur
{
    static final int TEXT     = 0; // Must be 0
    static final int ROOT     = 1;
    static final int ELEM     = 2;
    static final int ATTR     = 3;
    static final int COMMENT  = 4;
    static final int PROCINST = 5;

    static final int POOLED       = 0;
    static final int REGISTERED   = 1;
    static final int EMBEDDED     = 2;
    static final int DISPOSED     = 3;

    static final int END_POS = -1;
    static final int NO_POS  = -2;

    Cur ( Locale l )
    {
        _locale = l;
        _pos = NO_POS;

        _tempFrame = -1;

        _state = POOLED;

        _stackTop = Locations.NULL;
        _selectionFirst = -1;
        _selectionN = -1;
        _selectionLoc = Locations.NULL;
        _selectionCount = 0;
    }

    boolean isPositioned ( ) { assert isNormal(); return _xobj != null; }

    static boolean kindIsContainer ( int k ) { return k ==  ELEM || k ==  ROOT; }
    static boolean kindIsFinish    ( int k ) { return k == -ELEM || k == -ROOT; }

    int kind ( )
    {
        assert isPositioned();
        int kind = _xobj.kind();
        return _pos == 0 ? kind : (_pos == END_POS ? - kind : TEXT);
    }

    boolean isRoot      ( ) { assert isPositioned(); return _pos == 0 && _xobj.kind() == ROOT;     }
    boolean isElem      ( ) { assert isPositioned(); return _pos == 0 && _xobj.kind() == ELEM;     }
    boolean isAttr      ( ) { assert isPositioned(); return _pos == 0 && _xobj.kind() == ATTR;     }
    boolean isComment   ( ) { assert isPositioned(); return _pos == 0 && _xobj.kind() == COMMENT;  }
    boolean isProcinst  ( ) { assert isPositioned(); return _pos == 0 && _xobj.kind() == PROCINST; }
    boolean isText      ( ) { assert isPositioned(); return _pos > 0; }
    boolean isEnd       ( ) { assert isPositioned(); return _pos == END_POS && _xobj.kind() ==ELEM;}
    boolean isEndRoot   ( ) { assert isPositioned(); return _pos == END_POS && _xobj.kind() ==ROOT;}
    boolean isNode      ( ) { assert isPositioned(); return _pos == 0; }
    boolean isContainer ( ) { assert isPositioned(); return _pos == 0       && kindIsContainer( _xobj.kind() ); }
    boolean isFinish    ( ) { assert isPositioned(); return _pos == END_POS && kindIsContainer( _xobj.kind() ); }
    boolean isUserNode  ( ) { assert isPositioned(); int k = kind(); return k == ELEM || k == ROOT || (k == ATTR && !isXmlns()); }

    boolean isContainerOrFinish ( )
    {
        assert isPositioned();

        if (_pos!=0 && _pos!= END_POS)
            return false;

        int kind = _xobj.kind();
        return kind == ELEM || kind == -ELEM || kind == ROOT || kind == -ROOT;
    }

    boolean isNormalAttr ( ) { return isNode() && _xobj.isNormalAttr(); }
    boolean isXmlns      ( ) { return isNode() && _xobj.isXmlns(); }

    boolean isTextCData  ( ) { return _xobj.hasBookmark(CDataBookmark.class, _pos); }

    QName   getName  ( ) { assert isNode() || isEnd(); return _xobj._name; }
    String  getLocal ( ) { return getName().getLocalPart(); }
    String  getUri   ( ) { return getName().getNamespaceURI(); }

    String  getXmlnsPrefix ( ) { assert isXmlns(); return _xobj.getXmlnsPrefix(); }
    String  getXmlnsUri    ( ) { assert isXmlns(); return _xobj.getXmlnsUri(); }

    boolean isDomDocRoot  ( ) { return isRoot() && _xobj.getDom() instanceof Document; }
    boolean isDomFragRoot ( ) { return isRoot() && _xobj.getDom() instanceof DocumentFragment; }

    int cchRight ( ) { assert isPositioned(); return _xobj.cchRight( _pos ); }
    int cchLeft  ( ) { assert isPositioned(); return _xobj.cchLeft ( _pos ); }

    //
    // Creation methods
    //

    void createRoot ( )
    {
        createDomDocFragRoot();
    }

    void createDomDocFragRoot ( )
    {
        moveTo( new Xobj.DocumentFragXobj( _locale ) );
    }

    void createDomDocumentRoot ( )
    {
        moveTo( createDomDocumentRootXobj( _locale ) );
    }

    void createAttr ( QName name )
    {
        createHelper( new Xobj.AttrXobj( _locale, name ) );
    }

    void createComment ( )
    {
        createHelper( new Xobj.CommentXobj( _locale ) );
    }

    void createProcinst ( String target )
    {
        createHelper( new Xobj.ProcInstXobj( _locale, target ) );
    }

    void createElement ( QName name )
    {
        createElement( name, null );
    }

    void createElement ( QName name, QName parentName )
    {
        createHelper( createElementXobj( _locale, name, parentName ) );
    }

    static Xobj createDomDocumentRootXobj ( Locale l )
    {
        return createDomDocumentRootXobj(l, false);
    }

    static Xobj createDomDocumentRootXobj ( Locale l , boolean fragment)
    {
        Xobj xo;

        if (l._saaj == null)
            if (fragment)
                xo = new Xobj.DocumentFragXobj( l );
            else
                xo = new Xobj.DocumentXobj( l );
        else
            xo = new Xobj.SoapPartDocXobj( l );

        if (l._ownerDoc == null)
            l._ownerDoc = xo.getDom();

        return xo;
    }

    static Xobj createElementXobj ( Locale l, QName name, QName parentName )
    {
        if (l._saaj == null)
            return new Xobj.ElementXobj( l, name );

        Class c = l._saaj.identifyElement( name, parentName );

        if (c == SOAPElement.class)       return new Xobj.SoapElementXobj       ( l, name );
        if (c == SOAPBody.class)          return new Xobj.SoapBodyXobj          ( l, name );
        if (c == SOAPBodyElement.class)   return new Xobj.SoapBodyElementXobj   ( l, name );
        if (c == SOAPEnvelope.class)      return new Xobj.SoapEnvelopeXobj      ( l, name );
        if (c == SOAPHeader.class)        return new Xobj.SoapHeaderXobj        ( l, name );
        if (c == SOAPHeaderElement.class) return new Xobj.SoapHeaderElementXobj ( l, name );
        if (c == SOAPFaultElement.class)  return new Xobj.SoapFaultElementXobj  ( l, name );
        if (c == Detail.class)            return new Xobj.DetailXobj            ( l, name );
        if (c == DetailEntry.class)       return new Xobj.DetailEntryXobj       ( l, name );
        if (c == SOAPFault.class)         return new Xobj.SoapFaultXobj         ( l, name );

        throw new IllegalStateException( "Unknown SAAJ element class: " + c );
    }

    private void createHelper ( Xobj x )
    {
        assert x._locale == _locale;

        // insert the new Xobj into an exisiting tree.

        if (isPositioned())
        {
            Cur from = tempCur( x, 0 );
            from.moveNode( this );
            from.release();
        }

        moveTo( x );
    }

    //
    // General operations
    //

    boolean isSamePos ( Cur that )
    {
        assert isNormal() && (that == null || that.isNormal());

        return _xobj == that._xobj && _pos == that._pos;
    }

    // is this just after the end of that (that must be the start of a node)

    boolean isJustAfterEnd ( Cur that )
    {
        assert isNormal() && that != null && that.isNormal() && that.isNode();

        return that._xobj.isJustAfterEnd( _xobj, _pos );
    }

    boolean isJustAfterEnd ( Xobj x )
    {
        return x.isJustAfterEnd( _xobj, _pos );
    }

    boolean isAtEndOf ( Cur that )
    {
        assert that != null && that.isNormal() && that.isNode();

        return _xobj == that._xobj && _pos == END_POS;
    }

    boolean isInSameTree ( Cur that )
    {
        assert isPositioned() && that.isPositioned();

        return _xobj.isInSameTree( that._xobj );
    }

    // Retunr -1, 0 or 1 for relative cursor positions.  Return 2 is not in sames trees.

    int comparePosition ( Cur that )
    {
        assert isPositioned() && that.isPositioned();

        // If in differnet locales, then can't comapre

        if (_locale != that._locale)
            return 2;

        // No need to denormalize, but I want positions which I can compare (no END_POS)

        Xobj xThis = _xobj;
        int  pThis = _pos == END_POS ? xThis.posAfter() - 1 : _pos;

        Xobj xThat = that._xobj;
        int  pThat = that._pos == END_POS ? xThat.posAfter() - 1 : that._pos;

        // There are several cases:
        //
        // 1. Cursors are on the same xobj
        // 2. One cursor is a child of the other
        // 3. Cursors share a common parent
        // 4. Cursors are not in the same trees
        //
        // Check for the first, trivial, case.  Then, compute the depths of the nodes the
        // cursors are on, checkin for case 2
        //

        if (xThis == xThat)
            return pThis < pThat ? -1 : pThis == pThat ? 0 : 1;

        // Compute the depth of xThis.  See if I hit xThat (case 2)

        int dThis = 0;

        for ( Xobj x = xThis._parent ; x != null ; x = x._parent )
        {
            dThis++;

            if (x == xThat)
                return pThat < xThat.posAfter() - 1 ? 1 : -1;
        }

        // Compute the depth of xThat.  See if I hit xThis (case 2)

        int dThat = 0;

        for ( Xobj x = xThat._parent ; x != null ; x = x._parent )
        {
            dThat++;

            if (x == xThis)
                return pThis < xThis.posAfter() - 1 ? -1 : 1;
        }

        // Must be case 3 or 4 now.  Find a common parent.  If none, then it's case 4

        while ( dThis > dThat ) { dThis--; xThis = xThis._parent; }
        while ( dThat > dThis ) { dThat--; xThat = xThat._parent; }

        assert dThat == dThis;

        if (dThat == 0)
            return 2;

        assert xThis._parent != null && xThat._parent != null;

        while ( xThis._parent != xThat._parent )
        {
            if ((xThis = xThis._parent) == null)
                return 2;

            xThat = xThat._parent;
        }

        // Now, see where xThis and XThat are relative to eachother in the childlist.  Apply
        // some quick common checks before iterating.

        if (xThis._prevSibling == null || xThat._nextSibling == null)
            return -1;

        if (xThis._nextSibling == null || xThat._prevSibling == null)
            return 1;

        while ( xThis != null )
            if ((xThis = xThis._prevSibling) == xThat)
                return 1;

        return -1;
    }

    void setName ( QName newName )
    {
        assert isNode() && newName != null;

        _xobj.setName( newName );
    }

    void moveTo ( Xobj x )
    {
        moveTo( x, 0 );
    }

    void moveTo ( Xobj x, int p )
    {
        // This cursor may not be normalized upon entry, don't assert isNormal() here

        assert x == null || _locale == x._locale;
        assert x != null || p == NO_POS;
        assert x == null || x.isNormal( p ) ||  ( x.isVacant() && x._cchValue==0 && x._user == null );
        assert _state == REGISTERED || _state == EMBEDDED;
        assert _state == EMBEDDED || (_xobj == null || !isOnList( _xobj._embedded ));
        assert _state == REGISTERED || (_xobj != null && isOnList( _xobj._embedded ));

        moveToNoCheck( x, p );

        assert isNormal() ||  ( _xobj.isVacant() && _xobj._cchValue==0 && _xobj._user == null );
    }

    void moveToNoCheck ( Xobj x, int p )
    {
        if (_state == EMBEDDED && x != _xobj)
        {
            _xobj._embedded = listRemove( _xobj._embedded );
            _locale._registered = listInsert( _locale._registered );
            _state = REGISTERED;
        }

        _xobj = x;
        _pos = p;
    }

    void moveToCur ( Cur to )
    {
        assert isNormal() && (to == null || to.isNormal());

        if (to == null)
            moveTo( null, NO_POS );
        else
            moveTo( to._xobj, to._pos );
    }

    void moveToDom ( Dom d )
    {
        assert _locale == d.locale();
        assert d instanceof Xobj || d instanceof Xobj.SoapPartDom;

        moveTo( d instanceof Xobj ? (Xobj) d : ((Xobj.SoapPartDom) d)._docXobj );
    }

    static final class Locations
    {
        private static final int NULL = -1;

        Locations ( Locale l )
        {
            _locale = l;

            _xobjs = new Xobj [ _initialSize ];
            _poses = new int  [ _initialSize ];
            _curs  = new Cur  [ _initialSize ];
            _next  = new int  [ _initialSize ];
            _prev  = new int  [ _initialSize ];
            _nextN = new int  [ _initialSize ];
            _prevN = new int  [ _initialSize ];

            for ( int i = _initialSize - 1 ; i >= 0 ; i-- )
            {
                assert _xobjs[ i ] == null;
                _poses [ i ] = NO_POS;
                _next  [ i ] = i + 1;
                _prev  [ i ] = NULL;
                _nextN [ i ] = NULL;
                _prevN [ i ] = NULL;
            }

            _next [ _initialSize - 1 ] = NULL;

            _free = 0;
            _naked = NULL;
        }

        boolean isSamePos ( int i, Cur c )
        {
            if (_curs[ i ] == null)
                return c._xobj == _xobjs[ i ] && c._pos == _poses[ i ];
            else
                return c.isSamePos( _curs[ i ] );
        }

        boolean isAtEndOf ( int i, Cur c )
        {
            assert _curs[ i ] != null || _poses[ i ] == 0;
            assert _curs[ i ] == null || _curs[ i ].isNode();

            if (_curs[ i ] == null)
                return c._xobj == _xobjs[ i ] && c._pos == END_POS;
            else
                return c.isAtEndOf( _curs[ i ] );
        }

        void moveTo ( int i, Cur c )
        {
            if (_curs[ i ] == null)
                c.moveTo( _xobjs[ i ], _poses[ i ] );
            else
                c.moveToCur( _curs[ i ] );
        }

        int insert ( int head, int before, int i )
        {
            return insert( head, before, i, _next, _prev );
        }

        int remove ( int head, int i )
        {
            Cur c = _curs[ i ];

            assert c != null || _xobjs[ i ] != null;
            assert c != null || _xobjs[ i ] != null;

            if (c != null)
            {
                _curs[ i ].release();
                _curs[ i ] = null;

                assert _xobjs[ i ] == null;
                assert _poses [ i ] == NO_POS;
            }
            else
            {
                assert _xobjs[ i ] != null && _poses[ i ] != NO_POS;

                _xobjs[ i ] = null;
                _poses[ i ] = NO_POS;

                _naked = remove( _naked, i, _nextN, _prevN );
            }

            head = remove( head, i, _next, _prev );

            _next[ i ] = _free;
            _free = i;

            return head;
        }

        int allocate ( Cur addThis )
        {
            assert addThis.isPositioned();

            if (_free == NULL)
                makeRoom();

            int i = _free;

            _free = _next [ i ];

            _next [ i ] = NULL;
            assert _prev [ i ] == NULL;

            assert _curs [ i ] == null;
            assert _xobjs[ i ] == null;
            assert _poses[ i ] == NO_POS;

            _xobjs [ i ] = addThis._xobj;
            _poses [ i ] = addThis._pos;

            _naked = insert( _naked, NULL, i, _nextN, _prevN );

            return i;
        }

        private static int insert ( int head, int before, int i, int[] next, int[] prev )
        {
            if (head == NULL)
            {
                assert before == NULL;
                prev[ i ] = i;
                head = i;
            }
            else if (before != NULL)
            {
                prev[ i ] = prev[ before ];
                next[ i ] = before;
                prev[ before ] = i;

                if (head == before)
                    head = i;
            }
            else
            {
                prev[ i ] = prev[ head ];
                assert next[ i ] == NULL;
                next[ prev[ head ] ] = i;
                prev[ head ] = i;
            }

            return head;
        }

        private static int remove ( int head, int i, int[] next, int[] prev )
        {
            if (prev[ i ] == i)
            {
                assert head == i;
                head = NULL;
            }
            else
            {
                if (head == i)
                    head = next[ i ];
                else
                    next[ prev [ i ] ] = next[ i ];

                if (next[ i ] == NULL)
                    prev[ head ] = prev[ i ];
                else
                {
                    prev[ next[ i ] ] = prev[ i ];
                    next[ i ] = NULL;
                }
            }

            prev[ i ] = NULL;
            assert next[ i ] == NULL;

            return head;
        }

        void notifyChange ( )
        {
            for ( int i ; (i = _naked) != NULL ; )
            {
                assert _curs[ i ] == null && _xobjs[ i ] != null && _poses[ i ] != NO_POS;

                _naked = remove( _naked, i, _nextN, _prevN );

                _curs[ i ] = _locale.getCur();
                _curs[ i ].moveTo( _xobjs[ i ], _poses[ i ] );

                _xobjs[ i ] = null;
                _poses[ i ] = NO_POS;
            }
        }

        int next ( int i ) { return _next[ i ]; }
        int prev ( int i ) { return _prev[ i ]; }

        private void makeRoom ( )
        {
            assert _free == NULL;

            int l = _xobjs.length;

            Xobj [] oldXobjs = _xobjs;
            int  [] oldPoses = _poses;
            Cur  [] oldCurs  = _curs;
            int  [] oldNext  = _next;
            int  [] oldPrev  = _prev;
            int  [] oldNextN = _nextN;
            int  [] oldPrevN = _prevN;

            _xobjs = new Xobj [ l * 2 ];
            _poses = new int  [ l * 2 ];
            _curs  = new Cur  [ l * 2 ];
            _next  = new int  [ l * 2 ];
            _prev  = new int  [ l * 2 ];
            _nextN = new int  [ l * 2 ];
            _prevN = new int  [ l * 2 ];

            System.arraycopy( oldXobjs, 0, _xobjs, 0, l );
            System.arraycopy( oldPoses,  0, _poses, 0, l );
            System.arraycopy( oldCurs,  0, _curs,  0, l );
            System.arraycopy( oldNext,  0, _next,  0, l );
            System.arraycopy( oldPrev,  0, _prev,  0, l );
            System.arraycopy( oldNextN, 0, _nextN, 0, l );
            System.arraycopy( oldPrevN, 0, _prevN, 0, l );

            for ( int i = l * 2 - 1 ; i >= l ; i-- )
            {
                _next  [ i ] = i + 1;
                _prev  [ i ] = NULL;
                _nextN [ i ] = NULL;
                _prevN [ i ] = NULL;
                _poses [ i ] = NO_POS;
            }

            _next [ l * 2 - 1 ] = NULL;

            _free = l;
        }

        private static final int _initialSize = 32;

        private Locale _locale;

        private Xobj [] _xobjs;
        private int  [] _poses;
        private Cur  [] _curs;
        private int  [] _next;
        private int  [] _prev;
        private int  [] _nextN;
        private int  [] _prevN;

        private int _free;   // Unused entries
        private int _naked;  // Entries without Curs
    }

    void push ( )
    {
        assert isPositioned();

        int i = _locale._locations.allocate( this );
        _stackTop = _locale._locations.insert( _stackTop, _stackTop, i );
    }

    void pop ( boolean stay )
    {
        if (stay)
            popButStay();
        else
            pop();
    }

    void popButStay ( )
    {
        if (_stackTop != Locations.NULL)
            _stackTop = _locale._locations.remove( _stackTop, _stackTop );
    }

    boolean pop ( )
    {
        if (_stackTop == Locations.NULL)
            return false;

        _locale._locations.moveTo( _stackTop, this );
        _stackTop = _locale._locations.remove( _stackTop, _stackTop );

        return true;
    }

    boolean isAtLastPush ( )
    {
        assert _stackTop != Locations.NULL;

        return _locale._locations.isSamePos( _stackTop, this );
    }

    boolean isAtEndOfLastPush ( )
    {
        assert _stackTop != Locations.NULL;

        return _locale._locations.isAtEndOf( _stackTop, this );
    }

    void addToSelection ( Cur that )
    {
        assert that != null && that.isNormal();
        assert isPositioned() && that.isPositioned();

        int i = _locale._locations.allocate( that );
        _selectionFirst = _locale._locations.insert( _selectionFirst, Locations.NULL, i );

        _selectionCount++;
    }

    void addToSelection ( )
    {
        assert isPositioned();

        int i = _locale._locations.allocate( this );
        _selectionFirst = _locale._locations.insert( _selectionFirst, Locations.NULL, i );

        _selectionCount++;
    }

    private int selectionIndex ( int i )
    {
        assert _selectionN >= -1 && i >= 0 && i < _selectionCount;

        if (_selectionN == -1)
        {
            _selectionN = 0;
            _selectionLoc = _selectionFirst;
        }

        while ( _selectionN < i )
        {
            _selectionLoc = _locale._locations.next( _selectionLoc );
            _selectionN++;
        }

        while ( _selectionN > i )
        {
            _selectionLoc = _locale._locations.prev( _selectionLoc );
            _selectionN--;
        }

        return _selectionLoc;
    }

    void removeSelection ( int i )
    {
        assert i >= 0 && i < _selectionCount;

        int j = selectionIndex( i );

        // Update the nth selection indices to accomodate the deletion

        if (i < _selectionN)
            _selectionN--;
        else if (i == _selectionN)
        {
            _selectionN--;

            if (i == 0)
                _selectionLoc = Locations.NULL;
            else
                _selectionLoc = _locale._locations.prev( _selectionLoc );
        }

        _selectionFirst = _locale._locations.remove( _selectionFirst, j );

        _selectionCount--;
    }

    int selectionCount ( )
    {
        return _selectionCount;
    }

    void moveToSelection ( int i )
    {
        assert i >= 0 && i < _selectionCount;

        _locale._locations.moveTo( selectionIndex( i ), this );
    }

    void clearSelection ( )
    {
        assert _selectionCount >= 0;

        while ( _selectionCount > 0 )
            removeSelection( 0 );
    }

    boolean toParent    ( ) { return toParent( false ); }
    boolean toParentRaw ( ) { return toParent( true  ); }

    Xobj getParent    ( ) { return getParent( false ); }
    Xobj getParentRaw ( ) { return getParent( true  ); }

    boolean hasParent ( )
    {
        assert isPositioned();

        if (_pos == END_POS || (_pos >= 1 && _pos < _xobj.posAfter()))
            return true;

        assert _pos == 0 || _xobj._parent != null;

        return _xobj._parent != null;
    }

    Xobj getParentNoRoot()
    {
        assert isPositioned();

        if (_pos == END_POS || (_pos >= 1 && _pos < _xobj.posAfter()))
            return _xobj;

        assert _pos == 0 || _xobj._parent != null;

        if (_xobj._parent != null)
            return _xobj._parent;

        return null;
    }

    Xobj getParent ( boolean raw )
    {
        assert isPositioned();

        if (_pos == END_POS || (_pos >= 1 && _pos < _xobj.posAfter()))
            return _xobj;

        assert _pos == 0 || _xobj._parent != null;

        if (_xobj._parent != null)
            return _xobj._parent;

        if (raw || _xobj.isRoot())
            return null;

        Cur r = _locale.tempCur();

        r.createRoot();

        Xobj root = r._xobj;

        r.next();
        moveNode( r );
        r.release();

        return root;
    }

    boolean toParent ( boolean raw )
    {
        Xobj parent = getParent( raw );

        if (parent == null)
            return false;

        moveTo( parent );

        return true;
    }

    void toRoot ()
    {
        Xobj xobj = _xobj;
        while (!xobj.isRoot())
        {
            if (xobj._parent==null)
            {
                Cur r = _locale.tempCur();

                r.createRoot();

                Xobj root = r._xobj;

                r.next();
                moveNode( r );
                r.release();

                xobj = root;
                break;
            }
            xobj = xobj._parent;
        }
        moveTo(xobj);
    }

    boolean hasText ( )
    {
        assert isNode();

        return _xobj.hasTextEnsureOccupancy();
    }

    boolean hasAttrs ( )
    {
        assert isNode();

        return _xobj.hasAttrs();
    }

    boolean hasChildren ( )
    {
        assert isNode();

        return _xobj.hasChildren();
    }

    boolean toFirstChild ( )
    {
        assert isNode();

        if (!_xobj.hasChildren())
            return false;

        for ( Xobj x = _xobj._firstChild ; ; x = x._nextSibling )
        {
            if (!x.isAttr())
            {
                moveTo( x );
                return true;
            }
        }
    }

    protected boolean toLastChild ( )
    {
        assert isNode();

        if (!_xobj.hasChildren())
            return false;

        moveTo( _xobj._lastChild );

        return true;
    }

    boolean toNextSibling ( )
    {
        assert isNode();

        if (_xobj.isAttr())
        {
            if (_xobj._nextSibling != null && _xobj._nextSibling.isAttr())
            {
                moveTo( _xobj._nextSibling );
                return true;
            }
        }
        else if (_xobj._nextSibling != null)
        {
            moveTo( _xobj._nextSibling );
            return true;
        }

        return false;
    }

    void setValueAsQName ( QName qname )
    {
        assert isNode();

        String value  = qname.getLocalPart();
        String ns     = qname.getNamespaceURI();

        String prefix =
            prefixForNamespace(
                ns, qname.getPrefix().length() > 0 ? qname.getPrefix() : null, true );

        if (prefix.length() > 0)
            value = prefix + ":" + value;

        setValue( value );
    }

    void setValue ( String value )
    {
        assert isNode();

        moveNodeContents( null, false );

        next();

        insertString( value );

        toParent();
    }

    void removeFollowingAttrs ( )
    {
        assert isAttr();

        QName attrName = getName();

        push();

        if (toNextAttr())
        {
            while ( isAttr() )
            {
                if (getName().equals( attrName ))
                    moveNode( null );
                else if (!toNextAttr())
                    break;
            }
        }

        pop();
    }

    String getAttrValue ( QName name )
    {
        String s = null;

        push();

        if (toAttr( name ))
            s = getValueAsString();

        pop();

        return s;
    }

    void setAttrValueAsQName ( QName name, QName value )
    {
        assert isContainer();

        if (value == null)
        {
            _xobj.removeAttr( name );
        }
        else
        {
            if (toAttr( name ))
            {
                removeFollowingAttrs();
            }
            else
            {
                next();
                createAttr( name );
            }

            setValueAsQName( value );

            toParent();
        }
    }

    boolean removeAttr ( QName name )
    {
        assert isContainer();

        return _xobj.removeAttr( name );
    }

    void setAttrValue ( QName name, String value )
    {
        assert isContainer();

        _xobj.setAttr( name, value );
    }

    boolean toAttr ( QName name )
    {
        assert isNode();

        Xobj a = _xobj.getAttr( name );

        if (a == null)
            return false;

        moveTo( a );

        return true;
    }

    boolean toFirstAttr ( )
    {
        assert isNode();

        Xobj firstAttr = _xobj.firstAttr();

        if (firstAttr == null)
            return false;

        moveTo( firstAttr );

        return true;
    }

    boolean toLastAttr ( )
    {
        assert isNode();

        if (!toFirstAttr())
            return false;

        while ( toNextAttr() )
            ;

        return true;
    }

    boolean toNextAttr ( )
    {
        assert isAttr() || isContainer();

        Xobj nextAttr = _xobj.nextAttr();

        if (nextAttr == null)
            return false;

        moveTo( nextAttr );

        return true;
    }

    boolean toPrevAttr ( )
    {
        if (isAttr())
        {
            if (_xobj._prevSibling == null)
                moveTo( _xobj.ensureParent() );
            else
                moveTo( _xobj._prevSibling );

            return true;
        }

        prev();

        if (!isContainer())
        {
            next();
            return false;
        }

        return toLastAttr();
    }

    boolean skipWithAttrs ( )
    {
        assert isNode();

        if (skip())
            return true;

        if (_xobj.isRoot())
            return false;

        assert _xobj.isAttr();

        toParent();

        next();

        return true;
    }

    boolean skip ( )
    {
        assert isNode();

        if (_xobj.isRoot())
            return false;

        if (_xobj.isAttr())
        {
            if (_xobj._nextSibling == null || !_xobj._nextSibling.isAttr())
                return false;

            moveTo( _xobj._nextSibling, 0 );
        }
        else
            moveTo( getNormal( _xobj, _xobj.posAfter() ), _posTemp );

        return true;
    }

    void toEnd ( )
    {
        assert isNode();

        moveTo( _xobj, END_POS );
    }

    void moveToCharNode ( CharNode node )
    {
        assert node.getDom() != null && node.getDom().locale() == _locale;

        moveToDom( node.getDom() );

        CharNode n;

        _xobj.ensureOccupancy();

        n = _xobj._charNodesValue =
            updateCharNodes( _locale, _xobj, _xobj._charNodesValue, _xobj._cchValue );

        for ( ; n != null ; n = n._next )
        {
            if (node == n)
            {
                moveTo( getNormal( _xobj, n._off + 1 ), _posTemp );
                return;
            }
        }

        n = _xobj._charNodesAfter =
            updateCharNodes( _locale, _xobj, _xobj._charNodesAfter, _xobj._cchAfter );

        for ( ; n != null ; n = n._next )
        {
            if (node == n)
            {
                moveTo( getNormal( _xobj, n._off + _xobj._cchValue + 2 ), _posTemp );
                return;
            }
        }

        assert false;
    }

    boolean prevWithAttrs ( )
    {
        if (prev())
            return true;

        if (!isAttr())
            return false;

        toParent();

        return true;
    }

    boolean prev ( )
    {
        assert isPositioned();

        if (_xobj.isRoot() && _pos == 0)
            return false;

        if (_xobj.isAttr() && _pos == 0 && _xobj._prevSibling == null)
            return false;

        Xobj x = getDenormal();
        int  p = _posTemp;

        assert p > 0 && p != END_POS;

        int pa = x.posAfter();

        if (p > pa)
            p = pa;
        else if (p == pa)
        {
            // Text after an attr is allowed only on the last attr,
            // and that text belongs to the parent container..
            //
            // If we're a thte end of the last attr, then we were just
            // inside the container, and we need to skip the attrs.

            if (x.isAttr() &&
                (x._cchAfter > 0 || x._nextSibling == null || !x._nextSibling.isAttr()))
            {
                x = x.ensureParent();
                p = 0;
            }
            else
                p = END_POS;
        }
        else if (p == pa - 1)
        {
            x.ensureOccupancy();
            p = x._cchValue > 0 ? 1 : 0;
        }
        else if (p > 1)
            p = 1;
        else
        {
            assert p == 1;
            p = 0;
        }

        moveTo( getNormal( x, p ), _posTemp );

        return true;
    }

    boolean next ( boolean withAttrs )
    {
        return withAttrs ? nextWithAttrs() : next();
    }

    boolean nextWithAttrs ( )
    {
        int k = kind();

        if (kindIsContainer( k ))
        {
            if (toFirstAttr())
                return true;
        }
        else if (k == -ATTR)
        {
            if (next())
                return true;

            toParent();

            if (!toParentRaw())
                return false;
        }

        return next();
    }

    boolean next ( )
    {
        assert isNormal();

        Xobj x = _xobj;
        int  p = _pos;

        int pa = x.posAfter();

        if (p >= pa)
            p = _xobj.posMax();
        else if (p == END_POS)
        {
            if (x.isRoot() || (x.isAttr() && (x._nextSibling == null || !x._nextSibling.isAttr())))
                return false;

            p = pa;
        }
        else if (p > 0)
        {
            assert x._firstChild == null || !x._firstChild.isAttr();

            if (x._firstChild != null)
            {
                x = x._firstChild;
                p = 0;
            }
            else
                p = END_POS;
        }
        else
        {
            assert p == 0;

            x.ensureOccupancy();

            p = 1;

            if (x._cchValue == 0)
            {
                if (x._firstChild != null)
                {
                    if (x._firstChild.isAttr())
                    {
                        Xobj a = x._firstChild;

                        while ( a._nextSibling != null && a._nextSibling.isAttr() )
                            a = a._nextSibling;

                        if (a._cchAfter > 0)
                        {
                            x = a;
                            p = a.posAfter();
                        }
                        else if (a._nextSibling != null)
                        {
                            x = a._nextSibling;
                            p = 0;
                        }
                    }
                    else
                    {
                        x = x._firstChild;
                        p = 0;
                    }
                }
            }
        }

        moveTo( getNormal( x, p ), _posTemp );

        return true;
    }

    int prevChars ( int cch )
    {
        assert isPositioned();

        int cchLeft = cchLeft();

        if (cch < 0 || cch > cchLeft)
            cch = cchLeft;

        // Dang, I love this stmt :-)

        if (cch != 0)
            moveTo( getNormal( getDenormal(), _posTemp - cch ), _posTemp );

        return cch;
    }

    int nextChars ( int cch )
    {
        assert isPositioned();

        int cchRight = cchRight();

        if (cchRight == 0)
            return 0;

        if (cch < 0 || cch >= cchRight)
        {
            // Use next to not skip over children
            next();
            return cchRight;
        }

        moveTo( getNormal( _xobj, _pos + cch ), _posTemp );

        return cch;
    }

    void setCharNodes ( CharNode nodes )
    {
        assert nodes == null || _locale == nodes.locale();
        assert isPositioned();

        Xobj x = getDenormal();
        int  p = _posTemp;

        assert !x.isRoot() || (p > 0 && p < x.posAfter());

        if (p >= x.posAfter())
            x._charNodesAfter = nodes;
        else
            x._charNodesValue = nodes;

        for ( ; nodes != null ; nodes = nodes._next )
            nodes.setDom( (Dom) x );

        // No Need to notify text change or alter version, text nodes are
        // not part of the infoset
    }

    CharNode getCharNodes ( )
    {
        assert isPositioned();
        assert !isRoot();

        Xobj x = getDenormal();

        CharNode nodes;

        if (_posTemp >= x.posAfter())
        {
            nodes = x._charNodesAfter =
                updateCharNodes( _locale, x, x._charNodesAfter, x._cchAfter );
        }
        else
        {
            x.ensureOccupancy();

            nodes = x._charNodesValue =
                updateCharNodes( _locale, x, x._charNodesValue, x._cchValue );
        }

        return nodes;
    }

   // private
    static CharNode updateCharNodes ( Locale l, Xobj x, CharNode nodes, int cch )
    {
        assert nodes == null || nodes.locale() == l;

        CharNode node = nodes;
        int i = 0;

        while ( node != null && cch > 0 )
        {
            assert node.getDom() == x;

            if (node._cch > cch)
                node._cch = cch;

            node._off = i;
            i += node._cch;
            cch -= node._cch;

            node = node._next;
        }

        if (cch <= 0)
        {
            for ( ; node != null ; node = node._next )
            {
                assert node.getDom() == x;

                if (node._cch != 0)
                    node._cch = 0;

                node._off = i;
            }
        }
        else
        {
            node = l.createTextNode();
            node.setDom( (Dom) x );
            node._cch = cch;
            node._off = i;
            nodes = CharNode.appendNode( nodes, node );
        }

        return nodes;
    }

    final QName getXsiTypeName ( )
    {
        assert isNode();

        return _xobj.getXsiTypeName();
    }

    final void setXsiType ( QName value )
    {
        assert isContainer();

        setAttrValueAsQName( Locale._xsiType, value );
    }

    final QName valueAsQName ( )
    {
        throw new RuntimeException( "Not implemented" );
    }

    final String namespaceForPrefix ( String prefix, boolean defaultAlwaysMapped )
    {
        return _xobj.namespaceForPrefix( prefix, defaultAlwaysMapped );
    }

    final String prefixForNamespace ( String ns, String suggestion, boolean createIfMissing )
    {
        return
            (isContainer() ? _xobj : getParent()).
                prefixForNamespace( ns, suggestion, createIfMissing );
    }

    // Does the node at this cursor properly contain the position specified by the argument

    boolean contains ( Cur that )
    {
        assert isNode();
        assert that != null && that.isPositioned();

        return _xobj.contains( that );
    }

    void insertString ( String s )
    {
        if (s != null)
            insertChars( s, 0, s.length() );
    }

    void insertChars ( Object src, int off, int cch )
    {
        assert isPositioned() && !isRoot();
        assert CharUtil.isValid( src, off, cch );

        // Check for nothing to insert

        if (cch <= 0)
            return;

        _locale.notifyChange();

        // The only situation where I need to ensure occupancy is when I'm at the end of a node.
        // All other positions will require occupancy.  For example, if I'm at the beginning of a
        // node, then I will either insert in the after text of the previous sibling, or I will
        // insert in the value of the parent.  In the latter case, because the parent has a child,
        // it cannot be vacant.

        if (_pos == END_POS)
            _xobj.ensureOccupancy();

        // Get the denormailized Xobj and pos.  This is the Xobj which will actually receive
        // the new chars.  Note that a denormalized position can never be <= 0.

        Xobj x = getDenormal();
        int  p = _posTemp;

        assert p > 0;

        // This will move "this" cursor to be after the inserted text. No worries, I'll update its
        // position after.  This insertChars takes care of all the appropriate invalidations
        // (passing true as last arg).

        x.insertCharsHelper( p, src, off, cch, true );

        // Reposition the cursor to be just before the newly inserted text.  It's current
        // position could have been shifted, or it may have been just before the end tag, or
        // normalized on another Xobj.

        moveTo( x, p );

        _locale._versionAll++;
    }

    // Move the chars just after this Cur to the "to" Cur.  If no "to" Cur is specified,
    // then remove the chars.

    Object moveChars ( Cur to, int cchMove )
    {
        assert isPositioned();
        assert cchMove <= 0 || cchMove <= cchRight();
        assert to == null || (to.isPositioned() && !to.isRoot());

        if (cchMove < 0)
            cchMove = cchRight();

        // If we're instructed to move 0 characters, then return the null triple.

        if (cchMove == 0)
        {
            _offSrc = 0;
            _cchSrc = 0;

            return null;
        }

        // Here I record the triple of the chars to move.  I will return this.  No need to save
        // cch 'cause cchMove will be that value.

        Object srcMoved = getChars( cchMove );
        int    offMoved = _offSrc;

        // Either I'm moving text from the value or the after text.  If after, then the container
        // must be occupied.  If in the value, because we're just before text, it must be occupied.

        assert isText() && (_pos >= _xobj.posAfter() ? _xobj._parent : _xobj).isOccupied();

        if (to == null)
        {
            // In this case, I'm removing chars vs moving them.  Normally I would like to blow
            // them away entirely, but if there are any references to those chars via a bookmark
            // I need to keep them alive.  I do this by moving these chars to a new root.  Note
            // that because Curs will stay behind, I don't have to check for them.

            for ( Bookmark b = _xobj._bookmarks ; b != null ; b = b._next )
            {
                if (inChars( b, cchMove, false ))
                {
                    Cur c = _locale.tempCur();

                    c.createRoot();
                    c.next();

                    Object chars = moveChars( c, cchMove );

                    c.release();

                    return chars;
                }
            }
        }
        else
        {
            // If the target, "to", is inside or on the edge of the text to be moved, then this
            // is a no-op.  In this case, I still want to return the text "moved".
            //
            // Note how I move "to" and this cur around.  I move "to" to be at the beginning of the
            // chars moved and "this" to be at the end.  If the text were really moving to a
            // different location, then "to" would be at the beginning of the newly moved chars,
            // and "this" would be at the gap left by the newly removed chars.

            if (inChars( to, cchMove, true ))
            {
                // BUGBUG - may want to consider shuffling the interior cursors to the right just
                // like I move "this" to the right...

                to.moveToCur( this );
                nextChars( cchMove );

                _offSrc = offMoved;
                _cchSrc = cchMove;

                return srcMoved;
            }

            // Copy the chars here, I'll remove the originals next

            to.insertChars( srcMoved, offMoved, cchMove );
        }

        // Notice that I can delay the general change notification to this point because any
        // modifications up to this point are made by calling other high level operations which
        // generate this notification themselves.  Also, no need to notify of general change in
        // the "to" locale because the insertion of chars above handles that.

        _locale.notifyChange();

        //
        //if ( _xobj != null )
        {
            if (to == null)
                _xobj.removeCharsHelper( _pos, cchMove, null, NO_POS, false, true );
            else
                _xobj.removeCharsHelper( _pos, cchMove, to._xobj, to._pos, false, true );
        }

        // Need to update the position of this cursor even though it did not move anywhere.  This
        // needs to happen because it may not be properly normalized anymore.  Note that because
        // of the removal of the text, this cur may not be normal any more, thus I call moveTo
        // which does not assume this.

        _locale._versionAll++;

        _offSrc = offMoved;
        _cchSrc = cchMove;

        return srcMoved;
    }

    void moveNode ( Cur to )
    {
        assert isNode() && !isRoot();
        assert to == null || to.isPositioned();
        assert to == null || !contains( to );
        assert to == null || !to.isRoot();

        // TODO - should assert that is an attr is being moved, it is ok there


        // Record the node to move and skip this cur past it.  This moves this cur to be after
        // the move to move/remove -- it's final resting place.  The only piece of information
        // about the source of the move is the node itself.

        Xobj x = _xobj;

        skip();

        // I call another function here to move the node.  I do this because  I don't have to
        // worry about messing with "this" here given that it not should be treated like any other
        // cursor after this point.

        moveNode( x, to );
    }

    // Moves text from one place to another in a low-level way, used as a helper for the higher
    // level functions.  Takes care of moving bookmarks and cursors.  In the high level content
    // manipulation functions, cursors do not follow content, but this helper moves them.  The
    // arguments are denormalized.  The Xobj's must be different from eachother but from the same
    // locale.  The destination must not be not be vacant.

    private static void transferChars ( Xobj xFrom, int pFrom, Xobj xTo, int pTo, int cch )
    {
        assert xFrom != xTo;
        assert xFrom._locale == xTo._locale;
        assert pFrom > 0 && pFrom <  xFrom.posMax();
        assert pTo   > 0 && pTo   <= xTo  .posMax();
        assert cch > 0 && cch <= xFrom.cchRight( pFrom );
        assert pTo >= xTo.posAfter() || xTo.isOccupied();

        // Copy the chars from -> to without performing any invalidations.  This will scoot curs
        // and marks around appropriately.  Note that I get the cars with getCharsHelper which
        // does not check for normalization because the state of the tree at this moment may not
        // exactly be "correct" here.

        xTo.insertCharsHelper(
            pTo, xFrom.getCharsHelper( pFrom, cch ),
            xFrom._locale._offSrc, xFrom._locale._cchSrc, false );

        xFrom.removeCharsHelper( pFrom, cch, xTo, pTo, true, false );
    }

    // Moves the node x to "to", or removes it if to is null.

    static void moveNode ( Xobj x, Cur to )
    {
        assert x != null && !x.isRoot();
        assert to == null || to.isPositioned();
        assert to == null || !x.contains( to );
        assert to == null || !to.isRoot();

        if (to != null)
        {
            // Before I go much further, I want to make sure that if "to" is in the container of
            // a vacant node, I get it occupied.  I do not need to worry about the source being
            // vacant.

            if (to._pos == END_POS)
                to._xobj.ensureOccupancy();

            // See if the destination is on the edge of the node to be moved (a no-op).  It is
            // illegal to call this fcn when to is contained within the node to be moved.  Note
            // that I make sure that to gets oved to the beginning of the node.  The position of
            // to in all operations should leave to just before the content moved/inserted.

            if ((to._pos == 0 && to._xobj == x) || to.isJustAfterEnd( x ))
            {
                // TODO - should shuffle contained curs to the right???

                to.moveTo( x );
                return;
            }
        }

        // Notify the locale(s) about the change I am about to make.

        x._locale.notifyChange();

        x._locale._versionAll++;
        x._locale._versionSansText++;

        if (to != null && to._locale != x._locale)
        {
            to._locale.notifyChange();

            to._locale._versionAll++;
            to._locale._versionSansText++;
        }

        // Node is going away.  Invalidate the parent (the text around the node is merging).
        // Also, this node may be an attribute -- invalidate special attrs ...

        if (x.isAttr())
            x.invalidateSpecialAttr( to == null ? null : to.getParentRaw() );
        else
        {
            if (x._parent != null)
                x._parent.invalidateUser();

            if (to != null && to.hasParent())
                to.getParent().invalidateUser();
        }

        // If there is any text after x, I move it to be before x.  This frees me to extract x
        // and it's contents with out this text coming along for the ride.  Note that if this
        // node is the last attr and there is text after it, transferText will move the text
        // to a potential previous attr.  This is an invalid state for a short period of time.
        // I need to move this text away here so that when I walk the tree next, *all* curs
        // embedded in this node or deeper will be moved off this node.

        if (x._cchAfter > 0)
            transferChars( x, x.posAfter(), x.getDenormal( 0 ), x.posTemp(), x._cchAfter );

        assert x._cchAfter == 0;

        // Walk the node tree, moving curs out, disconnecting users and relocating to a, possibly,
        // new locale.  I embed the cursors in this locale before itersting to just cause the
        // embed to happen once.

        x._locale.embedCurs();

        for ( Xobj y = x ; y != null ; y = y.walk( x, true ) )
        {
            while ( y._embedded != null )
                y._embedded.moveTo( x.getNormal( x.posAfter() ) );

            y.disconnectUser();

            if (to != null)
                y._locale = to._locale;
        }

        // Now, actually remove the node

        x.removeXobj();

        // Now, if there is a destination, insert the node there and shuffle the text in the
        // vicinity of the destination appropriately.

        if (to != null)
        {
            // To know where I should insert/append the node to move, I need to see where "to"
            // would be if there were no text after it.  However, I need to keep "to" where it
            // is when I move the text after it later.

            Xobj here = to._xobj;
            boolean append = to._pos != 0;

            int cchRight = to.cchRight();

            if (cchRight > 0)
            {
                to.push();
                to.next();
                here = to._xobj;
                append = to._pos != 0;
                to.pop();
            }

            if (append)
                here.appendXobj( x );
            else
                here.insertXobj( x );

            // The only text I need to move is that to the right of "to".  Even considering all
            // the cases where an attribute is involed!

            if (cchRight > 0)
                transferChars( to._xobj, to._pos, x, x.posAfter(), cchRight );

            to.moveTo( x );
        }
    }

    void moveNodeContents ( Cur to, boolean moveAttrs )
    {
        assert _pos==0;
        assert to == null || !to.isRoot();

        // By calling this helper, I do not have to deal with this Cur any longer.  Basically,
        // this Cur is out of the picture, it behaves like any other cur at this point.

        moveNodeContents( _xobj, to, moveAttrs );
    }

    static void moveNodeContents ( Xobj x, Cur to, boolean moveAttrs )
    {
        // TODO - should assert that is an attr is being moved, it is ok there

        assert to == null || !to.isRoot();

        // Collect a bit of information about the contents to move first.  Note that the collection
        // of this info must not cause a vacant value to become occupied.

        boolean hasAttrs = x.hasAttrs();
        boolean noSubNodesToMove = !x.hasChildren() && (!moveAttrs || !hasAttrs);

        // Deal with the cases where only text is involved in the move

        if (noSubNodesToMove)
        {
            // If we're vacant and there is no place to move a potential value, then I can avoid
            // acquiring the text from the TypeStoreUser.  Otherwise, there may be text here I
            // need to move somewhere else.

            if (x.isVacant() && to == null)
            {
                x.clearBit( Xobj.VACANT );

                x.invalidateUser();
                x.invalidateSpecialAttr( null );
                x._locale._versionAll++;
            }
            else if (x.hasTextEnsureOccupancy())
            {
                Cur c = x.tempCur();
                c.next();
                c.moveChars( to, -1 );
                c.release();
            }

            return;
        }

        // Here I check to see if "to" is just inside x.  In this case this is a no-op.  Note that
        // the value of x may still be vacant.

        if (to != null)
        {
            // Quick check of the right edge.  If it is there, I need to move "to" to the left edge
            // so that it is positioned at the beginning of the "moved" content.

            if (x == to._xobj && to._pos == END_POS)
            {
                // TODO - shuffle interior curs?

                to.moveTo( x );
                to.next( moveAttrs && hasAttrs );

                return;
            }

            // Here I need to see if to is at the left edge.  I push to's current position and
            // then navigate it to the left edge then compare it to the pushed position...
            // Note: gotta be careful to make sure to and x are not in different locales, curs
            // may not go to a different locale.

            boolean isAtLeftEdge = false;

            if (to._locale == x._locale)
            {
                to.push();
                to.moveTo( x );
                to.next( moveAttrs && hasAttrs );
                isAtLeftEdge = to.isAtLastPush();
                to.pop();
            }

            // TODO - shuffle interior curs?

            if (isAtLeftEdge)
                return;

            // Now, after dealing with the edge condition, I can assert that to is not inside x

            assert !x.contains( to );

            // So, at this point, I've taken case of the no-op cases and the movement of just text.
            // Also, to must be occupied because I took care of the text only and nothing to move
            // cases.

            assert to.getParent().isOccupied();
        }

        // TODO - did I forget to put a changeNotification here?  Look more closely ...

        // Deal with the value text of x which is either on x or the last attribute of x.
        // I need to get it out of the way to properly deal with the walk of the contents.
        // In order to reposition "to" properly later, I need to record how many chars were moved.

        int valueMovedCch = 0;

        if (x.hasTextNoEnsureOccupancy())
        {
            Cur c = x.tempCur();
            c.next();
            c.moveChars( to, -1 );
            c.release();

            if (to != null)
                to.nextChars( valueMovedCch = c._cchSrc );
        }

        // Now, walk all the contents, invalidating special attrs, reportioning cursors,
        // disconnecting users and relocating to a potentially different locale.  Because I moved
        // the value text above, no top level attrs should have any text.

        x._locale.embedCurs();

        Xobj firstToMove = x.walk( x, true );
        boolean sawBookmark = false;

        for ( Xobj y = firstToMove ; y != null ; y = y.walk( x, true ) )
        {
            if (y._parent == x && y.isAttr())
            {
                assert y._cchAfter == 0;

                if (!moveAttrs)
                {
                    firstToMove = y._nextSibling;
                    continue;
                }

                y.invalidateSpecialAttr( to == null ? null : to.getParent() );
            }

            for ( Cur c ; (c = y._embedded) != null ; )
                c.moveTo( x, END_POS );

            y.disconnectUser();

            if (to != null)
                y._locale = to._locale;

            sawBookmark = sawBookmark || y._bookmarks != null;
        }

        Xobj lastToMove = x._lastChild;

        // If there were any bookmarks in the tree to remove, to preserve the content that these
        // bookmarks reference, move the contents to a new root.  Note that I already moved the
        // first piece of text above elsewhere.  Note: this has the effect of keeping all of the
        // contents alive even if there is one bookmark deep into the tree.  I should really
        // disband all the content, except for the pieces which are bookmarked.

        Cur surragateTo = null;

        if (sawBookmark && to == null)
        {
            surragateTo = to = x._locale.tempCur();
            to.createRoot();
            to.next();
        }

        // Perform the rest of the invalidations.  If only attrs are moving, then no user
        // invalidation needed.  If I've move text to "to" already, no need to invalidate
        // again.

        if (!lastToMove.isAttr())
            x.invalidateUser();

        x._locale._versionAll++;
        x._locale._versionSansText++;

        if (to != null && valueMovedCch == 0)
        {
            to.getParent().invalidateUser();
            to._locale._versionAll++;
            to._locale._versionSansText++;
        }

        // Remove the children and, if needed, move them

        x.removeXobjs( firstToMove, lastToMove );

        if (to != null)
        {
            // To know where I should insert/append the contents to move, I need to see where "to"
            // would be if there were no text after it.

            Xobj here = to._xobj;
            boolean append = to._pos != 0;

            int cchRight = to.cchRight();

            if (cchRight > 0)
            {
                to.push();
                to.next();
                here = to._xobj;
                append = to._pos != 0;
                to.pop();
            }

            // Now, I have to shuffle the text around "to" in special ways.  A complication is
            // the insertion of attributes.  First, if I'm inserting attrs here then, logically,
            // there can be no text to the left because attrs can only live after another attr
            // or just inside a container.  So, If attrs are being inserted and there is value
            // text on the target container, I will need to move this value text to be after
            // the lew last attribute.  Note that this value text may already live on a current
            // last attr (before the inserting).  Also, I need to figure this all out before I
            // move the text after "to" because this text may end up being sent to the same place
            // as the containers value text when the last new node being inserted is an attr!
            // Whew!

            if (firstToMove.isAttr())
            {
                Xobj lastNewAttr = firstToMove;

                while ( lastNewAttr._nextSibling != null && lastNewAttr._nextSibling.isAttr() )
                    lastNewAttr = lastNewAttr._nextSibling;

                // Get to's parnet now before I potentially move him with the next transfer

                Xobj y = to.getParent();

                if (cchRight > 0)
                    transferChars( to._xobj, to._pos, lastNewAttr, lastNewAttr.posMax(), cchRight );

                if (y.hasTextNoEnsureOccupancy())
                {
                    int p, cch;

                    if (y._cchValue > 0)
                    {
                        p = 1;
                        cch = y._cchValue;
                    }
                    else
                    {
                        y = y.lastAttr();
                        p = y.posAfter();
                        cch = y._cchAfter;
                    }

                    transferChars( y, p, lastNewAttr, lastNewAttr.posAfter(), cch );
                }
            }
            else if (cchRight > 0)
                transferChars( to._xobj, to._pos, lastToMove, lastToMove.posMax(), cchRight );

            // After mucking with the text, splice the new tree in

            if (append)
                here.appendXobjs( firstToMove, lastToMove );
            else
                here.insertXobjs( firstToMove, lastToMove );

            // Position "to" to be at the beginning of the newly inserted contents

            to.moveTo( firstToMove );
            to.prevChars( valueMovedCch );
        }

        // If I consed up a to, release it here

        if (surragateTo != null)
            surragateTo.release();
    }

    protected final Bookmark setBookmark ( Object key, Object value )
    {
        assert isNormal();
        assert key != null;

        return _xobj.setBookmark( _pos, key, value );
    }

    Object getBookmark ( Object key )
    {
        assert isNormal();
        assert key != null;

        for ( Bookmark b = _xobj._bookmarks ; b != null ; b = b._next )
            if (b._pos == _pos && b._key == key)
                return b._value;

        return null;
    }

    int firstBookmarkInChars ( Object key, int cch )
    {
        assert isNormal();
        assert key != null;
        assert cch > 0;
        assert cch <= cchRight();

        int d = -1;

        if (isText())
        {
            for ( Bookmark b = _xobj._bookmarks ; b != null ; b = b._next )
                if (b._key == key && inChars( b, cch, false ))
                    d = (d == -1 || b._pos - _pos < d) ? b._pos - _pos : d;
        }

        return d;
    }

    int firstBookmarkInCharsLeft ( Object key, int cch )
    {
        assert isNormal();
        assert key != null;
        assert cch > 0;
        assert cch <= cchLeft();

        int d = -1;

        if (cchLeft() > 0)
        {
            Xobj x = getDenormal();
            int  p = _posTemp - cch;

            for ( Bookmark b = x._bookmarks ; b != null ; b = b._next )
                if (b._key == key && x.inChars( p, b._xobj, b._pos, cch, false ))
                    d = (d == -1 || b._pos - p < d) ? b._pos - p : d;
        }

        return d;
    }

    String getCharsAsString ( int cch )
    {
        assert isNormal() && _xobj != null;

        return getCharsAsString( cch, Locale.WS_PRESERVE );
    }

    String getCharsAsString ( int cch, int wsr )
    {
        return _xobj.getCharsAsString( _pos, cch, wsr );
    }

    String getValueAsString ( int wsr )
    {
        assert isNode();

        return _xobj.getValueAsString( wsr );
    }

    String getValueAsString ( )
    {
        assert isNode();
        assert ! hasChildren();

        return _xobj.getValueAsString();
    }

    Object getChars ( int cch )
    {
        assert isPositioned();

        return _xobj.getChars( _pos, cch, this );
    }

    Object getFirstChars ( )
    {
        assert isNode();

        Object src = _xobj.getFirstChars();

        _offSrc = _locale._offSrc;
        _cchSrc = _locale._cchSrc;

        return src;
    }

    void copyNode ( Cur to )
    {
        assert to != null;
        assert isNode();

        Xobj copy = _xobj.copyNode( to._locale );

        // TODO - in the moveNode case, I would not have to walk the tree for cursors ... optimize

        if (to.isPositioned())
            Cur.moveNode( copy, to );
        else
            to.moveTo( copy );
    }

    Cur weakCur ( Object o )
    {
        Cur c = _locale.weakCur( o );
        c.moveToCur( this );
        return c;
    }

    Cur tempCur ( )
    {
        return tempCur( null );
    }

    Cur tempCur ( String id )
    {
        Cur c = _locale.tempCur( id );
        c.moveToCur( this );
        return c;
    }

    private Cur tempCur ( Xobj x, int p )
    {
        assert _locale == x._locale;
        assert x != null || p == NO_POS;

        Cur c = _locale.tempCur();

        if (x != null)
            c.moveTo( getNormal( x, p ), _posTemp );

        return c;
    }

    // Is a cursor (c) in the chars defined by cch chars after where this Cur is positioned.
    // Is inclusive on the left, and inclusive/exclusive on the right depending on the value
    // of includeEnd.

    boolean inChars ( Cur c, int cch, boolean includeEnd )
    {
        assert isPositioned() && isText() && cchRight() >= cch;
        assert c.isNormal();

        return _xobj.inChars( _pos, c._xobj, c._pos, cch, includeEnd );
    }

    boolean inChars ( Bookmark b, int cch, boolean includeEnd )
    {
        assert isPositioned() && isText() && cchRight() >= cch;
        assert b._xobj.isNormal( b._pos );

        return _xobj.inChars( _pos, b._xobj, b._pos, cch, includeEnd );
    }

    // Can't be static because I need to communicate pos in _posTemp :-(
    // I wish I had multiple return vars ...

    private Xobj getNormal ( Xobj x, int p )
    {
        Xobj nx = x.getNormal( p );
        _posTemp = x._locale._posTemp;
        return nx;
    }

    private Xobj getDenormal ( )
    {
        assert isPositioned();

        return getDenormal( _xobj, _pos );
    }

    private Xobj getDenormal ( Xobj x, int p )
    {
        Xobj dx = x.getDenormal( p );
        _posTemp = x._locale._posTemp;
        return dx;
    }

    // May throw IllegalArgumentException if can't change the type

    void setType ( SchemaType type )
    {
        setType( type, true );
    }

    void setType ( SchemaType type, boolean complain )
    {
        assert type != null;
        assert isUserNode();

        TypeStoreUser user = peekUser();

        if (user != null && user.get_schema_type() == type)
            return;

        if (isRoot())
        {
            _xobj.setStableType( type );
            return;
        }

        // Gotta get the parent user to make sure this type is ok here

        TypeStoreUser parentUser = _xobj.ensureParent().getUser();

        // One may only set the type of an attribute to its 'natural' type because
        // attributes cannot take advantage of the xsiType attribute.

        if (isAttr())
        {
            if (complain && parentUser.get_attribute_type( getName() ) != type)
            {
                throw
                    new IllegalArgumentException(
                        "Can't set type of attribute to " + type.toString() );
            }

            return;
        }

        assert isElem();

        // First check to see if this type can be here sans xsi:type.
        // If so, make sure there is no xsi:type

        if (parentUser.get_element_type( getName(), null ) == type)
        {
            removeAttr( Locale._xsiType );
            return;
        }

        // If the desired type has no name, then it cannot be
        // referenced via xsi:type

        QName typeName = type.getName();

        if (typeName == null)
        {
            if (complain)
                throw new IllegalArgumentException( "Can't set type of element, type is un-named" );
            else
                return;
        }

        // See if setting xsiType would result in the target type

        if (parentUser.get_element_type( getName(), typeName ) != type)
        {
            if (complain)
                throw new IllegalArgumentException( "Can't set type of element, invalid type" );
            else
                return;
        }

        setAttrValueAsQName( Locale._xsiType, typeName );
    }

    void setSubstitution ( QName name, SchemaType type )
    {
        setSubstitution( name, type, true );
    }
    
    void setSubstitution ( QName name, SchemaType type, boolean complain )
    {
        assert name != null;
        assert type != null;
        assert isUserNode();

        TypeStoreUser user = peekUser();

        if (user != null && user.get_schema_type() == type && name.equals(getName()))
            return;

        if (isRoot())
        {
            // If this is the root node, we can't set its name, so the whole
            // operation is aborted
            return;
        }

        // Gotta get the parent user to make sure this type is ok here

        TypeStoreUser parentUser = _xobj.ensureParent().getUser();

        // One may only set the type of an attribute to its 'natural' type because
        // attributes cannot take advantage of the xsiType attribute.

        if (isAttr())
        {
            if (complain)
            {
                throw
                    new IllegalArgumentException(
                        "Can't use substitution with attributes");
            }

            return;
        }

        assert isElem();

        // First check to see if this type can be here sans xsi:type.
        // If so, make sure there is no xsi:type

        if (parentUser.get_element_type( name, null ) == type)
        {
            setName( name );
            removeAttr( Locale._xsiType );
            return;
        }

        // If the desired type has no name, then it cannot be
        // referenced via xsi:type

        QName typeName = type.getName();

        if (typeName == null)
        {
            if (complain)
                throw new IllegalArgumentException( "Can't set xsi:type on element, type is un-named" );
            else
                return;
        }

        // See if setting xsiType would result in the target type
        
        if (parentUser.get_element_type( name, typeName ) != type)
        {
            if (complain)
                throw new IllegalArgumentException( "Can't set xsi:type on element, invalid type" );
            else
                return;
        }

        setName( name );
        setAttrValueAsQName( Locale._xsiType, typeName );
    }

    TypeStoreUser peekUser ( )
    {
        assert isUserNode();

        return _xobj._user;
    }

    XmlObject getObject ( )
    {
        return isUserNode() ? (XmlObject) getUser() : null;
    }

    TypeStoreUser getUser ( )
    {
        assert isUserNode();

        return _xobj.getUser();
    }

    Dom getDom ( )
    {
        assert isNormal();
        assert isPositioned();

        if (isText())
        {
            int cch = cchLeft();

            for ( CharNode cn = getCharNodes() ; ; cn = cn._next )
                if ((cch -= cn._cch) < 0)
                    return cn;
        }

        return _xobj.getDom();
    }

    static void release ( Cur c )
    {
        if (c != null)
            c.release();
    }

    void release ( )
    {
        if (_tempFrame >= 0)
        {
            if (_nextTemp != null)
                _nextTemp._prevTemp = _prevTemp;

            if (_prevTemp == null)
                _locale._tempFrames[ _tempFrame ] = _nextTemp;
            else
                _prevTemp._nextTemp = _nextTemp;

            _prevTemp = _nextTemp = null;
            _tempFrame = -1;
        }

        if (_state != POOLED && _state != DISPOSED)
        {
            // Clean up any state

            while ( _stackTop != -1 )
                popButStay();

            clearSelection();

            _id = null;

            // Unposition

            moveToCur( null );

            assert isNormal();

            assert _xobj == null;
            assert _pos  == NO_POS;

            // Release weak reference and attacked value

            if (_ref != null)
            {
                _ref.clear();
                _ref._cur = null;
            }

            _ref = null;

            // Unregister and either diapose of cursor or add it back to pool

            assert _state == REGISTERED;
            _locale._registered = listRemove( _locale._registered );

            if (_locale._curPoolCount < 16)
            {
                _locale._curPool = listInsert( _locale._curPool );
                _state = POOLED;
                _locale._curPoolCount++;
            }
            else
            {
                _locale = null;
                _state = DISPOSED;
            }
        }
    }

    boolean isOnList ( Cur head )
    {
        for ( ; head != null ; head = head._next )
            if (head == this)
                return true;

        return false;
    }

    Cur listInsert ( Cur head )
    {
        assert _next == null && _prev == null;

        if (head == null)
            head = _prev = this;
        else
        {
            _prev = head._prev;
            head._prev = head._prev._next = this;
        }

        return head;
    }

    Cur listRemove ( Cur head )
    {
        assert _prev != null && isOnList( head );

        if (_prev == this)
            head = null;
        else
        {
            if (head == this)
                head = _next;
            else
                _prev._next = _next;

            if (_next == null)
                head._prev = _prev;
            else
            {
                _next._prev = _prev;
                _next = null;
            }
        }

        _prev = null;
        assert _next == null;

        return head;
    }

//    boolean isNormal ( Cur that )
//    {
//        return isNormal() && (that == null || (_locale == that._locale && that.isNormal()));
//    }

    boolean isNormal ( )
    {
        if (_state == POOLED || _state == DISPOSED)
            return false;

        if (_xobj == null)
            return _pos == NO_POS;

        if (!_xobj.isNormal( _pos ))
            return false;

        if (_state == EMBEDDED)
            return isOnList( _xobj._embedded );

        assert _state == REGISTERED;

        return isOnList( _locale._registered );
    }

    static final String LOAD_USE_LOCALE_CHAR_UTIL = "LOAD_USE_LOCALE_CHAR_UTIL";

    static final class CurLoadContext extends LoadContext
    {
        CurLoadContext ( Locale l, XmlOptions options )
        {
            options = XmlOptions.maskNull( options );

            _locale = l;

            _charUtil =
                options.hasOption( LOAD_USE_LOCALE_CHAR_UTIL )
                    ? _locale.getCharUtil()
                    : CharUtil.getThreadLocalCharUtil();

            _frontier = createDomDocumentRootXobj( _locale );
            _after = false;

            _lastXobj = _frontier;
            _lastPos  = 0;

            if (options.hasOption( XmlOptions.LOAD_REPLACE_DOCUMENT_ELEMENT ))
            {
                _replaceDocElem = (QName) options.get( XmlOptions.LOAD_REPLACE_DOCUMENT_ELEMENT );
                _discardDocElem = true;
            }

            _stripWhitespace = options.hasOption( XmlOptions.LOAD_STRIP_WHITESPACE );
            _stripComments   = options.hasOption( XmlOptions.LOAD_STRIP_COMMENTS   );
            _stripProcinsts  = options.hasOption( XmlOptions.LOAD_STRIP_PROCINSTS  );

            _substituteNamespaces = (Map) options.get( XmlOptions.LOAD_SUBSTITUTE_NAMESPACES );
            _additionalNamespaces = (Map) options.get( XmlOptions.LOAD_ADDITIONAL_NAMESPACES );

            _locale._versionAll++;
            _locale._versionSansText++;
        }

        //
        // Really primitive load context operations
        //

        private void start ( Xobj xo )
        {
            assert _frontier != null;
            assert !_after || _frontier._parent != null;

            flushText();

            if (_after)
            {
                _frontier = _frontier._parent;
                _after = false;
            }

            _frontier.appendXobj( xo );
            _frontier = xo;

            _lastXobj = xo;
            _lastPos  = 0;
        }

        private void end ( )
        {
            assert _frontier != null;
            assert !_after || _frontier._parent != null;

            flushText();

            if (_after)
                _frontier = _frontier._parent;
            else
                _after = true;

            _lastXobj = _frontier;
            _lastPos  = END_POS;
        }

        private void text ( Object src, int off, int cch )
        {
            if (cch <= 0)
                return;

            _lastXobj = _frontier;
            _lastPos  = _frontier._cchValue + 1;

            if (_after)
            {
                _lastPos += _frontier._cchAfter + 1;

                _frontier._srcAfter =
                    _charUtil.saveChars(
                        src, off, cch,
                        _frontier._srcAfter, _frontier._offAfter, _frontier._cchAfter );

                _frontier._offAfter = _charUtil._offSrc;
                _frontier._cchAfter = _charUtil._cchSrc;

            }
            else
            {
                _frontier._srcValue =
                    _charUtil.saveChars(
                        src, off, cch,
                        _frontier._srcValue, _frontier._offValue, _frontier._cchValue );

                _frontier._offValue = _charUtil._offSrc;
                _frontier._cchValue = _charUtil._cchSrc;
            }
        }

        private void flushText ( )
        {
            if (_stripWhitespace)
            {
                if (_after)
                {
                    _frontier._srcAfter =
                        _charUtil.stripRight(
                            _frontier._srcAfter, _frontier._offAfter, _frontier._cchAfter );

                    _frontier._offAfter = _charUtil._offSrc;
                    _frontier._cchAfter = _charUtil._cchSrc;
                }
                else
                {
                    _frontier._srcValue =
                        _charUtil.stripRight(
                            _frontier._srcValue, _frontier._offValue, _frontier._cchValue );

                    _frontier._offValue = _charUtil._offSrc;
                    _frontier._cchValue = _charUtil._cchSrc;
                }
            }
        }

        private Xobj parent ( )
        {
            return _after ? _frontier._parent : _frontier;
        }

        private QName checkName ( QName name, boolean local )
        {
            if (_substituteNamespaces != null && (!local || name.getNamespaceURI().length() > 0))
            {
                String substituteUri = (String) _substituteNamespaces.get( name.getNamespaceURI() );

                if (substituteUri != null)
                    name = _locale.makeQName( substituteUri, name.getLocalPart(), name.getPrefix());
            }

            return name;
        }

        //
        //
        //

        protected void startDTD (String name, String publicId, String systemId )
        {
            _doctypeName = name;
            _doctypePublicId = publicId;
            _doctypeSystemId = systemId;
        }
        
        protected void endDTD ( )
        {
        }
        
        protected void startElement ( QName name )
        {
            start( createElementXobj( _locale, checkName( name, false ), parent()._name ) );
            _stripLeft = true;
        }

        protected void endElement ( )
        {
            assert parent().isElem();

            end();
            _stripLeft = true;
        }

        protected void xmlns ( String prefix, String uri )
        {
            assert parent().isContainer();
            // BUGBUG - should assert there that there is no text before this attr

            // Namespace attrs are different than regular attrs -- I don't change their name,
            // I change their value!

            if (_substituteNamespaces != null)
            {
                String substituteUri = (String) _substituteNamespaces.get( uri );

                if (substituteUri != null)
                    uri = substituteUri;
            }

            Xobj x = new Xobj.AttrXobj( _locale, _locale.createXmlns( prefix ) );

            start( x );
            text( uri, 0, uri.length() );
            end();

            _lastXobj = x;
            _lastPos  = 0;
        }

        protected void attr ( QName name, String value )
         {
            assert parent().isContainer();
            // BUGBUG - should assert there that there is no text before this attr

            QName parentName = _after?
                _lastXobj._parent.getQName(): _lastXobj.getQName();
            boolean isId = isAttrOfTypeId(name, parentName);

            Xobj x = isId ?
                new Xobj.AttrIdXobj(_locale, checkName(name, true)) :
                new Xobj.AttrXobj(_locale, checkName(name, true));
            start(x);
            text(value, 0, value.length());
            end();
            if (isId)
            {
                Cur c1 = x.tempCur();
                c1.toRoot();
                Xobj doc = c1._xobj;
                c1.release();
                if (doc instanceof Xobj.DocumentXobj)
                    ((Xobj.DocumentXobj) doc).addIdElement(value,
                        x._parent.getDom());
            }
            _lastXobj = x;
            _lastPos = 0;
        }
        protected void attr ( String local, String uri, String prefix, String value )
        {
            attr( _locale.makeQName( uri, local, prefix ), value );
        }

        protected void procInst ( String target, String value )
        {
            if (!_stripProcinsts)
            {
                Xobj x = new Xobj.ProcInstXobj( _locale, target );

                start( x );
                text( value, 0, value.length() );
                end();

                _lastXobj = x;
                _lastPos  = 0;
            }
            _stripLeft = true;
        }

        protected void comment ( String comment )
        {
            if (!_stripComments)
                comment( comment, 0, comment.length() );
            _stripLeft = true;
        }

        protected void comment ( char[] chars, int off, int cch )
        {
            if (!_stripComments)
            {
                comment(
                    _charUtil.saveChars( chars, off, cch ),
                    _charUtil._offSrc, _charUtil._cchSrc );
            }
            _stripLeft = true;
        }

        private void comment ( Object src, int off, int cch )
        {
            Xobj x = new Xobj.CommentXobj( _locale );

            start( x );
            text( src, off, cch );
            end();

            _lastXobj = x;
            _lastPos  = 0;
        }

        private boolean _stripLeft = true;

        private void stripText ( Object src, int off, int cch )
        {
            if (_stripWhitespace)
            {
                // this is to avoid bug in cases like Procter & Gamble
                if (_stripLeft)
                {
                    src = _charUtil.stripLeft( src, off, cch );
                    _stripLeft = false;
                    off = _charUtil._offSrc;
                    cch = _charUtil._cchSrc;
                }
            }

            text( src, off, cch );
        }

        protected void text ( String s )
        {
            if (s == null)
                return;

            stripText( s, 0, s.length() );
        }

        protected void text ( char[] src, int off, int cch )
        {
            stripText( src, off, cch );
        }

        protected void bookmark ( XmlBookmark bm )
        {
            _lastXobj.setBookmark( _lastPos, bm.getKey(), bm );
        }

        protected void bookmarkLastNonAttr ( XmlBookmark bm )
        {
            if (_lastPos > 0 || !_lastXobj.isAttr())
                _lastXobj.setBookmark( _lastPos, bm.getKey(), bm );
            else
            {
                assert _lastXobj._parent != null;

                _lastXobj._parent.setBookmark( 0, bm.getKey(), bm );
            }
        }

        protected void bookmarkLastAttr ( QName attrName, XmlBookmark bm )
        {
            if (_lastPos == 0 && _lastXobj.isAttr())
            {
                assert _lastXobj._parent != null;

                Xobj a = _lastXobj._parent.getAttr( attrName );

                if (a != null)
                    a.setBookmark( 0, bm.getKey(), bm );
            }
        }

        protected void lineNumber ( int line, int column, int offset )
        {
            _lastXobj.setBookmark(
                _lastPos,
                XmlLineNumber.class,
                new XmlLineNumber( line, column, offset ) );
        }

        protected void abort ( )
        {
            _stripLeft = true;
            while ( !parent().isRoot() )
                end();

            finish().release();
        }

        protected Cur finish ( )
        {
            flushText();

            if (_after)
                _frontier = _frontier._parent;

            assert _frontier != null && _frontier._parent == null && _frontier.isRoot();

            Cur c = _frontier.tempCur();

            if (!Locale.toFirstChildElement( c ))
                return c;

            // See if the document element is a fragment

            boolean isFrag = Locale.isFragmentQName( c.getName() );

            if (_discardDocElem || isFrag)
            {
                if (_replaceDocElem != null)
                    c.setName( _replaceDocElem );
                else
                {
                    // Remove the content around the element to remove so that that content
                    // does not appear to have been the contents of the removed element.

                    while ( c.toParent() )
                        ;

                    c.next();

                    while ( !c.isElem() )
                        if (c.isText()) c.moveChars( null, -1 ); else c.moveNode( null );

                    assert c.isElem();
                    c.skip();

                    while ( !c.isFinish() )
                        if (c.isText()) c.moveChars( null, -1 ); else c.moveNode( null );

                    c.toParent();

                    c.next();

                    assert c.isElem();

                    Cur c2 = c.tempCur();

                    c.moveNodeContents( c, true );

                    c.moveToCur( c2 );

                    c2.release();

                    c.moveNode( null );
                }

                // Remove the fragment namespace decl

                if (isFrag)
                {
                    c.moveTo( _frontier );

                    if (c.toFirstAttr())
                    {
                        for ( ; ; )
                        {
                            if (c.isXmlns() && c.getXmlnsUri().equals( Locale._openFragUri ))
                            {
                                c.moveNode( null );

                                if (!c.isAttr())
                                    break;
                            }
                            else if (!c.toNextAttr())
                                break;
                        }
                    }

                    c.moveTo(_frontier);
                    _frontier = createDomDocumentRootXobj( _locale, true );

                    Cur c2 = _frontier.tempCur();
                    c2.next();
                    c.moveNodeContents(c2, true);
                    c.moveTo(_frontier);
                    c2.release();
                }
            }


            if (_additionalNamespaces != null)
            {
                c.moveTo( _frontier );
                Locale.toFirstChildElement( c );
                Locale.applyNamespaces( c, _additionalNamespaces );
            }

            if (_doctypeName != null && (_doctypePublicId != null || _doctypeSystemId != null))
            {
                XmlDocumentProperties props = Locale.getDocProps(c, true);
                props.setDoctypeName(_doctypeName);
                if (_doctypePublicId != null)
                    props.setDoctypePublicId(_doctypePublicId);
                if (_doctypeSystemId != null)
                    props.setDoctypeSystemId(_doctypeSystemId);
            }
            
            c.moveTo( _frontier );

            assert c.isRoot();

            return c;
        }

        public void dump ( )
        {
            _frontier.dump();
        }

        private Locale   _locale;
        private CharUtil _charUtil;

        private Xobj     _frontier;
        private boolean  _after;

        private Xobj     _lastXobj;
        private int      _lastPos;

        private boolean  _discardDocElem;
        private QName    _replaceDocElem;
        private boolean  _stripWhitespace;
        private boolean  _stripComments;
        private boolean  _stripProcinsts;
        private Map      _substituteNamespaces;
        private Map      _additionalNamespaces;
        
        private String   _doctypeName;
        private String   _doctypePublicId;
        private String   _doctypeSystemId;
    }

    //
    //
    //

    static String kindName ( int kind )
    {
        switch ( kind )
        {
            case ROOT     : return "ROOT";
            case ELEM     : return "ELEM";
            case ATTR     : return "ATTR";
            case COMMENT  : return "COMMENT";
            case PROCINST : return "PROCINST";
            case TEXT     : return "TEXT";
            default       : return "<< Unknown Kind (" + kind + ") >>";
        }
    }

    static void dump ( PrintStream o, Dom d, Object ref )
    {
    }

    static void dump ( PrintStream o, Dom d )
    {
        d.dump( o );
    }

    static void dump ( Dom d )
    {
        dump( System.out, d );
    }

    static void dump ( Node n )
    {
        dump( System.out, n );
    }

    static void dump ( PrintStream o, Node n )
    {
        dump( o, (Dom) n );
    }

    void dump ( )
    {
        dump( System.out, _xobj, this );
    }

    void dump ( PrintStream o )
    {
        if (_xobj == null)
        {
            o.println( "Unpositioned xptr" );
            return;
        }

        dump( o, _xobj, this );
    }

    public static void dump ( PrintStream o, Xobj xo, Object ref )
    {
        if (ref == null)
            ref = xo;

        while ( xo._parent != null )
            xo = xo._parent;

        dumpXobj( o, xo, 0, ref );

        o.println();
    }

    private static void dumpCur ( PrintStream o, String prefix, Cur c, Object ref )
    {
        o.print( " " );

        if (ref == c)
            o.print( "*:" );

        o.print( prefix + (c._id == null ? "" : c._id) + "[" + c._pos + "]" );
    }

    private static void dumpCurs ( PrintStream o, Xobj xo, Object ref )
    {
        for ( Cur c = xo._embedded ; c != null ; c = c._next )
            dumpCur( o, "E:", c, ref );

        for ( Cur c = xo._locale._registered ; c != null ; c = c._next )
        {
            if (c._xobj == xo)
                dumpCur( o, "R:", c, ref );
        }
    }

    private static void dumpBookmarks ( PrintStream o, Xobj xo, Object ref )
    {
        for ( Bookmark b = xo._bookmarks ; b != null ; b = b._next )
        {
            o.print( " " );

            if (ref == b)
                o.print( "*:" );

            if (b._value instanceof XmlLineNumber)
            {
                XmlLineNumber l = (XmlLineNumber) b._value;
                o.print( "" + "[" + b._pos + "]" );
            }
            else
                o.print( "" + "[" + b._pos + "]" );
        }
    }

    private static void dumpCharNodes ( PrintStream o, CharNode nodes, Object ref )
    {
        for ( CharNode n = nodes ; n != null ; n = n._next )
        {
            o.print( " " );

            if (n == ref)
                o.print( "*" );

            o.print( (n instanceof TextNode ? "TEXT" : "CDATA") + "[" + n._cch + "]" );
        }
    }

    private static void dumpChars ( PrintStream o, Object src, int off, int cch )
    {
//        CharUtil.dumpChars( o, src, off, cch );

        o.print( "\"" );

        String s = CharUtil.getString( src, off, cch );

        for ( int i = 0 ; i < s.length(); )
        {
            if (i == 36)
            {
                o.print( "..." );
                break;
            }

            int codePoint = s.codePointAt( i );
            char[] chars = Character.toChars(codePoint);
            
            if ( chars.length == 1 ) {
                char ch = chars[0];
                if (ch >= 32 && ch < 127)
                    o.print( ch );
                else if (ch == '\n')
                    o.print( "\\n" );
                else if (ch == '\r')
                    o.print( "\\r" );
                else if (ch == '\t')
                    o.print( "\\t" );
                else if (ch == '\"')
                    o.print( "\\\"" );
                else
                    o.print( "<#" + ((int) ch) + ">" );
            } else {
                o.print( "<#" + codePoint + ">" );
            }
            
            i += Character.charCount(codePoint);
        }

        o.print( "\"" );
    }

    private static void dumpXobj ( PrintStream o, Xobj xo, int level, Object ref )
    {
        if (xo == null)
            return;

        if (xo == ref)
            o.print( "* " );
        else
            o.print( "  " );

        for ( int i = 0 ; i < level ; i++ )
            o.print( "  " );

        o.print( kindName( xo.kind() ) );

        if (xo._name != null)
        {
            o.print( " " );

            if (xo._name.getPrefix().length() > 0)
                o.print( xo._name.getPrefix() + ":" );

            o.print( xo._name.getLocalPart() );

            if (xo._name.getNamespaceURI().length() > 0)
                o.print( "@" + xo._name.getNamespaceURI() );
        }

        if (xo._srcValue != null || xo._charNodesValue != null)
        {
            o.print( " Value( " );
            dumpChars( o, xo._srcValue, xo._offValue, xo._cchValue );
            dumpCharNodes( o, xo._charNodesValue, ref );
            o.print( " )" );
        }

        if (xo._user != null)
            o.print( " (USER)" );

        if (xo.isVacant())
            o.print( " (VACANT)" );

        if (xo._srcAfter != null || xo._charNodesAfter != null)
        {
            o.print( " After( " );
            dumpChars( o, xo._srcAfter, xo._offAfter, xo._cchAfter );
            dumpCharNodes( o, xo._charNodesAfter, ref );
            o.print( " )" );
        }

        dumpCurs( o, xo, ref );
        dumpBookmarks( o, xo, ref );

        String className = xo.getClass().getName();

        int i = className.lastIndexOf( '.' );

        if (i > 0)
        {
            className = className.substring( i + 1 );

            i = className.lastIndexOf( '$' );

            if (i > 0)
                className = className.substring( i + 1 );
        }

        o.print( " (" );
        o.print( className );
        o.print( ")" );

        o.println();

        for ( xo = xo._firstChild ; xo != null ; xo = xo._nextSibling )
            dumpXobj( o, xo, level + 1, ref );
    }

    void setId ( String id )
    {
        _id = id;
    }

    //
    //
    //

    Locale _locale;

    Xobj _xobj;
    int _pos;

    int _state;

    String _id;

    Cur _nextTemp;
    Cur _prevTemp;
    int _tempFrame;

    Cur _next;
    Cur _prev;

    Locale.Ref _ref;

    int _stackTop;

    int _selectionFirst;
    int _selectionN;
    int _selectionLoc;
    int _selectionCount;

    private int _posTemp;

    int _offSrc;
    int _cchSrc;
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy