diff --git a/common/config/rush/pnpm-lock.yaml b/common/config/rush/pnpm-lock.yaml
index d840f79eb92b..4fb4404aacfc 100644
--- a/common/config/rush/pnpm-lock.yaml
+++ b/common/config/rush/pnpm-lock.yaml
@@ -13,6 +13,7 @@ dependencies:
'@rush-temp/core-lro': 'file:projects/core-lro.tgz'
'@rush-temp/core-paging': 'file:projects/core-paging.tgz'
'@rush-temp/core-tracing': 'file:projects/core-tracing.tgz'
+ '@rush-temp/core-xml': 'file:projects/core-xml.tgz'
'@rush-temp/cosmos': 'file:projects/cosmos.tgz'
'@rush-temp/dev-tool': 'file:projects/dev-tool.tgz'
'@rush-temp/eslint-plugin-azure-sdk': 'file:projects/eslint-plugin-azure-sdk.tgz'
@@ -818,7 +819,7 @@ packages:
integrity: sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
/@types/resolve/0.0.8:
dependencies:
- '@types/node': 8.10.61
+ '@types/node': 10.17.13
dev: false
resolution:
integrity: sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==
@@ -8277,7 +8278,7 @@ packages:
dev: false
name: '@rush-temp/core-client'
resolution:
- integrity: sha512-bP3AP2HOPvoVFsGY8BltZY5uoNhd3xheIM1XcJKB37qqYcaSnB85rW69LDv7brKgJxo6IoyUE0o1legUXpb+uw==
+ integrity: sha512-CtQXB25I+V7aFkvhCoEVdodBnzO0ko4pPXTZbJS8j8JOsSQpJ31mq1zUjZ8LlVO7z8WKSk4nY4s8IGpAcFXBgw==
tarball: 'file:projects/core-client.tgz'
version: 0.0.0
'file:projects/core-http.tgz':
@@ -8523,6 +8524,60 @@ packages:
integrity: sha512-DXwjq+nIqEQLgnm1tiM6e+eyySwsWYp0Kdzli3RjPzmxAmD382fIrChGURS7SU8TbXu8YzPJSq5aCAAg+RLYIg==
tarball: 'file:projects/core-tracing.tgz'
version: 0.0.0
+ 'file:projects/core-xml.tgz':
+ dependencies:
+ '@microsoft/api-extractor': 7.7.11
+ '@rollup/plugin-commonjs': 11.0.2_rollup@1.32.1
+ '@rollup/plugin-json': 4.1.0_rollup@1.32.1
+ '@rollup/plugin-multi-entry': 3.0.1_rollup@1.32.1
+ '@rollup/plugin-node-resolve': 8.1.0_rollup@1.32.1
+ '@rollup/plugin-replace': 2.3.3_rollup@1.32.1
+ '@types/chai': 4.2.11
+ '@types/mocha': 7.0.2
+ '@types/node': 8.10.61
+ '@types/sinon': 9.0.4
+ '@types/xml2js': 0.4.5
+ '@typescript-eslint/eslint-plugin': 2.34.0_3787943315ebc5ea524d5c102dc9e452
+ '@typescript-eslint/parser': 2.34.0_eslint@6.8.0+typescript@3.9.6
+ chai: 4.2.0
+ cross-env: 7.0.2
+ downlevel-dts: 0.4.0
+ eslint: 6.8.0
+ eslint-config-prettier: 6.11.0_eslint@6.8.0
+ eslint-plugin-no-null: 1.0.2_eslint@6.8.0
+ eslint-plugin-no-only-tests: 2.4.0
+ eslint-plugin-promise: 4.2.1
+ inherits: 2.0.4
+ karma: 5.1.0
+ karma-chrome-launcher: 3.1.0
+ karma-coverage: 2.0.2
+ karma-edge-launcher: 0.4.2_karma@5.1.0
+ karma-env-preprocessor: 0.1.1
+ karma-firefox-launcher: 1.3.0
+ karma-ie-launcher: 1.0.0_karma@5.1.0
+ karma-junit-reporter: 2.0.1_karma@5.1.0
+ karma-mocha: 2.0.1
+ karma-mocha-reporter: 2.2.5_karma@5.1.0
+ karma-remap-istanbul: 0.6.0_karma@5.1.0
+ mocha: 7.2.0
+ mocha-junit-reporter: 1.23.3_mocha@7.2.0
+ prettier: 1.19.1
+ rimraf: 3.0.2
+ rollup: 1.32.1
+ rollup-plugin-sourcemaps: 0.4.2_rollup@1.32.1
+ rollup-plugin-terser: 5.3.0_rollup@1.32.1
+ rollup-plugin-visualizer: 4.0.4_rollup@1.32.1
+ sinon: 9.0.2
+ tslib: 2.0.0
+ typescript: 3.9.6
+ util: 0.12.3
+ xml2js: 0.4.23
+ dev: false
+ name: '@rush-temp/core-xml'
+ resolution:
+ integrity: sha512-ailMjJ1lg/5smpH1qPSW4wXRSCofLz95inLHbp1lGaZ1K44jmJM4m0lGVgOMfLFtzCae8uLFJGG5Us8SoITlLg==
+ tarball: 'file:projects/core-xml.tgz'
+ version: 0.0.0
'file:projects/cosmos.tgz':
dependencies:
'@microsoft/api-extractor': 7.7.11
@@ -9961,6 +10016,7 @@ specifiers:
'@rush-temp/core-lro': 'file:./projects/core-lro.tgz'
'@rush-temp/core-paging': 'file:./projects/core-paging.tgz'
'@rush-temp/core-tracing': 'file:./projects/core-tracing.tgz'
+ '@rush-temp/core-xml': 'file:./projects/core-xml.tgz'
'@rush-temp/cosmos': 'file:./projects/cosmos.tgz'
'@rush-temp/dev-tool': 'file:./projects/dev-tool.tgz'
'@rush-temp/eslint-plugin-azure-sdk': 'file:./projects/eslint-plugin-azure-sdk.tgz'
diff --git a/dataplane.code-workspace b/dataplane.code-workspace
index 6e6ac10bccc0..59865ba60078 100644
--- a/dataplane.code-workspace
+++ b/dataplane.code-workspace
@@ -48,6 +48,10 @@
"name": "core-tracing",
"path": "sdk\\core\\core-tracing"
},
+ {
+ "name": "core-xml",
+ "path": "sdk\\core\\core-xml"
+ },
{
"name": "cosmos",
"path": "sdk\\cosmosdb\\cosmos"
diff --git a/rush.json b/rush.json
index d1e280ac465f..a6ed7f6b4b0b 100644
--- a/rush.json
+++ b/rush.json
@@ -397,6 +397,11 @@
"projectFolder": "sdk/core/core-tracing",
"versionPolicyName": "core"
},
+ {
+ "packageName": "@azure/core-xml",
+ "projectFolder": "sdk/core/core-xml",
+ "versionPolicyName": "core"
+ },
{
"packageName": "@azure/cosmos",
"projectFolder": "sdk/cosmosdb/cosmos",
diff --git a/sdk/core/core-client/package.json b/sdk/core/core-client/package.json
index 3eba7cb85e44..32ce9e3a25ac 100644
--- a/sdk/core/core-client/package.json
+++ b/sdk/core/core-client/package.json
@@ -85,6 +85,7 @@
"tslib": "^2.0.0"
},
"devDependencies": {
+ "@azure/core-xml": "1.0.0-preview.1",
"@microsoft/api-extractor": "7.7.11",
"@rollup/plugin-commonjs": "11.0.2",
"@rollup/plugin-json": "^4.0.0",
diff --git a/sdk/core/core-client/test/deserializationPolicy.spec.ts b/sdk/core/core-client/test/deserializationPolicy.spec.ts
index 4992964907ac..f758ab3e616f 100644
--- a/sdk/core/core-client/test/deserializationPolicy.spec.ts
+++ b/sdk/core/core-client/test/deserializationPolicy.spec.ts
@@ -18,6 +18,7 @@ import {
SendRequest,
RawHttpHeaders
} from "@azure/core-https";
+import { parseXML } from "@azure/core-xml";
describe("deserializationPolicy", function() {
it(`should not modify a request that has no request body mapper`, async function() {
@@ -68,7 +69,7 @@ describe("deserializationPolicy", function() {
assert.isUndefined(response.parsedHeaders);
});
- it.skip(`with xml response body, application/xml content-type, but no operation spec`, async function() {
+ it(`with xml response body, application/xml content-type, but no operation spec`, async function() {
const response = await getDeserializedResponse({
headers: { "content-type": "application/xml" },
bodyAsText: `3`
@@ -76,13 +77,12 @@ describe("deserializationPolicy", function() {
assert.exists(response);
assert.isUndefined(response.readableStreamBody);
assert.isUndefined(response.blobBody);
- assert.isUndefined(response.parsedBody);
assert.isUndefined(response.parsedHeaders);
assert.strictEqual(response.bodyAsText, `3`);
assert.deepEqual(response.parsedBody, { apples: "3" });
});
- it.skip(`with xml response body with child element with attributes and value, application/xml content-type, but no operation spec`, async function() {
+ it(`with xml response body with child element with attributes and value, application/xml content-type, but no operation spec`, async function() {
const response = await getDeserializedResponse({
headers: { "content-type": "application/xml" },
bodyAsText: `3`
@@ -103,7 +103,7 @@ describe("deserializationPolicy", function() {
assert.isUndefined(response.parsedHeaders);
});
- it.skip(`with xml response body, application/xml content-type, and operation spec for only String value`, async function() {
+ it(`with xml response body, application/xml content-type, and operation spec for only String value`, async function() {
const operationSpec: OperationSpec = {
httpMethod: "GET",
serializer: createSerializer({}, true),
@@ -144,7 +144,7 @@ describe("deserializationPolicy", function() {
assert.isUndefined(response.parsedHeaders);
});
- it.skip(`with xml response body, application/xml content-type, and operation spec for only number value`, async function() {
+ it(`with xml response body, application/xml content-type, and operation spec for only number value`, async function() {
const operationSpec: OperationSpec = {
httpMethod: "GET",
serializer: createSerializer({}, true),
@@ -180,11 +180,11 @@ describe("deserializationPolicy", function() {
assert.isUndefined(response.readableStreamBody);
assert.isUndefined(response.blobBody);
assert.strictEqual(response.bodyAsText, `3`);
- assert.deepEqual(response.parsedBody, { apples: "3" });
+ assert.deepEqual(response.parsedBody, { apples: 3 });
assert.isUndefined(response.parsedHeaders);
});
- it.skip(`with xml response body, application/xml content-type, and operation spec for only headers`, async function() {
+ it(`with xml response body, application/xml content-type, and operation spec for only headers`, async function() {
const operationSpec: OperationSpec = {
httpMethod: "GET",
serializer: createSerializer({}, true),
@@ -235,7 +235,7 @@ describe("deserializationPolicy", function() {
assert.deepEqual(response.parsedBody, { apples: { tasty: "yes" } });
});
- it.skip(`with xml response body, application/atom+xml content-type, but no operation spec`, async function() {
+ it(`with xml response body, application/atom+xml content-type, but no operation spec`, async function() {
const response = await getDeserializedResponse({
headers: { "content-type": "application/xml" },
bodyAsText: `3`
@@ -249,7 +249,7 @@ describe("deserializationPolicy", function() {
assert.deepEqual(response.parsedBody, { apples: "3" });
});
- it.skip(`with xml property with attribute and value, application/atom+xml content-type, but no operation spec`, async function() {
+ it(`with xml property with attribute and value, application/atom+xml content-type, but no operation spec`, async function() {
const response = await getDeserializedResponse({
headers: { "content-type": "application/atom+xml" },
bodyAsText: `3`
@@ -270,7 +270,7 @@ describe("deserializationPolicy", function() {
});
});
- it.skip(`with xml property with attribute and value, my/weird-xml content-type, but no operation spec`, async function() {
+ it(`with xml property with attribute and value, my/weird-xml content-type, but no operation spec`, async function() {
const response = await getDeserializedResponse({
headers: { "content-type": "my/weird-xml" },
bodyAsText: `3`,
@@ -292,7 +292,7 @@ describe("deserializationPolicy", function() {
});
});
- it.skip(`with service bus response body, application/atom+xml content-type, and no operationSpec`, async function() {
+ it(`with service bus response body, application/atom+xml content-type, and no operationSpec`, async function() {
const response = await getDeserializedResponse({
headers: { "content-type": "application/atom+xml;type=entry;charset=utf-8" },
bodyAsText: `https://daschulttest1.servicebus.windows.net/testQueuePath/?api-version=2017-04&enrich=FalsetestQueuePath2018-10-09T19:56:34Z2018-10-09T19:56:35Zdaschulttest1PT1M1024falsefalseP14DfalsePT10M10true00falseActive2018-10-09T19:56:34.903Z2018-10-09T19:56:35.013Z0001-01-01T00:00:00Ztrue00000P10675199DT2H48M5.4775807SfalseAvailablefalse`
@@ -444,7 +444,10 @@ async function getDeserializedResponse(
xmlContentTypes?: string[];
} = {}
): Promise {
- const policy = deserializationPolicy({ expectedContentTypes: { xml: options.xmlContentTypes } });
+ const policy = deserializationPolicy({
+ expectedContentTypes: { xml: options.xmlContentTypes },
+ parseXML
+ });
const request: OperationRequest = createPipelineRequest({ url: "https://example.com" });
request.additionalInfo = {
operationSpec: options.operationSpec
diff --git a/sdk/core/core-client/test/serviceClient.spec.ts b/sdk/core/core-client/test/serviceClient.spec.ts
index 306deb5a917d..5eb10b91bfd7 100644
--- a/sdk/core/core-client/test/serviceClient.spec.ts
+++ b/sdk/core/core-client/test/serviceClient.spec.ts
@@ -21,6 +21,7 @@ import {
HttpsClient,
createPipelineRequest
} from "@azure/core-https";
+import { stringifyXML } from "@azure/core-xml";
import { serializeRequestBody } from "../src/serviceClient";
import { getOperationArgumentValueFromParameter } from "../src/operationHelpers";
import { deserializationPolicy } from "../src/deserializationPolicy";
@@ -279,12 +280,13 @@ describe("ServiceClient", function() {
},
responses: { 200: {} },
serializer: createSerializer()
- }
+ },
+ stringifyXML
);
assert.strictEqual(httpRequest.body, "body value");
});
- it.skip("should serialize an XML String request body", () => {
+ it("should serialize an XML String request body", () => {
const httpRequest = createPipelineRequest({ url: "https://example.com" });
serializeRequestBody(
httpRequest,
@@ -306,7 +308,8 @@ describe("ServiceClient", function() {
responses: { 200: {} },
serializer: createSerializer(),
isXML: true
- }
+ },
+ stringifyXML
);
assert.strictEqual(
httpRequest.body,
@@ -314,7 +317,7 @@ describe("ServiceClient", function() {
);
});
- it.skip("should serialize an XML ByteArray request body", () => {
+ it("should serialize an XML ByteArray request body", () => {
const httpRequest = createPipelineRequest({ url: "https://example.com" });
serializeRequestBody(
httpRequest,
@@ -336,7 +339,8 @@ describe("ServiceClient", function() {
responses: { 200: {} },
serializer: createSerializer(),
isXML: true
- }
+ },
+ stringifyXML
);
assert.strictEqual(
httpRequest.body,
@@ -344,7 +348,7 @@ describe("ServiceClient", function() {
);
});
- it.skip("should serialize an XML Stream request body", () => {
+ it("should serialize an XML Stream request body", () => {
const httpRequest = createPipelineRequest({ url: "https://example.com" });
serializeRequestBody(
httpRequest,
@@ -366,7 +370,8 @@ describe("ServiceClient", function() {
responses: { 200: {} },
serializer: createSerializer(),
isXML: true
- }
+ },
+ stringifyXML
);
assert.strictEqual(httpRequest.body, "body value");
});
diff --git a/sdk/core/core-xml/.eslintrc.json b/sdk/core/core-xml/.eslintrc.json
new file mode 100644
index 000000000000..47d9fc35be4a
--- /dev/null
+++ b/sdk/core/core-xml/.eslintrc.json
@@ -0,0 +1,4 @@
+{
+ "plugins": ["@azure/azure-sdk"],
+ "extends": ["plugin:@azure/azure-sdk/azure-sdk-base"]
+}
diff --git a/sdk/core/core-xml/CHANGELOG.md b/sdk/core/core-xml/CHANGELOG.md
new file mode 100644
index 000000000000..b6532e70d7be
--- /dev/null
+++ b/sdk/core/core-xml/CHANGELOG.md
@@ -0,0 +1,3 @@
+# Release History
+
+## 1.0.0.preview.1 (UNRELEASED)
diff --git a/sdk/core/core-xml/LICENSE b/sdk/core/core-xml/LICENSE
new file mode 100644
index 000000000000..ea8fb1516028
--- /dev/null
+++ b/sdk/core/core-xml/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2020 Microsoft
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/sdk/core/core-xml/README.md b/sdk/core/core-xml/README.md
new file mode 100644
index 000000000000..95e3e1e8e749
--- /dev/null
+++ b/sdk/core/core-xml/README.md
@@ -0,0 +1,35 @@
+# Azure Core XML client library for JavaScript (Experimental)
+
+This library is primarily intended to be used in code generated by [AutoRest](https://github.com/Azure/Autorest) and [`autorest.typescript`](https://github.com/Azure/autorest.typescript) for APIs that require parsing XML payloads.
+
+## Getting started
+
+### Requirements
+
+- [Node.js](https://nodejs.org) version > 8.x
+
+### Installation
+
+This package is primarily used in generated code and not meant to be consumed directly by end users.
+
+## Key concepts
+
+XML parsing is mostly delegated to the browser and `xml2js`.
+
+## Examples
+
+Examples can be found in the `samples` folder.
+
+## Next steps
+
+See `@azure/core-client` for actual usage.
+
+## Troubleshooting
+
+If you run into issues while using this library, please feel free to [file an issue](https://github.com/Azure/azure-sdk-for-js/issues/new).
+
+## Contributing
+
+If you'd like to contribute to this library, please read the [contributing guide](https://github.com/Azure/azure-sdk-for-js/blob/master/CONTRIBUTING.md) to learn more about how to build and test the code.
+
+
diff --git a/sdk/core/core-xml/api-extractor.json b/sdk/core/core-xml/api-extractor.json
new file mode 100644
index 000000000000..7b2706ba7fe5
--- /dev/null
+++ b/sdk/core/core-xml/api-extractor.json
@@ -0,0 +1,31 @@
+{
+ "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
+ "mainEntryPointFilePath": "types/latest/src/index.d.ts",
+ "docModel": {
+ "enabled": false
+ },
+ "apiReport": {
+ "enabled": true,
+ "reportFolder": "./review"
+ },
+ "dtsRollup": {
+ "enabled": true,
+ "untrimmedFilePath": "",
+ "publicTrimmedFilePath": "./types/latest/core-xml.d.ts"
+ },
+ "messages": {
+ "tsdocMessageReporting": {
+ "default": {
+ "logLevel": "none"
+ }
+ },
+ "extractorMessageReporting": {
+ "ae-missing-release-tag": {
+ "logLevel": "none"
+ },
+ "ae-unresolved-link": {
+ "logLevel": "none"
+ }
+ }
+ }
+}
diff --git a/sdk/core/core-xml/karma.conf.js b/sdk/core/core-xml/karma.conf.js
new file mode 100644
index 000000000000..6c429c6c876c
--- /dev/null
+++ b/sdk/core/core-xml/karma.conf.js
@@ -0,0 +1,120 @@
+// https://github.com/karma-runner/karma-chrome-launcher
+process.env.CHROME_BIN = require("puppeteer").executablePath();
+
+module.exports = function(config) {
+ config.set({
+ // base path that will be used to resolve all patterns (eg. files, exclude)
+ basePath: "./",
+
+ // frameworks to use
+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+ frameworks: ["mocha"],
+
+ plugins: [
+ "karma-mocha",
+ "karma-mocha-reporter",
+ "karma-chrome-launcher",
+ "karma-edge-launcher",
+ "karma-firefox-launcher",
+ "karma-ie-launcher",
+ "karma-env-preprocessor",
+ "karma-coverage",
+ "karma-remap-istanbul",
+ "karma-junit-reporter"
+ ],
+
+ // list of files / patterns to load in the browser
+ files: [
+ // Uncomment the cdn link below for the polyfill service to support IE11 missing features
+ // Promise,String.prototype.startsWith,String.prototype.endsWith,String.prototype.repeat,String.prototype.includes,Array.prototype.includes,Object.keys
+ // "https://cdn.polyfill.io/v2/polyfill.js?features=Symbol,Promise,String.prototype.startsWith,String.prototype.endsWith,String.prototype.repeat,String.prototype.includes,Array.prototype.includes,Object.keys|always",
+ "dist-test/index.browser.js"
+ ],
+
+ // list of files / patterns to exclude
+ exclude: [],
+
+ // preprocess matching files before serving them to the browser
+ // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
+ preprocessors: {
+ "**/*.js": ["env"],
+ // IMPORTANT: COMMENT following line if you want to debug in your browsers!!
+ // Preprocess source file to calculate code coverage, however this will make source file unreadable
+ "test-browser/index.js": ["coverage"]
+ },
+
+ // inject following environment values into browser testing with window.__env__
+ // environment values MUST be exported or set with same console running "karma start"
+ // https://www.npmjs.com/package/karma-env-preprocessor
+ // EXAMPLE: envPreprocessor: ["ACCOUNT_NAME", "ACCOUNT_SAS"],
+
+ // test results reporter to use
+ // possible values: 'dots', 'progress'
+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+ reporters: ["mocha", "coverage", "karma-remap-istanbul", "junit"],
+
+ coverageReporter: {
+ // specify a common output directory
+ dir: "coverage-browser/",
+ reporters: [{ type: "json", subdir: ".", file: "coverage.json" }]
+ },
+
+ remapIstanbulReporter: {
+ src: "coverage-browser/coverage.json",
+ reports: {
+ lcovonly: "coverage-browser/lcov.info",
+ html: "coverage-browser/html/report",
+ "text-summary": null,
+ cobertura: "./coverage-browser/cobertura-coverage.xml"
+ }
+ },
+
+ junitReporter: {
+ outputDir: "", // results will be saved as $outputDir/$browserName.xml
+ outputFile: "test-results.browser.xml", // if included, results will be saved as $outputDir/$browserName/$outputFile
+ suite: "", // suite will become the package name attribute in xml testsuite element
+ useBrowserName: false, // add browser name to report and classes names
+ nameFormatter: undefined, // function (browser, result) to customize the name attribute in xml testcase element
+ classNameFormatter: undefined, // function (browser, result) to customize the classname attribute in xml testcase element
+ properties: {} // key value pair of properties to add to the section of the report
+ },
+
+ // web server port
+ port: 9876,
+
+ // enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+ // level of logging
+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: false,
+
+ // start these browsers
+ // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+ // 'ChromeHeadless', 'Chrome', 'Firefox', 'Edge', 'IE'
+ browsers: ["ChromeHeadless"],
+
+ // Continuous Integration mode
+ // if true, Karma captures browsers, runs the tests and exits
+ singleRun: true,
+
+ // Concurrency level
+ // how many browser should be started simultaneous
+ concurrency: 1,
+
+ browserNoActivityTimeout: 600000,
+ browserDisconnectTimeout: 10000,
+ browserDisconnectTolerance: 3,
+
+ client: {
+ mocha: {
+ // change Karma's debug.html to the mocha web reporter
+ reporter: "html",
+ timeout: "600000"
+ }
+ }
+ });
+};
diff --git a/sdk/core/core-xml/package.json b/sdk/core/core-xml/package.json
new file mode 100644
index 000000000000..558bc296a094
--- /dev/null
+++ b/sdk/core/core-xml/package.json
@@ -0,0 +1,129 @@
+{
+ "name": "@azure/core-xml",
+ "version": "1.0.0-preview.1",
+ "private": true,
+ "description": "Core library for interacting with XML payloads",
+ "sdk-type": "client",
+ "main": "dist/index.js",
+ "module": "dist-esm/src/index.js",
+ "browser": {
+ "./dist-esm/src/xml.js": "./dist-esm/src/xml.browser.js"
+ },
+ "types": "types/latest/core-xml.d.ts",
+ "typesVersions": {
+ "<3.6": {
+ "types/latest/*": [
+ "types/3.1/*"
+ ]
+ }
+ },
+ "scripts": {
+ "audit": "node ../../../common/scripts/rush-audit.js && rimraf node_modules package-lock.json && npm i --package-lock-only 2>&1 && npm audit",
+ "build:browser": "npm run build:ts && cross-env ONLY_BROWSER=true rollup -c 2>&1",
+ "build:node": "npm run build:ts && cross-env ONLY_NODE=true rollup -c 2>&1",
+ "build:samples": "cd samples && tsc -p .",
+ "build:test": "npm run build:ts && npm run bundle:test",
+ "build:test:browser": "npm run build:ts && npm run bundle:test:browser",
+ "build:test:node": "npm run build:ts && npm run bundle:test:node",
+ "build:ts": "tsc -p .",
+ "build:types": "downlevel-dts types/latest/ types/3.1/",
+ "build": "npm run build:ts && rollup -c 2>&1 && api-extractor run --local && npm run build:types",
+ "bundle:test": "rollup -c rollup.test.config.js 2>&1",
+ "bundle:test:browser": "cross-env ONLY_BROWSER=true rollup -c rollup.test.config.js 2>&1",
+ "bundle:test:node": "cross-env ONLY_NODE=true rollup -c rollup.test.config.js 2>&1",
+ "check-format": "prettier --list-different \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"",
+ "clean": "rimraf dist dist-* types *.tgz *.log",
+ "execute:samples": "echo skipped",
+ "extract-api": "npm run build:ts && api-extractor run --local",
+ "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\" \"*.{js,json}\"",
+ "integration-test:browser": "echo skipped",
+ "integration-test:node": "echo skipped",
+ "integration-test": "npm run integration-test:node && npm run integration-test:browser",
+ "lint:fix": "eslint package.json tsconfig.json api-extractor.json src test --ext .ts --fix --fix-type [problem,suggestion]",
+ "lint": "eslint package.json tsconfig.json api-extractor.json src test --ext .ts -f html -o coreXml-lintReport.html || exit 0",
+ "pack": "npm pack 2>&1",
+ "prebuild": "npm run clean",
+ "test:browser": "npm run build:test:browser && npm run unit-test:browser && npm run integration-test:browser",
+ "test:node": "npm run build:test:node && npm run unit-test:node && npm run integration-test:node",
+ "test": "npm run clean && npm run build:ts && npm run bundle:test:node && npm run unit-test:node && npm run bundle:test:browser && npm run unit-test:browser && npm run integration-test:node && npm run integration-test:browser",
+ "unit-test:browser": "karma start --single-run",
+ "unit-test:node": "mocha --require source-map-support/register --reporter ../../../common/tools/mocha-multi-reporter.js dist-test/index.node.js",
+ "unit-test": "npm run unit-test:node && npm run unit-test:browser"
+ },
+ "files": [
+ "dist/",
+ "dist-esm/src/",
+ "types/latest/core-xml.d.ts",
+ "types/3.1/core-xml.d.ts",
+ "README.md",
+ "LICENSE"
+ ],
+ "repository": "github:Azure/azure-sdk-for-js",
+ "keywords": [
+ "azure",
+ "cloud",
+ "Azure"
+ ],
+ "author": "Microsoft Corporation",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/Azure/azure-sdk-for-js/issues"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ },
+ "homepage": "https://github.com/Azure/azure-sdk-for-js/blob/master/sdk/core/core-xml/",
+ "sideEffects": false,
+ "prettier": "@azure/eslint-plugin-azure-sdk/prettier.json",
+ "dependencies": {
+ "tslib": "^2.0.0",
+ "xml2js": "^0.4.19"
+ },
+ "devDependencies": {
+ "@microsoft/api-extractor": "7.7.11",
+ "@rollup/plugin-commonjs": "11.0.2",
+ "@rollup/plugin-json": "^4.0.0",
+ "@rollup/plugin-multi-entry": "^3.0.0",
+ "@rollup/plugin-node-resolve": "^8.0.0",
+ "@rollup/plugin-replace": "^2.2.0",
+ "@types/chai": "^4.1.6",
+ "@types/mocha": "^7.0.2",
+ "@types/node": "^8.0.0",
+ "@types/sinon": "^9.0.4",
+ "@types/xml2js": "^0.4.3",
+ "@typescript-eslint/eslint-plugin": "^2.0.0",
+ "@typescript-eslint/parser": "^2.0.0",
+ "@azure/eslint-plugin-azure-sdk": "^3.0.0",
+ "chai": "^4.2.0",
+ "downlevel-dts": "~0.4.0",
+ "cross-env": "^7.0.2",
+ "eslint": "^6.1.0",
+ "eslint-config-prettier": "^6.0.0",
+ "eslint-plugin-no-null": "^1.0.2",
+ "eslint-plugin-no-only-tests": "^2.3.0",
+ "eslint-plugin-promise": "^4.1.1",
+ "inherits": "^2.0.3",
+ "karma": "^5.1.0",
+ "karma-chrome-launcher": "^3.0.0",
+ "karma-coverage": "^2.0.0",
+ "karma-edge-launcher": "^0.4.2",
+ "karma-env-preprocessor": "^0.1.1",
+ "karma-firefox-launcher": "^1.1.0",
+ "karma-ie-launcher": "^1.0.0",
+ "karma-junit-reporter": "^2.0.1",
+ "karma-mocha": "^2.0.1",
+ "karma-mocha-reporter": "^2.2.5",
+ "karma-remap-istanbul": "^0.6.0",
+ "mocha": "^7.1.1",
+ "mocha-junit-reporter": "^1.18.0",
+ "prettier": "^1.16.4",
+ "rimraf": "^3.0.0",
+ "rollup": "^1.16.3",
+ "rollup-plugin-sourcemaps": "^0.4.2",
+ "rollup-plugin-terser": "^5.1.1",
+ "rollup-plugin-visualizer": "^4.0.4",
+ "sinon": "^9.0.2",
+ "typescript": "~3.9.3",
+ "util": "^0.12.1"
+ }
+}
diff --git a/sdk/core/core-xml/review/core-xml.api.md b/sdk/core/core-xml/review/core-xml.api.md
new file mode 100644
index 000000000000..f02a4f7c64e9
--- /dev/null
+++ b/sdk/core/core-xml/review/core-xml.api.md
@@ -0,0 +1,20 @@
+## API Report File for "@azure/core-xml"
+
+> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+
+// @public
+export function parseXML(str: string, opts?: {
+ includeRoot?: boolean;
+}): Promise;
+
+// @public
+export function stringifyXML(obj: any, opts?: {
+ rootName?: string;
+}): string;
+
+
+// (No @packageDocumentation comment for this package)
+
+```
diff --git a/sdk/core/core-xml/rollup.base.config.js b/sdk/core/core-xml/rollup.base.config.js
new file mode 100644
index 000000000000..7f8dc473b25b
--- /dev/null
+++ b/sdk/core/core-xml/rollup.base.config.js
@@ -0,0 +1,120 @@
+import path from "path";
+import nodeResolve from "@rollup/plugin-node-resolve";
+import multiEntry from "@rollup/plugin-multi-entry";
+import cjs from "@rollup/plugin-commonjs";
+import replace from "@rollup/plugin-replace";
+import { terser } from "rollup-plugin-terser";
+import sourcemaps from "rollup-plugin-sourcemaps";
+import viz from "rollup-plugin-visualizer";
+
+const pkg = require("./package.json");
+const depNames = Object.keys(pkg.dependencies);
+const devDepNames = Object.keys(pkg.devDependencies);
+const input = "dist-esm/src/index.js";
+const production = process.env.NODE_ENV === "production";
+
+export function nodeConfig(test = false) {
+ const externalNodeBuiltins = ["url"];
+ const baseConfig = {
+ input: input,
+ external: depNames.concat(externalNodeBuiltins),
+ output: { file: "dist/index.js", format: "cjs", sourcemap: true },
+ preserveSymlinks: false,
+ plugins: [
+ sourcemaps(),
+ replace({
+ delimiters: ["", ""],
+ values: {
+ // replace dynamic checks with if (true) since this is for node only.
+ // Allows rollup's dead code elimination to be more aggressive.
+ "if (isNode)": "if (true)"
+ }
+ }),
+ nodeResolve({ preferBuiltins: true }),
+ cjs()
+ ]
+ };
+
+ if (test) {
+ // Entry points - test files under the `test` folder(common for both browser and node), node specific test files
+ baseConfig.input = ["dist-esm/test/*.spec.js", "dist-esm/test/node/*.spec.js"];
+ baseConfig.plugins.unshift(multiEntry({ exports: false }));
+
+ // different output file
+ baseConfig.output.file = "dist-test/index.node.js";
+
+ // mark devdeps as external
+ baseConfig.external.push(...devDepNames);
+
+ // Disable tree-shaking of test code. In rollup-plugin-node-resolve@5.0.0, rollup started respecting
+ // the "sideEffects" field in package.json. Since our package.json sets "sideEffects=false", this also
+ // applies to test code, which causes all tests to be removed by tree-shaking.
+ baseConfig.treeshake = false;
+ } else if (production) {
+ baseConfig.plugins.push(terser());
+ }
+
+ return baseConfig;
+}
+
+export function browserConfig(test = false) {
+ const baseConfig = {
+ input: input,
+ output: {
+ file: "dist-browser/azure-core-client.js",
+ format: "umd",
+ name: "Azure.Core.Client",
+ sourcemap: true
+ },
+ preserveSymlinks: false,
+ plugins: [
+ sourcemaps(),
+ replace({
+ delimiters: ["", ""],
+ values: {
+ // replace dynamic checks with if (false) since this is for
+ // browser only. Rollup's dead code elimination will remove
+ // any code guarded by if (isNode) { ... }
+ "if (isNode)": "if (false)"
+ }
+ }),
+ nodeResolve({
+ mainFields: ["module", "browser"],
+ preferBuiltins: false
+ }),
+ cjs({
+ namedExports: {
+ chai: ["assert"],
+ "@opentelemetry/api": ["CanonicalCode", "SpanKind", "TraceFlags"]
+ }
+ }),
+ viz({ filename: "dist-browser/browser-stats.html", sourcemap: false })
+ ]
+ };
+
+ if (test) {
+ // Entry points - test files under the `test` folder(common for both browser and node), browser specific test files
+ baseConfig.input = ["dist-esm/test/*.spec.js", "dist-esm/test/browser/*.spec.js"];
+ baseConfig.plugins.unshift(multiEntry({ exports: false }));
+ baseConfig.output.file = "dist-test/index.browser.js";
+
+ baseConfig.onwarn = (warning) => {
+ if (
+ warning.code === "CIRCULAR_DEPENDENCY" &&
+ warning.importer.indexOf(path.normalize("node_modules/chai/lib") === 0)
+ ) {
+ // Chai contains circular references, but they are not fatal and can be ignored.
+ return;
+ }
+
+ console.error(`(!) ${warning.message}`);
+ };
+
+ // Disable tree-shaking of test code. In rollup-plugin-node-resolve@5.0.0, rollup started respecting
+ // the "sideEffects" field in package.json. Since our package.json sets "sideEffects=false", this also
+ // applies to test code, which causes all tests to be removed by tree-shaking.
+ baseConfig.treeshake = false;
+ }
+
+ return baseConfig;
+}
diff --git a/sdk/core/core-xml/rollup.config.js b/sdk/core/core-xml/rollup.config.js
new file mode 100644
index 000000000000..14652aa67ed8
--- /dev/null
+++ b/sdk/core/core-xml/rollup.config.js
@@ -0,0 +1,13 @@
+import * as base from "./rollup.base.config";
+
+const inputs = [];
+
+if (!process.env.ONLY_BROWSER) {
+ inputs.push(base.nodeConfig());
+}
+
+if (!process.env.ONLY_NODE) {
+ inputs.push(base.browserConfig());
+}
+
+export default inputs;
diff --git a/sdk/core/core-xml/rollup.test.config.js b/sdk/core/core-xml/rollup.test.config.js
new file mode 100644
index 000000000000..fc843df2b5a3
--- /dev/null
+++ b/sdk/core/core-xml/rollup.test.config.js
@@ -0,0 +1,13 @@
+import * as base from "./rollup.base.config";
+
+const inputs = [];
+
+if (!process.env.ONLY_BROWSER) {
+ inputs.push(base.nodeConfig(true));
+}
+
+if (!process.env.ONLY_NODE) {
+ inputs.push(base.browserConfig(true));
+}
+
+export default inputs;
diff --git a/sdk/core/core-xml/samples/.gitkeep b/sdk/core/core-xml/samples/.gitkeep
new file mode 100644
index 000000000000..e69de29bb2d1
diff --git a/sdk/core/core-xml/src/index.ts b/sdk/core/core-xml/src/index.ts
new file mode 100644
index 000000000000..c2671da880b5
--- /dev/null
+++ b/sdk/core/core-xml/src/index.ts
@@ -0,0 +1,4 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+export { stringifyXML, parseXML } from "./xml";
diff --git a/sdk/core/core-xml/src/xml.browser.ts b/sdk/core/core-xml/src/xml.browser.ts
new file mode 100644
index 000000000000..91059acb0874
--- /dev/null
+++ b/sdk/core/core-xml/src/xml.browser.ts
@@ -0,0 +1,165 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+///
+
+const doc = document.implementation.createDocument(null, null, null);
+
+const parser = new DOMParser();
+export function parseXML(str: string, opts?: { includeRoot?: boolean }): Promise {
+ try {
+ const dom = parser.parseFromString(str, "application/xml");
+ throwIfError(dom);
+
+ let obj;
+ if (opts && opts.includeRoot) {
+ obj = domToObject(dom);
+ } else {
+ obj = domToObject(dom.childNodes[0]);
+ }
+
+ return Promise.resolve(obj);
+ } catch (err) {
+ return Promise.reject(err);
+ }
+}
+
+let errorNS = "";
+try {
+ errorNS = parser.parseFromString("INVALID", "text/xml").getElementsByTagName("parsererror")[0]
+ .namespaceURI!;
+} catch (ignored) {
+ // Most browsers will return a document containing , but IE will throw.
+}
+
+function throwIfError(dom: Document): void {
+ if (errorNS) {
+ const parserErrors = dom.getElementsByTagNameNS(errorNS, "parsererror");
+ if (parserErrors.length) {
+ throw new Error(parserErrors.item(0)!.innerHTML);
+ }
+ }
+}
+
+function isElement(node: Node): node is Element {
+ return !!(node as Element).attributes;
+}
+
+/**
+ * Get the Element-typed version of the provided Node if the provided node is an element with
+ * attributes. If it isn't, then undefined is returned.
+ */
+function asElementWithAttributes(node: Node): Element | undefined {
+ return isElement(node) && node.hasAttributes() ? node : undefined;
+}
+
+function domToObject(node: Node): any {
+ let result: any = {};
+
+ const childNodeCount: number = node.childNodes.length;
+
+ const firstChildNode: Node = node.childNodes[0];
+ const onlyChildTextValue: string | undefined =
+ (firstChildNode &&
+ childNodeCount === 1 &&
+ firstChildNode.nodeType === Node.TEXT_NODE &&
+ firstChildNode.nodeValue) ||
+ undefined;
+
+ const elementWithAttributes: Element | undefined = asElementWithAttributes(node);
+ if (elementWithAttributes) {
+ result["$"] = {};
+
+ for (let i = 0; i < elementWithAttributes.attributes.length; i++) {
+ const attr = elementWithAttributes.attributes[i];
+ result["$"][attr.nodeName] = attr.nodeValue;
+ }
+
+ if (onlyChildTextValue) {
+ result["_"] = onlyChildTextValue;
+ }
+ } else if (childNodeCount === 0) {
+ result = "";
+ } else if (onlyChildTextValue) {
+ result = onlyChildTextValue;
+ }
+
+ if (!onlyChildTextValue) {
+ for (let i = 0; i < childNodeCount; i++) {
+ const child = node.childNodes[i];
+ // Ignore leading/trailing whitespace nodes
+ if (child.nodeType !== Node.TEXT_NODE) {
+ const childObject: any = domToObject(child);
+ if (!result[child.nodeName]) {
+ result[child.nodeName] = childObject;
+ } else if (Array.isArray(result[child.nodeName])) {
+ result[child.nodeName].push(childObject);
+ } else {
+ result[child.nodeName] = [result[child.nodeName], childObject];
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+const serializer = new XMLSerializer();
+
+export function stringifyXML(content: any, opts?: { rootName?: string }): string {
+ const rootName = (opts && opts.rootName) || "root";
+ const dom = buildNode(content, rootName)[0];
+ return (
+ '' + 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 (
+ obj === undefined ||
+ obj === null ||
+ typeof obj === "string" ||
+ typeof obj === "number" ||
+ typeof obj === "boolean"
+ ) {
+ const elem = doc.createElement(elementName);
+ elem.textContent = obj === undefined || obj === null ? "" : 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 if (key === "_") {
+ elem.textContent = obj[key].toString();
+ } else {
+ for (const child of buildNode(obj[key], key)) {
+ elem.appendChild(child);
+ }
+ }
+ }
+ return [elem];
+ } else {
+ throw new Error(`Illegal value passed to buildObject: ${obj}`);
+ }
+}
diff --git a/sdk/core/core-xml/src/xml.ts b/sdk/core/core-xml/src/xml.ts
new file mode 100644
index 000000000000..e1d877793857
--- /dev/null
+++ b/sdk/core/core-xml/src/xml.ts
@@ -0,0 +1,97 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+import * as xml2js from "xml2js";
+
+// Note: The reason we re-define all of the xml2js default settings (version 2.0) here is because the default settings object exposed
+// by the xm2js library is mutable. See https://github.com/Leonidas-from-XIV/node-xml2js/issues/536
+// By creating a new copy of the settings each time we instantiate the parser,
+// we are safeguarding against the possibility of the default settings being mutated elsewhere unintentionally.
+const xml2jsDefaultOptionsV2 = {
+ explicitCharkey: false,
+ trim: false,
+ normalize: false,
+ normalizeTags: false,
+ attrkey: "$",
+ charkey: "_",
+ explicitArray: true,
+ ignoreAttrs: false,
+ mergeAttrs: false,
+ explicitRoot: true,
+ validator: null,
+ xmlns: false,
+ explicitChildren: false,
+ preserveChildrenOrder: false,
+ childkey: "$$",
+ charsAsChildren: false,
+ includeWhiteChars: false,
+ async: false,
+ strict: true,
+ attrNameProcessors: null,
+ attrValueProcessors: null,
+ tagNameProcessors: null,
+ valueProcessors: null,
+ rootName: "root",
+ xmldec: {
+ version: "1.0",
+ encoding: "UTF-8",
+ standalone: true
+ },
+ doctype: null,
+ renderOpts: {
+ pretty: true,
+ indent: " ",
+ newline: "\n"
+ },
+ headless: false,
+ chunkSize: 10000,
+ emptyTag: "",
+ cdata: false
+};
+
+// The xml2js settings for general XML parsing operations.
+const xml2jsParserSettings: any = Object.assign({}, xml2jsDefaultOptionsV2);
+xml2jsParserSettings.explicitArray = false;
+
+// The xml2js settings for general XML building operations.
+const xml2jsBuilderSettings: any = Object.assign({}, xml2jsDefaultOptionsV2);
+xml2jsBuilderSettings.explicitArray = false;
+xml2jsBuilderSettings.renderOpts = {
+ pretty: false
+};
+
+/**
+ * Converts given JSON object to XML string
+ * @param obj JSON object to be converted into XML string
+ * @param opts Options that govern the parsing of given JSON object
+ * `rootName` indicates the name of the root element in the resulting XML
+ */
+export function stringifyXML(obj: any, opts?: { rootName?: string }): string {
+ xml2jsBuilderSettings.rootName = (opts || {}).rootName;
+ const builder = new xml2js.Builder(xml2jsBuilderSettings);
+ return builder.buildObject(obj);
+}
+
+/**
+ * Converts given XML string into JSON
+ * @param str String containing the XML content to be parsed into JSON
+ * @param opts Options that govern the parsing of given xml string
+ * `includeRoot` indicates whether the root element is to be included or not in the output
+ */
+export function parseXML(str: string, opts?: { includeRoot?: boolean }): Promise {
+ xml2jsParserSettings.explicitRoot = !!(opts && opts.includeRoot);
+ const xmlParser = new xml2js.Parser(xml2jsParserSettings);
+ return new Promise((resolve, reject) => {
+ if (!str) {
+ reject(new Error("Document is empty"));
+ } else {
+ xmlParser.parseString(str, (err?: Error, res?: any) => {
+ if (err) {
+ reject(err);
+ } else {
+ resolve(res);
+ }
+ });
+ }
+ });
+}
diff --git a/sdk/core/core-xml/test/xml.spec.ts b/sdk/core/core-xml/test/xml.spec.ts
new file mode 100644
index 000000000000..d125f308c634
--- /dev/null
+++ b/sdk/core/core-xml/test/xml.spec.ts
@@ -0,0 +1,417 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT license.
+
+import { parseXML, stringifyXML } from "../src";
+import { assert } from "chai";
+
+describe("XML serializer", function() {
+ describe("parseXML(string)", function() {
+ it("with undefined", async function() {
+ try {
+ // @ts-expect-error
+ await parseXML(undefined);
+ assert.fail("Expected error");
+ } catch (error) {
+ assert.ok(
+ error.message.indexOf("Document is empty") !== -1 || // Chrome
+ (error.message.startsWith("XML Parsing Error: syntax error") &&
+ error.message.indexOf("undefined") !== -1), // Firefox
+ `error.message ("${error.message}") should have contained "Document is empty" or "undefined"`
+ );
+ }
+ });
+
+ it("with null", async function() {
+ try {
+ // @ts-expect-error
+ await parseXML(null);
+ assert.fail("Expected error");
+ } catch (error) {
+ assert.ok(
+ error.message.indexOf("Document is empty") !== -1 || // Chrome
+ (error.message.startsWith("XML Parsing Error: syntax error") &&
+ error.message.indexOf("null") !== -1), // Firefox
+ `error.message ("${error.message}") should have contained "Document is empty" or "null"`
+ );
+ }
+ });
+
+ it("with empty", async function() {
+ try {
+ await parseXML("");
+ assert.fail("Expected error");
+ } catch (error) {
+ // ignored
+ }
+ });
+
+ it("with text", async function() {
+ try {
+ await parseXML("");
+ assert.fail("Hello World!");
+ } catch (error) {
+ // ignored
+ }
+ });
+
+ it("with empty element", async function() {
+ const xml = await parseXML("");
+ assert.deepStrictEqual(xml, ``);
+ });
+
+ it("with empty element with attribute", async function() {
+ const xml = await parseXML(``);
+ assert.deepStrictEqual(xml, {
+ $: {
+ healthy: "true"
+ }
+ });
+ });
+
+ it("with element", async function() {
+ const xml = await parseXML("");
+ assert.deepStrictEqual(xml, ``);
+ });
+
+ it("with element with value", async function() {
+ const xml = await parseXML("hurray");
+ assert.deepStrictEqual(xml, `hurray`);
+ });
+
+ it("with element with attribute", async function() {
+ const xml = await parseXML(``);
+ assert.deepStrictEqual(xml, {
+ $: {
+ healthy: "true"
+ }
+ });
+ });
+
+ it("with element with attribute and value", async function() {
+ const xml = await parseXML(`yum`);
+ assert.deepStrictEqual(xml, {
+ $: {
+ healthy: "true"
+ },
+ _: "yum"
+ });
+ });
+
+ it("with element with child empty element", async function() {
+ const xml = await parseXML(``);
+ assert.deepStrictEqual(xml, {
+ apples: ``
+ });
+ });
+
+ it("with element with child empty element with attribute", async function() {
+ const xml = await parseXML(``);
+ assert.deepStrictEqual(xml, {
+ apples: {
+ $: {
+ tasty: "true"
+ }
+ }
+ });
+ });
+
+ it("with element with child element with value", async function() {
+ const xml = await parseXML(`yum`);
+ assert.deepStrictEqual(xml, {
+ apples: "yum"
+ });
+ });
+
+ it("with element with child element with attribute and value", async function() {
+ const xml = await parseXML(`yum`);
+ assert.deepStrictEqual(xml, {
+ apples: {
+ $: {
+ tasty: "true"
+ },
+ _: "yum"
+ }
+ });
+ });
+ });
+
+ describe("parseXML(string) with root", function() {
+ it("with empty element", async function() {
+ const json: any = await parseXML("", { includeRoot: true });
+ assert.deepStrictEqual(json, { fruit: `` });
+ });
+
+ it("with empty element with attribute", async function() {
+ const json: any = await parseXML(``, {
+ includeRoot: true
+ });
+ assert.deepStrictEqual(json, {
+ fruit: {
+ $: {
+ healthy: "true"
+ }
+ }
+ });
+ });
+
+ it("with element", async function() {
+ const json: any = await parseXML("", { includeRoot: true });
+ assert.deepStrictEqual(json, { fruit: `` });
+ });
+
+ it("with element with value", async function() {
+ const json: any = await parseXML("hurray", { includeRoot: true });
+ assert.deepStrictEqual(json, { fruit: `hurray` });
+ });
+
+ it("with unwanted BOM characters", async function() {
+ const json: any = await parseXML("\uFEFFapple", {
+ includeRoot: true
+ });
+ assert.deepStrictEqual(json, { fruit: "apple" });
+ });
+
+ it("with element with attribute", async function() {
+ const json: any = await parseXML(``, {
+ includeRoot: true
+ });
+ assert.deepStrictEqual(json, {
+ fruit: {
+ $: {
+ healthy: "true"
+ }
+ }
+ });
+ });
+
+ it("with element with attribute and value", async function() {
+ const json: any = await parseXML(`yum`, {
+ includeRoot: true
+ });
+ assert.deepStrictEqual(json, {
+ fruit: {
+ $: {
+ healthy: "true"
+ },
+ _: "yum"
+ }
+ });
+ });
+
+ it("with element with child empty element", async function() {
+ const json: any = await parseXML(``, {
+ includeRoot: true
+ });
+ assert.deepStrictEqual(json, {
+ fruit: {
+ apples: ``
+ }
+ });
+ });
+
+ it("with element with child empty element with attribute", async function() {
+ const json: any = await parseXML(``, { includeRoot: true });
+ assert.deepStrictEqual(json, {
+ apples: {
+ $: {
+ tasty: "true"
+ }
+ }
+ });
+ });
+
+ it("with element with child element with value", async function() {
+ const json: any = await parseXML(`yum`, { includeRoot: true });
+ assert.deepStrictEqual(json, {
+ apples: "yum"
+ });
+ });
+
+ it("with element with child element with attribute and value", async function() {
+ const json: any = await parseXML(`yum`, {
+ includeRoot: true
+ });
+ assert.deepStrictEqual(json, {
+ apples: {
+ $: {
+ tasty: "true"
+ },
+ _: "yum"
+ }
+ });
+ });
+
+ it("should handle errors gracefully", async function() {
+ try {
+ await parseXML("INVALID", { includeRoot: true });
+ throw new Error("did not throw");
+ } catch (err) {
+ if (err.message === "did not throw") {
+ throw err;
+ }
+ }
+ });
+ });
+
+ describe("stringifyXML(JSON) with root", function() {
+ it("with empty element with attribute", async function() {
+ const xml = await stringifyXML(
+ {
+ fruit: {
+ $: {
+ healthy: "true"
+ }
+ }
+ },
+ { rootName: "fruits" }
+ );
+ assert.deepStrictEqual(
+ xml,
+ ``
+ );
+ });
+
+ it("with element", async function() {
+ const xml = await stringifyXML({ fruit: `` }, { rootName: "fruits" });
+ assert.deepStrictEqual(
+ xml,
+ ``
+ );
+ });
+
+ it("with element with value", async function() {
+ const xml = await stringifyXML({ fruit: `hurray` }, { rootName: "fruits" });
+ assert.deepStrictEqual(
+ xml,
+ `hurray`
+ );
+ });
+
+ it("with element with attribute", async function() {
+ const xml = await stringifyXML(
+ {
+ fruit: {
+ $: {
+ healthy: "true"
+ }
+ }
+ },
+ { rootName: "fruits" }
+ );
+ assert.deepStrictEqual(
+ xml,
+ ``
+ );
+ });
+
+ it("with element with attribute and value", async function() {
+ const xml = await stringifyXML(
+ {
+ fruit: {
+ $: {
+ healthy: "true"
+ },
+ _: "yum"
+ }
+ },
+ { rootName: "fruits" }
+ );
+ assert.deepStrictEqual(
+ xml,
+ `yum`
+ );
+ });
+
+ it("with element with attribute and value", async function() {
+ const xml = await stringifyXML(
+ {
+ fruit: {
+ $: {
+ healthy: "true"
+ },
+ _: "yum"
+ }
+ },
+ { rootName: "fruits" }
+ );
+ assert.deepStrictEqual(
+ xml,
+ `yum`
+ );
+ });
+
+ it("with element with child undefined element", async function() {
+ const xml = await stringifyXML(
+ {
+ fruit: {
+ apples: undefined
+ }
+ },
+ { rootName: "fruits" }
+ );
+ assert.deepStrictEqual(
+ xml,
+ ``
+ );
+ });
+
+ it("with element with child empty element with attribute", async function() {
+ const xml = await stringifyXML(
+ {
+ apples: {
+ $: {
+ tasty: "true"
+ }
+ }
+ },
+ { rootName: "fruits" }
+ );
+ assert.deepStrictEqual(
+ xml,
+ ``
+ );
+ });
+
+ it("with element with child element with value", async function() {
+ const xml = await stringifyXML(
+ {
+ apples: "yum"
+ },
+ { rootName: "fruits" }
+ );
+ assert.deepStrictEqual(
+ xml,
+ `yum`
+ );
+ });
+
+ it("with element with child element with attribute and value", async function() {
+ const xml = await stringifyXML(
+ {
+ apples: {
+ $: {
+ tasty: "true"
+ },
+ _: "yum"
+ }
+ },
+ { rootName: "fruits" }
+ );
+ assert.deepStrictEqual(
+ xml,
+ `yum`
+ );
+ });
+ });
+
+ it("should handle errors gracefully", async function() {
+ try {
+ await parseXML("INVALID");
+ throw new Error("did not throw");
+ } catch (err) {
+ if (err.message === "did not throw") {
+ throw err;
+ }
+ }
+ });
+});
diff --git a/sdk/core/core-xml/tsconfig.json b/sdk/core/core-xml/tsconfig.json
new file mode 100644
index 000000000000..4a53cc511418
--- /dev/null
+++ b/sdk/core/core-xml/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../../../tsconfig.package",
+ "compilerOptions": {
+ "outDir": "./dist-esm",
+ "declarationDir": "./types/latest"
+ },
+ "exclude": ["node_modules", "types", "temp", "browser", "dist", "dist-esm", "./samples/**/*.ts"]
+}