text: // Licensed to the Apache Software Foundation (ASF) under one
  // or more contributor license agreements.  See the NOTICE file
  // distributed with this work for additional information
  // regarding copyright ownership.  The ASF licenses this file
  // to you under the Apache License, Version 2.0 (the
  // "License"); you may not use this file except in compliance
  // with the License.  You may obtain a copy of the License at
  //
  //   http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing,
  // software distributed under the License is distributed on an
  // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  // KIND, either express or implied.  See the License for the
  // specific language governing permissions and limitations
  // under the License.
  
  #include "DFPlatform.h"
  #include "DFXML.h"
  #include "DFDOM.h"
  #include "DFMarkupCompatibility.h"
  #include "DFHTML.h"
  #include "DFBuffer.h"
  #include "DFString.h"
  #include "DFCommon.h"
  #include <assert.h>
  #include <libxml/tree.h>
  #include <libxml/xmlwriter.h>
  #include <string.h>
  
  const xmlChar *INDENT =
  (const xmlChar *)"\n                                                                                "\
  "                                                                                "\
  "                                                                                "\
  "                                                                                ";
  
  static int HTML_requiresCloseTag(Tag tag)
  {
      // FIXME: Check for any other such tags
      switch (tag) {
          case HTML_IMG:
          case HTML_BR:
          case HTML_META:
          case HTML_LINK:
          case HTML_HR:
          case HTML_COL:
              return 0;
          default:
              return 1;
      }
  }
  
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  //                                                                                                //
  //                                           DFSAXParser                                          //
  //                                                                                                //
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  
  static void DFSAXSetup(xmlSAXHandler *handler);
  
  typedef struct DFSAXParser DFSAXParser;
  
  struct DFSAXParser {
      DFDocument *document;
      DFBuffer *warnings;
      DFBuffer *errors;
      DFBuffer *fatalErrors;
      DFMarkupCompatibility *compatibility;
      unsigned int ignoreDepth;
  
      DFNode *parent; // not explicitly retained
  };
  
  DFSAXParser *DFSAXParserNew(void)
  {
      DFSAXParser *parser = (DFSAXParser *)xcalloc(1,sizeof(DFSAXParser));
      parser->document = DFDocumentNew();
      parser->parent = parser->document->docNode;
      parser->warnings = DFBufferNew();
      parser->errors = DFBufferNew();
      parser->fatalErrors = DFBufferNew();
      parser->compatibility = DFMarkupCompatibilityNew();
      return parser;
  }
  
  void DFSAXParserFree(DFSAXParser *parser)
  {
      DFDocumentRelease(parser->document);
      DFBufferRelease(parser->warnings);
      DFBufferRelease(parser->errors);
      DFBufferRelease(parser->fatalErrors);
      DFMarkupCompatibilityFree(parser->compatibility);
      free(parser);
  }
  
  void DFSAXParserParse(DFSAXParser *parser, const void *data, size_t len)
  {
      xmlSAXHandler handler;
      DFSAXSetup(&handler);
      xmlSAXUserParseMemory(&handler,parser,data,(int)len);
  }
  
  static void SAXStartElementNS(void *ctx, const xmlChar *localname,
                                const xmlChar *prefix, const xmlChar *URI,
                                int nb_namespaces, const xmlChar **namespaces,
                                int nb_attributes, int nb_defaulted, const xmlChar **attributes)
  {
      DFSAXParser *parser = (DFSAXParser *)ctx;
  
      if (parser->ignoreDepth > 0) {
          parser->ignoreDepth++;
          return;
      }
  
      for (int i = 0; i < nb_namespaces; i++) {
          const xmlChar *nsPrefix = namespaces[i*2];
          const xmlChar *nsURI = namespaces[i*2+1];
          DFNameMapFoundNamespace(parser->document->map,(const char *)nsURI,(const char *)nsPrefix);
      }
  
      Tag tag = DFNameMapTagForName(parser->document->map,(const char *)URI,(const char *)localname);
  
      if (parser->compatibility != NULL) {
          const TagDecl *tagDecl = DFNameMapNameForTag(parser->document->map,tag);
          MCAction action = DFMarkupCompatibilityLookup(parser->compatibility,tagDecl->namespaceID,tag,1);
          if (action == MCActionIgnore) {
              parser->ignoreDepth++;
              return;
          }
      }
  
      if (parser->compatibility != NULL) {
          DFMarkupCompatibilityPush(parser->compatibility,nb_namespaces,(const char **)namespaces,parser->document->map);
      }
  
      DFNode *element = DFCreateElement(parser->document,tag);
      for (int i = 0; i < nb_attributes; i++) {
          const xmlChar *attrLocalName = attributes[i*5+0];
          const xmlChar *attrURI = attributes[i*5+2];
          const xmlChar *attrValueStart = attributes[i*5+3];
          const xmlChar *attrValueEnd = attributes[i*5+4];
          unsigned long attrValueLen = (unsigned long)(attrValueEnd - attrValueStart);
  
          Tag attrTag = DFNameMapTagForName(parser->document->map,(const char *)attrURI,(const char *)attrLocalName);
          const TagDecl *attrTagDecl = DFNameMapNameForTag(parser->document->map,attrTag);
          char *attrValue = (char *)xmalloc(attrValueLen+1);
          memcpy(attrValue,attrValueStart,attrValueLen);
          attrValue[attrValueLen] = '\0';
          if (parser->compatibility != NULL) {
              switch (attrTag) {
                  case MC_IGNORABLE:
                  case MC_PROCESSCONTENT:
                  case MC_MUSTUNDERSTAND:
                      DFMarkupCompatibilityProcessAttr(parser->compatibility,attrTag,attrValue,parser->document->map);
                      break;
                  default: {
                      MCAction action = DFMarkupCompatibilityLookup(parser->compatibility,attrTagDecl->namespaceID,0,0);
                      if (action != MCActionIgnore)
                          DFSetAttribute(element,attrTag,attrValue);
                      break;
                  }
              }
          }
          else {
              DFSetAttribute(element,attrTag,attrValue);
          }
          free(attrValue);
      }
  
      DFAppendChild(parser->parent,element);
      parser->parent = element;
      if (parser->document->root == NULL)
          parser->document->root = element;
  }
  
  static void SAXEndElementNS(void *ctx, const xmlChar *localname,
                              const xmlChar *prefix, const xmlChar *URI)
  {
      DFSAXParser *parser = (DFSAXParser *)ctx;
  
      if (parser->ignoreDepth > 0) {
          parser->ignoreDepth--;
          return;
      }
  
      if (parser->compatibility != NULL)
          DFMarkupCompatibilityPop(parser->compatibility);
  
      assert(parser->parent != NULL);
      parser->parent = parser->parent->parent;
      assert(parser->parent != NULL);
  }
  
  // SAXStartElement and SAXEndElement are used only when parsing HTML
  
  static void SAXStartElement(void *ctx, const xmlChar *fullname, const xmlChar **atts)
  {
      DFSAXParser *parser = (DFSAXParser *)ctx;
      const NamespaceDecl *namespaceDecl = DFNameMapNamespaceForID(parser->document->map,NAMESPACE_HTML);
      Tag tag = DFNameMapTagForName(parser->document->map,namespaceDecl->namespaceURI,(const char *)fullname);
      DFNode *element = DFCreateElement(parser->document,tag);
      if (atts != NULL) {
          for (int i = 0; atts[i] != NULL; i += 2) {
              const xmlChar *name = atts[i];
              const xmlChar *value = atts[i+1];
              Tag attrTag = DFNameMapTagForName(parser->document->map,namespaceDecl->namespaceURI,(const char *)name);
              DFSetAttribute(element,attrTag,(const char *)value);
          }
      }
      DFAppendChild(parser->parent,element);
      parser->parent = element;
      if (parser->document->root == NULL)
          parser->document->root = element;
  }
  
  static void SAXEndElement(void *ctx, const xmlChar *name)
  {
      DFSAXParser *parser = (DFSAXParser *)ctx;
      assert(parser->parent != NULL);
      parser->parent = parser->parent->parent;
      assert(parser->parent != NULL);
  }
  
  static void SAXCharacters(void *ctx, const xmlChar *ch, int len)
  {
      DFSAXParser *parser = (DFSAXParser *)ctx;
      if (parser->ignoreDepth > 0)
          return;
      char *data = (char *)xmalloc(len+1);
      memcpy(data,ch,len);
      data[len] = '\0';
      DFNode *text = DFCreateTextNode(parser->document,data);
      assert(parser->parent != NULL);
      DFAppendChild(parser->parent,text);
      free(data);
  }
  
  static void SAXComment(void *ctx, const xmlChar *value)
  {
      DFSAXParser *parser = (DFSAXParser *)ctx;
      if (parser->ignoreDepth > 0)
          return;;
      DFNode *comment = DFCreateComment(parser->document,(const char *)value);
      assert(parser->parent != NULL);
      DFAppendChild(parser->parent,comment);
  }
  
  static void SAXCDATABlock(void *ctx, const xmlChar *value, int len)
  {
      DFSAXParser *parser = (DFSAXParser *)ctx;
      if (parser->ignoreDepth > 0)
          return;
      char *data = (char *)xmalloc(len+1);
      memcpy(data,value,len);
      data[len] = '\0';
      DFNode *cdata = DFCreateTextNode(parser->document,data);
      assert(parser->parent != NULL);
      DFAppendChild(parser->parent,cdata);
      free(data);
  }
  
  static void SAXProcessingInstruction(void *ctx, const xmlChar *target, const xmlChar *data)
  {
      DFSAXParser *parser = (DFSAXParser *)ctx;
      if (parser->ignoreDepth > 0)
          return;;
      DFNode *pi = DFCreateProcessingInstruction(parser->document,(const char *)target,(const char *)data);
      assert(parser->parent != NULL);
      DFAppendChild(parser->parent,pi);
  }
  
  static void SAXWarning(void *ctx, const char *msg, ...)
  {
      DFSAXParser *parser = (DFSAXParser *)ctx;
      va_list ap;
      va_start(ap,msg);
      DFBufferFormat(parser->warnings,msg,ap);
      va_end(ap);
  }
  
  static void SAXError(void *ctx, const char *msg, ...)
  {
      DFSAXParser *parser = (DFSAXParser *)ctx;
      va_list ap;
      va_start(ap,msg);
      DFBufferFormat(parser->errors,msg,ap);
      va_end(ap);
  }
  
  static void SAXFatalError(void *ctx, const char *msg, ...)
  {
      DFSAXParser *parser = (DFSAXParser *)ctx;
      va_list ap;
      va_start(ap,msg);
      DFBufferFormat(parser->fatalErrors,msg,ap);
      va_end(ap);
  }
  
  static void DFSAXSetup(xmlSAXHandler *handler)
  {
      bzero(handler,sizeof(xmlSAXHandler));
      handler->characters = SAXCharacters;
      handler->processingInstruction = SAXProcessingInstruction;
      handler->comment = SAXComment;
      handler->cdataBlock = SAXCDATABlock;
      handler->warning = SAXWarning;
      handler->error = SAXError;
      handler->error = SAXFatalError;
      handler->startElementNs = SAXStartElementNS;
      handler->endElementNs = SAXEndElementNS;
      handler->startElement = SAXStartElement;
      handler->endElement = SAXEndElement;
      handler->initialized = XML_SAX2_MAGIC;
  }
  
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  //                                                                                                //
  //                                              DFXML                                             //
  //                                                                                                //
  ////////////////////////////////////////////////////////////////////////////////////////////////////
  
  void htmlSAXParseDoc(xmlChar * cur, const char * encoding, xmlSAXHandlerPtr sax, void * userData);
  
  typedef struct {
      xmlTextWriterPtr writer;
      DFDocument *doc;
      NamespaceID defaultNS;
      int html;
      int indent;
  } Serialization;
  
  static void writeNode(Serialization *serialization, DFNode *node, int depth);
  
  static void findUsedNamespaces(DFDocument *doc, DFNode *node, char *used, NamespaceID count)
  {
      if (node->tag < MIN_ELEMENT_TAG)
          return;;
      const TagDecl *tagDecl = DFNameMapNameForTag(doc->map,node->tag);
      assert(tagDecl != NULL);
      assert(tagDecl->namespaceID < count);
      used[tagDecl->namespaceID] = 1;
  
      // Attributes
      if (node->tag >= MIN_ELEMENT_TAG) {
          for (unsigned int i = 0; i < node->attrsCount; i++) {
              Tag tag = node->attrs[i].tag;
              const TagDecl *attrDecl = DFNameMapNameForTag(doc->map,tag);
              assert(attrDecl != NULL);
              if ((attrDecl->namespaceID != NAMESPACE_NULL) &&
                  (attrDecl->namespaceID != NAMESPACE_XML))
                  used[attrDecl->namespaceID] = 1;
          }
      }
  
      // Children
      for (DFNode *child = node->first; child != NULL; child = child->next)
          findUsedNamespaces(doc,child,used,count);
  }
  
  static void writeNamespaceDeclarations(Serialization *serialization, DFNode *node)
  {
      NamespaceID count = DFNameMapNamespaceCount(serialization->doc->map);
      char *used = (char *)xcalloc(1,count);
      findUsedNamespaces(serialization->doc,node,used,count);
      for (NamespaceID nsId = 1; nsId < count; nsId++) { // don't write null namespace
          if (used[nsId]) {
              const NamespaceDecl *nsDecl = DFNameMapNamespaceForID(serialization->doc->map,nsId);
              const xmlChar *prefix = (const xmlChar *)nsDecl->prefix;
              const xmlChar *URI = (const xmlChar *)nsDecl->namespaceURI;
              if (nsId == serialization->defaultNS)
                  xmlTextWriterWriteAttribute(serialization->writer,(const xmlChar *)"xmlns",URI);
              else
                  xmlTextWriterWriteAttributeNS(serialization->writer,(const xmlChar *)"xmlns",prefix,NULL,URI);
          }
      }
      free(used);
  }
  
  static int compareAttrs(const void *a, const void *b)
  {
      const DFAttribute *attrA = (const DFAttribute *)a;
      const DFAttribute *attrB = (const DFAttribute *)b;
      if (attrA->tag < attrB->tag)
          return -1;
      else if (attrA->tag > attrB->tag)
          return 1;
      else
          return 0;
  }
  
  static void writeAttributes(Serialization *serialization, DFNode *element)
  {
      // Sort the keys by their tag, to ensure that we always write attributes out in the same order.
      // This is important for automated tests which rely on consistent output for a given XML tree.
      DFAttribute *attrs = (DFAttribute *)xmalloc(element->attrsCount*sizeof(DFAttribute));
      memcpy(attrs,element->attrs,element->attrsCount*sizeof(DFAttribute));
      qsort(attrs,element->attrsCount,sizeof(DFAttribute),compareAttrs);
  
      for (unsigned int i = 0; i < element->attrsCount; i++) {
          Tag tag = attrs[i].tag;
  
          const TagDecl *tagDecl = DFNameMapNameForTag(serialization->doc->map,tag);
          assert(tagDecl != NULL);
          const NamespaceDecl *nsDecl = DFNameMapNamespaceForID(serialization->doc->map,tagDecl->namespaceID);
          assert(nsDecl != NULL);
  
          const xmlChar *prefix = (const xmlChar *)nsDecl->prefix;
          const xmlChar *localName = (const xmlChar *)tagDecl->localName;
          const xmlChar *value = (const xmlChar *)attrs[i].value;
  
          if (serialization->html && (tagDecl->namespaceID == NAMESPACE_HTML))
              xmlTextWriterWriteAttribute(serialization->writer,localName,value);
          else
              xmlTextWriterWriteAttributeNS(serialization->writer,prefix,localName,NULL,value);
      }
      free(attrs);
  }
  
  static void writeElement(Serialization *serialization, DFNode *element, int depth)
  {
      const TagDecl *tagDecl = DFNameMapNameForTag(serialization->doc->map,element->tag);
      assert(tagDecl != NULL);
      const NamespaceDecl *nsDecl = DFNameMapNamespaceForID(serialization->doc->map,tagDecl->namespaceID);
      assert(nsDecl != NULL);
  
      const xmlChar *prefix = (const xmlChar *)nsDecl->prefix;
      const xmlChar *localName = (const xmlChar *)tagDecl->localName;
  
      if (serialization->indent && (element->parent != element->doc->docNode))
          xmlTextWriterWriteRawLen(serialization->writer,INDENT,1+depth);
  
      if (serialization->html || (tagDecl->namespaceID == serialization->defaultNS))
          xmlTextWriterStartElement(serialization->writer,localName);
      else
          xmlTextWriterStartElementNS(serialization->writer,prefix,localName,NULL);
  
      if ((element->parent == serialization->doc->docNode) && !serialization->html)
          writeNamespaceDeclarations(serialization,element);
  
      writeAttributes(serialization,element);
  
      // Check if all children are text nodes. If this is true; we should treat them as if they are a single text
      // node, and not do any indentation.
      int allChildrenText = 1;
      for (DFNode *child = element->first; child != NULL; child = child->next) {
          if (child->tag != DOM_TEXT)
              allChildrenText = 0;
      }
  
      if (allChildrenText) {
          int oldIndent = serialization->indent;
          serialization->indent = 0;
          for (DFNode *child = element->first; child != NULL; child = child->next)
              writeNode(serialization,child,depth+2);
          serialization->indent = oldIndent;
      }
      else {
          for (DFNode *child = element->first; child != NULL; child = child->next)
              writeNode(serialization,child,depth+2);
      }
  
      if (serialization->indent && (element->first != NULL) && !allChildrenText) {
          if ((element->first != element->last) ||
              (element->first->tag != DOM_TEXT))
          xmlTextWriterWriteRawLen(serialization->writer,INDENT,1+depth);
      }
  
      if (serialization->html && (element->first == NULL) && HTML_requiresCloseTag(element->tag)) {
          xmlTextWriterWriteString(serialization->writer,(xmlChar *)"");
      }
  
      xmlTextWriterEndElement(serialization->writer);
  }
  
  static void writeNode(Serialization *serialization, DFNode *node, int depth)
  {
      switch (node->tag) {
          case DOM_DOCUMENT: {
              if (!serialization->html)
                  xmlTextWriterStartDocument(serialization->writer,"1.0","UTF-8","yes");
              if (serialization->html)
                  xmlTextWriterWriteDTD(serialization->writer,(xmlChar *)"html",NULL,NULL,NULL);
  //            xmlTextWriterWriteDTD(writer,
  //                                  (xmlChar *)"html",
  //                                  (xmlChar *)"-//W3C//DTD XHTML 1.0 Strict//EN",
  //                                  (xmlChar *)"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd",
  //                                  NULL);
              for (DFNode *child = node->first; child != NULL; child = child->next)
                  writeNode(serialization,child,0);
              xmlTextWriterEndDocument(serialization->writer);
              break;
          }
          case DOM_TEXT: {
              if (serialization->indent && ((node->prev != NULL) || (node->next != NULL)))
                  xmlTextWriterWriteRawLen(serialization->writer,INDENT,1+depth);
              if (serialization->html && (node->parent != NULL) && (node->parent->tag == HTML_STYLE)) {
                  xmlTextWriterWriteRaw(serialization->writer,(const xmlChar *)node->value);
              }
              else {
                  xmlTextWriterWriteString(serialization->writer,(const xmlChar *)node->value);
              }
              break;
          }
          case DOM_COMMENT: {
              xmlTextWriterWriteComment(serialization->writer,(const xmlChar *)node->value);
              break;
          }
          case DOM_CDATA: {
              xmlTextWriterWriteCDATA(serialization->writer,(const xmlChar *)node->value);
              break;
          }
          case DOM_PROCESSING_INSTRUCTION: {
              xmlTextWriterWritePI(serialization->writer,
                                   (const xmlChar *)node->target,
                                   (const xmlChar *)node->value);
              break;
          }
          default: {
              if (node->parent == serialization->doc->docNode)
                  writeElement(serialization,node,0);
              else
                  writeElement(serialization,node,depth);
              break;
          }
      }
  }
  
  DFDocument *DFParseXMLString(const char *str, DFError **error)
  {
      DFSAXParser *parser = DFSAXParserNew();
      DFSAXParserParse(parser,str,strlen(str));
      if (parser->fatalErrors->len > 0) {
          DFErrorFormat(error,"%s",parser->fatalErrors->data);
          DFSAXParserFree(parser);
          return NULL;
      }
      else if (parser->errors->len > 0) {
          DFErrorFormat(error,"%s",parser->errors->data);
          DFSAXParserFree(parser);
          return NULL;
      }
      else if (parser->document->root == NULL) {
          DFErrorFormat(error,"No root element");
          DFSAXParserFree(parser);
          return NULL;
      }
  
      DFDocument *result = DFDocumentRetain(parser->document);
      DFSAXParserFree(parser);
      return result;
  }
  
  DFDocument *DFParseXMLFile(const char *filename, DFError **error)
  {
      DFBuffer *buf = DFBufferReadFromFile(filename,error);
      if (buf == NULL)
          return NULL;;
      DFDocument *doc = DFParseXMLString(buf->data,error);
      DFBufferRelease(buf);
      return doc;
  }
  
  DFDocument *DFParseXMLStorage(DFStorage *storage, const char *filename, DFError **error)
  {
      DFBuffer *content = DFBufferReadFromStorage(storage,filename,error);
      if (content == NULL)
          return NULL;;
      DFDocument *doc = DFParseXMLString(content->data,error);
      DFBufferRelease(content);
      return doc;
  }
  
  static int StringBufferWrite(void *context, const char *buffer, int len)
  {
      DFBuffer *buf = (DFBuffer *)context;
      DFBufferAppendData(buf,buffer,len);
      return len;
  }
  
  static int StringBufferClose(void *context)
  {
      return 0;
  }
  
  void DFSerializeXMLBuffer(DFDocument *doc, NamespaceID defaultNS, int indent, DFBuffer *buf)
  {
      xmlOutputBufferPtr output = xmlOutputBufferCreateIO(StringBufferWrite,
                                                          StringBufferClose,
                                                          buf,
                                                          NULL);
      xmlTextWriterPtr writer = xmlNewTextWriter(output);
  
      int html = 0;
      for (DFNode *child = doc->docNode->first; child != NULL; child = child->next) {
          if (child->tag == HTML_HTML)
              html = 1;
      }
  
      Serialization serialization;
      bzero(&serialization,sizeof(serialization));
      serialization.writer = writer;
      serialization.doc = doc;
      serialization.defaultNS = defaultNS;
      serialization.html = html;
      serialization.indent = indent;
      writeNode(&serialization,doc->docNode,0);
      xmlFreeTextWriter(writer);
  }
  
  char *DFSerializeXMLString(DFDocument *doc, NamespaceID defaultNS, int indent)
  {
      DFBuffer *buf = DFBufferNew();
      DFSerializeXMLBuffer(doc,defaultNS,indent,buf);
      char *result = xstrdup(buf->data);
      DFBufferRelease(buf);
      return result;
  }
  
  int DFSerializeXMLFile(DFDocument *doc, NamespaceID defaultNS, int indent, const char *filename, DFError **error)
  {
      DFBuffer *buf = DFBufferNew();
      DFSerializeXMLBuffer(doc,defaultNS,indent,buf);
      int r = DFBufferWriteToFile(buf,filename,error);
      DFBufferRelease(buf);
      return r;
  }
  
  int DFSerializeXMLStorage(DFDocument *doc, NamespaceID defaultNS, int indent,
                            DFStorage *storage, const char *filename,
                            DFError **error)
  {
      char *str = DFSerializeXMLString(doc,defaultNS,indent);
      DFBuffer *content = DFBufferNew();
      DFBufferAppendString(content,str);
      int r = DFBufferWriteToStorage(content,storage,filename,error);
      DFBufferRelease(content);
      free(str);
      return r;
  }
  