/**
 * @file   SCFCBDOM.c
 * @brief  Sample program in C to parse an XML file via the FLUC byte interface
 *         and store the document into a simplified DOM (document object model)
 *         in memory.
 * @author limes datentechnik gmbh
 ******************************************************************************/

/* defines code page for literals for IBM mainframes to German */
#ifdef __ZOS__
#  pragma convert(1141)
#endif

#include<stdio.h>
#include<stdlib.h>
#include<string.h>

#include"FLCBYT.h"

#define TRUE            1
#define FALSE           0

#define NODE_XML        1
#define NODE_TAG        2
#define NODE_ATTRIBUTE  3
#define NODE_CDATA      4
#define NODE_TEXT       5
#define NODE_COMMENT    6

#define PRINTLIMIT      40   // max. number of characters to print of each element

//#define DEBUGOUTPUT

// A generic node in a DOM tree.
// The fields that may be set depend on the node type.
typedef struct XmlNode_ {
  struct XmlNode_* parent;
  int              type;
  size_t           nameLen;
  char*            name;
  size_t           valueLen;
  char*            value;
  struct XmlNode_* attributes;
  struct XmlNode_* children;
  struct XmlNode_* next;
} XmlNode;

// An XML document with a pointer to the XML root element and metadata
typedef struct XmlDoc_ {
   XmlNode* header;
   XmlNode* root;
} XmlDoc;

// Handle for an open input file and it's statistics
typedef struct ReadHdl_ {
   void*        fileHdl;
   unsigned int elementsRead;
   unsigned int elementsSkipped;
   unsigned int domNodesCreated;
} ReadHdl;

// Declarations
static ReadHdl*      openInputFile(const char* input);
static void          closeInputFile(ReadHdl* handle);
static FlmElmRec0*   readNextElement(ReadHdl* handle);
static XmlDoc*       parseXmlDocument(ReadHdl* handle);
static XmlNode*      processTag(XmlNode* parent, FlmElmRec0* element, ReadHdl* handle);
static void          processTagBody(XmlNode* tag, ReadHdl* handle);
static XmlNode*      processAttribute(XmlNode* parent, FlmElmRec0* element, ReadHdl* handle);
static XmlNode*      processText(XmlNode* parent, FlmElmRec0* element, ReadHdl* handle);
static XmlNode*      processCdata(XmlNode* parent, FlmElmRec0* element, ReadHdl* handle);
static XmlNode*      processComment(XmlNode* parent, FlmElmRec0* element, ReadHdl* handle);
static XmlNode*      newXmlNode(void);
static void          printXmlNode(XmlNode* node, int recursive, int filtered);
static unsigned int  indentNode(XmlNode* node);
static void          freeXmlNode(XmlNode* node);
static void          freeXmlDocument(XmlDoc* doc);
static char*         xmlTypeString(int type);
static char*         matrixTypeString(int type);
static char*         cloneBytes(char* buffer, size_t len);

/**
 * This test program takes an XML file as parameter to create a DOM using
 * the byte interface and prints the DOM to stdout.
 *
 * @param argc must be 2
 * @param argv contains the XML file to read
 * @return 8=parameter error, 6=open file error, 4=read write error, 2=close error, 0=Success
 */
int main(int argc, char * argv[])
{
   FlmElmRec0*             element;
   ReadHdl*                handle;
   XmlDoc*                 xmlDocument;
   char                    stats[512];

/* defines environment variable LANG to German EBCDIC for IBM mainframes */
#ifdef __ZOS__
   __setenv("LANG","de_DE.IBM-1141",1);
#endif

   if (argc!=2) {
      fprintf(stderr,"Please specify an XML file as the single parameter.\n");
      return 8;
   }

   handle = openInputFile(argv[1]);
   xmlDocument = parseXmlDocument(handle);
   for (element = readNextElement(handle); element != NULL; element = readNextElement(handle)) {
      handle->elementsSkipped++;
#ifdef DEBUGOUTPUT
      fprintf(stderr, "Skipped Element #%u (MatTyp: %s, ElmTyp: %s, DatLen: %u)\n", handle->elementsRead, matrixTypeString(element->uiMatTyp), xmlTypeString(element->uiElmTyp), element->uiDatLen);
#endif
   }
   snprintf(stats, sizeof(stats), "Total DOM Nodes: %u Processed FLAM elements: %u Skipped FLAM elements: %u\n", handle->domNodesCreated, handle->elementsRead, handle->elementsSkipped);
   closeInputFile(handle);

   printXmlNode(xmlDocument->root, TRUE, TRUE);
   freeXmlDocument(xmlDocument);

   fprintf(stderr, "Stats: %s\n", stats);

   return(0);
}

