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/TR-5014/Text converter #163

Merged
merged 14 commits into from
Dec 27, 2022
Merged
Show file tree
Hide file tree
Changes from 10 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
29 changes: 29 additions & 0 deletions src/util/converter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; under version 2
* of the License (non-upgradable).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2022 (original work) Open Assessment Technologies SA ;
*/

import module from 'module';
import converterFactory from 'util/converter/factory';
import ambiguousSymbolsConverter from 'util/converter/ambiguousSymbols';

/**
* Exposes a default text converter, including all builtin processors.
* It can be configured from the platform through the client registry.
*
* @export 'util/converter'
*/
export default converterFactory([ambiguousSymbolsConverter], module.config());
68 changes: 68 additions & 0 deletions src/util/converter/ambiguousSymbols.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; under version 2
* of the License (non-upgradable).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2022 (original work) Open Assessment Technologies SA ;
*/

/**
* Default mapping from ambiguous characters to ASCII.
* @type {object}
*/
const defaultMapping = {
'0': '0',
'1': '1',
'2': '2',
'3': '3',
'4': '4',
'5': '5',
'6': '6',
'7': '7',
'8': '8',
'9': '9'
};

/**
* Converter processor to register with the converter produce by 'util/converter/factory'.
*
* Processor that converts ambiguous unicode symbols into plain ASCII equivalent.
*
* @export 'util/converter/ambiguousSymbols'
*/
export default {
name: 'ambiguousSymbols',

/**
* Converts ambiguous unicode symbols into plain ASCII equivalent.
* @param {string} text - The text to convert.
* @param {object} [config] - An optional config object that may contain processor specific configuration.
* @param {object} [config.ambiguousSymbols] - A specific mapping of ambiguous symbols to plain ASCII chars.
* If omitted the default list will be taken.
* @returns {string} - Returns the converted text.
*/
convert(text, { ambiguousSymbols } = {}) {
let mapping = ambiguousSymbols;

if ('object' !== typeof mapping) {
rgomeztao marked this conversation as resolved.
Show resolved Hide resolved
mapping = defaultMapping;
}

let result = '';
for (const char of text) {
result += mapping[char] || char;
}

return result;
}
};
145 changes: 145 additions & 0 deletions src/util/converter/factory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/**
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; under version 2
* of the License (non-upgradable).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2022 (original work) Open Assessment Technologies SA ;
*/

/**
* Defines a converter processor runtime.
* @callback converterProcessorRuntime
* @param {string} text - The text to convert.
* @param {object} [config] - An optional config object that may contain processor specific configuration.
* @returns {string} - Returns the converted text.
*/

/**
* Defines a converter processor.
* @typedef {object} converterProcessor
* @property {string} name - The name of the converter processor. It needs to be unique within the collection.
* @property {converterProcessorRuntime} convert - The processor runtime that will actually convert the text.
*/

