1 /*
  2  * StringMapper.js - ilib string mapper class definition
  3  *
  4  * Copyright © 2014-2015, 2018, JEDLSoft
  5  *
  6  * Licensed under the Apache License, Version 2.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at
  9  *
 10  *     http://www.apache.org/licenses/LICENSE-2.0
 11  *
 12  * Unless required by applicable law or agreed to in writing, software
 13  * distributed under the License is distributed on an "AS IS" BASIS,
 14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  *
 16  * See the License for the specific language governing permissions and
 17  * limitations under the License.
 18  */
 19 
 20 var ilib = require("./ilib.js");
 21 var Utils = require("./Utils.js");
 22 
 23 var Locale = require("./Locale.js");
 24 var IString = require("./IString.js");
 25 
 26 /**
 27  * @class
 28  * Create a new string mapper instance. <p>
 29  *
 30  * The options may contain any of the following properties:
 31  *
 32  * <ul>
 33  * <li><i>locale</i> - locale to use when loading the mapper. Some maps are
 34  * locale-dependent, and this locale selects the right one. Default if this is
 35  * not specified is the current locale.
 36  *
 37  * <li><i>name</i> - the name of the map to load
 38  *
 39  * <li><i>mapFunction</i> - specify an algorithmic mapping function to use if
 40  * the mapper does not have an explicit mapping for a character. The idea is
 41  * to save disk and memory when algorithmic mapping can be done for some of
 42  * the characters, but not others. The exceptions can go into the json file,
 43  * and the characters that conform to the rule can be mapped algorithmically.
 44  * The map function should take a string containing 1 character as a parameter
 45  * and should return a string containing one or more characters. If the
 46  * character is outside of the range that can be mapped, it should be returned
 47  * unchanged.
 48  *
 49  * <li><i>onLoad</i> - a callback function to call when this object is fully
 50  * loaded. When the onLoad option is given, this object will attempt to
 51  * load any missing locale data using the ilib loader callback.
 52  * When the constructor is done (even if the data is already preassembled), the
 53  * onLoad function is called with the current instance as a parameter, so this
 54  * callback can be used with preassembled or dynamic loading or a mix of the two.
 55  *
 56  * <li><i>sync</i> - tell whether to load any missing locale data synchronously or
 57  * asynchronously. If this option is given as "false", then the "onLoad"
 58  * callback must be given, as the instance returned from this constructor will
 59  * not be usable for a while.
 60  *
 61  * <li><i>loadParams</i> - an object containing parameters to pass to the
 62  * loader callback function when locale data is missing. The parameters are not
 63  * interpretted or modified in any way. They are simply passed along. The object
 64  * may contain any property/value pairs as long as the calling code is in
 65  * agreement with the loader callback function as to what those parameters mean.
 66  * </ul>
 67  *
 68  *
 69  * @constructor
 70  * @param {Object=} options options to initialize this string mapper
 71  */
 72 var StringMapper = function (options) {
 73     var sync = true,
 74         loadParams = undefined;
 75 
 76     this.locale = new Locale();
 77     this.mapData = {};
 78     this.mapFunction = undefined;
 79 
 80     if (options) {
 81         if (typeof(options.locale) !== 'undefined') {
 82             this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale;
 83         }
 84 
 85         if (typeof(options.name) !== 'undefined') {
 86             this.name = options.name;
 87         }
 88 
 89         if (typeof(options.mapFunction) === 'function') {
 90             this.mapFunction = options.mapFunction;
 91         }
 92 
 93         if (typeof(options.sync) !== 'undefined') {
 94             sync = (options.sync == true);
 95         }
 96 
 97         if (typeof(options.loadParams) !== 'undefined') {
 98             loadParams = options.loadParams;
 99         }
100     }
101 
102     Utils.loadData({
103         object: "StringMapper",
104         locale: this.locale,
105         name: this.name + ".json",
106         sync: sync,
107         loadParams: loadParams,
108         callback: ilib.bind(this, function (map) {
109             this.mapData = map || {};
110             if (options && typeof(options.onLoad) === 'function') {
111                 options.onLoad(this);
112             }
113         })
114     });
115 };
116 
117 StringMapper.prototype = {
118     /**
119      * Return the locale that this mapper was constructed.
120      * @returns
121      */
122     getLocale: function () {
123         return this.locale;
124     },
125 
126     getName: function () {
127         return this.name;
128     },
129 
130     /**
131      * Map a string using the mapping defined in the constructor. This method
132      * iterates through all characters in the string and maps them one-by-one.
133      * If a particular character has a mapping, the mapping result will be
134      * added to the output. If there is no mapping, but there is a mapFunction
135      * defined, the mapFunction results will be added to the result. Otherwise,
136      * the original character from the input string will be added to the result.
137      *
138      * @param {string|IString|undefined} string
139      * @return {string|IString|undefined}
140      */
141     map: function (string) {
142         var input;
143         if (!string) {
144             return string;
145         }
146         if (typeof(string) === 'string') {
147             input = new IString(string);
148         } else {
149             input = string.toString();
150         }
151         var ret = "";
152         var it = input.charIterator();
153         var c;
154 
155         while (it.hasNext()) {
156             c = it.next();
157             if (this.mapData && this.mapData[c]) {
158                 ret += this.mapData[c];
159             } else if (this.mapFunction) {
160                 ret += this.mapFunction(c);
161             } else {
162                 ret += c;
163             }
164         }
165 
166         return ret;
167     }
168 };
169 
170 module.exports = StringMapper;