static ReadHdl* openInputFile(const char* filename) {
   char acInfile[4096];
   ReadHdl* hdl;

   hdl = (ReadHdl*)malloc(sizeof(ReadHdl));
   if (hdl==NULL) {
      fprintf(stderr,"Opening the input file for read failed.\n");
      fprintf(stderr,"Could not allocate handle.\n");
      exit(6);
   }
   hdl->elementsRead = 0;
   hdl->elementsSkipped = 0;
   hdl->domNodesCreated = 0;

   snprintf(acInfile, sizeof(acInfile), "read.xml(file=\"%s\")", filename);

   hdl->fileHdl=fcbopen(acInfile,"format.element()");
   if (hdl->fileHdl==NULL) {
      fprintf(stderr,"Opening the input file for read failed\n");
      fprintf(stderr,"pvRed=fcbopen(\"%s\",\"%s\");\n\n",acInfile,"format.element()");
      fprintf(stderr,"%s\n\n",fcberrms(fcberrno));
      fprintf(stderr,"%s\n\n",fcberrtr());
      free(hdl);
      exit(6);
   }

   return hdl;
}

static void closeInputFile(ReadHdl* handle) {
   int fail = 0;

   if (handle!=NULL) {
      if (handle->fileHdl) {
         if (fcbclose(handle->fileHdl)) {
            fprintf(stderr,"Closing the input file failed\n");
            fprintf(stderr,"fcbclose(handle->fileHdl);\n\n");
            fprintf(stderr,"%s\n\n",fcberrms(fcberrno));
            fprintf(stderr,"%s\n\n",fcberrtr());
            fail = 1;
         }
      }
      free(handle);
      if (fail)
         exit(2);
   }
}

static FlmElmRec0* readNextElement(ReadHdl* handle) {
   unsigned int   uiLen, uiErr;
   FlmElmRec0*    element;

   // read element (locate the pointer to the element structure with size=-1)
   uiLen = fcbread(&element, -1, 0, handle->fileHdl);
   if (uiLen==0) {
      // could not read element
      uiErr = fcberror(handle->fileHdl);
      if (uiErr==FLMRTC_EOF) {
         // there are no more elements
         return NULL;
      } else {
         // Some other error occurred
         fprintf(stderr, "Unexpected error %u\n", uiErr);
         fprintf(stderr, "%s\n",fcberrtr());
         exit(4);
      }
   }

   // Verify that the element structure is valid
   if (element->version!=0) {
      fprintf(stderr, "Version (%d) of element structure not supported\n",element->version);
      exit(4);
   }
   // Verify that the read element is an XML element
   if (element->matTyp!=FLMMAT_TYP_XMLELM) {
      fprintf(stderr, "Non-XML element encountered\n");
      exit(4);
   }
   // Plausibility check of the length field
   if (element->datLen+32!=uiLen) {
      fprintf(stderr, "The element length (%d) is not valid\n",element->datLen);
      exit(4);
   }

   // Increment read elements counter
   handle->elementsRead++;

   return element;
}

