summaryrefslogtreecommitdiffstats
path: root/src/kmplayerplaylist.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/kmplayerplaylist.cpp')
-rw-r--r--src/kmplayerplaylist.cpp1842
1 files changed, 1842 insertions, 0 deletions
diff --git a/src/kmplayerplaylist.cpp b/src/kmplayerplaylist.cpp
new file mode 100644
index 0000000..e2db4f3
--- /dev/null
+++ b/src/kmplayerplaylist.cpp
@@ -0,0 +1,1842 @@
+/**
+ * Copyright (C) 2004 by Koos Vriezen <[email protected]>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License version 2 as published by the Free Software Foundation.
+ *
+ * This library 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 library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Steet, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ **/
+
+#include <config.h>
+#include <time.h>
+
+#include <qtextstream.h>
+#include <kdebug.h>
+#include <kurl.h>
+#ifdef HAVE_EXPAT
+#include <expat.h>
+#endif
+#ifdef HAVE_CAIRO
+# include <cairo.h>
+#endif
+#include "kmplayerplaylist.h"
+#include "kmplayer_asx.h"
+#include "kmplayer_atom.h"
+#include "kmplayer_rp.h"
+#include "kmplayer_rss.h"
+#include "kmplayer_smil.h"
+#include "kmplayer_xspf.h"
+
+#ifdef SHAREDPTR_DEBUG
+int shared_data_count;
+#endif
+
+using namespace KMPlayer;
+
+//-----------------------------------------------------------------------------
+
+namespace KMPlayer {
+ Node * fromXMLDocumentTag (NodePtr & d, const QString & tag) {
+ const char * const name = tag.latin1 ();
+ if (!strcmp (name, "smil"))
+ return new SMIL::Smil (d);
+ else if (!strcasecmp (name, "asx"))
+ return new ASX::Asx (d);
+ else if (!strcasecmp (name, "imfl"))
+ return new RP::Imfl (d);
+ else if (!strcasecmp (name, "rss"))
+ return new RSS::Rss (d);
+ else if (!strcasecmp (name, "feed"))
+ return new ATOM::Feed (d);
+ else if (!strcasecmp (name, "playlist"))
+ return new XSPF::Playlist (d);
+ else if (!strcasecmp (name, "url"))
+ return new GenericURL (d, QString ());
+ else if (!strcasecmp (name, "mrl") ||
+ !strcasecmp (name, "document"))
+ return new GenericMrl (d);
+ return 0L;
+ }
+
+//-----------------------------------------------------------------------------
+
+ struct XMLStringlet {
+ const QString str;
+ XMLStringlet (const QString & s) : str (s) {}
+ };
+} // namespace
+
+QTextStream & operator << (QTextStream & out, const XMLStringlet & txt) {
+ int len = int (txt.str.length ());
+ for (int i = 0; i < len; ++i) {
+ if (txt.str [i] == QChar ('<')) {
+ out << "&lt;";
+ } else if (txt.str [i] == QChar ('>')) {
+ out << "&gt;";
+ } else if (txt.str [i] == QChar ('"')) {
+ out << "&quot;";
+ } else if (txt.str [i] == QChar ('&')) {
+ out << "&amp;";
+ } else
+ out << txt.str [i];
+ }
+ return out;
+}
+
+//-----------------------------------------------------------------------------
+
+KDE_NO_CDTOR_EXPORT
+Connection::Connection (NodeRefListPtr ls, NodePtr node, NodePtr inv)
+ : connectee (inv), listeners (ls) {
+ if (listeners) {
+ NodeRefItemPtr nci = new NodeRefItem (node);
+ listeners->append (nci);
+ listen_item = nci;
+ }
+}
+
+KDE_NO_EXPORT void Connection::disconnect () {
+ if (listen_item && listeners)
+ listeners->remove (listen_item);
+ listen_item = 0L;
+ listeners = 0L;
+}
+
+//-----------------------------------------------------------------------------
+
+KDE_NO_CDTOR_EXPORT
+TimerInfo::TimerInfo (NodePtr n, unsigned id, struct timeval & tv, int ms)
+ : node (n), event_id (id), timeout (tv), milli_sec (ms) {}
+
+//-----------------------------------------------------------------------------
+
+Matrix::Matrix () : a (1.0), b (0.0), c (0.0), d (1.0), tx (0), ty (0) {}
+
+Matrix::Matrix (const Matrix & m)
+ : a (m.a), b (m.b), c (m.c), d (m.d), tx (m.tx), ty (m.ty) {}
+
+Matrix::Matrix (Single xoff, Single yoff, float xscale, float yscale)
+ : a (xscale), b (0.0), c (0.0), d (yscale), tx (xoff), ty (yoff) {}
+
+void Matrix::getXY (Single & x, Single & y) const {
+ x = Single (x * a) + tx;
+ y = Single (y * d) + ty;
+}
+
+void Matrix::getXYWH (Single & x, Single & y, Single & w, Single & h) const {
+ getXY (x, y);
+ w *= a;
+ h *= d;
+}
+
+void Matrix::invXYWH (Single & x, Single & y, Single & w, Single & h) const {
+ if (a > 0.00001 && d > 0.00001) {
+ w /= a;
+ h /= d;
+ x = Single ((x - tx) / a);
+ y = Single ((y - ty) / d);
+ } else {
+ kdWarning () << "Not invering " << a << ", " << d << " scale" << endl;
+ }
+}
+
+void Matrix::transform (const Matrix & matrix) {
+ // TODO: rotate
+ a *= matrix.a;
+ d *= matrix.d;
+ tx = Single (tx * matrix.a) + matrix.tx;
+ ty = Single (ty * matrix.d) + matrix.ty;
+}
+
+void Matrix::scale (float sx, float sy) {
+ a *= sx;
+ d *= sy;
+ tx *= sx;
+ ty *= sy;
+}
+
+void Matrix::translate (Single x, Single y) {
+ tx += x;
+ ty += y;
+}
+
+//-----------------------------------------------------------------------------
+
+KDE_NO_CDTOR_EXPORT Node::Node (NodePtr & d, short _id)
+ : m_doc (d), state (state_init), id (_id),
+ auxiliary_node (false), editable (true) {}
+
+Node::~Node () {
+ clear ();
+}
+
+Document * Node::document () {
+ return convertNode <Document> (m_doc);
+}
+
+Mrl * Node::mrl () {
+ return 0L;
+}
+
+const char * Node::nodeName () const {
+ return "node";
+}
+
+void Node::setState (State nstate) {
+ if (state != nstate) {
+ State old = state;
+ state = nstate;
+ if (document ()->notify_listener)
+ document()->notify_listener->stateElementChanged (this, old, state);
+ }
+}
+
+bool Node::expose () const {
+ return true;
+}
+
+void Node::activate () {
+ //kdDebug () << nodeName () << " Node::activate" << endl;
+ setState (state_activated);
+ if (firstChild ())
+ firstChild ()->activate (); // activate only the first
+ else
+ finish (); // a quicky :-)
+}
+
+void Node::begin () {
+ if (active ()) {
+ setState (state_began);
+ } else
+ kdError () << nodeName() << " begin call on not active element" << endl;
+}
+
+void Node::defer () {
+ if (active ()) {
+ setState (state_deferred);
+ } else
+ kdError () << "Node::defer () call on not activated element" << endl;
+}
+
+void Node::undefer () {
+ if (state == state_deferred) {
+ setState (state_activated);
+ activate ();
+ } else
+ kdWarning () <<"Node::undefer () call on not deferred element"<< endl;
+}
+
+void Node::finish () {
+ if (active ()) {
+ setState (state_finished);
+ if (m_parent)
+ m_parent->childDone (this);
+ else
+ deactivate (); // document deactivates itself on finish
+ } else
+ kdWarning () <<"Node::finish () call on not active element"<< endl;
+}
+
+void Node::deactivate () {
+ //kdDebug () << nodeName () << " Node::deactivate" << endl;
+ bool need_finish (unfinished ());
+ setState (state_deactivated);
+ for (NodePtr e = firstChild (); e; e = e->nextSibling ()) {
+ if (e->state > state_init && e->state < state_deactivated)
+ e->deactivate ();
+ else
+ break; // remaining not yet activated
+ }
+ if (need_finish && m_parent)
+ m_parent->childDone (this);
+}
+
+void Node::reset () {
+ //kdDebug () << nodeName () << " Node::reset" << endl;
+ if (active ())
+ deactivate ();
+ setState (state_init);
+ for (NodePtr e = firstChild (); e; e = e->nextSibling ()) {
+ if (e->state != state_init)
+ e->reset ();
+ // else
+ // break; // rest not activated yet
+ }
+}
+
+void Node::childBegan (NodePtr /*child*/) {
+}
+
+void Node::childDone (NodePtr child) {
+ //kdDebug () << nodeName () << " Node::childDone" << endl;
+ if (unfinished ()) {
+ if (child->state == state_finished)
+ child->deactivate ();
+ if (child->nextSibling ())
+ child->nextSibling ()->activate ();
+ else
+ finish (); // we're done
+ }
+}
+
+void Node::clear () {
+ clearChildren ();
+}
+
+void Node::clearChildren () {
+ if (m_doc)
+ document()->m_tree_version++;
+ while (m_first_child != m_last_child) {
+ // avoid stack abuse with 10k children derefing each other
+ m_last_child->m_parent = 0L;
+ m_last_child = m_last_child->m_prev;
+ m_last_child->m_next = 0L;
+ }
+ if (m_first_child)
+ m_first_child->m_parent = 0L;
+ m_first_child = m_last_child = 0L;
+}
+
+void Node::appendChild (NodePtr c) {
+ document()->m_tree_version++;
+ ASSERT (!c->parentNode ());
+ TreeNode<Node>::appendChild (c);
+}
+
+void Node::insertBefore (NodePtr c, NodePtr b) {
+ if (!b) {
+ appendChild (c);
+ } else {
+ ASSERT (!c->parentNode ());
+ document()->m_tree_version++;
+ if (b->m_prev) {
+ b->m_prev->m_next = c;
+ c->m_prev = b->m_prev;
+ } else {
+ c->m_prev = 0L;
+ m_first_child = c;
+ }
+ b->m_prev = c;
+ c->m_next = b;
+ c->m_parent = this;
+ }
+}
+
+void Node::removeChild (NodePtr c) {
+ document()->m_tree_version++;
+ TreeNode <Node>::removeChild (c);
+}
+
+KDE_NO_EXPORT void Node::replaceChild (NodePtr _new, NodePtr old) {
+ document()->m_tree_version++;
+ if (old->m_prev) {
+ old->m_prev->m_next = _new;
+ _new->m_prev = old->m_prev;
+ old->m_prev = 0L;
+ } else {
+ _new->m_prev = 0L;
+ m_first_child = _new;
+ }
+ if (old->m_next) {
+ old->m_next->m_prev = _new;
+ _new->m_next = old->m_next;
+ old->m_next = 0L;
+ } else {
+ _new->m_next = 0L;
+ m_last_child = _new;
+ }
+ _new->m_parent = this;
+ old->m_parent = 0L;
+}
+
+NodePtr Node::childFromTag (const QString &) {
+ return NodePtr ();
+}
+
+KDE_NO_EXPORT void Node::characterData (const QString & s) {
+ document()->m_tree_version++;
+ if (!m_last_child || m_last_child->id != id_node_text)
+ appendChild (new TextNode (m_doc, s));
+ else
+ convertNode <TextNode> (m_last_child)->appendText (s);
+}
+
+void Node::normalize () {
+ NodePtr e = firstChild ();
+ while (e) {
+ NodePtr tmp = e->nextSibling ();
+ if (!e->isElementNode () && e->id == id_node_text) {
+ QString val = e->nodeValue ().simplifyWhiteSpace ();
+ if (val.isEmpty ())
+ removeChild (e);
+ else
+ convertNode <TextNode> (e)->setText (val);
+ } else
+ e->normalize ();
+ e = tmp;
+ }
+}
+
+static void getInnerText (const NodePtr p, QTextOStream & out) {
+ for (NodePtr e = p->firstChild (); e; e = e->nextSibling ()) {
+ if (e->id == id_node_text || e->id == id_node_cdata)
+ out << e->nodeValue ();
+ else
+ getInnerText (e, out);
+ }
+}
+
+QString Node::innerText () const {
+ QString buf;
+ QTextOStream out (&buf);
+ getInnerText (m_self, out);
+ return buf;
+}
+
+static void getOuterXML (const NodePtr p, QTextOStream & out, int depth) {
+ if (!p->isElementNode ()) { // #text or #cdata
+ if (p->id == id_node_cdata)
+ out << "<![CDATA[" << p->nodeValue () << "]]>" << QChar ('\n');
+ else
+ out << XMLStringlet (p->nodeValue ()) << QChar ('\n');
+ } else {
+ Element * e = convertNode <Element> (p);
+ QString indent (QString ().fill (QChar (' '), depth));
+ out << indent << QChar ('<') << XMLStringlet (e->nodeName ());
+ for (AttributePtr a = e->attributes()->first(); a; a = a->nextSibling())
+ out << " " << XMLStringlet (a->name ().toString ()) <<
+ "=\"" << XMLStringlet (a->value ()) << "\"";
+ if (e->hasChildNodes ()) {
+ out << QChar ('>') << QChar ('\n');
+ for (NodePtr c = e->firstChild (); c; c = c->nextSibling ())
+ getOuterXML (c, out, depth + 1);
+ out << indent << QString("</") << XMLStringlet (e->nodeName()) <<
+ QChar ('>') << QChar ('\n');
+ } else
+ out << QString ("/>") << QChar ('\n');
+ }
+}
+
+QString Node::innerXML () const {
+ QString buf;
+ QTextOStream out (&buf);
+ for (NodePtr e = firstChild (); e; e = e->nextSibling ())
+ getOuterXML (e, out, 0);
+ return buf;
+}
+
+QString Node::outerXML () const {
+ QString buf;
+ QTextOStream out (&buf);
+ getOuterXML (m_self, out, 0);
+ return buf;
+}
+
+Node::PlayType Node::playType () {
+ return play_type_none;
+}
+
+void Node::opened () {}
+
+void Node::closed () {}
+
+NodeRefListPtr Node::listeners (unsigned int /*event_id*/) {
+ return NodeRefListPtr ();
+}
+
+bool Node::handleEvent (EventPtr /*event*/) { return false; }
+
+KDE_NO_EXPORT void Node::propagateEvent (EventPtr event) {
+ NodeRefListPtr nl = listeners (event->id ());
+ if (nl)
+ for (NodeRefItemPtr c = nl->first(); c; c = c->nextSibling ())
+ if (c->data)
+ c->data->handleEvent (event);
+}
+
+void Node::accept (Visitor * v) {
+ v->visit (this);
+}
+
+KDE_NO_EXPORT
+ConnectionPtr Node::connectTo (NodePtr node, unsigned int evt_id) {
+ NodeRefListPtr nl = listeners (evt_id);
+ if (nl)
+ return ConnectionPtr (new Connection (nl, node, this));
+ return ConnectionPtr ();
+}
+
+QString Node::nodeValue () const {
+ return QString ();
+}
+
+SurfacePtr Node::getSurface (NodePtr) {
+ return 0L;
+}
+
+//-----------------------------------------------------------------------------
+
+RefNode::RefNode (NodePtr & d, NodePtr ref)
+ : Node (d) {
+ setRefNode (ref);
+}
+
+void RefNode::setRefNode (const NodePtr ref) {
+ ref_node = ref;
+ if (ref_node)
+ tag_name = QString ("&%1").arg (ref_node->nodeName ());
+}
+
+//-----------------------------------------------------------------------------
+
+namespace KMPlayer {
+ struct KMPLAYER_NO_EXPORT ParamValue {
+ QString val;
+ QStringList * modifications;
+ ParamValue (const QString & v) : val (v), modifications (0L) {}
+ ~ParamValue () { delete modifications; }
+ QString value ();
+ void setValue (const QString & v) { val = v; }
+ };
+ typedef QMap <TrieString, ParamValue *> ParamMap;
+ class KMPLAYER_NO_EXPORT ElementPrivate {
+ public:
+ ~ElementPrivate ();
+ ParamMap params;
+ void clear ();
+ };
+}
+
+KDE_NO_EXPORT QString ParamValue::value () {
+ return modifications && modifications->size ()
+ ? modifications->back () : val;
+}
+
+KDE_NO_CDTOR_EXPORT ElementPrivate::~ElementPrivate () {
+ clear ();
+}
+
+KDE_NO_EXPORT void ElementPrivate::clear () {
+ const ParamMap::iterator e = params.end ();
+ for (ParamMap::iterator i = params.begin (); i != e; ++i)
+ delete i.data ();
+ params.clear ();
+}
+
+Element::Element (NodePtr & d, short id)
+ : Node (d, id), m_attributes (new AttributeList), d (new ElementPrivate) {}
+
+Element::~Element () {
+ delete d;
+}
+
+void Element::setParam (const TrieString &param, const QString &val, int *mid) {
+ ParamValue * pv = d->params [param];
+ if (!pv) {
+ pv = new ParamValue (mid ? QString() : val);
+ d->params.insert (param, pv);
+ }
+ if (mid) {
+ if (!pv->modifications)
+ pv->modifications = new QStringList;
+ if (*mid >= 0 && *mid < int (pv->modifications->size ())) {
+ (*pv->modifications) [*mid] = val;
+ } else {
+ *mid = pv->modifications->size ();
+ pv->modifications->push_back (val);
+ }
+ } else
+ pv->setValue (val);
+ parseParam (param, val);
+}
+
+QString Element::param (const TrieString & name) {
+ ParamValue * pv = d->params [name];
+ if (pv)
+ return pv->value ();
+ return QString ();
+}
+
+void Element::resetParam (const TrieString & param, int mid) {
+ ParamValue * pv = d->params [param];
+ if (pv && pv->modifications) {
+ if (int (pv->modifications->size ()) > mid && mid > -1) {
+ (*pv->modifications) [mid] = QString ();
+ while (pv->modifications->size () > 0 &&
+ pv->modifications->back ().isNull ())
+ pv->modifications->pop_back ();
+ }
+ QString val = pv->value ();
+ if (pv->modifications->size () == 0) {
+ delete pv->modifications;
+ pv->modifications = 0L;
+ val = pv->value ();
+ if (val.isNull ()) {
+ delete pv;
+ d->params.remove (param);
+ }
+ }
+ parseParam (param, val);
+ } else
+ kdError () << "resetting " << param.toString() << " that doesn't exists" << endl;
+}
+
+void Element::setAttribute (const TrieString & name, const QString & value) {
+ for (AttributePtr a = m_attributes->first (); a; a = a->nextSibling ())
+ if (name == a->name ()) {
+ a->setValue (value);
+ return;
+ }
+ m_attributes->append (new Attribute (name, value));
+}
+
+QString Element::getAttribute (const TrieString & name) {
+ for (AttributePtr a = m_attributes->first (); a; a = a->nextSibling ())
+ if (name == a->name ())
+ return a->value ();
+ return QString ();
+}
+
+void Element::init () {
+ d->clear();
+ for (AttributePtr a = attributes ()->first (); a; a = a->nextSibling ())
+ setParam (a->name (), a->value ());
+}
+
+void Element::reset () {
+ d->clear();
+ Node::reset ();
+}
+
+void Element::clear () {
+ m_attributes = new AttributeList; // remove attributes
+ d->clear();
+ Node::clear ();
+}
+
+void Element::setAttributes (AttributeListPtr attrs) {
+ m_attributes = attrs;
+}
+
+//-----------------------------------------------------------------------------
+
+Attribute::Attribute (const TrieString & n, const QString & v)
+ : m_name (n), m_value (v) {}
+
+void Attribute::setName (const TrieString & n) {
+ m_name = n;
+}
+
+void Attribute::setValue (const QString & v) {
+ m_value = v;
+}
+
+//-----------------------------------------------------------------------------
+
+static bool hasMrlChildren (const NodePtr & e) {
+ for (NodePtr c = e->firstChild (); c; c = c->nextSibling ())
+ if (c->isPlayable () || hasMrlChildren (c))
+ return true;
+ return false;
+}
+
+Mrl::Mrl (NodePtr & d, short id)
+ : Element (d, id), cached_ismrl_version (~0),
+ aspect (0), repeat (0),
+ view_mode (SingleMode),
+ resolved (false), bookmarkable (true) {}
+
+Mrl::~Mrl () {}
+
+Node::PlayType Mrl::playType () {
+ if (cached_ismrl_version != document()->m_tree_version) {
+ bool ismrl = !hasMrlChildren (this);
+ cached_play_type = ismrl ? play_type_unknown : play_type_none;
+ cached_ismrl_version = document()->m_tree_version;
+ }
+ return cached_play_type;
+}
+
+QString Mrl::absolutePath () {
+ QString path = src;
+ if (!path.isEmpty()) {
+ for (NodePtr e = parentNode (); e; e = e->parentNode ()) {
+ Mrl * mrl = e->mrl ();
+ if (mrl && !mrl->src.isEmpty () && mrl->src != src) {
+ path = KURL (mrl->absolutePath (), src).url ();
+ break;
+ }
+ }
+ }
+ return path;
+}
+
+NodePtr Mrl::childFromTag (const QString & tag) {
+ Node * elm = fromXMLDocumentTag (m_doc, tag);
+ if (elm)
+ return elm;
+ return NodePtr ();
+}
+
+Mrl * Mrl::linkNode () {
+ return this;
+}
+
+Mrl * Mrl::mrl () {
+ return this;
+}
+
+void Mrl::endOfFile () {
+ if (state == state_deferred &&
+ !isPlayable () && firstChild ()) { // if backend added child links
+ state = state_activated;
+ firstChild ()->activate ();
+ } else
+ finish ();
+}
+
+void Mrl::activate () {
+ resolved |= linkNode ()->resolved;
+ if (!resolved && document ()->notify_listener)
+ resolved = document ()->notify_listener->resolveURL (this);
+ if (!resolved) {
+ setState (state_deferred);
+ return;
+ } else
+ linkNode ()->resolved = true;
+ if (!isPlayable ()) {
+ Element::activate ();
+ return;
+ }
+ setState (state_activated);
+ begin ();
+}
+
+void Mrl::begin () {
+ kdDebug () << nodeName () << " Mrl::activate" << endl;
+ if (document ()->notify_listener) {
+ if (linkNode () != this) {
+ linkNode ()->activate ();
+ if (linkNode ()->unfinished ())
+ setState (state_began);
+ } else if (!src.isEmpty ()) {
+ if (document ()->notify_listener->requestPlayURL (this))
+ setState (state_began);
+ } else
+ deactivate (); // nothing to activate
+ }
+}
+
+SurfacePtr Mrl::getSurface (NodePtr node) {
+ for (NodePtr p = parentNode (); p; p = p->parentNode ())
+ if (p->mrl ())
+ return p->getSurface (node);
+ return 0L;
+}
+
+bool Mrl::handleEvent (EventPtr) {
+ return false;
+}
+
+void Mrl::parseParam (const TrieString & para, const QString & val) {
+ if (para == StringPool::attr_src && !src.startsWith ("#")) {
+ QString abs = absolutePath ();
+ if (abs != src)
+ src = val;
+ else
+ src = KURL (abs, val).url ();
+ for (NodePtr c = firstChild (); c; c = c->nextSibling ())
+ if (c->mrl () && c->mrl ()->opener.ptr () == this) {
+ removeChild (c);
+ c->reset();
+ }
+ resolved = false;
+ }
+}
+
+Surface::Surface (NodePtr n, const SRect & r)
+ : node (n),
+ bounds (r),
+ xscale (1.0), yscale (1.0),
+ background_color (0),
+ dirty (false)
+#ifdef HAVE_CAIRO
+ , surface (0L)
+#endif
+{}
+
+Surface::~Surface() {
+#ifdef HAVE_CAIRO
+ if (surface)
+ cairo_surface_destroy (surface);
+#endif
+}
+
+void Surface::remove () {
+ Surface *sp = parentNode ().ptr ();
+ if (sp) {
+ sp->markDirty ();
+ sp->removeChild (this);
+ }
+}
+
+void Surface::markDirty () {
+ for (Surface *s = this; s; s = s->parentNode ().ptr ())
+ s->dirty = true;
+}
+
+//-----------------------------------------------------------------------------
+
+Postpone::Postpone (NodePtr doc) : m_doc (doc) {
+ if (m_doc)
+ m_doc->document ()->timeOfDay (postponed_time);
+}
+
+Postpone::~Postpone () {
+ if (m_doc)
+ m_doc->document ()->proceed (postponed_time);
+}
+
+//-----------------------------------------------------------------------------
+
+namespace KMPlayer {
+ static NodePtr dummy_element;
+}
+
+Document::Document (const QString & s, PlayListNotify * n)
+ : Mrl (dummy_element, id_node_document),
+ notify_listener (n),
+ m_tree_version (0),
+ m_PostponedListeners (new NodeRefList),
+ cur_timeout (-1), intimer (false) {
+ m_doc = m_self; // just-in-time setting fragile m_self to m_doc
+ src = s;
+ editable = false;
+}
+
+Document::~Document () {
+ kdDebug () << "~Document" << endl;
+}
+
+static NodePtr getElementByIdImpl (NodePtr n, const QString & id, bool inter) {
+ NodePtr elm;
+ if (!n->isElementNode ())
+ return elm;
+ Element * e = convertNode <Element> (n);
+ if (e->getAttribute (StringPool::attr_id) == id)
+ return n;
+ for (NodePtr c = e->firstChild (); c; c = c->nextSibling ()) {
+ if (!inter && c->mrl () && c->mrl ()->opener == n)
+ continue;
+ if ((elm = getElementByIdImpl (c, id, inter)))
+ break;
+ }
+ return elm;
+}
+
+NodePtr Document::getElementById (const QString & id) {
+ return getElementByIdImpl (this, id, true);
+}
+
+NodePtr Document::getElementById (NodePtr n, const QString & id, bool inter) {
+ return getElementByIdImpl (n, id, inter);
+}
+
+NodePtr Document::childFromTag (const QString & tag) {
+ Node * elm = fromXMLDocumentTag (m_doc, tag);
+ if (elm)
+ return elm;
+ return 0L;
+}
+
+void Document::dispose () {
+ clear ();
+ m_doc = 0L;
+}
+
+void Document::activate () {
+ first_event_time.tv_sec = 0;
+ last_event_time = 0;
+ Mrl::activate ();
+}
+
+void Document::defer () {
+ if (resolved)
+ postpone_lock = postpone ();
+ Mrl::defer ();
+}
+
+void Document::undefer () {
+ if (!postpone_lock) {
+ Mrl::undefer ();
+ } else {
+ setState (state_activated);
+ postpone_lock = 0L;
+ }
+}
+
+void Document::reset () {
+ Mrl::reset ();
+ if (timers.first ()) {
+ if (notify_listener)
+ notify_listener->setTimeout (-1);
+ timers.clear ();
+ }
+ postpone_lock = 0L;
+}
+
+static inline
+int diffTime (const struct timeval & tv1, const struct timeval & tv2) {
+ //kdDebug () << "diffTime sec:" << ((tv1.tv_sec - tv2.tv_sec) * 1000) << " usec:" << ((tv1.tv_usec - tv2.tv_usec) /1000) << endl;
+ return (tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) /1000;
+}
+
+static inline void addTime (struct timeval & tv, int ms) {
+ tv.tv_sec += (tv.tv_usec + ms*1000) / 1000000;
+ tv.tv_usec = (tv.tv_usec + ms*1000) % 1000000;
+}
+
+void Document::timeOfDay (struct timeval & tv) {
+ gettimeofday (&tv, 0L);
+ if (!first_event_time.tv_sec) {
+ first_event_time = tv;
+ last_event_time = 0;
+ } else
+ last_event_time = diffTime (tv, first_event_time) / 100;
+}
+
+TimerInfoPtrW Document::setTimeout (NodePtr n, int ms, unsigned id) {
+ if (!notify_listener)
+ return 0L;
+ TimerInfoPtr ti = timers.first ();
+ int pos = 0;
+ struct timeval tv;
+ timeOfDay (tv);
+ addTime (tv, ms);
+ for (; ti && diffTime (ti->timeout, tv) <= 0; ti = ti->nextSibling ()) {
+ pos++;
+ //kdDebug () << "setTimeout tv:" << tv.tv_sec << "." << tv.tv_usec << " " << ti->timeout.tv_sec << "." << ti->timeout.tv_usec << endl;
+ }
+ TimerInfo * tinfo = new TimerInfo (n, id, tv, ms);
+ timers.insertBefore (tinfo, ti);
+ //kdDebug () << "setTimeout " << ms << " at:" << pos << " tv:" << tv.tv_sec << "." << tv.tv_usec << endl;
+ if (!postpone_ref && pos == 0 && !intimer) { // timer() does that too
+ cur_timeout = ms;
+ notify_listener->setTimeout (ms);
+ }
+ return tinfo;
+}
+
+void Document::cancelTimer (TimerInfoPtr tinfo) {
+ if (!postpone_ref && !intimer && tinfo == timers.first ()) {
+ //kdDebug () << "cancel first" << endl;
+ TimerInfoPtr second = tinfo->nextSibling ();
+ if (second) {
+ struct timeval now;
+ timeOfDay (now);
+ int diff = diffTime (now, second->timeout);
+ cur_timeout = diff > 0 ? 0 : -diff;
+ } else
+ cur_timeout = -1;
+ notify_listener->setTimeout (cur_timeout);
+ }
+ timers.remove (tinfo);
+}
+
+bool Document::timer () {
+ struct timeval now = { 0, 0 }; // unset
+ int new_timeout = -1;
+ TimerInfoPtrW tinfo = timers.first (); // keep use_count on 1
+
+ intimer = true;
+ // handle max 100 timeouts with timeout set to now
+ for (int i = 0; !!tinfo && !postpone_ref && i < 100; ++i) {
+ if (tinfo && !tinfo->node) {
+ // some part of document has gone and didn't remove timer
+ kdError () << "spurious timer" << endl;
+ for (; tinfo && !tinfo->node; tinfo = timers.first ())
+ timers.remove (tinfo);
+ tinfo = timers.first ();
+ }
+ if (!tinfo)
+ break;
+ TimerEvent * te = new TimerEvent (tinfo);
+ EventPtr e (te);
+ tinfo->node->handleEvent (e);
+ if (tinfo) { // may be removed from timers and become 0
+ if (te->interval) {
+ TimerInfoPtr tinfo2 (tinfo); // prevent destruction
+ timers.remove (tinfo);
+ timeOfDay (now);
+ int drift = diffTime (now, tinfo2->timeout);
+ if (drift > tinfo2->milli_sec) {
+ drift = tinfo2->milli_sec;
+ //kdWarning() << "time drift" << endl;
+ }
+ tinfo2->timeout = now;
+ addTime (tinfo2->timeout, tinfo2->milli_sec - drift);
+ TimerInfoPtr ti = timers.first ();
+ int pos = 0;
+ for (; ti && diffTime (tinfo2->timeout, ti->timeout) >= 0; ti = ti->nextSibling ()) {
+ pos++;
+ }
+ timers.insertBefore (tinfo2, ti);
+ } else
+ timers.remove (tinfo);
+ }
+ tinfo = timers.first ();
+ if (!tinfo)
+ break;
+ if (!now.tv_sec)
+ timeOfDay (now);
+ int diff = diffTime (now, tinfo->timeout);
+ new_timeout = diff > 0 ? 0 : -diff;
+ if (new_timeout > 0)
+ break;
+ }
+ intimer = false;
+
+ // set new timeout to prevent interval timer events
+ if (notify_listener && !postpone_ref && tinfo) {
+ if (new_timeout != cur_timeout) {
+ cur_timeout = new_timeout;
+ notify_listener->setTimeout (cur_timeout);
+ }
+ // else keep the timer, no new setTimeout
+ } else {
+ cur_timeout = -1;
+ notify_listener->setTimeout (-1); // kill timer
+ }
+ return false;
+}
+
+PostponePtr Document::postpone () {
+ if (postpone_ref)
+ return postpone_ref;
+ kdDebug () << "postpone" << endl;
+ if (!intimer && notify_listener) {
+ cur_timeout = -1;
+ notify_listener->setTimeout (-1);
+ }
+ PostponePtr p = new Postpone (this);
+ postpone_ref = p;
+ propagateEvent (new PostponedEvent (true));
+ return p;
+}
+
+void Document::proceed (const struct timeval & postponed_time) {
+ kdDebug () << "proceed" << endl;
+ if (timers.first () && notify_listener) {
+ struct timeval now;
+ timeOfDay (now);
+ int diff = diffTime (now, postponed_time);
+ if (diff > 0) {
+ for (TimerInfoPtr t = timers.first (); t; t = t->nextSibling ())
+ addTime (t->timeout, diff);
+ }
+ if (!intimer) { // eg. postpone() + proceed() in same timer()
+ diff = diffTime (timers.first ()->timeout, now);
+ cur_timeout = diff < 0 ? 0 : diff;
+ notify_listener->setTimeout (cur_timeout);
+ }
+ }
+ propagateEvent (new PostponedEvent (false));
+}
+
+SurfacePtr Document::getSurface (NodePtr node) {
+ if (notify_listener)
+ return notify_listener->getSurface (node);
+ return 0L;
+}
+
+NodeRefListPtr Document::listeners (unsigned int id) {
+ if (id == event_postponed)
+ return m_PostponedListeners;
+ return Mrl::listeners (id);
+}
+
+//-----------------------------------------------------------------------------
+
+KDE_NO_CDTOR_EXPORT TextNode::TextNode (NodePtr & d, const QString & s, short i)
+ : Node (d, i), text (s) {}
+
+void TextNode::appendText (const QString & s) {
+ text += s;
+}
+
+QString TextNode::nodeValue () const {
+ return text;
+}
+
+KDE_NO_EXPORT bool TextNode::expose () const {
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+
+KDE_NO_CDTOR_EXPORT CData::CData (NodePtr & d, const QString & s)
+ : TextNode (d, s, id_node_cdata) {}
+
+//-----------------------------------------------------------------------------
+
+DarkNode::DarkNode (NodePtr & d, const QString & n, short id)
+ : Element (d, id), name (n) {
+}
+
+NodePtr DarkNode::childFromTag (const QString & tag) {
+ return new DarkNode (m_doc, tag);
+}
+
+KDE_NO_EXPORT bool DarkNode::expose () const {
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+
+GenericURL::GenericURL (NodePtr & d, const QString & s, const QString & name)
+ : Mrl (d, id_node_playlist_item) {
+ src = s;
+ if (!src.isEmpty ())
+ setAttribute (StringPool::attr_src, src);
+ pretty_name = name;
+}
+
+KDE_NO_EXPORT void GenericURL::closed () {
+ if (src.isEmpty ())
+ src = getAttribute (StringPool::attr_src);
+}
+
+//-----------------------------------------------------------------------------
+
+GenericMrl::GenericMrl (NodePtr & d, const QString & s, const QString & name, const QString & tag)
+ : Mrl (d, id_node_playlist_item), node_name (tag) {
+ src = s;
+ if (!src.isEmpty ())
+ setAttribute (StringPool::attr_src, src);
+ pretty_name = name;
+ if (!name.isEmpty ())
+ setAttribute (StringPool::attr_name, name);
+}
+
+void GenericMrl::closed () {
+ if (src.isEmpty ()) {
+ src = getAttribute (StringPool::attr_src);
+ if (src.isEmpty ())
+ src = getAttribute (StringPool::attr_url);
+ }
+ if (pretty_name.isEmpty ())
+ pretty_name = getAttribute (StringPool::attr_name);
+}
+
+bool GenericMrl::expose () const {
+ return !pretty_name.isEmpty () || //return false if no title and only one
+ previousSibling () || nextSibling ();
+}
+
+//-----------------------------------------------------------------------------
+
+namespace KMPlayer {
+
+class KMPLAYER_NO_EXPORT DocumentBuilder {
+ int m_ignore_depth;
+ bool m_set_opener;
+ bool m_root_is_first;
+ NodePtr m_node;
+ NodePtr m_root;
+public:
+ DocumentBuilder (NodePtr d, bool set_opener);
+ ~DocumentBuilder () {}
+ bool startTag (const QString & tag, AttributeListPtr attr);
+ bool endTag (const QString & tag);
+ bool characterData (const QString & data);
+ bool cdataData (const QString & data);
+#ifdef HAVE_EXPAT
+ void cdataStart ();
+ void cdataEnd ();
+private:
+ bool in_cdata;
+ QString cdata;
+#endif
+};
+
+} // namespace KMPlayer
+
+DocumentBuilder::DocumentBuilder (NodePtr d, bool set_opener)
+ : m_ignore_depth (0), m_set_opener (set_opener), m_root_is_first (false)
+ , m_node (d), m_root (d)
+#ifdef HAVE_EXPAT
+ , in_cdata (false)
+#endif
+{}
+
+bool DocumentBuilder::startTag(const QString &tag, AttributeListPtr attr) {
+ if (m_ignore_depth) {
+ m_ignore_depth++;
+ //kdDebug () << "Warning: ignored tag " << tag.latin1 () << " ignore depth = " << m_ignore_depth << endl;
+ } else {
+ NodePtr n = m_node->childFromTag (tag);
+ if (!n) {
+ kdDebug () << "Warning: unknown tag " << tag.latin1 () << endl;
+ NodePtr doc = m_root->document ();
+ n = new DarkNode (doc, tag);
+ }
+ //kdDebug () << "Found tag " << tag << endl;
+ if (n->isElementNode ())
+ convertNode <Element> (n)->setAttributes (attr);
+ if (m_node == n && m_node == m_root)
+ m_root_is_first = true;
+ else
+ m_node->appendChild (n);
+ if (m_set_opener && m_node == m_root) {
+ Mrl * mrl = n->mrl ();
+ if (mrl)
+ mrl->opener = m_root;
+ }
+ n->opened ();
+ m_node = n;
+ }
+ return true;
+}
+
+bool DocumentBuilder::endTag (const QString & tag) {
+ if (m_ignore_depth) { // endtag to ignore
+ m_ignore_depth--;
+ kdDebug () << "Warning: ignored end tag " << " ignore depth = " << m_ignore_depth << endl;
+ } else { // endtag
+ NodePtr n = m_node;
+ while (n) {
+ if (!strcasecmp (n->nodeName (), tag.local8Bit ().data ()) &&
+ (m_root_is_first || n != m_root)) {
+ while (n != m_node) {
+ kdWarning() << m_node->nodeName () << " not closed" << endl;
+ if (m_root == m_node->parentNode ())
+ break;
+ m_node->closed ();
+ m_node = m_node->parentNode ();
+ }
+ break;
+ }
+ if (n == m_root) {
+ if (n == m_node) {
+ kdError () << "m_node == m_doc, stack underflow " << endl;
+ return false;
+ }
+ kdWarning () << "endtag: no match " << tag.local8Bit () << endl;
+ break;
+ } else
+ kdWarning () << "tag " << tag << " not " << n->nodeName () << endl;
+ n = n ->parentNode ();
+ }
+ //kdDebug () << "end tag " << tag << endl;
+ m_node->closed ();
+ m_node = m_node->parentNode ();
+ }
+ return true;
+}
+
+bool DocumentBuilder::characterData (const QString & data) {
+ if (!m_ignore_depth) {
+#ifdef HAVE_EXPAT
+ if (in_cdata)
+ cdata += data;
+ else
+#endif
+ m_node->characterData (data);
+ }
+ //kdDebug () << "characterData " << d.latin1() << endl;
+ return true;
+}
+
+bool DocumentBuilder::cdataData (const QString & data) {
+ if (!m_ignore_depth) {
+ NodePtr d = m_node->document ();
+ m_node->appendChild (new CData (d, data));
+ }
+ //kdDebug () << "cdataData " << d.latin1() << endl;
+ return true;
+}
+
+#ifdef HAVE_EXPAT
+
+void DocumentBuilder::cdataStart () {
+ cdata.truncate (0);
+ in_cdata = true;
+}
+
+void DocumentBuilder::cdataEnd () {
+ cdataData (cdata);
+ cdata.truncate (0);
+ in_cdata = false;
+}
+
+static void startTag (void *data, const char * tag, const char **attr) {
+ DocumentBuilder * builder = static_cast <DocumentBuilder *> (data);
+ AttributeListPtr attributes = new AttributeList;
+ if (attr && attr [0]) {
+ for (int i = 0; attr[i]; i += 2)
+ attributes->append (new Attribute (QString::fromUtf8 (attr [i]), QString::fromUtf8 (attr [i+1])));
+ }
+ builder->startTag (QString::fromUtf8 (tag), attributes);
+}
+
+static void endTag (void *data, const char * tag) {
+ DocumentBuilder * builder = static_cast <DocumentBuilder *> (data);
+ builder->endTag (QString::fromUtf8 (tag));
+}
+
+static void characterData (void *data, const char *s, int len) {
+ DocumentBuilder * builder = static_cast <DocumentBuilder *> (data);
+ char * buf = new char [len + 1];
+ strncpy (buf, s, len);
+ buf[len] = 0;
+ builder->characterData (QString::fromUtf8 (buf));
+ delete [] buf;
+}
+
+static void cdataStart (void *data) {
+ DocumentBuilder * builder = static_cast <DocumentBuilder *> (data);
+ builder->cdataStart ();
+}
+
+static void cdataEnd (void *data) {
+ DocumentBuilder * builder = static_cast <DocumentBuilder *> (data);
+ builder->cdataEnd ();
+}
+
+namespace KMPlayer {
+
+KMPLAYER_EXPORT
+void readXML (NodePtr root, QTextStream & in, const QString & firstline, bool set_opener) {
+ bool ok = true;
+ DocumentBuilder builder (root, set_opener);
+ XML_Parser parser = XML_ParserCreate (0L);
+ XML_SetUserData (parser, &builder);
+ XML_SetElementHandler (parser, startTag, endTag);
+ XML_SetCharacterDataHandler (parser, characterData);
+ XML_SetCdataSectionHandler (parser, cdataStart, cdataEnd);
+ if (!firstline.isEmpty ()) {
+ QString str (firstline + QChar ('\n'));
+ QCString buf = str.utf8 ();
+ ok = XML_Parse(parser, buf, strlen (buf), false) != XML_STATUS_ERROR;
+ if (!ok)
+ kdWarning () << XML_ErrorString(XML_GetErrorCode(parser)) << " at " << XML_GetCurrentLineNumber(parser) << " col " << XML_GetCurrentColumnNumber(parser) << endl;
+ }
+ if (ok && !in.atEnd ()) {
+ QCString buf = in.read ().utf8 ();
+ ok = XML_Parse(parser, buf, strlen (buf), true) != XML_STATUS_ERROR;
+ if (!ok)
+ kdWarning () << XML_ErrorString(XML_GetErrorCode(parser)) << " at " << XML_GetCurrentLineNumber(parser) << " col " << XML_GetCurrentColumnNumber(parser) << endl;
+ }
+ XML_ParserFree(parser);
+ root->normalize ();
+ //return ok;
+}
+
+} // namespace KMPlayer
+
+//-----------------------------------------------------------------------------
+#else // HAVE_EXPAT
+
+namespace {
+
+class KMPLAYER_NO_EXPORT SimpleSAXParser {
+public:
+ SimpleSAXParser (DocumentBuilder & b) : builder (b), position (0), m_attributes (new AttributeList), equal_seen (false), in_dbl_quote (false), in_sngl_quote (false), have_error (false), no_entitity_look_ahead (false), have_next_char (false) {}
+ virtual ~SimpleSAXParser () {};
+ bool parse (QTextStream & d);
+private:
+ QTextStream * data;
+ DocumentBuilder & builder;
+ int position;
+ QChar next_char;
+ enum Token { tok_empty, tok_text, tok_white_space, tok_angle_open,
+ tok_equal, tok_double_quote, tok_single_quote, tok_angle_close,
+ tok_slash, tok_exclamation, tok_amp, tok_hash, tok_semi_colon,
+ tok_question_mark, tok_cdata_start };
+ enum State {
+ InTag, InStartTag, InPITag, InDTDTag, InEndTag, InAttributes, InContent, InCDATA, InComment
+ };
+ struct TokenInfo {
+ TokenInfo () : token (tok_empty) {}
+ Token token;
+ QString string;
+ SharedPtr <TokenInfo> next;
+ };
+ typedef SharedPtr <TokenInfo> TokenInfoPtr;
+ struct StateInfo {
+ StateInfo (State s, SharedPtr <StateInfo> n) : state (s), next (n) {}
+ State state;
+ QString data;
+ SharedPtr <StateInfo> next;
+ };
+ SharedPtr <StateInfo> m_state;
+ TokenInfoPtr next_token, token, prev_token;
+ // for element reading
+ QString tagname;
+ AttributeListPtr m_attributes;
+ QString attr_name, attr_value;
+ QString cdata;
+ bool equal_seen;
+ bool in_dbl_quote;
+ bool in_sngl_quote;
+ bool have_error;
+ bool no_entitity_look_ahead;
+ bool have_next_char;
+
+ bool readTag ();
+ bool readEndTag ();
+ bool readAttributes ();
+ bool readPI ();
+ bool readDTD ();
+ bool readCDATA ();
+ bool readComment ();
+ bool nextToken ();
+ void push ();
+ void push_attribute ();
+};
+
+} // namespace
+
+KMPLAYER_EXPORT
+void KMPlayer::readXML (NodePtr root, QTextStream & in, const QString & firstline, bool set_opener) {
+ DocumentBuilder builder (root, set_opener);
+ SimpleSAXParser parser (builder);
+ if (!firstline.isEmpty ()) {
+ QString str (firstline + QChar ('\n'));
+ QTextStream fl_in (&str, IO_ReadOnly);
+ parser.parse (fl_in);
+ }
+ if (!in.atEnd ())
+ parser.parse (in);
+ for (NodePtr e = root; e; e = e->parentNode ())
+ e->closed ();
+ //doc->normalize ();
+ //kdDebug () << root->outerXML ();
+}
+
+void SimpleSAXParser::push () {
+ if (next_token->string.length ()) {
+ prev_token = token;
+ token = next_token;
+ if (prev_token)
+ prev_token->next = token;
+ next_token = TokenInfoPtr (new TokenInfo);
+ //kdDebug () << "push " << token->string << endl;
+ }
+}
+
+void SimpleSAXParser::push_attribute () {
+ //kdDebug () << "attribute " << attr_name.latin1 () << "=" << attr_value.latin1 () << endl;
+ m_attributes->append (new Attribute (attr_name, attr_value));
+ attr_name.truncate (0);
+ attr_value.truncate (0);
+ equal_seen = in_sngl_quote = in_dbl_quote = false;
+}
+
+bool SimpleSAXParser::nextToken () {
+ TokenInfoPtr cur_token = token;
+ while (!data->atEnd () && cur_token == token && !(token && token->next)) {
+ if (have_next_char)
+ have_next_char = false;
+ else
+ *data >> next_char;
+ bool append_char = true;
+ if (next_char.isSpace ()) {
+ if (next_token->token != tok_white_space)
+ push ();
+ next_token->token = tok_white_space;
+ } else if (!next_char.isLetterOrNumber ()) {
+ if (next_char == QChar ('#')) {
+ //if (next_token->token == tok_empty) { // check last item on stack &
+ push ();
+ next_token->token = tok_hash;
+ //}
+ } else if (next_char == QChar ('/')) {
+ push ();
+ next_token->token = tok_slash;
+ } else if (next_char == QChar ('!')) {
+ push ();
+ next_token->token = tok_exclamation;
+ } else if (next_char == QChar ('?')) {
+ push ();
+ next_token->token = tok_question_mark;
+ } else if (next_char == QChar ('<')) {
+ push ();
+ next_token->token = tok_angle_open;
+ } else if (next_char == QChar ('>')) {
+ push ();
+ next_token->token = tok_angle_close;
+ } else if (next_char == QChar (';')) {
+ push ();
+ next_token->token = tok_semi_colon;
+ } else if (next_char == QChar ('=')) {
+ push ();
+ next_token->token = tok_equal;
+ } else if (next_char == QChar ('"')) {
+ push ();
+ next_token->token = tok_double_quote;
+ } else if (next_char == QChar ('\'')) {
+ push ();
+ next_token->token = tok_single_quote;
+ } else if (next_char == QChar ('&')) {
+ push ();
+ if (no_entitity_look_ahead) {
+ have_next_char = true;
+ break;
+ }
+ append_char = false;
+ no_entitity_look_ahead = true;
+ TokenInfoPtr tmp = token;
+ TokenInfoPtr prev_tmp = prev_token;
+ if (nextToken () && token->token == tok_text &&
+ nextToken () && token->token == tok_semi_colon) {
+ if (prev_token->string == QString ("amp"))
+ token->string = QChar ('&');
+ else if (prev_token->string == QString ("lt"))
+ token->string = QChar ('<');
+ else if (prev_token->string == QString ("gt"))
+ token->string = QChar ('>');
+ else if (prev_token->string == QString ("quot"))
+ token->string = QChar ('"');
+ else if (prev_token->string == QString ("apos"))
+ token->string = QChar ('\'');
+ else if (prev_token->string == QString ("copy"))
+ token->string = QChar (169);
+ else
+ token->string = QChar ('?');// TODO lookup more ..
+ token->token = tok_text;
+ if (tmp) { // cut out the & xxx ; tokens
+ tmp->next = token;
+ token = tmp;
+ }
+ //kdDebug () << "entity found "<<prev_token->string << endl;
+ } else if (token->token == tok_hash &&
+ nextToken () && token->token == tok_text &&
+ nextToken () && token->token == tok_semi_colon) {
+ //kdDebug () << "char entity found " << prev_token->string << prev_token->string.toInt (0L, 16) << endl;
+ token->token = tok_text;
+ if (!prev_token->string.startsWith (QChar ('x')))
+ token->string = QChar (prev_token->string.toInt ());
+ else
+ token->string = QChar (prev_token->string.mid (1).toInt (0L, 16));
+ if (tmp) { // cut out the '& # xxx ;' tokens
+ tmp->next = token;
+ token = tmp;
+ }
+ } else {
+ token = tmp; // restore and insert the lost & token
+ tmp = TokenInfoPtr (new TokenInfo);
+ tmp->token = tok_amp;
+ tmp->string += QChar ('&');
+ tmp->next = token->next;
+ if (token)
+ token->next = tmp;
+ else
+ token = tmp; // hmm
+ }
+ no_entitity_look_ahead = false;
+ prev_token = prev_tmp;
+ } else if (next_token->token != tok_text) {
+ push ();
+ next_token->token = tok_text;
+ }
+ } else if (next_token->token != tok_text) {
+ push ();
+ next_token->token = tok_text;
+ }
+ if (append_char)
+ next_token->string += next_char;
+ if (next_token->token == tok_text &&
+ next_char == QChar ('[' ) && next_token->string == "[CDATA[") {
+ next_token->token = tok_cdata_start;
+ break;
+ }
+ }
+ if (token == cur_token) {
+ if (token && token->next) {
+ prev_token = token;
+ token = token->next;
+ } else if (next_token->string.length ()) {
+ push (); // last token
+ } else
+ return false;
+ return true;
+ }
+ return true;
+}
+
+bool SimpleSAXParser::readAttributes () {
+ bool closed = false;
+ while (true) {
+ if (!nextToken ()) return false;
+ //kdDebug () << "readAttributes " << token->string.latin1() << endl;
+ if ((in_dbl_quote && token->token != tok_double_quote) ||
+ (in_sngl_quote && token->token != tok_single_quote)) {
+ attr_value += token->string;
+ } else if (token->token == tok_equal) {
+ if (attr_name.isEmpty ())
+ return false;
+ if (equal_seen)
+ attr_value += token->string; // EQ=a=2c ???
+ //kdDebug () << "equal_seen"<< endl;
+ equal_seen = true;
+ } else if (token->token == tok_white_space) {
+ if (!attr_value.isEmpty ())
+ push_attribute ();
+ } else if (token->token == tok_single_quote) {
+ if (!equal_seen)
+ attr_name += token->string; // D'OH=xxx ???
+ else if (in_sngl_quote) { // found one
+ push_attribute ();
+ } else if (attr_value.isEmpty ())
+ in_sngl_quote = true;
+ else
+ attr_value += token->string;
+ } else if (token->token == tok_double_quote) {
+ if (!equal_seen)
+ attr_name += token->string; // hmm
+ else if (in_dbl_quote) { // found one
+ push_attribute ();
+ } else if (attr_value.isEmpty ())
+ in_dbl_quote = true;
+ else
+ attr_value += token->string;
+ //kdDebug () << "in_dbl_quote:"<< in_dbl_quote << endl;
+ } else if (token->token == tok_slash) {
+ TokenInfoPtr mark_token = token;
+ if (nextToken () &&
+ (token->token != tok_white_space || nextToken()) &&//<e / >
+ token->token == tok_angle_close) {
+ //kdDebug () << "close mark:"<< endl;
+ closed = true;
+ break;
+ } else {
+ token = mark_token;
+ //kdDebug () << "not end mark:"<< equal_seen << endl;
+ if (equal_seen)
+ attr_value += token->string; // ABBR=w/o ???
+ else
+ attr_name += token->string;
+ }
+ } else if (token->token == tok_angle_close) {
+ if (!attr_name.isEmpty ())
+ push_attribute ();
+ break;
+ } else if (equal_seen) {
+ attr_value += token->string;
+ } else {
+ attr_name += token->string;
+ }
+ }
+ m_state = m_state->next;
+ if (m_state->state == InPITag) {
+ if (tagname == QString ("xml")) {
+ /*const AttributeMap::const_iterator e = attr.end ();
+ for (AttributeMap::const_iterator i = attr.begin (); i != e; ++i)
+ if (!strcasecmp (i.key ().latin1 (), "encoding"))
+ kdDebug () << "encodeing " << i.data().latin1() << endl;*/
+ }
+ } else {
+ have_error = builder.startTag (tagname, m_attributes);
+ if (closed)
+ have_error &= builder.endTag (tagname);
+ //kdDebug () << "readTag " << tagname << " closed:" << closed << " ok:" << have_error << endl;
+ }
+ m_state = m_state->next; // pop Node or PI
+ return true;
+}
+
+bool SimpleSAXParser::readPI () {
+ // TODO: <?xml .. encoding="ENC" .. ?>
+ if (!nextToken ()) return false;
+ if (token->token == tok_text && !token->string.compare ("xml")) {
+ m_state = new StateInfo (InAttributes, m_state);
+ return readAttributes ();
+ } else {
+ while (nextToken ())
+ if (token->token == tok_angle_close) {
+ m_state = m_state->next;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool SimpleSAXParser::readDTD () {
+ //TODO: <!ENTITY ..>
+ if (!nextToken ()) return false;
+ if (token->token == tok_text && token->string.startsWith (QString ("--"))) {
+ m_state = new StateInfo (InComment, m_state->next); // note: pop DTD
+ return readComment ();
+ }
+ //kdDebug () << "readDTD: " << token->string.latin1 () << endl;
+ if (token->token == tok_cdata_start) {
+ m_state = new StateInfo (InCDATA, m_state->next); // note: pop DTD
+ if (token->next) {
+ cdata = token->next->string;
+ token->next = 0;
+ } else {
+ cdata = next_token->string;
+ next_token->string.truncate (0);
+ next_token->token = tok_empty;
+ }
+ return readCDATA ();
+ }
+ while (nextToken ())
+ if (token->token == tok_angle_close) {
+ m_state = m_state->next;
+ return true;
+ }
+ return false;
+}
+
+bool SimpleSAXParser::readCDATA () {
+ while (!data->atEnd ()) {
+ *data >> next_char;
+ if (next_char == QChar ('>') && cdata.endsWith (QString ("]]"))) {
+ cdata.truncate (cdata.length () - 2);
+ m_state = m_state->next;
+ if (m_state->state == InContent)
+ have_error = builder.cdataData (cdata);
+ else if (m_state->state == InAttributes) {
+ if (equal_seen)
+ attr_value += cdata;
+ else
+ attr_name += cdata;
+ }
+ cdata.truncate (0);
+ return true;
+ }
+ cdata += next_char;
+ }
+ return false;
+}
+
+bool SimpleSAXParser::readComment () {
+ while (nextToken ()) {
+ if (token->token == tok_angle_close && prev_token)
+ if (prev_token->string.endsWith (QString ("--"))) {
+ m_state = m_state->next;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool SimpleSAXParser::readEndTag () {
+ if (!nextToken ()) return false;
+ if (token->token == tok_white_space)
+ if (!nextToken ()) return false;
+ tagname = token->string;
+ if (!nextToken ()) return false;
+ if (token->token == tok_white_space)
+ if (!nextToken ()) return false;
+ if (token->token != tok_angle_close)
+ return false;
+ have_error = builder.endTag (tagname);
+ m_state = m_state->next;
+ return true;
+}
+
+// TODO: <!ENTITY ..> &#1234;
+bool SimpleSAXParser::readTag () {
+ if (!nextToken ()) return false;
+ if (token->token == tok_exclamation) {
+ m_state = new StateInfo (InDTDTag, m_state->next);
+ //kdDebug () << "readTag: " << token->string.latin1 () << endl;
+ return readDTD ();
+ }
+ if (token->token == tok_white_space)
+ if (!nextToken ()) return false; // allow '< / foo', '< foo', '< ? foo'
+ if (token->token == tok_question_mark) {
+ m_state = new StateInfo (InPITag, m_state->next);
+ return readPI ();
+ }
+ if (token->token == tok_slash) {
+ m_state = new StateInfo (InEndTag, m_state->next);
+ return readEndTag ();
+ }
+ if (token->token != tok_text)
+ return false; // FIXME entities
+ tagname = token->string;
+ //kdDebug () << "readTag " << tagname.latin1() << endl;
+ m_state = new StateInfo (InAttributes, m_state);
+ return readAttributes ();
+}
+
+bool SimpleSAXParser::parse (QTextStream & d) {
+ data = &d;
+ if (!next_token) {
+ next_token = TokenInfoPtr (new TokenInfo);
+ m_state = new StateInfo (InContent, m_state);
+ }
+ bool ok = true;
+ bool in_character_data = false;
+ QString white_space;
+ while (ok) {
+ switch (m_state->state) {
+ case InTag:
+ ok = readTag ();
+ break;
+ case InPITag:
+ ok = readPI ();
+ break;
+ case InDTDTag:
+ ok = readDTD ();
+ break;
+ case InEndTag:
+ ok = readEndTag ();
+ break;
+ case InAttributes:
+ ok = readAttributes ();
+ break;
+ case InCDATA:
+ ok = readCDATA ();
+ break;
+ case InComment:
+ ok = readComment ();
+ break;
+ default:
+ if ((ok = nextToken ())) {
+ if (token->token == tok_angle_open) {
+ attr_name.truncate (0);
+ attr_value.truncate (0);
+ m_attributes = new AttributeList;
+ equal_seen = in_sngl_quote = in_dbl_quote = false;
+ m_state = new StateInfo (InTag, m_state);
+ ok = readTag ();
+ in_character_data = false;
+ white_space.truncate (0);
+ } else if (token->token == tok_white_space) {
+ white_space += token->string;
+ } else {
+ if (!white_space.isEmpty ()) {
+ if (!in_character_data) {
+ int pos = white_space.findRev (QChar ('\n'));
+ if (pos > -1)
+ white_space = white_space.mid (pos + 1);
+ }
+ have_error = builder.characterData (white_space);
+ white_space.truncate (0);
+ }
+ have_error = builder.characterData (token->string);
+ in_character_data = true;
+ }
+ }
+ }
+ if (!m_state)
+ return true; // end document
+ }
+ return false; // need more data
+}
+
+#endif // HAVE_EXPAT