/***************************************************************************
 * KoScriptingOdf.h
 * This file is part of the KDE project
 * copyright (C) 2007 Sebastian Sauer <mail@dipe.org>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 * This program 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
 * Library General Public License for more details.
 * You should have received a copy of the GNU Library General Public License
 * along with this program; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 ***************************************************************************/

#ifndef KOSCRIPTINGODF_H
#define KOSCRIPTINGODF_H

#include <QPair>
#include <QObject>
#include <QDomDocument>
#include <QtDebug>
#include <KoXmlReader.h>
#include <KoXmlWriter.h>
#include <KoDocument.h>

class KoScriptingOdfStore;

/**
* The KoScriptingOdfReader provides functionality to read content
* from a KoStore.
*
* The following python samples does open the content.xml file and
* reads all text:p elements from it;
* \code
* import Kross, KWord
* KWord.document().openUrl("/home/kde4/testDoc.odt")
* reader = KWord.store().open("META-INF/manifest.xml")
* reader = store.open("content.xml")
* def onElement():
*     if reader.name() != "text:p":
*         raise "This should never happen cause of the filter"
*     print "%s %s" % (reader.level(),reader.attributeNames())
* reader.connect("onElement()", onElement)
* reader.setNameFilter("text:p")
* reader.start()
* \endcode
*/
class KoScriptingOdfReader : public QObject
{
        Q_OBJECT
    public:
        /** Constructor. */
        explicit KoScriptingOdfReader(KoScriptingOdfStore* store, const KoXmlDocument& doc);
        /** Destructor. */
        virtual ~KoScriptingOdfReader();
        /** Return the KoScriptingOdfStore instance this reader belongs to. */
        KoScriptingOdfStore* store() const;
        /** Return the KoXmlDocument instance this reader operates on. */
        KoXmlDocument doc() const;
        /** Return the current element. */
        KoXmlElement currentElement() const;

    public Q_SLOTS:

        /**
        * Return the element tag-name filter that will be applied on reading.
        */
        QString nameFilter() const;

        /**
        * Set the element tag-name filter that will be applied on reading.
        *
        * This python sample demonstrates usage of the setNameFilter()-method;
        * \code
        * # Only handle text:p elements.
        * reader.setNameFilter("text:p")
        *
        * # Use a regular expression for the tag-name.
        * reader.setNameFilter(".*:p", True)
        * \endcode
        */
        void setNameFilter(const QString& name = QString(), bool regularExpression = false) const;

        //QString levelFilter() const;
        //void setLevelFilter(const QString& name = QString()) const;

        /**
        * Start the reading.
        *
        * This will fire up the whole reading process. We walk through
        * all elements and emit the onElement() signal or other signals
        * for each element we are interested in.
        */
        void start();

        /**
        * Return the tag-name of the current element. This could be
        * e.g. "office:text", "style:style" or "text:p".
        */
        QString name() const;

        /**
        * The namespace of the element. For example "office:document-content".
        */
        QString namespaceURI() const;

        //QString prefix() const { return m_currentElement.prefix(); }
        //QString localName() const { return m_currentElement.localName(); }

        /**
        * The level the element is on. Elements may nested and the level is
        * a number that defines how much elements are around this element.
        * The root-element has a level of 0, direct children of the
        * root-element have a level of 1, there children of 2, etc.
        */
        int level() const;
#ifndef KOXML_USE_QDOM
        /**
        * Return a list of attribute-names the element has. This could be
        * for example "text:style-name".
        */
        QStringList attributeNames();
#endif

        /**
        * Return the value of the attribute with the name \p name .
        */
        QString attribute(const QString& name, const QString& defaultValue = QString()) const;

        /**
        * Return the value of the attribute with the name \p name that is within
        * the namespace \p namespaceURI .
        */
        QString attributeNS(const QString& namespaceURI, const QString& localName, const QString& defaultValue = QString()) const;

        /**
        * Returns true if the current element has an attribute with name \p name.
        */
        bool hasAttribute(const QString& name) const;

        /**
        * Returns true if the current element has an attribute with name \p name that
        * is within the namespace \p namespaceURI .
        */
        bool hasAttributeNS(const QString& namespaceURI, const QString& localName) const;

