# XmlExport.java

    /*
    ** Module   : XmlExport.java
    ** Abstract : Write temp-table data into an XML stream.
    **
    ** Copyright (c) 2017-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
    ** 001 ECF 20171016 Created initial version.
    ** 002 CA  20180511 Added XML-NODE-NAME table option support.
    ** 003 ECF 20190123 Replaced TempTableSchema with XmlTempTableSchema.
    ** 004 OM  20190327 Renamed DataSource to avoid conflicts with DataSet source.
    ** 005 OM  20190711 API changes in favour of using SourceData and TargetData for all methods.
    **     OM  20190711 Implemented serialization of datasets and schema for tables and datasets.
    ** 006 OM  20190716 Fixed dataset serialized name.
    **     CA  20190812 Fixed problems with DATASET export.
    ** 007 CA  20191119 Do not explicitly XML-escape the value, as the writer will do this on its own.
    ** 008 ECF 20200906 New ORM implementation.
    ** 009 CA  20200914 Fixed raw/blob field export.
    **     OM  20201120 Added SERIALIZE-ROW. Improved TEMP-TABLE attribute serialization and compatibility with
    **                  the output of P4GL.
    **     OM  20201218 Fixed serialization of BEFORE-BUFFER specific attributes.
    **     OM  20210329 Added Column API to allow extraction of legacy-name, xml-node-name and serialize-name
    **                  independently from the precomputed name.
    **     CA  20210510 Fixed JSON and XML serialization when XML-NODE-NAME/SERIALIZE-NAME field options are set.
    **     CA  20210511 Removed XmlTempTableSchema class, as it was obsolete.
    **     CA  20210629 Added 'prodata:wordIndex' attribute for word indexes.
    **     CA  20210709 Fixed a NPE when writing a decimal unknown field.
    **     OM  20210809 Completed implementation of SERIALIZE-ROW|WRITE-JSON}(JsonObject|JsonArray).
    **     OM  20210922 Fixed default values for XML-NODE-NAME and SERIALIZE-NAME.
    **     OM  20211102 WRITE-XML was not using XML-NODE-NAME for the temp-table.
    **     CA  20211112 prodsid must be serialized only when in before-image mode.
    **     OM  20211020 Added _DATASOURCE_ROWID property.
    **     OM  20220120 Improve compatibility with P4GL. Optimizations. Fixed bugs.
    **     CA  20220218 Ensure the group and decimal separators are ',' and '.' during record write.
    **     CA  20221006 Do not access tableHandle() in the FWD runtime, get the TempTable instance directly.
    **                  Refs #6826
    **     TJD 20220504 Java 11 compatibility minor changes
    **     CA  20220520 Added support for TEXT and ATTRIBUTE XML-NODE-TYPE values.
    **     OM  20220525 Better encapsulation of schemaIndexes.
    **     CA  20220616 Unknown fields with XML-NODE-TYPE set to ATTRIBUTE or TEXT are not exported.
    **     IAS 20220926 Fixed WRITE-XMLSCHEMA support.
    **     IAS 20221013 Case-insensitive comparing of FORMAT value with a default one.
    **     IAS 20221014 Do not add prefix to the attribute name.
    **     IAS 20221110 Multiple fixes in the WRITE-XMLSCHEMA support. The most important one is the
    **                  external schema support for DATASETs
    **     IAS 20221111 Fixed WRITE-XML for DATASET.
    **     IAS 20221123 Fixed nodes' close in WRITE-XMLSCHEMA for DATASET.
    **     HC  20230116 Replaced some handle usages with the actual wrapped resources for
    **                  performance.
    **     CA  20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
    ** 009 IAS 20230223 Added support for recursive DATA-SET
    **         20230224 Fixed NS Prefix value in WRITE-XML(SCHEMA)
    **         20230313 Fixed WRITE-XML(SCHEMA) for multiple namespaces and NESTED relations.
    **         20230314 Added WRITE-XML(SCHEMA) support for relations with FOREIGN-KEY-HIDDEN = true.
    ** 010 IAS 20240425 Add XML escaping. Fix "tns" prefixes' creation.
    **         20240426 Fixed the indexes order.
    **         20230501 Fixed support for the INITIAL-NULL pseudo-attribute.
    **                  Added escaping for the "'"
    ** 011 IAS 20230415 Fixed support for PARENT-ID-RELATION.
    **     IAS 20230422 Fixed support for DATA-SET:SERIALIZE-HIDDEN in WRITE-XML
    **     IAS 20230424 Refactored WRITE-XMLSCHEMA
    **     IAS 20230501 Fixed support for DATA-SET:SERIALIZE-HIDDEN in WRITE-XMLSCHEMA
    ** 012 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
    ** 013 IAS 20230530 Fixed checks for empty nsPrefix.
    ** 014 CA  20230907 Added support for X-Document target.  X-Noderef is not yet supported.
    ** 015 CA  20240308 At WRITE-XML, empty 'encoding' arg acts the same as unknown.
    ** 016 CA  20240320 Avoid BDTs within internal FWD runtime.  Replaced iterators with Java 'for', where it
    **                  applies.
    ** 017 CA  20240523 Fixed DATASET:WRITE-/READ-XML when before tables or (in)active relations are involved.
    ** 018 TJD 20240123 Java 17 compatibility updates
    ** 019 SP  20240603 Added a catch for ErrorConditionException in write().
    **     SP  20240627 Error 4065 must not set ERROR-STATUS:ERROR flag.
    ** 020 RNC 20241029 Recreate relation queries if they are already used when serializing the temp tables.
    **     RNC 20241030 If the serializing happens because of a SOAP request, ignore the
    **                  FOREIGN-KEY-HIDDEN keyword from relations in the dataset.
    ** 021 ES  20250327 Use seralizationName, xmlNodeName, namspaceUri and namespacePrefix of the buffer,
    **                  instead of temp-table. In case of TEMP-TABLE:readXml use attributes of the defaultBuffer.
    **     ES  20250328 Use the NAMESPACE-URI, NAMESPACE-PREFIX and XML-NODE-NAME attributes of the buffer in
    **                  DATASET:WRITE-XML.
    */

    /*
    ** 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.persist.serial;

    import java.io.*;
    import java.lang.reflect.*;
    import java.math.*;
    import java.nio.file.*;
    import java.util.*;
    import java.util.concurrent.*;
    import java.util.function.*;
    import java.util.stream.*;
    import javax.xml.stream.*;
    import org.apache.commons.text.StringEscapeUtils;
    import org.apache.commons.lang3.*;
    import org.apache.commons.lang3.tuple.*;
    import org.codehaus.stax2.*;
    import org.codehaus.stax2.io.*;
    import com.ctc.wstx.api.*;
    import com.ctc.wstx.stax.*;
    import com.goldencode.p2j.persist.*;
    import com.goldencode.p2j.persist.Record;
    import com.goldencode.p2j.persist.orm.*;
    import com.goldencode.p2j.persist.serial.SerializeOptions.XmlNodeType;
    import com.goldencode.p2j.util.*;
    import com.goldencode.p2j.util.logging.*;
    import com.goldencode.p2j.xml.*;

    /**
     * An implementation of the 4GL WRITE-XML method, which writes the records of a temp-table into
     * an XML stream.
     * <p>
     * Runtime support is partial at this time; not all modes and features are supported.
     */
    public final class XmlExport
    {
       /** Logger */
       private static final CentralLogger LOG = CentralLogger.get(XmlExport.class);

       /** The 4GL output files use a 2-space indentation. */
       private static final String INDENT = "  ";

       /** XML namespace attribute */
       private static final String ATTR_NS_XSI = "xmlns:xsi";

       /** Namespace URI for XML schema */
       private static final String NS_URI = "http://www.w3.org/2001/XMLSchema-instance";

       /** Nil attribute */
       private static final String ATTR_XSI_NIL = "xsi:nil";

       /** Platform-specific line separator */
       // TODO: we need client's line separate, not server's
       private static final String lineSep = System.lineSeparator();

       /** noop XmlElementWriter */
       private static final XmlElementWriter NOOP = () -> {};

       /** Flag indicating whether XML output should be pretty-printed */
       private final boolean format;

       /** XML data target which normalizes access of various media to an output stream. */
       private final TargetData target;

       /** Character encoding (not currently used). */
       private final String encoding;

       /** Flag indicating that we're creating external schema */
       private boolean external = false;

       /** Flag indicating that we're creating schema for the tested table*/
       private boolean nested = false;

       /** XML stream writer */
       private XMLStreamWriter writer = null;

       /** The indentation level. */
       private int indentLevel = 0;

       /** The current {@code DataSet}, if one is processed. {@code null} if a table is processed. */
       private DataSet ds = null;

       /** The current {@code DataSet} name, if one is processed. {@code null} if a table is processed. */
       private String dsName = null;

       /** The current {@code DataSet} namespace, if one is processed. {@code null} if a table is processed. */
       private String nsUri = "";

       /** The current {@code DataSet} prefix, if one is processed. {@code null} if a table is processed. */
       private String nsPrefix = null;

       /** The value of the DataSet SERIALIZE-HIDDEN attribute */
       private boolean serializeHidden = false;

       /** Number of DataSet relations */
       private int relCount;

       /** Number of DataSet non PARENT-ID-RELATION relations */
       private int prodataRelationCount ;

    //   /**
    //    * Flags a {@code WRITE-XMLSCHEMA} method in progress (for both {@code DataSet} and
    //    * {@code TempTable}). When a {@code WRITE-XML} is processed the value is {@code false}.
    //    */
    //   private boolean isSchemaOnly = false;

       /** The list of indexes, in order of their buffers. */
       private final Map<Integer, Pair<String, TableMapper.LegacyIndexInfo>> indexes = new TreeMap<>();

       /** The map of DATA-SET tables by legacy names */
       private final Map<String, TempTableSchema> tables  = new LinkedHashMap<>();

       /** The DATA-SET tables grouped by namespace. */
       private Map<String, List<TempTableSchema>> tablesByNs = new LinkedHashMap<>();

       /** External schemas by NS */
       private Map<String, String> extSchemas = new LinkedHashMap<>();

       /** Tables schemas already written */
       private Set<String> writtenSchemas = new HashSet<>();

       /** Default prefix for NS by NS */
       private Map<String, String> prefixByNs = new LinkedHashMap<>();

       /** Parent -> Child table names for the NESTED relations */
       private final Map<String, List<String>> childByParent = new HashMap<>();

       /** Child ->  Parent table names for the NESTED relations */
       private final Map<String, String> parentByChild = new HashMap<>();

       /** Child fields for NESTED relations with FOREIGN-KEY-HIDDEN = true per child table name*/
       private Map<String, Set<String>> childFieldsByChild = new HashMap<>();

       /** Child fields for the current NESTED relations with FOREIGN-KEY-HIDDEN = true*/
       private Set<String> childFields = Collections.emptySet();

       /** The map with eventual error strings. */
       private final Map<String, Pair<String, String>> prodsErrors = new LinkedHashMap<>();

       /** XML schema writer*/
       private XmlElementWriter writeXmlSchema = null;

       /** The namespace for the currently exported temp-table, which is a root of the nested realtions tree */
       private String rootNsUri = null;

       /** The namespace prefix for the currently exported temp-table, which is a root of the nested realtions tree */
       private String rootNsPrefix = null;

       /** The namespace prefix for the currently exported temp-table, if any. */
       private String ttNsPrefix = null;

       /** Current table has childs */
       private boolean hasChild = false;

       /** Number of child records just read */
       private int childRecords = 0;

       /** Stack of 'hasChild' flag values */
       private final Stack<Boolean> savedHasChild = new Stack<>();

       /** xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" was written */
       private boolean xsiWritten = false;

       /** Number of dataset/tables root element written */
       private int rootsWritten = 0;

       /** Root table name (for DATA-SET:WRITE-XML(SCHEMA) with SERIALIZE-HIDDEN = true */
       private String rootTableName = null;

       /** Number of root table records written */
       private int rootTableRecordsWritten = 0;
       /**
        * Maps the {@code LegacyIndexInfo} objects to unique names for them, if their name is not unique
        * across the dataset.
        */
       private final Map<TableMapper.LegacyIndexInfo, String> uidxUniqueNames = new LinkedHashMap<>();

       /**
        * Unique names for unique indexes map to their original {@code LegacyIndexInfo} objects. This is the
        * reverse of {@code uidxUniqueNames}. All unique indexes can be found here.
        */
       private final Map<String, TableMapper.LegacyIndexInfo> uidxByUniqueNames = new LinkedHashMap<>();

       /**
        * We store here the {@code DataRelation}s which have promoted to {@code xsd:keyref}s. Those not present
        * in this set will be serialized as {@code prodata:relation}s.
        */
       private final Set<DataRelation> keyrefs = new HashSet<>();

       private static final TempTableSchema.Column ERROR_FLAG =
             new TempTableSchema.Column(Buffer.__ERROR_FLAG__,        Buffer.__ERROR_FLAG__,   new integer());

       private static final TempTableSchema.Column ORIGIN_ROWID =
             new TempTableSchema.Column(Buffer.__ORIGIN_ROWID__,      Buffer.__ORIGIN_ROWID__, new rowid());

       private static final TempTableSchema.Column DATASOURCE_ROWID =
             new TempTableSchema.Column(Buffer.__DATA_SOURCE_ROWID__, Buffer.__DATA_SOURCE_ROWID__, new rowid());

       private static final TempTableSchema.Column ERROR_STRING =
             new TempTableSchema.Column(Buffer.__ERROR_STRING__,      Buffer.__ERROR_STRING__, new character());

       private static final TempTableSchema.Column AFTER_ROWID =
             new TempTableSchema.Column(Buffer.__AFTER_ROWID__,       Buffer.__AFTER_ROWID__,  new rowid());

       private static final TempTableSchema.Column ROW_STATE =
             new TempTableSchema.Column(Buffer.__ROW_STATE__,         Buffer.__ROW_STATE__,    new integer());

       /**
        * Constructor which saves the internal data for writing a XML/XSD stream.
        *
        * @param   target
        *          XML data target which normalizes access of various media to an output stream.
        * @param   formatted
        *          {@code true} if serialized output should be pretty-printed for human-friendly
        *          reading; {@code false} to omit formatting.
        * @param   encoding
        *          Character encoding (not currently used).
        */
       public XmlExport(TargetData target, boolean formatted, String encoding)
       {
          this.target = target;
          this.encoding = encoding != null && encoding.isEmpty() ? null : encoding; // defaults to UTF-8
          this.format = formatted;
       }

       /**
        * Performs the export of a DATASET.
        *
        * @param   dataSet
        *          The {@code DatSet} from which data is exported.
        * @param   schemaLocation
        *          Path to XML schema (not currently used).
        * @param   writeXmlSchema
        *          {@code true} to write XML schema information; {@code false} to write only data.
        *          If {@code true}, schema will be written to {@code schemaLocation}, if provided;
        *          if not provided, schema will be embedded in XML output. Not currently honored.
        * @param   minXmlSchema
        *          {@code true} to write minimal schema information; {@code false} to write verbose
        *          schema information. Ignored if {@code writeXmlSchema} is {@code false}. Not
        *          currently honored.
        * @param   writeBeforeImage
        *          Not currently honored.
        * @param   omitInitialValues
        *          If {@code true}, record values which match default initial values will be omitted
        *          from serialized output, else they will be included. Not currently honored.
        *
        * @return  {@code true} on success. Otherwise the error is already dispatched to
        *          {@code ErrorManager} before returning.
        */
       public boolean writeDataset(DataSet dataSet,
                                   String schemaLocation,
                                   boolean writeXmlSchema,
                                   boolean minXmlSchema,
                                   boolean writeBeforeImage,
                                   boolean omitInitialValues)
       {
          if (!target.configureSupportedTargets(TargetData.TD_WRITE_XML,
                                                TargetData.TD_WRITE_XML,
                                                LegacyResource.DATASET + " widget"))
          {
             return false;
          }

          if (target.getType().equalsIgnoreCase(TargetData.TYPE_HANDLE))
          {
             return writeToHandle((export) -> export.writeDataset(dataSet,
                                                                  schemaLocation,
                                                                  writeXmlSchema,
                                                                  minXmlSchema,
                                                                  writeBeforeImage,
                                                                  omitInitialValues));
          }

          if (writeXmlSchema && schemaLocation != null && !schemaLocation.isEmpty())
          {
             ErrorManager.recordOrShowError(13031);
             // Cannot have both schema-location and write-schema arguments set.
             return false;
          }

          this.ds = dataSet;
          this.serializeHidden = this.ds.getSerializeHidden().booleanValue();

          String dsName1 = dataSet.name().toStringMessage();
          if (dsName1.isEmpty())
          {
             dsName1 = "ProDataSet";
          }
          this.dsName = dsName1;
          character _nsUri = dataSet.namespaceURI();
          character _nsPrefix = dataSet.namespacePrefix();
          this.nsUri = (_nsUri.isUnknown()) ? "" : _nsUri.toJavaType();
          this.nsPrefix = _nsPrefix.toJavaType();
          String nsNodeName = dataSet.getXmlNodeNameInternal();
          if (!tables(writeXmlSchema))
          {
             return false;
          }

          return write(() -> {
             if (!serializeHidden)
             {
                writer.writeStartElement((isEmpty(nsPrefix) ? "" : nsPrefix + ":") + nsNodeName);
                if (!nsUri.isEmpty())
                {
                   writer.writeAttribute(
                            "xmlns" + (isEmpty(nsPrefix) ? "" : ":" + nsPrefix),
                            nsUri);
                }
                writer.writeAttribute(ATTR_NS_XSI, NS_URI);
                xsiWritten = true;
                if (schemaLocation != null)
                {
                   if (nsUri.isEmpty())
                   {
                      writer.writeAttribute("xsi:noNamespaceSchemaLocation", schemaLocation);
                   }
                   else
                   {
                      writer.writeAttribute("xsi:schemaLocation", nsUri + " " + schemaLocation);
                   }
                }
                lineBreak();
                ++indentLevel;
             }

             if (writeXmlSchema)
             {
                if (serializeHidden)
                {
                   this.writeXmlSchema = () -> {
                      if (!writeDatasetSchemaImpl(minXmlSchema, !omitInitialValues, nsNodeName))
                      {
                         throw new XMLStreamException("Failed to write XML schema");
                      }
                   };
                }
                else if (!writeDatasetSchemaImpl(minXmlSchema, !omitInitialValues, nsNodeName))
                {
                   return false;
                }
             }

             if (writeBeforeImage)
             {
                // CA: my testing shows these are NOT emitted in the normal cases; who decides that
                // these are needed?

                indent();
                writer.writeStartElement("prods:datasetChanges");
                writer.writeAttribute("xmlns:prods",
                                      "urn:schemas-progress-com:xml-dataset-changes:0001");
                writer.writeAttribute(ATTR_NS_XSI, NS_URI);
                lineBreak();
                ++indentLevel;
                indent();

                writer.writeStartElement(nsNodeName == null ? dsName : nsNodeName);
                if (!nsUri.isEmpty())
                {
                   writer.writeAttribute("xmlns", nsUri);
                }
                writer.writeAttribute(ATTR_NS_XSI, NS_URI);
                lineBreak();
                ++indentLevel;
             }

             if (!serializeDsTables(writeBeforeImage, omitInitialValues))
             {
                throw new XMLStreamException("Failed to serialize dataset tables");
             }

             if (writeBeforeImage)
             {
                writeEndElement(); // ProDataSet
             }

             if (writeBeforeImage)
             {
                writeElement("prods:before", NOOP, () -> {
                   int ttCount = ds.numBuffers().intValue();
                   for (int k = 0; k < ttCount; k++)
                   {
                      BufferImpl after = (BufferImpl) dataSet.getBufferByIndex(k + 1);
                      if (!after.isAfterBuffer())
                      {
                         continue;
                      }
                      BufferImpl before = (BufferImpl) after.beforeBufferNative();
                      TemporaryBuffer buffer = (TemporaryBuffer) before.buffer();
                      if (buffer.count() <= 0)
                      {
                         continue;
                      }

                      String bufName = after.name().toStringMessage();
                      ttNsPrefix = after.namespacePrefix().toJavaType();
                      String ttNsUri = after.namespaceURI().isUnknown() ? "" : after.namespaceURI().toJavaType();
                      if (ttNsUri.isEmpty() || ttNsUri.equals(nsUri))
                      {
                         ttNsPrefix = nsPrefix;
                      }
                      if (serializeTempTable(null, buffer, bufName, ttNsUri,
                               omitInitialValues, false, writeBeforeImage, true) < 0)
                      {
                         throw new XMLStreamException("Failed to serialize [" + bufName + "]");
                      }
                   }
                });
             }

             writeProdErrors();

             if (writeBeforeImage)
             {
                writeEndElement(); // prods:datasetChanges
             }

             if (!serializeHidden)
             {
                writeEndElement(); // ProDataSet
             }
             return true;
          }, TargetData.TD_WRITE_XML,
             LegacyResource.DATASET + " widget");
       }

       /**
        * Serialize DataSet tables
        *
        * @param   writeBeforeImage
        *          Not currently honored.
        * @param   omitInitialValues
        *          If {@code true}, record values which match default initial values will be omitted
        *          from serialized output, else they will be included. Not currently honored.
        * @return  <code>true</code> if at least of the tables has changes
        */
       private boolean serializeDsTables(boolean writeBeforeImage, boolean omitInitialValues)
       {
          int ttCount = ds.numBuffers().intValue();
          boolean hasChanges = false;
          nextBuffer: for (int k = 0; k < ttCount; k++)
          {
             BufferImpl after = ds.getBufferByIndex(k + 1);
             List<DataRelation> rels = ds.getRelations(after, false, true, true);
             for (DataRelation rel : rels)
             {
                if (rel.isNested().getValue() || rel.isParentIdRelation().getValue())
                {
                   // if [after] is a child buffer in a active nested relation, its data was (or
                   // will be serialized with the parent)
                   continue nextBuffer;
                }
             }

             TemporaryBuffer buffer = (TemporaryBuffer) after.buffer();

             ttNsPrefix = after.namespacePrefix().toJavaType();
             rootNsUri = after.namespaceURI().isUnknown() ? "" : after.namespaceURI().toJavaType();
             if (rootNsUri.isEmpty() || rootNsUri.equals(nsUri))
             {
                ttNsPrefix = nsPrefix;
             }
             rootNsPrefix = ttNsPrefix;
             if (serializeTempTable(null, buffer, after.getXmlNodeName().toJavaType(), rootNsUri,
                      omitInitialValues, false, writeBeforeImage, false) < 0)
             {
                 return false;
             }
             if (serializeHidden)
             {
                ++indentLevel;
             }

             if (!hasChanges && after.isAfterBuffer())
             {
                BufferImpl before = (BufferImpl) after.beforeBufferNative();
                TemporaryBuffer beforeBuffer = (TemporaryBuffer) before.buffer();
                int count = beforeBuffer.count();
                if (count > 0)
                {
                   hasChanges = true;
                }
             }
          }
          return true;
       }
       /**
        * Write errors.
        * @throws XMLStreamException
        *         on Error
        */
       private void writeProdErrors() throws XMLStreamException
       {
          if (!prodsErrors.isEmpty())
          {
             writeElement("prods:errors", NOOP, () -> {
                Set<Map.Entry<String, Pair<String, String>>> errors = prodsErrors.entrySet();
                for (Map.Entry<String, Pair<String, String>> error : errors)
                {
                   writeElement(error.getValue().getLeft(), () -> {
                      writer.writeAttribute("prods:id", error.getKey());
                      writer.writeAttribute("prods:error", error.getValue().getRight());
                   });
                }
             });

             prodsErrors.clear();
          }
       }

       /**
        * Constructor which immediately performs the export of a TEMP-TABLE.
        *
        * @param   buffer
        *          Temp-table buffer from which data is exported.
        * @param   schemaLocation
        *          Path to XML schema (not currently used).
        * @param   writeXmlSchema
        *          {@code true} to write XML schema information; {@code false} to write only data.
        *          If {@code true}, schema will be written to {@code schemaLocation}, if provided;
        *          if not provided, schema will be embedded in XML output. Not currently honored.
        * @param   minXmlSchema
        *          {@code true} to write minimal schema information; {@code false} to write verbose
        *          schema information. Ignored if {@code writeXmlSchema} is {@code false}. Not
        *          currently honored.
        * @param   writeBeforeImage
        *          Not currently honored.
        * @param   omitInitialValues
        *          If {@code true}, record values which match default initial values will be omitted
        *          from serialized output, else they will be included. Not currently honored.
        *
        * @return  {@code true} on success. Otherwise the error is already dispatched to
        *          {@code ErrorManager} before returning.
        */
       public boolean writeTempTable(TemporaryBuffer buffer,
                                     String schemaLocation,
                                     boolean writeXmlSchema,
                                     boolean minXmlSchema,
                                     boolean writeBeforeImage,
                                     boolean omitInitialValues)
       {
          if (!target.configureSupportedTargets(TargetData.TD_WRITE_XML,
                                                TargetData.TD_WRITE_XML,
                                                LegacyResource.TEMP_TABLE + " widget"))
          {
             return false;
          }

          if (target.getType().equalsIgnoreCase(TargetData.TYPE_HANDLE))
          {
             return writeToHandle((export) -> export.writeTempTable(buffer,
                                                                    schemaLocation,
                                                                    writeXmlSchema,
                                                                    minXmlSchema,
                                                                    writeBeforeImage,
                                                                    omitInitialValues));
          }

          if (writeXmlSchema && schemaLocation != null && !schemaLocation.isEmpty())
          {
             ErrorManager.recordOrShowError(13031);
             // Cannot have both schema-location and write-schema arguments set.
             ErrorManager.recordOrShowError(4065, false, "WRITE-XML", "TEMP-TABLE widget");
             // **The <attribute> attribute on the <widget id> has invalid arguments.
             return false;
          }

          TempTableSchema schema = new TempTableSchema(buffer, false);
          if (schemaLocation != null && !schemaLocation.isEmpty())
          {
             // check the existence of incompatible fields
             String unsupportedType = Util.checkSupportedTypes(schema);
             if (unsupportedType != null)
             {
                ErrorManager.recordOrShowError(13078, unsupportedType);
                // Unsupported data type for XML Serialization: <type>.
                ErrorManager.recordOrShowError(13066, schema.getTableName());
                // Unable to write XML Schema for temp-table <table-name>.
                ErrorManager.recordOrShowError(13097);
                // Write schema failed for WRITE-XMLSCHEMA.
                return false;
             }
          }

          return write(() -> {
             Buffer currentBuffer = (Buffer) buffer.getDMOProxy();

             ttNsPrefix = currentBuffer.namespacePrefix().toJavaType();
             String nsUri = currentBuffer.namespaceURI().isUnknown() ? "" : currentBuffer.namespaceURI().toJavaType();
             String nodeName = currentBuffer.getXmlNodeName().toJavaType();

             String xmlNodeName = (isEmpty(ttNsPrefix))? nodeName : (ttNsPrefix + ":" + nodeName);

             writer.writeStartElement(xmlNodeName);
             if (!nsUri.isEmpty())
             {
                writer.writeAttribute(
                         "xmlns" + (isEmpty(ttNsPrefix) ? "" : ":" + ttNsPrefix),
                         nsUri);
             }
             writer.writeAttribute(ATTR_NS_XSI, NS_URI);
             xsiWritten = true;

             if (schemaLocation != null && !schemaLocation.isEmpty())
             {
                if (nsUri.isEmpty())
                {
                   writer.writeAttribute("xsi:noNamespaceSchemaLocation", schemaLocation);
                }
                else
                {
                   writer.writeAttribute("xsi:schemaLocation", nsUri + " " + schemaLocation);
                }
             }
             lineBreak();
             ++indentLevel;

             BufferImpl dmoProxy = (BufferImpl) buffer.getDMOProxy();

             if (writeXmlSchema)
             {
                if (!writeTableSchema(dmoProxy, schema, minXmlSchema, !omitInitialValues))
                {
                   return false;
                }
             }

             String rowName = xmlNodeName + "Row";
             if (serializeTempTable(null, buffer, rowName, nsUri,
                                     omitInitialValues, dmoProxy.isBeforeBuffer(), false, false) < 0)
             {
                return false;
             }

             writeEndElement();

             return true;
          }, TargetData.TD_WRITE_XML,
             LegacyResource.TEMP_TABLE + " widget");
       }

       /**
        * Performs the export of a DATASET schema.
        *
        * @param   dataSet
        *          The {@code DatSet} to be described.
        * @param   minSchema
        *          {@code true} to write minimal schema information; {@code false} to write verbose
        *          schema information. Ignored if {@code writeXmlSchema} is {@code false}. Not
        *          currently honored.
        * @param   noInitial
        *          If {@code true}, record values which match default initial values will be omitted
        *          from serialized output, else they will be included. Not currently honored.
        *
        * @return  {@code true} on success. Otherwise, the error is already dispatched to
        *          {@code ErrorManager} before returning.
        */
       public boolean writeDatasetSchema(DataSet dataSet, boolean minSchema, boolean noInitial)
       {
          if (!target.configureSupportedTargets(TargetData.TD_WRITE_XMLSCHEMA,
                                                TargetData.TD_WRITE_XMLSCHEMA,
                                                LegacyResource.DATASET + " widget"))
          {
             return false;
          }

          if (target.getType().equalsIgnoreCase(TargetData.TYPE_HANDLE))
          {
             return writeToHandle((export) -> export.writeDatasetSchema(dataSet, minSchema, noInitial));
          }

          this.ds = dataSet;
          this.serializeHidden = this.ds.getSerializeHidden().booleanValue();
          String dsName1 = dataSet.name().toStringMessage();
          if (dsName1.isEmpty())
          {
             dsName1 = "ProDataSet";
          }
          this.dsName = dsName1;
          character _nsUri = dataSet.namespaceURI();
          character _nsPrefix = dataSet.namespacePrefix();
          this.nsUri = (_nsUri.isUnknown()) ? "" : _nsUri.toJavaType();
          this.nsPrefix = _nsPrefix.toJavaType();
          String nodeNameStr = dataSet.getXmlNodeNameInternal();
          if (!tables(true))
          {
             return false;
          }
    //      this.isSchemaOnly = true;

          return write(() -> writeDatasetSchemaImpl(minSchema, !noInitial, nodeNameStr),
                       TargetData.TD_WRITE_XMLSCHEMA,
                       LegacyResource.DATASET + " widget");
       }

       /**
        * Extract tables's data from the DATASET
        *
        * @param forSchema
        *        Flag indicating that data is extracted for WRITE-XMLSCHEMA
        * @return <code>true</code> if OK.
        */
       private boolean tables(boolean forSchema)
       {
          ds.getBuffers().stream().
                   map(BufferImpl::buffer).
                   map(buffer -> new TempTableSchema(buffer, false)).
                   forEach(tts -> tables.put(tts.getTableName(), tts));

          tablesByNs.put(nsUri, new ArrayList<>());
          tables.values().stream().forEach(
                   ts -> {
                      String ns = ts.namespaceURI();
                      tablesByNs.computeIfAbsent("".equals(ns) ? nsUri : ns,
                               n -> new ArrayList<>()).add(ts);
                      });

          if (!Serializator.TYPE_FILE.equals(target.getType().toUpperCase()) &&
              tablesByNs.size() > 1 && forSchema)
          {
             ErrorManager.recordOrShowError(13095,false, false);
             return false;
          }
          String nsPrefix = ds.namespacePrefix().getValue();
          if ( nsPrefix == null || "".equals(nsPrefix))
          {
             nsPrefix = "".equals(nsUri) ? "" : "tns";
          }
          prefixByNs.put(nsUri, nsPrefix);
          prefixByNs.putIfAbsent("", nsPrefix);

          int n = 1;
          for (Map.Entry<String, List<TempTableSchema>> e: tablesByNs.entrySet())
          {
             if (e.getValue().isEmpty()) // this can be for nsUri only
             {
                continue;
             }
             String tns = "tns" + String.valueOf(n++);
             String prefix = e.getValue().get(0).namespacePrefix();
             prefixByNs.putIfAbsent(e.getKey(), isEmpty(prefix) ? ("".equals(nsUri) ? "" : tns) : prefix);
          }

          ds.getRelations().
             filter(r -> r.isActive().booleanValue() &&
                         (r.isNested().booleanValue() || r.isParentIdRelation().booleanValue())).
             forEach(r -> {
                String parent = r.getParentTable();
                String child = r.getChildTable();
                parentByChild.put(child, parent);
                childByParent.computeIfAbsent(parent, p -> new ArrayList<>()).add(child);
                if (r.getForeignKeyHidden().toJavaType())
                {
                   childFieldsByChild.put(child, new HashSet<>(Arrays.asList(r.getRelationFields(false))));
                }
             });
          return !tables.isEmpty();
       }

       /**
        * Performs the export of a TEMP-TABLE schema.
        *
        * @param   buffer
        *          A buffer of a {@code TempTable} to be described.
        * @param   minSchema
        *          {@code true} to write minimal schema information; {@code false} to write verbose
        *          schema information. Ignored if {@code writeXmlSchema} is {@code false}.
        * @param   noInitial
        *          If {@code true}, record values which match default initial values will be omitted
        *          from serialized output, else they will be included. Not currently honored.
        *
        * @return  {@code true} on success. Otherwise, the error is already dispatched to
        *          {@code ErrorManager} before returning.
        */
       public boolean writeTableSchema(TemporaryBuffer buffer, boolean minSchema, boolean noInitial)
       {
          if (!target.configureSupportedTargets(TargetData.TD_WRITE_XMLSCHEMA,
                                                TargetData.TD_WRITE_XMLSCHEMA,
                                                LegacyResource.TEMP_TABLE + " widget"))
          {
             return false;
          }

          if (target.getType().equalsIgnoreCase(TargetData.TYPE_HANDLE))
          {
             return writeToHandle((export) -> export.writeTableSchema(buffer, minSchema, noInitial));
          }

    //      this.isSchemaOnly = true;
          return write(() -> writeTableSchema(
                (BufferImpl) buffer.getDMOProxy(),
                new TempTableSchema(buffer, false),
                minSchema,
                !noInitial),
                TargetData.TD_WRITE_XMLSCHEMA,
                LegacyResource.TEMP_TABLE + " widget");
       }

       /**
        * Writes a single record.
        *
        * @param   bufferImpl
        *          The buffer which holds the record to be exported.
        * @param   omitInitial
        *          If {@code true} the unmodified fields (those that are equals to the declared initial value)
        *          are skipped in order to minimize the output.
        *
        * @return  {@code true} if operation is successful and {@code false} if an error occurred.
        */
       public boolean writeRecord(BufferImpl bufferImpl, boolean omitInitial)
       {
          if (!target.configureSupportedTargets(TargetData.TD_WRITE_XML,
                                                TargetData.TD_WRITE_XML,
                                                LegacyResource.BUFFER + " widget"))
          {
             return false;
          }

          TemporaryBuffer tempBuffer = (TemporaryBuffer) bufferImpl.buffer();
          TempTableSchema schema = new TempTableSchema(tempBuffer, false);

          String unsupportedType = Util.checkSupportedTypes(schema);
          if (unsupportedType != null)
          {
             ErrorManager.recordOrShowError(13078, unsupportedType);
             // Unsupported data type for XML Serialization: <type>.
             ErrorManager.recordOrShowError(13093);
             // Write temp-table data failed for WRITE-XML.
             return false;
          }

          String tableName = schema.getXmlName();
          TempRecord dmo = tempBuffer.getCurrentRecord();
          return write(() -> {
             XmlElementWriter attrWriter = () -> {
                if (!xsiWritten)
                {
                   writer.writeAttribute(ATTR_NS_XSI, NS_URI);
                }
                try
                {
                   // write all columns which must be serialized as attributes first, while the start element for the
                   // row is open
                   writeColumns(dmo, schema.attributeColumns(), false, omitInitial);
                }
                catch (IllegalAccessException | InvocationTargetException e)
                {
                   throw new XMLStreamException(e);
                }
             };
             if (schema.textColumns().iterator().hasNext())
             {
                writeTextElement(tableName, attrWriter, () -> {
                   try
                   {
                      writeColumns(dmo, schema.textColumns(), false, omitInitial);
                   }
                   catch (IllegalAccessException | InvocationTargetException e)
                   {
                      throw new XMLStreamException(e);
                   }
                });
             }
             else
             {
                writeElement(tableName, attrWriter, () -> {
                   try
                   {
                      // write all columns which must be serialized to elements as separate XML elements
                      writeColumns(dmo, schema.elementColumns(), false, omitInitial);
                   }
                   catch (IllegalAccessException | InvocationTargetException e)
                   {
                      throw new XMLStreamException(e);
                   }
                });

             }
             return true;
          }, TargetData.TD_WRITE_XML,
             LegacyResource.BUFFER + " widget");
       }

       /**
        * Export to a handle.  Only {@link XDocument X-Document} is currently supported.
        *
        * @param    writer
        *           The writer function.
        *
        * @return   <code>true</code> if the export was successful.
        */
       private boolean writeToHandle(Function<XmlExport, Boolean> writer)
       {
          longchar lc = new longchar("");
          TargetData td = new TargetData("longchar", lc);
          XmlExport export = new XmlExport(td, format, encoding);
          boolean res = writer.apply(export);

          if (res)
          {
             XDocumentImpl xdoc = (XDocumentImpl) ((handle) target.getDestination()).get();

             boolean[] res2 = new boolean[1];
             ErrorManager.nestedSilent(() -> res2[0] = xdoc.load("longchar", lc, false).booleanValue());

             return res2[0];
          }
          return false;
       }

       /**
        * Performs the export of a TEMP-TABLE schema.
        *
        * @param   buffer
        *          A buffer of a {@code TempTable} to be described.
        * @param   schema
        *          The schema of the TEMP-TABLE.
        * @param   minSchema
        *          {@code true} to write minimal schema information; {@code false} to write verbose
        *          schema information. Ignored if {@code writeXmlSchema} is {@code false}. Not
        *          currently honored.
        * @param   defaultValues
        *          If {@code true}, record values which match default initial values will be omitted
        *          from serialized output, else they will be included.
        *          NOTE: actually setting [defaultValues] to [TRUE] which is the [omit-initial-values] from
        *                ABL will cause some extra info to be added in field description (see + attributes):
        *                <pre>{@code
        *                   <xsd:element name="f11" type="xsd:string" nillable="true"  +minOccurs="0" +default=""/>
        *                   <xsd:element name="f12" type="xsd:int" nillable="true"     +minOccurs="0" +default="0"/>
        *                   <xsd:element name="f13" type="xsd:date" nillable="true"    +minOccurs="0" +prodata:initial="prodata:unknown"/>
        *                   <xsd:element name="f14" type="xsd:decimal" nillable="true" +minOccurs="0" +default="0.0"/>}
        *                </pre>
        *
        * @return  {@code true} on success. Otherwise the error is already dispatched to
        *          {@code ErrorManager} before returning.
        *
        * @throws  XMLStreamException
        *          If XML-related issued occur.
        */
       private boolean writeTableSchema(BufferImpl buffer,
                                        TempTableSchema schema,
                                        boolean minSchema,
                                        boolean defaultValues)
       throws XMLStreamException
       {
          return writeTableSchema(buffer, schema, minSchema, defaultValues, false);
       }
       /**
        * Performs the export of a TEMP-TABLE schema.
        *
        * @param   buffer
        *          A buffer of a {@code TempTable} to be described.
        * @param   schema
        *          The schema of the TEMP-TABLE.
        * @param   minSchema
        *          {@code true} to write minimal schema information; {@code false} to write verbose
        *          schema information. Ignored if {@code writeXmlSchema} is {@code false}. Not
        *          currently honored.
        * @param   defaultValues
        *          If {@code true}, record values which match default initial values will be omitted
        *          from serialized output, else they will be included.
        *          NOTE: actually setting [defaultValues] to [TRUE] which is the [omit-initial-values] from
        *                ABL will cause some extra info to be added in field description (see + attributes):
        *                <pre>{@code
        *                   <xsd:element name="f11" type="xsd:string" nillable="true"  +minOccurs="0" +default=""/>
        *                   <xsd:element name="f12" type="xsd:int" nillable="true"     +minOccurs="0" +default="0"/>
        *                   <xsd:element name="f13" type="xsd:date" nillable="true"    +minOccurs="0" +prodata:initial="prodata:unknown"/>
        *                   <xsd:element name="f14" type="xsd:decimal" nillable="true" +minOccurs="0" +default="0.0"/>}
        *                </pre>
        * @param   inDataset
        *          If {@code true}, this is part of a dataset SCHEMA and additional attributes will
        *          be written.
        *
        * @return  {@code true} on success. Otherwise the error is already dispatched to
        *          {@code ErrorManager} before returning.
        *
        * @throws  XMLStreamException
        *          If XML-related issued occur.
        */
       private boolean writeTableSchema(BufferImpl buffer,
                                        TempTableSchema schema,
                                        boolean minSchema,
                                        boolean defaultValues,
                                        boolean inDataset)
       throws XMLStreamException
       {
          String tableName = schema.getTableName();
          String legacyName = schema.getName();
          AbstractTempTable tempTable = (AbstractTempTable) buffer.buffer().getParentTable();
          String xmlNodeName = tempTable.getXmlNodeName().toJavaType();
          String nsPrefix = tempTable.namespacePrefix().toJavaType();
          String nsUri = tempTable.namespaceURI().toJavaType();
          boolean isDataSet = inDataset || buffer.isBeforeBuffer() || buffer.isAfterBuffer();

          if (!inDataset && nsUri != null && !nsUri.isEmpty() && (nsPrefix == null || nsPrefix.isEmpty()))
          {
             nsPrefix = "tns";
          }
          writeSchema(nsPrefix, nsUri);
          {
             writeTableElement(schema, minSchema, tableName, legacyName, xmlNodeName,
                      tempTable.namespacePrefix().toJavaType(), isDataSet);
             {
                writeStartElement("xsd:complexType");
                {
                   writeStartElement("xsd:sequence");
                   {
                      String rowName = (xmlNodeName == null ? tableName : xmlNodeName) +
                               (inDataset ? "" : "Row");
                      if (!writeTableSchemaImpl(buffer, schema, rowName,
                               defaultValues, minSchema, inDataset))
                      {
                         return false;
                      }
                   }
                   writeEndElement(); // xsd:sequenc
                }
                writeEndElement(); // xsd:complexType

                // in case of standalone TEMP-TABLE schema serialization unique indexes go here:
                if (!external && /*!minSchema &&*/ this.ds == null && !indexes.isEmpty())
                {
                   for (Map.Entry<Integer, Pair<String, TableMapper.LegacyIndexInfo>> indexEntry :
                        indexes.entrySet())
                   {
                      Pair<String, TableMapper.LegacyIndexInfo> idxPair = indexEntry.getValue();
                      TableMapper.LegacyIndexInfo index = idxPair.getRight();
                      if (!index.isUnique())
                      {
                         continue; // only UNIQUE indexes here
                      }

                      indent();
                      writer.writeStartElement("xsd:unique");
                      writer.writeAttribute("name", index.getLegacyName());
                      if (index.isEffectivePrimary())
                      {
                         writer.writeAttribute("prodata:primaryIndex", "true");
                      }
                      if (index.isWord())
                      {
                         writer.writeAttribute("prodata:wordIndex", "true");
                      }
                      lineBreak();
                      ++indentLevel;
                      {
                         indent();
                         writer.writeEmptyElement("xsd:selector");
                         String idxName = (isEmpty(nsPrefix))
                               ? (".//" + idxPair.getLeft())
                               : (".//" + nsPrefix + ":" + idxPair.getLeft());
                         writer.writeAttribute("xpath", idxName);
                         lineBreak();

                         String[] comps = index.getComponents();
                         boolean[] dirs = index.getComponentsDirection();
                         for (int i = 0; i < comps.length; i++)
                         {
                            indent();
                            writer.writeEmptyElement("xsd:field");
                            String compName = (isEmpty(nsPrefix)) ? comps[i] : (nsPrefix + ":" + comps[i]);
                            writer.writeAttribute("xpath", compName);
                            if (!dirs[i])
                            {
                               writer.writeAttribute("prodata:descending", "true");
                            }
                            lineBreak();
                         }
                      }
                      writeEndElement(); // prodata:index
                   }
                }
             }

             writeEndElement();  // xsd:element

             boolean hasIndexes = !external && !minSchema && this.ds == null
                                  /*&& !this.isSchemaOnly*/ && !indexes.isEmpty();
             int idxCounter = 0;
             if (hasIndexes)
             {
                idxCounter = indexes.size();
                for (Map.Entry<Integer, Pair<String, TableMapper.LegacyIndexInfo>> pair : indexes.entrySet())
                {
                   if (pair.getValue().getRight().isUnique())
                   {
                      // decrement counter for unique indexes. The remainder is the count of non-unique indexes
                      --idxCounter;
                   }
                }
             }

             // in case of standalone TEMP-TABLE schema serialization non unique indexes go here:
             if (hasIndexes && idxCounter > 0)
             {
                writeStartElement("xsd:annotation");
                {
                   writeStartElement("xsd:appinfo");
                   {
                      for (Map.Entry<Integer, Pair<String, TableMapper.LegacyIndexInfo>> indexEntry :
                            indexes.entrySet())
                      {
                         Pair<String, TableMapper.LegacyIndexInfo> idxPair = indexEntry.getValue();
                         TableMapper.LegacyIndexInfo index = idxPair.getRight();
                         if (index.isUnique())
                         {
                            continue; // only NON-unique indexes here
                         }

                         indent();
                         writer.writeStartElement("prodata:index");
                         {
                            writer.writeAttribute("name", index.getLegacyName());
                            if (index.isEffectivePrimary())
                            {
                               writer.writeAttribute("prodata:primaryIndex", "true");
                            }
                            if (index.isWord())
                            {
                               writer.writeAttribute("prodata:wordIndex", "true");
                            }
                            lineBreak();
                            ++indentLevel;
                            {
                               indent();
                               writer.writeEmptyElement("prodata:table");
                               writer.writeAttribute("name", legacyName);
                               lineBreak();

                               String[] comps = index.getComponents();
                               boolean[] dirs = index.getComponentsDirection();
                               for (int i = 0; i < comps.length; i++)
                               {
                                  indent();
                                  writer.writeEmptyElement("prodata:field");
                                  writer.writeAttribute("name", comps[i]);
                                  if (!dirs[i])
                                  {
                                     writer.writeAttribute("prodata:descending", "true");
                                  }
                                  lineBreak();
                               }
                            }
                         }
                         writeEndElement(); // prodata:index
                      }
                   }
                   writeEndElement(); // xsd:appinfo
                }
                writeEndElement(); // xsd:sequence
             }
          }
          if (!external)
          {
             writeEndElement(); // xsd:schema
          }

          return true;
       }

       /**
        * Write start of the element.
        *
        * @param name
        *       element name.
        */
       private void writeStartElement(String name)
       throws XMLStreamException
       {
          indent();
          try
          {
             writer.writeStartElement(name);
          }
          catch (XMLStreamException e)
          {
             LOG.severe("", e);
          }
          lineBreak();
          ++indentLevel;
       }

       /**
        * Write the element.
        * @param bodyWriter
        *        body writer
        * @param attrWriter
        *        attributes' writer
        */
       private void writeComplexType(XmlElementWriter bodyWriter, XmlElementWriter attrWriter)
       throws XMLStreamException
       {
          indent();
          writer.writeStartElement("xsd:complexType");
          lineBreak();
          ++indentLevel;
          {
             indent();
             writer.writeStartElement("xsd:sequence");
             lineBreak();
             ++indentLevel;
             bodyWriter.run();
             writeEndElement(); // xsd:sequence
             attrWriter.run();
          }
          writeEndElement(); // xsd:complexType
       }

       /**
        * Write the element.
        *
        * @param name
        *       element name.
        * @param attrWriter
        *        attributes' writer
        */
       private void writeElement(String name, XmlElementWriter attrWriter)
       throws XMLStreamException
       {
          indent();
          writer.writeStartElement(name);
          attrWriter.run();
          writer.writeEndElement();
          lineBreak();
       }

       /**
        * Write the element with text node only.
        *
        * @param name
        *       element name.
        * @param textWriter
        *        attributes' writer
        */
       private void writeTextElement(String name, XmlElementWriter attrWriter, XmlElementWriter textWriter)
       throws XMLStreamException
       {
          indent();
          writer.writeStartElement(name);
          attrWriter.run();
          textWriter.run();
          writer.writeEndElement();
          lineBreak();

       }
       /**
        * Write the element.
        *
        * @param name
        *       element name.
        * @param attrWriter
        *        attributes' writer
        * @param bodyWriter
        *        body writer
        */
       private void writeElement(String name, XmlElementWriter attrWriter, XmlElementWriter bodyWriter)
       throws XMLStreamException
       {
          indent();
          writer.writeStartElement(name);
          attrWriter.run();
          lineBreak();
          ++indentLevel;
          bodyWriter.run();
          --indentLevel;
          indent();
          writer.writeEndElement();
          lineBreak();

       }

       /**
        * Write end of the element.
        */
       private void writeEndElement()
       throws XMLStreamException
       {
          --indentLevel;
          indent();
          try
          {
             writer.writeEndElement();
          }
          catch (XMLStreamException e)
          {
             LOG.severe("", e);
             throw e;
          }
          lineBreak();
       }

       /**
        * Write <code>element</code> element of a table.
        *
        * @param schema
        *        table schema.
        * @param minSchema
        *        {@code true} to write minimal schema information; {@code false} to write verbose
        *        schema information.
        * @param tableName
        *        table name.
        * @param legacyName
        *        table legacy name.
        * @param xmlNodeName
        *        table XML node name.
        * @param nsPrefix
        *        table XML prefix.
        * @param isDataSet
        *        flag indicating that the table is a part of a data-set.
        *
        * @throws XMLStreamException
        *         on error
        */
       private void writeTableElement(TempTableSchema schema, boolean minSchema,
                String tableName,String legacyName, String xmlNodeName,
                String nsPrefix, boolean isDataSet)
       throws XMLStreamException
       {
          indent();
          writer.writeStartElement("xsd:element");
          writer.writeAttribute("name", xmlNodeName == null ? tableName : xmlNodeName);
          if (!external)
          {
             writer.writeAttribute("prodata:proTempTable", "true");
             // String.valueOf(isDataSet)); TODO: when is this attribute 'false' ?
          }
          if (isDataSet && !legacyName.equals(tableName))
          {
             writer.writeAttribute("prodata:tableName", legacyName);
          }

          if (!isEmpty(nsPrefix))
          {
             writer.writeAttribute("prodata:prefix", nsPrefix);
          }

          if (!minSchema && !schema.isNoUndo())
          {
             writer.writeAttribute("prodata:undo", "true"); // TODO: where this value came from ?
          }

          if (xmlNodeName != null && !xmlNodeName.equals(tableName))
          {
             writer.writeAttribute("prodata:tableName", tableName);
          }

          lineBreak();
          ++indentLevel;
       }

       /**
        * Write <code>schema</code> element of a table/data-set.
        *
        * @param nsPrefix
        *        XML prefix of a table/data-set.
        * @param nsUri
        *        XML namespace URI of a table/data-set.
        *
        * @throws XMLStreamException
        *         on error.
        */
       private void writeSchema(String nsPrefix, String nsUri)
       throws XMLStreamException
       {
          indent();
          writer.writeStartElement("xsd:schema");
          writer.writeAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
          writer.writeAttribute("xmlns", nsUri == null ? "" : nsUri);
          if (nsPrefix != null && !nsPrefix.isEmpty() && nsUri != null)
          {
             writer.writeAttribute("xmlns:" + nsPrefix, nsUri);
             writer.writeAttribute("targetNamespace", nsUri);
             writer.writeAttribute("elementFormDefault", "qualified");
          }
          writer.writeAttribute("xmlns:prodata", "urn:schemas-progress-com:xml-prodata:0001");
          for (Map.Entry<String, String> e: extSchemas.entrySet())
          {
             String ns = e.getKey();
             if (!nsUri.equals(ns) && !"".equals(ns))
             {
                writer.writeAttribute("xmlns:" + prefixByNs.get(ns), ns);
             }
          }
          lineBreak();
          ++indentLevel;
          for (Map.Entry<String, String> e: extSchemas.entrySet())
          {
             indent();
             writer.writeStartElement("xsd:import");
             writer.writeAttribute("namespace", e.getKey());
             writer.writeAttribute("schemaLocation", e.getValue());
             writer.writeEndElement();
             lineBreak();
          }
       }

       /**
        * Writes a XML-SCHEMA to XML stream.
        *
        * @param   minSchema
        *          {@code true} to write minimal schema information; {@code false} to write verbose
        *          schema information. Ignored if {@code writeXmlSchema} is {@code false}. Not
        *          currently honored.
        * @param   defaultValues
        *          If {@code true}, the initial values for the fields will be written.
        * @param   nodeName
        *          The {@code DataSet}'s node name.
        *
        * @return  {@code true} on success. Otherwise the error is already dispatched to
        *          {@code ErrorManager} before returning.
        *
        * @throws XMLStreamException
        *          If XML-related issued occurs.
        */
       private boolean writeDatasetSchemaImpl(boolean minSchema,
                                              boolean defaultValues,
                                              String nodeName)
       throws XMLStreamException
       {
          for(String ns: tablesByNs.keySet())
          {
             if (nsUri.equals(ns))
             {
                continue;
             }
             if (!writeExternalSchema(ns, minSchema, defaultValues))
             {
                return false;
             }
          }
          writeElement("xsd:schema", () -> {
                writer.writeAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
                writer.writeAttribute("xmlns", nsUri);
                if (!nsUri.isEmpty())
                {
                   writer.writeAttribute("xmlns:" + prefixByNs.get(nsUri), nsUri);
                   writer.writeAttribute("targetNamespace", nsUri);
                   writer.writeAttribute("elementFormDefault", "qualified");
                }
                writer.writeAttribute("xmlns:prodata", "urn:schemas-progress-com:xml-prodata:0001");
                for (Map.Entry<String, String> e: prefixByNs.entrySet())
                {
                   String ns = e.getKey();
                   if (!nsUri.equals(ns) && !"".equals(ns))
                   {
                      writer.writeAttribute("xmlns:" + e.getValue(), ns);
                   }
                }
             },
             () -> writeDsSchemaBody(nodeName, minSchema, defaultValues)
          );
          return true;
       }

       /**
        * Write DATA-SET schema body
        *
        * @param   minSchema
        *          {@code true} to write minimal schema information; {@code false} to write verbose
        *          schema information. Ignored if {@code writeXmlSchema} is {@code false}. Not
        *          currently honored.
        * @param   defaultValues
        *          If {@code true}, the initial values for the fields will be written.
        * @param   nodeName
        *          The {@code DataSet}'s node name.
        *
        * @throws XMLStreamException
        *          If XML-related issued occurs.
        */
       private void writeDsSchemaBody(String nodeName, boolean minSchema, boolean defaultValues)
       throws XMLStreamException
       {
          for (Map.Entry<String, String> e: extSchemas.entrySet())
          {
             writeElement("xsd:import", () -> {
                writer.writeAttribute("namespace", e.getKey());
                writer.writeAttribute("schemaLocation", e.getValue());
             });
          }
          if (!serializeHidden)
          {
             rootsWritten++;
             writeElement("xsd:element", () -> {
                   writer.writeAttribute("name", nodeName);
                   writer.writeAttribute("prodata:proDataSet", "true");
                   if (!isEmpty(nsPrefix))
                   {
                      writer.writeAttribute("prodata:prefix", nsPrefix);
                   }
                   if (!nodeName.equals(dsName) && dsName != null && !dsName.isEmpty())
                   {
                      writer.writeAttribute("prodata:datasetName", dsName);
                   }
                },
                () -> {
                   writeComplexType(() -> writeDsTables(minSchema, defaultValues), NOOP);
                   writeDatasetIndexes();
                   writeDataSetKeyRefs();
                });
          }
          else
          {
             writeDsTables(minSchema, defaultValues);
         }

          if (!minSchema || prodataRelationCount > 0 )
          {
             writeAnnotation(minSchema);
          }
       }

       /**
        * Write DATA-SET tables' schemas
        *
        * @param   minSchema
        *          {@code true} to write minimal schema information; {@code false} to write verbose
        *          schema information. Ignored if {@code writeXmlSchema} is {@code false}. Not
        *          currently honored.
        * @param   defaultValues
        *          If {@code true}, the initial values for the fields will be written.
        * @throws  XMLStreamException
        *          If XML-related issued occurs.
        */
       private void writeDsTables(boolean minSchema, boolean defaultValues)
       throws XMLStreamException
       {
          if (ds == null)
          {
             return;
          }
          int ttCount = ds.numBuffers().intValue();
          for (int k = 0; k < ttCount; k++)
          {
             BufferImpl after = (BufferImpl) ds.getBufferByIndex(k + 1);
             writeDsTableSchema(after, minSchema, defaultValues);
          }
       }

       /**
        * Write DATA-SET table schema
        *
        * @param   after
        *          table buffer.
        * @param   minSchema
        *          {@code true} to write minimal schema information; {@code false} to write verbose
        *          schema information. Ignored if {@code writeXmlSchema} is {@code false}. Not
        *          currently honored.
        * @param   defaultValues
        *          If {@code true}, the initial values for the fields will be written.
        *
        * @throws  XMLStreamException
        *          If XML-related issued occurs.
        */
      private void writeDsTableSchema(BufferImpl after, boolean minSchema,boolean defaultValues)
       throws XMLStreamException
       {
          TemporaryBuffer buffer = (TemporaryBuffer) after.buffer();
          TempTableSchema schema = new TempTableSchema(buffer, false);
          String tname =  schema.getName();
          if (!writtenSchemas.add(tname))
          {
             return;
          }
          if (!tablesByNs.get(nsUri).stream().
                   anyMatch(tts -> tname.equals(tts.getName())))
          {
             // already serialized to external schema
             if (!parentByChild.containsKey(tname)) // not a child of a nested relation
             {
                writeElement("xsd:element", () -> {
                   writer.writeAttribute("ref",
                            prefixByNs.get(schema.namespaceURI()) + ":" + tname);
                   writer.writeAttribute("minOccurs", "0");
                   writer.writeAttribute("maxOccurs", "unbounded");
                });
             }
          }
          else if (!parentByChild.containsKey(tname))
          {
             if (!writeTableSchemaImpl(
                 after, schema, schema.getTableName(), defaultValues, minSchema, true))
             {
                throw new XMLStreamException("Failed to write schema for table [" +
                            schema.getTableName() + "]");
             }
          }
       }

      /**
       * Write DATA-SET schema annotation
       *
       * @param   minSchema
       *          {@code true} to write minimal schema information; {@code false} to write verbose
       *          schema information. Ignored if {@code writeXmlSchema} is {@code false}. Not
       *          currently honored.
       *
       * @throws XMLStreamException
       *         If XML-related issued occurs.
       */
       private void writeAnnotation(boolean minSchema) throws XMLStreamException
       {
          if (prodataRelationCount > 0 || (/* !this.isSchemaOnly  && */ !indexes.isEmpty()))
          {
             indent();
             writer.writeStartElement("xsd:annotation");
             lineBreak();
             ++indentLevel;
             {
                indent();
                writer.writeStartElement("xsd:appinfo");
                lineBreak();
                ++indentLevel;
                {
    //                     if (!isSchemaOnly)
    //                     {
                   if(!minSchema)
                   {
                      for (Map.Entry<Integer, Pair<String, TableMapper.LegacyIndexInfo>> indexEntry :
                            indexes.entrySet())
                      {
                         Pair<String, TableMapper.LegacyIndexInfo> idxPair = indexEntry.getValue();
                         TableMapper.LegacyIndexInfo index = idxPair.getRight();
                         if (index.isUnique())
                         {
                            continue; // only NON-unique indexes here
                         }

                         indent();
                         writer.writeStartElement("prodata:index");
                         writer.writeAttribute("name", index.getLegacyName());
                         if (index.isEffectivePrimary())
                         {
                            writer.writeAttribute("prodata:primaryIndex", "true");
                         }
                         if (index.isWord())
                         {
                            writer.writeAttribute("prodata:wordIndex", "true");
                         }
                         lineBreak();
                         ++indentLevel;
                         {
                            indent();
                            writer.writeEmptyElement("prodata:table");
                            writer.writeAttribute("name", idxPair.getLeft());
                            lineBreak();

                            String[] comps = index.getComponents();
                            boolean[] dirs = index.getComponentsDirection();
                            for (int i = 0; i < comps.length; i++)
                            {
                               indent();
                               writer.writeEmptyElement("prodata:field");
                               writer.writeAttribute("name", comps[i]);
                               if (!dirs[i])
                               {
                                  writer.writeAttribute("prodata:descending", "true");
                               }
                               lineBreak();
                            }
                         }
                         writeEndElement(); // prodata:index
                      }
                   }
                }
                   for (int k = 0; k < relCount; k++)
                   {
                      DataRelation rel = (DataRelation) ds.getRelationNative(k + 1);// base-1
                      if (keyrefs.contains(rel))
                      {
                         continue; // this relation was serialized as a [xsd:keyref] above
                      }
                      if (rel.isParentIdRelation().getValue())
                      {
                         continue;
                      }

                      indent();
                      writer.writeEmptyElement("prodata:relation");
                      writer.writeAttribute("name", rel.name().toStringMessage());
                      if (rel.isNested().booleanValue())
                      {
                         writer.writeAttribute("prodata:nested", "true");
                      }
                      if (!rel.isActive().booleanValue())
                      {
                         writer.writeAttribute("prodata:notActive", "true");
                      }
                      if (rel.getForeignKeyHidden().toJavaType())
                      {
                         writer.writeAttribute("prodata:foreignKeyHidden", "true");
                      }
                      if (rel.isRecursive().booleanValue())
                      {
                         writer.writeAttribute("prodata:recursive", "true");
                      }
                      writer.writeAttribute("prodata:parent",
                                            rel.getParentBufferNative().name().toStringMessage());
                      writer.writeAttribute("prodata:child",
                                            rel.getChildBufferNative().name().toStringMessage());
                      writer.writeAttribute("prodata:relationFields", rel.getRelationFields().toStringMessage());
                      lineBreak();
                   }
                   writeEndElement(); // xsd:appinfo
                }
                writeEndElement(); // xsd:annotation
             }
       }

       /**
        * Write DATA-SET data relations.
        *
        * @throws XMLStreamException
        *         If XML-related issued occurs.
        */
       private void writeDataSetKeyRefs() throws XMLStreamException
       {
          // compute and dump all KEYREFs (relations which have the parent looked up by a unique index)
          relCount = ds.getNumRelations().intValue();
          prodataRelationCount = 0;
          for (int k = 0; k < relCount; k++)
          {
             DataRelation rel = (DataRelation) ds.getRelationNative(k + 1); // base-1
             if (!rel.isActive().booleanValue())
             {
                continue;
             }

             String[] parentFields = rel.getRelationFields(true);
             boolean isKeyref = false;
             String idxName = null;
             TableMapper.LegacyIndexInfo legacyIndexInfo = null;

             BufferImpl pBuff = (BufferImpl) rel.getParentBufferNative();
             TempTable pTempTable = (TempTable) pBuff.tableHandleNative();
             int j = 0;
             while (true)
             {
                legacyIndexInfo = TableMapper.getLegacyIndexInfo(pTempTable, j++);
                if (legacyIndexInfo == null)
                {
                   break; // no more indices
                }

                if (!legacyIndexInfo.isUnique())
                {
                   continue; // this is not unique, skip to next
                }

                // check fields
                String[] components = legacyIndexInfo.getComponents();
                if (components.length != parentFields.length)
                {
                   continue; // not same number of components, skip to next index
                }

                boolean match = true;
                for (int i = 0; i < components.length; i++)
                {
                   if (!components[i].equals(parentFields[i]))
                   {
                      match = false;
                      break;
                   }
                }
                if (!match)
                {
                   continue;  // this is unique but does not match fields, skip to next
                }

                // reaching this point means we have a candidate for keyref
                isKeyref = true;
                idxName = uidxUniqueNames.get(legacyIndexInfo);
                if (idxName == null)
                {
                   idxName = legacyIndexInfo.getLegacyName();
                }
                break;
             }

             if (isKeyref)
             {
                indent();
                String prefix1 = prefixByNs.get(nsUri);
                String prefix2 = prefixByNs.get(
                         tables.get(rel.getChildBufferNative().name().toJavaType()). namespaceURI()
                );
                if (!prefix1.isEmpty())
                {
                   prefix1 += ":";
                }
                if (!prefix2.isEmpty())
                {
                   prefix2 += ":";
                }

                writer.writeStartElement("xsd:keyref");
                writer.writeAttribute("name", rel.name().toJavaType());
                writer.writeAttribute("refer", prefix1 + idxName);
                if (rel.isNested().toJavaType())
                {
                   writer.writeAttribute("prodata:nested", "true");
                }
                if (rel.getForeignKeyHidden().toJavaType())
                {
                   writer.writeAttribute("prodata:foreignKeyHidden", "true");
                }
                lineBreak();
                ++indentLevel;
                {
                   indent();
                   writer.writeStartElement("xsd:selector");
                   writer.writeAttribute("xpath",
                            ".//" + prefix2 + rel.getChildBufferNative().name().toJavaType());
                   writer.writeEndElement();
                   lineBreak();

                   for (String field : rel.getRelationFields(false))
                   {
                      indent();
                      writer.writeStartElement("xsd:field");
                      writer.writeAttribute("xpath", prefix2 + field);
                      writer.writeEndElement();
                      lineBreak();
                   }
                }
                writeEndElement(); // xsd:unique

                keyrefs.add(rel);
             }
             else if (!rel.isParentIdRelation().getValue())
             {
                prodataRelationCount++;
             }
          }
       }

       /**
        * Write DATA-SET indexes
        *
        * @throws XMLStreamException
        *         If XML-related issued occurs.
        */
       private void writeDatasetIndexes() throws XMLStreamException
       {
          uidxByUniqueNames.clear();
          uidxUniqueNames.clear();
          // dump all UNIQUE indexes here
          for (Map.Entry<Integer, Pair<String, TableMapper.LegacyIndexInfo>> indexEntry : indexes.entrySet())
          {
             Pair<String, TableMapper.LegacyIndexInfo> idxPair = indexEntry.getValue();
             TableMapper.LegacyIndexInfo index = idxPair.getRight();
             if (!index.isUnique())
             {
                continue; // only UNIQUE indexes here
             }

             indent();
             String name = index.getLegacyName();
             String prodataIndexName = null;
             if (uidxByUniqueNames.containsKey(name))
             {
                prodataIndexName = name;
                name = idxPair.getKey() + "_" + name;
             }
             uidxByUniqueNames.put(name, index);

             writer.writeStartElement("xsd:unique");
             writer.writeAttribute("name", name);
             if (prodataIndexName != null)
             {
                uidxUniqueNames.put(index, name);
                writer.writeAttribute("prodata:indexName", prodataIndexName);
             }
             if (index.isEffectivePrimary())
             {
                writer.writeAttribute("prodata:primaryIndex", "true");
             }
             if (index.isWord())
             {
                writer.writeAttribute("prodata:wordIndex", "true");
             }
             lineBreak();
             ++indentLevel;
             {
                TempTableSchema tts = tables.get(idxPair.getLeft());
                String ttsNsUri = tts.namespaceURI();
                String prefix = prefixByNs.get(ttsNsUri);
                if (!prefix.isEmpty())
                {
                   prefix += ":";
                }
                indent();
                writer.writeEmptyElement("xsd:selector");
                writer.writeAttribute("xpath", ".//" + prefix + idxPair.getLeft());
                lineBreak();

                String[] comps = index.getComponents();
                boolean[] dirs = index.getComponentsDirection();
                for (int i = 0; i < comps.length; i++)
                {
                   indent();
                   writer.writeEmptyElement("xsd:field");
                   writer.writeAttribute("xpath", prefix + comps[i]);
                   if (!dirs[i])
                   {
                      writer.writeAttribute("prodata:descending", "true");
                   }
                   lineBreak();
                }
             }
             writeEndElement();
          }
       }

       /**
        * Write external schema for a namespace
        *
        * @param   ns
        *          namespace
        * @param   minSchema
        *          {@code true} to write minimal schema information; {@code false} to write verbose
        *          schema information. Ignored if {@code writeXmlSchema} is {@code false}. Not
        *          currently honored.
        * @param   defaultValues
        *          If {@code true}, the initial values for the fields will be written.
        *
        * @return name of the schema file
        *
        * @throws  XMLStreamException
        *          If XML-related issued occur.
        */
       private boolean writeExternalSchema(String ns,
                boolean minSchema, boolean defaultValues)
       throws XMLStreamException
       {
          LinkedBlockingQueue<String> childs = new LinkedBlockingQueue<>();
          Map<String, String> imports = new LinkedHashMap<>();
          tablesByNs.get(ns).stream().map(TempTableSchema::getName).forEach(childs::offer);
          for (String name = childs.poll(); name != null; name = childs.poll())
          {
             TempTableSchema tts = tables.get(name);
             String cns = tts.namespaceURI();
             if (!ns.equals(cns))
             {
                imports.putIfAbsent(cns, extSchemaName(cns));
             }
             else {
                List<String> clist = childByParent.get(name);
                if (clist != null)
                {
                   clist.stream().forEach(childs::offer);
                }
             }
          }
          String extSchemaName = extSchemaName(ns);
          extSchemas.put(ns, extSchemaName);
          Path path = Paths.get(((character) target.getDestination()).toJavaType());
          String extPath = path.getParent().toString() + "/" + extSchemaName;
          XmlExport export = new XmlExport(new TargetData("file", extPath), format, encoding);
          export.external = true;
          export.ds = ds;
          export.dsName = dsName;
          export.nsUri = nsUri;
          export.nsPrefix = nsPrefix;
          export.serializeHidden = serializeHidden;
          export.tablesByNs = tablesByNs;
          export.prefixByNs = prefixByNs;
          export.extSchemas = imports;
          export.childFieldsByChild = childFieldsByChild;
          boolean ok = export.write(
                   () -> {
                      boolean rc = true;
                      export.writeSchema(prefixByNs.get(ns), ns);
                      {
                         for(TempTableSchema tts: tablesByNs.get(ns))
                         {
                            TemporaryBuffer tb = (TemporaryBuffer)tts.buffer();
                            AbstractTempTable tempTable = (AbstractTempTable) tb.buffer().getParentTable();
                            String tableName = tts.getTableName();
                            String legacyName = tts.getName();
                            String xmlNodeName = tempTable.getXmlNodeName().toJavaType();
                            String nsUri = tts.namespaceURI();
                            String nsPrefix = tts.namespacePrefix();
                            export.writeTableElement(tts, minSchema, tableName, legacyName,
                                     xmlNodeName, nsPrefix, true);
                            {
                               rc = export.writeTableSchemaImpl((BufferImpl)tb.getDMOProxy(), tts,
                                        tts.getTableName(), defaultValues, minSchema, true);
                               export.writeEndElement(); // xsd:element
                               if (!rc)
                               {
                                  break;
                               }
                            }
                         }
                      }
                      export.writeEndElement(); // xsd:schema
                      return rc;
                   }, TargetData.TD_WRITE_XMLSCHEMA, LegacyResource.DATASET + " widget"
          );
          if (!ok)
          {
             return false;
          }
          this.indexes.putAll(export.indexes);
          this.uidxByUniqueNames.putAll(export.uidxByUniqueNames);
          this.uidxUniqueNames.putAll(export.uidxUniqueNames);

          return true;
       }

       /**
        * Get the name of the external schema for the namespace.
        *
        * @param ns
        *        namespace
        *
        * @return the name of the external schema for the namespace
        */
       private String extSchemaName(String ns)
       {
          String ttsn = tablesByNs.get(ns).get(0).getName();
          String fn = Paths.get(((character) target.getDestination()).toJavaType()).
                   getFileName().toString();
          return fn.substring(0, fn.length() - ".xsd".length()) + "_" + ttsn + ".xsd";
       }

       /**
        * Writes the XML-SCHEMA of a TEMP-TABLE to XML stream.
        *
        * @param   buffer
        *          The buffer that identifies the TEMP-TABLE to be described.
        * @param   schema
        *          The schema of the TEMP-TABLE.
        * @param   tableName
        *          The name of the table.
        * @param   defaultValues
        *          If {@code true} the initial values are written for the fields that declared them.
        * @param   minSchema
        *          {@code true} to write minimal schema information; {@code false} to write verbose
        *          schema information. Ignored if {@code writeXmlSchema} is {@code false}.
        * @param   inDataset
        *          If {@code true}, this is part of a dataset SCHEMA and additional attributes will
        *          be written.
        *
        * @return  {@code true} on success. Otherwise the error is already dispatched to
        *          {@code ErrorManager} before returning.
        *
        * @throws  XMLStreamException
        *          If XML-related issued occurs.
        */
       private boolean writeTableSchemaImpl(BufferImpl buffer,
                                            TempTableSchema schema,
                                            String tableName,
                                            boolean defaultValues,
                                            boolean minSchema,
                                            boolean inDataset)
       throws XMLStreamException
       {
          String legacyName = schema.getName();
          boolean isDataset = (ds != null);
          if (external)
          {
             writeTableColums(buffer, schema, tableName, defaultValues, minSchema, inDataset, isDataset);
          }
          else
          {
             if (serializeHidden && rootsWritten == 0)
             {
                rootTableName = tableName;
             }
             writeElement("xsd:element", () -> {
                writer.writeAttribute("name", tableName);
                if (serializeHidden && rootsWritten ==  0)
                {
                   String prefix =  !isEmpty(ttNsPrefix)? ttNsPrefix : nsPrefix;
                   if (!isEmpty(prefix) )
                   {
                      writer.writeAttribute("prodata:prefix", prefix);
                   }
                }
                if (!nested && rootsWritten++ > 0 || ds == null)
                {
                   writer.writeAttribute("minOccurs", "0");
                   writer.writeAttribute("maxOccurs", "unbounded");
                }
                if (isDataset && !legacyName.equals(tableName))
                {
                   writer.writeAttribute("prodata:tableName", legacyName);
                }
                if (inDataset && !minSchema && !schema.isNoUndo())
                {
                   writer.writeAttribute("prodata:undo", "true");
                }
                if (ds != null && buffer.isAfterBuffer())
                {
                   // using [buffer.beforeBuffer().unwrap().name()] will raise a [DeferredLegacyErrorException]
                   String beforeName = ((BufferImpl) buffer.beforeBufferNative()).doGetName();
                   writer.writeAttribute("prodata:beforeTable", beforeName);
                }
             },
             () -> {
                writeTableColums(buffer, schema, tableName, defaultValues, minSchema, inDataset, isDataset);
                if (serializeHidden && tableName.equals(rootTableName))
                {
                   writeDatasetIndexes();
                   writeDataSetKeyRefs();
                }
             });
          }

          // save now the index list to be processed later
          int keyPrefix = (this.ds == null) ? 0 : ds.getIndexOf(buffer);
          int i = 0;
          Iterator<TableMapper.LegacyIndexInfo> schemaIndexes = schema.getIndexes();
          while (schemaIndexes.hasNext())
          {
             TableMapper.LegacyIndexInfo index = schemaIndexes.next();
             this.indexes.put(keyPrefix * 100 + i,
                              new ImmutablePair<>(index.isUnique() ? tableName : legacyName, index));
             i++;
          }
          return true;
       }

       /**
        * Write table schema columns
        *
        * @param   buffer
        *          The buffer that identifies the TEMP-TABLE to be described.
        * @param   schema
        *          The schema of the TEMP-TABLE.
        * @param   tableName
        *          The name of the table.
        * @param   defaultValues
        *          If {@code true} the initial values are written for the fields that declared them.
        * @param   minSchema
        *          {@code true} to write minimal schema information; {@code false} to write verbose
        *          schema information. Ignored if {@code writeXmlSchema} is {@code false}.
        * @param   inDataset
        *          If {@code true}, this is part of a dataset SCHEMA and additional attributes will
        *          be written.
        * @param   isDataset
        *          Flag indicating that the table is a part of a dataset.
        *
        * @throws  XMLStreamException
        *          If XML-related issued occurs.
        */
       private void writeTableColums(BufferImpl buffer, TempTableSchema schema, String tableName,
                boolean defaultValues,  boolean minSchema, boolean inDataset, boolean isDataset)
       throws XMLStreamException
       {
          Iterable<TempTableSchema.Column> elements = schema.elementColumns();
          Iterable<TempTableSchema.Column> attributes = schema.attributeColumns();
          boolean hasAttrs = attributes.iterator().hasNext();
          SortedSet<Integer> fields = new TreeSet<>();
          if (hasAttrs)
          {
             ArrayList<TempTableSchema.Column> columns = schema.columns();
             for (int k = 0; k < columns.size(); k++)
             {
                TempTableSchema.Column column = columns.get(k);

                if (column.getNodeType() != XmlNodeType.HIDDEN)
                {
                   fields.add(column.getOrder());
                }
             }
          }
          writeComplexType(() -> {
             for (TempTableSchema.Column column : elements)
             {
                if (!writeColumnAttrs(schema, column, fields,
                         hasAttrs, defaultValues, minSchema, isDataset))
                {
                   throw new XMLStreamException("Failed to table element [" + column.getFieldName() + "]");
                }
                lineBreak();
             }

             if (buffer.isBeforeBuffer() && ds == null) // only for TEMP-TABLE:WRITE-XML(SCHEMA) API
             {
                writeBeforeBuffer();
             }

             DataSet ds = buffer._dataSet();
             if (inDataset && ds != null)
             {
                List<DataRelation> childRels = ds.getRelations(buffer, true, false, true);

                for (DataRelation dr : childRels)
                {
                   if (!dr.isNested().getValue() && !dr.isParentIdRelation().getValue())
                   {
                      continue;
                   }
                   BufferImpl childBuffer = (BufferImpl) dr.getChildBufferNative();
                   TemporaryBuffer child = (TemporaryBuffer) ((BufferReference) childBuffer).buffer();
                   TempTableSchema childSchema = new TempTableSchema(child, false);
                   if (schema.namespaceURI().equals(childSchema.namespaceURI()))
                   {
                      boolean ext = this.external;
                      Set<String> cFields = this.childFields;
                      this.external = false;
                      this.nested = ext;
                      String tname = childSchema.getTableName();
                      this.childFields = childFieldsByChild.getOrDefault(tname,
                                                             Collections.emptySet());
                      try
                      {
                         writeTableSchemaImpl(childBuffer,
                                  childSchema, tname,
                                  defaultValues, minSchema, inDataset);
                      }
                      finally
                      {
                         this.external = ext;
                         this.childFields = cFields;
                         this.nested = false;
                      }
                   }
                   else
                   {
                      writeElement("xsd:element", () -> {
                         writer.writeAttribute("ref",
                                  prefixByNs.get(childSchema.namespaceURI()) + ":" + childSchema.getName());
                         writer.writeAttribute("minOccurs", "0");
                         writer.writeAttribute("maxOccurs", "unbounded");
                      });

                   }
                }
             }
             if (!hasAttrs && tableName.equals(rootTableName))
             {
                writeDsTables(minSchema, defaultValues);
             }
          },
          () -> {
             for (TempTableSchema.Column column : attributes)
             {
                if (!writeColumnAttrs(schema, column, fields,
                         hasAttrs, defaultValues, minSchema, isDataset))
                {
                   throw new XMLStreamException("Failed to table elementL [" + column.getFieldName() + "]");
                }
                lineBreak();
             }
             if (hasAttrs && tableName.equals(rootTableName))
             {
                writeDsTables(minSchema, defaultValues);
             }
          });
       }

       /**
        * Write BEFORE-BUFFER data.
        *
        * @throws XMLStreamException
        *         If XML-related issued occurs.
        */
       private void writeBeforeBuffer() throws XMLStreamException
       {
          // <xsd:element name="__error-flag__" type="xsd:int" nillable="true" prodata:initial="prodata:unknown"/>
          writeElement("xsd:element", () -> {
             writer.writeAttribute("name", Buffer.__ERROR_FLAG__);
             writer.writeAttribute("type", "xsd:int");
             writer.writeAttribute("nillable", "true");
             writer.writeAttribute("prodata:initial", "prodata:unknown");
          });

          // <xsd:element name="__origin-rowid__" type="xsd:base64Binary" nillable="true" prodata:dataType="prodata:rowid"/>
          writeElement("xsd:element", () -> {
             writer.writeAttribute("name", Buffer.__ORIGIN_ROWID__);
             writer.writeAttribute("type", "xsd:base64Binary");
             writer.writeAttribute("nillable", "true");
             writer.writeAttribute("prodata:dataType", "prodata:rowid");
          });

          // Buffer.__DATA_SOURCE_ROWID__  field is not serialized

          // <xsd:element name="__error-string__" type="xsd:string" nillable="true" prodata:initial="prodata:unknown"/>
          writeElement("xsd:element", () -> {
             writer.writeAttribute("name", Buffer.__ERROR_STRING__);
             writer.writeAttribute("type", "xsd:string");
             writer.writeAttribute("nillable", "true");
             writer.writeAttribute("prodata:initial", "prodata:unknown");
          });

          // <xsd:element name="__after-rowid__" type="xsd:base64Binary" nillable="true" prodata:dataType="prodata:rowid"/>
          writeElement("xsd:element", () -> {
             writer.writeAttribute("name", Buffer.__AFTER_ROWID__);
             writer.writeAttribute("type", "xsd:base64Binary");
             writer.writeAttribute("nillable", "true");
             writer.writeAttribute("prodata:dataType", "prodata:rowid");
          });

          // <xsd:element name="__row-state__" type="xsd:int" nillable="true" prodata:initial="prodata:unknown"/>
          writeElement("xsd:element", () -> {
             writer.writeAttribute("name", Buffer.__ROW_STATE__);
             writer.writeAttribute("type", "xsd:int");
             writer.writeAttribute("nillable", "true");
             writer.writeAttribute("prodata:initial", "prodata:unknown");
          });
       }

       /**
        * Writes the XML-SCHEMA element or attrubute of a TEMP-TABLE column to XML stream.
        *
        * @param   schema
        *          The schema of the TEMP-TABLE.
        * @param   defaultValues
        *          If {@code true} the initial values are written for the fields that declared them.
        * @param   column
        *          The table column in question
        * @param   fields
        *          Sorted set of the taable fields ORDERs (to calculate userOrder attribute)
        * @param   hasAttributes
        *          Flag indicating that schema contains attributes
        * @param   minSchema
        *          {@code true} to write minimal schema information; {@code false} to write verbose
        *          schema information. Ignored if {@code writeXmlSchema} is {@code false}.
        * @param   isDataset
        *          If {@code true}, this is part of a dataset SCHEMA and additional attributes will
        *          be written.
        * @return  {@code true} on success. Otherwise the error is already dispatched to
        *          {@code ErrorManager} before returning.
        *
        * @throws  XMLStreamException
        *          If XML-related issued occurs.
        */
       private boolean writeColumnAttrs(
                TempTableSchema schema,
                TempTableSchema.Column column,
                SortedSet<Integer> fields,
                boolean hasAttributes,
                boolean defaultValues,
                boolean minSchema,
                boolean isDataset)
       throws XMLStreamException
       {
          XmlNodeType nodeType = column.getNodeType();
          if (nodeType == XmlNodeType.HIDDEN)
          {
             return true;
          }
          boolean isAttr = nodeType == XmlNodeType.ATTRIBUTE;
          Class<? extends BaseDataType> columnType = column.getType();
          String type = Util.xsdMap.get(columnType);
          if (type == null)
          {
             ErrorManager.recordOrShowError(13078, Util.getLegacyType(columnType));
             // Unsupported data type for XML Serialization: <type>.
             ErrorManager.recordOrShowError(13066, schema.getTableName());
             // Unable to write XML Schema for temp-table <table-name>.
             ErrorManager.recordOrShowError(13097);
             // Write schema failed for WRITE-XMLSCHEMA.
             return false;
          }
          String addType = Util.xsdProdataMap.get(columnType);
          indent();
          try
          {
             writer.writeEmptyElement(isAttr ? "xsd:attribute" : "xsd:element");
          }
          catch (XMLStreamException e)
          {
             // TODO Auto-generated catch block
             LOG.severe("", e);
             return false;
          }
          String name = column.getXmlNodeName();
          writer.writeAttribute("name", column.getXmlNodeName());
          String xmlDataType = column.getXmlDataType();
          if (!StringUtils.isEmpty(xmlDataType))
          {
             // TODO: validate. Should be OK in a valid temp-table
             type = "xsd:" + xmlDataType;
          }
          writer.writeAttribute("type", type);
          if (!isAttr)
          {
             writer.writeAttribute("nillable", String.valueOf(column.isNillable()));
          }
          if (addType != null && defaultValues)
          {
             writer.writeAttribute("prodata:dataType", addType);
          }
          Integer extent = column.getExtent();
          if (isDataset)
          {
             if (extent != null && extent > 0)
             {
                writer.writeAttribute("minOccurs", defaultValues ? String.valueOf(extent) : "0");
                writer.writeAttribute("maxOccurs", String.valueOf(extent));
             }
             else if (!defaultValues && !isAttr || childFields.contains(column.getFieldName()))
             {
                writer.writeAttribute("minOccurs", "0");
             }
          }
          else
          {
             if (extent != null && extent > 0)
             {
                writer.writeAttribute("minOccurs", defaultValues ? String.valueOf(extent) : "0");
                writer.writeAttribute("maxOccurs", String.valueOf(extent));
             }
             else if (!defaultValues && !isAttr)
             {
                writer.writeAttribute("minOccurs", "0");
             }

          }
          if (addType != null && !defaultValues)
          {
             writer.writeAttribute("prodata:dataType", addType);
          }

          if (isDataset && defaultValues)
          {
    //         if (!defaultValues)
    //         {
                String initial = column.getInitial();
                String defVal = Util.defaultValuesMap.get(columnType);
                if (initial == null && !minSchema)
                {
                   if (column.isInitialNull() && !"?".equals(defVal))
                   {
                      writer.writeAttribute("prodata:initial", "prodata:unknown");
                   }
                   else
                   {
                      initial = defVal;
                   }
                }
                if (initial != null && !initial.equals(defVal))
                {
                   if (type.startsWith("xsd:date") &&
                            ("today".equalsIgnoreCase(initial) || "now".equalsIgnoreCase(initial)))
                   {
                      writer.writeAttribute("prodata:initial", "prodata:" + initial.toLowerCase());
                   }
                   else if (initial.equals("?") ||
                            (column.getType().equals(raw.class) && "".equals(initial)))
                   {
                      writer.writeAttribute("prodata:initial", "prodata:unknown");
                   }
                   else
                   {
                      writer.writeAttribute("default", initial);
                   }
                }
    //         }
          }
          else
          {
             String initial = column.getInitial();
             if (initial != null)
             {
                boolean initialNull = "?".equals(initial) ||
                         (column.getType().equals(raw.class) && "".equals(initial));
                if (!defaultValues || !initial.equals(Util.defaultValuesMap.get(columnType)))
                {
                   if (!initialNull && type.contains("decimal") && !initial.contains("."))
                   {
                      // use [decimal] property attribute instead?
                      initial += ".0";
                   }
                   if (type.startsWith("xsd:date") &&
                            ("today".equalsIgnoreCase(initial) || "now".equalsIgnoreCase(initial)))
                   {
                      writer.writeAttribute("prodata:initial", "prodata:" + initial.toLowerCase());
                   }
                   else
                   {
                      writer.writeAttribute(initialNull ? "prodata:initial" : "default",
                                            initialNull ? "prodata:unknown" : initial);
                   }
                }
             }
             else if (!defaultValues)
             {
                initial = Util.defaultValuesMap.get(columnType);
                if ("?".equals(initial))
                {
                   writer.writeAttribute("prodata:initial", "prodata:unknown");
                }
                else
                {
                   if (type.contains("decimal") && !initial.contains("."))
                   {
                      // use [decimal] property attribute instead?
                      initial += ".0";
                   }
                   writer.writeAttribute("default", initial);
                }
             }
             else if (initial == null)
             {
                if (column.isInitialNull() && !"?".equals(Util.defaultValuesMap.get(columnType)))
                {
                   writer.writeAttribute("prodata:initial", "prodata:unknown");
                }
             }

          }
          String fieldName = column.getFieldName();
          if (!name.equals(fieldName))
          {
             writer.writeAttribute("prodata:fieldName", fieldName);
          }
          if (!minSchema && hasAttributes)
          {
             int userOrder = fields.headSet(column.getOrder()).size() + 1;
             writer.writeAttribute("prodata:userOrder", String.valueOf(userOrder));
          }

          if (!minSchema && "prodata:clob".equals(addType))
          {
             String cp = column.getCodePage();
             if (!StringUtils.isEmpty(cp))
             {
                writer.writeAttribute("prodata:columnCodepage", cp.toUpperCase());
             }
          }

          if (!minSchema )
          {
             String fmt = column.getDefinedFormat();

             BaseDataType defValue = BaseDataType.generateDefault(columnType);
             if (!StringUtils.isEmpty(fmt) && (
                      /* defaultValues || */ !fmt.equalsIgnoreCase(defValue.defaultFormatString())))
             {
                writer.writeAttribute("prodata:format", fmt);
             }
          }

          if (!minSchema)
          {
             String label = column.getDefinedLabel();
             if (label != null)
             {
                writer.writeAttribute("prodata:label", label);
             }
             String columnLabel = column.getColumnLabel();
             if (columnLabel != null)
             {
                writer.writeAttribute("prodata:columnLabel", columnLabel);
             }
             String hlp = column.getHelp();
             if (hlp != null && !hlp.isEmpty())
             {
                writer.writeAttribute("prodata:help", hlp);
             }
          }
          if (!minSchema && columnType == character.class && column.isCaseSensitive())
          {
             writer.writeAttribute("prodata:caseSensitive", "true");
          }
          if (!minSchema && columnType == decimal.class && column.getDecimals() > 0)
          {
             writer.writeAttribute("prodata:decimals", Integer.toString(column.getDecimals()));
          }
          return true;
       }
       /**
        * Writes the content of a TEMP-TABLE to serializer.
        *
        * @param   relation
        *          The relation on which this buffer is a child of.
        * @param   buffer
        *          The buffer to be used for accessing TEMP-TABLE records.
        * @param   rowName
        *          The name of the XML element that holds a record.
        * @param   xmlns
        *          The XML namespace uri, is set.
        * @param   omitInitialValues
        *          Flag indicating that initial values should be not serilaized
        * @param   includeHidden
        *          If {@code true}, the hidden fields will also be dumped.
        * @param   includeProds
        *          If {@code true}, the PROGRESS specific attribute will be written.
        * @param   before
        *          This is a {@code BEFORE} table. Rows written will be slightly decorated.
        *
        * @return  number of processed records on success. Otherwise the error is already dispatched to
        *          {@code ErrorManager} before returning.
        */
       private int serializeTempTable(DataRelation relation,
                                          TemporaryBuffer buffer,
                                          String rowName,
                                          String xmlns,
                                          boolean omitInitialValues,
                                          boolean includeHidden,
                                          boolean includeProds,
                                          boolean before)
       {
          int nr = 0;
          TempTableSchema schema = new TempTableSchema(buffer, false);
          if (serializeHidden && rootTableName == null)
          {
             rootTableName = rowName;
          }
          String unsupportedType = Util.checkSupportedTypes(schema);
          if (unsupportedType != null)
          {
             ErrorManager.recordOrShowError(13093);
             // Write temp-table data failed for WRITE-XML.
             ErrorManager.recordOrThrowError(13078, unsupportedType, "");
             // Unsupported data type for XML Serialization: <type>.
             return -1;
          }

          if (relation == null)
          {
             // reads records in order of primary index, if available, else in ascending primary key order
             nr = buffer.readAllRows(
                   (dmo) -> writeRecord(
                         buffer, dmo, rowName, schema, xmlns, includeHidden, includeProds, before, omitInitialValues));

             if (rowName.equals(rootTableName) && serializeHidden && hasChild && childRecords > 0)
             {
                try
                {
                   lineBreak();
                }
                catch (XMLStreamException ignore)
                {
                }
             }
             this.hasChild = false;
             return nr;
          }

          if (!relation.isNested().getValue() && !relation.isParentIdRelation().getValue())
          {
             this.hasChild = false;
             return nr;
          }

          Set<String> cFields = this.childFields;
          childFields = childFieldsByChild.getOrDefault(rowName, Collections.emptySet());
          try
          {
             // build a query depending on the relation, and add each record from the relation
             QueryWrapper query = (QueryWrapper) relation.getQuery(true);
             query.hintFullResults();
             query.queryOpen();
             query.first();
             while (!query._isOffEnd())
             {
                nr++;
                TempRecord dmo = buffer.getCurrentRecord();

                writeRecord(buffer, dmo, rowName, schema, xmlns, includeHidden, includeProds, before,
                                   omitInitialValues);

                query._getNext();
             }
             query.close();
          }
          finally
          {
             this.childFields = cFields;
          }

          this.hasChild = false;
          return nr;
       }

       /**
        * Write a temp-table record's data to the XML output stream.
        *
        * @param   buffer
        *          The buffer being serialized.
        * @param   dmo
        *          Object containing temp-table record's data.
        * @param   rowName
        *          Name applied to the element or attribute associated with the row data in the XML
        *          output.
        * @param   schema
        *          The table schema to be used.
        * @param   xmlns
        *          The XML namespace uri, is set.
        * @param   hidden
        *          If {@code true} include the hidden fields ({@code __name__}) for each record
        *          written to stream.
        * @param   prodsId
        *          If {@code true} include the {@code prods:id} attribute for each record written.
        * @param   before
        *          {@code true} if a BEFORE-TABLE record is processed.
        */
       private void writeRecord(TemporaryBuffer buffer,
                                TempRecord dmo,
                                String rowName,
                                TempTableSchema schema,
                                String xmlns,
                                boolean hidden,
                                boolean prodsId,
                                boolean before,
                                boolean omitInitialValues)
       {
          if (serializeHidden && rowName.equals(rootTableName) && rootTableRecordsWritten > 0 &&
                   schema.attributeColumns().iterator().hasNext())
          {
             attrFailure();
             return;
          }
          if (dmo == null)
          {
             return;
          }

          // CA: who uses this? I don't see it being used for normal DATASET usage.
          // prodsId = false;

          // TODO: noInit!!!

          try
          {
             Integer rowState = dmo._rowState();
             if (rowState == null)
             {
                rowState = Buffer.ROW_UNMODIFIED;
             }
             if (before && rowState == Buffer.ROW_CREATED)
             {
                // NOTE: it seems like the CREATED records are NOT serialized at all for
                //       BEFORE TEMP-TABLEs, so these value should not reach past this point, those
                //       records are cut here.
                return;
             }

             boolean startElementWritten = false;
             if (!serializeHidden || !hasChild || schema.attributeColumns().iterator().hasNext())
             {
                indent();
                writer.writeStartElement((isEmpty(ttNsPrefix) || rowName.contains(":") ?
                                        "" : ttNsPrefix + ":") + rowName);
                startElementWritten = true;
                if (!xsiWritten)
                {
                   if (xmlns == null || xmlns.isEmpty() )
                   {
                      xmlns = nsUri;
                   }
                   if (xmlns != null && !xmlns.isEmpty())
                   {
                      writer.writeAttribute("xmlns" + (isEmpty(ttNsPrefix) ? "" : ":" + ttNsPrefix) , xmlns);
                   }
                   writer.writeAttribute(ATTR_NS_XSI, NS_URI);
                   xsiWritten = true;
                }
                else if (xmlns != null && !xmlns.isEmpty() && (ds != null || isEmpty(ttNsPrefix) ))
                {
                   writer.writeAttribute("xmlns" + (isEmpty(ttNsPrefix) ? "" : ":" + ttNsPrefix) , xmlns);
                }
                // NOTE: the next conditional is half commented out because, in some cases which we were not able to
                //       properly identify, the [prods:id] and [prods:rowState] are not serialized
                if (prodsId /*&& before*/)
                {
                   // this links the BEFORE and AFTER image. Because the deleted records (from
                   // BEFORE-TABLE) do not have a peer their rowid will be used, as an exception.
                   if (rowState == Buffer.ROW_DELETED || !schema.isBeforeImage())
                   {
                      // DELETED before, CREATED or MODIFIED after images
                      writer.writeAttribute("prods:id", rowName + dmo.primaryKey());
                   }
                   else
                   {
                      // CREATED or MODIFIED before images
                      writer.writeAttribute("prods:id", rowName + dmo._peerRowid());
                   }

                   if (!before)
                   {
                      if (rowState != Buffer.ROW_UNMODIFIED)
                      {
                         writer.writeAttribute("prods:rowState", Util.getRowStateAsString(rowState));
                      }

                      Integer errorFlags = dmo._errorFlags();
                      if (errorFlags != null && errorFlags != 0) // also only for 'changes' dataset (?)
                      {
                         writer.writeAttribute("prods:hasErrors", "true");
                      }

                      String errorString = dmo._errorString();
                      if (errorString != null)  // also only for 'changes' dataset (?)
                      {
                         prodsErrors.put(rowName + dmo.primaryKey(), Pair.of(rowName, errorString));
                      }
                   }
                }
                if (!serializeHidden || !rowName.equals(rootTableName) || writeXmlSchema == null)
                {
                   // write all columns which must be serialized as attributes first, while the start element for the
                   // row is open
                   writeColumns(dmo, schema.attributeColumns(), hidden, omitInitialValues);
                   // write all columns which must be serialized to text
                   writeColumns(dmo, schema.textColumns(), hidden, omitInitialValues);
                }
             }

             ++indentLevel;
             if ( !serializeHidden || !hasChild || savedHasChild.size() > 1 || childRecords > 0)
             {
                lineBreak();
             }
             if (writeXmlSchema != null)
             {
                writeXmlSchema.run();
                writeXmlSchema = null;
                rootTableRecordsWritten++;
             }

             if (serializeHidden && rowName.equals(rootTableName) && rootTableRecordsWritten > 0 &&
                      schema.attributeColumns().iterator().hasNext())
             {
                attrFailure();
                return;
             }

             // write all columns which must be serialized to elements as separate XML elements
             writeColumns(dmo, schema.elementColumns(), hidden, omitInitialValues);

             BufferImpl buf = (BufferImpl) buffer.getDMOProxy();
             if (rowName.equals(rootTableName))
             {
                rootTableRecordsWritten++;
             }
             writeBeforeData(buf, dmo);

             writeChildRecords(buf, dmo, prodsId, omitInitialValues);

             --indentLevel;
             indent();
             boolean openTable = !serializeHidden || !hasChild;
             if (openTable || savedHasChild.size() > 0)
             {
                writer.writeEndElement();
             }
             if ( openTable || savedHasChild.size() > 1 )
             {
                lineBreak();
             }
          }
          catch (XMLStreamException exc)
          {
             ErrorManager.recordOrThrowError(0, exc.getMessage());
          }
          catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException exc)
          {
             throw new RuntimeException("Unable to access DMO data", exc);
          }
       }

       /**
        * Write before data
        *
        * @param   buf
        *          The buffer being serialized.
        * @param   dmo
        *          Object containing temp-table record's data.
        *
        * @throws  XMLStreamException
        *          on error
        *
        */
       private BufferImpl writeBeforeData(BufferImpl buf, TempRecord dmo)
       throws XMLStreamException
       {
          if (buf.isBeforeBuffer() && ds == null)
          {
             writeDatum(ERROR_FLAG, new integer(dmo._errorFlags()));
             writeDatum(ORIGIN_ROWID, new rowid(dmo._originRowid()));
             writeDatum(DATASOURCE_ROWID, new rowid(dmo._datasourceRowid()));
             writeDatum(ERROR_STRING, new character(dmo._errorString()));
             writeDatum(AFTER_ROWID, new rowid(dmo._peerRowid()));
             writeDatum(ROW_STATE, new integer(dmo._rowState()));
          }
          return buf;
       }

       /**
        * Write child records of the current record
        *
        * @param   buf
        *          The buffer being serialized.
        * @param   dmo
        *          Object containing temp-table record's data.
        * @param   prodsId
        *          If {@code true} include the {@code prods:id} attribute for each record written.
        * @param   omitInitialValues
        *          Flag indicating that initial values should be not serilaized
        */
       private void writeChildRecords(BufferImpl buf, TempRecord dmo, boolean prodsId, boolean omitInitialValues)
       {
          DataSet ds = buf._dataSet();
          if (this.ds != null && ds != null)
          {
             // position the buffer on this DMO
             buf.findByRowID(new rowid(dmo.primaryKey()));

             List<DataRelation> childRels = ds.getRelations(buf, true, false, true);
             childRecords = 0;
             for (DataRelation dr : childRels)
             {
                if (dr.isNested().booleanValue() || dr.isParentIdRelation().booleanValue())
                {
                   hasChild = true;
                   Buffer childBuffer = (Buffer) dr.getChildBufferNative();
                   TemporaryBuffer child = (TemporaryBuffer) ((BufferReference) childBuffer).buffer();

                   TempTableSchema childSchema = new TempTableSchema(child, false);
                   String ns = childSchema.namespaceURI();
                   String curentPrefix = ttNsPrefix;
                   ttNsPrefix = ns.equals(rootNsUri) ? rootNsPrefix : childSchema.namespacePrefix();
                   savedHasChild.push(hasChild);
                   try
                   {
                      hasChild = false;
                      childRecords = serializeTempTable(dr, child, childSchema.getXmlName(),
                               ns.equals(rootNsUri) ? "" : ns,
                               omitInitialValues, false, prodsId, false);
                   }
                   finally
                   {
                      hasChild = savedHasChild.pop();
                      ttNsPrefix = curentPrefix;
                   }
                }
             }
          }
       }

       /**
        * Write data for the given columns from a single record into the XML output stream.
        *
        * @param   dmo
        *          The DMO to be serialized.
        * @param   columns
        *          The set of columns for that DMO.
        * @param   hidden
        *          If {@code true} include the hidden fields ({@code __name__}) for each record
        *          written to stream.
        *
        * @throws  IllegalAccessException
        *          if a DMO getter method cannot be accessed using reflection.
        * @throws  IllegalArgumentException
        *          if an illegal argument is passed to the DMO getter method for a column.
        * @throws  InvocationTargetException
        *          if there is a problem invoking a getter method on the given DMO.
        * @throws  XMLStreamException
        *          if there is an error writing to the XML stream.
        */
       private void writeColumns(Record dmo,
                                 Iterable<TempTableSchema.Column> columns,
                                 boolean hidden,
                                 boolean noInit)
       throws IllegalAccessException,
              IllegalArgumentException,
              InvocationTargetException,
              XMLStreamException
       {
          for (TempTableSchema.Column column : columns)
          {
             if ((ds == null || !ds.getSerializedBySOAP()) && childFields.contains(column.getFieldName()))
             {
                continue;
             }
             if (!hidden && column.isHidden())
             {
                // hidden fields are not printed now
                continue;
             }

             Integer extent = column.getExtent();
             Method getter = column.getGetter();

             if (extent != null && extent > 0)
             {
                if (noInit)
                {
                   boolean untouched = true;
                   for (int i = 0; i < extent; i++)
                   {
                      if (column.isChanged((BaseDataType) column.getGetter().invoke(dmo, i)))
                      {
                         untouched = false;
                         break; // drop all other tests from this extent
                      }
                   }

                   if (untouched)
                   {
                      continue; // skip to next column
                   }
                }

                for (int i = 0; i < extent; i++)
                {
                   BaseDataType datum = (BaseDataType) getter.invoke(dmo, i);
                   writeDatum(column, datum);
                }
             }
             else
             {
                if (noInit)
                {
                   if (!column.isChanged((BaseDataType) column.getGetter().invoke(dmo)))
                   {
                      continue; // skip to next column
                   }
                }

                BaseDataType datum = (BaseDataType) getter.invoke(dmo);
                writeDatum(column, datum);
             }
          }
       }

       /**
        * Write a single data value to the XML output stream, according to the instructions stored
        * in the temp-table schema.
        * <p>
        * This version ensures that the group and decimal separators are ',' and '.' for the duration of the
        * serialization.
        *
        * @param   column
        *          Temp-table column schema information.
        * @param   datum
        *          Datum to be written to XML output.
        *
        * @throws  XMLStreamException
        *          if there is an error writing to the XML stream.
        */
       private void writeDatum(TempTableSchema.Column column, BaseDataType datum)
       throws XMLStreamException
       {
          if (datum.isUnknown() &&
              (column.getNodeType() == XmlNodeType.ATTRIBUTE || column.getNodeType() == XmlNodeType.TEXT))
          {
             // ignore unknown values when the column is written as ATTRIBUTE or TEXt
             return;
          }

          char groupSep = decimal.getGroupSeparator();
          char decSep = decimal.getDecimalSeparator();

          try
          {
             decimal.setNumericFormat('.', ',');

             writeDatum2(column, datum);
          }
          finally
          {
             decimal.setNumericFormat(decSep, groupSep);
          }
       }

       /**
        * Write a single data value to the XML output stream, according to the instructions stored
        * in the temp-table schema.
        *
        * @param   column
        *          Temp-table column schema information.
        * @param   datum
        *          Datum to be written to XML output.
        *
        * @throws  XMLStreamException
        *          if there is an error writing to the XML stream.
        */
       private void writeDatum2(TempTableSchema.Column column, BaseDataType datum)
       throws XMLStreamException
       {
          String name = !isEmpty(ttNsPrefix) && column.getNodeType() != XmlNodeType.ATTRIBUTE ?
                   (ttNsPrefix + ":" + column.getXmlNodeName()) : column.getXmlNodeName();
          String value;
          if (datum instanceof date)
          {
             // this includes datetime and datetime-tz, too
             value = ((date) datum).getIsoDate();
          }
          else if (datum instanceof logical)
          {
             value = Boolean.toString(((logical) datum).booleanValue());
          }
          else if (datum instanceof rowid)
          {
             value = Util.encodeBase64(((rowid) datum).getValue());
          }
          else if (datum instanceof raw || datum instanceof blob)
          {
             if (!datum.isUnknown())
             {
                Base64.Encoder enc = Base64.getEncoder();
                value = enc.encodeToString(((BinaryData) datum).getByteArray());
             }
             else
             {
                value = ""; // or null
             }
          }
          else
          {
             value = datum.toStringMessage();
             if (datum instanceof decimal && !datum.isUnknown())
             {
                int colDecs = column.getDecimals();
                if (colDecs > 0)
                {
                   value = ((decimal) datum).toJavaType().setScale(colDecs, RoundingMode.HALF_UP).toString();
                }
                else if (!value.contains("."))
                {
                   // use [decimal] property attribute instead?
                   value += ".0";
                }
             }
          }

          // no need to escape, the writer will automatically escape the value

          switch (column.getNodeType())
          {
             case TEXT:
                writer.writeCharacters(value);
                break;

             case ATTRIBUTE:
                writer.writeAttribute(name, value);
                break;

             case ELEMENT:
                indent();
                if (!datum.isUnknown() && value.isEmpty())
                {
                   writer.writeEmptyElement(name);
                }
                else
                {
                   writer.writeStartElement(name);
                   if (datum.isUnknown())
                   {
                      writer.writeAttribute(ATTR_XSI_NIL, "true");
                   }
                   else
                   {
                      writer.writeCharacters(value);
                   }
                   writer.writeEndElement();
                }
                lineBreak();
                break;
          }
       }

       /**
        * Write a line break to the XML output stream, if formatting is enabled.
        *
        * @throws  XMLStreamException
        *          if there is an error writing to the XML stream.
        */
       private void lineBreak()
       throws XMLStreamException
       {
          if (format)
          {
             writer.writeCharacters(lineSep);
          }
       }

       /**
        * Write a soft indent to the XML output stream, if formatting is enabled.
        *
        * @throws  XMLStreamException
        *          if there is an error writing to the XML stream.
        */
       private void indent()
       throws XMLStreamException
       {
          if (format)
          {
             for (int i = 0; i < indentLevel; i++)
             {
                writer.writeCharacters(INDENT);
             }
          }
       }

       /**
        * The framework for writing a XML file. Will open the stream specified by {@code target}
        * with requested {@code encoding}, write XML header and allow the {@code code} to write
        * the XML body (main element). At the end, the end of document is written and the file is
        * closed.
        * <p>
        * In case that the {@code code} exit abruptly, the expected {@code Exception} s are catched as
        * a last resort and the stack trace is printed.
        *
        * @param   code
        *          The code to be executed. It should build the actual XML content.
        * @param   method
        *          The identifier for the method using the source stream.
        *          Only used for composing the error messages.
        * @param   widgetType
        *          The parent widget. Only used for composing the error messages.
        *
        * @return  {@code true} on success. Otherwise the error is already dispatched to
        *          {@code ErrorManager} before returning.
        */
       private boolean write(XmlWriter code, String method, String widgetType)
       {
          XMLOutputFactory factory = XMLOutputFactory.newInstance();
          factory.setProperty(WstxOutputProperties.P_OUTPUT_INVALID_CHAR_HANDLER,
                              new InvalidCharHandler.ReplacingHandler((char) 0)
          {
             @Override
             public char convertInvalidChar(int c) throws IOException
             {
                // allow all chars, as 4GL is not XML1.0 compliant!
                return (char) c;
             }
          });
          if (factory instanceof WstxOutputFactory)
          {
             factory.setProperty(XMLOutputFactory2.P_TEXT_ESCAPER, new Escaper());
          }

          try (OutputStream stream = target.getStream())
          {
             try
             {
                writer = factory.createXMLStreamWriter(stream, encoding);
             }
             catch (XMLStreamException ex)
             {
                ErrorManager.recordOrShowError(13515);
                // Invalid encoding for WRITE-XML.
                return false;
             }
             writer.writeStartDocument(encoding, "1.0");
             lineBreak();

             boolean result = code.run();

             if (result)
             {
                writer.writeEndDocument();
             }
             writer.close();
             writer = null;
             return result;
          }
          catch (ErrorConditionException e)
          {
             ErrorManager.recordOrShowError(4065, false, method, widgetType);
             // **The <attribute> attribute on the <widget id> has invalid arguments.
             return false;
          }
          catch (XMLStreamException | IOException e)
          {
             LOG.severe("", e);
             return false;
          }
       }

       /**
        * Report warnings 13065/13093, and finish export.
        *
        * This is how 4GL does on DATA-SET:WRITE-XML when the DATA-SET:SERIALIZE-HIDDEN =- true
        * and the the first table contains fields with XML-NODE-TYPE  'attribute'.
        *
        */
       private void attrFailure()
       {
          ErrorManager.recordOrShowError(
             new int [] {13065, 13093},
             new String[] {
                "Insert-Attribute failed for WRITE-XML.  Error: 1",
                "Write temp-table data failed for WRITE-XML"
             },
             false, // modal,
             false,  // prefix,
             false, // isError,
             false, //  asMsg,
             true   // addDot
          );
          try
          {
             writer.flush();
          }
          catch (XMLStreamException ignore)
          {
          }
          throw new Fatal();
       }

       /**
        * Check if the string is empty.
        *
        * @param s
        *        String to be checked
        * @return {@code true} if the string is empty
        */
       private static boolean isEmpty(String s)
       {
          return s == null || s.trim().isEmpty();
       }

       /** Implementations of this interface actively write XML /XSD code. */
       @FunctionalInterface
       private static interface XmlWriter {
          /**
           * Similar to {@link Runnable} interface, this one allows the code to throw some specific
           * {@code Exceptions} that the caller is required to catch.
           *
           * @return  {@code true} on success. Otherwise the errors encountered were already notified
           *          to {@code ErrorManager}.
           * @throws XMLStreamException
           *         on unexpected processing error.
           * @throws IOException
           *         on I/O error
           *
           */
          public abstract boolean run()
          throws XMLStreamException, IOException;
       }

       /**
        * Implementation of the Interface that defines API for the factories stream writers use for creating
        * "escaping writers". These factories are used when defining custom escaping of text (as well as possibly
        * used by the implementations for default escaping too). Escaping in this context refers to the process of
        * replacing individual text/attribute content character with pre-defined and character entities, as per
        * XML specification (2.4, Appendix D).
        * Typical escaping writers replace characters like &lt; and &amp;, as well as some additional characters
        * depending on context. Custom implementations may choose to escape additional characters, for example to
        * make it easier to manually view or edit resulting serialized XML document.
        */
       public static class Escaper
       implements EscapingWriterFactory
       {
          /**
           * Method called to create an instance of escaping writer that will output to the specified stream,
           * using the specified encoding, and escaping necessary characters (depending on both type
           * [attr, element text] and encoding).
           *
           * @param   os
           *          Underlying stream that the encoding writer should use for output.
           * @param   enc
           *          Encoding to use, as specified by the stream writer (based on information application has
           *          passed).
           *
           * @return  escaping writer instance
           */
          @Override
          public Writer createEscapingWriterFor(OutputStream os, String enc)
          throws UnsupportedEncodingException
          {
              return createEscapingWriterFor(new OutputStreamWriter(os, enc), enc);
          }

          /**
           * Method called to create an instance of escaping writer that will output to the specified stream,
           * using the specified encoding, and escaping necessary characters (depending on both type
           * [attr, element text] and encoding).
           *
           * @param   writer
           *          Underlying writer that the encoding writer should output.
           * @param   enc
           *          Encoding to use, as specified by the stream writer (based on information application has
           *          passed).
           *
           * @return escaping writer instance
           */
          @Override
          public Writer createEscapingWriterFor(final Writer writer, String enc)
          throws UnsupportedEncodingException
          {
             return new Writer()
             {
                /** Closes the stream, flushing it first. */
                @Override
                public void close()
                throws IOException
                {
                   writer.close();
                }

                /** Flushes the stream. */
                @Override
                public void flush()
                throws IOException
                {
                   writer.flush();
                }

                /**
                 * Writes a portion of an array of characters.
                 *
                 * @param   buf
                 *          Array of characters.
                 * @param   off
                 *          Offset from which to start writing characters'
                 * @param   len
                 *          Number of characters to write.
                 */
                @Override
                public void write(char[] buf, int off, int len)
                throws IOException
                {
                   boolean hasEscapable = false;
                   for (int i = 0; i < len && !hasEscapable; i++)
                   {
                      char nextChar = buf[i + off];
                      switch (nextChar)
                      {
                         case '<':
                         case '>':
                         case '"':
                         case '&':
                         case '\'':
                            hasEscapable = true;
                            break;
                         default:
                            break;
                       }
                   }

                   if (!hasEscapable)
                   {
                      writer.write(buf, off, len);
                      return;
                   }

                   String escaped = StringEscapeUtils.escapeXml10(new String(buf, off, len));
                   writer.write(escaped.toCharArray());
                }
             };
          }
       }

       /** Implementations of this interface actively write XML /XSD code. */
       @FunctionalInterface
       private static interface XmlElementWriter {
          /**
           * Similar to {@link Runnable} interface, this one allows the code to throw some specific
           * {@code Exceptions} that the caller is required to catch.
           *
           * @throws XMLStreamException
           *         on unexpected processing error.
           *
           */
          public abstract void run()
          throws XMLStreamException;
       }

       /**
        * Exception to signal immediate end of export.
        *
        */
       public static class Fatal extends RuntimeException
       {

       }
    }