Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] Properties File Escaping #293

Merged
merged 27 commits into from
Jul 29, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2b8a1d2
[FEATURE] Properties File Escaping
tobiasso85 Jul 16, 2019
d4f1e62
[FEATURE] Properties File Escaping
tobiasso85 Jul 16, 2019
d1ffb97
[FEATURE] Properties File Escaping
tobiasso85 Jul 16, 2019
355bac3
[FEATURE] Properties File Escaping
tobiasso85 Jul 16, 2019
6504c6e
[FEATURE] Properties File Escaping
tobiasso85 Jul 18, 2019
4bfc662
Refactor implementation
Thodd Jul 19, 2019
e0331af
[FEATURE] Properties File Escaping
tobiasso85 Jul 20, 2019
ddb3751
[FEATURE] Properties File Escaping
tobiasso85 Jul 22, 2019
006cac5
[FEATURE] Properties File Escaping
tobiasso85 Jul 22, 2019
336e5d1
[FEATURE] Properties File Escaping
tobiasso85 Jul 22, 2019
d05d833
[FEATURE] Properties File Escaping
tobiasso85 Jul 22, 2019
7ba1022
[FEATURE] Properties File Escaping
tobiasso85 Jul 22, 2019
a6d2c6a
[FEATURE] Properties File Escaping
tobiasso85 Jul 23, 2019
bf84357
[FEATURE] Properties File Escaping
tobiasso85 Jul 23, 2019
a11dad9
[FEATURE] Properties File Escaping
tobiasso85 Jul 23, 2019
09ca138
Add formatter tests for invalid encoding
tobiasso85 Jul 23, 2019
71778d2
Task escapeNonAsciiCharacters: add tests for validation
tobiasso85 Jul 23, 2019
bb34d1b
Put propertiesFileEncoding option underneath configuration property in
tobiasso85 Jul 24, 2019
f0be15c
escapeNonAsciiCharacters task: Add meaningful error message
tobiasso85 Jul 24, 2019
a630973
improve tests
tobiasso85 Jul 24, 2019
e0028f0
Refactor escaping in LBT
Thodd Jul 25, 2019
4353a51
Fixed tests for Autosplitter and nonAsciiEscaper
Thodd Jul 25, 2019
5fd3da3
Change property name for encoding
tobiasso85 Jul 26, 2019
8d43a49
Change property name for encoding
tobiasso85 Jul 26, 2019
6b917b4
change name of escapePropertiesFile
tobiasso85 Jul 26, 2019
30ada8b
adjust test for builder and formatters
tobiasso85 Jul 29, 2019
46a7742
remove duplicate await
tobiasso85 Jul 29, 2019
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
59 changes: 59 additions & 0 deletions lib/processors/stringEscaper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const escapeUnicode = require("escape-unicode");

/**
* @see https://ascii.cl/
* @type {number}
*/
const NUMBER_OF_ASCII_CHARACTERS = 127;

// use memoization for escapeUnicode function for performance
const memoizeEscapeUnicodeMap = {};
const memoizeEscapeUnicode = function(sChar) {
if (memoizeEscapeUnicodeMap[sChar]) {
return memoizeEscapeUnicodeMap[sChar];
}
memoizeEscapeUnicodeMap[sChar] = escapeUnicode(sChar);
return memoizeEscapeUnicodeMap[sChar];
};

/**
* Escapes non ASCII characters with unicode characters.
*
* @see https://ascii.cl/
* @see https://tools.ietf.org/html/rfc5137#section-6.1
*
*
* @param {string} string input string with non ascii characters, e.g. L♥VE
* @returns {{string: (string), modified: boolean}} output string with all non ascii characters being escaped by unicode sequence, e.g. L\u2665VE
*/
const escapeNonAscii = function(string) {
let result = "";
let modified = false;
for (let i = 0; i < string.length; i++) {
const char = string[i];
// check for non ascii characters
if (string.charCodeAt(i) > NUMBER_OF_ASCII_CHARACTERS) {
result += memoizeEscapeUnicode(char);
modified = true;
} else {
result += char;
}
}
return {
modified,
string: result
};
};

module.exports = function({resources}) {
return Promise.all(resources.map((resource) => {
return resource.getString().then((resourceString) => {
const escaped = escapeNonAscii(resourceString);
// only modify the resource's string if it was changed
if (escaped.modified) {
resource.setString(escaped.string);
}
return resource;
});
}));
};
43 changes: 43 additions & 0 deletions lib/tasks/escapeNonAsciiCharacters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const stringEscaper = require("../processors/stringEscaper");

/**
* Encodings
*
* @type {{UTF_8: string, ISO_8859_1: string}}
*/
const ENCODING_TYPES = {
UTF_8: "UTF-8",
ISO_8859_1: "ISO-8859-1"
};