        /**
        * Returns true if the current element is invalid.
        */
        bool isNull() const;

        /**
        * Return true if the current element is an element.
        */
        bool isElement() const;

        //bool isText() const;
        //bool isCDATASection() const { return m_currentElement.isCDATASection(); }
        //bool isDocument() const { return m_currentElement.isDocument(); }
        //QString toText() const { return m_currentElement.toText().data(); }
        //KoXmlCDATASection toCDATASection() const;

        /**
        * Return true if the current element has child nodes. Please note, that
        * text-nodes are not counted as children by this method. Fetch any text
        * by using the text() method which will return an empty string if there
        * is actualy no text.
        */
        bool hasChildren() const;

        /**
        * Return the content of the element as string.
        */
        QString text() const;

    Q_SIGNALS:

        /**
        * This signal got emitted after start() was called for each
        * element we read.
        */
        void onElement();

    protected:
        /** Emit the onElement signal above. */
        void emitOnElement();
        /** Set the current element. */
        void setCurrentElement(const KoXmlElement& elem);
        /** Set the level. */
        void setLevel(int level);
        /** Element-handler. */
        virtual void handleElement(KoXmlElement& elem, int level = 0);
    private:
        /** \internal d-pointer class. */
        class Private;
        /** \internal d-pointer instance. */
        Private* const d;
};

/**
* The KoScriptingOdfManifestReader class handles reading ODF META-INF/manifest.xml files.
*
* The following python sample script does use the KWord scripting module to load a
* ISO OpenDocument Text file and print the content of the manifest-file to stdout;
* \code
* import Kross, KWord
* KWord.document().openUrl("/home/kde4/testDoc.odt")
* reader = KWord.store().open("META-INF/manifest.xml")
* if not reader:
*     raise "Failed to read the mainfest"
* for i in range( reader.count() ):
*     print "%s %s" % (reader.type(i),reader.path(i))
* \endcode
*/
class KoScriptingOdfManifestReader : public KoScriptingOdfReader
{
        Q_OBJECT
    public:
        /** Constructor. */
        KoScriptingOdfManifestReader(KoScriptingOdfStore* store, const KoXmlDocument& doc);
        /** Destructor. */
        virtual ~KoScriptingOdfManifestReader() {}
    public Q_SLOTS:
        /** Returns the number of file-entries the manifest has. */
        int count() const { return m_entries.count(); }
        /** Returns the type of the file-entry. This could be for example
        something like "text/xml" or "application/vnd.oasis.opendocument.text" */
        QString type(int index) { return m_entries.value(index).first; }
        /** Return the path of the file-entry. This could be for example
        something like "/", "content.xml" or "styles.xml". */
        QString path(int index) { return m_entries.value(index).second; }
        /**
        * Return a list of paths for the defined type. If not type is defined
        * just all paths are returned.
        *
        * Python sample that does use the paths-method;
        * \code
        * # Following may print ['/','content.xml','styles.xml']
        * print reader.paths()
        *
        * # Following may print ['content.xml','styles.xml']
        * print reader.paths("text/xml")
        * \endcode
        */
        QStringList paths(const QString& type = QString());
    private:
        QList< QPair<QString,QString> > m_entries;
};

/**
* The KoScriptingOdfManifestReader class handles reading ODF styles.xml files.
*/
class KoScriptingOdfStylesReader : public KoScriptingOdfReader
{
        Q_OBJECT
    public:
        /** Constructor. */
        KoScriptingOdfStylesReader(KoScriptingOdfStore* store, const KoXmlDocument& doc);
        /** Destructor. */
        virtual ~KoScriptingOdfStylesReader() {}
    public Q_SLOTS:
        //QString style(const QString& styleName);
};

/**
* The KoScriptingOdfManifestReader class handles reading ODF content.xml files.
*/
class KoScriptingOdfContentReader : public KoScriptingOdfReader
{
        Q_OBJECT
    public:
        /** Constructor. */
        KoScriptingOdfContentReader(KoScriptingOdfStore* store, const KoXmlDocument& doc);
        /** Destructor. */
        virtual ~KoScriptingOdfContentReader() {}
    public Q_SLOTS:
        //QStringList headers(const QString& filter);
        //QStringList lists(const QString& filter);
        //QStringList images(const QString& filter);
        //QStringList tables(const QString& filter);
};

