UNPKG

917 kBJavaScriptView Raw
1/*
2Copyright (c) 2009-2019 Frank Bennett
3
4 This program is free software: you can redistribute it and/or
5 modify it under EITHER
6
7 * the terms of the Common Public Attribution License (CPAL) as
8 published by the Open Source Initiative, either version 1 of
9 the CPAL, or (at your option) any later version; OR
10
11 * the terms of the GNU Affero General Public License (AGPL)
12 as published by the Free Software Foundation, either version
13 3 of the AGPL, or (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 Affero General Public License for more details.
19
20 You should have received copies of the Common Public Attribution
21 License and of the GNU Affero General Public License along with
22 this program. If not, see <https://opensource.org/licenses/> or
23 <http://www.gnu.org/licenses/> respectively.
24*/
25/*global CSL: true */
26
27/**
28 * A Javascript implementation of the CSL citation formatting language.
29 *
30 * <p>A configured instance of the process is built in two stages,
31 * using {@link CSL.Core.Build} and {@link CSL.Core.Configure}.
32 * The former sets up hash-accessible locale data and imports the CSL format file
33 * to be applied to the citations,
34 * transforming it into a one-dimensional token list, and
35 * registering functions and parameters on each token as appropriate.
36 * The latter sets jump-point information
37 * on tokens that constitute potential branch
38 * points, in a single back-to-front scan of the token list.
39 * This
40 * yields a token list that can be executed front-to-back by
41 * body methods available on the
42 * {@link CSL.Engine} class.</p>
43 *
44 * <p>This top-level {@link CSL} object itself carries
45 * constants that are needed during processing.</p>
46 * @namespace A CSL citation formatter.
47 */
48
49// IE6 does not implement Array.indexOf().
50// IE7 neither, according to rumour.
51
52
53// Potential skip words:
54// under; along; out; between; among; outside; inside; amid; amidst; against; toward; towards.
55// See https://forums.zotero.org/discussion/30484/?Focus=159613#Comment_159613
56
57'use strict';
58
59
60var CSL = {
61
62 PROCESSOR_VERSION: "1.2.22",
63
64 error: function(str) { // default error function
65 if ("undefined" === typeof Error) {
66 throw new Error("citeproc-js error: " + str);
67 } else {
68 throw "citeproc-js error: " + str;
69 }
70 },
71 debug: function(str) { // default debug function
72 if ("undefined" === typeof console) {
73 dump("CSL: " + str + "\n");
74 } else {
75 console.log("citeproc-js warning: " + str);
76 }
77 },
78
79 LOCATOR_LABELS_REGEXP: new RegExp("^((art|ch|subch|col|fig|l|n|no|op|p|pp|para|subpara|supp|pt|r|sec|subsec|sv|sch|tit|vrs|vol)\\.)\\s+(.*)"),
80
81 STATUTE_SUBDIV_PLAIN_REGEX: /(?:(?:^| )(?:art|bk|ch|subch|col|fig|fol|l|n|no|op|p|pp|para|subpara|supp|pt|r|sec|subsec|sv|sch|tit|vrs|vol)\. *)/,
82 STATUTE_SUBDIV_PLAIN_REGEX_FRONT: /(?:^\s*[.,;]*\s*(?:art|bk|ch|subch|col|fig|fol|l|n|no|op|p|pp|para|subpara|supp|pt|r|sec|subsec|sv|sch|tit|vrs|vol)\. *)/,
83 STATUTE_SUBDIV_STRINGS: {
84 "art.": "article",
85 "bk.": "book",
86 "ch.": "chapter",
87 "subch.": "subchapter",
88 "p.": "page",
89 "pp.": "page",
90 "para.": "paragraph",
91 "subpara.": "subparagraph",
92 "pt.": "part",
93 "r.": "rule",
94 "sec.": "section",
95 "subsec.": "subsection",
96 "supp.": "supplement",
97 "sch.": "schedule",
98 "tit.": "title",
99 "col.": "column",
100 "fig.": "figure",
101 "fol.": "folio",
102 "l.": "line",
103 "n.": "note",
104 "no.": "issue",
105 "op.": "opus",
106 "sv.": "sub-verbo",
107 "vrs.": "verse",
108 "vol.": "volume"
109 },
110 STATUTE_SUBDIV_STRINGS_REVERSE: {
111 "article": "art.",
112 "book": "bk.",
113 "chapter": "ch.",
114 "subchapter": "subch.",
115 "page": "p.",
116 "paragraph": "para.",
117 "subparagraph": "subpara.",
118 "part": "pt.",
119 "rule": "r.",
120 "section": "sec.",
121 "subsection": "subsec.",
122 "supplement": "supp.",
123 "schedule": "sch.",
124 "title": "tit.",
125 "column": "col.",
126 "figure": "fig.",
127 "folio": "fol.",
128 "line": "l.",
129 "note": "n.",
130 "issue": "no.",
131 "opus": "op.",
132 "sub-verbo": "sv.",
133 "sub verbo": "sv.",
134 "verse": "vrs.",
135 "volume": "vol."
136 },
137
138 LOCATOR_LABELS_MAP: {
139 "art": "article",
140 "bk": "book",
141 "ch": "chapter",
142 "subch": "subchapter",
143 "col": "column",
144 "fig": "figure",
145 "fol": "folio",
146 "l": "line",
147 "n": "note",
148 "no": "issue",
149 "op": "opus",
150 "p": "page",
151 "pp": "page",
152 "para": "paragraph",
153 "subpara": "subparagraph",
154 "pt": "part",
155 "r": "rule",
156 "sec": "section",
157 "subsec": "subsection",
158 "supp": "supplement",
159 "sv": "sub-verbo",
160 "sch": "schedule",
161 "tit": "title",
162 "vrs": "verse",
163 "vol": "volume"
164 },
165 MODULE_MACROS: {
166 "juris-pretitle": true,
167 "juris-title": true,
168 "juris-pretitle-short": true,
169 "juris-title-short": true,
170 "juris-main": true,
171 "juris-main-short": true,
172 "juris-tail": true,
173 "juris-tail-short": true,
174 "juris-locator": true
175 },
176 MODULE_TYPES: {
177 "legal_case": true,
178 "legislation": true,
179 "bill": true,
180 "hearing": true,
181 "gazette": true,
182 "report": true,
183 "regulation": true,
184 "standard": true,
185 "patent": true
186 },
187 checkNestedBrace: function(state) {
188 if (state.opt.xclass === "note") {
189 this.depth = 0;
190 this.update = function(str) {
191
192 // Receives affix string, returns with flipped parens.
193
194 var str = str ? str : "";
195 var lst = str.split(/([\(\)])/);
196 for (var i=1,ilen=lst.length;i<ilen;i += 2) {
197 if (lst[i] === "(") {
198 if (1 === (this.depth % 2)) {
199 lst[i] = "[";
200 }
201 this.depth += 1;
202 } else if (lst[i] === ")") {
203 if (0 === (this.depth % 2)) {
204 lst[i] = "]";
205 }
206 this.depth -= 1;
207 }
208 }
209 var ret = lst.join("");
210 return ret;
211 };
212 } else {
213 this.update = function(str) {
214 return str;
215 };
216 }
217 },
218
219 MULTI_FIELDS: ["event", "publisher", "publisher-place", "event-place", "title", "container-title", "collection-title", "authority","genre","title-short","medium","country","jurisdiction","archive","archive-place"],
220
221 LangPrefsMap: {
222 "title":"titles",
223 "title-short":"titles",
224 "event":"titles",
225 "genre":"titles",
226 "medium":"titles",
227 "container-title":"journals",
228 "collection-title":"titles",
229 "archive":"journals",
230 "publisher":"publishers",
231 "authority":"publishers",
232 "publisher-place": "places",
233 "event-place": "places",
234 "archive-place": "places",
235 "jurisdiction": "places",
236 "number": "places",
237 "edition":"places",
238 "issue":"places",
239 "volume":"places"
240 },
241
242 AbbreviationSegments: function () {
243 this["container-title"] = {};
244 this["collection-title"] = {};
245 this["institution-entire"] = {};
246 this["institution-part"] = {};
247 this.nickname = {};
248 this.number = {};
249 this.title = {};
250 this.place = {};
251 this.hereinafter = {};
252 this.classic = {};
253 this["container-phrase"] = {};
254 this["title-phrase"] = {};
255 },
256
257 FIELD_CATEGORY_REMAP: {
258 "title": "title",
259 "container-title": "container-title",
260 "collection-title": "collection-title",
261 "country": "place",
262 "number": "number",
263 "place": "place",
264 "archive": "collection-title",
265 "title-short": "title",
266 "genre": "title",
267 "event": "title",
268 "medium": "title",
269 "archive-place": "place",
270 "publisher-place": "place",
271 "event-place": "place",
272 "jurisdiction": "place",
273 "language-name": "place",
274 "language-name-original": "place",
275 "call-number": "number",
276 "chapter-number": "number",
277 "collection-number": "number",
278 "edition": "number",
279 "page": "number",
280 "issue": "number",
281 "locator": "number",
282 "locator-extra": "number",
283 "number-of-pages": "number",
284 "number-of-volumes": "number",
285 "volume": "number",
286 "citation-number": "number",
287 "publisher": "institution-part"
288 },
289
290 parseLocator: function(item) {
291 if (this.opt.development_extensions.locator_date_and_revision) {
292 // Break out locator elements if necessary
293 if (item.locator) {
294 item.locator = "" + item.locator;
295 var idx = item.locator.indexOf("|");
296 if (idx > -1) {
297 var raw_locator = item.locator;
298 item.locator = raw_locator.slice(0, idx);
299 raw_locator = raw_locator.slice(idx + 1);
300 var m = raw_locator.match(/^([0-9]{4}-[0-9]{2}-[0-9]{2}).*/);
301 if (m) {
302 item["locator-date"] = this.fun.dateparser.parseDateToObject(m[1]);
303 raw_locator = raw_locator.slice(m[1].length);
304 }
305 item["locator-extra"] = raw_locator.replace(/^\s+/, "").replace(/\s+$/, "");
306 }
307 }
308 }
309 if (item.locator) {
310 item.locator = ("" + item.locator).replace(/\s+$/, '');
311 }
312 return item;
313 },
314
315 normalizeLocaleStr: function(str) {
316 if (!str) {
317 return;
318 }
319 var lst = str.split('-');
320 lst[0] = lst[0].toLowerCase();
321 if (lst[1]) {
322 lst[1] = lst[1].toUpperCase();
323 }
324 return lst.join("-");
325 },
326
327 parseNoteFieldHacks: function(Item, validFieldsForType, allowDateOverride) {
328 if ("string" !== typeof Item.note) {
329 return;
330 }
331 var elems = [];
332 var lines = Item.note.split('\n');
333 // Normalize entries
334 for (var i=0, ilen=lines.length; i<ilen; i++) {
335 var line = lines[i];
336 var elems = [];
337 var m = line.match(CSL.NOTE_FIELDS_REGEXP);
338 if (m) {
339 var splt = line.split(CSL.NOTE_FIELDS_REGEXP);
340 for (var j=0,jlen=(splt.length-1);j<jlen;j++) {
341 elems.push(splt[j]);
342 elems.push(m[j]);
343 }
344 elems.push(splt[splt.length-1]);
345 for (var j=1,jlen=elems.length;j<jlen;j += 2) {
346 // Abort conversions if preceded by unparseable text
347 if (elems[j-1].trim() && (i>0 || j>1) && !elems[j-1].match(CSL.NOTE_FIELD_REGEXP)) {
348 break;
349 } else {
350 elems[j] = '\n' + elems[j].slice(2,-1).trim() + '\n';
351 }
352 }
353 lines[i] = elems.join('');
354 }
355 }
356 // Resplit
357 lines = lines.join('\n').split('\n');
358 var offset = 0;
359 var names = {};
360 for (var i=0,ilen=lines.length;i<ilen;i++) {
361 var line = lines[i];
362 var mm = line.match(CSL.NOTE_FIELD_REGEXP);
363 if (!line.trim()) {
364 continue;
365 } else if (!mm) {
366 if (i === 0) {
367 continue;
368 } else {
369 offset = i;
370 break;
371 }
372 }
373 var key = mm[1];
374 var val = mm[2].replace(/^\s+/, "").replace(/\s+$/, "");
375 if (key === "type") {
376 Item.type = val;
377 lines[i] = "";
378 } else if (CSL.DATE_VARIABLES.indexOf(key.replace(/^alt-/, "")) > -1) {
379 if (!Item[key] || allowDateOverride) {
380 Item[key] = CSL.DateParser.parseDateToArray(val);
381 if (!validFieldsForType || (validFieldsForType[key] && this.isDateString(val))) {
382 lines[i] = "";
383 }
384 }
385 } else if (!Item[key]) {
386 if (CSL.NAME_VARIABLES.indexOf(key.replace(/^alt-/, "")) > -1) {
387 if (!names[key]) {
388 names[key] = [];
389 }
390 var lst = val.split(/\s*\|\|\s*/);
391 if (lst.length === 1) {
392 names[key].push({literal:lst[0]});
393 } else if (lst.length === 2) {
394 var name = {family:lst[0],given:lst[1]};
395 CSL.parseParticles(name);
396 names[key].push(name);
397 }
398 } else {
399 Item[key] = val;
400 }
401 if (!validFieldsForType || validFieldsForType[key]) {
402 lines[i] = "";
403 }
404 }
405 }
406 for (var key in names) {
407 Item[key] = names[key];
408 }
409 // Final cleanup for validCslFields only: eliminate blank lines, add blank line to text
410 if (validFieldsForType) {
411 if (lines[offset].trim()) {
412 lines[offset] = '\n' + lines[offset];
413 }
414 for (var i=offset-1;i>-1;i--) {
415 if (!lines[i].trim()) {
416 lines = lines.slice(0, i).concat(lines.slice(i + 1));
417 }
418 }
419 }
420 Item.note = lines.join("\n").trim();
421 },
422
423 checkPrefixSpaceAppend: function (state, prefix) {
424 if (!prefix) {
425 prefix = "";
426 }
427 var sp = "";
428 // We need the raw string, without decorations
429 // of any kind. Markup scheme is known, though, so
430 // markup can be safely stripped at string level.
431 //
432 // U+201d = right double quotation mark
433 // U+2019 = right single quotation mark
434 // U+00bb = right double angle bracket (guillemet)
435 // U+202f = non-breaking thin space
436 // U+00a0 = non-breaking space
437 var test_prefix = prefix.replace(/<[^>]+>/g, "").replace(/["'\u201d\u2019\u00bb\u202f\u00a0 ]+$/g,"");
438 var test_char = test_prefix.slice(-1);
439 if (test_prefix.match(CSL.ENDSWITH_ROMANESQUE_REGEXP)) {
440 sp = " ";
441 } else if (CSL.TERMINAL_PUNCTUATION.slice(0,-1).indexOf(test_char) > -1) {
442 sp = " ";
443 } else if (test_char.match(/[\)\],0-9]/)) {
444 sp = " ";
445 }
446 // Protect against double spaces, which would trigger an extra,
447 // explicit, non-breaking space.
448 var prefix = (prefix + sp).replace(/\s+/g, " ");
449 return prefix;
450 },
451
452 checkIgnorePredecessor: function(state, prefix) {
453 var ignorePredecessor = false;
454 var test_prefix = prefix.replace(/<[^>]+>/g, "").replace(/["'\u201d\u2019\u00bb\u202f\u00a0 ]+$/g,"");
455 var test_char = test_prefix.slice(-1);
456 if (CSL.TERMINAL_PUNCTUATION.slice(0,-1).indexOf(test_char) > -1 && prefix.trim().indexOf(" ") > -1) {
457 state.tmp.term_predecessor = false;
458 return true;
459 }
460 return false;
461 },
462
463 checkSuffixSpacePrepend: function(state, suffix) {
464 if (!suffix) {
465 return "";
466 }
467 if (suffix.match(CSL.STARTSWITH_ROMANESQUE_REGEXP) || ['[','('].indexOf(suffix.slice(0,1)) > -1) {
468 suffix = " " + suffix;
469 }
470 return suffix;
471 },
472
473 GENDERS: ["masculine", "feminine"],
474
475 ERROR_NO_RENDERED_FORM: 1,
476
477 PREVIEW: "Just for laughs.",
478 ASSUME_ALL_ITEMS_REGISTERED: 2,
479
480 START: 0,
481 END: 1,
482 SINGLETON: 2,
483
484 SEEN: 6,
485 SUCCESSOR: 3,
486 SUCCESSOR_OF_SUCCESSOR: 4,
487 SUPPRESS: 5,
488
489 SINGULAR: 0,
490 PLURAL: 1,
491
492 LITERAL: true,
493
494 BEFORE: 1,
495 AFTER: 2,
496
497 DESCENDING: 1,
498 ASCENDING: 2,
499
500 PRIMARY: 1,
501 SECONDARY: 2,
502
503 POSITION_FIRST: 0,
504 POSITION_SUBSEQUENT: 1,
505 POSITION_IBID: 2,
506 POSITION_IBID_WITH_LOCATOR: 3,
507
508 POSITION_TEST_VARS: ["position", "first-reference-note-number", "near-note"],
509
510 AREAS: ["citation", "citation_sort", "bibliography", "bibliography_sort", "intext"],
511
512 CITE_FIELDS: ["first-reference-note-number", "locator", "locator-extra"],
513
514 SWAPPING_PUNCTUATION: [".", "!", "?", ":", ","],
515 TERMINAL_PUNCTUATION: [":", ".", ";", "!", "?", " "],
516
517 // update modes
518 NONE: 0,
519 NUMERIC: 1,
520 POSITION: 2,
521 TRIGRAPH: 3,
522
523 DATE_PARTS: ["year", "month", "day"],
524 DATE_PARTS_ALL: ["year", "month", "day", "season"],
525 DATE_PARTS_INTERNAL: ["year", "month", "day", "year_end", "month_end", "day_end"],
526
527 NAME_PARTS: ["non-dropping-particle", "family", "given", "dropping-particle", "suffix", "literal"],
528
529 DISAMBIGUATE_OPTIONS: [
530 "disambiguate-add-names",
531 "disambiguate-add-givenname",
532 "disambiguate-add-year-suffix"
533 ],
534
535 GIVENNAME_DISAMBIGUATION_RULES: [
536 "all-names",
537 "all-names-with-initials",
538 "primary-name",
539 "primary-name-with-initials",
540 "by-cite"
541 ],
542
543 NAME_ATTRIBUTES: [
544 "and",
545 "delimiter-precedes-last",
546 "delimiter-precedes-et-al",
547 "initialize-with",
548 "initialize",
549 "name-as-sort-order",
550 "sort-separator",
551 "et-al-min",
552 "et-al-use-first",
553 "et-al-subsequent-min",
554 "et-al-subsequent-use-first",
555 "form",
556 "prefix",
557 "suffix",
558 "delimiter"
559 ],
560
561 PARALLEL_MATCH_VARS: ["container-title"],
562 PARALLEL_TYPES: ["bill","gazette","regulation","legislation","legal_case","treaty","article-magazine","article-journal"],
563 PARALLEL_COLLAPSING_MID_VARSET: ["volume", "issue", "container-title", "section", "collection-number"],
564
565 LOOSE: 0,
566 STRICT: 1,
567 TOLERANT: 2,
568
569 PREFIX_PUNCTUATION: /[.;:]\s*$/,
570 SUFFIX_PUNCTUATION: /^\s*[.;:,\(\)]/,
571
572 NUMBER_REGEXP: /(?:^\d+|\d+$)/,
573 //
574 // \u0400-\u042f are cyrillic and extended cyrillic capitals
575 // this is not fully smart yet. can't do what this was trying to do
576 // with regexps, actually; we want to identify strings with a leading
577 // capital letter, and any subsequent capital letters. Have to compare
578 // locale caps version with existing version, character by character.
579 // hard stuff, but if it breaks, that's what to do.
580 // \u0600-\u06ff is Arabic/Persian
581 // \u200c-\u200e and \u202a-\u202e are special spaces and left-right
582 // control characters
583
584
585
586 NAME_INITIAL_REGEXP: /^([A-Z\u0e01-\u0e5b\u00c0-\u017f\u0400-\u042f\u0590-\u05d4\u05d6-\u05ff\u0600-\u06ff\u0370\u0372\u0376\u0386\u0388-\u03ab\u03e2\u03e4\u03e6\u03e8\u03ea\u03ec\u03ee\u03f4\u03f7\u03fd-\u03ff])([a-zA-Z\u0e01-\u0e5b\u00c0-\u017f\u0400-\u052f\u0600-\u06ff\u0370-\u03ff\u1f00-\u1fff]*|)(\.)*/,
587 ROMANESQUE_REGEXP: /[-0-9a-zA-Z\u0e01-\u0e5b\u00c0-\u017f\u0370-\u03ff\u0400-\u052f\u0590-\u05d4\u05d6-\u05ff\u1f00-\u1fff\u0600-\u06ff\u200c\u200d\u200e\u0218\u0219\u021a\u021b\u202a-\u202e]/,
588 ROMANESQUE_NOT_REGEXP: /[^a-zA-Z\u0e01-\u0e5b\u00c0-\u017f\u0370-\u03ff\u0400-\u052f\u0590-\u05d4\u05d6-\u05ff\u1f00-\u1fff\u0600-\u06ff\u200c\u200d\u200e\u0218\u0219\u021a\u021b\u202a-\u202e]/g,
589 STARTSWITH_ROMANESQUE_REGEXP: /^[&a-zA-Z\u0e01-\u0e5b\u00c0-\u017f\u0370-\u03ff\u0400-\u052f\u0590-\u05d4\u05d6-\u05ff\u1f00-\u1fff\u0600-\u06ff\u200c\u200d\u200e\u0218\u0219\u021a\u021b\u202a-\u202e]/,
590 ENDSWITH_ROMANESQUE_REGEXP: /[.;:&a-zA-Z\u0e01-\u0e5b\u00c0-\u017f\u0370-\u03ff\u0400-\u052f\u0590-\u05d4\u05d6-\u05ff\u1f00-\u1fff\u0600-\u06ff\u200c\u200d\u200e\u0218\u0219\u021a\u021b\u202a-\u202e]$/,
591 ALL_ROMANESQUE_REGEXP: /^[a-zA-Z\u0e01-\u0e5b\u00c0-\u017f\u0370-\u03ff\u0400-\u052f\u0590-\u05d4\u05d6-\u05ff\u1f00-\u1fff\u0600-\u06ff\u200c\u200d\u200e\u0218\u0219\u021a\u021b\u202a-\u202e]+$/,
592
593 VIETNAMESE_SPECIALS: /[\u00c0-\u00c3\u00c8-\u00ca\u00cc\u00cd\u00d2-\u00d5\u00d9\u00da\u00dd\u00e0-\u00e3\u00e8-\u00ea\u00ec\u00ed\u00f2-\u00f5\u00f9\u00fa\u00fd\u0101\u0103\u0110\u0111\u0128\u0129\u0168\u0169\u01a0\u01a1\u01af\u01b0\u1ea0-\u1ef9]/,
594
595 VIETNAMESE_NAMES: /^(?:(?:[.AaBbCcDdEeGgHhIiKkLlMmNnOoPpQqRrSsTtUuVvXxYy \u00c0-\u00c3\u00c8-\u00ca\u00cc\u00cd\u00d2-\u00d5\u00d9\u00da\u00dd\u00e0-\u00e3\u00e8-\u00ea\u00ec\u00ed\u00f2-\u00f5\u00f9\u00fa\u00fd\u0101\u0103\u0110\u0111\u0128\u0129\u0168\u0169\u01a0\u01a1\u01af\u01b0\u1ea0-\u1ef9]{2,6})(\s+|$))+$/,
596
597 NOTE_FIELDS_REGEXP: /\{:(?:[\-_a-z]+|[A-Z]+):[^\}]+\}/g,
598 NOTE_FIELD_REGEXP: /^([\-_a-z]+|[A-Z]+):\s*([^\}]+)$/,
599
600 PARTICLE_GIVEN_REGEXP: /^([^ ]+(?:\u02bb |\u2019 | |\' ) *)(.+)$/,
601 PARTICLE_FAMILY_REGEXP: /^([^ ]+(?:\-|\u02bb|\u2019| |\') *)(.+)$/,
602
603 DISPLAY_CLASSES: ["block", "left-margin", "right-inline", "indent"],
604
605 NAME_VARIABLES: [
606 "author",
607 "collection-editor",
608 "composer",
609 "container-author",
610 "director",
611 "editor",
612 "editorial-director",
613 "illustrator",
614 "interviewer",
615 "original-author",
616 "recipient",
617 "reviewed-author",
618 "translator"
619 ],
620 CREATORS: [
621 "author",
622 "collection-editor",
623 "composer",
624 "container-author",
625 "director",
626 "editor",
627 "editorial-director",
628 "illustrator",
629 "interviewer",
630 "original-author",
631 "recipient",
632 "reviewed-author",
633 "translator"
634 ],
635 NUMERIC_VARIABLES: [
636 "call-number",
637 "chapter-number",
638 "collection-number",
639 "division",
640 "edition",
641 "page",
642 "issue",
643 "locator",
644 "locator-extra",
645 "number",
646 "number-of-pages",
647 "number-of-volumes",
648 "volume",
649 // "section", ??? add this?
650 "supplement",
651 "citation-number"
652 ],
653 //var x = new Array();
654 //x = x.concat(["title","container-title","issued","page"]);
655 //x = x.concat(["locator","collection-number","original-date"]);
656 //x = x.concat(["reporting-date","decision-date","filing-date"]);
657 //x = x.concat(["revision-date"]);
658 //NUMERIC_VARIABLES = x.slice();
659 DATE_VARIABLES: [
660 "locator-date",
661 "issued",
662 "event-date",
663 "accessed",
664 "original-date",
665 "publication-date",
666 "available-date",
667 "submitted",
668 "alt-issued",
669 "alt-event"
670 ],
671 VARIABLES_WITH_SHORT_FORM: [
672 "title",
673 "container-title"
674 ],
675 TITLE_FIELD_SPLITS: function(seg) {
676 var keys = ["title", "short", "main", "sub", "subjoin"];
677 var ret = {};
678 for (var i=0,ilen=keys.length;i<ilen;i++) {
679 ret[keys[i]] = seg + "title" + (keys[i] === "title" ? "" : "-" + keys[i]);
680 }
681 return ret;
682 },
683
684 demoteNoiseWords: function (state, fld, drop_or_demote) {
685 var SKIP_WORDS = state.locale[state.opt.lang].opts["leading-noise-words"];
686 if (fld && drop_or_demote) {
687 fld = fld.split(/\s+/);
688 fld.reverse();
689 var toEnd = [];
690 for (var j = fld.length - 1; j > -1; j += -1) {
691 if (SKIP_WORDS.indexOf(fld[j].toLowerCase()) > -1) {
692 toEnd.push(fld.pop());
693 } else {
694 break;
695 }
696 }
697 fld.reverse();
698 var start = fld.join(" ");
699 var end = toEnd.join(" ");
700 if ("drop" === drop_or_demote || !end) {
701 fld = start;
702 } else if ("demote" === drop_or_demote) {
703 fld = [start, end].join(", ");
704 }
705 }
706 return fld;
707 },
708
709 extractTitleAndSubtitle: function (Item, narrowSpaceLocale) {
710 var narrowSpace = narrowSpaceLocale ? "\u202f" : "";
711 // XXX In this function, split on split-char, but prefer exact match
712 // XXX of subtitle to a split-char in title if found.
713 var segments = ["", "container-"];
714 for (var i=0,ilen=segments.length;i<ilen;i++) {
715 var seg = segments[i];
716 var title = CSL.TITLE_FIELD_SPLITS(seg);
717 var langs = [false];
718 if (Item.multi) {
719 for (var lang in Item.multi._keys[title.short]) {
720 langs.push(lang);
721 }
722 }
723 for (var j=0,jlen=langs.length;j<jlen;j++) {
724 var lang = langs[j];
725 var vals = {};
726 if (lang) {
727 if (Item.multi._keys[title.title]) {
728 vals[title.title] = Item.multi._keys[title.title][lang];
729 }
730 if (Item.multi._keys[title["short"]]) {
731 vals[title["short"]] = Item.multi._keys[title["short"]][lang];
732 }
733 } else {
734 vals[title.title] = Item[title.title];
735 vals[title["short"]] = Item[title["short"]];
736 }
737 vals[title.main] = vals[title.title];
738 vals[title.sub] = false;
739 var shortTitle = vals[title["short"]];
740 if (vals[title.title]) {
741 // Rules
742 // TITLE_SPLIT eliminates split-points of period-space preceded by a capital letter.
743 // If short title exists and matches exactly to a split-point, use that split-point only.
744 // Otherwise if there is just one split-point, use that as main/sub split.
745 // Otherwise use all split-points ... which is handled in titleCaseSentenceOrNormal, not here.
746 if (shortTitle && shortTitle.toLowerCase() === vals[title.title].toLowerCase()) {
747 vals[title.main] = vals[title.title];
748 vals[title.subjoin] = "";
749 vals[title.sub] = "";
750 } else if (shortTitle) {
751 // check for valid match to shortTitle
752 var tail = vals[title.title].slice(shortTitle.replace(/[\?\!]+$/, "").length);
753 var top = vals[title.title].replace(tail.replace(/^[\?\!]+/, ""), "").trim();
754 var m = CSL.TITLE_SPLIT_REGEXP.matchfirst.exec(tail);
755 if (m && top.toLowerCase() === shortTitle.toLowerCase()) {
756 vals[title.main] = top;
757 vals[title.subjoin] = m[1].replace(/[\?\!]+(\s*)$/, "$1");
758 vals[title.sub] = tail.replace(CSL.TITLE_SPLIT_REGEXP.matchfirst, "");
759 if (this.opt.development_extensions.force_short_title_casing_alignment) {
760 vals[title["short"]] = vals[title.main];
761 }
762 } else {
763 var splitTitle = CSL.TITLE_SPLIT(vals[title.title]);
764 if (splitTitle.length == 3) {
765 vals[title.main] = splitTitle[0];
766 vals[title.subjoin] = splitTitle[1];
767 vals[title.sub] = splitTitle[2];
768 } else {
769 vals[title.main] = vals[title.title];
770 vals[title.subjoin] = "";
771 vals[title.sub] = "";
772 }
773 }
774 } else {
775 var splitTitle = CSL.TITLE_SPLIT(vals[title.title]);
776 if (splitTitle.length == 3) {
777 vals[title.main] = splitTitle[0];
778 vals[title.subjoin] = splitTitle[1];
779 vals[title.sub] = splitTitle[2];
780 if (this.opt.development_extensions.implicit_short_title) {
781 if (!Item[title.short] && !vals[title.main].match(/^[\-\.[0-9]+$/)) {
782 var punct = vals[title.subjoin].trim();
783 if (["?", "!"].indexOf(punct) === -1) {
784 punct = "";
785 }
786 vals[title.short] = vals[title.main] + punct;
787 }
788 }
789 } else {
790 vals[title.main] = vals[title.title];
791 vals[title.subjoin] = "";
792 vals[title.sub] = "";
793 }
794 }
795 if (vals[title.subjoin]) {
796 if (vals[title.subjoin].match(/([\?\!])/)) {
797 var m = vals[title.subjoin].match(/(\s*)$/)
798 vals[title.main] = vals[title.main] + narrowSpace +vals[title.subjoin].trim();
799 vals[title.subjoin] = m[1];
800 }
801 }
802 }
803 if (vals[title.subjoin]) {
804 if (vals[title.subjoin].indexOf(":") > -1) {
805 vals[title.subjoin] = narrowSpace + ": ";
806 }
807 if (vals[title.subjoin].indexOf("-") > -1 || vals[title.subjoin].indexOf("—") > -1) {
808 vals[title.subjoin] = "—";
809 }
810 }
811 if (lang) {
812 for (var key in vals) {
813 if (!Item.multi._keys[key]) {
814 Item.multi._keys[key] = {};
815 }
816 Item.multi._keys[key][lang] = vals[key];
817 }
818 } else {
819 for (var key in vals) {
820 Item[key] = vals[key];
821 }
822 }
823 }
824 }
825 },
826
827 titlecaseSentenceOrNormal: function(state, Item, seg, lang, sentenceCase) {
828 // Hold on here.
829 // What is seg here?
830 // It's ... either "" or "container-". Which is ugly, but works.
831 // But this ALWAYS returns the full title, never short.
832 // So sentence-casing cannot be applied to short.
833 // Goes unnoticed because forced sentence-casing almost never appears in styles.
834 var title = CSL.TITLE_FIELD_SPLITS(seg);
835 var vals = {};
836 if (lang && Item.multi) {
837 if (Item.multi._keys[title.title]) {
838 vals[title.title] = Item.multi._keys[title.title][lang];
839 }
840 if (Item.multi._keys[title.main]) {
841 vals[title.main] = Item.multi._keys[title.main][lang];
842 }
843 if (Item.multi._keys[title.sub]) {
844 vals[title.sub] = Item.multi._keys[title.sub][lang];
845 }
846 if (Item.multi._keys[title.subjoin]) {
847 vals[title.subjoin] = Item.multi._keys[title.subjoin][lang];
848 }
849 } else {
850 vals[title.title] = Item[title.title];
851 vals[title.main] = Item[title.main];
852 vals[title.sub] = Item[title.sub];
853 vals[title.subjoin] = Item[title.subjoin];
854 }
855 if (vals[title.main] && vals[title.sub]) {
856 var mainTitle = vals[title.main];
857 var subJoin = vals[title.subjoin];
858 var subTitle = vals[title.sub];
859 if (sentenceCase) {
860 mainTitle = CSL.Output.Formatters.sentence(state, mainTitle);
861 subTitle = CSL.Output.Formatters.sentence(state, subTitle);
862 } else if (state.opt.development_extensions.uppercase_subtitles) {
863 subTitle = CSL.Output.Formatters["capitalize-first"](state, subTitle);
864 }
865 return [mainTitle, subJoin, subTitle].join("");
866 } else if (vals[title.title]) {
867 if (sentenceCase) {
868 return CSL.Output.Formatters.sentence(state, vals[title.title]);
869 } else if (state.opt.development_extensions.uppercase_subtitles) {
870 // Split and apply everywhere.
871 var splits = CSL.TITLE_SPLIT(vals[title.title]);
872 for (var i=0,ilen=splits.length; i<ilen; i += 2) {
873 splits[i] = CSL.Output.Formatters["capitalize-first"](state, splits[i]);
874 }
875 for (var i=1, ilen=splits.length-1; i < ilen; i += 2) {
876 var m = splits[i].match(/([:\?\!] )/);
877 if (m) {
878 var narrowSpace = state.opt["default-locale"][0].slice(0, 2).toLowerCase() === "fr" ? "\u202f" : "";
879 splits[i] = narrowSpace + m[1];
880 }
881 if (splits[i].indexOf("-") > -1 || splits[i].indexOf("—") > -1) {
882 splits[i] = "—";
883 }
884 }
885 vals[title.title] = splits.join("");
886 return vals[title.title];
887 } else {
888 return vals[title.title];
889 }
890 } else {
891 return "";
892 }
893 },
894
895 getSafeEscape: function(state) {
896 if (["bibliography", "citation"].indexOf(state.tmp.area) > -1) {
897 // Callback to apply thin space hack
898 // Callback to force LTR/RTL on parens and braces
899 // XXX Is this really necessary?
900 var callbacks = [];
901 if (state.opt.development_extensions.thin_non_breaking_space_html_hack && state.opt.mode === "html") {
902 callbacks.push(function (txt) {
903 return txt.replace(/\u202f/g, '<span style="white-space:nowrap">&thinsp;</span>');
904 });
905 }
906 if (callbacks.length) {
907 return function (txt) {
908 for (var i = 0, ilen = callbacks.length; i < ilen; i += 1) {
909 txt = callbacks[i](txt);
910 }
911 return CSL.Output.Formats[state.opt.mode].text_escape(txt);
912 };
913 } else {
914 return CSL.Output.Formats[state.opt.mode].text_escape;
915 }
916 } else {
917 return function (txt) { return txt; };
918 }
919 },
920
921 SKIP_WORDS: ["about","above","across","afore","after","against","al", "along","alongside","amid","amidst","among","amongst","anenst","apropos","apud","around","as","aside","astride","at","athwart","atop","barring","before","behind","below","beneath","beside","besides","between","beyond","but","by","circa","despite","down","during","et", "except","for","forenenst","from","given","in","inside","into","lest","like","modulo","near","next","notwithstanding","of","off","on","onto","out","over","per","plus","pro","qua","sans","since","than","through"," thru","throughout","thruout","till","to","toward","towards","under","underneath","until","unto","up","upon","versus","vs.","v.","vs","v","via","vis-à-vis","with","within","without","according to","ahead of","apart from","as for","as of","as per","as regards","aside from","back to","because of","close to","due to","except for","far from","inside of","instead of","near to","next to","on to","out from","out of","outside of","prior to","pursuant to","rather than","regardless of","such as","that of","up to","where as","or", "yet", "so", "for", "and", "nor", "a", "an", "the", "de", "d'", "von", "van", "c", "ca"],
922
923 FORMAT_KEY_SEQUENCE: [
924 "@strip-periods",
925 "@font-style",
926 "@font-variant",
927 "@font-weight",
928 "@text-decoration",
929 "@vertical-align",
930 "@quotes"
931 ],
932
933 INSTITUTION_KEYS: [
934 "font-style",
935 "font-variant",
936 "font-weight",
937 "text-decoration",
938 "text-case"
939 ],
940
941 SUFFIX_CHARS: "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z",
942 ROMAN_NUMERALS: [
943 [ "", "i", "ii", "iii", "iv", "v", "vi", "vii", "viii", "ix" ],
944 [ "", "x", "xx", "xxx", "xl", "l", "lx", "lxx", "lxxx", "xc" ],
945 [ "", "c", "cc", "ccc", "cd", "d", "dc", "dcc", "dccc", "cm" ],
946 [ "", "m", "mm", "mmm", "mmmm", "mmmmm"]
947 ],
948
949 LANGS: {
950 "af-ZA":"Afrikaans",
951 "ar":"Arabic",
952 "bg-BG":"Bulgarian",
953 "ca-AD":"Catalan",
954 "cs-CZ":"Czech",
955 "da-DK":"Danish",
956 "de-AT":"Austrian",
957 "de-CH":"German (CH)",
958 "de-DE":"German (DE)",
959 "el-GR":"Greek",
960 "en-GB":"English (GB)",
961 "en-US":"English (US)",
962 "es-ES":"Spanish",
963 "et-EE":"Estonian",
964 "eu":"European",
965 "fa-IR":"Persian",
966 "fi-FI":"Finnish",
967 "fr-CA":"French (CA)",
968 "fr-FR":"French (FR)",
969 "he-IL":"Hebrew",
970 "hr-HR":"Croatian",
971 "hu-HU":"Hungarian",
972 "is-IS":"Icelandic",
973 "it-IT":"Italian",
974 "ja-JP":"Japanese",
975 "km-KH":"Khmer",
976 "ko-KR":"Korean",
977 "lt-LT":"Lithuanian",
978 "lv-LV":"Latvian",
979 "mn-MN":"Mongolian",
980 "nb-NO":"Norwegian (Bokmål)",
981 "nl-NL":"Dutch",
982 "nn-NO":"Norwegian (Nynorsk)",
983 "pl-PL":"Polish",
984 "pt-BR":"Portuguese (BR)",
985 "pt-PT":"Portuguese (PT)",
986 "ro-RO":"Romanian",
987 "ru-RU":"Russian",
988 "sk-SK":"Slovak",
989 "sl-SI":"Slovenian",
990 "sr-RS":"Serbian",
991 "sv-SE":"Swedish",
992 "th-TH":"Thai",
993 "tr-TR":"Turkish",
994 "uk-UA":"Ukrainian",
995 "vi-VN":"Vietnamese",
996 "zh-CN":"Chinese (CN)",
997 "zh-TW":"Chinese (TW)"
998 },
999
1000 LANG_BASES: {
1001 af: "af_ZA",
1002 ar: "ar",
1003 bg: "bg_BG",
1004 ca: "ca_AD",
1005 cs: "cs_CZ",
1006 da: "da_DK",
1007 de: "de_DE",
1008 el: "el_GR",
1009 en: "en_US",
1010 es: "es_ES",
1011 et: "et_EE",
1012 eu: "eu",
1013 fa: "fa_IR",
1014 fi: "fi_FI",
1015 fr: "fr_FR",
1016 he: "he_IL",
1017 hr: "hr-HR",
1018 hu: "hu_HU",
1019 is: "is_IS",
1020 it: "it_IT",
1021 ja: "ja_JP",
1022 km: "km_KH",
1023 ko: "ko_KR",
1024 lt: "lt_LT",
1025 lv: "lv-LV",
1026 mn: "mn_MN",
1027 nb: "nb_NO",
1028 nl: "nl_NL",
1029 nn: "nn-NO",
1030 pl: "pl_PL",
1031 pt: "pt_PT",
1032 ro: "ro_RO",
1033 ru: "ru_RU",
1034 sk: "sk_SK",
1035 sl: "sl_SI",
1036 sr: "sr_RS",
1037 sv: "sv_SE",
1038 th: "th_TH",
1039 tr: "tr_TR",
1040 uk: "uk_UA",
1041 vi: "vi_VN",
1042 zh: "zh_CN"
1043 },
1044
1045 SUPERSCRIPTS: {
1046 "\u00AA": "\u0061",
1047 "\u00B2": "\u0032",
1048 "\u00B3": "\u0033",
1049 "\u00B9": "\u0031",
1050 "\u00BA": "\u006F",
1051 "\u02B0": "\u0068",
1052 "\u02B1": "\u0266",
1053 "\u02B2": "\u006A",
1054 "\u02B3": "\u0072",
1055 "\u02B4": "\u0279",
1056 "\u02B5": "\u027B",
1057 "\u02B6": "\u0281",
1058 "\u02B7": "\u0077",
1059 "\u02B8": "\u0079",
1060 "\u02E0": "\u0263",
1061 "\u02E1": "\u006C",
1062 "\u02E2": "\u0073",
1063 "\u02E3": "\u0078",
1064 "\u02E4": "\u0295",
1065 "\u1D2C": "\u0041",
1066 "\u1D2D": "\u00C6",
1067 "\u1D2E": "\u0042",
1068 "\u1D30": "\u0044",
1069 "\u1D31": "\u0045",
1070 "\u1D32": "\u018E",
1071 "\u1D33": "\u0047",
1072 "\u1D34": "\u0048",
1073 "\u1D35": "\u0049",
1074 "\u1D36": "\u004A",
1075 "\u1D37": "\u004B",
1076 "\u1D38": "\u004C",
1077 "\u1D39": "\u004D",
1078 "\u1D3A": "\u004E",
1079 "\u1D3C": "\u004F",
1080 "\u1D3D": "\u0222",
1081 "\u1D3E": "\u0050",
1082 "\u1D3F": "\u0052",
1083 "\u1D40": "\u0054",
1084 "\u1D41": "\u0055",
1085 "\u1D42": "\u0057",
1086 "\u1D43": "\u0061",
1087 "\u1D44": "\u0250",
1088 "\u1D45": "\u0251",
1089 "\u1D46": "\u1D02",
1090 "\u1D47": "\u0062",
1091 "\u1D48": "\u0064",
1092 "\u1D49": "\u0065",
1093 "\u1D4A": "\u0259",
1094 "\u1D4B": "\u025B",
1095 "\u1D4C": "\u025C",
1096 "\u1D4D": "\u0067",
1097 "\u1D4F": "\u006B",
1098 "\u1D50": "\u006D",
1099 "\u1D51": "\u014B",
1100 "\u1D52": "\u006F",
1101 "\u1D53": "\u0254",
1102 "\u1D54": "\u1D16",
1103 "\u1D55": "\u1D17",
1104 "\u1D56": "\u0070",
1105 "\u1D57": "\u0074",
1106 "\u1D58": "\u0075",
1107 "\u1D59": "\u1D1D",
1108 "\u1D5A": "\u026F",
1109 "\u1D5B": "\u0076",
1110 "\u1D5C": "\u1D25",
1111 "\u1D5D": "\u03B2",
1112 "\u1D5E": "\u03B3",
1113 "\u1D5F": "\u03B4",
1114 "\u1D60": "\u03C6",
1115 "\u1D61": "\u03C7",
1116 "\u2070": "\u0030",
1117 "\u2071": "\u0069",
1118 "\u2074": "\u0034",
1119 "\u2075": "\u0035",
1120 "\u2076": "\u0036",
1121 "\u2077": "\u0037",
1122 "\u2078": "\u0038",
1123 "\u2079": "\u0039",
1124 "\u207A": "\u002B",
1125 "\u207B": "\u2212",
1126 "\u207C": "\u003D",
1127 "\u207D": "\u0028",
1128 "\u207E": "\u0029",
1129 "\u207F": "\u006E",
1130 "\u2120": "\u0053\u004D",
1131 "\u2122": "\u0054\u004D",
1132 "\u3192": "\u4E00",
1133 "\u3193": "\u4E8C",
1134 "\u3194": "\u4E09",
1135 "\u3195": "\u56DB",
1136 "\u3196": "\u4E0A",
1137 "\u3197": "\u4E2D",
1138 "\u3198": "\u4E0B",
1139 "\u3199": "\u7532",
1140 "\u319A": "\u4E59",
1141 "\u319B": "\u4E19",
1142 "\u319C": "\u4E01",
1143 "\u319D": "\u5929",
1144 "\u319E": "\u5730",
1145 "\u319F": "\u4EBA",
1146 "\u02C0": "\u0294",
1147 "\u02C1": "\u0295",
1148 "\u06E5": "\u0648",
1149 "\u06E6": "\u064A"
1150 },
1151 SUPERSCRIPTS_REGEXP: new RegExp("[\u00AA\u00B2\u00B3\u00B9\u00BA\u02B0\u02B1\u02B2\u02B3\u02B4\u02B5\u02B6\u02B7\u02B8\u02E0\u02E1\u02E2\u02E3\u02E4\u1D2C\u1D2D\u1D2E\u1D30\u1D31\u1D32\u1D33\u1D34\u1D35\u1D36\u1D37\u1D38\u1D39\u1D3A\u1D3C\u1D3D\u1D3E\u1D3F\u1D40\u1D41\u1D42\u1D43\u1D44\u1D45\u1D46\u1D47\u1D48\u1D49\u1D4A\u1D4B\u1D4C\u1D4D\u1D4F\u1D50\u1D51\u1D52\u1D53\u1D54\u1D55\u1D56\u1D57\u1D58\u1D59\u1D5A\u1D5B\u1D5C\u1D5D\u1D5E\u1D5F\u1D60\u1D61\u2070\u2071\u2074\u2075\u2076\u2077\u2078\u2079\u207A\u207B\u207C\u207D\u207E\u207F\u2120\u2122\u3192\u3193\u3194\u3195\u3196\u3197\u3198\u3199\u319A\u319B\u319C\u319D\u319E\u319F\u02C0\u02C1\u06E5\u06E6]", "g"),
1152
1153 UPDATE_GROUP_CONTEXT_CONDITION: function (state, termtxt, valueTerm) {
1154 if (state.tmp.group_context.tip.condition) {
1155 if (state.tmp.group_context.tip.condition.test) {
1156 state.tmp.group_context.tip.condition.termtxt = termtxt;
1157 state.tmp.group_context.tip.condition.valueTerm = valueTerm;
1158 }
1159 } else {
1160 // If not inside a conditional group, raise numeric flag
1161 // if and only if the current term string ends in a number.
1162 if (termtxt.slice(-1).match(/[0-9]/)) {
1163 state.tmp.just_did_number = true;
1164 } else {
1165 state.tmp.just_did_number = false;
1166 }
1167 }
1168 },
1169
1170 EVALUATE_GROUP_CONDITION: function(state, flags) {
1171 var testres;
1172 if (flags.condition.test === "empty-label") {
1173 testres = !flags.condition.termtxt;
1174 } else if (flags.condition.test === "empty-label-no-decor") {
1175 testres = !flags.condition.termtxt || flags.condition.termtxt.indexOf("%s") > -1;
1176 } else if (flags.condition.test === "comma-safe") {
1177 var empty = !flags.condition.termtxt;
1178 var termStartAlpha = false;
1179 if (flags.condition.termtxt) {
1180 termStartAlpha = flags.condition.termtxt.slice(0,1).match(CSL.ALL_ROMANESQUE_REGEXP);
1181 }
1182 var num = state.tmp.just_did_number;
1183 if (empty) {
1184 // i.e. Big L. Rev. 100, 102
1185 // Little L. Rev. 102
1186 // L. Rev. for Plan 9, 102
1187 if (num) {
1188 testres = true;
1189 } else {
1190 testres = false;
1191 }
1192 } else if (flags.condition.valueTerm) {
1193 // i.e. Ibid. at 102
1194 testres = false;
1195 } else {
1196 if (termStartAlpha) {
1197 testres = true;
1198 } else {
1199 testres = false;
1200 }
1201 }
1202 }
1203 if (testres) {
1204 var force_suppress = false;
1205 } else {
1206 var force_suppress = true;
1207 }
1208 if (flags.condition.not) {
1209 force_suppress = !force_suppress;
1210 }
1211 return force_suppress;
1212 },
1213
1214 SYS_OPTIONS: [
1215 "prioritize_disambiguate_condition",
1216 "csl_reverse_lookup_support",
1217 "main_title_from_short_title",
1218 "uppercase_subtitles",
1219 "force_short_title_casing_alignment",
1220 "implicit_short_title"
1221 ],
1222
1223 TITLE_SPLIT_REGEXP: (function() {
1224 var splits = [
1225 "\\.\\s+",
1226 "\\!\\s+",
1227 "\\?\\s+",
1228 "\\s*::*\\s+",
1229 "\\s*—\\s*",
1230 "\\s+\\-\\s+",
1231 "\\s*\\-\\-\\-*\\s*"
1232 ]
1233 return {
1234 match: new RegExp("(" + splits.join("|") + ")", "g"),
1235 matchfirst: new RegExp("^(" + splits.join("|") + ")"),
1236 split: new RegExp("(?:" + splits.join("|") + ")")
1237 }
1238 })(),
1239
1240 TITLE_SPLIT: function(str) {
1241 if (!str) {
1242 return str;
1243 }
1244 var m = str.match(CSL.TITLE_SPLIT_REGEXP.match);
1245 var lst = str.split(CSL.TITLE_SPLIT_REGEXP.split);
1246 for (var i=lst.length-2; i>-1; i--) {
1247 lst[i] = lst[i].trim();
1248 if (lst[i] && lst[i].slice(-1).toLowerCase() !== lst[i].slice(-1)) {
1249 // recombine
1250 lst[i] = lst[i] + m[i] + lst[i+1];
1251 lst = lst.slice(0, i+1).concat(lst.slice(i+2))
1252 } else {
1253 // merge
1254 lst = lst.slice(0, i+1).concat([m[i]]).concat(lst.slice(i+1))
1255 }
1256 }
1257 return lst;
1258 }
1259};
1260
1261/**
1262 * Functions for parsing an XML object converted to JSON.
1263 */
1264
1265/*
1266 Style and locale JSON should be formatted as follows. Note that
1267 an empty literal should be set as an explicit empty strings within
1268 children:[]
1269
1270 {
1271 name:"term",
1272 children:[
1273 ""
1274 ],
1275 attrs:{
1276 name:"author"
1277 }
1278 }
1279
1280 The following script will generate correctly formatted JSON
1281 from a CSL style or locale file:
1282*/
1283
1284CSL.XmlJSON = function (dataObj) {
1285 this.dataObj = dataObj;
1286 this.institution = {
1287 name:"institution",
1288 attrs:{
1289 "institution-parts":"long",
1290 "delimiter":", ",
1291 "substitute-use-first":"1",
1292 "use-last":"1"
1293 },
1294 children:[
1295 {
1296 name:"institution-part",
1297 attrs:{
1298 name:"long"
1299 },
1300 children:[]
1301 }
1302 ]
1303 };
1304};
1305
1306/**
1307 * No need for cleaning with native JSON.
1308 */
1309CSL.XmlJSON.prototype.clean = function (json) {
1310 return json;
1311};
1312
1313
1314/**
1315 * Methods to call on a node.
1316 */
1317CSL.XmlJSON.prototype.getStyleId = function (myjson, styleName) {
1318 var tagName = 'id';
1319 if (styleName) {
1320 tagName = 'title';
1321 }
1322 var ret = "";
1323 var children = myjson.children;
1324 for (var i=0,ilen=children.length;i<ilen;i++) {
1325 if (children[i].name === 'info') {
1326 var grandkids = children[i].children;
1327 for (var j=0,jlen=grandkids.length;j<jlen;j++) {
1328 if (grandkids[j].name === tagName) {
1329 ret = grandkids[j].children[0];
1330 }
1331 }
1332 }
1333 }
1334 return ret;
1335};
1336
1337CSL.XmlJSON.prototype.children = function (myjson) {
1338 //print("children()");
1339 if (myjson && myjson.children.length) {
1340 return myjson.children.slice();
1341 } else {
1342 return false;
1343 }
1344};
1345
1346CSL.XmlJSON.prototype.nodename = function (myjson) {
1347 //print("nodename()");
1348 return myjson ? myjson.name : null;
1349};
1350
1351CSL.XmlJSON.prototype.attributes = function (myjson) {
1352 //print("attributes()");
1353 var ret = {};
1354 for (var attrname in myjson.attrs) {
1355 ret["@"+attrname] = myjson.attrs[attrname];
1356 }
1357 return ret;
1358};
1359
1360
1361CSL.XmlJSON.prototype.content = function (myjson) {
1362 //print("content()");
1363 // xmldom.js and xmle4x.js have "undefined" as default
1364 var ret = "";
1365 // This only catches content at first level, but that is good enough
1366 // for us.
1367 if (!myjson || !myjson.children) {
1368 return ret;
1369 }
1370 for (var i=0, ilen=myjson.children.length; i < ilen; i += 1) {
1371 if ("string" === typeof myjson.children[i]) {
1372 ret += myjson.children[i];
1373 }
1374 }
1375 return ret;
1376};
1377
1378
1379CSL.XmlJSON.prototype.namespace = {}
1380
1381CSL.XmlJSON.prototype.numberofnodes = function (myjson) {
1382 //print("numberofnodes()");
1383 if (myjson && "number" == typeof myjson.length) {
1384 return myjson.length;
1385 } else {
1386 return 0;
1387 }
1388};
1389
1390// getAttributeName() removed. Looks like it was not being used.
1391
1392CSL.XmlJSON.prototype.getAttributeValue = function (myjson,name,namespace) {
1393 //print("getAttributeValue()");
1394 var ret = "";
1395 if (namespace) {
1396 name = namespace+":"+name;
1397 }
1398 if (myjson) {
1399 if (myjson.attrs) {
1400 if (myjson.attrs[name]) {
1401 ret = myjson.attrs[name];
1402 } else {
1403 ret = "";
1404 }
1405 }
1406 }
1407 return ret;
1408}
1409
1410CSL.XmlJSON.prototype.getNodeValue = function (myjson,name) {
1411 //print("getNodeValue()");
1412 var ret = "";
1413 if (name){
1414 for (var i=0, ilen=myjson.children.length; i < ilen; i += 1) {
1415 if (myjson.children[i].name === name) {
1416 // This will always be Object() unless empty
1417 if (myjson.children[i].children.length) {
1418 ret = myjson.children[i];
1419 } else {
1420 ret = "";
1421 }
1422 }
1423 }
1424 } else if (myjson) {
1425 ret = myjson;
1426 }
1427 // Just being careful here, following the former DOM code. The JSON object we receive
1428 // for this should be fully normalized.
1429 if (ret && ret.children && ret.children.length == 1 && "string" === typeof ret.children[0]) {
1430 ret = ret.children[0];
1431 }
1432 return ret;
1433}
1434
1435CSL.XmlJSON.prototype.setAttributeOnNodeIdentifiedByNameAttribute = function (myjson,nodename,partname,attrname,val) {
1436 //print("setAttributeOnNodeIdentifiedByNameAttribute()");
1437 var pos, len, xml, nodes, node;
1438 if (attrname.slice(0,1) === '@'){
1439 attrname = attrname.slice(1);
1440 }
1441 // In the one place this is used in citeproc-js code, it doesn't need to recurse.
1442 for (var i=0,ilen=myjson.children.length; i<ilen; i += 1) {
1443 if (myjson.children[i].name === nodename && myjson.children[i].attrs.name === partname) {
1444 myjson.children[i].attrs[attrname] = val;
1445 }
1446 }
1447}
1448
1449CSL.XmlJSON.prototype.deleteNodeByNameAttribute = function (myjson,val) {
1450 //print("deleteNodeByNameAttribute()");
1451 var i, ilen;
1452 for (i = 0, ilen = myjson.children.length; i < ilen; i += 1) {
1453 if (!myjson.children[i] || "string" === typeof myjson.children[i]) {
1454 continue;
1455 }
1456 if (myjson.children[i].attrs.name == val) {
1457 myjson.children = myjson.children.slice(0,i).concat(myjson.children.slice(i+1));
1458 }
1459 }
1460}
1461
1462CSL.XmlJSON.prototype.deleteAttribute = function (myjson,attrname) {
1463 //print("deleteAttribute()");
1464 var i, ilen;
1465 if ("undefined" !== typeof myjson.attrs[attrname]) {
1466 myjson.attrs.pop(attrname);
1467 }
1468}
1469
1470CSL.XmlJSON.prototype.setAttribute = function (myjson,attr,val) {
1471 //print("setAttribute()");
1472 myjson.attrs[attr] = val;
1473 return false;
1474}
1475
1476CSL.XmlJSON.prototype.nodeCopy = function (myjson,clone) {
1477 //print("nodeCopy()");
1478 if (!clone) {
1479 var clone = {};
1480 }
1481 if ("object" === typeof clone && "undefined" === typeof clone.length) {
1482 // myjson is an object
1483 for (var key in myjson) {
1484 if ("string" === typeof myjson[key]) {
1485 clone[key] = myjson[key];
1486 } else if ("object" === typeof myjson[key]) {
1487 if ("undefined" === typeof myjson[key].length) {
1488 clone[key] = this.nodeCopy(myjson[key],{});
1489 } else {
1490 clone[key] = this.nodeCopy(myjson[key],[]);
1491 }
1492 }
1493 }
1494 } else {
1495 // myjson is an array
1496 for (var i=0,ilen=myjson.length;i<ilen; i += 1) {
1497 if ("string" === typeof myjson[i]) {
1498 clone[i] = myjson[i];
1499 } else {
1500 // If it's at the first level of an array, it's an object.
1501 clone[i] = this.nodeCopy(myjson[i],{});
1502 }
1503 }
1504 }
1505 return clone;
1506}
1507
1508CSL.XmlJSON.prototype.getNodesByName = function (myjson,name,nameattrval,ret) {
1509 //print("getNodesByName()");
1510 var nodes, node, pos, len;
1511 if (!ret) {
1512 var ret = [];
1513 }
1514 if (!myjson || !myjson.children) {
1515 return ret;
1516 }
1517 if (name === myjson.name) {
1518 if (nameattrval) {
1519 if (nameattrval === myjson.attrs.name) {
1520 ret.push(myjson);
1521 }
1522 } else {
1523 ret.push(myjson);
1524 }
1525 }
1526 for (var i=0,ilen=myjson.children.length;i<ilen;i+=1){
1527 if ("object" !== typeof myjson.children[i]) {
1528 continue;
1529 }
1530 this.getNodesByName(myjson.children[i],name,nameattrval,ret);
1531 }
1532 return ret;
1533}
1534
1535CSL.XmlJSON.prototype.nodeNameIs = function (myjson,name) {
1536 //print("nodeNameIs()");
1537 if (typeof myjson === "undefined") {
1538 return false;
1539 }
1540 if (name == myjson.name) {
1541 return true;
1542 }
1543 return false;
1544}
1545
1546CSL.XmlJSON.prototype.makeXml = function (myjson) {
1547 //print("makeXml()");
1548 if ("string" === typeof myjson) {
1549 if (myjson.slice(0, 1) === "<") {
1550 myjson = this.jsonStringWalker.walkToObject(myjson);
1551 } else {
1552 myjson = JSON.parse(myjson);
1553 }
1554 }
1555 return myjson;
1556};
1557
1558CSL.XmlJSON.prototype.insertChildNodeAfter = function (parent,node,pos,datejson) {
1559 //print("insertChildNodeAfter()");
1560 // Function is misnamed: this replaces the node
1561 for (var i=0,ilen=parent.children.length;i<ilen;i+=1) {
1562 if (node === parent.children[i]) {
1563 parent.children = parent.children.slice(0,i).concat([datejson]).concat(parent.children.slice(i+1));
1564 break;
1565 }
1566 }
1567 return parent;
1568};
1569
1570
1571CSL.XmlJSON.prototype.insertPublisherAndPlace = function(myjson) {
1572 if (myjson.name === "group") {
1573 var useme = true;
1574 var mustHaves = ["publisher","publisher-place"];
1575 for (var i=0,ilen=myjson.children.length;i<ilen;i+=1) {
1576 var haveVarname = mustHaves.indexOf(myjson.children[i].attrs.variable);
1577 var isText = myjson.children[i].name === "text";
1578 if (isText && haveVarname > -1 && !myjson.children[i].attrs.prefix && !myjson.children[i].attrs.suffix) {
1579 mustHaves = mustHaves.slice(0,haveVarname).concat(mustHaves.slice(haveVarname+1));
1580 } else {
1581 useme = false;
1582 break;
1583 }
1584 }
1585 if (useme && !mustHaves.length) {
1586 myjson.attrs["has-publisher-and-publisher-place"] = true;
1587 }
1588 }
1589 for (var i=0,ilen=myjson.children.length;i<ilen;i+=1) {
1590 if ("object" === typeof myjson.children[i]) {
1591 this.insertPublisherAndPlace(myjson.children[i]);
1592 }
1593 }
1594}
1595/*
1596CSL.XmlJSON.prototype.insertPublisherAndPlace = function(myxml) {
1597 var group = myxml.getElementsByTagName("group");
1598 for (var i = 0, ilen = group.length; i < ilen; i += 1) {
1599 var node = group.item(i);
1600 var skippers = [];
1601 for (var j = 0, jlen = node.childNodes.length; j < jlen; j += 1) {
1602 if (node.childNodes.item(j).nodeType !== 1) {
1603 skippers.push(j);
1604 }
1605 }
1606 if (node.childNodes.length - skippers.length === 2) {
1607 var twovars = [];
1608 for (var j = 0, jlen = 2; j < jlen; j += 1) {
1609 if (skippers.indexOf(j) > -1) {
1610 continue;
1611 }
1612 var child = node.childNodes.item(j);
1613 var subskippers = [];
1614 for (var k = 0, klen = child.childNodes.length; k < klen; k += 1) {
1615 if (child.childNodes.item(k).nodeType !== 1) {
1616 subskippers.push(k);
1617 }
1618 }
1619 if (child.childNodes.length - subskippers.length === 0) {
1620 twovars.push(child.getAttribute('variable'));
1621 if (child.getAttribute('suffix')
1622 || child.getAttribute('prefix')) {
1623 twovars = [];
1624 break;
1625 }
1626 }
1627 }
1628 if (twovars.indexOf("publisher") > -1 && twovars.indexOf("publisher-place") > -1) {
1629 node.setAttribute('has-publisher-and-publisher-place', true);
1630 }
1631 }
1632 }
1633};
1634*/
1635
1636CSL.XmlJSON.prototype.isChildOfSubstitute = function(parents) {
1637 if (parents.length > 0) {
1638 var myparents = parents.slice();
1639 var parent = myparents.pop();
1640 if (parent === "substitute") {
1641 return true;
1642 } else {
1643 return this.isChildOfSubstitute(myparents);
1644 }
1645 }
1646 return false;
1647};
1648
1649CSL.XmlJSON.prototype.addMissingNameNodes = function(myjson,parents) {
1650 if (!parents) {
1651 parents = [];
1652 }
1653 if (myjson.name === "names") {
1654 // Trawl through children to decide whether a name node is needed here
1655 if (!this.isChildOfSubstitute(parents)) {
1656 var addName = true;
1657 for (var i=0,ilen=myjson.children.length;i<ilen;i++) {
1658 if (myjson.children[i].name === "name") {
1659 addName = false;
1660 break;
1661 }
1662 }
1663 if (addName) {
1664 myjson.children = [{name:"name",attrs:{},children:[]}].concat(myjson.children);
1665 }
1666 }
1667 }
1668 parents.push(myjson.name);
1669 for (var i=0,ilen=myjson.children.length;i<ilen;i+=1) {
1670 if ("object" === typeof myjson.children[i]) {
1671 this.addMissingNameNodes(myjson.children[i],parents);
1672 }
1673 }
1674 parents.pop();
1675}
1676
1677
1678CSL.XmlJSON.prototype.addInstitutionNodes = function(myjson) {
1679 //print("addInstitutionNodes()");
1680 var names, thenames, institution, theinstitution, name, thename, xml, pos, len;
1681 // The idea here is to map relevant attributes from name and nampart=family
1682 // to the "long" institution-part node, when and only when forcing insert
1683 // of the default node.
1684 if (myjson.name === "names") {
1685 // do stuff
1686 var attributes = {};
1687 var insertPos = -1;
1688 for (var i=0,ilen=myjson.children.length;i<ilen;i+=1) {
1689 if (myjson.children[i].name == "name") {
1690 for (var key in myjson.children[i].attrs) {
1691 attributes[key] = myjson.children[i].attrs[key];
1692 }
1693 attributes.delimiter = myjson.children[i].attrs.delimiter;
1694 attributes.and = myjson.children[i].attrs.and;
1695 insertPos = i;
1696 for (var k=0,klen=myjson.children[i].children.length;k<klen;k+=1) {
1697 if (myjson.children[i].children[k].attrs.name !== 'family') {
1698 continue;
1699 }
1700 for (var key in myjson.children[i].children[k].attrs) {
1701 attributes[key] = myjson.children[i].children[k].attrs[key];
1702 }
1703 }
1704 }
1705 if (myjson.children[i].name == "institution") {
1706 insertPos = -1;
1707 break;
1708 }
1709 }
1710 if (insertPos > -1) {
1711 var institution = this.nodeCopy(this.institution);
1712 for (var i=0,ilen = CSL.INSTITUTION_KEYS.length;i<ilen;i+=1) {
1713 var attrname = CSL.INSTITUTION_KEYS[i];
1714 if ("undefined" !== typeof attributes[attrname]) {
1715 institution.children[0].attrs[attrname] = attributes[attrname];
1716 }
1717 if (attributes.delimiter) {
1718 institution.attrs.delimiter = attributes.delimiter;
1719 }
1720 if (attributes.and) {
1721 institution.attrs.and = "text";
1722 }
1723 }
1724 myjson.children = myjson.children.slice(0,insertPos+1).concat([institution]).concat(myjson.children.slice(insertPos+1));
1725 }
1726 }
1727 for (var i=0,ilen=myjson.children.length;i<ilen;i+=1) {
1728 if ("string" === typeof myjson.children[i]) {
1729 continue;
1730 }
1731 // Recurse
1732 this.addInstitutionNodes(myjson.children[i]);
1733 }
1734}
1735CSL.XmlJSON.prototype.flagDateMacros = function(myjson) {
1736 // print("flagDateMacros()");
1737 for (var i=0,ilen=myjson.children.length;i<ilen;i+=1) {
1738 if (myjson.children[i].name === "macro") {
1739 if (this.inspectDateMacros(myjson.children[i])) {
1740 myjson.children[i].attrs["macro-has-date"] = "true";
1741 }
1742 }
1743 }
1744}
1745CSL.XmlJSON.prototype.inspectDateMacros = function(myjson) {
1746 //print("inspectDateMacros()");
1747 if (!myjson || !myjson.children) {
1748 return false;
1749 }
1750 if (myjson.name === "date") {
1751 return true;
1752 } else {
1753 for (var i=0,ilen=myjson.children.length;i<ilen;i+=1) {
1754 if (this.inspectDateMacros(myjson.children[i])) {
1755 return true;
1756 }
1757 }
1758 }
1759 return false;
1760}
1761
1762/*
1763 * Clean serialized XML
1764 */
1765CSL.stripXmlProcessingInstruction = function (xml) {
1766 if (!xml) {
1767 return xml;
1768 }
1769 xml = xml.replace(/^<\?[^?]+\?>/, "");
1770 xml = xml.replace(/<!--[^>]+-->/g, "");
1771 xml = xml.replace(/^\s+/g, "");
1772 xml = xml.replace(/\s+$/g, "");
1773 return xml;
1774};
1775
1776
1777/*
1778 * String parser for XML inputs
1779 */
1780CSL.parseXml = function(str) {
1781
1782 var _pos = 0;
1783 var _obj = {children:[]};
1784 var _stack = [_obj.children];
1785
1786 function _listifyString(str) {
1787 str = str.split(/(?:\r\n|\n|\r)/).join(" ").replace(/>[ ]+</g, "><").replace(/<\!--.*?-->/g, "");
1788 var lst = str.split("><");
1789 var stylePos = null;
1790 for (var i=0,ilen=lst.length;i<ilen;i++) {
1791 if (i > 0) {
1792 lst[i] = "<" + lst[i];
1793 }
1794 if (i < (lst.length-1)) {
1795 lst[i] = lst[i] + ">";
1796 }
1797 if ("number" != typeof stylePos) {
1798 if (lst[i].slice(0, 7) === "<style " || lst[i].slice(0, 8) == "<locale ") {
1799 stylePos = i;
1800 }
1801 }
1802 }
1803 lst = lst.slice(stylePos);
1804 // Combine open/close elements for empty terms,
1805 // so that they will be passed through correctly
1806 // as empty strings.
1807 for (var i=lst.length-2;i>-1;i--) {
1808 if (lst[i].slice(1).indexOf("<") === -1) {
1809 var stub = lst[i].slice(0, 5);
1810 if (lst[i].slice(-2) !== "/>") {
1811 if (stub === "<term") {
1812 if (lst[i+1].slice(0, 6) === "</term") {
1813 lst[i] = lst[i] + lst[i+1];
1814 lst = lst.slice(0, i+1).concat(lst.slice(i+2));
1815 }
1816 } else if (["<sing", "<mult"].indexOf(stub) > -1) {
1817 if (lst[i].slice(-2) !== "/>" && lst[i+1].slice(0, 1) === "<") {
1818 lst[i] = lst[i] + lst[i+1];
1819 lst = lst.slice(0, i+1).concat(lst.slice(i+2));
1820 }
1821 }
1822 }
1823 }
1824 }
1825 return lst;
1826 }
1827
1828 function _decodeHtmlEntities(str) {
1829 return str
1830 .split("&amp;").join("&")
1831 .split("&quot;").join("\"")
1832 .split("&gt;").join(">").split("&lt;").join("<")
1833 .replace(/&#([0-9]{1,6});/gi, function(match, numStr) {
1834 var num = parseInt(numStr, 10); // read num as normal number
1835 return String.fromCharCode(num);
1836 })
1837 .replace(/&#x([a-f0-9]{1,6});/gi, function(match, numStr){
1838 var num = parseInt(numStr, 16); // read num as hex
1839 return String.fromCharCode(num);
1840 });
1841 }
1842
1843 function _getAttributes(elem) {
1844 var m = elem.match(/([^\'\"= ]+)=(?:\"[^\"]*\"|\'[^\']*\')/g);
1845 if (m) {
1846 for (var i=0,ilen=m.length;i<ilen;i++) {
1847 m[i] = m[i].replace(/=.*/, "");
1848 }
1849 }
1850 return m;
1851 }
1852
1853 function _getAttribute(elem, attr) {
1854 var rex = RegExp('^.*[ ]+' + attr + '=(\"(?:[^\"]*)\"|\'(?:[^\']*)\').*$');
1855 var m = elem.match(rex);
1856 return m ? m[1].slice(1, -1) : null;
1857 }
1858
1859 function _getTagName(elem) {
1860 var rex = RegExp("^<([^ />]+)");
1861 var m = elem.match(rex);
1862 return m ? m[1] : null;
1863 }
1864
1865
1866 function _castObjectFromOpeningTag(elem) {
1867 var obj = {};
1868 obj.name = _getTagName(elem);
1869 obj.attrs = {};
1870 var attributes = _getAttributes(elem);
1871 if (attributes) {
1872 for (var i=0,ilen=attributes.length;i<ilen;i++) {
1873 var attr = {
1874 name: attributes[i],
1875 value: _getAttribute(elem, attributes[i])
1876 }
1877 obj.attrs[attr.name] = _decodeHtmlEntities(attr.value);
1878 }
1879 }
1880 obj.children = [];
1881 return obj;
1882 }
1883
1884 function _extractTextFromCompositeElement(elem) {
1885 var m = elem.match(/^.*>([^<]*)<.*$/);
1886 return _decodeHtmlEntities(m[1]);
1887 }
1888
1889 function _appendToChildren(obj) {
1890 _stack.slice(-1)[0].push(obj);
1891 }
1892
1893 function _extendStackWithNewChildren(obj) {
1894 _stack.push(obj.children);
1895 }
1896
1897 function processElement(elem) {
1898 var obj;
1899 if (elem.slice(1).indexOf('<') > -1) {
1900 // withtext
1901 var tag = elem.slice(0, elem.indexOf('>')+1);
1902 obj = _castObjectFromOpeningTag(tag);
1903 obj.children = [_extractTextFromCompositeElement(elem)];
1904 _appendToChildren(obj);
1905 } else if (elem.slice(-2) === '/>') {
1906 // singleton
1907 obj = _castObjectFromOpeningTag(elem);
1908 // Empty term as singleton
1909 if (_getTagName(elem) === 'term') {
1910 obj.children.push('');
1911 }
1912 _appendToChildren(obj);
1913 } else if (elem.slice(0, 2) === '</') {
1914 // close
1915 _stack.pop();
1916 } else {
1917 // open
1918 obj = _castObjectFromOpeningTag(elem);
1919 _appendToChildren(obj)
1920 _extendStackWithNewChildren(obj);
1921 }
1922 }
1923
1924 var lst = _listifyString(str);
1925
1926 for (var i=0,ilen=lst.length;i<ilen;i++) {
1927 var elem = lst[i];
1928 processElement(elem);
1929 }
1930 return _obj.children[0];
1931}
1932
1933/**
1934 * Functions for parsing an XML object using E4X.
1935 */
1936
1937CSL.XmlDOM = function (dataObj) {
1938 this.dataObj = dataObj;
1939 if ("undefined" == typeof DOMParser) {
1940 DOMParser = function() {};
1941 DOMParser.prototype.parseFromString = function(str, contentType) {
1942 if ("undefined" != typeof ActiveXObject) {
1943 var xmldata = new ActiveXObject('MSXML.DomDocument');
1944 xmldata.async = false;
1945 xmldata.loadXML(str);
1946 return xmldata;
1947 } else if ("undefined" != typeof XMLHttpRequest) {
1948 var xmldata = new XMLHttpRequest;
1949 if (!contentType) {
1950 contentType = 'text/xml';
1951 }
1952 xmldata.open('GET', 'data:' + contentType + ';charset=utf-8,' + encodeURIComponent(str), false);
1953 if(xmldata.overrideMimeType) {
1954 xmldata.overrideMimeType(contentType);
1955 }
1956 xmldata.send(null);
1957 return xmldata.responseXML;
1958 } else if ("undefined" != typeof marknote) {
1959 var parser = new marknote.Parser();
1960 return parser.parse(str);
1961 }
1962 };
1963 this.hasAttributes = function (node) {
1964 var ret;
1965 if (node.attributes && node.attributes.length) {
1966 ret = true;
1967 } else {
1968 ret = false;
1969 }
1970 return ret;
1971 };
1972 } else {
1973 /*
1974 this.hasAttributes = function (node) {
1975 return node["hasAttributes"]();
1976 };
1977 */
1978 this.hasAttributes = function (node) {
1979 var ret;
1980 if (node.attributes && node.attributes.length) {
1981 ret = true;
1982 } else {
1983 ret = false;
1984 }
1985 return ret;
1986 };
1987 }
1988 this.importNode = function (doc, srcElement) {
1989 var ret;
1990 if ("undefined" == typeof doc.importNode) {
1991 ret = this._importNode(doc, srcElement, true);
1992 } else {
1993 ret = doc.importNode(srcElement, true);
1994 }
1995 return ret;
1996 };
1997 // In case importNode is not available.
1998 // Thx + hat tip to Anthony T. Holdener III
1999 // http://www.alistapart.com/articles/crossbrowserscripting
2000 // cases 3, 4, 8 = text, cdata, comment
2001 this._importNode = function(doc, node, allChildren) {
2002 switch (node.nodeType) {
2003 // element node
2004 case 1:
2005 var newNode = doc.createElement(node.nodeName);
2006 if (node.attributes && node.attributes.length > 0)
2007 for (var i = 0, il = node.attributes.length; i < il;)
2008 newNode.setAttribute(node.attributes[i].nodeName, node.getAttribute(node.attributes[i++].nodeName));
2009 if (allChildren && node.childNodes && node.childNodes.length > 0)
2010 for (var i = 0, il = node.childNodes.length; i < il;)
2011 newNode.appendChild(this._importNode(doc, node.childNodes[i++], allChildren));
2012 return newNode;
2013 break;
2014 case 3:
2015 case 4:
2016 case 8:
2017 // Drop comments on the floor as well.
2018 //return doc.createTextNode(node.nodeValue);
2019 //break;
2020 }
2021 };
2022 this.parser = new DOMParser();
2023
2024 // This seems horribly tormented, but there might be a reason for it.
2025 // Perhaps this was the only way I found to get namespacing to work ... ?
2026 var str = "<docco><institution institution-parts=\"long\" delimiter=\", \" substitute-use-first=\"1\" use-last=\"1\"><institution-part name=\"long\"/></institution></docco>";
2027 var inst_doc = this.parser.parseFromString(str, "text/xml");
2028 var inst_node = inst_doc.getElementsByTagName("institution");
2029 this.institution = inst_node.item(0);
2030 var inst_part_node = inst_doc.getElementsByTagName("institution-part");
2031 this.institutionpart = inst_part_node.item(0);
2032 this.ns = "http://purl.org/net/xbiblio/csl";
2033};
2034
2035/**
2036 * No need for cleaning with the DOM, I think. This will probably just be a noop.
2037 * But first, let's get XML mode switching up and running.
2038 */
2039CSL.XmlDOM.prototype.clean = function (xml) {
2040 xml = xml.replace(/<\?[^?]+\?>/g, "");
2041 xml = xml.replace(/<![^>]+>/g, "");
2042 xml = xml.replace(/^\s+/, "");
2043 xml = xml.replace(/\s+$/, "");
2044 xml = xml.replace(/^\n*/, "");
2045 return xml;
2046};
2047
2048
2049/**
2050 * Methods to call on a node.
2051 */
2052CSL.XmlDOM.prototype.getStyleId = function (myxml, styleName) {
2053 var text = "";
2054 var tagName = "id";
2055 if (styleName) {
2056 tagName = "title";
2057 }
2058 var node = myxml.getElementsByTagName(tagName);
2059 if (node && node.length) {
2060 node = node.item(0);
2061 }
2062 if (node) {
2063 // W3C conformant browsers
2064 text = node.textContent;
2065 }
2066 if (!text) {
2067 // Opera, IE 6 & 7
2068 text = node.innerText;
2069 }
2070 if (!text) {
2071 // Safari
2072 text = node.innerHTML;
2073 }
2074 return text;
2075};
2076
2077CSL.XmlDOM.prototype.children = function (myxml) {
2078 var children, pos, len, ret;
2079 if (myxml) {
2080 ret = [];
2081 children = myxml.childNodes;
2082 for (pos = 0, len = children.length; pos < len; pos += 1) {
2083 if (children[pos].nodeName != "#text") {
2084 ret.push(children[pos]);
2085 }
2086 }
2087 return ret;
2088 } else {
2089 return [];
2090 }
2091};
2092
2093CSL.XmlDOM.prototype.nodename = function (myxml) {
2094 var ret = myxml.nodeName;
2095 return ret;
2096};
2097
2098CSL.XmlDOM.prototype.attributes = function (myxml) {
2099 var ret, attrs, attr, key, xml, pos, len;
2100 ret = new Object();
2101 if (myxml && this.hasAttributes(myxml)) {
2102 attrs = myxml.attributes;
2103 for (pos = 0, len=attrs.length; pos < len; pos += 1) {
2104 attr = attrs[pos];
2105 ret["@" + attr.name] = attr.value;
2106 }
2107 }
2108 return ret;
2109};
2110
2111
2112CSL.XmlDOM.prototype.content = function (myxml) {
2113 var ret;
2114 if ("undefined" != typeof myxml.textContent) {
2115 ret = myxml.textContent;
2116 } else if ("undefined" != typeof myxml.innerText) {
2117 ret = myxml.innerText;
2118 } else {
2119 ret = myxml.txt;
2120 }
2121 return ret;
2122};
2123
2124
2125CSL.XmlDOM.prototype.namespace = {
2126 "xml":"http://www.w3.org/XML/1998/namespace"
2127}
2128
2129CSL.XmlDOM.prototype.numberofnodes = function (myxml) {
2130 if (myxml) {
2131 return myxml.length;
2132 } else {
2133 return 0;
2134 }
2135};
2136
2137CSL.XmlDOM.prototype.getAttributeName = function (attr) {
2138 var ret = attr.name;
2139 return ret;
2140}
2141
2142CSL.XmlDOM.prototype.getAttributeValue = function (myxml,name,namespace) {
2143 var ret = "";
2144 if (namespace) {
2145 name = namespace+":"+name;
2146 }
2147 if (myxml && this.hasAttributes(myxml) && myxml.getAttribute(name)) {
2148 ret = myxml.getAttribute(name);
2149 }
2150 return ret;
2151}
2152
2153//
2154// Can't this be, you know ... simplified?
2155//
2156CSL.XmlDOM.prototype.getNodeValue = function (myxml,name) {
2157 var ret = null;
2158 if (name){
2159 var vals = myxml.getElementsByTagName(name);
2160 if (vals.length > 0) {
2161 if ("undefined" != typeof vals[0].textContent) {
2162 ret = vals[0].textContent;
2163 } else if ("undefined" != typeof vals[0].innerText) {
2164 ret = vals[0].innerText;
2165 } else {
2166 ret = vals[0].text;
2167 }
2168 }
2169 }
2170 if (ret === null && myxml && myxml.childNodes && (myxml.childNodes.length == 0 || (myxml.childNodes.length == 1 && myxml.firstChild.nodeName == "#text"))) {
2171 if ("undefined" != typeof myxml.textContent) {
2172 ret = myxml.textContent;
2173 } else if ("undefined" != typeof myxml.innerText) {
2174 ret = myxml.innerText;
2175 } else {
2176 ret = myxml.text;
2177 }
2178 }
2179 if (ret === null) {
2180 ret = myxml;
2181 }
2182 return ret;
2183}
2184
2185CSL.XmlDOM.prototype.setAttributeOnNodeIdentifiedByNameAttribute = function (myxml,nodename,partname,attrname,val) {
2186 var pos, len, xml, nodes, node;
2187 if (attrname.slice(0,1) === '@'){
2188 attrname = attrname.slice(1);
2189 }
2190 nodes = myxml.getElementsByTagName(nodename);
2191 for (pos = 0, len = nodes.length; pos < len; pos += 1) {
2192 node = nodes[pos];
2193 if (node.getAttribute("name") != partname) {
2194 continue;
2195 }
2196 node.setAttribute(attrname, val);
2197 }
2198}
2199
2200CSL.XmlDOM.prototype.deleteNodeByNameAttribute = function (myxml,val) {
2201 var pos, len, node, nodes;
2202 nodes = myxml.childNodes;
2203 for (pos = 0, len = nodes.length; pos < len; pos += 1) {
2204 node = nodes[pos];
2205 if (!node || node.nodeType == node.TEXT_NODE) {
2206 continue;
2207 }
2208 if (this.hasAttributes(node) && node.getAttribute("name") == val) {
2209 myxml.removeChild(nodes[pos]);
2210 }
2211 }
2212}
2213
2214CSL.XmlDOM.prototype.deleteAttribute = function (myxml,attr) {
2215 myxml.removeAttribute(attr);
2216}
2217
2218CSL.XmlDOM.prototype.setAttribute = function (myxml,attr,val) {
2219 if (!myxml.ownerDocument) {
2220 myxml = myxml.firstChild;
2221 }
2222 // "unknown" to satisfy IE8, which crashes when setAttribute
2223 // is checked directly as a property, and report its type as
2224 // "unknown".
2225 // Many thanks to Phil Lord for tracing the cause of the fault.
2226 if (["function", "unknown"].indexOf(typeof myxml.setAttribute) > -1) {
2227 myxml.setAttribute(attr, val);
2228 }
2229 return false;
2230}
2231
2232CSL.XmlDOM.prototype.nodeCopy = function (myxml) {
2233 var cloned_node = myxml.cloneNode(true);
2234 return cloned_node;
2235}
2236
2237CSL.XmlDOM.prototype.getNodesByName = function (myxml,name,nameattrval) {
2238 var ret, nodes, node, pos, len;
2239 ret = [];
2240 nodes = myxml.getElementsByTagName(name);
2241 for (pos = 0, len = nodes.length; pos < len; pos += 1) {
2242 node = nodes.item(pos);
2243 if (nameattrval && !(this.hasAttributes(node) && node.getAttribute("name") == nameattrval)) {
2244// if (nameattrval && !(this.attributes && node.attributes.name && node.attributes.name.value == nameattrval)) {
2245 continue;
2246 }
2247 ret.push(node);
2248 }
2249 return ret;
2250}
2251
2252CSL.XmlDOM.prototype.nodeNameIs = function (myxml,name) {
2253 if (name == myxml.nodeName) {
2254 return true;
2255 }
2256 return false;
2257}
2258
2259CSL.XmlDOM.prototype.makeXml = function (myxml) {
2260 var ret, topnode;
2261 if (!myxml) {
2262 myxml = "<docco><bogus/></docco>";
2263 }
2264 myxml = myxml.replace(/\s*<\?[^>]*\?>\s*\n*/g, "");
2265 var nodetree = this.parser.parseFromString(myxml, "application/xml");
2266 return nodetree.firstChild;
2267};
2268
2269CSL.XmlDOM.prototype.insertChildNodeAfter = function (parent,node,pos,datexml) {
2270 var myxml, xml;
2271 myxml = this.importNode(node.ownerDocument, datexml);
2272 parent.replaceChild(myxml, node);
2273 return parent;
2274};
2275
2276CSL.XmlDOM.prototype.insertPublisherAndPlace = function(myxml) {
2277 var group = myxml.getElementsByTagName("group");
2278 for (var i = 0, ilen = group.length; i < ilen; i += 1) {
2279 var node = group.item(i);
2280 var skippers = [];
2281 for (var j = 0, jlen = node.childNodes.length; j < jlen; j += 1) {
2282 if (node.childNodes.item(j).nodeType !== 1) {
2283 skippers.push(j);
2284 }
2285 }
2286 if (node.childNodes.length - skippers.length === 2) {
2287 var twovars = [];
2288 for (var j = 0, jlen = 2; j < jlen; j += 1) {
2289 if (skippers.indexOf(j) > -1) {
2290 continue;
2291 }
2292 var child = node.childNodes.item(j);
2293 var subskippers = [];
2294 for (var k = 0, klen = child.childNodes.length; k < klen; k += 1) {
2295 if (child.childNodes.item(k).nodeType !== 1) {
2296 subskippers.push(k);
2297 }
2298 }
2299 if (child.childNodes.length - subskippers.length === 0) {
2300 twovars.push(child.getAttribute('variable'));
2301 if (child.getAttribute('suffix')
2302 || child.getAttribute('prefix')) {
2303 twovars = [];
2304 break;
2305 }
2306 }
2307 }
2308 if (twovars.indexOf("publisher") > -1 && twovars.indexOf("publisher-place") > -1) {
2309 node.setAttribute('has-publisher-and-publisher-place', true);
2310 }
2311 }
2312 }
2313};
2314
2315CSL.XmlDOM.prototype.isChildOfSubstitute = function(node) {
2316 if (node.parentNode) {
2317 if (node.parentNode.tagName.toLowerCase() === "substitute") {
2318 return true;
2319 } else {
2320 return this.isChildOfSubstitute(node.parentNode);
2321 }
2322 }
2323 return false;
2324};
2325
2326CSL.XmlDOM.prototype.addMissingNameNodes = function(myxml) {
2327 var nameslist = myxml.getElementsByTagName("names");
2328 for (var i = 0, ilen = nameslist.length; i < ilen; i += 1) {
2329 var names = nameslist.item(i);
2330 var namelist = names.getElementsByTagName("name");
2331 if ((!namelist || namelist.length === 0)
2332 && !this.isChildOfSubstitute(names)) {
2333
2334 var doc = names.ownerDocument;
2335 var name = doc.createElement("name");
2336 names.appendChild(name);
2337 }
2338 }
2339};
2340
2341
2342CSL.XmlDOM.prototype.addInstitutionNodes = function(myxml) {
2343 var names, thenames, institution, theinstitution, theinstitutionpart, name, thename, xml, pos, len;
2344 names = myxml.getElementsByTagName("names");
2345 for (pos = 0, len = names.length; pos < len; pos += 1) {
2346 thenames = names.item(pos);
2347 name = thenames.getElementsByTagName("name");
2348 if (name.length == 0) {
2349 continue;
2350 }
2351 institution = thenames.getElementsByTagName("institution");
2352 if (institution.length == 0) {
2353 theinstitution = this.importNode(myxml.ownerDocument, this.institution);
2354 theinstitutionpart = theinstitution.getElementsByTagName("institution-part").item(0);
2355 thename = name.item(0);
2356 thenames.insertBefore(theinstitution, thename.nextSibling);
2357 for (var j = 0, jlen = CSL.INSTITUTION_KEYS.length; j < jlen; j += 1) {
2358 var attrname = CSL.INSTITUTION_KEYS[j];
2359 var attrval = thename.getAttribute(attrname);
2360 if (attrval) {
2361 theinstitutionpart.setAttribute(attrname, attrval);
2362 }
2363 }
2364 var nameparts = thename.getElementsByTagName("name-part");
2365 for (var j = 0, jlen = nameparts.length; j < jlen; j += 1) {
2366 if ('family' === nameparts[j].getAttribute('name')) {
2367 for (var k = 0, klen = CSL.INSTITUTION_KEYS.length; k < klen; k += 1) {
2368 var attrname = CSL.INSTITUTION_KEYS[k];
2369 var attrval = nameparts[j].getAttribute(attrname);
2370 if (attrval) {
2371 theinstitutionpart.setAttribute(attrname, attrval);
2372 }
2373 }
2374 }
2375 }
2376 }
2377 }
2378};
2379
2380
2381CSL.XmlDOM.prototype.flagDateMacros = function(myxml) {
2382 var pos, len, thenode, thedate;
2383 var nodes = myxml.getElementsByTagName("macro");
2384 for (pos = 0, len = nodes.length; pos < len; pos += 1) {
2385 thenode = nodes.item(pos);
2386 thedate = thenode.getElementsByTagName("date");
2387 if (thedate.length) {
2388 thenode.setAttribute('macro-has-date', 'true');
2389 }
2390 }
2391};
2392
2393
2394/*global CSL: true */
2395
2396CSL.setupXml = function(xmlObject) {
2397 var dataObj = {};
2398 var parser = null;
2399 if ("undefined" !== typeof xmlObject) {
2400 if ("string" === typeof xmlObject) {
2401 xmlObject = xmlObject.replace("^\uFEFF", "")
2402 .replace(/^\s+/, "");
2403 if (xmlObject.slice(0, 1) === "<") {
2404 // Assume serialized XML
2405 dataObj = CSL.parseXml(xmlObject);
2406 } else {
2407 // Assume serialized JSON
2408 dataObj = JSON.parse(xmlObject);
2409 }
2410 parser = new CSL.XmlJSON(dataObj);
2411 } else if ("undefined" !== typeof xmlObject.getAttribute) {
2412 // Assume DOM instance
2413 parser = new CSL.XmlDOM(xmlObject);
2414 } else if ("undefined" !== typeof xmlObject.toXMLString) {
2415 // Assume E4X object
2416 parser = new CSL.XmlE4X(xmlObject);
2417 } else {
2418 // Assume JS object
2419 parser = new CSL.XmlJSON(xmlObject);
2420 }
2421 } else {
2422 CSL.error("unable to parse XML input");
2423 }
2424 if (!parser) {
2425 CSL.error("citeproc-js error: unable to parse CSL style or locale object");
2426 }
2427 return parser;
2428};
2429
2430/*global CSL: true */
2431
2432CSL.getSortCompare = function (default_locale) {
2433 if (CSL.stringCompare) {
2434 return CSL.stringCompare;
2435 }
2436 var strcmp;
2437 var strcmp_opts = {
2438 sensitivity:"base",
2439 ignorePunctuation:true,
2440 numeric:true
2441 };
2442 // In order, attempt the following:
2443 // (1) Set locale collation from processor language
2444 // (2) Use localeCompare()
2445 if (!default_locale) {
2446 default_locale = "en-US";
2447 }
2448 strcmp = function (a, b) {
2449 //var ret = a.toLocaleLowerCase().localeCompare(b.toLocaleLowerCase(),default_locale,strcmp_opts);
2450 // print(ret+' ('+a+') :: ('+b+')');
2451 //return ret;
2452 return a.toLocaleLowerCase().localeCompare(b.toLocaleLowerCase(),default_locale,strcmp_opts);
2453 };
2454 var stripPunct = function (str) {
2455 return str.replace(/^[\[\]\'\"]*/g, "");
2456 };
2457 var getBracketPreSort = function () {
2458 if (!strcmp("[x","x")) {
2459 return false;
2460 } else {
2461 return function (a, b) {
2462 return strcmp(stripPunct(a), stripPunct(b));
2463 };
2464 }
2465 };
2466 var bracketPreSort = getBracketPreSort();
2467 var sortCompare = function (a, b) {
2468 if (bracketPreSort) {
2469 return bracketPreSort(a, b);
2470 } else {
2471 return strcmp(a, b);
2472 }
2473 };
2474 return sortCompare;
2475};
2476
2477/*global CSL: true */
2478
2479CSL.ambigConfigDiff = function(a, b) {
2480 var pos, len, ppos, llen;
2481 // return of true means the ambig configs differ
2482 if (a.names.length !== b.names.length) {
2483 //print(" (1)");
2484 return 1;
2485 } else {
2486 for (pos = 0, len = a.names.length; pos < len; pos += 1) {
2487 if (a.names[pos] !== b.names[pos]) {
2488 //print(" (2) "+a.names[pos]+" "+b.names[pos]);
2489 return 1;
2490 } else {
2491 for (ppos = 0, llen = a.givens[pos]; ppos < llen; ppos += 1) {
2492 if (a.givens[pos][ppos] !== b.givens[pos][ppos]) {
2493 //print(" (3): "+a.givens[pos][ppos]+" "+b.givens[pos][ppos]+" "+pos+"/"+ppos+" "+b.givens[pos]);
2494 return 1;
2495 }
2496 }
2497 }
2498 }
2499 }
2500 if (a.disambiguate != b.disambiguate) {
2501 //print(" (4) "+a.disambiguate+" "+b.disambiguate);
2502 return 1;
2503 }
2504 if (a.year_suffix !== b.year_suffix) {
2505 //print(" (5) "+a.year_suffix+" "+b.year_suffix);
2506 return 1;
2507 }
2508 return 0;
2509};
2510
2511CSL.cloneAmbigConfig = function (config, oldconfig) {
2512 var i, ilen, j, jlen, param;
2513 var ret = {};
2514 ret.names = [];
2515 ret.givens = [];
2516 ret.year_suffix = false;
2517 ret.disambiguate = false;
2518 for (i = 0, ilen = config.names.length; i < ilen; i += 1) {
2519 param = config.names[i];
2520 // Fixes update bug affecting plugins, without impacting
2521 // efficiency with update of large numbers of year-suffixed
2522 // items.
2523 ret.names[i] = param;
2524 }
2525 for (i = 0, ilen = config.givens.length; i < ilen; i += 1) {
2526 param = [];
2527 for (j = 0, jlen = config.givens[i].length; j < jlen; j += 1) {
2528 // condition at line 312 of disambiguate.js protects against negative
2529 // values of j
2530 param.push(config.givens[i][j]);
2531 }
2532 ret.givens.push(param);
2533 }
2534 // XXXX Is this necessary at all?
2535 if (oldconfig) {
2536 ret.year_suffix = oldconfig.year_suffix;
2537 ret.disambiguate = oldconfig.disambiguate;
2538 } else {
2539 ret.year_suffix = config.year_suffix;
2540 ret.disambiguate = config.disambiguate;
2541 }
2542 return ret;
2543};
2544
2545/**
2546 * Return current base configuration for disambiguation
2547 */
2548CSL.getAmbigConfig = function () {
2549 var config, ret;
2550 config = this.tmp.disambig_request;
2551 if (!config) {
2552 config = this.tmp.disambig_settings;
2553 }
2554 var ret = CSL.cloneAmbigConfig(config);
2555 return ret;
2556};
2557
2558/**
2559 * Return max values for disambiguation
2560 */
2561CSL.getMaxVals = function () {
2562 return this.tmp.names_max.mystack.slice();
2563};
2564
2565/**
2566 * Return min value for disambiguation
2567 */
2568CSL.getMinVal = function () {
2569 return this.tmp["et-al-min"];
2570};
2571
2572/*global CSL: true */
2573
2574/* For node execution pretty-printing (see below) */
2575
2576/*
2577var INDENT = "";
2578*/
2579
2580CSL.tokenExec = function (token, Item, item) {
2581 // Called on state object
2582 var next, maybenext, exec, debug;
2583 debug = false;
2584 next = token.next;
2585 maybenext = false;
2586
2587 /* Pretty-print node executions */
2588
2589 /*
2590 if (["if", "else-if", "else"].indexOf(token.name) === -1) {
2591 if (token.tokentype == 1) {
2592 INDENT = INDENT.slice(0, -2);
2593 }
2594 }
2595 this.sys.print(INDENT + "---> Token: " + token.name + " (" + token.tokentype + ") in " + this.tmp.area + ", " + this.output.current.mystack.length);
2596 if (["if", "else-if", "else"].indexOf(token.name) === -1) {
2597 if (token.tokentype == 0) {
2598 INDENT += " ";
2599 }
2600 }
2601 */
2602
2603 var record = function (result) {
2604 if (result) {
2605 this.tmp.jump.replace("succeed");
2606 return token.succeed;
2607 } else {
2608 this.tmp.jump.replace("fail");
2609 return token.fail;
2610 }
2611 };
2612 if (token.test) {
2613 next = record.call(this,token.test(Item, item));
2614 }
2615 for (var i=0,ilen=token.execs.length;i<ilen;i++) {
2616 exec = token.execs[i];
2617 maybenext = exec.call(token, this, Item, item);
2618 if (maybenext) {
2619 next = maybenext;
2620 }
2621 }
2622 //SNIP-START
2623 if (debug) {
2624 CSL.debug(token.name + " (" + token.tokentype + ") ---> done");
2625 }
2626 //SNIP-END
2627 return next;
2628};
2629
2630/**
2631 * Macro expander.
2632 * <p>Called on the state object.</p>
2633 */
2634CSL.expandMacro = function (macro_key_token, target) {
2635 var mkey, macro_nodes, end_of_macro, func;
2636
2637 mkey = macro_key_token.postponed_macro;
2638
2639 var sort_direction = macro_key_token.strings.sort_direction;
2640
2641 // Decorations and affixes are in wrapper applied in cs:text
2642 macro_key_token = new CSL.Token("group", CSL.START);
2643
2644 var hasDate = false;
2645 var macroid = false;
2646 macro_nodes = this.cslXml.getNodesByName(this.cslXml.dataObj, 'macro', mkey);
2647 if (macro_nodes.length) {
2648 macroid = this.cslXml.getAttributeValue(macro_nodes[0],'cslid');
2649 hasDate = this.cslXml.getAttributeValue(macro_nodes[0], "macro-has-date");
2650 }
2651 if (hasDate) {
2652 mkey = mkey + "@" + this.build.current_default_locale;
2653 func = function (state) {
2654 if (state.tmp.extension) {
2655 state.tmp["doing-macro-with-date"] = true;
2656 }
2657 };
2658 macro_key_token.execs.push(func);
2659 }
2660
2661 if (this.build.macro_stack.indexOf(mkey) > -1) {
2662 CSL.error("CSL processor error: call to macro \"" + mkey + "\" would cause an infinite loop");
2663 } else {
2664 this.build.macro_stack.push(mkey);
2665 }
2666
2667 macro_key_token.cslid = macroid;
2668
2669 if (CSL.MODULE_MACROS[mkey]) {
2670 macro_key_token.juris = mkey;
2671 this.opt.update_mode = CSL.POSITION;
2672 }
2673 // Macro group is treated as a real node in the style
2674 CSL.Node.group.build.call(macro_key_token, this, target, true);
2675
2676 // Node does not exist in the CSL
2677 if (!this.cslXml.getNodeValue(macro_nodes)) {
2678 CSL.error("CSL style error: undefined macro \"" + mkey + "\"");
2679 }
2680
2681 // Let's macro
2682 var mytarget = CSL.getMacroTarget.call(this, mkey);
2683 if (mytarget) {
2684 CSL.buildMacro.call(this, mytarget, macro_nodes);
2685 CSL.configureMacro.call(this, mytarget);
2686 }
2687 if (!this.build.extension) {
2688 var func = (function(macro_name) {
2689 return function (state, Item, item) {
2690 var next = 0;
2691 while (next < state.macros[macro_name].length) {
2692 next = CSL.tokenExec.call(state, state.macros[macro_name][next], Item, item);
2693 }
2694 };
2695 }(mkey));
2696 var text_node = new CSL.Token("text", CSL.SINGLETON);
2697 text_node.execs.push(func);
2698 target.push(text_node);
2699 }
2700
2701 // Decorations and affixes are in wrapper applied in cs:text
2702 end_of_macro = new CSL.Token("group", CSL.END);
2703 end_of_macro.strings.sort_direction = sort_direction;
2704
2705 if (hasDate) {
2706 func = function (state) {
2707 if (state.tmp.extension) {
2708 state.tmp["doing-macro-with-date"] = false;
2709 }
2710 };
2711 end_of_macro.execs.push(func);
2712 }
2713 if (macro_key_token.juris) {
2714 end_of_macro.juris = mkey;
2715 }
2716 // Macro group is treated as a real node in the style
2717 CSL.Node.group.build.call(end_of_macro, this, target, true);
2718
2719 this.build.macro_stack.pop();
2720};
2721
2722CSL.getMacroTarget = function (mkey) {
2723 var mytarget = false;
2724 if (this.build.extension) {
2725 mytarget = this[this.build.root + this.build.extension].tokens;
2726 } else if (!this.macros[mkey]) {
2727 mytarget = [];
2728 this.macros[mkey] = mytarget;
2729 }
2730 return mytarget;
2731};
2732
2733CSL.buildMacro = function (mytarget, macro_nodes) {
2734 var builder = CSL.makeBuilder(this, mytarget);
2735 var mynode;
2736 if ("undefined" === typeof macro_nodes.length) {
2737 mynode = macro_nodes;
2738 } else {
2739 mynode = macro_nodes[0];
2740 }
2741 builder(mynode);
2742};
2743
2744CSL.configureMacro = function (mytarget) {
2745 if (!this.build.extension) {
2746 this.configureTokenList(mytarget);
2747 }
2748};
2749
2750
2751/**
2752 * Convert XML node to token.
2753 * <p>This is called on an XML node. After extracting the name and attribute
2754 * information from the node, it performs three operations. Attribute information
2755 * relating to output formatting is stored on the node as an array of tuples,
2756 * which fixes the sequence of execution of output functions to be invoked
2757 * in the next phase of processing. Other attribute information is reduced
2758 * to functions, and is pushed into an array on the token in no particular
2759 * order, for later execution. The element name is used as a key to
2760 * invoke the relevant <code>build</code> method of the target element.
2761 * Element methods are defined in {@link CSL.Node}.</p>
2762 * @param {Object} state The state object returned by {@link CSL.Engine}.
2763 * @param {Int} tokentype A CSL namespace constant (<code>CSL.START</code>,
2764 * <code>CSL.END</code> or <code>CSL.SINGLETON</code>.
2765 */
2766CSL.XmlToToken = function (state, tokentype, explicitTarget, var_stack) {
2767 var name, txt, attrfuncs, attributes, decorations, token, key, target;
2768 name = state.cslXml.nodename(this);
2769 //CSL.debug(tokentype + " : " + name);
2770 if (state.build.skip && state.build.skip !== name) {
2771 return;
2772 }
2773 if (!name) {
2774 txt = state.cslXml.content(this);
2775 if (txt) {
2776 state.build.text = txt;
2777 }
2778 return;
2779 }
2780 if (!CSL.Node[state.cslXml.nodename(this)]) {
2781 CSL.error("Undefined node name \"" + name + "\".");
2782 }
2783 attrfuncs = [];
2784 attributes = state.cslXml.attributes(this);
2785 decorations = CSL.setDecorations.call(this, state, attributes);
2786 token = new CSL.Token(name, tokentype);
2787 if (tokentype !== CSL.END || name === "if" || name === "else-if" || name === "layout") {
2788 //
2789 // xml: more xml stuff
2790 //
2791 for (var key in attributes) {
2792 if (attributes.hasOwnProperty(key)) {
2793 if (tokentype === CSL.END && key !== "@language" && key !== "@locale") {
2794 continue;
2795 }
2796 if (attributes.hasOwnProperty(key)) {
2797 if (CSL.Attributes[key]) {
2798 try {
2799 CSL.Attributes[key].call(token, state, "" + attributes[key]);
2800 } catch (e) {
2801 CSL.error(key + " attribute: " + e);
2802 }
2803 } else {
2804 CSL.debug("warning: undefined attribute \""+key+"\" in style");
2805 }
2806 }
2807 }
2808 }
2809 token.decorations = decorations;
2810 if (CSL.DATE_VARIABLES.indexOf(attributes['@variable']) > -1) {
2811 var_stack.push(token.variables);
2812 }
2813 } else if (tokentype === CSL.END && attributes['@variable']) {
2814 token.hasVariable = true;
2815 if (CSL.DATE_VARIABLES.indexOf(attributes['@variable']) > -1) {
2816 token.variables = var_stack.pop();
2817 }
2818 }
2819 //
2820 // !!!!!: eliminate diversion of tokens to separate
2821 // token list (formerly used for reading in macros
2822 // and terms).
2823 //
2824 if (explicitTarget) {
2825 target = explicitTarget;
2826 } else {
2827 target = state[state.build.area].tokens;
2828 }
2829 // True flags real nodes in the style
2830 CSL.Node[name].build.call(token, state, target, true);
2831};
2832
2833
2834
2835/*global CSL: true */
2836
2837
2838CSL.DateParser = function () {
2839
2840 /*
2841 * Fixed values
2842 */
2843
2844 // jse imperial years
2845 var epochPairs = [
2846 ["\u660E\u6CBB", 1867],
2847 ["\u5927\u6B63", 1911],
2848 ["\u662D\u548C", 1925],
2849 ["\u5E73\u6210", 1988]
2850 ];
2851
2852 // years by jse imperial epoch
2853 var epochYearByName = {};
2854 for (var i=0,ilen=epochPairs.length; i<ilen; i++) {
2855 var key = epochPairs[i][0];
2856 var val = epochPairs[i][1];
2857 epochYearByName[key] = val;
2858 }
2859
2860 var epochMatchStrings = [];
2861 var epochMap = {};
2862 for (var i=0,ilen=epochPairs.length; i<ilen; i++) {
2863 var pair = epochPairs[i];
2864 var val = pair[0];
2865 epochMatchStrings.push(val);
2866 epochMap[pair[0]] = pair[1];
2867 }
2868 var epochMatchString = epochMatchStrings.join("|");
2869
2870 // regular expression to trap year name and year
2871 // (splitter and matcher, to cope with ancient JS implementations)
2872 var epochSplitter = new RegExp("(?:" + epochMatchString + ")(?:[0-9]+)");
2873 var epochMatcher = new RegExp("(?:" + epochMatchString + ")(?:[0-9]+)", "g");
2874
2875 // regular expression for month or day kanji label
2876 var kanjiMonthDay = /(\u6708|\u5E74)/g;
2877
2878 // regular expression for year kanji label
2879 var kanjiYear = /\u65E5/g;
2880
2881 // regular expression for double-width Japanese range marker
2882 var kanjiRange = /\u301c/g;
2883
2884 // parsing regexps for normalized strings
2885 // raw materials
2886 var yearLast = "(?:[?0-9]{1,2}%%NUMD%%){0,2}[?0-9]{4}(?![0-9])";
2887 var yearFirst = "[?0-9]{4}(?:%%NUMD%%[?0-9]{1,2}){0,2}(?![0-9])";
2888 var numberVal = "[?0-9]{1,3}";
2889 var rangeSeparator = "[%%DATED%%]";
2890 var fuzzyChar = "[?~]";
2891 var chars = "[^\-\/\~\?0-9]+";
2892 var rexString = "(" + yearFirst + "|" + yearLast + "|" + numberVal + "|" + rangeSeparator + "|" + fuzzyChar + "|" + chars + ")";
2893 // composed regexps
2894 var rexDash = new RegExp(rexString.replace(/%%NUMD%%/g, "-").replace(/%%DATED%%/g, "-"));
2895 var rexDashSlash = new RegExp(rexString.replace(/%%NUMD%%/g, "-").replace(/%%DATED%%/g, "\/"));
2896 var rexSlashDash = new RegExp(rexString.replace(/%%NUMD%%/g, "\/").replace(/%%DATED%%/g, "-"));
2897
2898 /*
2899 * Mutable values
2900 */
2901
2902 // months
2903 var monthString = "january february march april may june july august september october november december spring summer fall winter spring summer";
2904 this.monthStrings = monthString.split(" ");
2905
2906 /*
2907 * Configuration functions
2908 */
2909
2910 this.setOrderDayMonth = function() {
2911 // preferred ordering for numeric dates
2912 this.monthGuess = 1;
2913 this.dayGuess = 0;
2914 };
2915
2916 this.setOrderMonthDay = function() {
2917 // preferred ordering for numeric dates
2918 this.monthGuess = 0;
2919 this.dayGuess = 1;
2920 };
2921
2922 this.resetDateParserMonths = function() {
2923 // Function to reset months to default.
2924 this.monthSets = [];
2925 for (var i=0,ilen=this.monthStrings.length; i<ilen; i++) {
2926 this.monthSets.push([this.monthStrings[i]]);
2927 }
2928 this.monthAbbrevs = [];
2929 for (var i=0,ilen=this.monthSets.length; i<ilen; i++) {
2930 this.monthAbbrevs.push([]);
2931 for (var j=0,jlen=this.monthSets[i].length; j<jlen; j++) {
2932 this.monthAbbrevs[i].push(this.monthSets[i][0].slice(0, 3));
2933 }
2934 }
2935 this.monthRexes = [];
2936 for (var i=0,ilen=this.monthAbbrevs.length; i<ilen; i++) {
2937 this.monthRexes.push(new RegExp("(?:" + this.monthAbbrevs[i].join("|") + ")"));
2938 }
2939 };
2940
2941 this.addDateParserMonths = function(lst) {
2942 // Extend list of months with an additional set of month abbreviations,
2943 // extending strings as required to resolve ambiguities.
2944
2945 // Normalize string to list
2946 if ("string" === typeof lst) {
2947 lst = lst.split(/\s+/);
2948 }
2949
2950 // Check that there are twelve (or sixteen) to add
2951 if (lst.length !== 12 && lst.length !== 16) {
2952 CSL.debug("month [+season] list of "+lst.length+", expected 12 or 16. Ignoring.");
2953 return;
2954 }
2955
2956 // Extend as necessary to resolve ambiguities
2957 // For each new month string ...
2958 for (var i=0,ilen=lst.length; i<ilen; i++) {
2959 var abbrevLength = null;
2960 var skip = false;
2961 var insert = 3;
2962 var extendedSets = {};
2963 for (var j=0,jlen=this.monthAbbrevs.length; j<jlen; j++) {
2964 extendedSets[j] = {};
2965 if (j === i) {
2966 // Mark for skipping if same as an existing abbreviation of same month
2967 for (var k=0,klen=this.monthAbbrevs[i].length; k<klen; k++) {
2968 if (this.monthAbbrevs[i][k] === lst[i].slice(0, this.monthAbbrevs[i][k].length)) {
2969 skip = true;
2970 break;
2971 }
2972 }
2973 } else {
2974 // Mark for extending if same as existing abbreviation of any expression of another month
2975 for (var k=0,klen=this.monthAbbrevs[j].length; k<klen; k++) {
2976 abbrevLength = this.monthAbbrevs[j][k].length;
2977 if (this.monthAbbrevs[j][k] === lst[i].slice(0, abbrevLength)) {
2978 while (this.monthSets[j][k].slice(0, abbrevLength) === lst[i].slice(0, abbrevLength)) {
2979 // Abort when full length is hit, otherwise extend
2980 if (abbrevLength > lst[i].length || abbrevLength > this.monthSets[j][k].length) {
2981 CSL.debug("unable to disambiguate month string in date parser: "+lst[i]);
2982 break;
2983 } else {
2984 // Mark both new entry and existing abbrev for extension
2985 abbrevLength += 1;
2986 }
2987 }
2988 insert = abbrevLength;
2989 extendedSets[j][k] = abbrevLength;
2990 }
2991 }
2992 }
2993 for (var jKey in extendedSets) {
2994 for (var kKey in extendedSets[jKey]) {
2995 abbrevLength = extendedSets[jKey][kKey];
2996 jKey = parseInt(jKey, 10);
2997 kKey = parseInt(kKey, 10);
2998 this.monthAbbrevs[jKey][kKey] = this.monthSets[jKey][kKey].slice(0, abbrevLength);
2999 }
3000 }
3001 }
3002 // Insert here
3003 if (!skip) {
3004 this.monthSets[i].push(lst[i]);
3005 this.monthAbbrevs[i].push(lst[i].slice(0, insert));
3006 }
3007 }
3008
3009 // Compose
3010 this.monthRexes = [];
3011 this.monthRexStrs = [];
3012 for (var i=0,ilen=this.monthAbbrevs.length; i<ilen; i++) {
3013 this.monthRexes.push(new RegExp("^(?:" + this.monthAbbrevs[i].join("|") + ")"));
3014 this.monthRexStrs.push("^(?:" + this.monthAbbrevs[i].join("|") + ")");
3015 }
3016 if (this.monthAbbrevs.length === 18) {
3017 for (var i=12,ilen=14; i<ilen; i++) {
3018 this.monthRexes[i+4] = new RegExp("^(?:" + this.monthAbbrevs[i].join("|") + ")");
3019 this.monthRexStrs[i+4] = "^(?:" + this.monthAbbrevs[i].join("|") + ")";
3020 }
3021 }
3022 };
3023
3024 /*
3025 * Conversion functions
3026 */
3027
3028 this.convertDateObjectToArray = function (thedate) {
3029 // Converts object in place and returns object
3030 thedate["date-parts"] = [];
3031 thedate["date-parts"].push([]);
3032 var slicelen = 0;
3033 var part;
3034 for (var i=0,ilen=3; i<ilen; i++) {
3035 part = ["year", "month", "day"][i];
3036 if (!thedate[part]) {
3037 break;
3038 }
3039 slicelen += 1;
3040 thedate["date-parts"][0].push(thedate[part]);
3041 delete thedate[part];
3042 }
3043 thedate["date-parts"].push([]);
3044 for (var i=0, ilen=slicelen; i<ilen; i++) {
3045 part = ["year_end", "month_end", "day_end"][i];
3046 if (!thedate[part]) {
3047 break;
3048 }
3049 thedate["date-parts"][1].push(thedate[part]);
3050 delete thedate[part];
3051 }
3052 if (thedate["date-parts"][0].length !== thedate["date-parts"][1].length) {
3053 thedate["date-parts"].pop();
3054 }
3055 return thedate;
3056 };
3057
3058 // XXXX String output is currently unable to represent ranges
3059 this.convertDateObjectToString = function(thedate) {
3060 // Returns string
3061 var ret = [];
3062 for (var i = 0, ilen = 3; i < ilen; i += 1) {
3063 if (thedate[CSL.DATE_PARTS_ALL[i]]) {
3064 ret.push(thedate[CSL.DATE_PARTS_ALL[i]]);
3065 } else {
3066 break;
3067 }
3068 }
3069 return ret.join("-");
3070 };
3071
3072 /*
3073 * Utility function
3074 */
3075
3076 this._parseNumericDate = function (ret, delim, suff, txt) {
3077 if (!suff) {
3078 suff = "";
3079 }
3080 var lst = txt.split(delim);
3081
3082 for (var i=0, ilen=lst.length; i<ilen; i++) {
3083 if (lst[i].length === 4) {
3084 ret[("year" + suff)] = lst[i].replace(/^0*/, "");
3085 if (!i) {
3086 lst = lst.slice(1);
3087 } else {
3088 lst = lst.slice(0, i);
3089 }
3090 break;
3091 }
3092 }
3093 for (var i=0,ilen=lst.length; i<ilen; i++) {
3094 lst[i] = parseInt(lst[i], 10);
3095 }
3096 if (lst.length === 1 || (lst.length === 2 && !lst[1])) {
3097 ret[("month" + suff)] = "" + lst[0];
3098 } else if (lst.length === 2) {
3099 if (lst[this.monthGuess] > 12) {
3100 ret[("month" + suff)] = "" + lst[this.dayGuess];
3101 ret[("day" + suff)] = "" + lst[this.monthGuess];
3102 } else {
3103 ret[("month" + suff)] = "" + lst[this.monthGuess];
3104 ret[("day" + suff)] = "" + lst[this.dayGuess];
3105 }
3106 }
3107 };
3108
3109 /*
3110 * Parsing functions
3111 */
3112
3113 this.parseDateToObject = function (txt) {
3114 //
3115 // Normalize the format and the year if it's a Japanese date
3116 //
3117 var orig = txt;
3118 var slashPos = -1;
3119 var dashPos = -1;
3120 var yearIsNegative = false;
3121 var lst;
3122 if (txt) {
3123 // If string leads with a minus sign, strip and memo it.
3124 if (txt.slice(0, 1) === "-") {
3125 yearIsNegative = true;
3126 txt = txt.slice(1);
3127 }
3128
3129 // If string is a number of 1 to 3 characters only, treat as year.
3130 if (txt.match(/^[0-9]{1,3}$/)) {
3131 while (txt.length < 4) {
3132 txt = "0" + txt;
3133 }
3134 }
3135
3136 // Normalize to string
3137 txt = "" + txt;
3138 // Remove things that look like times
3139 txt = txt.replace(/\s*[0-9]{2}:[0-9]{2}(?::[0-9]+)/,"");
3140 var m = txt.match(kanjiMonthDay);
3141 if (m) {
3142 txt = txt.replace(/\s+/g, "");
3143 txt = txt.replace(kanjiYear, "");
3144 txt = txt.replace(kanjiMonthDay, "-");
3145 txt = txt.replace(kanjiRange, "/");
3146 txt = txt.replace(/\-\//g, "/");
3147 txt = txt.replace(/-$/g,"");
3148
3149 // Tortuous workaround for IE6
3150 var slst = txt.split(epochSplitter);
3151 lst = [];
3152 var mm = txt.match(epochMatcher);
3153 if (mm) {
3154 var mmx = [];
3155 for (var i=0,ilen=mm.length; i<ilen; i++) {
3156 mmx = mmx.concat(mm[i].match(/([^0-9]+)([0-9]+)/).slice(1));
3157 }
3158 for (var i=0,ilen=slst.length; i<ilen; i++) {
3159 lst.push(slst[i]);
3160 if (i !== (ilen - 1)) {
3161 // pos is undeclared, and multiplying by 2 here is insane.
3162 var mmpos = (i * 2);
3163 lst.push(mmx[mmpos]);
3164 lst.push(mmx[mmpos + 1]);
3165 }
3166 }
3167 } else {
3168 lst = slst;
3169 }
3170 // workaround duly applied, this now works
3171 for (var i=1,ilen=lst.length; i<ilen; i+=3) {
3172 lst[i + 1] = epochMap[lst[i]] + parseInt(lst[i + 1], 10);
3173 lst[i] = "";
3174 }
3175 txt = lst.join("");
3176 txt = txt.replace(/\s*-\s*$/, "").replace(/\s*-\s*\//, "/");
3177 //
3178 // normalize date and identify delimiters
3179 //
3180 txt = txt.replace(/\.\s*$/, "");
3181
3182 // not sure what this is meant to do
3183 txt = txt.replace(/\.(?! )/, "");
3184
3185 slashPos = txt.indexOf("/");
3186 dashPos = txt.indexOf("-");
3187 }
3188 }
3189 // drop punctuation from a.d., b.c.
3190 txt = txt.replace(/([A-Za-z])\./g, "$1");
3191
3192 var number = "";
3193 var note = "";
3194 var thedate = {};
3195 var rangeDelim;
3196 var dateDelim;
3197 if (txt.slice(0, 1) === "\"" && txt.slice(-1) === "\"") {
3198 thedate.literal = txt.slice(1, -1);
3199 return thedate;
3200 }
3201 if (slashPos > -1 && dashPos > -1) {
3202 var slashCount = txt.split("/");
3203 if (slashCount.length > 3) {
3204 rangeDelim = "-";
3205 txt = txt.replace(/\_/g, "-");
3206 dateDelim = "/";
3207 lst = txt.split(rexSlashDash);
3208 } else {
3209 rangeDelim = "/";
3210 txt = txt.replace(/\_/g, "/");
3211 dateDelim = "-";
3212 lst = txt.split(rexDashSlash);
3213 }
3214 } else {
3215 txt = txt.replace(/\//g, "-");
3216 txt = txt.replace(/\_/g, "-");
3217 rangeDelim = "-";
3218 dateDelim = "-";
3219 lst = txt.split(rexDash);
3220 }
3221 var ret = [];
3222 for (var i=0,ilen=lst.length; i<ilen; i++) {
3223 var m = lst[i].match(/^\s*([\-\/]|[^\-\/\~\?0-9]+|[\-~?0-9]+)\s*$/);
3224 if (m) {
3225 ret.push(m[1]);
3226 }
3227 }
3228 //
3229 // Phase 2
3230 //
3231 var delimPos = ret.indexOf(rangeDelim);
3232 var delims = [];
3233 var isRange = false;
3234 if (delimPos > -1) {
3235 delims.push([0, delimPos]);
3236 delims.push([(delimPos + 1), ret.length]);
3237 isRange = true;
3238 } else {
3239 delims.push([0, ret.length]);
3240 }
3241 //
3242 // For each side of a range divide ...
3243 //
3244 var suff = "";
3245
3246 for (var i=0,ilen=delims.length; i<ilen; i++) {
3247 var delim = delims[i];
3248 //
3249 // Process each element ...
3250 //
3251 var date = ret.slice(delim[0], delim[1]);
3252 outer:
3253 for (var j=0,jlen=date.length; j<jlen; j++) {
3254 var element = date[j];
3255 //
3256 // If it's a numeric date, process it.
3257 //
3258 if (element.indexOf(dateDelim) > -1) {
3259 this._parseNumericDate(thedate, dateDelim, suff, element);
3260 continue;
3261 }
3262 //
3263 // If it's an obvious year, record it.
3264 //
3265 if (element.match(/[0-9]{4}/)) {
3266 thedate[("year" + suff)] = element.replace(/^0*/, "");
3267 continue;
3268 }
3269 //
3270 // If it's a fuzzy marker, record it.
3271 //
3272 if (element === "~" || element === "?" || element === "c" || element.match(/^cir/)) {
3273 thedate.circa = true;
3274 }
3275 //
3276 // If it's a month, record it.
3277 //
3278 for (var k=0,klen=this.monthRexes.length; k<klen; k++) {
3279 if (element.toLocaleLowerCase().match(this.monthRexes[k])) {
3280 thedate[("month" + suff)] = "" + (parseInt(k, 10) + 1);
3281 continue outer;
3282 }
3283 }
3284 //
3285 // If it's a number, make a note of it
3286 //
3287 if (element.match(/^[0-9]+$/)) {
3288 number = element;
3289 }
3290 //
3291 // If it's a BC or AD marker, make a year of
3292 // any note. Separate, reverse the sign of the year
3293 // if it's BC.
3294 //
3295 if (element.toLocaleLowerCase().match(/^bc/) && number) {
3296 thedate[("year" + suff)] = "" + (number * -1);
3297 number = "";
3298 continue;
3299 }
3300 if (element.toLocaleLowerCase().match(/^ad/) && number) {
3301 thedate[("year" + suff)] = "" + number;
3302 number = "";
3303 continue;
3304 }
3305 //
3306 // If it's cruft, make a note of it
3307 //
3308 if (element.toLocaleLowerCase().match(/(?:mic|tri|hil|eas)/) && !thedate[("season" + suff)]) {
3309 note = element;
3310 continue;
3311 }
3312 }
3313 //
3314 // If at the end of the string there's still a note
3315 // hanging around, make a day of it.
3316 //
3317 if (number) {
3318 thedate[("day" + suff)] = number;
3319 number = "";
3320 }
3321 //
3322 // If at the end of the string there's cruft lying
3323 // around, and the season field is empty, put the
3324 // cruft there.
3325 //
3326 if (note && !thedate[("season" + suff)]) {
3327 thedate[("season" + suff)] = note.trim();
3328 note = "";
3329 }
3330 suff = "_end";
3331 }
3332 //
3333 // update any missing elements on each side of the divide
3334 // from the other
3335 //
3336 if (isRange) {
3337 for (var j=0,jlen=CSL.DATE_PARTS_ALL.length; j<jlen; j++) {
3338 var item = CSL.DATE_PARTS_ALL[j];
3339 if (thedate[item] && !thedate[(item + "_end")]) {
3340 thedate[(item + "_end")] = thedate[item];
3341 } else if (!thedate[item] && thedate[(item + "_end")]) {
3342 thedate[item] = thedate[(item + "_end")];
3343 }
3344 }
3345 }
3346 //
3347 // If there's no year, or if there only a year and a day, it's a failure; pass through the literal
3348 //
3349 if (!thedate.year || (thedate.year && thedate.day && !thedate.month)) {
3350 thedate = { "literal": orig };
3351 }
3352 var parts = ["year", "month", "day", "year_end", "month_end", "day_end"];
3353 for (var i=0,ilen=parts.length; i<ilen; i++) {
3354 var part = parts[i];
3355 if ("string" === typeof thedate[part] && thedate[part].match(/^[0-9]+$/)) {
3356 thedate[part] = parseInt(thedate[part], 10);
3357 }
3358
3359 }
3360 if (yearIsNegative && Object.keys(thedate).indexOf("year") > -1) {
3361 thedate.year = (thedate.year * -1);
3362 }
3363 return thedate;
3364 };
3365
3366 this.parseDateToArray = function(txt) {
3367 return this.convertDateObjectToArray(this.parseDateToObject(txt));
3368 };
3369
3370 this.parseDateToString = function(txt) {
3371 return this.convertDateObjectToString(this.parseDateToObject(txt));
3372 };
3373
3374 this.parse = function(txt) {
3375 return this.parseDateToObject(txt);
3376 };
3377
3378 /*
3379
3380 * Setup
3381 */
3382
3383 this.setOrderMonthDay();
3384 this.resetDateParserMonths();
3385};
3386CSL.DateParser = new CSL.DateParser();
3387
3388/*global CSL: true */
3389
3390CSL.Engine = function (sys, style, lang, forceLang) {
3391 var attrs, langspec;
3392 this.processor_version = CSL.PROCESSOR_VERSION;
3393 this.csl_version = "1.0";
3394 this.sys = sys;
3395
3396 if (typeof Object.assign != 'function') {
3397 // Must be writable: true, enumerable: false, configurable: true
3398 Object.defineProperty(Object, "assign", {
3399 value: function assign(target) { // .length of function is 2
3400 'use strict';
3401 if (target == null) { // TypeError if undefined or null
3402 throw new TypeError('Cannot convert undefined or null to object');
3403 }
3404
3405 var to = Object(target);
3406
3407 for (var index = 1; index < arguments.length; index++) {
3408 var nextSource = arguments[index];
3409
3410 if (nextSource != null) { // Skip over if undefined or null
3411 for (var nextKey in nextSource) {
3412 // Avoid bugs when hasOwnProperty is shadowed
3413 if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
3414 to[nextKey] = nextSource[nextKey];
3415 }
3416 }
3417 }
3418 }
3419 return to;
3420 },
3421 writable: true,
3422 configurable: true
3423 });
3424 }
3425
3426 // XXX This may be excess code. Given the normalization performed on
3427 // XXX the output queue before variableWrapper() is run, a single
3428 // XXX space should be the most cruft that we ever see before a variable.
3429 if (sys.variableWrapper) {
3430 CSL.VARIABLE_WRAPPER_PREPUNCT_REX = new RegExp('^([' + [" "].concat(CSL.SWAPPING_PUNCTUATION).join("") + ']*)(.*)');
3431 }
3432 // XXXX This should be restored -- temporarily suspended for testing of JSON style support.
3433 if (CSL.retrieveStyleModule) {
3434 this.sys.retrieveStyleModule = CSL.retrieveStyleModule;
3435 }
3436 if (CSL.getAbbreviation) {
3437 this.sys.getAbbreviation = CSL.getAbbreviation;
3438 }
3439 if (this.sys.stringCompare) {
3440 CSL.stringCompare = this.sys.stringCompare;
3441 }
3442 this.sys.AbbreviationSegments = CSL.AbbreviationSegments;
3443 this.parallel = new CSL.Parallel(this);
3444 //this.parallel.use_parallels = true;
3445
3446 this.transform = new CSL.Transform(this);
3447 // true or false
3448 this.setParseNames = function (val) {
3449 this.opt['parse-names'] = val;
3450 };
3451
3452 this.opt = new CSL.Engine.Opt();
3453 this.tmp = new CSL.Engine.Tmp();
3454 this.build = new CSL.Engine.Build();
3455 this.fun = new CSL.Engine.Fun(this);
3456
3457 this.configure = new CSL.Engine.Configure();
3458 // Build citation before citation_sort in order to pick up
3459 // state.opt.update_mode, needed it determine whether
3460 // a grouped sort should be performed.
3461 this.citation_sort = new CSL.Engine.CitationSort();
3462 this.bibliography_sort = new CSL.Engine.BibliographySort();
3463 this.citation = new CSL.Engine.Citation(this);
3464 this.bibliography = new CSL.Engine.Bibliography();
3465 this.intext = new CSL.Engine.InText();
3466
3467 this.output = new CSL.Output.Queue(this);
3468
3469 //this.render = new CSL.Render(this);
3470 //
3471 // This latter queue is used for formatting date chunks
3472 // before they are folded back into the main queue.
3473 //
3474 this.dateput = new CSL.Output.Queue(this);
3475
3476 this.cslXml = CSL.setupXml(style);
3477
3478 for (var i in CSL.SYS_OPTIONS) {
3479 var option = CSL.SYS_OPTIONS[i];
3480 if ("boolean" === typeof this.sys[option]) {
3481 this.opt.development_extensions[option] = this.sys[option];
3482 }
3483
3484 }
3485 if (this.opt.development_extensions.uppercase_subtitles || this.opt.development_extensions.implicit_short_title) {
3486 this.opt.development_extensions.main_title_from_short_title = true;
3487 }
3488 if (this.opt.development_extensions.csl_reverse_lookup_support) {
3489 this.build.cslNodeId = 0;
3490 this.setCslNodeIds = function(myxml, nodename) {
3491 var children = this.cslXml.children(myxml);
3492 this.cslXml.setAttribute(myxml, 'cslid', this.build.cslNodeId);
3493 this.opt.nodenames.push(nodename);
3494 this.build.cslNodeId += 1;
3495 for (var i = 0, ilen = this.cslXml.numberofnodes(children); i < ilen; i += 1) {
3496 nodename = this.cslXml.nodename(children[i]);
3497 if (nodename) {
3498 this.setCslNodeIds(children[i], nodename);
3499 }
3500 }
3501 };
3502 this.setCslNodeIds(this.cslXml.dataObj, "style");
3503 }
3504 // Preprocessing ops for the XML input
3505 this.cslXml.addMissingNameNodes(this.cslXml.dataObj);
3506 this.cslXml.addInstitutionNodes(this.cslXml.dataObj);
3507 this.cslXml.insertPublisherAndPlace(this.cslXml.dataObj);
3508 this.cslXml.flagDateMacros(this.cslXml.dataObj);
3509 attrs = this.cslXml.attributes(this.cslXml.dataObj);
3510 if ("undefined" === typeof attrs["@sort-separator"]) {
3511 this.cslXml.setAttribute(this.cslXml.dataObj, "sort-separator", ", ");
3512 }
3513 // This setting does the right thing and seems not to be side-effects
3514 this.opt["initialize-with-hyphen"] = true;
3515
3516 // Locale resolution
3517 //
3518 // (1) Get three locale strings
3519 // -- default-locale (stripped)
3520 // -- processor-locale
3521 // -- en_US
3522
3523 this.setStyleAttributes();
3524
3525 this.opt.xclass = this.cslXml.getAttributeValue(this.cslXml.dataObj, "class");
3526 this.opt["class"] = this.opt.xclass;
3527 this.opt.styleID = this.cslXml.getStyleId(this.cslXml.dataObj);
3528 this.opt.styleName = this.cslXml.getStyleId(this.cslXml.dataObj, true);
3529
3530 if (this.opt.version.slice(0,4) === "1.1m") {
3531 this.opt.development_extensions.static_statute_locator = true;
3532 this.opt.development_extensions.handle_parallel_articles = true;
3533 this.opt.development_extensions.main_title_from_short_title = true;
3534 this.opt.development_extensions.expect_and_symbol_form = true;
3535 this.opt.development_extensions.require_explicit_legal_case_title_short = true;
3536 this.opt.development_extensions.force_jurisdiction = true;
3537 }
3538 // We seem to have two language specs flying around:
3539 // this.opt["default-locale"], and this.opt.lang
3540 // Keeping them aligned for safety's sake, pending
3541 // eventual cleanup.
3542 if (lang) {
3543 lang = lang.replace("_", "-");
3544 lang = CSL.normalizeLocaleStr(lang);
3545 }
3546 if (this.opt["default-locale"][0]) {
3547 this.opt["default-locale"][0] = this.opt["default-locale"][0].replace("_", "-");
3548 this.opt["default-locale"][0] = CSL.normalizeLocaleStr(this.opt["default-locale"][0]);
3549 }
3550 if (lang && forceLang) {
3551 this.opt["default-locale"] = [lang];
3552 }
3553 if (lang && !forceLang && this.opt["default-locale"][0]) {
3554 lang = this.opt["default-locale"][0];
3555 }
3556 if (this.opt["default-locale"].length === 0) {
3557 if (!lang) {
3558 lang = "en-US";
3559 }
3560 this.opt["default-locale"].push("en-US");
3561 }
3562 if (!lang) {
3563 lang = this.opt["default-locale"][0];
3564 }
3565 langspec = CSL.localeResolve(lang);
3566 this.opt.lang = langspec.best;
3567 this.opt["default-locale"][0] = langspec.best;
3568 this.locale = {};
3569 if (!this.opt["default-locale-sort"]) {
3570 this.opt["default-locale-sort"] = this.opt["default-locale"][0];
3571 }
3572 // Test processor against JS engine locale mess to find a field separator that works
3573 if ('dale|'.localeCompare('daleb', this.opt["default-locale-sort"]) > -1) {
3574 this.opt.sort_sep = "@";
3575 } else {
3576 this.opt.sort_sep = "|";
3577 }
3578 this.localeConfigure(langspec);
3579
3580 // Build skip-word regexp
3581 function makeRegExp(lst) {
3582 var lst = lst.slice();
3583 var ret = new RegExp( "(?:(?:[?!:]*\\s+|-|^)(?:" + lst.join("|") + ")(?=[!?:]*\\s+|-|$))", "g");
3584 return ret;
3585 }
3586 this.locale[this.opt.lang].opts["skip-words-regexp"] = makeRegExp(this.locale[this.opt.lang].opts["skip-words"]);
3587
3588 this.output.adjust = new CSL.Output.Queue.adjust(this.getOpt('punctuation-in-quote'));
3589
3590 this.registry = new CSL.Registry(this);
3591
3592 // XXX For modular jurisdiction support, parameterize buildTokenLists().
3593 // XXX Feed as arguments:
3594 // XXX * actual node to be walked (cslXml)
3595 // XXX * actual target array
3596
3597 this.macros = {};
3598
3599 this.build.area = "citation";
3600 var area_nodes = this.cslXml.getNodesByName(this.cslXml.dataObj, this.build.area);
3601 this.buildTokenLists(area_nodes, this[this.build.area].tokens);
3602
3603 this.build.area = "bibliography";
3604 var area_nodes = this.cslXml.getNodesByName(this.cslXml.dataObj, this.build.area);
3605 this.buildTokenLists(area_nodes, this[this.build.area].tokens);
3606
3607 this.build.area = "intext";
3608 var area_nodes = this.cslXml.getNodesByName(this.cslXml.dataObj, this.build.area);
3609 this.buildTokenLists(area_nodes, this[this.build.area].tokens);
3610
3611 this.juris = {};
3612
3613 this.configureTokenLists();
3614
3615 this.disambiguate = new CSL.Disambiguation(this);
3616
3617 this.splice_delimiter = false;
3618
3619 //
3620 // date parser
3621 //
3622 this.fun.dateparser = CSL.DateParser;
3623 //
3624 // flip-flopper for inline markup
3625 //
3626 this.fun.flipflopper = new CSL.Util.FlipFlopper(this);
3627 //
3628 // utility functions for quotes
3629 //
3630 this.setCloseQuotesArray();
3631 //
3632 // configure ordinal numbers generator
3633 //
3634 this.fun.ordinalizer.init(this);
3635 //
3636 // configure long ordinal numbers generator
3637 //
3638 this.fun.long_ordinalizer.init(this);
3639 //
3640 // set up page mangler
3641 //
3642 this.fun.page_mangler = CSL.Util.PageRangeMangler.getFunction(this, "page");
3643 this.fun.year_mangler = CSL.Util.PageRangeMangler.getFunction(this, "year");
3644
3645 this.setOutputFormat("html");
3646};
3647
3648CSL.Engine.prototype.setCloseQuotesArray = function () {
3649 var ret;
3650 ret = [];
3651 ret.push(this.getTerm("close-quote"));
3652 ret.push(this.getTerm("close-inner-quote"));
3653 ret.push('"');
3654 ret.push("'");
3655 this.opt.close_quotes_array = ret;
3656};
3657
3658// Walker for preparsed XML input
3659CSL.makeBuilder = function (me, target) {
3660 var var_stack = [];
3661 var node_stack = [];
3662 function runStart (node) {
3663 node_stack.push(node);
3664 CSL.XmlToToken.call(node, me, CSL.START, target, var_stack);
3665 }
3666 function runEnd () {
3667 var node = node_stack.pop();
3668 CSL.XmlToToken.call(node, me, CSL.END, target, var_stack);
3669 }
3670 function runSingle (node) {
3671 CSL.XmlToToken.call(node, me, CSL.SINGLETON, target, var_stack);
3672 }
3673 function buildStyle (nodes, parent, node_stack) {
3674 if (!node_stack) {
3675 node_stack = [];
3676 }
3677 if (!nodes) {
3678 nodes = [];
3679 }
3680 if ("undefined" === typeof nodes.length) {
3681 nodes = [nodes];
3682 }
3683 for (var i=0; i<nodes.length; i++) {
3684 var node = nodes[i];
3685 if (me.cslXml.nodename(node) === null) {
3686 continue;
3687 }
3688 if (parent && me.cslXml.nodename(node) === "date") {
3689 CSL.Util.fixDateNode.call(me, parent, i, node);
3690 node = me.cslXml.children(parent)[i];
3691 }
3692 if (me.cslXml.numberofnodes(me.cslXml.children(node))) {
3693 runStart(node);
3694 buildStyle(me.cslXml.children(node), node, node_stack);
3695 runEnd();
3696 } else {
3697 runSingle(node);
3698 }
3699 }
3700 }
3701 return buildStyle;
3702};
3703
3704
3705CSL.Engine.prototype.buildTokenLists = function (area_nodes, target) {
3706 if (!this.cslXml.getNodeValue(area_nodes)) {
3707 return;
3708 }
3709 var builder = CSL.makeBuilder(this, target);
3710 var mynode;
3711 if ("undefined" === typeof area_nodes.length) {
3712 mynode = area_nodes;
3713 } else {
3714 mynode = area_nodes[0];
3715 }
3716 builder(mynode);
3717};
3718
3719
3720CSL.Engine.prototype.setStyleAttributes = function () {
3721 var dummy, attributes, attrname;
3722 // Protect against DOM engines that deliver a top-level document
3723 // (needed for createElement) that does not contain our top-level node.
3724 //
3725 // The string coercion on this.cslXml.tagName addresses a bizarre
3726 // condition on the top-level node in jsdom running under node.js, in which:
3727 // (1) typeof this.cslXml.tagName === "undefined"; and
3728 // (2) !this.cslXml.tagName === false
3729 // Coerced, it becomes an empty string.
3730 var dummy = {};
3731 dummy.name = this.cslXml.nodename(this.cslXml.dataObj);
3732 attributes = this.cslXml.attributes(this.cslXml.dataObj);
3733 for (attrname in attributes) {
3734 if (attributes.hasOwnProperty(attrname)) {
3735 // attr = attributes[key];
3736 CSL.Attributes[attrname].call(dummy, this, attributes[attrname]);
3737 }
3738 }
3739};
3740
3741CSL.Engine.prototype.getTerm = function (term, form, plural, gender, mode, forceDefaultLocale) {
3742 if (term && term.match(/[A-Z]/) && term === term.toUpperCase()) {
3743 CSL.debug("Warning: term key is in uppercase form: "+term);
3744 term = term.toLowerCase();
3745 }
3746 var lang;
3747 if (forceDefaultLocale) {
3748 lang = this.opt["default-locale"][0];
3749 } else {
3750 lang = this.opt.lang;
3751 }
3752 var ret = CSL.Engine.getField(CSL.LOOSE, this.locale[lang].terms, term, form, plural, gender);
3753 // XXXXX Temporary, until locale term is deployed in CSL.
3754 if (!ret && term === "range-delimiter") {
3755 ret = "\u2013";
3756 }
3757 // XXXXX Not so good if mode is neither strict nor tolerant ...
3758 if (typeof ret === "undefined") {
3759 if (mode === CSL.STRICT) {
3760 CSL.error("Error in getTerm: term \"" + term + "\" does not exist.");
3761 } else if (mode === CSL.TOLERANT) {
3762 ret = "";
3763 }
3764 }
3765 if (ret) {
3766 this.tmp.cite_renders_content = true;
3767 }
3768 return ret;
3769};
3770
3771CSL.Engine.prototype.getDate = function (form, forceDefaultLocale) {
3772 var lang;
3773 if (forceDefaultLocale) {
3774 lang = this.opt["default-locale"];
3775 } else {
3776 lang = this.opt.lang;
3777 }
3778 if (this.locale[lang].dates[form]) {
3779 return this.locale[lang].dates[form];
3780 } else {
3781 return false;
3782 }
3783};
3784
3785CSL.Engine.prototype.getOpt = function (arg) {
3786 if ("undefined" !== typeof this.locale[this.opt.lang].opts[arg]) {
3787 return this.locale[this.opt.lang].opts[arg];
3788 } else {
3789 return false;
3790 }
3791};
3792
3793
3794
3795CSL.Engine.prototype.getVariable = function (Item, varname, form, plural) {
3796 return CSL.Engine.getField(CSL.LOOSE, Item, varname, form, plural);
3797};
3798
3799CSL.Engine.prototype.getDateNum = function (ItemField, partname) {
3800 if ("undefined" === typeof ItemField) {
3801 return 0;
3802 } else {
3803 return ItemField[partname];
3804 }
3805};
3806
3807CSL.Engine.getField = function (mode, hash, term, form, plural, gender) {
3808 var ret, forms, f, pos, len, hashterm;
3809 ret = "";
3810 if ("undefined" === typeof hash[term]) {
3811 if (mode === CSL.STRICT) {
3812 CSL.error("Error in getField: term \"" + term + "\" does not exist.");
3813 } else {
3814 return undefined;
3815 }
3816 }
3817 if (gender && hash[term][gender]) {
3818 hashterm = hash[term][gender];
3819 } else {
3820 hashterm = hash[term];
3821 }
3822 forms = [];
3823 if (form === "symbol") {
3824 forms = ["symbol", "short"];
3825 } else if (form === "verb-short") {
3826 forms = ["verb-short", "verb"];
3827 } else if (form !== "long") {
3828 forms = [form];
3829 }
3830 forms = forms.concat(["long"]);
3831 len = forms.length;
3832 for (pos = 0; pos < len; pos += 1) {
3833 f = forms[pos];
3834 if ("string" === typeof hashterm || "number" === typeof hashterm) {
3835 ret = hashterm;
3836 } else if ("undefined" !== typeof hashterm[f]) {
3837 if ("string" === typeof hashterm[f] || "number" === typeof hashterm[f]) {
3838 ret = hashterm[f];
3839 } else {
3840 if ("number" === typeof plural) {
3841 ret = hashterm[f][plural];
3842 } else {
3843 ret = hashterm[f][0];
3844 }
3845 }
3846 break;
3847 }
3848 }
3849 return ret;
3850};
3851
3852CSL.Engine.prototype.configureTokenLists = function () {
3853 var area, pos, len;
3854 //for each (var area in ["citation", "citation_sort", "bibliography","bibliography_sort"]) {
3855 len = CSL.AREAS.length;
3856 for (pos = 0; pos < len; pos += 1) {
3857 //var ret = [];
3858 area = CSL.AREAS[pos];
3859 var tokens = this[area].tokens;
3860 this.configureTokenList(tokens);
3861 }
3862 this.version = CSL.version;
3863 return this.state;
3864};
3865
3866CSL.Engine.prototype.configureTokenList = function (tokens) {
3867 var dateparts_master, token, dateparts, part, ppos, pppos, llen, lllen;
3868 dateparts_master = ["year", "month", "day"];
3869 llen = tokens.length - 1;
3870 for (ppos = llen; ppos > -1; ppos += -1) {
3871 token = tokens[ppos];
3872 //token.pos = ppos;
3873 //ret.push(token);
3874 if ("date" === token.name && CSL.END === token.tokentype) {
3875 dateparts = [];
3876 }
3877 if ("date-part" === token.name && token.strings.name) {
3878 lllen = dateparts_master.length;
3879 for (pppos = 0; pppos < lllen; pppos += 1) {
3880 part = dateparts_master[pppos];
3881 if (part === token.strings.name) {
3882 dateparts.push(token.strings.name);
3883 }
3884 }
3885 }
3886 if ("date" === token.name && CSL.START === token.tokentype) {
3887 dateparts.reverse();
3888 token.dateparts = dateparts;
3889 }
3890 token.next = (ppos + 1);
3891 if (token.name && CSL.Node[token.name].configure) {
3892 CSL.Node[token.name].configure.call(token, this, ppos);
3893 }
3894 }
3895};
3896
3897CSL.Engine.prototype.refetchItems = function (ids) {
3898 var ret = [];
3899 for (var i = 0, ilen = ids.length; i < ilen; i += 1) {
3900 ret.push(this.refetchItem("" + ids[i]));
3901 }
3902 return ret;
3903};
3904
3905CSL.ITERATION = 0;
3906
3907// Wrapper for sys.retrieveItem supplied by calling application.
3908// Adds experimental fields embedded in the note field for
3909// style development trial and testing purposes.
3910CSL.Engine.prototype.retrieveItem = function (id) {
3911 var Item, m, i;
3912
3913 if (!this.tmp.loadedItemIDs[id]) {
3914 this.tmp.loadedItemIDs[id] = true;
3915 } else {
3916 return this.registry.refhash[id];
3917 }
3918
3919 if (this.opt.development_extensions.normalize_lang_keys_to_lowercase &&
3920 "boolean" === typeof this.opt.development_extensions.normalize_lang_keys_to_lowercase) {
3921 // This is a hack. Should properly be configured by a processor method after build.
3922 for (var i=0,ilen=this.opt["default-locale"].length; i<ilen; i+=1) {
3923 this.opt["default-locale"][i] = this.opt["default-locale"][i].toLowerCase();
3924 }
3925 for (var i=0,ilen=this.opt["locale-translit"].length; i<ilen; i+=1) {
3926 this.opt["locale-translit"][i] = this.opt["locale-translit"][i].toLowerCase();
3927 }
3928 for (var i=0,ilen=this.opt["locale-translat"].length; i<ilen; i+=1) {
3929 this.opt["locale-translat"][i] = this.opt["locale-translat"][i].toLowerCase();
3930 }
3931 this.opt.development_extensions.normalize_lang_keys_to_lowercase = 100;
3932 }
3933
3934 //Zotero.debug("XXX === ITERATION " + CSL.ITERATION + " "+ id +" ===");
3935 CSL.ITERATION += 1;
3936
3937 Item = JSON.parse(JSON.stringify(this.sys.retrieveItem("" + id)));
3938
3939 // Optionally normalize keys to lowercase()
3940 if (this.opt.development_extensions.normalize_lang_keys_to_lowercase) {
3941 if (Item.multi) {
3942 if (Item.multi._keys) {
3943 for (var field in Item.multi._keys) {
3944 for (var key in Item.multi._keys[field]) {
3945 if (key !== key.toLowerCase()) {
3946 Item.multi._keys[field][key.toLowerCase()] = Item.multi._keys[field][key];
3947 delete Item.multi._keys[field][key];
3948 }
3949 }
3950 }
3951 }
3952 if (Item.multi.main) {
3953 for (var field in Item.multi.main) {
3954 Item.multi.main[field] = Item.multi.main[field].toLowerCase();
3955 }
3956 }
3957 }
3958 for (var i=0, ilen=CSL.NAME_VARIABLES.length; i>ilen; i+=1) {
3959 var ctype = CSL.NAME_VARIABLES[i];
3960 if (Item[ctype] && Item[ctype].multi) {
3961 for (var j=0, jlen=Item[ctype].length; j<jlen; j+=1) {
3962 var creator = Item[ctype][j];
3963 if (creator.multi) {
3964 if (creator.multi._key) {
3965 for (var key in creator.multi._key) {
3966 if (key !== key.toLowerCase()) {
3967 creator.multi._key[key.toLowerCase()] = creator.multi._key[key];
3968 delete creator.multi._key[key];
3969 }
3970 }
3971 }
3972 if (creator.multi.main) {
3973 creator.multi.main = creator.multi.main.toLowerCase();
3974 }
3975 }
3976 }
3977 }
3978 }
3979 }
3980
3981 // Normalize language field into "language" and "language-original"
3982 if (Item.language && Item.language.match(/[><]/)) {
3983 // Attempt to split field in two
3984 var m = Item.language.match(/(.*?)([<>])(.*)/);
3985 if (m[2] === "<") {
3986 Item["language-name"] = m[1];
3987 Item["language-name-original"] = m[3];
3988 } else {
3989 Item["language-name"] = m[3];
3990 Item["language-name-original"] = m[1];
3991 }
3992 if (this.opt.multi_layout) {
3993 if (Item["language-name-original"]) {
3994 Item.language = Item["language-name-original"];
3995 }
3996 } else {
3997 if (Item["language-name"]) {
3998 Item.language = Item["language-name"];
3999 }
4000 }
4001 }
4002
4003 if (Item.page) {
4004 Item["page-first"] = Item.page;
4005 var num = "" + Item.page;
4006 var m = num.split(/\s*(?:&|, |-|\u2013)\s*/);
4007 if (m[0].slice(-1) !== "\\") {
4008 Item["page-first"] = m[0];
4009 }
4010 }
4011 // Optional development extensions
4012 if (this.opt.development_extensions.field_hack && Item.note) {
4013 // false is for validFieldsForType (all conforming entries scrubbed when false)
4014 CSL.parseNoteFieldHacks(Item, false, this.opt.development_extensions.allow_field_hack_date_override);
4015 }
4016 // not including locator-date
4017 for (var key in Item) {
4018 if (CSL.DATE_VARIABLES.indexOf(key.replace(/^alt-/, "")) > -1) {
4019 var dateobj = Item[key];
4020 if (dateobj) {
4021 // raw date parsing is harmless, but can be disabled if desired
4022 if (this.opt.development_extensions.raw_date_parsing) {
4023 if (dateobj.raw && (!dateobj["date-parts"] || dateobj["date-parts"].length === 0)) {
4024 dateobj = this.fun.dateparser.parseDateToObject(dateobj.raw);
4025 }
4026 }
4027 Item[key] = this.dateParseArray(dateobj);
4028 }
4029 }
4030 }
4031 if (this.opt.development_extensions.static_statute_locator) {
4032 if (Item.type && ["bill","gazette","legislation","regulation","treaty"].indexOf(Item.type) > -1) {
4033
4034 var varname;
4035 var elements = ["type", "title", "jurisdiction", "genre", "volume", "container-title"];
4036 var legislation_id = [];
4037 for (var i = 0, ilen = elements.length; i < ilen; i += 1) {
4038 varname = elements[i];
4039 if (Item[varname]) {
4040 legislation_id.push(Item[varname]);
4041 }
4042 }
4043 elements = ["original-date", "issued"];
4044 for (var i = 0, ilen=elements.length; i < ilen; i += 1) {
4045 varname = elements[i];
4046 if (Item[varname] && Item[varname].year) {
4047 var value = Item[varname].year;
4048 legislation_id.push(value);
4049 break;
4050 }
4051 }
4052 Item.legislation_id = legislation_id.join("::");
4053 }
4054 }
4055 // For authority to name shape in legal styles
4056 if (this.opt.development_extensions.force_jurisdiction) {
4057 if ("string" === typeof Item.authority) {
4058 Item.authority = [
4059 {
4060 literal: Item.authority,
4061 multi: {
4062 _key: {}
4063 }
4064 }
4065 ];
4066 if (Item.multi && Item.multi._keys && Item.multi._keys.authority) {
4067 Item.authority[0].multi._key = {};
4068 for (var key in Item.multi._keys.authority) {
4069 Item.authority[0].multi._key[key] = {
4070 literal: Item.multi._keys.authority[key]
4071 };
4072 }
4073 }
4074 }
4075 }
4076 // Add getAbbreviation() call for title-short and container-title-short
4077 if (!Item["title-short"]) {
4078 Item["title-short"] = Item.shortTitle;
4079 }
4080 // Add support for main_title_from_short_title
4081 if (this.opt.development_extensions.main_title_from_short_title) {
4082 var narrowSpaceLocale = this.opt["default-locale"][0].slice(0, 2).toLowerCase() === "fr";
4083 CSL.extractTitleAndSubtitle.call(this, Item, narrowSpaceLocale);
4084 }
4085 var isLegalType = ["bill","legal_case","legislation","gazette","regulation"].indexOf(Item.type) > -1;
4086 if (this.opt.development_extensions.force_jurisdiction && isLegalType) {
4087 if (!Item.jurisdiction) {
4088 Item.jurisdiction = "us";
4089 }
4090 }
4091 var normalizedKey;
4092 if (!isLegalType && Item.title && this.sys.getAbbreviation) {
4093 var noHints = false;
4094 if (!Item.jurisdiction) {
4095 noHints = true;
4096 }
4097 if (this.sys.normalizeAbbrevsKey) {
4098 normalizedKey = this.sys.normalizeAbbrevsKey(Item.title);
4099 } else {
4100 normalizedKey = Item.title;
4101 }
4102 var jurisdiction = this.transform.loadAbbreviation(Item.jurisdiction, "title", normalizedKey, Item.type);
4103 if (this.transform.abbrevs[jurisdiction].title) {
4104 if (this.transform.abbrevs[jurisdiction].title[normalizedKey]) {
4105 Item["title-short"] = this.transform.abbrevs[jurisdiction].title[normalizedKey];
4106 }
4107 }
4108 }
4109 if (!Item["container-title-short"]) {
4110 Item["container-title-short"] = Item.journalAbbreviation;
4111 }
4112 if (Item["container-title"] && this.sys.getAbbreviation) {
4113 if (this.sys.normalizeAbbrevsKey) {
4114 normalizedKey = this.sys.normalizeAbbrevsKey(Item["container-title"]);
4115 } else {
4116 normalizedKey = Item["container-title"];
4117 }
4118 var jurisdiction = this.transform.loadAbbreviation(Item.jurisdiction, "container-title", normalizedKey);
4119 if (this.transform.abbrevs[jurisdiction]["container-title"]) {
4120 if (this.transform.abbrevs[jurisdiction]["container-title"][normalizedKey]) {
4121 Item["container-title-short"] = this.transform.abbrevs[jurisdiction]["container-title"][normalizedKey];
4122 }
4123 }
4124 }
4125 if (Item.jurisdiction) {
4126 Item.country = Item.jurisdiction.split(":")[0];
4127 }
4128 if (this.registry.refhash[id]) {
4129 if (JSON.stringify(this.registry.refhash[id]) != JSON.stringify(Item)) {
4130 for (var key in this.registry.refhash[id]) {
4131 delete this.registry.refhash[id][key];
4132 }
4133 this.tmp.taintedItemIDs[Item.id] = true;
4134 Object.assign(this.registry.refhash[id], Item);
4135 }
4136 } else {
4137 this.registry.refhash[id] = Item;
4138 }
4139 return this.registry.refhash[id];
4140};
4141
4142CSL.Engine.prototype.refetchItem = function (id) {
4143 return this.registry.refhash[id];
4144};
4145
4146CSL.Engine.prototype.refetchItem = function (id) {
4147 return this.registry.refhash[id];
4148}
4149
4150// Executed during style build
4151CSL.Engine.prototype.setOpt = function (token, name, value) {
4152 if (token.name === "style" || token.name === "cslstyle") {
4153 this.opt.inheritedAttributes[name] = value;
4154 this.citation.opt.inheritedAttributes[name] = value;
4155 this.bibliography.opt.inheritedAttributes[name] = value;
4156 } else if (["citation", "bibliography"].indexOf(token.name) > -1) {
4157 this[token.name].opt.inheritedAttributes[name] = value;
4158 } else {
4159 token.strings[name] = value;
4160 }
4161};
4162
4163// Executed at runtime, since macros can occur in the context of citation or bibliography
4164CSL.Engine.prototype.inheritOpt = function (token, attrname, parentname, defaultValue) {
4165 if ("undefined" !== typeof token.strings[attrname]) {
4166 return token.strings[attrname];
4167 } else {
4168 var parentValue = this[this.tmp.root].opt.inheritedAttributes[parentname ? parentname : attrname];
4169 if ("undefined" !== typeof parentValue) {
4170 return parentValue;
4171 } else {
4172 return defaultValue;
4173 }
4174 }
4175};
4176
4177CSL.Engine.prototype.remapSectionVariable = function (inputList) {
4178 // We have items with a value in the section field (on Item) that must
4179 // be mapped to the locator field (on item). We simply prepend it as
4180 // a string here, and handle all parsing of the resulting string
4181 // in processNumber(). Plurals and numeric are set in processNumber().
4182
4183 // Because the target is in the citation item (lowercase), the
4184 // remapping cannot take place when the Item data is received.
4185 // Citation provides a list of Item/item pairs, hence the iteration
4186 // used here.
4187 for (var i = 0, ilen = inputList.length; i < ilen; i += 1) {
4188 var Item = inputList[i][0];
4189 var item = inputList[i][1];
4190
4191 if (["bill","gazette","legislation","regulation","treaty"].indexOf(Item.type) > -1) {
4192 // If a locator value exists, then
4193 // (a) Leave be an overriding label at the start of the locator field, defaulting to label value
4194 if (item.locator) {
4195 item.locator = item.locator.trim();
4196 var m = item.locator.match(CSL.STATUTE_SUBDIV_PLAIN_REGEX_FRONT);
4197 if (!m) {
4198 if (item.label) {
4199 item.locator = CSL.STATUTE_SUBDIV_STRINGS_REVERSE[item.label] + " " + item.locator;
4200 } else {
4201 item.locator = "p. " + item.locator;
4202 }
4203 }
4204 }
4205 // If a section value exists, then
4206 // (a) Apply an overriding label at the start of the section field, defaulting to sec.
4207 var sectionMasterLabel = null;
4208 if (Item.section) {
4209 Item.section = Item.section.trim();
4210 var m = Item.section.match(CSL.STATUTE_SUBDIV_PLAIN_REGEX_FRONT);
4211 if (!m) {
4212 Item.section = "sec. " + Item.section;
4213 sectionMasterLabel = "sec.";
4214 } else {
4215 sectionMasterLabel = m[0].trim();
4216 }
4217 }
4218 // If section is nil, then
4219 // (a) Do nothing
4220 if (Item.section) {
4221 // If section exists and locator is nil
4222 // (a) Set section string in locator field
4223 if (!item.locator) {
4224 item.locator = Item.section;
4225 } else {
4226 // If both section and locator exist, then
4227 // (a) If locator starts with p., remove p., merge with space or no-space, and set in locator field
4228 // (b) If locator starts with non-p., prepend section value to locator with space, and set in locator field
4229 var m = item.locator.match(/^([^ ]*)\s*(.*)/);
4230 var space = " ";
4231 if (m) {
4232 if (m[1] === "p." && sectionMasterLabel !== "p.") {
4233 item.locator = m[2];
4234 }
4235 if (["[", "(", ".", ",", ";", ":", "?"].indexOf(item.locator.slice(0, 1)) > -1) {
4236 space = "";
4237 }
4238 } else {
4239 space = "";
4240 }
4241 item.locator = Item.section + space + item.locator;
4242 }
4243 //Item.section = "";
4244 }
4245 item.label = "";
4246 // And that's it. Pre-parse complete.
4247 }
4248 }
4249};
4250
4251
4252CSL.Engine.prototype.setNumberLabels = function (Item) {
4253 if (Item.number
4254 && ["bill", "gazette", "legislation","regulation","treaty"].indexOf(Item.type) > -1
4255 && this.opt.development_extensions.static_statute_locator
4256 && !this.tmp.shadow_numbers["number"]) {
4257
4258 this.tmp.shadow_numbers["number"] = {};
4259 this.tmp.shadow_numbers["number"].values = [];
4260 this.tmp.shadow_numbers["number"].plural = 0;
4261 this.tmp.shadow_numbers["number"].numeric = false;
4262 this.tmp.shadow_numbers["number"].label = false;
4263
4264 // Labels embedded in number variable
4265 var value = "" + Item.number;
4266 value = value.split("\\").join("");
4267 // Get first word, parse out labels only if it parses
4268 var firstword = value.split(/\s+/)[0];
4269 var firstlabel = CSL.STATUTE_SUBDIV_STRINGS[firstword];
4270 if (firstlabel) {
4271 // Get list and match
4272 var splt = value.split(CSL.STATUTE_SUBDIV_PLAIN_REGEX);
4273 if (splt.length > 1) {
4274 // Convert matches to localized form
4275 var lst = [];
4276 for (var j=1, jlen=splt.length; j < jlen; j += 1) {
4277 lst.push(splt[j].replace(/\s*$/, "").replace(/^\s*/, ""));
4278 }
4279 // Preemptively save to shadow_numbers
4280 value = lst.join(" ");
4281 } else {
4282 value = splt[0];
4283 }
4284 this.tmp.shadow_numbers["number"].label = firstlabel;
4285 this.tmp.shadow_numbers["number"].values.push(["Blob", value, false]);
4286 this.tmp.shadow_numbers["number"].numeric = false;
4287 } else {
4288 this.tmp.shadow_numbers["number"].values.push(["Blob", value, false]);
4289 this.tmp.shadow_numbers["number"].numeric = true;
4290 }
4291 }
4292};
4293
4294/*global CSL: true */
4295
4296CSL.substituteOne = function (template) {
4297 return function (state, list) {
4298 if (!list) {
4299 return "";
4300 } else {
4301 // ("string" === typeof list)
4302 return template.replace("%%STRING%%", list);
4303 }
4304 };
4305};
4306
4307
4308/**
4309 * Two-tiered substitutions gadget.
4310 * <p>This is used for
4311 * options like (now defunct) "font-family", where the option value
4312 * cannot be determined until the attribute is processed.
4313 * Need for this function might be reviewed at some point ...</p>
4314 * @param {String} template A template containing
4315 * <code>%%STRING%%</code> and <code>%%PARAM%%</code>
4316 * placeholders. See {@link CSL.Output.Formats.html} for
4317 * examples.
4318 */
4319CSL.substituteTwo = function (template) {
4320 return function (param) {
4321 var template2 = template.replace("%%PARAM%%", param);
4322 return function (state, list) {
4323 if (!list) {
4324 return "";
4325 } else {
4326 //("string" === typeof list){
4327 return template2.replace("%%STRING%%", list);
4328 }
4329 };
4330 };
4331};
4332
4333/**
4334 * Generate string functions for designated output mode.
4335 * <p>Only "html" (the default) is supported at present.</p>
4336 * @param {String} mode Either "html" or "rtf", eventually.
4337 */
4338CSL.Mode = function (mode) {
4339 var decorations, params, param, func, val, args;
4340 decorations = {};
4341 params = CSL.Output.Formats[mode];
4342 for (param in params) {
4343 if (true) {
4344
4345 if ("@" !== param.slice(0, 1)) {
4346 decorations[param] = params[param];
4347 continue;
4348 }
4349 func = false;
4350 val = params[param];
4351 args = param.split('/');
4352
4353 if (typeof val === "string" && val.indexOf("%%STRING%%") > -1) {
4354 if (val.indexOf("%%PARAM%%") > -1) {
4355 func = CSL.substituteTwo(val);
4356 } else {
4357 func = CSL.substituteOne(val);
4358 }
4359 } else if (typeof val === "boolean" && !val) {
4360 func = CSL.Output.Formatters.passthrough;
4361 } else if (typeof val === "function") {
4362 func = val;
4363 } else {
4364 CSL.error("Bad " + mode + " config entry for " + param + ": " + val);
4365 }
4366
4367 if (args.length === 1) {
4368 decorations[args[0]] = func;
4369 } else if (args.length === 2) {
4370 if (!decorations[args[0]]) {
4371 decorations[args[0]] = {};
4372 }
4373 decorations[args[0]][args[1]] = func;
4374 }
4375 }
4376 }
4377 return decorations;
4378};
4379
4380
4381/**
4382 * Generate a separate list of formatting attributes.
4383 * <p>This generates a list of tuples containing attribute
4384 * information relevant to output formatting, in the order
4385 * fixed in the constant {@link CSL.FORMAT_KEY_SEQUENCE}.
4386 * This function is called during {@link CSL.Core.Build}.
4387 * Formatting hints are distilled to functions
4388 * later, in the second compilation pass ({@link CSL.Core.Configure}).</p>
4389 * @param {Object} state The state object returned by
4390 * {@link CSL.Engine}.
4391 * @param {Object} attributes The hash object containing
4392 * the attributes and values extracted from an XML node.
4393 */
4394CSL.setDecorations = function (state, attributes) {
4395 var ret, key, pos;
4396 // This applies a fixed processing sequence
4397 ret = [];
4398 for (pos in CSL.FORMAT_KEY_SEQUENCE) {
4399 if (true) {
4400 var key = CSL.FORMAT_KEY_SEQUENCE[pos];
4401 if (attributes[key]) {
4402 ret.push([key, attributes[key]]);
4403 delete attributes[key];
4404 }
4405 }
4406 }
4407 return ret;
4408};
4409
4410CSL.Doppeler = function(rexStr, stringMangler) {
4411 var matchRex = new RegExp("(" + rexStr + ")", "g");
4412 var splitRex = new RegExp(rexStr, "g");
4413 this.split = function (str) {
4414 // Normalize markup
4415 if (stringMangler) {
4416 str = stringMangler(str);
4417 }
4418 var match = str.match(matchRex);
4419 if (!match) {
4420 return {
4421 tags: [],
4422 strings: [str]
4423 };
4424 }
4425 var split = str.split(splitRex);
4426 for (var i=match.length-1; i> -1; i--) {
4427 if (typeof match[i] === "number") {
4428 match[i] = "";
4429 }
4430 var tag = match[i];
4431 if (tag === "\'" && split[i+1].length > 0) {
4432 // Fixes https://forums.zotero.org/discussion/comment/294317
4433 split[i+1] = match[i] + split[i+1];
4434 match[i] = "";
4435 }
4436 }
4437 return {
4438 tags: match,
4439 strings: split,
4440 origStrings: split.slice()
4441 };
4442 };
4443 this.join = function (obj) {
4444 var lst = obj.strings.slice(-1);
4445 for (var i=obj.tags.length-1; i>-1; i--) {
4446 lst.push(obj.tags[i]);
4447 lst.push(obj.strings[i]);
4448 }
4449 lst.reverse();
4450 return lst.join("");
4451 };
4452};
4453
4454CSL.Engine.prototype.normalDecorIsOrphan = function (blob, params) {
4455 //print("params: "+JSON.stringify(params));
4456 if (params[1] === "normal") {
4457 var use_param = false;
4458 var all_the_decor;
4459 if (this.tmp.area === "citation") {
4460 all_the_decor = [this.citation.opt.layout_decorations].concat(blob.alldecor);
4461 } else {
4462 all_the_decor = blob.alldecor;
4463 }
4464 for (var k = all_the_decor.length - 1; k > -1; k += -1) {
4465 //print(" all decor: "+JSON.stringify(all_the_decor[k]));
4466 for (var n = all_the_decor[k].length - 1; n > -1; n += -1) {
4467 //print(" superior param"+n+": "+all_the_decor[k][n][0]);
4468 if (all_the_decor[k][n][0] === params[0]) {
4469 //print(" HIT!");
4470 if (all_the_decor[k][n][1] !== "normal") {
4471 use_param = true;
4472 }
4473 }
4474 }
4475 }
4476 if (!use_param) {
4477 return true;
4478 }
4479 }
4480 return false;
4481};
4482
4483/*global CSL: true */
4484
4485
4486CSL.Engine.prototype.getCitationLabel = function (Item) {
4487 var label = "";
4488 var params = this.getTrigraphParams();
4489 var config = params[0];
4490 var myname = this.getTerm("reference", "short", 0);
4491 if ("undefined" === typeof myname) {
4492 myname = "reference";
4493 }
4494 myname = myname.replace(".", "");
4495 myname = myname.slice(0, 1).toUpperCase() + myname.slice(1);
4496 for (var i = 0, ilen = CSL.NAME_VARIABLES.length; i < ilen; i += 1) {
4497 var n = CSL.NAME_VARIABLES[i];
4498 if (Item[n]) {
4499 var names = Item[n];
4500 if (names.length > params.length) {
4501 config = params[params.length - 1];
4502 } else {
4503 config = params[names.length - 1];
4504 }
4505 for (var j = 0, jlen = names.length; j < jlen; j += 1) {
4506 if (j === config.authors.length) {
4507 break;
4508 }
4509 var res = this.nameOutput.getName(names[j], "locale-translit", true);
4510 var name = res.name;
4511 if (name && name.family) {
4512 myname = name.family;
4513 myname = myname.replace(/^([ \'\u2019a-z]+\s+)/, "");
4514
4515 } else if (name && name.literal) {
4516 myname = name.literal;
4517 }
4518 var m = myname.toLowerCase().match(/^(a\s+|the\s+|an\s+)/);
4519 if (m) {
4520 myname = myname.slice(m[1].length);
4521 }
4522 myname = myname.replace(CSL.ROMANESQUE_NOT_REGEXP, "");
4523 if (!myname) {
4524 break;
4525 }
4526 myname = myname.slice(0, config.authors[j]);
4527 if (myname.length > 1) {
4528 myname = myname.slice(0, 1).toUpperCase() + myname.slice(1).toLowerCase();
4529 } else if (myname.length === 1) {
4530 myname = myname.toUpperCase();
4531 }
4532 label += myname;
4533 }
4534 break;
4535 }
4536 }
4537 if (!label) {
4538 // Try for something using title
4539 if (Item.title) {
4540 var skipWords = this.locale[this.opt.lang].opts["skip-words"];
4541 var lst = Item.title.split(/\s+/);
4542 for (var i = lst.length - 1; i > -1; i--) {
4543 if (skipWords.indexOf(lst[i]) > -1) {
4544 lst = lst.slice(0, i).concat(lst.slice(i + 1));
4545 }
4546 }
4547 var str = lst.join('');
4548 str = str.slice(0, params[0].authors[0]);
4549 if (str.length > 1) {
4550 str = str.slice(0, 1).toUpperCase() + str.slice(1).toLowerCase();
4551 } else if (str.length === 1) {
4552 str = str.toUpperCase();
4553 }
4554 label = str;
4555 }
4556 }
4557 var year = "0000";
4558 if (Item.issued) {
4559 if (Item.issued.year) {
4560 year = "" + Item.issued.year;
4561 }
4562 }
4563 year = year.slice((config.year * -1));
4564 label = label + year;
4565 return label;
4566};
4567
4568CSL.Engine.prototype.getTrigraphParams = function () {
4569 var params = [];
4570 var ilst = this.opt.trigraph.split(":");
4571 if (!this.opt.trigraph || this.opt.trigraph.slice(0,1) !== "A") {
4572 CSL.error("Bad trigraph definition: "+this.opt.trigraph);
4573 }
4574 for (var i = 0, ilen = ilst.length; i < ilen; i += 1) {
4575 var str = ilst[i];
4576 var config = {authors:[], year:0};
4577 for (var j = 0, jlen = str.length; j < jlen; j += 1) {
4578 switch (str.slice(j,j+1)) {
4579 case "A":
4580 config.authors.push(1);
4581 break;
4582 case "a":
4583 config.authors[config.authors.length - 1] += 1;
4584 break;
4585 case "0":
4586 config.year += 1;
4587 break;
4588 default:
4589 CSL.error("Invalid character in trigraph definition: "+this.opt.trigraph);
4590 }
4591 }
4592 params.push(config);
4593 }
4594 return params;
4595};
4596
4597/*global CSL: true */
4598
4599CSL.Engine.prototype.setOutputFormat = function (mode) {
4600 this.opt.mode = mode;
4601 this.fun.decorate = CSL.Mode(mode);
4602 if (!this.output[mode]) {
4603 this.output[mode] = {};
4604 this.output[mode].tmp = {};
4605 }
4606};
4607
4608CSL.Engine.prototype.getSortFunc = function () {
4609 return function (a,b) {
4610 a = a.split("-");
4611 b = b.split("-");
4612 if (a.length < b.length) {
4613 return 1;
4614 } else if (a.length > b.length) {
4615 return -1;
4616 } else {
4617 a = a.slice(-1)[0];
4618 b = b.slice(-1)[0];
4619 if (a.length < b.length) {
4620 return 1;
4621 } else if (a.length > b.length) {
4622 return -1;
4623 } else {
4624 return 0;
4625 }
4626 }
4627 };
4628};
4629
4630CSL.Engine.prototype.setLangTagsForCslSort = function (tags) {
4631 var i, ilen;
4632 if (tags) {
4633 this.opt['locale-sort'] = [];
4634 for (i = 0, ilen = tags.length; i < ilen; i += 1) {
4635 this.opt['locale-sort'].push(tags[i]);
4636 }
4637 }
4638 this.opt['locale-sort'].sort(this.getSortFunc());
4639};
4640
4641CSL.Engine.prototype.setLangTagsForCslTransliteration = function (tags) {
4642 var i, ilen;
4643 this.opt['locale-translit'] = [];
4644 if (tags) {
4645 for (i = 0, ilen = tags.length; i < ilen; i += 1) {
4646 this.opt['locale-translit'].push(tags[i]);
4647 }
4648 }
4649 this.opt['locale-translit'].sort(this.getSortFunc());
4650};
4651
4652CSL.Engine.prototype.setLangTagsForCslTranslation = function (tags) {
4653 var i, ilen;
4654 this.opt['locale-translat'] = [];
4655 if (tags) {
4656 for (i = 0, ilen = tags.length; i < ilen; i += 1) {
4657 this.opt['locale-translat'].push(tags[i]);
4658 }
4659 }
4660 this.opt['locale-translat'].sort(this.getSortFunc());
4661};
4662
4663CSL.Engine.prototype.setLangPrefsForCites = function (obj, conv) {
4664 var opt = this.opt['cite-lang-prefs'];
4665 if (!conv) {
4666 conv = function (key) {
4667 return key.toLowerCase();
4668 };
4669 }
4670 var segments = ['Persons', 'Institutions', 'Titles', 'Journals', 'Publishers', 'Places'];
4671 // Set values in place
4672 for (var i = 0, ilen = segments.length; i < ilen; i += 1) {
4673 var clientSegment = conv(segments[i]);
4674 var citeprocSegment = segments[i].toLowerCase();
4675 if (!obj[clientSegment]) {
4676 continue;
4677 }
4678 //
4679 // Normalize the sequence of secondary and tertiary
4680 // in the provided obj segment list.
4681 //
4682 var supplements = [];
4683 while (obj[clientSegment].length > 1) {
4684 supplements.push(obj[clientSegment].pop());
4685 }
4686 var sortval = {orig:1,translit:2,translat:3};
4687 if (supplements.length === 2 && sortval[supplements[0]] < sortval[supplements[1]]) {
4688 supplements.reverse();
4689 }
4690 while (supplements.length) {
4691 obj[clientSegment].push(supplements.pop());
4692 }
4693 //
4694 // normalization done.
4695 //
4696 var lst = opt[citeprocSegment];
4697 while (lst.length) {
4698 lst.pop();
4699 }
4700 for (var j = 0, jlen = obj[clientSegment].length; j < jlen; j += 1) {
4701 lst.push(obj[clientSegment][j]);
4702 }
4703 }
4704};
4705
4706CSL.Engine.prototype.setLangPrefsForCiteAffixes = function (affixList) {
4707 if (affixList && affixList.length === 48) {
4708 var affixes = this.opt.citeAffixes;
4709 var count = 0;
4710 var settings = ["persons", "institutions", "titles", "journals", "publishers", "places"];
4711 var forms = ["translit", "orig", "translit", "translat"];
4712 var value;
4713 for (var i = 0, ilen = settings.length; i < ilen; i += 1) {
4714 for (var j = 0, jlen = forms.length; j < jlen; j += 1) {
4715 value = "";
4716 if ((count % 8) === 4) {
4717 if (!affixes[settings[i]]["locale-"+forms[j]].prefix
4718 && !affixes[settings[i]]["locale-"+forms[j]].suffix) {
4719
4720 value = affixList[count] ? affixList[count] : "";
4721 affixes[settings[i]]["locale-" + forms[j]].prefix = value;
4722 value = affixList[count] ? affixList[count + 1] : "";
4723 affixes[settings[i]]["locale-" + forms[j]].suffix = value;
4724 }
4725 } else {
4726 value = affixList[count] ? affixList[count] : "";
4727 affixes[settings[i]]["locale-" + forms[j]].prefix = value;
4728 value = affixList[count] ? affixList[count + 1] : "";
4729 affixes[settings[i]]["locale-" + forms[j]].suffix = value;
4730 }
4731 count += 2;
4732 }
4733 }
4734 this.opt.citeAffixes = affixes;
4735 }
4736};
4737
4738CSL.Engine.prototype.setAutoVietnameseNamesOption = function (arg) {
4739 if (arg) {
4740 this.opt["auto-vietnamese-names"] = true;
4741 } else {
4742 this.opt["auto-vietnamese-names"] = false;
4743 }
4744};
4745
4746CSL.Engine.prototype.setAbbreviations = function (arg) {
4747 if (this.sys.setAbbreviations) {
4748 this.sys.setAbbreviations(arg);
4749 }
4750};
4751
4752CSL.Engine.prototype.setSuppressTrailingPunctuation = function (arg) {
4753 this.citation.opt.suppressTrailingPunctuation = !!arg;
4754};
4755
4756/*global CSL: true */
4757
4758CSL.Output = {};
4759/**
4760 * Output queue object.
4761 * @class
4762 */
4763CSL.Output.Queue = function (state) {
4764 this.levelname = ["top"];
4765 this.state = state;
4766 this.queue = [];
4767 this.empty = new CSL.Token("empty");
4768 var tokenstore = {};
4769 tokenstore.empty = this.empty;
4770 this.formats = new CSL.Stack(tokenstore);
4771 this.current = new CSL.Stack(this.queue);
4772};
4773
4774// XXX This works, but causes a mismatch in api_cite
4775// Could insert a placeholder
4776// Better to have a function that spits out an independent blob.
4777// Is that possible though?
4778// Okay. Use queue.append() with fake_queue instead.
4779CSL.Output.Queue.prototype.pop = function () {
4780 // For some reason, state.output.current.value() here can be an array,
4781 // not a blob ... ?
4782 var drip = this.current.value();
4783 if (drip.length) {
4784 return drip.pop();
4785 } else {
4786 return drip.blobs.pop();
4787 }
4788};
4789
4790CSL.Output.Queue.prototype.getToken = function (name) {
4791 var ret = this.formats.value()[name];
4792 return ret;
4793};
4794
4795CSL.Output.Queue.prototype.mergeTokenStrings = function (base, modifier) {
4796 var base_token, modifier_token, ret, key;
4797 base_token = this.formats.value()[base];
4798 modifier_token = this.formats.value()[modifier];
4799 ret = base_token;
4800 if (modifier_token) {
4801 if (!base_token) {
4802 base_token = new CSL.Token(base, CSL.SINGLETON);
4803 base_token.decorations = [];
4804 }
4805 ret = new CSL.Token(base, CSL.SINGLETON);
4806 var key = "";
4807 for (var key in base_token.strings) {
4808 if (base_token.strings.hasOwnProperty(key)) {
4809 ret.strings[key] = base_token.strings[key];
4810 }
4811 }
4812 for (var key in modifier_token.strings) {
4813 if (modifier_token.strings.hasOwnProperty(key)) {
4814 ret.strings[key] = modifier_token.strings[key];
4815 }
4816 }
4817 ret.decorations = base_token.decorations.concat(modifier_token.decorations);
4818 }
4819 return ret;
4820};
4821
4822// Store a new output format token based on another
4823CSL.Output.Queue.prototype.addToken = function (name, modifier, token) {
4824 var newtok, attr;
4825 newtok = new CSL.Token("output");
4826 if ("string" === typeof token) {
4827 token = this.formats.value()[token];
4828 }
4829 if (token && token.strings) {
4830 for (attr in token.strings) {
4831 if (token.strings.hasOwnProperty(attr)) {
4832 newtok.strings[attr] = token.strings[attr];
4833 }
4834 }
4835 newtok.decorations = token.decorations;
4836
4837 }
4838 if ("string" === typeof modifier) {
4839 newtok.strings.delimiter = modifier;
4840 }
4841 this.formats.value()[name] = newtok;
4842};
4843
4844//
4845// newFormat adds a new bundle of formatting tokens to
4846// the queue's internal stack of such bundles
4847CSL.Output.Queue.prototype.pushFormats = function (tokenstore) {
4848 if (!tokenstore) {
4849 tokenstore = {};
4850 }
4851 tokenstore.empty = this.empty;
4852 this.formats.push(tokenstore);
4853};
4854
4855
4856CSL.Output.Queue.prototype.popFormats = function () {
4857 this.formats.pop();
4858};
4859
4860CSL.Output.Queue.prototype.startTag = function (name, token) {
4861 var tokenstore = {};
4862 if (this.state.tmp["doing-macro-with-date"] && this.state.tmp.extension) {
4863 token = this.empty;
4864 name = "empty";
4865 }
4866 tokenstore[name] = token;
4867 this.pushFormats(tokenstore);
4868 this.openLevel(name);
4869};
4870
4871CSL.Output.Queue.prototype.endTag = function (name) {
4872 this.closeLevel(name);
4873 this.popFormats();
4874};
4875
4876//
4877// newlevel adds a new blob object to the end of the current
4878// list, and adjusts the current pointer so that subsequent
4879// appends are made to blob list of the new object.
4880
4881CSL.Output.Queue.prototype.openLevel = function (token) {
4882 var blob, curr;
4883 if ("object" === typeof token) {
4884 // delimiter, prefix, suffix, decorations from token
4885 blob = new CSL.Blob(undefined, token);
4886 } else if ("undefined" === typeof token) {
4887 blob = new CSL.Blob(undefined, this.formats.value().empty, "empty");
4888 } else {
4889 if (!this.formats.value() || !this.formats.value()[token]) {
4890 CSL.error("CSL processor error: call to nonexistent format token \"" + token + "\"");
4891 }
4892 // delimiter, prefix, suffix, decorations from token
4893 blob = new CSL.Blob(undefined, this.formats.value()[token], token);
4894 }
4895 curr = this.current.value();
4896 if (!this.state.tmp.just_looking && this.checkNestedBrace) {
4897 blob.strings.prefix = this.checkNestedBrace.update(blob.strings.prefix);
4898 }
4899 curr.push(blob);
4900 this.current.push(blob);
4901};
4902
4903/**
4904 * "merge" used to be real complicated, now it's real simple.
4905 */
4906CSL.Output.Queue.prototype.closeLevel = function (name) {
4907 // CLEANUP: Okay, so this.current.value() holds the blob at the
4908 // end of the current list. This is wrong. It should
4909 // be the parent, so that we have the choice of reading
4910 // the affixes and decorations, or appending to its
4911 // content. The code that manipulates blobs will be
4912 // much simpler that way.
4913 if (name && name !== this.current.value().levelname) {
4914 CSL.error("Level mismatch error: wanted " + name + " but found " + this.current.value().levelname);
4915 }
4916 var blob = this.current.pop();
4917 if (!this.state.tmp.just_looking && this.checkNestedBrace) {
4918 blob.strings.suffix = this.checkNestedBrace.update(blob.strings.suffix);
4919 }
4920};
4921
4922//
4923// append does the same thing as newlevel, except
4924// that the blob it pushes has text content,
4925// and the current pointer is not moved after the push.
4926
4927CSL.Output.Queue.prototype.append = function (str, tokname, notSerious, ignorePredecessor, noStripPeriods) {
4928 var token, blob, curr;
4929 var useblob = true;
4930 if (notSerious) {
4931 ignorePredecessor = true;
4932 }
4933 // XXXXX Nasty workaround, but still an improvement
4934 // over the reverse calls to the cs:date node build
4935 // function that we had before.
4936 if (this.state.tmp["doing-macro-with-date"] && !notSerious) {
4937 if (tokname !== "macro-with-date") {
4938 return false;
4939 }
4940 if (tokname === "macro-with-date") {
4941 tokname = "empty";
4942 }
4943 }
4944 if ("undefined" === typeof str) {
4945 return false;
4946 }
4947 if ("number" === typeof str) {
4948 str = "" + str;
4949 }
4950 if (!notSerious
4951 && this.state.tmp.element_trace
4952 && this.state.tmp.element_trace.value() === "suppress-me") {
4953
4954 return false;
4955 }
4956 blob = false;
4957 if (!tokname) {
4958 token = this.formats.value().empty;
4959 } else if (tokname === "literal") {
4960 token = true;
4961 useblob = false;
4962 } else if ("string" === typeof tokname) {
4963 token = this.formats.value()[tokname];
4964 } else {
4965 token = tokname;
4966 }
4967 if (!token) {
4968 CSL.error("CSL processor error: unknown format token name: " + tokname);
4969 }
4970 // Unset delimiters must be left undefined until they reach the queue
4971 // in order to discriminate unset from explicitly empty delimiters
4972 // when inheriting a default value from a superior node. [??? really ???]
4973 if (token.strings && "undefined" === typeof token.strings.delimiter) {
4974 token.strings.delimiter = "";
4975 }
4976 if ("string" === typeof str && str.length) {
4977
4978 // Source (;?!»«): http://en.wikipedia.org/wiki/Space_(punctuation)#Breaking_and_non-breaking_spaces
4979 // Source (:): http://forums.zotero.org/discussion/4933/localized-quotes/#Comment_88384
4980 str = str.replace(/ ([:;?!\u00bb])/g, "\u202f$1").replace(/\u00ab /g, "\u00ab\u202f");
4981
4982 this.last_char_rendered = str.slice(-1);
4983 // This, and not the str argument below on flipflop, is the
4984 // source of the flipflopper string source.
4985 str = str.replace(/\s+'/g, " \'");
4986 if (!notSerious) {
4987 // this condition for sort_LeadingApostropheOnNameParticle
4988 str = str.replace(/^'/g, " \'");
4989 }
4990
4991 // signal whether we end with terminal punctuation?
4992 if (!ignorePredecessor) {
4993 this.state.tmp.term_predecessor = true;
4994 this.state.tmp.in_cite_predecessor = true;
4995 } else if (notSerious) {
4996 this.state.tmp.term_predecessor_name = true;
4997 }
4998 }
4999 blob = new CSL.Blob(str, token);
5000 curr = this.current.value();
5001 if ("undefined" === typeof curr && this.current.mystack.length === 0) {
5002 // XXXX An operation like this is missing somewhere, this should NOT be necessary.
5003 // Addresses error triggered in multi-layouts.
5004 this.current.mystack.push([]);
5005 curr = this.current.value();
5006 }
5007 if ("string" === typeof blob.blobs) {
5008 if (!ignorePredecessor) {
5009 this.state.tmp.term_predecessor = true;
5010 this.state.tmp.in_cite_predecessor = true;
5011 } else if (notSerious) {
5012 this.state.tmp.term_predecessor_name = true;
5013 }
5014 }
5015 //
5016 // Caution: The parallel detection machinery will blow up if tracking
5017 // variables are not properly initialized elsewhere.
5018 //
5019 if ("string" === typeof str) {
5020 if ("string" === typeof blob.blobs) {
5021 if (blob.blobs.slice(0, 1) !== " ") {
5022 var blobPrefix = "";
5023 var blobBlobs = blob.blobs;
5024 while (CSL.TERMINAL_PUNCTUATION.indexOf(blobBlobs.slice(0, 1)) > -1) {
5025 blobPrefix = blobPrefix + blobBlobs.slice(0, 1);
5026 blobBlobs = blobBlobs.slice(1);
5027 }
5028 if (blobBlobs && blobPrefix) {
5029 blob.strings.prefix = blob.strings.prefix + blobPrefix;
5030 blob.blobs = blobBlobs;
5031 }
5032 }
5033 }
5034 if (blob.strings["text-case"]) {
5035 //
5036 // This one is _particularly_ hard to follow. It's not obvious,
5037 // but the blob already contains the input string at this
5038 // point, as blob.blobs -- it's a terminal node, as it were.
5039 // The str variable also contains the input string, but
5040 // that copy is not used for onward processing. We have to
5041 // apply our changes to the blob copy.
5042 //
5043 blob.blobs = CSL.Output.Formatters[blob.strings["text-case"]](this.state, str);
5044 }
5045 if (this.state.tmp.strip_periods && !noStripPeriods) {
5046 blob.blobs = blob.blobs.replace(/\.([^a-z]|$)/g, "$1");
5047 }
5048 for (var i = blob.decorations.length - 1; i > -1; i += -1) {
5049 if (blob.decorations[i][0] === "@quotes" && blob.decorations[i][1] !== "false") {
5050 blob.punctuation_in_quote = this.state.getOpt("punctuation-in-quote");
5051 }
5052 if (!blob.blobs.match(CSL.ROMANESQUE_REGEXP)) {
5053 if (blob.decorations[i][0] === "@font-style") {
5054 blob.decorations = blob.decorations.slice(0, i).concat(blob.decorations.slice(i + 1));
5055 }
5056 }
5057 }
5058 //
5059 // XXX: Beware superfluous code in your code. str in this
5060 // case is not the source of the final rendered string.
5061 // See note above.
5062 //
5063 curr.push(blob);
5064 this.state.fun.flipflopper.processTags(blob);
5065 } else if (useblob) {
5066 curr.push(blob);
5067 } else {
5068 curr.push(str);
5069 }
5070 return true;
5071};
5072
5073CSL.Output.Queue.prototype.string = function (state, myblobs, blob) {
5074 var i, ilen, j, jlen, b;
5075 //if (blob && blob.strings.delimiter) {
5076 // print("DELIMITER: "+blob.strings.delimiter+" on "+[x.blobs[0].num for each (x in myblobs)]);
5077 //}
5078 //var blobs, ret, blob_delimiter, i, params, blobjr, last_str, last_char, b, use_suffix, qres, addtoret, span_split, j, res, blobs_start, blobs_end, key, pos, len, ppos, llen, ttype, ltype, terminal, leading, delimiters, use_prefix, txt_esc;
5079 var txt_esc = CSL.getSafeEscape(this.state);
5080 var blobs = myblobs.slice();
5081 var ret = [];
5082
5083 if (blobs.length === 0) {
5084 return ret;
5085 }
5086
5087 var blob_delimiter = "";
5088 if (blob) {
5089 blob_delimiter = blob.strings.delimiter;
5090 } else {
5091 //print("=== Setting false to start ===");
5092 state.tmp.count_offset_characters = false;
5093 state.tmp.offset_characters = 0;
5094 }
5095
5096 if (blob && blob.new_locale) {
5097 blob.old_locale = state.opt.lang;
5098 state.opt.lang = blob.new_locale;
5099 }
5100
5101 var blobjr, use_suffix, use_prefix, params;
5102 for (var i = 0, ilen = blobs.length; i < ilen; i += 1) {
5103 blobjr = blobs[i];
5104
5105 if (blobjr.strings.first_blob) {
5106 // Being the Item.id of the the entry being rendered.
5107 //print(" -- turning on counting");
5108 state.tmp.count_offset_characters = blobjr.strings.first_blob;
5109 }
5110
5111 if ("string" === typeof blobjr.blobs) {
5112 if ("number" === typeof blobjr.num) {
5113 ret.push(blobjr);
5114 } else if (blobjr.blobs) {
5115 if (blobjr.particle) {
5116 blobjr.blobs = blobjr.particle + blobjr.blobs;
5117 blobjr.particle = "";
5118 }
5119 // (skips empty strings)
5120 //b = txt_esc(blobjr.blobs);
5121 b = txt_esc(blobjr.blobs);
5122 var blen = b.length;
5123
5124 if (!state.tmp.suppress_decorations) {
5125 for (j = 0, jlen = blobjr.decorations.length; j < jlen; j += 1) {
5126 params = blobjr.decorations[j];
5127 if (params[0] === "@showid") {
5128 continue;
5129 }
5130 if (state.normalDecorIsOrphan(blobjr, params)) {
5131 continue;
5132 }
5133 b = state.fun.decorate[params[0]][params[1]].call(blobjr, state, b, params[2]);
5134 }
5135 }
5136 //
5137 // because we will rip out portions of the output
5138 // queue before rendering, group wrappers need
5139 // to produce no output if they are found to be
5140 // empty.
5141 if (b && b.length) {
5142 b = txt_esc(blobjr.strings.prefix) + b + txt_esc(blobjr.strings.suffix);
5143 if (state.opt.development_extensions.csl_reverse_lookup_support && !state.tmp.suppress_decorations) {
5144 for (j = 0, jlen = blobjr.decorations.length; j < jlen; j += 1) {
5145 params = blobjr.decorations[j];
5146
5147 if (params[0] === "@showid") {
5148 b = state.fun.decorate[params[0]][params[1]].call(blobjr, state, b, params[2]);
5149 }
5150 }
5151 }
5152 ret.push(b);
5153 if (state.tmp.count_offset_characters) {
5154 state.tmp.offset_characters += (blen + blobjr.strings.suffix.length + blobjr.strings.prefix.length);
5155 }
5156 }
5157 }
5158 } else if (blobjr.blobs.length) {
5159 var addtoret = state.output.string(state, blobjr.blobs, blobjr);
5160 if (blob) {
5161 // Patch up world-class weird bug in the ill-constructed code of mine.
5162 if ("string" !== addtoret && addtoret.length > 1 && blobjr.strings.delimiter) {
5163 var numberSeen = false;
5164 for (var j=0,jlen=addtoret.length;j<jlen;j++) {
5165 if ("string" !== typeof addtoret[j]) {
5166 numberSeen = true;
5167 } else if (numberSeen) {
5168 addtoret[j] = (blobjr.strings.delimiter + addtoret[j]);
5169 }
5170 }
5171 }
5172 }
5173 ret = ret.concat(addtoret);
5174 }
5175 if (blobjr.strings.first_blob && state.registry.registry[blobjr.strings.first_blob]) {
5176 // The Item.id of the entry being rendered.
5177 state.registry.registry[blobjr.strings.first_blob].offset = state.tmp.offset_characters;
5178 state.tmp.count_offset_characters = false;
5179 }
5180 }
5181
5182 // Provide delimiters on adjacent numeric blobs
5183 for (i=0,ilen=ret.length - 1;i<ilen;i+=1) {
5184 if ("number" === typeof ret[i].num && "number" === typeof ret[i+1].num && !ret[i+1].UGLY_DELIMITER_SUPPRESS_HACK) {
5185 // XXX watch this
5186 ret[i].strings.suffix = ret[i].strings.suffix + (blob_delimiter ? blob_delimiter : "");
5187 ret[i+1].successor_prefix = "";
5188 ret[i+1].UGLY_DELIMITER_SUPPRESS_HACK = true;
5189 }
5190 }
5191
5192 var span_split = 0;
5193 for (var i = 0, ilen = ret.length; i < ilen; i += 1) {
5194 if ("string" === typeof ret[i]) {
5195 span_split = (parseInt(i, 10) + 1);
5196 if (i < ret.length - 1 && "object" === typeof ret[i + 1]) {
5197 if (blob_delimiter && !ret[i + 1].UGLY_DELIMITER_SUPPRESS_HACK) {
5198 ret[i] += txt_esc(blob_delimiter);
5199 }
5200 // One bite of the apple
5201 ret[i + 1].UGLY_DELIMITER_SUPPRESS_HACK = true;
5202 }
5203 //span_split = ret.length;
5204 //print("XXX ret: "+ret+" -- "+blob_delimiter);
5205 }
5206 }
5207/*
5208 if (blob && (blob.decorations.length || blob.strings.suffix || blob.strings.prefix)) {
5209 span_split = ret.length;
5210 }
5211*/
5212 if (blob && (blob.decorations.length || blob.strings.suffix)) {
5213 span_split = ret.length;
5214 } else if (blob && blob.strings.prefix) {
5215 for (var i=0,ilen=ret.length;i<ilen;i++) {
5216 if ("undefined" !== typeof ret[i].num) {
5217 span_split = i;
5218 if (i === 0) {
5219 ret[i].strings.prefix = blob.strings.prefix + ret[i].strings.prefix;
5220 }
5221 break;
5222 }
5223 }
5224 }
5225
5226 var blobs_start = state.output.renderBlobs(ret.slice(0, span_split), blob_delimiter, false, blob);
5227 if (blobs_start && blob && (blob.decorations.length || blob.strings.suffix || blob.strings.prefix)) {
5228 if (!state.tmp.suppress_decorations) {
5229 for (var i = 0, ilen = blob.decorations.length; i < ilen; i += 1) {
5230 params = blob.decorations[i];
5231 if (["@cite","@bibliography", "@display", "@showid"].indexOf(params[0]) > -1) {
5232 continue;
5233 }
5234 if (state.normalDecorIsOrphan(blobjr, params)) {
5235 continue;
5236 }
5237 if ("string" === typeof blobs_start) {
5238 blobs_start = state.fun.decorate[params[0]][params[1]].call(blob, state, blobs_start, params[2]);
5239 }
5240 }
5241 }
5242 //
5243 // XXXX: cut-and-paste warning. same as a code block above.
5244 //
5245 b = blobs_start;
5246 use_suffix = blob.strings.suffix;
5247 if (b && b.length) {
5248 use_prefix = blob.strings.prefix;
5249 b = txt_esc(use_prefix) + b + txt_esc(use_suffix);
5250 if (state.tmp.count_offset_characters) {
5251 state.tmp.offset_characters += (use_prefix.length + use_suffix.length);
5252 }
5253 }
5254 blobs_start = b;
5255 if (!state.tmp.suppress_decorations) {
5256 for (var i = 0, ilen = blob.decorations.length; i < ilen; i += 1) {
5257 params = blob.decorations[i];
5258 if (["@cite","@bibliography", "@display", "@showid"].indexOf(params[0]) === -1) {
5259 continue;
5260 }
5261 if ("string" === typeof blobs_start) {
5262 blobs_start = state.fun.decorate[params[0]][params[1]].call(blob, state, blobs_start, params[2]);
5263 }
5264 }
5265 }
5266 }
5267
5268 var blobs_end = ret.slice(span_split, ret.length);
5269 if (!blobs_end.length && blobs_start) {
5270 ret = [blobs_start];
5271 } else if (blobs_end.length && !blobs_start) {
5272 ret = blobs_end;
5273 } else if (blobs_start && blobs_end.length) {
5274 ret = [blobs_start].concat(blobs_end);
5275 }
5276 //
5277 // Blobs is now definitely a string with
5278 // trailing blobs. Return it.
5279 if ("undefined" === typeof blob) {
5280 this.queue = [];
5281 this.current.mystack = [];
5282 this.current.mystack.push(this.queue);
5283 if (state.tmp.suppress_decorations) {
5284 ret = state.output.renderBlobs(ret, undefined, false);
5285 }
5286 } else if ("boolean" === typeof blob) {
5287 ret = state.output.renderBlobs(ret, undefined, true);
5288 }
5289
5290 if (blob && blob.new_locale) {
5291 state.opt.lang = blob.old_locale;
5292 }
5293 //if (!blob && !state.tmp.just_looking) {
5294 // print("QUEUE ("+ state.tmp.just_looking +"): "+JSON.stringify(state.output.queue, ["num", "strings", "decorations", "blobs", "prefix", "suffix", "delimiter"], 2));
5295 //}
5296 return ret;
5297};
5298
5299CSL.Output.Queue.prototype.clearlevel = function () {
5300 var blob, pos, len;
5301 blob = this.current.value();
5302 len = blob.blobs.length;
5303 for (pos = 0; pos < len; pos += 1) {
5304 blob.blobs.pop();
5305 }
5306};
5307
5308CSL.Output.Queue.prototype.renderBlobs = function (blobs, delim, in_cite, parent) {
5309 var state, ret, ret_last_char, use_delim, blob, pos, len, ppos, llen, str, params, txt_esc;
5310 txt_esc = CSL.getSafeEscape(this.state);
5311 if (!delim) {
5312 delim = "";
5313 }
5314 state = this.state;
5315 ret = "";
5316 ret_last_char = [];
5317 use_delim = "";
5318 len = blobs.length;
5319 if (this.state.tmp.area === "citation" && !this.state.tmp.just_looking && len === 1 && typeof blobs[0] === "object" && parent) {
5320 blobs[0].strings.prefix = parent.strings.prefix + blobs[0].strings.prefix;
5321 blobs[0].strings.suffix = blobs[0].strings.suffix + parent.strings.suffix;
5322 blobs[0].decorations = blobs[0].decorations.concat(parent.decorations);
5323 blobs[0].params = parent.params;
5324 return blobs[0];
5325 }
5326 var start = true;
5327 for (pos = 0; pos < len; pos += 1) {
5328 if (blobs[pos].checkNext) {
5329 blobs[pos].checkNext(blobs[pos + 1],start);
5330 start = false;
5331 } else if (blobs[pos+1] && blobs[pos+1].splice_prefix) {
5332 start = false;
5333 //blobs[pos+1].checkNext(blobs[pos + 1],start);
5334 } else {
5335 start = true;
5336 }
5337 }
5338
5339 // print("LEN="+len+" "+JSON.stringify(blobs, null, 2));
5340 // Fix last non-range join
5341 var doit = true;
5342 for (pos = blobs.length - 1; pos > 0; pos += -1) {
5343 if (blobs[pos].checkLast) {
5344 if (doit && blobs[pos].checkLast(blobs[pos - 1])) {
5345 doit = false;
5346 }
5347 } else {
5348 doit = true;
5349 }
5350 }
5351 len = blobs.length;
5352 for (pos = 0; pos < len; pos += 1) {
5353 blob = blobs[pos];
5354 if (ret) {
5355 use_delim = delim;
5356 }
5357 if ("string" === typeof blob) {
5358 ret += txt_esc(use_delim);
5359 // XXX Blob should be run through flipflop and flattened here.
5360 // (I think it must be a fragment of text around a numeric
5361 // variable)
5362 ret += blob;
5363 if (state.tmp.count_offset_characters) {
5364 //state.tmp.offset_characters += (use_delim.length + blob.length);
5365 state.tmp.offset_characters += (use_delim.length);
5366 }
5367 } else if (in_cite) {
5368 // pass
5369 // Okay, so this does it -- but we're now not able to return a string!
5370 if (ret) {
5371 ret = [ret, blob];
5372 } else {
5373 ret = [blob];
5374 }
5375 } else if (blob.status !== CSL.SUPPRESS) {
5376 if (blob.particle) {
5377 str = blob.particle + blob.num;
5378 } else {
5379 str = blob.formatter.format(blob.num, blob.gender);
5380 }
5381 // Workaround to get a more or less accurate value.
5382 var strlen = str.replace(/<[^>]*>/g, "").length;
5383 // notSerious
5384 this.append(str, "empty", true);
5385 var str_blob = this.pop();
5386 var count_offset_characters = state.tmp.count_offset_characters;
5387 str = this.string(state, [str_blob], false);
5388 state.tmp.count_offset_characters = count_offset_characters;
5389 if (blob.strings["text-case"]) {
5390 str = CSL.Output.Formatters[blob.strings["text-case"]](this.state, str);
5391 }
5392 if (str && this.state.tmp.strip_periods) {
5393 str = str.replace(/\.([^a-z]|$)/g, "$1");
5394 }
5395 if (!state.tmp.suppress_decorations) {
5396 llen = blob.decorations.length;
5397 for (ppos = 0; ppos < llen; ppos += 1) {
5398 params = blob.decorations[ppos];
5399 if (state.normalDecorIsOrphan(blob, params)) {
5400 continue;
5401 }
5402 str = state.fun.decorate[params[0]][params[1]].call(blob, state, str, params[2]);
5403 }
5404 }
5405 str = txt_esc(blob.strings.prefix) + str + txt_esc(blob.strings.suffix);
5406 var addme = "";
5407 if (blob.status === CSL.END) {
5408 //print(" CSL.END");
5409 addme = txt_esc(blob.range_prefix);
5410 } else if (blob.status === CSL.SUCCESSOR) {
5411 //print(" CSL.SUCCESSOR");
5412 addme = txt_esc(blob.successor_prefix);
5413 } else if (blob.status === CSL.START) {
5414 //print(" CSL.START");
5415 if (pos > 0 && !blob.suppress_splice_prefix) {
5416 addme = txt_esc(blob.splice_prefix);
5417 } else {
5418 addme = "";
5419 }
5420 } else if (blob.status === CSL.SEEN) {
5421 //print(" CSL.SEEN");
5422
5423 // THIS IS NOT THE PROPER FUNCTION OF CSL.SEEN, IS IT?
5424
5425 addme = txt_esc(blob.splice_prefix);
5426 }
5427 ret += addme;
5428 ret += str;
5429 if (state.tmp.count_offset_characters) {
5430 state.tmp.offset_characters += (addme.length + blob.strings.prefix.length + strlen + blob.strings.suffix.length);
5431 }
5432 }
5433 }
5434 return ret;
5435};
5436
5437CSL.Output.Queue.purgeEmptyBlobs = function (parent) {
5438 //print("START1");
5439 if ("object" !== typeof parent || "object" !== typeof parent.blobs || !parent.blobs.length) {
5440 return;
5441 }
5442 // back-to-front, bottom-first
5443 for (var i=parent.blobs.length-1;i>-1;i--) {
5444 CSL.Output.Queue.purgeEmptyBlobs(parent.blobs[i]);
5445 var child = parent.blobs[i];
5446 if (!child || !child.blobs || !child.blobs.length) {
5447 var buf = [];
5448 while ((parent.blobs.length-1) > i) {
5449 buf.push(parent.blobs.pop());
5450 }
5451 parent.blobs.pop();
5452 while (buf.length) {
5453 parent.blobs.push(buf.pop());
5454 }
5455 }
5456 }
5457 //print(" end");
5458};
5459
5460// Adjustments to be made:
5461//
5462// * Never migrate beyond a @quotes node
5463// * Never migrate into a num node.
5464
5465CSL.Output.Queue.adjust = function (punctInQuote) {
5466
5467 var NO_SWAP_IN = {
5468 ";": true,
5469 ":": true
5470 };
5471
5472 var NO_SWAP_OUT = {
5473 ".": true,
5474 "!": true,
5475 "?": true
5476 };
5477
5478 var LtoR_MAP = {
5479 "!": {
5480 ".": "!",
5481 "?": "!?",
5482 ":": "!",
5483 ",": "!,",
5484 ";": "!;"
5485 },
5486 "?": {
5487 "!": "?!",
5488 ".": "?",
5489 ":": "?",
5490 ",": "?,",
5491 ";": "?;"
5492 },
5493 ".": {
5494 "!": ".!",
5495 "?": ".?",
5496 ":": ".:",
5497 ",": ".,",
5498 ";": ".;"
5499 },
5500 ":": {
5501 "!": "!",
5502 "?": "?",
5503 ".": ":",
5504 ",": ":,",
5505 ";": ":;"
5506 },
5507 ",": {
5508 "!": ",!",
5509 "?": ",?",
5510 ":": ",:",
5511 ".": ",.",
5512 ";": ",;"
5513 },
5514 ";": {
5515 "!": "!",
5516 "?": "?",
5517 ":": ";",
5518 ",": ";,",
5519 ".": ";"
5520 }
5521 };
5522
5523 var SWAP_IN = {};
5524 var SWAP_OUT = {};
5525 var PUNCT = {};
5526 var PUNCT_OR_SPACE = {};
5527 for (var key in LtoR_MAP) {
5528 PUNCT[key] = true;
5529 PUNCT_OR_SPACE[key] = true;
5530 if (!NO_SWAP_IN[key]) {
5531 SWAP_IN[key] = true;
5532 }
5533 if (!NO_SWAP_OUT[key]) {
5534 SWAP_OUT[key] = true;
5535 }
5536 }
5537 PUNCT_OR_SPACE[" "] = true;
5538 PUNCT_OR_SPACE[" "] = true;
5539
5540 var RtoL_MAP = {};
5541 for (var key in LtoR_MAP) {
5542 for (var subkey in LtoR_MAP[key]) {
5543 if (!RtoL_MAP[subkey]) {
5544 RtoL_MAP[subkey] = {};
5545 }
5546 RtoL_MAP[subkey][key] = LtoR_MAP[key][subkey];
5547 }
5548 }
5549
5550 function blobIsNumber(blob) {
5551 return ("number" === typeof blob.num || (blob.blobs && blob.blobs.length === 1 && "number" === typeof blob.blobs[0].num));
5552 }
5553
5554 function blobEndsInNumber(blob) {
5555 if ("number" === typeof blob.num) {
5556 return true;
5557 }
5558 if (!blob.blobs || "object" !== typeof blob.blobs) {
5559 return false;
5560 }
5561 if (blobEndsInNumber(blob.blobs[blob.blobs.length-1])) {
5562 return true;
5563 }
5564 }
5565
5566 function blobHasDecorations(blob,includeQuotes) {
5567 var ret = false;
5568 var decorlist = ['@font-style','@font-variant','@font-weight','@text-decoration','@vertical-align'];
5569 if (includeQuotes) {
5570 decorlist.push('@quotes');
5571 }
5572 if (blob.decorations) {
5573 for (var i=0,ilen=blob.decorations.length;i<ilen;i++) {
5574 if (decorlist.indexOf(blob.decorations[i][0]) > -1) {
5575 ret = true;
5576 break;
5577 }
5578 }
5579 }
5580 return ret;
5581 }
5582
5583 function blobHasDescendantQuotes(blob) {
5584 if (blob.decorations) {
5585 for (var i=0,ilen=blob.decorations.length;i<ilen;i++) {
5586 if (blob.decorations[i][0] === '@quotes' && blob.decorations[i][1] !== "false") {
5587 return true;
5588 }
5589 }
5590 }
5591 if ("object" !== typeof blob.blobs) {
5592 return false;
5593 }
5594 return blobHasDescendantQuotes(blob.blobs[blob.blobs.length-1]);
5595 //if (blobHasDescendantQuotes(blob.blobs[blob.blobs.length-1])) {
5596 // return true
5597 //};
5598 //return false;
5599 }
5600
5601 function blobHasDescendantMergingPunctuation(parentChar,blob) {
5602 var childChar = blob.strings.suffix.slice(-1);
5603 if (!childChar && "string" === typeof blob.blobs) {
5604 childChar = blob.blobs.slice(-1);
5605 }
5606 var mergedChars = RtoL_MAP[parentChar][childChar];
5607 if (mergedChars && mergedChars.length === 1) {
5608 return true;
5609 }
5610 if ("object" !== typeof blob.blobs) {
5611 return false;
5612 }
5613 if (blobHasDescendantMergingPunctuation(parentChar,blob.blobs[blob.blobs.length-1])) {
5614 return true;
5615 }
5616 return false;
5617 }
5618
5619 function matchLastChar(blob, chr) {
5620 if (!PUNCT[chr]) {
5621 return false;
5622 }
5623 if ("string" === typeof blob.blobs) {
5624
5625 if (blob.blobs.slice(-1) === chr) {
5626 return true;
5627 } else {
5628 return false;
5629 }
5630 } else {
5631 var child = blob.blobs[blob.blobs.length-1];
5632 if (child) {
5633 var childChar = child.strings.suffix.slice(-1);
5634 if (!childChar) {
5635 return matchLastChar(child,chr);
5636 } else if (child.strings.suffix.slice(-1) == chr) {
5637 return true;
5638 } else {
5639 return false;
5640 }
5641 } else {
5642 return false;
5643 }
5644 }
5645 }
5646
5647 function mergeChars (First, first, Second, second, merge_right) {
5648 var FirstStrings = "blobs" === first ? First : First.strings;
5649 var SecondStrings = "blobs" === second ? Second: Second.strings;
5650 var firstChar = FirstStrings[first].slice(-1);
5651 var secondChar = SecondStrings[second].slice(0,1);
5652 function cullRight () {
5653 SecondStrings[second] = SecondStrings[second].slice(1);
5654 }
5655 function cullLeft () {
5656 FirstStrings[first] = FirstStrings[first].slice(0,-1);
5657 }
5658 function addRight (chr) {
5659 SecondStrings[second] = chr + SecondStrings[second];
5660 }
5661 function addLeft (chr) {
5662 FirstStrings[first] += chr;
5663 }
5664 var cull = merge_right ? cullLeft : cullRight;
5665 function matchOnRight () {
5666 return RtoL_MAP[secondChar];
5667 }
5668 function matchOnLeft () {
5669 return LtoR_MAP[firstChar];
5670 }
5671 var match = merge_right ? matchOnLeft : matchOnRight;
5672 function mergeToRight () {
5673 var chr = LtoR_MAP[firstChar][secondChar];
5674 if ("string" === typeof chr) {
5675 cullLeft();
5676 cullRight();
5677 addRight(chr);
5678 } else {
5679 addRight(firstChar);
5680 cullLeft();
5681 }
5682 }
5683 function mergeToLeft () {
5684 var chr = RtoL_MAP[secondChar][firstChar];
5685 if ("string" === typeof chr) {
5686 cullLeft();
5687 cullRight();
5688 addLeft(chr);
5689 } else {
5690 addLeft(secondChar);
5691 cullRight();
5692 }
5693 }
5694 var merge = merge_right ? mergeToRight: mergeToLeft;
5695
5696 var isDuplicate = firstChar === secondChar;
5697 if (isDuplicate) {
5698 cull();
5699 } else {
5700 if (match()) {
5701 merge();
5702 }
5703 }
5704 }
5705
5706 function upward (parent) {
5707 //print("START2");
5708 // Terminus if no blobs
5709 if (parent.blobs && "string" == typeof parent.blobs) {
5710 if (PUNCT[parent.strings.suffix.slice(0,1)]
5711 && parent.strings.suffix.slice(0,1) === parent.blobs.slice(-1)) {
5712
5713 parent.strings.suffix = parent.strings.suffix.slice(1);
5714 }
5715 return;
5716 } else if ("object" !== typeof parent || "object" !== typeof parent.blobs || !parent.blobs.length) {
5717 return;
5718 }
5719
5720 // back-to-front, bottom-first
5721 var parentDecorations = blobHasDecorations(parent,true);
5722 for (var i=parent.blobs.length-1;i>-1;i--) {
5723 this.upward(parent.blobs[i]);
5724 var parentStrings = parent.strings;
5725 var childStrings = parent.blobs[i].strings;
5726 if (i === 0) {
5727 // Remove leading space on first-position child node prefix if there is a trailing space on the node prefix above
5728 if (" " === parentStrings.prefix.slice(-1) && " " === childStrings.prefix.slice(0, 1)) {
5729 childStrings.prefix = childStrings.prefix.slice(1);
5730 }
5731 // Migrate leading punctuation or space on a first-position prefix upward
5732 var childChar = childStrings.prefix.slice(0, 1);
5733 if (!parentDecorations && PUNCT_OR_SPACE[childChar] && !parentStrings.prefix) {
5734 parentStrings.prefix += childChar;
5735 childStrings.prefix = childStrings.prefix.slice(1);
5736 }
5737 }
5738 if (i === (parent.blobs.length - 1)) {
5739 // Migrate trailing space ONLY on a last-position suffix upward, controlling for duplicates
5740 var childChar = childStrings.suffix.slice(-1);
5741 // ZZZ Loosened to fix initialized names wrapped in a span and followed by a period
5742 if (!parentDecorations && [" "].indexOf(childChar) > -1) {
5743 if (parentStrings.suffix.slice(0,1) !== childChar) {
5744 parentStrings.suffix = childChar + parentStrings.suffix;
5745 }
5746 childStrings.suffix = childStrings.suffix.slice(0, -1);
5747 }
5748 }
5749 if (parentStrings.delimiter && i > 0) {
5750 // Remove leading space on mid-position child node prefix if there is a trailing space on delimiter above
5751 if (PUNCT_OR_SPACE[parentStrings.delimiter.slice(-1)]
5752 && parentStrings.delimiter.slice(-1) === childStrings.prefix.slice(0, 1)) {
5753
5754 childStrings.prefix = childStrings.prefix.slice(1);
5755 }
5756 }
5757 // Siblings are handled in adjustNearsideSuffixes()
5758 }
5759 //print(" end");
5760 }
5761
5762 function leftward (parent) {
5763 // Terminus if no blobs
5764 if ("object" !== typeof parent || "object" !== typeof parent.blobs || !parent.blobs.length) {
5765 return;
5766 }
5767
5768 for (var i=parent.blobs.length-1;i>-1;i--) {
5769 this.leftward(parent.blobs[i]);
5770 // This is a delicate one.
5771 //
5772 // Migrate if:
5773 // * there is no umbrella delimiter [ok]
5774 // * neither the child nor its sibling is a number [ok]
5775 // * decorations exist neither on the child nor on the sibling [ok]
5776 // * sibling prefix char is a swapping char [ok]
5777 //
5778 // Suppress without migration if:
5779 // * sibling prefix char matches child suffix char or
5780 // * child suffix is empty and sibling prefix char match last field char
5781 if ((i < parent.blobs.length -1) && !parent.strings.delimiter) {
5782 // If there is a trailing swappable character on a sibling prefix with no intervening delimiter, copy it to suffix,
5783 // controlling for duplicates
5784 var child = parent.blobs[i];
5785 var childChar = child.strings.suffix.slice(-1);
5786 var sibling = parent.blobs[i+1];
5787 var siblingChar = sibling.strings.prefix.slice(0, 1);
5788 var hasDecorations = blobHasDecorations(child) || blobHasDecorations(sibling);
5789 var hasNumber = "number" === typeof childChar || "number" === typeof siblingChar;
5790
5791 if (!hasDecorations && !hasNumber && PUNCT[siblingChar] && !hasNumber) {
5792 var suffixAndPrefixMatch = siblingChar === child.strings.suffix.slice(-1);
5793 var suffixAndFieldMatch = (!child.strings.suffix && "string" === typeof child.blobs && child.blobs.slice(-1) === siblingChar);
5794 if (!suffixAndPrefixMatch && !suffixAndFieldMatch) {
5795 mergeChars(child, 'suffix', sibling, 'prefix');
5796 //child.strings.suffix += siblingChar;
5797 } else {
5798 sibling.strings.prefix = sibling.strings.prefix.slice(1);
5799 }
5800 }
5801 }
5802 }
5803 }
5804
5805 function downward (parent) {
5806 //print("START3");
5807 // Terminus if no blobs
5808 if (parent.blobs && "string" == typeof parent.blobs) {
5809 if (PUNCT[parent.strings.suffix.slice(0,1)]
5810 && parent.strings.suffix.slice(0,1) === parent.blobs.slice(-1)) {
5811
5812 parent.strings.suffix = parent.strings.suffix.slice(1);
5813 }
5814 return;
5815 } else if ("object" !== typeof parent || "object" !== typeof parent.blobs || !parent.blobs.length) {
5816 return;
5817 }
5818 //if (top) {
5819 // print("JSON "+JSON.stringify(parent, ["strings", "decorations", "blobs", "prefix", "suffix", "delimiter"], 2));
5820 //}
5821
5822 var parentStrings = parent.strings;
5823 // Check for numeric child
5824 var someChildrenAreNumbers = false;
5825 for (var i=0,ilen=parent.blobs.length;i<ilen;i++) {
5826 if (blobIsNumber(parent.blobs[i])) {
5827 someChildrenAreNumbers = true;
5828 break;
5829 }
5830 }
5831 if (true || !someChildrenAreNumbers) {
5832 // If there is a leading swappable character on delimiter, copy it to suffixes IFF none of the targets are numbers
5833 if (parentStrings.delimiter && PUNCT[parentStrings.delimiter.slice(0, 1)]) {
5834 var delimChar = parentStrings.delimiter.slice(0, 1);
5835 for (var i=parent.blobs.length-2;i>-1;i--) {
5836 var childStrings = parent.blobs[i].strings;
5837 if (childStrings.suffix.slice(-1) !== delimChar) {
5838 childStrings.suffix += delimChar;
5839 }
5840 }
5841 parentStrings.delimiter = parentStrings.delimiter.slice(1);
5842 }
5843 }
5844 // back-to-front, top-first
5845 for (var i=parent.blobs.length-1;i>-1;i--) {
5846 var child = parent.blobs[i];
5847 var childStrings = parent.blobs[i].strings;
5848 var childDecorations = blobHasDecorations(child, true);
5849 var childIsNumber = blobIsNumber(child);
5850
5851 if (i === (parent.blobs.length - 1)) {
5852
5853 //if (blobHasDescendantQuotes(child)) {
5854 // print("JSON "+JSON.stringify(parent, ["strings", "decorations", "blobs", "prefix", "suffix", "delimiter"]));
5855 //}
5856
5857 if (true || !someChildrenAreNumbers) {
5858 // If we have decorations, drill down to see if there are quotes below.
5859 // If so, we allow migration anyway.
5860 // Original discussion is here:
5861 // https://forums.zotero.org/discussion/37091/citeproc-bug-punctuation-in-quotes/
5862 var parentChar = parentStrings.suffix.slice(0, 1);
5863
5864 // Hmm.
5865 // Consider writing out the matching child from blobHasDescendant functions.
5866 // It should save some cycles, and produce the same result.
5867
5868 var allowMigration = false;
5869 if (PUNCT[parentChar]) {
5870 allowMigration = blobHasDescendantMergingPunctuation(parentChar,child);
5871 if (!allowMigration && punctInQuote) {
5872 allowMigration = blobHasDescendantQuotes(child);
5873 }
5874 }
5875 if (allowMigration) {
5876 if (PUNCT[parentChar]) {
5877 if (!blobEndsInNumber(child)) {
5878 if ("string" === typeof child.blobs) {
5879 mergeChars(child, 'blobs', parent, 'suffix');
5880 } else {
5881 mergeChars(child, 'suffix', parent, 'suffix');
5882 }
5883 if (parentStrings.suffix.slice(0,1) === ".") {
5884 childStrings.suffix += parentStrings.suffix.slice(0,1);
5885 parentStrings.suffix = parentStrings.suffix.slice(1);
5886 }
5887 }
5888 }
5889 }
5890 if (childStrings.suffix.slice(-1) === " " && parentStrings.suffix.slice(0,1) === " ") {
5891 parentStrings.suffix = parentStrings.suffix.slice(1);
5892 }
5893 // More duplicates control
5894 if (PUNCT_OR_SPACE[childStrings.suffix.slice(0,1)]) {
5895 if ("string" === typeof child.blobs && child.blobs.slice(-1) === childStrings.suffix.slice(0,1)) {
5896 // Remove parent punctuation of it duplicates the last character of a field
5897 childStrings.suffix = childStrings.suffix.slice(1);
5898 }
5899 if (childStrings.suffix.slice(-1) === parentStrings.suffix.slice(0, 1)) {
5900 // Remove duplicate punctuation on child suffix
5901 parentStrings.suffix = parentStrings.suffix.slice(0, -1);
5902 }
5903 }
5904 }
5905 // Squash dupes
5906 if (matchLastChar(parent,parent.strings.suffix.slice(0,1))) {
5907 parent.strings.suffix = parent.strings.suffix.slice(1);
5908 }
5909 } else if (parentStrings.delimiter) {
5910 // Remove trailing space on mid-position child node suffix if there is a leading space on delimiter above
5911 if (PUNCT_OR_SPACE[parentStrings.delimiter.slice(0,1)]
5912 && parentStrings.delimiter.slice(0, 1) === childStrings.suffix.slice(-1)) {
5913
5914 parent.blobs[i].strings.suffix = parent.blobs[i].strings.suffix.slice(0, -1);
5915
5916 }
5917 } else {
5918 // Otherwise it's a sibling. We don't care about moving spaces here, just suppress a duplicate
5919 var siblingStrings = parent.blobs[i+1].strings;
5920 if (!blobIsNumber(child)
5921 && !childDecorations
5922 && PUNCT_OR_SPACE[childStrings.suffix.slice(-1)]
5923 && childStrings.suffix.slice(-1) === siblingStrings.prefix.slice(0, 1)) {
5924
5925 siblingStrings.prefix = siblingStrings.prefix.slice(1);
5926 }
5927 }
5928 // If field content ends with swappable punctuation, suppress swappable punctuation in style suffix.
5929 if (!childIsNumber && !childDecorations && PUNCT[childStrings.suffix.slice(0,1)]
5930 && "string" === typeof child.blobs) {
5931
5932 mergeChars(child, 'blobs', child, 'suffix');
5933 }
5934 this.downward(parent.blobs[i]);
5935 }
5936/*
5937 if (top) {
5938
5939 var seen = [];
5940 print(JSON.stringify(parent, function(key, val) {
5941 if (!val || key === 'alldecor') return;
5942 if (typeof val == "object") {
5943 if (seen.indexOf(val) >= 0)
5944 return
5945 seen.push(val)
5946 }
5947 return val
5948 },2));
5949 }
5950*/
5951
5952 //print(" end");
5953 }
5954 // Abstract out a couple of utility functions, used in fix() below.
5955 function swapToTheLeft (child) {
5956 var childChar = child.strings.suffix.slice(0,1);
5957 if ("string" === typeof child.blobs) {
5958 while (SWAP_IN[childChar]) {
5959 mergeChars(child, 'blobs', child, 'suffix');
5960 childChar = child.strings.suffix.slice(0,1);
5961 }
5962 } else {
5963 while (SWAP_IN[childChar]) {
5964 mergeChars(child.blobs[child.blobs.length-1], 'suffix', child, 'suffix');
5965 childChar = child.strings.suffix.slice(0,1);
5966 }
5967 }
5968 }
5969 function swapToTheRight (child) {
5970 if ("string" === typeof child.blobs) {
5971 var childChar = child.blobs.slice(-1);
5972 while (SWAP_OUT[childChar]) {
5973 mergeChars(child, 'blobs', child, 'suffix', true);
5974 childChar = child.blobs.slice(-1);
5975 }
5976 } else {
5977 var childChar = child.blobs[child.blobs.length-1].strings.suffix.slice(-1);
5978 while (SWAP_OUT[childChar]) {
5979 mergeChars(child.blobs[child.blobs.length-1], 'suffix', child, 'suffix', true);
5980 childChar = child.blobs[child.blobs.length-1].strings.suffix.slice(-1);
5981 }
5982 }
5983 }
5984
5985 function fix (parent) {
5986 // Terminus if no blobs
5987 if ("object" !== typeof parent || "object" !== typeof parent.blobs || !parent.blobs.length) {
5988 return;
5989 }
5990
5991 //print("START4");
5992 // Do the swap, front-to-back, bottom-first
5993 var lastChar;
5994
5995 // XXX Two things to fix with this:
5996 // XXX (1) Stalls after one character
5997 // XXX (2) Moves colon and semicolon, both of which SHOULD stall
5998
5999 for (var i=0,ilen=parent.blobs.length;i<ilen;i++) {
6000 var child = parent.blobs[i];
6001 var quoteSwap = false;
6002 for (var j=0,jlen=child.decorations.length;j<jlen;j++) {
6003 var decoration = child.decorations[j];
6004 if (decoration[0] === "@quotes" && decoration[1] !== "false") {
6005 quoteSwap = true;
6006 }
6007 }
6008 if (quoteSwap) {
6009 if (punctInQuote) {
6010 swapToTheLeft(child);
6011 } else {
6012 swapToTheRight(child);
6013 }
6014 }
6015 lastChar = this.fix(parent.blobs[i]);
6016 if (child.blobs && "string" === typeof child.blobs) {
6017 lastChar = child.blobs.slice(-1);
6018 }
6019 }
6020 return lastChar;
6021 }
6022 this.upward = upward;
6023 this.leftward = leftward;
6024 this.downward = downward;
6025 this.fix = fix;
6026};
6027
6028/*global CSL: true */
6029
6030CSL.Engine.Opt = function () {
6031 this.has_disambiguate = false;
6032 this.mode = "html";
6033 this.dates = {};
6034 this.jurisdictions_seen = {};
6035 this.suppressedJurisdictions = {};
6036 this.inheritedAttributes = {};
6037 this["locale-sort"] = [];
6038 this["locale-translit"] = [];
6039 this["locale-translat"] = [];
6040 this.citeAffixes = {
6041 persons:{
6042 "locale-orig":{
6043 prefix:"",
6044 suffix:""
6045 },
6046 "locale-translit":{
6047 prefix:"",
6048 suffix:""
6049 },
6050 "locale-translat":{
6051 prefix:"",
6052 suffix:""
6053 }
6054 },
6055 institutions:{
6056 "locale-orig":{
6057 prefix:"",
6058 suffix:""
6059 },
6060 "locale-translit":{
6061 prefix:"",
6062 suffix:""
6063 },
6064 "locale-translat":{
6065 prefix:"",
6066 suffix:""
6067 }
6068 },
6069 titles:{
6070 "locale-orig":{
6071 prefix:"",
6072 suffix:""
6073 },
6074 "locale-translit":{
6075 prefix:"",
6076 suffix:""
6077 },
6078 "locale-translat":{
6079 prefix:"",
6080 suffix:""
6081 }
6082 },
6083 journals:{
6084 "locale-orig":{
6085 prefix:"",
6086 suffix:""
6087 },
6088 "locale-translit":{
6089 prefix:"",
6090 suffix:""
6091 },
6092 "locale-translat":{
6093 prefix:"",
6094 suffix:""
6095 }
6096 },
6097 publishers:{
6098 "locale-orig":{
6099 prefix:"",
6100 suffix:""
6101 },
6102 "locale-translit":{
6103 prefix:"",
6104 suffix:""
6105 },
6106 "locale-translat":{
6107 prefix:"",
6108 suffix:""
6109 }
6110 },
6111 places:{
6112 "locale-orig":{
6113 prefix:"",
6114 suffix:""
6115 },
6116 "locale-translit":{
6117 prefix:"",
6118 suffix:""
6119 },
6120 "locale-translat":{
6121 prefix:"",
6122 suffix:""
6123 }
6124 }
6125 };
6126 this["default-locale"] = [];
6127 this.update_mode = CSL.NONE;
6128 this.bib_mode = CSL.NONE;
6129 this.sort_citations = false;
6130 /*
6131 * Default values.
6132 * The various et-al values are set globally,
6133 * and the appropriate value is set by the names start
6134 * tag at runtime, depending on whether the Item is a
6135 * first or a subsequent reference.
6136 */
6137 this["et-al-min"] = 0;
6138 this["et-al-use-first"] = 1;
6139 this["et-al-use-last"] = false;
6140 this["et-al-subsequent-min"] = false;
6141 this["et-al-subsequent-use-first"] = false;
6142
6143 this["demote-non-dropping-particle"] = "display-and-sort";
6144 // default of true, because none of our consuming
6145 // applications so far store the various prefixes and
6146 // suffixes we support in separate fields.
6147 this["parse-names"] = true;
6148 // this["auto-vietnamese-names"] = true;
6149
6150 this.citation_number_slug = false;
6151 this.trigraph = "Aaaa00:AaAa00:AaAA00:AAAA00";
6152
6153 this.nodenames = [];
6154
6155 this.gender = {};
6156 this['cite-lang-prefs'] = {
6157 persons:['orig'],
6158 institutions:['orig'],
6159 titles:['orig'],
6160 journals:['orig'],
6161 publishers:['orig'],
6162 places:['orig'],
6163 number:['orig']
6164 };
6165
6166 this.has_layout_locale = false;
6167
6168 this.development_extensions = {};
6169 this.development_extensions.field_hack = true;
6170 this.development_extensions.allow_field_hack_date_override = true;
6171 this.development_extensions.locator_date_and_revision = true;
6172 this.development_extensions.locator_label_parse = true;
6173 this.development_extensions.raw_date_parsing = true;
6174 this.development_extensions.clean_up_csl_flaws = true;
6175 this.development_extensions.static_statute_locator = false;
6176 this.development_extensions.csl_reverse_lookup_support = false;
6177 this.development_extensions.wrap_url_and_doi = false;
6178 this.development_extensions.handle_parallel_articles = false;
6179 this.development_extensions.thin_non_breaking_space_html_hack = false;
6180 this.development_extensions.apply_citation_wrapper = false;
6181 this.development_extensions.main_title_from_short_title = false;
6182 this.development_extensions.uppercase_subtitles = false;
6183 this.development_extensions.normalize_lang_keys_to_lowercase = false;
6184 this.development_extensions.strict_text_case_locales = false;
6185 this.development_extensions.expect_and_symbol_form = false;
6186 this.development_extensions.require_explicit_legal_case_title_short = false;
6187 this.development_extensions.spoof_institutional_affiliations = false;
6188 this.development_extensions.force_jurisdiction = false;
6189 this.development_extensions.parse_names = true;
6190 this.development_extensions.hanging_indent_legacy_number = false;
6191 this.development_extensions.throw_on_empty = false;
6192 this.development_extensions.strict_inputs = true;
6193 this.development_extensions.prioritize_disambiguate_condition = false;
6194 this.development_extensions.force_short_title_casing_alignment = true;
6195 this.development_extensions.implicit_short_title = false;
6196};
6197
6198CSL.Engine.Tmp = function () {
6199 //
6200 // scratch variable to display the total
6201 // number of names in all rendered variables
6202 // in a cite. initialized to zero by the
6203 // citation element, incremented by each
6204 // name variable actually rendered
6205 this.names_max = new CSL.Stack();
6206 this.names_base = new CSL.Stack();
6207 this.givens_base = new CSL.Stack();
6208 //
6209 // this holds the field values collected by the @value
6210 // and @variable attributes, for processing by the
6211 // element functions.
6212 this.value = [];
6213 /**
6214 * Object to hold the decorations declared by a name-part
6215 * element.
6216 */
6217 this.namepart_decorations = {};
6218 /**
6219 * String variable to hold the type of a name-part
6220 * element.
6221 */
6222 this.namepart_type = false;
6223 //
6224 // scratch variable to flag whether we are processing
6225 // a citation or a bibiliography. this diverts token and
6226 // configuration to the appropriateo objects inside
6227 // state. the default is "citation".
6228 this.area = "citation";
6229 this.root = "citation";
6230 this.extension = "";
6231 //
6232 // controls the implicit conditional wrappers applied
6233 // to top-level elements inside a names substitute span.
6234 // false by default, names start tag pushes a new true level,
6235 // names end tag pops it. Output value check in @variable
6236 // function of attributes.js sets level to false. closing names
6237 // tag maps a false value to superior level.
6238 this.can_substitute = new CSL.Stack(0, CSL.LITERAL);
6239 //
6240 // notes whether the formatted elements of a date span
6241 // rendered anything. controls whether literal fallback
6242 // is used.
6243 this.element_rendered_ok = false;
6244 //
6245 // element_trace keeps a record of rendered elements.
6246 // used to implement author-only.
6247 //
6248 this.element_trace = new CSL.Stack("style");
6249 //
6250 // counter for total namesets
6251 this.nameset_counter = 0;
6252 //
6253 ///// this.fun.check_for_output = CSL.check_for_output;
6254 //
6255 // stack flag used for term handling. Set to true
6256 // if at least one variable has tried to render, and
6257 // no variables had content.
6258 this.group_context = new CSL.Stack({
6259 term_intended: false,
6260 variable_attempt: false,
6261 variable_success: false,
6262 output_tip: undefined,
6263 label_form: undefined,
6264 parallel_condition: undefined,
6265 parallel_result: undefined,
6266 no_repeat_condition: undefined,
6267 parallel_repeats: undefined,
6268 condition: false,
6269 force_suppress: false,
6270 done_vars: []
6271 });
6272 //
6273 // boolean flag used to control first-letter capitalization
6274 // of terms. Set to true if any item preceding the term
6275 // being handled has rendered successfully, otherwise
6276 // false.
6277 this.term_predecessor = false;
6278 //
6279 // boolean flag to control use of layout delimiter
6280 // immediately before numbers. This hack is needed for
6281 // some numeric styles.
6282 this.in_cite_predecessor = false;
6283 //
6284 // stack flag used to control jumps in the closing
6285 // token of a conditional.
6286 this.jump = new CSL.Stack(0, CSL.LITERAL);
6287 //
6288 // holds string parameters for group formatting, between
6289 // the start of a group and the closing token.
6290 this.decorations = new CSL.Stack();
6291 //
6292 // token store stack.
6293 this.tokenstore_stack = new CSL.Stack();
6294
6295 // for collapsing
6296 this.last_suffix_used = "";
6297 this.last_names_used = [];
6298 this.last_years_used = [];
6299 this.years_used = [];
6300 this.names_used = [];
6301
6302 this.taintedItemIDs = {};
6303 this.taintedCitationIDs = {};
6304 //
6305 // scratch stack containing initialize-with strings or null values
6306 this.initialize_with = new CSL.Stack();
6307 //
6308 // this is used to set a requested set of
6309 // disambiguation parameters in the output.
6310 // for the array elements, the base array
6311 // (either zero for each nameset, or full-up
6312 // if givens are already used) is set
6313 // during names processing, if no value
6314 // is set in the processor before a rendering
6315 // run. to simplify things for the calling
6316 // function, these are just bog-standard arrays,
6317 // and can be safely overwritten.
6318 this.disambig_request = false;
6319 //
6320 // scratch variable to toggle an attempt to set a
6321 // name in sort order rather than display
6322 // order.
6323 this["name-as-sort-order"] = false;
6324 //
6325 // suppress decorations (used for generating
6326 // sort keys and disambiguation keys)
6327 this.suppress_decorations = false;
6328 //
6329 // empty settings array, used to report settings used
6330 // if disambig_request is not set at runtime
6331 this.disambig_settings = new CSL.AmbigConfig();
6332 //
6333 // sort key array
6334 this.bib_sort_keys = [];
6335 //
6336 // holds the prefix between the start of a group
6337 // and the closing token.
6338 this.prefix = new CSL.Stack("", CSL.LITERAL);
6339 //
6340 // holds the suffix between the start of a group
6341 // and the closing token.
6342 this.suffix = new CSL.Stack("", CSL.LITERAL);
6343 //
6344 // holds the group delimiter between the start of a group
6345 // and the closing token.
6346 this.delimiter = new CSL.Stack("", CSL.LITERAL);
6347 //
6348 // Used for conditional locale switching.
6349 this.cite_locales = [];
6350 this.cite_affixes = {
6351 citation: false,
6352 bibliography: false,
6353 citation_sort: false,
6354 bibliography_sort: false
6355 };
6356 this.strip_periods = 0;
6357 this.shadow_numbers = {};
6358 this.authority_stop_last = 0;
6359 this.loadedItemIDs = {};
6360 //
6361 // Push/pop array for set/unset of opt.lang setting, used
6362 // in if locale="XX" to force terms to language of item.
6363 // @locale tests track their nesting level in a counter,
6364 // and push the current value of state.opt.lang to one array,
6365 // and the counter value to another. On the way back up,
6366 // closing node decrements the counter, compares its value
6367 // with the trailing value on the array, and pops both
6368 // arrays, resetting state.opt.lang to the previous value.
6369 // A hack to solve a surprisingly difficult problem caused
6370 // by the use of an execution stack for the nested structure.
6371 this.condition_counter = 0; //incremented/decremented on ALL conditions
6372 this.condition_lang_val_arr = [];
6373 this.condition_lang_counter_arr = [];
6374};
6375
6376
6377CSL.Engine.Fun = function (state) {
6378 //
6379 // matcher
6380 this.match = new CSL.Util.Match();
6381 //
6382 // utility to get standard suffixes for disambiguation
6383 this.suffixator = new CSL.Util.Suffixator(CSL.SUFFIX_CHARS);
6384 //
6385 // utility to romanize a numeric value
6386 this.romanizer = new CSL.Util.Romanizer();
6387 //
6388 // utility to make an ordinal form of a number
6389 this.ordinalizer = new CSL.Util.Ordinalizer(state);
6390 //
6391 // utility to make the long ordinal form of a number, if possible
6392 this.long_ordinalizer = new CSL.Util.LongOrdinalizer();
6393};
6394
6395
6396CSL.Engine.Build = function () {
6397 // Alternate et-al term
6398 // Holds the localization key of the alternative term
6399 // to be used for et-al in a names environment. Reduced
6400 // to a term object when the element tag is processed during
6401 // Build.
6402 this["alternate-term"] = false;
6403 //
6404 // flags that we are in the bibliography area.
6405 // used by sort.
6406 this.in_bibliography = false;
6407 //
6408 // scratch variable to alter behaviour when processing
6409 // locale files
6410 this.in_style = false;
6411 //
6412 // used to ignore info
6413 this.skip = false;
6414 //
6415 // the macro ATTRIBUTE stores a macro name on this
6416 // scratch variable anywhere outside the layout area
6417 // during build. The macro name is picked up when
6418 // the token is encountered inside the layout area,
6419 // either through a direct call, or as part of a nested
6420 // macro expansion, and the macro content is exploded
6421 // into the token list.
6422 this.postponed_macro = false;
6423 //
6424 // used especially for controlling macro expansion
6425 // during Build.
6426 this.layout_flag = false;
6427 //
6428 // (was buffer_name)
6429 // scratch variable to hold the name of a macro
6430 // or a term until its children have been collected.
6431 this.name = false;
6432 this.names_variables = [[]];
6433 this.name_label = [{}];
6434 //
6435 // scratch variable to hold the value of a form
6436 // attribute until other attributes needed for
6437 // processing have been collected.
6438 this.form = false;
6439 this.term = false;
6440 //
6441 // the macros themselves are discarded after Build
6442 this.macro = {};
6443 //
6444 // the macro build stack. used to raise an error
6445 // when macros would attempt to call themselves.
6446 this.macro_stack = [];
6447 //
6448 // stores the content of an XML text node during processing
6449 this.text = false;
6450 //
6451 // this is a scratch variable for holding an attribute
6452 // value during processing
6453 this.lang = false;
6454 //
6455 // should be able to run uninitialized; may attract some
6456 // cruft this way.
6457 this.area = "citation";
6458 this.root = "citation";
6459 this.extension = "";
6460 //
6461 // controls the application of implicit conditional wrappers
6462 // to top-level elements inside a names substitute span.
6463 // zero by default, build of names tag pushes a
6464 // new level with value 1. group start tag increments by 1,
6465 // group end tag decrements by 1. conditional wrappers are
6466 // only applied if value is exactly 1.
6467 this.substitute_level = new CSL.Stack(0, CSL.LITERAL);
6468 this.names_level = 0;
6469 this.render_nesting_level = 0;
6470 this.render_seen = false;
6471 this.bibliography_key_pos = 0;
6472};
6473
6474
6475CSL.Engine.Configure = function () {
6476 //
6477 // the fail and succeed arrays are used for stack
6478 // processing during configure.
6479 this.tests = [];
6480 this.fail = [];
6481 this.succeed = [];
6482};
6483
6484
6485CSL.Engine.Citation = function (state) {
6486 // Citation options area.
6487 // Holds a mixture of persistent and ephemeral
6488 // options and scratch data used during processing of
6489 // a citation.</p>
6490 this.opt = {
6491 inheritedAttributes: {}
6492 };
6493
6494 this.tokens = [];
6495 // Placeholder function
6496 this.srt = new CSL.Registry.Comparifier(state, "citation_sort");
6497 //
6498 // configuration array to hold the collapse
6499 // options, if any.
6500 this.opt.collapse = [];
6501 //
6502 // disambiguate options
6503 this.opt["disambiguate-add-names"] = false;
6504 this.opt["disambiguate-add-givenname"] = false;
6505 this.opt["disambiguate-add-year-suffix"] = false;
6506 this.opt["givenname-disambiguation-rule"] = "by-cite";
6507 this.opt["near-note-distance"] = 5;
6508
6509 this.opt.topdecor = [];
6510 this.opt.layout_decorations = [];
6511 this.opt.layout_prefix = "";
6512 this.opt.layout_suffix = "";
6513 this.opt.layout_delimiter = "";
6514 //
6515 // sorting
6516 this.opt.sort_locales = [];
6517 this.opt.max_number_of_names = 0;
6518 this.root = "citation";
6519};
6520
6521
6522CSL.Engine.Bibliography = function () {
6523 this.opt = {
6524 inheritedAttributes: {}
6525 };
6526 this.tokens = [];
6527
6528 this.opt.collapse = [];
6529
6530 this.opt.topdecor = [];
6531 this.opt.layout_decorations = [];
6532 this.opt.layout_prefix = "";
6533 this.opt.layout_suffix = "";
6534 this.opt.layout_delimiter = "";
6535 this.opt["line-spacing"] = 1;
6536 this.opt["entry-spacing"] = 1;
6537 //
6538 // sorting
6539 this.opt.sort_locales = [];
6540 this.opt.max_number_of_names = 0;
6541 this.root = "bibliography";
6542};
6543
6544
6545CSL.Engine.BibliographySort = function () {
6546 this.tokens = [];
6547 this.opt = {};
6548 this.opt.sort_directions = [];
6549 this.opt.topdecor = [];
6550 // Holds the final citation-number sort direction, for use
6551 // in applying numbers in cs:citation and cs:bibliography.
6552 // Value is exclusively controlled by cs:key in bibliography_sort
6553 this.opt.citation_number_sort_direction = CSL.ASCENDING;
6554 this.opt.citation_number_secondary = false;
6555 this.tmp = {};
6556 this.keys = [];
6557 this.root = "bibliography";
6558};
6559
6560
6561CSL.Engine.CitationSort = function () {
6562 this.tokens = [];
6563 this.opt = {};
6564 this.opt.sort_directions = [];
6565 this.keys = [];
6566 this.opt.topdecor = [];
6567 this.root = "citation";
6568};
6569
6570CSL.Engine.InText = function () {
6571 // InText options area.
6572 // Holds a mixture of persistent and ephemeral
6573 // options and scratch data used during processing of
6574 // a citation.</p>
6575 this.opt = {
6576 inheritedAttributes: {}
6577 };
6578
6579 this.tokens = [];
6580 // Placeholder function
6581 //this.srt = new CSL.Registry.Comparifier(state, "citation_sort");
6582 //
6583 // configuration array to hold the collapse
6584 // options, if any.
6585 this.opt.collapse = [];
6586 //
6587 // disambiguate options
6588 this.opt["disambiguate-add-names"] = false;
6589 this.opt["disambiguate-add-givenname"] = false;
6590 this.opt["disambiguate-add-year-suffix"] = false;
6591 this.opt["givenname-disambiguation-rule"] = "by-cite";
6592 this.opt["near-note-distance"] = 5;
6593
6594 this.opt.topdecor = [];
6595 this.opt.layout_decorations = [];
6596 this.opt.layout_prefix = "";
6597 this.opt.layout_suffix = "";
6598 this.opt.layout_delimiter = "";
6599 //
6600 // sorting
6601 this.opt.sort_locales = [];
6602 this.opt.max_number_of_names = 0;
6603 this.root = "intext";
6604};
6605
6606/*global CSL: true */
6607
6608CSL.Engine.prototype.previewCitationCluster = function (citation, citationsPre, citationsPost, newMode) {
6609 // Generate output for a hypothetical citation at the current position,
6610 // Leave the registry in the same state in which it was found.
6611 //print("################### previewCitationCluster() #################");
6612 var oldMode = this.opt.mode;
6613 this.setOutputFormat(newMode);
6614 // Avoids generating unwanted ibids, if the citationID already exists in document
6615 if (citation.citationID) {
6616 delete citation.citationID;
6617 }
6618 var ret = this.processCitationCluster(citation, citationsPre, citationsPost, CSL.PREVIEW);
6619
6620 this.setOutputFormat(oldMode);
6621 return ret[1];
6622};
6623
6624CSL.Engine.prototype.appendCitationCluster = function (citation) {
6625 var citationsPre = [];
6626 var len = this.registry.citationreg.citationByIndex.length;
6627 for (var pos = 0; pos < len; pos += 1) {
6628 var c = this.registry.citationreg.citationByIndex[pos];
6629 citationsPre.push(["" + c.citationID, c.properties.noteIndex]);
6630 }
6631 // Drop the data segment to return a list of pos/string pairs.
6632 return this.processCitationCluster(citation, citationsPre, [])[1];
6633};
6634
6635
6636CSL.Engine.prototype.processCitationCluster = function (citation, citationsPre, citationsPost, flag) {
6637 var c, preCitation, postCitation, i, ilen, j, jlen, k, klen, n, nlen, key, Item, item, noteCitations, textCitations, m, citationsInNote;
6638 this.debug = false;
6639 this.tmp.loadedItemIDs = {};
6640
6641 // Revert citation dereference from 2ffc4664ae
6642 //citation = JSON.parse(JSON.stringify(citation));
6643
6644 //print("################### processCitationCluster() #################");
6645 this.tmp.citation_errors = [];
6646 this.registry.return_data = {"bibchange": false};
6647
6648 // make sure this citation has a unique ID, and register it in citationById.
6649 this.setCitationId(citation);
6650
6651 var oldCitationList;
6652 var oldItemList;
6653 var oldAmbigs;
6654 if (flag === CSL.PREVIEW) {
6655 //SNIP-START
6656 if (this.debug) {
6657 CSL.debug("****** start state save *********");
6658 }
6659 //SNIP-END
6660 //
6661 // Simplify.
6662
6663 // Take a slice of existing citations.
6664 oldCitationList = this.registry.citationreg.citationByIndex.slice();
6665
6666 // Take a slice of current items, for later use with update.
6667 oldItemList = this.registry.reflist.slice();
6668
6669 // Make a list of preview citation ref objects. Omit the current
6670 // citation, because it will not exist in registry if: (a) this is
6671 // a new citation; or (b) the calling application is assigning
6672 // new citationIDs for every transaction.
6673 var newCitationList = citationsPre.concat(citationsPost);
6674
6675 // Make a full list of desired ids, for use in preview update,
6676 // and a hash list of same while we're at it.
6677 // First step through known citations, then step through
6678 // the items in the citation for preview.
6679 var newItemIds = {};
6680 var newItemIdsList = [];
6681 for (var i = 0, ilen = newCitationList.length; i < ilen; i += 1) {
6682 c = this.registry.citationreg.citationById[newCitationList[i][0]];
6683 for (j = 0, jlen = c.citationItems.length; j < jlen; j += 1) {
6684 newItemIds[c.citationItems[j].id] = true;
6685 newItemIdsList.push("" + c.citationItems[j].id);
6686 }
6687 }
6688 for (j = 0, jlen = citation.citationItems.length; j < jlen; j += 1) {
6689 newItemIds[citation.citationItems[j].id] = true;
6690 newItemIdsList.push("" + citation.citationItems[j].id);
6691 }
6692
6693 // Clone and save off disambigs of items that will be lost.
6694 oldAmbigs = {};
6695 for (var i = 0, ilen = oldItemList.length; i < ilen; i += 1) {
6696 if (!newItemIds[oldItemList[i].id]) {
6697 var oldAkey = this.registry.registry[oldItemList[i].id].ambig;
6698 var ids = this.registry.ambigcites[oldAkey];
6699 if (ids) {
6700 for (j = 0, jlen = ids.length; j < jlen; j += 1) {
6701 oldAmbigs[ids[j]] = CSL.cloneAmbigConfig(this.registry.registry[ids[j]].disambig);
6702 }
6703 }
6704 }
6705 }
6706
6707 // Update items. This will produce the base name data and sort things.
6708 // Possibly unnecessary?
6709 //this.updateItems(this.registry.mylist.concat(tmpItems));
6710
6711 //SNIP-START
6712 if (this.debug) {
6713 CSL.debug("****** end state save *********");
6714 }
6715 //SNIP-END
6716 }
6717
6718 this.tmp.taintedCitationIDs = {};
6719 var sortedItems = [];
6720
6721 // Styles that use note backreferencing with a by-cite
6722 // givenname disambiguation rule include the note number
6723 // in the cite for disambiguation purposes. Correct resolution
6724 // of disambiguate="true" conditions on first-reference cites
6725 // in certain editing scenarios (e.g. where a cite is moved across
6726 // notes) requires that disambiguation be rerun on cites
6727 // affected by the edit.
6728 var rerunAkeys = {};
6729
6730 // retrieve item data and compose items for use in rendering
6731 // attach pointer to item data to shared copy for good measure
6732 for (var i = 0, ilen = citation.citationItems.length; i < ilen; i += 1) {
6733 // Protect against caller-side overwrites to locator strings etc
6734 item = {};
6735 for (var key in citation.citationItems[i]) {
6736 item[key] = citation.citationItems[i][key];
6737 }
6738 Item = this.retrieveItem("" + item.id);
6739 if (Item.id) {
6740 this.transform.loadAbbreviation("default", "hereinafter", Item.id);
6741 }
6742 item = CSL.parseLocator.call(this, item);
6743 if (this.opt.development_extensions.static_statute_locator) {
6744 this.remapSectionVariable([[Item,item]]);
6745 }
6746 if (this.opt.development_extensions.locator_label_parse) {
6747 if (item.locator && ["bill","gazette","legislation","regulation","treaty"].indexOf(Item.type) === -1 && (!item.label || item.label === 'page')) {
6748 var m = CSL.LOCATOR_LABELS_REGEXP.exec(item.locator);
6749 if (m) {
6750 var tryLabel = CSL.LOCATOR_LABELS_MAP[m[2]];
6751 if (this.getTerm(tryLabel)) {
6752 item.label = tryLabel;
6753 item.locator = m[3];
6754 }
6755 }
6756 }
6757 }
6758 var newitem = [Item, item];
6759 sortedItems.push(newitem);
6760 citation.citationItems[i].item = Item;
6761 }
6762
6763 // ZZZ sort stuff moved from here.
6764
6765 // attach the sorted list to the citation item
6766 citation.sortedItems = sortedItems;
6767
6768 // build reconstituted citations list in current document order
6769 var citationByIndex = [];
6770 var citationById = {};
6771 var lastNotePos;
6772 for (i=0, ilen=citationsPre.length; i<ilen; i += 1) {
6773 preCitation = citationsPre[i];
6774 if (this.opt.development_extensions.strict_inputs) {
6775 if (citationById[preCitation[0]]) {
6776 CSL.error("Previously referenced citationID " + preCitation[0] + " encountered in citationsPre");
6777 }
6778 if (preCitation[1]) {
6779 if (lastNotePos > preCitation[1]) {
6780 CSL.debug("Note index sequence is not sane at citationsPre[" + i + "]");
6781 }
6782 lastNotePos = preCitation[1];
6783 }
6784 }
6785 this.registry.citationreg.citationById[preCitation[0]].properties.noteIndex = preCitation[1];
6786 citationByIndex.push(this.registry.citationreg.citationById[preCitation[0]]);
6787 citationById[preCitation[0]] = this.registry.citationreg.citationById[preCitation[0]];
6788 }
6789 if (!citation.properties) {
6790 citation.properties = {
6791 noteIndex: 0
6792 };
6793 }
6794 if (this.opt.development_extensions.strict_inputs) {
6795 if (citationById[citation.citationID]) {
6796 CSL.error("Citation with previously referenced citationID " + citation.citationID);
6797 }
6798 if (citation.properties.noteIndex) {
6799 if (lastNotePos > citation.properties.noteIndex) {
6800 CSL.debug("Note index sequence is not sane for citation " + citation.citationID);
6801 }
6802 lastNotePos = citation.properties.noteIndex;
6803 }
6804 }
6805 citationByIndex.push(citation);
6806 citationById[citation.citationID] = citation;
6807 for (i=0, ilen=citationsPost.length; i<ilen; i += 1) {
6808 postCitation = citationsPost[i];
6809 if (this.opt.development_extensions.strict_inputs) {
6810 if (citationById[postCitation[0]]) {
6811 CSL.error("Previously referenced citationID " + postCitation[0] + " encountered in citationsPost");
6812 }
6813 if (postCitation[1]) {
6814 if (lastNotePos > postCitation[1]) {
6815 CSL.debug("Note index sequence is not sane at postCitation[" + i + "]");
6816 }
6817 lastNotePos = postCitation[1];
6818 }
6819 }
6820 this.registry.citationreg.citationById[postCitation[0]].properties.noteIndex = postCitation[1];
6821 citationByIndex.push(this.registry.citationreg.citationById[postCitation[0]]);
6822 citationById[postCitation[0]] = this.registry.citationreg.citationById[postCitation[0]];
6823 }
6824 this.registry.citationreg.citationByIndex = citationByIndex;
6825 this.registry.citationreg.citationById = citationById;
6826
6827 //
6828 // The processor provides three facilities to support
6829 // updates following position reevaluation.
6830 //
6831 // (1) The updateItems() function reports tainted ItemIDs
6832 // to state.tmp.taintedItemIDs.
6833 //
6834 // (2) The processor memos the type of style referencing as
6835 // CSL.NONE, CSL.NUMERIC or CSL.POSITION in state.opt.update_mode.
6836 //
6837 // XXXX: NO LONGER
6838 // (3) For citations containing cites with backreference note numbers,
6839 // a string image of the rendered citation is held in
6840 // citation.properties.backref_citation, and a list of
6841 // ItemIDs to be used to update the backreference note numbers
6842 // is memoed at citation.properties.backref_index. When such
6843 // citations change position, they can be updated with a
6844 // series of simple find and replace operations, without
6845 // need for rerendering.
6846 //
6847
6848 //
6849 // Position evaluation!
6850 //
6851 // set positions in reconstituted list, noting taints
6852 this.registry.citationreg.citationsByItemId = {};
6853 if (this.opt.update_mode === CSL.POSITION) {
6854 textCitations = [];
6855 noteCitations = [];
6856 citationsInNote = {};
6857 }
6858 var update_items = [];
6859 for (var i = 0, ilen = citationByIndex.length; i < ilen; i += 1) {
6860 citationByIndex[i].properties.index = i;
6861 for (j = 0, jlen = citationByIndex[i].sortedItems.length; j < jlen; j += 1) {
6862 item = citationByIndex[i].sortedItems[j];
6863 if (!this.registry.citationreg.citationsByItemId[item[1].id]) {
6864 this.registry.citationreg.citationsByItemId[item[1].id] = [];
6865 update_items.push("" + item[1].id);
6866 }
6867 if (this.registry.citationreg.citationsByItemId[item[1].id].indexOf(citationByIndex[i]) === -1) {
6868 this.registry.citationreg.citationsByItemId[item[1].id].push(citationByIndex[i]);
6869 }
6870 }
6871 if (this.opt.update_mode === CSL.POSITION) {
6872 if (citationByIndex[i].properties.noteIndex) {
6873 noteCitations.push(citationByIndex[i]);
6874 } else {
6875 citationByIndex[i].properties.noteIndex = 0;
6876 textCitations.push(citationByIndex[i]);
6877 }
6878 }
6879 }
6880 //
6881 // update bibliography items here
6882 //
6883 if (flag !== CSL.ASSUME_ALL_ITEMS_REGISTERED) {
6884 //SNIP-START
6885 if (this.debug) {
6886 CSL.debug("****** start update items *********");
6887 }
6888 //SNIP-END
6889 // true signals implicit updateItems (will not rerun sys.retrieveItem())
6890 this.updateItems(update_items, null, null, true);
6891 //SNIP-START
6892 if (this.debug) {
6893 CSL.debug("****** endo update items *********");
6894 }
6895 //SNIP-END
6896 }
6897
6898 if (!this.opt.citation_number_sort && sortedItems && sortedItems.length > 1 && this.citation_sort.tokens.length > 0) {
6899 for (var i = 0, ilen = sortedItems.length; i < ilen; i += 1) {
6900 sortedItems[i][1].sortkeys = CSL.getSortKeys.call(this, sortedItems[i][0], "citation_sort");
6901 }
6902
6903 /*
6904 * Grouped sort stuff (start)
6905 */
6906
6907 if (this.opt.grouped_sort && !citation.properties.unsorted) {
6908 // Insert authorstring as key.
6909 for (var i = 0, ilen = sortedItems.length; i < ilen; i += 1) {
6910 var sortkeys = sortedItems[i][1].sortkeys;
6911 this.tmp.authorstring_request = true;
6912 // Run getAmbiguousCite() with the current disambig
6913 // parameters, and pick up authorstring from the registry.
6914 var mydisambig = this.registry.registry[sortedItems[i][0].id].disambig;
6915
6916 this.tmp.authorstring_request = true;
6917 CSL.getAmbiguousCite.call(this, sortedItems[i][0], mydisambig);
6918 var authorstring = this.registry.authorstrings[sortedItems[i][0].id];
6919 this.tmp.authorstring_request = false;
6920
6921 sortedItems[i][1].sortkeys = [authorstring].concat(sortkeys);
6922 }
6923
6924 sortedItems.sort(this.citation.srt.compareCompositeKeys);
6925 // Replace authorstring key in items with same (authorstring) with the
6926 // keystring of first normal key. This forces grouped sorts,
6927 // as discussed here:
6928 // https://github.com/citation-style-language/schema/issues/40
6929 var lastauthor = false;
6930 var thiskey = false;
6931 var thisauthor = false;
6932 for (var i = 0, ilen = sortedItems.length; i < ilen; i += 1) {
6933 if (sortedItems[i][1].sortkeys[0] !== lastauthor) {
6934 thisauthor = sortedItems[i][1].sortkeys[0];
6935 thiskey = sortedItems[i][1].sortkeys[1];
6936 }
6937 sortedItems[i][1].sortkeys[0] = "" + thiskey + i;
6938 lastauthor = thisauthor;
6939 }
6940 }
6941 /*
6942 * Grouped sort stuff (end)
6943 */
6944
6945 if (!citation.properties.unsorted) {
6946 sortedItems.sort(this.citation.srt.compareCompositeKeys);
6947 }
6948 }
6949
6950 // evaluate parallels
6951 this.parallel.StartCitation(citation.sortedItems);
6952
6953 var citations;
6954 if (this.opt.update_mode === CSL.POSITION) {
6955 for (var i = 0; i < 2; i += 1) {
6956 citations = [textCitations, noteCitations][i];
6957 var first_ref = {};
6958 var last_ref = {};
6959 for (j = 0, jlen = citations.length; j < jlen; j += 1) {
6960 var onecitation = citations[j];
6961 if (!citations[j].properties.noteIndex) {
6962 citations[j].properties.noteIndex = 0;
6963 }
6964 citations[j].properties.noteIndex = parseInt(citations[j].properties.noteIndex, 10);
6965 if (j > 0 && citations[j - 1].properties.noteIndex > citations[j].properties.noteIndex) {
6966 citationsInNote = {};
6967 first_ref = {};
6968 last_ref = {};
6969 }
6970 for (k = 0, klen = onecitation.sortedItems.length; k < klen; k += 1) {
6971 if (onecitation.sortedItems[k][1].parallel && onecitation.sortedItems[k][1].parallel !== "first") {
6972 continue;
6973 }
6974 if (!citationsInNote[onecitation.properties.noteIndex]) {
6975 citationsInNote[onecitation.properties.noteIndex] = 1;
6976 } else {
6977 citationsInNote[onecitation.properties.noteIndex] += 1;
6978 }
6979 }
6980 // Set the following:
6981 //
6982 // (1) position as required (as per current Zotero)
6983 // (2) first-reference-note-number as required (on onecitation item)
6984 // (3) near-note as required (on onecitation item, according to
6985 // state.opt["near-note-distance"] parameter)
6986 // (4) state.registry.citationreg.citationsByItemId.
6987 //
6988 // Any state changes caused by unsetting or resetting should
6989 // trigger a single entry for the citations in
6990 // state.tmp.taintedCitationIDs (can block on presence of
6991 // state.registry.citationreg.citationsByItemId).
6992 //
6993 for (k = 0, klen = citations[j].sortedItems.length; k < klen; k += 1) {
6994 item = citations[j].sortedItems[k];
6995 var myid = item[0].id;
6996 var myxloc = item[1]["locator-extra"];
6997 var mylocator = item[1].locator;
6998 var mylabel = item[1].label;
6999 if (item[0].legislation_id) {
7000 myid = item[0].legislation_id;
7001 }
7002 var incitationid;
7003 var incitationxloc;
7004 if (k > 0) {
7005 // incitationid is only reached in the else branch
7006 // following "undefined" === typeof first_ref[myid]
7007 // below
7008 if (onecitation.sortedItems[k - 1][0].legislation_id) {
7009 incitationid = onecitation.sortedItems[k - 1][0].legislation_id;
7010 } else {
7011 incitationid = onecitation.sortedItems[k - 1][1].id;
7012 incitationxloc = onecitation.sortedItems[k - 1][1]["locator-extra"];
7013 //if (onecitation.sortedItems[k-1][1].parallel === "last") {
7014 for (var l=k-2; l>-1; l--) {
7015 if (onecitation.sortedItems[l][1].parallel === "first") {
7016 incitationid = onecitation.sortedItems[l][1].id;
7017 incitationxloc = onecitation.sortedItems[l][1]["locator-extra"];
7018 }
7019 }
7020 //}
7021 }
7022 }
7023 // Don't touch item data of other cites when previewing
7024 if (flag === CSL.PREVIEW) {
7025 if (onecitation.citationID != citation.citationID) {
7026 if ("undefined" === typeof first_ref[item[1].id]) {
7027 first_ref[myid] = onecitation.properties.noteIndex;
7028 last_ref[myid] = onecitation.properties.noteIndex;
7029 } else {
7030 last_ref[myid] = onecitation.properties.noteIndex;
7031 }
7032 continue;
7033 }
7034 }
7035 var oldvalue = {};
7036 oldvalue.position = item[1].position;
7037 oldvalue["first-reference-note-number"] = item[1]["first-reference-note-number"];
7038 oldvalue["near-note"] = item[1]["near-note"];
7039 item[1]["first-reference-note-number"] = 0;
7040 item[1]["near-note"] = false;
7041 if (this.registry.citationreg.citationsByItemId[myid]) {
7042 if (this.opt.xclass === 'note' && this.opt.has_disambiguate) {
7043 var oldCount = this.registry.registry[myid]["citation-count"];
7044 var newCount = this.registry.citationreg.citationsByItemId[myid].length;
7045 this.registry.registry[myid]["citation-count"] = this.registry.citationreg.citationsByItemId[myid].length;
7046 if ("number" === typeof oldCount) {
7047 var oldCountCheck = (oldCount < 2);
7048 var newCountCheck = (newCount < 2);
7049 if (oldCountCheck !== newCountCheck) {
7050 for (var l=0,llen=this.registry.citationreg.citationsByItemId[myid].length;l<llen;l++) {
7051 rerunAkeys[this.registry.registry[myid].ambig] = true;
7052 this.tmp.taintedCitationIDs[this.registry.citationreg.citationsByItemId[myid][l].citationID] = true;
7053 }
7054 }
7055 } else {
7056 for (var l=0,llen=this.registry.citationreg.citationsByItemId[myid].length;l<llen;l++) {
7057 rerunAkeys[this.registry.registry[myid].ambig] = true;
7058 this.tmp.taintedCitationIDs[this.registry.citationreg.citationsByItemId[myid][l].citationID] = true;
7059 }
7060 }
7061 }
7062 }
7063 var oldlastid;
7064 var oldlastxloc;
7065
7066 if ("undefined" === typeof first_ref[myid] && onecitation.properties.mode !== "author-only") {
7067 first_ref[myid] = onecitation.properties.noteIndex;
7068 if (this.registry.registry[myid]) {
7069 this.registry.registry[myid]['first-reference-note-number'] = onecitation.properties.noteIndex;
7070 }
7071 last_ref[myid] = onecitation.properties.noteIndex;
7072 item[1].position = CSL.POSITION_FIRST;
7073 } else {
7074 //
7075 // backward-looking position evaluation happens here.
7076 //
7077 //
7078 //
7079 var ibidme = false;
7080 var suprame = false;
7081 var prevCitation = null;
7082 if (j > 0) {
7083 var prevCitation = citations[j-1];
7084 }
7085 var thisCitation = citations[j];
7086 // XXX Ugly, but This is used in the second else-if branch condition below.
7087 if (j > 0) {
7088 var old_last_id_offset = 1;
7089 if (prevCitation.properties.mode === "author-only" && j > 1) {
7090 old_last_id_offset = 2;
7091 }
7092 oldlastid = citations[j - old_last_id_offset].sortedItems.slice(-1)[0][1].id;
7093 oldlastxloc = citations[j - old_last_id_offset].sortedItems.slice(-1)[0][1]["locator-extra"];
7094 if (prevCitation.sortedItems[0].slice(-1)[0].legislation_id) {
7095 oldlastid = prevCitation.sortedItems[0].slice(-1)[0].legislation_id;
7096 }
7097 }
7098 if (j > 0 && k === 0 && prevCitation.properties.noteIndex !== thisCitation.properties.noteIndex) {
7099 // Case 1: source in previous onecitation
7100 // (1) Threshold conditions
7101 // (a) there must be a previous onecitation with one item
7102 // (b) this item must be the first in this onecitation
7103 // (c) the previous onecitation must contain a reference
7104 // to the same item ...
7105 // (d) the note numbers must be the same or consecutive.
7106 // (this has some jiggery-pokery in it for parallels)
7107 var useme = false;
7108 // XXX Can oldid be equated with oldlastid, I wonder ...
7109 var oldid = prevCitation.sortedItems[0][0].id;
7110 if (prevCitation.sortedItems[0][0].legislation_id) {
7111 oldid = prevCitation.sortedItems[0][0].legislation_id;
7112 }
7113 if ((oldid == myid && prevCitation.properties.noteIndex >= (thisCitation.properties.noteIndex - 1))) {
7114 var prevxloc = prevCitation.sortedItems[0][1]["locator-extra"];
7115 var thisxloc = thisCitation.sortedItems[0][1]["locator-extra"];
7116 if ((citationsInNote[prevCitation.properties.noteIndex] === 1 || prevCitation.properties.noteIndex === 0) && prevxloc === thisxloc) {
7117 useme = true;
7118 }
7119 }
7120 if (useme) {
7121 ibidme = true;
7122 } else {
7123 suprame = true;
7124 }
7125 } else if (k > 0 && incitationid == myid && incitationxloc == myxloc) {
7126 // Case 2: immediately preceding source in this onecitation
7127 // (1) Threshold conditions
7128 // (a) there must be an imediately preceding reference to the
7129 // same item in this onecitation; and
7130 ibidme = true;
7131 } else if (k === 0 && j > 0 && prevCitation.properties.noteIndex == thisCitation.properties.noteIndex
7132 && prevCitation.sortedItems.length
7133 && oldlastid == myid && oldlastxloc == myxloc) {
7134 // ... in case there are separate citations in the same note ...
7135 // Case 2 [take 2]: immediately preceding source in this onecitation
7136 // (1) Threshold conditions
7137 // (a) there must be an imediately preceding reference to the
7138 // same item in this onecitation; and
7139 ibidme = true;
7140 } else {
7141 // everything else is definitely subsequent
7142 suprame = true;
7143 }
7144 // conditions
7145 var prev, prev_locator, prev_label, curr_locator, curr_label;
7146 if (ibidme) {
7147 if (k > 0) {
7148 prev = onecitation.sortedItems[(k - 1)][1];
7149 } else {
7150 prev = citations[(j - 1)].sortedItems[0][1];
7151 }
7152 if (prev.locator) {
7153 if (prev.label) {
7154 prev_label = prev.label;
7155 } else {
7156 prev_label = "";
7157 }
7158 prev_locator = "" + prev.locator + prev_label;
7159 } else {
7160 prev_locator = prev.locator;
7161 }
7162 if (mylocator) {
7163 if (mylabel) {
7164 curr_label = mylabel;
7165 } else {
7166 curr_label = "";
7167 }
7168 curr_locator = "" + mylocator + curr_label;
7169 } else {
7170 curr_locator = mylocator;
7171 }
7172 }
7173 // triage
7174 if (ibidme && prev_locator && !curr_locator) {
7175 ibidme = false;
7176 suprame = true;
7177
7178 }
7179 if (ibidme) {
7180 if (!prev_locator && curr_locator) {
7181 // (a) if the previous onecitation had no locator
7182 // and this onecitation has one, use ibid+pages
7183 item[1].position = CSL.POSITION_IBID_WITH_LOCATOR;
7184 } else if (!prev_locator && !curr_locator) {
7185 // (b) if the previous onecitation had no locator
7186 // and this onecitation also has none, use ibid
7187 item[1].position = CSL.POSITION_IBID;
7188 //print("setting ibid in cmd_cite()");
7189 } else if (prev_locator && curr_locator === prev_locator) {
7190 // (c) if the previous onecitation had a locator
7191 // (page number, etc.) and this onecitation has
7192 // a locator that is identical, use ibid
7193
7194 item[1].position = CSL.POSITION_IBID;
7195 //print("setting ibid in cmd_cite() [2]");
7196 } else if (prev_locator && curr_locator && curr_locator !== prev_locator) {
7197 // (d) if the previous onecitation had a locator,
7198 // and this onecitation has one that differs,
7199 // use ibid+pages
7200 item[1].position = CSL.POSITION_IBID_WITH_LOCATOR;
7201 } else {
7202 // (e) if the previous onecitation had a locator
7203 // and this onecitation has none, use subsequent
7204 //
7205 // ... and everything else would be subsequent also
7206 ibidme = false; // just to be clear
7207 suprame = true;
7208 }
7209 }
7210 if (suprame) {
7211 item[1].position = CSL.POSITION_SUBSEQUENT;
7212 }
7213 if (suprame || ibidme) {
7214 if (onecitation.properties.mode === "author-only") {
7215 item[1].position = CSL.POSITION_FIRST;
7216 }
7217 if (first_ref[myid] != onecitation.properties.noteIndex) {
7218 item[1]["first-reference-note-number"] = first_ref[myid];
7219 if (this.registry.registry[myid]) {
7220 // This is either the earliest recorded number, or the number of the current citation, whichever is smaller.
7221 var oldFirst = this.registry.citationreg.citationsByItemId[myid][0].properties.noteIndex;
7222 var newFirst = onecitation.properties.noteIndex;
7223 this.registry.registry[myid]['first-reference-note-number'] = newFirst < oldFirst ? newFirst: oldFirst;
7224 }
7225 }
7226 }
7227 }
7228 if (onecitation.properties.noteIndex) {
7229 var note_distance = parseInt(onecitation.properties.noteIndex, 10) - parseInt(last_ref[myid], 10);
7230 if (item[1].position !== CSL.POSITION_FIRST
7231 && note_distance <= this.citation.opt["near-note-distance"]) {
7232 item[1]["near-note"] = true;
7233 }
7234 last_ref[myid] = onecitation.properties.noteIndex;
7235 }
7236 if (onecitation.citationID != citation.citationID) {
7237 for (n = 0, nlen = CSL.POSITION_TEST_VARS.length; n < nlen; n += 1) {
7238 var param = CSL.POSITION_TEST_VARS[n];
7239 if (item[1][param] !== oldvalue[param]) {
7240 if (this.registry.registry[myid]) {
7241 if (param === 'first-reference-note-number') {
7242 rerunAkeys[this.registry.registry[myid].ambig] = true;
7243 this.tmp.taintedItemIDs[myid] = true;
7244 }
7245 }
7246 this.tmp.taintedCitationIDs[onecitation.citationID] = true;
7247 }
7248 }
7249 }
7250 if (this.sys.variableWrapper) {
7251 item[1].index = onecitation.properties.index;
7252 item[1].noteIndex = onecitation.properties.noteIndex;
7253 }
7254 }
7255 }
7256 }
7257 }
7258 if (this.opt.citation_number_sort && sortedItems && sortedItems.length > 1 && this.citation_sort.tokens.length > 0) {
7259 if (!citation.properties.unsorted) {
7260 for (var i = 0, ilen = sortedItems.length; i < ilen; i += 1) {
7261 sortedItems[i][1].sortkeys = CSL.getSortKeys.call(this, sortedItems[i][0], "citation_sort");
7262 }
7263 sortedItems.sort(this.citation.srt.compareCompositeKeys);
7264 }
7265 }
7266 for (var key in this.tmp.taintedItemIDs) {
7267 if (this.tmp.taintedItemIDs.hasOwnProperty(key)) {
7268 citations = this.registry.citationreg.citationsByItemId[key];
7269 // Current citation may be tainted but will not exist
7270 // during previewing.
7271 if (citations) {
7272 for (var i = 0, ilen = citations.length; i < ilen; i += 1) {
7273 this.tmp.taintedCitationIDs[citations[i].citationID] = true;
7274 }
7275 }
7276 }
7277 }
7278
7279 var ret = [];
7280 if (flag === CSL.PREVIEW) {
7281 // If previewing, return only a rendered string
7282 //SNIP-START
7283 if (this.debug) {
7284 CSL.debug("****** start run processor *********");
7285 }
7286 //SNIP-END
7287 try {
7288 ret = this.process_CitationCluster.call(this, citation.sortedItems, citation);
7289 } catch (e) {
7290 CSL.error("Error running CSL processor for preview: "+e);
7291 }
7292
7293 //SNIP-START
7294 if (this.debug) {
7295 CSL.debug("****** end run processor *********");
7296 CSL.debug("****** start state restore *********");
7297 }
7298 //SNIP-END
7299 // Wind out anything related to new items added for the preview.
7300 // This means (1) names, (2) disambig state for affected items,
7301 // (3) keys registered in the ambigs pool arrays, and (4) registry
7302 // items.
7303 //
7304
7305 // restore sliced citations
7306 this.registry.citationreg.citationByIndex = oldCitationList;
7307 this.registry.citationreg.citationById = {};
7308 for (var i = 0, ilen = oldCitationList.length; i < ilen; i += 1) {
7309 this.registry.citationreg.citationById[oldCitationList[i].citationID] = oldCitationList[i];
7310 }
7311
7312 //SNIP-START
7313 if (this.debug) {
7314 CSL.debug("****** start final update *********");
7315 }
7316 //SNIP-END
7317 var oldItemIds = [];
7318 for (var i = 0, ilen = oldItemList.length; i < ilen; i += 1) {
7319 oldItemIds.push("" + oldItemList[i].id);
7320 }
7321 this.updateItems(oldItemIds, null, null, true);
7322 //SNIP-START
7323 if (this.debug) {
7324 CSL.debug("****** end final update *********");
7325 }
7326 //SNIP-END
7327 // Roll back disambig states
7328 for (var key in oldAmbigs) {
7329 if (oldAmbigs.hasOwnProperty(key)) {
7330 this.registry.registry[key].disambig = oldAmbigs[key];
7331 }
7332 }
7333 //SNIP-START
7334 if (this.debug) {
7335 CSL.debug("****** end state restore *********");
7336 }
7337 //SNIP-END
7338 } else {
7339 // Rerun cites that have moved across citations or had a change
7340 // in their number of subsequent references, so that disambiguate
7341 // and subsequent-reference-count conditions are applied
7342 // correctly in output.
7343 for (var rerunAkey in rerunAkeys) {
7344 this.disambiguate.run(rerunAkey, citation);
7345 }
7346 // Run taints only if not previewing
7347 //
7348 // Push taints to the return object
7349 //
7350 var obj;
7351 for (var key in this.tmp.taintedCitationIDs) {
7352 if (key == citation.citationID) {
7353 continue;
7354 }
7355 var mycitation = this.registry.citationreg.citationById[key];
7356 if (!mycitation.properties.unsorted) {
7357 for (var i = 0, ilen = mycitation.sortedItems.length; i < ilen; i += 1) {
7358 mycitation.sortedItems[i][1].sortkeys = CSL.getSortKeys.call(this, mycitation.sortedItems[i][0], "citation_sort");
7359 }
7360 mycitation.sortedItems.sort(this.citation.srt.compareCompositeKeys);
7361 }
7362 // For error reporting
7363 this.tmp.citation_pos = mycitation.properties.index;
7364 this.tmp.citation_note_index = mycitation.properties.noteIndex;
7365 this.tmp.citation_id = "" + mycitation.citationID;
7366 obj = [];
7367 obj.push(mycitation.properties.index);
7368 obj.push(this.process_CitationCluster.call(this, mycitation.sortedItems, mycitation));
7369 obj.push(mycitation.citationID);
7370 ret.push(obj);
7371 }
7372 this.tmp.taintedItemIDs = {};
7373 this.tmp.taintedCitationIDs = {};
7374
7375 // For error reporting again
7376 this.tmp.citation_pos = citation.properties.index;
7377 this.tmp.citation_note_index = citation.properties.noteIndex;
7378 this.tmp.citation_id = "" + citation.citationID;
7379
7380 obj = [];
7381 obj.push(citationsPre.length);
7382 obj.push(this.process_CitationCluster.call(this, sortedItems, citation));
7383 obj.push(citation.citationID);
7384 ret.push(obj);
7385 //
7386 // note for posterity: Rhino and Spidermonkey produce different
7387 // sort results for items with matching keys. That discrepancy
7388 // turned up a subtle bug in the parallel detection code, trapped
7389 // at line 266, above, and in line 94 of util_parallel.js.
7390 //
7391 ret.sort(function (a, b) {
7392 if (a[0] > b[0]) {
7393 return 1;
7394 } else if (a[0] < b[0]) {
7395 return -1;
7396 } else {
7397 return 0;
7398 }
7399 });
7400 //
7401 // In normal rendering, return is a list of two-part arrays, with the first element
7402 // a citation index number, and the second the text to be inserted.
7403 //
7404 }
7405 this.registry.return_data.citation_errors = this.tmp.citation_errors.slice();
7406 return [this.registry.return_data, ret];
7407};
7408
7409CSL.Engine.prototype.process_CitationCluster = function (sortedItems, citation) {
7410 var str = "";
7411 // Parallels must be evaluated in the calling function
7412 //this.parallel.StartCitation(sortedItems);
7413 if (citation && citation.properties && citation.properties.mode === "composite") {
7414 citation.properties.mode = "author-only";
7415 var firstChunk = CSL.getCitationCluster.call(this, sortedItems, citation);
7416 citation.properties.mode = "suppress-author";
7417 var secondChunk = "";
7418 if (citation.properties.infix) {
7419 this.output.append(citation.properties.infix);
7420 secondChunk = this.output.string(this, this.output.queue);
7421 // Had no idea this could return a single-element array! Go figure.
7422 if ("object" === typeof secondChunk) {
7423 secondChunk = secondChunk.join("");
7424 }
7425 }
7426 var thirdChunk = CSL.getCitationCluster.call(this, sortedItems, citation);
7427 citation.properties.mode = "composite";
7428 if (firstChunk && secondChunk && CSL.SWAPPING_PUNCTUATION.concat(["\u2019", "\'"]).indexOf(secondChunk[0]) > -1) {
7429 firstChunk += secondChunk;
7430 secondChunk = false;
7431 }
7432 str = [firstChunk, secondChunk, thirdChunk].filter(function(obj) {
7433 return obj;
7434 }).join(" ");
7435 } else {
7436 str = CSL.getCitationCluster.call(this, sortedItems, citation);
7437 }
7438 return str;
7439};
7440
7441CSL.Engine.prototype.makeCitationCluster = function (rawList) {
7442 var inputList, newitem, str, pos, len, item, Item;
7443 inputList = [];
7444 len = rawList.length;
7445 for (pos = 0; pos < len; pos += 1) {
7446 item = {};
7447 for (var key in rawList[pos]) {
7448 item[key] = rawList[pos][key];
7449 }
7450 Item = this.retrieveItem("" + item.id);
7451 // Code block is copied from processCitationCluster() above
7452 if (this.opt.development_extensions.locator_label_parse) {
7453 if (item.locator && ["bill","gazette","legislation","regulation","treaty"].indexOf(Item.type) === -1 && (!item.label || item.label === 'page')) {
7454 var m = CSL.LOCATOR_LABELS_REGEXP.exec(item.locator);
7455 if (m) {
7456 var tryLabel = CSL.LOCATOR_LABELS_MAP[m[2]];
7457 if (this.getTerm(tryLabel)) {
7458 item.label = tryLabel;
7459 item.locator = m[3];
7460 }
7461 }
7462 }
7463 }
7464 if (item.locator) {
7465 item.locator = ("" + item.locator).replace(/\s+$/, '');
7466 }
7467 newitem = [Item, item];
7468 inputList.push(newitem);
7469 }
7470 if (this.opt.development_extensions.static_statute_locator) {
7471 this.remapSectionVariable(inputList);
7472 }
7473 if (inputList && inputList.length > 1 && this.citation_sort.tokens.length > 0) {
7474 len = inputList.length;
7475 for (pos = 0; pos < len; pos += 1) {
7476 inputList[pos][1].sortkeys = CSL.getSortKeys.call(this, inputList[pos][0], "citation_sort");
7477 }
7478 inputList.sort(this.citation.srt.compareCompositeKeys);
7479 }
7480 this.tmp.citation_errors = [];
7481 this.parallel.StartCitation(inputList);
7482 var str = CSL.getCitationCluster.call(this, inputList);
7483 return str;
7484};
7485
7486
7487/**
7488 * Get the undisambiguated version of a cite, without decorations
7489 * <p>This is used internally by the Registry.</p>
7490 *
7491 * [object] CSL Item
7492 * [object] disambiguation parameters
7493 * [boolean] If true, include first-reference-note-number value in cite
7494 */
7495CSL.getAmbiguousCite = function (Item, disambig, visualForm, item) {
7496 var ret;
7497 var flags = this.tmp.group_context.tip;
7498 var oldTermSiblingLayer = {
7499 term_intended: flags.term_intended,
7500 variable_attempt: flags.variable_attempt,
7501 variable_success: flags.variable_success,
7502 output_tip: flags.output_tip,
7503 label_form: flags.label_form,
7504 parallel_condition: flags.parallel_condition,
7505 parallel_result: flags.parallel_result,
7506 no_repeat_condition: flags.no_repeat_condition,
7507 parallel_repeats: flags.parallel_result,
7508 condition: flags.condition,
7509 force_suppress: flags.force_suppress,
7510 done_vars: flags.done_vars.slice()
7511 };
7512 if (disambig) {
7513 this.tmp.disambig_request = disambig;
7514 } else {
7515 this.tmp.disambig_request = false;
7516 }
7517 var itemSupp = {
7518 position: 1,
7519 "near-note": true
7520 };
7521
7522 if (item) {
7523 itemSupp.locator = item.locator;
7524 itemSupp.label = item.label;
7525 }
7526
7527 if (this.registry.registry[Item.id]
7528 && this.registry.citationreg.citationsByItemId
7529 && this.registry.citationreg.citationsByItemId[Item.id]
7530 && this.registry.citationreg.citationsByItemId[Item.id].length
7531 && visualForm) {
7532 if (this.citation.opt["givenname-disambiguation-rule"] === "by-cite") {
7533 itemSupp['first-reference-note-number'] = this.registry.registry[Item.id]['first-reference-note-number'];
7534 }
7535 }
7536 this.tmp.area = "citation";
7537 this.tmp.root = "citation";
7538 var origSuppressDecorations = this.tmp.suppress_decorations;
7539 this.tmp.suppress_decorations = true;
7540 this.tmp.just_looking = true;
7541
7542 CSL.getCite.call(this, Item, itemSupp, null, false);
7543 // !!!
7544 for (var i=0,ilen=this.output.queue.length;i<ilen;i+=1) {
7545 CSL.Output.Queue.purgeEmptyBlobs(this.output.queue[i]);
7546 }
7547 if (this.opt.development_extensions.clean_up_csl_flaws) {
7548 for (var j=0,jlen=this.output.queue.length;j<jlen;j+=1) {
7549 this.output.adjust.upward(this.output.queue[j]);
7550 this.output.adjust.leftward(this.output.queue[j]);
7551 this.output.adjust.downward(this.output.queue[j]);
7552 this.output.adjust.fix(this.output.queue[j]);
7553 }
7554 }
7555 var ret = this.output.string(this, this.output.queue);
7556 this.tmp.just_looking = false;
7557 this.tmp.suppress_decorations = origSuppressDecorations;
7558 // Cache the result.
7559 this.tmp.group_context.replace(oldTermSiblingLayer);
7560 return ret;
7561};
7562
7563/**
7564 * Return delimiter for use in join
7565 * <p>Splice evaluation is done during cite
7566 * rendering, and this method returns the
7567 * result. Evaluation requires three items
7568 * of information from the preceding cite, if
7569 * one is present: the names used; the years
7570 * used; and the suffix appended to the
7571 * citation. These details are copied into
7572 * the state object before processing begins,
7573 * and are cleared by the processor on
7574 * completion of the run.</p>
7575 */
7576
7577CSL.getSpliceDelimiter = function (last_locator, last_collapsed, pos) {
7578 //print(pos + " after-collapse-delimiter="+this.citation.opt["after-collapse-delimiter"] + "\n cite_group_delimiter=" + this.tmp.use_cite_group_delimiter + "\n last_collapsed=" +last_collapsed + "\n have_collapsed=" +this.tmp.have_collapsed + "\n last_locator=" + last_locator)
7579 if (undefined !== this.citation.opt["after-collapse-delimiter"]) {
7580 if (last_locator) {
7581 this.tmp.splice_delimiter = this.citation.opt["after-collapse-delimiter"];
7582 } else if (last_collapsed && !this.tmp.have_collapsed) {
7583 this.tmp.splice_delimiter = this.citation.opt["after-collapse-delimiter"];
7584 } else if (!last_collapsed && !this.tmp.have_collapsed && this.citation.opt.collapse !== "year-suffix") {
7585 this.tmp.splice_delimiter = this.citation.opt["after-collapse-delimiter"];
7586 } else {
7587 this.tmp.splice_delimiter = this.citation.opt.layout_delimiter;
7588 }
7589 } else if (this.tmp.use_cite_group_delimiter) {
7590 this.tmp.splice_delimiter = this.citation.opt.cite_group_delimiter;
7591 } else {
7592 if (this.tmp.have_collapsed && this.opt.xclass === "in-text" && this.opt.update_mode !== CSL.NUMERIC) {
7593 this.tmp.splice_delimiter = ", ";
7594 } else if (this.tmp.cite_locales[pos - 1]) {
7595 //
7596 // Must have a value to take effect. Use zero width space to force empty delimiter.
7597 var alt_affixes = this.tmp.cite_affixes[this.tmp.area][this.tmp.cite_locales[pos - 1]];
7598 if (alt_affixes && alt_affixes.delimiter) {
7599 this.tmp.splice_delimiter = alt_affixes.delimiter;
7600 }
7601 } else if (!this.tmp.splice_delimiter) {
7602 // This happens when no delimiter is set on cs:layout under cs:citation
7603 this.tmp.splice_delimiter = "";
7604 }
7605 }
7606
7607/*
7608 if (last_locator && "string" === typeof this.citation.opt["after-collapse-delimiter"]) {
7609 this.tmp.splice_delimiter = this.citation.opt["after-collapse-delimiter"];
7610 } else if (last_collapsed && !this.tmp.have_collapsed && "string" === typeof this.citation.opt["after-collapse-delimiter"]) {
7611 this.tmp.splice_delimiter = this.citation.opt["after-collapse-delimiter"];
7612 } else if (!last_collapsed && !this.tmp.have_collapsed && "string" === typeof this.citation.opt["after-collapse-delimiter"] && !this.citation.opt.collapse === "year-suffix") {
7613 this.tmp.splice_delimiter = this.citation.opt["after-collapse-delimiter"];
7614 } else if (this.tmp.use_cite_group_delimiter) {
7615 this.tmp.splice_delimiter = this.citation.opt.cite_group_delimiter;
7616 } else if (this.tmp.have_collapsed && this.opt.xclass === "in-text" && this.opt.update_mode !== CSL.NUMERIC) {
7617 this.tmp.splice_delimiter = ", ";
7618 } else if (this.tmp.cite_locales[pos - 1]) {
7619 //
7620 // Must have a value to take effect. Use zero width space to force empty delimiter.
7621 var alt_affixes = this.tmp.cite_affixes[this.tmp.area][this.tmp.cite_locales[pos - 1]];
7622 if (alt_affixes && alt_affixes.delimiter) {
7623 this.tmp.splice_delimiter = alt_affixes.delimiter;
7624 }
7625 } else if (!this.tmp.splice_delimiter) {
7626 // This happens when no delimiter is set on cs:layout under cs:citation
7627 this.tmp.splice_delimiter = "";
7628 }
7629*/
7630 // Paranoia
7631 //if (!this.tmp.splice_delimiter) {
7632 // this.tmp.splice_delimiter = "";
7633 //}
7634 return this.tmp.splice_delimiter;
7635};
7636
7637/*
7638 * Compose individual cites into a single string, with
7639 * flexible inter-cite splicing.
7640 */
7641CSL.getCitationCluster = function (inputList, citation) {
7642 var result, objects, myparams, len, pos, item, last_collapsed, params, empties, composite, compie, myblobs, Item, llen, ppos, obj, preceding_item, txt_esc, error_object, citationID, authorOnly, suppressAuthor;
7643 var citation_prefix = "";
7644 this.output.checkNestedBrace = new CSL.checkNestedBrace(this);
7645 if (citation) {
7646 citationID = citation.citationID;
7647 authorOnly = citation.properties.mode === "author-only" ? !!citation.properties.mode : false;
7648 if (this.opt.xclass !== "note") {
7649 suppressAuthor = citation.properties.mode === "suppress-author" ? !!citation.properties.mode : false;
7650 }
7651 if (citation.properties.prefix) {
7652 citation_prefix = CSL.checkPrefixSpaceAppend(this, citation.properties.prefix);
7653 }
7654 }
7655 inputList = inputList ? inputList : [];
7656 this.tmp.last_primary_names_string = false;
7657 txt_esc = CSL.getSafeEscape(this);
7658 this.tmp.area = "citation";
7659 this.tmp.root = "citation";
7660 result = "";
7661 objects = [];
7662 this.tmp.last_suffix_used = "";
7663 this.tmp.last_names_used = [];
7664 this.tmp.last_years_used = [];
7665 this.tmp.backref_index = [];
7666 this.tmp.cite_locales = [];
7667
7668 var use_layout_prefix = this.output.checkNestedBrace.update(this.citation.opt.layout_prefix + citation_prefix);
7669 //var use_layout_prefix = this.citation.opt.layout_prefix;
7670
7671 var suppressTrailingPunctuation = false;
7672 if (this.opt.xclass === "note" && this.citation.opt.suppressTrailingPunctuation) {
7673 suppressTrailingPunctuation = true;
7674 }
7675 if (citationID) {
7676 //this.registry.citationreg.citationById[citationID].properties.backref_index = false;
7677 //this.registry.citationreg.citationById[citationID].properties.backref_citation = false;
7678 if (this.registry.citationreg.citationById[citationID].properties["suppress-trailing-punctuation"]) {
7679 suppressTrailingPunctuation = true;
7680 }
7681 }
7682
7683 // Adjust locator positions if that looks like a sensible thing to do.
7684 if (this.opt.xclass === "note") {
7685 var parasets = [];
7686 var lastTitle = false;
7687 var lastPosition = false;
7688 var lastID = false;
7689 var lst = [];
7690 for (var i=0, ilen = inputList.length; i < ilen; i += 1) {
7691 var type = inputList[i][0].type;
7692 var title = inputList[i][0].title;
7693 var position = inputList[i][1].position;
7694 var id = inputList[i][0].id;
7695 if (title && type === "legal_case" && id !== lastID && position) {
7696 // Start a fresh sublist if the item title does not match the last one
7697 if (title !== lastTitle || parasets.length === 0) {
7698 lst = [];
7699 parasets.push(lst);
7700 }
7701 lst.push(inputList[i][1]);
7702 }
7703 lastTitle = title;
7704 lastPosition = position;
7705 lastID = id;
7706 }
7707 // We now have a list of sublists, each w/matching titles
7708 for (i=0, ilen=parasets.length; i < ilen; i += 1) {
7709 lst = parasets[i];
7710 if (lst.length < 2) {
7711 continue;
7712 }
7713 // Get the locator in last position, but only if it's the only one in the set.
7714 var locatorInLastPosition = lst.slice(-1)[0].locator;
7715 if (locatorInLastPosition) {
7716 for (var j=0, jlen=lst.length - 1; j < jlen; j += 1) {
7717 if (lst[j].locator) {
7718 locatorInLastPosition = false;
7719 }
7720 }
7721 }
7722 // move the locator here, if it's called for.
7723 if (locatorInLastPosition) {
7724 lst[0].locator = locatorInLastPosition;
7725 delete lst.slice(-1)[0].locator;
7726 lst[0].label = lst.slice(-1)[0].label;
7727 if (lst.slice(-1)[0].label) {
7728 delete lst.slice(-1)[0].label;
7729 }
7730 }
7731 }
7732 }
7733 myparams = [];
7734 len = inputList.length;
7735 if (inputList[0] && inputList[0][1]) {
7736 if (authorOnly) {
7737 delete inputList[0][1]["suppress-author"];
7738 inputList[0][1]["author-only"] = true;
7739 } else if (suppressAuthor) {
7740 delete inputList[0][1]["author-only"];
7741 inputList[0][1]["suppress-author"] = true;
7742 }
7743 }
7744 for (pos = 0; pos < len; pos += 1) {
7745 Item = inputList[pos][0];
7746 item = inputList[pos][1];
7747 item = CSL.parseLocator.call(this, item);
7748 last_collapsed = this.tmp.have_collapsed;
7749 var last_locator = false;
7750 if (pos > 0 && inputList[pos-1][1]) {
7751 last_locator = !!inputList[pos-1][1].locator;
7752 }
7753 params = {};
7754
7755 // Reset shadow_numbers here, suppress reset in getCite()
7756 this.tmp.shadow_numbers = {};
7757 if (!this.tmp.just_looking && this.opt.hasPlaceholderTerm) {
7758 var output = this.output;
7759 this.output = new CSL.Output.Queue(this);
7760 this.output.adjust = new CSL.Output.Queue.adjust();
7761 CSL.getAmbiguousCite.call(this, Item, null, false, item);
7762 this.output = output;
7763 }
7764
7765 this.tmp.in_cite_predecessor = false;
7766 // true is to block reset of shadow numbers
7767 if (pos > 0) {
7768 CSL.getCite.call(this, Item, item, "" + inputList[(pos - 1)][0].id, true);
7769 } else {
7770 this.tmp.term_predecessor = false;
7771 CSL.getCite.call(this, Item, item, null, true);
7772 }
7773
7774 // Make a note of any errors
7775 if (!this.tmp.cite_renders_content) {
7776 error_object = {
7777 citationID: "" + this.tmp.citation_id,
7778 index: this.tmp.citation_pos,
7779 noteIndex: this.tmp.citation_note_index,
7780 itemID: "" + Item.id,
7781 citationItems_pos: pos,
7782 error_code: CSL.ERROR_NO_RENDERED_FORM
7783 };
7784 this.tmp.citation_errors.push(error_object);
7785 }
7786 params.splice_delimiter = CSL.getSpliceDelimiter.call(this, last_locator, last_collapsed, pos);
7787 // XXX This appears to be superfluous.
7788 if (item && item["author-only"]) {
7789 this.tmp.suppress_decorations = true;
7790 }
7791
7792 if (pos > 0) {
7793 preceding_item = inputList[pos - 1][1];
7794
7795 // XXX OR if preceding suffix is empty, and the current prefix begins with a full stop.
7796
7797 var precedingEndsInPeriodOrComma = preceding_item.suffix && [";", ".", ","].indexOf(preceding_item.suffix.slice(-1)) > -1;
7798 var currentStartsWithPeriodOrComma = !preceding_item.suffix && item.prefix && [";", ".", ","].indexOf(item.prefix.slice(0, 1)) > -1;
7799 if (precedingEndsInPeriodOrComma || currentStartsWithPeriodOrComma) {
7800 var spaceidx = params.splice_delimiter.indexOf(" ");
7801 if (spaceidx > -1 && !currentStartsWithPeriodOrComma) {
7802 params.splice_delimiter = params.splice_delimiter.slice(spaceidx);
7803 } else {
7804 params.splice_delimiter = "";
7805 }
7806 }
7807 }
7808 params.suppress_decorations = this.tmp.suppress_decorations;
7809 params.have_collapsed = this.tmp.have_collapsed;
7810 //
7811 // XXXXX: capture parameters to an array, which
7812 // will be of the same length as this.output.queue,
7813 // corresponding to each element.
7814 //
7815 myparams.push(params);
7816 if (item["author-only"]) {
7817 break;
7818 }
7819 }
7820
7821 this.parallel.purgeGroupsIfParallel();
7822 //
7823 // output.queue is a simple array. do a slice
7824 // of it to get each cite item, setting params from
7825 // the array that was built in the preceding loop.
7826 //
7827 empties = 0;
7828 myblobs = this.output.queue.slice();
7829
7830 var citation_suffix = "";
7831 if (citation) {
7832 citation_suffix = CSL.checkSuffixSpacePrepend(this, citation.properties.suffix);
7833 }
7834 var suffix = this.citation.opt.layout_suffix;
7835 var last_locale = this.tmp.cite_locales[this.tmp.cite_locales.length - 1];
7836 //
7837 // Must have a value to take effect. Use zero width space to force empty suffix.
7838 if (last_locale && this.tmp.cite_affixes[this.tmp.area][last_locale] && this.tmp.cite_affixes[this.tmp.area][last_locale].suffix) {
7839 suffix = this.tmp.cite_affixes[this.tmp.area][last_locale].suffix;
7840 }
7841 if (CSL.TERMINAL_PUNCTUATION.slice(0, -1).indexOf(suffix.slice(0, 1)) > -1) {
7842 suffix = suffix.slice(0, 1);
7843 }
7844 //print("=== FROM CITE ===");
7845 suffix = this.output.checkNestedBrace.update(citation_suffix + suffix);
7846
7847
7848 for (var i=0,ilen=this.output.queue.length;i<ilen;i+=1) {
7849 CSL.Output.Queue.purgeEmptyBlobs(this.output.queue[i]);
7850 }
7851 if (!this.tmp.suppress_decorations && this.output.queue.length) {
7852 if (!(this.opt.development_extensions.apply_citation_wrapper
7853 && this.sys.wrapCitationEntry
7854 && !this.tmp.just_looking
7855 && this.tmp.area === "citation")) {
7856
7857 if (!suppressTrailingPunctuation) {
7858 this.output.queue[this.output.queue.length - 1].strings.suffix = suffix;
7859 }
7860 this.output.queue[0].strings.prefix = use_layout_prefix;
7861 }
7862 }
7863 if (this.opt.development_extensions.clean_up_csl_flaws) {
7864 for (var j=0,jlen=this.output.queue.length;j<jlen;j+=1) {
7865 //print("OUTPUT[5]: "+JSON.stringify(this.output.queue[j],['strings','prefix','suffix','delimiter','blobs','decorations'],2))
7866 this.output.adjust.upward(this.output.queue[j]);
7867 //print("OUTPUT[4]: "+JSON.stringify(this.output.queue[j],['strings','prefix','suffix','delimiter','blobs','decorations'],2))
7868 this.output.adjust.leftward(this.output.queue[j]);
7869 //print("OUTPUT[3]: "+JSON.stringify(this.output.queue[j],['strings','prefix','suffix','delimiter','blobs','decorations'],2))
7870 this.output.adjust.downward(this.output.queue[j]);
7871 //print("OUTPUT[2]: "+JSON.stringify(this.output.queue[j],['strings','prefix','suffix','delimiter','blobs','decorations'],2))
7872 this.tmp.last_chr = this.output.adjust.fix(this.output.queue[j]);
7873 //print("OUTPUT[1]: "+JSON.stringify(this.output.queue[j],['strings','prefix','suffix','delimiter','blobs','decorations','num'],2))
7874 }
7875 }
7876 //print("this.tmp.last_chr="+this.tmp.last_chr);
7877 for (pos = 0, len = myblobs.length; pos < len; pos += 1) {
7878 var buffer = [];
7879 this.output.queue = [myblobs[pos]];
7880 this.tmp.suppress_decorations = myparams[pos].suppress_decorations;
7881 this.tmp.splice_delimiter = myparams[pos].splice_delimiter;
7882 //
7883 // oh, one last second thought on delimiters ...
7884 //
7885
7886 if (myblobs[pos].parallel_delimiter) {
7887 this.tmp.splice_delimiter = myblobs[pos].parallel_delimiter;
7888 }
7889 this.tmp.have_collapsed = myparams[pos].have_collapsed;
7890
7891 composite = this.output.string(this, this.output.queue);
7892
7893 this.tmp.suppress_decorations = false;
7894 // meaningless assignment
7895 // this.tmp.handle_ranges = false;
7896 if ("string" === typeof composite) {
7897 this.tmp.suppress_decorations = false;
7898 if (!composite) {
7899 if (this.opt.development_extensions.throw_on_empty) {
7900 CSL.error("Citation would render no content");
7901 } else {
7902 composite = "[NO_PRINTED_FORM]"
7903 }
7904 }
7905 return composite;
7906 }
7907 if ("object" === typeof composite && composite.length === 0 && !item["suppress-author"]) {
7908 var errStr = "[CSL STYLE ERROR: reference with no printed form.]";
7909 var preStr = pos === 0 ? txt_esc(this.citation.opt.layout_prefix) : "";
7910 var sufStr = pos === (myblobs.length - 1) ? txt_esc(this.citation.opt.layout_suffix) : "";
7911 composite.push(preStr + errStr + sufStr);
7912 }
7913 if (buffer.length && "string" === typeof composite[0]) {
7914 composite.reverse();
7915 var tmpstr = composite.pop();
7916 if (tmpstr && tmpstr.slice(0, 1) === ",") {
7917 buffer.push(tmpstr);
7918 } else if ("string" == typeof buffer.slice(-1)[0] && buffer.slice(-1)[0].slice(-1) === ",") {
7919 buffer.push(" " + tmpstr);
7920 } else if (tmpstr) {
7921 buffer.push(txt_esc(this.tmp.splice_delimiter) + tmpstr);
7922 }
7923 } else {
7924 composite.reverse();
7925 compie = composite.pop();
7926 if ("undefined" !== typeof compie) {
7927 if (buffer.length && "string" === typeof buffer[buffer.length - 1]) {
7928 buffer[buffer.length - 1] += compie.successor_prefix;
7929 }
7930 buffer.push(compie);
7931 }
7932 }
7933 // Seems odd, but this was unnecessary and broken.
7934 //composite.reverse();
7935 llen = composite.length;
7936 for (ppos = 0; ppos < llen; ppos += 1) {
7937 obj = composite[ppos];
7938 if ("string" === typeof obj) {
7939 buffer.push(txt_esc(this.tmp.splice_delimiter) + obj);
7940 continue;
7941 }
7942 compie = composite.pop();
7943 if ("undefined" !== typeof compie) {
7944 buffer.push(compie);
7945 }
7946 }
7947 if (buffer.length === 0 && !inputList[pos][1]["suppress-author"]) {
7948 empties += 1;
7949 }
7950 if (buffer.length > 1 && typeof buffer[0] !== "string") {
7951 buffer = [this.output.renderBlobs(buffer)];
7952 }
7953 if (buffer.length) {
7954 if ("string" === typeof buffer[0]) {
7955 if (pos > 0) {
7956 buffer[0] = txt_esc(this.tmp.splice_delimiter) + buffer[0];
7957 }
7958 } else {
7959 if (pos > 0) {
7960 buffer[0].splice_prefix = this.tmp.splice_delimiter;
7961 } else {
7962 buffer[0].splice_prefix = "";
7963 }
7964 }
7965 }
7966 objects = objects.concat(buffer);
7967 }
7968 // print("OBJECTS="+objects);
7969 result += this.output.renderBlobs(objects);
7970
7971 if (result) {
7972 //if (CSL.TERMINAL_PUNCTUATION.indexOf(this.tmp.last_chr) > -1
7973 // && this.tmp.last_chr === use_layout_suffix.slice(0, 1)) {
7974 // use_layout_suffix = use_layout_suffix.slice(1);
7975 //}
7976 if (!this.tmp.suppress_decorations) {
7977 len = this.citation.opt.layout_decorations.length;
7978 for (pos = 0; pos < len; pos += 1) {
7979 params = this.citation.opt.layout_decorations[pos];
7980 // The "normal" formats in some output modes expect
7981 // a superior nested decoration environment, and
7982 // so should produce no output here.
7983 if (params[1] === "normal") {
7984 continue;
7985 }
7986 if (!item || !item["author-only"]) {
7987 result = this.fun.decorate[params[0]][params[1]](this, result);
7988 }
7989 }
7990 }
7991 }
7992 this.tmp.suppress_decorations = false;
7993 if (!result) {
7994 if (this.opt.development_extensions.throw_on_empty) {
7995 CSL.error("Citation would render no content");
7996 } else {
7997 result = "[NO_PRINTED_FORM]"
7998 }
7999 }
8000 return result;
8001};
8002
8003/*
8004 * Render a single cite item.
8005 *
8006 * This is called on the state object, with a single
8007 * Item as input. It iterates exactly once over the style
8008 * citation tokens, and leaves the result of rendering in
8009 * the top-level list in the relevant *.opt.output
8010 * stack, as a list item consisting of a single string.
8011 *
8012 * (This is dual-purposed for generating individual
8013 * entries in a bibliography.)
8014 */
8015CSL.getCite = function (Item, item, prevItemID, blockShadowNumberReset) {
8016 var next, error_object;
8017 var areaOrig = this.tmp.area;
8018 if (item && item["author-only"] && this.intext && this.intext.tokens.length > 0) {
8019 this.tmp.area = "intext";
8020 }
8021 this.tmp.cite_renders_content = false;
8022 this.tmp.probably_rendered_something = false;
8023
8024 CSL.citeStart.call(this, Item, item, blockShadowNumberReset);
8025 next = 0;
8026 this.tmp.name_node = {};
8027 this.nameOutput = new CSL.NameOutput(this, Item, item);
8028
8029 // rerun?
8030 while (next < this[this.tmp.area].tokens.length) {
8031 next = CSL.tokenExec.call(this, this[this.tmp.area].tokens[next], Item, item);
8032 }
8033
8034 CSL.citeEnd.call(this, Item, item);
8035 // Odd place for this, but it seems to fit here
8036 if (!this.tmp.cite_renders_content && !this.tmp.just_looking) {
8037 if (this.tmp.area === "bibliography") {
8038 error_object = {
8039 index: this.tmp.bibliography_pos,
8040 itemID: "" + Item.id,
8041 error_code: CSL.ERROR_NO_RENDERED_FORM
8042 };
8043 this.tmp.bibliography_errors.push(error_object);
8044 }
8045 }
8046 this.tmp.area = areaOrig;
8047 return "" + Item.id;
8048};
8049
8050
8051CSL.citeStart = function (Item, item, blockShadowNumberReset) {
8052 if (!blockShadowNumberReset) {
8053 this.tmp.shadow_numbers = {};
8054 }
8055 this.tmp.disambiguate_count = 0;
8056 this.tmp.disambiguate_maxMax = 0;
8057 this.tmp.same_author_as_previous_cite = false;
8058 if (!this.tmp.suppress_decorations) {
8059 this.tmp.subsequent_author_substitute_ok = true;
8060 } else {
8061 this.tmp.subsequent_author_substitute_ok = false;
8062 }
8063 this.tmp.lastchr = "";
8064 if (this.tmp.area === "citation" && this.citation.opt.collapse && this.citation.opt.collapse.length) {
8065 //this.tmp.have_collapsed = "year";
8066 this.tmp.have_collapsed = true;
8067 } else {
8068 this.tmp.have_collapsed = false;
8069 }
8070 this.tmp.render_seen = false;
8071 if (this.tmp.disambig_request && ! this.tmp.disambig_override) {
8072 this.tmp.disambig_settings = this.tmp.disambig_request;
8073 } else if (this.registry.registry[Item.id] && ! this.tmp.disambig_override) {
8074 this.tmp.disambig_request = this.registry.registry[Item.id].disambig;
8075 this.tmp.disambig_settings = this.registry.registry[Item.id].disambig;
8076 } else {
8077 this.tmp.disambig_settings = new CSL.AmbigConfig();
8078 }
8079 if (this.tmp.area !== 'citation') {
8080 if (!this.registry.registry[Item.id]) {
8081 this.tmp.disambig_restore = new CSL.AmbigConfig();
8082 } else {
8083 this.tmp.disambig_restore = CSL.cloneAmbigConfig(this.registry.registry[Item.id].disambig);
8084 if (this.tmp.area === 'bibliography' && this.tmp.disambig_settings && this.tmp.disambig_override) {
8085 if (this.opt["disambiguate-add-names"]) {
8086 this.tmp.disambig_settings.names = this.registry.registry[Item.id].disambig.names.slice();
8087 if (this.tmp.disambig_request) {
8088 this.tmp.disambig_request.names = this.registry.registry[Item.id].disambig.names.slice();
8089 }
8090 }
8091 if (this.opt["disambiguate-add-givenname"]) {
8092 // This is weird and delicate and not fully understood
8093 this.tmp.disambig_request = this.tmp.disambig_settings;
8094 this.tmp.disambig_settings.givens = this.registry.registry[Item.id].disambig.givens.slice();
8095 this.tmp.disambig_request.givens = this.registry.registry[Item.id].disambig.givens.slice();
8096 for (var i=0,ilen=this.tmp.disambig_settings.givens.length;i<ilen;i+=1) {
8097 this.tmp.disambig_settings.givens[i] = this.registry.registry[Item.id].disambig.givens[i].slice();
8098 }
8099 for (var i=0,ilen=this.tmp.disambig_request.givens.length;i<ilen;i+=1) {
8100 this.tmp.disambig_request.givens[i] = this.registry.registry[Item.id].disambig.givens[i].slice();
8101 }
8102 }
8103 }
8104 }
8105 }
8106
8107 this.tmp.names_used = [];
8108 this.tmp.nameset_counter = 0;
8109 this.tmp.years_used = [];
8110 this.tmp.names_max.clear();
8111
8112 this.tmp.splice_delimiter = this[this.tmp.area].opt.layout_delimiter;
8113 //this.tmp.splice_delimiter = this[this.tmp.area].opt.delimiter;
8114
8115 this.bibliography_sort.keys = [];
8116 this.citation_sort.keys = [];
8117
8118 this.tmp.has_done_year_suffix = false;
8119 this.tmp.last_cite_locale = false;
8120 // SAVE PARAMETERS HERE, IF APPROPRIATE
8121 // (promiscuous addition of global parameters => death by a thousand cuts)
8122 if (!this.tmp.just_looking && item && !item.position && this.registry.registry[Item.id]) {
8123 this.tmp.disambig_restore = CSL.cloneAmbigConfig(this.registry.registry[Item.id].disambig);
8124 }
8125 // XXX This only applied to the "number" variable itself? Huh?
8126 //this.setNumberLabels(Item);
8127 this.tmp.first_name_string = false;
8128 this.tmp.authority_stop_last = 0;
8129};
8130
8131CSL.citeEnd = function (Item, item) {
8132 // RESTORE PARAMETERS IF APPROPRIATE
8133 if (this.tmp.disambig_restore && this.registry.registry[Item.id]) {
8134 this.registry.registry[Item.id].disambig.names = this.tmp.disambig_restore.names.slice();
8135 this.registry.registry[Item.id].disambig.givens = this.tmp.disambig_restore.givens.slice();
8136 for (var i=0,ilen=this.registry.registry[Item.id].disambig.givens.length;i<ilen;i+=1) {
8137 this.registry.registry[Item.id].disambig.givens[i] = this.tmp.disambig_restore.givens[i].slice();
8138 }
8139 }
8140 this.tmp.disambig_restore = false;
8141
8142 if (item && item.suffix) {
8143 //this.tmp.last_suffix_used = this.tmp.suffix.value();
8144 this.tmp.last_suffix_used = item.suffix;
8145 } else {
8146 this.tmp.last_suffix_used = "";
8147 }
8148 this.tmp.last_years_used = this.tmp.years_used.slice();
8149 this.tmp.last_names_used = this.tmp.names_used.slice();
8150 this.tmp.cut_var = false;
8151
8152 // This is a hack, in a way; I have lost track of where
8153 // the disambig (name rendering) settings used for rendering work their way
8154 // into the registry. This resets defaults to the subsequent form,
8155 // when first cites are rendered.
8156 //if (this.tmp.disambig_restore && this.registry.registry[Item.id]) {
8157 // this.registry.registry[Item.id].disambig = this.tmp.disambig_restore;
8158 //}
8159 //this.tmp.disambig_restore = false;
8160 this.tmp.disambig_request = false;
8161
8162 this.tmp.cite_locales.push(this.tmp.last_cite_locale);
8163
8164 if (this.tmp.issued_date && this.tmp.renders_collection_number) {
8165 var buf = [];
8166 for (var i = this.tmp.issued_date.list.length - 1; i > this.tmp.issued_date.pos; i += -1) {
8167 buf.push(this.tmp.issued_date.list.pop());
8168 }
8169 // Throw away the unwanted blob
8170 this.tmp.issued_date.list.pop();
8171 // Put the other stuff back
8172 for (i = buf.length - 1; i > -1; i += -1) {
8173 this.tmp.issued_date.list.push(buf.pop());
8174 }
8175 }
8176 this.tmp.issued_date = false;
8177 this.tmp.renders_collection_number = false;
8178
8179};
8180
8181/*global CSL: true */
8182
8183CSL.Engine.prototype.makeBibliography = function (bibsection) {
8184 var debug, ret, params, maxoffset, item, len, pos, tok, tokk, tokkk, entry_ids, entry_strings;
8185 debug = false;
8186 if (!bibsection && (this.bibliography.opt.exclude_types || this.bibliography.opt.exclude_with_fields)) {
8187 bibsection = {
8188 exclude: []
8189 };
8190 if (this.bibliography.opt.exclude_types) {
8191 for (var i in this.bibliography.opt.exclude_types) {
8192 var val = this.bibliography.opt.exclude_types[i];
8193 bibsection.exclude.push({
8194 field: "type",
8195 value: val
8196 });
8197 }
8198 }
8199 if (this.bibliography.opt.exclude_with_fields) {
8200 for (var i in this.bibliography.opt.exclude_with_fields) {
8201 var field = this.bibliography.opt.exclude_with_fields[i];
8202 bibsection.exclude.push({
8203 field: field, value: true
8204 });
8205 }
8206 }
8207 }
8208 // API change: added in version 1.0.51
8209 if (!this.bibliography.tokens.length) {
8210 return false;
8211 }
8212 if ("string" === typeof bibsection) {
8213 this.opt.citation_number_slug = bibsection;
8214 bibsection = false;
8215 }
8216 //SNIP-START
8217 if (debug) {
8218 len = this.bibliography.tokens.length;
8219 for (pos = 0; pos < len; pos += 1) {
8220 tok = this.bibliography.tokens[pos];
8221 CSL.debug("bibtok: " + tok.name);
8222 }
8223 CSL.debug("---");
8224 len = this.citation.tokens.length;
8225 for (pos = 0; pos < len; pos += 1) {
8226 tokk = this.citation.tokens[pos];
8227 CSL.debug("cittok: " + tok.name);
8228 }
8229 CSL.debug("---");
8230 len = this.bibliography_sort.tokens.length;
8231 for (pos = 0; pos < len; pos += 1) {
8232 tokkk = this.bibliography_sort.tokens[pos];
8233 CSL.debug("bibsorttok: " + tok.name);
8234 }
8235 }
8236 //SNIP-END
8237
8238 // For paged returns
8239 ret = CSL.getBibliographyEntries.call(this, bibsection);
8240 entry_ids = ret[0];
8241 entry_strings = ret[1];
8242
8243 // For paged returns
8244 var done = ret[2];
8245
8246 params = {
8247 "maxoffset": 0,
8248 "entryspacing": this.bibliography.opt["entry-spacing"],
8249 "linespacing": this.bibliography.opt["line-spacing"],
8250 "second-field-align": false,
8251 "entry_ids": entry_ids,
8252 "bibliography_errors": this.tmp.bibliography_errors.slice(),
8253 "done": done
8254 };
8255 if (this.bibliography.opt["second-field-align"]) {
8256 params["second-field-align"] = this.bibliography.opt["second-field-align"];
8257 }
8258 maxoffset = 0;
8259 len = this.registry.reflist.length;
8260 for (pos = 0; pos < len; pos += 1) {
8261 item = this.registry.reflist[pos];
8262 if (item.offset > params.maxoffset) {
8263 params.maxoffset = item.offset;
8264 }
8265 }
8266 if (this.bibliography.opt.hangingindent) {
8267 params.hangingindent = this.bibliography.opt.hangingindent;
8268 }
8269 params.bibstart = this.fun.decorate.bibstart;
8270 params.bibend = this.fun.decorate.bibend;
8271
8272 this.opt.citation_number_slug = false;
8273 return [params, entry_strings];
8274};
8275
8276/*
8277 * Compose individual cites into a single string.
8278 */
8279CSL.getBibliographyEntries = function (bibsection) {
8280 var ret, input, include, anymatch, allmatch, bib_entry, res, item, spec, lllen, pppos, topblobs, entry_item_ids, debug, collapse_parallel, i, ilen, siblings, skips, sortedItems, eyetem, entry_item_data, j, jlen;
8281 ret = [];
8282 entry_item_data = [];
8283 this.tmp.area = "bibliography";
8284 this.tmp.root = "bibliography";
8285 this.tmp.last_rendered_name = false;
8286 this.tmp.bibliography_errors = [];
8287 this.tmp.bibliography_pos = 0;
8288
8289 // For paged returns: disable generated entries and
8290 // do not fetch full items as a batch (input variable
8291 // consists of ids only in this case)
8292 if (bibsection && bibsection.page_start && bibsection.page_length) {
8293 input = this.registry.getSortedIds();
8294 } else {
8295 input = this.refetchItems(this.registry.getSortedIds());
8296 }
8297
8298 this.tmp.disambig_override = true;
8299 function eval_string(a, b) {
8300 if (a === b) {
8301 return true;
8302 }
8303 return false;
8304 }
8305 function eval_list(a, lst) {
8306 lllen = lst.length;
8307 for (pppos = 0; pppos < lllen; pppos += 1) {
8308 if (eval_string(a, lst[pppos])) {
8309 return true;
8310 }
8311 }
8312 return false;
8313 }
8314 function eval_spec(a, b) {
8315 if ("boolean" === typeof a || !a) {
8316 if (a) {
8317 return !!b;
8318 } else {
8319 return !b;
8320 }
8321 } else {
8322 if ("string" === typeof b) {
8323 return eval_string(a, b);
8324 } else if (!b) {
8325 return false;
8326 } else {
8327 return eval_list(a, b);
8328 }
8329 }
8330 }
8331
8332 skips = {};
8333
8334 // For paged returns
8335 var page_item_count;
8336 if (bibsection && bibsection.page_start && bibsection.page_length) {
8337 page_item_count = 0;
8338 if (bibsection.page_start !== true) {
8339 for (i = 0, ilen = input.length; i < ilen; i += 1) {
8340 skips[input[i]] = true;
8341 if (bibsection.page_start == input[i]) {
8342 break;
8343 }
8344 }
8345 }
8346 }
8347
8348 var processed_item_ids = [];
8349
8350 for (i = 0, ilen = input.length; i < ilen; i += 1) {
8351
8352 // For paged returns
8353 if (bibsection && bibsection.page_start && bibsection.page_length) {
8354 if (skips[input[i]]) {
8355 continue;
8356 }
8357 item = this.refetchItem(input[i]);
8358 if (page_item_count === bibsection.page_length) {
8359 break;
8360 }
8361 } else {
8362 item = input[i];
8363 if (skips[item.id]) {
8364 continue;
8365 }
8366 }
8367 if (bibsection) {
8368 include = true;
8369 if (bibsection.include) {
8370 //
8371 // Opt-in: these are OR-ed.
8372 //
8373 include = false;
8374 for (j = 0, jlen = bibsection.include.length; j < jlen; j += 1) {
8375 spec = bibsection.include[j];
8376 if (eval_spec(spec.value, item[spec.field])) {
8377 include = true;
8378 break;
8379 }
8380 }
8381 } else if (bibsection.exclude) {
8382 //
8383 // Opt-out: these are also OR-ed.
8384 //
8385 anymatch = false;
8386 for (j = 0, jlen = bibsection.exclude.length; j < jlen; j += 1) {
8387 spec = bibsection.exclude[j];
8388 if (eval_spec(spec.value, item[spec.field])) {
8389 anymatch = true;
8390 break;
8391 }
8392 }
8393 if (anymatch) {
8394 include = false;
8395 }
8396 } else if (bibsection.select) {
8397 //
8398 // Multiple condition opt-in: these are AND-ed.
8399 //
8400 include = false;
8401 allmatch = true;
8402 for (j = 0, jlen = bibsection.select.length; j < jlen; j += 1) {
8403 spec = bibsection.select[j];
8404 if (!eval_spec(spec.value, item[spec.field])) {
8405 allmatch = false;
8406 }
8407 }
8408 if (allmatch) {
8409 include = true;
8410 }
8411 }
8412 if (bibsection.quash) {
8413 //
8414 // Stop criteria: These are AND-ed.
8415 //
8416 allmatch = true;
8417 for (j = 0, jlen = bibsection.quash.length; j < jlen; j += 1) {
8418 spec = bibsection.quash[j];
8419 if (!eval_spec(spec.value, item[spec.field])) {
8420 allmatch = false;
8421 }
8422 }
8423 if (allmatch) {
8424 include = false;
8425 }
8426 }
8427 if (!include) {
8428 continue;
8429 }
8430 }
8431 //SNIP-START
8432 if (debug) {
8433 CSL.debug("BIB: " + item.id);
8434 }
8435 //SNIP-END
8436 bib_entry = new CSL.Token("group", CSL.START);
8437 bib_entry.decorations = [["@bibliography", "entry"]].concat(this.bibliography.opt.layout_decorations);
8438 this.output.startTag("bib_entry", bib_entry);
8439 if (item.system_id && this.sys.embedBibliographyEntry) {
8440 this.output.current.value().item_id = item.system_id;
8441 } else {
8442 this.output.current.value().system_id = item.id;
8443 }
8444
8445 // 2019-06-25 Hacked to conform to new parallels evaluation method
8446 entry_item_ids = [];
8447 if (this.registry.registry[item.id].master
8448 && !(bibsection && bibsection.page_start && bibsection.page_length)) {
8449
8450 sortedItems = [[item, {id: item.id}]];
8451 var siblings = this.registry.registry[item.id].siblings;
8452 for (var j=0,jlen=siblings.length; j<jlen; j++) {
8453 sortedItems.push([{id: siblings[j]}, {id: siblings[j]}]);
8454 }
8455 collapse_parallel = true;
8456 this.parallel.StartCitation(sortedItems);
8457 this.output.queue[0].strings.delimiter = ", ";
8458 this.tmp.term_predecessor = false;
8459 entry_item_ids.push("" + CSL.getCite.call(this, item, sortedItems[0][1]));
8460 skips[item.id] = true;
8461 siblings = this.registry.registry[item.id].siblings;
8462 for (j = 0, jlen = siblings.length; j < jlen; j += 1) {
8463 var k = this.registry.registry[item.id].siblings[j];
8464 eyetem = this.refetchItem(k);
8465 entry_item_ids.push("" + CSL.getCite.call(this, eyetem, sortedItems[j+1][1]));
8466 skips[eyetem.id] = true;
8467 }
8468 this.parallel.purgeGroupsIfParallel();
8469 } else if (!this.registry.registry[item.id].siblings) {
8470 this.tmp.term_predecessor = false;
8471 entry_item_ids.push("" + CSL.getCite.call(this, item));
8472 if (bibsection && bibsection.page_start && bibsection.page_length) {
8473 page_item_count += 1;
8474 }
8475 //skips[item.id] = true;
8476 }
8477 // For RDF support
8478 entry_item_data.push("");
8479
8480 this.tmp.bibliography_pos += 1;
8481
8482 processed_item_ids.push(entry_item_ids);
8483 //
8484 // XXX: loop to render parallels goes here
8485 // XXX: just have to mark them somehow ...
8486 //
8487 this.output.endTag("bib_entry");
8488 //
8489 // place layout prefix on first blob of each cite, and suffix
8490 // on the last non-empty blob of each cite. there be dragons
8491 // here.
8492 //
8493 if (this.output.queue[0].blobs.length && this.output.queue[0].blobs[0].blobs.length) {
8494 // The output queue stuff needs cleaning up. the result of
8495 // output.current.value() is sometimes a blob, sometimes its list
8496 // of blobs. this inconsistency is a source of confusion, and
8497 // should be cleaned up across the code base in the first
8498 // instance, before making any other changes to output code.
8499 if (collapse_parallel || !this.output.queue[0].blobs[0].blobs[0].strings) {
8500 topblobs = this.output.queue[0].blobs;
8501 collapse_parallel = false;
8502 } else {
8503 topblobs = this.output.queue[0].blobs[0].blobs;
8504 }
8505 topblobs[0].strings.prefix = this.bibliography.opt.layout_prefix + topblobs[0].strings.prefix;
8506 }
8507 for (j=0,jlen=this.output.queue.length;j<jlen;j+=1) {
8508 CSL.Output.Queue.purgeEmptyBlobs(this.output.queue[j]);
8509 //print("XXX: "+JSON.stringify(this.output.queue[j],['strings','prefix','suffix','delimiter','blobs','decorations'],2))
8510 }
8511 for (j=0,jlen=this.output.queue.length;j<jlen;j+=1) {
8512 this.output.adjust.upward(this.output.queue[j]);
8513 this.output.adjust.leftward(this.output.queue[j]);
8514 this.output.adjust.downward(this.output.queue[j],true);
8515 this.output.adjust.fix(this.output.queue[j]);
8516 //print("OUTPUT: "+JSON.stringify(this.output.queue[j],['strings','prefix','suffix','delimiter','blobs','decorations'],2))
8517 }
8518
8519 //print("DUMP "+JSON.stringify(this.output.queue, ["strings", "decorations", "prefix", "suffix", "delimiter", "blobs"], 2));
8520
8521 // XXX Need to account for numeric blobs in input.
8522 // XXX No idea how this could have worked previously.
8523
8524 //print("BLOBS "+this.output.queue[0].blobs[0].blobs);
8525
8526 //print("JSON "+JSON.stringify(this.output.queue[0].blobs, null, 2));
8527
8528 res = this.output.string(this, this.output.queue)[0];
8529
8530 if (!res && this.opt.update_mode === CSL.NUMERIC) {
8531 var err = (ret.length + 1) + ". [CSL STYLE ERROR: reference with no printed form.]";
8532 res = CSL.Output.Formats[this.opt.mode]["@bibliography/entry"](this, err);
8533 }
8534 if (res) {
8535 ret.push(res);
8536 }
8537 }
8538
8539 var done = false;
8540 if (bibsection && bibsection.page_start && bibsection.page_length) {
8541 var last_expected_id = input.slice(-1)[0];
8542 var last_seen_id = processed_item_ids.slice(-1)[0];
8543 if (!last_expected_id || !last_seen_id || last_expected_id == last_seen_id) {
8544 done = true;
8545 }
8546 }
8547 this.tmp.disambig_override = false;
8548
8549 // XXX done
8550 return [processed_item_ids, ret, done];
8551};
8552
8553/*global CSL: true */
8554
8555
8556CSL.Engine.prototype.setCitationId = function (citation, force) {
8557 var ret, id, direction;
8558 ret = false;
8559 if (!citation.citationID || force) {
8560 id = Math.floor(Math.random() * 100000000000000);
8561 while (true) {
8562 direction = 0;
8563 if (!this.registry.citationreg.citationById[id]) {
8564 // In case the ID is used as an HTML identifier in the
8565 // calling application.
8566 // https://github.com/Juris-M/citeproc-js/issues/22
8567 citation.citationID = "a" + id.toString(32);
8568 break;
8569 } else if (!direction && id < 50000000000000) {
8570 direction = 1;
8571 } else {
8572 direction = -1;
8573 }
8574 if (direction === 1) {
8575 id += 1;
8576 } else {
8577 id += -1;
8578 }
8579 }
8580 ret = "" + id;
8581 }
8582 this.registry.citationreg.citationById[citation.citationID] = citation;
8583 return ret;
8584};
8585
8586CSL.Engine.prototype.rebuildProcessorState = function (citations, mode, uncitedItemIDs) {
8587 // Rebuilds the processor from scratch, based on a list of citation
8588 // objects. In a dynamic application, once the internal state of processor
8589 // is established, citations should edited with individual invocations
8590 // of processCitationCluster().
8591
8592 // citations is a list of citation objects in document order.
8593 // mode is one of "html", "text" or "rtf".
8594 // uncitedItemIDs is a list of itemIDs or a JS object with itemIDs as keys.
8595 // Returns a list of [citationID,noteIndex,string] triples in document order.
8596 // Set citation.properties.noteIndex to 0 for in-text citations.
8597 // It is not necessary to run updateItems() before this function.
8598 if (!citations) {
8599 citations = [];
8600 }
8601 if (!mode) {
8602 mode = 'html';
8603 }
8604 var doneIDs = {};
8605 var itemIDs = [];
8606 for (var i=0,ilen=citations.length;i<ilen;i+=1) {
8607 for (var j=0,jlen=citations[i].citationItems.length;j<jlen;j+=1) {
8608 var itemID = "" + citations[i].citationItems[j].id;
8609 if (!doneIDs[itemID]) {
8610 itemIDs.push(itemID);
8611 }
8612 doneIDs[itemID] = true;
8613 }
8614 }
8615 this.updateItems(itemIDs);
8616 var pre = [];
8617 var post = [];
8618 var ret = [];
8619 var oldMode = this.opt.mode;
8620 this.setOutputFormat(mode);
8621 for (var i=0,ilen=citations.length;i<ilen;i+=1) {
8622 // res contains a result report and a list of [index,string] pairs
8623 // index begins at 0
8624 var res = this.processCitationCluster(citations[i],pre,post,CSL.ASSUME_ALL_ITEMS_REGISTERED);
8625 pre.push([citations[i].citationID,citations[i].properties.noteIndex]);
8626 for (var j=0,jlen=res[1].length;j<jlen;j+=1) {
8627 var index = res[1][j][0];
8628 ret[index] = [
8629 pre[index][0],
8630 pre[index][1],
8631 res[1][j][1]
8632 ];
8633 }
8634 }
8635 this.updateUncitedItems(uncitedItemIDs);
8636 this.setOutputFormat(oldMode);
8637 return ret;
8638};
8639
8640
8641CSL.Engine.prototype.restoreProcessorState = function (citations) {
8642 var i, ilen, j, jlen, item, Item, newitem, citationList, itemList, sortedItems;
8643
8644 // This function is deprecated.
8645 // Use rebuildProcessorState() instead.
8646
8647 // Quickly restore state from citation details retained by
8648 // calling application.
8649 //
8650 // if citations are provided, position details and sortkeys
8651 // on the citation objects are are assumed to be correct. Item
8652 // data is retrieved, and sortedItems arrays are created and
8653 // sorted as required by the current style.
8654 //
8655 // If citations is an empty list or nil, reset processor to
8656 // empty state.
8657 citationList = [];
8658 itemList = [];
8659 if (!citations) {
8660 citations = [];
8661 }
8662 // Adjust citationIDs to avoid duplicates, save off index numbers
8663 var indexNumbers = [];
8664 var citationIds = {};
8665 for (i = 0, ilen = citations.length; i < ilen; i += 1) {
8666 if (citationIds[citations[i].citationID]) {
8667 this.setCitationId(citations[i], true);
8668 }
8669 citationIds[citations[i].citationID] = true;
8670 indexNumbers.push(citations[i].properties.index);
8671 }
8672 // Slice citations and sort by their declared index positions, if any,
8673 // then reassign index and noteIndex numbers.
8674 var oldCitations = citations.slice();
8675 oldCitations.sort(
8676 function (a,b) {
8677 if (a.properties.index < b.properties.index) {
8678 return -1;
8679 } else if (a.properties.index > b.properties.index) {
8680 return 1;
8681 } else {
8682 return 0;
8683 }
8684 }
8685 );
8686 for (i = 0, ilen = oldCitations.length; i < ilen; i += 1) {
8687 oldCitations[i].properties.index = i;
8688 }
8689 for (i = 0, ilen = oldCitations.length; i < ilen; i += 1) {
8690 sortedItems = [];
8691 for (j = 0, jlen = oldCitations[i].citationItems.length; j < jlen; j += 1) {
8692 item = oldCitations[i].citationItems[j];
8693 if ("undefined" === typeof item.sortkeys) {
8694 item.sortkeys = [];
8695 }
8696 Item = this.retrieveItem("" + item.id);
8697 newitem = [Item, item];
8698 sortedItems.push(newitem);
8699 oldCitations[i].citationItems[j].item = Item;
8700 itemList.push("" + item.id);
8701 }
8702 if (!oldCitations[i].properties.unsorted) {
8703 sortedItems.sort(this.citation.srt.compareCompositeKeys);
8704 }
8705 oldCitations[i].sortedItems = sortedItems;
8706 // Save citation data in registry
8707 this.registry.citationreg.citationById[oldCitations[i].citationID] = oldCitations[i];
8708 }
8709 // Register Items
8710 this.updateItems(itemList);
8711
8712 // Construct citationList from original copy
8713 for (i = 0, ilen = citations.length; i < ilen; i += 1) {
8714 citationList.push(["" + citations[i].citationID, citations[i].properties.noteIndex]);
8715 }
8716
8717 var ret = [];
8718 if (citations && citations.length) {
8719 // Rendering one citation restores remainder of processor state.
8720 // If citations is empty, rest to empty state.
8721 ret = this.processCitationCluster(citations[0], [], citationList.slice(1));
8722 } else {
8723 this.registry = new CSL.Registry(this);
8724 this.tmp = new CSL.Engine.Tmp();
8725 this.disambiguate = new CSL.Disambiguation(this);
8726 }
8727 return ret;
8728};
8729
8730
8731CSL.Engine.prototype.updateItems = function (idList, nosort, rerun_ambigs, implicitUpdate) {
8732 var debug = false;
8733 var oldArea = this.tmp.area;
8734 var oldRoot = this.tmp.root;
8735 var oldExtension = this.tmp.extension;
8736 if (this.bibliography_sort.tokens.length === 0) {
8737 nosort = true;
8738 }
8739 this.tmp.area = "citation";
8740 this.tmp.root = "citation";
8741 this.tmp.extension = "";
8742 if (!implicitUpdate) {
8743 this.tmp.loadedItemIDs = {};
8744 }
8745 //CSL.debug = print
8746 //SNIP-START
8747 if (debug) {
8748 CSL.debug("--> init <--");
8749 }
8750 //SNIP-END
8751 this.registry.init(idList);
8752
8753 if (rerun_ambigs) {
8754 for (var ambig in this.registry.ambigcites) {
8755 this.registry.ambigsTouched[ambig] = true;
8756 }
8757 }
8758
8759 this.registry.dodeletes(this.registry.myhash);
8760
8761 this.registry.doinserts(this.registry.mylist);
8762
8763 this.registry.dorefreshes();
8764
8765 // *** affects reflist
8766 this.registry.rebuildlist(nosort);
8767
8768 this.registry.setsortkeys();
8769
8770 // taints always
8771 this.registry.setdisambigs();
8772
8773 // *** affects reflist
8774 this.registry.sorttokens(nosort);
8775
8776 // *** affects reflist
8777 // taints if numbered style
8778 this.registry.renumber();
8779
8780 // taints always
8781 //this.registry.yearsuffix();
8782
8783 this.tmp.extension = oldExtension;
8784 this.tmp.area = oldArea;
8785 this.tmp.root = oldRoot;
8786
8787 return this.registry.getSortedIds();
8788};
8789
8790CSL.Engine.prototype.updateUncitedItems = function (idList, nosort) {
8791 var idHash;
8792 var oldArea = this.tmp.area;
8793 var oldRoot = this.tmp.root;
8794 var oldExtension = this.tmp.extension;
8795 if (this.bibliography_sort.tokens.length === 0) {
8796 nosort = true;
8797 }
8798 this.tmp.area = "citation";
8799 this.tmp.root = "citation";
8800 this.tmp.extension = "";
8801 this.tmp.loadedItemIDs = {};
8802 // This should be a utility function
8803 if (!idList) {
8804 idList = [];
8805 }
8806 if ("object" == typeof idList) {
8807 if ("undefined" == typeof idList.length) {
8808 idHash = idList;
8809 idList = [];
8810 for (var key in idHash) {
8811 idList.push(key);
8812 }
8813 } else if ("number" == typeof idList.length) {
8814 idHash = {};
8815 for (var i=0,ilen=idList.length;i<ilen;i+=1) {
8816 idHash[idList[i]] = true;
8817 }
8818 }
8819 }
8820
8821 // prepare extended list of items
8822 this.registry.init(idList, true);
8823
8824 // Use purge instead of delete.
8825 // this.registry.dodeletes(this.registry.myhash);
8826 this.registry.dopurge(idHash);
8827
8828 // everything else is the same as updateItems()
8829 this.registry.doinserts(this.registry.mylist);
8830
8831 this.registry.dorefreshes();
8832
8833 this.registry.rebuildlist(nosort);
8834
8835 this.registry.setsortkeys();
8836
8837 this.registry.setdisambigs();
8838
8839 this.registry.sorttokens(nosort);
8840
8841 this.registry.renumber();
8842
8843 this.tmp.extension = oldExtension;
8844 this.tmp.area = oldArea;
8845 this.tmp.root = oldRoot;
8846
8847 return this.registry.getSortedIds();
8848};
8849
8850/*global CSL: true */
8851
8852CSL.localeResolve = function (langstr, defaultLocale) {
8853 var ret, langlst;
8854 if (!defaultLocale) {
8855 defaultLocale = "en-US";
8856 }
8857 if (!langstr) {
8858 langstr = defaultLocale;
8859 }
8860 ret = {};
8861 //if ("undefined" === typeof langstr) {
8862 // langstr = "en_US";
8863 //}
8864 langlst = langstr.split(/[\-_]/);
8865 ret.base = CSL.LANG_BASES[langlst[0]];
8866 if ("undefined" === typeof ret.base) {
8867 //CSL.debug("Warning: unknown locale "+langstr+", setting fallback to "+defaultLocale);
8868 return {base:defaultLocale, best:langstr, bare:langlst[0]};
8869 }
8870 if (langlst.length === 1) {
8871 ret.generic = true;
8872 }
8873 if (langlst.length === 1 || langlst[1] === "x") {
8874 ret.best = ret.base.replace("_", "-");
8875 } else {
8876 ret.best = langlst.slice(0, 2).join("-");
8877 }
8878 ret.base = ret.base.replace("_", "-");
8879 ret.bare = langlst[0];
8880 return ret;
8881};
8882
8883// Use call to invoke this.
8884CSL.Engine.prototype.localeConfigure = function (langspec, beShy) {
8885 var localexml;
8886 if (beShy && this.locale[langspec.best]) {
8887 return;
8888 }
8889 if (langspec.best === "en-US") {
8890 localexml = CSL.setupXml(this.sys.retrieveLocale("en-US"));
8891 this.localeSet(localexml, "en-US", langspec.best);
8892 } else if (langspec.best !== "en-US") {
8893 if (langspec.base !== langspec.best) {
8894 localexml = CSL.setupXml(this.sys.retrieveLocale(langspec.base));
8895 this.localeSet(localexml, langspec.base, langspec.best);
8896 }
8897 localexml = CSL.setupXml(this.sys.retrieveLocale(langspec.best));
8898 this.localeSet(localexml, langspec.best, langspec.best);
8899 }
8900 this.localeSet(this.cslXml, "", langspec.best);
8901 this.localeSet(this.cslXml, langspec.bare, langspec.best);
8902 if (langspec.base !== langspec.best) {
8903 this.localeSet(this.cslXml, langspec.base, langspec.best);
8904 }
8905 this.localeSet(this.cslXml, langspec.best, langspec.best);
8906 if ("undefined" === typeof this.locale[langspec.best].terms["page-range-delimiter"]) {
8907 if (["fr", "pt"].indexOf(langspec.best.slice(0, 2).toLowerCase()) > -1) {
8908 this.locale[langspec.best].terms["page-range-delimiter"] = "-";
8909 } else {
8910 this.locale[langspec.best].terms["page-range-delimiter"] = "\u2013";
8911 }
8912 }
8913 if ("undefined" === typeof this.locale[langspec.best].terms["year-range-delimiter"]) {
8914 this.locale[langspec.best].terms["year-range-delimiter"] = "\u2013";
8915 }
8916 if ("undefined" === typeof this.locale[langspec.best].terms["citation-range-delimiter"]) {
8917 this.locale[langspec.best].terms["citation-range-delimiter"] = "\u2013";
8918 }
8919 if (this.opt.development_extensions.normalize_lang_keys_to_lowercase) {
8920 var localeLists = ["default-locale","locale-sort","locale-translit","locale-translat"];
8921 for (var i=0,ilen=localeLists.length;i<ilen;i+=1) {
8922 for (var j=0,jlen=this.opt[localeLists[i]].length;j<jlen;j+=1) {
8923 this.opt[localeLists[i]][j] = this.opt[localeLists[i]][j].toLowerCase();
8924 }
8925 }
8926 this.opt.lang = this.opt.lang.toLowerCase();
8927 }
8928};
8929
8930//
8931// XXXXX: Got it. The locales objects need to be reorganized,
8932// with a top-level local specifier, and terms, opts, dates
8933// below.
8934//
8935CSL.Engine.prototype.localeSet = function (myxml, lang_in, lang_out) {
8936 var blob, locale, nodes, attributes, pos, term, form, termname, styleopts, date, attrname, len, genderform, target, i, ilen;
8937 lang_in = lang_in.replace("_", "-");
8938 lang_out = lang_out.replace("_", "-");
8939
8940 if (this.opt.development_extensions.normalize_lang_keys_to_lowercase) {
8941 lang_in = lang_in.toLowerCase();
8942 lang_out = lang_out.toLowerCase();
8943 }
8944
8945 if (!this.locale[lang_out]) {
8946 this.locale[lang_out] = {};
8947 this.locale[lang_out].terms = {};
8948 this.locale[lang_out].opts = {};
8949 // Set default skip words. Can be overridden in locale by attribute on style-options node.
8950 this.locale[lang_out].opts["skip-words"] = CSL.SKIP_WORDS;
8951 // Initialise leading noise word to false. Actual assignment is below. Empty by default, can be overridden in locale by attribute on style-options node.
8952 if (!this.locale[lang_out].opts["leading-noise-words"]) {
8953 this.locale[lang_out].opts["leading-noise-words"] = [];
8954 }
8955 this.locale[lang_out].dates = {};
8956 // For ordinals
8957 this.locale[lang_out].ord = {'1.0.1':false,keys:{}};
8958 this.locale[lang_out]["noun-genders"] = {};
8959 }
8960
8961 //
8962 // Xml: Test if node is "locale" (nb: ns declarations need to be invoked
8963 // on every access to the xml object; bundle this with the functions
8964 //
8965 locale = myxml.makeXml();
8966 if (myxml.nodeNameIs(myxml.dataObj, 'locale')) {
8967 locale = myxml.dataObj;
8968 } else {
8969 //
8970 // Xml: get a list of all "locale" nodes
8971 //
8972 nodes = myxml.getNodesByName(myxml.dataObj, "locale");
8973 for (pos = 0, len = myxml.numberofnodes(nodes); pos < len; pos += 1) {
8974 blob = nodes[pos];
8975 //
8976 // Xml: get locale xml:lang
8977 //
8978 if (myxml.getAttributeValue(blob, 'lang', 'xml') === lang_in) {
8979 locale = blob;
8980 break;
8981 }
8982 }
8983 }
8984 //
8985 // Xml: get a list of any cs:type nodes within locale
8986 //
8987 nodes = myxml.getNodesByName(locale, 'type');
8988 for (i = 0, ilen = myxml.numberofnodes(nodes); i < ilen; i += 1) {
8989 var typenode = nodes[i];
8990 var type = myxml.getAttributeValue(typenode, 'name');
8991 var gender = myxml.getAttributeValue(typenode, 'gender');
8992 this.opt.gender[type] = gender;
8993 }
8994 //
8995 // Xml: get a list of term nodes within locale
8996 //
8997
8998 // If we are setting CSL 1.0.1 ordinals inside a style, wipe the
8999 // slate clean and start over.
9000 var hasCslOrdinals101 = myxml.getNodesByName(locale, 'term', 'ordinal').length;
9001 if (hasCslOrdinals101) {
9002 for (var key in this.locale[lang_out].ord.keys) {
9003 delete this.locale[lang_out].terms[key];
9004 }
9005 this.locale[lang_out].ord = {"1.0.1":false,keys:{}};
9006 }
9007
9008 nodes = myxml.getNodesByName(locale, 'term');
9009 // Collect ordinals info as for 1.0.1, but save only if 1.0.1 toggle triggers
9010 var ordinals101 = {"last-digit":{},"last-two-digits":{},"whole-number":{}};
9011 var ordinals101_toggle = false;
9012 var genderized_terms = {};
9013 for (pos = 0, len = myxml.numberofnodes(nodes); pos < len; pos += 1) {
9014 term = nodes[pos];
9015 //
9016 // Xml: get string value of attribute
9017 //
9018 termname = myxml.getAttributeValue(term, 'name');
9019 if (termname === "sub verbo") {
9020 termname = "sub-verbo";
9021 }
9022 if (termname.slice(0,7) === "ordinal") {
9023 if (termname === "ordinal") {
9024 ordinals101_toggle = true;
9025 } else {
9026 var match = myxml.getAttributeValue(term, 'match');
9027 var termstub = termname.slice(8);
9028 var genderform = myxml.getAttributeValue(term, 'gender-form');
9029 if (!genderform) {
9030 genderform = "neuter";
9031 }
9032 if (!match) {
9033 match = "last-two-digits";
9034 if (termstub.slice(0,1) === "0") {
9035 match = "last-digit";
9036 }
9037 }
9038 if (termstub.slice(0,1) === "0") {
9039 termstub = termstub.slice(1);
9040 }
9041 if (!ordinals101[match][termstub]) {
9042 ordinals101[match][termstub] = {};
9043 }
9044 ordinals101[match][termstub][genderform] = termname;
9045 }
9046 this.locale[lang_out].ord.keys[termname] = true;
9047 }
9048 if ("undefined" === typeof this.locale[lang_out].terms[termname]) {
9049 this.locale[lang_out].terms[termname] = {};
9050 }
9051 form = "long";
9052 genderform = false;
9053 //
9054 // Xml: get string value of form attribute, if any
9055 //
9056 if (myxml.getAttributeValue(term, 'form')) {
9057 form = myxml.getAttributeValue(term, 'form');
9058 }
9059 //
9060 // Xml: get string value of gender attribute, if any
9061 //
9062 if (myxml.getAttributeValue(term, 'gender-form')) {
9063 genderform = myxml.getAttributeValue(term, 'gender-form');
9064 }
9065 //
9066 // Xml: set global gender assignment for variable associated
9067 // with term name
9068 //
9069 if (myxml.getAttributeValue(term, 'gender')) {
9070 this.locale[lang_out]["noun-genders"][termname] = myxml.getAttributeValue(term, 'gender');
9071 }
9072 // Work on main segment or gender-specific sub-segment as appropriate
9073 if (genderform) {
9074 this.locale[lang_out].terms[termname][genderform] = {};
9075 this.locale[lang_out].terms[termname][genderform][form] = [];
9076 target = this.locale[lang_out].terms[termname][genderform];
9077 genderized_terms[termname] = true;
9078 } else {
9079 this.locale[lang_out].terms[termname][form] = [];
9080 target = this.locale[lang_out].terms[termname];
9081 }
9082 //
9083 // Xml: test of existence of node
9084 //
9085 if (myxml.numberofnodes(myxml.getNodesByName(term, 'multiple'))) {
9086 //
9087 // Xml: get string value of attribute, plus
9088 // Xml: get string value of node content
9089 //
9090 target[form][0] = myxml.getNodeValue(term, 'single');
9091 if (target[form][0].indexOf("%s") > -1) {
9092 this.opt.hasPlaceholderTerm = true;
9093 }
9094 //
9095 // Xml: get string value of attribute, plus
9096 // Xml: get string value of node content
9097 //
9098 target[form][1] = myxml.getNodeValue(term, 'multiple');
9099 if (target[form][1].indexOf("%s") > -1) {
9100 this.opt.hasPlaceholderTerm = true;
9101 }
9102 } else {
9103 //
9104 // Xml: get string value of attribute, plus
9105 // Xml: get string value of node content
9106 //
9107 target[form] = myxml.getNodeValue(term);
9108 if (target[form].indexOf("%s") > -1) {
9109 this.opt.hasPlaceholderTerm = true;
9110 }
9111 }
9112 }
9113 if (!this.locale[lang_out].terms.supplement) {
9114 this.locale[lang_out].terms.supplement = {};
9115 }
9116 if (!this.locale[lang_out].terms.supplement["long"]) {
9117 this.locale[lang_out].terms.supplement["long"] = ["supplement", "supplements"];
9118 }
9119 // If locale had a CSL 1.0.1-style ordinal definition, install the logic object
9120 // and iterate over gendered terms, filling in default values for use by getTerm.
9121 if (ordinals101_toggle) {
9122 for (var ikey in genderized_terms) {
9123 var gender_segments = {};
9124 var form_segments = 0;
9125 for (var jkey in this.locale[lang_out].terms[ikey]) {
9126 if (["masculine","feminine"].indexOf(jkey) > -1) {
9127 gender_segments[jkey] = this.locale[lang_out].terms[ikey][jkey];
9128 } else {
9129 form_segments += 1;
9130 }
9131 }
9132 if (!form_segments) {
9133 if (gender_segments.feminine) {
9134 // Link each feminine form segment to default
9135 // (no need to filter, these will not have gender segments mixed in)
9136 for (var jkey in gender_segments.feminine) {
9137 this.locale[lang_out].terms[ikey][jkey] = gender_segments.feminine[jkey];
9138 }
9139 } else if (gender_segments.masculine) {
9140 // Otherwise link each masculine form segment to default
9141 for (var jkey in gender_segments.masculine) {
9142 this.locale[lang_out].terms[ikey][jkey] = gender_segments.masculine[jkey];
9143 }
9144 }
9145 }
9146 }
9147 this.locale[lang_out].ord['1.0.1'] = ordinals101;
9148 }
9149
9150 // Iterate over main segments, and fill in any holes in gender-specific data
9151 // sub-segments
9152 for (termname in this.locale[lang_out].terms) {
9153 for (i = 0, ilen = 2; i < ilen; i += 1) {
9154 genderform = CSL.GENDERS[i];
9155 if (this.locale[lang_out].terms[termname][genderform]) {
9156 for (form in this.locale[lang_out].terms[termname]) {
9157 if (!this.locale[lang_out].terms[termname][genderform][form]) {
9158 this.locale[lang_out].terms[termname][genderform][form] = this.locale[lang_out].terms[termname][form];
9159 }
9160 }
9161 }
9162 }
9163 }
9164 //
9165 // Xml: get list of nodes by node type
9166 //
9167 nodes = myxml.getNodesByName(locale, 'style-options');
9168 for (pos = 0, len = myxml.numberofnodes(nodes); pos < len; pos += 1) {
9169 if (true) {
9170 styleopts = nodes[pos];
9171 //
9172 // Xml: get list of attributes on a node
9173 //
9174 attributes = myxml.attributes(styleopts);
9175 for (attrname in attributes) {
9176 if (attributes.hasOwnProperty(attrname)) {
9177 if (attrname === "@punctuation-in-quote" || attrname === "@limit-day-ordinals-to-day-1") {
9178 if (attributes[attrname] === "true") {
9179 // trim off leading @
9180 this.locale[lang_out].opts[attrname.slice(1)] = true;
9181 } else {
9182 // trim off leading @
9183 this.locale[lang_out].opts[attrname.slice(1)] = false;
9184 }
9185 } else if (attrname === "@jurisdiction-preference") {
9186 var jurisdiction_preference = attributes[attrname].split(/\s+/);
9187 this.locale[lang_out].opts[attrname.slice(1)] = jurisdiction_preference;
9188 } else if (attrname === "@skip-words") {
9189 var skip_words = attributes[attrname].split(/\s*,\s*/);
9190 this.locale[lang_out].opts[attrname.slice(1)] = skip_words;
9191 } else if (attrname === "@leading-noise-words") {
9192 var val = attributes[attrname].split(/\s*,\s*/);
9193 this.locale[lang_out].opts["leading-noise-words"] = val;
9194 } else if (attrname === "@name-as-sort-order") {
9195 // Fallback is okay here.
9196 this.locale[lang_out].opts["name-as-sort-order"] = {};
9197 var lst = attributes[attrname].split(/\s+/);
9198 for (var i=0,ilen=lst.length;i<ilen;i+=1) {
9199 this.locale[lang_out].opts["name-as-sort-order"][lst[i]] = true;
9200 }
9201 } else if (attrname === "@name-as-reverse-order") {
9202 // Fallback is okay here.
9203 this.locale[lang_out].opts["name-as-reverse-order"] = {};
9204 var lst = attributes[attrname].split(/\s+/);
9205 for (var i=0,ilen=lst.length;i<ilen;i+=1) {
9206 this.locale[lang_out].opts["name-as-reverse-order"][lst[i]] = true;
9207 }
9208 } else if (attrname === "@name-never-short") {
9209 // Here too.
9210 this.locale[lang_out].opts["name-never-short"] = {};
9211 var lst = attributes[attrname].split(/\s+/);
9212 for (var i=0,ilen=lst.length;i<ilen;i+=1) {
9213 this.locale[lang_out].opts["name-never-short"][lst[i]] = true;
9214 }
9215 }
9216 }
9217 }
9218 }
9219 }
9220 //
9221 // Xml: get list of nodes by type
9222 //
9223 nodes = myxml.getNodesByName(locale, 'date');
9224 for (pos = 0, len = myxml.numberofnodes(nodes); pos < len; pos += 1) {
9225 if (true) {
9226 var date = nodes[pos];
9227 //
9228 // Xml: get string value of attribute
9229 //
9230 this.locale[lang_out].dates[myxml.getAttributeValue(date, "form")] = date;
9231 }
9232 }
9233};
9234
9235
9236CSL.getLocaleNames = function (myxml, preferredLocale) {
9237 var stylexml = CSL.setupXml(myxml);
9238
9239 function extendLocaleList(localeList, locale) {
9240 var forms = ["base", "best"];
9241 if (locale) {
9242 var normalizedLocale = CSL.localeResolve(locale);
9243 for (var i=0,ilen=forms.length;i<ilen;i++) {
9244 if (normalizedLocale[forms[i]] && localeList.indexOf(normalizedLocale[forms[i]]) === -1) {
9245 localeList.push(normalizedLocale[forms[i]]);
9246 }
9247 }
9248 }
9249 }
9250
9251 var localeIDs = ["en-US"];
9252
9253 function sniffLocaleOnOneNodeName(nodeName) {
9254 var nodes = stylexml.getNodesByName(stylexml.dataObj, nodeName);
9255 for (var i=0,ilen=nodes.length;i<ilen;i++) {
9256 var nodeLocales = stylexml.getAttributeValue(nodes[i], "locale");
9257 if (nodeLocales) {
9258 nodeLocales = nodeLocales.split(/ +/);
9259 for (var j=0,jlen=nodeLocales.length;j<jlen;j++) {
9260 this.extendLocaleList(localeIDs, nodeLocales[j]);
9261 }
9262 }
9263 }
9264 }
9265
9266 extendLocaleList(localeIDs, preferredLocale);
9267
9268 var styleNode = stylexml.getNodesByName(stylexml.dataObj, "style")[0];
9269 var defaultLocale = stylexml.getAttributeValue(styleNode, "default-locale");
9270 extendLocaleList(localeIDs, defaultLocale);
9271
9272 var nodeNames = ["layout", "if", "else-if", "condition"];
9273 for (var i=0,ilen=nodeNames.length;i<ilen;i++) {
9274 sniffLocaleOnOneNodeName(stylexml, localeIDs, nodeNames[i]);
9275 }
9276 return localeIDs;
9277};
9278
9279/*global CSL: true */
9280
9281CSL.Node = {};
9282
9283CSL.Node.bibliography = {
9284 build: function (state, target) {
9285 if (this.tokentype === CSL.START) {
9286
9287 state.build.area = "bibliography";
9288 state.build.root = "bibliography";
9289 state.build.extension = "";
9290
9291 var func = function(state) {
9292 state.tmp.area = "bibliography";
9293 state.tmp.root = "bibliography";
9294 state.tmp.extension = "";
9295 };
9296 this.execs.push(func);
9297
9298 //state.parallel.use_parallels = false;
9299/*
9300 state.fixOpt(this, "names-delimiter", "delimiter");
9301 state.fixOpt(this, "name-delimiter", "delimiter");
9302 state.fixOpt(this, "name-form", "form");
9303
9304 state.fixOpt(this, "and", "and");
9305 state.fixOpt(this, "delimiter-precedes-last", "delimiter-precedes-last");
9306 state.fixOpt(this, "delimiter-precedes-et-al", "delimiter-precedes-et-al");
9307 print("PUSH bibliography");
9308 state.fixOpt(this, "initialize-with", "initialize-with");
9309 state.fixOpt(this, "initialize", "initialize");
9310 state.fixOpt(this, "name-as-sort-order", "name-as-sort-order");
9311 state.fixOpt(this, "sort-separator", "sort-separator");
9312 state.fixOpt(this, "and", "and");
9313
9314 state.fixOpt(this, "et-al-min", "et-al-min");
9315 state.fixOpt(this, "et-al-use-first", "et-al-use-first");
9316 state.fixOpt(this, "et-al-use-last", "et-al-use-last");
9317 state.fixOpt(this, "et-al-subsequent-min", "et-al-subsequent-min");
9318 state.fixOpt(this, "et-al-subsequent-use-first", "et-al-subsequent-use-first");
9319*/
9320 }
9321 target.push(this);
9322 }
9323};
9324
9325
9326/*global CSL: true */
9327
9328CSL.Node.choose = {
9329 build: function (state, target) {
9330 var func;
9331 if (this.tokentype === CSL.START) {
9332 //open condition
9333 func = function (state) {
9334 state.tmp.jump.push(undefined, CSL.LITERAL);
9335 };
9336 }
9337 if (this.tokentype === CSL.END) {
9338 //close condition
9339 func = function (state) {
9340 state.tmp.jump.pop();
9341 };
9342 }
9343 this.execs.push(func);
9344 target.push(this);
9345 },
9346
9347 configure: function (state, pos) {
9348 if (this.tokentype === CSL.END) {
9349 state.configure.fail.push((pos));
9350 state.configure.succeed.push((pos));
9351 } else {
9352 state.configure.fail.pop();
9353 state.configure.succeed.pop();
9354 }
9355 }
9356};
9357
9358/*global CSL: true */
9359
9360CSL.Node.citation = {
9361 build: function (state, target) {
9362 if (this.tokentype === CSL.START) {
9363
9364 state.build.area = "citation";
9365 state.build.root = "citation";
9366 state.build.extension = "";
9367
9368
9369 var func = function(state) {
9370 state.tmp.area = "citation";
9371 state.tmp.root = "citation";
9372 state.tmp.extension = "";
9373 };
9374 this.execs.push(func);
9375
9376/*
9377 state.build.root = "citation";
9378
9379 OK state.fixOpt(this, "names-delimiter", "delimiter");
9380 OK state.fixOpt(this, "name-delimiter", "delimiter");
9381 OK state.fixOpt(this, "name-form", "form");
9382 OK state.fixOpt(this, "and", "and");
9383 OK state.fixOpt(this, "delimiter-precedes-last", "delimiter-precedes-last");
9384 OK state.fixOpt(this, "delimiter-precedes-et-al", "delimiter-precedes-et-al");
9385 OK state.fixOpt(this, "initialize-with", "initialize-with");
9386 OK state.fixOpt(this, "initialize", "initialize");
9387 OK state.fixOpt(this, "name-as-sort-order", "name-as-sort-order");
9388 OK state.fixOpt(this, "sort-separator", "sort-separator");
9389
9390 OK state.fixOpt(this, "et-al-min", "et-al-min");
9391 OK state.fixOpt(this, "et-al-use-first", "et-al-use-first");
9392 OK state.fixOpt(this, "et-al-use-last", "et-al-use-last");
9393 state.fixOpt(this, "et-al-subsequent-min", "et-al-subsequent-min");
9394 state.fixOpt(this, "et-al-subsequent-use-first", "et-al-subsequent-use-first");
9395*/
9396 }
9397 if (this.tokentype === CSL.END) {
9398
9399 // Open an extra key at first position for use in
9400 // grouped sorts.
9401 // print("in cs:citation END");
9402 state.opt.grouped_sort = state.opt.xclass === "in-text"
9403 && (state.citation.opt.collapse
9404 && state.citation.opt.collapse.length)
9405 || (state.citation.opt.cite_group_delimiter
9406 && state.citation.opt.cite_group_delimiter.length)
9407 && state.opt.update_mode !== CSL.POSITION
9408 && state.opt.update_mode !== CSL.NUMERIC;
9409
9410 if (state.opt.grouped_sort
9411 && state.citation_sort.opt.sort_directions.length) {
9412
9413 var firstkey = state.citation_sort.opt.sort_directions[0].slice();
9414 //print("extending sort keys "+state.citation_sort.opt.sort_directions+" with "+firstkey);
9415 state.citation_sort.opt.sort_directions = [firstkey].concat(state.citation_sort.opt.sort_directions);
9416 // print("new key directions in effect: "+state.citation_sort.opt.sort_directions);
9417 }
9418 // print("creating new comparifier");
9419 state.citation.srt = new CSL.Registry.Comparifier(state, "citation_sort");
9420 }
9421 target.push(this);
9422 }
9423};
9424
9425
9426/*global CSL: true */
9427
9428CSL.Node["#comment"] = {
9429 // This is a comment in the CSL file.
9430 build: function () {
9431 // Save some space in the log files -- no need to mention this, really.
9432 // CSL.debug("CSL processor warning: comment node reached");
9433 }
9434};
9435
9436/*global CSL: true */
9437
9438CSL.Node.date = {
9439 build: function (state, target) {
9440 var func, date_obj, len, pos, part, dpx, parts, mypos, start, end;
9441 if (this.tokentype === CSL.START || this.tokentype === CSL.SINGLETON) {
9442 // used to collect rendered date part names in node_datepart,
9443 // for passing through to node_key, for use in dates embedded
9444 // in macros
9445 state.dateput.string(state, state.dateput.queue);
9446 state.tmp.date_token = CSL.Util.cloneToken(this);
9447 state.tmp.date_token.strings.prefix = "";
9448 state.tmp.date_token.strings.suffix = "";
9449 state.dateput.openLevel(this);
9450 state.build.date_parts = [];
9451 state.build.date_variables = this.variables;
9452 if (!state.build.extension) {
9453 CSL.Util.substituteStart.call(this, state, target);
9454 }
9455 if (state.build.extension) {
9456 func = CSL.dateMacroAsSortKey;
9457 } else {
9458 func = function (state, Item, item) {
9459 var dp;
9460 state.tmp.element_rendered_ok = false;
9461 state.tmp.donesies = [];
9462 state.tmp.dateparts = [];
9463 dp = [];
9464 //if (this.variables.length && Item[this.variables[0]]){
9465 if (this.variables.length
9466 && !(state.tmp.just_looking
9467 && this.variables[0] === "accessed")) {
9468
9469 date_obj = Item[this.variables[0]];
9470 if ("undefined" === typeof date_obj) {
9471 date_obj = {"date-parts": [[0]] };
9472 if (state.opt.development_extensions.locator_date_and_revision) {
9473 if (item && this.variables[0] === "locator-date" && item["locator-date"]) {
9474 date_obj = item["locator-date"];
9475 }
9476 }
9477 }
9478 state.tmp.date_object = date_obj;
9479 //
9480 // Call a function here to analyze the
9481 // data and set the name of the date-part that
9482 // should collapse for this range, if any.
9483 //
9484 // (1) build a filtered list, in y-m-d order,
9485 // consisting only of items that are (a) in the
9486 // date-parts and (b) in the *_end data.
9487 // (note to self: remember that season is a
9488 // fallback var when month and day are empty)
9489
9490 //if ("undefined" === typeof this.dateparts) {
9491 // this.dateparts = ["year", "month", "day"];
9492 //}
9493 len = this.dateparts.length;
9494 for (pos = 0; pos < len; pos += 1) {
9495 part = this.dateparts[pos];
9496 if ("undefined" !== typeof state.tmp.date_object[(part + "_end")]) {
9497 dp.push(part);
9498 } else if (part === "month" && "undefined" !== typeof state.tmp.date_object.season_end) {
9499 dp.push(part);
9500 }
9501 }
9502 dpx = [];
9503 parts = ["year", "month", "day"];
9504 len = parts.length;
9505 for (pos = 0; pos < len; pos += 1) {
9506 if (dp.indexOf(parts[pos]) > -1) {
9507 dpx.push(parts[pos]);
9508 }
9509 }
9510 dp = dpx.slice();
9511 //
9512 // (2) Reverse the list and step through in
9513 // reverse order, popping each item if the
9514 // primary and *_end data match.
9515 mypos = 2;
9516 len = dp.length;
9517 for (pos = 0; pos < len; pos += 1) {
9518 part = dp[pos];
9519 start = state.tmp.date_object[part];
9520 end = state.tmp.date_object[(part + "_end")];
9521 if (start !== end) {
9522 mypos = pos;
9523 break;
9524 }
9525 }
9526
9527 //
9528 // (3) When finished, the first item in the
9529 // list, if any, is the date-part where
9530 // the collapse should occur.
9531
9532 // XXXXX: was that it?
9533 state.tmp.date_collapse_at = dp.slice(mypos);
9534 //
9535 // The collapse itself will be done by appending
9536 // string output for the date, less suffix,
9537 // placing a delimiter on output, then then
9538 // doing the *_end of the range, dropping only
9539 // the prefix. That should give us concise expressions
9540 // of ranges.
9541 //
9542 // Numeric dates should not collapse, though,
9543 // and should probably use a slash delimiter.
9544 // Scope for configurability will remain (all over
9545 // the place), but this will do to get this feature
9546 // started.
9547 //
9548 } else {
9549 state.tmp.date_object = false;
9550 }
9551 };
9552 }
9553 this.execs.push(func);
9554
9555 // newoutput
9556 func = function (state, Item) {
9557 if (!Item[this.variables[0]]) {
9558 return;
9559 }
9560 state.output.startTag("date", this);
9561 if (this.variables[0] === "issued"
9562 && Item.type === "legal_case"
9563 && !state.tmp.extension
9564 && "" + Item["collection-number"] === "" + state.tmp.date_object.year
9565 && this.dateparts.length === 1
9566 && this.dateparts[0] === "year") {
9567
9568 // Set up to (maybe) suppress the year if we're not sorting, and
9569 // it's the same as the collection-number, and we would render
9570 // only the year, with not month or day, and this is a legal_case item.
9571 // We save a pointer to the blob parent and its position here. The
9572 // blob will be popped from output if at the end of processing for
9573 // this cite we find that we have rendered the collection-number
9574 // variable also.
9575 for (var key in state.tmp.date_object) {
9576 if (state.tmp.date_object.hasOwnProperty(key)) {
9577 if (key.slice(0, 4) === "year") {
9578
9579 state.tmp.issued_date = {};
9580 var lst = state.output.current.mystack.slice(-2)[0].blobs;
9581 state.tmp.issued_date.list = lst;
9582 state.tmp.issued_date.pos = lst.length - 1;
9583 }
9584 }
9585 }
9586 }
9587 };
9588 this.execs.push(func);
9589 }
9590
9591 if (!state.build.extension && (this.tokentype === CSL.END || this.tokentype === CSL.SINGLETON)) {
9592 // mergeoutput
9593 func = function (state, Item) {
9594 if (!Item[this.variables[0]]) {
9595 return;
9596 }
9597 state.output.endTag();
9598 };
9599 this.execs.push(func);
9600 }
9601 target.push(this);
9602
9603 if (this.tokentype === CSL.END || this.tokentype === CSL.SINGLETON) {
9604 if (!state.build.extension) {
9605 CSL.Util.substituteEnd.call(this, state, target);
9606 }
9607 }
9608 }
9609};
9610
9611/*global CSL: true */
9612
9613CSL.Node["date-part"] = {
9614 build: function (state, target) {
9615 var func, pos, len, first_date, value, value_end, real, have_collapsed, invoked, precondition, known_year, bc, ad, bc_end, ad_end, ready, curr, dcurr, number, num, formatter, item;
9616 if (!this.strings.form) {
9617 this.strings.form = "long";
9618 }
9619 // used in node_date, to send a list of rendering date parts
9620 // to node_key, for dates embedded in macros.
9621 state.build.date_parts.push(this.strings.name);
9622 //
9623 // Set delimiter here, if poss.
9624 //
9625
9626 var date_variable = state.build.date_variables[0];
9627
9628 function formatAndStrip(myform, gender, val) {
9629 if (!val) {
9630 return val;
9631 }
9632 val = "" + CSL.Util.Dates[this.strings.name][myform](state, val, gender, this.default_locale);
9633 if ("month" === this.strings.name) {
9634 if (state.tmp.strip_periods) {
9635 val = val.replace(/\./g, "");
9636 } else {
9637 for (var i = 0, ilen = this.decorations.length; i < ilen; i += 1) {
9638 if ("@strip-periods" === this.decorations[i][0] && "true" === this.decorations[i][1]) {
9639 val = val.replace(/\./g, "");
9640 break;
9641 }
9642 }
9643 }
9644 }
9645 return val;
9646 }
9647
9648 func = function (state, Item) {
9649
9650 if (!state.tmp.date_object) {
9651 return;
9652 } else {
9653 state.tmp.probably_rendered_something = true;
9654 }
9655
9656 first_date = true;
9657 value = "";
9658 value_end = "";
9659 state.tmp.donesies.push(this.strings.name);
9660
9661 // Render literal only when year is included in date output
9662 if (state.tmp.date_object.literal && "year" === this.strings.name) {
9663 state.output.append(state.tmp.date_object.literal, this);
9664 }
9665
9666 if (state.tmp.date_object) {
9667 value = state.tmp.date_object[this.strings.name];
9668 value_end = state.tmp.date_object[(this.strings.name + "_end")];
9669 }
9670 if ("year" === this.strings.name && value === 0 && !state.tmp.suppress_decorations) {
9671 value = false;
9672 }
9673 real = !state.tmp.suppress_decorations;
9674 have_collapsed = state.tmp.have_collapsed;
9675 invoked = state[state.tmp.area].opt.collapse === "year-suffix" || state[state.tmp.area].opt.collapse === "year-suffix-ranged";
9676 precondition = state.opt["disambiguate-add-year-suffix"];
9677 if (real && precondition && invoked) {
9678 state.tmp.years_used.push(value);
9679 known_year = state.tmp.last_years_used.length >= state.tmp.years_used.length;
9680 if (known_year && have_collapsed) {
9681 if (state.tmp.last_years_used[(state.tmp.years_used.length - 1)] === value) {
9682 value = false;
9683 }
9684 }
9685 }
9686 if ("undefined" !== typeof value) {
9687 bc = false;
9688 ad = false;
9689 bc_end = false;
9690 ad_end = false;
9691 if ("year" === this.strings.name) {
9692 if (parseInt(value, 10) < 500 && parseInt(value, 10) > 0) {
9693 ad = state.getTerm("ad");
9694 }
9695 if (parseInt(value, 10) < 0) {
9696 bc = state.getTerm("bc");
9697 value = (parseInt(value, 10) * -1);
9698 }
9699 if (value_end) {
9700 if (parseInt(value_end, 10) < 500 && parseInt(value_end, 10) > 0) {
9701 ad_end = state.getTerm("ad");
9702 }
9703 if (parseInt(value_end, 10) < 0) {
9704 bc_end = state.getTerm("bc");
9705 value_end = (parseInt(value_end, 10) * -1);
9706 }
9707 }
9708 }
9709
9710 // For gendered locales
9711 var monthnameid = ""+state.tmp.date_object.month;
9712 while (monthnameid.length < 2) {
9713 monthnameid = "0"+monthnameid;
9714 }
9715 monthnameid = "month-"+monthnameid;
9716 var gender = state.locale[state.opt.lang]["noun-genders"][monthnameid];
9717 if (this.strings.form) {
9718 var myform = this.strings.form;
9719 if (this.strings.name === "day") {
9720 if (myform === "ordinal"
9721 && state.locale[state.opt.lang].opts["limit-day-ordinals-to-day-1"]
9722 && ("" + value) !== "1") {
9723
9724 myform = "numeric";
9725 }
9726 }
9727 value = formatAndStrip.call(this, myform, gender, value);
9728 value_end = formatAndStrip.call(this, myform, gender, value_end);
9729 }
9730 state.output.openLevel("empty");
9731 if (state.tmp.date_collapse_at.length) {
9732 //state.output.startTag(this.strings.name,this);
9733 ready = true;
9734 len = state.tmp.date_collapse_at.length;
9735 for (pos = 0; pos < len; pos += 1) {
9736 item = state.tmp.date_collapse_at[pos];
9737 if (state.tmp.donesies.indexOf(item) === -1) {
9738 ready = false;
9739 break;
9740 }
9741 }
9742 if (ready) {
9743 if ("" + value_end !== "0") {
9744 if (state.dateput.queue.length === 0) {
9745 first_date = true;
9746 }
9747
9748 // OK! So if the actual data has no month, day or season,
9749 // and we reach this block, then we can combine the dates
9750 // to a string, run minimial-two, and output the trailing
9751 // year right here. No impact on other functionality.
9752
9753 if (state.opt["year-range-format"]
9754 && state.opt["year-range-format"] !== "expanded"
9755 && !state.tmp.date_object.day
9756 && !state.tmp.date_object.month
9757 && !state.tmp.date_object.season
9758 && this.strings.name === "year"
9759 && value && value_end) {
9760
9761 // second argument adjusts collapse as required for years
9762 // See OSCOLA section 1.3.2
9763 value_end = state.fun.year_mangler(value + "-" + value_end, true);
9764 var range_delimiter = state.getTerm("year-range-delimiter");
9765 value_end = value_end.slice(value_end.indexOf(range_delimiter) + 1);
9766 }
9767 state.dateput.append(value_end, this);
9768 if (first_date) {
9769 state.dateput.current.value().blobs[0].strings.prefix = "";
9770 }
9771 }
9772 state.output.append(value, this);
9773 curr = state.output.current.value();
9774 curr.blobs[(curr.blobs.length - 1)].strings.suffix = "";
9775 if (this.strings["range-delimiter"]) {
9776 state.output.append(this.strings["range-delimiter"]);
9777 } else {
9778 state.output.append(state.getTerm("year-range-delimiter"), "empty");
9779 }
9780 state.dateput.closeLevel();
9781 dcurr = state.dateput.current.value();
9782 curr.blobs = curr.blobs.concat(dcurr);
9783 // This may leave the stack pointer on a lower level.
9784 // It's not a problem because the stack will be clobbered
9785 // when the queue is initialized by the next cs:date node.
9786 state.dateput.string(state, state.dateput.queue);
9787 state.dateput.openLevel(state.tmp.date_token);
9788 state.tmp.date_collapse_at = [];
9789 } else {
9790 state.output.append(value, this);
9791 // print("collapse_at: "+state.tmp.date_collapse_at);
9792 if (state.tmp.date_collapse_at.indexOf(this.strings.name) > -1) {
9793 //
9794 // Use ghost dateput queue
9795 //
9796 if ("" + value_end !== "0") {
9797 //
9798 // XXXXX: It's a workaround. It's ugly.
9799 // There's another one above.
9800 //
9801 if (state.dateput.queue.length === 0) {
9802 first_date = true;
9803 }
9804 state.dateput.openLevel("empty");
9805 state.dateput.append(value_end, this);
9806 if (first_date) {
9807 state.dateput.current.value().blobs[0].strings.prefix = "";
9808 }
9809 if (bc) {
9810 state.dateput.append(bc);
9811 }
9812 if (ad) {
9813 state.dateput.append(ad);
9814 }
9815 state.dateput.closeLevel();
9816 }
9817 }
9818 }
9819 } else {
9820 state.output.append(value, this);
9821 }
9822
9823 if (bc) {
9824 state.output.append(bc);
9825 }
9826 if (ad) {
9827 state.output.append(ad);
9828 }
9829 state.output.closeLevel();
9830 //state.output.endTag();
9831 } else if ("month" === this.strings.name) {
9832 // XXX The simpler solution here will be to
9833 // directly install season and season_end on
9834 // month, with a value of 13, 14, 15, 16, or
9835 // (to allow correct ranging with Down Under
9836 // dates) 17 or 18. That will allow ranging
9837 // to take place in the normal way. With this
9838 // "approach", it doesn't.
9839 //
9840 // No value for this target variable
9841 //
9842 if (state.tmp.date_object.season) {
9843 value = "" + state.tmp.date_object.season;
9844 if (value && value.match(/^[1-4]$/)) {
9845 // XXXXXXXXXXXXXXXXXXX was replace([false, false, true]);
9846 //state.tmp.group_context.replace([false, false, true]);
9847 state.tmp.group_context.tip.variable_success = true;
9848 state.output.append(state.getTerm(("season-0" + value)), this);
9849 } else if (value) {
9850 state.output.append(value, this);
9851 }
9852 }
9853 }
9854 state.tmp.value = [];
9855 if (Item[date_variable] && (value || state.tmp.have_collapsed) && !state.opt.has_year_suffix && "year" === this.strings.name && !state.tmp.just_looking) {
9856 if (state.registry.registry[Item.id] && state.registry.registry[Item.id].disambig.year_suffix !== false && !state.tmp.has_done_year_suffix) {
9857 state.tmp.has_done_year_suffix = true;
9858 num = parseInt(state.registry.registry[Item.id].disambig.year_suffix, 10);
9859 // first argument is for number particle [a-zA-Z], never present on dates
9860 number = new CSL.NumericBlob(false, num, this, Item.id);
9861 this.successor_prefix = state[state.build.area].opt.layout_delimiter;
9862 this.splice_prefix = state[state.build.area].opt.layout_delimiter;
9863 formatter = new CSL.Util.Suffixator(CSL.SUFFIX_CHARS);
9864 number.setFormatter(formatter);
9865 if (state[state.tmp.area].opt.collapse === "year-suffix-ranged") {
9866 number.range_prefix = state.getTerm("citation-range-delimiter");
9867 }
9868 if (state[state.tmp.area].opt.cite_group_delimiter) {
9869 number.successor_prefix = state[state.tmp.area].opt.cite_group_delimiter;
9870 } else if (state[state.tmp.area].opt["year-suffix-delimiter"]) {
9871 number.successor_prefix = state[state.tmp.area].opt["year-suffix-delimiter"];
9872 } else {
9873 number.successor_prefix = state[state.tmp.area].opt.layout_delimiter;
9874 }
9875 number.UGLY_DELIMITER_SUPPRESS_HACK = true;
9876 state.output.append(number, "literal");
9877 }
9878 }
9879
9880 };
9881 this.execs.push(func);
9882 target.push(this);
9883 }
9884};
9885
9886
9887
9888/*global CSL: true */
9889
9890CSL.Node["else-if"] = {
9891 //
9892 // these function are the same as those in if, might just clone
9893 build: function (state, target) {
9894 CSL.Conditions.TopNode.call(this, state, target);
9895 target.push(this);
9896 },
9897 configure: function (state, pos) {
9898 CSL.Conditions.Configure.call(this, state, pos);
9899 }
9900};
9901
9902/*global CSL: true */
9903
9904CSL.Node["else"] = {
9905 build: function (state, target) {
9906 target.push(this);
9907 },
9908 configure: function (state, pos) {
9909 if (this.tokentype === CSL.START) {
9910 state.configure.fail[(state.configure.fail.length - 1)] = pos;
9911 }
9912 }
9913};
9914
9915
9916/*global CSL: true */
9917
9918CSL.Node["et-al"] = {
9919 build: function (state, target) {
9920 if (state.build.area === "citation" || state.build.area === "bibliography") {
9921 var func = function (state) {
9922 state.tmp.etal_node = this;
9923 if ("string" === typeof this.strings.term) {
9924 state.tmp.etal_term = this.strings.term;
9925 }
9926 };
9927 this.execs.push(func);
9928 }
9929 target.push(this);
9930 }
9931};
9932
9933/*global CSL: true */
9934
9935CSL.Node.group = {
9936 build: function (state, target, realGroup) {
9937 var func, execs, done_vars;
9938 this.realGroup = realGroup;
9939 if (this.tokentype === CSL.START) {
9940 CSL.Util.substituteStart.call(this, state, target);
9941 if (state.build.substitute_level.value()) {
9942 state.build.substitute_level.replace((state.build.substitute_level.value() + 1));
9943 }
9944 if (!this.juris) {
9945 target.push(this);
9946 }
9947
9948 // newoutput
9949 func = function (state) {
9950 state.output.startTag("group", this);
9951
9952 if (this.strings.label_form_override) {
9953 if (!state.tmp.group_context.tip.label_form) {
9954 state.tmp.group_context.tip.label_form = this.strings.label_form_override;
9955 }
9956 }
9957
9958 if (this.strings.label_capitalize_if_first_override) {
9959 if (!state.tmp.group_context.tip.label_capitalize_if_first) {
9960 state.tmp.group_context.tip.label_capitalize_if_first = this.strings.label_capitalize_if_first_override;
9961 }
9962 }
9963
9964 if (this.realGroup) {
9965 var condition = false;
9966 var force_suppress = false;
9967
9968 // XXX Can we do something better for length here?
9969 if (state.tmp.group_context.mystack.length) {
9970 state.output.current.value().parent = state.tmp.group_context.tip.output_tip;
9971 }
9972
9973 // fieldcontextflag
9974 var label_form = state.tmp.group_context.tip.label_form;
9975 if (!label_form) {
9976 label_form = this.strings.label_form_override;
9977 }
9978
9979 var label_capitalize_if_first = state.tmp.group_context.tip.label_capitalize_if_first;
9980 if (!label_capitalize_if_first) {
9981 label_capitalize_if_first = this.strings.label_capitalize_if_first;
9982 }
9983 if (state.tmp.group_context.tip.condition) {
9984 condition = state.tmp.group_context.tip.condition;
9985 force_suppress = state.tmp.group_context.tip.force_suppress;
9986 //force_suppress: false;
9987 } else if (this.strings.reject) {
9988 condition = {
9989 test: this.strings.reject,
9990 not: true
9991 };
9992 done_vars = [];
9993 } else if (this.strings.require) {
9994 condition = {
9995 test: this.strings.require,
9996 not: false
9997 };
9998 done_vars = [];
9999 }
10000 // CONDITION
10001 //if (!state.tmp.just_looking) {
10002 // print(" pushing condition[" + state.tmp.group_context.mystack.length + "]: "+condition+" "+force_suppress);
10003 //}
10004 //if (!state.tmp.just_looking) {
10005 // var params = ["variable_success", "force_suppress","term_intended", "variable_attempt"]
10006 // print("PUSH parent="+JSON.stringify(state.tmp.group_context.tip, params))
10007 //}
10008 state.tmp.group_context.push({
10009 old_term_predecessor: state.tmp.term_predecessor,
10010 term_intended: false,
10011 variable_attempt: false,
10012 variable_success: false,
10013 variable_success_parent: state.tmp.group_context.tip.variable_success,
10014 output_tip: state.output.current.tip,
10015 label_form: label_form,
10016 label_capitalize_if_first: label_capitalize_if_first,
10017 parallel_condition: this.strings.set_parallel_condition,
10018 no_repeat_condition: this.strings.set_no_repeat_condition,
10019 parallel_result: undefined,
10020 parallel_repeats: undefined,
10021 condition: condition,
10022 force_suppress: force_suppress,
10023 done_vars: state.tmp.group_context.tip.done_vars.slice()
10024 });
10025 //if (!state.tmp.just_looking) {
10026 // print(" flags="+JSON.stringify(state.tmp.group_context.tip, params))
10027 //}
10028 }
10029 };
10030 //
10031 // Paranoia. Assure that this init function is the first executed.
10032 execs = [];
10033 execs.push(func);
10034 this.execs = execs.concat(this.execs);
10035
10036 // "Special handling" for nodes that contain only
10037 // publisher and place, with no affixes. For such
10038 // nodes only, parallel publisher/place pairs
10039 // will be parsed out and properly joined, piggybacking on
10040 // join parameters set on cs:citation or cs:bibliography.
10041 if (this.strings["has-publisher-and-publisher-place"]) {
10042 // Pass variable string values to the closing
10043 // tag via a global, iff they conform to expectations.
10044 state.build["publisher-special"] = true;
10045 if (this.strings["subgroup-delimiter"]) {
10046 // Set the handling function only if name-delimiter
10047 // is set on the parent cs:citation or cs:bibliography
10048 // node.
10049 func = function (state, Item) {
10050 if (Item.publisher && Item["publisher-place"]) {
10051 var publisher_lst = Item.publisher.split(/;\s*/);
10052 var publisher_place_lst = Item["publisher-place"].split(/;\s*/);
10053 if (publisher_lst.length > 1
10054 && publisher_lst.length === publisher_place_lst.length) {
10055 state.publisherOutput = new CSL.PublisherOutput(state, this);
10056 state.publisherOutput["publisher-list"] = publisher_lst;
10057 state.publisherOutput["publisher-place-list"] = publisher_place_lst;
10058 }
10059 }
10060 };
10061 this.execs.push(func);
10062 }
10063 }
10064
10065 if (this.juris) {
10066 // "Special handling" for jurisdiction macros
10067 // We try to instantiate these as standalone token lists.
10068 // If available, the token list is executed,
10069 // the result is written directly into output,
10070 // and control returns here.
10071
10072 // So we'll have something like this:
10073 // * expandMacro() in util_node.js flags juris- macros
10074 // on build. [DONE]
10075 // * Those are picked up here, and
10076 // - A runtime function attempts to fetch and instantiate
10077 // the macros in separate token lists under a segment
10078 // opened for the jurisdiction. We assume that the
10079 // jurisdiction has a full set of macros. That will need
10080 // to be enforced by validation. [DONE HERE, function is TODO]
10081 // - Success or failure is marked in a runtime flag object
10082 // (in citeproc.opt). [DONE]
10083 // - After the instantiation function comes a test, for
10084 // juris- macros only, which either runs diverted code,
10085 // or proceeds as per normal through the token list. [TODO]
10086 // I think that's all there is to it.
10087
10088 // Code for fetching an instantiating?
10089
10090 var choose_start = new CSL.Token("choose", CSL.START);
10091 CSL.Node.choose.build.call(choose_start, state, target);
10092
10093 var if_start = new CSL.Token("if", CSL.START);
10094
10095 func = (function (macroName) {
10096 return function (Item) {
10097 if (!state.sys.retrieveStyleModule || !CSL.MODULE_MACROS[macroName] || !Item.jurisdiction) {
10098 return false;
10099 }
10100 var jurisdictionList = state.getJurisdictionList(Item.jurisdiction);
10101 // Set up a list of jurisdictions here, we will reuse it
10102 if (!state.opt.jurisdictions_seen[jurisdictionList[0]]) {
10103 var res = state.retrieveAllStyleModules(jurisdictionList);
10104 // Okay. We have code for each of the novel modules in the
10105 // hierarchy. Load them all into the processor.
10106 for (var jurisdiction in res) {
10107 var macroCount = 0;
10108 state.juris[jurisdiction] = {};
10109 var myXml = CSL.setupXml(res[jurisdiction]);
10110 var myNodes = myXml.getNodesByName(myXml.dataObj, "law-module");
10111 for (var i=0,ilen=myNodes.length;i<ilen;i++) {
10112 var myTypes = myXml.getAttributeValue(myNodes[i],"types");
10113 if (myTypes) {
10114 state.juris[jurisdiction].types = {};
10115 myTypes = myTypes.split(/\s+/);
10116 for (var j=0,jlen=myTypes.length;j<jlen;j++) {
10117 state.juris[jurisdiction].types[myTypes[j]] = true;
10118 }
10119 }
10120 }
10121 if (!state.juris[jurisdiction].types) {
10122 state.juris[jurisdiction].types = CSL.MODULE_TYPES;
10123 }
10124 var myNodes = myXml.getNodesByName(myXml.dataObj, "macro");
10125 for (var i=0,ilen=myNodes.length;i<ilen;i++) {
10126 var myName = myXml.getAttributeValue(myNodes[i], "name");
10127 if (!CSL.MODULE_MACROS[myName]) {
10128 CSL.debug("CSL: skipping non-modular macro name \"" + myName + "\" in module context");
10129 continue;
10130 }
10131 macroCount++;
10132 state.juris[jurisdiction][myName] = [];
10133 // Must use the same XML parser for style and modules.
10134 state.buildTokenLists(myNodes[i], state.juris[jurisdiction][myName]);
10135 state.configureTokenList(state.juris[jurisdiction][myName]);
10136 }
10137 //if (macroCount < Object.keys(CSL.MODULE_MACROS).length) {
10138 // var missing = [];
10139 // throw "CSL ERROR: Incomplete jurisdiction style module for: " + jurisdiction;
10140 //}
10141 }
10142 }
10143 // Identify the best jurisdiction for the item and return true, otherwise return false
10144 for (var i=0,ilen=jurisdictionList.length;i<ilen;i++) {
10145 var jurisdiction = jurisdictionList[i];
10146 if(state.juris[jurisdiction] && state.juris[jurisdiction].types[Item.type]) {
10147 Item["best-jurisdiction"] = jurisdiction;
10148 return true;
10149 }
10150 }
10151 return false;
10152 };
10153 }(this.juris));
10154
10155 if_start.tests ? {} : if_start.tests = [];
10156 if_start.tests.push(func);
10157 if_start.test = state.fun.match.any(if_start, state, if_start.tests);
10158 target.push(if_start);
10159 var text_node = new CSL.Token("text", CSL.SINGLETON);
10160 func = function (state, Item, item) {
10161 // This will run the juris- token list.
10162 var next = 0;
10163 if (state.juris[Item["best-jurisdiction"]][this.juris]) {
10164 while (next < state.juris[Item["best-jurisdiction"]][this.juris].length) {
10165 next = CSL.tokenExec.call(state, state.juris[Item["best-jurisdiction"]][this.juris][next], Item, item);
10166 }
10167 }
10168 };
10169 text_node.juris = this.juris;
10170 text_node.execs.push(func);
10171 target.push(text_node);
10172
10173 var if_end = new CSL.Token("if", CSL.END);
10174 CSL.Node["if"].build.call(if_end, state, target);
10175 var else_start = new CSL.Token("else", CSL.START);
10176 CSL.Node["else"].build.call(else_start, state, target);
10177 }
10178 }
10179
10180 if (this.tokentype === CSL.END) {
10181
10182 // Unbundle and print publisher lists
10183 // Same constraints on creating the necessary function here
10184 // as above. The full content of the group formatting token
10185 // is apparently not available on the closing tag here,
10186 // hence the global flag on state.build.
10187 if (state.build["publisher-special"]) {
10188 state.build["publisher-special"] = false;
10189 func = function (state) {
10190 if (state.publisherOutput) {
10191 state.publisherOutput.render();
10192 state.publisherOutput = false;
10193 }
10194 };
10195 this.execs.push(func);
10196 }
10197
10198 // quashnonfields
10199 func = function (state, Item) {
10200 state.output.endTag();
10201 if (this.realGroup) {
10202 var flags = state.tmp.group_context.pop();
10203 if (state.tmp.area === "bibliography_sort") {
10204 var citationNumberIdx = flags.done_vars.indexOf("citation-number");
10205 if (this.strings.sort_direction && citationNumberIdx > -1 && state.tmp.group_context.length() == 1) {
10206 if (this.strings.sort_direction === CSL.DESCENDING) {
10207 state.bibliography_sort.opt.citation_number_sort_direction = CSL.DESCENDING;
10208 } else {
10209 state.bibliography_sort.opt.citation_number_sort_direction = CSL.ASCENDING;
10210 }
10211 flags.done_vars = flags.done_vars.slice(0, citationNumberIdx).concat(flags.done_vars.slice(citationNumberIdx + 1))
10212 }
10213 }
10214 //var params = ["condition", "variable_success", "force_suppress","term_intended", "variable_attempt"]
10215 //if (!state.tmp.just_looking) {
10216 // print("POP parent="+JSON.stringify(state.tmp.group_context.tip, params))
10217 // print(" flags="+JSON.stringify(flags, params));
10218 //}
10219 if (flags.condition) {
10220 flags.force_suppress = CSL.EVALUATE_GROUP_CONDITION(state, flags);
10221 }
10222 if (state.tmp.group_context.tip.condition) {
10223 state.tmp.group_context.tip.force_suppress = flags.force_suppress;
10224 }
10225 if (!flags.force_suppress && (flags.variable_success || (flags.term_intended && !flags.variable_attempt))) {
10226 if (!this.isJurisLocatorLabel) {
10227 state.tmp.group_context.tip.variable_success = true;
10228 }
10229 var blobs = state.output.current.value().blobs;
10230 var pos = state.output.current.value().blobs.length - 1;
10231 if (!state.tmp.just_looking && (flags.parallel_condition || flags.no_repeat_condition)) {
10232 var parallel_condition_object = {
10233 blobs: blobs,
10234 condition: flags.parallel_condition,
10235 result: flags.parallel_result,
10236 norepeat: flags.no_repeat_condition,
10237 repeats: flags.parallel_repeats,
10238 id: Item.id,
10239 pos: pos
10240 };
10241 state.parallel.parallel_conditional_blobs_list.push(parallel_condition_object);
10242 }
10243 } else {
10244 state.tmp.term_predecessor = flags.old_term_predecessor;
10245 state.tmp.group_context.tip.variable_attempt = flags.variable_attempt;
10246 if (flags.force_suppress && !state.tmp.group_context.tip.condition) {
10247 state.tmp.group_context.tip.variable_attempt = true;
10248 state.tmp.group_context.tip.variable_success = flags.variable_success_parent;
10249 }
10250 if (flags.force_suppress) {
10251 // 2019-04-15
10252 // This is removing variables done within the group we're leaveing from global
10253 // done_vars? How does that make sense?
10254 // Ah. This is a FAILURE. So removing from done_vars allows it to re-render
10255 // later in the cite if desired.
10256 // Currently no tests fail from removing the condition, but leaving it in.
10257 for (var i=0,ilen=flags.done_vars.length;i<ilen;i++) {
10258 var doneVar = flags.done_vars[i];
10259 for (var j=0,jlen=state.tmp.done_vars.length; j<jlen; j++) {
10260 if (state.tmp.done_vars[j] === doneVar) {
10261 state.tmp.done_vars = state.tmp.done_vars.slice(0, j).concat(state.tmp.done_vars.slice(j+1));
10262 }
10263 }
10264 }
10265 }
10266 if (state.output.current.value().blobs) {
10267 state.output.current.value().blobs.pop();
10268 }
10269 }
10270 }
10271 };
10272 this.execs.push(func);
10273
10274 if (this.juris) {
10275 var else_end = new CSL.Token("else", CSL.END);
10276 CSL.Node["else"].build.call(else_end, state, target);
10277 var choose_end = new CSL.Token("choose", CSL.END);
10278 CSL.Node.choose.build.call(choose_end, state, target);
10279 }
10280 }
10281
10282 if (this.tokentype === CSL.END) {
10283 if (!this.juris) {
10284 target.push(this);
10285 }
10286 if (state.build.substitute_level.value()) {
10287 state.build.substitute_level.replace((state.build.substitute_level.value() - 1));
10288 }
10289 CSL.Util.substituteEnd.call(this, state, target);
10290 }
10291 }
10292};
10293
10294
10295/*global CSL: true */
10296
10297CSL.Node["if"] = {
10298 build: function (state, target) {
10299 CSL.Conditions.TopNode.call(this, state, target);
10300 target.push(this);
10301 },
10302 configure: function (state, pos) {
10303 CSL.Conditions.Configure.call(this, state, pos);
10304 }
10305};
10306
10307
10308CSL.Node["conditions"] = {
10309 build: function (state) {
10310 if (this.tokentype === CSL.START) {
10311 state.tmp.conditions.addMatch(this.match);
10312 }
10313 if (this.tokentype === CSL.END) {
10314 state.tmp.conditions.matchCombine();
10315 }
10316 }
10317};
10318
10319CSL.Node["condition"] = {
10320 build: function (state) {
10321 if (this.tokentype === CSL.SINGLETON) {
10322 var test = state.fun.match[this.match](this, state, this.tests);
10323 state.tmp.conditions.addTest(test);
10324 }
10325 }
10326};
10327
10328CSL.Conditions = {};
10329
10330CSL.Conditions.TopNode = function (state) {
10331 var func;
10332 if (this.tokentype === CSL.START || this.tokentype === CSL.SINGLETON) {
10333 if (this.locale) {
10334 state.opt.lang = this.locale;
10335 }
10336 if (!this.tests || !this.tests.length) {
10337 // Set up the condition compiler with our current context
10338 state.tmp.conditions = new CSL.Conditions.Engine(state, this);
10339 } else {
10340 // The usual.
10341 this.test = state.fun.match[this.match](this, state, this.tests);
10342 }
10343 if (state.build.substitute_level.value() === 0) {
10344 func = function(state) {
10345 state.tmp.condition_counter++;
10346 }
10347 this.execs.push(func);
10348 }
10349 }
10350 if (this.tokentype === CSL.END || this.tokentype === CSL.SINGLETON) {
10351 if (state.build.substitute_level.value() === 0) {
10352 func = function (state) {
10353 state.tmp.condition_counter--;
10354 if (state.tmp.condition_lang_counter_arr.length > 0) {
10355 var counter = state.tmp.condition_lang_counter_arr.slice(-1)[0];
10356 if (counter === state.tmp.condition_counter) {
10357 state.opt.lang = state.tmp.condition_lang_val_arr.pop();
10358 state.tmp.condition_lang_counter_arr.pop();
10359 }
10360 }
10361 if (this.locale_default) {
10362 // Open output tag with locale marker
10363 state.output.current.value().old_locale = this.locale_default;
10364 state.output.closeLevel("empty");
10365 state.opt.lang = this.locale_default;
10366 }
10367 };
10368 this.execs.push(func);
10369 }
10370 // closingjump
10371 func = function (state) {
10372 var next = this[state.tmp.jump.value()];
10373 return next;
10374 };
10375 this.execs.push(func);
10376 if (this.locale_default) {
10377 state.opt.lang = this.locale_default;
10378 }
10379 }
10380};
10381
10382CSL.Conditions.Configure = function (state, pos) {
10383 if (this.tokentype === CSL.START) {
10384 // jump index on failure
10385 this.fail = state.configure.fail.slice(-1)[0];
10386 this.succeed = this.next;
10387 state.configure.fail[(state.configure.fail.length - 1)] = pos;
10388 } else if (this.tokentype === CSL.SINGLETON) {
10389 // jump index on failure
10390 this.fail = this.next;
10391 this.succeed = state.configure.succeed.slice(-1)[0];
10392 state.configure.fail[(state.configure.fail.length - 1)] = pos;
10393 } else {
10394 // jump index on success
10395 this.succeed = state.configure.succeed.slice(-1)[0];
10396 this.fail = this.next;
10397 }
10398};
10399
10400CSL.Conditions.Engine = function (state, token) {
10401 this.token = token;
10402 this.state = state;
10403};
10404
10405CSL.Conditions.Engine.prototype.addTest = function (test) {
10406 this.token.tests ? {} : this.token.tests = [];
10407 this.token.tests.push(test);
10408};
10409
10410CSL.Conditions.Engine.prototype.addMatch = function (match) {
10411 this.token.match = match;
10412};
10413
10414CSL.Conditions.Engine.prototype.matchCombine = function () {
10415 this.token.test = this.state.fun.match[this.token.match](this.token, this.state, this.token.tests);
10416};
10417
10418/*global CSL: true */
10419
10420CSL.Node.info = {
10421 build: function (state) {
10422 if (this.tokentype === CSL.START) {
10423 state.build.skip = "info";
10424 } else {
10425 state.build.skip = false;
10426 }
10427 }
10428};
10429
10430
10431/*global CSL: true */
10432
10433CSL.Node.institution = {
10434 build: function (state, target) {
10435 if ([CSL.SINGLETON, CSL.START].indexOf(this.tokentype) > -1) {
10436
10437 var func = function (state) {
10438 if ("string" === typeof this.strings.delimiter) {
10439 state.tmp.institution_delimiter = this.strings.delimiter;
10440 } else {
10441 state.tmp.institution_delimiter = state.tmp.name_delimiter;
10442 }
10443
10444 // This is the same code for the same result as in node_name.js,
10445 // but when cs:institution comes on stream, it may produce
10446 // different results.
10447 if ("text" === state.inheritOpt(this, "and")) {
10448 this.and_term = state.getTerm("and", "long", 0);
10449 } else if ("symbol" === state.inheritOpt(this, "and")) {
10450 if (state.opt.development_extensions.expect_and_symbol_form) {
10451 this.and_term = state.getTerm("and", "symbol", 0);
10452 } else {
10453 this.and_term = "&";
10454 }
10455 } else if ("none" === state.inheritOpt(this, "and")) {
10456 this.and_term = state.tmp.institution_delimiter;
10457 }
10458 if ("undefined" === typeof this.and_term && state.tmp.and_term) {
10459 this.and_term = state.getTerm("and", "long", 0);
10460 }
10461 if (CSL.STARTSWITH_ROMANESQUE_REGEXP.test(this.and_term)) {
10462 this.and_prefix_single = " ";
10463 this.and_prefix_multiple = ", ";
10464 if ("string" === typeof state.tmp.institution_delimiter) {
10465 this.and_prefix_multiple = state.tmp.institution_delimiter;
10466 }
10467 this.and_suffix = " ";
10468 } else {
10469 this.and_prefix_single = "";
10470 this.and_prefix_multiple = "";
10471 this.and_suffix = "";
10472 }
10473 if (state.inheritOpt(this, "delimiter-precedes-last") === "always") {
10474 this.and_prefix_single = state.tmp.institution_delimiter;
10475 } else if (state.inheritOpt(this, "delimiter-precedes-last") === "never") {
10476 // Slightly fragile: could test for charset here to make
10477 // this more certain.
10478 if (this.and_prefix_multiple) {
10479 this.and_prefix_multiple = " ";
10480 }
10481 }
10482
10483 this.and = {};
10484 if ("undefined" !== typeof this.and_term) {
10485 state.output.append(this.and_term, "empty", true);
10486 this.and.single = state.output.pop();
10487 this.and.single.strings.prefix = this.and_prefix_single;
10488 this.and.single.strings.suffix = this.and_suffix;
10489 state.output.append(this.and_term, "empty", true);
10490 this.and.multiple = state.output.pop();
10491 this.and.multiple.strings.prefix = this.and_prefix_multiple;
10492 this.and.multiple.strings.suffix = this.and_suffix;
10493 } else if ("undefined" !== this.strings.delimiter) {
10494 this.and.single = new CSL.Blob(state.tmp.institution_delimiter);
10495 this.and.single.strings.prefix = "";
10496 this.and.single.strings.suffix = "";
10497 this.and.multiple = new CSL.Blob(state.tmp.institution_delimiter);
10498 this.and.multiple.strings.prefix = "";
10499 this.and.multiple.strings.suffix = "";
10500 }
10501 state.nameOutput.institution = this;
10502 };
10503 this.execs.push(func);
10504 }
10505 target.push(this);
10506 },
10507 configure: function (state) {
10508 if ([CSL.SINGLETON, CSL.START].indexOf(this.tokentype) > -1) {
10509 state.build.has_institution = true;
10510 }
10511 }
10512};
10513
10514/*global CSL: true */
10515
10516CSL.Node["institution-part"] = {
10517 build: function (state, target) {
10518 var func;
10519 if ("long" === this.strings.name) {
10520 if (this.strings["if-short"]) {
10521 func = function (state) {
10522 state.nameOutput.institutionpart["long-with-short"] = this;
10523 };
10524 } else {
10525 func = function (state) {
10526 state.nameOutput.institutionpart["long"] = this;
10527 };
10528 }
10529 } else if ("short" === this.strings.name) {
10530 func = function (state) {
10531 state.nameOutput.institutionpart["short"] = this;
10532 };
10533 }
10534 this.execs.push(func);
10535 target.push(this);
10536 }
10537};
10538
10539/*global CSL: true */
10540
10541CSL.Node.key = {
10542 build: function (state, target) {
10543
10544 target = state[state.build.root + "_sort"].tokens;
10545
10546 var func;
10547 var debug = false;
10548 var start_key = new CSL.Token("key", CSL.START);
10549
10550 state.tmp.root = state.build.root;
10551
10552 // The params object for build and runtime (tmp) really shouldn't have been separated.
10553 // Oh, well.
10554 start_key.strings["et-al-min"] = state.inheritOpt(this, "et-al-min");
10555 start_key.strings["et-al-use-first"] = state.inheritOpt(this, "et-al-use-first");
10556 start_key.strings["et-al-use-last"] = state.inheritOpt(this, "et-al-use-last");
10557
10558
10559 // initialize done vars
10560 func = function (state) {
10561 state.tmp.done_vars = [];
10562 };
10563 start_key.execs.push(func);
10564
10565 // initialize output queue
10566 func = function (state) {
10567 state.output.openLevel("empty");
10568 };
10569 start_key.execs.push(func);
10570
10571 // sort direction
10572 var sort_direction = [];
10573 if (this.strings.sort_direction === CSL.DESCENDING) {
10574 //print("sort: descending on "+state.tmp.area);
10575 sort_direction.push(1);
10576 sort_direction.push(-1);
10577 } else {
10578 //print("sort: ascending");
10579 sort_direction.push(-1);
10580 sort_direction.push(1);
10581 }
10582 state[state.build.area].opt.sort_directions.push(sort_direction);
10583
10584 if (CSL.DATE_VARIABLES.indexOf(this.variables[0]) > -1) {
10585 state.build.date_key = true;
10586 }
10587
10588 // et al init
10589 func = function (state) {
10590 state.tmp.sort_key_flag = true;
10591 //print("== key node function ==");
10592 if (state.inheritOpt(this, "et-al-min")) {
10593 state.tmp["et-al-min"] = state.inheritOpt(this, "et-al-min");
10594 }
10595 if (state.inheritOpt(this, "et-al-use-first")) {
10596 state.tmp["et-al-use-first"] = state.inheritOpt(this, "et-al-use-first");
10597 }
10598 if ("boolean" === typeof state.inheritOpt(this, "et-al-use-last")) {
10599 state.tmp["et-al-use-last"] = state.inheritOpt(this, "et-al-use-last");
10600 //print(" set tmp et-al-use-last: "+this.strings["et-al-use-last"])
10601 }
10602 };
10603 start_key.execs.push(func);
10604 target.push(start_key);
10605
10606 //
10607 // ops to initialize the key's output structures
10608 if (this.variables.length) {
10609 var variable = this.variables[0];
10610 if (CSL.NAME_VARIABLES.indexOf(variable) > -1) {
10611 //
10612 // Start tag
10613 var names_start_token = new CSL.Token("names", CSL.START);
10614 names_start_token.tokentype = CSL.START;
10615 names_start_token.variables = this.variables;
10616 CSL.Node.names.build.call(names_start_token, state, target);
10617 //
10618 // Name tag
10619 var name_token = new CSL.Token("name", CSL.SINGLETON);
10620 name_token.tokentype = CSL.SINGLETON;
10621 name_token.strings["name-as-sort-order"] = "all";
10622 name_token.strings["sort-separator"] = " ";
10623 name_token.strings["et-al-use-last"] = state.inheritOpt(this, "et-al-use-last");
10624 name_token.strings["et-al-min"] = state.inheritOpt(this, "et-al-min");
10625 name_token.strings["et-al-use-first"] = state.inheritOpt(this, "et-al-use-first");
10626 CSL.Node.name.build.call(name_token, state, target);
10627 //
10628 // Institution tag
10629 var institution_token = new CSL.Token("institution", CSL.SINGLETON);
10630 institution_token.tokentype = CSL.SINGLETON;
10631 CSL.Node.institution.build.call(institution_token, state, target);
10632 //
10633 // End tag
10634 var names_end_token = new CSL.Token("names", CSL.END);
10635 names_end_token.tokentype = CSL.END;
10636 CSL.Node.names.build.call(names_end_token, state, target);
10637 } else {
10638 var single_text = new CSL.Token("text", CSL.SINGLETON);
10639 single_text.strings.sort_direction = this.strings.sort_direction;
10640 single_text.dateparts = this.dateparts;
10641 if (CSL.NUMERIC_VARIABLES.indexOf(variable) > -1) {
10642 // citation-number is virtualized. As a sort key it has no effect on registry
10643 // sort order per se, but if set to DESCENDING, it reverses the sequence of numbers representing
10644 // bib entries.
10645 if (variable === "citation-number") {
10646 func = function (state, Item) {
10647 if (state.tmp.area === "bibliography_sort") {
10648 if (this.strings.sort_direction === CSL.DESCENDING) {
10649 state.bibliography_sort.opt.citation_number_sort_direction = CSL.DESCENDING;
10650 } else {
10651 state.bibliography_sort.opt.citation_number_sort_direction = CSL.ASCENDING;
10652 }
10653 }
10654 if (state.tmp.area === "citation_sort" && state.bibliography_sort.tmp.citation_number_map) {
10655 var num = state.bibliography_sort.tmp.citation_number_map[state.registry.registry[Item.id].seq];
10656 } else {
10657 var num = state.registry.registry[Item.id].seq;
10658 }
10659 if (num) {
10660 // Code currently in util_number.js
10661 num = CSL.Util.padding("" + num);
10662 }
10663 state.output.append(num, this);
10664 };
10665 } else {
10666 func = function (state, Item) {
10667 var num = false;
10668 num = Item[variable];
10669 // XXX What if this is NaN?
10670 if (num) {
10671 // Code currently in util_number.js
10672 num = CSL.Util.padding(num);
10673 }
10674 state.output.append(num, this);
10675 };
10676 }
10677 } else if (variable === "citation-label") {
10678 func = function (state, Item) {
10679 var trigraph = state.getCitationLabel(Item);
10680 state.output.append(trigraph, this);
10681 };
10682 } else if (CSL.DATE_VARIABLES.indexOf(variable) > -1) {
10683 func = CSL.dateAsSortKey;
10684 single_text.variables = this.variables;
10685 } else if ("title" === variable) {
10686 var abbrevfam = "title";
10687 var abbrfall = false;
10688 var altvar = false;
10689 var transfall = true;
10690 func = state.transform.getOutputFunction(this.variables, abbrevfam, abbrfall, altvar, transfall);
10691 } else {
10692 func = function (state, Item) {
10693 var varval = Item[variable];
10694 state.output.append(varval, "empty");
10695 };
10696 }
10697 single_text.execs.push(func);
10698 target.push(single_text);
10699 }
10700 } else { // macro
10701 //
10702 // if it's not a variable, it's a macro
10703 var token = new CSL.Token("text", CSL.SINGLETON);
10704 token.strings.sort_direction = this.strings.sort_direction;
10705 token.postponed_macro = this.postponed_macro;
10706 CSL.expandMacro.call(state, token, target);
10707 }
10708 //
10709 // ops to output the key string result to an array go
10710 // on the closing "key" tag before it is pushed.
10711 // Do not close the level.
10712 var end_key = new CSL.Token("key", CSL.END);
10713
10714 // Eliminated at revision 1.0.159.
10715 // Was causing non-fatal error "wanted empty but found group".
10716 // Possible contributor to weird "PAGES" bug?
10717 //func = function (state, Item) {
10718 //state.output.closeLevel("empty");
10719 //};
10720 //end_key.execs.push(func);
10721
10722 // store key for use
10723 func = function (state) {
10724 var keystring = state.output.string(state, state.output.queue);
10725 if (state.sys.normalizeUnicode) {
10726 keystring = state.sys.normalizeUnicode(keystring);
10727 }
10728 keystring = keystring ? (keystring.split(" ").join(state.opt.sort_sep) + state.opt.sort_sep) : "";
10729 //SNIP-START
10730 if (debug) {
10731 CSL.debug("keystring: " + keystring + " " + typeof keystring);
10732 }
10733 //print("keystring: (" + keystring + ") " + typeof keystring + " " + state.tmp.area);
10734 //SNIP-END
10735 //state.sys.print("keystring: (" + keystring + ") " + typeof keystring + " " + state.tmp.area);
10736 if ("" === keystring) {
10737 keystring = undefined;
10738 }
10739 if ("string" !== typeof keystring || state.tmp.empty_date) {
10740 keystring = undefined;
10741 state.tmp.empty_date = false;
10742 }
10743 state[state[state.tmp.area].root + "_sort"].keys.push(keystring);
10744 state.tmp.value = [];
10745 };
10746 end_key.execs.push(func);
10747
10748 // Set year-suffix key on anything that looks like a date
10749 if (state.build.date_key) {
10750 if (state.build.area === "citation" && state.build.extension === "_sort") {
10751 // ascending sort always
10752 state[state.build.area].opt.sort_directions.push([-1,1]);
10753 func = function (state, Item) {
10754 // year-suffix Key
10755 var year_suffix = state.registry.registry[Item.id].disambig.year_suffix;
10756 if (!year_suffix) {
10757 year_suffix = 0;
10758 }
10759 var key = CSL.Util.padding("" + year_suffix);
10760 state[state.tmp.area].keys.push(key);
10761 };
10762 end_key.execs.push(func);
10763 }
10764 state.build.date_key = false;
10765 }
10766
10767 // reset key params
10768 func = function (state) {
10769 // state.tmp.name_quash = new Object();
10770
10771 // XXX This should work, should be necessary, but doesn't and isn't.
10772 //state.output.closeLevel("empty");
10773
10774 state.tmp["et-al-min"] = undefined;
10775 state.tmp["et-al-use-first"] = undefined;
10776 state.tmp["et-al-use-last"] = undefined;
10777 state.tmp.sort_key_flag = false;
10778 };
10779 end_key.execs.push(func);
10780 target.push(end_key);
10781 }
10782};
10783
10784/*global CSL: true */
10785
10786CSL.Node.label = {
10787 build: function (state, target) {
10788
10789 if (this.strings.term) {
10790 // Non-names labels
10791 var func = function (state, Item, item) {
10792 // Must accomplish this without touching strings
10793 // shared with the calling application: "sub verbo"
10794 // and "sub-verbo" must both pass, as they stand.
10795 //if (item && item.label === "sub verbo") {
10796 // item.label = "sub-verbo";
10797 //}
10798 // This is abstracted away, because the same
10799 // logic must be run in cs:names.
10800 var termtxt = CSL.evaluateLabel(this, state, Item, item);
10801 if (item && this.strings.term === "locator") {
10802
10803 item.section_form_override = this.strings.form;
10804
10805 }
10806 if (termtxt) {
10807 state.tmp.group_context.tip.term_intended = true;
10808 }
10809 CSL.UPDATE_GROUP_CONTEXT_CONDITION(state, termtxt);
10810 if (termtxt.indexOf("%s") === -1) {
10811 // ^ Suppress output here if we have an embedded term
10812 if (this.strings.capitalize_if_first) {
10813 if (!state.tmp.term_predecessor && !(state.opt["class"] === "in-text" && state.tmp.area === "citation")) {
10814 termtxt = CSL.Output.Formatters["capitalize-first"](state, termtxt);
10815 }
10816 }
10817 state.output.append(termtxt, this);
10818 }
10819 };
10820 this.execs.push(func);
10821 } else {
10822 if (!this.strings.form) {
10823 this.strings.form = "long";
10824 }
10825 // Names labels
10826 // Picked up in names END
10827 var namevars = state.build.names_variables[state.build.names_variables.length-1];
10828 var namelabels = state.build.name_label[state.build.name_label.length-1];
10829 for (var i = 0, ilen = namevars.length; i < ilen; i += 1) {
10830 if (!namelabels[namevars[i]]) {
10831 namelabels[namevars[i]] = {};
10832 }
10833 }
10834 if (!state.build.name_flag) {
10835 for (var i = 0, ilen = namevars.length; i < ilen; i += 1) {
10836 namelabels[namevars[i]].before = this;
10837 }
10838 } else {
10839 for (var i = 0, ilen = namevars.length; i < ilen; i += 1) {
10840 namelabels[namevars[i]].after = this;
10841 }
10842 }
10843 }
10844 target.push(this);
10845 }
10846};
10847
10848/*global CSL: true */
10849
10850CSL.Node.layout = {
10851 build: function (state, target) {
10852 var func, prefix_token, suffix_token, tok;
10853
10854 function setSuffix() {
10855 if (state.build.area === "bibliography") {
10856 suffix_token = new CSL.Token("text", CSL.SINGLETON);
10857 func = function(state) {
10858 var suffix;
10859 if (state.tmp.cite_affixes[state.tmp.area][state.tmp.last_cite_locale]) {
10860 suffix = state.tmp.cite_affixes[state.tmp.area][state.tmp.last_cite_locale].suffix;
10861 } else {
10862 suffix = state.bibliography.opt.layout_suffix;
10863 }
10864
10865 // If @display is used, layout suffix is placed on the last
10866 // immediate child of the layout, which we assume will be a
10867 // @display group node.
10868 var topblob = state.output.current.value();
10869 if (state.opt.using_display) {
10870 topblob.blobs[topblob.blobs.length-1].strings.suffix = suffix;
10871 } else {
10872 topblob.strings.suffix = suffix;
10873 }
10874 if (state.bibliography.opt["second-field-align"]) {
10875 // closes bib_other
10876 state.output.endTag("bib_other");
10877 }
10878 };
10879 suffix_token.execs.push(func);
10880 target.push(suffix_token);
10881 }
10882 }
10883
10884 if (this.tokentype === CSL.START) {
10885
10886 if (this.locale_raw) {
10887 state.build.current_default_locale = this.locale_raw;
10888 } else {
10889 state.build.current_default_locale = state.opt["default-locale"];
10890 }
10891
10892 func = function (state, Item, item) {
10893 if (state.opt.development_extensions.apply_citation_wrapper
10894 && state.sys.wrapCitationEntry
10895 && !state.tmp.just_looking
10896 && Item.system_id
10897 && state.tmp.area === "citation") {
10898
10899 var cite_entry = new CSL.Token("group", CSL.START);
10900 cite_entry.decorations = [["@cite", "entry"]];
10901 state.output.startTag("cite_entry", cite_entry);
10902 state.output.current.value().item_id = Item.system_id;
10903 if (item) {
10904 state.output.current.value().locator_txt = item.locator_txt;
10905 state.output.current.value().suffix_txt = item.suffix_txt;
10906 }
10907 }
10908 };
10909 this.execs.push(func);
10910 }
10911
10912 // XXX Works, but using state.tmp looks wrong here? We're in the build layer ...
10913 if (this.tokentype === CSL.START && !state.tmp.cite_affixes[state.build.area]) {
10914 //
10915 // done_vars is used to prevent the repeated
10916 // rendering of variables
10917 //
10918 // initalize done vars
10919 func = function (state, Item) {
10920
10921 state.tmp.done_vars = [];
10922 if (state.opt.suppressedJurisdictions[Item["country"]]
10923 && Item["country"]
10924 && ["treaty", "patent"].indexOf(Item.type) === -1) {
10925
10926 state.tmp.done_vars.push("country");
10927 }
10928 if (!state.tmp.just_looking && state.registry.registry[Item.id] && state.registry.registry[Item.id].parallel) {
10929 state.tmp.done_vars.push("first-reference-note-number");
10930 }
10931 //CSL.debug(" === init rendered_name === ");
10932 state.tmp.rendered_name = false;
10933 };
10934 this.execs.push(func);
10935 // set opt delimiter
10936 func = function (state) {
10937 // just in case
10938 state.tmp.sort_key_flag = false;
10939 };
10940 this.execs.push(func);
10941
10942 // reset nameset counter [all nodes]
10943 func = function (state) {
10944 state.tmp.nameset_counter = 0;
10945 };
10946 this.execs.push(func);
10947
10948 func = function (state, Item) {
10949 var tok = new CSL.Token();
10950 state.output.openLevel(tok);
10951 };
10952 this.execs.push(func);
10953 target.push(this);
10954
10955 if (state.build.area === "citation") {
10956 prefix_token = new CSL.Token("text", CSL.SINGLETON);
10957 func = function (state, Item, item) {
10958 if (item && item.prefix) {
10959 var prefix = CSL.checkPrefixSpaceAppend(state, item.prefix);
10960 if (!state.tmp.just_looking) {
10961 prefix = state.output.checkNestedBrace.update(prefix);
10962 }
10963 var ignorePredecessor = CSL.checkIgnorePredecessor(state, prefix);
10964 state.output.append(prefix, this, false, ignorePredecessor);
10965 }
10966 };
10967 prefix_token.execs.push(func);
10968 target.push(prefix_token);
10969 }
10970 }
10971
10972 // Cast token to be used in one of the configurations below.
10973 var my_tok;
10974 if (this.locale_raw) {
10975 my_tok = new CSL.Token("dummy", CSL.START);
10976 my_tok.locale = this.locale_raw;
10977 my_tok.strings.delimiter = this.strings.delimiter;
10978 my_tok.strings.suffix = this.strings.suffix;
10979 if (!state.tmp.cite_affixes[state.build.area]) {
10980 state.tmp.cite_affixes[state.build.area] = {};
10981 }
10982 }
10983
10984 if (this.tokentype === CSL.START) {
10985 state.build.layout_flag = true;
10986
10987 // Only run the following once, to set up the final layout node ...
10988 if (!this.locale_raw) {
10989 //
10990 // save out decorations for flipflop processing [final node only]
10991 //
10992 state[state.tmp.area].opt.topdecor = [this.decorations];
10993 state[(state.tmp.area + "_sort")].opt.topdecor = [this.decorations];
10994
10995 state[state.build.area].opt.layout_prefix = this.strings.prefix;
10996 state[state.build.area].opt.layout_suffix = this.strings.suffix;
10997 state[state.build.area].opt.layout_delimiter = this.strings.delimiter;
10998
10999 state[state.build.area].opt.layout_decorations = this.decorations;
11000
11001 // Only do this if we're running conditionals
11002 if (state.tmp.cite_affixes[state.build.area]) {
11003 // if build_layout_locale_flag is true,
11004 // write cs:else START to the token list.
11005 tok = new CSL.Token("else", CSL.START);
11006 CSL.Node["else"].build.call(tok, state, target);
11007 }
11008
11009 } // !this.locale_raw
11010
11011 // Conditionals
11012 if (this.locale_raw) {
11013 if (!state.build.layout_locale_flag) {
11014 // if layout_locale_flag is untrue,
11015 // write cs:choose START and cs:if START
11016 // to the token list.
11017 var choose_tok = new CSL.Token("choose", CSL.START);
11018 CSL.Node.choose.build.call(choose_tok, state, target);
11019 my_tok.name = "if";
11020 CSL.Attributes["@locale-internal"].call(my_tok, state, this.locale_raw);
11021 CSL.Node["if"].build.call(my_tok, state, target);
11022 } else {
11023 // if build_layout_locale_flag is true,
11024 // write cs:else-if START to the token list.
11025 my_tok.name = "else-if";
11026 CSL.Attributes["@locale-internal"].call(my_tok, state, this.locale_raw);
11027 CSL.Node["else-if"].build.call(my_tok, state, target);
11028 }
11029 // cite_affixes for this node
11030 state.tmp.cite_affixes[state.build.area][my_tok.locale] = {};
11031 state.tmp.cite_affixes[state.build.area][my_tok.locale].delimiter = this.strings.delimiter;
11032 state.tmp.cite_affixes[state.build.area][my_tok.locale].suffix = this.strings.suffix;
11033 }
11034 }
11035 if (this.tokentype === CSL.END) {
11036 if (this.locale_raw) {
11037 setSuffix();
11038 if (!state.build.layout_locale_flag) {
11039 // If layout_locale_flag is untrue, write cs:if END
11040 // to the token list.
11041 my_tok.name = "if";
11042 my_tok.tokentype = CSL.END;
11043 CSL.Attributes["@locale-internal"].call(my_tok, state, this.locale_raw);
11044 CSL.Node["if"].build.call(my_tok, state, target);
11045 state.build.layout_locale_flag = true;
11046 } else {
11047 // If layout_locale_flag is true, write cs:else-if END
11048 // to the token list.
11049 my_tok.name = "else-if";
11050 my_tok.tokentype = CSL.END;
11051 CSL.Attributes["@locale-internal"].call(my_tok, state, this.locale_raw);
11052 CSL.Node["else-if"].build.call(my_tok, state, target);
11053 }
11054 }
11055 if (!this.locale_raw) {
11056 setSuffix();
11057 // Only add this if we're running conditionals
11058 if (state.tmp.cite_affixes[state.build.area]) {
11059 // If layout_locale_flag is true, write cs:else END
11060 // and cs:choose END to the token list.
11061 if (state.build.layout_locale_flag) {
11062 tok = new CSL.Token("else", CSL.END);
11063 CSL.Node["else"].build.call(tok, state, target);
11064 tok = new CSL.Token("choose", CSL.END);
11065 CSL.Node.choose.build.call(tok, state, target);
11066 }
11067 }
11068 state.build_layout_locale_flag = true;
11069 if (state.build.area === "citation") {
11070 suffix_token = new CSL.Token("text", CSL.SINGLETON);
11071 func = function (state, Item, item) {
11072 var sp;
11073 if (item && item.suffix) {
11074 var suffix = CSL.checkSuffixSpacePrepend(state, item.suffix);
11075 if (!state.tmp.just_looking) {
11076 suffix = state.output.checkNestedBrace.update(suffix);
11077 }
11078 state.output.append((suffix), this);
11079 }
11080 };
11081 suffix_token.execs.push(func);
11082 target.push(suffix_token);
11083 }
11084
11085 // Closes wrapper token
11086 func = function (state) {
11087 state.output.closeLevel();
11088 };
11089 this.execs.push(func);
11090 func = function (state, Item) {
11091 if (state.opt.development_extensions.apply_citation_wrapper
11092 && state.sys.wrapCitationEntry
11093 && !state.tmp.just_looking
11094 && Item.system_id
11095 && state.tmp.area === "citation") {
11096
11097 state.output.endTag(); // closes citation link wrapper
11098 }
11099 };
11100 this.execs.push(func);
11101 target.push(this);
11102 state.build.layout_flag = false;
11103 state.build.layout_locale_flag = false;
11104 } // !this.layout_raw
11105 }
11106 }
11107};
11108
11109/*global CSL: true */
11110
11111CSL.Node.macro = {
11112 build: function () {}
11113};
11114
11115/*global CSL: true */
11116
11117CSL.Node.alternative = {
11118 build: function (state, target) {
11119 if (this.tokentype === CSL.START) {
11120
11121 var choose_tok = new CSL.Token("choose", CSL.START);
11122 CSL.Node["choose"].build.call(choose_tok, state, target);
11123
11124 var if_tok = new CSL.Token("if", CSL.START);
11125 CSL.Attributes["@alternative-node-internal"].call(if_tok, state);
11126 CSL.Node["if"].build.call(if_tok, state, target);
11127
11128 var func = function(state, Item) {
11129
11130 state.tmp.oldItem = Item;
11131 state.tmp.oldLang = state.opt.lang;
11132 state.tmp.abort_alternative = true;
11133
11134 if (Item["language-name"] && Item["language-name-original"]) {
11135
11136 var newItem = JSON.parse(JSON.stringify(Item));
11137
11138 newItem.language = newItem["language-name"];
11139 var langspec = CSL.localeResolve(newItem.language, state.opt["default-locale"][0]);
11140
11141 if (state.opt.multi_layout) {
11142 for (var i in state.opt.multi_layout) {
11143 var locale_list = state.opt.multi_layout[i];
11144 var gotlang = false;
11145 for (var j in locale_list) {
11146 var tryspec = locale_list[j];
11147 if (langspec.best === tryspec.best || langspec.base === tryspec.base || langspec.bare === tryspec.bare) {
11148 gotlang = locale_list[0].best;
11149 break;
11150 }
11151 }
11152 if (!gotlang) {
11153 gotlang = state.opt["default-locale"][0];
11154 }
11155 state.opt.lang = gotlang;
11156 }
11157 }
11158
11159 for (var key in newItem) {
11160 if (["id", "type", "language", "multi"].indexOf(key) === -1 && key.slice(0, 4) !== "alt-") {
11161 if (newItem.multi && newItem.multi._keys[key]) {
11162 var deleteme = true;
11163 for (var lang in newItem.multi._keys[key]) {
11164 if (langspec.bare === lang.replace(/^([a-zA-Z]+).*/, "$1")) {
11165 deleteme = false;
11166 break;
11167 }
11168 }
11169 if (deleteme) {
11170 delete newItem[key];
11171 }
11172 } else {
11173 delete newItem[key];
11174 }
11175 }
11176 }
11177 for (var key in newItem) {
11178 if (key.slice(0, 4) === "alt-") {
11179 newItem[key.slice(4)] = newItem[key];
11180 state.tmp.abort_alternative = false;
11181 } else {
11182 if (newItem.multi && newItem.multi._keys) {
11183 if (!newItem["alt-" + key] && newItem.multi._keys[key]) {
11184 if (newItem.multi._keys[key][langspec.best]) {
11185 newItem[key] = newItem.multi._keys[key][langspec.best];
11186 state.tmp.abort_alternative = false;
11187 } else if (newItem.multi._keys[key][langspec.base]) {
11188 newItem[key] = newItem.multi._keys[key][langspec.base];
11189 state.tmp.abort_alternative = false;
11190 } else if (newItem.multi._keys[key][langspec.bare]) {
11191 newItem[key] = newItem.multi._keys[key][langspec.bare];
11192 state.tmp.abort_alternative = false;
11193 }
11194 }
11195 }
11196 }
11197 }
11198 }
11199
11200 state.output.openLevel(this);
11201 state.registry.refhash[Item.id] = newItem;
11202 state.nameOutput = new CSL.NameOutput(state, newItem);
11203 };
11204 this.execs.push(func);
11205 target.push(this);
11206
11207 var choose_tok = new CSL.Token("choose", CSL.START);
11208 CSL.Node["choose"].build.call(choose_tok, state, target);
11209
11210 var if_tok = new CSL.Token("if", CSL.START);
11211 CSL.Attributes["@alternative-node-internal"].call(if_tok, state);
11212 var func = function(state) {
11213 state.tmp.abort_alternative = true;
11214 }
11215 if_tok.execs.push(func);
11216 CSL.Node["if"].build.call(if_tok, state, target);
11217
11218 } else if (this.tokentype === CSL.END) {
11219
11220 var if_tok = new CSL.Token("if", CSL.END);
11221 CSL.Node["if"].build.call(if_tok, state, target);
11222
11223 var choose_tok = new CSL.Token("choose", CSL.END);
11224 CSL.Node["choose"].build.call(choose_tok, state, target);
11225
11226 var func = function(state, Item) {
11227 state.output.closeLevel();
11228 state.registry.refhash[Item.id] = state.tmp.oldItem;
11229 state.opt.lang = state.tmp.oldLang;
11230 state.nameOutput = new CSL.NameOutput(state, state.tmp.oldItem);
11231 state.tmp.abort_alternative = false;
11232 };
11233 this.execs.push(func);
11234 target.push(this);
11235
11236 var if_tok = new CSL.Token("if", CSL.END);
11237 CSL.Node["if"].build.call(if_tok, state, target);
11238
11239 var choose_tok = new CSL.Token("choose", CSL.END);
11240 CSL.Node["choose"].build.call(choose_tok, state, target);
11241
11242 }
11243 }
11244};
11245
11246CSL.Node["alternative-text"] = {
11247 build: function (state, target) {
11248 if (this.tokentype === CSL.SINGLETON) {
11249 // do stuff
11250 var func = function(state, Item) {
11251 var Item = state.refetchItem(Item.id);
11252 CSL.getCite.call(state, Item);
11253 };
11254 this.execs.push(func);
11255 }
11256 target.push(this);
11257 }
11258};
11259
11260
11261
11262/*global CSL: true */
11263
11264CSL.NameOutput = function(state, Item, item) {
11265 this.debug = false;
11266 //SNIP-START
11267 if (this.debug) {
11268 print("(1)");
11269 }
11270 //SNIP-END
11271 this.state = state;
11272 this.Item = Item;
11273 this.item = item;
11274 this.nameset_base = 0;
11275 this.etal_spec = {};
11276 this._first_creator_variable = false;
11277 this._please_chop = false;
11278};
11279
11280CSL.NameOutput.prototype.init = function (names) {
11281 this.requireMatch = names.requireMatch;
11282 if (this.state.tmp.term_predecessor) {
11283 this.state.tmp.subsequent_author_substitute_ok = false;
11284 }
11285 if (this.nameset_offset) {
11286 this.nameset_base = this.nameset_base + this.nameset_offset;
11287 }
11288 this.nameset_offset = 0;
11289 this.names = names;
11290 this.variables = names.variables;
11291
11292 this.state.tmp.value = [];
11293 this.state.tmp.rendered_name = [];
11294 this.state.tmp.label_blob = false;
11295 this.state.tmp.etal_node = false;
11296 this.state.tmp.etal_term = false;
11297 for (var i = 0, ilen = this.variables.length; i < ilen; i += 1) {
11298 if (this.Item[this.variables[i]] && this.Item[this.variables[i]].length) {
11299 this.state.tmp.value = this.state.tmp.value.concat(this.Item[this.variables[i]]);
11300 }
11301 }
11302 this["et-al"] = undefined;
11303 // REMOVE THIS
11304 this["with"] = undefined;
11305
11306 this.name = undefined;
11307 // long, long-with-short, short
11308 this.institutionpart = {};
11309 // family, given
11310 //this.namepart = {};
11311 // before, after
11312 //this.label = {};
11313
11314 this.state.tmp.group_context.tip.variable_attempt = true;
11315
11316 this.labelVariable = this.variables[0];
11317
11318 if (!this.state.tmp.value.length) {
11319 return;
11320 }
11321
11322 // Abort and proceed to the next substitution if a match is required,
11323 // two variables are called, and they do not match.
11324 var checkCommonTerm = this.checkCommonAuthor(this.requireMatch);
11325 if (checkCommonTerm) {
11326 this.state.tmp.can_substitute.pop();
11327 this.state.tmp.can_substitute.push(true);
11328 //this.state.tmp.group_context.mystack[this.state.tmp.group_context.mystack.length-1].variable_success = false;
11329 for (var i in this.variables) {
11330 var idx = this.state.tmp.done_vars.indexOf(this.variables[i]);
11331 if (idx > -1) {
11332 this.state.tmp.done_vars = this.state.tmp.done_vars.slice(0, idx).concat(this.state.tmp.done_vars.slice(i+1));
11333 }
11334 }
11335 this.state.tmp.common_term_match_fail = true;
11336 this.variables = [];
11337 }
11338};
11339
11340
11341CSL.NameOutput.prototype.reinit = function (names, labelVariable) {
11342 this.requireMatch = names.requireMatch;
11343 this.labelVariable = labelVariable;
11344
11345 if (this.state.tmp.can_substitute.value()) {
11346 this.nameset_offset = 0;
11347 // What-all should be carried across from the subsidiary
11348 // names node, and on what conditions? For each attribute,
11349 // and decoration, is it an override, or is it additive?
11350 this.variables = names.variables;
11351
11352 // Not sure why this is necessary. Guards against a memory leak perhaps?
11353 var oldval = this.state.tmp.value.slice();
11354 this.state.tmp.value = [];
11355
11356 for (var i = 0, ilen = this.variables.length; i < ilen; i += 1) {
11357 if (this.Item[this.variables[i]] && this.Item[this.variables[i]].length) {
11358 this.state.tmp.value = this.state.tmp.value.concat(this.Item[this.variables[i]]);
11359 }
11360 }
11361 if (this.state.tmp.value.length) {
11362 this.state.tmp.can_substitute.replace(false, CSL.LITERAL);
11363 }
11364
11365 this.state.tmp.value = oldval;
11366
11367 }
11368 // Abort and proceed to the next substitution if a match is required,
11369 // two variables are called, and they do not match.
11370 var checkCommonTerm = this.checkCommonAuthor(this.requireMatch);
11371 if (checkCommonTerm) {
11372 this.state.tmp.can_substitute.pop();
11373 this.state.tmp.can_substitute.push(true);
11374 for (var i in this.variables) {
11375 var idx = this.state.tmp.done_vars.indexOf(this.variables[i]);
11376 if (idx > -1) {
11377 this.state.tmp.done_vars = this.state.tmp.done_vars.slice(0, idx).concat(this.state.tmp.done_vars.slice(i+1));
11378 }
11379 }
11380 this.variables = [];
11381 }
11382};
11383
11384CSL.NameOutput.prototype.outputNames = function () {
11385 var i, ilen;
11386 var variables = this.variables;
11387 if (this.institution.and) {
11388 if (!this.institution.and.single.blobs || !this.institution.and.single.blobs.length) {
11389 this.institution.and.single.blobs = this.name.and.single.blobs;
11390 }
11391 if (!this.institution.and.multiple.blobs || !this.institution.and.multiple.blobs.length) {
11392 this.institution.and.multiple.blobs = this.name.and.multiple.blobs;
11393 }
11394 }
11395
11396 this.variable_offset = {};
11397 if (this.family) {
11398 this.family_decor = CSL.Util.cloneToken(this.family);
11399 this.family_decor.strings.prefix = "";
11400 this.family_decor.strings.suffix = "";
11401 // Sets text-case value (text-case="title" is suppressed for items
11402 // non-English with non-English value in Item.language)
11403 for (i = 0, ilen = this.family.execs.length; i < ilen; i += 1) {
11404 this.family.execs[i].call(this.family_decor, this.state, this.Item);
11405 }
11406 } else {
11407 this.family_decor = false;
11408 }
11409
11410 if (this.given) {
11411 this.given_decor = CSL.Util.cloneToken(this.given);
11412 this.given_decor.strings.prefix = "";
11413 this.given_decor.strings.suffix = "";
11414 // Sets text-case value (text-case="title" is suppressed for items
11415 // non-English with non-English value in Item.language)
11416 for (i = 0, ilen = this.given.execs.length; i < ilen; i += 1) {
11417 this.given.execs[i].call(this.given_decor, this.state, this.Item);
11418 }
11419 } else {
11420 this.given_decor = false;
11421 }
11422
11423 //SNIP-START
11424 if (this.debug) {
11425 print("(2)");
11426 }
11427 //SNIP-END
11428 // util_names_etalconfig.js
11429 this.getEtAlConfig();
11430 //SNIP-START
11431 if (this.debug) {
11432 print("(3)");
11433 }
11434 //SNIP-END
11435 // util_names_divide.js
11436 this.divideAndTransliterateNames();
11437 //SNIP-START
11438 if (this.debug) {
11439 print("(4)");
11440 }
11441 //SNIP-END
11442 // util_names_truncate.js
11443
11444 this.truncatePersonalNameLists();
11445 //SNIP-START
11446 if (this.debug) {
11447 print("(5)");
11448 }
11449 //SNIP-END
11450
11451 //SNIP-START
11452 if (this.debug) {
11453 print("(6)");
11454 }
11455 //SNIP-END
11456 // util_names_disambig.js
11457 this.disambigNames();
11458
11459 // util_names_constraints.js
11460 this.constrainNames();
11461 //SNIP-START
11462 if (this.debug) {
11463 print("(7)");
11464 }
11465 //SNIP-END
11466 // form="count"
11467 if (this.name.strings.form === "count") {
11468 if (this.state.tmp.extension || this.names_count != 0) {
11469 this.state.output.append(this.names_count, "empty");
11470 this.state.tmp.group_context.tip.variable_success = true;
11471 }
11472 return;
11473 }
11474
11475 //SNIP-START
11476 if (this.debug) {
11477 print("(8)");
11478 }
11479 //SNIP-END
11480 this.setEtAlParameters();
11481 //SNIP-START
11482 if (this.debug) {
11483 print("(9)");
11484 }
11485 //SNIP-END
11486 this.setCommonTerm(this.requireMatch);
11487 //SNIP-START
11488 if (this.debug) {
11489 print("(10)");
11490 }
11491 //SNIP-END
11492 this.state.tmp.name_node = {};
11493 this.state.tmp.name_node.children = [];
11494 this.renderAllNames();
11495 //SNIP-START
11496 if (this.debug) {
11497 print("(11)");
11498 }
11499 //SNIP-END
11500 var blob_list = [];
11501 for (i = 0, ilen = variables.length; i < ilen; i += 1) {
11502 var v = variables[i];
11503 var institution_sets = [];
11504 var institutions = false;
11505 var varblob = null;
11506 if (!this.state.opt.development_extensions.spoof_institutional_affiliations) {
11507 varblob = this._join([this.freeters[v]], "");
11508 } else {
11509 //SNIP-START
11510 if (this.debug) {
11511 print("(11a)");
11512 }
11513 //SNIP-END
11514 for (var j = 0, jlen = this.institutions[v].length; j < jlen; j += 1) {
11515 institution_sets.push(this.joinPersonsAndInstitutions([this.persons[v][j], this.institutions[v][j]]));
11516 }
11517 //SNIP-START
11518 if (this.debug) {
11519 print("(11b)");
11520 }
11521 //SNIP-END
11522 if (this.institutions[v].length) {
11523 var pos = this.nameset_base + this.variable_offset[v];
11524 if (this.freeters[v].length) {
11525 pos += 1;
11526 }
11527 institutions = this.joinInstitutionSets(institution_sets, pos);
11528 }
11529 //SNIP-START
11530 if (this.debug) {
11531 print("(11c)");
11532 }
11533 //SNIP-END
11534 var varblob = this.joinFreetersAndInstitutionSets([this.freeters[v], institutions]);
11535 //SNIP-START
11536 if (this.debug) {
11537 print("(11d)");
11538 }
11539 //SNIP-END
11540 }
11541 if (varblob) {
11542 // Apply labels, if any
11543 if (!this.state.tmp.extension) {
11544 varblob = this._applyLabels(varblob, v);
11545 }
11546 blob_list.push(varblob);
11547 }
11548 //SNIP-START
11549 if (this.debug) {
11550 print("(11e)");
11551 }
11552 //SNIP-END
11553 if (this.common_term) {
11554 break;
11555 }
11556 }
11557 //SNIP-START
11558 if (this.debug) {
11559 print("(12)");
11560 }
11561 //SNIP-END
11562 this.state.output.openLevel("empty");
11563 this.state.output.current.value().strings.delimiter = this.state.inheritOpt(this.names, "delimiter", "names-delimiter");
11564 //SNIP-START
11565 if (this.debug) {
11566 print("(13)");
11567 }
11568 //SNIP-END
11569 for (i = 0, ilen = blob_list.length; i < ilen; i += 1) {
11570 // notSerious
11571 this.state.output.append(blob_list[i], "literal", true);
11572 }
11573 if (!this.state.tmp.just_looking && blob_list.length > 0) {
11574 this.state.tmp.probably_rendered_something = true;
11575 }
11576 //SNIP-START
11577 if (this.debug) {
11578 print("(14)");
11579 }
11580 //SNIP-END
11581 this.state.output.closeLevel("empty");
11582 //SNIP-START
11583 if (this.debug) {
11584 print("(15)");
11585 }
11586 //SNIP-END
11587 var blob = this.state.output.pop();
11588 this.state.tmp.name_node.top = blob;
11589 //SNIP-START
11590 if (this.debug) {
11591 print("(16)");
11592 }
11593 //SNIP-END
11594
11595 // Append will drop the names on the floor here if suppress-me is
11596 // set on element_trace.
11597 // Need to rescue the value for collapse comparison.
11598 var namesToken = CSL.Util.cloneToken(this.names);
11599 this.state.output.append(blob, namesToken);
11600 if (this.state.tmp.term_predecessor_name) {
11601 this.state.tmp.term_predecessor = true;
11602 }
11603 //SNIP-START
11604 if (this.debug) {
11605 print("(17)");
11606 }
11607 //SNIP-END
11608 // Also used in CSL.Util.substituteEnd (which could do with
11609 // some cleanup at this writing).
11610 //SNIP-START
11611 if (this.debug) {
11612 print("(18)");
11613 }
11614 //SNIP-END
11615 if (variables[0] !== "authority") {
11616 // Just grab the string values in the name
11617 var name_node_string = [];
11618 var nameobjs = this.Item[variables[0]];
11619 if (nameobjs) {
11620 for (var i = 0, ilen = nameobjs.length; i < ilen; i += 1) {
11621 var substring = CSL.Util.Names.getRawName(nameobjs[i]);
11622 if (substring) {
11623 name_node_string.push(substring);
11624 }
11625 }
11626 }
11627 name_node_string = name_node_string.join(", ");
11628 if (name_node_string) {
11629 this.state.tmp.name_node.string = name_node_string;
11630 }
11631 }
11632 // for classic support
11633 // This may be more convoluted than it needs to be. Or maybe not.
11634 //
11635 // Check for classic abbreviation
11636 //
11637 // If found, then (1) suppress title rendering, (2) replace the node
11638 // with the abbreviation output [and (3) do not run this._collapseAuthor() ?]
11639 if (this.state.tmp.name_node.string && !this.state.tmp.first_name_string) {
11640 this.state.tmp.first_name_string = this.state.tmp.name_node.string;
11641 }
11642 if ("classic" === this.Item.type) {
11643 if (this.state.tmp.first_name_string) {
11644 var author_title = [];
11645 author_title.push(this.state.tmp.first_name_string);
11646 if (this.Item.title) {
11647 author_title.push(this.Item.title);
11648 }
11649 author_title = author_title.join(", ");
11650 if (author_title && this.state.sys.getAbbreviation) {
11651 this.state.transform.loadAbbreviation("default", "classic", author_title);
11652 if (this.state.transform.abbrevs["default"].classic[author_title]) {
11653 this.state.tmp.done_vars.push("title");
11654 this.state.output.append(this.state.transform.abbrevs["default"].classic[author_title], "empty", true);
11655 blob = this.state.output.pop();
11656 this.state.tmp.name_node.top.blobs.pop();
11657 this.state.tmp.name_node.top.blobs.push(blob);
11658 }
11659 }
11660 }
11661 }
11662
11663 // Let's try something clever here.
11664 this._collapseAuthor();
11665
11666 // For name_SubstituteOnNamesSpanNamesSpanFail
11667 this.variables = [];
11668 //SNIP-START
11669 if (this.debug) {
11670 print("(19)");
11671 }
11672 //SNIP-END
11673};
11674
11675CSL.NameOutput.prototype._applyLabels = function (blob, v) {
11676 var txt;
11677 if (!this.label || !this.label[this.labelVariable]) {
11678 return blob;
11679 }
11680 var plural = 0;
11681 var num = this.freeters_count[v] + this.institutions_count[v];
11682 if (num > 1) {
11683 plural = 1;
11684 } else {
11685 for (var i = 0, ilen = this.persons[v].length; i < ilen; i += 1) {
11686 num += this.persons_count[v][i];
11687 }
11688 if (num > 1) {
11689 plural = 1;
11690 }
11691 }
11692 // Some code duplication here, should be factored out.
11693 if (this.label[this.labelVariable].before) {
11694 if ("number" === typeof this.label[this.labelVariable].before.strings.plural) {
11695 plural = this.label[this.labelVariable].before.strings.plural;
11696 }
11697 txt = this._buildLabel(v, plural, "before", this.labelVariable);
11698 this.state.output.openLevel("empty");
11699 this.state.output.append(txt, this.label[this.labelVariable].before, true);
11700 this.state.output.append(blob, "literal", true);
11701 this.state.output.closeLevel("empty");
11702 blob = this.state.output.pop();
11703 } else if (this.label[this.labelVariable].after) {
11704 if ("number" === typeof this.label[this.labelVariable].after.strings.plural) {
11705 plural = this.label[this.labelVariable].after.strings.plural;
11706 }
11707 txt = this._buildLabel(v, plural, "after", this.labelVariable);
11708 this.state.output.openLevel("empty");
11709 this.state.output.append(blob, "literal", true);
11710 this.state.output.append(txt, this.label[this.labelVariable].after, true);
11711 this.state.tmp.label_blob = this.state.output.pop();
11712 this.state.output.append(this.state.tmp.label_blob,"literal",true);
11713 this.state.output.closeLevel("empty");
11714 blob = this.state.output.pop();
11715 }
11716 return blob;
11717};
11718
11719CSL.NameOutput.prototype._buildLabel = function (term, plural, position, v) {
11720 if (this.common_term) {
11721 term = this.common_term;
11722 }
11723
11724 var ret = false;
11725 var node = this.label[v][position];
11726 if (node) {
11727 ret = CSL.castLabel(this.state, node, term, plural, CSL.TOLERANT);
11728 }
11729 return ret;
11730};
11731
11732
11733CSL.NameOutput.prototype._collapseAuthor = function () {
11734 var myqueue, mystr, oldchars;
11735 // collapse can be undefined, an array of length zero, and probably
11736 // other things ... ugh.
11737 if (this.state.tmp.name_node.top.blobs.length === 0) {
11738 return;
11739 }
11740 if (this.nameset_base === 0 && this.Item[this.variables[0]] && !this._first_creator_variable) {
11741 this._first_creator_variable = this.variables[0];
11742 }
11743 if ((this.state[this.state.tmp.area].opt.collapse
11744 && this.state[this.state.tmp.area].opt.collapse.length)
11745 || (this.state[this.state.tmp.area].opt.cite_group_delimiter
11746 && this.state[this.state.tmp.area].opt.cite_group_delimiter.length)) {
11747
11748 if (this.state.tmp.authorstring_request) {
11749 // Avoid running this on every call to getAmbiguousCite()?
11750 mystr = "";
11751 myqueue = this.state.tmp.name_node.top.blobs.slice(-1)[0].blobs;
11752 oldchars = this.state.tmp.offset_characters;
11753 if (myqueue) {
11754 mystr = this.state.output.string(this.state, myqueue, false);
11755 }
11756 // Avoid side-effects on character counting: we're only interested
11757 // in the final rendering.
11758 this.state.tmp.offset_characters = oldchars;
11759 this.state.registry.authorstrings[this.Item.id] = mystr;
11760 } else if (!this.state.tmp.just_looking
11761 && !this.state.tmp.suppress_decorations && ((this.state[this.state.tmp.area].opt.collapse && this.state[this.state.tmp.area].opt.collapse.length) || this.state[this.state.tmp.area].opt.cite_group_delimiter && this.state[this.state.tmp.area].opt.cite_group_delimiter)) {
11762 // XX1 print("RENDER: "+this.Item.id);
11763 mystr = "";
11764 myqueue = this.state.tmp.name_node.top.blobs.slice(-1)[0].blobs;
11765 oldchars = this.state.tmp.offset_characters;
11766 if (myqueue) {
11767 mystr = this.state.output.string(this.state, myqueue, false);
11768 }
11769 if (mystr === this.state.tmp.last_primary_names_string) {
11770 if (this.item["suppress-author"] || (this.state[this.state.tmp.area].opt.collapse && this.state[this.state.tmp.area].opt.collapse.length)) {
11771 // XX1 print(" CUT!");
11772 this.state.tmp.name_node.top.blobs.pop();
11773 this.state.tmp.name_node.children = [];
11774 // If popped, avoid side-effects on character counting: we're only interested
11775 // in things that actually render.
11776 this.state.tmp.offset_characters = oldchars;
11777 }
11778 // Needed
11779 if (this.state[this.state.tmp.area].opt.cite_group_delimiter && this.state[this.state.tmp.area].opt.cite_group_delimiter) {
11780 this.state.tmp.use_cite_group_delimiter = true;
11781 }
11782 } else {
11783 // XX1 print("remembering: "+mystr);
11784 this.state.tmp.last_primary_names_string = mystr;
11785 // XXXXX A little more precision would be nice.
11786 // This will clobber variable="author editor" as well as variable="author".
11787
11788 if (this.variables.indexOf(this._first_creator_variable) > -1 && this.item && this.item["suppress-author"] && this.Item.type !== "legal_case") {
11789 this.state.tmp.name_node.top.blobs.pop();
11790 this.state.tmp.name_node.children = [];
11791 // If popped, avoid side-effects on character counting: we're only interested
11792 // in things that actually render.
11793 this.state.tmp.offset_characters = oldchars;
11794
11795 // A wild guess, but will usually be correct
11796 this.state.tmp.term_predecessor = false;
11797 }
11798 // Arcane and probably unnecessarily complicated?
11799 this.state.tmp.have_collapsed = false;
11800 // Needed
11801 if (this.state[this.state.tmp.area].opt.cite_group_delimiter && this.state[this.state.tmp.area].opt.cite_group_delimiter) {
11802 this.state.tmp.use_cite_group_delimiter = false;
11803 }
11804 }
11805 }
11806 }
11807};
11808
11809/*
11810CSL.NameOutput.prototype.suppressNames = function() {
11811 suppress_condition = suppress_min && display_names.length >= suppress_min;
11812 if (suppress_condition) {
11813 continue;
11814 }
11815}
11816*/
11817
11818/*global CSL: true */
11819
11820CSL.NameOutput.prototype.isPerson = function (value) {
11821 if (value.literal
11822 || (!value.given && value.family && value.isInstitution)) {
11823
11824 return false;
11825 } else {
11826 return true;
11827 }
11828};
11829
11830/*global CSL: true */
11831
11832CSL.NameOutput.prototype.truncatePersonalNameLists = function () {
11833 var v, i, ilen, j, jlen, chopvar, values;
11834 // XXX Before truncation, make a note of the original number
11835 // of names, for use in et-al evaluation.
11836 this.freeters_count = {};
11837 this.persons_count = {};
11838 this.institutions_count = {};
11839 // By key is okay here, as we don't care about sequence.
11840 for (v in this.freeters) {
11841 if (this.freeters.hasOwnProperty(v)) {
11842 this.freeters_count[v] = this.freeters[v].length;
11843 this.freeters[v] = this._truncateNameList(this.freeters, v);
11844 }
11845 }
11846
11847 for (v in this.persons) {
11848 if (this.persons.hasOwnProperty(v)) {
11849 this.institutions_count[v] = this.institutions[v].length;
11850 this._truncateNameList(this.institutions, v);
11851 this.persons[v] = this.persons[v].slice(0, this.institutions[v].length);
11852 this.persons_count[v] = [];
11853 for (j = 0, jlen = this.persons[v].length; j < jlen; j += 1) {
11854 this.persons_count[v][j] = this.persons[v][j].length;
11855 this.persons[v][j] = this._truncateNameList(this.persons, v, j);
11856 }
11857 }
11858 }
11859 // Could be factored out to a separate function for clarity.
11860 if (this.etal_min === 1 && this.etal_use_first === 1
11861 && !(this.state.tmp.extension
11862 || this.state.tmp.just_looking)) {
11863 chopvar = v;
11864 } else {
11865 chopvar = false;
11866 }
11867 if (chopvar || this._please_chop) {
11868 for (i = 0, ilen = this.variables.length; i < ilen; i += 1) {
11869 v = this.variables[i];
11870 if (this.freeters[v].length) {
11871 if (this._please_chop === v) {
11872 this.freeters[v] = this.freeters[v].slice(1);
11873 this.freeters_count[v] += -1;
11874 this._please_chop = false;
11875 } else if (chopvar && !this._please_chop) {
11876 this.freeters[v] = this.freeters[v].slice(0, 1);
11877 this.freeters_count[v] = 1;
11878 this.institutions[v] = [];
11879 this.persons[v] = [];
11880 this._please_chop = chopvar;
11881 }
11882 }
11883 for (var j=0,jlen = this.persons[v].length;j<jlen;j++) {
11884 if (this.persons[v][j].length) {
11885 if (this._please_chop === v) {
11886 this.persons[v][j] = this.persons[v][j].slice(1);
11887 this.persons_count[v][j] += -1;
11888 this._please_chop = false;
11889 break;
11890 } else if (chopvar && !this._please_chop) {
11891 this.freeters[v] = this.persons[v][j].slice(0, 1);
11892 this.freeters_count[v] = 1;
11893 this.institutions[v] = [];
11894 this.persons[v] = [];
11895 values = [];
11896 this._please_chop = chopvar;
11897 break;
11898 }
11899 }
11900 }
11901 if (this.institutions[v].length) {
11902 if (this._please_chop === v) {
11903 this.institutions[v] = this.institutions[v].slice(1);
11904 this.institutions_count[v] += -1;
11905 this._please_chop = false;
11906 } else if (chopvar && !this._please_chop) {
11907 this.institutions[v] = this.institutions[v].slice(0, 1);
11908 this.institutions_count[v] = 1;
11909 values = [];
11910 this._please_chop = chopvar;
11911 }
11912 }
11913 }
11914 }
11915
11916 // Transliteration and abbreviation mapping
11917
11918 // Hmm. This could produce three lists for each nameset:
11919 // - primary (transformed in place)
11920 // - secondary
11921 // - tertiary
11922 // with items that produce no result in the secondary and tertiary
11923 // transforms set to false. Maybe.
11924
11925 // Actually that would be insane, so forget it.
11926 // What we need is to add suitable parameters to getName(), and merge
11927 // the single-name-level operations below into that function. Then the
11928 // operation can be applied in util_names_render.js, and the logic
11929 // becomes very similar to what we already have running in util_transform.js.
11930
11931/*
11932 for (v in this.freeters) {
11933 this._transformNameset(this.freeters[v]);
11934 }
11935 for (v in this.persons) {
11936 for (i = 0, ilen = this.persons[v].length; i < ilen; i += 1) {
11937 this._transformNameset(this.persons[v][i]);
11938 }
11939 this._transformNameset(this.institutions[v]);
11940 }
11941*/
11942
11943 // Could also be factored out to a separate function for clarity.
11944 // ???? XXX Does this belong?
11945 for (i = 0, ilen = this.variables.length; i < ilen; i += 1) {
11946 if (this.institutions[v].length) {
11947 this.nameset_offset += 1;
11948 }
11949 for (var j=0,jlen=this.persons[v].length;j<jlen;j++) {
11950 if (this.persons[v][j].length) {
11951 this.nameset_offset += 1;
11952 }
11953 // this.institutions[v][i] = this._splitInstitution(this.institutions[v][i], v, i);
11954 }
11955 }
11956};
11957
11958CSL.NameOutput.prototype._truncateNameList = function (container, variable, index) {
11959 var lst;
11960 if ("undefined" === typeof index) {
11961 lst = container[variable];
11962 } else {
11963 lst = container[variable][index];
11964 }
11965 if (this.state[this.state[this.state.tmp.area].root].opt.max_number_of_names
11966 && lst.length > 50
11967 && lst.length > (this.state[this.state[this.state.tmp.area].root].opt.max_number_of_names + 2)) {
11968
11969 // Preserve the last name in the list, in case we're rendering with a PI ellipsis (et-al-use-last)
11970 var limit = this.state[this.state[this.state.tmp.area].root].opt.max_number_of_names;
11971 lst = lst.slice(0, limit+1).concat(lst.slice(-1));
11972 }
11973 return lst;
11974};
11975
11976
11977/*global CSL: true */
11978
11979CSL.NameOutput.prototype.divideAndTransliterateNames = function () {
11980 var i, ilen, j, jlen;
11981 var Item = this.Item;
11982 var variables = this.variables;
11983 this.varnames = variables.slice();
11984 this.freeters = {};
11985 this.persons = {};
11986 this.institutions = {};
11987 for (i = 0, ilen = variables.length; i < ilen; i += 1) {
11988 var v = variables[i];
11989 this.variable_offset[v] = this.nameset_offset;
11990 var values = this._normalizeVariableValue(Item, v);
11991 if (this.name.strings["suppress-min"] && values.length >= this.name.strings["suppress-min"]) {
11992 values = [];
11993 }
11994 if (this.name.strings["suppress-max"] && values.length <= this.name.strings["suppress-max"]) {
11995 values = [];
11996 }
11997 this._getFreeters(v, values);
11998 this._getPersonsAndInstitutions(v, values);
11999 if (this.state.opt.development_extensions.spoof_institutional_affiliations) {
12000 if (this.name.strings["suppress-min"] === 0) {
12001 this.freeters[v] = [];
12002 for (j = 0, jlen = this.persons[v].length; j < jlen; j += 1) {
12003 this.persons[v][j] = [];
12004 }
12005 } else if (this.institution.strings["suppress-min"] === 0) {
12006 this.institutions[v] = [];
12007 this.freeters[v] = this.freeters[v].concat(this.persons[v]);
12008 for (j = 0, jlen = this.persons[v].length; j < jlen; j += 1) {
12009 for (var k = 0, klen = this.persons[v][j].length; k < klen; k += 1) {
12010 this.freeters[v].push(this.persons[v][j][k]);
12011 }
12012 }
12013 this.persons[v] = [];
12014 }
12015 }
12016 }
12017};
12018
12019CSL.NameOutput.prototype._normalizeVariableValue = function (Item, variable) {
12020 var names;
12021 if ("string" === typeof Item[variable] || "number" === typeof Item[variable]) {
12022 CSL.debug("name variable \"" + variable + "\" is string or number, not array. Attempting to fix.");
12023 names = [{literal: Item[variable] + ""}];
12024 } else if (!Item[variable]) {
12025 names = [];
12026 } else if ("number" !== typeof Item[variable].length) {
12027 CSL.debug("name variable \"" + variable + "\" is object, not array. Attempting to fix.");
12028 Item[variable] = [Item[variable]];
12029 names = Item[variable].slice();
12030 } else {
12031 names = Item[variable].slice();
12032 }
12033 return names;
12034};
12035
12036CSL.NameOutput.prototype._getFreeters = function (v, values) {
12037 this.freeters[v] = [];
12038 if (this.state.opt.development_extensions.spoof_institutional_affiliations) {
12039 for (var i=values.length-1;i>-1;i--) {
12040 if (this.isPerson(values[i])) {
12041 var value = this._checkNickname(values.pop());
12042 if (value) {
12043 this.freeters[v].push(value);
12044 }
12045 } else {
12046 break;
12047 }
12048 }
12049 } else {
12050 for (var i=values.length-1;i>-1;i--) {
12051 var value = values.pop();
12052 if (this.isPerson(value)) {
12053 var value = this._checkNickname(value);
12054 }
12055 this.freeters[v].push(value);
12056 }
12057 }
12058 this.freeters[v].reverse();
12059 if (this.freeters[v].length) {
12060 this.nameset_offset += 1;
12061 }
12062};
12063
12064CSL.NameOutput.prototype._getPersonsAndInstitutions = function (v, values) {
12065 this.persons[v] = [];
12066 this.institutions[v] = [];
12067 if (!this.state.opt.development_extensions.spoof_institutional_affiliations) {
12068 return;
12069 }
12070 var persons = [];
12071 var has_affiliates = false;
12072 var first = true;
12073 for (var i = values.length - 1; i > -1; i += -1) {
12074 if (this.isPerson(values[i])) {
12075 var value = this._checkNickname(values[i]);
12076 if (value) {
12077 persons.push(value);
12078 }
12079 } else {
12080 has_affiliates = true;
12081 this.institutions[v].push(values[i]);
12082 if (!first) {
12083 persons.reverse();
12084 this.persons[v].push(persons);
12085 persons = [];
12086 }
12087 first = false;
12088 }
12089 }
12090 if (has_affiliates) {
12091 persons.reverse();
12092 this.persons[v].push(persons);
12093 this.persons[v].reverse();
12094 this.institutions[v].reverse();
12095 }
12096};
12097
12098CSL.NameOutput.prototype._clearValues = function (values) {
12099 for (var i = values.length - 1; i > -1; i += -1) {
12100 values.pop();
12101 }
12102};
12103
12104CSL.NameOutput.prototype._checkNickname = function (name) {
12105 if (["interview", "personal_communication"].indexOf(this.Item.type) > -1) {
12106 var author = "";
12107 author = CSL.Util.Names.getRawName(name);
12108 if (author && this.state.sys.getAbbreviation && !(this.item && this.item["suppress-author"])) {
12109 var normalizedKey = author;
12110 if (this.state.sys.normalizeAbbrevsKey) {
12111 // The first argument does not have to be the exact variable name.
12112 normalizedKey = this.state.sys.normalizeAbbrevsKey("author", author);
12113 }
12114 this.state.transform.loadAbbreviation("default", "nickname", normalizedKey);
12115 // XXX Why does this have to happen here?
12116 var myLocalName = this.state.transform.abbrevs["default"].nickname[normalizedKey];
12117 if (myLocalName) {
12118 if (myLocalName === "!here>>>") {
12119 name = false;
12120 } else {
12121 name = {family:myLocalName,given:''};
12122 }
12123 }
12124 }
12125 }
12126 return name;
12127};
12128
12129/*global CSL: true */
12130
12131CSL.NameOutput.prototype.joinPersons = function (blobs, pos, j, tokenname) {
12132 var ret;
12133 if (!tokenname) {
12134 tokenname = "name";
12135 }
12136 if ("undefined" === typeof j) {
12137 if (this.etal_spec[pos].freeters === 1) {
12138 ret = this._joinEtAl(blobs, tokenname);
12139 } else if (this.etal_spec[pos].freeters === 2) {
12140 ret = this._joinEllipsis(blobs, tokenname);
12141 } else if (!this.state.tmp.sort_key_flag) {
12142 ret = this._joinAnd(blobs, tokenname);
12143 } else {
12144 ret = this._join(blobs, " ");
12145 }
12146 } else {
12147 if (this.etal_spec[pos].persons[j] === 1) {
12148 ret = this._joinEtAl(blobs, tokenname);
12149 } else if (this.etal_spec[pos].persons[j] === 2) {
12150 ret = this._joinEllipsis(blobs, tokenname);
12151 } else if (!this.state.tmp.sort_key_flag) {
12152 ret = this._joinAnd(blobs, tokenname);
12153 } else {
12154 ret = this._join(blobs, " ");
12155 }
12156 }
12157 return ret;
12158};
12159
12160
12161CSL.NameOutput.prototype.joinInstitutionSets = function (blobs, pos) {
12162 var ret;
12163 if (this.etal_spec[pos].institutions === 1) {
12164 ret = this._joinEtAl(blobs, "institution");
12165 } else if (this.etal_spec[pos].institutions === 2) {
12166 ret = this._joinEllipsis(blobs, "institution");
12167 } else {
12168 ret = this._joinAnd(blobs, "institution");
12169 }
12170 return ret;
12171};
12172
12173
12174CSL.NameOutput.prototype.joinPersonsAndInstitutions = function (blobs) {
12175 //
12176 return this._join(blobs, this.state.tmp.name_delimiter);
12177};
12178
12179// LEGACY
12180// This should go away eventually
12181CSL.NameOutput.prototype.joinFreetersAndInstitutionSets = function (blobs) {
12182 // Nothing, one or two, never more
12183 var ret = this._join(blobs, "[never here]", this["with"].single, this["with"].multiple);
12184 //var ret = this._join(blobs, "");
12185 return ret;
12186};
12187
12188CSL.NameOutput.prototype._joinEtAl = function (blobs, tokenname) {
12189 //
12190 var blob = this._join(blobs, this.state.tmp.name_delimiter);
12191
12192 // notSerious
12193 this.state.output.openLevel(this._getToken(tokenname));
12194 // Delimiter is applied from separately saved source in this case,
12195 // for discriminate application of single and multiple joins.
12196 this.state.output.current.value().strings.delimiter = "";
12197 this.state.output.append(blob, "literal", true);
12198 if (blobs.length > 1) {
12199 this.state.output.append(this["et-al"].multiple, "literal", true);
12200 } else if (blobs.length === 1) {
12201 this.state.output.append(this["et-al"].single, "literal", true);
12202 }
12203 this.state.output.closeLevel();
12204 return this.state.output.pop();
12205};
12206
12207
12208CSL.NameOutput.prototype._joinEllipsis = function (blobs, tokenname) {
12209 return this._join(blobs, this.state.tmp.name_delimiter, this.name.ellipsis.single, this.name.ellipsis.multiple, tokenname);
12210};
12211
12212
12213CSL.NameOutput.prototype._joinAnd = function (blobs, tokenname) {
12214 return this._join(blobs, this.state.inheritOpt(this[tokenname], "delimiter", (tokenname + "-delimiter"), ", "), this[tokenname].and.single, this[tokenname].and.multiple, tokenname);
12215};
12216
12217
12218CSL.NameOutput.prototype._join = function (blobs, delimiter, single, multiple) {
12219 var i, ilen;
12220 if (!blobs) {
12221 return false;
12222 }
12223 // Eliminate false and empty blobs
12224 for (i = blobs.length - 1; i > -1; i += -1) {
12225 if (!blobs[i] || blobs[i].length === 0 || !blobs[i].blobs.length) {
12226 blobs = blobs.slice(0, i).concat(blobs.slice(i + 1));
12227 }
12228 }
12229 // XXXX This needs some attention before moving further.
12230 // Code is not sufficiently transparent.
12231 if (!blobs.length) {
12232 return false;
12233 } else if (single && blobs.length === 2) {
12234 // Clone to avoid corruption of style by affix migration during output
12235 if (single) {
12236 single = new CSL.Blob(single.blobs,single);
12237 }
12238 blobs = [blobs[0], single, blobs[1]];
12239 } else {
12240 var delimiter_offset;
12241 if (multiple) {
12242 delimiter_offset = 2;
12243 } else {
12244 delimiter_offset = 1;
12245 }
12246 // It kind of makes sense down to here.
12247 for (i = 0, ilen = blobs.length - delimiter_offset; i < ilen; i += 1) {
12248 blobs[i].strings.suffix += delimiter;
12249 }
12250 if (blobs.length > 1) {
12251 var blob = blobs.pop();
12252 if (multiple) {
12253 // Clone to avoid corruption of style by affix migration during output
12254 multiple = new CSL.Blob(multiple.blobs,multiple);
12255 blobs.push(multiple);
12256 } else {
12257 // Clone to avoid corruption of style by affix migration during output
12258 if (single) {
12259 single = new CSL.Blob(single.blobs,single);
12260 }
12261 blobs.push(single);
12262 }
12263 blobs.push(blob);
12264 }
12265 }
12266
12267 //this.state.output.openLevel(this._getToken(tokenname));
12268 this.state.output.openLevel();
12269
12270 //this.state.output.openLevel(this._getToken("empty"));
12271 // Delimiter is applied from separately saved source in this case,
12272 // for discriminate application of single and multiple joins.
12273 if (single && multiple) {
12274 this.state.output.current.value().strings.delimiter = "";
12275 }
12276 for (i = 0, ilen = blobs.length; i < ilen; i += 1) {
12277 this.state.output.append(blobs[i], false, true);
12278 }
12279 this.state.output.closeLevel();
12280 return this.state.output.pop();
12281};
12282
12283
12284CSL.NameOutput.prototype._getToken = function (tokenname) {
12285 var token = this[tokenname];
12286 if (tokenname === "institution") {
12287 var newtoken = new CSL.Token();
12288 // Which, hmm, is the same thing as "empty"
12289 // Oh, well.
12290 //newtoken.strings.prefix = token.prefix;
12291 //newtoken.strings.suffix = token.suffix;
12292 return newtoken;
12293 }
12294 return token;
12295};
12296
12297/*global CSL: true */
12298
12299CSL.NameOutput.prototype.checkCommonAuthor = function(requireMatch) {
12300 if (!requireMatch) {
12301 return false;
12302 }
12303 var common_term = false;
12304 if (this.variables.length === 2) {
12305 var variables = this.variables;
12306 var varnames = variables.slice();
12307 varnames.sort();
12308 common_term = varnames.join("");
12309 }
12310 if (!common_term) {
12311 return false;
12312 }
12313 var has_term = false;
12314 if (this.state.locale[this.state.opt.lang].terms[common_term]) {
12315 has_term = true;
12316 }
12317 if (!has_term) {
12318 this.state.tmp.done_vars.push(this.variables[0]);
12319 this.state.tmp.done_vars.push(this.variables[1]);
12320 return false;
12321 }
12322 var firstSet = this.Item[this.variables[0]];
12323 var secondSet = this.Item[this.variables[1]];
12324 var perfectMatch = this._compareNamesets(firstSet, secondSet);
12325 if (perfectMatch === true) {
12326 this.state.tmp.done_vars.push(this.variables[0]);
12327 this.state.tmp.done_vars.push(this.variables[1]);
12328 }
12329 // This may be counter-intuitive.
12330 // This check controls whether we will fail on the this attempt at rendering
12331 // and proceed with substitution. If the names match exactly (true), then
12332 // we do *not* want to abort and continue with substitution.
12333 return !perfectMatch;
12334};
12335
12336CSL.NameOutput.prototype.setCommonTerm = function () {
12337 var variables = this.variables;
12338 var varnames = variables.slice();
12339 varnames.sort();
12340 this.common_term = varnames.join("");
12341 // When no varnames are on offer
12342 if (!this.common_term) {
12343 return;
12344 }
12345 var has_term = false;
12346 if (this.label && this.label[this.variables[0]]) {
12347 if (this.label[this.variables[0]].before) {
12348 has_term = this.state.getTerm(this.common_term, this.label[this.variables[0]].before.strings.form, 0);
12349 } else if (this.label[this.variables[0]].after) {
12350 has_term = this.state.getTerm(this.common_term, this.label[this.variables[0]].after.strings.form, 0);
12351 }
12352 }
12353
12354 // When there is no common term
12355 if (!this.state.locale[this.state.opt.lang].terms[this.common_term]
12356 || !has_term
12357 || this.variables.length < 2) {
12358 this.common_term = false;
12359 return;
12360 }
12361 var freeters_offset = 0;
12362 for (var i = 0, ilen = this.variables.length - 1; i < ilen; i += 1) {
12363 var v = this.variables[i];
12364 var vv = this.variables[i + 1];
12365 if (this.freeters[v].length || this.freeters[vv].length) {
12366 if (this.etal_spec[v].freeters !== this.etal_spec[vv].freeters
12367 || !this._compareNamesets(this.freeters[v], this.freeters[vv])) {
12368 this.common_term = false;
12369 return;
12370 }
12371 freeters_offset += 1;
12372 }
12373 if (this.persons[v].length !== this.persons[vv].length) {
12374 this.common_term = false;
12375 return;
12376 }
12377 for (var j = 0, jlen = this.persons[v].length; j < jlen; j += 1) {
12378 if (this.etal_spec[v].persons[j] !== this.etal_spec[vv].persons[j]
12379 || !this._compareNamesets(this.persons[v][j], this.persons[vv][j])) {
12380 this.common_term = false;
12381 return;
12382 }
12383 }
12384 }
12385};
12386
12387CSL.NameOutput.prototype._compareNamesets = function (base_nameset, nameset) {
12388 if (!base_nameset || !nameset || base_nameset.length !== nameset.length) {
12389 return false;
12390 }
12391 for (var i = 0, ilen = nameset.length; i < ilen; i += 1) {
12392 for (var j = 0, jlen = CSL.NAME_PARTS.length; j < jlen; j += 1) {
12393 var part = CSL.NAME_PARTS[j];
12394 if (!base_nameset[i] || base_nameset[i][part] != nameset[i][part]) {
12395 return false;
12396 }
12397 }
12398 }
12399 return true;
12400};
12401
12402/*global CSL: true */
12403
12404CSL.NameOutput.prototype.constrainNames = function () {
12405 // figure out how many names to include, in light of the disambig params
12406 //
12407 this.names_count = 0;
12408 //var pos = 0;
12409 var pos;
12410 for (var i = 0, ilen = this.variables.length; i < ilen; i += 1) {
12411 var v = this.variables[i];
12412 pos = this.nameset_base + i;
12413 // Constrain independent authors here
12414 if (this.freeters[v].length) {
12415 this.state.tmp.names_max.push(this.freeters[v].length, "literal");
12416 this._imposeNameConstraints(this.freeters, this.freeters_count, v, pos);
12417 this.names_count += this.freeters[v].length;
12418 }
12419
12420 // Constrain institutions here
12421 if (this.institutions[v].length) {
12422 this.state.tmp.names_max.push(this.institutions[v].length, "literal");
12423 this._imposeNameConstraints(this.institutions, this.institutions_count, v, pos);
12424 this.persons[v] = this.persons[v].slice(0, this.institutions[v].length);
12425 this.names_count += this.institutions[v].length;
12426 }
12427
12428 for (var j = 0, jlen = this.persons[v].length; j < jlen; j += 1) {
12429 // Constrain affiliated authors here
12430 if (this.persons[v][j].length) {
12431 this.state.tmp.names_max.push(this.persons[v][j].length, "literal");
12432 this._imposeNameConstraints(this.persons[v], this.persons_count[v], j, pos);
12433 this.names_count += this.persons[v][j].length;
12434 }
12435 }
12436 }
12437};
12438
12439CSL.NameOutput.prototype._imposeNameConstraints = function (lst, count, key, pos) {
12440 // display_names starts as the original length of this list of names.
12441 var display_names = lst[key];
12442 var discretionary_names_length = this.state.tmp["et-al-min"];
12443
12444 // Mappings, to allow existing disambiguation machinery to
12445 // remain untouched.
12446 if (this.state.tmp.suppress_decorations) {
12447 if (this.state.tmp.disambig_request && this.state.tmp.disambig_request.names[pos]) {
12448 // Oh. Trouble.
12449 // state.tmp.nameset_counter is the number of the nameset
12450 // in the disambiguation try-sequence. Ouch.
12451 discretionary_names_length = this.state.tmp.disambig_request.names[pos];
12452 } else if (count[key] >= this.etal_min) {
12453 discretionary_names_length = this.etal_use_first;
12454 }
12455 } else {
12456 if (this.state.tmp.disambig_request
12457 && this.state.tmp.disambig_request.names[pos] > this.etal_use_first) {
12458
12459 if (count[key] < this.etal_min) {
12460 discretionary_names_length = count[key];
12461 } else {
12462 discretionary_names_length = this.state.tmp.disambig_request.names[pos];
12463 }
12464 } else if (count[key] >= this.etal_min) {
12465 //discretionary_names_length = this.state.tmp["et-al-use-first"];
12466 discretionary_names_length = this.etal_use_first;
12467 }
12468 // XXXX: This is a workaround. Under some conditions.
12469 // Where namesets disambiguate on one of the two names
12470 // dropped here, it is possible for more than one
12471 // in-text citation to be close (and indistinguishable)
12472 // matches to a single bibliography entry.
12473 //
12474 //
12475 if (this.etal_use_last && discretionary_names_length > (this.etal_min - 2)) {
12476 discretionary_names_length = this.etal_min - 2;
12477 }
12478 }
12479 var sane = this.etal_min >= this.etal_use_first;
12480 var overlength = count[key] > discretionary_names_length;
12481 // This var is used to control contextual join, and
12482 // lies about the number of names when forceEtAl is true,
12483 // unless normalized.
12484 if (discretionary_names_length > count[key]) {
12485
12486 // Use actual truncated list length, to avoid overrun.
12487 discretionary_names_length = display_names.length;
12488 }
12489 // forceEtAl is relevant when the author list is
12490 // truncated to eliminate clutter.
12491 if (sane && overlength) {
12492 if (this.etal_use_last) {
12493 lst[key] = display_names.slice(0, discretionary_names_length).concat(display_names.slice(-1));
12494 } else {
12495 lst[key] = display_names.slice(0, discretionary_names_length);
12496 }
12497 }
12498 this.state.tmp.disambig_settings.names[pos] = lst[key].length;
12499 this.state.disambiguate.padBase(this.state.tmp.disambig_settings);
12500
12501
12502 // ???
12503 //if (!this.state.tmp.disambig_request) {
12504 // this.state.tmp.disambig_settings.givens[pos] = [];
12505 //}
12506};
12507
12508// Disambiguate names (the number of names is controlled externally, by successive
12509// runs of the processor).
12510
12511/*global CSL: true */
12512
12513CSL.NameOutput.prototype.disambigNames = function () {
12514 var pos;
12515 for (var i = 0, ilen = this.variables.length; i < ilen; i += 1) {
12516 var v = this.variables[i];
12517 pos = this.nameset_base + i;
12518 if (this.freeters[v].length) {
12519 this._runDisambigNames(this.freeters[v], pos);
12520 }
12521 // Is this even necessary???
12522 if (this.institutions[v].length) {
12523 if ("undefined" === typeof this.state.tmp.disambig_settings.givens[pos]) {
12524 this.state.tmp.disambig_settings.givens[pos] = [];
12525 }
12526 for (var j=0,jlen=this.institutions[v].length;j<jlen;j+=1) {
12527 if ("undefined" === typeof this.state.tmp.disambig_settings.givens[pos][j]) {
12528 this.state.tmp.disambig_settings.givens[pos].push(2);
12529 }
12530 }
12531 }
12532 for (var j = 0, jlen = this.persons[v].length; j < jlen; j += 1) {
12533 if (this.persons[v][j].length) {
12534 this._runDisambigNames(this.persons[v][j], pos);
12535 }
12536 }
12537 }
12538};
12539
12540CSL.NameOutput.prototype._runDisambigNames = function (lst, pos) {
12541 var chk, myform, myinitials, param, i, ilen, paramx;
12542 //if (this.state.tmp.root === "bibliography") {
12543 // return;
12544 //}
12545 for (i = 0, ilen = lst.length; i < ilen; i += 1) {
12546 //
12547 // register the name in the global names disambiguation
12548 // registry
12549
12550 if (!lst[i].given && !lst[i].family) {
12551 continue;
12552 }
12553
12554 myinitials = this.state.inheritOpt(this.name, "initialize-with");
12555 this.state.registry.namereg.addname("" + this.Item.id, lst[i], i);
12556 chk = this.state.tmp.disambig_settings.givens[pos];
12557 if ("undefined" === typeof chk) {
12558 // Holes can appear in the list, probably due to institutional
12559 // names that this doesn't touch. Maybe. This fills them up.
12560 for (var j = 0, jlen = pos + 1; j < jlen; j += 1) {
12561 if (!this.state.tmp.disambig_settings.givens[j]) {
12562 this.state.tmp.disambig_settings.givens[j] = [];
12563 }
12564 }
12565 }
12566 chk = this.state.tmp.disambig_settings.givens[pos][i];
12567 //if ("undefined" !== typeof chk && this.state.tmp.root === 'citation') {
12568 //this.state.tmp.disambig_settings.givens[pos] = [];
12569 //chk = undefined;
12570 //}
12571 if ("undefined" === typeof chk) {
12572 myform = this.state.inheritOpt(this.name, "form", "name-form", "long");
12573 param = this.state.registry.namereg.evalname("" + this.Item.id, lst[i], i, 0, myform, myinitials);
12574 this.state.tmp.disambig_settings.givens[pos].push(param);
12575 }
12576 //
12577 // set the display mode default for givennames if required
12578 myform = this.state.inheritOpt(this.name, "form", "name-form", "long");
12579 paramx = this.state.registry.namereg.evalname("" + this.Item.id, lst[i], i, 0, myform, myinitials);
12580 // this.state.registry.namereg.evalname("" + this.Item.id, lst[i], i, 0, myform, myinitials);
12581 if (this.state.tmp.disambig_request) {
12582 //
12583 // fix a request for initials that makes no sense.
12584 // can't do this in disambig, because the availability
12585 // of initials is not a global parameter.
12586 var val = this.state.tmp.disambig_settings.givens[pos][i];
12587 // This is limited to by-cite disambiguation.
12588 // 2012-09-13: added lst[i].given check to condition
12589 if (val === 1 &&
12590 this.state.citation.opt["givenname-disambiguation-rule"] === "by-cite" &&
12591 ("undefined" === typeof this.state.inheritOpt(this.name, "initialize-with")
12592 || "undefined" === typeof lst[i].given)) {
12593 val = 2;
12594 }
12595 param = val;
12596 // 2012-09-13: lst[i].given check protects against personal names
12597 // that have no first name element. These were causing an infinite loop,
12598 // this prevents that.
12599 if (this.state.opt["disambiguate-add-givenname"] && lst[i].given) {
12600 param = this.state.registry.namereg.evalname("" + this.Item.id, lst[i], i, param, this.state.inheritOpt(this.name, "form", "name-form", "long"), this.state.inheritOpt(this.name, "initialize-with"));
12601 }
12602 } else {
12603 //
12604 // it clicks. here is where we will put the
12605 // call to the names register, to get the floor value
12606 // for an individual name.
12607 //
12608 param = paramx;
12609 }
12610 // Need to save off the settings based on subsequent
12611 // form, when first cites are rendered.
12612 if (!this.state.tmp.just_looking && this.item && this.item.position === CSL.POSITION_FIRST) {
12613 if (paramx > param) {
12614 param = paramx;
12615 }
12616 }
12617 if (!this.state.tmp.sort_key_flag) {
12618 this.state.tmp.disambig_settings.givens[pos][i] = param;
12619 if ("string" === typeof myinitials
12620 && ("undefined" === typeof this.name.strings["initialize"]
12621 || true === this.name.strings["initialize"])) {
12622
12623 this.state.tmp.disambig_settings.use_initials = true;
12624 }
12625 }
12626 }
12627 //this.state.registry.registry[this.Item.id].disambig.givens = this.state.tmp.disambig_settings.givens.slice();
12628};
12629
12630/*global CSL: true */
12631
12632CSL.NameOutput.prototype.getEtAlConfig = function () {
12633 var item = this.item;
12634 this["et-al"] = {};
12635
12636 this.state.output.append(this.etal_term, this.etal_style, true);
12637 this["et-al"].single = this.state.output.pop();
12638 this["et-al"].single.strings.suffix = this.etal_suffix;
12639 this["et-al"].single.strings.prefix = this.etal_prefix_single;
12640
12641 this.state.output.append(this.etal_term, this.etal_style, true);
12642 this["et-al"].multiple = this.state.output.pop();
12643 this["et-al"].multiple.strings.suffix = this.etal_suffix;
12644 this["et-al"].multiple.strings.prefix = this.etal_prefix_multiple;
12645
12646 // Et-al style parameters (may be sidestepped by disambiguation
12647 // in util_names_constraints.js)
12648 if ("undefined" === typeof item) {
12649 item = {};
12650 }
12651 //print("== getEtAlConfig() == "+this.state.tmp.area);
12652
12653 if (item.position) {
12654 if (this.state.inheritOpt(this.name, "et-al-subsequent-min")) {
12655 // XX
12656 this.etal_min = this.state.inheritOpt(this.name, "et-al-subsequent-min");
12657 } else {
12658 // XX
12659 this.etal_min = this.state.inheritOpt(this.name, "et-al-min");
12660 }
12661 if (this.state.inheritOpt(this.name, "et-al-subsequent-use-first")) {
12662 // XX
12663 this.etal_use_first = this.state.inheritOpt(this.name, "et-al-subsequent-use-first");
12664 } else {
12665 // XX
12666 this.etal_use_first = this.state.inheritOpt(this.name, "et-al-use-first");
12667 }
12668 } else {
12669 if (this.state.tmp["et-al-min"]) {
12670 this.etal_min = this.state.tmp["et-al-min"];
12671 } else {
12672 // XX
12673 this.etal_min = this.state.inheritOpt(this.name, "et-al-min");
12674 }
12675 if (this.state.tmp["et-al-use-first"]) {
12676 this.etal_use_first = this.state.tmp["et-al-use-first"];
12677 } else {
12678 // XX
12679 this.etal_use_first = this.state.inheritOpt(this.name, "et-al-use-first");
12680 }
12681 if ("boolean" === typeof this.state.tmp["et-al-use-last"]) {
12682 //print(" etal_use_last from tmp: "+this.state.tmp["et-al-use-last"]);
12683 this.etal_use_last = this.state.tmp["et-al-use-last"];
12684 } else {
12685 //print(" etal_use_last from name: "+this.name.strings["et-al-use-last"]);
12686 // XX
12687 this.etal_use_last = this.state.inheritOpt(this.name, "et-al-use-last");
12688 }
12689 //print(" etal_use_last: "+this.etal_use_last);
12690 }
12691 // Provided for use as the starting level for disambiguation.
12692 if (!this.state.tmp["et-al-min"]) {
12693 this.state.tmp["et-al-min"] = this.etal_min;
12694 }
12695};
12696
12697/*global CSL: true */
12698
12699CSL.NameOutput.prototype.setEtAlParameters = function () {
12700 var i, ilen, j, jlen;
12701 for (i = 0, ilen = this.variables.length; i < ilen; i += 1) {
12702 var v = this.variables[i];
12703 if ("undefined" === typeof this.etal_spec[v]) {
12704 this.etal_spec[v] = {freeters:0,institutions:0,persons:[]};
12705 }
12706 this.etal_spec[this.nameset_base + i] = this.etal_spec[v];
12707 if (this.freeters[v].length) {
12708 this._setEtAlParameter("freeters", v);
12709 }
12710 for (j = 0, jlen = this.persons[v].length; j < jlen; j += 1) {
12711 if ("undefined" === typeof this.etal_spec[v][j]) {
12712 this.etal_spec[v].persons[j] = 0;
12713 }
12714 this._setEtAlParameter("persons", v, j);
12715 }
12716 if (this.institutions[v].length) {
12717 this._setEtAlParameter("institutions", v);
12718 }
12719 }
12720};
12721
12722CSL.NameOutput.prototype._setEtAlParameter = function (type, v, j) {
12723 var lst, count;
12724 if (type === "persons") {
12725 lst = this.persons[v][j];
12726 count = this.persons_count[v][j];
12727 } else {
12728 lst = this[type][v];
12729 count = this[type + "_count"][v];
12730 }
12731 if (lst.length < count && !this.state.tmp.sort_key_flag) {
12732 if (this.etal_use_last) {
12733 if (type === "persons") {
12734 this.etal_spec[v].persons[j] = 2;
12735 } else {
12736 this.etal_spec[v][type] = 2;
12737 }
12738 } else {
12739 if (type === "persons") {
12740 this.etal_spec[v].persons[j] = 1;
12741 } else {
12742 this.etal_spec[v][type] = 1;
12743 }
12744 }
12745 } else {
12746 if (type === "persons") {
12747 this.etal_spec[v].persons[j] = 0;
12748 } else {
12749 this.etal_spec[v][type] = 0;
12750 }
12751 }
12752};
12753
12754/*global CSL: true */
12755
12756CSL.NameOutput.prototype.renderAllNames = function () {
12757 // Note that et-al/ellipsis parameters are set on the basis
12758 // of rendering order through the whole cite.
12759 var pos;
12760 for (var i = 0, ilen = this.variables.length; i < ilen; i += 1) {
12761 var v = this.variables[i];
12762
12763 if (this.freeters[v].length || this.institutions[v].length) {
12764 if (!this.state.tmp.group_context.tip.condition) {
12765 this.state.tmp.just_did_number = false;
12766 }
12767 }
12768
12769 pos = this.nameset_base + i;
12770 if (this.freeters[v].length) {
12771 this.freeters[v] = this._renderNames(v, this.freeters[v], pos);
12772 }
12773 for (var j = 0, jlen = this.institutions[v].length; j < jlen; j += 1) {
12774 this.persons[v][j] = this._renderNames(v, this.persons[v][j], pos, j);
12775 }
12776 }
12777 this.renderInstitutionNames();
12778};
12779
12780CSL.NameOutput.prototype.renderInstitutionNames = function () {
12781 // Institutions are split to string list as
12782 // this.institutions[v]["long"] and this.institutions[v]["short"]
12783 for (var i = 0, ilen = this.variables.length; i < ilen; i += 1) {
12784 var v = this.variables[i];
12785 for (var j = 0, jlen = this.institutions[v].length; j < jlen; j += 1) {
12786 var institution;
12787
12788 var name = this.institutions[v][j];
12789
12790
12791
12792 // XXX Start here for institutions
12793 // Figure out the three segments: primary, secondary, tertiary
12794 var j, jlen, localesets;
12795 if (this.state.tmp.extension) {
12796 localesets = ["sort"];
12797 } else if (name.isInstitution || name.literal) {
12798 // Will never hit this in this function, but preserving
12799 // in case we factor this out.
12800 localesets = this.state.opt['cite-lang-prefs'].institutions;
12801 } else {
12802 localesets = this.state.opt['cite-lang-prefs'].persons;
12803 }
12804
12805 var slot = {primary:'locale-orig',secondary:false,tertiary:false};
12806 if (localesets) {
12807 var slotnames = ["primary", "secondary", "tertiary"];
12808 for (var k = 0, klen = slotnames.length; k < klen; k += 1) {
12809 if (localesets.length - 1 < k) {
12810 break;
12811 }
12812 if (localesets[k]) {
12813 slot[slotnames[k]] = 'locale-' + localesets[k];
12814 }
12815 }
12816 } else {
12817 slot.primary = 'locale-translat';
12818 }
12819 if (this.state.tmp.area !== "bibliography"
12820 && !(this.state.tmp.area === "citation"
12821 && this.state.opt.xclass === "note"
12822 && this.item && !this.item.position)) {
12823
12824 slot.secondary = false;
12825 slot.tertiary = false;
12826 }
12827 // Get normalized name object for a start.
12828 // true invokes fallback
12829 this.setRenderedName(name);
12830
12831 // XXXX FROM HERE (instututions)
12832 var institution = this._renderInstitutionName(v, name, slot, j);
12833 //this.institutions[v][j] = this._join(institution, "");
12834 this.institutions[v][j] = institution;
12835 }
12836 }
12837};
12838
12839CSL.NameOutput.prototype._renderInstitutionName = function (v, name, slot, j) {
12840 var secondary, tertiary, long_style, short_style, institution, institution_short, institution_long;
12841 var res = this.getName(name, slot.primary, true);
12842 var primary = res.name;
12843 var usedOrig = res.usedOrig;
12844 if (primary) {
12845 //print("primary, v, j = "+primary+", "+v+", "+j);
12846 primary = this.fixupInstitution(primary, v, j);
12847 }
12848 secondary = false;
12849 if (slot.secondary) {
12850 res = this.getName(name, slot.secondary, false, usedOrig);
12851 var secondary = res.name;
12852 usedOrig = res.usedOrig;
12853 if (secondary) {
12854 secondary = this.fixupInstitution(secondary, v, j);
12855 }
12856 }
12857 //Zotero.debug("XXX [2] secondary: "+secondary["long"].literal+", slot.secondary: "+slot.secondary);
12858 tertiary = false;
12859 if (slot.tertiary) {
12860 res = this.getName(name, slot.tertiary, false, usedOrig);
12861 tertiary = res.name;
12862 if (tertiary) {
12863 tertiary = this.fixupInstitution(tertiary, v, j);
12864 }
12865 }
12866 var n = {
12867 l: {
12868 pri: false,
12869 sec: false,
12870 ter: false
12871 },
12872 s: {
12873 pri: false,
12874 sec: false,
12875 ter: false
12876 }
12877 };
12878 if (primary) {
12879 n.l.pri = primary["long"];
12880 n.s.pri = primary["short"].length ? primary["short"] : primary["long"];
12881 }
12882 if (secondary) {
12883 n.l.sec = secondary["long"];
12884 n.s.sec = secondary["short"].length ? secondary["short"] : secondary["long"];
12885 }
12886 if (tertiary) {
12887 n.l.ter = tertiary["long"];
12888 n.s.ter = tertiary["short"].length ? tertiary["short"] : tertiary["long"];
12889 }
12890 switch (this.institution.strings["institution-parts"]) {
12891 case "short":
12892 // No multilingual for pure short form institution names.
12893 if (primary["short"].length) {
12894 short_style = this._getShortStyle();
12895 institution = [this._composeOneInstitutionPart([n.s.pri, n.s.sec, n.s.ter], slot, short_style, v)];
12896 } else {
12897 // Fail over to long.
12898 long_style = this._getLongStyle(primary, v, j);
12899 institution = [this._composeOneInstitutionPart([n.l.pri, n.l.sec, n.l.ter], slot, long_style, v)];
12900 }
12901 break;
12902 case "short-long":
12903 long_style = this._getLongStyle(primary, v, j);
12904 short_style = this._getShortStyle();
12905 institution_short = this._renderOneInstitutionPart(primary["short"], short_style);
12906 // true is to include multilingual supplement
12907 institution_long = this._composeOneInstitutionPart([n.l.pri, n.l.sec, n.l.ter], slot, long_style, v);
12908 institution = [institution_short, institution_long];
12909 break;
12910 case "long-short":
12911 long_style = this._getLongStyle(primary, v, j);
12912 short_style = this._getShortStyle();
12913 institution_short = this._renderOneInstitutionPart(primary["short"], short_style);
12914 // true is to include multilingual supplement
12915 institution_long = this._composeOneInstitutionPart([n.l.pri, n.l.sec, n.l.ter], slot, long_style, v);
12916 institution = [institution_long, institution_short];
12917 break;
12918 default:
12919 long_style = this._getLongStyle(primary, v, j);
12920 // true is to include multilingual supplement
12921 institution = [this._composeOneInstitutionPart([n.l.pri, n.l.sec, n.l.ter], slot, long_style, v)];
12922 break;
12923 }
12924 var blob = this._join(institution, " ");
12925 this.state.tmp.name_node.children.push(blob);
12926 return blob;
12927};
12928
12929CSL.NameOutput.prototype._composeOneInstitutionPart = function (names, slot, style) {
12930 var primary = false, secondary = false, tertiary = false, primary_tok, secondary_tok, tertiary_tok;
12931 if (names[0]) {
12932 primary_tok = CSL.Util.cloneToken(style);
12933 if (this.state.opt.citeAffixes[slot.primary]){
12934 if ("<i>" === this.state.opt.citeAffixes.institutions[slot.primary].prefix) {
12935 var hasItalic = false;
12936 for (var i = 0, ilen = primary_tok.decorations.length; i < ilen; i += 1) {
12937 if (style.decorations[i][0] === "@font-style"
12938 && primary_tok.decorations[i][1] === "italic") {
12939 hasItalic = true;
12940 }
12941 }
12942 if (!hasItalic) {
12943 primary_tok.decorations.push(["@font-style", "italic"]);
12944 }
12945 }
12946 }
12947 primary = this._renderOneInstitutionPart(names[0], primary_tok);
12948 }
12949 if (names[1]) {
12950 secondary = this._renderOneInstitutionPart(names[1], style);
12951 }
12952 if (names[2]) {
12953 tertiary = this._renderOneInstitutionPart(names[2], style);
12954 }
12955 // Compose
12956 var institutionblob;
12957 if (secondary || tertiary) {
12958 this.state.output.openLevel("empty");
12959
12960 this.state.output.append(primary);
12961
12962 secondary_tok = CSL.Util.cloneToken(style);
12963 if (slot.secondary) {
12964 secondary_tok.strings.prefix = this.state.opt.citeAffixes.institutions[slot.secondary].prefix;
12965 secondary_tok.strings.suffix = this.state.opt.citeAffixes.institutions[slot.secondary].suffix;
12966 // Add a space if empty
12967 if (!secondary_tok.strings.prefix) {
12968 secondary_tok.strings.prefix = " ";
12969 }
12970 }
12971 var secondary_outer = new CSL.Token();
12972 secondary_outer.decorations.push(["@font-style", "normal"]);
12973 secondary_outer.decorations.push(["@font-weight", "normal"]);
12974 this.state.output.openLevel(secondary_outer);
12975 this.state.output.append(secondary, secondary_tok);
12976 this.state.output.closeLevel();
12977
12978 tertiary_tok = CSL.Util.cloneToken(style);
12979 if (slot.tertiary) {
12980 tertiary_tok.strings.prefix = this.state.opt.citeAffixes.institutions[slot.tertiary].prefix;
12981 tertiary_tok.strings.suffix = this.state.opt.citeAffixes.institutions[slot.tertiary].suffix;
12982 // Add a space if empty
12983 if (!tertiary_tok.strings.prefix) {
12984 tertiary_tok.strings.prefix = " ";
12985 }
12986 }
12987 var tertiary_outer = new CSL.Token();
12988 tertiary_outer.decorations.push(["@font-style", "normal"]);
12989 tertiary_outer.decorations.push(["@font-weight", "normal"]);
12990 this.state.output.openLevel(tertiary_outer);
12991 this.state.output.append(tertiary, tertiary_tok);
12992 this.state.output.closeLevel();
12993
12994 this.state.output.closeLevel();
12995
12996 institutionblob = this.state.output.pop();
12997 } else {
12998 institutionblob = primary;
12999 }
13000 return institutionblob;
13001};
13002
13003CSL.NameOutput.prototype._renderOneInstitutionPart = function (blobs, style) {
13004 for (var i = 0, ilen = blobs.length; i < ilen; i += 1) {
13005 if (blobs[i]) {
13006 var str = blobs[i];
13007 // XXXXX Cut-and-paste code in multiple locations. This code block should be
13008 // collected in a function.
13009 // Tag: strip-periods-block
13010 if (this.state.tmp.strip_periods) {
13011 str = str.replace(/\./g, "");
13012 } else {
13013 for (var j = 0, jlen = style.decorations.length; j < jlen; j += 1) {
13014 if ("@strip-periods" === style.decorations[j][0] && "true" === style.decorations[j][1]) {
13015 str = str.replace(/\./g, "");
13016 break;
13017 }
13018 }
13019 }
13020 //this.state.output.append(blobs[i], style, true);
13021 this.state.tmp.group_context.tip.variable_success = true;
13022 this.state.tmp.can_substitute.replace(false, CSL.LITERAL);
13023 if (str === "!here>>>") {
13024 blobs[i] = false;
13025 } else {
13026 this.state.output.append(str, style, true);
13027 blobs[i] = this.state.output.pop();
13028 }
13029 }
13030 }
13031 if ("undefined" === typeof this.institution.strings["part-separator"]) {
13032 this.institution.strings["part-separator"] = this.state.tmp.name_delimiter;
13033 }
13034 return this._join(blobs, this.institution.strings["part-separator"]);
13035};
13036
13037CSL.NameOutput.prototype._renderNames = function (v, values, pos, j) {
13038 //
13039 var ret = false;
13040 if (values.length) {
13041 var names = [];
13042 for (var i = 0, ilen = values.length; i < ilen; i += 1) {
13043 var name = values[i];
13044
13045 // XXX We'll start here with attempts.
13046 // Figure out the three segments: primary, secondary, tertiary
13047 var ret, localesets;
13048
13049 if (this.state.tmp.extension) {
13050 localesets = ["sort"];
13051 } else if (name.isInstitution || name.literal) {
13052 // Will never hit this in this function, but preserving
13053 // in case we factor this out.
13054 localesets = this.state.opt['cite-lang-prefs'].institutions;
13055 } else {
13056 localesets = this.state.opt['cite-lang-prefs'].persons;
13057 }
13058 var slot = {primary:'locale-orig',secondary:false,tertiary:false};
13059 if (localesets) {
13060 var slotnames = ["primary", "secondary", "tertiary"];
13061 for (var k = 0, klen = slotnames.length; k < klen; k += 1) {
13062 if (localesets.length - 1 < k) {
13063 break;
13064 }
13065 slot[slotnames[k]] = 'locale-' + localesets[k];
13066 }
13067 } else {
13068 slot.primary = 'locale-translat';
13069 }
13070 if (this.state.tmp.sort_key_flag || (this.state.tmp.area !== "bibliography"
13071 && !(this.state.tmp.area === "citation"
13072 && this.state.opt.xclass === "note"
13073 && this.item && !this.item.position))) {
13074
13075 slot.secondary = false;
13076 slot.tertiary = false;
13077 }
13078
13079 // primary
13080 // true is for fallback
13081 this.setRenderedName(name);
13082
13083 if (!name.literal && !name.isInstitution) {
13084 var nameBlob = this._renderPersonalName(v, name, slot, pos, i, j);
13085 var nameToken = CSL.Util.cloneToken(this.name);
13086 this.state.output.append(nameBlob, nameToken, true);
13087 names.push(this.state.output.pop());
13088 } else {
13089 names.push(this._renderInstitutionName(v, name, slot, j));
13090 }
13091 }
13092 //ret = this._join(names, "");
13093 ret = this.joinPersons(names, pos, j);
13094 }
13095 return ret;
13096};
13097
13098
13099CSL.NameOutput.prototype._renderPersonalName = function (v, name, slot, pos, i, j) {
13100 // XXXX FROM HERE (persons)
13101
13102 var res = this.getName(name, slot.primary, true);
13103 var primary = this._renderOnePersonalName(res.name, pos, i, j);
13104 var secondary = false;
13105 if (slot.secondary) {
13106 res = this.getName(name, slot.secondary, false, res.usedOrig);
13107 if (res.name) {
13108 secondary = this._renderOnePersonalName(res.name, pos, i, j);
13109 }
13110 }
13111 var tertiary = false;
13112 if (slot.tertiary) {
13113 res = this.getName(name, slot.tertiary, false, res.usedOrig);
13114 if (res.name) {
13115 tertiary = this._renderOnePersonalName(res.name, pos, i, j);
13116 }
13117 }
13118 // Now compose them to a unit
13119 var personblob;
13120 if (secondary || tertiary) {
13121
13122 this.state.output.openLevel("empty");
13123
13124 this.state.output.append(primary);
13125
13126 var secondary_tok = new CSL.Token();
13127 if (slot.secondary) {
13128 secondary_tok.strings.prefix = this.state.opt.citeAffixes.persons[slot.secondary].prefix;
13129 secondary_tok.strings.suffix = this.state.opt.citeAffixes.persons[slot.secondary].suffix;
13130 // Add a space if empty
13131 if (!secondary_tok.strings.prefix) {
13132 secondary_tok.strings.prefix = " ";
13133 }
13134 }
13135 this.state.output.append(secondary, secondary_tok);
13136
13137 var tertiary_tok = new CSL.Token();
13138 if (slot.tertiary) {
13139 tertiary_tok.strings.prefix = this.state.opt.citeAffixes.persons[slot.tertiary].prefix;
13140 tertiary_tok.strings.suffix = this.state.opt.citeAffixes.persons[slot.tertiary].suffix;
13141 // Add a space if empty
13142 if (!tertiary_tok.strings.prefix) {
13143 tertiary_tok.strings.prefix = " ";
13144 }
13145 }
13146 this.state.output.append(tertiary, tertiary_tok);
13147
13148 this.state.output.closeLevel();
13149
13150 personblob = this.state.output.pop();
13151 } else {
13152 personblob = primary;
13153 }
13154 return personblob;
13155};
13156
13157CSL.NameOutput.prototype._isRomanesque = function (name) {
13158 // 0 = entirely non-romanesque
13159 // 1 = mixed content
13160 // 2 = pure romanesque
13161 var ret = 2;
13162 if (!name.family.replace(/\"/g, '').match(CSL.ROMANESQUE_REGEXP)) {
13163 ret = 0;
13164 }
13165 if (!ret && name.given && name.given.match(CSL.STARTSWITH_ROMANESQUE_REGEXP)) {
13166 ret = 1;
13167 }
13168 var top_locale;
13169 if (ret == 2) {
13170 if (name.multi && name.multi.main) {
13171 top_locale = name.multi.main.slice(0, 2);
13172 } else if (this.Item.language) {
13173 top_locale = this.Item.language.slice(0, 2);
13174 }
13175 if (["ja", "zh"].indexOf(top_locale) > -1) {
13176 ret = 1;
13177 }
13178 }
13179 //print("name: "+name.given+", multi: "+name.multi+", ret: "+ret);
13180 return ret;
13181};
13182
13183CSL.NameOutput.prototype._renderOnePersonalName = function (value, pos, i, j) {
13184 var name = value;
13185 var dropping_particle = this._droppingParticle(name, pos, j);
13186 var family = this._familyName(name);
13187 var non_dropping_particle = this._nonDroppingParticle(name);
13188 var given = this._givenName(name, pos, i);
13189 var suffix = this._nameSuffix(name);
13190 if (given === false) {
13191 dropping_particle = false;
13192 suffix = false;
13193 }
13194 var sort_sep = this.state.inheritOpt(this.name, "sort-separator");
13195 if (!sort_sep) {
13196 sort_sep = "";
13197 }
13198 var suffix_sep;
13199 if (name["comma-suffix"]) {
13200 suffix_sep = ", ";
13201 } else {
13202 suffix_sep = " ";
13203 }
13204 var romanesque = this._isRomanesque(name);
13205 function hasJoiningPunctuation(blob) {
13206 if (!blob) {
13207 return false;
13208 } else if ("string" === typeof blob.blobs) {
13209 if (["\u2019", "\'", "-", " "].indexOf(blob.blobs.slice(-1)) > -1) {
13210 return true;
13211 } else {
13212 return false;
13213 }
13214 } else {
13215 return hasJoiningPunctuation(blob.blobs[blob.blobs.length-1]);
13216 }
13217 }
13218 var has_hyphenated_non_dropping_particle = hasJoiningPunctuation(non_dropping_particle);
13219
13220 var blob, merged, first, second;
13221 if (romanesque === 0) {
13222 // XXX handle affixes for given and family
13223 blob = this._join([non_dropping_particle, family, given], "");
13224 } else if (romanesque === 1 || name["static-ordering"]) { // entry likes sort order
13225 blob = this._join([non_dropping_particle, family, given], " ");
13226 } else if (name["reverse-ordering"]) { // entry likes reverse order
13227 blob = this._join([given, non_dropping_particle, family], " ");
13228 } else if (this.state.tmp.sort_key_flag) {
13229 // ok with no affixes here
13230 if (this.state.opt["demote-non-dropping-particle"] === "never") {
13231 first = this._join([non_dropping_particle, family, dropping_particle], " ");
13232 merged = this._join([first, given], this.state.opt.sort_sep);
13233 blob = this._join([merged, suffix], " ");
13234 } else {
13235 second = this._join([given, dropping_particle, non_dropping_particle], " ");
13236 merged = this._join([family, second], this.state.opt.sort_sep);
13237 blob = this._join([merged, suffix], " ");
13238 }
13239 } else if (this.state.inheritOpt(this.name, "name-as-sort-order") === "all" || (this.state.inheritOpt(this.name, "name-as-sort-order") === "first" && i === 0 && (j === 0 || "undefined" === typeof j))) {
13240 //
13241 // Discretionary sort ordering and inversions
13242 //
13243 if (["Lord", "Lady"].indexOf(name.given) > -1) {
13244 sort_sep = ", ";
13245 }
13246
13247 // XXX Needs a more robust solution than this
13248 // XXX See https://forums.zotero.org/discussion/30974/any-idea-why-an-a-author-comes-last-in-the-bibliography/#Item_30
13249
13250 //if (["always", "display-and-sort"].indexOf(this.state.opt["demote-non-dropping-particle"]) > -1 && !has_hyphenated_non_dropping_particle) {
13251 if (["always", "display-and-sort"].indexOf(this.state.opt["demote-non-dropping-particle"]) > -1) {
13252 // Drop non-dropping particle
13253 //second = this._join([given, dropping_particle, non_dropping_particle], " ");
13254 second = this._join([given, dropping_particle], (name["comma-dropping-particle"] + " "));
13255
13256 // This would be a problem with al-Ghazali. Avoided by has_hyphenated_non_dropping_particle check above.
13257 second = this._join([second, non_dropping_particle], " ");
13258 if (second && this.given) {
13259 second.strings.prefix = this.given.strings.prefix;
13260 second.strings.suffix = this.given.strings.suffix;
13261 }
13262 if (family && this.family) {
13263 family.strings.prefix = this.family.strings.prefix;
13264 family.strings.suffix = this.family.strings.suffix;
13265 }
13266 merged = this._join([family, second], sort_sep);
13267 blob = this._join([merged, suffix], sort_sep);
13268 } else {
13269 // Don't drop particle.
13270 // Don't do this
13271 //if (this.state.tmp.area === "bibliography" && !this.state.tmp.term_predecessor && non_dropping_particle) {
13272 // if (!has_hyphenated_non_dropping_particle) {
13273 // non_dropping_particle.blobs = CSL.Output.Formatters["capitalize-first"](this.state, non_dropping_particle.blobs)
13274 // }
13275 //}
13276 if (has_hyphenated_non_dropping_particle) {
13277 first = this._join([non_dropping_particle, family], "");
13278 } else {
13279 first = this._join([non_dropping_particle, family], " ");
13280 }
13281 if (first && this.family) {
13282 first.strings.prefix = this.family.strings.prefix;
13283 first.strings.suffix = this.family.strings.suffix;
13284 }
13285
13286 second = this._join([given, dropping_particle], (name["comma-dropping-particle"] + " "));
13287 //second = this._join([given, dropping_particle], " ");
13288 if (second && this.given) {
13289 second.strings.prefix = this.given.strings.prefix;
13290 second.strings.suffix = this.given.strings.suffix;
13291 }
13292
13293 merged = this._join([first, second], sort_sep);
13294 blob = this._join([merged, suffix], sort_sep);
13295 }
13296 } else { // plain vanilla
13297 if (name["dropping-particle"] && name.family && !name["non-dropping-particle"]) {
13298 var dp = name["dropping-particle"];
13299 var apostrophes = ["'","\u02bc","\u2019","-"];
13300 if (apostrophes.indexOf(dp.slice(-1)) > -1 && dp.slice(0, -1) !== "de") {
13301 family = this._join([dropping_particle, family], "");
13302 dropping_particle = false;
13303 }
13304 }
13305
13306 var space = " ";
13307 if (this.state.inheritOpt(this.name, "initialize-with")
13308 && this.state.inheritOpt(this.name, "initialize-with").match(/[\u00a0\ufeff]/)
13309 && ["fr", "ru", "cs"].indexOf(this.state.opt["default-locale"][0].slice(0, 2)) > -1) {
13310 space = "\u00a0";
13311 }
13312
13313 if (has_hyphenated_non_dropping_particle) {
13314 second = this._join([non_dropping_particle, family], "");
13315 second = this._join([dropping_particle, second], space);
13316 } else {
13317 second = this._join([dropping_particle, non_dropping_particle, family], space);
13318 }
13319 second = this._join([second, suffix], suffix_sep);
13320 if (second && this.family) {
13321 second.strings.prefix = this.family.strings.prefix;
13322 second.strings.suffix = this.family.strings.suffix;
13323 }
13324 if (given && this.given) {
13325 given.strings.prefix = this.given.strings.prefix;
13326 given.strings.suffix = this.given.strings.suffix;
13327 }
13328 if (second.strings.prefix) {
13329 name["comma-dropping-particle"] = "";
13330 }
13331 blob = this._join([given, second], (name["comma-dropping-particle"] + space));
13332 }
13333 // XXX Just generally assume for the present that personal names render something
13334 this.state.tmp.group_context.tip.variable_success = true;
13335 this.state.tmp.can_substitute.replace(false, CSL.LITERAL);
13336 this.state.tmp.term_predecessor = true;
13337 // notSerious
13338 //this.state.output.append(blob, "literal", true);
13339 //var ret = this.state.output.pop();
13340 this.state.tmp.name_node.children.push(blob);
13341 return blob;
13342};
13343
13344/*
13345 // Do not include given name, dropping particle or suffix in strict short form of name
13346
13347 // initialize if appropriate
13348*/
13349
13350// Input names should be touched by _normalizeNameInput()
13351// exactly once: this is not idempotent.
13352CSL.NameOutput.prototype._normalizeNameInput = function (value) {
13353 var name = {
13354 literal:value.literal,
13355 family:value.family,
13356 isInstitution:value.isInstitution,
13357 given:value.given,
13358 suffix:value.suffix,
13359 "comma-suffix":value["comma-suffix"],
13360 "non-dropping-particle":value["non-dropping-particle"],
13361 "dropping-particle":value["dropping-particle"],
13362 "static-ordering":value["static-ordering"],
13363 "static-particles":value["static-particles"],
13364 "reverse-ordering":value["reverse-ordering"],
13365 "full-form-always": value["full-form-always"],
13366 "parse-names":value["parse-names"],
13367 "comma-dropping-particle": "",
13368 block_initialize:value.block_initialize,
13369 multi:value.multi
13370 };
13371 this._parseName(name);
13372 return name;
13373};
13374
13375// _transformNameset() replaced with enhanced transform.name().
13376
13377CSL.NameOutput.prototype._stripPeriods = function (tokname, str) {
13378 var decor_tok = this[tokname + "_decor"];
13379 if (str) {
13380 if (this.state.tmp.strip_periods) {
13381 str = str.replace(/\./g, "");
13382 } else if (decor_tok) {
13383 for (var i = 0, ilen = decor_tok.decorations.length; i < ilen; i += 1) {
13384 if ("@strip-periods" === decor_tok.decorations[i][0] && "true" === decor_tok.decorations[i][1]) {
13385 str = str.replace(/\./g, "");
13386 break;
13387 }
13388 }
13389 }
13390 }
13391 return str;
13392};
13393
13394CSL.NameOutput.prototype._nonDroppingParticle = function (name) {
13395 var ndp = name["non-dropping-particle"];
13396 if (ndp && this.state.tmp.sort_key_flag) {
13397 ndp = ndp.replace(/[\'\u2019]/, "");
13398 }
13399 var str = this._stripPeriods("family", ndp);
13400 if (this.state.output.append(str, this.family_decor, true)) {
13401 return this.state.output.pop();
13402 }
13403 return false;
13404};
13405
13406CSL.NameOutput.prototype._droppingParticle = function (name, pos, j) {
13407 var dp = name["dropping-particle"];
13408 if (dp && this.state.tmp.sort_key_flag) {
13409 dp = dp.replace(/[\'\u2019]/, "");
13410 }
13411 var str = this._stripPeriods("given", dp);
13412 if (name["dropping-particle"] && name["dropping-particle"].match(/^et.?al[^a-z]$/)) {
13413 if (this.state.inheritOpt(this.name, "et-al-use-last")) {
13414 if ("undefined" === typeof j) {
13415 this.etal_spec[pos].freeters = 2;
13416 } else {
13417 this.etal_spec[pos].persons = 2;
13418 }
13419 } else {
13420 if ("undefined" === typeof j) {
13421 this.etal_spec[pos].freeters = 1;
13422 } else {
13423 this.etal_spec[pos].persons = 1;
13424 }
13425 }
13426 name["comma-dropping-particle"] = "";
13427 } else if (this.state.output.append(str, this.given_decor, true)) {
13428 return this.state.output.pop();
13429 }
13430 return false;
13431};
13432
13433CSL.NameOutput.prototype._familyName = function (name) {
13434 var str = this._stripPeriods("family", name.family);
13435 if (this.state.output.append(str, this.family_decor, true)) {
13436 return this.state.output.pop();
13437 }
13438 return false;
13439};
13440
13441CSL.NameOutput.prototype._givenName = function (name, pos, i) {
13442 var ret;
13443 // citation
13444 // use disambig as-is
13445 // biblography
13446 // use disambig only if it boosts over the default
13447 // SO WHAT IS THE DEFAULT?
13448 // A: If "form" is short, it's 0.
13449 // If "form" is long, initialize-with exists (and initialize is not false) it's 1
13450 // If "form" is long, and initialize_with does not exist, it's 2.
13451 var formIsShort = this.state.inheritOpt(this.name, "form", "name-form", "long") !== "long";
13452 var initializeIsTurnedOn = this.state.inheritOpt(this.name, "initialize") !== false;
13453 var hasInitializeWith = "string" === typeof this.state.inheritOpt(this.name, "initialize-with") && !name.block_initialize;
13454 var defaultLevel;
13455 var useLevel;
13456 if (name["full-form-always"]) {
13457 useLevel = 2;
13458 } else {
13459 if (formIsShort) {
13460 defaultLevel = 0;
13461 } else if (hasInitializeWith) {
13462 defaultLevel = 1;
13463 } else {
13464 defaultLevel = 2;
13465 }
13466 var requestedLevel = this.state.tmp.disambig_settings.givens[pos][i];
13467 if (requestedLevel > defaultLevel) {
13468 useLevel = requestedLevel;
13469 } else {
13470 useLevel = defaultLevel;
13471 }
13472 }
13473 var gdropt = this.state.citation.opt["givenname-disambiguation-rule"];
13474 if (gdropt && gdropt.slice(-14) === "-with-initials") {
13475 hasInitializeWith = true;
13476 }
13477 if (name.family && useLevel === 1) {
13478 if (hasInitializeWith) {
13479 var initialize_with = this.state.inheritOpt(this.name, "initialize-with", false, "");
13480 name.given = CSL.Util.Names.initializeWith(this.state, name.given, initialize_with, !initializeIsTurnedOn);
13481 } else {
13482 name.given = CSL.Util.Names.unInitialize(this.state, name.given);
13483 }
13484 } else if (useLevel === 0) {
13485 return false;
13486 } else if (useLevel === 2) {
13487 name.given = CSL.Util.Names.unInitialize(this.state, name.given);
13488 }
13489
13490 var str = this._stripPeriods("given", name.given);
13491 var rendered = this.state.output.append(str, this.given_decor, true);
13492 if (rendered) {
13493 ret = this.state.output.pop();
13494 return ret;
13495 }
13496 return false;
13497};
13498
13499CSL.NameOutput.prototype._nameSuffix = function (name) {
13500
13501 var str = name.suffix, ret;
13502
13503 if ("string" === typeof this.state.inheritOpt(this.name, "initialize-with")) {
13504 str = CSL.Util.Names.initializeWith(this.state, name.suffix, this.state.inheritOpt(this.name, "initialize-with"), true);
13505 }
13506
13507 str = this._stripPeriods("family", str);
13508 var toSuffix = '';
13509 if (str && str.slice(-1) === '.') {
13510 str = str.slice(0, -1);
13511 toSuffix = '.';
13512 }
13513 var rendered = this.state.output.append(str, "empty", true);
13514 if (rendered) {
13515 ret = this.state.output.pop();
13516 ret.strings.suffix = toSuffix + ret.strings.suffix;
13517 return ret;
13518 }
13519 return false;
13520};
13521
13522CSL.NameOutput.prototype._getLongStyle = function (name) {
13523 var long_style;
13524 if (name["short"].length) {
13525 if (this.institutionpart["long-with-short"]) {
13526 long_style = this.institutionpart["long-with-short"];
13527 } else {
13528 long_style = this.institutionpart["long"];
13529 }
13530 } else {
13531 long_style = this.institutionpart["long"];
13532 }
13533 if (!long_style) {
13534 long_style = new CSL.Token();
13535 }
13536 return long_style;
13537};
13538
13539CSL.NameOutput.prototype._getShortStyle = function () {
13540 var short_style;
13541 if (this.institutionpart["short"]) {
13542 short_style = this.institutionpart["short"];
13543 } else {
13544 short_style = new CSL.Token();
13545 }
13546 return short_style;
13547};
13548
13549CSL.NameOutput.prototype._parseName = function (name) {
13550 if (!name["parse-names"] && "undefined" !== typeof name["parse-names"]) {
13551 return name;
13552 }
13553 if (name.family && !name.given && name.isInstitution) {
13554 name.literal = name.family;
13555 name.family = undefined;
13556 name.isInstitution = undefined;
13557 }
13558 var noparse;
13559 if (name.family
13560 && (name.family.slice(0, 1) === '"' && name.family.slice(-1) === '"')
13561 || (!name["parse-names"] && "undefined" !== typeof name["parse-names"])) {
13562
13563 name.family = name.family.slice(1, -1);
13564 noparse = true;
13565 name["parse-names"] = 0;
13566 } else {
13567 noparse = false;
13568 }
13569 if (this.state.opt.development_extensions.parse_names) {
13570 if (!name["non-dropping-particle"] && name.family && !noparse && name.given) {
13571 if (!name["static-particles"]) {
13572 CSL.parseParticles(name, true);
13573 }
13574 }
13575 }
13576};
13577
13578/*
13579 * Return a single name object
13580 */
13581
13582// The interface is a mess, but this should serve.
13583
13584CSL.NameOutput.prototype.getName = function (name, slotLocaleset, fallback, stopOrig) {
13585
13586 // Needs to tell us whether we used orig or not.
13587
13588 if (stopOrig && slotLocaleset === 'locale-orig') {
13589 return {name:false,usedOrig:stopOrig};
13590 }
13591
13592 // Normalize to string
13593 if (!name.family) {
13594 name.family = "";
13595 }
13596 if (!name.given) {
13597 name.given = "";
13598 }
13599
13600 // Recognized params are:
13601 // block-initialize
13602 // transliterated
13603 // static-ordering
13604 // full-form-always
13605 // All default to false, except for static-ordering, which is initialized
13606 // with a sniff.
13607 var name_params = {};
13608 // Determines the default static-order setting based on the characters
13609 // used in the headline field. Will be overridden by locale-based
13610 // parameters evaluated against explicit lang tags set on the (sub)field.
13611 name_params["static-ordering"] = this.getStaticOrder(name);
13612
13613 var foundTag = true;
13614 var langTag;
13615 if (slotLocaleset !== 'locale-orig') {
13616 foundTag = false;
13617 if (name.multi) {
13618 var langTags = this.state.opt[slotLocaleset];
13619 for (var i = 0, ilen = langTags.length; i < ilen; i += 1) {
13620 langTag = langTags[i];
13621 if (name.multi._key[langTag]) {
13622 foundTag = true;
13623 var isInstitution = name.isInstitution;
13624 name = name.multi._key[langTag];
13625 name.isInstitution = isInstitution;
13626 // Set name formatting params
13627 name_params = this.getNameParams(langTag);
13628 name_params.transliterated = true;
13629 break;
13630 }
13631 }
13632 }
13633 }
13634
13635 if (!foundTag) {
13636 langTag = false;
13637 if (name.multi && name.multi.main) {
13638 langTag = name.multi.main;
13639 } else if (this.Item.language) {
13640 langTag = this.Item.language;
13641 }
13642 if (langTag) {
13643 name_params = this.getNameParams(langTag);
13644 }
13645 }
13646
13647 if (!fallback && !foundTag) {
13648 return {name:false,usedOrig:stopOrig};
13649 }
13650
13651 // Normalize to string (again)
13652 if (!name.family) {
13653 name.family = "";
13654 }
13655 if (!name.given) {
13656 name.given = "";
13657 }
13658 if (name.literal) {
13659 delete name.family;
13660 delete name.given;
13661 }
13662 // var clone the item before writing into it
13663 name = {
13664 family:name.family,
13665 given:name.given,
13666 "non-dropping-particle":name["non-dropping-particle"],
13667 "dropping-particle":name["dropping-particle"],
13668 suffix:name.suffix,
13669 "static-ordering":name_params["static-ordering"],
13670 "static-particles":name["static-particles"],
13671 "reverse-ordering":name_params["reverse-ordering"],
13672 "full-form-always": name_params["full-form-always"],
13673 "parse-names":name["parse-names"],
13674 "comma-suffix":name["comma-suffix"],
13675 "comma-dropping-particle":name["comma-dropping-particle"],
13676 transliterated: name_params.transliterated,
13677 block_initialize: name_params["block-initialize"],
13678 literal:name.literal,
13679 isInstitution:name.isInstitution,
13680 multi:name.multi
13681 };
13682
13683 if (!name.literal && (!name.given && name.family && name.isInstitution)) {
13684 name.literal = name.family;
13685 }
13686 if (name.literal) {
13687 delete name.family;
13688 delete name.given;
13689 }
13690 name = this._normalizeNameInput(name);
13691 var usedOrig;
13692 if (stopOrig) {
13693 usedOrig = stopOrig;
13694 } else {
13695 usedOrig = !foundTag;
13696 }
13697 return {name:name,usedOrig:usedOrig};
13698};
13699
13700CSL.NameOutput.prototype.getNameParams = function (langTag) {
13701 var ret = {};
13702 var langspec = CSL.localeResolve(this.Item.language, this.state.opt["default-locale"][0]);
13703 var try_locale = this.state.locale[langspec.best] ? langspec.best : this.state.opt["default-locale"][0];
13704 var name_as_sort_order = this.state.locale[try_locale].opts["name-as-sort-order"];
13705 var name_as_reverse_order = this.state.locale[try_locale].opts["name-as-reverse-order"];
13706 var name_never_short = this.state.locale[try_locale].opts["name-never-short"];
13707 var field_lang_bare = langTag.split("-")[0];
13708 if (name_as_sort_order && name_as_sort_order[field_lang_bare]) {
13709 ret["static-ordering"] = true;
13710 ret["reverse-ordering"] = false;
13711 }
13712 if (name_as_reverse_order && name_as_reverse_order[field_lang_bare]) {
13713 ret["reverse-ordering"] = true;
13714 ret["static-ordering"] = false;
13715 }
13716 if (name_never_short && name_never_short[field_lang_bare]) {
13717 ret["full-form-always"] = true;
13718 }
13719
13720 if (ret["static-ordering"]) {
13721 ret["block-initialize"] = true;
13722 }
13723 return ret;
13724};
13725
13726CSL.NameOutput.prototype.setRenderedName = function (name) {
13727 if (this.state.tmp.area === "bibliography") {
13728 var strname = "";
13729 for (var j=0,jlen=CSL.NAME_PARTS.length;j<jlen;j+=1) {
13730 if (name[CSL.NAME_PARTS[j]]) {
13731 strname += name[CSL.NAME_PARTS[j]];
13732 }
13733 }
13734 this.state.tmp.rendered_name.push(strname);
13735 }
13736};
13737
13738CSL.NameOutput.prototype.fixupInstitution = function (name, varname, listpos) {
13739 name = this._splitInstitution(name, varname, listpos);
13740 // XXX This should be embedded in the institution name function.
13741 if (this.institution.strings["reverse-order"]) {
13742 name["long"].reverse();
13743 }
13744
13745 var long_form = name["long"];
13746 var short_form = name["long"].slice();
13747 var use_short_form = false;
13748 if (this.state.sys.getAbbreviation) {
13749 var jurisdiction = this.Item.jurisdiction;
13750 for (var j = 0, jlen = long_form.length; j < jlen; j += 1) {
13751 var abbrevKey = long_form[j];
13752 jurisdiction = this.state.transform.loadAbbreviation(jurisdiction, "institution-part", abbrevKey);
13753 if (this.state.transform.abbrevs[jurisdiction]["institution-part"][abbrevKey]) {
13754 short_form[j] = this.state.transform.abbrevs[jurisdiction]["institution-part"][abbrevKey];
13755 use_short_form = true;
13756 }
13757 }
13758 }
13759 if (use_short_form) {
13760 name["short"] = short_form;
13761 } else {
13762 name["short"] = [];
13763 }
13764 return name;
13765};
13766
13767
13768CSL.NameOutput.prototype.getStaticOrder = function (name, refresh) {
13769 var static_ordering_val = false;
13770 if (!refresh && name["static-ordering"]) {
13771 static_ordering_val = true;
13772 } else if (this._isRomanesque(name) === 0) {
13773 static_ordering_val = true;
13774 } else if ((!name.multi || !name.multi.main) && this.Item.language && ['vi', 'hu'].indexOf(this.Item.language) > -1) {
13775 static_ordering_val = true;
13776 } else if (name.multi && name.multi.main && ['vi', 'hu'].indexOf(name.multi.main.slice(0,2)) > -1) {
13777 static_ordering_val = true;
13778 } else {
13779 if (this.state.opt['auto-vietnamese-names']
13780 && (CSL.VIETNAMESE_NAMES.exec(name.family + " " + name.given)
13781 && CSL.VIETNAMESE_SPECIALS.exec(name.family + name.given))) {
13782
13783 static_ordering_val = true;
13784 }
13785 }
13786 return static_ordering_val;
13787};
13788
13789
13790CSL.NameOutput.prototype._splitInstitution = function (value, v, i) {
13791 var ret = {};
13792 // Due to a bug in Juris-M, there are a small number of items in my accounts that have
13793 // a institution parent, and a personal child. The bug that created them was fixed before
13794 // release, but this hack keeps them from crashing the processor.
13795 if (!value.literal && value.family) {
13796 value.literal = value.family;
13797 delete value.family;
13798 }
13799 var splitInstitution = value.literal.replace(/\s*\|\s*/g, "|");
13800 // check for total and utter abbreviation IFF form="short"
13801 splitInstitution = splitInstitution.split("|");
13802 if (this.institution.strings.form === "short" && this.state.sys.getAbbreviation) {
13803 // On a match, drop unused elements to yield a single key.
13804 var jurisdiction = this.Item.jurisdiction;
13805 for (var j = splitInstitution.length; j > 0; j += -1) {
13806 var str = splitInstitution.slice(0, j).join("|");
13807 var abbrevKey = str;
13808 jurisdiction = this.state.transform.loadAbbreviation(jurisdiction, "institution-entire", abbrevKey);
13809 if (this.state.transform.abbrevs[jurisdiction]["institution-entire"][abbrevKey]) {
13810 var splitLst = this.state.transform.abbrevs[jurisdiction]["institution-entire"][abbrevKey];
13811
13812 splitLst = this.state.transform.quashCheck(splitLst);
13813
13814 // If the abbreviation has date cut-offs, find the most recent
13815 // abbreviation within scope.
13816 var splitSplitLst = splitLst.split(/>>[0-9]{4}>>/);
13817 var m = splitLst.match(/>>([0-9]{4})>>/);
13818 splitLst = splitSplitLst.pop();
13819 if (splitSplitLst.length > 0 && this.Item["original-date"] && this.Item["original-date"].year) {
13820 for (var k=m.length - 1; k > 0; k += -1) {
13821 if (parseInt(this.Item["original-date"].year, 10) >= parseInt(m[k], 10)) {
13822 break;
13823 }
13824 splitLst = splitSplitLst.pop();
13825 }
13826 }
13827 splitLst = splitLst.replace(/\s*\|\s*/g, "|");
13828 splitInstitution = [splitLst];
13829 break;
13830 }
13831 }
13832 }
13833 splitInstitution.reverse();
13834 //print("into _trimInstitution with splitInstitution, v, i = "+splitInstitution+", "+v+", "+i);
13835 ret["long"] = this._trimInstitution(splitInstitution, v, i);
13836 return ret;
13837};
13838
13839CSL.NameOutput.prototype._trimInstitution = function (subunits, v) {
13840 //
13841 var use_first = false;
13842 var append_last = false;
13843 var s = subunits.slice();
13844 var stop_last = false;
13845 if (this.institution) {
13846 if ("undefined" !== typeof this.institution.strings["use-first"]) {
13847 use_first = this.institution.strings["use-first"];
13848 }
13849 if ("undefined" !== typeof this.institution.strings["stop-last"]) {
13850 // stop-last is negative when present
13851 stop_last = this.institution.strings["stop-last"];
13852 } else if ("authority" === v && this.state.tmp.authority_stop_last) {
13853 stop_last = this.state.tmp.authority_stop_last;
13854 }
13855 if (stop_last) {
13856 s = s.slice(0, stop_last);
13857 subunits = subunits.slice(0, stop_last);
13858 }
13859 if ("undefined" !== typeof this.institution.strings["use-last"]) {
13860 append_last = this.institution.strings["use-last"];
13861 }
13862 if ("authority" === v) {
13863 if (stop_last) {
13864 this.state.tmp.authority_stop_last = stop_last;
13865 }
13866 if (append_last) {
13867 this.state.tmp.authority_stop_last += (append_last * -1);
13868 }
13869 }
13870 }
13871 if (false === use_first) {
13872 if (this.persons[v].length === 0) {
13873 use_first = this.institution.strings["substitute-use-first"];
13874 }
13875 if (!use_first) {
13876 use_first = 0;
13877 }
13878 }
13879 if (false === append_last) {
13880 if (!use_first) {
13881 append_last = subunits.length;
13882 } else {
13883 append_last = 0;
13884 }
13885 }
13886 // Now that we've determined the value of append_last
13887 // (use-last), avoid overlaps.
13888 if (use_first > subunits.length - append_last) {
13889 use_first = subunits.length - append_last;
13890 }
13891
13892 // This could be more clear. use-last takes priority
13893 // in the event of overlap, because of adjustment above
13894 subunits = subunits.slice(0, use_first);
13895 s = s.slice(use_first);
13896 if (append_last) {
13897 if (append_last > s.length) {
13898 append_last = s.length;
13899 }
13900 if (append_last) {
13901 subunits = subunits.concat(s.slice((s.length - append_last)));
13902 }
13903 }
13904 return subunits;
13905};
13906
13907/*global CSL: true */
13908
13909CSL.PublisherOutput = function (state, group_tok) {
13910 this.state = state;
13911 this.group_tok = group_tok;
13912 this.varlist = [];
13913};
13914
13915CSL.PublisherOutput.prototype.render = function () {
13916 this.clearVars();
13917 this.composeAndBlob();
13918 this.composeElements();
13919 this.composePublishers();
13920 this.joinPublishers();
13921};
13922
13923
13924// XXX Figure out how to adapt this to the House of Lords / House of Commons
13925// joint committee case
13926
13927// name_delimiter
13928// delimiter_precedes_last
13929// and
13930
13931CSL.PublisherOutput.prototype.composeAndBlob = function () {
13932 this.and_blob = {};
13933 var and_term = false;
13934 if (this.group_tok.strings.and === "text") {
13935 and_term = this.state.getTerm("and");
13936 } else if (this.group_tok.strings.and === "symbol") {
13937 and_term = "&";
13938 }
13939 var tok = new CSL.Token();
13940 tok.strings.suffix = " ";
13941 tok.strings.prefix = " ";
13942 this.state.output.append(and_term, tok, true);
13943 var no_delim = this.state.output.pop();
13944
13945 tok.strings.prefix = this.group_tok.strings["subgroup-delimiter"];
13946 this.state.output.append(and_term, tok, true);
13947 var with_delim = this.state.output.pop();
13948
13949 this.and_blob.single = false;
13950 this.and_blob.multiple = false;
13951 if (and_term) {
13952 if (this.group_tok.strings["subgroup-delimiter-precedes-last"] === "always") {
13953 this.and_blob.single = with_delim;
13954 } else if (this.group_tok.strings["subgroup-delimiter-precedes-last"] === "never") {
13955 this.and_blob.single = no_delim;
13956 this.and_blob.multiple = no_delim;
13957 } else {
13958 this.and_blob.single = no_delim;
13959 this.and_blob.multiple = with_delim;
13960 }
13961 }
13962};
13963
13964
13965CSL.PublisherOutput.prototype.composeElements = function () {
13966 for (var i = 0, ilen = 2; i < ilen; i += 1) {
13967 var varname = ["publisher", "publisher-place"][i];
13968 for (var j = 0, jlen = this["publisher-list"].length; j < jlen; j += 1) {
13969 var str = this[varname + "-list"][j];
13970 var tok = this[varname + "-token"];
13971 // notSerious
13972 this.state.output.append(str, tok, true);
13973 this[varname + "-list"][j] = this.state.output.pop();
13974 }
13975 }
13976};
13977
13978
13979CSL.PublisherOutput.prototype.composePublishers = function () {
13980 var blobs;
13981 for (var i = 0, ilen = this["publisher-list"].length; i < ilen; i += 1) {
13982 blobs = [this[this.varlist[0] + "-list"][i], this[this.varlist[1] + "-list"][i]];
13983 this["publisher-list"][i] = this._join(blobs, this.group_tok.strings.delimiter);
13984 }
13985};
13986
13987
13988CSL.PublisherOutput.prototype.joinPublishers = function () {
13989 var blobs = this["publisher-list"];
13990 var publishers = this._join(blobs, this.group_tok.strings["subgroup-delimiter"], this.and_blob.single, this.and_blob.multiple, this.group_tok);
13991 this.state.output.append(publishers, "literal");
13992};
13993
13994
13995// blobs, delimiter, single, multiple, tokenname
13996// Tokenname is a key at top level of this object.
13997CSL.PublisherOutput.prototype._join = CSL.NameOutput.prototype._join;
13998CSL.PublisherOutput.prototype._getToken = CSL.NameOutput.prototype._getToken;
13999
14000
14001CSL.PublisherOutput.prototype.clearVars = function () {
14002 this.state.tmp["publisher-list"] = false;
14003 this.state.tmp["publisher-place-list"] = false;
14004 this.state.tmp["publisher-group-token"] = false;
14005 this.state.tmp["publisher-token"] = false;
14006 this.state.tmp["publisher-place-token"] = false;
14007};
14008
14009/*global CSL: true */
14010
14011CSL.evaluateLabel = function (node, state, Item, item) {
14012 var myterm;
14013 if ("locator" === node.strings.term) {
14014 if (item && item.label) {
14015 if (item.label === "sub verbo") {
14016 myterm = "sub-verbo";
14017 } else {
14018 myterm = item.label;
14019 }
14020 }
14021 if (!myterm) {
14022 myterm = "page";
14023 }
14024 } else {
14025 myterm = node.strings.term;
14026 }
14027
14028 // Plurals detection.
14029 var plural = node.strings.plural;
14030 if ("number" !== typeof plural) {
14031 // (node, ItemObject, variable, type)
14032 var theItem = (item && node.strings.term === "locator") ? item : Item;
14033 if (theItem[node.strings.term]) {
14034 state.processNumber(false, theItem, node.strings.term, Item.type);
14035 plural = state.tmp.shadow_numbers[node.strings.term].plural;
14036 if (!state.tmp.shadow_numbers[node.strings.term].labelForm
14037 && !state.tmp.shadow_numbers[node.strings.term].labelDecorations) {
14038 state.tmp.shadow_numbers[node.strings.term].labelForm = node.strings.form;
14039 state.tmp.shadow_numbers[node.strings.term].labelCapitalizeIfFirst = node.strings.capitalize_if_first;
14040 state.tmp.shadow_numbers[node.strings.term].labelDecorations = node.decorations.slice();
14041 }
14042
14043 if (["locator", "number", "page"].indexOf(node.strings.term) > -1 && state.tmp.shadow_numbers[node.strings.term].label) {
14044 myterm = state.tmp.shadow_numbers[node.strings.term].label;
14045 }
14046 if (node.decorations && state.opt.development_extensions.csl_reverse_lookup_support) {
14047 node.decorations.reverse();
14048 node.decorations.push(["@showid","true", node.cslid]);
14049 node.decorations.reverse();
14050 }
14051 }
14052 }
14053 return CSL.castLabel(state, node, myterm, plural, CSL.TOLERANT);
14054};
14055
14056CSL.castLabel = function (state, node, term, plural, mode) {
14057 var label_form = node.strings.form;
14058 var label_capitalize_if_first = node.strings.capitalize_if_first;
14059 if (state.tmp.group_context.tip.label_form && label_form !== "static") {
14060 label_form = state.tmp.group_context.tip.label_form;
14061 }
14062 if (state.tmp.group_context.tip.label_capitalize_if_first) {
14063 label_capitalize_if_first = state.tmp.group_context.tip.label_capitalize_if_first;
14064 }
14065 var ret = state.getTerm(term, label_form, plural, false, mode, node.default_locale);
14066 if (label_capitalize_if_first) {
14067 ret = CSL.Output.Formatters["capitalize-first"](state, ret);
14068 }
14069 // XXXXX Cut-and-paste code in multiple locations. This code block should be
14070 // collected in a function.
14071 // Tag: strip-periods-block
14072 if (state.tmp.strip_periods) {
14073 ret = ret.replace(/\./g, "");
14074 } else {
14075 for (var i = 0, ilen = node.decorations.length; i < ilen; i += 1) {
14076 if ("@strip-periods" === node.decorations[i][0] && "true" === node.decorations[i][1]) {
14077 ret = ret.replace(/\./g, "");
14078 break;
14079 }
14080 }
14081 }
14082 return ret;
14083};
14084
14085/*global CSL: true */
14086
14087CSL.Node.name = {
14088 build: function (state, target) {
14089 var func;
14090 if ([CSL.SINGLETON, CSL.START].indexOf(this.tokentype) > -1) {
14091 var oldTmpRoot;
14092 if ("undefined" === typeof state.tmp.root) {
14093 oldTmpRoot = undefined;
14094 state.tmp.root = "citation";
14095 } else {
14096 oldTmpRoot = state.tmp.root;
14097 }
14098 // Many CSL styles set et-al-[min|use-first]
14099 // and et-al-subsequent-[min|use-first] to the same
14100 // value.
14101 // Set state.opt.update_mode = CSL.POSITION if
14102 // et-al-subsequent-min or et-al-subsequent-use-first
14103 // are set AND their value differs from their plain
14104 // counterparts.
14105 if (state.inheritOpt(this, "et-al-subsequent-min")
14106 && (state.inheritOpt(this, "et-al-subsequent-min") !== state.inheritOpt(this, "et-al-min"))) {
14107
14108 state.opt.update_mode = CSL.POSITION;
14109 }
14110 if (state.inheritOpt(this, "et-al-subsequent-use-first")
14111 && (state.inheritOpt(this, "et-al-subsequent-use-first") !== state.inheritOpt(this, "et-al-use-first"))) {
14112
14113 state.opt.update_mode = CSL.POSITION;
14114 }
14115
14116 state.tmp.root = oldTmpRoot;
14117
14118 func = function (state) {
14119 // Et-al (onward processing in node_etal.js and node_names.js)
14120 // XXXXX Why is this necessary? This is available on this.name, right?
14121 state.tmp.etal_term = "et-al";
14122
14123 // Use default delimiter as fallback, in a way that allows explicit
14124 // empty strings.
14125 state.tmp.name_delimiter = state.inheritOpt(this, "delimiter", "name-delimiter", ", ");
14126 state.tmp["delimiter-precedes-et-al"] = state.inheritOpt(this, "delimiter-precedes-et-al");
14127
14128 // And
14129 if ("text" === state.inheritOpt(this, "and")) {
14130 this.and_term = state.getTerm("and", "long", 0);
14131 } else if ("symbol" === state.inheritOpt(this, "and")) {
14132 if (state.opt.development_extensions.expect_and_symbol_form) {
14133 this.and_term = state.getTerm("and", "symbol", 0);
14134 } else {
14135 this.and_term = "&";
14136 }
14137 }
14138 state.tmp.and_term = this.and_term;
14139 if (CSL.STARTSWITH_ROMANESQUE_REGEXP.test(this.and_term)) {
14140 this.and_prefix_single = " ";
14141 this.and_prefix_multiple = ", ";
14142 // Workaround to allow explicit empty string
14143 // on cs:name delimiter.
14144 if ("string" === typeof state.tmp.name_delimiter) {
14145 this.and_prefix_multiple = state.tmp.name_delimiter;
14146 }
14147 this.and_suffix = " ";
14148
14149 // Really can't inspect these values in the build phase. Sorry.
14150 //state.build.name_delimiter = this.strings.delimiter;
14151
14152 } else {
14153 this.and_prefix_single = "";
14154 this.and_prefix_multiple = "";
14155 this.and_suffix = "";
14156 }
14157 if (state.inheritOpt(this, "delimiter-precedes-last") === "always") {
14158 this.and_prefix_single = state.tmp.name_delimiter;
14159 } else if (state.inheritOpt(this, "delimiter-precedes-last") === "never") {
14160 // Slightly fragile: could test for charset here to make
14161 // this more certain.
14162 if (this.and_prefix_multiple) {
14163 this.and_prefix_multiple = " ";
14164 }
14165 } else if (state.inheritOpt(this, "delimiter-precedes-last") === "after-inverted-name") {
14166 if (this.and_prefix_single) {
14167 this.and_prefix_single = state.tmp.name_delimiter;
14168 }
14169 if (this.and_prefix_multiple) {
14170 this.and_prefix_multiple = " ";
14171 }
14172 }
14173
14174 this.and = {};
14175 if (state.inheritOpt(this, "and")) {
14176 state.output.append(this.and_term, "empty", true);
14177 this.and.single = state.output.pop();
14178 this.and.single.strings.prefix = this.and_prefix_single;
14179 this.and.single.strings.suffix = this.and_suffix;
14180 state.output.append(this.and_term, "empty", true);
14181 this.and.multiple = state.output.pop();
14182 this.and.multiple.strings.prefix = this.and_prefix_multiple;
14183 this.and.multiple.strings.suffix = this.and_suffix;
14184 } else if (state.tmp.name_delimiter) {
14185 // This is a little weird, but it works.
14186 this.and.single = new CSL.Blob(state.tmp.name_delimiter);
14187 this.and.single.strings.prefix = "";
14188 this.and.single.strings.suffix = "";
14189 this.and.multiple = new CSL.Blob(state.tmp.name_delimiter);
14190 this.and.multiple.strings.prefix = "";
14191 this.and.multiple.strings.suffix = "";
14192 }
14193
14194 this.ellipsis = {};
14195 if (state.inheritOpt(this, "et-al-use-last")) {
14196 // We use the dedicated Unicode ellipsis character because
14197 // it is recommended by some editors, and can be more easily
14198 // identified for find and replace operations.
14199 // Source: http://en.wikipedia.org/wiki/Ellipsis#Computer_representations
14200 //
14201
14202 // Eventually, this should be localized as a term in CSL, with some
14203 // mechanism for triggering appropriate punctuation handling around
14204 // the ellipsis placeholder (Polish is a particularly tough case for that).
14205 this.ellipsis_term = "\u2026";
14206 // Similar treatment to "and", above, will be needed
14207 // here when this becomes a locale term.
14208 this.ellipsis_prefix_single = " ";
14209 this.ellipsis_prefix_multiple = state.inheritOpt(this, "delimiter", "name-delimiter", ", ");
14210 this.ellipsis_suffix = " ";
14211 this.ellipsis.single = new CSL.Blob(this.ellipsis_term);
14212 this.ellipsis.single.strings.prefix = this.ellipsis_prefix_single;
14213 this.ellipsis.single.strings.suffix = this.ellipsis_suffix;
14214 this.ellipsis.multiple = new CSL.Blob(this.ellipsis_term);
14215 this.ellipsis.multiple.strings.prefix = this.ellipsis_prefix_multiple;
14216 this.ellipsis.multiple.strings.suffix = this.ellipsis_suffix;
14217 }
14218
14219 // et-al parameters are annoyingly incomprehensible
14220 // again.
14221 //
14222 // Explanation probably just adds a further layer of
14223 // irritation, but what's INTENDED here is that
14224 // the state.tmp et-al variables are set from the
14225 // cs:key element when composing sort keys, and a
14226 // macro containing a name can be called from cs:key.
14227 // So when cs:key sets et-al attributes, they are
14228 // set on state.tmp, and when the key is finished
14229 // processing, the state.tmp variables are reset to
14230 // undefined. IN THEORY the state.tmp et-al variables
14231 // will not be used in other contexts. I hope.
14232 //
14233 // Anyway, the current tests now seem to pass.
14234 if ("undefined" === typeof state.tmp["et-al-min"]) {
14235 state.tmp["et-al-min"] = state.inheritOpt(this, "et-al-min");
14236 }
14237 if ("undefined" === typeof state.tmp["et-al-use-first"]) {
14238 state.tmp["et-al-use-first"] = state.inheritOpt(this, "et-al-use-first");
14239 }
14240 if ("undefined" === typeof state.tmp["et-al-use-last"]) {
14241 //print(" setting et-al-use-last from name: "+this.strings["et-al-use-last"]);
14242 state.tmp["et-al-use-last"] = state.inheritOpt(this, "et-al-use-last");
14243 }
14244
14245 state.nameOutput.name = this;
14246 };
14247
14248 state.build.name_flag = true;
14249
14250 this.execs.push(func);
14251 }
14252 target.push(this);
14253 }
14254};
14255
14256
14257
14258/*global CSL: true */
14259
14260CSL.Node["name-part"] = {
14261 build: function (state) {
14262 state.build[this.strings.name] = this;
14263 }
14264};
14265
14266/*global CSL: true */
14267
14268CSL.Node.names = {
14269 build: function (state, target) {
14270 var func;
14271 // CSL.debug = print;
14272
14273 if (this.tokentype === CSL.START || this.tokentype === CSL.SINGLETON) {
14274 CSL.Util.substituteStart.call(this, state, target);
14275 state.build.substitute_level.push(1);
14276 }
14277
14278 if (this.tokentype === CSL.SINGLETON) {
14279 state.build.names_variables[state.build.names_variables.length-1].concat(this.variables);
14280 for (var i in this.variables) {
14281 var variable = this.variables[i];
14282 var name_labels = state.build.name_label[state.build.name_label.length-1];
14283 if (Object.keys(name_labels).length) {
14284 name_labels[variable] = name_labels[Object.keys(name_labels)[0]];
14285 }
14286 }
14287 func = function (state) {
14288 state.nameOutput.reinit(this, this.variables_real[0]);
14289 };
14290 this.execs.push(func);
14291 }
14292
14293 if (this.tokentype === CSL.START) {
14294
14295 state.build.names_flag = true;
14296 state.build.name_flag = false;
14297 state.build.names_level += 1;
14298 state.build.names_variables.push(this.variables);
14299 state.build.name_label.push({});
14300 // init can substitute
14301 // init names
14302 func = function (state) {
14303 state.tmp.can_substitute.push(true);
14304 state.nameOutput.init(this);
14305 };
14306 this.execs.push(func);
14307
14308 }
14309
14310 if (this.tokentype === CSL.END) {
14311
14312 // Set/reset name blobs if they exist, for processing
14313 // by namesOutput()
14314 for (var i = 0, ilen = 3; i < ilen; i += 1) {
14315 var key = ["family", "given", "et-al"][i];
14316 this[key] = state.build[key];
14317 if (state.build.names_level === 1) {
14318 state.build[key] = undefined;
14319 }
14320 }
14321 // Labels, if any
14322 this.label = state.build.name_label[state.build.name_label.length-1];
14323 state.build.names_level += -1;
14324 state.build.names_variables.pop();
14325 state.build.name_label.pop();
14326
14327 // The with term. This isn't the right place
14328 // for this, but it's all hard-wired at the
14329 // moment.
14330
14331 // "and" and "ellipsis" are set in node_name.js
14332 func = function (state) {
14333 // Et-al (strings only)
14334 // Blob production has to happen inside nameOutput()
14335 // since proper escaping requires access to the output
14336 // queue.
14337 if (state.tmp.etal_node) {
14338 this.etal_style = state.tmp.etal_node;
14339 } else {
14340 this.etal_style = "empty";
14341 }
14342
14343 this.etal_term = state.getTerm(state.tmp.etal_term, "long", 0);
14344 this.etal_prefix_single = " ";
14345 // Should be name delimiter, not hard-wired.
14346 this.etal_prefix_multiple = state.tmp.name_delimiter;
14347 if (state.tmp["delimiter-precedes-et-al"] === "always") {
14348 this.etal_prefix_single = state.tmp.name_delimiter;
14349 } else if (state.tmp["delimiter-precedes-et-al"] === "never") {
14350 this.etal_prefix_multiple = " ";
14351 } else if (state.tmp["delimiter-precedes-et-al"] === "after-inverted-name") {
14352 this.etal_prefix_single = state.tmp.name_delimiter;
14353 this.etal_prefix_multiple = " ";
14354 }
14355 this.etal_suffix = "";
14356 if (!CSL.STARTSWITH_ROMANESQUE_REGEXP.test(this.etal_term)) {
14357 // Not sure what the correct treatment is here, but we should not suppress
14358 // a comma-space.
14359 // https://forums.zotero.org/discussion/76679/delimiter-precedes-et-al-always-dose-not-work-in-locale-zh-cn
14360 if (this.etal_prefix_single === " ") {
14361 this.etal_prefix_single = "";
14362 }
14363 if (this.etal_prefix_multiple === " ") {
14364 this.etal_prefix_multiple = "";
14365 }
14366 if (this.etal_suffix === " ") {
14367 this.etal_suffix = "";
14368 }
14369 }
14370 // et-al affixes are further adjusted in nameOutput(),
14371 // after the term (possibly changed in cs:et-al) is known.
14372
14373
14374 for (var i = 0, ilen = 3; i < ilen; i += 1) {
14375 var key = ["family", "given"][i];
14376 state.nameOutput[key] = this[key];
14377 }
14378 state.nameOutput["with"] = this["with"];
14379
14380 // REMOVE THIS
14381 var mywith = "with";
14382 var with_default_prefix = "";
14383 var with_suffix = "";
14384 if (CSL.STARTSWITH_ROMANESQUE_REGEXP.test(mywith)) {
14385 with_default_prefix = " ";
14386 with_suffix = " ";
14387 }
14388 var thewith = {};
14389 thewith.single = new CSL.Blob(mywith);
14390 thewith.single.strings.suffix = with_suffix;
14391 thewith.multiple = new CSL.Blob(mywith);
14392 thewith.multiple.strings.suffix = with_suffix;
14393 if (state.inheritOpt(state.nameOutput.name, "delimiter-precedes-last") === "always") {
14394 thewith.single.strings.prefix = state.inheritOpt(this, "delimiter", "names-delimiter");
14395 thewith.multiple.strings.prefix = state.inheritOpt(this, "delimiter", "names-delimiter");
14396 } else if (state.inheritOpt(state.nameOutput.name, "delimiter-precedes-last") === "contextual") {
14397 thewith.single.strings.prefix = with_default_prefix;
14398 thewith.multiple.strings.prefix = state.inheritOpt(this, "delimiter", "names-delimiter");
14399 } else if (state.inheritOpt(state.nameOutput.name, "delimiter-precedes-last") === "after-inverted-name") {
14400 thewith.single.strings.prefix = state.inheritOpt(this, "delimiter", "names-delimiter");
14401 thewith.multiple.strings.prefix = with_default_prefix;
14402 } else {
14403 thewith.single.strings.prefix = with_default_prefix;
14404 thewith.multiple.strings.prefix = with_default_prefix;
14405 }
14406 state.nameOutput["with"] = thewith;
14407
14408
14409 // XXX label style should be set per variable, since they may differ
14410 // XXX with full-form nested names constructs
14411 state.nameOutput.label = this.label;
14412
14413 state.nameOutput.etal_style = this.etal_style;
14414 state.nameOutput.etal_term = this.etal_term;
14415 state.nameOutput.etal_prefix_single = this.etal_prefix_single;
14416 state.nameOutput.etal_prefix_multiple = this.etal_prefix_multiple;
14417 state.nameOutput.etal_suffix = this.etal_suffix;
14418 state.nameOutput.outputNames();
14419 state.tmp["et-al-use-first"] = undefined;
14420 state.tmp["et-al-min"] = undefined;
14421 state.tmp["et-al-use-last"] = undefined;
14422 };
14423 this.execs.push(func);
14424
14425 // unsets
14426 func = function (state) {
14427 if (!state.tmp.can_substitute.pop()) {
14428 state.tmp.can_substitute.replace(false, CSL.LITERAL);
14429 }
14430
14431 // For posterity ...
14432 //
14433 // This was enough to fix the issue reported here:
14434 //
14435 // http://forums.zotero.org/discussion/25223/citeproc-bug-substitute-doesnt-work-correctly-for-title-macro/
14436 //
14437 // The remainder of the changes applied in the same patch
14438 // relate to a label assignments, which were found to be
14439 // buggy while working on the issue. The test covering
14440 // both problems is here:
14441 //
14442 // https://bitbucket.org/bdarcus/citeproc-test/src/ab136a6aa8f2/processor-tests/humans/substitute_SuppressOrdinaryVariable.txt
14443 if (state.tmp.can_substitute.mystack.length === 1) {
14444 state.tmp.can_block_substitute = false;
14445 }
14446 };
14447 this.execs.push(func);
14448
14449 state.build.name_flag = false;
14450 }
14451 target.push(this);
14452
14453 if (this.tokentype === CSL.END || this.tokentype === CSL.SINGLETON) {
14454 state.build.substitute_level.pop();
14455 CSL.Util.substituteEnd.call(this, state, target);
14456 }
14457 }
14458};
14459
14460/*global CSL: true */
14461
14462CSL.Node.number = {
14463 build: function (state, target) {
14464 var func;
14465 CSL.Util.substituteStart.call(this, state, target);
14466 //
14467 // This should push a rangeable object to the queue.
14468 //
14469 if (this.strings.form === "roman") {
14470 this.formatter = state.fun.romanizer;
14471 } else if (this.strings.form === "ordinal") {
14472 this.formatter = state.fun.ordinalizer;
14473 } else if (this.strings.form === "long-ordinal") {
14474 this.formatter = state.fun.long_ordinalizer;
14475 }
14476 if ("undefined" === typeof this.successor_prefix) {
14477 this.successor_prefix = state[state.build.area].opt.layout_delimiter;
14478 }
14479 if ("undefined" === typeof this.splice_prefix) {
14480 this.splice_prefix = state[state.build.area].opt.layout_delimiter;
14481 }
14482 // is this needed?
14483 //if ("undefined" === typeof this.splice_prefix){
14484 // this.splice_prefix = state[state.tmp.area].opt.layout_delimiter;
14485 //}
14486 //
14487 // Whether we actually stick a number object on
14488 // the output queue depends on whether the field
14489 // contains a pure number.
14490 //
14491 // push number or text
14492 func = function (state, Item, item) {
14493 // NOTE: this works because this is the ONLY function in this node.
14494 // If further functions are added, they need to start with the same
14495 // abort condition.
14496 if (this.variables.length === 0) {
14497 return;
14498 }
14499 var varname;
14500 varname = this.variables[0];
14501 if ("undefined" === typeof item) {
14502 var item = {};
14503 }
14504 if (["locator", "locator-extra"].indexOf(varname) > -1) {
14505 if (state.tmp.just_looking) {
14506 return;
14507 }
14508 if (!item[varname]) {
14509 return;
14510 }
14511 } else {
14512 if (!Item[varname]) {
14513 return;
14514 }
14515 }
14516
14517 if (varname === 'collection-number' && Item.type === 'legal_case') {
14518 state.tmp.renders_collection_number = true;
14519 }
14520
14521 // For bill or legislation items that have a label-form
14522 // attribute set on the cs:number node rendering the locator,
14523 // the form and pluralism of locator terms are controlled
14524 // separately from those of the initial label. Form is
14525 // straightforward: the label uses the value set on
14526 // the cs:label node that renders it, and the embedded
14527 // labels use the value of label-form set on the cs:number
14528 // node. Both default to "long".
14529 //
14530 // Pluralism is more complicated. For embedded labels,
14531 // pluralism is evaluated using a simple heuristic that
14532 // can be found below (it just looks for comma, ampersand etc).
14533 // The item.label rendered independently via cs:label
14534 // defaults to singular. It is always singular if embedded
14535 // labels exist that (when expanded to their valid CSL
14536 // value) do not match the value of item.label. Otherwise,
14537 // if one or more matching embedded labels exist, the
14538 // cs:label is set to plural.
14539 //
14540 // The code that does all this is divided between this module,
14541 // util_static_locator.js, and util_label.js. It's not easy
14542 // to follow, but seems to do the job. Let's home for good
14543 // luck out there in the wild.
14544
14545 var node = this;
14546
14547 if (state.tmp.group_context.tip.force_suppress) {
14548 return false;
14549 }
14550
14551 if (["locator", "locator-extra"].indexOf(varname) > -1) {
14552 // amazing that we reach this. should abort sooner if no content?
14553 state.processNumber.call(state, node, item, varname, Item.type);
14554 } else {
14555 if (!state.tmp.group_context.tip.condition) {
14556 state.tmp.just_did_number = true;
14557 }
14558 state.processNumber.call(state, node, Item, varname, Item.type);
14559 }
14560
14561 CSL.Util.outputNumericField(state, varname, Item.id);
14562
14563 if (["locator", "locator-extra"].indexOf(this.variables_real[0]) > -1
14564 && !state.tmp.just_looking) {
14565 state.tmp.done_vars.push(this.variables_real[0]);
14566 state.tmp.group_context.tip.done_vars.push(this.variables_real[0]);
14567 }
14568 };
14569 this.execs.push(func);
14570 target.push(this);
14571
14572 CSL.Util.substituteEnd.call(this, state, target);
14573 }
14574};
14575
14576/*global CSL: true */
14577
14578/*
14579 * Yikes, these functions were running out of scope for yonks.
14580 * now that they are set in the correct token list,
14581 * they might be useful for things.
14582 * FB 2013.11.09
14583*/
14584
14585CSL.Node.sort = {
14586 build: function (state, target) {
14587 target = state[state.build.root + "_sort"].tokens;
14588 if (this.tokentype === CSL.START) {
14589 if (state.build.area === "citation") {
14590 state.parallel.use_parallels = false;
14591 state.opt.sort_citations = true;
14592 }
14593 state.build.area = state.build.root + "_sort";
14594 state.build.extension = "_sort";
14595
14596 var func = function (state, Item) {
14597 //state.tmp.area = state.tmp.root + "_sort";
14598 //state.tmp.extension = "_sort";
14599 if (state.opt.has_layout_locale) {
14600 var langspec = CSL.localeResolve(Item.language, state.opt["default-locale"][0]);
14601 var sort_locales = state[state.tmp.area.slice(0,-5)].opt.sort_locales;
14602 var langForItem;
14603 for (var i=0,ilen=sort_locales.length;i<ilen;i+=1) {
14604 langForItem = sort_locales[i][langspec.bare];
14605 if (!langForItem) {
14606 langForItem = sort_locales[i][langspec.best];
14607 }
14608 if (langForItem) {
14609 break;
14610 }
14611 }
14612 if (!langForItem) {
14613 langForItem = state.opt["default-locale"][0];
14614 }
14615 state.tmp.lang_sort_hold = state.opt.lang;
14616 state.opt.lang = langForItem;
14617 }
14618 };
14619 this.execs.push(func);
14620
14621 }
14622 if (this.tokentype === CSL.END) {
14623 state.build.area = state.build.root;
14624 state.build.extension = "";
14625 var func = function (state) {
14626 if (state.opt.has_layout_locale) {
14627 state.opt.lang = state.tmp.lang_sort_hold;
14628 delete state.tmp.lang_sort_hold;
14629 }
14630 //state.tmp.area = state.tmp.root;
14631 //state.tmp.extension = "";
14632 };
14633 this.execs.push(func);
14634 /*
14635 var func = function (state, Item) {
14636 state.tmp.area = state.tmp.root;
14637 state.tmp.extension = "";
14638 }
14639 this.execs.push(func);
14640 */
14641 }
14642 target.push(this);
14643 }
14644};
14645
14646
14647
14648/*global CSL: true */
14649
14650CSL.Node.substitute = {
14651 build: function (state, target) {
14652 var func;
14653 if (this.tokentype === CSL.START) {
14654 /* */
14655 // set conditional
14656 var choose_start = new CSL.Token("choose", CSL.START);
14657 CSL.Node.choose.build.call(choose_start, state, target);
14658 var if_singleton = new CSL.Token("if", CSL.SINGLETON);
14659 func = function() {
14660 if (state.tmp.value.length && !state.tmp.common_term_match_fail) {
14661 return true;
14662 }
14663 return false;
14664 }
14665 if_singleton.tests = [func];
14666 if_singleton.test = state.fun.match.any(if_singleton, state, if_singleton.tests);
14667 target.push(if_singleton);
14668
14669 func = function (state) {
14670 state.tmp.can_block_substitute = true;
14671 if (state.tmp.value.length && !state.tmp.common_term_match_fail) {
14672 state.tmp.can_substitute.replace(false, CSL.LITERAL);
14673 }
14674 state.tmp.common_term_match_fail = false;
14675 };
14676 this.execs.push(func);
14677 target.push(this);
14678 /* */
14679 }
14680 if (this.tokentype === CSL.END) {
14681 //var if_end = new CSL.Token("if", CSL.END);
14682 //CSL.Node["if"].build.call(if_end, state, target);
14683 /* */
14684 target.push(this);
14685 var choose_end = new CSL.Token("choose", CSL.END);
14686 CSL.Node.choose.build.call(choose_end, state, target);
14687 /* */
14688 }
14689 }
14690};
14691
14692
14693
14694/*global CSL: true */
14695
14696CSL.Node.text = {
14697 build: function (state, target) {
14698 var func, form, plural, id, num, number, formatter, firstoutput, specialdelimiter, label, suffix, term;
14699 if (this.postponed_macro) {
14700 var group_start = CSL.Util.cloneToken(this);
14701 group_start.name = "group";
14702 group_start.tokentype = CSL.START;
14703 CSL.Node.group.build.call(group_start, state, target);
14704
14705 CSL.expandMacro.call(state, this, target);
14706
14707 var group_end = CSL.Util.cloneToken(this);
14708 group_end.name = "group";
14709 group_end.tokentype = CSL.END;
14710 if (this.postponed_macro === 'juris-locator-label') {
14711 group_end.isJurisLocatorLabel = true;
14712 }
14713 CSL.Node.group.build.call(group_end, state, target);
14714
14715 } else {
14716 CSL.Util.substituteStart.call(this, state, target);
14717 // ...
14718 //
14719 // Do non-macro stuff
14720
14721 // Guess again. this.variables is ephemeral, adjusted by an initial
14722 // function set on the node via @variable attribute setup.
14723 //variable = this.variables[0];
14724
14725 if (!this.variables_real) {
14726 this.variables_real = [];
14727 }
14728 if (!this.variables) {
14729 this.variables = [];
14730 }
14731
14732 form = "long";
14733 plural = 0;
14734 if (this.strings.form) {
14735 form = this.strings.form;
14736 }
14737 if (this.strings.plural) {
14738 plural = this.strings.plural;
14739 }
14740 if ("citation-number" === this.variables_real[0] || "year-suffix" === this.variables_real[0] || "citation-label" === this.variables_real[0]) {
14741 //
14742 // citation-number and year-suffix are super special,
14743 // because they are rangeables, and require a completely
14744 // different set of formatting parameters on the output
14745 // queue.
14746 if (this.variables_real[0] === "citation-number") {
14747
14748 if (state.build.root === "citation") {
14749 state.opt.update_mode = CSL.NUMERIC;
14750 }
14751 if (state.build.root === "bibliography") {
14752 state.opt.bib_mode = CSL.NUMERIC;
14753 }
14754 //this.strings.is_rangeable = true;
14755 if ("citation-number" === state[state.tmp.area].opt.collapse) {
14756 this.range_prefix = state.getTerm("citation-range-delimiter");
14757 }
14758 this.successor_prefix = state[state.build.area].opt.layout_delimiter;
14759 this.splice_prefix = state[state.build.area].opt.layout_delimiter;
14760 func = function (state, Item, item) {
14761
14762 id = "" + Item.id;
14763 if (!state.tmp.just_looking) {
14764 if (state.tmp.area.slice(-5) === "_sort" && this.variables[0] === "citation-number") {
14765 if (state.tmp.area === "bibliography_sort") {
14766 state.tmp.group_context.tip.done_vars.push("citation-number");
14767 }
14768 if (state.tmp.area === "citation_sort" && state.bibliography_sort.tmp.citation_number_map) {
14769 var num = state.bibliography_sort.tmp.citation_number_map[state.registry.registry[Item.id].seq];
14770 } else {
14771 var num = state.registry.registry[Item.id].seq;
14772 }
14773 if (num) {
14774 // Code currently in util_number.js
14775 num = CSL.Util.padding("" + num);
14776 }
14777 state.output.append(num, this);
14778 return;
14779 }
14780 if (item && item["author-only"]) {
14781 state.tmp.element_trace.replace("suppress-me");
14782 }
14783 if (state.tmp.area !== "bibliography_sort" && state.bibliography_sort.tmp.citation_number_map && state.bibliography_sort.opt.citation_number_sort_direction === CSL.DESCENDING) {
14784 num = state.bibliography_sort.tmp.citation_number_map[state.registry.registry[id].seq];
14785 } else {
14786 num = state.registry.registry[id].seq;
14787 }
14788 if (state.opt.citation_number_slug) {
14789 state.output.append(state.opt.citation_number_slug, this);
14790 } else {
14791 number = new CSL.NumericBlob(false, num, this, Item.id);
14792 if (state.tmp.in_cite_predecessor) {
14793 number.suppress_splice_prefix = true;
14794 }
14795 state.output.append(number, "literal");
14796 }
14797 }
14798 };
14799 this.execs.push(func);
14800 } else if (this.variables_real[0] === "year-suffix") {
14801
14802 state.opt.has_year_suffix = true;
14803
14804 if (state[state.tmp.area].opt.collapse === "year-suffix-ranged") {
14805 //this.range_prefix = "-";
14806 this.range_prefix = state.getTerm("citation-range-delimiter");
14807 }
14808 this.successor_prefix = state[state.build.area].opt.layout_delimiter;
14809 if (state[state.tmp.area].opt["year-suffix-delimiter"]) {
14810 this.successor_prefix = state[state.build.area].opt["year-suffix-delimiter"];
14811 }
14812 func = function (state, Item) {
14813 if (state.registry.registry[Item.id] && state.registry.registry[Item.id].disambig.year_suffix !== false && !state.tmp.just_looking) {
14814 //state.output.append(state.registry.registry[Item.id].disambig[2],this);
14815 num = parseInt(state.registry.registry[Item.id].disambig.year_suffix, 10);
14816
14817 //if (state[state.tmp.area].opt.collapse === "year-suffix-ranged") {
14818 // //this.range_prefix = "-";
14819 // this.range_prefix = state.getTerm("citation-range-delimiter");
14820 //}
14821 //this.successor_prefix = state[state.tmp.area].opt.layout_delimiter;
14822 if (state[state.tmp.area].opt.cite_group_delimiter) {
14823 this.successor_prefix = state[state.tmp.area].opt.cite_group_delimiter;
14824 }
14825 number = new CSL.NumericBlob(false, num, this, Item.id);
14826 formatter = new CSL.Util.Suffixator(CSL.SUFFIX_CHARS);
14827 number.setFormatter(formatter);
14828 state.output.append(number, "literal");
14829 firstoutput = false;
14830 // XXX Can we do something better for length here?
14831 for (var i=0,ilen=state.tmp.group_context.mystack.length; i<ilen; i++) {
14832 var flags = state.tmp.group_context.mystack[i];
14833 if (!flags.variable_success && (flags.variable_attempt || (!flags.variable_attempt && !flags.term_intended))) {
14834 firstoutput = true;
14835 break;
14836 }
14837 }
14838 specialdelimiter = state[state.tmp.area].opt["year-suffix-delimiter"];
14839 if (firstoutput && specialdelimiter && !state.tmp.sort_key_flag) {
14840 state.tmp.splice_delimiter = state[state.tmp.area].opt["year-suffix-delimiter"];
14841 }
14842 }
14843 };
14844 this.execs.push(func);
14845 } else if (this.variables_real[0] === "citation-label") {
14846 if (state.build.root === "bibliography") {
14847 state.opt.bib_mode = CSL.TRIGRAPH;
14848 }
14849 state.opt.has_year_suffix = true;
14850 func = function (state, Item) {
14851 label = Item["citation-label"];
14852 if (!label) {
14853 label = state.getCitationLabel(Item);
14854 }
14855 if (!state.tmp.just_looking) {
14856 suffix = "";
14857 if (state.registry.registry[Item.id] && state.registry.registry[Item.id].disambig.year_suffix !== false) {
14858 num = parseInt(state.registry.registry[Item.id].disambig.year_suffix, 10);
14859 suffix = state.fun.suffixator.format(num);
14860 }
14861 label += suffix;
14862 }
14863 state.output.append(label, this);
14864 };
14865 this.execs.push(func);
14866 }
14867 } else {
14868 if (this.strings.term) {
14869
14870 // printterm
14871 func = function (state, Item) {
14872 var gender = state.opt.gender[Item.type];
14873 var term = this.strings.term;
14874 term = state.getTerm(term, form, plural, gender, CSL.TOLERANT, this.default_locale);
14875 var myterm;
14876 // if the term is not an empty string, say
14877 // that we rendered a term
14878 if (term !== "") {
14879 state.tmp.group_context.tip.term_intended = true;
14880 }
14881 CSL.UPDATE_GROUP_CONTEXT_CONDITION(state, term);
14882
14883 // capitalize the first letter of a term, if it is the
14884 // first thing rendered in a citation (or if it is
14885 // being rendered immediately after terminal punctuation,
14886 // I guess, actually).
14887 if (!state.tmp.term_predecessor && !(state.opt["class"] === "in-text" && state.tmp.area === "citation")) {
14888 myterm = CSL.Output.Formatters["capitalize-first"](state, term);
14889 //CSL.debug("Capitalize");
14890 } else {
14891 myterm = term;
14892 }
14893
14894 // XXXXX Cut-and-paste code in multiple locations. This code block should be
14895 // collected in a function.
14896 // Tag: strip-periods-block
14897 if (state.tmp.strip_periods) {
14898 myterm = myterm.replace(/\./g, "");
14899 } else {
14900 for (var i = 0, ilen = this.decorations.length; i < ilen; i += 1) {
14901 if ("@strip-periods" === this.decorations[i][0] && "true" === this.decorations[i][1]) {
14902 myterm = myterm.replace(/\./g, "");
14903 break;
14904 }
14905 }
14906 }
14907 state.output.append(myterm, this);
14908 };
14909 this.execs.push(func);
14910 state.build.term = false;
14911 state.build.form = false;
14912 state.build.plural = false;
14913 } else if (this.variables_real.length) {
14914 func = function (state, Item) {
14915
14916 // If some text variable is rendered, we're not collapsing.
14917 if (this.variables_real[0] !== "locator") {
14918 state.tmp.have_collapsed = false;
14919 }
14920
14921 if (!state.tmp.group_context.tip.condition && Item[this.variables[0]]) {
14922 state.tmp.just_did_number = false;
14923 }
14924 var val = Item[this.variables[0]];
14925 if (val && !state.tmp.group_context.tip.condition) {
14926 if (("" + val).slice(-1).match(/[0-9]/)) {
14927 state.tmp.just_did_number = true;
14928 } else {
14929 state.tmp.just_did_number = false;
14930 }
14931 }
14932 };
14933 this.execs.push(func);
14934
14935 // plain string fields
14936
14937 // Deal with multi-fields and ordinary fields separately.
14938 if (CSL.MULTI_FIELDS.indexOf(this.variables_real[0]) > -1
14939 || ["language-name", "language-name-original"].indexOf(this.variables_real[0]) > -1) {
14940
14941 // multi-fields
14942 // Initialize transform factory according to whether
14943 // abbreviation is desired.
14944 var abbrevfam = this.variables[0];
14945 var abbrfall = false;
14946 var altvar = false;
14947 var transfall = false;
14948 if (form === "short") {
14949 if (this.variables_real[0].slice(-6) !== "-short") {
14950 altvar = this.variables_real[0] + "-short";
14951 }
14952 } else {
14953 abbrevfam = false;
14954 }
14955 if (state.build.extension) {
14956 // multi-fields for sorting get a sort transform,
14957 // (abbreviated if the short form was selected)
14958 transfall = true;
14959 } else {
14960 transfall = true;
14961 abbrfall = true;
14962 }
14963
14964 func = state.transform.getOutputFunction(this.variables, abbrevfam, abbrfall, altvar, transfall);
14965 } else {
14966 // ordinary fields
14967 if (CSL.CITE_FIELDS.indexOf(this.variables_real[0]) > -1) {
14968 // per-cite fields are read from item, rather than Item
14969 func = function (state, Item, item) {
14970 if (item && item[this.variables[0]]) {
14971 // Code copied to page variable as well; both
14972 // become cs:number in MLZ extended schema
14973
14974 // If locator, use cs:number. Otherwise, render
14975 // normally.
14976
14977 // XXX The code below is pretty-much copied from
14978 // XXX node_number.js. Should be a common function.
14979 // XXX BEGIN
14980 state.processNumber(this, item, this.variables[0], Item.type);
14981 CSL.Util.outputNumericField(state, this.variables[0], Item.id);
14982 // XXX END
14983
14984 if (["locator", "locator-extra"].indexOf(this.variables_real[0]) > -1
14985 && !state.tmp.just_looking) {
14986 state.tmp.done_vars.push(this.variables_real[0]);
14987 }
14988 }
14989 };
14990 } else if (["page", "page-first", "chapter-number", "collection-number", "edition", "issue", "number", "number-of-pages", "number-of-volumes", "volume"].indexOf(this.variables_real[0]) > -1) {
14991 // page gets mangled with the correct collapsing
14992 // algorithm
14993 func = function(state, Item) {
14994 state.processNumber(this, Item, this.variables[0], Item.type);
14995 CSL.Util.outputNumericField(state, this.variables[0], Item.id);
14996 };
14997 } else if (["URL", "DOI"].indexOf(this.variables_real[0]) > -1) {
14998 func = function (state, Item) {
14999 var value;
15000 if (this.variables[0]) {
15001 value = state.getVariable(Item, this.variables[0], form);
15002 if (value) {
15003 if (this.variables[0] === "URL" && form === "short") {
15004 value = value.replace(/(.*\.[^\/]+)\/.*/, "$1");
15005 if (value.match(/\/\/www\./)) {
15006 value = value.replace(/https?:\/\//, "");
15007 }
15008 }
15009 // true is for non-suppression of periods
15010 if (state.opt.development_extensions.wrap_url_and_doi) {
15011 if (!this.decorations.length || this.decorations[0][0] !== "@" + this.variables[0]) {
15012 // Special-casing to fix https://github.com/Juris-M/citeproc-js/issues/57
15013 // clone current token, to avoid collateral damage
15014 var clonetoken = CSL.Util.cloneToken(this);
15015 // cast a group blob
15016 var groupblob = new CSL.Blob(null, null, "url-wrapper");
15017 // set the DOI decoration on the blob
15018 groupblob.decorations.push(["@DOI", "true"]);
15019 if (this.variables_real[0] === "DOI") {
15020 // strip a proper DOI prefix
15021 var prefix;
15022 if (this.strings.prefix && this.strings.prefix.match(/^.*https:\/\/doi\.org\/$/)) {
15023 value = value.replace(/^https?:\/\/doi\.org\//, "");
15024 if (value.match(/^https?:\/\//)) {
15025 // Do not tamper with another protocol + domain if already set in field value
15026 prefix = "";
15027 } else {
15028 // Otherwise https + domain
15029 prefix = "https://doi.org/";
15030 }
15031 // set any string prefix on the clone
15032 clonetoken.strings.prefix = this.strings.prefix.slice(0, clonetoken.strings.prefix.length-16);
15033 }
15034 // cast a text blob
15035 // set the prefix as the content of the blob
15036 var prefixblob = new CSL.Blob(prefix);
15037 // cast another text blob
15038 // set the value as the content of the second blob
15039 var valueblob = new CSL.Blob(value);
15040 // append new text token and clone to group token
15041 groupblob.push(prefixblob);
15042 groupblob.push(valueblob);
15043 // append group token to output
15044 state.output.append(groupblob, clonetoken, false, false, true);
15045 } else {
15046 var valueblob = new CSL.Blob(value);
15047 // append new text token and clone to group token
15048 groupblob.push(valueblob);
15049 // append group token to output
15050 //this.decorations = [["@" + this.variables[0], "true"]].concat(this.decorations);
15051 state.output.append(groupblob, clonetoken, false, false, true);
15052 }
15053 } else {
15054 state.output.append(value, this, false, false, true);
15055 }
15056 } else {
15057 // This is totally unnecessary, isn't it?
15058 if (this.decorations.length) {
15059 for (var i=this.decorations.length-1; i>-1; i--) {
15060 if (this.decorations[i][0] === "@" + this.variables[0]) {
15061 this.decorations = this.decorations.slice(0, i).concat(this.decorations.slice(i+1));
15062 }
15063 }
15064 }
15065 state.output.append(value, this, false, false, true);
15066 }
15067 }
15068 }
15069 };
15070 } else if (this.variables_real[0] === "section") {
15071 // Sections for statutes are special. This is an uncommon
15072 // variable, so we save the cost of the runtime check
15073 // unless it's being used.
15074 func = function (state, Item) {
15075 var value;
15076 value = state.getVariable(Item, this.variables[0], form);
15077 if (value) {
15078 state.output.append(value, this);
15079 }
15080 };
15081 } else if (this.variables_real[0] === "hereinafter") {
15082 func = function (state, Item) {
15083 var value = state.transform.abbrevs["default"]["hereinafter"][Item.id];
15084 if (value) {
15085 state.output.append(value, this);
15086 state.tmp.group_context.tip.variable_success = true;
15087 }
15088 };
15089 } else {
15090 // anything left over just gets output in the normal way.
15091 func = function (state, Item) {
15092 var value;
15093 if (this.variables[0]) {
15094 value = state.getVariable(Item, this.variables[0], form);
15095 if (value) {
15096 value = "" + value;
15097 value = value.split("\\").join("");
15098 state.output.append(value, this);
15099 }
15100 }
15101 };
15102 }
15103 }
15104 this.execs.push(func);
15105 } else if (this.strings.value) {
15106 // for the text value attribute.
15107 func = function (state) {
15108 state.tmp.group_context.tip.term_intended = true;
15109 // true flags that this is a literal-value term
15110 CSL.UPDATE_GROUP_CONTEXT_CONDITION(state, this.strings.value, true);
15111 state.output.append(this.strings.value, this);
15112 };
15113 this.execs.push(func);
15114 // otherwise no output
15115 }
15116 }
15117 target.push(this);
15118 CSL.Util.substituteEnd.call(this, state, target);
15119 }
15120 }
15121};
15122
15123
15124
15125/*global CSL: true */
15126
15127CSL.Node.intext = {
15128 build: function (state, target) {
15129 if (this.tokentype === CSL.START) {
15130
15131 state.build.area = "intext";
15132 state.build.root = "intext";
15133 state.build.extension = "";
15134
15135 var func = function(state, Item) {
15136 state.tmp.area = "intext";
15137 state.tmp.root = "intext";
15138 state.tmp.extension = "";
15139 }
15140 this.execs.push(func);
15141 }
15142 if (this.tokentype === CSL.END) {
15143
15144 // Do whatever cs:citation does with sorting.
15145 state.intext_sort = {
15146 opt: {
15147 sort_directions: state.citation_sort.opt.sort_directions
15148 }
15149 }
15150 state.intext.srt = state.citation.srt;
15151 }
15152 target.push(this);
15153 }
15154};
15155
15156
15157/*global CSL: true */
15158
15159CSL.Attributes = {};
15160
15161CSL.Attributes["@disambiguate"] = function (state, arg) {
15162 this.tests ? {} : this.tests = [];
15163 if (arg === "true") {
15164 state.opt.has_disambiguate = true;
15165 var func = function (Item) {
15166 if (state.tmp.area === "bibliography") {
15167 if (state.tmp.disambiguate_count < state.registry.registry[Item.id].disambig.disambiguate) {
15168 state.tmp.disambiguate_count += 1;
15169 return true;
15170 }
15171 } else {
15172 state.tmp.disambiguate_maxMax += 1;
15173 if (state.tmp.disambig_settings.disambiguate
15174 && state.tmp.disambiguate_count < state.tmp.disambig_settings.disambiguate) {
15175 state.tmp.disambiguate_count += 1;
15176 return true;
15177 }
15178 }
15179 return false;
15180 };
15181 this.tests.push(func);
15182 } else if (arg === "check-ambiguity-and-backreference") {
15183 var func = function (Item) {
15184 if (state.registry.registry[Item.id].disambig.disambiguate && state.registry.registry[Item.id]["citation-count"] > 1) {
15185 return true;
15186 }
15187 return false;
15188 };
15189 this.tests.push(func);
15190 }
15191};
15192
15193CSL.Attributes["@is-numeric"] = function (state, arg) {
15194 this.tests ? {} : this.tests = [];
15195 var variables = arg.split(/\s+/);
15196 var maketest = function(variable) {
15197 return function (Item, item) {
15198 var myitem = Item;
15199 if (item && ["locator","locator-extra"].indexOf(variable) > -1) {
15200 myitem = item;
15201 }
15202 if (!myitem[variable]) {
15203 return false;
15204 }
15205 if (CSL.NUMERIC_VARIABLES.indexOf(variable) > -1) {
15206 if (!state.tmp.shadow_numbers[variable]) {
15207 state.processNumber(false, myitem, variable, Item.type);
15208 }
15209 if (state.tmp.shadow_numbers[variable].numeric) {
15210 return true;
15211 }
15212 } else if (["title", "locator-extra","version"].indexOf(variable) > -1) {
15213 if (myitem[variable].slice(-1) === "" + parseInt(myitem[variable].slice(-1), 10)) {
15214 return true;
15215 }
15216 }
15217 return false;
15218 };
15219 };
15220 for (var i=0; i<variables.length; i+=1) {
15221 this.tests.push(maketest(variables[i]));
15222 }
15223};
15224
15225
15226CSL.Attributes["@is-uncertain-date"] = function (state, arg) {
15227 this.tests ? {} : this.tests = [];
15228 var variables = arg.split(/\s+/);
15229 // Strip off any boolean prefix.
15230 var maketest = function (myvariable) {
15231 return function(Item) {
15232 if (Item[myvariable] && Item[myvariable].circa) {
15233 return true;
15234 } else {
15235 return false;
15236 }
15237 };
15238 };
15239 for (var i=0,ilen=variables.length;i<ilen;i+=1) {
15240 this.tests.push(maketest(variables[i]));
15241 }
15242};
15243
15244
15245CSL.Attributes["@locator"] = function (state, arg) {
15246 this.tests ? {} : this.tests = [];
15247 var trylabels = arg.replace("sub verbo", "sub-verbo");
15248 trylabels = trylabels.split(/\s+/);
15249 // Strip off any boolean prefix.
15250 var maketest = function (trylabel) {
15251 return function(Item, item) {
15252 var label;
15253 state.processNumber(false, item, "locator");
15254 label = state.tmp.shadow_numbers.locator.label;
15255 if (trylabel === label) {
15256 return true;
15257 } else {
15258 return false;
15259 }
15260 };
15261 };
15262 for (var i=0,ilen=trylabels.length;i<ilen;i+=1) {
15263 this.tests.push(maketest(trylabels[i]));
15264 }
15265};
15266
15267
15268CSL.Attributes["@position"] = function (state, arg) {
15269 this.tests ? {} : this.tests = [];
15270 var tryposition;
15271 state.opt.update_mode = CSL.POSITION;
15272 state.parallel.use_parallels = null;
15273 var trypositions = arg.split(/\s+/);
15274 var testSubsequentNear = function (Item, item) {
15275 if (item && item.position >= CSL.POSITION_SUBSEQUENT && item["near-note"]) {
15276 return true;
15277 }
15278 return false;
15279 };
15280 var testSubsequentNotNear = function (Item, item) {
15281 if (item && item.position == CSL.POSITION_SUBSEQUENT && !item["near-note"]) {
15282 return true;
15283 }
15284 return false;
15285 };
15286 var maketest = function(tryposition) {
15287 return function (Item, item) {
15288 if (state.tmp.area === "bibliography") {
15289 return false;
15290 }
15291 if (item && "undefined" === typeof item.position) {
15292 item.position = 0;
15293 }
15294 if (item && typeof item.position === "number") {
15295 if (item.position === 0 && tryposition === 0) {
15296 return true;
15297 } else if (tryposition > 0 && item.position >= tryposition) {
15298 return true;
15299 }
15300 } else if (tryposition === 0) {
15301 return true;
15302 }
15303 return false;
15304 };
15305 };
15306 for (var i=0,ilen=trypositions.length;i<ilen;i+=1) {
15307 var tryposition = trypositions[i];
15308 if (tryposition === "first") {
15309 tryposition = CSL.POSITION_FIRST;
15310 } else if (tryposition === "subsequent") {
15311 tryposition = CSL.POSITION_SUBSEQUENT;
15312 } else if (tryposition === "ibid") {
15313 tryposition = CSL.POSITION_IBID;
15314 } else if (tryposition === "ibid-with-locator") {
15315 tryposition = CSL.POSITION_IBID_WITH_LOCATOR;
15316 }
15317 if ("near-note" === tryposition) {
15318 this.tests.push(testSubsequentNear);
15319 } else if ("far-note" === tryposition) {
15320 this.tests.push(testSubsequentNotNear);
15321 } else {
15322 this.tests.push(maketest(tryposition));
15323 }
15324 }
15325};
15326
15327CSL.Attributes["@type"] = function (state, arg) {
15328 this.tests ? {} : this.tests = [];
15329 // XXX This is ALWAYS composed as an "any" match
15330 var types = arg.split(/\s+/);
15331 // Strip off any boolean prefix.
15332 var maketest = function (mytype) {
15333 return function(Item) {
15334 var ret = (Item.type === mytype);
15335 if (ret) {
15336 return true;
15337 } else {
15338 return false;
15339 }
15340 };
15341 };
15342 var tests = [];
15343 for (var i=0,ilen=types.length;i<ilen;i+=1) {
15344 tests.push(maketest(types[i]));
15345 }
15346 this.tests.push(state.fun.match.any(this, state, tests));
15347};
15348
15349CSL.Attributes["@variable"] = function (state, arg) {
15350 this.tests ? {} : this.tests = [];
15351 var func;
15352 this.variables = arg.split(/\s+/);
15353 this.variables_real = this.variables.slice();
15354
15355 // First the non-conditional code.
15356 if ("label" === this.name && this.variables[0]) {
15357 this.strings.term = this.variables[0];
15358 } else if (["names", "date", "text", "number"].indexOf(this.name) > -1) {
15359 //
15360 // An oddity of variable handling is that this.variables
15361 // is actually ephemeral; the full list of variables is
15362 // held in the variables_real var, and pushed into this.variables
15363 // conditionally in order to suppress repeat renderings of
15364 // the same item variable. [STILL FUNCTIONAL? 2010.01.15]
15365 //
15366 // set variable names
15367 func = function (state, Item, item) {
15368 // Clear this.variables in place
15369 for (var i = this.variables.length - 1; i > -1; i += -1) {
15370 this.variables.pop();
15371 }
15372 for (var i=0,ilen=this.variables_real.length;i<ilen;i++) {
15373 // set variable name if not quashed, and if not the title of a legal case w/suppress-author
15374 if (state.tmp.done_vars.indexOf(this.variables_real[i]) === -1
15375// This looks nuts. Why suppress a case name if not required by context?
15376// && !(item && Item.type === "legal_case" && item["suppress-author"] && this.variables_real[i] === "title")
15377 ) {
15378 this.variables.push(this.variables_real[i]);
15379 }
15380 if (state.tmp.can_block_substitute) {
15381 state.tmp.done_vars.push(this.variables_real[i]);
15382 }
15383 }
15384 };
15385 this.execs.push(func);
15386
15387 // check for output
15388 func = function (state, Item, item) {
15389 var output = false;
15390 for (var i=0,ilen=this.variables.length;i<ilen;i++) {
15391 var variable = this.variables[i];
15392 if (["authority", "committee"].indexOf(variable) > -1
15393 && "string" === typeof Item[variable]
15394 && "names" === this.name) {
15395
15396 // Great! So for each of these, we split.
15397 // And we only recombine everything if the length
15398 // of all the splits matches.
15399
15400 // Preflight
15401 var isValid = true;
15402 var rawNames = Item[variable].split(/\s*;\s*/);
15403 var rawMultiNames = {};
15404 if (Item.multi && Item.multi._keys[variable]) {
15405 for (var langTag in Item.multi._keys[variable]) {
15406 rawMultiNames[langTag] = Item.multi._keys[variable][langTag].split(/\s*;\s*/);
15407 if (rawMultiNames[langTag].length !== rawNames.length) {
15408 isValid = false;
15409 break;
15410 }
15411 }
15412 }
15413 if (!isValid) {
15414 rawNames = [Item[variable]];
15415 rawMultiNames = Item.multi._keys[variable];
15416 }
15417 for (var j = 0, jlen = rawNames.length; j < jlen; j++) {
15418 var creatorParent = {
15419 literal:rawNames[j],
15420 multi:{
15421 _key:{}
15422 }
15423 };
15424 for (var langTag in rawMultiNames) {
15425 var creatorChild = {
15426 literal:rawMultiNames[langTag][j]
15427 };
15428 creatorParent.multi._key[langTag] = creatorChild;
15429 }
15430 rawNames[j] = creatorParent;
15431 }
15432 Item[variable] = rawNames;
15433 }
15434 if (this.strings.form === "short" && !Item[variable]) {
15435 if (variable === "title") {
15436 variable = "title-short";
15437 } else if (variable === "container-title") {
15438 variable = "container-title-short";
15439 }
15440 }
15441 if (variable === "year-suffix") {
15442 // year-suffix always signals that it produces output,
15443 // even when it doesn't. This permits it to be used with
15444 // the "no date" term inside a group used exclusively
15445 // to control formatting.
15446 output = true;
15447 break;
15448 } else if (CSL.DATE_VARIABLES.indexOf(variable) > -1) {
15449 if (state.opt.development_extensions.locator_date_and_revision && "locator-date" === variable) {
15450 // If locator-date is set, it's valid.
15451 output = true;
15452 break;
15453 }
15454 if (Item[variable]) {
15455 for (var key in Item[variable]) {
15456 if (this.dateparts.indexOf(key) === -1 && "literal" !== key) {
15457 continue;
15458 }
15459 if (Item[variable][key]) {
15460 output = true;
15461 break;
15462 }
15463 }
15464 if (output) {
15465 break;
15466 }
15467 }
15468 } else if ("locator" === variable) {
15469 if (item && item.locator) {
15470 output = true;
15471 }
15472 break;
15473 } else if ("locator-extra" === variable) {
15474 if (item && item["locator-extra"]) {
15475 output = true;
15476 }
15477 break;
15478 } else if (["citation-number","citation-label"].indexOf(variable) > -1) {
15479 output = true;
15480 break;
15481 } else if ("first-reference-note-number" === variable) {
15482 if (item && item["first-reference-note-number"]) {
15483 output = true;
15484 }
15485 break;
15486 } else if ("hereinafter" === variable) {
15487 if (state.transform.abbrevs["default"].hereinafter[Item.id]
15488 && state.sys.getAbbreviation
15489 && Item.id) {
15490
15491 output = true;
15492 }
15493 break;
15494 } else if ("object" === typeof Item[variable]) {
15495 break;
15496 } else if ("string" === typeof Item[variable] && Item[variable]) {
15497 output = true;
15498 break;
15499 } else if ("number" === typeof Item[variable]) {
15500 output = true;
15501 break;
15502 }
15503 if (output) {
15504 break;
15505 }
15506 }
15507 //print("-- VAR: "+variable);
15508 //flag = state.tmp.group_context.tip;
15509 if (output) {
15510 for (var i=0,ilen=this.variables_real.length;i<ilen;i++) {
15511 var variable = this.variables_real[i];
15512 if (variable !== "citation-number" || state.tmp.area !== "bibliography") {
15513 state.tmp.cite_renders_content = true;
15514 }
15515 //print(" setting [2] to true based on: " + arg);
15516 state.tmp.group_context.tip.variable_success = true;
15517 // For util_substitute.js, subsequent-author-substitute
15518 if (state.tmp.can_substitute.value()
15519 && state.tmp.area === "bibliography"
15520 && "string" === typeof Item[variable]) {
15521
15522 state.tmp.name_node.top = state.output.current.value();
15523 state.tmp.rendered_name.push(Item[variable]);
15524 }
15525 }
15526 state.tmp.can_substitute.replace(false, CSL.LITERAL);
15527 } else {
15528 //print(" setting [1] to true based on: " + arg);
15529 state.tmp.group_context.tip.variable_attempt = true;
15530 }
15531 //state.tmp.group_context.replace(flag);
15532 };
15533 this.execs.push(func);
15534 } else if (["if", "else-if", "condition"].indexOf(this.name) > -1) {
15535 // Strip off any boolean prefix.
15536 // Now the conditionals.
15537 var maketest = function (variable) {
15538 return function(Item,item){
15539 var myitem = Item;
15540 if (item && ["locator", "locator-extra", "first-reference-note-number", "locator-date"].indexOf(variable) > -1) {
15541 myitem = item;
15542 }
15543 // We don't run loadAbbreviation() here; it is run by the application-supplied
15544 // retrieveItem() if hereinafter functionality is to be used, so this key will
15545 // always exist in memory, possibly with a nil value.
15546 if (variable === "hereinafter" && state.sys.getAbbreviation && myitem.id) {
15547 if (state.transform.abbrevs["default"].hereinafter[myitem.id]) {
15548 return true;
15549 }
15550 } else if (myitem[variable]) {
15551 if ("number" === typeof myitem[variable] || "string" === typeof myitem[variable]) {
15552 return true;
15553 } else if ("object" === typeof myitem[variable]) {
15554 //
15555 // this will turn true only for hash objects
15556 // that have at least one attribute, or for a
15557 // non-zero-length list
15558 //
15559 for (var key in myitem[variable]) {
15560 if (myitem[variable][key]) {
15561 return true;
15562 }
15563 }
15564 }
15565 }
15566 return false;
15567 };
15568 };
15569 for (var i=0,ilen=this.variables.length;i<ilen;i+=1) {
15570 this.tests.push(maketest(this.variables[i]));
15571 }
15572 }
15573};
15574
15575
15576CSL.Attributes["@page"] = function (state, arg) {
15577 this.tests ? {} : this.tests = [];
15578 var trylabels = arg.replace("sub verbo", "sub-verbo");
15579 trylabels = trylabels.split(/\s+/);
15580 // Strip off any boolean prefix.
15581 var maketest = function (trylabel) {
15582 return function(Item) {
15583 var label;
15584 state.processNumber(false, Item, "page", Item.type);
15585 if (!state.tmp.shadow_numbers.page.label) {
15586 label = "page";
15587 } else if (state.tmp.shadow_numbers.page.label === "sub verbo") {
15588 label = "sub-verbo";
15589 } else {
15590 label = state.tmp.shadow_numbers.page.label;
15591 }
15592 if (trylabel === label) {
15593 return true;
15594 } else {
15595 return false;
15596 }
15597 };
15598 };
15599 for (var i=0,ilen=trylabels.length;i<ilen;i+=1) {
15600 this.tests.push(maketest(trylabels[i]));
15601 }
15602};
15603
15604
15605// a near duplicate of code above
15606CSL.Attributes["@number"] = function (state, arg) {
15607 this.tests ? {} : this.tests = [];
15608 var trylabels = arg.split(/\s+/);
15609 var maketest = function(trylabel) {
15610 return function (Item) {
15611 var label;
15612 state.processNumber(false, Item, "number", Item.type);
15613 if (!state.tmp.shadow_numbers.number.label) {
15614 label = "number";
15615 } else {
15616 label = state.tmp.shadow_numbers.number.label;
15617 }
15618 if (trylabel === label) {
15619 return true;
15620 } else {
15621 return false;
15622 }
15623 };
15624 };
15625 for (var i=0,ilen=trylabels.length;i<ilen;i+=1) {
15626 this.tests.push(maketest(trylabels[i]));
15627 }
15628};
15629
15630CSL.Attributes["@jurisdiction"] = function (state, arg) {
15631 this.tests ? {} : this.tests = [];
15632 var tryjurisdictions = arg.split(/\s+/);
15633 // Strip off any boolean prefix.
15634 for (var i=0,ilen=tryjurisdictions.length;i<ilen;i+=1) {
15635 tryjurisdictions[i] = tryjurisdictions[i].split(":");
15636 }
15637 var maketests = function (tryjurisdiction) {
15638 return function(Item) {
15639 if (!Item.jurisdiction) {
15640 return false;
15641 }
15642 var jurisdictions = Item.jurisdiction.split(":");
15643 for (var i=0,ilen=jurisdictions.length;i<ilen;i+=1) {
15644 jurisdictions[i] = jurisdictions[i].split(":");
15645 }
15646 for (i=tryjurisdiction.length;i>0;i+=-1) {
15647 var tryjurisdictionStr = tryjurisdiction.slice(0,i).join(":");
15648 var jurisdiction = jurisdictions.slice(0,i).join(":");
15649 if (tryjurisdictionStr !== jurisdiction) {
15650 return false;
15651 }
15652 // this should be okay to enable.
15653 // break;
15654 }
15655 return true;
15656 };
15657 };
15658 for (var i=0,ilen=tryjurisdictions.length;i<ilen;i+=1) {
15659 var tryjurisdictionSlice = tryjurisdictions[i].slice();
15660 this.tests.push(maketests(tryjurisdictionSlice));
15661 }
15662};
15663
15664
15665CSL.Attributes["@context"] = function (state, arg) {
15666 this.tests ? {} : this.tests = [];
15667 var func = function () {
15668 if (["bibliography", "citation"].indexOf(arg) > -1) {
15669 var area = state.tmp.area.slice(0, arg.length);
15670 if (area === arg) {
15671 return true;
15672 }
15673 return false;
15674 } else if ("alternative" === arg) {
15675 return !!state.tmp.abort_alternative;
15676 }
15677 };
15678 this.tests.push(func);
15679};
15680
15681CSL.Attributes["@has-year-only"] = function (state, arg) {
15682 this.tests ? {} : this.tests = [];
15683 var trydates = arg.split(/\s+/);
15684 var maketest = function (trydate) {
15685 return function(Item) {
15686 var date = Item[trydate];
15687 if (!date || date.month || date.season) {
15688 return false;
15689 } else {
15690 return true;
15691 }
15692 };
15693 };
15694 for (var i=0,ilen=trydates.length;i<ilen;i+=1) {
15695 this.tests.push(maketest(trydates[i]));
15696 }
15697};
15698
15699CSL.Attributes["@has-to-month-or-season"] = function (state, arg) {
15700 this.tests ? {} : this.tests = [];
15701 var trydates = arg.split(/\s+/);
15702 var maketest = function (trydate) {
15703 return function(Item) {
15704 var date = Item[trydate];
15705 if (!date || (!date.month && !date.season) || date.day) {
15706 return false;
15707 } else {
15708 return true;
15709 }
15710 };
15711 };
15712 for (var i=0,ilen=trydates.length;i<ilen;i+=1) {
15713 this.tests.push(maketest(trydates[i]));
15714 }
15715};
15716
15717CSL.Attributes["@has-day"] = function (state, arg) {
15718 this.tests ? {} : this.tests = [];
15719 var trydates = arg.split(/\s+/);
15720 var maketest = function (trydate) {
15721 return function(Item) {
15722 var date = Item[trydate];
15723 if (!date || !date.day) {
15724 return false;
15725 } else {
15726 return true;
15727 }
15728 };
15729 };
15730 for (var i=0,ilen=trydates.length;i<ilen;i+=1) {
15731 this.tests.push(maketest(trydates[i]));
15732 }
15733};
15734
15735CSL.Attributes["@is-plural"] = function (state, arg) {
15736 this.tests ? {} : this.tests = [];
15737 var func = function (Item) {
15738 var nameList = Item[arg];
15739 if (nameList && nameList.length) {
15740 var persons = 0;
15741 var institutions = 0;
15742 var last_is_person = false;
15743 for (var i = 0, ilen = nameList.length; i < ilen; i += 1) {
15744 if (state.opt.development_extensions.spoof_institutional_affiliations
15745 && (nameList[i].literal || (nameList[i].isInstitution && nameList[i].family && !nameList[i].given))) {
15746 institutions += 1;
15747 last_is_person = false;
15748 } else {
15749 persons += 1;
15750 last_is_person = true;
15751 }
15752 }
15753 if (persons > 1) {
15754 return true;
15755 } else if (institutions > 1) {
15756 return true;
15757 } else if (institutions && last_is_person) {
15758 return true;
15759 }
15760 }
15761 return false;
15762 };
15763 this.tests.push(func);
15764};
15765
15766CSL.Attributes["@locale"] = function (state, arg) {
15767 this.tests ? {} : this.tests = [];
15768 var ret, langspec, lang, lst, i, ilen;
15769 // Style default
15770 var locale_default = state.opt["default-locale"][0];
15771
15772 if (this.name === "layout") {
15773 // For layout
15774 this.locale_raw = arg;
15775 if (this.tokentype === CSL.START) {
15776 if (!state.opt.multi_layout) {
15777 state.opt.multi_layout = [];
15778 }
15779 var locale_data = [];
15780 // Register the primary locale in the set, and others that "map" to it,
15781 // so that they can be used when generating sort keys. See node_sort.js.
15782 // Not idempotent. Only do this once.
15783 var locales = arg.split(/\s+/);
15784 var sort_locale = {};
15785 var localeMaster = CSL.localeResolve(locales[0], locale_default);
15786 locale_data.push(localeMaster);
15787 if (localeMaster.generic) {
15788 sort_locale[localeMaster.generic] = localeMaster.best;
15789 } else {
15790 sort_locale[localeMaster.best] = localeMaster.best;
15791 }
15792 for (var i=1,ilen=locales.length;i<ilen;i+=1) {
15793 var localeServant = CSL.localeResolve(locales[i], locale_default);
15794 locale_data.push(localeServant);
15795 if (localeServant.generic) {
15796 sort_locale[localeServant.generic] = localeMaster.best;
15797 } else {
15798 sort_locale[localeServant.best] = localeMaster.best;
15799 }
15800
15801 }
15802 state[state.build.area].opt.sort_locales.push(sort_locale);
15803 state.opt.multi_layout.push(locale_data);
15804 }
15805 state.opt.has_layout_locale = true;
15806 } else {
15807 // For if and if-else
15808
15809 // Split argument
15810 lst = arg.split(/\s+/);
15811
15812 // Expand each list element
15813 var locale_bares = [];
15814 for (i = 0, ilen = lst.length; i < ilen; i += 1) {
15815 // Parse out language string
15816 lang = lst[i];
15817
15818 // Analyze the locale
15819 langspec = CSL.localeResolve(lang, locale_default);
15820 if (lst[i].length === 2) {
15821 // For fallback
15822 locale_bares.push(langspec.bare);
15823 }
15824 // Load the locale terms etc.
15825 // (second argument causes immediate return if locale already exists)
15826 state.localeConfigure(langspec, true);
15827
15828 // Replace string with locale spec object
15829 lst[i] = langspec;
15830 }
15831 // Locales to test
15832 var locale_list = lst.slice();
15833
15834 // check for variable value
15835 // Closure probably not necessary here.
15836 var maketest = function (locale_list, locale_default,locale_bares) {
15837 return function (Item) {
15838 var res;
15839 ret = [];
15840 res = false;
15841 var langspec = false;
15842
15843 var lang;
15844 if (!Item.language) {
15845 lang = locale_default;
15846 } else {
15847 lang = Item.language;
15848 }
15849 langspec = CSL.localeResolve(lang, locale_default);
15850 for (i = 0, ilen = locale_list.length; i < ilen; i += 1) {
15851 if (langspec.best === locale_list[i].best) {
15852 state.tmp.condition_lang_counter_arr.push(state.tmp.condition_counter);
15853 state.tmp.condition_lang_val_arr.push(state.opt.lang);
15854 state.opt.lang = locale_list[0].best;
15855 res = true;
15856 break;
15857 }
15858 }
15859 if (!res && locale_bares.indexOf(langspec.bare) > -1) {
15860 state.tmp.condition_lang_counter_arr.push(state.tmp.condition_counter);
15861 state.tmp.condition_lang_val_arr.push(state.opt.lang);
15862 state.opt.lang = locale_list[0].best;
15863 res = true;
15864 }
15865 return res;
15866 };
15867 };
15868 this.tests.push(maketest(locale_list,locale_default,locale_bares));
15869 }
15870};
15871
15872CSL.Attributes["@alternative-node-internal"] = function (state) {
15873 this.tests ? {} : this.tests = [];
15874 var maketest = function () {
15875 return function() {
15876 return !state.tmp.abort_alternative;
15877 };
15878 };
15879 var me = this;
15880 this.tests.push(maketest(me));
15881};
15882
15883CSL.Attributes["@locale-internal"] = function (state, arg) {
15884 this.tests ? {} : this.tests = [];
15885 var langspec, lang, lst, i, ilen;
15886 // For if and if-else
15887
15888 // Split argument
15889 lst = arg.split(/\s+/);
15890
15891 // Expand each list element
15892 this.locale_bares = [];
15893 for (i = 0, ilen = lst.length; i < ilen; i += 1) {
15894 // Parse out language string
15895 lang = lst[i];
15896
15897 // Analyze the locale
15898 langspec = CSL.localeResolve(lang, state.opt["default-locale"][0]);
15899 if (lst[i].length === 2) {
15900 // For fallback
15901 this.locale_bares.push(langspec.bare);
15902 }
15903 // Load the locale terms etc.
15904 state.localeConfigure(langspec);
15905
15906 // Replace string with locale spec object
15907 lst[i] = langspec;
15908 }
15909 // Set locale tag on node
15910 this.locale_default = state.opt["default-locale"][0];
15911 // The locale to set on node children if match is successful
15912 this.locale = lst[0].best;
15913 // Locales to test
15914 this.locale_list = lst.slice();
15915
15916 // check for variable value
15917 // Closure probably not necessary here.
15918 var maketest = function (me) {
15919 return function (Item) {
15920 var ret, res;
15921 ret = [];
15922 res = false;
15923 var langspec = false;
15924 if (Item.language) {
15925 lang = Item.language;
15926 langspec = CSL.localeResolve(lang, state.opt["default-locale"][0]);
15927 if (langspec.best === state.opt["default-locale"][0]) {
15928 langspec = false;
15929 }
15930 }
15931 if (langspec) {
15932 // We attempt to match a specific locale from the
15933 // list of parameters. If that fails, we fall back
15934 // to the base locale of the first element. The
15935 // locale applied is always the first local
15936 // in the list of parameters (or base locale, for a
15937 // single two-character language code)
15938 for (i = 0, ilen = me.locale_list.length; i < ilen; i += 1) {
15939 if (langspec.best === me.locale_list[i].best) {
15940 state.opt.lang = me.locale;
15941 state.tmp.last_cite_locale = me.locale;
15942 // Set empty group open tag with locale set marker
15943 state.output.openLevel("empty");
15944 state.output.current.value().new_locale = me.locale;
15945 res = true;
15946 break;
15947 }
15948 }
15949 if (!res && me.locale_bares.indexOf(langspec.bare) > -1) {
15950 state.opt.lang = me.locale;
15951 state.tmp.last_cite_locale = me.locale;
15952 // Set empty group open tag with locale set marker
15953 state.output.openLevel("empty");
15954 state.output.current.value().new_locale = me.locale;
15955 res = true;
15956 }
15957 }
15958 return res;
15959 };
15960 };
15961 var me = this;
15962 this.tests.push(maketest(me));
15963};
15964
15965
15966// These are not evaluated as conditions immediately: they only
15967// set parameters that are picked up during processing.
15968CSL.Attributes["@is-parallel"] = function (state, arg) {
15969 this.strings.set_parallel_condition = arg;
15970};
15971CSL.Attributes["@no-repeat"] = function (state, arg) {
15972 this.strings.set_no_repeat_condition = arg.split(/\s+/);
15973};
15974
15975
15976
15977CSL.Attributes["@require"] = function (state, arg) {
15978 this.strings.require = arg;
15979
15980 // Introduced to constrain rendering of the group with a
15981 // requirement that it either render an alpha term via cs:label or
15982 // cs:text at least once, or render without any label. That
15983 // behaviour is invoked with "label-empty-or-alpha" as arg.
15984
15985 // This attribute is a complement to @label-form and modular
15986 // jurisdiction support, as it makes macros that adapt to shifting
15987 // local term definitions possible.
15988};
15989
15990CSL.Attributes["@reject"] = function (state, arg) {
15991 this.strings.reject = arg;
15992
15993 // Introduced to constrain rendering of the group with a
15994 // requirement that it render some label via cs:label or cs:text,
15995 // and that it NOT be alpha. That behaviour is invoked with
15996 // "label-empty-or-alpha" as arg.
15997
15998 // This attribute is a complement to @label-form and modular
15999 // jurisdiction support, as it makes macros that adapt to shifting
16000 // local term definitions possible.
16001};
16002
16003CSL.Attributes["@gender"] = function (state, arg) {
16004 this.gender = arg;
16005};
16006
16007CSL.Attributes["@cslid"] = function (state, arg) {
16008 // @cslid is a noop
16009 // The value set on this attribute is used to
16010 // generate reverse lookup wrappers on output when
16011 // this.development_extensions.csl_reverse_lookup_support is
16012 // set to true in state.js (there is no runtime option,
16013 // it must be set in state.js)
16014 //
16015 // See the @showid method in the html output
16016 // section of formats.js for the function that
16017 // renders the wrappers.
16018 this.cslid = parseInt(arg, 10);
16019};
16020
16021CSL.Attributes["@capitalize-if-first"] = function (state, arg) {
16022 this.strings.capitalize_if_first_override = arg;
16023};
16024
16025CSL.Attributes["@label-capitalize-if-first"] = function (state, arg) {
16026 this.strings.label_capitalize_if_first_override = arg;
16027};
16028
16029CSL.Attributes["@label-form"] = function (state, arg) {
16030 this.strings.label_form_override = arg;
16031};
16032
16033CSL.Attributes["@part-separator"] = function (state, arg) {
16034 this.strings["part-separator"] = arg;
16035};
16036
16037CSL.Attributes["@leading-noise-words"] = function (state, arg) {
16038 this["leading-noise-words"] = arg;
16039};
16040
16041CSL.Attributes["@name-never-short"] = function (state, arg) {
16042 this["name-never-short"] = arg;
16043};
16044
16045CSL.Attributes["@class"] = function (state, arg) {
16046 state.opt["class"] = arg;
16047};
16048
16049CSL.Attributes["@version"] = function (state, arg) {
16050 state.opt.version = arg;
16051};
16052
16053/**
16054 * Store the value attribute on the token.
16055 * @name CSL.Attributes.@value
16056 * @function
16057 */
16058CSL.Attributes["@value"] = function (state, arg) {
16059 this.strings.value = arg;
16060};
16061
16062
16063/**
16064 * Store the name attribute (of a macro or term node)
16065 * on the state object.
16066 * <p>For reference when the closing node of a macro
16067 * or locale definition is encountered.</p>
16068 * @name CSL.Attributes.@name
16069 * @function
16070 */
16071CSL.Attributes["@name"] = function (state, arg) {
16072 this.strings.name = arg;
16073};
16074
16075/**
16076 * Store the form attribute (of a term node) on the state object.
16077 * <p>For reference when the closing node of a macro
16078 * or locale definition is encountered.</p>
16079 * @name CSL.Attributes.@form
16080 * @function
16081 */
16082CSL.Attributes["@form"] = function (state, arg) {
16083 this.strings.form = arg;
16084};
16085
16086CSL.Attributes["@date-parts"] = function (state, arg) {
16087 this.strings["date-parts"] = arg;
16088};
16089
16090CSL.Attributes["@range-delimiter"] = function (state, arg) {
16091 this.strings["range-delimiter"] = arg;
16092};
16093
16094/**
16095 * Store macro tokens in a buffer on the state object.
16096 * <p>For reference when the enclosing text token is
16097 * processed.</p>
16098 * @name CSL.Attributes.@macro
16099 * @function
16100 */
16101CSL.Attributes["@macro"] = function (state, arg) {
16102 this.postponed_macro = arg;
16103};
16104
16105/*
16106 * CSL.Attributes["@prefer-jurisdiction"] = function (state, arg) {
16107 * this.prefer_jurisdiction = true;
16108 * };
16109 */
16110
16111CSL.Attributes["@term"] = function (state, arg) {
16112 if (arg === "sub verbo") {
16113 this.strings.term = "sub-verbo";
16114 } else {
16115 this.strings.term = arg;
16116 }
16117};
16118
16119
16120/*
16121 * Ignore xmlns attribute.
16122 * <p>This should always be <p>http://purl.org/net/xbiblio/csl</code>
16123 * anyway. At least for the present we will blindly assume
16124 * that it is.</p>
16125 * @name CSL.Attributes.@xmlns
16126 * @function
16127 */
16128CSL.Attributes["@xmlns"] = function () {};
16129
16130
16131/*
16132 * Store language attribute to a buffer field.
16133 * <p>Will be placed in the appropriate location
16134 * when the element is processed.</p>
16135 * @name CSL.Attributes.@lang
16136 * @function
16137 */
16138CSL.Attributes["@lang"] = function (state, arg) {
16139 if (arg) {
16140 state.build.lang = arg;
16141 }
16142};
16143
16144
16145// Used as a flag during dates processing
16146CSL.Attributes["@lingo"] = function () {};
16147
16148// Used as a flag during dates processing
16149CSL.Attributes["@macro-has-date"] = function () {
16150 this["macro-has-date"] = true;
16151};
16152
16153/*
16154 * Store suffix string on token.
16155 * @name CSL.Attributes.@suffix
16156 * @function
16157 */
16158CSL.Attributes["@suffix"] = function (state, arg) {
16159 this.strings.suffix = arg;
16160};
16161
16162
16163/*
16164 * Store prefix string on token.
16165 * @name CSL.Attributes.@prefix
16166 * @function
16167 */
16168CSL.Attributes["@prefix"] = function (state, arg) {
16169 this.strings.prefix = arg;
16170};
16171
16172
16173/*
16174 * Store delimiter string on token.
16175 * @name CSL.Attributes.@delimiter
16176 * @function
16177 */
16178CSL.Attributes["@delimiter"] = function (state, arg) {
16179 this.strings.delimiter = arg;
16180};
16181
16182
16183/*
16184 * Store match evaluator on token.
16185 */
16186CSL.Attributes["@match"] = function (state, arg) {
16187 this.match = arg;
16188};
16189
16190
16191CSL.Attributes["@names-min"] = function (state, arg) {
16192 var val = parseInt(arg, 10);
16193 if (state[state.build.area].opt.max_number_of_names < val) {
16194 state[state.build.area].opt.max_number_of_names = val;
16195 }
16196 this.strings["et-al-min"] = val;
16197};
16198
16199CSL.Attributes["@names-use-first"] = function (state, arg) {
16200 this.strings["et-al-use-first"] = parseInt(arg, 10);
16201};
16202
16203CSL.Attributes["@names-use-last"] = function (state, arg) {
16204 if (arg === "true") {
16205 this.strings["et-al-use-last"] = true;
16206 } else {
16207 this.strings["et-al-use-last"] = false;
16208 }
16209};
16210
16211CSL.Attributes["@sort"] = function (state, arg) {
16212 if (arg === "descending") {
16213 this.strings.sort_direction = CSL.DESCENDING;
16214 }
16215};
16216
16217CSL.Attributes["@plural"] = function (state, arg) {
16218 // Accepted values of plural attribute differ on cs:text
16219 // and cs:label nodes.
16220 if ("always" === arg || "true" === arg) {
16221 this.strings.plural = 1;
16222 } else if ("never" === arg || "false" === arg) {
16223 this.strings.plural = 0;
16224 } else if ("contextual" === arg) {
16225 this.strings.plural = false;
16226 }
16227};
16228
16229CSL.Attributes["@has-publisher-and-publisher-place"] = function () {
16230 this.strings["has-publisher-and-publisher-place"] = true;
16231};
16232
16233CSL.Attributes["@publisher-delimiter-precedes-last"] = function (state, arg) {
16234 this.strings["publisher-delimiter-precedes-last"] = arg;
16235};
16236
16237CSL.Attributes["@publisher-delimiter"] = function (state, arg) {
16238 this.strings["publisher-delimiter"] = arg;
16239};
16240
16241CSL.Attributes["@publisher-and"] = function (state, arg) {
16242 this.strings["publisher-and"] = arg;
16243};
16244
16245CSL.Attributes["@givenname-disambiguation-rule"] = function (state, arg) {
16246 if (CSL.GIVENNAME_DISAMBIGUATION_RULES.indexOf(arg) > -1) {
16247 state.citation.opt["givenname-disambiguation-rule"] = arg;
16248 }
16249};
16250
16251CSL.Attributes["@collapse"] = function (state, arg) {
16252 // only one collapse value will be honoured.
16253 if (arg) {
16254 state[this.name].opt.collapse = arg;
16255 }
16256};
16257
16258CSL.Attributes["@cite-group-delimiter"] = function (state, arg) {
16259 if (arg) {
16260 state[state.tmp.area].opt.cite_group_delimiter = arg;
16261 }
16262};
16263
16264
16265
16266CSL.Attributes["@names-delimiter"] = function (state, arg) {
16267 state.setOpt(this, "names-delimiter", arg);
16268};
16269
16270CSL.Attributes["@name-form"] = function (state, arg) {
16271 state.setOpt(this, "name-form", arg);
16272};
16273
16274CSL.Attributes["@subgroup-delimiter"] = function (state, arg) {
16275 this.strings["subgroup-delimiter"] = arg;
16276};
16277
16278CSL.Attributes["@subgroup-delimiter-precedes-last"] = function (state, arg) {
16279 this.strings["subgroup-delimiter-precedes-last"] = arg;
16280};
16281
16282
16283CSL.Attributes["@name-delimiter"] = function (state, arg) {
16284 state.setOpt(this, "name-delimiter", arg);
16285};
16286
16287CSL.Attributes["@et-al-min"] = function (state, arg) {
16288 var val = parseInt(arg, 10);
16289 if (state[state.build.area].opt.max_number_of_names < val) {
16290 state[state.build.area].opt.max_number_of_names = val;
16291 }
16292 state.setOpt(this, "et-al-min", val);
16293};
16294
16295CSL.Attributes["@et-al-use-first"] = function (state, arg) {
16296 state.setOpt(this, "et-al-use-first", parseInt(arg, 10));
16297};
16298
16299CSL.Attributes["@et-al-use-last"] = function (state, arg) {
16300 if (arg === "true") {
16301 state.setOpt(this, "et-al-use-last", true);
16302 } else {
16303 state.setOpt(this, "et-al-use-last", false);
16304 }
16305};
16306
16307CSL.Attributes["@et-al-subsequent-min"] = function (state, arg) {
16308 var val = parseInt(arg, 10);
16309 if (state[state.build.area].opt.max_number_of_names < val) {
16310 state[state.build.area].opt.max_number_of_names = val;
16311 }
16312 state.setOpt(this, "et-al-subsequent-min", val);
16313};
16314
16315CSL.Attributes["@et-al-subsequent-use-first"] = function (state, arg) {
16316 state.setOpt(this, "et-al-subsequent-use-first", parseInt(arg, 10));
16317};
16318
16319CSL.Attributes["@suppress-min"] = function (state, arg) {
16320 this.strings["suppress-min"] = parseInt(arg, 10);
16321};
16322
16323CSL.Attributes["@suppress-max"] = function (state, arg) {
16324 this.strings["suppress-max"] = parseInt(arg, 10);
16325};
16326
16327
16328CSL.Attributes["@and"] = function (state, arg) {
16329 state.setOpt(this, "and", arg);
16330};
16331
16332CSL.Attributes["@delimiter-precedes-last"] = function (state, arg) {
16333 state.setOpt(this, "delimiter-precedes-last", arg);
16334};
16335
16336CSL.Attributes["@delimiter-precedes-et-al"] = function (state, arg) {
16337 state.setOpt(this, "delimiter-precedes-et-al", arg);
16338};
16339
16340CSL.Attributes["@initialize-with"] = function (state, arg) {
16341 state.setOpt(this, "initialize-with", arg);
16342};
16343
16344CSL.Attributes["@initialize"] = function (state, arg) {
16345 if (arg === "false") {
16346 state.setOpt(this, "initialize", false);
16347 }
16348};
16349
16350CSL.Attributes["@name-as-reverse-order"] = function (state, arg) {
16351 this["name-as-reverse-order"] = arg;
16352};
16353
16354CSL.Attributes["@name-as-sort-order"] = function (state, arg) {
16355 if (this.name === "style-options") {
16356 this["name-as-sort-order"] = arg;
16357 } else {
16358 state.setOpt(this, "name-as-sort-order", arg);
16359 }
16360};
16361
16362CSL.Attributes["@sort-separator"] = function (state, arg) {
16363 state.setOpt(this, "sort-separator", arg);
16364};
16365
16366CSL.Attributes["@require-match"] = function (state, arg) {
16367 if (arg === "true") {
16368 this.requireMatch = true;
16369 }
16370};
16371
16372CSL.Attributes["@exclude-types"] = function (state, arg) {
16373 state.bibliography.opt.exclude_types = arg.split(/\s+/);
16374};
16375
16376CSL.Attributes["@exclude-with-fields"] = function (state, arg) {
16377 state.bibliography.opt.exclude_with_fields = arg.split(/\s+/);
16378};
16379
16380
16381CSL.Attributes["@year-suffix-delimiter"] = function (state, arg) {
16382 state[this.name].opt["year-suffix-delimiter"] = arg;
16383};
16384
16385CSL.Attributes["@after-collapse-delimiter"] = function (state, arg) {
16386 state[this.name].opt["after-collapse-delimiter"] = arg;
16387};
16388
16389CSL.Attributes["@subsequent-author-substitute"] = function (state, arg) {
16390 state[this.name].opt["subsequent-author-substitute"] = arg;
16391};
16392
16393CSL.Attributes["@subsequent-author-substitute-rule"] = function (state, arg) {
16394 state[this.name].opt["subsequent-author-substitute-rule"] = arg;
16395};
16396
16397CSL.Attributes["@disambiguate-add-names"] = function (state, arg) {
16398 if (arg === "true") {
16399 state.opt["disambiguate-add-names"] = true;
16400 }
16401};
16402
16403CSL.Attributes["@disambiguate-add-givenname"] = function (state, arg) {
16404 if (arg === "true") {
16405 state.opt["disambiguate-add-givenname"] = true;
16406 }
16407};
16408
16409CSL.Attributes["@disambiguate-add-year-suffix"] = function (state, arg) {
16410 if (arg === "true" && state.opt.xclass !== "numeric") {
16411 state.opt["disambiguate-add-year-suffix"] = true;
16412 }
16413};
16414
16415
16416CSL.Attributes["@second-field-align"] = function (state, arg) {
16417 if (arg === "flush" || arg === "margin") {
16418 state[this.name].opt["second-field-align"] = arg;
16419 }
16420};
16421
16422
16423CSL.Attributes["@hanging-indent"] = function (state, arg) {
16424 if (arg === "true") {
16425 if (state.opt.development_extensions.hanging_indent_legacy_number) {
16426 state[this.name].opt.hangingindent = 2;
16427 } else {
16428 state[this.name].opt.hangingindent = true;
16429 }
16430 }
16431};
16432
16433
16434CSL.Attributes["@line-spacing"] = function (state, arg) {
16435 if (arg && arg.match(/^[.0-9]+$/)) {
16436 state[this.name].opt["line-spacing"] = parseFloat(arg, 10);
16437 }
16438};
16439
16440
16441CSL.Attributes["@entry-spacing"] = function (state, arg) {
16442 if (arg && arg.match(/^[.0-9]+$/)) {
16443 state[this.name].opt["entry-spacing"] = parseFloat(arg, 10);
16444 }
16445};
16446
16447
16448CSL.Attributes["@near-note-distance"] = function (state, arg) {
16449 state[this.name].opt["near-note-distance"] = parseInt(arg, 10);
16450};
16451
16452CSL.Attributes["@text-case"] = function (state, arg) {
16453 var func = function (state, Item) {
16454 if (arg === "normal") {
16455 this.text_case_normal = true;
16456 } else {
16457 this.strings["text-case"] = arg;
16458 if (arg === "title") {
16459 if (Item.jurisdiction) {
16460 this.strings["text-case"] = "passthrough";
16461 }
16462 }
16463 }
16464 };
16465 this.execs.push(func);
16466};
16467
16468
16469CSL.Attributes["@page-range-format"] = function (state, arg) {
16470 state.opt["page-range-format"] = arg;
16471};
16472
16473
16474CSL.Attributes["@year-range-format"] = function (state, arg) {
16475 state.opt["year-range-format"] = arg;
16476};
16477
16478
16479CSL.Attributes["@default-locale"] = function (state, arg) {
16480 if (this.name === 'style') {
16481 var lst, len, pos, m, ret;
16482 //
16483 // Workaround for Internet Exploder 6 (doesn't recognize
16484 // groups in str.split(/something(braced-group)something/)
16485 //
16486 var m = arg.match(/-x-(sort|translit|translat)-/g);
16487 if (m) {
16488 for (pos = 0, len = m.length; pos < len; pos += 1) {
16489 m[pos] = m[pos].replace(/^-x-/, "").replace(/-$/, "");
16490 }
16491 }
16492 lst = arg.split(/-x-(?:sort|translit|translat)-/);
16493 ret = [lst[0]];
16494 for (pos = 1, len = lst.length; pos < len; pos += 1) {
16495 ret.push(m[pos - 1]);
16496 ret.push(lst[pos]);
16497 }
16498 lst = ret.slice();
16499 len = lst.length;
16500 for (pos = 1; pos < len; pos += 2) {
16501 state.opt[("locale-" + lst[pos])].push(lst[(pos + 1)].replace(/^\s*/g, "").replace(/\s*$/g, ""));
16502 }
16503 if (lst.length) {
16504 state.opt["default-locale"] = lst.slice(0, 1);
16505 } else {
16506 state.opt["default-locale"] = ["en"];
16507 }
16508 } else if (arg === "true") {
16509 this.default_locale = true;
16510 }
16511};
16512
16513CSL.Attributes["@default-locale-sort"] = function (state, arg) {
16514 state.opt["default-locale-sort"] = arg;
16515};
16516
16517CSL.Attributes["@demote-non-dropping-particle"] = function (state, arg) {
16518 state.opt["demote-non-dropping-particle"] = arg;
16519};
16520
16521CSL.Attributes["@initialize-with-hyphen"] = function (state, arg) {
16522 if (arg === "false") {
16523 state.opt["initialize-with-hyphen"] = false;
16524 }
16525};
16526
16527CSL.Attributes["@institution-parts"] = function (state, arg) {
16528 this.strings["institution-parts"] = arg;
16529};
16530
16531CSL.Attributes["@if-short"] = function (state, arg) {
16532 if (arg === "true") {
16533 this.strings["if-short"] = true;
16534 }
16535};
16536
16537CSL.Attributes["@substitute-use-first"] = function (state, arg) {
16538 this.strings["substitute-use-first"] = parseInt(arg, 10);
16539};
16540
16541CSL.Attributes["@use-first"] = function (state, arg) {
16542 this.strings["use-first"] = parseInt(arg, 10);
16543};
16544
16545CSL.Attributes["@stop-last"] = function (state, arg) {
16546 this.strings["stop-last"] = parseInt(arg, 10) * -1;
16547};
16548
16549CSL.Attributes["@use-last"] = function (state, arg) {
16550 this.strings["use-last"] = parseInt(arg, 10);
16551};
16552
16553
16554CSL.Attributes["@reverse-order"] = function (state, arg) {
16555 if ("true" === arg) {
16556 this.strings["reverse-order"] = true;
16557 }
16558};
16559
16560CSL.Attributes["@display"] = function (state, arg) {
16561 if (state.bibliography.tokens.length === 2) {
16562 state.opt.using_display = true;
16563 }
16564 this.strings.cls = arg;
16565};
16566
16567
16568/*global CSL: true */
16569
16570
16571/**
16572 * String stack object.
16573 * <p>Numerous string stacks are used to track nested
16574 * parameters at runtime. This class provides methods
16575 * that remove some of the aggravation of managing
16576 * them.</p>
16577 * @class
16578 */
16579CSL.Stack = function (val, literal) {
16580 this.mystack = [];
16581 if (literal || val) {
16582 this.mystack.push(val);
16583 }
16584 this.tip = this.mystack[0];
16585};
16586
16587/**
16588 * Push a value onto the stack.
16589 * <p>This just does what it says.</p>
16590 */
16591CSL.Stack.prototype.push = function (val, literal) {
16592 if (literal || val) {
16593 this.mystack.push(val);
16594 } else {
16595 this.mystack.push("");
16596 }
16597 this.tip = this.mystack[this.mystack.length - 1];
16598};
16599
16600/**
16601 * Clear the stack
16602 */
16603CSL.Stack.prototype.clear = function () {
16604 this.mystack = [];
16605 this.tip = {};
16606};
16607
16608/**
16609 * Replace the top value on the stack.
16610 * <p>This removes some ugly syntax from the
16611 * main code.</p>
16612 */
16613CSL.Stack.prototype.replace = function (val, literal) {
16614 //
16615 // safety fix after a bug was chased down. Rhino
16616 // JS will process a negative index without error (!).
16617 if (this.mystack.length === 0) {
16618 CSL.error("Internal CSL processor error: attempt to replace nonexistent stack item with " + val);
16619 }
16620 if (literal || val) {
16621 this.mystack[(this.mystack.length - 1)] = val;
16622 } else {
16623 this.mystack[(this.mystack.length - 1)] = "";
16624 }
16625 this.tip = this.mystack[this.mystack.length - 1];
16626};
16627
16628
16629/**
16630 * Remove the top value from the stack.
16631 * <p>Just does what it says.</p>
16632 */
16633CSL.Stack.prototype.pop = function () {
16634 var ret = this.mystack.pop();
16635 if (this.mystack.length) {
16636 this.tip = this.mystack[this.mystack.length - 1];
16637 } else {
16638 this.tip = {};
16639 }
16640 return ret;
16641};
16642
16643
16644/**
16645 * Return the top value on the stack.
16646 * <p>Removes a little hideous complication from
16647 * the main code.</p>
16648 */
16649CSL.Stack.prototype.value = function () {
16650 return this.mystack.slice(-1)[0];
16651};
16652
16653
16654/**
16655 * Return length (depth) of stack.
16656 * <p>Used to identify if there is content to
16657 * be handled on the stack</p>
16658 */
16659CSL.Stack.prototype.length = function () {
16660 return this.mystack.length;
16661};
16662
16663/*global CSL: true */
16664
16665/**
16666 * Initializes the parallel cite tracking arrays
16667 */
16668CSL.Parallel = function (state) {
16669 this.state = state;
16670 this.info = {};
16671 this.parallel_conditional_blobs_list = [];
16672};
16673
16674CSL.Parallel.prototype.setSeriesRels = function(prevID, currID, seriesRels) {
16675 if (this.info[prevID][currID]) {
16676 if (!seriesRels) {
16677 seriesRels = JSON.parse(JSON.stringify(this.info[prevID]));
16678 }
16679 } else {
16680 seriesRels = false;
16681 }
16682 return seriesRels;
16683}
16684
16685CSL.Parallel.prototype.getRepeats = function(prev, curr) {
16686 var rex = /(?:type|id|seeAlso|.*-sub|.*-subjoin|.*-main)/;
16687 var ret = {};
16688 for (var key in prev) {
16689 if (key.match(rex)) {
16690 continue;
16691 }
16692 if (typeof prev[key] === "string" || !prev[key]) {
16693 if (prev[key] && prev[key] === curr[key]) {
16694 ret[key] = true;
16695 }
16696 } else if (typeof prev[key] === "object") {
16697 // Could do better than this.
16698 if (JSON.stringify(prev[key]) === JSON.stringify(curr[key])) {
16699 ret[key] = true;
16700 }
16701 }
16702 }
16703 return ret;
16704}
16705
16706CSL.Parallel.prototype.StartCitation = function (sortedItems, out) {
16707 this.parallel_conditional_blobs_list = [];
16708 this.info = {};
16709 if (sortedItems.length > 1) {
16710 // Harder than it looks.
16711 // On a first pass, get the seeAlso of each item.
16712 for (var i=0,ilen=sortedItems.length; i<ilen; i++) {
16713 var curr = sortedItems[i][0];
16714 this.info[curr.id] = {};
16715 if (curr.seeAlso) {
16716 for (var j=0,jlen=curr.seeAlso.length; j<jlen; j++) {
16717 if (curr.id === curr.seeAlso[j]) {
16718 continue;
16719 }
16720 this.info[curr.id][curr.seeAlso[j]] = true;
16721 }
16722 }
16723 }
16724 // On a second pass, set an item to FIRST if the current
16725 // item is in its seeAlso. The seeAlso of the FIRST item control
16726 // until (a) a non-member is encountered at CURRENT, or
16727 // (b) the end of the array is reached.
16728 // The seeAlso keys are deleted as each is seen.
16729 // If neither (a) nor (b), set the current item to MID.
16730 // If (a) and the previous item is FIRST, delete its
16731 // parallel marker.
16732 // If (b) and the current item is FIRST, delete its
16733 // parallel marker.
16734 // If (a) and the previous item is not FIRST, set it to
16735 // LAST, and reset seeAlso from the current item.
16736 // If (b) and the current item is not FIRST, set it to
16737 // LAST.
16738 var seriesRels = false;
16739 var masterID = false;
16740 for (var i=1,ilen=sortedItems.length; i<ilen; i++) {
16741 var prev = sortedItems[i-1][0];
16742 var curr = sortedItems[i][0];
16743 var newSeriesRels = this.setSeriesRels(prev.id, curr.id, seriesRels);
16744 if (!seriesRels) {
16745 if (newSeriesRels) {
16746 // first
16747 seriesRels = newSeriesRels;
16748 delete seriesRels[curr.id];
16749 sortedItems[i-1][1].parallel = "first";
16750 sortedItems[i][1].parallel = "mid";
16751 sortedItems[i][1].repeats = this.getRepeats(prev, curr);
16752 sortedItems[i-1][1].repeats = sortedItems[i][1].repeats;
16753 if (!sortedItems[i][1].prefix) {
16754 sortedItems[i][1].prefix = ", ";
16755 }
16756 masterID = prev.id;
16757 this.state.registry.registry[masterID].master = true;
16758 this.state.registry.registry[masterID].siblings = [curr.id];
16759
16760 }
16761 } else {
16762 if (seriesRels[curr.id]) {
16763 sortedItems[i][1].parallel = "mid";
16764 if (!sortedItems[i][1].prefix) {
16765 sortedItems[i][1].prefix = ", ";
16766 }
16767 delete seriesRels[curr.id];
16768 sortedItems[i][1].repeats = this.getRepeats(prev, curr);
16769 //sortedItems[i-1][1].repeats = sortedItems[i][1].repeats;
16770 this.state.registry.registry[masterID].siblings.push(curr.id);
16771 } else {
16772 sortedItems[i-1][1].parallel = "last";
16773 sortedItems[i][1].repeats = this.getRepeats(prev, curr);
16774 seriesRels = false;
16775 }
16776 }
16777 if (i === (sortedItems.length-1)) {
16778 if (sortedItems[i][1].parallel === "mid") {
16779 sortedItems[i][1].parallel = "last";
16780 sortedItems[i][1].repeats = this.getRepeats(prev, curr);
16781 } else if (sortedItems[i][1].parallel !== "last") {
16782 delete sortedItems[i][1].repeats;
16783 }
16784 }
16785 }
16786 }
16787};
16788
16789
16790CSL.Parallel.prototype.purgeGroupsIfParallel = function () {
16791 for (var i = this.parallel_conditional_blobs_list.length - 1; i > -1; i += -1) {
16792 var obj = this.parallel_conditional_blobs_list[i];
16793 if (!obj.result && !obj.repeats) {
16794 purgeme = false;
16795 } else {
16796 if (obj.condition) {
16797 var purgeme = true;
16798 if (obj.result === obj.condition) {
16799 purgeme = false;
16800 }
16801 }
16802 if (purgeme && obj.norepeat && obj.repeats) {
16803 purgeme = false;
16804 var matches = 0;
16805 for (var j=0,jlen=obj.norepeat.length; j<jlen; j++) {
16806 if (obj.repeats[obj.norepeat[j]]) {
16807 matches += 1;
16808 }
16809 }
16810 if (matches === obj.norepeat.length) {
16811 purgeme = true;
16812 }
16813 }
16814 }
16815 if (purgeme) {
16816 var buffer = [];
16817 while (obj.blobs.length > obj.pos) {
16818 buffer.push(obj.blobs.pop());
16819 }
16820 if (buffer.length) {
16821 buffer.pop();
16822 }
16823 while (buffer.length) {
16824 obj.blobs.push(buffer.pop());
16825 }
16826 }
16827 this.parallel_conditional_blobs_list.pop();
16828 }
16829};
16830
16831/*global CSL: true */
16832
16833
16834CSL.Util = {};
16835
16836CSL.Util.Match = function () {
16837
16838 this.any = function (token, state, tests) {
16839 return function (Item, item) {
16840 for (var i=0, ilen=tests.length; i < ilen; i += 1) {
16841 var result = tests[i](Item, item);
16842 if (result) {
16843 return true;
16844 }
16845 }
16846 return false;
16847 };
16848 };
16849
16850 this.none = function (token, state, tests) {
16851 return function (Item, item) {
16852 for (var i=0,ilen=tests.length;i<ilen;i+=1) {
16853 var result = tests[i](Item,item);
16854 if (result) {
16855 return false;
16856 }
16857 }
16858 return true;
16859 };
16860 };
16861
16862 this.all = function (token, state, tests) {
16863 return function (Item, item) {
16864 for (var i=0,ilen=tests.length;i<ilen;i+=1) {
16865 var result = tests[i](Item,item);
16866 if (!result) {
16867 return false;
16868 }
16869 }
16870 return true;
16871 };
16872 };
16873
16874 this[undefined] = this.all;
16875
16876 this.nand = function (token, state, tests) {
16877 return function (Item, item) {
16878 for (var i=0,ilen=tests.length;i<ilen;i+=1) {
16879 var result = tests[i](Item,item);
16880 if (!result) {
16881 return true;
16882 }
16883 }
16884 return false;
16885 };
16886 };
16887
16888};
16889
16890/*global CSL: true */
16891
16892/*
16893 * Fields can be transformed by translation/transliteration, or by
16894 * abbreviation. Transformations are performed in that order.
16895 *
16896 * Renderings of original, translated or transliterated content
16897 * (followed by abbreviation if requested) are placed in the primary
16898 * output slot or the (implicitly punctuated) secondary and tertiary
16899 * output slots according to the settings registered in the
16900 * state.opt['cite-lang-prefs'] arrays. The array has six segments:
16901 * 'persons', 'institutions', 'titles', 'journals', 'publishers', and
16902 * 'places'. Each segment always contains at least one item, and may
16903 * hold values 'orig', 'translit' or 'translat'. The array defaults to
16904 * a single item 'orig'.
16905 *
16906 * All multilingual variables are associated with segments,
16907 * with the exception of 'edition' and 'genre'. These two
16908 * exceptions are always rendered with the first matching
16909 * language form found in state.opt['locale-translit'] or, if
16910 * composing a sort key, state.opt['locale-sort']. No secondary
16911 * slot rendering is performed for this two variables.
16912 *
16913 * The balance of multilingual variables are rendered with
16914 * the first matching value in the transform locales spec
16915 * (no transform, state.opt['locale-translit'], or
16916 * state.opt['locale-translat']) mapped to the target
16917 * slot.
16918 *
16919 * Full primary+secondary+tertiary rendering is performed only in
16920 * note-style citations and the bibliography. In-text citations are
16921 * rendered in the primary output slot only, following the same spec
16922 * parameters.
16923 *
16924 * Optional setters:
16925 * .setAbbreviationFallback(); fallback flag
16926 * (if true, a failed abbreviation will fallback to long)
16927 *
16928 * .setAlternativeVariableName(): alternative variable name in Item,
16929 * for use as a fallback abbreviation source
16930 *
16931 * Translation/transliteration
16932 *
16933 * Optional setter:
16934 * .setTransformFallback():
16935 * default flag (if true, the original field value will be used as a fallback)
16936 *
16937 * The getTextSubField() method may be used to obtain a string transform
16938 * of a field, without abbreviation, as needed for setting sort keys
16939 * (for example).
16940 *
16941 */
16942
16943CSL.Transform = function (state) {
16944 // Abbreviation families
16945 this.abbrevs = {};
16946 this.abbrevs["default"] = new state.sys.AbbreviationSegments();
16947
16948 function getCountryOrJurisdiction(variable, normalizedKey, quashCountry) {
16949 var value = "";
16950 if (state.sys.getHumanForm) {
16951 if (variable === "country") {
16952 value = state.sys.getHumanForm(normalizedKey.toLowerCase(), false, true);
16953 value = value.split("|")[0];
16954 } else if (variable === "jurisdiction") {
16955 value = state.sys.getHumanForm(normalizedKey.toLowerCase(), false, true);
16956 if (!quashCountry) {
16957 value = value.split("|").slice(1).join(", ");
16958 } else {
16959 // Bare country name is rendered by "country", not "jurisdiction"
16960 value = "";
16961 }
16962 }
16963 }
16964 return value;
16965 }
16966
16967 // Internal function
16968 function abbreviate(state, tok, Item, altvar, basevalue, family_var, use_field) {
16969 var value = "";
16970 var myabbrev_family = CSL.FIELD_CATEGORY_REMAP[family_var];
16971 var preferredJurisdiction;
16972 if (!myabbrev_family) {
16973 return basevalue;
16974 }
16975
16976 var variable = family_var;
16977 var normalizedKey = basevalue;
16978 if (state.sys.normalizeAbbrevsKey) {
16979 normalizedKey = state.sys.normalizeAbbrevsKey(family_var, basevalue);
16980 }
16981 var quashCountry = false;
16982 if (variable === "jurisdiction" && normalizedKey) {
16983 quashCountry = normalizedKey.indexOf(":") === -1;
16984 }
16985
16986 // Lazy retrieval of abbreviations.
16987 if (state.sys.getAbbreviation) {
16988
16989 if (["jurisdiction", "country", "language-name", "language-name-original"].indexOf(variable) > -1) {
16990 preferredJurisdiction = "default";
16991 } else if (Item.jurisdiction) {
16992 preferredJurisdiction = Item.jurisdiction;
16993 } else {
16994 preferredJurisdiction = "default";
16995 }
16996 var jurisdiction = state.transform.loadAbbreviation(preferredJurisdiction, myabbrev_family, normalizedKey, Item.type);
16997
16998 // Some rules:
16999 // # variable === "country"
17000 // (1) If an abbreviation is associated with the code, then:
17001 // (a) return the abbreviated form if form="short"
17002 // (2) Otherwise:
17003 // (a) return the human-readable country name, or whatever is there if it's not a code
17004 // # variable === "jurisdiction"
17005 // (1) If !!getHumanForm(jurisdictionID, false, false):
17006 // (a) If the code is top-level (i.e. a country):
17007 // (i) return nothing -- this is what the "country" variable is for.
17008 // (b) otherwise:
17009 // (i) If an abbreviation is associated with the code, then:
17010 // (A) return the abbreviated form
17011 // (ii) Otherwise
17012 // (A) return the human-readable form, with the country name & code removed from the front
17013 // (2) Otherwise:
17014 // (a) abbreviate as per normal.
17015 // # other variables
17016 // (1) Abbreviate as per normal.
17017
17018 if (state.transform.abbrevs[jurisdiction][myabbrev_family] && normalizedKey) {
17019 // Safe to test presence of abbrev against raw object in this block
17020 var abbrev = state.transform.abbrevs[jurisdiction][myabbrev_family][normalizedKey];
17021 if (tok.strings.form === "short" && abbrev) {
17022 if (quashCountry) {
17023 value = "";
17024 } else {
17025 value = abbrev;
17026 }
17027 } else {
17028 value = getCountryOrJurisdiction(variable, normalizedKey, quashCountry);
17029 }
17030 }
17031 }
17032
17033 // Was for:
17034 if (!value
17035 && (!state.opt.development_extensions.require_explicit_legal_case_title_short || Item.type !== 'legal_case')
17036 && altvar && Item[altvar] && use_field) {
17037 value = Item[altvar];
17038 }
17039 if (!value && !state.sys.getAbbreviation && state.sys.getHumanForm) {
17040 value = getCountryOrJurisdiction(variable, normalizedKey, quashCountry);
17041 }
17042 if (!value && !quashCountry && (!state.sys.getHumanForm || variable !== "jurisdiction")) {
17043 value = basevalue;
17044 }
17045 return value;
17046 }
17047
17048 function getFieldLocale(Item,field) {
17049 var ret = state.opt["default-locale"][0].slice(0, 2);
17050 var localeRex;
17051 if (state.opt.development_extensions.strict_text_case_locales) {
17052 localeRex = new RegExp("^([a-zA-Z]{2})(?:$|-.*| .*)");
17053 } else {
17054 localeRex = new RegExp("^([a-zA-Z]{2})(?:$|-.*|.*)");
17055 }
17056 if (Item.language) {
17057 var m = ("" + Item.language).match(localeRex);
17058 if (m) {
17059 ret = m[1];
17060 } else {
17061 // Set garbage to "Klingon".
17062 ret = "tlh";
17063 }
17064 }
17065 if (Item.multi && Item.multi && Item.multi.main && Item.multi.main[field]) {
17066 ret = Item.multi.main[field];
17067 }
17068 if (!state.opt.development_extensions.strict_text_case_locales
17069 || state.opt.development_extensions.normalize_lang_keys_to_lowercase) {
17070
17071 ret = ret.toLowerCase();
17072 }
17073 return ret;
17074 }
17075
17076 // Internal functions
17077 function getTextSubField (Item, field, locale_type, use_default, stopOrig, family_var) {
17078 var opt, o, ret, opts;
17079 var usedOrig = stopOrig;
17080 var usingOrig = false;
17081
17082 if (!Item[field]) {
17083 return {
17084 name:"",
17085 usedOrig:stopOrig,
17086 token: CSL.Util.cloneToken(this)
17087 };
17088 }
17089 // If form="short" is selected ("family_var" is a misnomer
17090 // here, it means short-form requested), and the variable
17091 // has a short-form partner (i.e. it is in array
17092 // VARIABLES_WITH_SHORT_FORM), then it is run here as *-short".
17093 var stickyLongForm = false;
17094 if (CSL.VARIABLES_WITH_SHORT_FORM.indexOf(field) > -1
17095 && family_var) {
17096
17097 field = field + "-short";
17098 stickyLongForm = true;
17099 }
17100 var breakMe = false;
17101 var firstValue = null;
17102 var fieldsToTry = [];
17103 if (field.slice(-6) === "-short") {
17104 fieldsToTry.push(field);
17105 fieldsToTry.push(field.slice(0, -6))
17106 } else {
17107 fieldsToTry.push(field);
17108 }
17109
17110 for (var h=0,hlen=fieldsToTry.length; h<hlen; h++) {
17111 var variantMatch = false;
17112 var field = fieldsToTry[h];
17113
17114 ret = {name:"", usedOrig:stopOrig,locale:getFieldLocale(Item,field)};
17115
17116 opts = state.opt[locale_type];
17117 var hasVal = false;
17118
17119 if (locale_type === 'locale-orig') {
17120 if (stopOrig) {
17121 ret = {name:"", usedOrig:stopOrig};
17122 } else {
17123 ret = {name:Item[field], usedOrig:false, locale:getFieldLocale(Item,field)};
17124 }
17125 hasVal = true;
17126 usingOrig = true;
17127 } else if (use_default && ("undefined" === typeof opts || opts.length === 0)) {
17128 // If we want the original, or if we don't have any specific guidance and we
17129 // definitely want output, just return the original value.
17130 var ret = {name:Item[field], usedOrig:true, locale:getFieldLocale(Item,field)};
17131 hasVal = true;
17132 usingOrig = true;
17133 }
17134
17135 if (!hasVal) {
17136 for (var i = 0, ilen = opts.length; i < ilen; i += 1) {
17137 opt = opts[i];
17138 o = opt.split(/[\-_]/)[0];
17139 if (opt && Item.multi && Item.multi._keys[field] && Item.multi._keys[field][opt]) {
17140 ret.name = Item.multi._keys[field][opt];
17141 ret.locale = opt;
17142 hasVal = true;
17143 variantMatch = true;
17144 usingOrig = false;
17145 break;
17146 } else if (o && Item.multi && Item.multi._keys[field] && Item.multi._keys[field][o]) {
17147 ret.name = Item.multi._keys[field][o];
17148 ret.locale = o;
17149 hasVal = true;
17150 variantMatch = true;
17151 usingOrig = false;
17152 break;
17153 }
17154 }
17155 if (!ret.name && use_default) {
17156 ret = {name:Item[field], usedOrig:true, locale:getFieldLocale(Item,field)};
17157 usingOrig = true;
17158 }
17159 }
17160 ret.token = CSL.Util.cloneToken(this);
17161 if (h === 0) {
17162 if (variantMatch) {
17163 ret.found_variant_ok = true;
17164 }
17165 firstValue = ret;
17166 if (!stickyLongForm && ("undefined" === typeof opts || opts.length === 0)) {
17167 breakMe = true;
17168 }
17169 if (variantMatch) {
17170 breakMe = true;
17171 }
17172 } else {
17173 if (!stickyLongForm && !variantMatch && firstValue) {
17174 ret = firstValue;
17175 field = fieldsToTry[0];
17176 } else if (variantMatch) {
17177 ret.found_variant_ok = true;
17178 }
17179 }
17180 if (["title", "container-title"].indexOf(field) > -1) {
17181 if (!usedOrig
17182 && (!ret.token.strings["text-case"]
17183 || ret.token.strings["text-case"] === "sentence"
17184 || ret.token.strings["text-case"] === "normal")) {
17185 var locale = state.opt.lang;
17186 var lang;
17187 if (usingOrig) {
17188 lang = false;
17189 } else {
17190 lang = ret.locale;
17191 }
17192 var seg = field.slice(0,-5);
17193 var sentenceCase = ret.token.strings["text-case"] === "sentence" ? true : false;
17194 ret.name = CSL.titlecaseSentenceOrNormal(state, Item, seg, lang, sentenceCase);
17195 delete ret.token.strings["text-case"];
17196 }
17197 }
17198 if (breakMe) {
17199 break;
17200 }
17201 }
17202 return ret;
17203 }
17204 this.getTextSubField = getTextSubField;
17205
17206 // Setter for abbreviation lists
17207 // This initializes a single abbreviation based on known
17208 // data.
17209 function loadAbbreviation(jurisdiction, category, orig, itemType) {
17210 if (!jurisdiction) {
17211 jurisdiction = "default";
17212 }
17213 if (!orig) {
17214 if (!state.transform.abbrevs[jurisdiction]) {
17215 state.transform.abbrevs[jurisdiction] = new state.sys.AbbreviationSegments();
17216 }
17217 if (!state.transform.abbrevs[jurisdiction][category]) {
17218 state.transform.abbrevs[jurisdiction][category] = {};
17219 }
17220 return jurisdiction;
17221 }
17222 // The getAbbreviation() function should check the
17223 // external DB for the content key. If a value exists
17224 // in this[category] and no value exists in DB, the entry
17225 // in memory is left untouched. If a value does exist in
17226 // DB, the memory value is created.
17227 //
17228 // See testrunner_stdrhino.js for an example.
17229 if (state.sys.getAbbreviation) {
17230 jurisdiction = state.sys.getAbbreviation(state.opt.styleID, state.transform.abbrevs, jurisdiction, category, orig, itemType, true);
17231 if (!jurisdiction) {
17232 jurisdiction = "default";
17233 }
17234 }
17235 return jurisdiction;
17236 }
17237 this.loadAbbreviation = loadAbbreviation;
17238
17239 function publisherCheck (tok, Item, primary, family_var) {
17240 var varname = tok.variables[0];
17241 if (state.publisherOutput && primary) {
17242 if (["publisher","publisher-place"].indexOf(varname) === -1) {
17243 return false;
17244 } else {
17245 // In this case, the publisher bundle will be rendered
17246 // at the close of the group, by the closing group node.
17247 state.publisherOutput[varname + "-token"] = tok;
17248 state.publisherOutput.varlist.push(varname);
17249 var lst = primary.split(/;\s*/);
17250 if (lst.length === state.publisherOutput[varname + "-list"].length) {
17251 state.publisherOutput[varname + "-list"] = lst;
17252 }
17253 // Abbreviate each of the items in the list here!
17254 for (var i = 0, ilen = lst.length; i < ilen; i += 1) {
17255 lst[i] = abbreviate(state, tok, Item, false, lst[i], family_var, true);
17256 }
17257 state.tmp[varname + "-token"] = tok;
17258 return true;
17259 }
17260 }
17261 return false;
17262 }
17263
17264
17265 // The name transform code is placed here to keep similar things
17266 // in one place. Obviously this module could do with a little
17267 // tidying up.
17268 function quashCheck(value) {
17269 var m = value.match(/^!([-,_a-z]+)>>>/);
17270 if (m) {
17271 var fields = m[1].split(",");
17272 value = value.slice(m[0].length);
17273 for (var i = 0, ilen = fields.length; i < ilen; i += 1) {
17274 if (state.tmp.done_vars.indexOf(fields[i]) === -1) {
17275 state.tmp.done_vars.push(fields[i]);
17276 }
17277 }
17278 }
17279 return value;
17280 }
17281 this.quashCheck = quashCheck;
17282
17283 // Return function appropriate to selected options
17284 function getOutputFunction(variables, family_var, abbreviation_fallback, alternative_varname) {
17285 // var mytoken;
17286
17287 // Set the primary_locale and secondary_locale lists appropriately.
17288 // No instance helper function for this; everything can be derived
17289 // from processor settings and rendering context.
17290
17291 var localesets;
17292 var langPrefs = CSL.LangPrefsMap[variables[0]];
17293 if (!langPrefs) {
17294 localesets = false;
17295 } else {
17296 localesets = state.opt['cite-lang-prefs'][langPrefs];
17297 }
17298
17299 return function (state, Item, item) {
17300 var primary, primary_locale, secondary, secondary_locale, tertiary, tertiary_locale, primary_tok;
17301 if (!variables[0] || (!Item[variables[0]] && !Item[alternative_varname])) {
17302 return null;
17303 }
17304 //
17305 // Exploring the edges here.
17306 // "suppress-author" for string variables (mostly titles).
17307 //
17308 if (!state.tmp.just_looking && item && item["suppress-author"]) {
17309 if (!state.tmp.probably_rendered_something && state.tmp.can_substitute.length() > 1) {
17310 return null;
17311 }
17312 }
17313 var slot = {primary:false, secondary:false, tertiary:false};
17314 if (state.tmp.area.slice(-5) === "_sort") {
17315 slot.primary = 'locale-sort';
17316 } else {
17317 if (localesets && localesets.length === 1 && localesets[0] === "locale-orig") {
17318 slot.primary = "locale-orig";
17319 localesets = false;
17320 } else if (localesets && !state.tmp.multi_layout) {
17321 var slotnames = ["primary", "secondary", "tertiary"];
17322 for (var i = 0, ilen = slotnames.length; i < ilen; i += 1) {
17323 if (localesets.length - 1 < i) {
17324 break;
17325 }
17326 if (localesets[i]) {
17327 slot[slotnames[i]] = 'locale-' + localesets[i];
17328 }
17329 }
17330 } else {
17331 slot.primary = 'locale-orig';
17332 }
17333 }
17334
17335 if (variables[0] === "title-short"
17336 || (state.tmp.area !== "bibliography"
17337 && !(state.tmp.area === "citation"
17338 && state.opt.xclass === "note"
17339 && item && !item.position))) {
17340
17341 slot.secondary = false;
17342 slot.tertiary = false;
17343 }
17344
17345 if (state.tmp.multi_layout) {
17346 slot.secondary = false;
17347 slot.tertiary = false;
17348 }
17349
17350 // Problem for multilingual: we really should be
17351 // checking for sanity on the basis of the output
17352 // strings to be actually used. (also below)
17353 if (state.tmp["publisher-list"]) {
17354 if (variables[0] === "publisher") {
17355 state.tmp["publisher-token"] = this;
17356 } else if (variables[0] === "publisher-place") {
17357 state.tmp["publisher-place-token"] = this;
17358 }
17359 return null;
17360 }
17361 // True is for transform fallback
17362 var res = getTextSubField.call(this, Item, variables[0], slot.primary, true, null, family_var);
17363 primary = res.name;
17364 primary_locale = res.locale;
17365 var primary_tok = res.token;
17366 var primaryUsedOrig = res.usedOrig;
17367 if (family_var && !res.found_variant_ok) {
17368 primary = abbreviate(state, primary_tok, Item, alternative_varname, primary, family_var, true);
17369 // Suppress subsequent use of another variable if requested by
17370 // hack syntax in this abbreviation short form.
17371 if (primary) {
17372 // The abbreviate() function could use a cleanup, after Zotero correct to use title-short
17373 primary = quashCheck(primary);
17374 }
17375 }
17376 if (publisherCheck(this, Item, primary, family_var)) {
17377 return null;
17378 }
17379
17380 // No fallback for secondary and tertiary
17381 secondary = false;
17382 tertiary = false;
17383 var secondary_tok;
17384 var tertiary_tok;
17385 if (slot.secondary) {
17386 res = getTextSubField.call(this, Item, variables[0], slot.secondary, false, res.usedOrig, null, family_var);
17387 secondary = res.name;
17388 secondary_locale = res.locale;
17389 secondary_tok = res.token;
17390 if (family_var && !res.found_variant_ok) {
17391 if (secondary) {
17392 // The abbreviate() function could use a cleanup, after Zotero correct to use title-short
17393 secondary = abbreviate(state, secondary_tok, Item, false, secondary, family_var, true);
17394 }
17395 }
17396 //print("XXX secondary_locale: "+secondary_locale);
17397 }
17398 if (slot.tertiary) {
17399 res = getTextSubField.call(this, Item, variables[0], slot.tertiary, false, res.usedOrig, null, family_var);
17400 tertiary = res.name;
17401 tertiary_locale = res.locale;
17402 tertiary_tok = res.token;
17403 if (family_var && !res.found_variant_ok) {
17404 if (tertiary) {
17405 // The abbreviate() function could use a cleanup, after Zotero correct to use title-short
17406 tertiary = abbreviate(state, tertiary_tok, Item, false, tertiary, family_var, true);
17407 }
17408 }
17409 //print("XXX tertiary_locale: "+tertiary_locale);
17410 }
17411
17412 // Decoration of primary (currently translit only) goes here
17413 var primaryPrefix;
17414 if (slot.primary === "locale-translit") {
17415 primaryPrefix = state.opt.citeAffixes[langPrefs][slot.primary].prefix;
17416 }
17417 // XXX This should probably protect against italics at higher
17418 // levels.
17419
17420 if (primaryPrefix === "<i>" && variables[0] === 'title' && !primaryUsedOrig) {
17421 var hasItalic = false;
17422 for (var i = 0, ilen = primary_tok.decorations.length; i < ilen; i += 1) {
17423 if (primary_tok.decorations[i][0] === "@font-style"
17424 && primary_tok.decorations[i][1] === "italic") {
17425
17426 hasItalic = true;
17427 }
17428 }
17429 if (!hasItalic) {
17430 primary_tok.decorations.push(["@font-style", "italic"]);
17431 }
17432 }
17433
17434 //print("XXX "+primary_tok.strings["text-case"]);
17435 if (primary_locale !== "en" && primary_tok.strings["text-case"] === "title") {
17436 primary_tok.strings["text-case"] = "passthrough";
17437 }
17438
17439 if ("title" === variables[0]) {
17440 primary = CSL.demoteNoiseWords(state, primary, this["leading-noise-words"]);
17441 }
17442
17443 if (secondary || tertiary) {
17444
17445 state.output.openLevel("empty");
17446
17447 // A little too aggressive maybe.
17448 primary_tok.strings.suffix = primary_tok.strings.suffix.replace(/[ .,]+$/,"");
17449 state.output.append(primary, primary_tok);
17450 state.tmp.probably_rendered_something = true;
17451
17452 if (primary === secondary) {
17453 secondary = false;
17454 }
17455 if (secondary) {
17456 secondary_tok.strings.prefix = state.opt.citeAffixes[langPrefs][slot.secondary].prefix;
17457 secondary_tok.strings.suffix = state.opt.citeAffixes[langPrefs][slot.secondary].suffix;
17458 // Add a space if empty
17459 if (!secondary_tok.strings.prefix) {
17460 secondary_tok.strings.prefix = " ";
17461 }
17462 // Remove quotes
17463 for (var i = secondary_tok.decorations.length - 1; i > -1; i += -1) {
17464 if (['@quotes/true', '@font-style/italic', '@font-style/oblique', '@font-weight/bold'].indexOf(secondary_tok.decorations[i].join('/')) > -1) {
17465 secondary_tok.decorations = secondary_tok.decorations.slice(0, i).concat(secondary_tok.decorations.slice(i + 1));
17466 }
17467 }
17468 if (secondary_locale !== "en" && secondary_tok.strings["text-case"] === "title") {
17469 secondary_tok.strings["text-case"] = "passthrough";
17470 }
17471 var secondary_outer = new CSL.Token();
17472 secondary_outer.decorations.push(["@font-style", "normal"]);
17473 secondary_outer.decorations.push(["@font-weight", "normal"]);
17474 state.output.openLevel(secondary_outer);
17475 state.output.append(secondary, secondary_tok);
17476 state.output.closeLevel();
17477
17478 var blob_obj = state.output.current.value();
17479 var blobs_pos = state.output.current.value().blobs.length - 1;
17480 // Suppress supplementary multilingual info on subsequent
17481 // partners of a parallel cite?
17482 }
17483 if (primary === tertiary) {
17484 tertiary = false;
17485 }
17486
17487 if (tertiary) {
17488 tertiary_tok.strings.prefix = state.opt.citeAffixes[langPrefs][slot.tertiary].prefix;
17489 tertiary_tok.strings.suffix = state.opt.citeAffixes[langPrefs][slot.tertiary].suffix;
17490 // Add a space if empty
17491 if (!tertiary_tok.strings.prefix) {
17492 tertiary_tok.strings.prefix = " ";
17493 }
17494 // Remove quotes
17495 for (var i = tertiary_tok.decorations.length - 1; i > -1; i += -1) {
17496 if (['@quotes/true', '@font-style/italic', '@font-style/oblique', '@font-weight/bold'].indexOf(tertiary_tok.decorations[i].join('/')) > -1) {
17497 tertiary_tok.decorations = tertiary_tok.decorations.slice(0, i).concat(tertiary_tok.decorations.slice(i + 1));
17498 }
17499 }
17500 if (tertiary_locale !== "en" && tertiary_tok.strings["text-case"] === "title") {
17501 tertiary_tok.strings["text-case"] = "passthrough";
17502 }
17503 var tertiary_outer = new CSL.Token();
17504 tertiary_outer.decorations.push(["@font-style", "normal"]);
17505 tertiary_outer.decorations.push(["@font-weight", "normal"]);
17506 state.output.openLevel(tertiary_outer);
17507 state.output.append(tertiary, tertiary_tok);
17508 state.output.closeLevel();
17509
17510 var blob_obj = state.output.current.value();
17511 var blobs_pos = state.output.current.value().blobs.length - 1;
17512 // Suppress supplementary multilingual info on subsequent
17513 // partners of a parallel cite?
17514 // See note above.
17515 }
17516 state.output.closeLevel();
17517 } else {
17518 state.output.append(primary, primary_tok);
17519 state.tmp.probably_rendered_something = true;
17520 }
17521 return null;
17522 };
17523 }
17524 this.getOutputFunction = getOutputFunction;
17525};
17526
17527/*global CSL: true */
17528
17529/**
17530 * Style token.
17531 * <p>This class provides the tokens that define
17532 * the runtime version of the style. The tokens are
17533 * instantiated by {@link CSL.Core.Build}, but the token list
17534 * must be post-processed with
17535 * {@link CSL.Core.Configure} before it can be used to generate
17536 * citations.</p>
17537 * @param {String} name The node name represented by this token.
17538 * @param {Int} tokentype A flag indicating whether this token
17539 * marks the start of a node, the end of a node, or is a singleton.
17540 * @class
17541 */
17542CSL.Token = function (name, tokentype, conditional) {
17543 /**
17544 * Name of the element.
17545 * <p>This corresponds to the element name of the
17546 * relevant tag in the CSL file.
17547 */
17548 this.name = name;
17549 /**
17550 * Strings and other static content specific to the element.
17551 */
17552 this.strings = {};
17553 this.strings.delimiter = undefined;
17554 this.strings.prefix = "";
17555 this.strings.suffix = "";
17556 /**
17557 * Formatting parameters.
17558 * <p>This is a placeholder at instantiation. It is
17559 * replaced by the result of {@link CSL.setDecorations}
17560 * when the tag is created and configured during {@link CSL.Core.Build}
17561 * by {@link CSL.XmlToToken}. The parameters for particular
17562 * formatting attributes are stored as string arrays, which
17563 * map to formatting functions at runtime,
17564 * when the output format is known. Note that the order in which
17565 * parameters are registered is fixed by the constant
17566 * {@link CSL.FORMAT_KEY_SEQUENCE}.
17567 */
17568 this.decorations = [];
17569 this.variables = [];
17570 /**
17571 * Element functions.
17572 * <p>Functions implementing the styling behaviour of the element
17573 * are pushed into this array in the {@link CSL.Core.Build} phase.
17574 */
17575 this.execs = [];
17576 /**
17577 * Token type.
17578 * <p>This is a flag constant indicating whether the token represents
17579 * a start tag, an end tag, or is a singleton.</p>
17580 */
17581 this.tokentype = tokentype;
17582
17583 // Conditional attributes added to bare tokens at runtime
17584
17585 /**
17586 * Condition evaluator.
17587 * <p>This is a placeholder that receives a single function, and is
17588 * only relevant for a conditional branching tag (<code>if</code> or
17589 * <code>else-if</code>). The function implements the argument to
17590 * the <code>match=</code> attribute (<code>any</code>, <code>all</code>
17591 * or <code>none</code>), by executing the functions registered in the
17592 * <code>tests</code> array (see below), and reacting accordingly. This
17593 * function is invoked by the execution wrappers found in
17594 * {@link CSL.Engine}.</p>
17595 */
17596 // this.evaluator = false;
17597 /**
17598 * Conditions.
17599 * <p>Functions that evaluate to true or false, implementing
17600 * various posisble attributes to the conditional branching tags,
17601 * are registered here during {@link CSL.Core.Build}.
17602 * </p>
17603 */
17604 // this.tests = [];
17605 /**
17606 * Jump point on success.
17607 * <p>This holds the list jump point to be used when the
17608 * <code>evaluator</code> function of a conditional tag
17609 * returns true (success). The jump index value is set during the
17610 * back-to-front token pass performed during {@link CSL.Core.Configure}.
17611 * </p>
17612 */
17613 // this.succeed = false;
17614 /**
17615 * Jump point on failure.
17616 * <p>This holds the list jump point to be used when the
17617 * <code>evaluator</code> function of a conditional tag
17618 * returns false (failure). Jump index values are set during the
17619 * back-to-front token pass performed during {@link CSL.Core.Configure}.
17620 * </p>
17621 */
17622 // this.fail = false;
17623 /**
17624 * Index of next token.
17625 * <p>This holds the index of the next token in the
17626 * token list, which is the default "jump-point" for ordinary
17627 * processing. Jump index values are set during the
17628 * back-to-front token pass performed during {@link CSL.Core.Configure}.
17629 * </p>
17630 */
17631 // this.next = false;
17632};
17633
17634// Have needed this for yonks
17635CSL.Util.cloneToken = function (token) {
17636 var newtok, key, pos, len;
17637 if ("string" === typeof token) {
17638 return token;
17639 }
17640 newtok = new CSL.Token(token.name, token.tokentype);
17641 for (var key in token.strings) {
17642 if (token.strings.hasOwnProperty(key)) {
17643 newtok.strings[key] = token.strings[key];
17644 }
17645 }
17646 if (token.decorations) {
17647 newtok.decorations = [];
17648 for (pos = 0, len = token.decorations.length; pos < len; pos += 1) {
17649 newtok.decorations.push(token.decorations[pos].slice());
17650 }
17651 }
17652 if (token.variables) {
17653 newtok.variables = token.variables.slice();
17654 }
17655 // Probably overkill; this is only used for cloning formatting
17656 // tokens.
17657 if (token.execs) {
17658 newtok.execs = token.execs.slice();
17659 if (token.tests) {
17660 newtok.tests = token.tests.slice();
17661 }
17662 }
17663 return newtok;
17664};
17665
17666/*global CSL: true */
17667
17668/**
17669 * Ambiguous Cite Configuration Object
17670 * @class
17671 */
17672CSL.AmbigConfig = function () {
17673 this.maxvals = [];
17674 this.minval = 1;
17675 this.names = [];
17676 this.givens = [];
17677 this.year_suffix = false;
17678 this.disambiguate = 0;
17679};
17680
17681/*global CSL: true */
17682
17683CSL.Blob = function (str, token, levelname) {
17684 var len, pos, key;
17685 this.levelname = levelname;
17686 //print(levelname);
17687 if (token) {
17688 this.strings = {"prefix":"","suffix":""};
17689 for (var key in token.strings) {
17690 if (token.strings.hasOwnProperty(key)) {
17691 this.strings[key] = token.strings[key];
17692 }
17693 }
17694 this.decorations = [];
17695 if (token.decorations === undefined) {
17696 len = 0;
17697 } else {
17698 len = token.decorations.length;
17699 }
17700 for (pos = 0; pos < len; pos += 1) {
17701 this.decorations.push(token.decorations[pos].slice());
17702 }
17703 } else {
17704 this.strings = {};
17705 this.strings.prefix = "";
17706 this.strings.suffix = "";
17707 this.strings.delimiter = "";
17708 this.decorations = [];
17709 }
17710 if ("string" === typeof str) {
17711 this.blobs = str;
17712 } else if (str) {
17713 this.blobs = [str];
17714 } else {
17715 this.blobs = [];
17716 }
17717 this.alldecor = [this.decorations];
17718};
17719
17720
17721CSL.Blob.prototype.push = function (blob) {
17722 if ("string" === typeof this.blobs) {
17723 CSL.error("Attempt to push blob onto string object");
17724 } else if (false !== blob) {
17725 blob.alldecor = blob.alldecor.concat(this.alldecor);
17726 this.blobs.push(blob);
17727 }
17728};
17729
17730/*global CSL: true */
17731
17732/**
17733 * An output instance object representing a number or a range
17734 *
17735 * with attributes next and start, and
17736 * methods isRange(), renderStart(), renderEnd() and renderRange().
17737 * At render time, the output queue will perform optional
17738 * collapsing of these objects in the queue, according to
17739 * configurable options, and apply any decorations registered
17740 * in the object to the output elements.
17741 * @namespace Range object and friends.
17742 */
17743
17744CSL.NumericBlob = function (particle, num, mother_token, id) {
17745 // item id is used to assure that prefix delimiter is invoked only
17746 // when joining blobs across items
17747 this.id = id;
17748 this.alldecor = [];
17749 this.num = num;
17750 this.particle = particle;
17751 this.blobs = num.toString();
17752 this.status = CSL.START;
17753 this.strings = {};
17754 if (mother_token) {
17755 this.gender = mother_token.gender;
17756 this.decorations = mother_token.decorations;
17757 this.strings.prefix = mother_token.strings.prefix;
17758 this.strings.suffix = mother_token.strings.suffix;
17759 this.strings["text-case"] = mother_token.strings["text-case"];
17760 this.successor_prefix = mother_token.successor_prefix;
17761 this.range_prefix = mother_token.range_prefix;
17762 this.splice_prefix = mother_token.splice_prefix;
17763 this.formatter = mother_token.formatter;
17764 if (!this.formatter) {
17765 this.formatter = new CSL.Output.DefaultFormatter();
17766 }
17767 if (this.formatter) {
17768 this.type = this.formatter.format(1);
17769 }
17770 } else {
17771 this.decorations = [];
17772 this.strings.prefix = "";
17773 this.strings.suffix = "";
17774 this.successor_prefix = "";
17775 this.range_prefix = "";
17776 this.splice_prefix = "";
17777 this.formatter = new CSL.Output.DefaultFormatter();
17778 }
17779};
17780
17781
17782CSL.NumericBlob.prototype.setFormatter = function (formatter) {
17783 this.formatter = formatter;
17784 this.type = this.formatter.format(1);
17785};
17786
17787
17788CSL.Output.DefaultFormatter = function () {};
17789
17790CSL.Output.DefaultFormatter.prototype.format = function (num) {
17791 return num.toString();
17792};
17793
17794CSL.NumericBlob.prototype.checkNext = function (next,start) {
17795 if (start) {
17796 this.status = CSL.START;
17797 if ("object" === typeof next) {
17798 if (next.num === (this.num + 1)) {
17799 next.status = CSL.SUCCESSOR;
17800 } else {
17801 next.status = CSL.SEEN;
17802 }
17803 }
17804 } else if (! next || !next.num || this.type !== next.type || next.num !== (this.num + 1)) {
17805 if (this.status === CSL.SUCCESSOR_OF_SUCCESSOR) {
17806 this.status = CSL.END;
17807 }
17808 if ("object" === typeof next) {
17809 next.status = CSL.SEEN;
17810 }
17811 } else { // next number is in the sequence
17812 if (this.status === CSL.START || this.status === CSL.SEEN) {
17813 next.status = CSL.SUCCESSOR;
17814 } else if (this.status === CSL.SUCCESSOR || this.status === CSL.SUCCESSOR_OF_SUCCESSOR) {
17815 if (this.range_prefix) {
17816 next.status = CSL.SUCCESSOR_OF_SUCCESSOR;
17817 this.status = CSL.SUPPRESS;
17818 } else {
17819 next.status = CSL.SUCCESSOR;
17820 }
17821 }
17822 // wakes up the correct delimiter.
17823 //if (this.status === CSL.SEEN) {
17824 // this.status = CSL.SUCCESSOR;
17825 //}
17826 }
17827};
17828
17829
17830CSL.NumericBlob.prototype.checkLast = function (last) {
17831 // Used to adjust final non-range join
17832 if (this.status === CSL.SEEN
17833 || (last.num !== (this.num - 1) && this.status === CSL.SUCCESSOR)) {
17834 this.status = CSL.SUCCESSOR;
17835 return true;
17836 }
17837 return false;
17838};
17839
17840/*global CSL: true */
17841
17842CSL.Util.fixDateNode = function (parent, pos, node) {
17843 var form, variable, datexml, subnode, partname, attr, val, prefix, suffix, children, subchildren, display, cslid;
17844
17845 var lingo = this.cslXml.getAttributeValue(node, "lingo");
17846
17847 var default_locale = this.cslXml.getAttributeValue(node, "default-locale");
17848
17849 // Raise date flag, used to control inclusion of year-suffix key in sorts
17850 // This may be a little reckless: not sure what happens on no-date conditions
17851 this.build.date_key = true;
17852
17853 form = this.cslXml.getAttributeValue(node, "form");
17854 var lingo;
17855 if (default_locale) {
17856 lingo = this.opt["default-locale"][0];
17857 } else {
17858 lingo = this.cslXml.getAttributeValue(node, "lingo");
17859 }
17860
17861 if (!this.getDate(form, default_locale)) {
17862 return parent;
17863 }
17864
17865 var dateparts = this.cslXml.getAttributeValue(node, "date-parts");
17866
17867 variable = this.cslXml.getAttributeValue(node, "variable");
17868 prefix = this.cslXml.getAttributeValue(node, "prefix");
17869 suffix = this.cslXml.getAttributeValue(node, "suffix");
17870 display = this.cslXml.getAttributeValue(node, "display");
17871 cslid = this.cslXml.getAttributeValue(node, "cslid");
17872
17873 //
17874 // Xml: Copy a node
17875 //
17876 datexml = this.cslXml.nodeCopy(this.getDate(form, default_locale));
17877 this.cslXml.setAttribute(datexml, 'lingo', this.opt.lang);
17878 this.cslXml.setAttribute(datexml, 'form', form);
17879 this.cslXml.setAttribute(datexml, 'date-parts', dateparts);
17880 this.cslXml.setAttribute(datexml, "cslid", cslid);
17881 //
17882 // Xml: Set attribute
17883 //
17884 this.cslXml.setAttribute(datexml, 'variable', variable);
17885 this.cslXml.setAttribute(datexml, 'default-locale', default_locale);
17886 //
17887 // Xml: Set flag
17888 //
17889 if (prefix) {
17890 //
17891 // Xml: Set attribute
17892 //
17893 this.cslXml.setAttribute(datexml, "prefix", prefix);
17894 }
17895 if (suffix) {
17896 //
17897 // Xml: Set attribute
17898 //
17899 this.cslXml.setAttribute(datexml, "suffix", suffix);
17900 }
17901 if (display) {
17902 //
17903 // Xml: Set attribute
17904 //
17905 this.cslXml.setAttribute(datexml, "display", display);
17906 }
17907 //
17908 // Step through any date-part children of the layout date node,
17909 // and lay their attributes onto the corresponding node in the
17910 // locale template node copy.
17911 //
17912 // tests: language_BaseLocale
17913 // tests: date_LocalizedTextInStyleLocaleWithTextCase
17914 //
17915 children = this.cslXml.children(datexml);
17916 for (var key in children) {
17917 subnode = children[key];
17918 if ("date-part" === this.cslXml.nodename(subnode)) {
17919 partname = this.cslXml.getAttributeValue(subnode, "name");
17920 if (default_locale) {
17921 this.cslXml.setAttributeOnNodeIdentifiedByNameAttribute(datexml, "date-part", partname, "@default-locale", "true");
17922 }
17923 }
17924 }
17925
17926 children = this.cslXml.children(node);
17927 for (var key in children) {
17928 subnode = children[key];
17929 if ("date-part" === this.cslXml.nodename(subnode)) {
17930 partname = this.cslXml.getAttributeValue(subnode, "name");
17931 subchildren = this.cslXml.attributes(subnode);
17932 for (attr in subchildren) {
17933 if ("@name" === attr) {
17934 continue;
17935 }
17936 if (lingo && lingo !== this.opt.lang) {
17937 if (["@suffix", "@prefix", "@form"].indexOf(attr) > -1) {
17938 continue;
17939 }
17940 }
17941 val = subchildren[attr];
17942 this.cslXml.setAttributeOnNodeIdentifiedByNameAttribute(datexml, "date-part", partname, attr, val);
17943 }
17944 }
17945 }
17946
17947 if ("year" === this.cslXml.getAttributeValue(node, "date-parts")) {
17948
17949 //
17950 // Xml: Find one node by attribute and delete
17951 //
17952 this.cslXml.deleteNodeByNameAttribute(datexml, 'month');
17953 //
17954 // Xml: Find one node by attribute and delete
17955 //
17956 this.cslXml.deleteNodeByNameAttribute(datexml, 'day');
17957
17958 } else if ("year-month" === this.cslXml.getAttributeValue(node, "date-parts")) {
17959 //
17960 // Xml: Find one node by attribute and delete
17961 //
17962 this.cslXml.deleteNodeByNameAttribute(datexml, 'day');
17963 } else if ("month-day" === this.cslXml.getAttributeValue(node, "date-parts")) {
17964 //
17965 // Xml: Get child nodes
17966 //
17967 var childNodes = this.cslXml.children(datexml);
17968 for (var i=1,ilen=this.cslXml.numberofnodes(childNodes);i<ilen;i++) {
17969 //
17970 // Xml: Get attribute value (for string comparison)
17971 //
17972 if (this.cslXml.getAttributeValue(childNodes[i], 'name') === "year") {
17973 //
17974 // Xml: Set attribute value
17975 //
17976 this.cslXml.setAttribute(childNodes[i-1], "suffix", "");
17977 break;
17978 }
17979 }
17980 //
17981 // Xml: Find one node by attribute and delete
17982 //
17983 this.cslXml.deleteNodeByNameAttribute(datexml, 'year');
17984 }
17985 return this.cslXml.insertChildNodeAfter(parent, node, pos, datexml);
17986};
17987
17988/*global CSL: true */
17989
17990CSL.dateMacroAsSortKey = function (state, Item) {
17991 CSL.dateAsSortKey.call(this, state, Item, true);
17992};
17993
17994
17995CSL.dateAsSortKey = function (state, Item, isMacro) {
17996 var dp, elem, value, e, yr, prefix, i, ilen;
17997 var variable = this.variables[0];
17998 var macroFlag = "empty";
17999 if (isMacro && state.tmp.extension) {
18000 macroFlag = "macro-with-date";
18001 }
18002 dp = Item[variable];
18003 if ("undefined" === typeof dp) {
18004 dp = {"date-parts": [[0]] };
18005 if (!dp.year) {
18006 state.tmp.empty_date = true;
18007 }
18008 }
18009 if ("undefined" === typeof this.dateparts) {
18010 this.dateparts = ["year", "month", "day"];
18011 }
18012 if (dp.raw) {
18013 dp = state.fun.dateparser.parseDateToArray(dp.raw);
18014 } else if (dp["date-parts"]) {
18015 dp = state.dateParseArray(dp);
18016 }
18017 if ("undefined" === typeof dp) {
18018 dp = {};
18019 }
18020 for (i = 0, ilen = CSL.DATE_PARTS_INTERNAL.length; i < ilen; i += 1) {
18021 elem = CSL.DATE_PARTS_INTERNAL[i];
18022 value = 0;
18023 e = elem;
18024 if (e.slice(-4) === "_end") {
18025 e = e.slice(0, -4);
18026 }
18027 if (dp[elem] && this.dateparts.indexOf(e) > -1) {
18028 value = dp[elem];
18029 }
18030 if (elem.slice(0, 4) === "year") {
18031 yr = CSL.Util.Dates[e].numeric(state, value);
18032 var prefix = "Y";
18033 if (yr[0] === "-") {
18034 prefix = "X";
18035 yr = yr.slice(1);
18036 yr = 9999 - parseInt(yr, 10);
18037 }
18038 state.output.append(CSL.Util.Dates[elem.slice(0, 4)].numeric(state, (prefix + yr)), macroFlag);
18039 } else {
18040 value = CSL.Util.Dates[e]["numeric-leading-zeros"](state, value);
18041 // Ugh.
18042 if (!value) {
18043 value = "00";
18044 }
18045 state.output.append(value, macroFlag);
18046 }
18047 }
18048};
18049
18050CSL.Engine.prototype.dateParseArray = function (date_obj) {
18051 var ret, field, dp, exts;
18052 ret = {};
18053 for (field in date_obj) {
18054 if (field === "date-parts") {
18055 dp = date_obj["date-parts"];
18056 if (dp.length > 1) {
18057 if (dp[0].length !== dp[1].length) {
18058 CSL.error("CSL data error: element mismatch in date range input.");
18059 }
18060 }
18061 exts = ["", "_end"];
18062 for (var i = 0, ilen = dp.length; i < ilen; i += 1) {
18063 for (var j = 0, jlen = CSL.DATE_PARTS.length; j < jlen; j += 1) {
18064 if (isNaN(parseInt(dp[i][j], 10))) {
18065 ret[(CSL.DATE_PARTS[j] + exts[i])] = undefined;
18066 } else {
18067 ret[(CSL.DATE_PARTS[j] + exts[i])] = parseInt(dp[i][j], 10);
18068 }
18069 }
18070 }
18071 } else if (date_obj.hasOwnProperty(field)) {
18072
18073 // XXXX: temporary workaround
18074
18075 if (field === "literal" && "object" === typeof date_obj.literal && "string" === typeof date_obj.literal.part) {
18076 CSL.debug("Warning: fixing up weird literal date value");
18077 ret.literal = date_obj.literal.part;
18078 } else {
18079 ret[field] = date_obj[field];
18080 }
18081 }
18082 }
18083 return ret;
18084};
18085
18086/*global CSL: true */
18087
18088CSL.Util.Names = {};
18089
18090CSL.Util.Names.compareNamesets = CSL.NameOutput.prototype._compareNamesets;
18091
18092/**
18093 * Un-initialize a name (quash caps after first character)
18094 */
18095CSL.Util.Names.unInitialize = function (state, name) {
18096 var i, ilen, namelist, punctlist, ret;
18097 if (!name) {
18098 return "";
18099 }
18100 namelist = name.split(/(?:\-|\s+)/);
18101 punctlist = name.match(/(\-|\s+)/g);
18102 ret = "";
18103 for (i = 0, ilen = namelist.length; i < ilen; i += 1) {
18104 // if (CSL.ALL_ROMANESQUE_REGEXP.exec(namelist[i].slice(0,-1))
18105 // && namelist[i]
18106 // && namelist[i] !== namelist[i].toUpperCase()) {
18107
18108 // More or less like this, to address the following fault report:
18109 // http://forums.zotero.org/discussion/17610/apsa-problems-with-capitalization-of-mc-mac-etc/
18110
18111 // Leaving the name string untouched because name capitalization is varied and wonderful.
18112 // https://github.com/Juris-M/citeproc-js/issues/43
18113
18114 //namelist[i] = namelist[i].slice(0, 1) + namelist[i].slice(1, 2).toLowerCase() + namelist[i].slice(2);
18115 // }
18116 ret += namelist[i];
18117 if (i < ilen - 1) {
18118 ret += punctlist[i];
18119 }
18120 }
18121 return ret;
18122};
18123
18124/**
18125 * Initialize a name.
18126 */
18127CSL.Util.Names.initializeWith = function (state, name, terminator, normalizeOnly) {
18128 var i, ilen, mm, lst, ret;
18129 if (!name) {
18130 return "";
18131 }
18132 if (!terminator) {
18133 terminator = "";
18134 }
18135 if (["Lord", "Lady"].indexOf(name) > -1
18136 || (!name.match(CSL.STARTSWITH_ROMANESQUE_REGEXP)
18137 && !terminator.match("%s"))) {
18138 return name;
18139 }
18140 var namelist = name;
18141 if (state.opt["initialize-with-hyphen"] === false) {
18142 namelist = namelist.replace(/\-/g, " ");
18143 }
18144
18145 // Oh boy.
18146 // We need to suss out what is a set of initials or abbreviation,
18147 // so that they can be selectively normalized. Steps might be:
18148 // (1) Split the string
18149 // (2) Step through the string, deleting periods and, if initalize="false", then
18150 // (a) note abbreviations and initials (separately).
18151 // (3) If initialize="false" then:
18152 // (a) Do the thing below, but only pushing terminator; or else
18153 // (b) Do the thing below
18154
18155 // (1) Split the string
18156 namelist = namelist.replace(/\s*\-\s*/g, "-").replace(/\s+/g, " ");
18157 namelist = namelist.replace(/-([a-z])/g, "\u2013$1");
18158
18159 for (var i=namelist.length-2; i>-1; i += -1) {
18160 if (namelist.slice(i, i+1) === "." && namelist.slice(i+1, i+2) !== " ") {
18161 namelist = namelist.slice(0, i) + ". " + namelist.slice(i+1);
18162 }
18163 }
18164
18165 // Workaround for Internet Explorer
18166 //namelist = namelist.split(/(\-|\s+)/);
18167 // Workaround for Internet Explorer
18168 mm = namelist.match(/[\-\s]+/g);
18169 lst = namelist.split(/[\-\s]+/);
18170 if (mm === null) {
18171 var mmm = lst[0].match(/[^\.]+$/);
18172 if (mmm && mmm[0].length === 1 && mmm[0] !== mmm[0].toLowerCase()) {
18173 lst[0] += ".";
18174 }
18175 }
18176
18177 if (lst.length === 0) {
18178 // This doesn't make much sense, and may be impossible.
18179 namelist = mm;
18180 } else {
18181 namelist = [lst[0]];
18182 for (i = 1, ilen = lst.length; i < ilen; i += 1) {
18183 namelist.push(mm[i - 1]);
18184 namelist.push(lst[i]);
18185 }
18186 }
18187 lst = namelist;
18188
18189 // Use doInitializeName or doNormalizeName, depending on requirements.
18190 if (normalizeOnly) {
18191 ret = CSL.Util.Names.doNormalize(state, lst, terminator);
18192 } else {
18193 ret = CSL.Util.Names.doInitialize(state, lst, terminator);
18194 }
18195 ret = ret.replace(/\u2013([a-z])/g, "-$1");
18196 return ret;
18197};
18198
18199CSL.Util.Names.doNormalize = function (state, namelist, terminator) {
18200 var i, ilen;
18201 // namelist is a flat list of given-name elements and space-like separators between them
18202 terminator = terminator ? terminator : "";
18203 // Flag elements that look like abbreviations
18204 var isAbbrev = [];
18205 for (i = 0, ilen = namelist.length; i < ilen; i += 1) {
18206 if (namelist[i].length > 1 && namelist[i].slice(-1) === ".") {
18207 namelist[i] = namelist[i].slice(0, -1);
18208 isAbbrev.push(true);
18209 } else if (namelist[i].length === 1 && namelist[i].toUpperCase() === namelist[i]) {
18210 isAbbrev.push(true);
18211 } else {
18212 isAbbrev.push(false);
18213 }
18214 }
18215 // Step through the elements of the givenname array
18216 for (i = 0, ilen = namelist.length; i < ilen; i += 2) {
18217 // If the element is not an abbreviation, leave it and its trailing spaces alone
18218 if (isAbbrev[i]) {
18219 // For all elements but the last
18220 if (i < namelist.length - 2) {
18221 // Start from scratch on space-like things following an abbreviation
18222 namelist[i + 1] = "";
18223
18224 if (!isAbbrev[i+2]) {
18225 namelist[i + 1] = " ";
18226 }
18227
18228 // Add the terminator to the element
18229 // If the following element is not a single-character abbreviation, remove a trailing zero-width non-break space, if present
18230 // These ops may leave some duplicate cruft in the elements and separators. This will be cleaned at the end of the function.
18231 if (namelist[i + 2].length > 1) {
18232 namelist[i] = namelist[i] + terminator.replace(/\ufeff$/, "");
18233 } else {
18234 namelist[i] = namelist[i] + terminator;
18235 }
18236 }
18237 // For the last element (if it is an abbreviation), just append the terminator
18238 if (i === namelist.length - 1) {
18239 namelist[i] = namelist[i] + terminator;
18240 }
18241 }
18242 }
18243 // Remove trailing cruft and duplicate spaces, and return
18244 return namelist.join("").replace(/[\u0009\u000a\u000b\u000c\u000d\u0020\ufeff\u00a0]+$/,"").replace(/\s*\-\s*/g, "-").replace(/[\u0009\u000a\u000b\u000c\u000d\u0020]+/g, " ");
18245};
18246
18247CSL.Util.Names.doInitialize = function (state, namelist, terminator) {
18248 var i, ilen, m, j, jlen, lst, n;
18249 for (i = 0, ilen = namelist.length; i < ilen; i += 2) {
18250 n = namelist[i];
18251 if (!n) {
18252 continue;
18253 }
18254 m = n.match(CSL.NAME_INITIAL_REGEXP);
18255 if (!m && (!n.match(CSL.STARTSWITH_ROMANESQUE_REGEXP) && n.length > 1 && terminator.match("%s"))) {
18256 m = n.match(/(.)(.*)/);
18257 }
18258 if (m && m[2] && m[3]) {
18259 m[1] = m[1] + m[2];
18260 m[2] = "";
18261 }
18262 if (m && m[1].slice(0, 1) === m[1].slice(0, 1).toUpperCase()) {
18263 var extra = "";
18264 if (m[2]) {
18265 var s = "";
18266 lst = m[2].split("");
18267 for (j = 0, jlen = lst.length; j < jlen; j += 1) {
18268 var c = lst[j];
18269 if (c === c.toUpperCase()) {
18270 s += c;
18271 } else {
18272 break;
18273 }
18274 }
18275 if (s.length < m[2].length) {
18276 extra = s.toLocaleLowerCase();
18277 }
18278 }
18279 // namelist[i] = m[1].toLocaleUpperCase() + extra;
18280 namelist[i] = m[1] + extra;
18281 if (i < (ilen - 1)) {
18282 if (terminator.match("%s")) {
18283 namelist[i] = terminator.replace("%s", namelist[i]);
18284 } else {
18285 if (namelist[i + 1].indexOf("-") > -1) {
18286 namelist[i + 1] = terminator + namelist[i + 1];
18287 } else {
18288 namelist[i + 1] = terminator;
18289 }
18290 }
18291 } else {
18292 if (terminator.match("%s")) {
18293 namelist[i] = terminator.replace("%s", namelist[i]);
18294 } else {
18295 namelist.push(terminator);
18296 }
18297 }
18298 } else if (n.match(CSL.ROMANESQUE_REGEXP) && (!m || !m[3])) {
18299 namelist[i] = " " + n;
18300 }
18301 }
18302 var ret = namelist.join("");
18303 ret = ret.replace(/[\u0009\u000a\u000b\u000c\u000d\u0020\ufeff\u00a0]+$/,"").replace(/\s*\-\s*/g, "-").replace(/[\u0009\u000a\u000b\u000c\u000d\u0020]+/g, " ");
18304 return ret;
18305};
18306
18307CSL.Util.Names.getRawName = function (name) {
18308 var ret = [];
18309 if (name.literal) {
18310 ret.push(name.literal);
18311 } else {
18312 if (name.given) {
18313 ret.push(name.given);
18314 }
18315 if (name.family) {
18316 ret.push(name.family);
18317 }
18318 }
18319 return ret.join(" ");
18320};
18321
18322// deleted CSL.Util.Names.initNameSlices()
18323// no longer used.
18324
18325// deleted CSL.Util.Names,rescueNameElements()
18326// apparently not used.
18327
18328
18329
18330/*global CSL: true */
18331
18332/**
18333 * Date mangling functions.
18334 * @namespace Date construction utilities
18335 */
18336CSL.Util.Dates = {};
18337
18338/**
18339 * Year manglers
18340 * <p>short, long</p>
18341 */
18342CSL.Util.Dates.year = {};
18343
18344/**
18345 * Convert year to long form
18346 * <p>This just passes the number back as a string.</p>
18347 */
18348CSL.Util.Dates.year["long"] = function (state, num) {
18349 if (!num) {
18350 if ("boolean" === typeof num) {
18351 num = "";
18352 } else {
18353 num = 0;
18354 }
18355 }
18356 return num.toString();
18357};
18358
18359/**
18360 * Crudely convert to Japanese Imperial form.
18361 * <p>Returns the result as a string.</p>
18362 */
18363CSL.Util.Dates.year.imperial = function (state, num, end) {
18364 var year = "";
18365 if (!num) {
18366 if ("boolean" === typeof num) {
18367 num = "";
18368 } else {
18369 num = 0;
18370 }
18371 }
18372 end = end ? "_end" : "";
18373 var month = state.tmp.date_object["month" + end];
18374 month = month ? ""+month : "1";
18375 while (month.length < 2) {
18376 month = "0" + month;
18377 }
18378 var day = state.tmp.date_object["day" + end];
18379 day = day ? ""+day : "1";
18380 while (day.length < 2) {
18381 day = "0" + day;
18382 }
18383 var date = parseInt(num + month + day, 10);
18384 var label;
18385 var offset;
18386 if (date >= 18680908 && date < 19120730) {
18387 label = '\u660e\u6cbb';
18388 offset = 1867;
18389 } else if (date >= 19120730 && date < 19261225) {
18390 label = '\u5927\u6b63';
18391 offset = 1911;
18392 } else if (date >= 19261225 && date < 19890108) {
18393 label = '\u662d\u548c';
18394 offset = 1925;
18395 } else if (date >= 19890108) {
18396 label = '\u5e73\u6210';
18397 offset = 1988;
18398 }
18399 if (label && offset) {
18400 var normalizedKey = label;
18401 if (state.sys.normalizeAbbrevsKey) {
18402 // The first argument does not need to specify the exact variable
18403 // name.
18404 normalizedKey = state.sys.normalizeAbbrevsKey("number", label);
18405 }
18406 if (!state.transform.abbrevs['default']['number'][normalizedKey]) {
18407 state.transform.loadAbbreviation('default', "number", normalizedKey);
18408 }
18409 if (state.transform.abbrevs['default']['number'][normalizedKey]) {
18410 label = state.transform.abbrevs['default']['number'][normalizedKey];
18411 }
18412 year = label + (num - offset);
18413 }
18414 return year;
18415};
18416
18417/**
18418 * Convert year to short form
18419 * <p>Just crops any 4-digit year to the last two digits.</p>
18420 */
18421CSL.Util.Dates.year["short"] = function (state, num) {
18422 num = num.toString();
18423 if (num && num.length === 4) {
18424 return num.substr(2);
18425 }
18426};
18427
18428
18429/**
18430 * Convert year to short form
18431 * <p>Just crops any 4-digit year to the last two digits.</p>
18432 */
18433CSL.Util.Dates.year.numeric = function (state, num) {
18434 var m, pre;
18435 num = "" + num;
18436 var m = num.match(/([0-9]*)$/);
18437 if (m) {
18438 pre = num.slice(0, m[1].length * -1);
18439 num = m[1];
18440 } else {
18441 pre = num;
18442 num = "";
18443 }
18444 while (num.length < 4) {
18445 num = "0" + num;
18446 }
18447 return (pre + num);
18448};
18449
18450
18451/*
18452 * MONTH manglers
18453 * normalize
18454 * long, short, numeric, numeric-leading-zeros
18455 */
18456CSL.Util.Dates.normalizeMonth = function (num, useSeason) {
18457 var ret;
18458 if (!num) {
18459 num = 0;
18460 }
18461 num = "" + num;
18462 if (!num.match(/^[0-9]+$/)) {
18463 num = 0;
18464 }
18465 num = parseInt(num, 10);
18466 if (useSeason) {
18467 var res = {stub: "month-", num: num};
18468 if (res.num < 1 || res.num > 24) {
18469 res.num = 0;
18470 } else {
18471 while (res.num > 16) {
18472 res.num = res.num - 4;
18473 }
18474 if (res.num > 12) {
18475 res.stub = "season-";
18476 res.num = res.num - 12;
18477 }
18478 }
18479 ret = res;
18480 } else {
18481 if (num < 1 || num > 12) {
18482 num = 0;
18483 }
18484 ret = num;
18485 }
18486 return ret;
18487};
18488
18489CSL.Util.Dates.month = {};
18490
18491/**
18492 * Convert month to numeric form
18493 * <p>This just passes the number back as a string.</p>
18494 */
18495CSL.Util.Dates.month.numeric = function (state, num) {
18496 var num = CSL.Util.Dates.normalizeMonth(num);
18497 if (!num) {
18498 num = "";
18499 }
18500 return num;
18501};
18502
18503/**
18504 * Convert month to numeric-leading-zeros form
18505 * <p>This just passes the number back as string padded with zeros.</p>
18506 */
18507CSL.Util.Dates.month["numeric-leading-zeros"] = function (state, num) {
18508 var num = CSL.Util.Dates.normalizeMonth(num);
18509 if (!num) {
18510 num = "";
18511 } else {
18512 num = "" + num;
18513 while (num.length < 2) {
18514 num = "0" + num;
18515 }
18516 }
18517 return num;
18518};
18519
18520/**
18521 * Convert month to long form
18522 * <p>This passes back the month of the locale in long form.</p>
18523 */
18524
18525// Gender is not currently used. Is it needed?
18526
18527CSL.Util.Dates.month["long"] = function (state, num, gender, forceDefaultLocale) {
18528 var res = CSL.Util.Dates.normalizeMonth(num, true);
18529 var num = res.num;
18530 if (!num) {
18531 num = "";
18532 } else {
18533 num = "" + num;
18534 while (num.length < 2) {
18535 num = "0" + num;
18536 }
18537 num = state.getTerm(res.stub + num, "long", 0, 0, false, forceDefaultLocale);
18538 }
18539 return num;
18540};
18541
18542/**
18543 * Convert month to long form
18544 * <p>This passes back the month of the locale in short form.</p>
18545 */
18546
18547// See above.
18548
18549CSL.Util.Dates.month["short"] = function (state, num, gender, forceDefaultLocale) {
18550 var res = CSL.Util.Dates.normalizeMonth(num, true);
18551 var num = res.num;
18552 if (!num) {
18553 num = "";
18554 } else {
18555 num = "" + num;
18556 while (num.length < 2) {
18557 num = "0" + num;
18558 }
18559 num = state.getTerm(res.stub + num, "short", 0, 0, false, forceDefaultLocale);
18560 }
18561 return num;
18562};
18563
18564/*
18565 * DAY manglers
18566 * numeric, numeric-leading-zeros, ordinal
18567 */
18568CSL.Util.Dates.day = {};
18569
18570/**
18571 * Convert day to numeric form
18572 * <p>This just passes the number back as a string.</p>
18573 */
18574CSL.Util.Dates.day.numeric = function (state, num) {
18575 return num.toString();
18576};
18577
18578CSL.Util.Dates.day["long"] = CSL.Util.Dates.day.numeric;
18579
18580/**
18581 * Convert day to numeric-leading-zeros form
18582 * <p>This just passes the number back as a string padded with zeros.</p>
18583 */
18584CSL.Util.Dates.day["numeric-leading-zeros"] = function (state, num) {
18585 if (!num) {
18586 num = 0;
18587 }
18588 num = num.toString();
18589 while (num.length < 2) {
18590 num = "0" + num;
18591 }
18592 return num.toString();
18593};
18594
18595/**
18596 * Convert day to ordinal form
18597 * <p>This will one day pass back the number as a string with the
18598 * ordinal suffix appropriate to the locale. For the present,
18599 * it just does what is most of the time right for English.</p>
18600 */
18601CSL.Util.Dates.day.ordinal = function (state, num, gender) {
18602 return state.fun.ordinalizer.format(num, gender);
18603};
18604
18605/*global CSL: true */
18606
18607/**
18608 * Helper functions for constructing sort keys.
18609 * @namespace Sort key utilities
18610 */
18611CSL.Util.Sort = {};
18612
18613/**
18614 * Strip prepositions from a string
18615 * <p>Used when generating sort keys.</p>
18616 */
18617CSL.Util.Sort.strip_prepositions = function (str) {
18618 var m;
18619 if ("string" === typeof str) {
18620 m = str.toLocaleLowerCase();
18621 m = str.match(/^((a|an|the)\s+)/);
18622 }
18623 if (m) {
18624 str = str.substr(m[1].length);
18625 }
18626 return str;
18627};
18628
18629/*global CSL: true */
18630
18631CSL.Util.substituteStart = function (state, target) {
18632 var element_trace, display, bib_first, func, choose_start, if_start, nodetypes;
18633 func = function (state, Item, item) {
18634 if (item && item.parallel) {
18635 state.tmp.group_context.tip.parallel_result = item.parallel;
18636 }
18637 if (item && item.repeats && Object.keys(item.repeats).length > 0) {
18638 state.tmp.group_context.tip.parallel_repeats = item.repeats;
18639 }
18640 for (var i = 0, ilen = this.decorations.length; i < ilen; i += 1) {
18641 if ("@strip-periods" === this.decorations[i][0] && "true" === this.decorations[i][1]) {
18642 state.tmp.strip_periods += 1;
18643 break;
18644 }
18645 }
18646 };
18647 this.execs.push(func);
18648 if (this.decorations && state.opt.development_extensions.csl_reverse_lookup_support) {
18649 this.decorations.reverse();
18650 this.decorations.push(["@showid","true", this.cslid]);
18651 this.decorations.reverse();
18652 }
18653 //
18654 // Contains body code for both substitute and first-field/remaining-fields
18655 // formatting.
18656 //
18657 nodetypes = ["number", "date", "names"];
18658 if (("text" === this.name && !this.postponed_macro) || nodetypes.indexOf(this.name) > -1) {
18659 element_trace = function (state, Item, item) {
18660 if (state.tmp.element_trace.value() === "author" || "names" === this.name) {
18661 if (!state.tmp.just_looking && item && item["author-only"] && state.tmp.area !== "intext") {
18662 if (!state.tmp.probably_rendered_something) {
18663 } else {
18664 state.tmp.element_trace.push("suppress-me");
18665 }
18666 }
18667 if (!state.tmp.just_looking && item && item["suppress-author"]) {
18668 if (!state.tmp.probably_rendered_something) {
18669 state.tmp.element_trace.push("suppress-me");
18670 }
18671 }
18672 }
18673 else if ("date" === this.name) {
18674 if (!state.tmp.just_looking && item && item["author-only"] && state.tmp.area !== "intext") {
18675 if (state.tmp.probably_rendered_something) {
18676 state.tmp.element_trace.push("suppress-me");
18677 }
18678 }
18679 /*
18680 if (!state.tmp.just_looking && item && item["suppress-author"]) {
18681 if (state.tmp.probably_rendered_something) {
18682 //state.tmp.element_trace.push("suppress-me");
18683 }
18684 }
18685 */
18686 } else {
18687 if (!state.tmp.just_looking && item && item["author-only"] && state.tmp.area !== "intext") {
18688 // XXX can_block_substitute probably is doing nothing here. The value is always true.
18689 if (!state.tmp.probably_rendered_something && state.tmp.can_block_substitute) {
18690 } else {
18691 state.tmp.element_trace.push("suppress-me");
18692 }
18693 } else if (item && item["suppress-author"]) {
18694 state.tmp.element_trace.push("do-not-suppress-me");
18695 }
18696 }
18697 };
18698 this.execs.push(element_trace);
18699 }
18700 display = this.strings.cls;
18701 this.strings.cls = false;
18702 if (state.build.render_nesting_level === 0) {
18703 //
18704 // The markup formerly known as @bibliography/first
18705 //
18706 // Separate second-field-align from the generic display logic.
18707 // There will be some code replication, but not in the
18708 // assembled style.
18709 //
18710 if (state.build.area === "bibliography" && state.bibliography.opt["second-field-align"]) {
18711 bib_first = new CSL.Token("group", CSL.START);
18712 bib_first.decorations = [["@display", "left-margin"]];
18713 func = function (state, Item) {
18714 if (!state.tmp.render_seen) {
18715 bib_first.strings.first_blob = Item.id;
18716 state.output.startTag("bib_first", bib_first);
18717 }
18718 };
18719 bib_first.execs.push(func);
18720 target.push(bib_first);
18721 } else if (CSL.DISPLAY_CLASSES.indexOf(display) > -1) {
18722 bib_first = new CSL.Token("group", CSL.START);
18723 bib_first.decorations = [["@display", display]];
18724 func = function (state, Item) {
18725 bib_first.strings.first_blob = Item.id;
18726 state.output.startTag("bib_first", bib_first);
18727 };
18728 bib_first.execs.push(func);
18729 target.push(bib_first);
18730 }
18731 state.build.cls = display;
18732 }
18733 state.build.render_nesting_level += 1;
18734 // Should this be render_nesting_level, with the increment
18735 // below? ... ?
18736 if (state.build.substitute_level.value() === 1) {
18737 //
18738 // All top-level elements in a substitute environment get
18739 // wrapped in conditionals. The substitute_level variable
18740 // is a stack, because spanned names elements (with their
18741 // own substitute environments) can be nested inside
18742 // a substitute environment.
18743 //
18744 // (okay, we use conditionals a lot more than that.
18745 // we slot them in for author-only as well...)
18746 choose_start = new CSL.Token("choose", CSL.START);
18747 CSL.Node.choose.build.call(choose_start, state, target);
18748 if_start = new CSL.Token("if", CSL.START);
18749 //
18750 // Set a test of the shadow if token to skip this
18751 // macro if we have acquired a name value.
18752
18753 // check for variable
18754 func = function () {
18755 if (state.tmp.can_substitute.value()) {
18756 return true;
18757 }
18758 return false;
18759 };
18760 if_start.tests ? {} : if_start.tests = [];
18761 if_start.tests.push(func);
18762 if_start.test = state.fun.match.any(this, state, if_start.tests);
18763 target.push(if_start);
18764 }
18765
18766 if (state.sys.variableWrapper
18767 && this.variables_real
18768 && this.variables_real.length) {
18769
18770 func = function (state, Item, item) {
18771 if (!state.tmp.just_looking && !state.tmp.suppress_decorations) {
18772 // Attach item data and variable names.
18773 // Do with them what you will.
18774 var variable_entry = new CSL.Token("text", CSL.START);
18775 variable_entry.decorations = [["@showid", "true"]];
18776 state.output.startTag("variable_entry", variable_entry);
18777 var position = null;
18778 if (item) {
18779 position = item.position;
18780 }
18781 if (!position) {
18782 position = 0;
18783 }
18784 var positionMap = [
18785 "first",
18786 "subsequent",
18787 "ibid",
18788 "ibid-with-locator"
18789 ];
18790 var noteNumber = 0;
18791 if (item && item.noteIndex) {
18792 noteNumber = item.noteIndex;
18793 }
18794 var firstReferenceNoteNumber = 0;
18795 if (item && item['first-reference-note-number']) {
18796 firstReferenceNoteNumber = item['first-reference-note-number'];
18797 }
18798 var citationNumber = 0;
18799 // XXX Will this EVER happen?
18800 if (item && item['citation-number']) {
18801 citationNumber = item['citation-number'];
18802 }
18803 var index = 0;
18804 if (item && item.index) {
18805 index = item.index;
18806 }
18807 var params = {
18808 itemData: Item,
18809 variableNames: this.variables,
18810 context: state.tmp.area,
18811 xclass: state.opt.xclass,
18812 position: positionMap[position],
18813 "note-number": noteNumber,
18814 "first-reference-note-number": firstReferenceNoteNumber,
18815 "citation-number": citationNumber,
18816 "index": index,
18817 "mode": state.opt.mode
18818 };
18819 state.output.current.value().params = params;
18820 }
18821 };
18822 this.execs.push(func);
18823 }
18824};
18825
18826
18827CSL.Util.substituteEnd = function (state, target) {
18828 var func, bib_first_end, bib_other, if_end, choose_end, author_substitute, str;
18829
18830 if (state.sys.variableWrapper
18831 && (this.hasVariable || (this.variables_real && this.variables_real.length))) {
18832
18833 func = function (state) {
18834 if (!state.tmp.just_looking && !state.tmp.suppress_decorations) {
18835 state.output.endTag("variable_entry");
18836 }
18837 };
18838 this.execs.push(func);
18839 }
18840
18841 func = function (state) {
18842 for (var i = 0, ilen = this.decorations.length; i < ilen; i += 1) {
18843 if ("@strip-periods" === this.decorations[i][0] && "true" === this.decorations[i][1]) {
18844 state.tmp.strip_periods += -1;
18845 break;
18846 }
18847 }
18848 };
18849 this.execs.push(func);
18850
18851 state.build.render_nesting_level += -1;
18852 if (state.build.render_nesting_level === 0) {
18853 if (state.build.cls) {
18854 func = function (state) {
18855 state.output.endTag("bib_first");
18856 };
18857 this.execs.push(func);
18858 state.build.cls = false;
18859 } else if (state.build.area === "bibliography" && state.bibliography.opt["second-field-align"]) {
18860 bib_first_end = new CSL.Token("group", CSL.END);
18861 // first func end
18862 func = function (state) {
18863 if (!state.tmp.render_seen) {
18864 state.output.endTag("bib_first"); // closes bib_first
18865 }
18866 };
18867 bib_first_end.execs.push(func);
18868 target.push(bib_first_end);
18869 bib_other = new CSL.Token("group", CSL.START);
18870 bib_other.decorations = [["@display", "right-inline"]];
18871 func = function (state) {
18872 if (!state.tmp.render_seen) {
18873 state.tmp.render_seen = true;
18874 state.output.startTag("bib_other", bib_other);
18875 }
18876 };
18877 bib_other.execs.push(func);
18878 target.push(bib_other);
18879 }
18880 }
18881 if (state.build.substitute_level.value() === 1) {
18882 if_end = new CSL.Token("if", CSL.END);
18883 target.push(if_end);
18884 choose_end = new CSL.Token("choose", CSL.END);
18885 CSL.Node.choose.build.call(choose_end, state, target);
18886 }
18887
18888 if ("names" === this.name || ("text" === this.name && this.variables_real !== "title")) {
18889 author_substitute = new CSL.Token("text", CSL.SINGLETON);
18890 func = function (state, Item) {
18891 if (state.tmp.area !== "bibliography") {
18892 return;
18893 }
18894 if ("string" !== typeof state.bibliography.opt["subsequent-author-substitute"]) {
18895 return;
18896 }
18897 if (this.variables_real && !Item[this.variables_real]) {
18898 return;
18899 }
18900 if (state.tmp.substituted_variable !== this.variables_real) {
18901 return;
18902 }
18903
18904 var subrule = state.bibliography.opt["subsequent-author-substitute-rule"];
18905 var i, ilen;
18906 //var text_esc = CSL.getSafeEscape(state);
18907 var printing = !state.tmp.suppress_decorations;
18908 if (printing && state.tmp.subsequent_author_substitute_ok) {
18909 if (state.tmp.rendered_name) {
18910 if ("partial-each" === subrule || "partial-first" === subrule) {
18911 var dosub = true;
18912 var rendered_name = [];
18913 // This is a wee bit risky, as we're assuming that the name
18914 // children and the list of stringified names are congruent.
18915 // That *should* always be true, but you never know.
18916 for (i = 0, ilen = state.tmp.name_node.children.length; i < ilen; i += 1) {
18917 var name = state.tmp.rendered_name[i];
18918 if (dosub
18919 && state.tmp.last_rendered_name && state.tmp.last_rendered_name.length > (i - 1)
18920 && name && !name.localeCompare(state.tmp.last_rendered_name[i])) {
18921
18922 str = new CSL.Blob(state[state.tmp.area].opt["subsequent-author-substitute"]);
18923 state.tmp.name_node.children[i].blobs = [str];
18924 if ("partial-first" === subrule) {
18925 dosub = false;
18926 }
18927 } else {
18928 dosub = false;
18929 }
18930 rendered_name.push(name);
18931 }
18932 // might want to slice this?
18933 state.tmp.last_rendered_name = rendered_name;
18934 } else if ("complete-each" === subrule) {
18935 var rendered_name = state.tmp.rendered_name.join(",");
18936 if (rendered_name) {
18937 if (state.tmp.last_rendered_name && !rendered_name.localeCompare(state.tmp.last_rendered_name)) {
18938 for (i = 0, ilen = state.tmp.name_node.children.length; i < ilen; i += 1) {
18939 str = new CSL.Blob(state[state.tmp.area].opt["subsequent-author-substitute"]);
18940 state.tmp.name_node.children[i].blobs = [str];
18941 }
18942 }
18943 state.tmp.last_rendered_name = rendered_name;
18944 }
18945 } else {
18946 var rendered_name = state.tmp.rendered_name.join(",");
18947 if (rendered_name) {
18948 if (state.tmp.last_rendered_name && !rendered_name.localeCompare(state.tmp.last_rendered_name)) {
18949 str = new CSL.Blob(state[state.tmp.area].opt["subsequent-author-substitute"]);
18950 if (state.tmp.label_blob) {
18951 state.tmp.name_node.top.blobs = [str,state.tmp.label_blob];
18952 } else if (state.tmp.name_node.top.blobs.length) {
18953 state.tmp.name_node.top.blobs[0].blobs = [str];
18954 } else {
18955 state.tmp.name_node.top.blobs = [str];
18956 }
18957 state.tmp.substituted_variable = this.variables_real;
18958 }
18959 state.tmp.last_rendered_name = rendered_name;
18960 }
18961 }
18962 state.tmp.subsequent_author_substitute_ok = false;
18963 }
18964 }
18965 };
18966 this.execs.push(func);
18967 }
18968
18969 if (("text" === this.name && !this.postponed_macro) || ["number", "date", "names"].indexOf(this.name) > -1) {
18970 // element trace
18971 func = function (state, Item) {
18972 // element_trace is a mess, but it's trying to do something simple.
18973 // A queue append is done, and element_trace.value() returns "suppress-me"
18974 // the append is aborted. That's it.
18975 // It seems only to be used on numeric elements of numeric styles ATM.
18976 // If used only for that purpose, it could be greatly simplified.
18977 // If cleaned up, it could do more interesting things, like control
18978 // the suppression of names set later than first position.
18979 if (state.tmp.element_trace.mystack.length>1) {
18980 state.tmp.element_trace.pop();
18981 }
18982 };
18983 this.execs.push(func);
18984 }
18985};
18986
18987/*global CSL: true */
18988
18989CSL.Util.padding = function (num) {
18990 var m = num.match(/\s*(-{0,1}[0-9]+)/);
18991 if (m) {
18992 num = parseInt(m[1], 10);
18993 if (num < 0) {
18994 num = 99999999999999999999 + num;
18995 }
18996 num = "" + num;
18997 while (num.length < 20) {
18998 num = "0" + num;
18999 }
19000 }
19001 return num;
19002};
19003
19004CSL.Util.LongOrdinalizer = function () {};
19005
19006CSL.Util.LongOrdinalizer.prototype.init = function (state) {
19007 this.state = state;
19008};
19009
19010CSL.Util.LongOrdinalizer.prototype.format = function (num, gender) {
19011 if (num < 10) {
19012 num = "0" + num;
19013 }
19014 // Argument true means "loose".
19015 var ret = CSL.Engine.getField(
19016 CSL.LOOSE,
19017 this.state.locale[this.state.opt.lang].terms,
19018 "long-ordinal-" + num,
19019 "long",
19020 0,
19021 gender
19022 );
19023 if (!ret) {
19024 ret = this.state.fun.ordinalizer.format(num, gender);
19025 }
19026 // Probably too optimistic -- what if only renders in _sort?
19027 this.state.tmp.cite_renders_content = true;
19028 return ret;
19029};
19030
19031
19032CSL.Util.Ordinalizer = function (state) {
19033 this.state = state;
19034 this.suffixes = {};
19035};
19036
19037CSL.Util.Ordinalizer.prototype.init = function () {
19038 if (!this.suffixes[this.state.opt.lang]) {
19039 this.suffixes[this.state.opt.lang] = {};
19040 for (var i = 0, ilen = 3; i < ilen; i += 1) {
19041 var gender = [undefined, "masculine", "feminine"][i];
19042 this.suffixes[this.state.opt.lang][gender] = [];
19043 for (var j = 1; j < 5; j += 1) {
19044 var ordinal = this.state.getTerm("ordinal-0" + j, "long", false, gender);
19045 if ("undefined" === typeof ordinal) {
19046 delete this.suffixes[this.state.opt.lang][gender];
19047 break;
19048 }
19049 this.suffixes[this.state.opt.lang][gender].push(ordinal);
19050 }
19051 }
19052 }
19053};
19054
19055CSL.Util.Ordinalizer.prototype.format = function (num, gender) {
19056 var str;
19057 num = parseInt(num, 10);
19058 str = "" + num;
19059 var suffix = "";
19060 var trygenders = [];
19061 if (gender) {
19062 trygenders.push(gender);
19063 }
19064 trygenders.push("neuter");
19065 if (this.state.locale[this.state.opt.lang].ord["1.0.1"]) {
19066 suffix = this.state.getTerm("ordinal",false,0,gender);
19067 var trygender;
19068 for (var i = 0, ilen = trygenders.length; i < ilen; i += 1) {
19069 trygender = trygenders[i];
19070 var ordinfo = this.state.locale[this.state.opt.lang].ord["1.0.1"];
19071 if (ordinfo["whole-number"][str] && ordinfo["whole-number"][str][trygender]) {
19072 suffix = this.state.getTerm(this.state.locale[this.state.opt.lang].ord["1.0.1"]["whole-number"][str][trygender],false,0,gender);
19073 } else if (ordinfo["last-two-digits"][str.slice(str.length - 2)] && ordinfo["last-two-digits"][str.slice(str.length - 2)][trygender]) {
19074 suffix = this.state.getTerm(this.state.locale[this.state.opt.lang].ord["1.0.1"]["last-two-digits"][str.slice(str.length - 2)][trygender],false,0,gender);
19075 } else if (ordinfo["last-digit"][str.slice(str.length - 1)] && ordinfo["last-digit"][str.slice(str.length - 1)][trygender]) {
19076 suffix = this.state.getTerm(this.state.locale[this.state.opt.lang].ord["1.0.1"]["last-digit"][str.slice(str.length - 1)][trygender],false,0,gender);
19077 }
19078 if (suffix) {
19079 break;
19080 }
19081 }
19082 } else {
19083 if (!gender) {
19084 // XXX hack to prevent crash on CSL 1.0 styles.
19085 // Reported by Carles.
19086 gender = undefined;
19087 }
19088 this.state.fun.ordinalizer.init();
19089 if ((num / 10) % 10 === 1 || (num > 10 && num < 20)) {
19090 suffix = this.suffixes[this.state.opt.lang][gender][3];
19091 } else if (num % 10 === 1 && num % 100 !== 11) {
19092 suffix = this.suffixes[this.state.opt.lang][gender][0];
19093 } else if (num % 10 === 2 && num % 100 !== 12) {
19094 suffix = this.suffixes[this.state.opt.lang][gender][1];
19095 } else if (num % 10 === 3 && num % 100 !== 13) {
19096 suffix = this.suffixes[this.state.opt.lang][gender][2];
19097 } else {
19098 suffix = this.suffixes[this.state.opt.lang][gender][3];
19099 }
19100 }
19101 str = str += suffix;
19102 return str;
19103};
19104
19105CSL.Util.Romanizer = function () {};
19106
19107CSL.Util.Romanizer.prototype.format = function (num) {
19108 var ret, pos, n, numstr, len;
19109 ret = "";
19110 if (num < 6000) {
19111 numstr = num.toString().split("");
19112 numstr.reverse();
19113 pos = 0;
19114 n = 0;
19115 len = numstr.length;
19116 for (pos = 0; pos < len; pos += 1) {
19117 n = parseInt(numstr[pos], 10);
19118 ret = CSL.ROMAN_NUMERALS[pos][n] + ret;
19119 }
19120 }
19121 return ret;
19122};
19123
19124
19125/**
19126 * Create a suffix formed from a list of arbitrary characters of arbitrary length.
19127 * <p>This is a <i>lot</i> harder than it seems.</p>
19128 */
19129CSL.Util.Suffixator = function (slist) {
19130 if (!slist) {
19131 slist = CSL.SUFFIX_CHARS;
19132 }
19133 this.slist = slist.split(",");
19134};
19135
19136/**
19137 * The format method.
19138 * <p>This method is used in generating ranges. Every numeric
19139 * formatter (of which Suffixator is one) must be an instantiated
19140 * object with such a "format" method.</p>
19141 */
19142
19143CSL.Util.Suffixator.prototype.format = function (N) {
19144 // Many thanks to Avram Lyon for this code, and good
19145 // riddance to the several functions that it replaces.
19146 var X;
19147 N += 1;
19148 var key = "";
19149 do {
19150 X = ((N % 26) === 0) ? 26 : (N % 26);
19151 var key = this.slist[X-1] + key;
19152 N = (N - X) / 26;
19153 } while ( N !== 0 );
19154 return key;
19155};
19156
19157
19158CSL.Engine.prototype.processNumber = function (node, ItemObject, variable) {
19159 //print("** processNumber() ItemObject[variable]="+ItemObject[variable]);
19160 var val;
19161
19162 var me = this;
19163
19164 var fullformAnd = ",\\s+and\\s+|\\s+and\\s+";
19165 if (this.opt.lang.slice(0, 2) !== "en") {
19166 fullformAnd += "|,\\s+" + this.getTerm("and") + "\\s+|\\s+" + this.getTerm("and") + "\\s+";
19167 }
19168 var symbolAnd = "\\s*&\\s*";
19169 var andRex = new RegExp("^" + symbolAnd+ "$");
19170 var joinerMatchRex = new RegExp("(" + symbolAnd + "|" + fullformAnd + "|;\\s+|,\\s+|\\s*\\\\*[\\-\\u2013]+\\s*)", "g");
19171 var joinerSplitRex = new RegExp("(?:" + symbolAnd + "|" + fullformAnd + "|;\\s+|,\\s+|\\s*\\\\*[\\-\\u2013]+\\s*|\\s*&\\s*)");
19172
19173 // This guesses whether the symbol form is defined or not.
19174 // It's the best we can do, because when locales are built, all of the
19175 // holes are filled explictly with fallback values: the symbol form is never undefined.
19176 var localeAnd = this.getTerm("and");
19177 var localeAmpersand = this.getTerm("and", "symbol");
19178 if (localeAnd === localeAmpersand) {
19179 localeAmpersand = "&";
19180 }
19181
19182 // XXXX shadow_numbers should carry an array of objects with
19183 // XXXX full data for each. The test of a number should be
19184 // XXXX a separate function, possibly supported by a splitter
19185 // XXXX method also used here. Keep code for each action in one place,
19186 // XXXX to prevent debugging from becoming a nightmare.
19187
19188 // The capture pattern below would apply affixes to all sub-elements,
19189 // which is not what we want. Sub-elements should nest within, or
19190 // affixes should be edited. The latter is probably easier to handle.
19191
19192 // values = [
19193 // {
19194 // label: "sec.",
19195 // label-form: "plural",
19196 // value: 100,
19197 // styling: [object],
19198 // numeric: true
19199 // joiningSuffix: " & ",
19200 // },
19201 // {
19202 // label: "sec.",
19203 // label-form: "none",
19204 // value: 103,
19205 // styling: [object],
19206 // numeric: true,
19207 // joiningSuffix: ""
19208 // }
19209 // ]
19210
19211 function normalizeFieldValue(str) {
19212 str = str.trim();
19213 var m = str.match(/^([^ ]+)/);
19214 if (m && !CSL.STATUTE_SUBDIV_STRINGS[m[1]]) {
19215 var embeddedLabel = null;
19216 if (["locator", "locator-extra"].indexOf(variable) > -1) {
19217 if (ItemObject.label) {
19218 embeddedLabel = CSL.STATUTE_SUBDIV_STRINGS_REVERSE[ItemObject.label];
19219 } else {
19220 embeddedLabel = "p.";
19221 }
19222 } else {
19223 embeddedLabel = CSL.STATUTE_SUBDIV_STRINGS_REVERSE[variable];
19224 }
19225 if (embeddedLabel) {
19226 str = embeddedLabel + " " + str;
19227 }
19228 }
19229 return str;
19230 }
19231
19232
19233 function composeNumberInfo(origLabel, label, val, joiningSuffix) {
19234 joiningSuffix = joiningSuffix ? joiningSuffix : "";
19235 var info = {};
19236
19237 if (!label && !CSL.STATUTE_SUBDIV_STRINGS_REVERSE[variable]) {
19238 label = "var:"+variable;
19239 }
19240
19241 if (label) {
19242 var m = label.match(/(\s*)([^\s]+)(\s*)/);
19243 info.label = m[2];
19244 info.origLabel = origLabel;
19245 info.labelSuffix = m[3] ? m[3] : "";
19246 info.plural = 0;
19247 info.labelVisibility = false;
19248 }
19249
19250 var m = val.match(/^([0-9]*[a-zA-Z]+0*)?([0-9]+(?:[a-zA-Z]*|[-,a-zA-Z]+))$/);
19251 //var m = val.match(/^([0-9]*[a-zA-Z]0*)([0-9]+(?:[a-zA-Z]*|[-,a-zA-Z]+))$/);
19252 if (m) {
19253 info.particle = m[1] ? m[1] : "";
19254 info.value = m[2];
19255 } else {
19256 info.particle = "";
19257 info.value = val;
19258 }
19259 info.joiningSuffix = joiningSuffix.replace(/\s*-\s*/, "-");
19260 return info;
19261 }
19262
19263 function fixupSubsections(elems) {
19264 // This catches things like p. 12a-c, recombining content to yield
19265 // numeric true despite the hyphen.
19266 for (var i=elems.length-2;i>-1;i-=2) {
19267 if (elems[i] === "-"
19268 && elems[i-1].match(/^(?:(?:[a-z]|[a-z][a-z]|[a-z][a-z][a-z]|[a-z][a-z][a-z][a-z])\. *)*[0-9]+[,a-zA-Z]+$/)
19269 && elems[i+1].match(/^[,a-zA-Z]+$/)) {
19270 elems[i-1] = elems.slice(i-1,i+2).join("");
19271 elems = elems.slice(0,i).concat(elems.slice(i+2));
19272 }
19273 }
19274 return elems;
19275 }
19276
19277 function parseString(str, defaultLabel) {
19278 defaultLabel = defaultLabel ? defaultLabel : "";
19279
19280 str = normalizeFieldValue(str, defaultLabel);
19281
19282 // Split chunks and collate delimiters.
19283 var elems = [];
19284 var m = str.match(joinerMatchRex);
19285 if (m) {
19286 for (var i=0, ilen=m.length; i<ilen; i++) {
19287 if (m[i].match(andRex)) {
19288 m[i] = " " + localeAmpersand + " ";
19289 }
19290 }
19291 var lst = str.split(joinerSplitRex);
19292 var recombine = false;
19293 for (var i in lst) {
19294 if (("" + lst[i]).replace(/^[a-z]\.\s+/, "").match(/[^\s0-9ivxlcmIVXLCM]/)) {
19295 //recombine = true;
19296 break;
19297 }
19298 }
19299 if (recombine) {
19300 elems = [str];
19301 } else {
19302 for (var i=0,ilen=lst.length-1; i<ilen; i++) {
19303 elems.push(lst[i]);
19304 elems.push(m[i]);
19305 }
19306 elems.push(lst[lst.length-1]);
19307 //print("ELEMS: "+elems);
19308 elems = fixupSubsections(elems);
19309 //print(" fixup: "+elems);
19310 }
19311 } else {
19312 var elems = [str];
19313 }
19314 // Split elements within each chunk build list of value objects.
19315 var values = [];
19316 var label = defaultLabel;
19317 var origLabel = "";
19318 for (var i=0,ilen=elems.length;i<ilen;i += 2) {
19319 var m = elems[i].match(/((?:^| )(?:[a-z]|[a-z][a-z]|[a-z][a-z][a-z]|[a-z][a-z][a-z][a-z])(?:\.| ) *)/g);
19320 if (m) {
19321 var lst = elems[i].split(/(?:(?:^| )(?:[a-z]|[a-z][a-z]|[a-z][a-z][a-z]|[a-z][a-z][a-z][a-z])(?:\.| ) *)/);
19322 // Head off disaster by merging parsed labels on non-numeric values into content
19323 for (var j=lst.length-1;j>0;j--) {
19324 if (lst[j-1] && (!lst[j].match(/^[0-9]+([-;,:a-zA-Z]*)$/) || !lst[j-1].match(/^[0-9]+([-;,:a-zA-Z]*)$/))) {
19325 lst[j-1] = lst[j-1] + m[j-1] + lst[j];
19326 lst = lst.slice(0,j).concat(lst.slice(j+1));
19327 m = m.slice(0,j-1).concat(m.slice(j));
19328 }
19329 }
19330 // merge bad leading label into content
19331 if (m.length > 0) {
19332 var slug = m[0].trim();
19333 var notAlabel = !CSL.STATUTE_SUBDIV_STRINGS[slug]
19334 || !me.getTerm(CSL.STATUTE_SUBDIV_STRINGS[slug])
19335 || (["locator", "number", "locator-extra"].indexOf(variable) === -1 && CSL.STATUTE_SUBDIV_STRINGS[slug] !== variable);
19336 if (notAlabel) {
19337 if (i === 0) {
19338 m = m.slice(1);
19339 lst[0] = lst[0] + " " + slug + " " + lst[1];
19340 lst = lst.slice(0,1).concat(lst.slice(2));
19341 }
19342 } else {
19343 origLabel = slug;
19344 }
19345 }
19346
19347 for (var j=0,jlen=lst.length; j<jlen; j++) {
19348 if (lst[j] || j === (lst.length-1)) {
19349 var filteredOrigLabel;
19350 label = m[j-1] ? m[j-1] : label;
19351 if (origLabel === label.trim()) {
19352 filteredOrigLabel = "";
19353 } else {
19354 filteredOrigLabel = origLabel;
19355 }
19356 //var origLabel = j > 1 ? m[j-1] : "";
19357 str = lst[j] ? lst[j].trim() : "";
19358 if (j === (lst.length-1)) {
19359 values.push(composeNumberInfo(filteredOrigLabel, label, str, elems[i+1]));
19360 } else {
19361 values.push(composeNumberInfo(filteredOrigLabel, label, str));
19362 }
19363 }
19364 }
19365 } else {
19366 var filteredOrigLabel;
19367 if (origLabel === label.trim()) {
19368 filteredOrigLabel = "";
19369 } else {
19370 filteredOrigLabel = origLabel;
19371 }
19372 values.push(composeNumberInfo(filteredOrigLabel, label, elems[i], elems[i+1]));
19373 }
19374 }
19375 return values;
19376 }
19377
19378 function setSpaces(values) {
19379 // Add space joins (is this really right?)
19380 for (var i=0,ilen=values.length-1;i<ilen;i++) {
19381 if (!values[i].joiningSuffix && values[i+1].label) {
19382 values[i].joiningSuffix = " ";
19383 }
19384 }
19385 }
19386
19387 function fixNumericAndCount(values, i, currentLabelInfo) {
19388 var master = values[currentLabelInfo.pos];
19389 var val = values[i].value;
19390 var isEscapedHyphen = master.joiningSuffix === "\\-";
19391 if (val.particle && val.particle !== master.particle) {
19392 currentLabelInfo.collapsible = false;
19393 }
19394 var mVal = val.match(/^[0-9]+([-,:a-zA-Z]*)$/);
19395 var mCurrentLabel = master.value.match(/^[0-9]+([-,:a-zA-Z]*)$/);
19396 if (!val || !mVal || !mCurrentLabel || isEscapedHyphen) {
19397 currentLabelInfo.collapsible = false;
19398 if (!val || !mCurrentLabel) {
19399 currentLabelInfo.numeric = false;
19400 }
19401 if (isEscapedHyphen) {
19402 currentLabelInfo.count--;
19403 }
19404 }
19405 if ((mVal && mVal[1]) || (mCurrentLabel && mCurrentLabel[1])) {
19406 currentLabelInfo.collapsible = false;
19407 }
19408 if (undefined === values[i].collapsible) {
19409 for (var j=i,jlen=i+currentLabelInfo.count;j<jlen;j++) {
19410 if (isNaN(parseInt(values[j].value)) && !values[j].value.match(/^[ivxlcmIVXLCM]+$/)) {
19411 values[j].collapsible = false;
19412 } else {
19413 values[j].collapsible = true;
19414 }
19415 }
19416 currentLabelInfo.collapsible = values[i].collapsible;
19417 }
19418 var isCollapsible = currentLabelInfo.collapsible;
19419 for (var j=currentLabelInfo.pos,jlen=(currentLabelInfo.pos + currentLabelInfo.count); j<jlen; j++) {
19420 if (currentLabelInfo.count > 1 && isCollapsible) {
19421 values[j].plural = 1;
19422 }
19423 values[j].numeric = currentLabelInfo.numeric;
19424 values[j].collapsible = currentLabelInfo.collapsible;
19425 }
19426 }
19427
19428 function fixLabelVisibility(values, groupStartPos, currentLabelInfo) {
19429 if (currentLabelInfo.label.slice(0, 4) !== "var:") {
19430 if (currentLabelInfo.pos === 0) {
19431 if (["locator", "number", "locator-extra"].indexOf(variable) > -1) {
19432 // Actually, shouldn't we do this always?
19433 if (!me.getTerm(CSL.STATUTE_SUBDIV_STRINGS[currentLabelInfo.label])) {
19434 values[currentLabelInfo.pos].labelVisibility = true;
19435 }
19436 }
19437 // If there is an explicit
19438 // label embedded at the start of a field that
19439 // does not match the context, it should be
19440 // marked for rendering.
19441 if (["locator", "number", "locator-extra"].indexOf(variable) === -1) {
19442 if (CSL.STATUTE_SUBDIV_STRINGS[currentLabelInfo.label] !== variable) {
19443 values[0].labelVisibility = true;
19444 }
19445 }
19446 } else {
19447 // Also, mark initial mid-field labels for
19448 // rendering.
19449 //if (values[i-1].label !== values[i].label && currentLabelInfo.label.slice(0, 4) !== "var:") {
19450 values[currentLabelInfo.pos].labelVisibility = true;
19451 //}
19452 }
19453 }
19454 }
19455
19456 function setPluralsAndNumerics(values) {
19457 if (values.length === 0) {
19458 return;
19459 }
19460 var groupStartPos = 0;
19461 var groupCount = 1;
19462
19463 for (var i=1,ilen=values.length;i<ilen;i++) {
19464 var lastVal = values[i-1];
19465 var thisVal = values[i];
19466 if (lastVal.label === thisVal.label && lastVal.particle === lastVal.particle) {
19467 groupCount++;
19468 } else {
19469 var currentLabelInfo = JSON.parse(JSON.stringify(values[groupStartPos]));
19470 currentLabelInfo.pos = groupStartPos;
19471 currentLabelInfo.count = groupCount;
19472 currentLabelInfo.numeric = true;
19473 fixNumericAndCount(values, groupStartPos, currentLabelInfo);
19474 if (lastVal.label !== thisVal.label) {
19475 fixLabelVisibility(values, groupStartPos, currentLabelInfo);
19476 }
19477 groupStartPos = i;
19478 groupCount = 1;
19479 }
19480 }
19481 // Not sure why this repetition is necessary?
19482 var currentLabelInfo = JSON.parse(JSON.stringify(values[groupStartPos]));
19483 currentLabelInfo.pos = groupStartPos;
19484 currentLabelInfo.count = groupCount;
19485 currentLabelInfo.numeric = true;
19486 fixNumericAndCount(values, groupStartPos, currentLabelInfo);
19487 fixLabelVisibility(values, groupStartPos, currentLabelInfo);
19488 if (values.length && values[0].numeric && variable.slice(0, 10) === "number-of-") {
19489 if (parseInt(ItemObject[variable], 10) > 1) {
19490 values[0].plural = 1;
19491 }
19492 }
19493 }
19494
19495 function stripHyphenBackslash(joiningSuffix) {
19496 return joiningSuffix.replace("\\-", "-");
19497 }
19498
19499 function setStyling(values) {
19500 var masterNode = CSL.Util.cloneToken(node);
19501 var masterStyling = new CSL.Token();
19502 if (!me.tmp.just_looking) {
19503 // Per discussion @ https://discourse.citationstyles.org/t/formatting-attributes-and-hyphen/1518
19504 masterStyling.decorations = masterNode.decorations;
19505 masterNode.decorations = [];
19506 //for (var j=masterNode.decorations.length-1;j>-1;j--) {
19507 // if (masterNode.decorations[j][0] === "@quotes") {
19508 // // Add to styling
19509 // masterStyling.decorations = masterStyling.decorations.concat(masterNode.decorations.slice(j, j+1));
19510 // // Remove from node
19511 // masterNode.decorations = masterNode.decorations.slice(0, j).concat(masterNode.decorations.slice(j+1));
19512 // }
19513 //}
19514 masterStyling.strings.prefix = masterNode.strings.prefix;
19515 masterNode.strings.prefix = "";
19516 masterStyling.strings.suffix = masterNode.strings.suffix;
19517 masterNode.strings.suffix = "";
19518 }
19519 var masterLabel = values.length ? values[0].label : null;
19520 if (values.length) {
19521 for (var i=0,ilen=values.length; i<ilen; i++) {
19522 var val = values[i];
19523 // Clone node, make styling parameters on each instance sane.
19524 var newnode = CSL.Util.cloneToken(masterNode);
19525 newnode.gender = node.gender;
19526 if (masterLabel === val.label) {
19527 newnode.formatter = node.formatter;
19528 }
19529 if (val.numeric) {
19530 newnode.successor_prefix = val.successor_prefix;
19531 }
19532 newnode.strings.suffix = newnode.strings.suffix + stripHyphenBackslash(val.joiningSuffix);
19533 val.styling = newnode;
19534 }
19535 if (!me.tmp.just_looking) {
19536 if (values[0].value.slice(0,1) === "\"" && values[values.length-1].value.slice(-1) === "\"") {
19537 values[0].value = values[0].value.slice(1);
19538 values[values.length-1].value = values[values.length-1].value.slice(0,-1);
19539 masterStyling.decorations.push(["@quotes", true]);
19540 }
19541 }
19542 }
19543 return masterStyling;
19544 }
19545
19546 function checkTerm(variable, val) {
19547 var ret = true;
19548 if (["locator", "locator-extra"].indexOf(variable) > -1) {
19549 var label;
19550 if (val.origLabel) {
19551 label = val.origLabel;
19552 } else {
19553 label = val.label;
19554 }
19555 ret = !!me.getTerm(CSL.STATUTE_SUBDIV_STRINGS[label]);
19556 }
19557 return ret;
19558 }
19559
19560 function checkPage(variable, val) {
19561 return variable === "page"
19562 || (["locator", "locator-extra"].indexOf(variable) > -1 && (["p."].indexOf(val.label) > -1 || ["p."].indexOf(val.origLabel) > -1));
19563 }
19564
19565 function fixupRangeDelimiter(variable, val, rangeDelimiter, isNumeric) {
19566 var isPage = checkPage(variable, val);
19567 var hasTerm = checkTerm(variable, val);
19568 if (hasTerm && rangeDelimiter === "-") {
19569 if (isNumeric) {
19570 if (isPage || ["locator", "locator-extra", "issue", "volume", "edition", "number"].indexOf(variable) > -1) {
19571 rangeDelimiter = me.getTerm("page-range-delimiter");
19572 if (!rangeDelimiter) {
19573 rangeDelimiter = "\u2013";
19574 }
19575 }
19576 if (variable === "collection-number") {
19577 rangeDelimiter = me.getTerm("year-range-delimiter");
19578 if (!rangeDelimiter) {
19579 rangeDelimiter = "\u2013";
19580 }
19581 }
19582 }
19583 }
19584 //if (rangeDelimiter === "\\-") {
19585 // rangeDelimiter = "-";
19586 //}
19587 return rangeDelimiter;
19588 }
19589
19590 function manglePageNumbers(values, i, currentInfo) {
19591 if (i<1) {
19592 return;
19593 }
19594 if (currentInfo.count !== 2) {
19595 return;
19596 }
19597 if (values[i-1].particle !== values[i].particle) {
19598 return;
19599 }
19600 if (values[i-1].joiningSuffix !== "-") {
19601 currentInfo.count = 1;
19602 return;
19603 }
19604 if (!me.opt["page-range-format"] && (parseInt(values[i-1].value, 10) > parseInt(values[i].value, 10))) {
19605 values[i-1].joiningSuffix = fixupRangeDelimiter(variable, values[i], values[i-1].joiningSuffix, true);
19606 return;
19607 }
19608 var val = values[i];
19609
19610 var isPage = checkPage(variable, val);
19611 var str;
19612 if (isPage && !isNaN(parseInt(values[i-1].value)) && !isNaN(parseInt(values[i].value))) {
19613 str = values[i-1].particle + values[i-1].value + " - " + values[i].particle + values[i].value;
19614 str = me.fun.page_mangler(str);
19615 } else {
19616 // if (("" + values[i-1].value).match(/[0-9]$/) && ("" + values[i].value).match(/^[0-9]/)) {
19617 if (("" + values[i-1].value).match(/^([0-9]+|[ivxlcmIVXLCM]+)$/) && ("" + values[i].value).match(/^([0-9]+|[ivxlcmIVXLCM]+)$/)) {
19618 values[i-1].joiningSuffix = me.getTerm("page-range-delimiter");
19619 }
19620 str = values[i-1].value + stripHyphenBackslash(values[i-1].joiningSuffix) + values[i].value;
19621 }
19622 var m = str.match(/^((?:[0-9]*[a-zA-Z]+0*))?([0-9]+[a-z]*)(\s*[^0-9]+\s*)([-,a-zA-Z]?0*)([0-9]+[a-z]*)$/);
19623 // var m = str.match(/^((?:[0-9]*[a-zA-Z]+0*))?([0-9]+[a-z]*)(\s*[^0-9]+\s*)([-,a-zA-Z]?0*)([0-9]+[a-z]*)$/);
19624 if (m) {
19625 var rangeDelimiter = m[3];
19626 rangeDelimiter = fixupRangeDelimiter(variable, val, rangeDelimiter, values[i].numeric);
19627 values[i-1].particle = m[1];
19628 values[i-1].value = m[2];
19629 values[i-1].joiningSuffix = rangeDelimiter;
19630 values[i].particle = m[4];
19631 values[i].value = m[5];
19632 }
19633 currentInfo.count = 0;
19634 }
19635
19636 function fixRanges(values) {
19637
19638 if (!node) {
19639 return;
19640 }
19641 if (["page", "page-first", "chapter-number", "collection-number", "edition", "issue", "number", "number-of-pages", "number-of-volumes", "volume", "locator", "locator-extra"].indexOf(variable) === -1) {
19642 return;
19643 }
19644
19645 var currentInfo = {
19646 count: 0,
19647 label: null,
19648 lastHadRangeDelimiter: false
19649 };
19650
19651 for (var i=0,ilen=values.length; i<ilen; i++) {
19652 var val = values[i];
19653 if (!val.collapsible) {
19654 currentInfo.count = 0;
19655 currentInfo.label = null;
19656 var isNumeric = val.numeric;
19657 val.joiningSuffix = fixupRangeDelimiter(variable, val, val.joiningSuffix, isNumeric);
19658 } else if (currentInfo.label === val.label && val.joiningSuffix === "-") {
19659 // So if there is a hyphen here, and none previous, reset to 1
19660 currentInfo.count = 1;
19661 } else if (currentInfo.label === val.label && val.joiningSuffix !== "-") {
19662 // If there is NO hyphen here, count up
19663 currentInfo.count++;
19664 if (currentInfo.count === 2) {
19665 manglePageNumbers(values, i, currentInfo);
19666 }
19667 } else if (currentInfo.label !== val.label) {
19668 // If the label doesn't match and count is 2, process
19669 currentInfo.label = val.label;
19670 currentInfo.count = 1;
19671 } else {
19672 // Safety belt: label doesn't match and count is some other value, so reset to 1
19673 // This never happens, though.
19674 currentInfo.count = 1;
19675 currentInfo.label = val.label;
19676 }
19677 }
19678 // Finally clear, if needed
19679 if (currentInfo.count === 2) {
19680 manglePageNumbers(values, values.length-1, currentInfo);
19681 }
19682 }
19683
19684 function setVariableParams(shadow_numbers, variable, values) {
19685 var obj = shadow_numbers[variable];
19686 if (values.length) {
19687 obj.numeric = values[0].numeric;
19688 obj.collapsible = values[0].collapsible;
19689 obj.plural = values[0].plural;
19690 obj.label = CSL.STATUTE_SUBDIV_STRINGS[values[0].label];
19691 if (variable === "number" && obj.label === "issue" && me.getTerm("number")) {
19692 obj.label = "number";
19693 }
19694 }
19695 }
19696
19697 // Split out the labels and values.
19698
19699 // short-circuit if object exists: if numeric, set styling, no other action
19700 if (node && this.tmp.shadow_numbers[variable] && this.tmp.shadow_numbers[variable].values.length) {
19701 var values = this.tmp.shadow_numbers[variable].values;
19702 fixRanges(values);
19703 //if (!this.tmp.shadow_numbers[variable].masterStyling && !this.tmp.just_looking) {
19704 this.tmp.shadow_numbers[variable].masterStyling = setStyling(values);
19705 //}
19706 return;
19707 }
19708
19709 // info.styling = node;
19710
19711 // This carries value, pluralization and numeric info for use in other contexts.
19712 // XXX We used to use one set of params for the entire variable value.
19713 // XXX Now params are set on individual objects, of which there may be several after parsing.
19714 if (!this.tmp.shadow_numbers[variable]) {
19715 this.tmp.shadow_numbers[variable] = {
19716 values:[]
19717 };
19718 }
19719 //this.tmp.shadow_numbers[variable].values = [];
19720 //this.tmp.shadow_numbers[variable].plural = 0;
19721 //this.tmp.shadow_numbers[variable].numeric = false;
19722 //this.tmp.shadow_numbers[variable].label = false;
19723
19724 if (!ItemObject) {
19725 return;
19726 }
19727
19728 // Possibly apply multilingual transform
19729 var languageRole = CSL.LangPrefsMap[variable];
19730 if (languageRole) {
19731 var localeType = this.opt["cite-lang-prefs"][languageRole][0];
19732 val = this.transform.getTextSubField(ItemObject, variable, "locale-"+localeType, true);
19733 val = val.name;
19734 } else {
19735 val = ItemObject[variable];
19736 }
19737
19738 // XXX HOLDING THIS
19739 // Apply short form ONLY if first element tests is-numeric=false
19740 if (val && this.sys.getAbbreviation) {
19741 // RefMe bug report: print("XX D'oh! (3): "+num);
19742 // true as the fourth argument suppresses update of the UI
19743
19744 // No need for this.
19745 //val = ("" + val).replace(/^\"/, "").replace(/\"$/, "");
19746
19747 var jurisdiction = this.transform.loadAbbreviation(ItemObject.jurisdiction, "number", val);
19748 if (this.transform.abbrevs[jurisdiction].number) {
19749 if (this.transform.abbrevs[jurisdiction].number[val]) {
19750 val = this.transform.abbrevs[jurisdiction].number[val];
19751 } else {
19752 // Strings rendered via cs:number should not be added to the abbreviations
19753 // UI unless they test non-numeric. The test happens below.
19754 if ("undefined" !== typeof this.transform.abbrevs[jurisdiction].number[val]) {
19755 delete this.transform.abbrevs[jurisdiction].number[val];
19756 }
19757 }
19758 }
19759 }
19760
19761 // {
19762 // label: "sec.",
19763 // labelForm: "plural",
19764 // labelVisibility: true,
19765 // value: 100,
19766 // styling: [object],
19767 // numeric: true
19768 // joiningSuffix: " & ",
19769 // },
19770
19771 // Process only if there is a value.
19772 if ("undefined" !== typeof val && ("string" === typeof val || "number" === typeof val)) {
19773
19774 if ("number" === typeof val) {
19775 val = "" + val;
19776 }
19777 var defaultLabel = CSL.STATUTE_SUBDIV_STRINGS_REVERSE[variable];
19778
19779 if (!this.tmp.shadow_numbers.values) {
19780 // XXX
19781 var values = parseString(val, defaultLabel);
19782
19783 setSpaces(values);
19784 //print("setSpaces(): "+JSON.stringify(values, null, 2));
19785
19786 setPluralsAndNumerics(values);
19787 //print("setPluralsAndNumerics(): "+JSON.stringify(values, null, 2));
19788
19789 this.tmp.shadow_numbers[variable].values = values;
19790
19791 }
19792
19793 if (node) {
19794 fixRanges(values);
19795
19796 this.tmp.shadow_numbers[variable].masterStyling = setStyling(values);
19797 //print("setStyling(): "+JSON.stringify(values, null, 2));
19798 }
19799
19800 setVariableParams(this.tmp.shadow_numbers, variable, values);
19801 //print("OK "+JSON.stringify(values, ["label", "origLabel", "labelSuffix", "particle", "collapsible", "value", "numeric", "joiningSuffix", "labelVisibility", "plural"], 2));
19802 }
19803};
19804
19805CSL.Util.outputNumericField = function(state, varname, itemID) {
19806
19807 state.output.openLevel(state.tmp.shadow_numbers[varname].masterStyling);
19808 var nums = state.tmp.shadow_numbers[varname].values;
19809 var masterLabel = nums.length ? nums[0].label : null;
19810 var labelForm = state.tmp.shadow_numbers[varname].labelForm;
19811 var embeddedLabelForm;
19812 if (labelForm) {
19813 embeddedLabelForm = labelForm;
19814 } else {
19815 embeddedLabelForm = "short";
19816 //labelForm = "short";
19817 }
19818 var labelCapitalizeIfFirst = state.tmp.shadow_numbers[varname].labelCapitalizeIfFirst;
19819 var labelDecorations = state.tmp.shadow_numbers[varname].labelDecorations;
19820 var lastLabelName = null;
19821 for (var i=0,ilen=nums.length;i<ilen;i++) {
19822 var num = nums[i];
19823 var label = "";
19824 var labelName;
19825 if (num.label) {
19826 if ('var:' === num.label.slice(0,4)) {
19827 labelName = num.label.slice(4);
19828 } else {
19829 labelName = CSL.STATUTE_SUBDIV_STRINGS[num.label];
19830 }
19831 if (labelName) {
19832 if (num.label === masterLabel) {
19833 label = state.getTerm(labelName, labelForm, num.plural);
19834 } else {
19835 label = state.getTerm(labelName, embeddedLabelForm, num.plural);
19836 }
19837 if (labelCapitalizeIfFirst) {
19838 label = CSL.Output.Formatters["capitalize-first"](state, label);
19839 }
19840 }
19841 }
19842 var labelPlaceholderPos = -1;
19843 if (label) {
19844 labelPlaceholderPos = label.indexOf("%s");
19845 }
19846 var numStyling = CSL.Util.cloneToken(num.styling);
19847 numStyling.formatter = num.styling.formatter;
19848 numStyling.type = num.styling.type;
19849 numStyling.num = num.styling.num;
19850 numStyling.gender = num.styling.gender;
19851 if (labelPlaceholderPos > 0 && labelPlaceholderPos < (label.length-2)) {
19852 numStyling.strings.prefix += label.slice(0,labelPlaceholderPos);
19853 numStyling.strings.suffix = label.slice(labelPlaceholderPos+2) + numStyling.strings.suffix;
19854 } else if (num.labelVisibility) {
19855 if (!label) {
19856 label = num.label;
19857 labelName = num.label;
19858 }
19859 if (labelPlaceholderPos > 0) {
19860 var prefixLabelStyling = new CSL.Token();
19861 prefixLabelStyling.decorations = labelDecorations;
19862 state.output.append(label.slice(0,labelPlaceholderPos), prefixLabelStyling);
19863 } else if (labelPlaceholderPos === (label.length-2) || labelPlaceholderPos === -1) {
19864 // And add a trailing delimiter.
19865 state.output.append(label+num.labelSuffix, "empty");
19866 }
19867 }
19868 if (num.collapsible) {
19869 var blob;
19870 if (num.value.match(/^[1-9][0-9]*$/)) {
19871 blob = new CSL.NumericBlob(num.particle, parseInt(num.value, 10), numStyling, itemID);
19872 } else {
19873 blob = new CSL.NumericBlob(num.particle, num.value, numStyling, itemID);
19874 }
19875 if ("undefined" === typeof blob.gender) {
19876 blob.gender = state.locale[state.opt.lang]["noun-genders"][varname];
19877 }
19878 state.output.append(blob, "literal");
19879 } else {
19880 state.output.append(num.particle + num.value, numStyling);
19881 }
19882 if (labelPlaceholderPos === 0 && labelPlaceholderPos < (label.length-2)) {
19883 // Only and always if this is the last entry of this label
19884 if (lastLabelName === null) {
19885 lastLabelName = labelName;
19886 }
19887 if (labelName !== lastLabelName || i === (nums.length-1)) {
19888 var suffixLabelStyling = new CSL.Token();
19889 suffixLabelStyling.decorations = labelDecorations;
19890 state.output.append(label.slice(labelPlaceholderPos+2), suffixLabelStyling);
19891 }
19892 }
19893 lastLabelName = labelName;
19894 state.tmp.term_predecessor = true;
19895 }
19896 state.output.closeLevel();
19897};
19898
19899/*global CSL: true */
19900
19901CSL.Util.PageRangeMangler = {};
19902
19903CSL.Util.PageRangeMangler.getFunction = function (state, rangeType) {
19904 var rangerex, pos, len, stringify, listify, expand, minimize, minimize_internal, chicago, lst, m, b, e, ret, begin, end, ret_func;
19905
19906 var range_delimiter = state.getTerm(rangeType + "-range-delimiter");
19907
19908 rangerex = /([0-9]*[a-zA-Z]+0*)?([0-9]+[a-z]*)\s*(?:\u2013|-)\s*([0-9]*[a-zA-Z]+0*)?([0-9]+[a-z]*)/;
19909
19910 stringify = function (lst) {
19911 len = lst.length;
19912 for (pos = 1; pos < len; pos += 2) {
19913 if ("object" === typeof lst[pos]) {
19914 lst[pos] = lst[pos].join("");
19915 }
19916 }
19917 var ret = lst.join("");
19918 ret = ret.replace(/([^\\])\-/g, "$1"+state.getTerm(rangeType + "-range-delimiter"));
19919 return ret;
19920 };
19921
19922 listify = function (str) {
19923 var m, lst, ret;
19924 // Normalized delimiter form, for use in regexps
19925 var hyphens = "\\s+\\-\\s+";
19926 // Normalize delimiters to hyphen wrapped in single spaces
19927 var this_range_delimiter = range_delimiter === "-" ? "" : range_delimiter;
19928 var delimRex = new RegExp("([^\\\\])[-" + this_range_delimiter + "\\u2013]", "g");
19929 str = str.replace(delimRex, "$1 - ").replace(/\s+-\s+/g, " - ");
19930 // Workaround for Internet Explorer
19931 //var rexm = new RegExp("((?:[0-9]*[a-zA-Z]+)?[0-9]+" + hyphens + "(?:[0-9]*[a-zA-Z]+)?[0-9]+)", "g");
19932 //var rexlst = new RegExp("(?:[0-9]*[a-zA-Z]+)?[0-9]+" + hyphens + "(?:[0-9]*[a-zA-Z]+)?[0-9]+");
19933 var rexm = new RegExp("((?:[0-9]*[a-zA-Z]+0*)?[0-9]+[a-z]*" + hyphens + "(?:[0-9]*[a-zA-Z]+0*)?[0-9]+[a-z]*)", "g");
19934 var rexlst = new RegExp("(?:[0-9]*[a-zA-Z]+0*)?[0-9]+[a-z]*" + hyphens + "(?:[0-9]*[a-zA-Z]+0*)?[0-9]+[a-z]*");
19935 m = str.match(rexm);
19936 lst = str.split(rexlst);
19937 if (lst.length === 0) {
19938 ret = m;
19939 } else {
19940 ret = [lst[0]];
19941 for (pos = 1, len = lst.length; pos < len; pos += 1) {
19942 ret.push(m[pos - 1].replace(/\s*\-\s*/g, "-"));
19943 ret.push(lst[pos]);
19944 }
19945 }
19946 return ret;
19947 };
19948
19949 expand = function (str) {
19950 str = "" + str;
19951 lst = listify(str);
19952 len = lst.length;
19953 for (pos = 1; pos < len; pos += 2) {
19954 m = lst[pos].match(rangerex);
19955 if (m) {
19956 if (!m[3] || m[1] === m[3]) {
19957 if (m[4].length < m[2].length) {
19958 m[4] = m[2].slice(0, (m[2].length - m[4].length)) + m[4];
19959 }
19960 if (parseInt(m[2], 10) < parseInt(m[4], 10)) {
19961 m[3] = range_delimiter + (m[1] ? m[1] : "");
19962 lst[pos] = m.slice(1);
19963 }
19964 }
19965 }
19966 if ("string" === typeof lst[pos]) {
19967 lst[pos] = lst[pos].replace(/\-/g, range_delimiter);
19968 }
19969 }
19970 return lst;
19971 };
19972
19973 minimize = function (lst, minchars, isyear) {
19974 len = lst.length;
19975 for (var i = 1, ilen = lst.length; i < ilen; i += 2) {
19976 if ("object" === typeof lst[i]) {
19977 lst[i][3] = minimize_internal(lst[i][1], lst[i][3], minchars, isyear);
19978 if (lst[i][2].slice(1) === lst[i][0]) {
19979 lst[i][2] = range_delimiter;
19980 }
19981 }
19982 }
19983 return stringify(lst);
19984 };
19985
19986 minimize_internal = function (begin, end, minchars, isyear) {
19987 if (!minchars) {
19988 minchars = 0;
19989 }
19990 b = ("" + begin).split("");
19991 e = ("" + end).split("");
19992 ret = e.slice();
19993 ret.reverse();
19994 if (b.length === e.length) {
19995 for (var i = 0, ilen = b.length; i < ilen; i += 1) {
19996 if (b[i] === e[i] && ret.length > minchars) {
19997 ret.pop();
19998 } else {
19999 if (minchars && isyear && ret.length === 3) {
20000 var front = b.slice(0, i);
20001 front.reverse();
20002 ret = ret.concat(front);
20003 }
20004 break;
20005 }
20006 }
20007 }
20008 ret.reverse();
20009 return ret.join("");
20010 };
20011
20012 chicago = function (lst) {
20013 len = lst.length;
20014 for (pos = 1; pos < len; pos += 2) {
20015 if ("object" === typeof lst[pos]) {
20016 m = lst[pos];
20017 begin = parseInt(m[1], 10);
20018 end = parseInt(m[3], 10);
20019 if (begin > 100 && begin % 100 && parseInt((begin / 100), 10) === parseInt((end / 100), 10)) {
20020 m[3] = "" + (end % 100);
20021 } else if (begin >= 10000) {
20022 m[3] = "" + (end % 1000);
20023 }
20024 }
20025 if (m[2].slice(1) === m[0]) {
20026 m[2] = range_delimiter;
20027 }
20028 }
20029 return stringify(lst);
20030 };
20031
20032 //
20033 // The top-level option handlers.
20034 //
20035 var sniff = function (str, func, minchars, isyear) {
20036 var ret;
20037 str = "" + str;
20038 var lst = expand(str);
20039 var ret = func(lst, minchars, isyear);
20040 return ret;
20041 };
20042 if (!state.opt[rangeType + "-range-format"]) {
20043 ret_func = function (str) {
20044 //return str.replace("-", "\u2013", "g");
20045 return sniff(str, stringify);
20046 };
20047 } else if (state.opt[rangeType + "-range-format"] === "expanded") {
20048 ret_func = function (str) {
20049 return sniff(str, stringify);
20050 };
20051 } else if (state.opt[rangeType + "-range-format"] === "minimal") {
20052 ret_func = function (str) {
20053 return sniff(str, minimize);
20054 };
20055 } else if (state.opt[rangeType + "-range-format"] === "minimal-two") {
20056 ret_func = function (str, isyear) {
20057 return sniff(str, minimize, 2, isyear);
20058 };
20059 } else if (state.opt[rangeType + "-range-format"] === "chicago") {
20060 ret_func = function (str) {
20061 return sniff(str, chicago);
20062 };
20063 }
20064
20065 return ret_func;
20066};
20067
20068
20069/*global CSL: true */
20070
20071// Use a state machine
20072
20073// Okay, good!
20074// Needs some tweaks:
20075// 1. First pass: quotes only
20076// Special: Convert all sandwiched single-quote markup to apostrophe
20077// 2. Second pass: tags
20078
20079CSL.Util.FlipFlopper = function(state) {
20080
20081 /**
20082 * INTERNAL
20083 */
20084
20085 var _nestingState = [];
20086
20087 var _nestingData = {
20088 "<span class=\"nocase\">": {
20089 type: "nocase",
20090 opener: "<span class=\"nocase\">",
20091 closer: "</span>",
20092 attr: null,
20093 outer: null,
20094 flipflop: null
20095 },
20096 "<span class=\"nodecor\">": {
20097 type: "nodecor",
20098 opener: "<span class=\"nodecor\">",
20099 closer: "</span>",
20100 attr: "@class",
20101 outer: "nodecor",
20102 flipflop: {
20103 "nodecor": "nodecor"
20104 }
20105 },
20106 "<span style=\"font-variant:small-caps;\">": {
20107 type: "tag",
20108 opener: "<span style=\"font-variant:small-caps;\">",
20109 closer: "</span>",
20110 attr: "@font-variant",
20111 outer: "small-caps",
20112 flipflop: {
20113 "small-caps": "normal",
20114 "normal": "small-caps"
20115 }
20116 },
20117 "<sc>": {
20118 type: "tag",
20119 opener: "<sc>",
20120 closer: "</sc>",
20121 attr: "@font-variant",
20122 outer: "small-caps",
20123 flipflop: {
20124 "small-caps": "normal",
20125 "normal": "small-caps"
20126 }
20127 },
20128 "<i>": {
20129 type: "tag",
20130 opener: "<i>",
20131 closer: "</i>",
20132 attr: "@font-style",
20133 outer: "italic",
20134 flipflop: {
20135 "italic": "normal",
20136 "normal": "italic"
20137 }
20138 },
20139 "<b>": {
20140 type: "tag",
20141 opener: "<b>",
20142 closer: "</b>",
20143 attr: "@font-weight",
20144 outer: "bold",
20145 flipflop: {
20146 "bold": "normal",
20147 "normal": "bold"
20148 }
20149 },
20150 "<sup>": {
20151 type: "tag",
20152 opener: "<sup>",
20153 closer: "</sup>",
20154 attr: "@vertical-align",
20155 outer: "sup",
20156 flipflop: {
20157 "sub": "sup",
20158 "sup": "sup"
20159 }
20160 },
20161 "<sub>": {
20162 type: "tag",
20163 opener: "<sub>",
20164 closer: "</sub>",
20165 attr: "@vertical-align",
20166 outer: "sub",
20167 flipflop: {
20168 "sup": "sub",
20169 "sub": "sub"
20170 }
20171 },
20172 " \"": {
20173 type: "quote",
20174 opener: " \"",
20175 closer: "\"",
20176 attr: "@quotes",
20177 outer: "true",
20178 flipflop: {
20179 "true": "inner",
20180 "inner": "true",
20181 "false": "true"
20182 }
20183 },
20184 " \'": {
20185 type: "quote",
20186 opener: " \'",
20187 closer: "\'",
20188 attr: "@quotes",
20189 outer: "inner",
20190 flipflop: {
20191 "true": "inner",
20192 "inner": "true",
20193 "false": "true"
20194 }
20195 }
20196 };
20197
20198 _nestingData["(\""] = _nestingData[" \""];
20199 _nestingData["(\'"] = _nestingData[" \'"];
20200
20201 var localeOpenQuote = state.getTerm("open-quote");
20202 var localeCloseQuote = state.getTerm("close-quote");
20203 var localeOpenInnerQuote = state.getTerm("open-inner-quote");
20204 var localeCloseInnerQuote = state.getTerm("close-inner-quote");
20205
20206 // If locale uses straight quotes, do not register them. All will be well.
20207 // Otherwise, clone straight-quote data, and adjust.
20208 if (localeOpenQuote && localeCloseQuote && [" \""," \'","\"","\'"].indexOf(localeOpenQuote) === -1) {
20209 _nestingData[localeOpenQuote] = JSON.parse(JSON.stringify(_nestingData[" \""]));
20210 _nestingData[localeOpenQuote].opener = localeOpenQuote;
20211 _nestingData[localeOpenQuote].closer = localeCloseQuote;
20212 }
20213
20214 if (localeOpenInnerQuote && localeCloseInnerQuote && [" \""," \'","\"","\'"].indexOf(localeOpenInnerQuote) === -1) {
20215 _nestingData[localeOpenInnerQuote] = JSON.parse(JSON.stringify(_nestingData[" \'"]));
20216 _nestingData[localeOpenInnerQuote].opener = localeOpenInnerQuote;
20217 _nestingData[localeOpenInnerQuote].closer = localeCloseInnerQuote;
20218 }
20219
20220 function _setOuterQuoteForm(quot) {
20221 var flip = {
20222 " \'": " \"",
20223 " \"": " \'",
20224 "(\"": "(\'",
20225 "(\'": "(\""
20226 };
20227 _nestingData[quot].outer = "true";
20228 _nestingData[flip[quot]].outer = "inner";
20229 }
20230
20231 function _getNestingOpenerParams(opener) {
20232 var openers = [];
20233 var keys = Object.keys(_nestingData);
20234 for (var i = 0, l = keys.length; i < l; i++) {
20235 var key = keys[i];
20236 if (_nestingData[opener].type !== "quote" || !_nestingData[opener]) {
20237 openers.push(key);
20238 }
20239 }
20240 var ret = _nestingData[opener];
20241 ret.opener = new RegExp("^(?:" + openers.map(function(str){
20242 return str.replace("(", "\\(");
20243 }).join("|") + ")");
20244 return ret;
20245 }
20246
20247 var _nestingParams = (function() {
20248 var ret = {};
20249 var keys = Object.keys(_nestingData);
20250 for (var i = 0, l = keys.length; i < l; i++) {
20251 var key = keys[i];
20252 ret[key] = _getNestingOpenerParams(key);
20253 }
20254 return ret;
20255 }());
20256
20257 var _tagRex = (function() {
20258 var openers = [];
20259 var closers = [];
20260 var vals = {};
20261 for (var opener in _nestingParams) {
20262 openers.push(opener);
20263 vals[_nestingParams[opener].closer] = true;
20264 }
20265 var keys = Object.keys(vals);
20266 for (var i = 0, l = keys.length; i < l; i++) {
20267 var closer = keys[i];
20268 closers.push(closer);
20269 }
20270
20271 var all = openers.concat(closers).map(function(str){
20272 return str.replace("(", "\\(");
20273 }).join("|");
20274
20275 return {
20276 matchAll: new RegExp("((?:" + all + "))", "g"),
20277 splitAll: new RegExp("(?:" + all + ")", "g"),
20278 open: new RegExp("(^(?:" + openers.map(function(str){
20279 return str.replace("(", "\\(");
20280 }).join("|") + ")$)"),
20281 close: new RegExp("(^(?:" + closers.join("|") + ")$)"),
20282 };
20283 }());
20284
20285 function _tryOpen(tag, pos) {
20286 var params = _nestingState[_nestingState.length - 1];
20287 if (!params || tag.match(params.opener)) {
20288 _nestingState.push({
20289 type: _nestingParams[tag].type,
20290 opener: _nestingParams[tag].opener,
20291 closer: _nestingParams[tag].closer,
20292 pos: pos
20293 });
20294 return false;
20295 } else {
20296 _nestingState.pop();
20297 _nestingState.push({
20298 type: _nestingParams[tag].type,
20299 opener: _nestingParams[tag].opener,
20300 closer: _nestingParams[tag].closer,
20301 pos: pos
20302 });
20303 return {
20304 fixtag: params.pos
20305 };
20306 }
20307 }
20308
20309 function _tryClose(tag, pos) {
20310 var params = _nestingState[_nestingState.length - 1];
20311 if (params && tag === params.closer) {
20312 _nestingState.pop();
20313 if (params.type === "nocase") {
20314 return {
20315 nocase: {
20316 open: params.pos,
20317 close: pos
20318 }
20319 };
20320 } else {
20321 return false;
20322 }
20323 } else {
20324 if (params) {
20325 return {
20326 fixtag: params.pos
20327 };
20328 } else {
20329 return {
20330 fixtag: pos
20331 };
20332 }
20333 }
20334 }
20335
20336 function _pushNestingState(tag, pos) {
20337 if (tag.match(_tagRex.open)) {
20338 return _tryOpen(tag, pos);
20339 } else {
20340 return _tryClose(tag, pos);
20341 }
20342 }
20343
20344 function _nestingFix (tag, pos) {
20345 return _pushNestingState(tag, pos);
20346 }
20347
20348 function _doppelString(str) {
20349 var forcedSpaces = [];
20350 // Normalize markup
20351 str = str.replace(/(<span)\s+(style=\"font-variant:)\s*(small-caps);?\"[^>]*(>)/g, "$1 $2$3;\"$4");
20352 str = str.replace(/(<span)\s+(class=\"no(?:case|decor)\")[^>]*(>)/g, "$1 $2$3");
20353
20354 var match = str.match(_tagRex.matchAll);
20355 if (!match) {
20356 return {
20357 tags: [],
20358 strings: [str],
20359 forcedSpaces: []
20360 };
20361 }
20362 var split = str.split(_tagRex.splitAll);
20363
20364 for (var i=0,ilen=match.length-1;i<ilen;i++) {
20365 if (_nestingData[match[i]]) {
20366 if (split[i+1] === "" && ["\"", "'"].indexOf(match[i+1]) > -1) {
20367 match[i+1] = " " + match[i+1];
20368 forcedSpaces.push(true);
20369 } else {
20370 forcedSpaces.push(false);
20371 }
20372 }
20373 }
20374 return {
20375 tags: match,
20376 strings: split,
20377 forcedSpaces: forcedSpaces
20378 };
20379 }
20380
20381 var TagReg = function(blob) {
20382 var _stack = [];
20383 this.set = function (tag) {
20384 var attr = _nestingData[tag].attr;
20385 var decor = null;
20386 for (var i=_stack.length-1;i>-1;i--) {
20387 var _decor = _stack[i];
20388 if (_decor[0] === attr) {
20389 decor = _decor;
20390 break;
20391 }
20392 }
20393 if (!decor) {
20394 var allTheDecor = [state[state.tmp.area].opt.layout_decorations].concat(blob.alldecor);
20395 outer:
20396 for (var i=allTheDecor.length-1;i>-1;i--) {
20397 var decorset = allTheDecor[i];
20398 if (!decorset) {
20399 continue;
20400 }
20401 for (var j=decorset.length-1;j>-1;j--) {
20402 var _decor = decorset[j];
20403 if (_decor[0] === attr) {
20404 decor = _decor;
20405 break outer;
20406 }
20407 }
20408 }
20409 }
20410 if (!decor) {
20411 decor = [attr, _nestingData[tag].outer];
20412 } else {
20413 decor = [attr, _nestingData[tag].flipflop[decor[1]]];
20414 }
20415 _stack.push(decor);
20416 };
20417 this.pair = function () {
20418 return _stack[_stack.length-1];
20419 };
20420 this.pop = function () {
20421 _stack.pop();
20422 };
20423 };
20424
20425 function _apostropheForce(tag, str) {
20426 if (tag === "\'") {
20427 if (str && str.match(/^[^\,\.\?\:\;\ ]/)) {
20428 return true;
20429 }
20430 } else if (tag === " \'" && str && str.match(/^[\ ]/)) {
20431 return true;
20432 }
20433 return false;
20434 }
20435
20436 function _undoppelToQueue(blob, doppel, leadingSpace) {
20437 var firstString = true;
20438 var tagReg = new TagReg(blob);
20439 blob.blobs = [];
20440 function Stack (blob) {
20441 this.stack = [blob];
20442 this.latest = blob;
20443 this.addStyling = function(str, decor) {
20444 if (firstString) {
20445 if (str.slice(0, 1) === " ") {
20446 str = str.slice(1);
20447 }
20448 if (str.slice(0, 1) === " ") {
20449 str = str.slice(1);
20450 }
20451 firstString = false;
20452 }
20453 this.latest = this.stack[this.stack.length-1];
20454 if (decor) {
20455 if ("string" === typeof this.latest.blobs) {
20456 var child = new CSL.Blob();
20457 child.blobs = this.latest.blobs;
20458 child.alldecor = this.latest.alldecor.slice();
20459 this.latest.blobs = [child];
20460 }
20461 var tok = new CSL.Token();
20462 var newblob = new CSL.Blob(null, tok);
20463 newblob.alldecor = this.latest.alldecor.slice();
20464
20465 // AHA! Bad naming. There is _decorset from the list, and
20466 // there WAS decorset that we are building. Dumb. Fix the
20467 // names and fix it up.
20468
20469 if (decor[0] === "@class" && decor[1] === "nodecor") {
20470 var newdecorset = [];
20471 var seen = {};
20472 var allTheDecor = [state[state.tmp.area].opt.layout_decorations].concat(newblob.alldecor);
20473 for (var i=allTheDecor.length-1;i>-1;i--) {
20474 var _decorset = allTheDecor[i];
20475 if (!_decorset) {
20476 continue;
20477 }
20478 for (var j=_decorset.length-1;j>-1;j--) {
20479 var _olddecor = _decorset[j];
20480 if (["@font-weight", "@font-style", "@font-variant"].indexOf(_olddecor[0]) > -1
20481 && !seen[_olddecor[0]]) {
20482
20483 if (decor[1] !== "normal") {
20484 newblob.decorations.push([_olddecor[0], "normal"]);
20485 newdecorset.push([_olddecor[0], "normal"]);
20486 }
20487 seen[_olddecor[0]] = true;
20488 }
20489 }
20490 }
20491 newblob.alldecor.push(newdecorset);
20492
20493 } else {
20494 newblob.decorations.push(decor);
20495 newblob.alldecor.push([decor]);
20496 }
20497 this.latest.blobs.push(newblob);
20498 this.stack.push(newblob);
20499 this.latest = newblob;
20500 if (str) {
20501 var tok = new CSL.Token();
20502 var newblob = new CSL.Blob(null, tok);
20503 newblob.blobs = str;
20504 newblob.alldecor = this.latest.alldecor.slice();
20505 this.latest.blobs.push(newblob);
20506 }
20507 } else {
20508 if (str) {
20509 var child = new CSL.Blob();
20510 child.blobs = str;
20511 child.alldecor = this.latest.alldecor.slice();
20512 this.latest.blobs.push(child);
20513 }
20514 }
20515 };
20516 this.popStyling = function() {
20517 this.stack.pop();
20518 };
20519 }
20520 var stack = new Stack(blob);
20521 if (doppel.strings.length) {
20522 var str = doppel.strings[0];
20523 if (leadingSpace) {
20524 str = " " + str;
20525 }
20526 stack.addStyling(str);
20527 }
20528 for (var i=0,ilen=doppel.tags.length;i<ilen;i++) {
20529 var tag = doppel.tags[i];
20530 var str = doppel.strings[i+1];
20531 if (tag.match(_tagRex.open)) {
20532 tagReg.set(tag);
20533 stack.addStyling(str, tagReg.pair());
20534 } else {
20535 tagReg.pop();
20536 stack.popStyling();
20537 stack.addStyling(str);
20538 }
20539 }
20540 }
20541
20542 /**
20543 * PUBLIC
20544 */
20545
20546 this.processTags = function (blob) {
20547 var str = blob.blobs;
20548 var leadingSpace = false;
20549 if (str.slice(0, 1) === " " && !str.match(/^\s+[\'\"]/)) {
20550 leadingSpace = true;
20551 }
20552 var rex = new RegExp("(" + CSL.ROMANESQUE_REGEXP.source + ")\u2019(" + CSL.ROMANESQUE_REGEXP.source + ")", "g");
20553 var str = " " + str.replace(rex, "$1\'$2");
20554 var doppel = _doppelString(str);
20555 if (doppel.tags.length === 0) {
20556 return;
20557 }
20558 var quoteFormSeen = false;
20559 // ZZZ
20560 // It is inside THIS loop that we can convert the nocase and nodecor
20561 // tags and companion spans to string
20562 // Um. Maybe. Or maybe it needs to happen inside _nestingFix() somewhere.
20563
20564 for (var i=0,ilen=doppel.tags.length;i<ilen;i++) {
20565 var tag = doppel.tags[i];
20566 var str = doppel.strings[i+1];
20567 if (_apostropheForce(tag, str)) {
20568 if (tag === " \'") {
20569 doppel.strings[i+1] = " \u2019" + doppel.strings[i+1];
20570 } else {
20571 doppel.strings[i+1] = "\u2019" + doppel.strings[i+1];
20572 }
20573 doppel.tags[i] = "";
20574 } else {
20575 var tagInfo;
20576 while (true) {
20577 tagInfo = _nestingFix(tag, i);
20578 if (tagInfo) {
20579 if (Object.keys(tagInfo).indexOf("fixtag") > -1) {
20580 if (tag.match(_tagRex.close)
20581 && tag === "\'") {
20582
20583 doppel.strings[i+1] = "\u2019" + doppel.strings[i+1];
20584 doppel.tags[i] = "";
20585 } else {
20586 var failedTag = doppel.tags[tagInfo.fixtag];
20587 if (doppel.forcedSpaces[tagInfo.fixtag-1]) {
20588 failedTag = failedTag.slice(1);
20589 }
20590 doppel.strings[tagInfo.fixtag+1] = failedTag + doppel.strings[tagInfo.fixtag+1];
20591 doppel.tags[tagInfo.fixtag] = "";
20592 }
20593 if (_nestingState.length > 0) {
20594 _nestingState.pop();
20595 } else {
20596 break;
20597 }
20598 } else if (tagInfo.nocase) {
20599 doppel.tags[tagInfo.nocase.open] = "";
20600 doppel.tags[tagInfo.nocase.close] = "";
20601 break;
20602 } else {
20603 break;
20604 }
20605 } else {
20606 break;
20607 }
20608 }
20609 if (tagInfo && (tagInfo.fixtag|| tagInfo.fixtag === 0)) {
20610 doppel.strings[i+1] = doppel.tags[i] + doppel.strings[i+1];
20611 doppel.tags[i] = "";
20612 }
20613 }
20614 }
20615 // Stray tags are neutralized here
20616 for (var i=_nestingState.length-1;i>-1;i--) {
20617 var tagPos = _nestingState[i].pos;
20618 var tag = doppel.tags[tagPos];
20619 if (tag === " \'" || tag === "\'") {
20620
20621 doppel.strings[tagPos+1] = " \u2019" + doppel.strings[tagPos+1];
20622 } else {
20623 doppel.strings[tagPos+1] = doppel.tags[tagPos] + doppel.strings[tagPos+1];
20624 }
20625 doppel.tags[tagPos] = "";
20626 _nestingState.pop();
20627 }
20628 for (var i=doppel.tags.length-1;i>-1;i--) {
20629 if (!doppel.tags[i]) {
20630 doppel.tags = doppel.tags.slice(0,i).concat(doppel.tags.slice(i+1));
20631 doppel.strings[i] = doppel.strings[i] + doppel.strings[i+1];
20632 doppel.strings = doppel.strings.slice(0,i+1).concat(doppel.strings.slice(i+2));
20633 }
20634 }
20635 // Sniff initial (outer) quote form (single or double) and configure parser
20636 // Also add leading spaces.
20637 for (var i=0,ilen=doppel.tags.length;i<ilen;i++) {
20638 var tag = doppel.tags[i];
20639 var forcedSpace = doppel.forcedSpaces[i-1];
20640 if ([" \"", " \'", "(\"", "(\'"].indexOf(tag) > -1) {
20641 if (!quoteFormSeen) {
20642 _setOuterQuoteForm(tag);
20643 quoteFormSeen = true;
20644 }
20645 if (!forcedSpace) {
20646 doppel.strings[i] += tag.slice(0, 1);
20647 }
20648 }
20649 }
20650 //print(JSON.stringify(doppel, null, 2))
20651 //print(_undoppelString(doppel));
20652 _undoppelToQueue(blob, doppel, leadingSpace);
20653 };
20654};
20655
20656/*global CSL: true */
20657
20658CSL.Output.Formatters = (function () {
20659 var rexStr = "(?:\u2018|\u2019|\u201C|\u201D| \"| \'|\"|\'|[-\u2013\u2014\/.,;?!:]|\\[|\\]|\\(|\\)|<span style=\"font-variant: small-caps;\">|<span class=\"no(?:case|decor)\">|<\/span>|<\/?(?:i|sc|b|sub|sup)>)";
20660 var tagDoppel = new CSL.Doppeler(rexStr, function(str) {
20661 return str.replace(/(<span)\s+(class=\"no(?:case|decor)\")[^>]*(>)/g, "$1 $2$3").replace(/(<span)\s+(style=\"font-variant:)\s*(small-caps);?(\")[^>]*(>)/g, "$1 $2 $3;$4$5");
20662 });
20663
20664 var wordDoppel = new CSL.Doppeler("(?:[\u0020\u00A0\u2000-\u200B\u205F\u3000]+)");
20665
20666 /**
20667 * INTERNAL
20668 */
20669
20670 var _tagParams = {
20671 "<span style=\"font-variant: small-caps;\">": "</span>",
20672 "<span class=\"nocase\">": "</span>",
20673 "<span class=\"nodecor\">": "</span>",
20674 "<sc>": "</sc>",
20675 "<sub>": "</sub>",
20676 "<sup>": "</sup>"
20677 };
20678
20679 function _capitalise (word) {
20680 // Weird stuff is (.) transpiled with regexpu
20681 // https://github.com/mathiasbynens/regexpu
20682 var m = word.match(/(^\s*)((?:[\0-\t\x0B\f\x0E-\u2027\u202A-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]))(.*)/);
20683 // Do not uppercase lone Greek letters
20684 // (No case transforms in Greek citations, but chars used in titles to science papers)
20685 if (m && !(m[2].match(/^[\u0370-\u03FF]$/) && !m[3])) {
20686 return m[1] + m[2].toUpperCase() + m[3];
20687 }
20688 return word;
20689 }
20690
20691 function _textcaseEngine(config, string) {
20692 if (!string) {
20693 return "";
20694 }
20695 config.doppel = tagDoppel.split(string);
20696 var quoteParams = {
20697 " \"": {
20698 opener: " \'",
20699 closer: "\""
20700 },
20701 " \'": {
20702 opener: " \"",
20703 closer: "\'"
20704 },
20705 "\u2018": {
20706 opener: "\u2018",
20707 closer: "\u2019"
20708 },
20709 "\u201C": {
20710 opener: "\u201C",
20711 closer: "\u201D"
20712 },
20713 };
20714 function tryOpen(tag, pos) {
20715 if (config.quoteState.length === 0 || tag === config.quoteState[config.quoteState.length - 1].opener) {
20716 config.quoteState.push({
20717 opener: quoteParams[tag].opener,
20718 closer: quoteParams[tag].closer,
20719 pos: pos
20720 });
20721 return false;
20722 } else {
20723 var prevPos = config.quoteState[config.quoteState.length-1].pos;
20724 config.quoteState.pop();
20725 config.quoteState.push({
20726 opener: quoteParams[tag].opener,
20727 closer: quoteParams[tag].closer,
20728 positions: pos
20729 });
20730 return prevPos;
20731 }
20732 }
20733 function tryClose(tag, pos) {
20734 if (config.quoteState.length > 0 && tag === config.quoteState[config.quoteState.length - 1].closer) {
20735 config.quoteState.pop();
20736 } else {
20737 return pos;
20738 }
20739 }
20740 function pushQuoteState(tag, pos) {
20741 var isOpener = ["\u201C", "\u2018", " \"", " \'"].indexOf(tag) > -1 ? true : false;
20742 if (isOpener) {
20743 return tryOpen(tag, pos);
20744 } else {
20745 return tryClose(tag, pos);
20746 }
20747 }
20748 function quoteFix (tag, positions) {
20749 var m = tag.match(/(^(?:\u2018|\u2019|\u201C|\u201D|\"|\')|(?: \"| \')$)/);
20750 if (m) {
20751 return pushQuoteState(m[1], positions);
20752 }
20753 }
20754 // Run state machine
20755 if (config.doppel.strings.length && config.doppel.strings[0].trim()) {
20756 config.doppel.strings[0] = config.capitaliseWords(config.doppel.strings[0], 0, config.doppel.tags[0]);
20757 }
20758
20759 for (var i=0,ilen=config.doppel.tags.length;i<ilen;i++) {
20760 var tag = config.doppel.tags[i];
20761 var str = config.doppel.strings[i+1];
20762
20763 if (config.tagState !== null) {
20764 // Evaluate tag state for current string
20765 if (_tagParams[tag]) {
20766 config.tagState.push(_tagParams[tag]);
20767 } else if (config.tagState.length && tag === config.tagState[config.tagState.length - 1]) {
20768 config.tagState.pop();
20769 }
20770 }
20771
20772 if (config.afterPunct !== null) {
20773 // Evaluate punctuation state of current string
20774 if (tag.match(/[\!\?\:]$/)) {
20775 config.afterPunct = true;
20776 }
20777 }
20778
20779 // Process if outside tag scope, else noop for upper-casing
20780 if (config.tagState.length === 0) {
20781 config.doppel.strings[i+1] = config.capitaliseWords(str, i+1, config.doppel,config.doppel.tags[i+1]);
20782
20783 } else if (config.doppel.strings[i+1].trim()) {
20784 config.lastWordPos = null;
20785 }
20786
20787 if (config.quoteState !== null) {
20788 // Evaluate quote state of current string and fix chars that have flown
20789 var quotePos = quoteFix(tag, i);
20790 if (quotePos || quotePos === 0) {
20791 var origChar = config.doppel.origStrings[quotePos+1].slice(0, 1);
20792 config.doppel.strings[quotePos+1] = origChar + config.doppel.strings[quotePos+1].slice(1);
20793 config.lastWordPos = null;
20794 }
20795 }
20796
20797 // If there was a printable string, unset first-word and after-punctuation
20798 if (config.isFirst) {
20799 if (str.trim()) {
20800 config.isFirst = false;
20801 }
20802 }
20803 if (config.afterPunct) {
20804 if (str.trim()) {
20805 config.afterPunct = false;
20806 }
20807 }
20808 }
20809 if (config.quoteState) {
20810 for (var i=0,ilen=config.quoteState.length;i<ilen;i++) {
20811 var quotePos = config.quoteState[i].pos;
20812 // Test for quotePos avoids a crashing error:
20813 // https://github.com/citation-style-language/test-suite/blob/master/processor-tests/humans/flipflop_OrphanQuote.txt
20814 if (typeof quotePos !== 'undefined') {
20815 var origChar = config.doppel.origStrings[quotePos+1].slice(0, 1);
20816 config.doppel.strings[quotePos+1] = origChar + config.doppel.strings[quotePos+1].slice(1);
20817 }
20818 }
20819 }
20820 // Specially capitalize the last word if necessary (invert stop-word list)
20821 if (config.lastWordPos) {
20822 var lastWords = wordDoppel.split(config.doppel.strings[config.lastWordPos.strings]);
20823 var lastWord = lastWords.strings[config.lastWordPos.words];
20824 if (lastWord.length > 1 && lastWord.toLowerCase().match(config.skipWordsRex)) {
20825 lastWord = _capitalise(lastWord);
20826 lastWords.strings[config.lastWordPos.words] = lastWord;
20827 }
20828 config.doppel.strings[config.lastWordPos.strings] = wordDoppel.join(lastWords);
20829 }
20830
20831 // Recombine the string
20832 return tagDoppel.join(config.doppel);
20833 }
20834
20835 /**
20836 * PUBLIC
20837 */
20838
20839 /**
20840 * A noop that just delivers the string.
20841 */
20842 function passthrough (state, str) {
20843 return str;
20844 }
20845
20846 /**
20847 * Force all letters in the string to lowercase, skipping nocase spans
20848 */
20849 function lowercase(state, string) {
20850 var config = {
20851 quoteState: null,
20852 capitaliseWords: function(str) {
20853 var words = str.split(" ");
20854 for (var i=0,ilen=words.length;i<ilen;i++) {
20855 var word = words[i];
20856 if (word) {
20857 words[i] = word.toLowerCase();
20858 }
20859 }
20860 return words.join(" ");
20861 },
20862 skipWordsRex: null,
20863 tagState: [],
20864 afterPunct: null,
20865 isFirst: null
20866 };
20867 return _textcaseEngine(config, string);
20868 }
20869
20870 /**
20871 * Force all letters in the string to uppercase.
20872 */
20873 function uppercase(state, string) {
20874 var config = {
20875 quoteState: null,
20876 capitaliseWords: function(str) {
20877 var words = str.split(" ");
20878 for (var i=0,ilen=words.length;i<ilen;i++) {
20879 var word = words[i];
20880 if (word) {
20881 words[i] = word.toUpperCase();
20882 }
20883 }
20884 return words.join(" ");
20885 },
20886 skipWordsRex: null,
20887 tagState: [],
20888 afterPunct: null,
20889 isFirst: null
20890 };
20891 return _textcaseEngine(config, string);
20892 }
20893
20894 /**
20895 * Similar to <b>capitalize_first</b>, but force the
20896 * subsequent characters to lowercase.
20897 */
20898 function sentence(state, string) {
20899 var config = {
20900 quoteState: [],
20901 capitaliseWords: function(str) {
20902 var words = str.split(" ");
20903 for (var i=0,ilen=words.length;i<ilen;i++) {
20904 var word = words[i];
20905 if (word) {
20906 if (config.isFirst) {
20907 words[i] = _capitalise(word);
20908 config.isFirst = false;
20909 } else {
20910 words[i] = word.toLowerCase();
20911 }
20912 }
20913 }
20914 return words.join(" ");
20915 },
20916 skipWordsRex: null,
20917 tagState: [],
20918 afterPunct: null,
20919 isFirst: true
20920 };
20921 return _textcaseEngine(config, string);
20922 }
20923
20924 function title(state, string) {
20925 var config = {
20926 quoteState: [],
20927 capitaliseWords: function(str, i, followingTag) {
20928 if (str.trim()) {
20929 var words = str.split(/[ \u00A0]+/);
20930 var wordle = wordDoppel.split(str);
20931 var words = wordle.strings;
20932 for (var j=0,jlen=words.length;j<jlen;j++) {
20933 var word = words[j];
20934 if (!word) {
20935 continue;
20936 }
20937 if (word.length > 1 && !word.toLowerCase().match(config.skipWordsRex)) {
20938 // Capitalize every word that is not a stop-word
20939 words[j] = _capitalise(words[j]);
20940 } else if (j === (words.length - 1) && followingTag === "-") {
20941 words[j] = _capitalise(words[j]);
20942 } else if (config.isFirst) {
20943 // Capitalize first word, even if a stop-word
20944 words[j] = _capitalise(words[j]);
20945 } else if (config.afterPunct) {
20946 // Capitalize after punctuation
20947 words[j] = _capitalise(words[j]);
20948 }
20949 config.afterPunct = false;
20950 config.isFirst = false;
20951 config.lastWordPos = {
20952 strings: i,
20953 words: j
20954 };
20955 }
20956 str = wordDoppel.join(wordle);
20957 }
20958 return str;
20959 },
20960 skipWordsRex: state.locale[state.opt.lang].opts["skip-words-regexp"],
20961 tagState: [],
20962 afterPunct: false,
20963 isFirst: true
20964 };
20965 return _textcaseEngine(config, string);
20966 }
20967
20968
20969 /**
20970 * Force capitalization of the first letter in the string, leave
20971 * the rest of the characters untouched.
20972 */
20973 function capitalizeFirst(state, string) {
20974 var config = {
20975 quoteState: [],
20976 capitaliseWords: function(str) {
20977 var words = str.split(" ");
20978 for (var i=0,ilen=words.length;i<ilen;i++) {
20979 var word = words[i];
20980 if (word) {
20981 if (config.isFirst) {
20982 words[i] = _capitalise(word);
20983 config.isFirst = false;
20984 break;
20985 }
20986 }
20987 }
20988 return words.join(" ");
20989 },
20990 skipWordsRex: null,
20991 tagState: [],
20992 afterPunct: null,
20993 isFirst: true
20994 };
20995 return _textcaseEngine(config, string);
20996 }
20997
20998 /**
20999 * Force the first letter of each space-delimited
21000 * word in the string to uppercase, and leave the remainder
21001 * of the string untouched. Single characters are forced
21002 * to uppercase.
21003 */
21004 function capitalizeAll (state, string) {
21005 var config = {
21006 quoteState: [],
21007 capitaliseWords: function(str) {
21008 var words = str.split(" ");
21009 for (var i=0,ilen=words.length;i<ilen;i++) {
21010 var word = words[i];
21011 if (word) {
21012 words[i] = _capitalise(word);
21013 }
21014 }
21015 return words.join(" ");
21016 },
21017 skipWordsRex: null,
21018 tagState: [],
21019 afterPunct: null,
21020 isFirst: null
21021 };
21022 return _textcaseEngine(config, string);
21023 }
21024 return {
21025 passthrough: passthrough,
21026 lowercase: lowercase,
21027 uppercase: uppercase,
21028 sentence: sentence,
21029 title: title,
21030 "capitalize-first": capitalizeFirst,
21031 "capitalize-all": capitalizeAll
21032 };
21033}());
21034
21035/*global CSL: true */
21036
21037
21038/**
21039 * Output specifications.
21040 * @class
21041 */
21042CSL.Output.Formats = function () {};
21043
21044/**
21045 * HTML output format specification.
21046 * <p>The headline says it all. The source code for this
21047 * object can be used as a template for producing other
21048 * output modes.</p>
21049 */
21050CSL.Output.Formats.prototype.html = {
21051 //
21052 // text_escape: Format-specific function for escaping text destined
21053 // for output. Takes the text to be escaped as sole argument. Function
21054 // will be run only once across each portion of text to be escaped, it
21055 // need not be idempotent.
21056 //
21057 "text_escape": function (text) {
21058 // Numeric entities, in case the output is processed as
21059 // xml in an environment in which HTML named entities are
21060 // not declared.
21061 if (!text) {
21062 text = "";
21063 }
21064 return text.replace(/&/g, "&#38;")
21065 .replace(/</g, "&#60;")
21066 .replace(/>/g, "&#62;")
21067 .replace(/\s\s/g, "\u00A0 ")
21068 .replace(CSL.SUPERSCRIPTS_REGEXP,
21069 function(aChar) {
21070 // return "&#60;sup&#62;" + CSL.SUPERSCRIPTS[aChar] + "&#60;/sup&#62;";
21071 return "<sup>" + CSL.SUPERSCRIPTS[aChar] + "</sup>";
21072 });
21073 },
21074 "bibstart": "<div class=\"csl-bib-body\">\n",
21075 "bibend": "</div>",
21076 "@font-style/italic": "<i>%%STRING%%</i>",
21077 "@font-style/oblique": "<em>%%STRING%%</em>",
21078 "@font-style/normal": "<span style=\"font-style:normal;\">%%STRING%%</span>",
21079 "@font-variant/small-caps": "<span style=\"font-variant:small-caps;\">%%STRING%%</span>",
21080 "@passthrough/true": CSL.Output.Formatters.passthrough,
21081 "@font-variant/normal": "<span style=\"font-variant:normal;\">%%STRING%%</span>",
21082 "@font-weight/bold": "<b>%%STRING%%</b>",
21083 "@font-weight/normal": "<span style=\"font-weight:normal;\">%%STRING%%</span>",
21084 "@font-weight/light": false,
21085 "@text-decoration/none": "<span style=\"text-decoration:none;\">%%STRING%%</span>",
21086 "@text-decoration/underline": "<span style=\"text-decoration:underline;\">%%STRING%%</span>",
21087 "@vertical-align/sup": "<sup>%%STRING%%</sup>",
21088 "@vertical-align/sub": "<sub>%%STRING%%</sub>",
21089 "@vertical-align/baseline": "<span style=\"baseline\">%%STRING%%</span>",
21090 "@strip-periods/true": CSL.Output.Formatters.passthrough,
21091 "@strip-periods/false": CSL.Output.Formatters.passthrough,
21092 "@quotes/true": function (state, str) {
21093 if ("undefined" === typeof str) {
21094 return state.getTerm("open-quote");
21095 }
21096 return state.getTerm("open-quote") + str + state.getTerm("close-quote");
21097 },
21098 "@quotes/inner": function (state, str) {
21099 if ("undefined" === typeof str) {
21100 //
21101 // Mostly right by being wrong (for apostrophes)
21102 //
21103 return "\u2019";
21104 }
21105 return state.getTerm("open-inner-quote") + str + state.getTerm("close-inner-quote");
21106 },
21107 "@quotes/false": false,
21108 //"@bibliography/body": function (state,str){
21109 // return "<div class=\"csl-bib-body\">\n"+str+"</div>";
21110 //},
21111 "@cite/entry": function (state, str) {
21112 return state.sys.wrapCitationEntry(str, this.item_id, this.locator_txt, this.suffix_txt);
21113 },
21114 "@bibliography/entry": function (state, str) {
21115 // Test for this.item_id to add decorations to
21116 // bibliography output of individual entries.
21117 //
21118 // Full item content can be obtained from
21119 // state.registry.registry[id].ref, using
21120 // CSL variable keys.
21121 //
21122 // Example:
21123 //
21124 // print(state.registry.registry[this.item_id].ref["title"]);
21125 //
21126 // At present, for parallel citations, only the
21127 // id of the master item is supplied on this.item_id.
21128 var insert = "";
21129 if (state.sys.embedBibliographyEntry) {
21130 insert = state.sys.embedBibliographyEntry(this.item_id) + "\n";
21131 }
21132 return " <div class=\"csl-entry\">" + str + "</div>\n" + insert;
21133 },
21134 "@display/block": function (state, str) {
21135 return "\n\n <div class=\"csl-block\">" + str + "</div>\n";
21136 },
21137 "@display/left-margin": function (state, str) {
21138 return "\n <div class=\"csl-left-margin\">" + str + "</div>";
21139 },
21140 "@display/right-inline": function (state, str) {
21141 return "<div class=\"csl-right-inline\">" + str + "</div>\n ";
21142 },
21143 "@display/indent": function (state, str) {
21144 return "<div class=\"csl-indent\">" + str + "</div>\n ";
21145 },
21146 "@showid/true": function (state, str, cslid) {
21147 if (!state.tmp.just_looking && ! state.tmp.suppress_decorations) {
21148 if (cslid) {
21149 return "<span class=\"" + state.opt.nodenames[cslid] + "\" cslid=\"" + cslid + "\">" + str + "</span>";
21150 } else if (this.params && "string" === typeof str) {
21151 var prePunct = "";
21152 if (str) {
21153 var m = str.match(CSL.VARIABLE_WRAPPER_PREPUNCT_REX);
21154 prePunct = m[1];
21155 str = m[2];
21156 }
21157 var postPunct = "";
21158 if (str && CSL.SWAPPING_PUNCTUATION.indexOf(str.slice(-1)) > -1) {
21159 postPunct = str.slice(-1);
21160 str = str.slice(0,-1);
21161 }
21162 return state.sys.variableWrapper(this.params, prePunct, str, postPunct);
21163 } else {
21164 return str;
21165 }
21166 } else {
21167 return str;
21168 }
21169 },
21170 "@URL/true": function (state, str) {
21171 return "<a href=\"" + str + "\">" + str + "</a>";
21172 },
21173 "@DOI/true": function (state, str) {
21174 var doiurl = str;
21175 if (!str.match(/^https?:\/\//)) {
21176 doiurl = "https://doi.org/" + str;
21177 }
21178 return "<a href=\"" + doiurl + "\">" + str + "</a>";
21179 }
21180};
21181
21182/**
21183 * Plain text output specification.
21184 *
21185 * (Code contributed by Simon Kornblith, Center for History and New Media,
21186 * George Mason University.)
21187 */
21188CSL.Output.Formats.prototype.text = {
21189 //
21190 // text_escape: Format-specific function for escaping text destined
21191 // for output. Takes the text to be escaped as sole argument. Function
21192 // will be run only once across each portion of text to be escaped, it
21193 // need not be idempotent.
21194 //
21195 "text_escape": function (text) {
21196 if (!text) {
21197 text = "";
21198 }
21199 return text;
21200 },
21201 "bibstart": "",
21202 "bibend": "",
21203 "@font-style/italic": false,
21204 "@font-style/oblique": false,
21205 "@font-style/normal": false,
21206 "@font-variant/small-caps": false,
21207 "@passthrough/true": CSL.Output.Formatters.passthrough,
21208 "@font-variant/normal": false,
21209 "@font-weight/bold": false,
21210 "@font-weight/normal": false,
21211 "@font-weight/light": false,
21212 "@text-decoration/none": false,
21213 "@text-decoration/underline": false,
21214 "@vertical-align/baseline": false,
21215 "@vertical-align/sup": false,
21216 "@vertical-align/sub": false,
21217 "@strip-periods/true": CSL.Output.Formatters.passthrough,
21218 "@strip-periods/false": CSL.Output.Formatters.passthrough,
21219 "@quotes/true": function (state, str) {
21220 if ("undefined" === typeof str) {
21221 return state.getTerm("open-quote");
21222 }
21223 return state.getTerm("open-quote") + str + state.getTerm("close-quote");
21224 },
21225 "@quotes/inner": function (state, str) {
21226 if ("undefined" === typeof str) {
21227 //
21228 // Mostly right by being wrong (for apostrophes)
21229 //
21230 return "\u2019";
21231 }
21232 return state.getTerm("open-inner-quote") + str + state.getTerm("close-inner-quote");
21233 },
21234 "@quotes/false": false,
21235 //"@bibliography/body": function (state,str){
21236 // return "<div class=\"csl-bib-body\">\n"+str+"</div>";
21237 //},
21238 "@cite/entry": function (state, str) {
21239 return state.sys.wrapCitationEntry(str, this.item_id, this.locator_txt, this.suffix_txt);
21240 },
21241 "@bibliography/entry": function (state, str) {
21242 return str+"\n";
21243 },
21244 "@display/block": function (state, str) {
21245 return "\n"+str;
21246 },
21247 "@display/left-margin": function (state, str) {
21248 return str;
21249 },
21250 "@display/right-inline": function (state, str) {
21251 return str;
21252 },
21253 "@display/indent": function (state, str) {
21254 return "\n "+str;
21255 },
21256 "@showid/true": function (state, str) {
21257 return str;
21258 },
21259 "@URL/true": function (state, str) {
21260 return str;
21261 },
21262 "@DOI/true": function (state, str) {
21263 return str;
21264 }
21265};
21266
21267/**
21268 * Plain text output specification.
21269 *
21270 * (Code contributed by Simon Kornblith, Center for History and New Media,
21271 * George Mason University.)
21272 */
21273CSL.Output.Formats.prototype.rtf = {
21274 //
21275 // text_escape: Format-specific function for escaping text destined
21276 // for output. Takes the text to be escaped as sole argument. Function
21277 // will be run only once across each portion of text to be escaped, it
21278 // need not be idempotent.
21279 //
21280 "text_escape": function (text) {
21281 if (!text) {
21282 text = "";
21283 }
21284 return text
21285 .replace(/([\\{}])/g, "\\$1")
21286 .replace(CSL.SUPERSCRIPTS_REGEXP,
21287 function(aChar) {
21288 return "\\super " + CSL.SUPERSCRIPTS[aChar] + "\\nosupersub{}";
21289 })
21290 .replace(/[\u007F-\uFFFF]/g,
21291 function(aChar) { return "\\uc0\\u"+aChar.charCodeAt(0).toString()+"{}"; })
21292 .split("\t").join("\\tab{}");
21293 },
21294 "@passthrough/true": CSL.Output.Formatters.passthrough,
21295 "@font-style/italic":"{\\i{}%%STRING%%}",
21296 "@font-style/normal":"{\\i0{}%%STRING%%}",
21297 "@font-style/oblique":"{\\i{}%%STRING%%}",
21298 "@font-variant/small-caps":"{\\scaps %%STRING%%}",
21299 "@font-variant/normal":"{\\scaps0{}%%STRING%%}",
21300 "@font-weight/bold":"{\\b{}%%STRING%%}",
21301 "@font-weight/normal":"{\\b0{}%%STRING%%}",
21302 "@font-weight/light":false,
21303 "@text-decoration/none":false,
21304 "@text-decoration/underline":"{\\ul{}%%STRING%%}",
21305 "@vertical-align/baseline":false,
21306 "@vertical-align/sup":"\\super %%STRING%%\\nosupersub{}",
21307 "@vertical-align/sub":"\\sub %%STRING%%\\nosupersub{}",
21308 "@strip-periods/true": CSL.Output.Formatters.passthrough,
21309 "@strip-periods/false": CSL.Output.Formatters.passthrough,
21310 "@quotes/true": function (state, str) {
21311 if ("undefined" === typeof str) {
21312 return CSL.Output.Formats.rtf.text_escape(state.getTerm("open-quote"));
21313 }
21314 return CSL.Output.Formats.rtf.text_escape(state.getTerm("open-quote")) + str + CSL.Output.Formats.rtf.text_escape(state.getTerm("close-quote"));
21315 },
21316 "@quotes/inner": function (state, str) {
21317 if ("undefined" === typeof str) {
21318 return CSL.Output.Formats.rtf.text_escape("\u2019");
21319 }
21320 return CSL.Output.Formats.rtf.text_escape(state.getTerm("open-inner-quote")) + str + CSL.Output.Formats.rtf.text_escape(state.getTerm("close-inner-quote"));
21321 },
21322 "@quotes/false": false,
21323 "bibstart":"{\\rtf ",
21324 "bibend":"}",
21325 "@display/block": "\\line{}%%STRING%%\\line\r\n",
21326 "@cite/entry": function (state, str) {
21327 // If wrapCitationEntry does not exist, cite/entry
21328 // is not applied.
21329 return state.sys.wrapCitationEntry(str, this.item_id, this.locator_txt, this.suffix_txt);
21330 },
21331 "@bibliography/entry": function(state,str){
21332 return str;
21333 },
21334 "@display/left-margin": function(state,str){
21335 return str+"\\tab ";
21336 },
21337 "@display/right-inline": function (state, str) {
21338 return str+"\r\n";
21339 },
21340 "@display/indent": function (state, str) {
21341 return "\n\\tab "+str+"\\line\r\n";
21342 },
21343 "@showid/true": function (state, str) {
21344 if (!state.tmp.just_looking && ! state.tmp.suppress_decorations) {
21345 var prePunct = "";
21346 if (str) {
21347 var m = str.match(CSL.VARIABLE_WRAPPER_PREPUNCT_REX);
21348 prePunct = m[1];
21349 str = m[2];
21350 }
21351 var postPunct = "";
21352 if (str && CSL.SWAPPING_PUNCTUATION.indexOf(str.slice(-1)) > -1) {
21353 postPunct = str.slice(-1);
21354 str = str.slice(0,-1);
21355 }
21356 return state.sys.variableWrapper(this.params, prePunct, str, postPunct);
21357 } else {
21358 return str;
21359 }
21360 },
21361 "@URL/true": function (state, str) {
21362 return str;
21363 },
21364 "@DOI/true": function (state, str) {
21365 return str;
21366 }
21367};
21368
21369/*
21370
21371 This does not seem to work in Zotero plugins. For some reason the scope of the link does not
21372 close when interpreted by the LibreOffice. Perhaps this creates a field within a field,
21373 and that is not allowed?
21374
21375 "@URL/true": function (state, str) {
21376 return "\\field{\\*\\fldinst{HYPERLINK \"" + str + "\"}}{\\fldrslt{"+ str +"}}";
21377 },
21378 "@DOI/true": function (state, str) {
21379 return "\\field{\\*\\fldinst{HYPERLINK \"https://doi.org/" + str + "\"}}{\\fldrslt{"+ str +"}}";
21380 }
21381*/
21382
21383/**
21384 * AsciiDoc output specification.
21385 *
21386 * See http://asciidoc.org/ or https://asciidoctor.org/
21387 */
21388CSL.Output.Formats.prototype.asciidoc = {
21389 "text_escape": function (text) {
21390 if (!text) {
21391 text = "";
21392 }
21393 return text.replace("*", "pass:[*]", "g")
21394 .replace("_", "pass:[_]", "g")
21395 .replace("#", "pass:[#]", "g")
21396 .replace("^", "pass:[^]", "g")
21397 .replace("~", "pass:[~]", "g")
21398 .replace("[[", "pass:[[[]", "g")
21399 .replace(" ", "&#160; ", "g")
21400 .replace(CSL.SUPERSCRIPTS_REGEXP, function(aChar) {
21401 return "^" + CSL.SUPERSCRIPTS[aChar] + "^";
21402 });
21403 },
21404 "bibstart": "",
21405 "bibend": "",
21406 "@passthrough/true": CSL.Output.Formatters.passthrough,
21407 "@font-style/italic": "__%%STRING%%__",
21408 "@font-style/oblique": "__%%STRING%%__",
21409 "@font-style/normal": false,
21410 "@font-variant/small-caps": "[small-caps]#%%STRING%%#",
21411 "@font-variant/normal": false,
21412 "@font-weight/bold": "**%%STRING%%**",
21413 "@font-weight/normal": false,
21414 "@font-weight/light": false,
21415 "@text-decoration/none": false,
21416 "@text-decoration/underline": "[underline]##%%STRING%%##",
21417 "@vertical-align/sup": "^^%%STRING%%^^",
21418 "@vertical-align/sub": "~~%%STRING%%~~",
21419 "@vertical-align/baseline": false,
21420 "@strip-periods/true": CSL.Output.Formatters.passthrough,
21421 "@strip-periods/false": CSL.Output.Formatters.passthrough,
21422 "@quotes/true": function (state, str) {
21423 if ("undefined" === typeof str) {
21424 return "``";
21425 }
21426 return "``" + str + "''";
21427 },
21428 "@quotes/inner": function (state, str) {
21429 if ("undefined" === typeof str) {
21430 return "`";
21431 }
21432 return "`" + str + "'";
21433 },
21434 "@quotes/false": false,
21435 "@cite/entry": function (state, str) {
21436 // if wrapCitationEntry does not exist, cite/entry is not applied
21437 return state.sys.wrapCitationEntry(str, this.item_id, this.locator_txt, this.suffix_txt);
21438 },
21439 "@bibliography/entry": function (state, str) {
21440 return str + "\n";
21441 },
21442 "@display/block": function (state, str) {
21443 return str;
21444 },
21445 "@display/left-margin": function (state, str) {
21446 return str;
21447 },
21448 "@display/right-inline": function (state, str) {
21449 return " " + str;
21450 },
21451 "@display/indent": function (state, str) {
21452 return " " + str;
21453 },
21454 "@showid/true": function (state, str) {
21455 if (!state.tmp.just_looking && !state.tmp.suppress_decorations && this.params && "string" === typeof str) {
21456 var prePunct = "";
21457 if (str) {
21458 var m = str.match(CSL.VARIABLE_WRAPPER_PREPUNCT_REX);
21459 prePunct = m[1];
21460 str = m[2];
21461 }
21462 var postPunct = "";
21463 if (str && CSL.SWAPPING_PUNCTUATION.indexOf(str.slice(-1)) > -1) {
21464 postPunct = str.slice(-1);
21465 str = str.slice(0,-1);
21466 }
21467 return state.sys.variableWrapper(this.params, prePunct, str, postPunct);
21468 } else {
21469 return str;
21470 }
21471 },
21472 "@URL/true": function (state, str) {
21473 // AsciiDoc renders URLs automatically as links
21474 return str;
21475 },
21476 "@DOI/true": function (state, str) {
21477 var doiurl = str;
21478 if (!str.match(/^https?:\/\//)) {
21479 doiurl = "https://doi.org/" + str;
21480 }
21481 return doiurl + "[" + str + "]";
21482 }
21483};
21484
21485/**
21486 * Output specification for XSL-FO (Extensible Stylesheet
21487 * Language - Formatting Objects)
21488 *
21489 * See https://www.w3.org/TR/xsl11/#fo-section
21490 */
21491CSL.Output.Formats.prototype.fo = {
21492 "text_escape": function (text) {
21493 if (!text) {
21494 text = "";
21495 }
21496 return text.replace(/&/g, "&#38;")
21497 .replace(/</g, "&#60;")
21498 .replace(/>/g, "&#62;")
21499 .replace(" ", "&#160; ", "g")
21500 .replace(CSL.SUPERSCRIPTS_REGEXP, function(aChar) {
21501 return "<fo:inline vertical-align=\"super\">" + CSL.SUPERSCRIPTS[aChar] + "</fo:inline>";
21502 });
21503 },
21504 "bibstart": "",
21505 "bibend": "",
21506 "@passthrough/true": CSL.Output.Formatters.passthrough,
21507 "@font-style/italic": "<fo:inline font-style=\"italic\">%%STRING%%</fo:inline>",
21508 "@font-style/oblique": "<fo:inline font-style=\"oblique\">%%STRING%%</fo:inline>",
21509 "@font-style/normal": "<fo:inline font-style=\"normal\">%%STRING%%</fo:inline>",
21510 "@font-variant/small-caps": "<fo:inline font-variant=\"small-caps\">%%STRING%%</fo:inline>",
21511 "@font-variant/normal": "<fo:inline font-variant=\"normal\">%%STRING%%</fo:inline>",
21512 "@font-weight/bold": "<fo:inline font-weight=\"bold\">%%STRING%%</fo:inline>",
21513 "@font-weight/normal": "<fo:inline font-weight=\"normal\">%%STRING%%</fo:inline>",
21514 "@font-weight/light": "<fo:inline font-weight=\"lighter\">%%STRING%%</fo:inline>",
21515 "@text-decoration/none": "<fo:inline text-decoration=\"none\">%%STRING%%</fo:inline>",
21516 "@text-decoration/underline": "<fo:inline text-decoration=\"underline\">%%STRING%%</fo:inline>",
21517 "@vertical-align/sup": "<fo:inline vertical-align=\"super\">%%STRING%%</fo:inline>",
21518 "@vertical-align/sub": "<fo:inline vertical-align=\"sub\">%%STRING%%</fo:inline>",
21519 "@vertical-align/baseline": "<fo:inline vertical-align=\"baseline\">%%STRING%%</fo:inline>",
21520 "@strip-periods/true": CSL.Output.Formatters.passthrough,
21521 "@strip-periods/false": CSL.Output.Formatters.passthrough,
21522 "@quotes/true": function (state, str) {
21523 if ("undefined" === typeof str) {
21524 return state.getTerm("open-quote");
21525 }
21526 return state.getTerm("open-quote") + str + state.getTerm("close-quote");
21527 },
21528 "@quotes/inner": function (state, str) {
21529 if ("undefined" === typeof str) {
21530 return "\u2019";
21531 }
21532 return state.getTerm("open-inner-quote") + str + state.getTerm("close-inner-quote");
21533 },
21534 "@quotes/false": false,
21535 "@cite/entry": function (state, str) {
21536 return state.sys.wrapCitationEntry(str, this.item_id, this.locator_txt, this.suffix_txt);
21537 },
21538 "@bibliography/entry": function (state, str) {
21539 var indent = "";
21540 if (state.bibliography && state.bibliography.opt && state.bibliography.opt.hangingindent) {
21541 var hi = state.bibliography.opt.hangingindent;
21542 indent = " start-indent=\"" + hi +"em\" text-indent=\"-" + hi + "em\"";
21543 }
21544 var insert = "";
21545 if (state.sys.embedBibliographyEntry) {
21546 insert = state.sys.embedBibliographyEntry(this.item_id) + "\n";
21547 }
21548 return "<fo:block id=\"" + this.system_id + "\"" + indent + ">" + str + "</fo:block>\n" + insert;
21549 },
21550 "@display/block": function (state, str) {
21551 return "\n <fo:block>" + str + "</fo:block>\n";
21552 },
21553 "@display/left-margin": function (state, str) {
21554 return "\n <fo:table table-layout=\"fixed\" width=\"100%\">\n " +
21555 "<fo:table-column column-number=\"1\" column-width=\"$$$__COLUMN_WIDTH_1__$$$\"/>\n " +
21556 "<fo:table-column column-number=\"2\" column-width=\"proportional-column-width(1)\"/>\n " +
21557 "<fo:table-body>\n " +
21558 "<fo:table-row>\n " +
21559 "<fo:table-cell>\n " +
21560 "<fo:block>" + str + "</fo:block>\n " +
21561 "</fo:table-cell>\n ";
21562 },
21563 "@display/right-inline": function (state, str) {
21564 return "<fo:table-cell>\n " +
21565 "<fo:block>" + str + "</fo:block>\n " +
21566 "</fo:table-cell>\n " +
21567 "</fo:table-row>\n " +
21568 "</fo:table-body>\n " +
21569 "</fo:table>\n";
21570 },
21571 "@display/indent": function (state, str) {
21572 return "<fo:block margin-left=\"2em\">" + str + "</fo:block>\n";
21573 },
21574 "@showid/true": function (state, str) {
21575 if (!state.tmp.just_looking && !state.tmp.suppress_decorations && this.params && "string" === typeof str) {
21576 var prePunct = "";
21577 if (str) {
21578 var m = str.match(CSL.VARIABLE_WRAPPER_PREPUNCT_REX);
21579 prePunct = m[1];
21580 str = m[2];
21581 }
21582 var postPunct = "";
21583 if (str && CSL.SWAPPING_PUNCTUATION.indexOf(str.slice(-1)) > -1) {
21584 postPunct = str.slice(-1);
21585 str = str.slice(0,-1);
21586 }
21587 return state.sys.variableWrapper(this.params, prePunct, str, postPunct);
21588 } else {
21589 return str;
21590 }
21591 },
21592 "@URL/true": function (state, str) {
21593 return "<fo:basic-link external-destination=\"url('" + str + "')\">" + str + "</fo:basic-link>";
21594 },
21595 "@DOI/true": function (state, str) {
21596 var doiurl = str;
21597 if (!str.match(/^https?:\/\//)) {
21598 doiurl = "https://doi.org/" + str;
21599 }
21600 return "<fo:basic-link external-destination=\"url('" + doiurl + "')\">" + str + "</fo:basic-link>";
21601 }
21602};
21603
21604/**
21605 * LaTeX .bbl output.
21606 *
21607 * (Code contributed by Egon Willighagen, based on the prototype.text code.)
21608 */
21609CSL.Output.Formats.prototype.latex = {
21610 "text_escape": function (text) {
21611 if (!text) {
21612 text = "";
21613 }
21614 return text;
21615 },
21616 "bibstart": "\\begin{thebibliography}{4}",
21617 "bibend": "\end{thebibliography}",
21618 "@font-style/italic": "{\\em %%STRING%%}",
21619 "@font-style/oblique": false,
21620 "@font-style/normal": false,
21621 "@font-variant/small-caps": false,
21622 "@passthrough/true": CSL.Output.Formatters.passthrough,
21623 "@font-variant/normal": false,
21624 "@font-weight/bold": "{\\bf %%STRING%%}",
21625 "@font-weight/normal": false,
21626 "@font-weight/light": false,
21627 "@text-decoration/none": false,
21628 "@text-decoration/underline": false,
21629 "@vertical-align/baseline": false,
21630 "@vertical-align/sup": false,
21631 "@vertical-align/sub": false,
21632 "@strip-periods/true": CSL.Output.Formatters.passthrough,
21633 "@strip-periods/false": CSL.Output.Formatters.passthrough,
21634 "@quotes/true": function (state, str) {
21635 if ("undefined" === typeof str) {
21636 return state.getTerm("open-quote");
21637 }
21638 return state.getTerm("open-quote") + str + state.getTerm("close-quote");
21639 },
21640 "@quotes/inner": function (state, str) {
21641 if ("undefined" === typeof str) {
21642 //
21643 // Mostly right by being wrong (for apostrophes)
21644 //
21645 return "\u2019";
21646 }
21647 return state.getTerm("open-inner-quote") + str + state.getTerm("close-inner-quote");
21648 },
21649 "@quotes/false": false,
21650 //"@bibliography/body": function (state,str){
21651 // return "<div class=\"csl-bib-body\">\n"+str+"</div>";
21652 //},
21653 "@cite/entry": function (state, str) {
21654 return state.sys.wrapCitationEntry(str, this.item_id, this.locator_txt, this.suffix_txt);
21655 },
21656 "@bibliography/entry": function (state, str) {
21657 return "\\bibitem{" + state.sys.embedBibliographyEntry(this.item_id) + "}\n";
21658 },
21659 "@display/block": function (state, str) {
21660 return "\n"+str;
21661 },
21662 "@display/left-margin": function (state, str) {
21663 return str;
21664 },
21665 "@display/right-inline": function (state, str) {
21666 return str;
21667 },
21668 "@display/indent": function (state, str) {
21669 return "\n "+str;
21670 },
21671 "@showid/true": function (state, str, cslid) {
21672 return str;
21673 },
21674 "@URL/true": function (state, str) {
21675 return str;
21676 },
21677 "@DOI/true": function (state, str) {
21678 return str;
21679 }
21680};
21681
21682CSL.Output.Formats = new CSL.Output.Formats();
21683
21684/*global CSL: true */
21685
21686
21687//
21688// Time for a rewrite of this module.
21689//
21690// Simon has pointed out that list and hash behavior can
21691// be obtained by ... just using a list and a hash. This
21692// is faster for batched operations, because sorting is
21693// greatly optimized. Since most of the interaction
21694// with plugins at runtime will involve batches of
21695// references, there will be solid gains if the current,
21696// one-reference-at-a-time approach implemented here
21697// can be replaced with something that leverages the native
21698// sort method of the Array() type.
21699//
21700// That's going to take some redesign, but it will simplify
21701// things in the long run, so it might as well happen now.
21702//
21703// We'll keep makeCitationCluster and makeBibliography as
21704// simple methods that return a string. Neither should
21705// have any effect on internal state. This will be a change
21706// in behavior for makeCitationCluster.
21707//
21708// A new updateItems command will be introduced, to replace
21709// insertItems. It will be a simple list of IDs, in the
21710// sequence of first reference in the document.
21711//
21712// The calling application should always invoke updateItems
21713// before makeCitationCluster.
21714//
21715
21716//
21717// should allow batched registration of items by
21718// key. should behave as an update, with deletion
21719// of items and the tainting of disambiguation
21720// partner sets affected by a deletes and additions.
21721//
21722//
21723// we'll need a reset method, to clear the decks
21724// in the citation area and start over.
21725
21726/**
21727 * Registry of cited items.
21728 * <p>This is a persistent store of disambiguation and
21729 * sort order information relating to individual items
21730 * for which rendering is requested. Item data is stored
21731 * in a hash, with the item key as hash key, for quick
21732 * retrieval. A virtual sequence within the hashed store
21733 * is maintained on the fly as items are added to the
21734 * store, using <code>*_next</code> and <code>*_prev</code>
21735 * attributes on each item. A separate hash of items
21736 * based on their undisambiguated cite form is
21737 * maintained, and the item id list and disambiguation
21738 * level for each set of disambiguation partners is shared
21739 * through the registry item.</p>
21740 * @class
21741 */
21742CSL.Registry = function (state) {
21743 this.debug = false;
21744 this.state = state;
21745 this.registry = {};
21746 this.reflist = [];
21747 this.refhash = {};
21748 this.namereg = new CSL.Registry.NameReg(state);
21749 this.citationreg = new CSL.Registry.CitationReg(state);
21750 // See CSL.NameOutput.prototype.outputNames
21751 // and CSL.Registry.prototype.doinserts
21752 this.authorstrings = {};
21753
21754 //
21755 // shared scratch vars
21756 this.mylist = [];
21757 this.myhash = {};
21758 this.deletes = [];
21759 this.inserts = [];
21760 this.uncited = {};
21761 this.refreshes = {};
21762 this.akeys = {};
21763 this.oldseq = {};
21764 this.return_data = {};
21765 //
21766 // each ambig is a list of the ids of other objects
21767 // that have the same base-level rendering
21768 this.ambigcites = {};
21769 this.ambigresets = {};
21770 this.sorter = new CSL.Registry.Comparifier(state, "bibliography_sort");
21771 //this.modes = CSL.getModes.call(this.state);
21772 //this.checkerator = new CSL.Checkerator();
21773
21774 this.getSortedIds = function () {
21775 var ret = [];
21776 for (var i = 0, ilen = this.reflist.length; i < ilen; i += 1) {
21777 ret.push("" + this.reflist[i].id);
21778 }
21779 return ret;
21780 };
21781
21782 this.getSortedRegistryItems = function () {
21783 var ret = [];
21784 for (var i = 0, ilen = this.reflist.length; i < ilen; i += 1) {
21785 ret.push(this.reflist[i]);
21786 }
21787 return ret;
21788 };
21789};
21790
21791//
21792// Here's the sequence of operations to be performed on
21793// update:
21794//
21795// 1. (o) [init] Receive list as function argument, store as hash and as list.
21796// 2. (o) [init] Initialize refresh list. Never needs sorting, only hash required.
21797
21798// 3. (o) [dodeletes] Delete loop.
21799// 3a. (o) [dodeletes] Delete names in items to be deleted from names reg.
21800// 3b. (o) [dodeletes] Complement refreshes list with items affected by
21801// possible name changes. We'll actually perform the refresh once
21802// all of the necessary data and parameters have been established
21803// in the registry.
21804// 3c. (o) [dodeletes] Delete all items to be deleted from their disambig pools.
21805// 3d. (o) [dodeletes] Delete all items in deletion list from hash.
21806
21807// 4. (o) [doinserts] Insert loop.
21808// 4a. (o) [doinserts] Retrieve entries for items to insert.
21809// 4b. (o) [doinserts] Generate ambig key.
21810// 4c. (o) [doinserts] Add names in items to be inserted to names reg
21811// (implicit in getAmbiguousCite).
21812// 4d. (o) [doinserts] Record ambig pool key on akey list (used for updating further
21813// down the chain).
21814// 4e. (o) [doinserts] Create registry token.
21815// 4f. (o) [doinserts] Add item ID to hash.
21816// 4g. (o) [doinserts] Set and record the base token to hold disambiguation
21817// results ("disambig" in the object above).
21818// 5. (o) [rebuildlist] Create "new" list of hash pointers, in the order given
21819// in the argument to the update function.
21820// 6. (o) [rebuildlist] Apply citation numbers to new list.
21821// 7. (o) [dorefreshes] Refresh items requiring update.
21822
21823
21824
21825// 5. (o) [delnames] Delete names in items to be deleted from names reg, and obtain IDs
21826// of other items that would be affected by changes around that surname.
21827// 6. (o) [delnames] Complement delete and insert lists with items affected by
21828// possible name changes.
21829// 7. (o) [delambigs] Delete all items to be deleted from their disambig pools.
21830// 8. (o) [delhash] Delete all items in deletion list from hash.
21831
21832// 9. (o) [addtohash] Retrieve entries for items to insert.
21833// 10. (o) [addtohash] Add items to be inserted to their disambig pools.
21834// 11. (o) [addtohash] Add names in items to be inserted to names reg
21835// (implicit in getAmbiguousCite).
21836// 12. (o) [addtohash] Create registry token for each item to be inserted.
21837// 13. (o) [addtohash] Add items for insert to hash.
21838
21839// 14. (o) [buildlist] Create "new" list of hash pointers, in the order given in the argument
21840// to the update function.
21841// 15. (o) [renumber] Apply citation numbers to new list.
21842// 16. (o) [setdisambigs] Set disambiguation parameters on each inserted item token.
21843// 17. (o) [setsortkeys] Set sort keys on each item token.
21844// 18. (o) [sorttokens] Resort token list
21845// 19. (o) [renumber] Reset citation numbers on list items
21846//
21847
21848CSL.Registry.prototype.init = function (itemIDs, uncited_flag) {
21849 var i, ilen;
21850 this.oldseq = {};
21851 // 1. Receive list as function argument, store as hash and as list.
21852 //
21853 // Result:
21854 // this.mylist: a list of all itemIDs of referenced items, cited and uncited.
21855 // this.myhash: a hash of index positions in this.mylist.
21856 // this.uncited: hash of uncited itemIDs.
21857 //
21858 // Proceed as follows.
21859 //
21860 if (uncited_flag) {
21861 // If uncited_flag is non-nil, add any missing itemIDs to this.mylist
21862 // from itemIDs input list, and set the itemIDs in itemIDs on this.uncited.
21863 this.uncited = {};
21864 for (var i=0,ilen=itemIDs.length;i<ilen; i += 1) {
21865 if (!this.myhash[itemIDs[i]]) {
21866 this.mylist.push("" + itemIDs[i]);
21867 }
21868 this.uncited[itemIDs[i]] = true;
21869 this.myhash[itemIDs[i]] = true;
21870 }
21871 } else {
21872 // If uncited_flag is nil, remove duplicate itemIDs from itemIDs input
21873 // list, set the result on this.mylist, and add missing itemIDs to
21874 // this.mylist from itemIDs input list.
21875 for (var key in this.uncited) {
21876 itemIDs.push(key);
21877 }
21878 var myhash = {};
21879 for (i=itemIDs.length-1;i>-1; i += -1) {
21880 if (myhash[itemIDs[i]]) {
21881 itemIDs = itemIDs.slice(0, i).concat(itemIDs.slice(i + 1));
21882 } else {
21883 myhash[itemIDs[i]] = true;
21884 }
21885 }
21886 this.mylist = itemIDs;
21887 this.myhash = myhash;
21888 }
21889 //
21890 // 2. Initialize refresh list. Never needs sorting, only hash required.
21891 //
21892 this.refreshes = {};
21893 this.touched = {};
21894 this.ambigsTouched = {};
21895 this.ambigresets = {};
21896};
21897
21898CSL.Registry.prototype.dopurge = function (myhash) {
21899 // Remove any uncited items not in myhash
21900 for (var i=this.mylist.length-1;i>-1;i+=-1) {
21901 // Might not want to be quite this restrictive.
21902 if (this.citationreg.citationsByItemId) {
21903 if (!this.citationreg.citationsByItemId[this.mylist[i]] && !myhash[this.mylist[i]]) {
21904 delete this.myhash[this.mylist[i]];
21905 this.mylist = this.mylist.slice(0,i).concat(this.mylist.slice(i+1));
21906 }
21907 }
21908 }
21909 this.dodeletes(this.myhash);
21910};
21911
21912CSL.Registry.prototype.dodeletes = function (myhash) {
21913 var otheritems, key, ambig, pos, len, items, kkey, mypos, id;
21914 if ("string" === typeof myhash) {
21915 var key = myhash;
21916 myhash = {};
21917 myhash[key] = true;
21918 }
21919 //
21920 // 3. Delete loop.
21921 //
21922 for (var key in this.registry) {
21923 if (!myhash[key]) {
21924 // skip items explicitly marked as uncited
21925 if (this.uncited[key]) {
21926 continue;
21927 }
21928 //
21929 // 3a. Delete names in items to be deleted from names reg.
21930 //
21931 otheritems = this.namereg.delitems(key);
21932 //
21933 // 3b. Complement refreshes list with items affected by
21934 // possible name changes. We'll actually perform the refresh once
21935 // all of the necessary data and parameters have been established
21936 // in the registry.
21937 //
21938 for (kkey in otheritems) {
21939 this.refreshes[kkey] = true;
21940 }
21941 //
21942 // 3c. Delete all items to be deleted from their disambig pools.
21943 //
21944 ambig = this.registry[key].ambig;
21945 mypos = this.ambigcites[ambig].indexOf(key);
21946 if (mypos > -1) {
21947 items = this.ambigcites[ambig].slice();
21948 this.ambigcites[ambig] = items.slice(0, mypos).concat(items.slice(mypos+1, items.length));
21949 this.ambigresets[ambig] = this.ambigcites[ambig].length;
21950 }
21951 //
21952 // XX. What we've missed is to provide an update of all
21953 // items sharing the same ambig += -1 the remaining items in
21954 // ambigcites. So let's do that here, just in case the
21955 // names update above doesn't catch them all.
21956 //
21957 len = this.ambigcites[ambig].length;
21958 for (pos = 0; pos < len; pos += 1) {
21959 id = "" + this.ambigcites[ambig][pos];
21960 this.refreshes[id] = true;
21961 }
21962 //
21963 // 3d-0. Remove parallel id references and realign
21964 // parallel ID refs.
21965 //
21966 if (this.registry[key].siblings) {
21967 if (this.registry[key].siblings.length == 1) {
21968 var loneSiblingID = this.registry[key].siblings[0];
21969 this.registry[loneSiblingID].master = true;
21970 this.registry[loneSiblingID].siblings.pop();
21971 this.registry[loneSiblingID].parallel = false;
21972 } else if (this.registry[key].siblings.length > 1) {
21973 var removeIDs = [key];
21974 if (this.registry[key].master) {
21975 var newmasterID = this.registry[key].siblings[0];
21976 var newmaster = this.registry[newmasterID];
21977 newmaster.master = true;
21978 newmaster.parallel = false;
21979 removeIDs.push(newmasterID);
21980 for (var k = 0, klen = this.registry[key].siblings.length; k < klen; k += 1) {
21981 this.registry[this.registry[key].siblings[k]].parallel = newmasterID;
21982 }
21983 }
21984 var buffer = [];
21985 for (var k = this.registry[key].siblings.length - 1; k > -1; k += -1) {
21986 var siblingID = this.registry[key].siblings.pop();
21987 if (removeIDs.indexOf(siblingID) === -1) {
21988 buffer.push(siblingID);
21989 }
21990 }
21991 for (var k = buffer.length - 1; k > -1; k += -1) {
21992 this.registry[key].siblings.push(buffer[k]);
21993 }
21994 }
21995 }
21996 //
21997 // 3d-1. Remove item from reflist
21998 for (var i=this.reflist.length-1;i>-1;i--) {
21999 if (this.reflist[i].id === key) {
22000 this.reflist = this.reflist.slice(0, i).concat(this.reflist.slice(i+1));
22001 }
22002 }
22003 //
22004 // 3d. Delete all items in deletion list from hash.
22005 //
22006 delete this.registry[key];
22007 delete this.refhash[key];
22008
22009 // For processCitationCluster()
22010 this.return_data.bibchange = true;
22011 }
22012 }
22013 // Disabled. See formats.js for code.
22014 // this.state.fun.decorate.items_delete( this.state.output[this.state.opt.mode].tmp, myhash );
22015};
22016
22017CSL.Registry.prototype.doinserts = function (mylist) {
22018 var item, Item, akey, newitem, abase, i, ilen;
22019 if ("string" === typeof mylist) {
22020 mylist = [mylist];
22021 }
22022 //
22023 // 4. Insert loop.
22024 //
22025 for (var i = 0, ilen = mylist.length; i < ilen; i += 1) {
22026 item = mylist[i];
22027 if (!this.registry[item]) {
22028 //
22029 // 4a. Retrieve entries for items to insert.
22030 //
22031 Item = this.state.retrieveItem(item);
22032
22033 //
22034 // 4b. Generate ambig key.
22035 //
22036 // AND
22037 //
22038 // 4c. Add names in items to be inserted to names reg
22039 // (implicit in getAmbiguousCite).
22040 //
22041 akey = CSL.getAmbiguousCite.call(this.state, Item);
22042 this.ambigsTouched[akey] = true;
22043 //
22044 // 4d. Record ambig pool key on akey list (used for updating further
22045 // down the chain).
22046 //
22047 if (!Item.legislation_id) {
22048 this.akeys[akey] = true;
22049 }
22050 //
22051 // 4e. Create registry token.
22052 //
22053 newitem = {
22054 "id": "" + item,
22055 "seq": 0,
22056 "offset": 0,
22057 "sortkeys": false,
22058 "ambig": false,
22059 "rendered": false,
22060 "disambig": false,
22061 "ref": Item,
22062 "newItem": true
22063 };
22064 //
22065 //
22066 // 4f. Add item ID to hash.
22067 //
22068 this.registry[item] = newitem;
22069 //
22070 // 4f(a). Add first reference note number
22071 // (this may be redundant)
22072 if (this.citationreg.citationsByItemId && this.citationreg.citationsByItemId[item]) {
22073 this.registry[item]["first-reference-note-number"] = this.citationreg.citationsByItemId[item][0].properties.noteIndex;
22074 }
22075
22076 //
22077 // 4g. Set and record the base token to hold disambiguation
22078 // results ("disambig" in the object above).
22079 //
22080 abase = CSL.getAmbigConfig.call(this.state);
22081 this.registerAmbigToken(akey, item, abase);
22082
22083 //if (!this.ambigcites[akey]){
22084 // this.ambigcites[akey] = [];
22085 //}
22086 //CSL.debug("Run: "+item+"("+this.ambigcites[akey]+")");
22087 //if (this.ambigcites[akey].indexOf(item) === -1){
22088 // CSL.debug(" Add: "+item);
22089 // this.ambigcites[akey].push(item);
22090 //}
22091 //
22092 // 4h. Make a note that this item needs its sort keys refreshed.
22093 //
22094 this.touched[item] = true;
22095 // For processCitationCluster()
22096 this.return_data.bibchange = true;
22097 }
22098 }
22099 // Disabled. See formats.js for code.
22100 // this.state.fun.decorate.items_add( this.state.output[this.state.opt.mode].tmp, mylist );
22101};
22102
22103/*
22104// No longer required.
22105CSL.Registry.prototype.douncited = function () {
22106 var pos, len;
22107 var cited_len = this.mylist.length - this.uncited.length;
22108 for (pos = 0, len = cited_len; pos < len; pos += 1) {
22109 this.registry[this.mylist[pos]].uncited = false;
22110 }
22111 for (pos = cited_len, len = this.mylist.length; pos < len; pos += 1) {
22112 this.registry[this.mylist[pos]].uncited = true;
22113 }
22114};
22115*/
22116
22117CSL.Registry.prototype.rebuildlist = function (nosort) {
22118 var len, pos, item, Item;
22119 //
22120 // 5. Create "new" list of hash pointers, in the order given in the argument
22121 // to the update function.
22122 //
22123 //
22124 // XXX Keep reflist in place.
22125 //
22126 if (!nosort) {
22127 this.reflist_inserts = [];
22128 //
22129 // 6. Apply citation numbers to new list,
22130 // saving off old sequence numbers as we go.
22131 //
22132 // XXX Just memo inserts -- actual insert happens below, at last "sort"
22133 //
22134 len = this.mylist.length;
22135 for (pos = 0; pos < len; pos += 1) {
22136 item = this.mylist[pos];
22137 Item = this.registry[item];
22138 if (Item.newItem) {
22139 this.reflist_inserts.push(Item);
22140 }
22141 this.oldseq[item] = this.registry[item].seq;
22142 this.registry[item].seq = (pos + 1);
22143 }
22144 } else {
22145 this.reflist = [];
22146 len = this.mylist.length;
22147 for (pos = 0; pos < len; pos += 1) {
22148 item = this.mylist[pos];
22149 Item = this.registry[item];
22150 this.reflist.push(Item);
22151 this.oldseq[item] = this.registry[item].seq;
22152 this.registry[item].seq = (pos + 1);
22153 }
22154 }
22155};
22156
22157/*
22158 * Okay, at this point we should have a numbered list
22159 * of registry tokens in the notional order requested,
22160 * with sequence numbers to reconstruct the ordering
22161 * if the list is remangled. So far so good.
22162 */
22163
22164CSL.Registry.prototype.dorefreshes = function () {
22165 var key, regtoken, Item, akey, abase;
22166 //
22167 // 7. Refresh items requiring update.
22168 //
22169 // It looks like we need to do four things on each cite for refresh:
22170 // (1) Generate the akey for the cite.
22171 // (2) Register it on the ambig token.
22172 // (3) Register the akey in this.akeys
22173 // (4) Register the item ID in this.touched
22174 //
22175 for (var key in this.refreshes) {
22176 regtoken = this.registry[key];
22177 if (!regtoken) {
22178 continue;
22179 }
22180 regtoken.sortkeys = undefined;
22181 Item = this.state.refetchItem(key);
22182 var akey = regtoken.ambig;
22183
22184 if ("undefined" === typeof akey) {
22185 this.state.tmp.disambig_settings = false;
22186 akey = CSL.getAmbiguousCite.call(this.state, Item);
22187 abase = CSL.getAmbigConfig.call(this.state);
22188 this.registerAmbigToken(akey, key, abase);
22189 }
22190 for (var akkey in this.ambigresets) {
22191 if (this.ambigresets[akkey] === 1) {
22192 var loneKey = this.ambigcites[akey][0];
22193 var Item = this.state.refetchItem(loneKey);
22194 this.registry[loneKey].disambig = new CSL.AmbigConfig();
22195 this.state.tmp.disambig_settings = false;
22196 var akey = CSL.getAmbiguousCite.call(this.state, Item);
22197 var abase = CSL.getAmbigConfig.call(this.state);
22198 this.registerAmbigToken(akey, loneKey, abase);
22199 }
22200 }
22201 this.state.tmp.taintedItemIDs[key] = true;
22202 this.ambigsTouched[akey] = true;
22203 if (!Item.legislation_id) {
22204 this.akeys[akey] = true;
22205 }
22206 this.touched[key] = true;
22207 }
22208};
22209
22210/*
22211 * Main disambiguation += -1 can everything for disambiguation be
22212 * crunched into this function?
22213 */
22214CSL.Registry.prototype.setdisambigs = function () {
22215 //
22216 // Okay, more changes. Here is where we resolve all disambiguation
22217 // issues for cites touched by the update. The this.ambigcites set is
22218 // based on the complete short form of citations, and is the basis on
22219 // which names are added and minimal adding of initials or given names
22220 // is performed.
22221 //
22222
22223 //
22224 // 8. Set disambiguation parameters on each inserted item token.
22225 //
22226 for (var akey in this.ambigsTouched) {
22227 //
22228 // Disambiguation is fully encapsulated.
22229 // Disambiguator will run only if there are multiple
22230 // items, and at least one disambiguation mode is
22231 // in effect.
22232 this.state.disambiguate.run(akey);
22233 }
22234 this.ambigsTouched = {};
22235 this.akeys = {};
22236};
22237
22238
22239
22240CSL.Registry.prototype.renumber = function () {
22241 var len, pos, item;
22242 //
22243 // 19. Reset citation numbers on list items
22244 //
22245 if (this.state.bibliography_sort.opt.citation_number_sort_direction === CSL.DESCENDING) {
22246 this.state.bibliography_sort.tmp.citation_number_map = {};
22247 }
22248 len = this.reflist.length;
22249 for (pos = 0; pos < len; pos += 1) {
22250 item = this.reflist[pos];
22251 // save the overhead of rerenderings if citation-number is not
22252 // used in the style.
22253 item.seq = (pos + 1);
22254 if (this.state.bibliography_sort.opt.citation_number_sort_direction === CSL.DESCENDING) {
22255 this.state.bibliography_sort.tmp.citation_number_map[item.seq] = (this.reflist.length - item.seq + 1);
22256 }
22257 // update_mode is set to CSL.NUMERIC if citation-number is rendered
22258 // in citations.
22259 if (this.state.opt.update_mode === CSL.NUMERIC && item.seq != this.oldseq[item.id]) {
22260 this.state.tmp.taintedItemIDs[item.id] = true;
22261 }
22262 if (item.seq != this.oldseq[item.id]) {
22263 this.return_data.bibchange = true;
22264 }
22265 }
22266};
22267
22268CSL.Registry.prototype.setsortkeys = function () {
22269 var key;
22270 //
22271 // 17. Set sort keys on each item token.
22272 //
22273 for (var i = 0, ilen = this.mylist.length; i < ilen; i += 1) {
22274 var key = this.mylist[i];
22275 // The last of these conditions may create some thrashing on styles that do not require sorting.
22276 if (this.touched[key] || this.state.tmp.taintedItemIDs[key] || !this.registry[key].sortkeys) {
22277 this.registry[key].sortkeys = CSL.getSortKeys.call(this.state, this.state.retrieveItem(key), "bibliography_sort");
22278 }
22279 }
22280};
22281
22282CSL.Registry.prototype._insertItem = function(element, array) {
22283 array.splice(this._locationOf(element, array) + 1, 0, element);
22284 return array;
22285};
22286
22287CSL.Registry.prototype._locationOf = function(element, array, start, end) {
22288 if (array.length === 0) {
22289 return -1;
22290 }
22291 start = start || 0;
22292 end = end || array.length;
22293 var pivot = (start + end) >> 1; // should be faster than dividing by 2
22294
22295 var c = this.sorter.compareKeys(element, array[pivot]);
22296 if (end - start <= 1) {
22297 return c == -1 ? pivot - 1 : pivot;
22298 }
22299 switch (c) {
22300 case -1: return this._locationOf(element, array, start, pivot);
22301 case 0: return pivot;
22302 case 1: return this._locationOf(element, array, pivot, end);
22303 }
22304};
22305
22306CSL.Registry.prototype.sorttokens = function (nosort) {
22307 var len, item, Item, pos;
22308 //
22309 // 18. Resort token list.
22310 //
22311 if (!nosort) {
22312 this.reflist_inserts = [];
22313 len = this.mylist.length;
22314 for (pos = 0; pos < len; pos += 1) {
22315 item = this.mylist[pos];
22316 Item = this.registry[item];
22317 if (Item.newItem) {
22318 this.reflist_inserts.push(Item);
22319 }
22320 }
22321 // There is a thin possibility that tainted items in a sorted list
22322 // will change position due to disambiguation. We cover for that here.
22323 for (var key in this.state.tmp.taintedItemIDs) {
22324 if (this.registry[key] && !this.registry[key].newItem) {
22325 // Move tainted items from reflist to reflist_inserts
22326 for (var i=this.reflist.length-1;i>-1;i--) {
22327 if (this.reflist[i].id === key) {
22328 this.reflist_inserts.push(this.reflist[i]);
22329 this.reflist = this.reflist.slice(0, i).concat(this.reflist.slice(i+1));
22330 }
22331 }
22332 }
22333 }
22334 for (var i=0,ilen=this.reflist_inserts.length;i<ilen;i++) {
22335 var Item = this.reflist_inserts[i];
22336 delete Item.newItem;
22337 this.reflist = this._insertItem(Item, this.reflist);
22338 }
22339 for (pos = 0; pos < len; pos += 1) {
22340 item = this.mylist[pos];
22341 Item = this.registry[item];
22342 this.registry[item].seq = (pos + 1);
22343 }
22344 }
22345};
22346
22347/**
22348 * Compare two sort keys
22349 * <p>Nested, because keys are an array.</p>
22350 */
22351CSL.Registry.Comparifier = function (state, keyset) {
22352 var sort_directions, len, pos, compareKeys;
22353 var sortCompare = CSL.getSortCompare(state.opt["default-locale-sort"]);
22354 sort_directions = state[keyset].opt.sort_directions;
22355 this.compareKeys = function (a, b) {
22356 len = a.sortkeys ? a.sortkeys.length : 0;
22357 for (pos = 0; pos < len; pos += 1) {
22358 //
22359 // for ascending sort 1 uses 1, -1 uses -1.
22360 // For descending sort, the values are reversed.
22361 //
22362 // Need to handle undefined values. No way around it.
22363 // So have to screen .localeCompare (which is also
22364 // needed) from undefined values. Everywhere, in all
22365 // compares.
22366 //
22367 var cmp = 0;
22368 if (a.sortkeys[pos] === b.sortkeys[pos]) {
22369 cmp = 0;
22370 } else if ("undefined" === typeof a.sortkeys[pos]) {
22371 cmp = sort_directions[pos][1];
22372 } else if ("undefined" === typeof b.sortkeys[pos]) {
22373 cmp = sort_directions[pos][0];
22374 } else {
22375 // cmp = a.sortkeys[pos].localeCompare(b.sortkeys[pos]);
22376 cmp = sortCompare(a.sortkeys[pos], b.sortkeys[pos]);
22377 }
22378 if (0 < cmp) {
22379 return sort_directions[pos][1];
22380 } else if (0 > cmp) {
22381 return sort_directions[pos][0];
22382 }
22383 }
22384 if (a.seq > b.seq) {
22385 return 1;
22386 } else if (a.seq < b.seq) {
22387 return -1;
22388 }
22389 return 0;
22390 };
22391 compareKeys = this.compareKeys;
22392 this.compareCompositeKeys = function (a, b) {
22393 return compareKeys(a[1], b[1]);
22394 };
22395};
22396
22397
22398/**
22399 * Compare two disambiguation tokens by their registry sort order
22400 * <p>Disambiguation lists need to be sorted this way, to
22401 * obtain the correct year-suffix when that's used.</p>
22402 */
22403CSL.Registry.prototype.compareRegistryTokens = function (a, b) {
22404 if (a.seq > b.seq) {
22405 return 1;
22406 } else if (a.seq < b.seq) {
22407 return -1;
22408 }
22409 return 0;
22410};
22411
22412CSL.Registry.prototype.registerAmbigToken = function (akey, id, ambig_config) {
22413 //SNIP-START
22414 if (!this.registry[id]) {
22415 CSL.debug("Warning: unregistered item: itemID=("+id+"), akey=("+akey+")");
22416 }
22417 //SNIP-END
22418 // Taint if number of names to be included has changed
22419 if (this.registry[id] && this.registry[id].disambig && this.registry[id].disambig.names) {
22420 for (var i = 0, ilen = ambig_config.names.length; i < ilen; i += 1) {
22421 var new_names_params = ambig_config.names[i];
22422 var old_names_params = this.registry[id].disambig.names[i];
22423 if (new_names_params !== old_names_params) {
22424 this.state.tmp.taintedItemIDs[id] = true;
22425 } else if (ambig_config.givens[i]) {
22426 // Compare givenses only if the number of names is aligned.
22427 for (var j=0,jlen=ambig_config.givens[i].length;j<jlen;j+=1) {
22428 var new_gnames_params = ambig_config.givens[i][j];
22429 var old_gnames_params = this.registry[id].disambig.givens[i][j];
22430 if (new_gnames_params !== old_gnames_params) {
22431 this.state.tmp.taintedItemIDs[id] = true;
22432 }
22433 }
22434 }
22435 }
22436 }
22437
22438 if (!this.ambigcites[akey]) {
22439 this.ambigcites[akey] = [];
22440 }
22441 if (this.ambigcites[akey].indexOf("" + id) === -1) {
22442 this.ambigcites[akey].push("" + id);
22443 }
22444 this.registry[id].ambig = akey;
22445 this.registry[id].disambig = CSL.cloneAmbigConfig(ambig_config);
22446};
22447
22448
22449/**
22450 * Get the sort key of an item, without decorations
22451 * <p>This is used internally by the Registry.</p>
22452 */
22453CSL.getSortKeys = function (Item, key_type) {
22454 var area, root, extension, strip_prepositions, len, pos;
22455 //SNIP-START
22456 if (false) {
22457 CSL.debug("KEY TYPE: " + key_type);
22458 }
22459 //SNIP-END
22460 area = this.tmp.area;
22461 root = this.tmp.root;
22462 extension = this.tmp.extension;
22463 strip_prepositions = CSL.Util.Sort.strip_prepositions;
22464 this.tmp.area = key_type;
22465 // Gawdawful, this.
22466 this.tmp.root = key_type.indexOf("_") > -1 ? key_type.slice(0,-5) : key_type;
22467 this.tmp.extension = "_sort";
22468 this.tmp.disambig_override = true;
22469 this.tmp.disambig_request = false;
22470 this.parallel.use_parallels = (this.parallel.use_parallels === true || this.parallel.use_parallels === null) ? null : false;
22471 this.tmp.suppress_decorations = true;
22472 CSL.getCite.call(this, Item);
22473 this.tmp.suppress_decorations = false;
22474 this.parallel.use_parallels = this.parallel.use_parallels === null ? true : false;
22475 this.tmp.disambig_override = false;
22476 len = this[key_type].keys.length;
22477 for (pos = 0; pos < len; pos += 1) {
22478 this[key_type].keys[pos] = strip_prepositions(this[key_type].keys[pos]);
22479 }
22480 //SNIP-START
22481 if (false) {
22482 CSL.debug("sort keys (" + key_type + "): " + this[key_type].keys);
22483 }
22484 //SNIP-END
22485
22486 this.tmp.area = area;
22487 this.tmp.root = root;
22488 this.tmp.extension = extension;
22489 return this[key_type].keys;
22490};
22491
22492
22493/*global CSL: true */
22494
22495CSL.Registry.NameReg = function (state) {
22496 var pkey, ikey, skey, dagopt, gdropt, items, strip_periods, set_keys, evalname, delitems, addname, myitems;
22497 this.state = state;
22498 this.namereg = {};
22499 this.nameind = {};
22500 // used for restoring state following preview
22501 this.nameindpkeys = {};
22502 //
22503 // family, initials form, fullname (with given stripped of periods)
22504 //
22505 // keys registered, indexed by ID
22506 this.itemkeyreg = {};
22507
22508 strip_periods = function (str) {
22509 if (!str) {
22510 str = "";
22511 }
22512 return str.replace(/\./g, " ").replace(/\s+/g, " ").replace(/\s+$/,"");
22513 };
22514
22515 set_keys = function (state, itemid, nameobj) {
22516 pkey = strip_periods(nameobj.family);
22517 skey = strip_periods(nameobj.given);
22518 // Drop lowercase suffixes (such as et al.) from given name field
22519 // for disambiguation purposes.
22520 var m = skey.match(/[,\!]* ([^,]+)$/);
22521 if (m && m[1] === m[1].toLowerCase()) {
22522 skey = skey.replace(/[,\!]* [^,]+$/, "");
22523 }
22524 // The %s terminator enables normal initialization behavior
22525 // with non-Byzantine names.
22526 ikey = CSL.Util.Names.initializeWith(state, skey, "%s");
22527 if (state.citation.opt["givenname-disambiguation-rule"] === "by-cite") {
22528 pkey = "" + itemid + pkey;
22529 }
22530 };
22531
22532 evalname = function (item_id, nameobj, namenum, request_base, form, initials) {
22533 var param;
22534 // XXX THIS CAN NO LONGER HAPPEN
22535 if (state.tmp.area.slice(0, 12) === "bibliography" && !form) {
22536 if ("string" === typeof initials) {
22537 return 1;
22538 } else {
22539 return 2;
22540 }
22541 }
22542 var res = state.nameOutput.getName(nameobj, "locale-translit", true);
22543 nameobj = res.name;
22544 set_keys(this.state, "" + item_id, nameobj);
22545 //
22546 // possible options are:
22547 //
22548 // <option disambiguate-add-givenname value="true"/> (a)
22549 // <option disambiguate-add-givenname value="all-names"/> (a)
22550 // <option disambiguate-add-givenname value="all-names-with-initials"/> (b)
22551 // <option disambiguate-add-givenname value="primary-name"/> (d)
22552 // <option disambiguate-add-givenname value="primary-name-with-initials"/> (e)
22553 // <option disambiguate-add-givenname value="by-cite"/> (g)
22554 //
22555 param = 2;
22556 dagopt = state.opt["disambiguate-add-givenname"];
22557 gdropt = state.citation.opt["givenname-disambiguation-rule"];
22558 var gdropt_orig = gdropt;
22559 if (gdropt === "by-cite") {
22560 gdropt = "all-names";
22561 }
22562 //
22563 // set initial value
22564 //
22565 if ("short" === form) {
22566 param = 0;
22567 } else if ("string" === typeof initials) {
22568 param = 1;
22569 }
22570 //
22571 // give literals a pass
22572 if ("undefined" === typeof this.namereg[pkey] || "undefined" === typeof this.namereg[pkey].ikey[ikey]) {
22573 return param;
22574 }
22575 //
22576 // adjust value upward if appropriate -- only if running
22577 // a non-names-global disambiguation strategy
22578 //
22579 if (gdropt_orig === "by-cite" && param <= request_base) {
22580 //param = request_base;
22581 return request_base;
22582 }
22583 if (!dagopt) {
22584 return param;
22585 }
22586 if ("string" === typeof gdropt && gdropt.slice(0, 12) === "primary-name" && namenum > 0) {
22587 return param;
22588 }
22589 //
22590 // the last composite condition is for backward compatibility
22591 //
22592 if (!gdropt || gdropt === "all-names" || gdropt === "primary-name") {
22593 if (this.namereg[pkey].count > 1) {
22594 param = 1;
22595 }
22596 if ((this.namereg[pkey].ikey
22597 && this.namereg[pkey].ikey[ikey].count > 1)
22598 || (this.namereg[pkey].count > 1
22599 && "string" !== typeof initials)) {
22600
22601 param = 2;
22602 }
22603 } else if (gdropt === "all-names-with-initials" || gdropt === "primary-name-with-initials") {
22604 if (this.namereg[pkey].count > 1) {
22605 param = 1;
22606 } else {
22607 param = 0;
22608 }
22609 }
22610 if (!state.registry.registry[item_id]) {
22611 if (form == "short") {
22612 return 0;
22613 } else if ("string" == typeof initials) {
22614 return 1;
22615 }
22616 } else {
22617 return param;
22618 }
22619 };
22620
22621 //
22622 // The operation of this function does not show up in the
22623 // standard test suite, but it has been hand-tested with
22624 // a print trace, and seems to work okay.
22625 //
22626 delitems = function (ids) {
22627 var pos, len, posB, id, fullkey;
22628 if ("string" === typeof ids || "number" === typeof ids) {
22629 ids = ["" + ids];
22630 }
22631 // ret carries the IDs of other items using this name.
22632 var ret = {};
22633 len = ids.length;
22634 for (pos = 0; pos < len; pos += 1) {
22635 id = "" + ids[pos];
22636 if (!this.nameind[id]) {
22637 continue;
22638 }
22639 for (fullkey in this.nameind[id]) {
22640 if (this.nameind[id].hasOwnProperty(fullkey)) {
22641 var key = fullkey.split("::");
22642 pkey = key[0];
22643 ikey = key[1];
22644 skey = key[2];
22645 // Skip names that have been deleted already.
22646 // Needed to clear integration DisambiguateAddGivenname1.txt
22647 // and integration DisambiguateAddGivenname2.txt
22648 if ("undefined" === typeof this.namereg[pkey]) {
22649 continue;
22650 }
22651
22652 // ????
22653 //posA = this.namereg[pkey].items.indexOf(posA);
22654
22655 items = this.namereg[pkey].items;
22656 // This was really, really unperceptive. They key elements
22657 // have absolutely nothing to do with whether there was ever
22658 // a registration at each key level.
22659 if (skey && this.namereg[pkey].ikey[ikey] && this.namereg[pkey].ikey[ikey].skey[skey]) {
22660 myitems = this.namereg[pkey].ikey[ikey].skey[skey].items;
22661 posB = myitems.indexOf("" + id);
22662 if (posB > -1) {
22663 this.namereg[pkey].ikey[ikey].skey[skey].items = myitems.slice(0, posB).concat(myitems.slice([(posB + 1)]));
22664 }
22665 if (this.namereg[pkey].ikey[ikey].skey[skey].items.length === 0) {
22666 delete this.namereg[pkey].ikey[ikey].skey[skey];
22667 this.namereg[pkey].ikey[ikey].count += -1;
22668 if (this.namereg[pkey].ikey[ikey].count < 2) {
22669 for (var i = 0, ilen = this.namereg[pkey].ikey[ikey].items.length; i < ilen; i += 1) {
22670 state.tmp.taintedItemIDs[this.namereg[pkey].ikey[ikey].items[i]] = true;
22671 }
22672 }
22673 }
22674 }
22675 if (ikey && this.namereg[pkey].ikey[ikey]) {
22676 posB = this.namereg[pkey].ikey[ikey].items.indexOf("" + id);
22677 if (posB > -1) {
22678 items = this.namereg[pkey].ikey[ikey].items.slice();
22679 this.namereg[pkey].ikey[ikey].items = items.slice(0, posB).concat(items.slice([posB + 1]));
22680 }
22681 if (this.namereg[pkey].ikey[ikey].items.length === 0) {
22682 delete this.namereg[pkey].ikey[ikey];
22683 this.namereg[pkey].count += -1;
22684 if (this.namereg[pkey].count < 2) {
22685 for (var i = 0, ilen = this.namereg[pkey].items.length; i < ilen; i += 1) {
22686 state.tmp.taintedItemIDs[this.namereg[pkey].items[i]] = true;
22687 }
22688 }
22689 }
22690 }
22691 if (pkey) {
22692 posB = this.namereg[pkey].items.indexOf("" + id);
22693 if (posB > -1) {
22694 items = this.namereg[pkey].items.slice();
22695 this.namereg[pkey].items = items.slice(0, posB).concat(items.slice([posB + 1], items.length));
22696 }
22697 if (this.namereg[pkey].items.length < 2) {
22698 delete this.namereg[pkey];
22699 }
22700 }
22701 delete this.nameind[id][fullkey];
22702 }
22703 }
22704 delete this.nameind[id];
22705 delete this.nameindpkeys[id];
22706 }
22707 return ret;
22708 };
22709 //
22710 // Run ALL
22711 // renderings with disambiguate-add-givenname set to a value
22712 // with the by-cite behaviour, and then set the names-based
22713 // expanded form when the final makeCitationCluster rendering
22714 // is output. This could be done with a single var set on
22715 // the state object in the execution wrappers that run the
22716 // style.
22717 //
22718 addname = function (item_id, nameobj, pos) {
22719 var i, ilen;
22720 var res = state.nameOutput.getName(nameobj, "locale-translit", true);
22721 nameobj = res.name;
22722
22723 if (state.citation.opt["givenname-disambiguation-rule"]
22724 && state.citation.opt["givenname-disambiguation-rule"].slice(0, 8) === "primary-"
22725 && pos !== 0) {
22726 return;
22727 }
22728 //CSL.debug("INS");
22729 set_keys(this.state, "" + item_id, nameobj);
22730 // pkey, ikey and skey should be stored in separate cascading objects.
22731 // there should also be a kkey, on each, which holds the item ids using
22732 // that form of the name.
22733 //
22734 // (later note: well, we seem to have slipped a notch here.
22735 // Adding lists of IDs all over the place here makes no sense;
22736 // the lists need to include _only_ the items currently rendered
22737 // at the given level, and the place to do that is in evalname,
22738 // and in delnames, not here.)
22739 if (pkey) {
22740 if ("undefined" === typeof this.namereg[pkey]) {
22741 this.namereg[pkey] = {};
22742 this.namereg[pkey].count = 0;
22743 this.namereg[pkey].ikey = {};
22744 this.namereg[pkey].items = [item_id];
22745 } else if (this.namereg[pkey].items.indexOf(item_id) === -1) {
22746 this.namereg[pkey].items.push(item_id);
22747 }
22748// if (this.namereg[pkey].items.indexOf(item_id) === -1) {
22749// this.namereg[pkey].items.push(item_id);
22750// }
22751 }
22752 if (pkey && ikey) {
22753 if ("undefined" === typeof this.namereg[pkey].ikey[ikey]) {
22754 this.namereg[pkey].ikey[ikey] = {};
22755 this.namereg[pkey].ikey[ikey].count = 0;
22756 this.namereg[pkey].ikey[ikey].skey = {};
22757 this.namereg[pkey].ikey[ikey].items = [item_id];
22758 this.namereg[pkey].count += 1;
22759 if (this.namereg[pkey].count === 2) {
22760 for (var i = 0, ilen = this.namereg[pkey].items.length; i < ilen; i += 1) {
22761 state.tmp.taintedItemIDs[this.namereg[pkey].items[i]] = true;
22762 }
22763 }
22764 } else if (this.namereg[pkey].ikey[ikey].items.indexOf(item_id) === -1) {
22765 this.namereg[pkey].ikey[ikey].items.push(item_id);
22766 }
22767// if (this.namereg[pkey].ikey[ikey].items.indexOf(item_id) === -1) {
22768// this.namereg[pkey].ikey[ikey].items.push(item_id);
22769// }
22770 }
22771 if (pkey && ikey && skey) {
22772 if ("undefined" === typeof this.namereg[pkey].ikey[ikey].skey[skey]) {
22773 this.namereg[pkey].ikey[ikey].skey[skey] = {};
22774 this.namereg[pkey].ikey[ikey].skey[skey].items = [item_id];
22775 this.namereg[pkey].ikey[ikey].count += 1;
22776 if (this.namereg[pkey].ikey[ikey].count === 2) {
22777 for (var i = 0, ilen = this.namereg[pkey].ikey[ikey].items.length; i < ilen; i += 1) {
22778 state.tmp.taintedItemIDs[this.namereg[pkey].ikey[ikey].items[i]] = true;
22779 }
22780 }
22781 } else if (this.namereg[pkey].ikey[ikey].skey[skey].items.indexOf(item_id) === -1) {
22782 this.namereg[pkey].ikey[ikey].skey[skey].items.push(item_id);
22783 }
22784// if (this.namereg[pkey].ikey[ikey].skey[skey].items.indexOf(item_id) === -1) {
22785// this.namereg[pkey].ikey[ikey].skey[skey].items.push(item_id);
22786// }
22787 }
22788 if ("undefined" === typeof this.nameind[item_id]) {
22789 this.nameind[item_id] = {};
22790 this.nameindpkeys[item_id] = {};
22791 }
22792 //CSL.debug("INS-A: [" + pkey + "] [" + ikey + "] [" + skey + "]");
22793 if (pkey) {
22794 this.nameind[item_id][pkey + "::" + ikey + "::" + skey] = true;
22795 this.nameindpkeys[item_id][pkey] = this.namereg[pkey];
22796 }
22797 //CSL.debug("INS-B");
22798 };
22799 this.addname = addname;
22800 this.delitems = delitems;
22801 this.evalname = evalname;
22802};
22803
22804/*global CSL: true */
22805
22806CSL.Registry.CitationReg = function () {
22807 this.citationById = {};
22808 this.citationByIndex = [];
22809};
22810
22811/*global CSL: true */
22812
22813CSL.Disambiguation = function (state) {
22814 this.state = state;
22815 this.sys = this.state.sys;
22816 this.registry = state.registry.registry;
22817 this.ambigcites = state.registry.ambigcites;
22818 this.configModes();
22819 this.debug = false;
22820};
22821
22822CSL.Disambiguation.prototype.run = function(akey) {
22823 if (!this.modes.length) {
22824 return;
22825 }
22826 //SNIP-START
22827 if (this.debug) {
22828 print("[A] === RUN ===");
22829 }
22830 //SNIP-END
22831 this.akey = akey;
22832 if (this.initVars(akey)) {
22833 this.runDisambig();
22834 }
22835
22836};
22837
22838CSL.Disambiguation.prototype.runDisambig = function () {
22839 var ismax;
22840 //SNIP-START
22841 if (this.debug) {
22842 print("[C] === runDisambig() ===");
22843 }
22844 //SNIP-END
22845 this.initGivens = true;
22846 //
22847 // Length of list may change during processing
22848 while (this.lists.length) {
22849 this.gnameset = 0;
22850 this.gname = 0;
22851 this.clashes = [1, 0];
22852 // each list is scanned repeatedly until all
22853 // items either succeed or ultimately fail.
22854 while(this.lists[0][1].length) {
22855 this.listpos = 0;
22856 if (!this.base) {
22857 this.base = this.lists[0][0];
22858 }
22859 ismax = this.incrementDisambig();
22860 this.scanItems(this.lists[0]);
22861 this.evalScan(ismax);
22862 }
22863 this.lists = this.lists.slice(1);
22864 }
22865};
22866
22867CSL.Disambiguation.prototype.scanItems = function (list) {
22868 var pos, len, otherItem;
22869 //SNIP-START
22870 if (this.debug) {
22871 print("[2] === scanItems() ===");
22872 }
22873 //SNIP-END
22874
22875 this.Item = list[1][0];
22876 this.ItemCite = CSL.getAmbiguousCite.call(this.state, this.Item, this.base, true);
22877
22878 this.scanlist = list[1];
22879 this.partners = [];
22880 this.partners.push(this.Item);
22881 this.nonpartners = [];
22882 var clashes = 0;
22883
22884 for (var pos = 1, len = list[1].length; pos < len; pos += 1) {
22885 otherItem = list[1][pos];
22886 var otherItemCite = CSL.getAmbiguousCite.call(this.state, otherItem, this.base, true);
22887 //SNIP-START
22888 if (this.debug) {
22889 if (pos > 1) {
22890 print(" -----------");
22891 }
22892 }
22893 //SNIP-END
22894 if (this.ItemCite === otherItemCite) {
22895 //SNIP-START
22896 if (this.debug) {
22897 print(" [CLASH]--> "+this.Item.id+": "+this.ItemCite);
22898 print(" "+otherItem.id+": "+otherItemCite);
22899 }
22900 //SNIP-END
22901 clashes += 1;
22902 this.partners.push(otherItem);
22903 } else {
22904 //SNIP-START
22905 if (this.debug) {
22906 print(" [clear]--> "+this.Item.id+": "+this.ItemCite);
22907 print(" "+otherItem.id+": "+otherItemCite);
22908 }
22909 //SNIP-END
22910 this.nonpartners.push(otherItem);
22911 }
22912 }
22913 this.clashes[0] = this.clashes[1];
22914 this.clashes[1] = clashes;
22915};
22916
22917CSL.Disambiguation.prototype.evalScan = function (maxed) {
22918 this[this.modes[this.modeindex]](maxed);
22919 if (maxed) {
22920 if (this.modeindex < this.modes.length - 1) {
22921 this.modeindex += 1;
22922 } else {
22923 this.lists[this.listpos + 1] = [this.base, []];
22924 }
22925 }
22926};
22927
22928CSL.Disambiguation.prototype.disNames = function (ismax) {
22929 var i, ilen;
22930
22931 //SNIP-START
22932 if (this.debug) {
22933 print("[3] == disNames() ==");
22934 //print(" partners: "+[this.partners[i].id for (i in this.partners)].join(", "));
22935 //print(" nonpartners: "+[this.nonpartners[i].id for (i in this.nonpartners)].join(", "));
22936 }
22937 //SNIP-END
22938
22939 // New design
22940 // this.base is a forward-only counter. Values are never
22941 // reduced, and the counter object is never overwritten.
22942 // It is methodically pushed forward in single-unit increments
22943 // in incrementDisambig() until disNames() wipes out the list.
22944
22945 // this.betterbase is cloned from this.base exactly once,
22946 // at the start of a disambiguation run. Whenever an operation
22947 // results in improvement, the just-incremented elements
22948 // identified as this.base.names[this.gnameset] (number of
22949 // names)and as this.base.givens[this.gnameset][this.gname]
22950 // (level of given name) are copied from this.base.
22951
22952 // The this.base object is used to control disambiguation
22953 // renderings. These will be more fully expanded than the final
22954 // text, but the flip side of the fact that the extra data does
22955 // not contribute anything to disambiguation is that leaving
22956 // it in does no harm -- think of it as the Cold Dark Matter of
22957 // disambiguation.
22958
22959 if (this.clashes[1] === 0 && this.nonpartners.length === 1) {
22960 this.captureStepToBase();
22961 //SNIP-START
22962 if (this.debug) {
22963 print(" ** RESOLUTION [a]: lone partner, one nonpartner");
22964 print(" registering "+this.partners[0].id+" and "+this.nonpartners[0].id);
22965 }
22966 //SNIP-END
22967 this.state.registry.registerAmbigToken(this.akey, "" + this.nonpartners[0].id, this.betterbase);
22968 this.state.registry.registerAmbigToken(this.akey, "" + this.partners[0].id, this.betterbase);
22969 this.lists[this.listpos] = [this.betterbase, []];
22970 } else if (this.clashes[1] === 0) {
22971 this.captureStepToBase();
22972 //SNIP-START
22973 if (this.debug) {
22974 print(" ** RESOLUTION [b]: lone partner, unknown number of remaining nonpartners");
22975 print(" registering "+this.partners[0].id);
22976 }
22977 //SNIP-END
22978 this.state.registry.registerAmbigToken(this.akey, "" + this.partners[0].id, this.betterbase);
22979 this.lists[this.listpos] = [this.betterbase, this.nonpartners];
22980 if (this.nonpartners.length) {
22981 this.initGivens = true;
22982 }
22983 } else if (this.nonpartners.length === 1) {
22984 this.captureStepToBase();
22985 //SNIP-START
22986 if (this.debug) {
22987 print(" ** RESOLUTION [c]: lone nonpartner, unknown number of partners remaining");
22988 print(" registering "+this.nonpartners[0].id);
22989 }
22990 //SNIP-END
22991 this.state.registry.registerAmbigToken(this.akey, "" + this.nonpartners[0].id, this.betterbase);
22992 //this.lists[this.listpos] = [this.betterbase, this.partners];
22993 this.lists[this.listpos] = [this.betterbase, this.partners];
22994 } else if (this.clashes[1] < this.clashes[0]) {
22995 this.captureStepToBase();
22996 //SNIP-START
22997 if (this.debug) {
22998 print(" ** RESOLUTION [d]: better result, but no entries safe to register");
22999 }
23000 //SNIP-END
23001 this.lists[this.listpos] = [this.betterbase, this.partners];
23002 this.lists.push([this.betterbase, this.nonpartners]);
23003 } else {
23004 //SNIP-START
23005 if (this.debug) {
23006 print(" ** RESOLUTION [e]: no improvement, and clashes remain");
23007 }
23008 //SNIP-END
23009 if (ismax) {
23010 this.lists[this.listpos] = [this.betterbase, this.nonpartners];
23011 this.lists.push([this.betterbase, this.partners]);
23012 if (this.modeindex === this.modes.length - 1) {
23013 //SNIP-START
23014 if (this.debug) {
23015 print(" (registering clashing entries because we've run out of options)");
23016 }
23017 //SNIP-END
23018 for (var i = 0, ilen = this.partners.length; i < ilen; i += 1) {
23019 this.state.registry.registerAmbigToken(this.akey, "" + this.partners[i].id, this.betterbase);
23020 }
23021 this.lists[this.listpos] = [this.betterbase, []];
23022 }
23023 }
23024 }
23025};
23026
23027CSL.Disambiguation.prototype.disExtraText = function () {
23028 //SNIP-START
23029 if (this.debug) {
23030 print("[3] === disExtraText ==");
23031 }
23032 //SNIP-END
23033
23034 var done = false;
23035
23036 if (this.clashes[1] === 0 && this.nonpartners.length < 2) {
23037 done = true;
23038 }
23039
23040 // If first encounter in this cycle and multiple modes are
23041 // available, decrement mode and reset base
23042 if (!done && (!this.base.disambiguate || this.state.tmp.disambiguate_count !== this.state.tmp.disambiguate_maxMax)) {
23043 // Rerun everything on each subcycle? This doesn't work currently.
23044 //this.initVars(this.akey)
23045 this.modeindex = 0;
23046 this.base.disambiguate = this.state.tmp.disambiguate_count;
23047 this.betterbase.disambiguate = this.state.tmp.disambiguate_count;
23048 if (!this.base.disambiguate) {
23049 // Evaluate here?
23050 this.initGivens = true;
23051 // If disambiguate is false set to true
23052 this.base.disambiguate = 1;
23053 // There may be changes
23054 for (var i = 0, ilen = this.lists[this.listpos][1].length; i < ilen; i += 1) {
23055 this.state.tmp.taintedItemIDs[this.lists[this.listpos][1][i].id] = true;
23056 }
23057 } else {
23058 this.disNames();
23059 }
23060 } else if (done || this.state.tmp.disambiguate_count === this.state.tmp.disambiguate_maxMax) {
23061 if (done || this.modeindex === this.modes.length - 1) {
23062 // If this is the end, disambiguation failed.
23063 // Discard disambiguate=true (?) and set parameters
23064 var base = this.lists[this.listpos][0];
23065 for (var i = 0, ilen = this.lists[this.listpos][1].length; i < ilen; i += 1) {
23066 this.state.tmp.taintedItemIDs[this.lists[this.listpos][1][i].id] = true;
23067 this.state.registry.registerAmbigToken(this.akey, "" + this.lists[this.listpos][1][i].id, base);
23068 }
23069 this.lists[this.listpos] = [this.betterbase, []];
23070 } else {
23071 // If this is followed by year-suffix, keep
23072 // parameters and set disambiguate=true since it MIGHT
23073 // include the date, needed for year-suffix.
23074 // This may be a bit over-aggressive for cases in which the
23075 // disambiguate condition does not add the date
23076 this.modeindex = this.modes.length - 1;
23077 var base = this.lists[this.listpos][0];
23078 base.disambiguate = true;
23079 for (var i = 0, ilen = this.lists[this.listpos][1].length; i < ilen; i += 1) {
23080 // Always tainting here might be a little over-aggressive, but a taint may be required.
23081 this.state.tmp.taintedItemIDs[this.lists[this.listpos][1][i].id] = true;
23082 this.state.registry.registerAmbigToken(this.akey, "" + this.lists[this.listpos][1][i].id, base);
23083 }
23084 }
23085 }
23086};
23087
23088CSL.Disambiguation.prototype.disYears = function () {
23089 var pos, len, tokens, token;
23090 //SNIP-START
23091 if (this.debug) {
23092 print("[3] === disYears ==");
23093 }
23094 //SNIP-END
23095 tokens = [];
23096 var base = this.lists[this.listpos][0];
23097 if (this.clashes[1]) {
23098 // That is, if the initial increment on the ambigs group returns no
23099 // clashes, don't apply suffix. The condition is a necessary failsafe.
23100 // In original submission order
23101 for (var i = 0, ilen = this.state.registry.mylist.length; i < ilen; i += 1) {
23102 var origid = this.state.registry.mylist[i];
23103 for (var j = 0, jlen = this.lists[this.listpos][1].length; j < jlen; j += 1) {
23104 var token = this.lists[this.listpos][1][j];
23105 // Warning: token.id can be number. This should be fixed at a higher level in citeproc-js if poss.
23106 if (token.id == origid) {
23107 tokens.push(this.registry[token.id]);
23108 break;
23109 }
23110 }
23111 }
23112 }
23113 tokens.sort(this.state.registry.sorter.compareKeys);
23114 for (var pos = 0, len = tokens.length; pos < len; pos += 1) {
23115 base.year_suffix = ""+pos;
23116 var oldBase = this.state.registry.registry[tokens[pos].id].disambig;
23117 this.state.registry.registerAmbigToken(this.akey, "" + tokens[pos].id, base);
23118 if (CSL.ambigConfigDiff(oldBase,base)) {
23119 this.state.tmp.taintedItemIDs[tokens[pos].id] = true;
23120 }
23121 }
23122 this.lists[this.listpos] = [this.betterbase, []];
23123};
23124
23125CSL.Disambiguation.prototype.incrementDisambig = function () {
23126 //SNIP-START
23127 if (this.debug) {
23128 print("\n[1] === incrementDisambig() ===");
23129 }
23130 //SNIP-END
23131 if (this.initGivens) {
23132 this.initGivens = false;
23133 return false;
23134 }
23135 var maxed = false;
23136 var increment_names = true;
23137 if ("disNames" === this.modes[this.modeindex]) {
23138 // this.gnameset: the index pos of the current nameset
23139 // this.gname: the index pos of the current name w/in the current nameset
23140
23141 // Stages:
23142 // - Increment givenname (optional)
23143 // - Add a name (optional)
23144 // - Move to next nameset
23145
23146 // Incrementing is done forward-only on this.base. Values
23147 // that improve disambiguation results are copied to
23148 // this.betterbase, which is used to set the disambig parameters
23149 // in the processor registry.
23150
23151
23152 // Increment
23153 // Max val is always true if a level is inactive.
23154 increment_names = false;
23155 if ("number" !== typeof this.givensMax) {
23156 increment_names = true;
23157 }
23158 var increment_namesets = false;
23159 if ("number" !== typeof this.namesMax) {
23160 increment_namesets = true;
23161 }
23162 if ("number" === typeof this.givensMax) {
23163 if (this.base.givens.length && this.base.givens[this.gnameset][this.gname] < this.givensMax) {
23164 this.base.givens[this.gnameset][this.gname] += 1;
23165 } else {
23166 increment_names = true;
23167 }
23168 }
23169 if ("number" === typeof this.namesMax
23170 && increment_names) {
23171 if (this.state.opt["disambiguate-add-names"]) {
23172 increment_namesets = false;
23173 if (this.gname < this.namesMax) {
23174 this.base.names[this.gnameset] += 1;
23175 this.gname += 1;
23176 } else {
23177 increment_namesets = true;
23178 }
23179 } else {
23180 increment_namesets = true;
23181 }
23182 }
23183 if ("number" === typeof this.namesetsMax && increment_namesets) {
23184 if (this.gnameset < this.namesetsMax) {
23185 this.gnameset += 1;
23186 this.base.names[this.gnameset] = 1;
23187 this.gname = 0;
23188 }
23189 }
23190 //SNIP-START
23191 if (this.debug) {
23192 print(" ------------------");
23193 print(" incremented values");
23194 print(" ------------------");
23195 print(" | gnameset: "+this.gnameset);
23196 print(" | gname: "+this.gname);
23197 print(" | names value: "+this.base.names[this.gnameset]);
23198 if (this.base.givens.length) {
23199 print(" | givens value: "+this.base.givens[this.gnameset][this.gname]);
23200 } else {
23201 print(" | givens value: nil");
23202 }
23203 print(" | namesetsMax: "+this.namesetsMax);
23204 print(" | namesMax: "+this.namesMax);
23205 print(" | givensMax: "+this.givensMax);
23206 }
23207 //SNIP-END
23208 if (("number" !== typeof this.namesetsMax || this.namesetsMax === -1 || this.gnameset === this.namesetsMax)
23209 && (!this.state.opt["disambiguate-add-names"] || "number" !== typeof this.namesMax || this.gname === this.namesMax)
23210 && ("number" != typeof this.givensMax || "undefined" === typeof this.base.givens[this.gnameset] || "undefined" === typeof this.base.givens[this.gnameset][this.gname] || this.base.givens[this.gnameset][this.gname] === this.givensMax)) {
23211
23212
23213 maxed = true;
23214 //SNIP-START
23215 if (this.debug) {
23216 print(" MAXED");
23217 }
23218 //SNIP-END
23219 }
23220 } else if ("disExtraText" === this.modes[this.modeindex]) {
23221 this.base.disambiguate += 1;
23222 this.betterbase.disambiguate += 1;
23223 }
23224 return maxed;
23225};
23226
23227CSL.Disambiguation.prototype.initVars = function (akey) {
23228 var i, ilen, myIds, myItemBundles, myItems;
23229 //SNIP-START
23230 if (this.debug) {
23231 print("[B] === initVars() ===");
23232 }
23233 //SNIP-END
23234 this.lists = [];
23235 this.base = false;
23236 this.betterbase = false;
23237 this.akey = akey;
23238
23239 this.maxNamesByItemId = {};
23240
23241
23242 myItemBundles = [];
23243 myIds = this.ambigcites[akey];
23244 if (!myIds || !myIds.length) {
23245 return false;
23246 }
23247 var myItem = this.state.refetchItem("" + myIds[0]);
23248 this.getCiteData(myItem);
23249 this.base = CSL.getAmbigConfig.call(this.state);
23250 if (myIds && myIds.length > 1) {
23251 myItemBundles.push([this.maxNamesByItemId[myItem.id], myItem]);
23252 // Build a composite list of Items and associated
23253 // max names. This is messy, but it's the only
23254 // way to get the items sorted by the number of names
23255 // to be disambiguated. If they are in descending order
23256 // with name expansions, the processor will hang.
23257 for (var i = 1, ilen = myIds.length; i < ilen; i += 1) {
23258 myItem = this.state.refetchItem("" + myIds[i]);
23259 this.getCiteData(myItem, this.base);
23260 myItemBundles.push([this.maxNamesByItemId[myItem.id], myItem]);
23261 }
23262 myItemBundles.sort(
23263 function (a, b) {
23264 if (a[0] > b[0]) {
23265 return 1;
23266 } else if (a[0] < b[0]) {
23267 return -1;
23268 } else {
23269 if (a[1].id > b[1].id) {
23270 return 1;
23271 } else if (a[1].id < b[1].id) {
23272 return -1;
23273 } else {
23274 return 0;
23275 }
23276 }
23277 }
23278 );
23279 myItems = [];
23280 for (var i = 0, ilen = myItemBundles.length; i < ilen; i += 1) {
23281 myItems.push(myItemBundles[i][1]);
23282 }
23283 this.lists.push([this.base, myItems]);
23284 this.Item = this.lists[0][1][0];
23285 } else {
23286 this.Item = this.state.refetchItem("" + myIds[0]);
23287 }
23288
23289 this.modeindex = 0;
23290 if (this.state.citation.opt["disambiguate-add-names"] || true) {
23291 this.namesMax = this.maxNamesByItemId[this.Item.id][0];
23292 } else {
23293 var namesMax = this.base.names[0];
23294 for (var i=1,ilen=this.base.names.length;i<ilen;i+=1){
23295 namesMax = Math.max(namesMax,this.base.names.names[i]);
23296 }
23297 }
23298
23299
23300 this.padBase(this.base);
23301 this.padBase(this.betterbase);
23302 this.base.year_suffix = false;
23303 this.base.disambiguate = false;
23304 this.betterbase.year_suffix = false;
23305 this.betterbase.disambiguate = false;
23306 if (this.state.citation.opt["givenname-disambiguation-rule"] === "by-cite"
23307 && this.state.opt["disambiguate-add-givenname"]) {
23308 this.givensMax = 2;
23309 }
23310 return true;
23311};
23312
23313
23314CSL.Disambiguation.prototype.padBase = function (base) {
23315 for (var i = 0, ilen = base.names.length; i < ilen; i += 1) {
23316 if (!base.givens[i]) {
23317 base.givens[i] = [];
23318 }
23319 for (var j=0,jlen=base.names[i];j<jlen;j+=1) {
23320 if (!base.givens[i][j]) {
23321 base.givens[i][j] = 0;
23322 }
23323 }
23324 }
23325};
23326
23327/**
23328 * Set available modes for disambiguation
23329 */
23330CSL.Disambiguation.prototype.configModes = function () {
23331 var dagopt, gdropt;
23332 // Modes are function names prototyped to this instance.
23333 this.modes = [];
23334 dagopt = this.state.opt["disambiguate-add-givenname"];
23335 gdropt = this.state.citation.opt["givenname-disambiguation-rule"];
23336 if (this.state.opt['disambiguate-add-names'] || (dagopt && gdropt === "by-cite")) {
23337 this.modes.push("disNames");
23338 }
23339
23340 if (this.state.opt.development_extensions.prioritize_disambiguate_condition) {
23341 if (this.state.opt.has_disambiguate) {
23342 this.modes.push("disExtraText");
23343 }
23344 if (this.state.opt["disambiguate-add-year-suffix"]) {
23345 this.modes.push("disYears");
23346 }
23347 } else {
23348 if (this.state.opt["disambiguate-add-year-suffix"]) {
23349 this.modes.push("disYears");
23350 }
23351 if (this.state.opt.has_disambiguate) {
23352 this.modes.push("disExtraText");
23353 }
23354 }
23355};
23356
23357CSL.Disambiguation.prototype.getCiteData = function(Item, base) {
23358 // Initialize base if first set item seen
23359 if (!this.maxNamesByItemId[Item.id]) {
23360 CSL.getAmbiguousCite.call(this.state, Item, base);
23361 base = CSL.getAmbigConfig.call(this.state);
23362 this.maxNamesByItemId[Item.id] = CSL.getMaxVals.call(this.state);
23363 this.state.registry.registry[Item.id].disambig.givens = this.state.tmp.disambig_settings.givens.slice();
23364 // Slice the nested lists as well. Without this, disambiguate_YearSuffixFiftyTwoEntriesByCite fails.
23365 for (var i=0,ilen=this.state.registry.registry[Item.id].disambig.givens.length;i<ilen;i+=1) {
23366 this.state.registry.registry[Item.id].disambig.givens[i] = this.state.tmp.disambig_settings.givens[i].slice();
23367 }
23368 this.namesetsMax = this.state.registry.registry[Item.id].disambig.names.length - 1;
23369 if (!this.base) {
23370 this.base = base;
23371 this.betterbase = CSL.cloneAmbigConfig(base);
23372 }
23373 if (base.names.length < this.base.names.length) {
23374 // I don't know what would happen with discrepancies in the number
23375 // of namesets rendered on items, so we use the fewer of the two
23376 // and limit the other to that size.
23377 this.base = base;
23378 }
23379 // Padding. Within namesets, we use the longer of the two throughout.
23380 for (var i = 0, ilen = base.names.length; i < ilen; i += 1) {
23381 if (base.names[i] > this.base.names[i]) {
23382 // XXX The old must have been wrong surely. The new, I'm not sure.
23383 //this.base.givens[i] = this.base.givens[i].concat(this.base.givens[i].slice(this.base.names[i]));
23384 this.base.givens[i] = base.givens[i].slice();
23385 this.base.names[i] = base.names[i];
23386 this.betterbase.names = this.base.names.slice();
23387 this.betterbase.givens = this.base.givens.slice();
23388 this.padBase(this.base);
23389 this.padBase(this.betterbase);
23390 }
23391 }
23392 // This shouldn't be necessary
23393 // getAmbiguousCite() should return a valid and complete
23394 // givens segment under all conditions, but it does not
23395 // do so for institution authors, so we clean up after it
23396 // here.
23397 // Relevant test: sort_ChicagoYearSuffix2
23398 this.betterbase.givens = this.base.givens.slice();
23399 for (var j = 0, jlen = this.base.givens.length; j < jlen; j += 1) {
23400 this.betterbase.givens[j] = this.base.givens[j].slice();
23401 }
23402 }
23403};
23404
23405CSL.Disambiguation.prototype.captureStepToBase = function() {
23406 // Be paranoid about the presence of givens
23407 if (this.state.citation.opt["givenname-disambiguation-rule"] === "by-cite"
23408 && this.base.givens && this.base.givens.length) {
23409 if ("undefined" !== typeof this.base.givens[this.gnameset][this.gname]) {
23410 if (this.betterbase.givens.length < this.base.givens.length) {
23411 this.betterbase.givens = JSON.parse(JSON.stringify(this.base.givens));
23412 }
23413 this.betterbase.givens[this.gnameset][this.gname] = this.base.givens[this.gnameset][this.gname];
23414 }
23415 }
23416 this.betterbase.names[this.gnameset] = this.base.names[this.gnameset];
23417};
23418
23419CSL.Engine.prototype.getJurisdictionList = function (jurisdiction) {
23420 var jurisdictionList = [];
23421 var jurisdictionElems = jurisdiction.split(":");
23422 for (var j=jurisdictionElems.length;j>0;j--) {
23423 jurisdictionList.push(jurisdictionElems.slice(0,j).join(":"));
23424 }
23425 if (jurisdictionList.indexOf("us") === -1) {
23426 jurisdictionList.push("us");
23427 }
23428 return jurisdictionList;
23429};
23430
23431CSL.Engine.prototype.retrieveAllStyleModules = function (jurisdictionList) {
23432 var ret = {};
23433 var preferences = this.locale[this.opt.lang].opts["jurisdiction-preference"];
23434 preferences = preferences ? preferences : [];
23435 preferences = [""].concat(preferences);
23436 for (var i=preferences.length-1;i>-1;i--) {
23437 var preference = preferences[i];
23438 for (var j=0,jlen=jurisdictionList.length;j<jlen;j++) {
23439 var jurisdiction = jurisdictionList[j];
23440 // If we've "seen" it, we have it already, or we're not going to get it.
23441 if (this.opt.jurisdictions_seen[jurisdiction]) {
23442 continue;
23443 }
23444 // Try to get the module
23445 var res = this.sys.retrieveStyleModule(jurisdiction, preference);
23446 // If we fail and we've run out of preferences, mark as "seen"
23447 // Otherwise mark as "seen" if we get something.
23448 if ((!res && !preference) || res) {
23449 this.opt.jurisdictions_seen[jurisdiction] = true;
23450 }
23451 // Don't memo unless get got style code.
23452 if (!res) {
23453 continue;
23454 }
23455 ret[jurisdiction] = res;
23456 }
23457 }
23458 // Give 'em what we got.
23459 return ret;
23460};
23461
23462CSL.ParticleList = (function() {
23463 var always_dropping_1 = [[[0,1], null]];
23464 var always_dropping_3 = [[[0,3], null]];
23465 var always_non_dropping_1 = [[null, [0,1]]];
23466 var always_non_dropping_2 = [[null, [0,2]]];
23467 var always_non_dropping_3 = [[null, [0,3]]];
23468 var either_1 = [[null, [0,1]],[[0,1],null]];
23469 var either_2 = [[null, [0,2]],[[0,2],null]];
23470 var either_1_dropping_best = [[[0,1],null],[null, [0,1]]];
23471 var either_2_dropping_best = [[[0,2],null],[null, [0,2]]];
23472 var either_3_dropping_best = [[[0,3],null],[null, [0,3]]];
23473 var non_dropping_2_alt_dropping_1_non_dropping_1 = [[null, [0,2]], [[0,1], [1,2]]];
23474 var PARTICLES = [
23475 ["'s", always_non_dropping_1],
23476 ["'s-", always_non_dropping_1],
23477 ["'t", always_non_dropping_1],
23478 ["a", always_non_dropping_1],
23479 ["aan 't", always_non_dropping_2],
23480 ["aan de", always_non_dropping_2],
23481 ["aan den", always_non_dropping_2],
23482 ["aan der", always_non_dropping_2],
23483 ["aan het", always_non_dropping_2],
23484 ["aan t", always_non_dropping_2],
23485 ["aan", always_non_dropping_1],
23486 ["ad-", either_1],
23487 ["adh-", either_1],
23488 ["af", either_1],
23489 ["al", either_1],
23490 ["al-", either_1],
23491 ["am de", always_non_dropping_2],
23492 ["am", always_non_dropping_1],
23493 ["an-", either_1],
23494 ["ar-", either_1],
23495 ["as-", either_1],
23496 ["ash-", either_1],
23497 ["at-", either_1],
23498 ["ath-", either_1],
23499 ["auf dem", either_2_dropping_best],
23500 ["auf den", either_2_dropping_best],
23501 ["auf der", either_2_dropping_best],
23502 ["auf ter", always_non_dropping_2],
23503 ["auf", either_1_dropping_best],
23504 ["aus 'm", either_2_dropping_best],
23505 ["aus dem", either_2_dropping_best],
23506 ["aus den", either_2_dropping_best],
23507 ["aus der", either_2_dropping_best],
23508 ["aus m", either_2_dropping_best],
23509 ["aus", either_1_dropping_best],
23510 ["aus'm", either_2_dropping_best],
23511 ["az-", either_1],
23512 ["aš-", either_1],
23513 ["aḍ-", either_1],
23514 ["aḏ-", either_1],
23515 ["aṣ-", either_1],
23516 ["aṭ-", either_1],
23517 ["aṯ-", either_1],
23518 ["aẓ-", either_1],
23519 ["ben", always_non_dropping_1],
23520 ["bij 't", always_non_dropping_2],
23521 ["bij de", always_non_dropping_2],
23522 ["bij den", always_non_dropping_2],
23523 ["bij het", always_non_dropping_2],
23524 ["bij t", always_non_dropping_2],
23525 ["bij", always_non_dropping_1],
23526 ["bin", always_non_dropping_1],
23527 ["boven d", always_non_dropping_2],
23528 ["boven d'", always_non_dropping_2],
23529 ["d", always_non_dropping_1],
23530 ["d'", either_1],
23531 ["da", either_1],
23532 ["dal", always_non_dropping_1],
23533 ["dal'", always_non_dropping_1],
23534 ["dall'", always_non_dropping_1],
23535 ["dalla", always_non_dropping_1],
23536 ["das", either_1],
23537 ["de die le", always_non_dropping_3],
23538 ["de die", always_non_dropping_2],
23539 ["de l", always_non_dropping_2],
23540 ["de l'", always_non_dropping_2],
23541 ["de la", non_dropping_2_alt_dropping_1_non_dropping_1],
23542 ["de las", non_dropping_2_alt_dropping_1_non_dropping_1],
23543 ["de le", always_non_dropping_2],
23544 ["de li", either_2],
23545 ["de van der", always_non_dropping_3],
23546 ["de", either_1],
23547 ["de'", either_1],
23548 ["deca", always_non_dropping_1],
23549 ["degli", either_1],
23550 ["dei", either_1],
23551 ["del", either_1],
23552 ["dela", always_dropping_1],
23553 ["dell'", either_1],
23554 ["della", either_1],
23555 ["delle", either_1],
23556 ["dello", either_1],
23557 ["den", either_1],
23558 ["der", either_1],
23559 ["des", either_1],
23560 ["di", either_1],
23561 ["die le", always_non_dropping_2],
23562 ["do", always_non_dropping_1],
23563 ["don", always_non_dropping_1],
23564 ["dos", either_1],
23565 ["du", either_1],
23566 ["ed-", either_1],
23567 ["edh-", either_1],
23568 ["el", either_1],
23569 ["el-", either_1],
23570 ["en-", either_1],
23571 ["er-", either_1],
23572 ["es-", either_1],
23573 ["esh-", either_1],
23574 ["et-", either_1],
23575 ["eth-", either_1],
23576 ["ez-", either_1],
23577 ["eš-", either_1],
23578 ["eḍ-", either_1],
23579 ["eḏ-", either_1],
23580 ["eṣ-", either_1],
23581 ["eṭ-", either_1],
23582 ["eṯ-", either_1],
23583 ["eẓ-", either_1],
23584 ["het", always_non_dropping_1],
23585 ["i", always_non_dropping_1],
23586 ["il", always_dropping_1],
23587 ["im", always_non_dropping_1],
23588 ["in 't", always_non_dropping_2],
23589 ["in de", always_non_dropping_2],
23590 ["in den", always_non_dropping_2],
23591 ["in der", either_2],
23592 ["in het", always_non_dropping_2],
23593 ["in t", always_non_dropping_2],
23594 ["in", always_non_dropping_1],
23595 ["l", always_non_dropping_1],
23596 ["l'", always_non_dropping_1],
23597 ["la", always_non_dropping_1],
23598 ["las", always_non_dropping_1],
23599 ["le", always_non_dropping_1],
23600 ["les", either_1],
23601 ["lo", either_1],
23602 ["los", always_non_dropping_1],
23603 ["lou", always_non_dropping_1],
23604 ["of", always_non_dropping_1],
23605 ["onder 't", always_non_dropping_2],
23606 ["onder de", always_non_dropping_2],
23607 ["onder den", always_non_dropping_2],
23608 ["onder het", always_non_dropping_2],
23609 ["onder t", always_non_dropping_2],
23610 ["onder", always_non_dropping_1],
23611 ["op 't", always_non_dropping_2],
23612 ["op de", either_2],
23613 ["op den", always_non_dropping_2],
23614 ["op der", always_non_dropping_2],
23615 ["op gen", always_non_dropping_2],
23616 ["op het", always_non_dropping_2],
23617 ["op t", always_non_dropping_2],
23618 ["op ten", always_non_dropping_2],
23619 ["op", always_non_dropping_1],
23620 ["over 't", always_non_dropping_2],
23621 ["over de", always_non_dropping_2],
23622 ["over den", always_non_dropping_2],
23623 ["over het", always_non_dropping_2],
23624 ["over t", always_non_dropping_2],
23625 ["over", always_non_dropping_1],
23626 ["s", always_non_dropping_1],
23627 ["s'", always_non_dropping_1],
23628 ["sen", always_dropping_1],
23629 ["t", always_non_dropping_1],
23630 ["te", always_non_dropping_1],
23631 ["ten", always_non_dropping_1],
23632 ["ter", always_non_dropping_1],
23633 ["tho", always_non_dropping_1],
23634 ["thoe", always_non_dropping_1],
23635 ["thor", always_non_dropping_1],
23636 ["to", always_non_dropping_1],
23637 ["toe", always_non_dropping_1],
23638 ["tot", always_non_dropping_1],
23639 ["uijt 't", always_non_dropping_2],
23640 ["uijt de", always_non_dropping_2],
23641 ["uijt den", always_non_dropping_2],
23642 ["uijt te de", always_non_dropping_3],
23643 ["uijt ten", always_non_dropping_2],
23644 ["uijt", always_non_dropping_1],
23645 ["uit 't", always_non_dropping_2],
23646 ["uit de", always_non_dropping_2],
23647 ["uit den", always_non_dropping_2],
23648 ["uit het", always_non_dropping_2],
23649 ["uit t", always_non_dropping_2],
23650 ["uit te de", always_non_dropping_3],
23651 ["uit ten", always_non_dropping_2],
23652 ["uit", always_non_dropping_1],
23653 ["unter", always_non_dropping_1],
23654 ["v", always_non_dropping_1],
23655 ["v.", always_non_dropping_1],
23656 ["v.d.", always_non_dropping_1],
23657 ["van 't", always_non_dropping_2],
23658 ["van de l", always_non_dropping_3],
23659 ["van de l'", always_non_dropping_3],
23660 ["van de", always_non_dropping_2],
23661 ["van de", always_non_dropping_2],
23662 ["van den", always_non_dropping_2],
23663 ["van der", always_non_dropping_2],
23664 ["van gen", always_non_dropping_2],
23665 ["van het", always_non_dropping_2],
23666 ["van la", always_non_dropping_2],
23667 ["van t", always_non_dropping_2],
23668 ["van ter", always_non_dropping_2],
23669 ["van van de", always_non_dropping_3],
23670 ["van", either_1],
23671 ["vander", always_non_dropping_1],
23672 ["vd", always_non_dropping_1],
23673 ["ver", always_non_dropping_1],
23674 ["vom und zum", always_dropping_3],
23675 ["vom", either_1],
23676 ["von 't", always_non_dropping_2],
23677 ["von dem", either_2_dropping_best],
23678 ["von den", either_2_dropping_best],
23679 ["von der", either_2_dropping_best],
23680 ["von t", always_non_dropping_2],
23681 ["von und zu", either_3_dropping_best],
23682 ["von zu", either_2_dropping_best],
23683 ["von", either_1_dropping_best],
23684 ["voor 't", always_non_dropping_2],
23685 ["voor de", always_non_dropping_2],
23686 ["voor den", always_non_dropping_2],
23687 ["voor in 't", always_non_dropping_3],
23688 ["voor in t", always_non_dropping_3],
23689 ["voor", always_non_dropping_1],
23690 ["vor der", either_2_dropping_best],
23691 ["vor", either_1_dropping_best],
23692 ["z", always_dropping_1],
23693 ["ze", always_dropping_1],
23694 ["zu", either_1_dropping_best],
23695 ["zum", either_1],
23696 ["zur", either_1]
23697 ];
23698 return PARTICLES;
23699}());
23700
23701CSL.parseParticles = (function(){
23702 function splitParticles(nameValue, firstNameFlag, caseOverride) {
23703 // Parse particles out from name fields.
23704 // * nameValue (string) is the field content to be parsed.
23705 // * firstNameFlag (boolean) parse trailing particles
23706 // (default is to parse leading particles)
23707 // * caseOverride (boolean) include all but one word in particle set
23708 // (default is to include only words with lowercase first char)
23709 // [caseOverride is not used in this application]
23710 // Returns an array with:
23711 // * (boolean) flag indicating whether a particle was found
23712 // * (string) the name after removal of particles
23713 // * (array) the list of particles found
23714 var origNameValue = nameValue;
23715 nameValue = caseOverride ? nameValue.toLowerCase() : nameValue;
23716 var particleList = [];
23717 var rex;
23718 var hasParticle;
23719 if (firstNameFlag) {
23720 nameValue = nameValue.split("").reverse().join("");
23721 rex = CSL.PARTICLE_GIVEN_REGEXP;
23722 } else {
23723 rex = CSL.PARTICLE_FAMILY_REGEXP;
23724 }
23725 var m = nameValue.match(rex);
23726 while (m) {
23727 var m1 = firstNameFlag ? m[1].split("").reverse().join("") : m[1];
23728 var firstChar = m ? m1 : false;
23729 var firstChar = firstChar ? m1.replace(/^[-\'\u02bb\u2019\s]*(.).*$/, "$1") : false;
23730 hasParticle = firstChar ? firstChar.toUpperCase() !== firstChar : false;
23731 if (!hasParticle) {
23732 break;
23733 }
23734 if (firstNameFlag) {
23735 particleList.push(origNameValue.slice(m1.length * -1));
23736 origNameValue = origNameValue.slice(0,m1.length * -1);
23737 } else {
23738 particleList.push(origNameValue.slice(0,m1.length));
23739 origNameValue = origNameValue.slice(m1.length);
23740 }
23741 //particleList.push(m1);
23742 nameValue = m[2];
23743 m = nameValue.match(rex);
23744 }
23745 if (firstNameFlag) {
23746 nameValue = nameValue.split("").reverse().join("");
23747 particleList.reverse();
23748 for (var i=1,ilen=particleList.length;i<ilen;i++) {
23749 if (particleList[i].slice(0, 1) == " ") {
23750 particleList[i-1] += " ";
23751 }
23752 }
23753 for (var i=0,ilen=particleList.length;i<ilen;i++) {
23754 if (particleList[i].slice(0, 1) == " ") {
23755 particleList[i] = particleList[i].slice(1);
23756 }
23757 }
23758 nameValue = origNameValue.slice(0, nameValue.length);
23759 } else {
23760 nameValue = origNameValue.slice(nameValue.length * -1);
23761 }
23762 return [hasParticle, nameValue, particleList];
23763 }
23764 function trimLast(str) {
23765 var lastChar = str.slice(-1);
23766 str = str.trim();
23767 if (lastChar === " " && ["\'", "\u2019"].indexOf(str.slice(-1)) > -1) {
23768 str += " ";
23769 }
23770 return str;
23771 }
23772 function parseSuffix(nameObj) {
23773 if (!nameObj.suffix && nameObj.given) {
23774 var m = nameObj.given.match(/(\s*,!*\s*)/);
23775 if (m) {
23776 var idx = nameObj.given.indexOf(m[1]);
23777 var possible_suffix = nameObj.given.slice(idx + m[1].length);
23778 var possible_comma = nameObj.given.slice(idx, idx + m[1].length).replace(/\s*/g, "");
23779 if (possible_suffix.replace(/\./g, "") === 'et al' && !nameObj["dropping-particle"]) {
23780 // This hack covers the case where "et al." is explicitly used in the
23781 // authorship information of the work.
23782 nameObj["dropping-particle"] = possible_suffix;
23783 nameObj["comma-dropping-particle"] = ",";
23784 } else {
23785 if (possible_comma.length === 2) {
23786 nameObj["comma-suffix"] = true;
23787 }
23788 nameObj.suffix = possible_suffix;
23789 }
23790 nameObj.given = nameObj.given.slice(0, idx);
23791 }
23792 }
23793 }
23794 return function(nameObj) {
23795 // Extract and set non-dropping particle(s) from family name field
23796 var res = splitParticles(nameObj.family);
23797 var lastNameValue = res[1];
23798 var lastParticleList = res[2];
23799 nameObj.family = lastNameValue;
23800 var nonDroppingParticle = trimLast(lastParticleList.join(""));
23801 if (nonDroppingParticle) {
23802 nameObj['non-dropping-particle'] = nonDroppingParticle;
23803 }
23804 // Split off suffix first of all
23805 parseSuffix(nameObj);
23806 // Extract and set dropping particle(s) from given name field
23807 var res = splitParticles(nameObj.given, true);
23808 var firstNameValue = res[1];
23809 var firstParticleList = res[2];
23810 nameObj.given = firstNameValue;
23811 var droppingParticle = firstParticleList.join("").trim();
23812 if (droppingParticle) {
23813 nameObj['dropping-particle'] = droppingParticle;
23814 }
23815 };
23816}());
23817
23818
23819module.exports = CSL
\No newline at end of file