Optimize XML parsing

Using XPath expressions appears to be expensive so that has been
replaced with direct child node access. This seems to have improved
performance by 5%.

A side effect of this change is that XML_IGNORE_NAMESPACE option is
no longer needed as namespaces are ignored by default.
This commit is contained in:
Zaid Abdulla committed 2018-09-08 23:49:34 -07:00
1 parent 037aa9786d
commit ec215ffb82
2 files changed
+293 -175

No files matched your search

+293 -171
View File
@@ -74,18 +74,6 @@ const LIBXMLJS_SAVE_OPTIONS = {
selfCloseEmpty: !config.get("XML_NO_EMPTY")
};
const XML_IGNORE_NAMESPACE = config.get("XML_IGNORE_NAMESPACE");
if (XML_IGNORE_NAMESPACE) {
libxmljs.Element.prototype.__find = libxmljs.Element.prototype.find;
libxmljs.Element.prototype.find = function(xpath, namespaces) {
const p = xpath
.replace(/([^/:]+:)?([^/]+)(\/|$)/g, "$2$3")
.replace(/([a-zA-Z0-9_-]+)([^/]*)(\/|$)/g, '*[local-name()="$1"]$2$3');
return libxmljs.Element.prototype.__find.call(this, p, namespaces);
};
}
let warnings;
function parseBool(v) {
@@ -97,65 +85,100 @@ function parseBool(v) {
}
function event(xml) {
return xml.find("EventStruct/EventCode").map(e => e.text().trim());
return xml
.childNodes()
.filter(n => n.name() === "EventStruct")
.map(c =>
c
.childNodes()
.find(n => n.name() === "EventCode")
.text()
.trim()
);
}
function parameterInfoList(xml) {
return xml.find("ParameterInfoStruct").map(e => {
const param = e.get("Name").text();
const value = e.get("Writable").text();
let parsed = parseBool(value);
return xml
.childNodes()
.filter(e => e.name() === "ParameterInfoStruct")
.map(e => {
let param, value;
for (const c of e.childNodes()) {
switch (c.name()) {
case "Name":
param = c.text();
break;
case "Writable":
value = c.text();
break;
}
}
if (parsed === null) {
warnings.push({
message: "Invalid writable attribute",
parameter: param
});
parsed = false;
}
let parsed = parseBool(value);
return [param, parsed];
});
if (parsed === null) {
warnings.push({
message: "Invalid writable attribute",
parameter: param
});
parsed = false;
}
return [param, parsed];
});
}
function parameterValueList(xml) {
return xml.find("ParameterValueStruct").map(e => {
const valueElement = e.get("Value");
const valueType = valueElement.attr("type").value();
const param = e.get("Name").text();
const value = valueElement.text();
let parsed = value;
if (valueType === "xsd:boolean") {
parsed = parseBool(value);
if (parsed === null) {
warnings.push({
message: "Invalid value attribute",
parameter: param
});
parsed = value;
return xml
.childNodes()
.filter(e => e.name() === "ParameterValueStruct")
.map(e => {
let valueElement, param;
for (const c of e.childNodes()) {
switch (c.name()) {
case "Name":
param = c.text();
break;
case "Value":
valueElement = c;
break;
}
}
} else if (valueType === "xsd:int" || valueType === "xsd:unsignedInt") {
parsed = parseInt(value);
if (isNaN(parsed)) {
warnings.push({
message: "Invalid value attribute",
parameter: param
});
parsed = value;
}
} else if (valueType === "xsd:dateTime") {
parsed = Date.parse(value);
if (isNaN(parsed)) {
warnings.push({
message: "Invalid value attribute",
parameter: param
});
parsed = value;
}
}
return [param, parsed, valueType];
});
const valueType = valueElement.attr("type").value();
const value = valueElement.text();
let parsed = value;
if (valueType === "xsd:boolean") {
parsed = parseBool(value);
if (parsed === null) {
warnings.push({
message: "Invalid value attribute",
parameter: param
});
parsed = value;
}
} else if (valueType === "xsd:int" || valueType === "xsd:unsignedInt") {
parsed = parseInt(value);
if (isNaN(parsed)) {
warnings.push({
message: "Invalid value attribute",
parameter: param
});
parsed = value;
}
} else if (valueType === "xsd:dateTime") {
parsed = Date.parse(value);
if (isNaN(parsed)) {
warnings.push({
message: "Invalid value attribute",
parameter: param
});
parsed = value;
}
}
return [param, parsed, valueType];
});
}
function GetParameterNames(xml, methodRequest) {
@@ -167,7 +190,9 @@ function GetParameterNames(xml, methodRequest) {
function GetParameterNamesResponse(xml) {
return {
name: "GetParameterNamesResponse",
parameterList: parameterInfoList(xml.get("ParameterList"))
parameterList: parameterInfoList(
xml.childNodes().find(n => n.name() === "ParameterList")
)
};
}
@@ -183,7 +208,9 @@ function GetParameterValues(xml, methodRequest) {
function GetParameterValuesResponse(xml) {
return {
name: "GetParameterValuesResponse",
parameterList: parameterValueList(xml.get("ParameterList"))
parameterList: parameterValueList(
xml.childNodes().find(n => n.name() === "ParameterList")
)
};
}
@@ -220,7 +247,12 @@ function SetParameterValues(xml, methodRequest) {
function SetParameterValuesResponse(xml) {
return {
name: "SetParameterValuesResponse",
status: parseInt(xml.get("Status").text())
status: parseInt(
xml
.childNodes()
.find(n => n.name() === "Status")
.text()
)
};
}
@@ -231,10 +263,22 @@ function AddObject(xml, methodRequest) {
}
function AddObjectResponse(xml) {
let instanceNumber, status;
for (const c of xml.childNodes()) {
switch (c.name()) {
case "InstanceNumber":
instanceNumber = parseInt(c.text());
break;
case "Status":
status = parseInt(c.text());
break;
}
}
return {
name: "AddObjectResponse",
instanceNumber: parseInt(xml.get("InstanceNumber").text()),
status: parseInt(xml.get("Status").text())
instanceNumber: instanceNumber,
status: status
};
}
@@ -247,7 +291,12 @@ function DeleteObject(xml, methodRequest) {
function DeleteObjectResponse(xml) {
return {
name: "DeleteObjectResponse",
status: parseInt(xml.get("Status").text())
status: parseInt(
xml
.childNodes()
.find(n => n.name() === "Status")
.text()
)
};
}
@@ -287,31 +336,64 @@ function Download(xml, methodRequest) {
}
function DownloadResponse(xml) {
const res = {
name: "DownloadResponse",
status: parseInt(xml.get("Status").text())
};
if (res.status === 0) {
res.startTime = Date.parse(xml.get("StartTime").text());
res.completeTime = Date.parse(xml.get("CompleteTime").text());
let status, startTime, completeTime;
for (const c of xml.childNodes()) {
switch (c.name()) {
case "Status":
status = parseInt(c.text());
break;
case "StartTime":
startTime = Date.parse(c.text());
break;
case "CompleteTime":
completeTime = Date.parse(c.text());
break;
}
}
return res;
return {
name: "DownloadResponse",
status: status,
startTime: startTime,
completeTime: completeTime
};
}
function Inform(xml) {
let retryCount, evnt, parameterList;
const deviceId = {
Manufacturer: null,
OUI: null,
ProductClass: null,
SerialNumber: null
};
for (const c of xml.childNodes()) {
switch (c.name()) {
case "ParameterList":
parameterList = parameterValueList(c);
break;
case "DeviceId":
for (const cc of c.childNodes()) {
const n = cc.name();
if (n in deviceId) deviceId[n] = cc.text();
}
break;
case "Event":
evnt = event(c);
break;
case "RetryCount":
retryCount = parseInt(c.text());
break;
}
}
return {
name: "Inform",
parameterList: parameterValueList(xml.get("ParameterList")),
deviceId: {
Manufacturer: xml.get("DeviceId/Manufacturer").text(),
OUI: xml.get("DeviceId/OUI").text(),
ProductClass: xml.get("DeviceId/ProductClass").text(),
SerialNumber: xml.get("DeviceId/SerialNumber").text()
},
event: event(xml.get("Event")),
retryCount: parseInt(xml.get("RetryCount").text())
parameterList: parameterList,
deviceId: deviceId,
event: evnt,
retryCount: retryCount
};
}
@@ -336,12 +418,30 @@ function GetRPCMethodsResponse(xml, methodResponse) {
}
function TransferComplete(xml) {
let commandKey, _faultStruct, startTime, completeTime;
for (const c of xml.childNodes()) {
switch (c.name()) {
case "CommandKey":
commandKey = c.text();
break;
case "FaultStruct":
_faultStruct = faultStruct(c);
break;
case "StartTime":
startTime = Date.parse(c.text());
break;
case "CompleteTime":
completeTime = Date.parse(c.text());
break;
}
}
return {
name: "TransferComplete",
commandKey: xml.get("CommandKey").text(),
faultStruct: faultStruct(xml.get("FaultStruct")),
startTime: Date.parse(xml.get("StartTime").text()),
completeTime: Date.parse(xml.get("CompleteTime").text())
commandKey: commandKey,
faultStruct: _faultStruct,
startTime: startTime,
completeTime: completeTime
};
}
@@ -352,7 +452,10 @@ function TransferCompleteResponse(xml) {
function RequestDownload(xml) {
return {
name: "RequestDownload",
fileType: xml.get("FileType").text()
fileType: xml
.childNodes()
.find(n => n.name() === "FileType")
.text()
};
}
@@ -361,103 +464,125 @@ function RequestDownloadResponse(xml) {
}
function faultStruct(xml) {
const f = {
faultCode: xml.get("FaultCode").text(),
faultString: xml.get("FaultString").text()
};
for (const e of xml.find("SetParameterValuesFault")) {
if (!f.setParameterValuesFault) f.setParameterValuesFault = [];
f.setParameterValuesFault.push({
parameterName: e.get("ParameterName").text(),
faultCode: e.get("FaultCode").text(),
faultString: e.get("FaultString").text()
});
let faultCode, faultString, setParameterValuesFault, pn, fc, fs;
for (const c of xml.childNodes()) {
switch (c.name()) {
case "FaultCode":
faultCode = c.text();
break;
case "FaultString":
faultString = c.text();
break;
case "SetParameterValuesFault":
setParameterValuesFault = setParameterValuesFault || [];
for (const cc of c.childNodes()) {
switch (cc.name()) {
case "ParameterName":
pn = cc.text();
break;
case "FaultCode":
fc = cc.text();
break;
case "FaultString":
fs = cc.text();
break;
}
}
setParameterValuesFault.push({
parameterName: pn,
faultCode: fc,
faultString: fs
});
}
}
return f;
return { faultCode, faultString, setParameterValuesFault };
}
function fault(xml, cwmpVersion) {
return {
faultCode: xml.get("faultcode").text(),
faultString: xml.get("faultstring").text(),
detail: faultStruct(xml.get("detail/cwmp:Fault", NAMESPACES[cwmpVersion]))
};
function fault(xml) {
let faultCode, faultString, detail;
for (const c of xml.childNodes) {
switch (c.name()) {
case "faultcode":
faultCode = c.text();
break;
case "faultstring":
faultString = c.text();
break;
case "detail":
detail = faultStruct(c.childNodes().find(n => n.name() === "Fault"));
break;
}
}
return { faultCode, faultString, detail };
}
function request(data, cwmpVersion, warn) {
warnings = warn;
const rpc = {
cwmpVersion: cwmpVersion
id: null,
cwmpVersion: cwmpVersion,
sessionTimeout: null,
cpeRequest: null,
cpeFault: null
};
if (!data.length) return rpc;
const xml = libxmljs.parseXml(data, LIBXMLJS_PARSE_OPTIONS);
let methodElement;
let headerElement, bodyElement;
if (rpc.cwmpVersion) {
methodElement = xml.get(
"/soap-env:Envelope/soap-env:Body/cwmp:*",
NAMESPACES[rpc.cwmpVersion]
);
} else {
// cwmpVersion not passed, thus it's an inform request
methodElement = xml.get(
"/soap-env:Envelope/soap-env:Body/*",
NAMESPACES["1.0"]
);
let timeoutElement;
switch (methodElement.namespace().href()) {
case "urn:dslforum-org:cwmp-1-0":
rpc.cwmpVersion = "1.0";
for (const c of xml.childNodes()) {
switch (c.name()) {
case "Header":
headerElement = c;
break;
case "urn:dslforum-org:cwmp-1-1":
rpc.cwmpVersion = "1.1";
case "Body":
bodyElement = c;
break;
case "urn:dslforum-org:cwmp-1-2":
timeoutElement = xml.get(
"/soap-env:Envelope/soap-env:Header/cwmp:sessionTimeout",
NAMESPACES["1.2"]
);
if (timeoutElement) {
rpc.sessionTimeout = parseInt(timeoutElement.text());
rpc.cwmpVersion = "1.3";
} else {
rpc.cwmpVersion = "1.2";
}
break;
case "urn:dslforum-org:cwmp-1-3":
rpc.cwmpVersion = "1.4";
break;
default:
throw new Error("Unrecognized CWMP version");
}
if (!rpc.sessionTimeout) {
timeoutElement = xml.get(
"/soap-env:Envelope/soap-env:Header/cwmp:sessionTimeout",
NAMESPACES[rpc.cwmpVersion]
);
if (timeoutElement) rpc.sessionTimeout = parseInt(timeoutElement.text());
}
}
const idElement = xml.get(
"/soap-env:Envelope/soap-env:Header/cwmp:ID",
NAMESPACES[rpc.cwmpVersion]
);
if (idElement) rpc.id = idElement.text();
for (const c of headerElement.childNodes()) {
switch (c.name()) {
case "ID":
rpc.id = c.text();
break;
case "sessionTimeout":
rpc.sessionTimeout = parseInt(c.text());
break;
}
}
if (
methodElement &&
!(XML_IGNORE_NAMESPACE && methodElement.name() === "Fault")
) {
switch (methodElement.name()) {
for (const methodElement of bodyElement.childNodes()) {
const methodName = methodElement.name();
if (methodName === "text") continue;
if (!rpc.cwmpVersion && methodName !== "Fault") {
switch (methodElement.namespace().href()) {
case "urn:dslforum-org:cwmp-1-0":
rpc.cwmpVersion = "1.0";
break;
case "urn:dslforum-org:cwmp-1-1":
rpc.cwmpVersion = "1.1";
break;
case "urn:dslforum-org:cwmp-1-2":
if (rpc.sessionTimeout) rpc.cwmpVersion = "1.3";
else rpc.cwmpVersion = "1.2";
break;
case "urn:dslforum-org:cwmp-1-3":
rpc.cwmpVersion = "1.4";
break;
default:
throw new Error("Unrecognized CWMP version");
}
}
switch (methodName) {
case "Inform":
rpc.cpeRequest = Inform(methodElement);
break;
@@ -494,15 +619,12 @@ function request(data, cwmpVersion, warn) {
case "DownloadResponse":
rpc.cpeResponse = DownloadResponse(methodElement);
break;
case "Fault":
rpc.cpeFault = fault(methodElement, rpc.cwmpVersion);
break;
default:
throw new Error(`8000 Method not supported ${methodElement.name()}`);
throw new Error(`8000 Method not supported ${methodName}`);
}
} else {
const faultElement = xml.get(
"/soap-env:Envelope/soap-env:Body/soap-env:Fault",
NAMESPACES[rpc.cwmpVersion]
);
rpc.cpeFault = fault(faultElement, rpc.cwmpVersion);
}
return rpc;