/**
* The KoScriptingOdfStore class provides access to the KoStore functionality.
*
* The following python sample does use KWord do read a ODT document and then
* flushes the DOM tree to stdout.
* \code
* import Kross
* KWord = Kross.module("kword")
*
* # Get the KWord KoApplicationAdaptor instance.
* docAdaptor = KWord.document()
* # Open an ISO OpenDocument Text File.
* docAdaptor.openUrl("/home/kde4/testDoc.odt")
*
* # Get a KoStore instance.
* store = KWord.store()
* # Open the content.xml file within the KoStore.
* reader = store.open("content.xml")
* if not reader:
*     raise "Failed to read file from the store"
*
* # This function got called for each element
* def onElement(name, level):
*     print "ELEMENT name=%s level=%s" % (name, level)
* # Connect our callback function with the reader.
* reader.connect("onElement(QString,int)", onElement)
*
* # Start the reading.
* reader.start()
* \endcode
*/
class KoScriptingOdfStore : public QObject
{
        Q_OBJECT
    public:
        /** Constructor. */
        explicit KoScriptingOdfStore(QObject* parent, KoDocument* doc);
        /** Destructor. */
        virtual ~KoScriptingOdfStore();

        //KoStore* readStore() const;
        //QIODevice* readDevice() const;

    public Q_SLOTS:

        /**
        * Returns true if there exists a file with the defined name
        * \p fileName in the store else false is returned.
        *
        * The following python sample does use the hasFile-method;
        * \code
        * if not store.hasFile("content.xml"):
        *     raise "No content.xml file within the store."
        * if not store.hasFile("tar:/META-INF/manifest.xml"):
        *     raise "No manifest.xml file within the store."
        * \endcode
        */
        bool hasFile(const QString& fileName);

        /**
        * Returns true if the store is already opened else false
        * got returned. The store does allow to open and deal
        * with maximal one file the same time.
        */
        bool isOpen() const;

        /**
        * Open the file with the defined name \p fileName and
        * return a \a KoScriptingOdfReader object if the file
        * was open successful else NULL (aka no object) got
        * returned.
        *
        * If the store is already opened, it got closed before
        * we try to open the new file. This means, that all
        * previous \a KoScriptingOdfReader instances are
        * invalid and that only the last one returned by using
        * the open-method is valid till closed.
        *
        * The following python sample does use the open-method;
        * \code
        * # Fetch a reader for the styles.xml file.
        * stylesReader = store.open("styles.xml")
        * if not stylesReader:
        *     raise "Failed to read styles"
        *
        * # Now fetch a reader for the manifest. Please note,
        * # that stylesReader.close() is implicit done here.
        * manifestReader = store.open("META-INF/manifest.xml")
        * if not manifestReader:
        *     raise "Failed to read mainfest"
        * \endcode
        */
        QObject* open(const QString& fileName);

        /**
        * Closes the store if it's opened. If the store was not opened
        * or got closed successful true is returned.
        */
        bool close();

        /**
        * Extract the content of the file named \p fileName and return
        * it as an array of bytes.
        */
        QByteArray extract(const QString& fileName);

        /**
        * Extract the content of the file named \p fileName to the defined
        * target-file \p toFileName and return true on success.
        */
        bool extractToFile(const QString& fileName, const QString& toFileName);

        //QStringList directories();
        //bool hasDirectory(const QString& directoryName);
        //QString currentDirectory();
        //bool changeDirectory(const QString& directoryName);

        /**
        * Returns the document the store is the backend for.
        */
        QObject* document() const;

        /**
        * Set the document the store is the backend for. This could
        * be a \a KoDocumentAdaptor or a \a KoDocument object.
        */
        bool setDocument(QObject* document);

        //void setFilter(const QString& filter) {}
        //void start() {}

    Q_SIGNALS:

        //void onElement();
        //void onHeader();
        //void onParagraph();

    private:
        /** \internal d-pointer class. */
        class Private;
        /** \internal d-pointer instance. */
        Private* const d;
};



#endif
