上一节我们已经详细了解了 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 客户端。