static XmlDoc* parseXmlDocument(ReadHdl* handle) {
   XmlDoc* xmlDoc;
   FlmElmRec0* element;

   // allocate XML document structure
   xmlDoc = (XmlDoc*)malloc(sizeof(XmlDoc));
   if (xmlDoc==NULL) {
      fprintf(stderr, "Could not allocate XmlDoc structure.\n");
      exit(4);
   }
   xmlDoc->root=NULL;
   xmlDoc->header=NULL;

   // fetch first element from FLUC
   element = readNextElement(handle);

   // check if it is the <?XML ... ?> tag
   if (element && element->elmTyp==FLMELM_TYPXML_XML) {
      xmlDoc->header = newXmlNode();
      xmlDoc->header->type = NODE_XML;
      handle->domNodesCreated++;
      // TODO: parse attributes of <?XML ... ?> tag
      element = readNextElement(handle);
   }

   if (element==NULL) {
      fprintf(stderr, "No XML elements found.\n");
      exit(4);
   }

   // find and process root tag
   do {
      if (element->elmTyp==FLMELM_TYPXML_STARTELM && xmlDoc->root==NULL) {
         xmlDoc->root = processTag(NULL, element, handle);
      } else {
         if (element->elmTyp==FLMELM_TYPXML_STARTELM) {
            fprintf(stderr, "Document contains more than one root tag.\n");
            exit(4);
         }
#ifdef DEBUGOUTPUT
         fprintf(stderr, "Skipped Element #%u (MatTyp: %s, ElmTyp: %s, DatLen: %u)\n", handle->elementsRead, matrixTypeString(element->uiMatTyp), xmlTypeString(element->uiElmTyp), element->uiDatLen);
#endif
      }
      element = readNextElement(handle);
   } while (element != NULL);

   if (xmlDoc->root==NULL) {
      fprintf(stderr, "Document contains no root tag.\n");
      exit(4);
   }

   // ignore everything after the root tag (may be comments, processing instructions, whitespace)

   return xmlDoc;
}

static XmlNode* processTag(XmlNode* parent, FlmElmRec0* element, ReadHdl* handle) {
   XmlNode* node = newXmlNode();
   XmlNode* attrTail = NULL;
   FlmElmRec0* nextElement;

   // set node type to tag
   node->type = NODE_TAG;
   // set parent node
   node->parent = parent;
   // set tag name
   node->name = cloneBytes((char*)element->buffer, element->datLen);
   node->nameLen = element->datLen;

#ifdef DEBUGOUTPUT
   fprintf(stderr, "New tag: %.*s\n", (int)node->nameLen, node->name);
#endif

   // process the first attribute
   nextElement = readNextElement(handle);
   if (nextElement!=NULL && nextElement->elmTyp==FLMELM_TYPXML_ATTNAME) {
      node->attributes = attrTail = processAttribute(node, nextElement, handle);
      // process all other attributes
      for (nextElement = readNextElement(handle); nextElement!=NULL && nextElement->elmTyp==FLMELM_TYPXML_ATTNAME; nextElement = readNextElement(handle)) {
         attrTail->next = processAttribute(node, nextElement, handle);
         attrTail = attrTail->next;
      }
   }

   // attributes MUST be followed by a closing bracket >
   if (nextElement==NULL) {
      fprintf(stderr, "Unexpected end of element stream after element #%u.\n", handle->elementsRead);
      fprintf(stderr, "Expected end of tag (closing bracket).\n");
      exit(4);
   }
   if (nextElement->elmTyp!=FLMELM_TYPXML_ENDSTRTELM) {
      fprintf(stderr, "Expected end of starting tag (closing bracket), but encountered this element:\n");
      fprintf(stderr, "Element #%u (MatTyp: %s, ElmTyp: %s, DatLen: %d)\n", handle->elementsRead, matrixTypeString(nextElement->matTyp), xmlTypeString(nextElement->elmTyp), nextElement->datLen);
      exit(4);
   }

   // process tag body
   processTagBody(node, handle);

   handle->domNodesCreated++;
   return node;
}

