// MyXml.cpp

#include "StdAfx.h"

#include "MyXml.h"

static bool IsValidChar(char c)
{
  return
    c >= 'a' && c <= 'z' ||
    c >= 'A' && c <= 'Z' ||
    c >= '0' && c <= '9' ||
    c == '-';
}

static bool IsSpaceChar(char c)
{
  return (c == ' ' || c == '\t' || c == 0x0D || c == 0x0A);
}

#define SKEEP_SPACES(s, pos) while (IsSpaceChar(s[pos])) pos++;

static bool ReadProperty(const AString &s, int &pos, CXmlProp &prop)
{
  prop.Name.Empty();
  prop.Value.Empty();
  for (; pos < s.Length(); pos++)
  {
    char c = s[pos];
    if (!IsValidChar(c))
      break;
    prop.Name += c;
  }
  
  if (prop.Name.IsEmpty())
    return false;

  SKEEP_SPACES(s, pos);
  if (s[pos++] != '=')
    return false;

  SKEEP_SPACES(s, pos);
  if (s[pos++] != '\"')
    return false;
  
  while (pos < s.Length())
  {
    char c = s[pos++];
    if (c == '\"')
      return true;
    prop.Value += c;
  }
  return false;
}

int CXmlItem::FindProperty(const AString &propName) const
{
  for (int i = 0; i < Props.Size(); i++)
    if (Props[i].Name == propName)
      return i;
  return -1;
}

AString CXmlItem::GetPropertyValue(const AString &propName) const
{
  int index = FindProperty(propName);
  if (index >= 0)
    return Props[index].Value;
  return AString();
}

bool CXmlItem::IsTagged(const AString &tag) const
{
  return (IsTag && Name == tag);
}

int CXmlItem::FindSubTag(const AString &tag) const
{
  for (int i = 0; i < SubItems.Size(); i++)
    if (SubItems[i].IsTagged(tag))
      return i;
  return -1;
}

AString CXmlItem::GetSubString() const
{
  if (SubItems.Size() == 1)
  {
    const CXmlItem &item = SubItems[0];
    if (!item.IsTag)
      return item.Name;
  }
  return AString();
}

AString CXmlItem::GetSubStringForTag(const AString &tag) const
{
  int index = FindSubTag(tag);
  if (index >= 0)
    return SubItems[index].GetSubString();
  return AString();
}

bool CXmlItem::ParseItems(const AString &s, int &pos, int numAllowedLevels)
{
  if (numAllowedLevels == 0)
    return false;
  SubItems.Clear();
  AString finishString = "</";
  for (;;)
  {
    SKEEP_SPACES(s, pos);

    if (s.Mid(pos, finishString.Length()) == finishString)
      return true;
      
    CXmlItem item;
    if (!item.ParseItem(s, pos, numAllowedLevels - 1))
      return false;
    SubItems.Add(item);
  }
}

bool CXmlItem::ParseItem(const AString &s, int &pos, int numAllowedLevels)
{
  SKEEP_SPACES(s, pos);

  int pos2 = s.Find('<', pos);
  if (pos2 < 0)
    return false;
  if (pos2 != pos)
  {
    IsTag = false;
    Name += s.Mid(pos, pos2 - pos);
    pos = pos2;
    return true;
  }
  IsTag = true;

  pos++;
  SKEEP_SPACES(s, pos);

  for (; pos < s.Length(); pos++)
  {
    char c = s[pos];
    if (!IsValidChar(c))
      break;
    Name += c;
  }
  if (Name.IsEmpty() || pos == s.Length())
    return false;

  int posTemp = pos;
  for (;;)
  {
    SKEEP_SPACES(s, pos);
    if (s[pos] == '/')
    {
      pos++;
      // SKEEP_SPACES(s, pos);
      return (s[pos++] == '>');
    }
    if (s[pos] == '>')
    {
      if (!ParseItems(s, ++pos, numAllowedLevels))
        return false;
      AString finishString = AString("</") + Name + AString(">");
      if (s.Mid(pos, finishString.Length()) != finishString)
        return false;
      pos += finishString.Length();
      return true;
    }
    if (posTemp == pos)
      return false;

    CXmlProp prop;
    if (!ReadProperty(s, pos, prop))
      return false;
    Props.Add(prop);
    posTemp = pos;
  }
}

bool SkeepHeader(const AString &s, int &pos, const AString &startString, const AString &endString)
{
  SKEEP_SPACES(s, pos);
  if (s.Mid(pos, startString.Length()) == startString)
  {
    pos = s.Find(endString, pos);
    if (pos < 0)
      return false;
    pos += endString.Length();
    SKEEP_SPACES(s, pos);
  }
  return true;
}

bool CXml::Parse(const AString &s)
{
  int pos = 0;
  if (!SkeepHeader(s, pos, "<?xml", "?>"))
    return false;
  if (!SkeepHeader(s, pos, "<!DOCTYPE", ">"))
    return false;
  if (!Root.ParseItem(s, pos, 1000))
    return false;
  SKEEP_SPACES(s, pos);
  return (pos == s.Length() && Root.IsTag);
}
