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.
1843 lines
56 KiB
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 << "<";
|
|
} else if (txt.str [i] == TQChar ('>')) {
|
|
out << ">";
|
|
} else if (txt.str [i] == TQChar ('"')) {
|
|
out << """;
|
|
} else if (txt.str [i] == TQChar ('&')) {
|
|
out << "&";
|
|
} 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 ¶m, 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 ..> Ӓ
|
|
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
|