#include "xmlreader.h"
#include <string.h>

#define min(a,b) (a<b?a:b)

int indexOf(char* s1, char* s2, int i0){
  int n= strlen(s2);
  for (int i= i0; s1[i]; i++){
    if (!strncmp(s1 + i, s2, n)){
      return i;
    }
  }
  return -1;
}
//------------------------------------------------------------------------------

int indexOf2(char* s1, char* s2a, char* s2b, int i0){
  int na= strlen(s2a);
  int nb= strlen(s2b);
  for (int i= i0; s1[i]; i++){
    if (!strncmp(s1 + i, s2a, na)
        ||!strncmp(s1 + i, s2b, nb)){
      return i;
    }
  }
  return -1;
}
//------------------------------------------------------------------------------

/**
 * Zoek het eerste karakter dat voorkomt in s2.
 */
int indexOfAny(char* s1, char* s2, int i0){
  int n= strlen(s2);
  for (int i= i0; s1[i]; i++){
    for (int j= 0; j < n; j++){
      if (s1[i]==s2[j]){
        return i;
      }
    }
  }
  return -1;
}
//------------------------------------------------------------------------------

XmlReader::~XmlReader(){
  delete axParent; // niet de gecreeerde XML-elementen; gebruik deleteCascade
}
//------------------------------------------------------------------------------

XmlReader::XmlReader(FILE* pfIn){
  fseek(pfIn, 0, SEEK_END);
  long lFSize= ftell(pfIn);
  fseek(pfIn, 0, SEEK_SET);
  char* sBuf= new char[lFSize + 1];
  fread(sBuf, lFSize, 1, pfIn);

  // verwerk nu de buffer
  axParent= new XmlElement*[32]; // meer inspringing is toch niet te verwachten?
  readElements(sBuf, lFSize);
  char* sName= axParent[0]->getName();
  fprintf(stderr, "%s: %d elementen\n", sName, axParent[0]->countChildren());
  delete sName;
} // XmlReader::XmlReader(FILE*)
//------------------------------------------------------------------------------

int XmlReader::readElements(char* sBuf, int lFSize){
  bool bBeforeEl= true;
  int iTab= 0; // TODO: als parameter
  int nElementen= 0;
  int i= 0;
  while (i < lFSize){
    if (bBeforeEl){
      int iLT= indexOf(sBuf, "<", i);
      if (iLT < 0){
        break; // geen tags meer
      }
      // hier: controle op soort tag (<!--, <!, <? of een gewoon element)
      i= iLT + 1; // begin van de naam
      bool bTagX= sBuf[i]=='!';
      bool bTagC= bTagX && sBuf[(i)+1]=='-';
      bool bTagQ= sBuf[i]=='?';
      // bijzondere tags apart behandelen
      if (bTagC){
        int iEndComment= indexOf(sBuf, "-->", i);
        if (iEndComment < 0){
          fprintf(stderr, "xml: <!---element niet afgesloten\n");
          break; // foute xml
        }
        i= iEndComment + 3;
        continue; // commentaar overgeslagen
      } else if (bTagX){
        int iEndX= indexOf(sBuf, ">", i);
        if (iEndX < 0){
          fprintf(stderr, "xml: <!-element niet afgesloten\n");
          break; // foute xml
        }
        i= iEndX + 1;
        continue; // <!..> overgeslagen
      } else if (bTagQ){
        int iEndQ= indexOf(sBuf, ">", i);
        if (iEndQ < 0){
          fprintf(stderr, "xml: <?-element niet afgesloten\n");
          break; // foute xml
        }
        i= iEndQ + 2;
        continue; // <?..?> overgeslagen
      }
      int iEndName= indexOfAny(sBuf, "> /\n\r\t", i);
      while (sBuf[iEndName]=='/' && sBuf[iEndName+1]!='>'){
        iEndName= indexOfAny(sBuf, "> /\n\r\t", iEndName + 1);
      }
      int iTagEnd=  indexOf2(sBuf, ">", "/>", i);
      if (iEndName < 0){
        fprintf(stderr, "xml: elementnaam niet afgesloten.\n");
        break; // foute xml
      }
      bool bShortEnd= sBuf[iTagEnd]=='/';
      bool bAttributes= sBuf[iEndName]==' ';
      bool bEndTag= sBuf[i]=='/';
      if (bEndTag){
        iTab--;
        if (iTab==0){
          return i; // afsluiting root-element
        }
        continue; // element, eerder geopend, nu gesloten.
      }
      // interessant: naam, eventuele attributen en daarachter een waarde of andere elementen
      nElementen++;
      char* sName= new char[iEndName - i + 1];
      strncpy(sName, sBuf + i, iEndName - i);
      sName[iEndName - i]= '\0';
      XmlElement* xel= new XmlElement(iTab, sName);
      xel->parseAttributes(sBuf + iEndName, iTagEnd - iEndName);
      if (!bShortEnd){
        int iNextEl= indexOf(sBuf, "<", iTagEnd);
        if (iNextEl > iTagEnd){
          xel->parseValue(sBuf + iTagEnd + 1, iNextEl - (iTagEnd + 1));
        }
      }
      if (iTab > 0){
        axParent[iTab-1]->addChild(xel);
      }
      // afdrukken van de elementnaam met correcte inspringing
      xel->print();
      // en hoe nu verder?:
      if (!bShortEnd){
        axParent[iTab]= xel;
        iTab++;
      }
      delete sName;
    }
    i++;
  }
  return i; // TODO: hier positie waar we gebleven waren
} // XmlReader::readElement
//------------------------------------------------------------------------------