/**
* Creates a text converter.
* @param {converterProcessor[]} builtinProcessors - A list of built-in converter processors.
* @param {object} [builtinConfig] - An optional default config object that may contain processor specific configuration.
* It will be forwarded to each call to the converter.
* @returns {converter} - Returns the text converter, ready for use.
* @export 'util/converter/factory'
*/
export default function converterFactory(builtinProcessors = [], builtinConfig = {}) {
let processors = [];

/**
* @typedef {object} converter
*/
const converter = {
/**
* Converts a text with respect to the registered converter processors.
* @param {string} text - The text to convert.
* @param {object} [config] - An optional config object that may contain processor specific configuration.
* It will be merged with the possible builtin config.
* @returns {string} - Returns the converted text.
*/
convert(text, config = {}) {
const localConfig = Object.assign({}, builtinConfig, config);
shaveko marked this conversation as resolved.
Show resolved Hide resolved

for (const processor of processors) {
text = processor.convert.call(converter, text, localConfig);
}

return text;
},

/**
* Registers a converter processor.
* A processor is an object that contains both a `name`, which must be unique,
* and a `convert()` function for converting the given text.
* @param {converterProcessor} processor - The converter processor to register.
* @returns {converter} - Chains the instance.
* @throws {TypeError} - If the processor does not comply with the requirements.
*/
register(processor) {
validateProcessor(processor);

processors.push(processor);

return this;
},

/**
* Unregisters a converter processor.
* @param {string|converterProcessor} name - The name of the processor to remove.
* @returns {converter} - Chains the instance.
*/
unregister(name) {
if ('object' === typeof name) {
name = name.name;
}

processors = processors.filter(processor => processor.name !== name);

return this;
},

/**
* Removes all converter processors.
* @returns {converter} - Chains the instance.
*/
clear() {
processors = [];

return this;
},

/**
* Tells whether a converter processor is registered or not.
* @param {string} name - The name of the processor to check.
* @returns {boolean} - Returns `true` if the converter processor is registered ; returns `false` otherwise.
*/
isRegistered(name) {
return processors.findIndex(processor => processor.name === name) > -1;
}
};

/**
* Checks a converter processor is valid, and throws an error if not.
* @param {converterProcessor} processor - The converter processor to validate.
* @throws {TypeError} - If the processor does not comply with the requirements.
*/
function validateProcessor(processor) {
if ('object' !== typeof processor) {
throw new TypeError('The given processor must be an object!');
}

if ('string' !== typeof processor.name || !processor.name) {
throw new TypeError('A processor needs a name to identify it!');
}

if ('function' !== typeof processor.convert) {
throw new TypeError('A processor needs a runtime function for converting the text!');
}

if (converter.isRegistered(processor.name)) {
throw new TypeError(`The processor "${processor.name}" is already registered!`);
}
}

for (const processor of builtinProcessors) {
rgomeztao marked this conversation as resolved.
Show resolved Hide resolved
converter.register(processor);
}

return converter;
}
21 changes: 21 additions & 0 deletions test/util/converter/ambiguousSymbols/test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Test - Converter for ambiguous symbols</title>
<script type="text/javascript" src="/environment/require.js"></script>
<script type="text/javascript">
require(['/environment/config.js'], function() {
require(['qunitEnv'], function() {
require(['test/util/converter/ambiguousSymbols/test'], function() {
QUnit.start();
});
});
});
</script>
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
</body>
</html>
70 changes: 70 additions & 0 deletions test/util/converter/ambiguousSymbols/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*/**
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; under version 2
* of the License (non-upgradable).
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* Copyright (c) 2022 (original work) Open Assessment Technologies SA ;
*/
define(['util/converter/ambiguousSymbols'], function (converter) {
'use strict';

QUnit.module('converter: ambiguousSymbols');

QUnit.test('module', function (assert) {
assert.equal(typeof converter, 'object', 'The module exposes an object');
});

QUnit.test('API', function (assert) {
assert.equal(converter.name, 'ambiguousSymbols', 'The converter has the expected name');
assert.equal(typeof converter.convert, 'function', 'The converter exposes a "convert" function');
});

QUnit.test('allows to replace the list of ambiguous signs', function (assert) {
const ambiguousSymbols = {
i: '1',
e: '3',
a: '4'
};
const text = '0this is a test0';
const expected = '0th1s 1s 4 t3st0';

assert.equal(
converter.convert(text, { ambiguousSymbols }),
expected,
'The text is converted using the provided list only'
);
});

QUnit.cases
.init([
{ title: '42', expected: '42' },
{ title: '42', expected: '42' },
{ title: '42', expected: '42' },
{ title: '42', expected: '42' },
{ title: 'this is a test', expected: 'this is a test' },
{ title: '42 is the answer', expected: '42 is the answer' },
{ title: '42 is the answer', expected: '42 is the answer' },
{ title: 'the answer is 42', expected: 'the answer is 42' },
{
title: 'japanese 1234567890 ascii 1234567890',
expected: 'japanese 1234567890 ascii 1234567890'
}
])
.test('converts ', function (data, assert) {
assert.strictEqual(
converter.convert(data.title),
data.expected,
`The converter converts "${data.title}" into ${data.expected}`
);
});
});
21 changes: 21 additions & 0 deletions test/util/converter/factory/test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Test - Converter Factory</title>
<script type="text/javascript" src="/environment/require.js"></script>
<script type="text/javascript">
require(['/environment/config.js'], function() {
require(['qunitEnv'], function() {
require(['test/util/converter/factory/test'], function() {
QUnit.start();
});
});
});
</script>
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
</body>
</html>
Loading