1 /*
  2  * ListFmt.js - Represent a list formatter.
  3  *
  4  * Copyright © 2017-2020, 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 
 21 // !data list
 22 
 23 var ilib = require("../index.js");
 24 var Utils = require("./Utils.js");
 25 var Locale = require("./Locale.js");
 26 
 27 // map our style parameter to the cldr style in the list.json files
 28 var styleMap = {
 29     "standard": "standard",
 30     "conjunction": "standard",
 31     "disjunction": "or",
 32     "unit": "unit"
 33 };
 34 
 35 /**
 36  * @class
 37  * Create a new list formatter object that formats lists of items according to
 38  * the options.<p>
 39  *
 40  * The options object can contain zero or more of the following parameters:
 41  *
 42  * <ul>
 43  * <li><i>locale</i> locale to use to format this list, or undefined to use the
 44  * default locale
 45  *
 46  * <li><i>length</i> - Specify the length of the format to use. The length is the approximate size of the
 47  * formatted string.
 48  *
 49  * <ul>
 50  * <li><i>short</i>
 51  * <li><i>medium</i>
 52  * <li><i>long</i>
 53  * <li><i>full</i>
 54  * </ul>
 55  *
 56  * <li><i>style</i> the name of style to use to format the list, or undefined
 57  * to use the default "conjunction" style. Valid values are:
 58  *
 59  * <ul>
 60  *   <ul><i>standard</i> create a standard list.
 61  *   <ul><i>conjunction</i> this list should be concatenated with a conjunction "and".
 62  *   This is the default style for "standard".
 63  *   <ul><i>disjunction</i> this list should be concatenated with a disjunction "or".
 64  *   <ul><i>unit</i> this is a list of measures like "5 minutes, 4 seconds". In
 65  *   some languages, these type of lists are concatenated without a conjunction.
 66  * </ul>
 67  *
 68  * <li><i>onLoad</i> - a callback function to call when the locale data is fully loaded and the address has been
 69  * parsed. When the onLoad option is given, the address formatter object
 70  * will attempt to load any missing locale data using the ilib loader callback.
 71  * When the constructor is done (even if the data is already preassembled), the
 72  * onLoad function is called with the current instance as a parameter, so this
 73  * callback can be used with preassembled or dynamic loading or a mix of the two.
 74  *
 75  * <li><i>sync</i> - tell whether to load any missing locale data synchronously or
 76  * asynchronously. If this option is given as "false", then the "onLoad"
 77  * callback must be given, as the instance returned from this constructor will
 78  * not be usable for a while.
 79  *
 80  * <li><i>loadParams</i> - an object containing parameters to pass to the
 81  * loader callback function when locale data is missing. The parameters are not
 82  * interpretted or modified in any way. They are simply passed along. The object
 83  * may contain any property/value pairs as long as the calling code is in
 84  * agreement with the loader callback function as to what those parameters mean.
 85  * </ul>
 86  *
 87  * @constructor
 88  * @param {Object} options properties that control how this formatter behaves
 89  */
 90 var ListFmt = function(options) {
 91     this.locale = new Locale();
 92     this.sync = true;
 93     this.style = "standard";
 94     this.length = "medium";
 95     this.loadParams = {};
 96 
 97     if (options) {
 98         if (options.type) {
 99             this.type = options.type;
100         }
101 
102         if (options.locale) {
103             this.locale = options.locale;
104         }
105 
106         if (typeof(options.sync) !== 'undefined') {
107             this.sync = !!options.sync;
108         }
109 
110         if (options.length) {
111             this.length = options.length;
112         }
113 
114         if (options.loadParams) {
115             this.loadParams = options.loadParams;
116         }
117 
118         if (options.style) {
119             if (styleMap[options.style]) {
120                 this.style = styleMap[options.style];
121             }
122         }
123     }
124 
125     Utils.loadData({
126         name: "list.json",
127         object: "ListFmt",
128         locale: this.locale,
129         sync: this.sync,
130         loadParams: this.loadParams,
131         callback: ilib.bind(this, function (fmtdata) {
132             this.fmtdata = fmtdata;
133 
134             // if the requested style is not available in this locale, fall back
135             // to the default "standard" style
136             if (!fmtdata[this.style]) {
137                 this.style = "standard";
138             }
139             if (options && typeof(options.onLoad) === 'function') {
140                 options.onLoad(this);
141             }
142         })
143     });
144 };
145 
146 /**
147  * Format a list of strings as grammatical text that is appropriate
148  * for the locale of this formatter.
149  *
150  * @param {Array.<string>} items an array of strings to format in
151  * order that you would like them to appear
152  * @returns {string} a string containing the list of items that
153  * is grammatically correct for the locale of this formatter
154  */
155 
156 ListFmt.prototype.format = function(items) {
157     if (!items || (!ilib.isArray(items))) {
158         return "";
159     }
160 
161     var itemCount = items.length;
162     var fmtTemplate, formattedList;
163     var startFmt, middleFmt, endFmt;
164     var i;
165 
166     fmtTemplate = this.fmtdata[this.style][this.length] || this.fmtdata[this.style];
167     startFmt = fmtTemplate["start"];
168     middleFmt = fmtTemplate["middle"];
169     endFmt = fmtTemplate["end"];
170 
171     if (itemCount === 0) {
172         return "";
173     }
174     else if (itemCount === 1) {
175         formattedList =  items.toString();
176 
177     } else if ( itemCount === 2) {
178         fmtTemplate = fmtTemplate["2"];
179         formattedList = fmtTemplate.replace("{0}", items[0]).replace("{1}", items[1]);
180 
181     } else {
182         for(i = itemCount; i >= 0 ; i--){
183             if (i === itemCount) {
184                 formattedList = endFmt.replace("{0}", items[itemCount-2]).replace("{1}", items[itemCount-1]);
185                 i = i-2;
186             } else if (i === 0) {
187                 formattedList = startFmt.replace("{0}",items[i]).replace("{1}", formattedList);
188             }
189              else {
190                 formattedList = middleFmt.replace("{0}",items[i]).replace("{1}", formattedList);
191             }
192         }
193     }
194     return formattedList;
195 };
196 
197 /**
198  * Return the locale of this formatter.
199  *
200  * @returns {string} the locale of this formatter
201  */
202 ListFmt.prototype.getLocale = function() {
203     return this.locale.getSpec();
204 };
205 
206 /**
207  * Return the style of names returned by this formatter
208  * @return {string} the style of names returned by this formatter
209  */
210 ListFmt.prototype.getStyle = function() {
211     return this.style;
212 };
213 
214 module.exports = ListFmt;
215