// -------------------------------------------------------------------- // 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 // --> 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 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, ¤tLayer); if (!obj) return false; page.push_back(IpePgObject(IpePgObject::ENone, currentLayer, obj)); tag = ParseToTag(); } } //! Parse an \c 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 page(new IpePage); if (!ParsePage(*page)) return 0; tag = ParseToTag(); if (tag != "/ipepage") return 0; return page.Take(); } //! Parse an \c 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::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 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; } // --------------------------------------------------------------------