static void processTagBody(XmlNode* tag, ReadHdl* handle) {
   XmlNode* childTail = NULL;
   XmlNode* child;
   FlmElmRec0* nextElement;

   while(1) {
      nextElement = readNextElement(handle);
      if (nextElement==NULL) {
         fprintf(stderr, "Unexpected end of element stream after element #%u.\n", handle->elementsRead);
         fprintf(stderr, "Expected closing tag.\n");
         exit(4);
      }
      child = NULL;
      switch(nextElement->elmTyp) {
         case FLMELM_TYPXML_ENDELM: // the current tag's matching closing tag
            // check that closing tag name matches opening tag
            if (tag->nameLen!=nextElement->datLen || memcmp(tag->name, nextElement->buffer, tag->nameLen)!=0) {
               fprintf(stderr, "Encountered closing tag %.*s, which does not match starting tag %.*s.\n", nextElement->datLen, nextElement->buffer, (int)tag->nameLen, tag->name);
               fprintf(stderr, "Element #%u (MatTyp: %s, ElmTyp: %s, DatLen: %d)\n", handle->elementsRead, matrixTypeString(nextElement->matTyp), xmlTypeString(nextElement->elmTyp), nextElement->datLen);
               exit(4);
            }
            return;
         case FLMELM_TYPXML_DATA:
            child = processText(tag, nextElement, handle);
            break;
         case FLMELM_TYPXML_STARTCD:
            child = processCdata(tag, nextElement, handle);
            break;
         case FLMELM_TYPXML_STARTELM:
            child = processTag(tag, nextElement, handle);
            break;
         case FLMELM_TYPXML_COMMENT:
            child = processComment(tag, nextElement, handle);
            break;
         case FLMELM_TYPXML_SKIPENT:
         case FLMELM_TYPXML_PROCINST:
         case FLMELM_TYPXML_DEFAULT:
            handle->elementsSkipped++;
#ifdef DEBUGOUTPUT
            fprintf(stderr, "Ignoring Element #%u (MatTyp: %s, ElmTyp: %s, DatLen: %u)\n", handle->elementsRead, matrixTypeString(nextElement->uiMatTyp), xmlTypeString(nextElement->uiElmTyp), nextElement->uiDatLen);
#endif
            // ignore default type (whitespace) and processing instructions
            break;
         default:
            // Everything else must not appear inside a tag
            fprintf(stderr, "Encountered illegal element within tag %.*s:\n", (int)tag->nameLen, tag->name);
            fprintf(stderr, "Element #%u (MatTyp: %s, ElmTyp: %s, DatLen: %d)\n", handle->elementsRead, matrixTypeString(nextElement->matTyp), xmlTypeString(nextElement->elmTyp), nextElement->datLen);
            exit(4);
      }
      if (child!=NULL) {
         if (childTail==NULL) {
            tag->children = childTail = child;
         } else {
            childTail->next = child;
            childTail = child;
         }
      }
   }
}

static XmlNode* processAttribute(XmlNode* parent, FlmElmRec0* element, ReadHdl* handle) {
   XmlNode* node = newXmlNode();
   FlmElmRec0* nextElement;

   // set node type to tag
   node->type = NODE_ATTRIBUTE;
   // set parent node
   node->parent = parent;
   // set attribute name
   node->name = cloneBytes((char*)element->buffer, element->datLen);
   node->nameLen = element->datLen;

   // get next element, which must be an attribute value element
   nextElement = readNextElement(handle);
   if (nextElement==NULL) {
      fprintf(stderr, "Unexpected end of element stream after element #%u.\n", handle->elementsRead);
      fprintf(stderr, "Expected attribute value.\n");
      exit(4);
   }
   if (nextElement->elmTyp!=FLMELM_TYPXML_ATTRVAL) {
      fprintf(stderr, "Expected attribute value, but encountered this element:\n");
      fprintf(stderr, "Element #%u (MatTyp: %s, ElmTyp: %s, DatLen: %d)\n", handle->elementsRead, matrixTypeString(nextElement->matTyp), xmlTypeString(nextElement->elmTyp), nextElement->datLen);
      exit(4);
   }

   // set attribute value
   node->value = cloneBytes((char*)nextElement->buffer, nextElement->datLen);
   node->valueLen = nextElement->datLen;

#ifdef DEBUGOUTPUT
   fprintf(stderr, "New attribute: %.*s=%.*s\n", (int)node->nameLen, node->name, (int)node->valueLen, node->value);
#endif

   handle->domNodesCreated++;
   return node;
}

static XmlNode* processText(XmlNode* parent, FlmElmRec0* element, ReadHdl* handle) {
   XmlNode* node = newXmlNode();

   // set node type to tag
   node->type = NODE_TEXT;
   // set parent node
   node->parent = parent;
   // set data
   node->value = cloneBytes((char*)element->buffer, element->datLen);
   node->valueLen = element->datLen;

#ifdef DEBUGOUTPUT
   fprintf(stderr, "New data: length=%lu\n", (unsigned long)node->valueLen);
#endif

   handle->domNodesCreated++;
   return node;
}

