2
* xmlprotocol.cpp - state machine for 'jabber-like' protocols
3
* Copyright (C) 2004 Justin Karneges
5
* This library is free software; you can redistribute it and/or
6
* modify it under the terms of the GNU Lesser General Public
7
* License as published by the Free Software Foundation; either
8
* version 2.1 of the License, or (at your option) any later version.
10
* This library is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
* Lesser General Public License for more details.
15
* You should have received a copy of the GNU Lesser General Public
16
* License along with this library; if not, write to the Free Software
17
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
#include"xmlprotocol.h"
23
#include"bytestream.h"
29
// This function removes namespace information from various nodes for
30
// display purposes only (the element is pretty much useless for processing
31
// after this). We do this because QXml is a bit overzealous about outputting
32
// redundant namespaces.
33
static QDomElement stripExtraNS(const QDomElement &e)
35
// find closest parent with a namespace
36
QDomNode par = e.parentNode();
37
while(!par.isNull() && par.namespaceURI().isNull())
38
par = par.parentNode();
39
bool noShowNS = false;
40
if(!par.isNull() && par.namespaceURI() == e.namespaceURI())
43
// build qName (prefix:localName)
45
if(!e.prefix().isEmpty())
46
qName = e.prefix() + ':' + e.localName();
53
i = e.ownerDocument().createElement(qName);
55
i = e.ownerDocument().createElementNS(e.namespaceURI(), qName);
58
QDomNamedNodeMap al = e.attributes();
59
for(x = 0; x < al.count(); ++x) {
60
QDomAttr a = al.item(x).cloneNode().toAttr();
62
// don't show xml namespace
63
if(a.namespaceURI() == NS_XML)
64
i.setAttribute(QString("xml:") + a.name(), a.value());
66
i.setAttributeNodeNS(a);
70
QDomNodeList nl = e.childNodes();
71
for(x = 0; x < nl.count(); ++x) {
72
QDomNode n = nl.item(x);
74
i.appendChild(stripExtraNS(n.toElement()));
76
i.appendChild(n.cloneNode());
83
// This function converts a QDomElement into a QString, using stripExtraNS
85
static QString xmlToString(const QDomElement &e, const QString &fakeNS, const QString &fakeQName, bool clip)
87
QDomElement i = e.cloneNode().toElement();
89
// It seems QDom can only have one namespace attribute at a time (see docElement 'HACK').
90
// Fortunately we only need one kind depending on the input, so it is specified here.
91
QDomElement fake = e.ownerDocument().createElementNS(fakeNS, fakeQName);
93
fake = stripExtraNS(fake);
96
QTextStream ts(&out, IO_WriteOnly);
97
fake.firstChild().save(ts, 0);
99
// 'clip' means to remove any unwanted (and unneeded) characters, such as a trailing newline
101
int n = out.findRev('>');
109
// This function creates three QStrings, one being an <?xml .. ?> processing
110
// instruction, and the others being the opening and closing tags of an
111
// element, <foo> and </foo>. This basically allows us to get the raw XML
112
// text needed to open/close an XML stream, without resorting to generating
113
// the XML ourselves. This function uses QDom to do the generation, which
114
// ensures proper encoding and entity output.
115
static void createRootXmlTags(const QDomElement &root, QString *xmlHeader, QString *tagOpen, QString *tagClose)
117
QDomElement e = root.cloneNode(false).toElement();
119
// insert a dummy element to ensure open and closing tags are generated
120
QDomElement dummy = e.ownerDocument().createElement("dummy");
121
e.appendChild(dummy);
123
// convert to xml->text
126
QTextStream ts(&str, IO_WriteOnly);
130
// parse the tags out
131
int n = str.find('<');
132
int n2 = str.find('>', n);
134
*tagOpen = str.mid(n, n2-n);
135
n2 = str.findRev('>');
136
n = str.findRev('<');
138
*tagClose = str.mid(n, n2-n);
140
// generate a nice xml processing header
141
*xmlHeader = "<?xml version=\"1.0\"?>";
144
//----------------------------------------------------------------------------
146
//----------------------------------------------------------------------------
147
XmlProtocol::TransferItem::TransferItem()
151
XmlProtocol::TransferItem::TransferItem(const QString &_str, bool sent, bool external)
155
isExternal = external;
159
XmlProtocol::TransferItem::TransferItem(const QDomElement &_elem, bool sent, bool external)
163
isExternal = external;
167
XmlProtocol::XmlProtocol()
172
XmlProtocol::~XmlProtocol()
176
void XmlProtocol::init()
180
closeWritten = false;
183
void XmlProtocol::reset()
187
elem = QDomElement();
189
tagClose = QString();
193
transferItemList.clear();
196
void XmlProtocol::addIncomingData(const QByteArray &a)
201
QByteArray XmlProtocol::takeOutgoingData()
203
QByteArray a = outData.copy();
208
void XmlProtocol::outgoingDataWritten(int bytes)
210
for(QValueList<TrackItem>::Iterator it = trackQueue.begin(); it != trackQueue.end();) {
222
it = trackQueue.remove(it);
224
if(type == TrackItem::Raw) {
227
else if(type == TrackItem::Close) {
230
else if(type == TrackItem::Custom) {
231
itemWritten(id, size);
236
bool XmlProtocol::processStep()
240
transferItemList.clear();
242
if(state != Closing && (state == RecvOpen || stepAdvancesParser())) {
243
// if we get here, then it's because we're in some step that advances the parser
246
// note: error/close events should be handled for ALL steps, so do them here
248
case Parser::Event::DocumentOpen: {
249
transferItemList += TransferItem(pe.actualString(), false);
251
//stringRecv(pe.actualString());
254
case Parser::Event::DocumentClose: {
255
transferItemList += TransferItem(pe.actualString(), false);
257
//stringRecv(pe.actualString());
269
case Parser::Event::Element: {
270
transferItemList += TransferItem(pe.element(), false);
272
//elementRecv(pe.element());
275
case Parser::Event::Error: {
277
// If we get a parse error during the initial element exchange,
278
// flip immediately into 'open' mode so that we can report an error.
279
if(state == RecvOpen) {
283
return handleError();
287
errorCode = ErrParse;
294
if(state == RecvOpen || stepRequiresElement()) {
305
QString XmlProtocol::xmlEncoding() const
307
return xml.encoding();
310
QString XmlProtocol::elementToString(const QDomElement &e, bool clip)
313
elem = elemDoc.importNode(docElement(), true).toElement();
315
// Determine the appropriate 'fakeNS' to use
318
// first, check root namespace
319
QString pre = e.prefix();
322
if(pre == elem.prefix()) {
323
ns = elem.namespaceURI();
326
// scan the root attributes for 'xmlns' (oh joyous hacks)
327
QDomNamedNodeMap al = elem.attributes();
329
for(n = 0; n < al.count(); ++n) {
330
QDomAttr a = al.item(n).toAttr();
331
QString s = a.name();
342
if(n >= al.count()) {
343
// if we get here, then no appropriate ns was found. use root then..
344
ns = elem.namespaceURI();
350
if(!elem.prefix().isEmpty())
351
qn = elem.prefix() + ':';
352
qn += elem.localName();
355
return xmlToString(e, ns, qn, clip);
358
bool XmlProtocol::stepRequiresElement() const
360
// default returns false
364
void XmlProtocol::itemWritten(int, int)
366
// default does nothing
369
void XmlProtocol::stringSend(const QString &)
371
// default does nothing
374
void XmlProtocol::stringRecv(const QString &)
376
// default does nothing
379
void XmlProtocol::elementSend(const QDomElement &)
381
// default does nothing
384
void XmlProtocol::elementRecv(const QDomElement &)
386
// default does nothing
389
void XmlProtocol::startConnect()
395
void XmlProtocol::startAccept()
401
bool XmlProtocol::close()
409
int XmlProtocol::writeString(const QString &s, int id, bool external)
411
transferItemList += TransferItem(s, true, external);
412
return internalWriteString(s, TrackItem::Custom, id);
415
int XmlProtocol::writeElement(const QDomElement &e, int id, bool external, bool clip)
419
transferItemList += TransferItem(e, true, external);
422
QString out = elementToString(e, clip);
423
return internalWriteString(out, TrackItem::Custom, id);
426
QByteArray XmlProtocol::resetStream()
434
// grab unprocessed data before resetting
435
QByteArray spare = xml.unprocessed();
440
int XmlProtocol::internalWriteData(const QByteArray &a, TrackItem::Type t, int id)
448
ByteStream::appendArray(&outData, a);
452
int XmlProtocol::internalWriteString(const QString &s, TrackItem::Type t, int id)
454
QCString cs = s.utf8();
455
QByteArray a(cs.length());
456
memcpy(a.data(), cs.data(), a.size());
457
return internalWriteData(a, t, id);
460
void XmlProtocol::sendTagOpen()
463
elem = elemDoc.importNode(docElement(), true).toElement();
466
createRootXmlTags(elem, &xmlHeader, &tagOpen, &tagClose);
469
s += xmlHeader + '\n';
472
transferItemList += TransferItem(xmlHeader, true);
473
transferItemList += TransferItem(tagOpen, true);
475
//stringSend(xmlHeader);
476
//stringSend(tagOpen);
477
internalWriteString(s, TrackItem::Raw);
480
void XmlProtocol::sendTagClose()
482
transferItemList += TransferItem(tagClose, true);
484
//stringSend(tagClose);
485
internalWriteString(tagClose, TrackItem::Close);
488
bool XmlProtocol::baseStep(const Parser::Event &pe)
491
if(state == SendOpen) {
500
else if(state == RecvOpen) {
506
// note: event will always be DocumentOpen here
511
else if(state == Open) {
513
if(pe.type() == Parser::Event::Element)
525
return handleCloseFinished();
534
void XmlProtocol::setIncomingAsExternal()
536
for(QValueList<TransferItem>::Iterator it = transferItemList.begin(); it != transferItemList.end(); ++it) {
537
TransferItem &i = *it;
538
// look for elements received
539
if(!i.isString && !i.isSent)