上一节我们已经详细了解了 XML-RPC 协议的规范。从上节的内容可以看出,解析 XML-RPC,主要就是对 XML-RPC 协议中请求/响应体进行解析。而这些请求/响应体都是 XML 格式的,因此,我们需要使用 Qt 的 XML API 来完成这个工作。
我们的解析工作需要从两个方面进行:将实际数据转换成 XML 格式,以及将 XML 格式的数据转换成实际数据。
首先,我们需要解决的问题是,所谓的实际数据应当是什么格式。我们可以找到一个很适合做这种实际数据类型的 Qt 类:QVariant
。这个类的作用就是将具体数据封装为统一的类型,类似于提供一种单根机制。几乎所有 Qt 能够识别出的数据类型都可以存入QVariant
,因此,我们只需提供QVariant
和 XML 的转换即可。
QVariant -> XML
QVariant
到 XML 的转换函数名定义为toXML
。这个函数接受一个QVariant
作为参数,返回值是QDomElement
类型。下面我们给出这个函数的具体代码:
QDomElement XmlRpcValue::toXml(const QVariant &var) { QDomDocument doc; QDomElement tagValue = doc.createElement("value"); switch(var.type()) { case QVariant::Int: // <int> { QDomElement tagInt = doc.createElement("int"); tagInt.appendChild(doc.createTextNode(QString::number(var.toInt()))); tagValue.appendChild(tagInt); } break; case QVariant::Bool: // <boolean> { QDomElement tagBoolean = doc.createElement("boolean"); tagBoolean.appendChild(doc.createTextNode(var.toBool() ? "1" : "0")); tagValue.appendChild(tagBoolean); } break; case QVariant::String: // <string> { QDomElement tagString = doc.createElement("string"); tagString.appendChild(doc.createTextNode(var.toString())); tagValue.appendChild(tagString); } break; case QVariant::Double: // <double> { QDomElement tagDouble = doc.createElement("double"); tagDouble.appendChild(doc.createTextNode(QString::number(var.toDouble()))); tagValue.appendChild(tagDouble); } break; case QVariant::DateTime: // <dateTime.iso8601> { QDomElement tagDateTime = doc.createElement("datetime.iso8601"); tagDateTime.appendChild(doc.createTextNode( var.toDateTime() .toString("yyyyMMddThh:mm:ss"))); tagValue.appendChild(tagDateTime); } break; case QVariant::ByteArray: // <base64> { QDomElement tagBase64 = doc.createElement("base64"); tagBase64.appendChild(doc.createTextNode(var.toByteArray().toBase64())); tagValue.appendChild(tagBase64); } break; case QVariant::List: // <array> case QVariant::StringList: // <array> { QDomElement tagArray = doc.createElement("array"); QDomElement tagData = doc.createElement("data"); foreach(QVariant v, var.toList()) { tagData.appendChild(toXml(v)); } tagArray.appendChild(tagData); tagValue.appendChild(tagArray); } break; case QVariant::Map: // <struct> { QDomElement tagStruct = doc.createElement("struct"); QDomElement member; QDomElement name; QMap<QString, QVariant> map = var.toMap(); QMapIterator<QString, QVariant> i(map); while(i.hasNext()) { i.next(); name = doc.createElement("name"); // <name> name.appendChild(doc.createTextNode(i.key())); member = doc.createElement("member"); // <member> member.appendChild(name); member.appendChild(toXml(i.value())); tagStruct.appendChild(member); } tagValue.appendChild(tagStruct); } break; default: { LOG_WARN(QString("Failed to marshal unknown variant type: %1." "Use string instead.").arg(var.type())); QDomElement tagString = doc.createElement("string"); tagString.appendChild(doc.createTextNode(var.toString())); tagValue.appendChild(tagString); } } return tagValue; }
上面代码就是将一个QVariant
类型的数据转换成其对应的 XML 格式。代码很长,但是思路很简单。按照 XML-RPC 规范的要求,我们首先判断这个QVariant
是什么类型的,但是将其转换成对应的 XML 格式。注意,对于一些可以嵌套的类型,比如 struct,是递归调用的。而对于任何不能识别的类型,我们在 default 分支里面当做 string 进行处理。
XML ->QVariant
我们将 XML 到QVariant
的转换函数名定义为 fromXML。这个函数接受一个QDomElement
作为参数,返回值是QVariant
类型。下面我们给出这个函数的具体代码:
QVariant XmlRpcValue::fromXml(const QDomElement &xml) { if(xml.tagName().toLower() != "value") { return QVariant(); } // If no type is indicated, the type is string. if(!xml.firstChild().isElement()) { return QVariant(xml.text()); } const QDomElement typeElement = xml.firstChild().toElement(); const QString typeName = typeElement.tagName().toLower(); if(typeName == "string") { return QVariant(typeElement.text()); } else if(typeName == "i4" || typeName == "int") { return QVariant(typeElement.text().toInt()); } else if(typeName == "double") { return QVariant(typeElement.text().toDouble()); } else if (typeName == "boolean") { return QVariant(typeElement.text().toLower() == "true" || typeElement.text() == "1"); } else if(typeName == "base64") { return QVariant(QByteArray::fromBase64(typeElement.text().toLatin1())); } else if(typeName == "datetime" || typeName == "datetime.iso8601") { return QVariant(QDateTime::fromString(typeElement.text(), "yyyyMMddThh:mm:ss")); } else if(typeName == "nil") { // Non-standard extension // http://ontosys.com/xml-rpc/extensions.php return QVariant(); } else if (typeName == "array") { QList values; QDomNode valueNode = typeElement.firstChild().firstChild(); while(!valueNode.isNull()) { values << fromXml(valueNode.toElement()); valueNode = valueNode.nextSibling(); } return QVariant(values); } else if ( typeName == "struct" ) { QMap map; QDomNode memberNode = typeElement.firstChild(); while(!memberNode.isNull()) { const QString key = memberNode.toElement().elementsByTagName("name") .item(0).toElement().text(); const QVariant data = fromXml(memberNode.toElement() .elementsByTagName("value") .item(0).toElement()); map[key] = data; memberNode = memberNode.nextSibling(); } return QVariant(map); } else { LOG_WARN(QString("Cannot demarshal unknown type: %1.") .arg(typeElement.tagName().toLower())); } return QVariant(); }
这个函数正好与前面的toXML()
相反。我们需要从QDomElement
获取每一个标签名字,然后与 XML-RPC 标准进行比对。这里,我们所需注意的是,值需要符合标准提供的格式,比如时间等。而在函数中,我们增加了一个 nil 类型,这个类型是对标准的一个扩展。而对于不能识别的类型,则返回一个空的QVariant
。
虽然代码比较长,但是思路和写法都很简单,这里我们就不详细介绍。对于数据的解析是 XML-RPC 的基础之一。在后面的章节中,我们将基于这些代码,继续实现一个 XML-RPC 客户端。