Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/msRest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export {
stripRequest, stripResponse, delay,
executePromisesSequentially, generateUuid, encodeUri, ServiceCallback,
promiseToCallback, responseToBody, promiseToServiceCallback, isValidUuid,
applyMixins, isNode, stringifyXML, prepareXMLRootList, isDuration
applyMixins, isNode, isDuration
} from "./util/utils";
export { URLBuilder, URLQuery } from "./url";

Expand Down
23 changes: 7 additions & 16 deletions lib/policies/deserializationPolicy.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

import * as xml2js from "isomorphic-xml2js";
import { HttpOperationResponse } from "../httpOperationResponse";
import { OperationResponse } from "../operationResponse";
import { OperationSpec } from "../operationSpec";
import { RestError } from "../restError";
import { Mapper, MapperType } from "../serializer";
import * as utils from "../util/utils";
import { parseXML } from "../util/xml";
import { WebResource } from "../webResource";
import { BaseRequestPolicy, RequestPolicy, RequestPolicyFactory, RequestPolicyOptions } from "./requestPolicy";

Expand Down Expand Up @@ -153,21 +153,12 @@ function parse(operationResponse: HttpOperationResponse): Promise<HttpOperationR
const contentType = operationResponse.headers.get("Content-Type") || "";
const contentComponents = contentType.split(";").map(component => component.toLowerCase());
if (contentComponents.some(component => component === "application/xml" || component === "text/xml")) {
const xmlParser = new xml2js.Parser({
explicitArray: false,
explicitCharkey: false,
explicitRoot: false
});
return new Promise<HttpOperationResponse>(function (resolve, reject) {
xmlParser.parseString(text, function (err: any, result: any) {
if (err) {
reject(err);
} else {
operationResponse.parsedBody = result;
resolve(operationResponse);
}
});
}).catch(errorHandler);
return parseXML(text)
.then(body => {
operationResponse.parsedBody = body;
return operationResponse;
})
.catch(errorHandler);
} else if (contentComponents.some(component => component === "application/json" || component === "text/json") || !contentType) {
return new Promise<HttpOperationResponse>(resolve => {
operationResponse.parsedBody = JSON.parse(text);
Expand Down
5 changes: 3 additions & 2 deletions lib/serviceClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { CompositeMapper, DictionaryMapper, Mapper, MapperType, Serializer } fro
import { URLBuilder } from "./url";
import { Constants } from "./util/constants";
import * as utils from "./util/utils";
import { stringifyXML } from "./util/xml";
import { RequestPrepareOptions, WebResource, RequestOptionsBase } from "./webResource";

/**
Expand Down Expand Up @@ -326,10 +327,10 @@ export function serializeRequestBody(serviceClient: ServiceClient, httpRequest:
const isStream = typeName === MapperType.Stream;
if (operationSpec.isXML) {
if (typeName === MapperType.Sequence) {
httpRequest.body = utils.stringifyXML(utils.prepareXMLRootList(httpRequest.body, xmlElementName || xmlName || serializedName!), { rootName: xmlName || serializedName });
httpRequest.body = stringifyXML(utils.prepareXMLRootList(httpRequest.body, xmlElementName || xmlName || serializedName!), { rootName: xmlName || serializedName });
}
else if (!isStream) {
httpRequest.body = utils.stringifyXML(httpRequest.body, { rootName: xmlName || serializedName });
httpRequest.body = stringifyXML(httpRequest.body, { rootName: xmlName || serializedName });
}
} else if (!isStream) {
httpRequest.body = JSON.stringify(httpRequest.body);
Expand Down
13 changes: 0 additions & 13 deletions lib/util/utils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

import * as xml2js from "isomorphic-xml2js";
import * as uuidv4 from "uuid/v4";
import { HttpOperationResponse } from "../httpOperationResponse";
import { RestError } from "../restError";
Expand Down Expand Up @@ -240,18 +239,6 @@ export function promiseToServiceCallback<T>(promise: Promise<HttpOperationRespon
};
}

export function stringifyXML(obj: any, opts?: { rootName?: string }) {
const builder = new xml2js.Builder({
explicitArray: false,
explicitCharkey: false,
rootName: (opts || {}).rootName,
renderOpts: {
pretty: false
}
});
return builder.buildObject(obj);
}

export function prepareXMLRootList(obj: any, elementName: string) {
if (!Array.isArray(obj)) {
obj = [obj];
Expand Down
123 changes: 123 additions & 0 deletions lib/util/xml.browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

const parser = new DOMParser();
export function parseXML(str: string): Promise<any> {
try {
const dom = parser.parseFromString(str, "application/xml");
const errorMessage = getErrorMessage(dom);
if (errorMessage) {
throw new Error(errorMessage);
}

const obj = domToObject(dom.childNodes[0]);
return Promise.resolve(obj);
} catch (err) {
return Promise.reject(err);
}
}

const errorNS = parser.parseFromString("INVALID", "text/xml").getElementsByTagName("parsererror")[0].namespaceURI!;
function getErrorMessage(dom: Document): string | undefined {
const parserErrors = dom.getElementsByTagNameNS(errorNS, "parsererror");
if (parserErrors.length) {
return parserErrors.item(0).innerHTML;
} else {
return undefined;
}
}

function isElement(node: Node): node is Element {
return !!(node as Element).attributes;
}

function domToObject(node: Node): any {
// empty node
if (node.childNodes.length === 0 && !(isElement(node) && node.hasAttributes())) {
return "";
}

if (node.childNodes.length === 1 && node.childNodes[0].nodeType === Node.TEXT_NODE) {
return node.childNodes[0].nodeValue;
}

const result: { [key: string]: any } = {};
for (let i = 0; i < node.childNodes.length; i++) {
const child = node.childNodes[i];
// Ignore leading/trailing whitespace nodes
if (child.nodeType !== Node.TEXT_NODE) {
if (!result[child.nodeName]) {
result[child.nodeName] = domToObject(child);
} else if (Array.isArray(result[child.nodeName])) {
result[child.nodeName].push(domToObject(child));
} else {
result[child.nodeName] = [result[child.nodeName], domToObject(child)];
}
}
}

if (isElement(node) && node.hasAttributes()) {
result["$"] = {};

for (let i = 0; i < node.attributes.length; i++) {
const attr = node.attributes[i];
result["$"][attr.nodeName] = attr.nodeValue;
}
}

return result;
}

// tslint:disable-next-line:no-null-keyword
const doc = document.implementation.createDocument(null, null, null);
const serializer = new XMLSerializer();

export function stringifyXML(obj: any, opts?: { rootName?: string }) {
const rootName = (opts || {}).rootName || "root";
const dom = buildNode(obj, rootName)[0];
return '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + serializer.serializeToString(dom);
}

function buildAttributes(attrs: { [key: string]: { toString(): string; } }): Attr[] {
const result = [];
for (const key of Object.keys(attrs)) {
const attr = doc.createAttribute(key);
attr.value = attrs[key].toString();
result.push(attr);
}
return result;
}

function buildNode(obj: any, elementName: string): Node[] {
if (typeof obj === "string" || typeof obj === "number" || typeof obj === "boolean") {
const elem = doc.createElement(elementName);
elem.textContent = obj.toString();
return [elem];
}
else if (Array.isArray(obj)) {
const result = [];
for (const arrayElem of obj) {
for (const child of buildNode(arrayElem, elementName)) {
result.push(child);
}
}
return result;
} else if (typeof obj === "object") {
const elem = doc.createElement(elementName);
for (const key of Object.keys(obj)) {
if (key === "$") {
for (const attr of buildAttributes(obj[key])) {
elem.attributes.setNamedItem(attr);
}
} else {
for (const child of buildNode(obj[key], key)) {
elem.appendChild(child);
}
}
}
return [elem];
}
else {
throw new Error(`Illegal value passed to buildObject: ${obj}`);
}
}
33 changes: 33 additions & 0 deletions lib/util/xml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

import * as xml2js from "xml2js";

export function stringifyXML(obj: any, opts?: { rootName?: string }) {
const builder = new xml2js.Builder({
explicitArray: false,
explicitCharkey: false,
rootName: (opts || {}).rootName,
renderOpts: {
pretty: false
}
});
return builder.buildObject(obj);
}

export function parseXML(str: string): Promise<any> {
const xmlParser = new xml2js.Parser({
explicitArray: false,
explicitCharkey: false,
explicitRoot: false
});
return new Promise((resolve, reject) => {
xmlParser.parseString(str, (err?: Error, res?: any) => {
if (err) {
reject(err);
} else {
resolve(res);
}
});
});
}
12 changes: 2 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"./dist/lib/msRest.js": "./es/lib/msRest.js",
"./es/lib/policies/msRestUserAgentPolicy.js": "./es/lib/policies/msRestUserAgentPolicy.stub.js",
"./es/lib/util/base64.js": "./es/lib/util/base64.browser.js",
"./es/lib/util/xml.js": "./es/lib/util/xml.browser.js",
"./es/lib/defaultHttpClient.js": "./es/lib/defaultHttpClient.browser.js"
},
"license": "MIT",
Expand All @@ -42,9 +43,9 @@
"axios": "^0.18.0",
"form-data": "^2.3.2",
"tough-cookie": "^2.4.3",
"isomorphic-xml2js": "^0.1.3",
"tslib": "^1.9.2",
"uuid": "^3.2.1"
"uuid": "^3.2.1",
"xml2js": "^0.4.19"
},
"devDependencies": {
"@types/glob": "^5.0.35",
Expand All @@ -53,6 +54,7 @@
"@types/tough-cookie": "^2.3.3",
"@types/webpack": "^4.1.3",
"@types/webpack-dev-middleware": "^2.0.1",
"@types/xml2js": "^0.4.3",
"abortcontroller-polyfill": "^1.1.9",
"express": "^4.16.3",
"glob": "^7.1.2",
Expand Down
1 change: 1 addition & 0 deletions webpack.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const config: webpack.Configuration = {
},
plugins: [
new webpack.NormalModuleReplacementPlugin(/(\.).+util\/base64/, path.resolve(__dirname, "./lib/util/base64.browser.ts")),
new webpack.NormalModuleReplacementPlugin(/(\.).+util\/xml/, path.resolve(__dirname, "./lib/util/xml.browser.ts")),
new webpack.NormalModuleReplacementPlugin(/(\.).+defaultHttpClient/, path.resolve(__dirname, "./lib/defaultHttpClient.browser.ts"))
],
module: {
Expand Down
4 changes: 1 addition & 3 deletions webpack.testconfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@ const config: webpack.Configuration = {
entry: [...glob.sync(path.join(__dirname, 'test/shared/**/*.ts')), ...glob.sync(path.join(__dirname, 'test/browser/**/*.ts'))],
mode: 'development',
devtool: 'source-map',
devServer: {
contentBase: __dirname
},
output: {
filename: 'testBundle.js',
path: __dirname
},
plugins: [
new webpack.NormalModuleReplacementPlugin(/(\.).+util\/base64/, path.resolve(__dirname, "./lib/util/base64.browser.ts")),
new webpack.NormalModuleReplacementPlugin(/(\.).+util\/xml/, path.resolve(__dirname, "./lib/util/xml.browser.ts")),
new webpack.NormalModuleReplacementPlugin(/(\.).+defaultHttpClient/, path.resolve(__dirname, "./lib/defaultHttpClient.browser.ts"))
],
module: {
Expand Down