From 94f7443aa1d73c070dda97de571c9a8e7ad905f3 Mon Sep 17 00:00:00 2001 From: Fatme Date: Wed, 10 Jun 2015 10:07:05 +0300 Subject: [PATCH] Polish xml merge --- lib/definitions/platform.d.ts | 1 + lib/services/android-project-service.ts | 3 +- lib/services/plugins-service.ts | 34 ++++- package.json | 1 + test/plugins-service.ts | 165 +++++++++++++++++++++++- 5 files changed, 196 insertions(+), 8 deletions(-) diff --git a/lib/definitions/platform.d.ts b/lib/definitions/platform.d.ts index 1a7d6c8a1b..120719878b 100644 --- a/lib/definitions/platform.d.ts +++ b/lib/definitions/platform.d.ts @@ -36,6 +36,7 @@ interface IPlatformData { targetedOS?: string[]; configurationFileName?: string; configurationFilePath?: string; + mergeXmlConfig?: any[]; } interface IPlatformsData { diff --git a/lib/services/android-project-service.ts b/lib/services/android-project-service.ts index b703fbae4a..1e5e6d9632 100644 --- a/lib/services/android-project-service.ts +++ b/lib/services/android-project-service.ts @@ -48,7 +48,8 @@ class AndroidProjectService implements IPlatformProjectService { ], frameworkFilesExtensions: [".jar", ".dat", ".so"], configurationFileName: "AndroidManifest.xml", - configurationFilePath: path.join(this.$projectData.platformsDir, "android", "AndroidManifest.xml") + configurationFilePath: path.join(this.$projectData.platformsDir, "android", "AndroidManifest.xml"), + mergeXmlConfig: [{ "nodename": "manifest", "attrname": "*" }] }; } diff --git a/lib/services/plugins-service.ts b/lib/services/plugins-service.ts index 7e3f45e6f6..79ac58903d 100644 --- a/lib/services/plugins-service.ts +++ b/lib/services/plugins-service.ts @@ -6,6 +6,7 @@ import semver = require("semver"); import Future = require("fibers/future"); import constants = require("./../constants"); let xmlmerge = require("xmlmerge-js"); +let DOMParser = require('xmldom').DOMParser; export class PluginsService implements IPluginsService { private static INSTALL_COMMAND_NAME = "install"; @@ -77,12 +78,22 @@ export class PluginsService implements IPluginsService { shelljs.cp("-R", pluginData.fullPath, pluginDestinationPath); let pluginPlatformsFolderPath = path.join(pluginDestinationPath, pluginData.name, "platforms", platform); - let pluginConfigurationFilePath = path.join(pluginPlatformsFolderPath, platformData.configurationFileName); + let pluginConfigurationFilePath = path.join(pluginPlatformsFolderPath, platformData.configurationFileName); + let configurationFilePath = platformData.configurationFilePath; + if(this.$fs.exists(pluginConfigurationFilePath).wait()) { + // Validate plugin configuration file let pluginConfigurationFileContent = this.$fs.readText(pluginConfigurationFilePath).wait(); - let configurationFileContent = this.$fs.readText(platformData.configurationFilePath).wait(); - let resultXml = this.mergeXml(pluginConfigurationFileContent, configurationFileContent).wait(); - this.$fs.writeFile(platformData.configurationFilePath, resultXml).wait(); + this.validateXml(pluginConfigurationFileContent, pluginConfigurationFilePath); + + // Validate configuration file + let configurationFileContent = this.$fs.readText(configurationFilePath).wait(); + this.validateXml(configurationFileContent, configurationFilePath); + + // Merge xml + let resultXml = this.mergeXml(configurationFileContent, pluginConfigurationFileContent, platformData.mergeXmlConfig || []).wait(); + this.validateXml(resultXml); + this.$fs.writeFile(configurationFilePath, resultXml).wait(); } if(this.$fs.exists(pluginPlatformsFolderPath).wait()) { @@ -209,11 +220,11 @@ export class PluginsService implements IPluginsService { }).future()(); } - private mergeXml(xml1: string, xml2: string): IFuture { + private mergeXml(xml1: string, xml2: string, config: any[]): IFuture { let future = new Future(); try { - xmlmerge.merge(xml1, xml2, "", (mergedXml: string) => { + xmlmerge.merge(xml1, xml2, config, (mergedXml: string) => { future.return(mergedXml); }); } catch(err) { @@ -222,5 +233,16 @@ export class PluginsService implements IPluginsService { return future; } + + private validateXml(xml: string, xmlFilePath?: string): void { + let doc = new DOMParser({ + locator: {}, + errorHandler: (level: any, msg: string) => { + let errorMessage = xmlFilePath ? `Invalid xml file ${xmlFilePath}.` : `Invalid xml ${xml}.`; + this.$errors.fail(errorMessage + ` Additional technical information: ${msg}.` ) + } + }); + doc.parseFromString(xml, 'text/xml'); + } } $injector.register("pluginsService", PluginsService); \ No newline at end of file diff --git a/package.json b/package.json index 08c155fdb7..3a7c64d46f 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,7 @@ "winreg": "0.0.12", "ws": "0.7.1", "xcode": "https://github.com/NativeScript/node-xcode/archive/NativeScript-0.9.tar.gz", + "xmldom": "0.1.19", "xmlhttprequest": "https://github.com/telerik/node-XMLHttpRequest/tarball/master", "xmlmerge-js": "0.2.4", "yargs": "1.2.2" diff --git a/test/plugins-service.ts b/test/plugins-service.ts index 7cb83fcbbe..781410308b 100644 --- a/test/plugins-service.ts +++ b/test/plugins-service.ts @@ -16,6 +16,8 @@ import ErrorsLib = require("../lib/common/errors"); import ProjectHelperLib = require("../lib/common/project-helper"); import PlatformsDataLib = require("../lib/platforms-data"); import ProjectDataServiceLib = require("../lib/services/project-data-service"); +import helpers = require("../lib/common/helpers"); +import os = require("os"); import PluginsServiceLib = require("../lib/services/plugins-service"); import AddPluginCommandLib = require("../lib/commands/plugin/add-plugin"); @@ -124,6 +126,30 @@ function addPluginWhenExpectingToFail(testInjector: IInjector, plugin: string, e assert.isTrue(isErrorThrown); } +function createAndroidManifestFile(projectFolder: string, fs:IFileSystem): void { + let manifest = '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + +''; + + fs.createDirectory(path.join(projectFolder, "platforms")).wait(); + fs.createDirectory(path.join(projectFolder, "platforms", "android")).wait(); + fs.writeFile(path.join(projectFolder, "platforms", "android", "AndroidManifest.xml"), manifest).wait(); +} + describe("Plugins service", () => { let testInjector: IInjector; beforeEach(() => { @@ -256,7 +282,7 @@ describe("Plugins service", () => { let packageJsonContent = fs.readJson(path.join(projectFolder, "package.json")).wait(); let actualDependencies = packageJsonContent.dependencies; let expectedDependencies = { - "plugin1": "^1.0.0" + "plugin1": "^1.0.3" }; assert.deepEqual(actualDependencies, expectedDependencies); }); @@ -419,4 +445,141 @@ describe("Plugins service", () => { commandsService.tryExecuteCommand("plugin|add", [pluginFolderPath]).wait(); }); }); + + describe("merge xmls tests", () => { + let testInjector: IInjector; + beforeEach(() => { + testInjector = createTestInjector(); + testInjector.registerCommand("plugin|add", AddPluginCommandLib.AddPluginCommand); + }); + it("fails if the plugin contains incorrect xml", () => { + let pluginName = "mySamplePlugin"; + let projectFolder = createProjectFile(testInjector); + let pluginFolderPath = path.join(projectFolder, pluginName); + let pluginJsonData = { + "name": pluginName, + "version": "0.0.1", + "nativescript": { + "platforms": { + "android": "0.10.0" + } + } + }; + let fs = testInjector.resolve("fs"); + fs.writeJson(path.join(pluginFolderPath, "package.json"), pluginJsonData).wait(); + + // Adds AndroidManifest.xml file in platforms/android folder + createAndroidManifestFile(projectFolder, fs); + + // Mock plugins service + let pluginsService = testInjector.resolve("pluginsService"); + pluginsService.getAllInstalledPlugins = () => { + return (() => { + return [{ + name: "" + }]; + }).future()(); + } + + let appDestinationDirectoryPath = path.join(projectFolder, "platforms", "android"); + + // Mock platformsData + let platformsData = testInjector.resolve("platformsData"); + platformsData.getPlatformData = (platform: string) => { + return { + appDestinationDirectoryPath: appDestinationDirectoryPath, + frameworkPackageName: "tns-android", + configurationFileName: "AndroidManifest.xml" + } + } + + // Ensure the pluginDestinationPath folder exists + let pluginPlatformsDirPath = path.join(appDestinationDirectoryPath, "app", "tns_modules", pluginName, "platforms", "android"); + fs.ensureDirectoryExists(pluginPlatformsDirPath).wait(); + + // Creates invalid plugin's AndroidManifest.xml file + let xml = '' + + '' + + ''; + let pluginConfigurationFilePath = path.join(pluginPlatformsDirPath, "AndroidManifest.xml"); + fs.writeFile(pluginConfigurationFilePath, xml).wait(); + + // Expected error message. The assertion happens in mockBeginCommand + let expectedErrorMessage = `Exception: Invalid xml file ${pluginConfigurationFilePath}. Additional technical information: element parse error: Exception: Invalid xml file ` + + `${pluginConfigurationFilePath}. Additional technical information: unclosed xml attribute` + + `\n@#[line:1,col:39].` + + `\n@#[line:1,col:39].`; + mockBeginCommand(testInjector, expectedErrorMessage); + + let commandsService = testInjector.resolve(CommandsServiceLib.CommandsService); + commandsService.tryExecuteCommand("plugin|add", [pluginFolderPath]).wait(); + }); + it("merges AndroidManifest.xml and produces correct xml", () => { + let pluginName = "mySamplePlugin"; + let projectFolder = createProjectFile(testInjector); + let pluginFolderPath = path.join(projectFolder, pluginName); + let pluginJsonData = { + "name": pluginName, + "version": "0.0.1", + "nativescript": { + "platforms": { + "android": "0.10.0" + } + } + }; + let fs = testInjector.resolve("fs"); + fs.writeJson(path.join(pluginFolderPath, "package.json"), pluginJsonData).wait(); + + // Adds AndroidManifest.xml file in platforms/android folder + createAndroidManifestFile(projectFolder, fs); + + // Mock plugins service + let pluginsService = testInjector.resolve("pluginsService"); + pluginsService.getAllInstalledPlugins = () => { + return (() => { + return [{ + name: "" + }]; + }).future()(); + } + + let appDestinationDirectoryPath = path.join(projectFolder, "platforms", "android"); + + // Mock platformsData + let platformsData = testInjector.resolve("platformsData"); + platformsData.getPlatformData = (platform: string) => { + return { + appDestinationDirectoryPath: appDestinationDirectoryPath, + frameworkPackageName: "tns-android", + configurationFileName: "AndroidManifest.xml", + configurationFilePath: path.join(appDestinationDirectoryPath, "AndroidManifest.xml"), + mergeXmlConfig: [{ "nodename": "manifest", "attrname": "*" }] + } + } + + // Ensure the pluginDestinationPath folder exists + let pluginPlatformsDirPath = path.join(appDestinationDirectoryPath, "app", "tns_modules", pluginName, "platforms", "android"); + fs.ensureDirectoryExists(pluginPlatformsDirPath).wait(); + + // Creates valid plugin's AndroidManifest.xml file + let xml = '' + + '' + + '' + + ''; + let pluginConfigurationFilePath = path.join(pluginPlatformsDirPath, "AndroidManifest.xml"); + fs.writeFile(pluginConfigurationFilePath, xml).wait(); + + pluginsService.add(pluginFolderPath).wait(); + + let expectedXml = ''; + expectedXml = helpers.stringReplaceAll(expectedXml, os.EOL, ""); + expectedXml = helpers.stringReplaceAll(expectedXml, " ", ""); + + let actualXml = fs.readText(path.join(appDestinationDirectoryPath, "AndroidManifest.xml")).wait(); + actualXml = helpers.stringReplaceAll(actualXml, "\n", ""); + actualXml = helpers.stringReplaceAll(actualXml, " ", ""); + + assert.equal(expectedXml, actualXml); + }); + }); }); \ No newline at end of file