// --------------------------------------------------------------------
// IpeImlParser
// --------------------------------------------------------------------
/*

    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 "ipeiml.h"
#include "ipeobj.h"
#include "ipegroup.h"
#include "ipepage.h"
#include "ipefactory.h"
#include "ipestyle.h"
// #include "ipeimage.h"

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

/*! \class IpeImlParser
  \ingroup high
  \brief Ipe XML Parser.

  A recursive descent parser for the Ipe XML streams.

  After experimenting with various XML parsing frameworks, this turned
  out to work best for Ipe.

*/

IpeImlParser::IpeImlParser(IpeDataSource &source,
			   IpeRepository *rep)
  : IpeXmlParser(source), iRepository(rep)
{
  // this variable is for the fixing of the
  // <textmatrix> --> <textstretch> semantic change
  iTextStretch = 1.0;
}

//! XML contents can refer to data in PDF.
/*! If the XML stream is embedded in a PDF file, XML contents can
  refer to PDF objects.  A derived parser must implement this method
  to access PDF data.

  It is assumed that PDF object \a objNum is a stream.  Its contents
  (uncompressed!) is returned in a buffer.
*/
IpeBuffer IpeImlParser::PdfStream(int /* objNum */)
{
  return IpeBuffer();
}

//! Read a complete Ipe document from IML stream.
/*! Sets \a requires to the Ipelib version required by the document,
  e.g. 60100 for the Ipelib of Ipe 6.1.  The version is not actually
  checked, so parsing continues (until an error occurs). */
bool IpeImlParser::ParseDocument(IpeDocument &doc, int &requires)
{
  requires = 0;
  IpeDocument::SProperties properties = doc.Properties();

  IpeString tag = ParseToTag();
  if (tag == "?xml") {
    IpeXmlAttributes attr;
    if (!ParseAttributes(attr))
      return false;
    tag = ParseToTag();
  }
  if (tag != "ipe")
    return false;

  IpeXmlAttributes attr;
  if (!ParseAttributes(attr))
    return false;

  IpeString mediabox;
  if (attr.Has("media", mediabox)) {
    IpeLex lex(mediabox);
    IpeVector mbmin;
    IpeVector mbmax;
    lex >> mbmin.iX >> mbmin.iY >> mbmax.iX >> mbmax.iY;
    properties.iMedia = IpeRect(mbmin, mbmax);
  } else
    properties.iMedia = IpeRect(IpeVector::Zero, IpeVector(595,842)); // A4

  IpeLex versLex(attr["version"]);
  versLex >> requires;

  attr.Has("creator", properties.iCreator);

  tag = ParseToTag();
  if (tag == "info") {
    IpeXmlAttributes att;
    if (!ParseAttributes(att))
      return false;

    properties.iTitle = att["title"];
    properties.iAuthor = att["author"];
    properties.iSubject = att["subject"];
    properties.iKeywords = att["keywords"];
    properties.iFullScreen = (att["pagemode"] == "fullscreen");
    properties.iCropBox = (att["bbox"] == "cropbox");
    properties.iCreated = att["created"];
    properties.iModified = att["modified"];

    tag = ParseToTag();
  }
  if (tag == "preamble") {
    IpeXmlAttributes att;
    if (!ParseAttributes(att))
      return false;
    if (!ParsePCDATA("preamble", properties.iPreamble))
      return false;
    tag = ParseToTag();
  }

  // bottom style sheet in cascade is always "standard"
  IpeStyleSheet *sheet = IpeStyleSheet::Standard(iRepository);
  if (!sheet)
    return false;
  doc.SetStyleSheet(sheet);

  while (tag == "ipestyle") {
    IpeStyleSheet *sheet = new IpeStyleSheet(iRepository);
    if (!ParseStyle(*sheet, true)) {
      delete sheet;
      return false;
    }
    sheet->SetCascade(doc.GetStyleSheet());
    doc.SetStyleSheet(sheet);
    tag = ParseToTag();
  }

  while (tag == "bitmap") {
    if (!ParseBitmap())
      return false;
    tag = ParseToTag();
  }

  while (tag == "page") {
    // read one page
    IpePage *page = new IpePage;
    doc.push_back(page);
    if (!ParsePage(*page))
      return false;
    tag = ParseToTag();
  }

  // OBSOLETE: page mediabox can override document media
  if (!iMediaBox.IsEmpty())
    properties.iMedia = iMediaBox;

  doc.SetProperties(properties);
  doc.SetEdited(false);
  return (tag == "/ipe");
}