/**
* Task to escape non ascii characters in properties files resources.
*
* @public
* @alias module:@ui5/builder.tasks.escapePropertiesFiles
* @param {Object} parameters Parameters
* @param {module:@ui5/fs.DuplexCollection} parameters.workspace DuplexCollection to read and write files
* @param {Object} parameters.options Options
* @param {string} parameters.options.pattern Pattern to locate the files to be processed
* @param {string} [parameters.options.sourceEncoding] source file encoding: "UTF-8" or "ISO-8859-1". Defaults to "ISO-8859-1"
* @returns {Promise<undefined>} Promise resolving with <code>undefined</code> once data has been written
*/
module.exports = function({workspace, options}) {
if (options.sourceEncoding && !Object.values(ENCODING_TYPES).includes(options.sourceEncoding)) {
return Promise.reject(new Error(`Invalid encoding specified: '${options.sourceEncoding}'. Must be one of ${Object.values(ENCODING_TYPES)}`));
}
if (options.sourceEncoding && options.sourceEncoding === ENCODING_TYPES.UTF_8) {
return Promise.resolve();
}
return workspace.byGlob(options.pattern)
.then((allResources) => {
return stringEscaper({
resources: allResources
});
})
.then((processedResources) => {
return Promise.all(processedResources.map((resource) => {
return workspace.write(resource);
}));
});
};
1 change: 1 addition & 0 deletions lib/tasks/taskRepository.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const tasks = {
replaceCopyright: require("./replaceCopyright"),
replaceVersion: require("./replaceVersion"),
createDebugFiles: require("./createDebugFiles"),
escapeNonAsciiCharacters: require("./escapeNonAsciiCharacters"),
executeJsdocSdkTransformation: require("./jsdoc/executeJsdocSdkTransformation"),
generateApiIndex: require("./jsdoc/generateApiIndex"),
generateJsdoc: require("./jsdoc/generateJsdoc"),
Expand Down
11 changes: 11 additions & 0 deletions lib/types/application/ApplicationBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const tasks = { // can't require index.js due to circular dependency
generateStandaloneAppBundle: require("../../tasks/bundlers/generateStandaloneAppBundle"),
generateBundle: require("../../tasks/bundlers/generateBundle"),
generateCachebusterInfo: require("../../tasks/generateCachebusterInfo"),
escapeNonAsciiCharacters: require("../../tasks/escapeNonAsciiCharacters"),
buildThemes: require("../../tasks/buildThemes"),
createDebugFiles: require("../../tasks/createDebugFiles"),
generateVersionInfo: require("../../tasks/generateVersionInfo"),
Expand All @@ -26,6 +27,16 @@ class ApplicationBuilder extends AbstractBuilder {
"Also see: https://github.com/SAP/ui5-builder#application");
}

this.addTask("escapeNonAsciiCharacters", () => {
return tasks.escapeNonAsciiCharacters({
workspace: resourceCollections.workspace,
options: {
sourceEncoding: project.resources && project.resources.propertiesFileEncoding,
pattern: "/**/*.properties"
}
});
});

this.addTask("replaceCopyright", () => {
return tasks.replaceCopyright({
workspace: resourceCollections.workspace,
Expand Down
11 changes: 11 additions & 0 deletions lib/types/library/LibraryBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const tasks = { // can't require index.js due to circular dependency
generateLibraryPreload: require("../../tasks/bundlers/generateLibraryPreload"),
generateManifestBundle: require("../../tasks/bundlers/generateManifestBundle"),
generateStandaloneAppBundle: require("../../tasks/bundlers/generateStandaloneAppBundle"),
escapeNonAsciiCharacters: require("../../tasks/escapeNonAsciiCharacters"),
buildThemes: require("../../tasks/buildThemes"),
createDebugFiles: require("../../tasks/createDebugFiles"),
generateJsdoc: require("../../tasks/jsdoc/generateJsdoc"),
Expand All @@ -25,6 +26,16 @@ class LibraryBuilder extends AbstractBuilder {
"Also see: https://github.com/SAP/ui5-builder#library");
}

this.addTask("escapeNonAsciiCharacters", () => {
return tasks.escapeNonAsciiCharacters({
workspace: resourceCollections.workspace,
options: {
sourceEncoding: project.resources && project.resources.propertiesFileEncoding,
pattern: "/**/*.properties"
}
});
});

this.addTask("replaceCopyright", () => {
const replaceCopyright = tasks.replaceCopyright;
return replaceCopyright({
Expand Down
15 changes: 10 additions & 5 deletions package-lock.json

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

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@
"@ui5/fs": "^1.1.2",
"@ui5/logger": "^1.0.1",
"cheerio": "^0.22.0",
"escape-unicode": "^0.2.0",
"escodegen": "^1.11.1",
"escope": "^3.6.0",
"esprima": "^4.0.1",
Expand Down
3 changes: 2 additions & 1 deletion test/expected/build/application.b/dest/i18n/i18n.properties
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
title=app-i18n
title=app-i18n
fame=Stra\u00dfe
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
title=app-i18n_de
title=app-i18n_de
fame=Stra\u00dfe
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
title=app-i18n
title=app-i18n
fame=Stra�e
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
title=app-i18n_de
title=app-i18n_de
fame=Stra�e
3 changes: 2 additions & 1 deletion test/expected/build/application.b/standalone/i18n.properties
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
title=app-i18n-wrong
title=app-i18n-wrong
fame=Stra�e
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
title=app-i18n
title=app-i18n
fame=Stra�e
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
title=app-i18n_de
title=app-i18n_de
fame=Stra�e
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
title=app-i18n-wrong
title=app-i18n-wrong
fame=Stra�e
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ jQuery.sap.registerPreloadedModules({
"id1/embedded/i18n/i18n_de.properties":'title=embedded-i18n_de',
"id1/embedded/i18n_fr.properties":'title=embedded-i18n_fr-wrong',
"id1/embedded/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"id1.embedded","type":"component","applicationVersion":{"version":"1.2.2"},"embeddedBy":"../","title":"{{title}}"}}',
"id1/i18n.properties":'title=app-i18n-wrong',
"id1/i18n/i18n.properties":'title=app-i18n',
"id1/i18n/i18n_de.properties":'title=app-i18n_de',
"id1/i18n/l10n.properties":'title=app-i18n-wrong',
"id1/i18n.properties":'title=app-i18n-wrong\nfame=Straße',
"id1/i18n/i18n.properties":'title=app-i18n\nfame=Straße',
"id1/i18n/i18n_de.properties":'title=app-i18n_de\nfame=Straße',
"id1/i18n/l10n.properties":'title=app-i18n-wrong\nfame=Straße',
"id1/manifest.json":'{"_version":"1.1.0","sap.app":{"_version":"1.1.0","id":"id1","type":"application","applicationVersion":{"version":"1.2.2"},"embeds":["embedded"],"title":"{{title}}"}}',
"sap/ui/core/Core.js":function(){
}
Expand Down
3 changes: 2 additions & 1 deletion test/fixtures/application.b/webapp/i18n.properties
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
title=app-i18n-wrong
title=app-i18n-wrong
fame=Stra�e
3 changes: 2 additions & 1 deletion test/fixtures/application.b/webapp/i18n/i18n.properties
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
title=app-i18n
title=app-i18n
fame=Stra�e
3 changes: 2 additions & 1 deletion test/fixtures/application.b/webapp/i18n/i18n_de.properties
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
title=app-i18n_de
title=app-i18n_de
fame=Stra�e
3 changes: 2 additions & 1 deletion test/fixtures/application.b/webapp/i18n/l10n.properties
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
title=app-i18n-wrong
title=app-i18n-wrong
fame=Stra�e
76 changes: 76 additions & 0 deletions test/lib/processors/stringEscaper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
const test = require("ava");

const stringEscaper = require("../../../lib/processors/stringEscaper");

/**
* Executes string escaping. Returns <code>undefined</code> if nothing was escaped.
*
* @param {string} input string
* @returns {Promise<string|undefined>} escaped string if non-ascii characters present, <code>undefined</code> otherwise
*/
const escape = async function(input) {
let result = undefined;
const resource = {
getString: () => Promise.resolve(input),
setString: (actual) => {
result = actual;
}
};
await stringEscaper({resources: [resource]});
return result;
};

test("Replace symbol characters", async (t) => {
t.plan(1);

const input = `L♥VE is everywhere`;
const expected = `L\\u2665VE is everywhere`;
const output = await escape(input);
t.deepEqual(output, expected, "Correct file content should be set");
});

test("Replace chinese characters", async (t) => {
t.plan(1);

const input = `These are 人物 characters`;
const expected = "These are \\u4eba\\u7269 characters";
const output = await escape(input);
t.deepEqual(output, expected, "Correct file content should be set");
});

test("Replace umlaut characters", async (t) => {
t.plan(1);

const input = `Achso Ähem`;
const expected = "Achso \\u00c4hem";
const output = await escape(input);
t.deepEqual(output, expected, "Correct file content should be set");
});

test("Replace constructed characters", async (t) => {
t.plan(1);

const input = `Oh ẛ̣ that's ẛ̣ yes`;
const expected = "Oh \\u1e9b\\u0323 that's \\u1e9b\\u0323 yes";
const output = await escape(input);
t.deepEqual(output, expected, "Correct file content should be set");
});


test("Replace multiple times same character", async (t) => {
t.plan(1);

const input = `♥H L♥VE AND HARM♥NY ♥MG`;
const expected = "\\u2665H L\\u2665VE AND HARM\\u2665NY \\u2665MG";
const output = await escape(input);
t.deepEqual(output, expected, "Correct file content should be set");
});

test("No Replace of characters", async (t) => {
t.plan(1);

const input = `ONE LOVE`;
const expected = undefined;
const output = await escape(input);
t.deepEqual(output, expected, "Correct file content should not be modified");
});
Loading