// -------------------------------------------------------------------- // 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::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::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 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 \, 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(); } } // --------------------------------------------------------------------