//! Parse an IpeBitmap.
/*! On calling, stream must be just past \c bitmap. */
bool IpeImlParser::ParseBitmap()
{
  IpeXmlAttributes att;
  if (!ParseAttributes(att))
    return false;
  IpeString objNumStr;
  if (att.Slash() && att.Has("pdfObject", objNumStr)) {
    int objNum = IpeLex(objNumStr).GetInt();
    // ipeDebug("ParseBitmap: objNum = %d", objNum);
    IpeBitmap bitmap(att, PdfStream(objNum));
    iBitmaps.push_back(bitmap);
  } else {
    IpeString bits;
    if (!ParsePCDATA("bitmap", bits))
      return false;
    IpeBitmap bitmap(att, bits);
    iBitmaps.push_back(bitmap);
  }
  return true;
}

//! Parse an IpePage.
/*! On calling, stream must be just past \c page. */
bool IpeImlParser::ParsePage(IpePage &page)
{
  IpeXmlAttributes att;
  if (!ParseAttributes(att))
    return false;

  // OBSOLETE: remove later
  IpeString mediabox;
  if (att.Has("mediabox", mediabox)) {
    IpeLex lex(mediabox);
    IpeVector mbmin;
    IpeVector mbmax;
    lex >> mbmin.iX >> mbmin.iY >> mbmax.iX >> mbmax.iY;
    iMediaBox = IpeRect(mbmin, mbmax);
  }

  IpeString str;
  if (att.Has("gridsize", str))
    page.SetGridSize(IpeLex(str).GetInt());
  if (att.Has("section", str))
    page.SetSection(0, str);
  if (att.Has("subsection", str))
    page.SetSection(1, str);

  std::vector<IpeString> hiddenLayers;
  IpeString tag = ParseToTag();
  while (tag == "layer") {
    IpeXmlAttributes att;
    if (!ParseAttributes(att))
      return false;
    IpeLayer layer(att);
    // OBSOLETE: remove later
    if (att["visible"] == "no")
      hiddenLayers.push_back(layer.Name());
    page.AddLayer(layer);
    tag = ParseToTag();
  }
  // default layer: 'alpha'
  if (page.CountLayers() == 0) {
    IpeLayer layer("alpha");
    page.AddLayer(layer);
  }
  while (tag == "view") {
    IpeXmlAttributes att;
    if (!ParseAttributes(att))
      return false;
    IpeView view(att);
    page.AddView(view);
    tag = ParseToTag();
  }

  // default view: include all layers
  if (page.CountViews() == 0) {
    // need to synthesize a view
    IpeView view;
    for (int i = 0; i < page.CountLayers(); ++i) {
      IpeString name = page.Layer(i).Name();
      // OBSOLETE: remove later
      if (std::find(hiddenLayers.begin(), hiddenLayers.end(), name) ==
	  hiddenLayers.end())
	view.AddLayer(name);
    }
    int al = 0;
    while (al < page.CountLayers() && page.Layer(al).IsLocked())
      ++al;
    if (al == page.CountLayers())
      return false; // no unlocked layer
    view.SetActive(page.Layer(al).Name());
    page.AddView(view);
  }

  int currentLayer = 0;
  IpeString layerName;
  for (;;) {
    if (tag == "/page") {
      return true;
    }
    // if (tag == "path")
    // ipeDebug("path at %d", ParsePosition());
    IpeObject *obj = ParseObject(tag, &page, &currentLayer);
    if (!obj)
      return false;
    page.push_back(IpePgObject(IpePgObject::ENone, currentLayer, obj));
    tag = ParseToTag();
  }
}

//! Parse an \c <ipepage> element (used on clipboard).
IpePage *IpeImlParser::ParsePageSelection()
{
  IpeString tag = ParseToTag();
  if (tag != "ipepage")
    return 0;
  IpeXmlAttributes attr;
  if (!ParseAttributes(attr))
    return 0;
  tag = ParseToTag();

  while (tag == "bitmap") {
    if (!ParseBitmap())
      return 0;
    tag = ParseToTag();
  }

  if (tag != "page")
    return 0;

  IpeAutoPtr<IpePage> page(new IpePage);
  if (!ParsePage(*page))
    return 0;

  tag = ParseToTag();
  if (tag != "/ipepage")
    return 0;
  return page.Take();
}

//! Parse an \c <ipeselection> element.
/*! An IpePgObjectSeq is used to own the objects, but selection mode
  and layer are not set. */
bool IpeImlParser::ParseSelection(IpePgObjectSeq &seq)
{
  IpeString tag = ParseToTag();
  if (tag != "ipeselection")
    return false;
  IpeXmlAttributes attr;
  if (!ParseAttributes(attr))
    return 0;
  tag = ParseToTag();

  while (tag == "bitmap") {
    if (!ParseBitmap())
      return false;
    tag = ParseToTag();
  }

  for (;;) {
    if (tag == "/ipeselection")
      return true;
    IpeObject *obj = ParseObject(tag);
    if (!obj)
      return false;
    seq.push_back(IpePgObject(IpePgObject::ENone, 0, obj));
    tag = ParseToTag();
  }
}

