// --------------------------------------------------------------------
// XML parsing
// --------------------------------------------------------------------
/*

    This file is part of the extensible drawing editor Ipe.
    Copyright (C) 1993-2004  Otfried Cheong

    Ipe 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.

    As a special exception, you have permission to link Ipe with the
    CGAL library and distribute executables, as long as you follow the
    requirements of the Gnu General Public License in regard to all of
    the software in the executable aside from CGAL.

    Ipe 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 Ipe; if not, you can find it at
    "http://www.gnu.org/copyleft/gpl.html", or write to the Free
    Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

#include "ipexml.h"

// --------------------------------------------------------------------

/*! \class IpeXmlAttributes
  \ingroup base
  \brief Stores attributes of an XML tag.
*/

//! Constructor for an empty collection.
IpeXmlAttributes::IpeXmlAttributes()
{
  iSlash = false;
}

//! Remove all attributes.
void IpeXmlAttributes::Clear()
{
  iSlash = false;
  iMap.clear();
}

//! Return attribute with given key.
/*! Returns an empty string if no attribute with this key exists. */
IpeString IpeXmlAttributes::operator[](IpeString str) const
{
  std::map<IpeString, IpeString>::const_iterator it = iMap.find(str);
  if (it == iMap.end())
    return IpeString();
  else
    return it->second;
}

//! Add a new attribute.
void IpeXmlAttributes::Add(IpeString key, IpeString val)
{
  iMap[key] = val;
}

//! Check whether attribute exists, set \c val if so.
bool IpeXmlAttributes::Has(IpeString str, IpeString &val) const
{
  std::map<IpeString, IpeString>::const_iterator it = iMap.find(str);
  if (it != iMap.end()) {
    val = it->second;
    return true;
  }
  return false;
}

// --------------------------------------------------------------------

/*! \class IpeXmlParser
 * \ingroup base
 * \brief Base class for XML stream parsing.

 This is the base class for Ipe's XML parser. It only provides some
 utility functions for parsing tags and PCDATA.  Derived classes
 implement the actual parsing using recursive descent parsers---after
 experimenting with various schemes for XML parsing, this seems to
 work best for Ipe.

 Tag names and attribute names must consist of ASCII letters only.
 Only entities for '&', '<', and '>' are recognized.

*/

//! Construct with a data source.
IpeXmlParser::IpeXmlParser(IpeDataSource &source)
  : iSource(source)
{
  iPos = 0;
  GetChar(); // init iCh
}

//! Virtual destructor, so one can destroy through pointer.
IpeXmlParser::~IpeXmlParser()
{
  // nothing
}

void IpeXmlParser::SkipWhitespace()
{
  while (iCh <= ' ' && !Eos())
    GetChar();
}

//! Parse whitespace and the name of a tag.
/*! If the tag is a closing tag, skips > and returns with stream after that.
  Otherwise, returns with stream just after the tag name. */
IpeString IpeXmlParser::ParseToTag()
{
  bool comment_found;
  do {
    SkipWhitespace();
    if (iCh != '<')
      return IpeString();
    GetChar();
    SkipWhitespace();
    comment_found = (iCh == '-');
    if (comment_found) {
      // can only be an XML comment
      GetChar();
      if (iCh != '-')
	return IpeString();
      GetChar();
      int last[2] = { ' ', ' ' };
      while (!Eos() && (iCh != '>' || last[0] != '-' || last[1] != '-')) {
	last[0] = last[1];
	last[1] = iCh;
	GetChar();
      }
      GetChar();
      if (Eos())
	return IpeString();
    }
  } while (comment_found);
  IpeString tagname;
  if (iCh == '?' || iCh == '/') {
    tagname += char(iCh);
    GetChar();
  }
  while (IsTagChar(iCh)) {
    tagname += char(iCh);
    GetChar();
  }
  if (tagname[0] == '/') {
    SkipWhitespace();
    if (iCh != '>')
      return IpeString();
    GetChar();
  }
  // ipeDebug("<%s>", tagname.CString());
  return tagname;
}

//! Parse XML attributes.
/*! Returns with stream just after >.
  Caller can check whether the tag ended with a / by checking attr.Slash(). */
bool IpeXmlParser::ParseAttributes(IpeXmlAttributes &attr, bool qm)
{
  // looking at char after tagname
  attr.Clear();
  SkipWhitespace();
  while (iCh != '>' && iCh != '/' && iCh != '?') {
    IpeString attname;
    while (IsTagChar(iCh)) {
      attname += char(iCh);
      GetChar();
    }
    // for the moment, allow whitespace between attribute name and '='
    SkipWhitespace();
    if (attname.empty() || iCh != '=')
      return false;
    GetChar();
    if (iCh != '\"')
      return false;
    GetChar();
    IpeString val;
    while (!Eos() && iCh != '\"') {
      val += char(iCh);
      GetChar();
    }
    if (iCh != '\"')
      return false;
    GetChar();
    SkipWhitespace();
    attr.Add(attname, val);
    // fprintf(stderr, " .. found attribute %s = %s (%c)\n", attname.c_str(),
    // val.c_str(), iCh);
  }
  // looking at '/' or '>' (or '?' in <?xml> tag)
  if (iCh == '/' || qm && iCh == '?') {
    attr.SetSlash();
    GetChar();
    SkipWhitespace();
  }
  // looking at '>'
  if (iCh != '>')
    return false;
  GetChar();
  return true;
}

//! Parse PCDATA.
/*! Checks whether the data is terminated by \c \</tag\>, and returns
  with stream past the \>. */
bool IpeXmlParser::ParsePCDATA(IpeString tag, IpeString &pcdata)
{
  pcdata.erase();
  // fprintf(stderr, "ParsePCDATA(%s)\n", tag.c_str());
  for (;;) {
    if (Eos())
      return false;
    if (iCh == '<') {
      GetChar();
      if (iCh != '/')
	return false;
      GetChar();
      for (int i = 0; i < tag.size(); i++) {
	if (iCh != tag[i])
	  return false;
	GetChar();
      }
      SkipWhitespace();
      if (iCh != '>')
	return false;
      GetChar();
      // fprintf(stderr, "---> %s\n", pcdata.c_str());
      return true;
    } else if (iCh == '&') {
      IpeString ent;
      GetChar();
      while (!Eos() && iCh != ';') {
	ent += char(iCh);
	GetChar();
      }
      if (ent == "amp")
	pcdata += '&';
      else if (ent == "lt")
	pcdata += '<';
      else if (ent == "gt")
	pcdata += '>';
      else {
	// unknown entity
	pcdata += '&';
	pcdata.append(ent);
	pcdata += ';';
      }
    } else {
      // just store it
      pcdata += char(iCh);
    }
    GetChar();
  }
}

// --------------------------------------------------------------------