XmlElement* XmlReader::getRootElement(){
  return axParent[0]->clone();
}
//------------------------------------------------------------------------------

XmlElement::XmlElement(int iTab, char* sName){
  this->iTab= iTab;
  this->sName= new char[strlen(sName) + 1];
  strcpy(this->sName, sName);
  this->nAttrs= 0;
  this->asAttr= NULL;
  this->sValue= NULL;
  this->nChildren= 0;
  this->axChild= NULL;
}
//------------------------------------------------------------------------------

XmlElement* XmlElement::clone(){
  XmlElement* x= new XmlElement(iTab, sName);
  x->nAttrs= nAttrs;
  x->asAttr= nAttrs > 0 ? new char*[nAttrs] : NULL;
  for (int i= 0; i < nAttrs; i++){
    x->asAttr[i]= new char[strlen(asAttr[i]) + 1];
    strcpy(x->asAttr[i], asAttr[i]);
  }
  if (sValue){
    x->sValue= new char[strlen(sValue) + 1];
    strcpy(x->sValue, sValue);
  }
  x->nChildren= nChildren;
  if (nChildren > 0){
    x->axChild= new XmlElement*[nChildren];
    for (int i= 0; i < nChildren; i++){
      x->axChild[i]= axChild[i]; // geen kopie dus; het betreft hier geen eigendom
    }
  }
  return x;
}
//------------------------------------------------------------------------------

XmlElement::~XmlElement(){
  // ruim de eigen attributenlijst
  for (int i= nAttrs - 1; i >= 0; i--){
    delete asAttr[i];
  }
  if (asAttr){
    delete asAttr;
  }
  if (sValue){
    delete sValue;
  }
  delete sName;
  if (axChild){
    delete axChild; // niet de children zelf, want die zijn niet in eigendom
  }
}
//------------------------------------------------------------------------------

/**
 *  Verwijdert de kinderen (cascade)
 */
void XmlElement::deleteChildren(){
  for (int i= 0; i < nChildren; i++){
    axChild[i]->deleteChildren();
    delete axChild[i];
  }
}
//------------------------------------------------------------------------------

/**
 * Zet de &#-codes binnen de gegeven waarde om in de juiste ascii-karakters
 */