//! Parse an IpeObject.
/*! On calling, stream must be just past the tag. */
IpeObject *IpeImlParser::ParseObject(IpeString tag, IpePage *page,
				     int *currentLayer)
{
  if (tag[0] == '/')
    return 0;

  IpeXmlAttributes attr;
  if (!ParseAttributes(attr))
    return 0;

  IpeString layer;
  if (page && currentLayer && attr.Has("layer", layer)) {
    for (int i = 0; i < page->CountLayers(); ++i) {
      if (page->Layer(i).iName == layer) {
	*currentLayer = i;
	break;
      }
    }
  }

  if (tag == "group") {
    IpeGroup group(iRepository, attr);
    for (;;) {
      IpeString tag = ParseToTag();
      if (tag == "/group") {
	return new IpeGroup(group);
      }
      IpeObject *obj = ParseObject(tag);
      if (!obj)
	return 0;
      group.push_back(obj);
    }
  }

  IpeString pcdata;
  if (!attr.Slash() && !ParsePCDATA(tag, pcdata))
    return 0;
#if 1
  // obsolete - remove later
  IpeString textStretch;
  IpeStringStream(textStretch) << iTextStretch;
  attr.Add("textstretch", textStretch);
#endif
  IpeString bitmapId;
  if (tag == "image" && attr.Has("bitmap", bitmapId)) {
    int objNum = IpeLex(bitmapId).GetInt();
    IpeBitmap bitmap;
    for (std::vector<IpeBitmap>::const_iterator it = iBitmaps.begin();
	 it != iBitmaps.end(); ++it) {
      if (it->ObjNum() == objNum) {
	bitmap = *it;
	break;
      }
    }
    assert(!bitmap.IsNull());
    return IpeObjectFactory::CreateImage(iRepository, tag, attr, bitmap);
  } else
    return IpeObjectFactory::CreateObject(iRepository, tag, attr, pcdata);
}

