You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
kmplayer/src/kmplayerplaylist.cpp

1843 lines
56 KiB

/**
* Copyright (C) 2004 by Koos Vriezen <koos.vriezen@gmail.com>
*
* 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 <tqtextstream.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 TQString & 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, TQString ());
else if (!strcasecmp (name, "mrl") ||
!strcasecmp (name, "document"))
return new GenericMrl (d);
return 0L;
}
//-----------------------------------------------------------------------------
struct XMLStringlet {
const TQString str;
XMLStringlet (const TQString & s) : str (s) {}
};
} // namespace
TQTextStream & operator << (TQTextStream & out, const XMLStringlet & txt) {
int len = int (txt.str.length ());
for (int i = 0; i < len; ++i) {
if (txt.str [i] == TQChar ('<')) {
out << "&lt;";
} else if (txt.str [i] == TQChar ('>')) {
out << "&gt;";
} else if (txt.str [i] == TQChar ('"')) {
out << "&quot;";
} else if (txt.str [i] == TQChar ('&')) {
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 TQString &) {
return NodePtr ();
}
KDE_NO_EXPORT void Node::characterData (const TQString & 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) {
TQString 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, TQTextOStream & 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);
}
}
TQString Node::innerText () const {
TQString buf;
TQTextOStream out (&buf);
getInnerText (m_self, out);
return buf;
}
static void getOuterXML (const NodePtr p, TQTextOStream & out, int depth) {
if (!p->isElementNode ()) { // #text or #cdata
if (p->id == id_node_cdata)
out << "<![CDATA[" << p->nodeValue () << "]]>" << TQChar ('\n');
else
out << XMLStringlet (p->nodeValue ()) << TQChar ('\n');
} else {
Element * e = convertNode <Element> (p);
TQString indent (TQString ().fill (TQChar (' '), depth));
out << indent << TQChar ('<') << 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 << TQChar ('>') << TQChar ('\n');
for (NodePtr c = e->firstChild (); c; c = c->nextSibling ())
getOuterXML (c, out, depth + 1);
out << indent << TQString("</") << XMLStringlet (e->nodeName()) <<
TQChar ('>') << TQChar ('\n');
} else
out << TQString ("/>") << TQChar ('\n');
}
}
TQString Node::innerXML () const {
TQString buf;
TQTextOStream out (&buf);
for (NodePtr e = firstChild (); e; e = e->nextSibling ())
getOuterXML (e, out, 0);
return buf;
}
TQString Node::outerXML () const {
TQString buf;
TQTextOStream 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 ();
}
TQString Node::nodeValue () const {
return TQString ();
}
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 = TQString ("&%1").arg (ref_node->nodeName ());
}
//-----------------------------------------------------------------------------
namespace KMPlayer {
struct KMPLAYER_NO_EXPORT ParamValue {
TQString val;
TQStringList * modifications;
ParamValue (const TQString & v) : val (v), modifications (0L) {}
~ParamValue () { delete modifications; }
TQString value ();
void setValue (const TQString & v) { val = v; }
};
typedef TQMap <TrieString, ParamValue *> ParamMap;
class KMPLAYER_NO_EXPORT ElementPrivate {
public:
~ElementPrivate ();
ParamMap params;
void clear ();
};
}
KDE_NO_EXPORT TQString 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 TQString &val, int *mid) {
ParamValue * pv = d->params [param];
if (!pv) {
pv = new ParamValue (mid ? TQString() : val);
d->params.insert (param, pv);
}
if (mid) {
if (!pv->modifications)
pv->modifications = new TQStringList;
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);
}
TQString Element::param (const TrieString & name) {
ParamValue * pv = d->params [name];
if (pv)
return pv->value ();
return TQString ();
}
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] = TQString ();
while (pv->modifications->size () > 0 &&
pv->modifications->back ().isNull ())
pv->modifications->pop_back ();
}
TQString 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 TQString & 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));
}
TQString Element::getAttribute (const TrieString & name) {
for (AttributePtr a = m_attributes->first (); a; a = a->nextSibling ())
if (name == a->name ())
return a->value ();
return TQString ();
}
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 TQString & v)
: m_name (n), m_value (v) {}
void Attribute::setName (const TrieString & n) {
m_name = n;
}
void Attribute::setValue (const TQString & 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;
}
TQString Mrl::absolutePath () {
TQString 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 TQString & 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 TQString & val) {
if (para == StringPool::attr_src && !src.startsWith ("#")) {
TQString 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 TQString & 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 TQString & 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 TQString & id) {
return getElementByIdImpl (this, id, true);
}
NodePtr Document::getElementById (NodePtr n, const TQString & id, bool inter) {
return getElementByIdImpl (n, id, inter);
}
NodePtr Document::childFromTag (const TQString & 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 TQString & s, short i)
: Node (d, i), text (s) {}
void TextNode::appendText (const TQString & s) {
text += s;
}
TQString TextNode::nodeValue () const {
return text;
}
KDE_NO_EXPORT bool TextNode::expose () const {
return false;
}
//-----------------------------------------------------------------------------
KDE_NO_CDTOR_EXPORT CData::CData (NodePtr & d, const TQString & s)
: TextNode (d, s, id_node_cdata) {}
//-----------------------------------------------------------------------------
DarkNode::DarkNode (NodePtr & d, const TQString & n, short id)
: Element (d, id), name (n) {
}
NodePtr DarkNode::childFromTag (const TQString & tag) {
return new DarkNode (m_doc, tag);
}
KDE_NO_EXPORT bool DarkNode::expose () const {
return false;
}
//-----------------------------------------------------------------------------
GenericURL::GenericURL (NodePtr & d, const TQString & s, const TQString & 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 TQString & s, const TQString & name, const TQString & 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 TQString & tag, AttributeListPtr attr);
bool endTag (const TQString & tag);
bool characterData (const TQString & data);
bool cdataData (const TQString & data);
#ifdef HAVE_EXPAT
void cdataStart ();
void cdataEnd ();
private:
bool in_cdata;
TQString 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 TQString &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 TQString & 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 TQString & 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 TQString & 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 (TQString::fromUtf8 (attr [i]), TQString::fromUtf8 (attr [i+1])));
}
builder->startTag (TQString::fromUtf8 (tag), attributes);
}
static void endTag (void *data, const char * tag) {
DocumentBuilder * builder = static_cast <DocumentBuilder *> (data);
builder->endTag (TQString::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 (TQString::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, TQTextStream & in, const TQString & 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 ()) {
TQString str (firstline + TQChar ('\n'));
TQCString 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 ()) {
TQCString 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 (TQTextStream & d);
private:
TQTextStream * data;
DocumentBuilder & builder;
int position;
TQChar 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;
TQString string;
SharedPtr <TokenInfo> next;
};
typedef SharedPtr <TokenInfo> TokenInfoPtr;
struct StateInfo {
StateInfo (State s, SharedPtr <StateInfo> n) : state (s), next (n) {}
State state;
TQString data;
SharedPtr <StateInfo> next;
};
SharedPtr <StateInfo> m_state;
TokenInfoPtr next_token, token, prev_token;
// for element reading
TQString tagname;
AttributeListPtr m_attributes;
TQString attr_name, attr_value;
TQString 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, TQTextStream & in, const TQString & firstline, bool set_opener) {
DocumentBuilder builder (root, set_opener);
SimpleSAXParser parser (builder);
if (!firstline.isEmpty ()) {
TQString str (firstline + TQChar ('\n'));
TQTextStream 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 == TQChar ('#')) {
//if (next_token->token == tok_empty) { // check last item on stack &
push ();
next_token->token = tok_hash;
//}
} else if (next_char == TQChar ('/')) {
push ();
next_token->token = tok_slash;
} else if (next_char == TQChar ('!')) {
push ();
next_token->token = tok_exclamation;
} else if (next_char == TQChar ('?')) {
push ();
next_token->token = tok_question_mark;
} else if (next_char == TQChar ('<')) {
push ();
next_token->token = tok_angle_open;
} else if (next_char == TQChar ('>')) {
push ();
next_token->token = tok_angle_close;
} else if (next_char == TQChar (';')) {
push ();
next_token->token = tok_semi_colon;
} else if (next_char == TQChar ('=')) {
push ();
next_token->token = tok_equal;
} else if (next_char == TQChar ('"')) {
push ();
next_token->token = tok_double_quote;
} else if (next_char == TQChar ('\'')) {
push ();
next_token->token = tok_single_quote;
} else if (next_char == TQChar ('&')) {
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 == TQString ("amp"))
token->string = TQChar ('&');
else if (prev_token->string == TQString ("lt"))
token->string = TQChar ('<');
else if (prev_token->string == TQString ("gt"))
token->string = TQChar ('>');
else if (prev_token->string == TQString ("quot"))
token->string = TQChar ('"');
else if (prev_token->string == TQString ("apos"))
token->string = TQChar ('\'');
else if (prev_token->string == TQString ("copy"))
token->string = TQChar (169);
else
token->string = TQChar ('?');// 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 (TQChar ('x')))
token->string = TQChar (prev_token->string.toInt ());
else
token->string = TQChar (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 += TQChar ('&');
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 == TQChar ('[' ) && 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 == TQString ("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 (TQString ("--"))) {
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 == TQChar ('>') && cdata.endsWith (TQString ("]]"))) {
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 (TQString ("--"))) {
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 (TQTextStream & 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;
TQString 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 (TQChar ('\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