void XmlElement::decode(char* sValue){
  for(;;){
    int iStartCode= indexOf(sValue, "&#", 0);
    if (iStartCode < 0){
      break;
    }
    int iEndCode= indexOf(sValue, ";", iStartCode + 2);
    int iAscii= 0;
    int iMultip= 10;
    for (int i= iStartCode + 2; i < iEndCode; i++){
      if (sValue[i]=='x'){
        iMultip= 16;
        continue;
      }
      iAscii*=iMultip;
      int iDec;
      if (sValue[i] <= '9'){
        iDec= sValue[i] - '0';
      } else if (sValue[i] >= 'a'){
        iDec= sValue[i] + 10 - 'a';
      } else if (sValue[i] >= 'A'){
        iDec= sValue[i] + 10 - 'A';
      } else{
        // ongeldig decimaal cijfer, impliciet vervangen door '0'
      }
      iAscii+=iDec;
    }
    sValue[iStartCode]= (char) iAscii;
    strcpy(sValue + iStartCode + 1, sValue + iEndCode + 1);
  }
  // waarde zelf is aangepast
} // XmlElement::decode(char*)
//------------------------------------------------------------------------------


/**
 * Geeft een bewerkte kopie van de attribuutwaarde: &#-codes vervangen.
 */
char* XmlElement::getAttributeValue(int i){
  int iTab= indexOf(asAttr[i], "\t", 0);
  char* sValue= new char[strlen(asAttr[i]) - iTab];
  strcpy(sValue, asAttr[i] + iTab + 1);
  decode(sValue);
  return sValue;
}
//------------------------------------------------------------------------------

/**
 * Geeft een bewerkte variant van de waarde van het attribuut met de gegeven naam
 */
char* XmlElement::getAttribute(char* sName){
  for (int i= 0; i < nAttrs; i++){
    if (indexOf(asAttr[i], sName, 0)==0 && asAttr[i][strlen(sName)]=='\t'){
      return getAttributeValue(i);
    }
  }
  return NULL;
}
//------------------------------------------------------------------------------

char* XmlElement::getValue(){
  if (!sValue){
    return NULL;
  }
  char* sRet= new char[strlen(sValue) + 1];
  strcpy(sRet, sValue);
  decode(sRet);
  return sRet;
}
//------------------------------------------------------------------------------

/**
 * Drukt de naam van dit element af met de juiste inspringing,
 * daaronder de attributen en de waarde.
 */
void XmlElement::print(){
  for (int j= 0; j < iTab; j++){
    printf("  ");
  }
  printf("%s\n", sName, nAttrs);
  for (int i= 0; i < nAttrs; i++){
    int iSep= indexOf(asAttr[i], "\t", 0);
    char* sValue= getAttributeValue(i);
    for (int j= 0; j < iTab; j++){
      printf("  ");
    }
    printf("> %.*s = %s\n", iSep, asAttr[i], sValue);
    delete sValue;
  }
  if (sValue){
    for (int j= 0; j < iTab; j++){
      printf("  ");
    }
    printf("\\ %s\n", sValue);
  }
}
//------------------------------------------------------------------------------

/**
 * Extraheert de attributen uit de gegeven string
 * @param sSrc het begin van de string;
 * @param nChars de lengte van de string.
 */