//! Parse a style sheet.
/*! On calling, stream must be just past the style tag. */
bool IpeImlParser::ParseStyle(IpeStyleSheet &sheet, bool inDoc)
{
  IpeXmlAttributes att;
  if (!ParseAttributes(att))
    return false;
  IpeString name;
  if (att.Has("name", name))
    sheet.SetName(name);

  IpeString tag = ParseToTag();
  while (tag != "/ipestyle") {
    if (tag == "template") {
      if (!ParseAttributes(att))
	return false;
      IpeString tag1 = ParseToTag();
      IpeObject *obj = ParseObject(tag1);
      if (!obj)
	return false;
      IpeString name = att["name"];
      if (name.empty())
	return false;
      IpeAttribute attr =
	iRepository->MakeSymbol(IpeAttribute::ETemplate, name);
      sheet.AddTemplate(attr, obj);
      if (ParseToTag() != "/template")
	return false;
    } else if (tag == "margins") {
      if (!ParseAttributes(att) || !att.Slash())
	return false;
      IpeLex lex1(att["tl"]), lex2(att["br"]);
      IpeVector tl, br;
      lex1 >> tl.iX >> tl.iY;
      lex2 >> br.iX >> br.iY;
      sheet.SetMargins(tl, br);
    } else if (tag == "preamble") {
      if (!ParseAttributes(att))
	return false;
      IpeString pcdata;
      if (!att.Slash() && !ParsePCDATA(tag, pcdata))
	return false;
      sheet.SetPreamble(pcdata);
    } else if (tag == "cmap") {
      if (!ParseAttributes(att) || !att.Slash())
	return false;
      sheet.AddCMap(att["font"]);
    } else if (tag == "color") {
      if (!ParseAttributes(att) || !att.Slash())
	return false;
      IpeAttribute col = iRepository->MakeColor(att["value"]);
      if (!col.IsAbsolute())
	return false;
      IpeString name = att["name"];
      if (name.empty() || !(('a' <= name[0] && name[0] <= 'z') ||
			    ('A' <= name[0] && name[0] <= 'Z')))
	return false;
      IpeAttribute attr = iRepository->MakeSymbol(IpeAttribute::EColor, name);
      sheet.Add(attr, col);
    } else if (tag == "dashstyle") {
      if (!ParseAttributes(att) || !att.Slash())
	return false;
      IpeAttribute dash = iRepository->MakeDashStyle(att["value"]);
      if (!dash.IsAbsolute())
	return false;
      IpeString name = att["name"];
      if (name.empty() || !(('a' <= name[0] && name[0] <= 'z') ||
			    ('A' <= name[0] && name[0] <= 'Z')))
	return false;
      IpeAttribute attr =
	iRepository->MakeSymbol(IpeAttribute::EDashStyle, name);
      sheet.Add(attr, dash);
    } else if (tag == "textsize") {
      if (!ParseAttributes(att) || !att.Slash())
	return false;
      IpeString name = att["name"];
      if (name.empty())
	return false;
      IpeAttribute attr =
	iRepository->MakeSymbol(IpeAttribute::ETextSize, name);
      IpeAttribute value =
	iRepository->MakeTextSize(att["value"]);
      sheet.Add(attr, value);
    } else if (tag == "textstretch") {
      if (!ParseAttributes(att) || !att.Slash())
	return false;
      IpeString name = att["name"];
      if (name.empty())
	return false;
      IpeAttribute attr =
	iRepository->MakeSymbol(IpeAttribute::ETextStretch, name);
      IpeAttribute value =
	iRepository->MakeVector(IpeAttribute::ETextStretch, att["value"]);
      sheet.Add(attr, value);
    } else if (tag == "shading") {
      if (!ParseAttributes(att) || !att.Slash())
	return false;
      IpeShading s;
      s.iType = (att["type"] == "radial") ?
	IpeShading::ERadial : IpeShading::EAxial;
      IpeLex lex(att["coords"]);
      if (s.iType == IpeShading::ERadial)
	lex >> s.iV[0].iX >> s.iV[0].iY >> s.iRadius[0]
	    >> s.iV[1].iX >> s.iV[1].iY >> s.iRadius[1];
      else
	lex >> s.iV[0].iX >> s.iV[0].iY
	    >> s.iV[1].iX >> s.iV[1].iY;
      IpeLex lex1(att["extend"]);
      int flag;
      lex1 >> flag; s.iExtend[0] = !!flag;
      lex1 >> flag; s.iExtend[1] = !!flag;
      IpeLex lex2(att["colora"]);
      lex2 >> s.iColor[0].iRed >> s.iColor[0].iGreen >> s.iColor[0].iBlue;
      IpeLex lex3(att["colorb"]);
      lex3 >> s.iColor[1].iRed >> s.iColor[1].iGreen >> s.iColor[1].iBlue;
      sheet.SetShading(s);
    } else if (inDoc && tag == "textmatrix") {
      if (!ParseAttributes(att) || !att.Slash())
	return false;
      // obsolete --- this code should be removed at some point
#if 1
      IpeString name = att["name"];
      if (name.empty())
	return false;
      IpeAttribute attr =
	iRepository->MakeSymbol(IpeAttribute::ETextStretch, name);
      IpeMatrix m(att["value"]);
      IpeAttribute value =
	iRepository->ToAttribute(IpeAttribute::ETextStretch,
				 IpeVector(m.iA[0], m.iA[3]));
      iTextStretch = m.iA[0];
      sheet.Add(attr, value);
#endif
    } else if (tag == "media") {
      if (!ParseAttributes(att) || !att.Slash())
	return false;
      IpeAttribute media =
	iRepository->MakeVector(IpeAttribute::EMedia, att["value"]);
      IpeString name = att["name"];
      if (name.empty())
	return false;
      IpeAttribute attr = iRepository->MakeSymbol(IpeAttribute::EMedia, name);
      sheet.Add(attr, media);
    } else {
      IpeKind kind;
      if (tag == "linewidth")
	kind = IpeAttribute::ELineWidth;
      else if (tag == "marksize")
	kind = IpeAttribute::EMarkSize;
      else if (tag == "arrowsize")
	kind = IpeAttribute::EArrowSize;
      else if (tag == "grid")
	kind = IpeAttribute::EGridSize;
      else if (tag == "angle")
	kind = IpeAttribute::EAngleSize;
      else
	return false; // error
      if (!ParseAttributes(att) || !att.Slash())
	return false;
      IpeAttribute value = iRepository->MakeScalar(kind, att["value"]);
      IpeString name = att["name"];
      if (name.empty() || !value.IsAbsolute())
	return false;
      IpeAttribute attr = iRepository->MakeSymbol(kind, name);
      sheet.Add(attr, value);
    }
    tag = ParseToTag();
  }
  return true;
}

//! Parse a complete style sheet.
/*! On calling, stream must be before the 'ipestyle' tag.
  A <?xml> tag is allowed.
 */
IpeStyleSheet *IpeImlParser::ParseStyleSheet()
{
  IpeString tag = ParseToTag();
  if (tag == "?xml") {
    IpeXmlAttributes attr;
    if (!ParseAttributes(attr))
      return 0;
    tag = ParseToTag();
  }
  if (tag != "ipestyle")
    return 0;
  IpeStyleSheet *sheet = new IpeStyleSheet(iRepository);
  if (ParseStyle(*sheet, false))
    return sheet;
  delete sheet;
  return 0;
}

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