# XDocumentImpl.java

    /*
    ** Module   : XDocumentImpl
    ** Abstract : DOM XML Document resource type implementation.
    **
    ** Copyright (c) 2013-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
    ** 001 EVL 20130204 Created initial version. Major rework to use w3c backend.
    ** 002 OM  20130304 Refactored isValid and isUnknown of WrappedResource to valid and unknown.
    ** 003 CA  20130308 Refactored as XmlFactory APIs now receive a handle instance instead of
    **                  returning a new one.
    ** 004 EVK 20130531 Adding support PUBLIC-ID, SYSTEM-ID, ENCODING,
    **                  SCHEMA-LOCATION, SCHEMA-PATH attributes and
    **                  INITIALIZE-DOCUMENT-TYPE,IMPORT-NODE methods.
    **                  Implement valid(),unknown(),getNode() methods.
    **                  Add implementation for CREATE-NODE,GET-DOCUMENT-ELEMENT,
    ** 005 CA  20130930 Changes related to determining resource type from LegacyResource annotation.
    ** 006 SVL 20131203 Changes caused by renaming of Nameable.get/setName to Nameable.name.
    ** 007 HC  20131215 Implemented UNIQUE-ID attribute.
    ** 008 EVK 20140113 Removed method valid(moved to XCommonImpl class) and implemented
    **                  resourceDelete method to properly remove the object.
    ** 009 ECF 20140312 Fixed save for memptr mode: use direct assign instead of setBytes.
    ** 010 MAG 20140625 Adding importNode method with Java boolean parameter.
    ** 011 GES 20150209 NPE protection logic added to resource delete.  longchar support added to
    **                  load().  load() is pretty badly broken and needs work, see TODOs there.
    ** 012 OM  20160331 Moved SAVE and LOAD file operation on client side. Improved error handling for
    **                  these operations.
    ** 012 CA  20160428 When parsing a byte stream from a memptr, the content is read only until '\0'
    **                  is encountered - everything else after the first '\0' will be discarded;
    **                  otherwise, SAX parser will throw 'Content is not allowed in trailing section.'
    **                  exception.
    **                  Added a c'tor to create a X-DOCUMENT resource from an existing Document.
    **                  Added support for X-DOCUMENT:SAVE("LONGCHAR", lcvar) mode.
    ** 013 HC  20160502 Javadoc fix.
    ** 014 ECF 20171101 Replaced internal com.sun imports with org.apache equivalents.
    ** 015 CA  20180520 Added all-string initializeDocumentType version.
    ** 016 OM  20201120 Renamed NAMESPACE-PREFIX setter to fit buffer attributes naming convention.
    **     OM  20210309 Removed deprecation warnings.
    **     OM  20210312 Fixed error handling when LOADing from a LONGCHAR stream.
    **     ME  20210326 Remove last trailing null char, if found, when loading data from memptr.
    **     CA  20210709 XML parse must use a pristine byte stream and use the reader to decode it (using the
    **                  encoding as specified at the XML prolog).
    **     CA  20220127 Empty CDATA sections must be emitted for legacy DOM serialization - added a new
    **                  XmlHelper.writeLegacyCompatible which uses W3C DOM to serialize the XML.
    **     CA  20220211 Fixed setEncoding - find the Charset alias which is IANA-compatible (a side-effect moving
    **                  to xerces implementation in CA/20220127, in XmlHelper.writeLegacyCompatible).
    **     TJD 20220331 Exchanged DocumentImpl.setEncoding to DocumentImpl.setEncoding
    **     CA  20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
    ** 017 CA  20230720 'isSuppressNamespaceProcessing' needs to be negated when passed to 'XmlHelper.parse', to
    **                  properly report LOCAL-NAME attribute without the namespace prefix.
    ** 018 CA  20240325 Fixed support for NONAMESPACE-SCHEMA-LOCATION, ADD-SCHEMA-LOCATION, SCHEMA-PATH for XML
    **                  document load.
    ** 019 ICP 20250129 Used logical constants to leverage cached instances.
    */

    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.xml;

    import com.goldencode.p2j.util.*;
    import com.goldencode.p2j.util.UniqueIdGenerator.IdKind;
    import com.goldencode.util.*;
    import org.apache.xerces.dom.*;
    import org.apache.xerces.impl.*;
    import org.apache.xerces.util.*;
    import org.w3c.dom.*;
    import org.w3c.dom.traversal.*;
    import org.xml.sax.*;

    import javax.xml.*;
    import javax.xml.parsers.*;
    import javax.xml.transform.*;
    import javax.xml.transform.stream.StreamSource;
    import javax.xml.validation.*;

    import java.io.*;
    import java.nio.charset.*;
    import java.util.*;

    /**
     * Implementation of the X-Document specific features of the Progress DOM XML
     * support objects.
     */
    public class XDocumentImpl
    extends XEntityImpl
    implements XDocument
    {
       /** The real document to assign with handle.  */
       private Document xdoc;

       /** The flag to maintain suppress namespace processing. */
       private boolean isSuppressNamespaceProcessing = false;

       /** Schema paths locations variable */
       private String  schemaPaths = null;

       /** The value of the NONAMESPACE-SCHEMA-LOCATION attribute. */
       private String noNamespaceSchemaLocation = null;

       /** The added schema locations, per namespace URI. */
       private Map<String, String> schemaLocations = new HashMap<>();

       /**
        * Associate this X-DOCUMENT resource with an existing document
        *
        * @param    xdoc
        *           The real document to assign with handle.
        */
       public XDocumentImpl(Document xdoc)
       {
          super(IdKind.X_DOCUMENT);

          this.xdoc = xdoc;
       }

       /**
        * Default constructor.
        */
       public XDocumentImpl()
       {
          super(IdKind.X_DOCUMENT);

          try
          {
             xdoc = XmlHelper.newDocument();
          }
          catch (ParserConfigurationException e)
          {
             // impossible error
             xdoc = null;
          }
       }

       /**
        * Getting the Element backend object for this XEntity.
        *
        * @return The Element associated with object.
        */
       @Override
       public Node getNode()
       {
          return xdoc;
       }

       /**
        * Delete the resource.
        *
        * @return   <code>true</code> if the resource was deleted.
        */
       @Override
       protected boolean resourceDelete()
       {
          if (xdoc == null)
          {
             isValid = false;
             return true;
          }
          DocumentTraversal traversal = (DocumentTraversal) xdoc;
          if (xdoc.getDocumentElement() != null)
          {
             NodeIterator iterator = traversal.createNodeIterator(xdoc.getDocumentElement(),
                                                                  NodeFilter.SHOW_ELEMENT,
                                                                  null,
                                                                  true);
             for (Node node = iterator.nextNode(); node != null; node = iterator.nextNode())
             {
                node.setUserData(XEntityImpl.IS_INVALID_NODE, true, null);
             }
          }

          isValid = false;
          xdoc.setUserData(XEntityImpl.IS_INVALID_NODE, true, null);
          xdoc = null;

          return true;
       }

       /**
        * Setter for NAMESPACE-PREFIX attribute.
        *
        * @param    prefix
        *           The new value of the NAMESPACE-PREFIX attribute.
        */
       @Override
       public void namespacePrefix(character prefix)
       {
          readOnlyError("NAMESPACE-PREFIX", handle.UNKNOWN_ARGUMENT);
       }

       /**
        * Get the <code>name</code> attribute of handle.
        *
        * @return See above.
        */
       @Override
       public character name()
       {
          if (!isInitialized())
          {
             invalidNodeError("NAME");
             return new character();
          }

          return new character("");
       }

       /**
        * Get the <code>name</code> attribute of handle as a string.
        *
        * @return See above.
        */
       @Override
       public String _name()
       {
          return name().toJavaType();
       }

       /**
        * Creating the new node within the current Document object.
        *
        * @param    hChild
        *           The valid handle class instance of the child to be created and
        *           returned on method call to use further.
        * @param    name
        *           The name of the node to be created.
        * @param    type
        *           The type of the node to be created.
        *
        * @return   The <code>true</code> in case of success
        *           <code>false</code> otherwise.
        */
       @Override
       public logical createNode(handle hChild, String name, String type)
       {
          return createNode(hChild, new character(name), new character(type));
       }

       /**
        * Creating the new node within the current Document object.
        *
        * @param    hChild
        *           The valid handle class instance of the child to be created and
        *           returned on method call to use further.
        * @param    name
        *           The name of the node to be created.
        * @param    type
        *           The type of the node to be created.
        *
        * @return   The <code>true</code> in case of success
        *           <code>false</code> otherwise.
        */
       @Override
       public logical createNode(handle hChild, character name, String type)
       {
          return createNode(hChild, name, new character(type));
       }

       /**
        * Creating the new node within the current Document object.
        *
        * @param    hChild
        *           The valid handle class instance of the child to be created and
        *           returned on method call to use further.
        * @param    name
        *           The name of the node to be created.
        * @param    type
        *           The type of the node to be created.
        *
        * @return   The <code>true</code> in case of success
        *           <code>false</code> otherwise.
        */
       @Override
       public logical createNode(handle hChild, String name, character type)
       {
          return createNode(hChild, new character(name), type);
       }

       /**
        * Creating the new node within the current Document object.
        *
        * @param    hChild
        *           The valid handle class instance of the child to be created and
        *           returned on method call to use further.
        * @param    name
        *           The name of the node to be created.
        * @param    type
        *           The type of the node to be created.
        *
        * @return   The <code>true</code> in case of success <code>false</code> otherwise.
        */
       @Override
       public logical createNode(handle hChild, character name, character type)
       {
          if (!isInitialized())
          {
             invalidNodeError("CREATE-NODE");
             return new logical(false);
          }

          if (!isXNodeHandle(hChild))
          {
             invalidArgumentError("CREATE-NODE");
             return new logical(false);
          }
          try
          {
             XmlFactory.createXNodeReference(hChild, new handle(this), name, type);
          }
          catch (DOMException e)
          {
             if (e.code == DOMException.INVALID_CHARACTER_ERR)
             {
                commonDomError("CREATE-NODE", "An invalid character was specified");
             }
             else if (e.code == DOMException.NAMESPACE_ERR)
             {
                commonDomError("CREATE-NODE", "XML Parser not initialized");
             }
             else if (e.code == DOMException.SYNTAX_ERR)
             {
                unsupportedDomError(type.toStringMessage());
                invalidArgumentError("CREATE-NODE");
             }
             else
             {
                unsupportedDomError(type.toStringMessage());
             }
             return new logical(false);
          }

          return new logical(true);
       }

       /**
        * Creating the new node within the current Document object using different
        * approach based on the name space URI.
        *
        * @param    hChild
        *           The valid handle class instance of the child to be created and
        *           returned on method call to use further.
        * @param    uri
        *           The URI namespace of the node to be created.
        * @param    name
        *           The name of the node to be created.
        * @param    type
        *           The type of the node to be created.
        *
        * @return   The <code>true</code> in case of success
        *           <code>false</code> otherwise.
        */
       @Override
       public logical createNodeNamespace(handle hChild, String uri, String name, String type)
       {
          return createNodeNamespace(hChild,
                                     new character(uri),
                                     new character(name),
                                     new character(type));
       }

       /**
        * Creating the new node within the current Document object using different
        * approach based on the name space URI.
        *
        * @param    hChild
        *           The valid handle class instance of the child to be created and
        *           returned on method call to use further.
        * @param    uri
        *           The URI namespace of the node to be created.
        * @param    name
        *           The name of the node to be created.
        * @param    type
        *           The type of the node to be created.
        *
        * @return   The <code>true</code> in case of success
        *           <code>false</code> otherwise.
        */
       @Override
       public logical createNodeNamespace(handle hChild, character uri, String name, String type)
       {
          return createNodeNamespace(hChild, uri, new character(name), new character(type));
       }

       /**
        * Creating the new node within the current Document object using different
        * approach based on the name space URI.
        *
        * @param    hChild
        *           The valid handle class instance of the child to be created and
        *           returned on method call to use further.
        * @param    uri
        *           The URI namespace of the node to be created.
        * @param    name
        *           The name of the node to be created.
        * @param    type
        *           The type of the node to be created.
        *
        * @return   The <code>true</code> in case of success
        *           <code>false</code> otherwise.
        */
       @Override
       public logical createNodeNamespace(handle hChild, String uri, character name, String type)
       {
          return createNodeNamespace(hChild, new character(uri), name, new character(type));
       }

       /**
        * Creating the new node within the current Document object using different
        * approach based on the name space URI.
        *
        * @param    hChild
        *           The valid handle class instance of the child to be created and
        *           returned on method call to use further.
        * @param    uri
        *           The URI namespace of the node to be created.
        * @param    name
        *           The name of the node to be created.
        * @param    type
        *           The type of the node to be created.
        *
        * @return   The <code>true</code> in case of success
        *           <code>false</code> otherwise.
        */
       @Override
       public logical createNodeNamespace(handle hChild, String uri,String name, character type)
       {
          return createNodeNamespace(hChild, new character(uri), new character(name), type);
       }

       /**
        * Creating the new node within the current Document object using different
        * approach based on the name space URI.
        *
        * @param    hChild
        *           The valid handle class instance of the child to be created and
        *           returned on method call to use further.
        * @param    uri
        *           The URI namespace of the node to be created.
        * @param    name
        *           The name of the node to be created.
        * @param    type
        *           The type of the node to be created.
        *
        * @return   The <code>true</code> in case of success
        *           <code>false</code> otherwise.
        */
       @Override
       public logical createNodeNamespace(handle    hChild,
                                          String    uri,
                                          character name,
                                          character type)
       {
          return createNodeNamespace(hChild, new character(uri), name, type);
       }

       /**
        * Creating the new node within the current Document object using different
        * approach based on the name space URI.
        *
        * @param    hChild
        *           The valid handle class instance of the child to be created and
        *           returned on method call to use further.
        * @param    uri
        *           The URI namespace of the node to be created.
        * @param    name
        *           The name of the node to be created.
        * @param    type
        *           The type of the node to be created.
        *
        * @return   The <code>true</code> in case of success
        *           <code>false</code> otherwise.
        */
       @Override
       public logical createNodeNamespace(handle    hChild,
                                          character uri,
                                          String    name,
                                          character type)
       {
          return createNodeNamespace(hChild, uri, new character(name), type);
       }

       /**
        * Creating the new node within the current Document object using different
        * approach based on the name space URI.
        *
        * @param    hChild
        *           The valid handle class instance of the child to be created and
        *           returned on method call to use further.
        * @param    uri
        *           The URI namespace of the node to be created.
        * @param    name
        *           The name of the node to be created.
        * @param    type
        *           The type of the node to be created.
        *
        * @return   The <code>true</code> in case of success
        *           <code>false</code> otherwise.
        */
       @Override
       public logical createNodeNamespace(handle    hChild,
                                          character uri,
                                          character name,
                                          String    type)
       {
          return createNodeNamespace(hChild, uri, name, new character(type));
       }

       /**
        * Creating the new node within the current Document object using different
        * approach based on the name space URI.
        *
        * @param    hChild
        *           The valid handle class instance of the child to be created and
        *           returned on method call to use further.
        * @param    uri
        *           The URI namespace of the node to be created.
        * @param    name
        *           The name of the node to be created.
        * @param    type
        *           The type of the node to be created.
        *
        * @return   The <code>true</code> in case of success
        *           <code>false</code> otherwise.
        */
       @Override
       public logical createNodeNamespace(handle    hChild,
                                          character uri,
                                          character name,
                                          character type)
       {
          if (!isInitialized())
          {
             invalidNodeError("CREATE-NODE-NAMESPACE");
             return new logical(false);
          }

          if (!type.isUnknown() && !type.toStringMessage().isEmpty())
          {
             Integer nodeType = XmlFactory.convertAblNodeToNodeId(type.toStringMessage());
             if (nodeType == null ||
                 !(nodeType == XmlTokenTypes.ELEMENT_NODE ||
                   nodeType == XmlTokenTypes.ATTRIBUTE_NODE))
             {
                unsupportedDomError(type.toStringMessage());
                invalidArgumentError("CREATE-NODE-NAMESPACE");
                return new logical(false);
             }
          }

          if (!isXNodeHandle(hChild))
          {
             invalidArgumentError("CREATE-NODE-NAMESPACE");
             return new logical(false);
          }

          if (!uri.isUnknown() && uri.toStringMessage().isEmpty())
          {
             commonDomError("CREATE-NODE-NAMESPACE", "XML Parser not initialized");
             return new logical(false);
          }

          try
          {
             XmlFactory.createXNodeReference(hChild, new handle(this), uri, name, type);
          }
          catch (DOMException e)
          {
             if (e.code == DOMException.INVALID_CHARACTER_ERR)
             {
                commonDomError("CREATE-NODE-NAMESPACE", "An invalid character was specified");
             }
             else if (e.code == DOMException.NAMESPACE_ERR)
             {
                commonDomError("CREATE-NODE-NAMESPACE", "XML Parser not initialized");
             }

             return new logical(false);
          }

          return new logical(true);
       }

       /**
        * Getting the root node for the current document. The handle is placed
        * into passed object reference.
        *
        * @param    hRoot
        *           The valid handle class instance variable to receive the
        *           result.
        *
        * @return   The <code>true</code> in case of success
        *           <code>false</code> otherwise.
        */
       @Override
       public logical getDocumentElement(handle hRoot)
       {
          if (!isInitialized() || !valid() || !isXNodeHandle(hRoot))
          {
             invalidArgumentError("GET-DOCUMENT-ELEMENT");
             return new logical(false);
          }

          Node documentElement = xdoc.getDocumentElement();

          setNodeToHandle(hRoot, documentElement);

          return new logical(true);
       }

       /**
        * Loading X-Document form file or memptr sources.
        *
        * @param    mode
        *           Variable indicating the type of source to read. "file" means
        *           reading from file, "memptr" means reading from memory buffer.
        * @param    source
        *           If mode == "file" this is the file name, if "memptr" this is
        *           the memptr object.
        * @param    validate
        *           Flag indicating whether to validate while reading or not.
        *
        * @return   The <code>true</code> in case of success
        *           <code>false</code> otherwise.
        */
       @Override
       public logical load(String mode, Object source, logical validate)
       {
          return load(new character(mode), source, validate);
       }

       /**
        * Loading X-Document form file or memptr sources.
        *
        * @param    mode
        *           Variable indicating the type of source to read. "file" means
        *           reading from file, "memptr" means reading from memory buffer.
        * @param    source
        *           If mode == "file" this is the file name, if "memptr" this is
        *           the memptr object.
        * @param    validate
        *           Flag indicating whether to validate while reading or not.
        *
        * @return   The <code>true</code> in case of success
        *           <code>false</code> otherwise.
        */
       @Override
       public logical load(String mode, Object source, boolean validate)
       {
          return load(new character(mode), source, logical.of(validate));
       }

       /**
        * Loading X-Document form file or memptr sources.
        *
        * @param   mode
        *          Variable indicating the type of source to read. "file" means reading from file and
        *          source contains file name, "memptr" means reading from memory buffer.
        * @param   source
        *          If mode == "file" this is the file name, if "memptr" this is the memptr object.
        * @param   validate
        *          Flag indicating whether to validate while reading or not.
        *
        * @return  The {@code true} in case of success {@code false} otherwise. If {@code validate}
        *          is {@code true} and xml failed validation return {@code false} but the xml document
        *          is loaded anyway.
        */
       @Override
       public logical load(character mode, Object source, boolean validate)
       {
          return load(mode, source, logical.of(validate));
       }

       /**
        * Loading X-Document form file or memptr sources.
        *
        * @param   mode
        *          Variable indicating the type of source to read. "file" means reading from file and
        *          source contains file name, "memptr" means reading from memory buffer.
        * @param   source
        *          If mode == "file" this is the file name, if "memptr" this is the memptr object.
        * @param   validate
        *          Flag indicating whether to validate while reading or not. If {@code unknown},
        *          validation will not be done.
        *
        * @return  The {@code true} in case of success {@code false} otherwise. If {@code validate}
        *          is {@code true} and xml failed validation return {@code false} but the xml document
        *          is loaded anyway.
        */
       @Override
       public logical load(character mode, Object source, logical validate)
       {
          // TODO: refactor the code below here with a shared implementation of the error handling
          if (source == null ||
              ((source instanceof BaseDataType) && ((BaseDataType) source).isUnknown()))
          {
             invalidArgumentError("LOAD");
             return logical.FALSE;
          }

          String loadMode = mode.isUnknown()
                            ? "file"
                            : StringHelper.safeTrimTrailing(mode.toStringMessage()).toLowerCase();
          if ("file".equals(loadMode))
          {
             String fName = null;
             // we can't assume that source is a string
             if (source instanceof String)
             {
                fName = (String) source;
             }
             else if (source instanceof com.goldencode.p2j.util.Text)
             {
                com.goldencode.p2j.util.Text txt = (com.goldencode.p2j.util.Text) source;
                fName = txt.toStringMessage();
             }
             else
             {
                invalidArgumentError("LOAD");
                return logical.FALSE;
             }

             Stream inStr;
             try
             {
                inStr = StreamFactory.openFileStream(fName, false, false, -1, false);
             }
             catch (ErrorConditionException e)
             {
                String err = String.format(
                      "FATAL ERROR: file '', line '0', column '0', message 'An exception occurred! " +
                      "Type:RuntimeException, Message:The primary document entity could not be " +
                      "opened. Id=%s'",
                      fName);
                commonDomError("LOAD", err);

                return logical.FALSE;
             }
             try
             {
                logical retVal = new logical();
                InputStream inputStream = new InputStreamWrapper(inStr);
                String encoding = XmlHelper.readXMLEncoding(inputStream);
                if (encoding == null)
                {
                   // always default to UTF-8
                   encoding = "UTF-8";
                }
                Reader filtered = new EscapeControlCharsReader(new InputStreamReader(inputStream, encoding));
                InputSource is = new InputSource(new EscapeControlCharsReader(filtered));
                xdoc = XmlHelper.parse(is,
                                       validate.booleanValue(), // unknown & false mean no validation
                                       !isSuppressNamespaceProcessing,
                                       retVal, // OUTPUT param mechanism
                                       buildSchema());
                inStr.close();

                return retVal;
             }
             catch (IOException e)
             {
                // should not enter here, the exception is thrown by XmlHelper.parse() but the file
                // is already "open" by StreamFactory.openFileStream()
                String err = String.format("\"%s\" was not found", fName);
                ErrorManager.recordOrShowError(293, err, false, true, false);
                invalidArgumentError("LOAD");
             }
             catch (ParserConfigurationException e)
             {
                // impossible error
                invalidArgumentError("LOAD");
             }
             catch (SAXParseException e)
             {
                String msg = "FATAL ERROR: file '%s', line '%s', column '%s', message '%s'";
                String err = String.format(msg, source, e.getLineNumber(),
                                           e.getColumnNumber(), e.getMessage());
                commonDomError("LOAD", err);
             }
             catch (SAXException e)
             {
                String msg = "FATAL ERROR: file '%s', message '%s'";
                String err = String.format(msg, source, e.getMessage());

                commonDomError("LOAD", err);
             }
          }
          else if ("memptr".equals(loadMode))
          {
             if (!(source instanceof memptr))
             {
                invalidArgumentError("LOAD");
                return new logical(false);
             }

             try
             {
                memptr mptr = (memptr) source;
                byte[] bytes = mptr.getByteArray();

                ByteArrayInputStream bais = null;

                // prepare the input stream: keep all bytes until '\0' is encountered
                int last = 0;
                for (byte b : bytes)
                {
                   if (b == 0)
                   {
                      break;
                   }
                   last = last + 1;
                }
                if (last != bytes.length)
                {
                   // we have at least 1 non-zero byte
                   byte[] cleaned = new byte[last];
                   System.arraycopy(bytes, 0, cleaned, 0, last);

                   bais = new ByteArrayInputStream(cleaned);
                }
                else
                {
                   bais = new ByteArrayInputStream(bytes);
                }

                xdoc = XmlHelper.parse(bais,
                                       validate.getValue(),
                                       null,
                                       !isSuppressNamespaceProcessing);

                return new logical(true);
             }
             catch (IOException | ParserConfigurationException e)
             {
                invalidArgumentError("LOAD");
             }
             catch (SAXException e)
             {
                String msg = "X-NODEREF or X-DOCUMENT LOAD got an error: FATAL ERROR: " +
                             "file '%s', message '%s'";
                String err = String.format(msg, source, e.getMessage());

                commonDomError("LOAD", err);
             }
          }
          else if ("longchar".equals(loadMode))
          {
             if (!(source instanceof longchar))
             {
                invalidArgumentError("LOAD");
                return new logical(false);
             }

             try
             {
                longchar lc = (longchar) source;
                String content = lc.toStringMessage();

                // TODO: I18N implications need to be handled, this is probably WRONG!
                ByteArrayInputStream bais = new ByteArrayInputStream(content.getBytes());
                xdoc = XmlHelper.parse(bais,
                                       validate.getValue(),
                                       null,
                                       !isSuppressNamespaceProcessing);

                return new logical(true);
             }
             catch (IOException | ParserConfigurationException e)
             {
                invalidArgumentError("LOAD");
             }
             catch (SAXException e)
             {
                commonDomError("LOAD", "X-NODEREF or X-DOCUMENT LOAD got an error: '" + e.getMessage() + "'");
             }
          }
          else
          {
             invalidReadWriteModeError("LOAD");
          }

          return new logical(false);
       }

       /**
        * Saving X-Document to file or stream or memptr object.
        *
        * @param    mode
        *           Variable indicating the type of target to write. "file" means writing to file,
        *           "stream" means writing to the stream and "memptr" means writing to memory buffer.
        * @param    target
        *           If mode == "file" this is the file name, if "stream" means output stream, if
        *           "memptr" this is the memptr object.
        *
        * @return   The {@code true} in case of success {@code false} otherwise.
        */
       @Override
       public logical save(String mode, Object target)
       {
          return save(new character(mode), target);
       }

       /**
        * Saving X-Document to file or stream or memptr object.
        *
        * @param    mode
        *           Variable indicating the type of target to write. "file" means writing to file,
        *           "stream" means writing to the stream and "memptr" means writing to memory buffer.
        * @param    target
        *           If mode == "file" this is the file name, if "stream" means output stream, if
        *           "memptr" this is the memptr object.
        *
        * @return   The {@code true} in case of success {@code false} otherwise.
        */
       @Override
       public logical save(character mode, Object target)
       {
          if (target == null ||
              ((target instanceof BaseDataType) && ((BaseDataType) target).isUnknown()))
          {
             invalidArgumentError("SAVE");
             return new logical(false);
          }

          String writeMode = mode.isUnknown()
                             ? "file"
                             : StringHelper.safeTrimTrailing(mode.toStringMessage()).toLowerCase();

          if ("file".equals(writeMode))
          {
             String fName = null;
             try
             {
                // we can't assume that source is a string
                if (target instanceof String)
                {
                   fName = (String) target;
                }
                else if (target instanceof com.goldencode.p2j.util.Text)
                {
                   com.goldencode.p2j.util.Text txt = (com.goldencode.p2j.util.Text) target;
                   fName = txt.toStringMessage();
                }
                else
                {
                   invalidArgumentError("SAVE");
                   return new logical(false);
                }

                Stream outStr = StreamFactory.openFileStream(fName, true, false, -1, false);
                XmlHelper.writeLegacyCompatible(xdoc, new OutputStreamWrapper(outStr));
                outStr.close(); // flush and close remote
                return new logical(true);
             }
             catch (IOException e)
             {
                commonDomError("SAVE", "XML Parser Exception: Invalid file or path name");
             }
          }
          else if ("stream".equals(writeMode))
          {
             if (!(target instanceof OutputStream))
             {
                invalidArgumentError("LOAD");
                return new logical(false);
             }

             try
             {
                XmlHelper.writeLegacyCompatible(xdoc, (OutputStream) target);
                return new logical(true);
             }
             catch (IOException e)
             {
                commonDomError("SAVE", "XML Parser Exception: Invalid stream");
             }
          }
          else if ("memptr".equals(writeMode))
          {
             if (!(target instanceof memptr))
             {
                invalidArgumentError("LOAD");
                return new logical(false);
             }

             try
             {
                memptr memptrTarget = (memptr)target;
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                XmlHelper.writeLegacyCompatible(xdoc, baos);
                memptrTarget.assign(baos.toByteArray());
                return new logical(true);
             }
             catch (IOException e)
             {
                commonDomError("SAVE", "XML Parser Exception: Invalid memptr");
             }
          }
          else if ("longchar".equals(writeMode))
          {
             if (!(target instanceof longchar))
             {
                invalidArgumentError("LOAD");
                return new logical(false);
             }

             try
             {
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                XmlHelper.writeLegacyCompatible(xdoc, baos);
                longchar lc = (longchar) target;
                lc.assign(baos.toString());

                return new logical(true);
             }
             catch (IOException e)
             {
                commonDomError("SAVE", "XML Parser Exception: Invalid longchar");
             }
          }
          else
          {
             invalidReadWriteModeError("SAVE");
          }

          return new logical(false);
       }

       /**
        * Creates a new XML document, initializes the document based on the referenced DTD,
        * and creates its root node.
        *
        * @param    namespaceUri
        *           character expression representing the namespace Uniform Resource Identifier
        *           (URI) you want associated with the root node of the XML document. The
        *           namespace-uri must be unique and persistent.
        * @param    rootNodeName
        *           A character expression representing the name of the root node as defined in the
        *           XML document. If you are using namespaces and you want to associate a prefix
        *           with the namespace, you must qualify this node name with the namespace-uri
        *           and a colon character prefix (for example, namespace-uri:root-node-name).
        *           You must explicitly set the xmlns attribute on the root node.
        * @param    publicId
        *           An optional character expression representing the public ID of the DTD.
        * @param    systemId
        *           A required character expression representing the system ID of the DTD. This
        *           contains the path to the DTD which is either a file system path or an HTTP URL.
        *
        * @return   The <code>true</code> in case of success
        *           <code>false</code> otherwise.
        */
       @Override
       public logical initializeDocumentType(character namespaceUri,
                                             character rootNodeName,
                                             character publicId,
                                             character systemId)
       {
          if (rootNodeName.isUnknown())
          {
             invalidArgumentError("INITIALIZE-DOCUMENT-TYPE");
             return new logical(false);
          }
          try
          {
             xdoc = XmlHelper.newDocument(rootNodeName.getValue(),
                                          publicId.getValue(),
                                          systemId.getValue(),
                                          namespaceUri.getValue());
          }
          catch (ParserConfigurationException e)
          {
             //impossible error
             return new logical(false);
          }
          catch (DOMException e)
          {
             if (e.code == DOMException.INVALID_CHARACTER_ERR)
             {
                commonDomError("INITIALIZE-DOCUMENT-TYPE", "XML Parser not initialized");
                return new logical(false);
             }
             else if (e.code == DOMException.NAMESPACE_ERR)
             {
                commonDomError("INITIALIZE-DOCUMENT-TYPE", "XML Parser not initialized");
                return new logical(false);
             }
             else if (e.code == DOMException.WRONG_DOCUMENT_ERR)
             {
                //impossible error, cues document has been created inside XmlHelper.newDocument
                commonDomError("INITIALIZE-DOCUMENT-TYPE", "XML Parser not initialized");
                return new logical(false);
             }
          }
          return new logical(true);
       }

       /**
        * Creates a new XML document, initializes the document based on the referenced DTD,
        * and creates its root node.
        *
        * @param    namespaceUri
        *           character expression representing the namespace Uniform Resource Identifier
        *           (URI) you want associated with the root node of the XML document. The
        *           namespace-uri must be unique and persistent.
        * @param    rootNodeName
        *           A character expression representing the name of the root node as defined in the
        *           XML document. If you are using namespaces and you want to associate a prefix
        *           with the namespace, you must qualify this node name with the namespace-uri
        *           and a colon character prefix (for example, namespace-uri:root-node-name).
        *           You must explicitly set the xmlns attribute on the root node.
        * @param    publicId
        *           An optional character expression representing the public ID of the DTD.
        * @param    systemId
        *           A required character expression representing the system ID of the DTD. This
        *           contains the path to the DTD which is either a file system path or an HTTP URL.
        *
        * @return   The <code>true</code> in case of success
        *           <code>false</code> otherwise.
        */
       @Override
       public logical initializeDocumentType(character namespaceUri,
                                             String    rootNodeName,
                                             character publicId,
                                             character systemId)
       {
          return initializeDocumentType(namespaceUri, new character(rootNodeName),
                                        publicId, systemId);
       }

       /**
        * Creates a new XML document, initializes the document based on the referenced DTD,
        * and creates its root node.
        *
        * @param    namespaceUri
        *           character expression representing the namespace Uniform Resource Identifier
        *           (URI) you want associated with the root node of the XML document. The
        *           namespace-uri must be unique and persistent.
        * @param    rootNodeName
        *           A character expression representing the name of the root node as defined in the
        *           XML document. If you are using namespaces and you want to associate a prefix
        *           with the namespace, you must qualify this node name with the namespace-uri
        *           and a colon character prefix (for example, namespace-uri:root-node-name).
        *           You must explicitly set the xmlns attribute on the root node.
        * @param    publicId
        *           An optional character expression representing the public ID of the DTD.
        * @param    systemId
        *           A required character expression representing the system ID of the DTD. This
        *           contains the path to the DTD which is either a file system path or an HTTP URL.
        *
        * @return   The <code>true</code> in case of success
        *           <code>false</code> otherwise.
        */
       @Override
       public logical initializeDocumentType(String    namespaceUri,
                                             character rootNodeName,
                                             String    publicId,
                                             String    systemId)
       {
          return initializeDocumentType(new character(namespaceUri), new character(rootNodeName),
                                        new character(publicId), new character(systemId));
       }

       /**
        * Creates a new XML document, initializes the document based on the referenced DTD,
        * and creates its root node.
        *
        * @param    namespaceUri
        *           character expression representing the namespace Uniform Resource Identifier
        *           (URI) you want associated with the root node of the XML document. The
        *           namespace-uri must be unique and persistent.
        * @param    rootNodeName
        *           A character expression representing the name of the root node as defined in the
        *           XML document. If you are using namespaces and you want to associate a prefix
        *           with the namespace, you must qualify this node name with the namespace-uri
        *           and a colon character prefix (for example, namespace-uri:root-node-name).
        *           You must explicitly set the xmlns attribute on the root node.
        * @param    publicId
        *           An optional character expression representing the public ID of the DTD.
        * @param    systemId
        *           A required character expression representing the system ID of the DTD. This
        *           contains the path to the DTD which is either a file system path or an HTTP URL.
        *
        * @return   The <code>true</code> in case of success
        *           <code>false</code> otherwise.
        */
       @Override
       public logical initializeDocumentType(String namespaceUri,
                                             String rootNodeName,
                                             String publicId,
                                             String systemId)
       {
          return initializeDocumentType(new character(namespaceUri), new character(rootNodeName),
                                        new character(publicId), new character(systemId));
       }

       /**
        * This method returns the public ID of the external DTD from which an XML document was
        * generated.
        *
        * @return public-id of DTD
        */
       @Override
       public character getPublicId()
       {
          if (!isInitialized())
          {
             invalidNodeError("PUBLIC-ID");
             return new character();
          }

          DocumentType documentType = xdoc.getDoctype();
          if (documentType != null)
          {
             String publicId = documentType.getPublicId();
             return new character(publicId == null ? "" : publicId);
          }
          return new character("");
       }

       /**
        * Returns the system ID of the external DTD from which an XML document was generated. This
        * contains the path to the DTD which is either a file system path or an HTTP URL. The ABL
        * parser uses this information to retrieve the DTD when parsing the document.
        *
        * @return system-id of DTD
        */
       @Override
       public character getSystemId()
       {
          if (!isInitialized())
          {
             invalidNodeError("SYSTEM-ID");
             return new character();
          }

          DocumentType documentType = xdoc.getDoctype();
          if (documentType != null)
          {
             String systemId = documentType.getSystemId();
             return new character(systemId == null ? "" : systemId);
          }
          return new character("");
       }

       /**
        * Determines the list of namespace/location pairs of an XML Schema file to validate
        * against.
        *
        * @return   schema location, default value is "".
        */
       @Override
       public character getSchemaLocation()
       {
          if (!isInitialized())
          {
             invalidNodeError("SCHEMA-LOCATION");
             return new character();
          }

          String schemaLocation =
                  (String)xdoc.getDomConfig().getParameter(Constants.DOM_SCHEMA_LOCATION);

          return new character(schemaLocation);
       }

       /**
        * Creating the new node within the current Document object.
        *
        * @param    xNodeRef
        *           The valid handle class instance to use for the new XML node.
        * @param    xSourceNodeRef
        *           The valid handle class instance to import from.
        * @param    deep
        *           A logical that if <code>true</code> specifies that the whole sub-tree is to be
        *           copied. The default value is {@code false}.
        *
        * @return   The <code>true</code> in case of success <code>false</code> otherwise.
        */
       @Override
       public logical importNode(handle xNodeRef, handle xSourceNodeRef, logical deep)
       {
          if (!isInitialized())
          {
             invalidArgumentError("IMPORT-NODE");
             return new logical(false);
          }
          if (!isXNodeHandle(xNodeRef)       ||
              !isXNodeHandle(xSourceNodeRef) ||
              !((XEntityImpl) xSourceNodeRef.getResource()).isInitialized())
          {
             invalidArgumentError("IMPORT-NODE");
             return new logical(false);
          }
          if (xNodeRef == xSourceNodeRef) // check that xNodeRef and xSourceNodeRef are different
          {
             xSourceNodeRef.assign(new XNodeRefImpl());//make handle unknown
             invalidNodeError("IMPORT-NODE");
             return new logical(false);
          }

          XNodeRefImpl nodeRef = (XNodeRefImpl) xSourceNodeRef.get();
          try
          {
             Node node = xdoc.importNode(nodeRef.getNode(), deep.booleanValue());
             setNodeToHandle(xNodeRef, node);
             return xNodeRef.isValid();
          }
          catch (DOMException e)
          {
             if (e.code == DOMException.INVALID_CHARACTER_ERR)
             {
                commonDomError("CREATE-NODE-NAMESPACE", "An invalid character was specified");
             }
             return new logical(false);
          }
       }

       /**
        * Creating the new node within the current Document object.
        *
        * @param    xNodeRef
        *           The valid handle class instance to use for the new XML node.
        * @param    xSourceNodeRef
        *           The valid handle class instance to import from.
        * @param    deep
        *           A boolean that if <code>true</code> specifies that the whole sub-tree is to be
        *           copied. The default value is {@code false}.
        *
        * @return   The <code>true</code> in case of success <code>false</code> otherwise.
        */
       @Override
       public logical importNode(handle xNodeRef, handle xSourceNodeRef, boolean deep)
       {
          return importNode(xNodeRef, xSourceNodeRef, new logical(deep));
       }

       /**
        * Determines the list of namespace/location pairs of an XML Schema file to validate
        * against.
        *
        * @param    schemaLocation
        *           Determines the list of namespace/location pairs of an XML Schema file to
        *           validate against.
        */
       @Override
       public void setSchemaLocation(character schemaLocation)
       {
          if (!isInitialized())
          {
             invalidNodeError("SCHEMA-LOCATION");
             return;
          }
          if (schemaLocation.isUnknown())
          {
             invalidArgumentAssignError("SCHEMA-LOCATION");
             return;
          }

          String schemaLoc = schemaLocation.toStringMessage();
          xdoc.getDomConfig().setParameter(Constants.DOM_SCHEMA_LOCATION, schemaLoc);
       }

       /**
        * Get encoding
        *
        * @return encoding of xDoc
        */
       @Override
       public character getEncoding()
       {
          if (!isInitialized())
          {
             invalidNodeError("ENCODING");
             return new character();
          }

          String xdocEncoding = xdoc.getXmlEncoding();

          final character result;
          if (xdocEncoding == null)
          {
             result = new character("");
          }
          else
          {
             result = new character(xdocEncoding);
          }
          return result;
       }

       /**
        * A delimiter-separated list of directory paths used to locate the XML Document Type
        * Definition (DTD) associated with a particular XML document. It is searched if the XML
        * document contains a relative path to the DTD.
        *
        * @return   schema path, default value is unknown(?).
        */
       @Override
       public character getSchemaPath()
       {
          if (!isInitialized())
          {
             return new character();
          }

          return new character(schemaPaths);
       }

       /**
        * Sets the new value of the encoding attribute for the given X-Document object.
        *
        * @param    encoding
        *           The encoding value to be set for the given writer.
        */
       @Override
       public void setEncoding(String encoding)
       {
          setEncoding(new character(encoding));
       }

       /**
        * A delimiter-separated list of directory paths used to locate the XML Document Type
        * Definition (DTD) associated with a particular XML document. It is searched if the XML
        * document contains a relative path to the DTD.
        *
        * @param    schemaPath
        *           To separate directory paths, use a comma, or a delimiter character that your
        *           operating system recognizes.
        */
       @Override
       public void setSchemaPath(character schemaPath)
       {
          if (!isInitialized())
          {
             invalidNodeError("SCHEMA-PATH");
             return;
          }
          if (schemaPath.isUnknown())
          {
             invalidArgumentAssignError("SCHEMA-PATH");
             return;
          }

          schemaPaths = schemaPath.toStringMessage();
       }

       /**
        * Sets the new value of the encoding attribute for the given X-Document object.
        *
        * @param    encoding
        *           The encoding value to be set for the given writer.
        */
       @Override
       public void setEncoding(character encoding)
       {
          if (!isInitialized() || encoding.isUnknown())
          {
             return;
          }

          String encodingStr = encoding.toStringMessage();

          String javaEncoding = EncodingMap.getIANA2JavaMapping(encodingStr);
          if (javaEncoding == null)
          {
             Charset charset = Charset.forName(encodingStr);
             for (String alias : charset.aliases())
             {
                javaEncoding = EncodingMap.getIANA2JavaMapping(alias);
                if (javaEncoding != null)
                {
                   encodingStr = alias;
                   break;
                }
             }
          }

          ((DocumentImpl) xdoc).setXmlEncoding(encodingStr);

       }

       /**
        * Gets the value of the XML schema file for elements with no namespaces.
        *
        * @return The value of the nonamespace-schema-location attribute.
        */
       @Override
       public character getNonamespaceSchemaLocation()
       {
          if (!isInitialized())
          {
             return new character();
          }

          DOMConfiguration domConfig = xdoc.getDomConfig();
          String schemaLocation;
          try
          {
             schemaLocation = (String) domConfig.getParameter(Constants.SCHEMA_NONS_LOCATION);
          }
          catch (DOMException e)
          {
             // impossible error for in our case
             return new character("");
          }
          return new character(schemaLocation);
       }

       /**
        * Setting the new value for the nonamespace-schema-location attribute.
        *
        * @param    location
        *           New value of the nonamespace-schema-location attribute to set.
        */
       @Override
       public void setNonamespaceSchemaLocation(character location)
       {
          if (!isInitialized()|| location.isUnknown())
          {
             return;
          }

          this.noNamespaceSchemaLocation = location.toStringMessage();
       }

       /**
        * Adding new match pair for namespace schema and physical file location.
        *
        * @param    namespace
        *           Target namespace of the schema or am empty string or Unknown value.
        * @param    location
        *           Location of the XML schema file.
        *
        * @return   The <code>true</code> in case of success <code>false</code> otherwise.
        */
       @Override
       public logical addSchemaLocation(character namespace, character location)
       {
          if (!isInitialized())
          {
             invalidNodeError("SCHEMA-LOCATION");
             return new logical(false);
          }
          if (namespace.isUnknown() || location.isUnknown())
          {
             invalidArgumentError("SCHEMA-LOCATION");
             return new logical(false);
          }

          DOMConfiguration domConfig = xdoc.getDomConfig();
          String namespaceStr = namespace.toStringMessage();
          String locationEntity = namespaceStr + ' ' + location.toStringMessage();
          String oldSchemaLocation =
                  (String)xdoc.getDomConfig().getParameter(Constants.DOM_SCHEMA_LOCATION);
          String newSchemaLocation =
                  oldSchemaLocation == null ? " " : oldSchemaLocation;

          domConfig.setParameter(Constants.DOM_SCHEMA_LOCATION, newSchemaLocation + locationEntity);

          this.schemaLocations.put(namespace.toStringMessage(), location.toStringMessage());

          return new logical(true);

       }

       /**
        * Gets the number of child for the current one. Only for XNodRef
        *
        * @return   The integer number of the child, 1-based index of the child node.
        */
       @Override
       public integer getChildNum()
       {
          if (!isInitialized())
          {
             invalidNodeError("NUM-CHILDREN");
             return new integer();
          }

          return new integer();
       }

       /**
        * Indicates if the namespace processing is suppressed or not.
        *
        * @return   The logical value representing the current suppress-namespace-processing
        *           attribute.
        */
       @Override
       public logical getSuppressNamespaceProcessing()
       {
          if (!isInitialized())
          {
             return new logical(false);
          }

          return new logical(isSuppressNamespaceProcessing);
       }

       /**
        * Redefines whether the namespace processing is suppressed or not.
        *
        * @param    suppress
        *           The new value the current suppress-namespace-processing attribute.
        */
       @Override
       public void setSuppressNamespaceProcessing(logical suppress)
       {
          if (!isInitialized())
          {
             return;
          }
          if (suppress.isUnknown())
          {
             invalidArgumentAssignError("SUPPRESS-NAMESPACE-PROCESSING");
             return;
          }

          isSuppressNamespaceProcessing = suppress.booleanValue();
       }

       /**
        * Redefines whether the namespace processing is suppressed or not.
        *
        * @param    suppress
        *           The new value the current suppress-namespace-processing attribute.
        */
       @Override
       public void setSuppressNamespaceProcessing(boolean suppress)
       {
          setSuppressNamespaceProcessing(new logical(suppress));
       }

       /**
        * Indicates if the strict entity resolution is enabled.
        *
        * @return   The logical value representing the current value.
        */
       @Override
       public logical getStrictEntityResolution()
       {
          // TODO
          return new logical();
       }

       /**
        * Enable or disable strict entity resolution.
        *
        * @param    strict
        *           The new value for the attribute.
        */
       @Override
       public void setStrictEntityResolution(logical strict)
       {
          // TODO
       }

       /**
        * Enable or disable strict entity resolution.
        *
        * @param    strict
        *           The new value for the attribute.
        */
       @Override
       public void setStrictEntityResolution(boolean strict)
       {
          setStrictEntityResolution(new logical(strict));
       }

       /**
        * Get the entity expansion limit.
        *
        * @return   The integer value of the attribute.
        */
       @Override
       public integer getEntityExpansionLimit()
       {
          // TODO
          return new integer();
       }

       /**
        * Set the entity expansion limit.
        *
        * @param    limit
        *           The new value for the attribute.
        */
       @Override
       public void setEntityExpansionLimit(int64 limit)
       {
          // TODO
       }

       /**
        * Set the entity expansion limit.
        *
        * @param    limit
        *           The new value for the attribute.
        */
       @Override
       public void setEntityExpansionLimit(long limit)
       {
          setEntityExpansionLimit(new int64(limit));
       }

       /**
        * Getting the Document the current reference belongs to.
        *
        * @return   The handle of the Document containing tne current reference.
        */
       @Override
       public handle getOwnerDocument()
       {
          invalidNodeError("OWNER-DOCUMENT");
          return new handle();
       }

       /**
        * Process and display invalid node error. It displays appropriate error message.
        *
        * @param    attribute
        *           The read-only attribute.
        */
       private void invalidReadWriteModeError(String attribute)
       {
          String msg = "Argument for LOAD/SAVE must be 'file', 'stream', " +
                  "'stream-handle' or 'memptr'";
          ErrorManager.recordOrShowError(9170, msg, false, false, false);

          invalidArgumentError(attribute);
       }

       /**
        * Build the validation schema using the {@link #noNamespaceSchemaLocation}, {@link #schemaPaths} and
        * {@link #addSchemaLocation} values.
        *
        * @return   A schema used for validation, or <code>null</code> if one could ont be built.
        */
       private Schema buildSchema()
       {
          List<Source> sources = new ArrayList<>();

          String sep = EnvironmentOps.getLegacyPathSeparator();
          String[] paths = schemaPaths == null ? null : schemaPaths.split(sep);

          if (noNamespaceSchemaLocation != null && !noNamespaceSchemaLocation.trim().isEmpty())
          {
             byte[] schemaFile = resolveSchemaFile(paths, noNamespaceSchemaLocation);

             if (schemaFile != null)
             {
                sources.add(new StreamSource(new ByteArrayInputStream(schemaFile)));
             }
          }

          // TODO: use [ns, location] for other 'add-schema-location'; TODO: remote URLs?
          for (String file : schemaLocations.values())
          {
             byte[] schemaFile = resolveSchemaFile(paths, file);

             if (schemaFile != null)
             {
                sources.add(new StreamSource(new ByteArrayInputStream(schemaFile)));
             }
          }

          if (sources.isEmpty())
          {
             return null;
          }

          try
          {
             Schema schema = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI)
                                          .newSchema(sources.toArray(new Source[0]));
             return schema;
          }
          catch (SAXException e)
          {
             return null;
          }
       }

       /**
        * Resolve the schema file from the FWD client's file-system.
        *
        * @param    paths
        *           The paths to check against.
        * @param    file
        *           The file to locate.
        *
        * @return   The content of the file or <code>null</code> if it can not be resolved.
        */
       private byte[] resolveSchemaFile(String[] paths, String file)
       {
          if (paths == null)
          {
             try
             {
                return FileSystemOps.getFileContents(file);
             }
             catch (IOException exc)
             {
                return null;
             }
          }

          String sep = EnvironmentOps.getLegacyFileSeparator();
          for (int i = 0; i < paths.length; i++)
          {
             try
             {
                return FileSystemOps.getFileContents(paths[i] + sep + file);
             }
             catch (IOException exc)
             {
                // ignore
             }
          }

          return null;
       }
    }