void XmlElement::parseAttributes(char* sSrc, int nChars){
  char* sAttrs= new char[nChars + 1];
  strncpy(sAttrs, sSrc, nChars);
  sAttrs[nChars]= '\0';
  // loop de string af op [_..]naam[_..]=[_..]"waarde"
  for (int i= 0; i < nChars; i++){
    if (sAttrs[i] <= ' '){
      continue;
    }
    // begin van een attribuut gevonden
    int j= i;
    for(;;){if (sAttrs[j]=='='||sAttrs[j]<=' '){break;} j++;}
    // j is het eerste karakter na de attribuutnaam
    int k= j;
    for(;;){if (sAttrs[k]=='='){break;} k++;}
    // k is de positie van de '='
    int l= k + 1;
    for(;;){if (sAttrs[l]=='"'){break;} l++;}
    // l is de positie van de openings-aanhalingstekens
    int m= l + 1;
    for(;;){if (sAttrs[m]=='"'){break;} m++;}
    // m is de positie van de sluit-aanhalingstekens
    int nAttrValueChars= (j-i) + 1 + (m - l - 1); // naam[TAB]waarde
    char* sAttrValue= new char[nAttrValueChars + 1];
    strncpy(sAttrValue, sAttrs + i, (j-i));
    strncpy(sAttrValue + (j-i), "\t", 1);
    strncpy(sAttrValue + (j-i) + 1, sAttrs + l + 1, (m - l - 1));
    sAttrValue[nAttrValueChars]= '\0';
    addAttrValue(sAttrValue);
    delete sAttrValue;
    i= m + 1;
  }
  delete sAttrs;
} // XmlElement::parseAttributes(char*, int){
//------------------------------------------------------------------------------

/**
 * Neemt een attribuut op in de object-interne lijst
 * param sAttrValue attribuutnaam[TAB]waarde
 */
void XmlElement::addAttrValue(char* sAttrValue){
  char* sNew= new char[strlen(sAttrValue) + 1];
  strcpy(sNew, sAttrValue);
  char** asAttr0= asAttr;
  // array verlengen, bestaande gegevens overnemen:
  asAttr= new char*[nAttrs + 1];
  for (int i= 0; i < nAttrs; i++){
    asAttr[i]= asAttr0[i];
  }
  // nieuwe item aan het eind toevoegen:
  asAttr[nAttrs]= sNew;
  nAttrs++;
  if (asAttr0){
    delete asAttr0;
  }
} // XmlElement::addAttrValue(char*)
//------------------------------------------------------------------------------

/**
 * Extraheert de elementwaarde uit de gegeven string
 * @param sSrc het begin van de string;
 * @param nChars de lengte van de string.
 */
void XmlElement::parseValue(char* sSrc, int nChars){
  int i0= 0;
  while (i0 < nChars){
    if (sSrc[i0] > ' '){
      break;
    }
    i0++;
  }
  if (i0==nChars){
    return; // alleen whitespace als waarde - da's waardeloos
  }
  sValue= new char[nChars + 1];
  strncpy(sValue, sSrc, nChars);
  sValue[nChars]= '\0';
} // XmlElement::parseValue(char*, int)
//------------------------------------------------------------------------------

void XmlElement::addChild(XmlElement* xChild){
  XmlElement** axChild0= axChild;
  // array verlengen, bestaande gegevens overnemen:
  axChild= new XmlElement*[nChildren + 1];
  for (int i= 0; i < nChildren; i++){
    axChild[i]= axChild0[i];
  }
  // nieuwe item aan het eind toevoegen:
  axChild[nChildren]= xChild;
  nChildren++;
  if (axChild0){
    delete axChild0;
  }
}
//------------------------------------------------------------------------------

int XmlElement::countChildren(){
  return nChildren;
}
//------------------------------------------------------------------------------

char* XmlElement::getName(){
  char* sRet= new char[strlen(sName) + 1];
  strcpy(sRet, sName);
  return sRet;
}
//------------------------------------------------------------------------------

/**
 *  Geeft een kopie van het kind met het gegeven volgnummer,
 *  of NULL indien index buiten domein
 */
XmlElement* XmlElement::getChild(int i){
  if (i >= nChildren){
    return NULL;
  } else{
    return axChild[i]->clone();
  }
}
//------------------------------------------------------------------------------

/**
 *  Geeft een kopie van het kind met de gegeven naam,
 *  of NULL indien geen gevonden.
 */
XmlElement* XmlElement::getChild(char* sName){
  for (int i= 0; i < nChildren; i++){
    if (strcmp(axChild[i]->sName, sName)){
      continue;
    }
    return axChild[i]->clone();
  }
  return NULL;
}
//------------------------------------------------------------------------------

bool XmlElement::isName(char* sName){
  return !strcmp(sName, this->sName);
}
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------