filters.js (17054B)
1 /** 2 * Create io-filters from an array of vocab filters. 3 * @param {Array} rawFilters The array of filters. 4 * @returns {Array} The io-filters. 5 */ 6 function GetVocabFilters(rawFilters) { 7 // Expand "All directions" filters 8 let filters = []; 9 for (let filter of rawFilters) { 10 if (filter.direction === "Eng. ↔ Esp.") { 11 filters.push({category:filter.category, type: filter.type, direction:"Eng. → Esp."}); 12 filters.push({category:filter.category, type: filter.type, direction:"Esp. → Eng."}); 13 } 14 else { 15 filters.push({category:filter.category, type: filter.type, direction:filter.direction}); 16 } 17 } 18 19 // Get category regex filter 20 for (let filter of filters) { 21 if (filter.category === "All Categories") { 22 filter.category = ".*"; 23 } 24 } 25 26 // Get type regex filter 27 for (let filter of filters) { 28 switch (filter.type.toLowerCase()) { 29 case "adjectives": 30 filter.type = "Adjective"; 31 break; 32 case "nouns": 33 filter.type = "Noun"; 34 break; 35 case "verbs": 36 filter.type = "Verb"; 37 break; 38 case "all types": 39 filter.type = ".*"; 40 break; 41 default: 42 throw `Unrecognized filter: ${type}.`; 43 } 44 } 45 46 // Create io-filters 47 let ioFilters = []; // Format: [{outputIndex:0, inputIndex:0, filters:[{index:0, value:"regex"}]}] 48 for (let filter of filters) { 49 // Create filter 50 if (filter.direction.toLowerCase().startsWith("eng")) { 51 ioFilters.push({outputIndex:0, inputIndex:1, filters:[{index:2, value:filter.type}, {index:3, value:filter.category}]}); 52 } 53 else { 54 ioFilters.push({outputIndex:1, inputIndex:0, filters:[{index:2, value:filter.type}, {index:3, value:filter.category}]}); 55 } 56 } 57 58 // Return io-filters 59 return ioFilters; 60 } 61 62 63 64 /** 65 * Create io-filters from an array of verb filters. 66 * @param {Array} rawFilters The array of filters. 67 * @returns {Array} The io-filters. 68 */ 69 function GetVerbFilters(rawFilters) { 70 // Expand "All Tenses" filters 71 let filters = []; // Format: [{tense:"specific tense", subject:"specific subject", type:"regex"}] 72 for (let filter of rawFilters) { 73 if (filter.tense.toLowerCase() === "all tenses") { 74 filters.push({ tense: "present participles", type: filter.type, subject: filter.subject, direction: filter.direction }); 75 filters.push({ tense: "past participles", type: filter.type, subject: filter.subject, direction: filter.direction }); 76 filters.push({ tense: "present tense", type: filter.type, subject: filter.subject, direction: filter.direction }); 77 filters.push({ tense: "preterite tense", type: filter.type, subject: filter.subject, direction: filter.direction }); 78 filters.push({ tense: "imperfect tense", type: filter.type, subject: filter.subject, direction: filter.direction }); 79 filters.push({ tense: "conditional tense", type: filter.type, subject: filter.subject, direction: filter.direction }); 80 filters.push({ tense: "simple future tense", type: filter.type, subject: filter.subject, direction: filter.direction }); 81 filters.push({ tense: "present subjunctive tense", type: filter.type, subject: filter.subject, direction: filter.direction }); 82 filters.push({ tense: "imperfect subjunctive tense", type: filter.type, subject: filter.subject, direction: filter.direction }); 83 } 84 else { 85 filters.push({ tense: filter.tense.toLowerCase(), type: filter.type, subject: filter.subject, direction: filter.direction }); 86 } 87 } 88 89 // Expand "All Subjects" filters 90 for (let filter of filters) { 91 if (filter.subject.toLowerCase() === "all subjects" && !["present participles", "past participles"].includes(filter.tense)) { 92 filter.subject = "yo"; 93 filters.push({ tense: filter.tense, type: filter.type, subject: "tú", direction: filter.direction }); 94 filters.push({ tense: filter.tense, type: filter.type, subject: "él", direction: filter.direction }); 95 filters.push({ tense: filter.tense, type: filter.type, subject: "nosotros", direction: filter.direction }); 96 filters.push({ tense: filter.tense, type: filter.type, subject: "ellos", direction: filter.direction }); 97 } 98 else { 99 filter.subject = filter.subject.toLowerCase(); 100 } 101 } 102 103 // Replace regularities with regex filters 104 for (let filter of filters) { 105 switch (filter.type.toLowerCase()) { 106 case "regular": 107 filter.type = "Regular"; 108 break; 109 case "reflexive": 110 filter.type = "Reflexive"; 111 break; 112 case "irregular": 113 filter.type = "Irregular"; 114 break; 115 case "stem changing": 116 filter.type = "Stem Changing"; 117 break; 118 case "orthographic": 119 filter.type = "Orthographic"; 120 break; 121 case "nonregular": 122 filter.type = "Irregular|Stem Changing|Orthographic"; 123 break; 124 case "all types": 125 filter.type = ".*"; 126 break; 127 default: 128 throw `Unrecognized filter: ${filter.type}.`; 129 } 130 } 131 132 // Create io-filters 133 let ioFilters = []; // Format: [{outputIndex:0, inputIndex:0, filters:[{index:0, value:"regex"}]}] 134 for (let filter of filters) { 135 // Get output index 136 let outputIndex; 137 if (filter.direction.toLowerCase().includes("eng")) { 138 outputIndex = 0; 139 } 140 else if (filter.direction.toLowerCase().includes("esp")) { 141 outputIndex = 1; 142 } 143 144 // Get input index and filter index 145 let inputIndex; 146 let filterIndex; 147 switch (filter.tense) { 148 case "present participles": 149 filterIndex = 2; 150 switch (filter.subject) { 151 case "type": 152 inputIndex = filterIndex; 153 break; 154 default: 155 inputIndex = 3; 156 break; 157 } 158 break; 159 case "past participles": 160 filterIndex = 4; 161 switch (filter.subject) { 162 case "type": 163 inputIndex = filterIndex; 164 break; 165 default: 166 inputIndex = 5; 167 break; 168 } 169 break; 170 case "present tense": 171 filterIndex = 6; 172 switch (filter.subject) { 173 case "type": 174 inputIndex = filterIndex; 175 break; 176 case "yo": 177 inputIndex = 7; 178 break; 179 case "tú": 180 inputIndex = 8; 181 break; 182 case "él": 183 inputIndex = 9; 184 break; 185 case "nosotros": 186 inputIndex = 10; 187 break; 188 case "ellos": 189 inputIndex = 11; 190 break; 191 default: 192 throw `Unrecognized subject: ${filter.subject}.`; 193 } 194 break; 195 case "preterite tense": 196 filterIndex = 12; 197 switch (filter.subject) { 198 case "type": 199 inputIndex = filterIndex; 200 break; 201 case "yo": 202 inputIndex = 13; 203 break; 204 case "tú": 205 inputIndex = 14; 206 break; 207 case "él": 208 inputIndex = 15; 209 break; 210 case "nosotros": 211 inputIndex = 16; 212 break; 213 case "ellos": 214 inputIndex = 17; 215 break; 216 default: 217 throw `Unrecognized subject: ${filter.subject}.`; 218 } 219 break; 220 case "imperfect tense": 221 filterIndex = 18; 222 switch (filter.subject) { 223 case "type": 224 inputIndex = filterIndex; 225 break; 226 case "yo": 227 inputIndex = 19; 228 break; 229 case "tú": 230 inputIndex = 20; 231 break; 232 case "él": 233 inputIndex = 21; 234 break; 235 case "nosotros": 236 inputIndex = 22; 237 break; 238 case "ellos": 239 inputIndex = 23; 240 break; 241 default: 242 throw `Unrecognized subject: ${filter.subject}.`; 243 } 244 break; 245 case "conditional tense": 246 filterIndex = 24; 247 switch (filter.subject) { 248 case "type": 249 inputIndex = filterIndex; 250 break; 251 case "yo": 252 inputIndex = 25; 253 break; 254 case "tú": 255 inputIndex = 26; 256 break; 257 case "él": 258 inputIndex = 27; 259 break; 260 case "nosotros": 261 inputIndex = 28; 262 break; 263 case "ellos": 264 inputIndex = 29; 265 break; 266 default: 267 throw `Unrecognized subject: ${filter.subject}.`; 268 } 269 break; 270 case "simple future tense": 271 filterIndex = 30; 272 switch (filter.subject) { 273 case "type": 274 inputIndex = filterIndex; 275 break; 276 case "yo": 277 inputIndex = 31; 278 break; 279 case "tú": 280 inputIndex = 32; 281 break; 282 case "él": 283 inputIndex = 33; 284 break; 285 case "nosotros": 286 inputIndex = 34; 287 break; 288 case "ellos": 289 inputIndex = 35; 290 break; 291 default: 292 throw `Unrecognized subject: ${filter.subject}.`; 293 } 294 break; 295 case "present subjunctive tense": 296 filterIndex = 36; 297 switch (filter.subject) { 298 case "type": 299 inputIndex = filterIndex; 300 break; 301 case "yo": 302 inputIndex = 37; 303 break; 304 case "tú": 305 inputIndex = 38; 306 break; 307 case "él": 308 inputIndex = 39; 309 break; 310 case "nosotros": 311 inputIndex = 40; 312 break; 313 case "ellos": 314 inputIndex = 41; 315 break; 316 default: 317 throw `Unrecognized subject: ${filter.subject}.`; 318 } 319 break; 320 case "imperfect subjunctive tense": 321 filterIndex = 42; 322 switch (filter.subject) { 323 case "type": 324 inputIndex = filterIndex; 325 break; 326 case "yo": 327 inputIndex = 43; 328 break; 329 case "tú": 330 inputIndex = 44; 331 break; 332 case "él": 333 inputIndex = 45; 334 break; 335 case "nosotros": 336 inputIndex = 46; 337 break; 338 case "ellos": 339 inputIndex = 47; 340 break; 341 default: 342 throw `Unrecognized subject: ${filter.subject}.`; 343 } 344 break; 345 default: 346 throw `Unrecognized tense: ${filter.tense}.`; 347 } 348 349 // Create filter 350 if (filter.direction.toLowerCase().startsWith("conj")) { 351 // Swap input and output 352 ioFilters.push({outputIndex:inputIndex, inputIndex:outputIndex, filters:[{index:filterIndex, value:filter.type}]}) 353 } 354 else { 355 ioFilters.push({outputIndex:outputIndex, inputIndex:inputIndex, filters:[{index:filterIndex, value:filter.type}]}) 356 } 357 } 358 359 // Return io-filters 360 return ioFilters; 361 } 362 363 364 365 /** 366 * Creates an array of prompts from an array of io-filters. 367 * @param {Object} terms The terms to filter. 368 * @param {Array} filters The io-filters. 369 * @returns {Array} The prompts. 370 */ 371 function ApplyFilters(terms, filters, options={}) { 372 // Set options 373 if (!"multiplePrompts" in options) options.multiplePrompts = "Show together"; 374 if (!"removeDuplicates" in options) options.removeDuplicates = false; 375 376 // Filter terms 377 let results = []; // Format: [[<output label>, <output>, <input label>, <input>]] 378 for (let filter of filters) { 379 // Iterate over terms (minus headers) 380 for (let term of terms.slice(1)) { 381 // Check against filters 382 let matchesFilter = true; 383 for (let filterFilter of filter.filters) { 384 if (!term[filterFilter.index].match(filterFilter.value)) { 385 matchesFilter = false; 386 } 387 } 388 389 // Add prompt 390 if (matchesFilter) { 391 results.push([terms[0][filter.outputIndex], term[filter.outputIndex], terms[0][filter.inputIndex], term[filter.inputIndex]]); 392 } 393 } 394 } 395 396 // Iterate over prompts to enforce multiplePrompts setting 397 for (let result of results) { 398 // Get array of prompt outputs 399 let prompts = result[1].split(/\s*,\s*/); 400 401 // Check if multiple outputs exist 402 if (prompts.length > 1) { 403 switch (options.multiplePrompts) { 404 case "Show one": 405 // Set current prompt's output to a random prompt 406 result[1] = prompts[Math.floor(Math.random() * (prompts.length - 1))] 407 break; 408 409 case "Show separately": 410 result[1] = prompts[0]; // Set current prompt's output to 1st prompt 411 for (let prompt of prompts.splice(1)) { 412 // Add seperate prompts for extra outputs 413 results.push([result[0], prompt, result[2], result[3]]) 414 } 415 break; 416 417 case "Show together": 418 default: 419 // Do nothing 420 break; 421 } 422 } 423 } 424 425 // Remove duplicate prompts 426 if (options.removeDuplicates) { 427 results = results.filter((result, index) => { 428 return index === results.findIndex(obj => { 429 try { 430 return obj[0] === result[0] && obj[1] === result[1] && obj[2] === result[2] && obj[3] === result[3]; 431 } 432 catch { 433 return false; 434 } 435 }); 436 }); 437 } 438 439 // Return prompts 440 return results; 441 } 442 443 444 445 /** 446 * Shuffles an array of items. 447 * @param {Array} items - The array. 448 */ 449 function Shuffle(items) { 450 // Initialize variables 451 var currentIndex = items.length; 452 var temp; 453 var randomIndex; 454 455 // While there are more elements to shuffle 456 while (0 !== currentIndex) { 457 // Pick a remaining element 458 randomIndex = Math.floor(Math.random() * currentIndex); 459 currentIndex--; 460 461 // Swap the two elements 462 temp = items[currentIndex]; 463 items[currentIndex] = items[randomIndex]; 464 items[randomIndex] = temp; 465 } 466 467 // Return shuffled items 468 return items; 469 }