| /********************************* |
| * Symbols for Basic Mathematics |
| ********************************/ |
| |
| var Digit = P(VanillaSymbol, function(_, super_) { |
| _.createLeftOf = function(cursor) { |
| if (cursor.options.autoSubscriptNumerals |
| && cursor.parent !== cursor.parent.parent.sub |
| && ((cursor[L] instanceof Variable && cursor[L].isItalic !== false) |
| || (cursor[L] instanceof SupSub |
| && cursor[L][L] instanceof Variable |
| && cursor[L][L].isItalic !== false))) { |
| LatexCmds._().createLeftOf(cursor); |
| super_.createLeftOf.call(this, cursor); |
| cursor.insRightOf(cursor.parent.parent); |
| } |
| else super_.createLeftOf.call(this, cursor); |
| }; |
| }); |
| |
| var Variable = P(Symbol, function(_, super_) { |
| _.init = function(ch, html) { |
| super_.init.call(this, ch, '<var>'+(html || ch)+'</var>'); |
| }; |
| _.text = function() { |
| var text = this.ctrlSeq; |
| if (this.isPartOfOperator) { |
| if (text[0] == '\\') { |
| text = text.slice(1, text.length); |
| } |
| else if (text[text.length-1] == ' ') { |
| text = text.slice (0, -1); |
| } |
| } else { |
| if (this[L] && !(this[L] instanceof Variable) |
| && !(this[L] instanceof BinaryOperator) |
| && this[L].ctrlSeq !== '\\ ') |
| text = '*' + text; |
| if (this[R] && !(this[R] instanceof BinaryOperator) |
| && !(this[R] instanceof SupSub)) |
| text += '*'; |
| } |
| return text; |
| }; |
| }); |
| |
| Options.p.autoCommands = { _maxLength: 0 }; |
| optionProcessors.autoCommands = function(cmds) { |
| if (!/^[a-z]+(?: [a-z]+)*$/i.test(cmds)) { |
| throw '"'+cmds+'" not a space-delimited list of only letters'; |
| } |
| var list = cmds.split(' '), dict = {}, maxLength = 0; |
| for (var i = 0; i < list.length; i += 1) { |
| var cmd = list[i]; |
| if (cmd.length < 2) { |
| throw 'autocommand "'+cmd+'" not minimum length of 2'; |
| } |
| if (LatexCmds[cmd] === OperatorName) { |
| throw '"' + cmd + '" is a built-in operator name'; |
| } |
| dict[cmd] = 1; |
| maxLength = max(maxLength, cmd.length); |
| } |
| dict._maxLength = maxLength; |
| return dict; |
| }; |
| |
| var Letter = P(Variable, function(_, super_) { |
| _.init = function(ch) { return super_.init.call(this, this.letter = ch); }; |
| _.createLeftOf = function(cursor) { |
| super_.createLeftOf.apply(this, arguments); |
| var autoCmds = cursor.options.autoCommands, maxLength = autoCmds._maxLength; |
| if (maxLength > 0) { |
| // want longest possible autocommand, so join together longest |
| // sequence of letters |
| var str = '', l = this, i = 0; |
| // FIXME: l.ctrlSeq === l.letter checks if first or last in an operator name |
| while (l instanceof Letter && l.ctrlSeq === l.letter && i < maxLength) { |
| str = l.letter + str, l = l[L], i += 1; |
| } |
| // check for an autocommand, going thru substrings longest to shortest |
| while (str.length) { |
| if (autoCmds.hasOwnProperty(str)) { |
| for (var i = 1, l = this; i < str.length; i += 1, l = l[L]); |
| Fragment(l, this).remove(); |
| cursor[L] = l[L]; |
| return LatexCmds[str](str).createLeftOf(cursor); |
| } |
| str = str.slice(1); |
| } |
| } |
| }; |
| _.italicize = function(bool) { |
| this.isItalic = bool; |
| this.isPartOfOperator = !bool; |
| this.jQ.toggleClass('mq-operator-name', !bool); |
| return this; |
| }; |
| _.finalizeTree = _.siblingDeleted = _.siblingCreated = function(opts, dir) { |
| // don't auto-un-italicize if the sibling to my right changed (dir === R or |
| // undefined) and it's now a Letter, it will un-italicize everyone |
| if (dir !== L && this[R] instanceof Letter) return; |
| this.autoUnItalicize(opts); |
| }; |
| _.autoUnItalicize = function(opts) { |
| var autoOps = opts.autoOperatorNames; |
| if (autoOps._maxLength === 0) return; |
| // want longest possible operator names, so join together entire contiguous |
| // sequence of letters |
| var str = this.letter; |
| for (var l = this[L]; l instanceof Letter; l = l[L]) str = l.letter + str; |
| for (var r = this[R]; r instanceof Letter; r = r[R]) str += r.letter; |
| |
| // removeClass and delete flags from all letters before figuring out |
| // which, if any, are part of an operator name |
| Fragment(l[R] || this.parent.ends[L], r[L] || this.parent.ends[R]).each(function(el) { |
| el.italicize(true).jQ.removeClass('mq-first mq-last mq-followed-by-supsub'); |
| el.ctrlSeq = el.letter; |
| }); |
| |
| // check for operator names: at each position from left to right, check |
| // substrings from longest to shortest |
| outer: for (var i = 0, first = l[R] || this.parent.ends[L]; i < str.length; i += 1, first = first[R]) { |
| for (var len = min(autoOps._maxLength, str.length - i); len > 0; len -= 1) { |
| var word = str.slice(i, i + len); |
| if (autoOps.hasOwnProperty(word)) { |
| for (var j = 0, letter = first; j < len; j += 1, letter = letter[R]) { |
| letter.italicize(false); |
| var last = letter; |
| } |
| |
| var isBuiltIn = BuiltInOpNames.hasOwnProperty(word); |
| first.ctrlSeq = (isBuiltIn ? '\\' : '\\operatorname{') + first.ctrlSeq; |
| last.ctrlSeq += (isBuiltIn ? ' ' : '}'); |
| if (TwoWordOpNames.hasOwnProperty(word)) last[L][L][L].jQ.addClass('mq-last'); |
| if (!shouldOmitPadding(first[L])) first.jQ.addClass('mq-first'); |
| if (!shouldOmitPadding(last[R])) { |
| if (last[R] instanceof SupSub) { |
| var supsub = last[R]; // XXX monkey-patching, but what's the right thing here? |
| // Have operatorname-specific code in SupSub? A CSS-like language to style the |
| // math tree, but which ignores cursor and selection (which CSS can't)? |
| var respace = supsub.siblingCreated = supsub.siblingDeleted = function() { |
| supsub.jQ.toggleClass('mq-after-operator-name', !(supsub[R] instanceof Bracket)); |
| }; |
| respace(); |
| } |
| else { |
| last.jQ.toggleClass('mq-last', !(last[R] instanceof Bracket)); |
| } |
| } |
| |
| i += len - 1; |
| first = last; |
| continue outer; |
| } |
| } |
| } |
| }; |
| function shouldOmitPadding(node) { |
| // omit padding if no node, or if node already has padding (to avoid double-padding) |
| return !node || (node instanceof BinaryOperator) || (node instanceof SummationNotation); |
| } |
| }); |
| var BuiltInOpNames = {}; // the set of operator names like \sin, \cos, etc that |
| // are built-into LaTeX, see Section 3.17 of the Short Math Guide: http://tinyurl.com/jm9okjc |
| // MathQuill auto-unitalicizes some operator names not in that set, like 'hcf' |
| // and 'arsinh', which must be exported as \operatorname{hcf} and |
| // \operatorname{arsinh}. Note: over/under line/arrow \lim variants like |
| // \varlimsup are not supported |
| var AutoOpNames = Options.p.autoOperatorNames = { _maxLength: 9 }; // the set |
| // of operator names that MathQuill auto-unitalicizes by default; overridable |
| var TwoWordOpNames = { limsup: 1, liminf: 1, projlim: 1, injlim: 1 }; |
| (function() { |
| var mostOps = ('arg deg det dim exp gcd hom inf ker lg lim ln log max min sup' |
| + ' limsup liminf injlim projlim Pr').split(' '); |
| for (var i = 0; i < mostOps.length; i += 1) { |
| BuiltInOpNames[mostOps[i]] = AutoOpNames[mostOps[i]] = 1; |
| } |
| |
| var builtInTrigs = // why coth but not sech and csch, LaTeX? |
| 'sin cos tan arcsin arccos arctan sinh cosh tanh sec csc cot coth'.split(' '); |
| for (var i = 0; i < builtInTrigs.length; i += 1) { |
| BuiltInOpNames[builtInTrigs[i]] = 1; |
| } |
| |
| var autoTrigs = 'sin cos tan sec cosec csc cotan cot ctg'.split(' '); |
| for (var i = 0; i < autoTrigs.length; i += 1) { |
| AutoOpNames[autoTrigs[i]] = |
| AutoOpNames['arc'+autoTrigs[i]] = |
| AutoOpNames[autoTrigs[i]+'h'] = |
| AutoOpNames['ar'+autoTrigs[i]+'h'] = |
| AutoOpNames['arc'+autoTrigs[i]+'h'] = 1; |
| } |
| |
| // compat with some of the nonstandard LaTeX exported by MathQuill |
| // before #247. None of these are real LaTeX commands so, seems safe |
| var moreNonstandardOps = 'gcf hcf lcm proj span'.split(' '); |
| for (var i = 0; i < moreNonstandardOps.length; i += 1) { |
| AutoOpNames[moreNonstandardOps[i]] = 1; |
| } |
| }()); |
| optionProcessors.autoOperatorNames = function(cmds) { |
| if (!/^[a-z]+(?: [a-z]+)*$/i.test(cmds)) { |
| throw '"'+cmds+'" not a space-delimited list of only letters'; |
| } |
| var list = cmds.split(' '), dict = {}, maxLength = 0; |
| for (var i = 0; i < list.length; i += 1) { |
| var cmd = list[i]; |
| if (cmd.length < 2) { |
| throw '"'+cmd+'" not minimum length of 2'; |
| } |
| dict[cmd] = 1; |
| maxLength = max(maxLength, cmd.length); |
| } |
| dict._maxLength = maxLength; |
| return dict; |
| }; |
| var OperatorName = P(Symbol, function(_, super_) { |
| _.init = function(fn) { this.ctrlSeq = fn; }; |
| _.createLeftOf = function(cursor) { |
| var fn = this.ctrlSeq; |
| for (var i = 0; i < fn.length; i += 1) { |
| Letter(fn.charAt(i)).createLeftOf(cursor); |
| } |
| }; |
| _.parser = function() { |
| var fn = this.ctrlSeq; |
| var block = MathBlock(); |
| for (var i = 0; i < fn.length; i += 1) { |
| Letter(fn.charAt(i)).adopt(block, block.ends[R], 0); |
| } |
| return Parser.succeed(block.children()); |
| }; |
| }); |
| for (var fn in AutoOpNames) if (AutoOpNames.hasOwnProperty(fn)) { |
| LatexCmds[fn] = OperatorName; |
| } |
| LatexCmds.operatorname = P(MathCommand, function(_) { |
| _.createLeftOf = noop; |
| _.numBlocks = function() { return 1; }; |
| _.parser = function() { |
| return latexMathParser.block.map(function(b) { return b.children(); }); |
| }; |
| }); |
| |
| LatexCmds.f = P(Letter, function(_, super_) { |
| _.init = function() { |
| Symbol.p.init.call(this, this.letter = 'f', '<var class="mq-f">f</var>'); |
| }; |
| _.italicize = function(bool) { |
| this.jQ.html('f').toggleClass('mq-f', bool); |
| return super_.italicize.apply(this, arguments); |
| }; |
| }); |
| |
| // VanillaSymbol's |
| LatexCmds[' '] = LatexCmds.space = bind(VanillaSymbol, '\\ ', ' '); |
| |
| LatexCmds["'"] = LatexCmds.prime = bind(VanillaSymbol, "'", '′'); |
| LatexCmds['″'] = LatexCmds.dprime = bind(VanillaSymbol, '″', '″'); |
| |
| LatexCmds.backslash = bind(VanillaSymbol,'\\backslash ','\\'); |
| if (!CharCmds['\\']) CharCmds['\\'] = LatexCmds.backslash; |
| |
| LatexCmds.$ = bind(VanillaSymbol, '\\$', '$'); |
| |
| // does not use Symbola font |
| var NonSymbolaSymbol = P(Symbol, function(_, super_) { |
| _.init = function(ch, html) { |
| super_.init.call(this, ch, '<span class="mq-nonSymbola">'+(html || ch)+'</span>'); |
| }; |
| }); |
| |
| LatexCmds['@'] = NonSymbolaSymbol; |
| LatexCmds['&'] = bind(NonSymbolaSymbol, '\\&', '&'); |
| LatexCmds['%'] = bind(NonSymbolaSymbol, '\\%', '%'); |
| |
| //the following are all Greek to me, but this helped a lot: http://www.ams.org/STIX/ion/stixsig03.html |
| |
| //lowercase Greek letter variables |
| LatexCmds.alpha = |
| LatexCmds.beta = |
| LatexCmds.gamma = |
| LatexCmds.delta = |
| LatexCmds.zeta = |
| LatexCmds.eta = |
| LatexCmds.theta = |
| LatexCmds.iota = |
| LatexCmds.kappa = |
| LatexCmds.mu = |
| LatexCmds.nu = |
| LatexCmds.xi = |
| LatexCmds.rho = |
| LatexCmds.sigma = |
| LatexCmds.tau = |
| LatexCmds.chi = |
| LatexCmds.psi = |
| LatexCmds.omega = P(Variable, function(_, super_) { |
| _.init = function(latex) { |
| super_.init.call(this,'\\'+latex+' ','&'+latex+';'); |
| }; |
| }); |
| |
| //why can't anybody FUCKING agree on these |
| LatexCmds.phi = //W3C or Unicode? |
| bind(Variable,'\\phi ','ϕ'); |
| |
| LatexCmds.phiv = //Elsevier and 9573-13 |
| LatexCmds.varphi = //AMS and LaTeX |
| bind(Variable,'\\varphi ','φ'); |
| |
| LatexCmds.epsilon = //W3C or Unicode? |
| bind(Variable,'\\epsilon ','ϵ'); |
| |
| LatexCmds.epsiv = //Elsevier and 9573-13 |
| LatexCmds.varepsilon = //AMS and LaTeX |
| bind(Variable,'\\varepsilon ','ε'); |
| |
| LatexCmds.piv = //W3C/Unicode and Elsevier and 9573-13 |
| LatexCmds.varpi = //AMS and LaTeX |
| bind(Variable,'\\varpi ','ϖ'); |
| |
| LatexCmds.sigmaf = //W3C/Unicode |
| LatexCmds.sigmav = //Elsevier |
| LatexCmds.varsigma = //LaTeX |
| bind(Variable,'\\varsigma ','ς'); |
| |
| LatexCmds.thetav = //Elsevier and 9573-13 |
| LatexCmds.vartheta = //AMS and LaTeX |
| LatexCmds.thetasym = //W3C/Unicode |
| bind(Variable,'\\vartheta ','ϑ'); |
| |
| LatexCmds.upsilon = //AMS and LaTeX and W3C/Unicode |
| LatexCmds.upsi = //Elsevier and 9573-13 |
| bind(Variable,'\\upsilon ','υ'); |
| |
| //these aren't even mentioned in the HTML character entity references |
| LatexCmds.gammad = //Elsevier |
| LatexCmds.Gammad = //9573-13 -- WTF, right? I dunno if this was a typo in the reference (see above) |
| LatexCmds.digamma = //LaTeX |
| bind(Variable,'\\digamma ','ϝ'); |
| |
| LatexCmds.kappav = //Elsevier |
| LatexCmds.varkappa = //AMS and LaTeX |
| bind(Variable,'\\varkappa ','ϰ'); |
| |
| LatexCmds.rhov = //Elsevier and 9573-13 |
| LatexCmds.varrho = //AMS and LaTeX |
| bind(Variable,'\\varrho ','ϱ'); |
| |
| //Greek constants, look best in non-italicized Times New Roman |
| LatexCmds.pi = LatexCmds['π'] = bind(NonSymbolaSymbol,'\\pi ','π'); |
| LatexCmds.lambda = bind(NonSymbolaSymbol,'\\lambda ','λ'); |
| |
| //uppercase greek letters |
| |
| LatexCmds.Upsilon = //LaTeX |
| LatexCmds.Upsi = //Elsevier and 9573-13 |
| LatexCmds.upsih = //W3C/Unicode "upsilon with hook" |
| LatexCmds.Upsih = //'cos it makes sense to me |
| bind(Symbol,'\\Upsilon ','<var style="font-family: serif">ϒ</var>'); //Symbola's 'upsilon with a hook' is a capital Y without hooks :( |
| |
| //other symbols with the same LaTeX command and HTML character entity reference |
| LatexCmds.Gamma = |
| LatexCmds.Delta = |
| LatexCmds.Theta = |
| LatexCmds.Lambda = |
| LatexCmds.Xi = |
| LatexCmds.Pi = |
| LatexCmds.Sigma = |
| LatexCmds.Phi = |
| LatexCmds.Psi = |
| LatexCmds.Omega = |
| LatexCmds.forall = P(VanillaSymbol, function(_, super_) { |
| _.init = function(latex) { |
| super_.init.call(this,'\\'+latex+' ','&'+latex+';'); |
| }; |
| }); |
| |
| // symbols that aren't a single MathCommand, but are instead a whole |
| // Fragment. Creates the Fragment from a LaTeX string |
| var LatexFragment = P(MathCommand, function(_) { |
| _.init = function(latex) { this.latex = latex; }; |
| _.createLeftOf = function(cursor) { |
| var block = latexMathParser.parse(this.latex); |
| block.children().adopt(cursor.parent, cursor[L], cursor[R]); |
| cursor[L] = block.ends[R]; |
| block.jQize().insertBefore(cursor.jQ); |
| block.finalizeInsert(cursor.options, cursor); |
| if (block.ends[R][R].siblingCreated) block.ends[R][R].siblingCreated(cursor.options, L); |
| if (block.ends[L][L].siblingCreated) block.ends[L][L].siblingCreated(cursor.options, R); |
| cursor.parent.bubble('reflow'); |
| }; |
| _.parser = function() { |
| var frag = latexMathParser.parse(this.latex).children(); |
| return Parser.succeed(frag); |
| }; |
| }); |
| |
| // for what seems to me like [stupid reasons][1], Unicode provides |
| // subscripted and superscripted versions of all ten Arabic numerals, |
| // as well as [so-called "vulgar fractions"][2]. |
| // Nobody really cares about most of them, but some of them actually |
| // predate Unicode, dating back to [ISO-8859-1][3], apparently also |
| // known as "Latin-1", which among other things [Windows-1252][4] |
| // largely coincides with, so Microsoft Word sometimes inserts them |
| // and they get copy-pasted into MathQuill. |
| // |
| // (Irrelevant but funny story: though not a superset of Latin-1 aka |
| // ISO-8859-1, Windows-1252 **is** a strict superset of the "closely |
| // related but distinct"[3] "ISO 8859-1" -- see the lack of a dash |
| // after "ISO"? Completely different character set, like elephants vs |
| // elephant seals, or "Zombies" vs "Zombie Redneck Torture Family". |
| // What kind of idiot would get them confused. |
| // People in fact got them confused so much, it was so common to |
| // mislabel Windows-1252 text as ISO-8859-1, that most modern web |
| // browsers and email clients treat the MIME charset of ISO-8859-1 |
| // as actually Windows-1252, behavior now standard in the HTML5 spec.) |
| // |
| // [1]: http://en.wikipedia.org/wiki/Unicode_subscripts_andsuper_scripts |
| // [2]: http://en.wikipedia.org/wiki/Number_Forms |
| // [3]: http://en.wikipedia.org/wiki/ISO/IEC_8859-1 |
| // [4]: http://en.wikipedia.org/wiki/Windows-1252 |
| LatexCmds['¹'] = bind(LatexFragment, '^1'); |
| LatexCmds['²'] = bind(LatexFragment, '^2'); |
| LatexCmds['³'] = bind(LatexFragment, '^3'); |
| LatexCmds['¼'] = bind(LatexFragment, '\\frac14'); |
| LatexCmds['½'] = bind(LatexFragment, '\\frac12'); |
| LatexCmds['¾'] = bind(LatexFragment, '\\frac34'); |
| |
| var PlusMinus = P(BinaryOperator, function(_) { |
| _.init = VanillaSymbol.prototype.init; |
| |
| _.contactWeld = _.siblingCreated = _.siblingDeleted = function(opts, dir) { |
| function determineOpClassType(node) { |
| if (node[L]) { |
| // If the left sibling is a binary operator or a separator (comma, semicolon, colon) |
| // or an open bracket (open parenthesis, open square bracket) |
| // consider the operator to be unary |
| if (node[L] instanceof BinaryOperator || /^[,;:\(\[]$/.test(node[L].ctrlSeq)) { |
| return ''; |
| } |
| } else if (node.parent && node.parent.parent && node.parent.parent.isStyleBlock()) { |
| //if we are in a style block at the leftmost edge, determine unary/binary based on |
| //the style block |
| //this allows style blocks to be transparent for unary/binary purposes |
| return determineOpClassType(node.parent.parent); |
| } else { |
| return ''; |
| } |
| |
| return 'mq-binary-operator'; |
| }; |
| |
| if (dir === R) return; // ignore if sibling only changed on the right |
| this.jQ[0].className = determineOpClassType(this); |
| return this; |
| }; |
| }); |
| |
| LatexCmds['+'] = bind(PlusMinus, '+', '+'); |
| //yes, these are different dashes, I think one is an en dash and the other is a hyphen |
| LatexCmds['–'] = LatexCmds['-'] = bind(PlusMinus, '-', '−'); |
| LatexCmds['±'] = LatexCmds.pm = LatexCmds.plusmn = LatexCmds.plusminus = |
| bind(PlusMinus,'\\pm ','±'); |
| LatexCmds.mp = LatexCmds.mnplus = LatexCmds.minusplus = |
| bind(PlusMinus,'\\mp ','∓'); |
| |
| CharCmds['*'] = LatexCmds.sdot = LatexCmds.cdot = |
| bind(BinaryOperator, '\\cdot ', '·', '*'); |
| //semantically should be ⋅, but · looks better |
| |
| var Inequality = P(BinaryOperator, function(_, super_) { |
| _.init = function(data, strict) { |
| this.data = data; |
| this.strict = strict; |
| var strictness = (strict ? 'Strict' : ''); |
| super_.init.call(this, data['ctrlSeq'+strictness], data['html'+strictness], |
| data['text'+strictness]); |
| }; |
| _.swap = function(strict) { |
| this.strict = strict; |
| var strictness = (strict ? 'Strict' : ''); |
| this.ctrlSeq = this.data['ctrlSeq'+strictness]; |
| this.jQ.html(this.data['html'+strictness]); |
| this.textTemplate = [ this.data['text'+strictness] ]; |
| }; |
| _.deleteTowards = function(dir, cursor) { |
| if (dir === L && !this.strict) { |
| this.swap(true); |
| this.bubble('reflow'); |
| return; |
| } |
| super_.deleteTowards.apply(this, arguments); |
| }; |
| }); |
| |
| var less = { ctrlSeq: '\\le ', html: '≤', text: '≤', |
| ctrlSeqStrict: '<', htmlStrict: '<', textStrict: '<' }; |
| var greater = { ctrlSeq: '\\ge ', html: '≥', text: '≥', |
| ctrlSeqStrict: '>', htmlStrict: '>', textStrict: '>' }; |
| |
| LatexCmds['<'] = LatexCmds.lt = bind(Inequality, less, true); |
| LatexCmds['>'] = LatexCmds.gt = bind(Inequality, greater, true); |
| LatexCmds['≤'] = LatexCmds.le = LatexCmds.leq = bind(Inequality, less, false); |
| LatexCmds['≥'] = LatexCmds.ge = LatexCmds.geq = bind(Inequality, greater, false); |
| |
| var Equality = P(BinaryOperator, function(_, super_) { |
| _.init = function() { |
| super_.init.call(this, '=', '='); |
| }; |
| _.createLeftOf = function(cursor) { |
| if (cursor[L] instanceof Inequality && cursor[L].strict) { |
| cursor[L].swap(false); |
| cursor[L].bubble('reflow'); |
| return; |
| } |
| super_.createLeftOf.apply(this, arguments); |
| }; |
| }); |
| LatexCmds['='] = Equality; |
| |
| LatexCmds['×'] = LatexCmds.times = bind(BinaryOperator, '\\times ', '×', '[x]'); |
| |
| LatexCmds['÷'] = LatexCmds.div = LatexCmds.divide = LatexCmds.divides = |
| bind(BinaryOperator,'\\div ','÷', '[/]'); |
| |
| CharCmds['~'] = LatexCmds.sim = bind(BinaryOperator, '\\sim ', '~', '~'); |