static XmlNode* processCdata(XmlNode* parent, FlmElmRec0* element, ReadHdl* handle) {
   XmlNode* node = newXmlNode();
   XmlNode* childTail = NULL;
   FlmElmRec0* nextElement;

   // set node type to tag
   node->type = NODE_CDATA;
   // set parent node
   node->parent = parent;

#ifdef DEBUGOUTPUT
   fprintf(stderr, "New CDATA section\n");
#endif

   // process the first data element
   nextElement = readNextElement(handle);
   if (nextElement!=NULL && nextElement->elmTyp==FLMELM_TYPXML_DATA) {
      node->children = childTail = processText(node, nextElement, handle);
      // process all other data elements
      for (nextElement = readNextElement(handle); nextElement!=NULL && nextElement->elmTyp==FLMELM_TYPXML_DATA; nextElement = readNextElement(handle)) {
         childTail->next = processText(node, nextElement, handle);
         childTail = childTail->next;
      }
   }

   // data MUST be followed by the CDATA end marker ]]>
   if (nextElement==NULL) {
      fprintf(stderr, "Unexpected end of element stream after element #%u.\n", handle->elementsRead);
      fprintf(stderr, "Expected end of CDATA section.\n");
      exit(4);
   }
   if (nextElement->elmTyp!=FLMELM_TYPXML_ENDCD) {
      fprintf(stderr, "Expected end of CDATA section, but encountered this element:\n");
      fprintf(stderr, "Element #%u (MatTyp: %s, ElmTyp: %s, DatLen: %d)\n", handle->elementsRead, matrixTypeString(nextElement->matTyp), xmlTypeString(nextElement->elmTyp), nextElement->datLen);
      exit(4);
   }

   handle->domNodesCreated++;
   return node;
}

static XmlNode* processComment(XmlNode* parent, FlmElmRec0* element, ReadHdl* handle) {
   XmlNode* node = newXmlNode();

   // set node type to tag
   node->type = NODE_COMMENT;
   // set parent node
   node->parent = parent;
   // set data
   node->value = cloneBytes((char*)element->buffer, element->datLen);
   node->valueLen = element->datLen;

#ifdef DEBUGOUTPUT
   fprintf(stderr, "New comment: length=%lu\n", (unsigned long)node->valueLen);
#endif

   handle->domNodesCreated++;
   return node;
}

static XmlNode* newXmlNode(void) {
   XmlNode* node;

   node = (XmlNode*)malloc(sizeof(XmlNode));
   if (node==NULL) {
      fprintf(stderr, "Could not allocate XmlNode structure.\n");
      exit(4);
   }
   node->parent = NULL;
   node->type = 0;
   node->nameLen = 0;
   node->name = NULL;
   node->valueLen = 0;
   node->value = NULL;
   node->attributes = NULL;
   node->children = NULL;
   node->next = NULL;

   return node;
}

static void freeXmlDocument(XmlDoc* doc) {
   if (doc->header)
      freeXmlNode(doc->header);
   if (doc->root)
      freeXmlNode(doc->root);
   free(doc);
}

static void freeXmlNode(XmlNode* node) {
   // deallocate nodes recursively
   // (i.e. all linked nodes are destroyed as well)
   if (node->attributes)
      freeXmlNode(node->attributes);
   if (node->children)
      freeXmlNode(node->children);
   if (node->next)
      freeXmlNode(node->next);
   if (node->name)
      free(node->name);
   if (node->value)
      free(node->value);
   free(node);
}

static void printXmlNode(XmlNode* node, int recursive, int filtered) {
   switch(node->type) {
      case NODE_XML:
         printf("XML tag\n");
         break;
      case NODE_TAG:
         indentNode(node);
         if (node->attributes!=NULL) {
            printf("<%.*s> (", (int)node->nameLen, node->name);
            printXmlNode(node->attributes, recursive, filtered);
            printf(")\n");
         } else {
            printf("<%.*s>\n", (int)node->nameLen, node->name);
         }
         break;
      case NODE_ATTRIBUTE:
         if (node->next!=NULL) {
            printf("%.*s=%.*s, ", (int)node->nameLen, node->name, (int)node->valueLen, node->value);
         } else {
            printf("%.*s=%.*s", (int)node->nameLen, node->name, (int)node->valueLen, node->value);
         }
         break;
      case NODE_CDATA:
         indentNode(node);
         printf("CDATA section\n");
         break;
      case NODE_TEXT:
         if (filtered) {
            for (size_t i=0; i<node->valueLen; i++) {
               if (node->value[i]<0 || node->value[i]>0x20) {
                  indentNode(node);
                  if (node->valueLen > PRINTLIMIT) {
                     printf("TEXT: %.*s...\n", PRINTLIMIT, node->value);
                  } else {
                     printf("TEXT: %.*s\n", (int)node->valueLen, node->value);
                  }
                  break;
               }
            }
         } else {
            indentNode(node);
            if (node->valueLen > PRINTLIMIT) {
               printf("TEXT: %.*s...\n", PRINTLIMIT, node->value);
            } else {
               printf("TEXT: %.*s\n", (int)node->valueLen, node->value);
            }
         }
         break;
      case NODE_COMMENT:
         indentNode(node);
         if (node->valueLen > PRINTLIMIT) {
            printf("COMMENT: %.*s...\n", PRINTLIMIT, node->value);
         } else {
            printf("COMMENT: %.*s...\n", (int)node->valueLen, node->value);
         }
         break;
   }
   if (recursive) {
      if (node->children!=NULL)
         printXmlNode(node->children, recursive, filtered);
      if (node->next!=NULL)
         printXmlNode(node->next, recursive, filtered);
   }
}

