/* * Italic/Bold handling. * * - list of tokens * - NEWLINE * - ticks (2+) -> list with link in line token list? * - process on newline * - need access to text nodes before for conversion back to text */ function QuoteTransformer ( ) { this.italics = []; this.bolds = []; this.inserted = 0; } QuoteTransformer.prototype.register = function ( tokenTransformer ) { // Register for NEWLINE and QUOTE tag tokens var self = this; tokenTransformer.appendListener( function (ctx) { return self.onNewLine(ctx); }, 'newline' ); tokenTransformer.appendListener( function (ctx) { return self.onQuote(ctx); }, 'tag', 'QUOTE' ); }; // Extract a copy of the token context with the info we need QuoteTransformer.prototype.ctx = function ( tokenCTX ) { return { accum: tokenCTX.accum, token: tokenCTX.token, lastToken: tokenCTX.lastToken, pos: tokenCTX.pos }; }; QuoteTransformer.prototype.onQuote = function ( tokenCTX ) { // depending on length, add starting 's to preceding text node // (if any) // add token index to italic/bold lists // add placeholder for token var token = tokenCTX.token, qlen = token.value.length, out = null, lastToken = tokenCTX.lastToken, ctx = this.ctx(tokenCTX), ctx2, accum = tokenCTX.accum; switch (qlen) { case 2: accum = tokenCTX.transformer.newAccumulator(accum); this.italics.push(ctx); break; case 3: accum = tokenCTX.transformer.newAccumulator(accum); this.bolds.push(ctx); break; case 4: if (lastToken && lastToken.type === 'TEXT') { lastToken.value += "'"; } else { out = {type: 'TEXT', value: "'"}; } accum = tokenCTX.transformer.newAccumulator(accum); this.bolds.push(ctx); break; case 5: // order does not matter here, will be fixed // by HTML tree builder accum = tokenCTX.transformer.newAccumulator(accum, 2); this.italics.push(ctx); ctx2 = this.ctx(tokenCTX); ctx2.token = {attribs: ctx.token.attribs}; this.bolds.push(ctx2); break; default: // longer than 5, only use the last 5 ticks var newvalue = token.value.substr(0, qlen - 5 ); if (lastToken && lastToken.type === 'TEXT') { lastToken.value += newvalue; } else { out = {type: 'TEXT', value: newvalue}; } accum = tokenCTX.transformer.newAccumulator(accum, 2); this.italics.push(ctx); ctx2 = this.ctx(tokenCTX); ctx2.token = {attribs: ctx.token.attribs}; this.bolds.push(ctx2); break; } tokenCTX.token = out; tokenCTX.accum = accum; return tokenCTX; }; QuoteTransformer.prototype.onNewLine = function ( tokenCTX ) { if(!this.bolds && !this.italics) { // Nothing to do, quick abort. return tokenCTX; } //console.log("onNewLine: " + this.italics + this.bolds); // balance out tokens, convert placeholders into tags if (this.italics.length % 2 && this.bolds.length % 2) { var firstsingleletterword = -1, firstmultiletterword = -1, firstspace = -1; for (var j = 0; j < this.bolds.length; j++) { var ctx = this.bolds[j]; //console.log("balancing!" + JSON.stringify(ctx.lastToken, null, 2)); if (ctx.lastToken) { if (ctx.lastToken.type === 'TEXT') { var lastchar = ctx.lastToken.value[ctx.lastToken.value.length - 1], secondtolastchar = ctx.lastToken.value[ctx.lastToken.value.length - 2]; if (lastchar === ' ' && firstspace === -1) { firstspace = j; } else if (lastchar !== ' ') { if ( secondtolastchar === ' ' && firstsingleletterword === -1) { firstsingleletterword = j; } else if ( firstmultiletterword == -1) { firstmultiletterword = j; } } } else if ( ( ctx.lastToken.type === 'NEWLINE' || ctx.lastToken.type === 'TAG' ) && firstspace == -1 ) { firstmultiletterword = j; } } } // now see if we can convert a bold to an italic and // an apostrophe if (firstsingleletterword > -1) { this.convertBold(firstsingleletterword); } else if (firstmultiletterword > -1) { this.convertBold(firstmultiletterword); } else if (firstspace > -1) { this.convertBold(firstspace); } } this.quotesToTags(this.italics, 'i', tokenCTX.transformer); this.quotesToTags(this.bolds, 'b', tokenCTX.transformer); this.bolds = []; this.italics = []; // Pass through the NEWLINE token unchanged return tokenCTX; }; QuoteTransformer.prototype.convertBold = function ( i ) { var ctx = this.bolds[i]; //console.log('convertbold!'); if ( ctx.lastToken && ctx.lastToken.type === 'TEXT' ) { ctx.lastToken.value += "'"; } else { // Add a text token! ctx.token = [{type: 'TEXT', value: "'"}, ctx.token]; } this.bolds.splice(i, 1); this.italics.push(ctx); this.italics.sort(function(a,b) { return a.pos - b.pos; } ); //console.log(this.italics.map(function(a) { return a.pos })); //console.log(this.bolds.map(function(a) { return a.pos })); }; // convert italics/bolds into tags QuoteTransformer.prototype.quotesToTags = function ( contexts, name, transformer ) { var toggle = true, t, out = []; for (var j = 0; j < contexts.length; j++) { t = contexts[j].token; if ( $.isArray(t) ) { // Slip in a text token from bold to italic rebalancing var realToken = t.pop(); transformer.transformTokens( t, contexts[j].accum, 0 ); t = realToken; } if(toggle) { t.type = 'TAG'; } else { t.type = 'ENDTAG'; } t.name = name; delete t.value; toggle = !toggle; // Re-add and process the new token with the original accumulator transformer.transformTokens( [t], contexts[j].accum, 0 ); } var l = contexts.length; if (!toggle) { // add end tag, but don't count it towards the finish transformer.transformTokens( [{type: 'ENDTAG', name: name}], contexts[contexts.length - 1].accum, 0 ); } // now allow the transformer to finish transformer.finish( contexts.length ); }; if (typeof module == "object") { module.exports.QuoteTransformer = QuoteTransformer; }