/* Copyright (c) 2005-2008 DN-Systems GmbH http://rfdump.org

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */

/* $Header: /cvs/rfdump/src/xml.c,v 1.13 2008/01/23 00:51:50 dirk Exp $ */

#include <ctype.h>
#include <errno.h>
#include <expat.h>
#include <stdio.h>
#include <string.h>

#include "main.h"
#include "tools.h"
#include "xml.h"

int xmlWriteTag(const struct RFIDTag *tag, const char *fn)
{
  FILE *fp;

  g_assert(tag);
  g_assert(fn);

  if ((fp=fopen(fn,"w")) == NULL)
    {
      fprintf(stderr, "Could not open %s: %s\n", fn, strerror(errno));
      return -1;
    }

  fprintf(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
  fprintf(fp, "<!DOCTYPE tag SYSTEM \"rfd.dtd\">\n");
  fprintf(fp, "<rfd:tag xmlns:rfd=\"http://www.rfdump.org/rfd\" version=\"1.0\">\n");
  fprintf(fp, "  <rfd:tagID>%s</rfd:tagID>\n", tag->tagID);
  fprintf(fp, "  <rfd:tagTypeName>%s</rfd:tagTypeName>\n", tag->tagTypeName);
  fprintf(fp, "  <rfd:tagManufacturerName>%s</rfd:tagManufacturerName>\n", tag->tagManufacturerName);
  fprintf(fp, "  <rfd:tagData encoding=\"HEX\" pagesize=\"%X\">\n", tag->pageSize);

  int i;
  for (i=0; i<sizeof_array(tag->mem); i++)
    if (tag->mem[i] != NULL)
      {
	fprintf(fp, "    <rfd:tagDataItem offset=\"%02X\"", i);
	if(tag->keya[i])
	  fprintf(fp, " mifare_keya=\"%s\"", tag->keya[i]);
	if(tag->keyb[i])
	  fprintf(fp, " mifare_keyb=\"%s\"", tag->keyb[i]);
	fprintf(fp, ">%s</rfd:tagDataItem>", tag->mem[i]);

	fprintf(fp, " <!-- ");
	char *p=tag->mem[i];
	while(*p)
	  {
	    int ascii=getCharValue(*p++)*16;
	    if(*p)
	      {
		ascii+=getCharValue(*p++);
		fputc((isprint(ascii) && ascii!='>' && ascii<128)?ascii:'.', fp);
	      }
	  }
	fprintf(fp, " -->\n");
      }

  fprintf(fp, "  </rfd:tagData>\n");
  fprintf(fp, "</rfd:tag>\n");

  fclose(fp);

  return 0;
}

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

static int depth;
static int offset;
static int offset_max;
static const char *element;
static const char *mifare_keya, *mifare_keyb;
static struct RFIDTag tmpTag;

#define XMLDEBUG 0

static void XMLCALL start(void *data, const char *el, const char **attr)
{
  int i;

  g_assert(el);
  g_assert(attr);

#if XMLDEBUG
  for (i = 0; i < depth; i++)
    printf("  ");
  printf("%s:",el);
#endif

  offset=-1;
  element=el;
  mifare_keya=mifare_keyb=0;

  for (i = 0; attr[i]; i += 2)
    {
#if XMLDEBUG
      printf(" %s='%s'", attr[i], attr[i+1]);
#endif
      if(!strcmp(element, "rfd:tagDataItem"))
	{
	  if (!strcmp(attr[i], "offset"))
	    offset=strtoul(attr[i+1], 0, 16);
	  else if(!strcmp(attr[i], "mifare_keya"))
	    mifare_keya=attr[i+1];
	  else if(!strcmp(attr[i], "mifare_keyb"))
	    mifare_keyb=attr[i+1];
	}
      else if(!strcmp(element, "rfd:tagData"))
	{
	  if (!strcmp(attr[i], "pagesize"))
	    tmpTag.pageSize=strtoul(attr[i+1], 0, 16);
	}
    }

#if XMLDEBUG
  printf("\n");
#endif

  depth++;
}

static void XMLCALL content(void *data, const char *s, int len)
{
  g_assert(s);

  char *textinfo=g_strndup(s, len);
  /* strip whitespace */
  g_strstrip(textinfo);

  len=strlen(textinfo);
  if(len<=0)
    {
#if XMLDEBUG
      printf(" [empty]\n");
#endif
      g_free(textinfo);
      return;
    }

  if(!strcmp(element, "rfd:tagDataItem"))
    {
      element="";
#if XMLDEBUG
      int i;
      for (i = 0; i < depth; i++)
	printf("  ");
#endif

#if XMLDEBUG
      printf(" [content]: %s\n", textinfo);
#endif

      /* check for hex characters */
      int i;
      for(i=0; i<len; ++i)
	if(!isxdigit(textinfo[i]))
	  {
	    g_free(textinfo);
	    return;
	  }

      if(offset>=0 && offset < sizeof_array(tmpTag.mem))
	{
	  tmpTag.mem[offset]=g_strdup(textinfo);
	  if(mifare_keya)
	    tmpTag.keya[offset]=g_strdup(mifare_keya);
	  if(mifare_keyb)
	    tmpTag.keyb[offset]=g_strdup(mifare_keyb);

	  if(offset>offset_max)
	    offset_max=offset;
	}
      else
	{
	  fprintf(stderr, "offset attribute exceeds tag storate: %02X >= %02X", offset, sizeof_array(tmpTag.mem));
	}
    }
  else if(!strcmp(element, "rfd:tagID"))
    {
      element="";
      tmpTag.tagID[0]=0;
      strcat(tmpTag.tagID, textinfo);
    }
  else if(!strcmp(element, "rfd:tagTypeName"))
    {
      element="";
      tmpTag.tagTypeName[0]=0;
      strcat(tmpTag.tagTypeName, textinfo);
    }
  else if(!strcmp(element, "rfd:tagManufacturerName"))
    {
      element="";
      tmpTag.tagManufacturerName[0]=0;
      strcat(tmpTag.tagManufacturerName, textinfo);
    }
  g_free(textinfo);
}

static void XMLCALL end(void *data, const char *el)
{
  depth--;
}

int xmlReadTag(struct RFIDTag *tag, const char *fn)
{
  g_assert(tag);
  g_assert(fn);

  /* read fn into a memory buffer */
  FILE *fp;
  if ((fp=fopen(fn,"r")) == NULL)
    {
      fprintf(stderr, "Could not open %s: %s\n", fn, strerror(errno));
      return -1;
    }

  if(fseek(fp, 0, SEEK_END) != 0)
    {
      fprintf(stderr, "could not seek to end of %s: %s\n", fn, strerror(errno));
      fclose(fp);
      return -1;
    }

  const int len=ftell(fp);
  if(len>1024*1024*1024)
    {
      fprintf(stderr, "%s is larger than 1GB\n", fn);
      fclose(fp);
      return -1;
    }
  rewind(fp);

  char *buff=g_try_malloc(len);
  if(!buff)
    {
      fprintf(stderr, "could not alloc %i bytes\n", len);
      fclose(fp);
      return -1;
    }

  if(fread(buff, 1, len, fp) != len)
    {
      fprintf(stderr, "could not read complete file %s: %s\n", fn, strerror(errno));
      fclose(fp);
      return -1;
    }

  fclose(fp);

  /* prepare xml reading environment */
  tag_clear(&tmpTag);
  depth=offset=offset_max=0;
  element=0;

  /* create xml parser and parse the xml document */
  XML_Parser p = XML_ParserCreate(NULL);
  if (!p)
    {
      fprintf(stderr, "Couldn't allocate memory for parser\n");
      g_free(buff);
      return -1;
    }

  XML_SetElementHandler(p, start, end);
  XML_SetCharacterDataHandler(p, content);

  int done=0;
  if (XML_Parse(p, buff, len, done) == XML_STATUS_ERROR)
    {
      fprintf(stderr, "Parse error at line %d:\n%s\n", (int)XML_GetCurrentLineNumber(p), XML_ErrorString(XML_GetErrorCode(p)));
      done=-1;
    }
  else
    {
      tmpTag.memSize=offset_max+1;
      tag_copydata(tag, &tmpTag);
      done=0;
    }

  g_free(buff);
  XML_ParserFree(p);
  return done;
}