static unsigned int indentNode(XmlNode* node) {
   unsigned int depth = 0;

   while (node->parent!=NULL) {
      depth++;
      node = node->parent;
   }

   if (depth) {
      for (int i=0; i<depth; i++) {
         printf("  ");
      }
   }

   return depth;
}

static char* cloneBytes(char* buffer, size_t len) {
   char* clone = (char*)malloc(len);
   if (clone==NULL) {
      fprintf(stderr, "Could not allocate clone buffer.\n");
      exit(4);
   }
   memcpy(clone, buffer, len);
   return clone;
}

static char* xmlTypeString(int type) {
   switch (type) {
     case FLMELM_TYPXML_DATA:       return "DATA";
     case FLMELM_TYPXML_STARTELM:   return "STARTELM";
     case FLMELM_TYPXML_ENDSTRTELM: return "ENDSTRTELM";
     case FLMELM_TYPXML_ENDELM:     return "ENDELM";
     case FLMELM_TYPXML_ATTNAME:    return "ATTNAME";
     case FLMELM_TYPXML_ATTRVAL:    return "ATTRVAL";
     case FLMELM_TYPXML_XML:        return "XML";
     case FLMELM_TYPXML_SKIPENT:    return "SKIPENT";
     case FLMELM_TYPXML_STARTDTD:   return "STARTDTD";
     case FLMELM_TYPXML_ENDDTD:     return "ENDDTD";
     case FLMELM_TYPXML_ELMDECL:    return "ELMDECL";
     case FLMELM_TYPXML_ATTLDECL:   return "ATTLDECL";
     case FLMELM_TYPXML_INTENTDECL: return "INTENTDECL";
     case FLMELM_TYPXML_SYSENTDECL: return "SYSENTDECL";
     case FLMELM_TYPXML_PUBENTDECL: return "PUBENTDECL";
     case FLMELM_TYPXML_NOTDECLS:   return "NOTDECLS";
     case FLMELM_TYPXML_NOTDECLP:   return "NOTDECLP";
     case FLMELM_TYPXML_NOTDECLPS:  return "NOTDECLPS";
     case FLMELM_TYPXML_PROCINST:   return "PROCINST";
     case FLMELM_TYPXML_STARTCD:    return "STARTCD";
     case FLMELM_TYPXML_ENDCD:      return "ENDCD";
     case FLMELM_TYPXML_COMMENT:    return "COMMENT";
     case FLMELM_TYPXML_DEFAULT:    return "DEFAULT";
   }
   return "Unknown";
}

static char* matrixTypeString(int type) {
   switch(type) {
      case FLMMAT_TYP_DATBLK:    return "DATBLK";
      case FLMMAT_TYP_STDREC:    return "STDREC";
      case FLMMAT_TYP_STDASAREC: return "STDASAREC";
      case FLMMAT_TYP_STDMCCREC: return "STDMCCREC";
      case FLMMAT_TYP_RELREC:    return "RELREC";
      case FLMMAT_TYP_RELASAREC: return "RELASAREC";
      case FLMMAT_TYP_RELMCCREC: return "RELMCCREC";
      case FLMMAT_TYP_TXTREC:    return "TXTREC";
      case FLMMAT_TYP_TXTDLM:    return "TXTDLM";
      case FLMMAT_TYP_TXTRST:    return "TXTRST";
      case FLMMAT_TYP_XMLELM:    return "XMLELM";
   }
   return "Unknown";
}

/******************************************************************************/

