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;
   }
}