1 /*
  2  * CaseMapper.js - define upper- and lower-case mapper
  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 
 22 var Locale = require("./Locale.js");
 23 var IString = require("./IString.js");
 24 
 25 /**
 26  * @class
 27  * Create a new string mapper instance that maps strings to upper or
 28  * lower case. This mapping will work for any string as characters
 29  * that have no case will be returned unchanged.<p>
 30  *
 31  * The options may contain any of the following properties:
 32  *
 33  * <ul>
 34  * <li><i>locale</i> - locale to use when loading the mapper. Some maps are
 35  * locale-dependent, and this locale selects the right one. Default if this is
 36  * not specified is the current locale.
 37  *
 38  * <li><i>direction</i> - "toupper" for upper-casing, or "tolower" for lower-casing.
 39  * Default if not specified is "toupper".
 40  * </ul>
 41  *
 42  *
 43  * @constructor
 44  * @param {Object=} options options to initialize this mapper
 45  */
 46 var CaseMapper = function (options) {
 47     this.up = true;
 48     this.locale = new Locale();
 49 
 50     if (options) {
 51         if (typeof(options.locale) !== 'undefined') {
 52             this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale;
 53         }
 54 
 55         this.up = (!options.direction || options.direction === "toupper");
 56     }
 57 
 58     this.mapData = this.up ? {
 59         "ß": "SS",        // German
 60         'ΐ': 'Ι',        // Greek
 61         'ά': 'Α',
 62         'έ': 'Ε',
 63         'ή': 'Η',
 64         'ί': 'Ι',
 65         'ΰ': 'Υ',
 66         'ϊ': 'Ι',
 67         'ϋ': 'Υ',
 68         'ό': 'Ο',
 69         'ύ': 'Υ',
 70         'ώ': 'Ω',
 71         'Ӏ': 'Ӏ',        // Russian and slavic languages
 72         'ӏ': 'Ӏ'
 73     } : {
 74         'Ӏ': 'Ӏ'        // Russian and slavic languages
 75     };
 76 
 77     switch (this.locale.getLanguage()) {
 78         case "az":
 79         case "tr":
 80         case "crh":
 81         case "kk":
 82         case "krc":
 83         case "tt":
 84             var lower = "iı";
 85             var upper = "İI";
 86             this._setUpMap(lower, upper);
 87             break;
 88     }
 89 
 90     if (ilib._getBrowser() === "ie" || ilib._getBrowser() === "Edge") {
 91         // IE is missing these mappings for some reason
 92         if (this.up) {
 93             this.mapData['ς'] = 'Σ';
 94         }
 95         this._setUpMap("ⲁⲃⲅⲇⲉⲋⲍⲏⲑⲓⲕⲗⲙⲛⲝⲟⲡⲣⲥⲧⲩⲫⲭⲯⲱⳁⳉⳋ", "ⲀⲂⲄⲆⲈⲊⲌⲎⲐⲒⲔⲖⲘⲚⲜⲞⲠⲢⲤⲦⲨⲪⲬⲮⲰⳀⳈⳊ"); // Coptic
 96         // Georgian Nuskhuri <-> Asomtavruli
 97         this._setUpMap("ⴀⴁⴂⴃⴄⴅⴆⴇⴈⴉⴊⴋⴌⴍⴎⴏⴐⴑⴒⴓⴔⴕⴖⴗⴘⴙⴚⴛⴜⴝⴞⴟⴠⴡⴢⴣⴤⴥ", "ႠႡႢႣႤႥႦႧႨႩႪႫႬႭႮႯႰႱႲႳႴႵႶႷႸႹႺႻႼႽႾႿჀჁჂჃჄჅ");
 98     }
 99 };
100 
101 CaseMapper.prototype = {
102     /**
103      * @private
104      */
105     _charMapper: function(string) {
106         if (!string) {
107             return string;
108         }
109         var input = (typeof(string) === 'string') ? new IString(string) : string.toString();
110         var ret = "";
111         var it = input.charIterator();
112         var c;
113 
114         while (it.hasNext()) {
115             c = it.next();
116             if (!this.up && c === 'Σ') {
117                 if (it.hasNext()) {
118                     c = it.next();
119                     var code = c.charCodeAt(0);
120                     // if the next char is not a greek letter, this is the end of the word so use the
121                     // final form of sigma. Otherwise, use the mid-word form.
122                     ret += ((code < 0x0388 && code !== 0x0386) || code > 0x03CE) ? 'ς' : 'σ';
123                     ret += c.toLowerCase();
124                 } else {
125                     // no next char means this is the end of the word, so use the final form of sigma
126                     ret += 'ς';
127                 }
128             } else {
129                 if (this.mapData[c]) {
130                     ret += this.mapData[c];
131                 } else {
132                     ret += this.up ? c.toUpperCase() : c.toLowerCase();
133                 }
134             }
135         }
136 
137         return ret;
138     },
139 
140     /** @private */
141     _setUpMap: function(lower, upper) {
142         var from, to;
143         if (this.up) {
144             from = lower;
145             to = upper;
146         } else {
147             from = upper;
148             to = lower;
149         }
150         for (var i = 0; i < upper.length; i++) {
151             this.mapData[from[i]] = to[i];
152         }
153     },
154 
155     /**
156      * Return the locale that this mapper was constructed with.
157      * @returns {Locale} the locale that this mapper was constructed with
158      */
159     getLocale: function () {
160         return this.locale;
161     },
162 
163     /**
164      * Map a string to lower case in a locale-sensitive manner.
165      *
166      * @param {string|undefined} string
167      * @return {string|undefined}
168      */
169     map: function (string) {
170         return this._charMapper(string);
171     }
172 };
173 
174 module.exports = CaseMapper;
175