9ea723213ec886c32f0a40172b83646a1ec04d70
[ckeditor.git] / _source / plugins / bbcode / plugin.js
1 /*
2 Copyright (c) 2003-2011, CKSource - Frederico Knabben. All rights reserved.
3 For licensing, see LICENSE.html or http://ckeditor.com/license
4 */
5
6 (function()
7 {
8 CKEDITOR.on( 'dialogDefinition', function( ev )
9 {
10 var tab, name = ev.data.name,
11 definition = ev.data.definition;
12
13 if ( name == 'link' )
14 {
15 definition.removeContents( 'target' );
16 definition.removeContents( 'upload' );
17 definition.removeContents( 'advanced' );
18 tab = definition.getContents( 'info' );
19 tab.remove( 'emailSubject' );
20 tab.remove( 'emailBody' );
21 }
22 else if ( name == 'image' )
23 {
24 definition.removeContents( 'advanced' );
25 tab = definition.getContents( 'Link' );
26 tab.remove( 'cmbTarget' );
27 tab = definition.getContents( 'info' );
28 tab.remove( 'txtAlt' );
29 tab.remove( 'basic' );
30 }
31 });
32
33 var bbcodeMap = { 'b' : 'strong', 'u': 'u', 'i' : 'em', 'color' : 'span', 'size' : 'span', 'quote' : 'blockquote', 'code' : 'code', 'url' : 'a', 'email' : 'span', 'img' : 'span', '*' : 'li', 'list' : 'ol' },
34 convertMap = { 'strong' : 'b' , 'b' : 'b', 'u': 'u', 'em' : 'i', 'i': 'i', 'code' : 'code', 'li' : '*' },
35 tagnameMap = { 'strong' : 'b', 'em' : 'i', 'u' : 'u', 'li' : '*', 'ul' : 'list', 'ol' : 'list', 'code' : 'code', 'a' : 'link', 'img' : 'img', 'blockquote' : 'quote' },
36 stylesMap = { 'color' : 'color', 'size' : 'font-size' },
37 attributesMap = { 'url' : 'href', 'email' : 'mailhref', 'quote': 'cite', 'list' : 'listType' };
38
39 // List of block-like tags.
40 var dtd = CKEDITOR.dtd,
41 blockLikeTags = CKEDITOR.tools.extend( { table:1 }, dtd.$block, dtd.$listItem, dtd.$tableContent, dtd.$list );
42
43 var semicolonFixRegex = /\s*(?:;\s*|$)/;
44 function serializeStyleText( stylesObject )
45 {
46 var styleText = '';
47 for ( var style in stylesObject )
48 {
49 var styleVal = stylesObject[ style ],
50 text = ( style + ':' + styleVal ).replace( semicolonFixRegex, ';' );
51
52 styleText += text;
53 }
54 return styleText;
55 }
56
57 function parseStyleText( styleText )
58 {
59 var retval = {};
60 ( styleText || '' )
61 .replace( /"/g, '"' )
62 .replace( /\s*([^ :;]+)\s*:\s*([^;]+)\s*(?=;|$)/g, function( match, name, value )
63 {
64 retval[ name.toLowerCase() ] = value;
65 } );
66 return retval;
67 }
68
69 function RGBToHex( cssStyle )
70 {
71 return cssStyle.replace( /(?:rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\))/gi, function( match, red, green, blue )
72 {
73 red = parseInt( red, 10 ).toString( 16 );
74 green = parseInt( green, 10 ).toString( 16 );
75 blue = parseInt( blue, 10 ).toString( 16 );
76 var color = [red, green, blue] ;
77
78 // Add padding zeros if the hex value is less than 0x10.
79 for ( var i = 0 ; i < color.length ; i++ )
80 color[i] = String( '0' + color[i] ).slice( -2 ) ;
81
82 return '#' + color.join( '' ) ;
83 });
84 }
85
86 // Maintain the map of smiley-to-description.
87 var smileyMap = {"smiley":":)","sad":":(","wink":";)","laugh":":D","cheeky":":P","blush":":*)","surprise":":-o","indecision":":|","angry":">:(","angel":"o:)","cool":"8-)","devil":">:-)","crying":";(","kiss":":-*" },
88 smileyReverseMap = {},
89 smileyRegExp = [];
90
91 // Build regexp for the list of smiley text.
92 for ( var i in smileyMap )
93 {
94 smileyReverseMap[ smileyMap[ i ] ] = i;
95 smileyRegExp.push( smileyMap[ i ].replace( /\(|\)|\:|\/|\*|\-|\|/g, function( match ) { return '\\' + match; } ) );
96 }
97
98 smileyRegExp = new RegExp( smileyRegExp.join( '|' ), 'g' );
99
100 var decodeHtml = ( function ()
101 {
102 var regex = [],
103 entities =
104 {
105 nbsp : '\u00A0', // IE | FF
106 shy : '\u00AD', // IE
107 gt : '\u003E', // IE | FF | -- | Opera
108 lt : '\u003C' // IE | FF | Safari | Opera
109 };
110
111 for ( var entity in entities )
112 regex.push( entity );
113
114 regex = new RegExp( '&(' + regex.join( '|' ) + ');', 'g' );
115
116 return function( html )
117 {
118 return html.replace( regex, function( match, entity )
119 {
120 return entities[ entity ];
121 });
122 };
123 })();
124
125 CKEDITOR.BBCodeParser = function()
126 {
127 this._ =
128 {
129 bbcPartsRegex : /(?:\[([^\/\]=]*?)(?:=([^\]]*?))?\])|(?:\[\/([a-z]{1,16})\])/ig
130 };
131 };
132
133 CKEDITOR.BBCodeParser.prototype =
134 {
135 parse : function( bbcode )
136 {
137 var parts,
138 part,
139 lastIndex = 0;
140
141 while ( ( parts = this._.bbcPartsRegex.exec( bbcode ) ) )
142 {
143 var tagIndex = parts.index;
144 if ( tagIndex > lastIndex )
145 {
146 var text = bbcode.substring( lastIndex, tagIndex );
147 this.onText( text, 1 );
148 }
149
150 lastIndex = this._.bbcPartsRegex.lastIndex;
151
152 /*
153 "parts" is an array with the following items:
154 0 : The entire match for opening/closing tags and line-break;
155 1 : line-break;
156 2 : open of tag excludes option;
157 3 : tag option;
158 4 : close of tag;
159 */
160
161 part = ( parts[ 1 ] || parts[ 3 ] || '' ).toLowerCase();
162 // Unrecognized tags should be delivered as a simple text (#7860).
163 if ( part && !bbcodeMap[ part ] )
164 {
165 this.onText( parts[ 0 ] );
166 continue;
167 }
168
169 // Opening tag
170 if ( parts[ 1 ] )
171 {
172 var tagName = bbcodeMap[ part ],
173 attribs = {},
174 styles = {},
175 optionPart = parts[ 2 ];
176
177 if ( optionPart )
178 {
179 if ( part == 'list' )
180 {
181 if ( !isNaN( optionPart ) )
182 optionPart = 'decimal';
183 else if ( /^[a-z]+$/.test( optionPart ) )
184 optionPart = 'lower-alpha';
185 else if ( /^[A-Z]+$/.test( optionPart ) )
186 optionPart = 'upper-alpha';
187 }
188
189 if ( stylesMap[ part ] )
190 {
191 // Font size represents percentage.
192 if ( part == 'size' )
193 optionPart += '%';
194
195 styles[ stylesMap[ part ] ] = optionPart;
196 attribs.style = serializeStyleText( styles );
197 }
198 else if ( attributesMap[ part ] )
199 attribs[ attributesMap[ part ] ] = optionPart;
200 }
201
202 // Two special handling - image and email, protect them
203 // as "span" with an attribute marker.
204 if ( part == 'email' || part == 'img' )
205 attribs[ 'bbcode' ] = part;
206
207 this.onTagOpen( tagName, attribs, CKEDITOR.dtd.$empty[ tagName ] );
208 }
209 // Closing tag
210 else if ( parts[ 3 ] )
211 this.onTagClose( bbcodeMap[ part ] );
212 }
213
214 if ( bbcode.length > lastIndex )
215 this.onText( bbcode.substring( lastIndex, bbcode.length ), 1 );
216 }
217 };
218
219 /**
220 * Creates a {@link CKEDITOR.htmlParser.fragment} from an HTML string.
221 * @param {String} source The HTML to be parsed, filling the fragment.
222 * @param {Number} [fixForBody=false] Wrap body with specified element if needed.
223 * @returns CKEDITOR.htmlParser.fragment The fragment created.
224 * @example
225 * var fragment = CKEDITOR.htmlParser.fragment.fromHtml( '<b>Sample</b> Text' );
226 * alert( fragment.children[0].name ); "b"
227 * alert( fragment.children[1].value ); " Text"
228 */
229 CKEDITOR.htmlParser.fragment.fromBBCode = function( source )
230 {
231 var parser = new CKEDITOR.BBCodeParser(),
232 fragment = new CKEDITOR.htmlParser.fragment(),
233 pendingInline = [],
234 pendingBrs = 0,
235 currentNode = fragment,
236 returnPoint;
237
238 function checkPending( newTagName )
239 {
240 if ( pendingInline.length > 0 )
241 {
242 for ( var i = 0 ; i < pendingInline.length ; i++ )
243 {
244 var pendingElement = pendingInline[ i ],
245 pendingName = pendingElement.name,
246 pendingDtd = CKEDITOR.dtd[ pendingName ],
247 currentDtd = currentNode.name && CKEDITOR.dtd[ currentNode.name ];
248
249 if ( ( !currentDtd || currentDtd[ pendingName ] ) && ( !newTagName || !pendingDtd || pendingDtd[ newTagName ] || !CKEDITOR.dtd[ newTagName ] ) )
250 {
251 // Get a clone for the pending element.
252 pendingElement = pendingElement.clone();
253
254 // Add it to the current node and make it the current,
255 // so the new element will be added inside of it.
256 pendingElement.parent = currentNode;
257 currentNode = pendingElement;
258
259 // Remove the pending element (back the index by one
260 // to properly process the next entry).
261 pendingInline.splice( i, 1 );
262 i--;
263 }
264 }
265 }
266 }
267
268 function checkPendingBrs( tagName, closing )
269 {
270 var len = currentNode.children.length,
271 previous = len > 0 && currentNode.children[ len - 1 ],
272 lineBreakParent = !previous && BBCodeWriter.getRule( tagnameMap[ currentNode.name ], 'breakAfterOpen' ),
273 lineBreakPrevious = previous && previous.type == CKEDITOR.NODE_ELEMENT && BBCodeWriter.getRule( tagnameMap[ previous.name ], 'breakAfterClose' ),
274 lineBreakCurrent = tagName && BBCodeWriter.getRule( tagnameMap[ tagName ], closing ? 'breakBeforeClose' : 'breakBeforeOpen' );
275
276 if ( pendingBrs && ( lineBreakParent || lineBreakPrevious || lineBreakCurrent ) )
277 pendingBrs--;
278
279 // 1. Either we're at the end of block, where it requires us to compensate the br filler
280 // removing logic (from htmldataprocessor).
281 // 2. Or we're at the end of pseudo block, where it requires us to compensate
282 // the bogus br effect.
283 if ( pendingBrs && tagName in blockLikeTags )
284 pendingBrs++;
285
286 while ( pendingBrs && pendingBrs-- )
287 currentNode.children.push( previous = new CKEDITOR.htmlParser.element( 'br' ) );
288 }
289
290 function addElement( node, target )
291 {
292 checkPendingBrs( node.name, 1 );
293
294 target = target || currentNode || fragment;
295
296 var len = target.children.length,
297 previous = len > 0 && target.children[ len - 1 ] || null;
298
299 node.previous = previous;
300 node.parent = target;
301
302 target.children.push( node );
303
304 if ( node.returnPoint )
305 {
306 currentNode = node.returnPoint;
307 delete node.returnPoint;
308 }
309 }
310
311 parser.onTagOpen = function( tagName, attributes, selfClosing )
312 {
313 var element = new CKEDITOR.htmlParser.element( tagName, attributes );
314
315 // This is a tag to be removed if empty, so do not add it immediately.
316 if ( CKEDITOR.dtd.$removeEmpty[ tagName ] )
317 {
318 pendingInline.push( element );
319 return;
320 }
321
322 var currentName = currentNode.name;
323
324 var currentDtd = currentName
325 && ( CKEDITOR.dtd[ currentName ]
326 || ( currentNode._.isBlockLike ? CKEDITOR.dtd.div : CKEDITOR.dtd.span ) );
327
328 // If the element cannot be child of the current element.
329 if ( currentDtd && !currentDtd[ tagName ] )
330 {
331 var reApply = false,
332 addPoint; // New position to start adding nodes.
333
334 // If the element name is the same as the current element name,
335 // then just close the current one and append the new one to the
336 // parent. This situation usually happens with <p>, <li>, <dt> and
337 // <dd>, specially in IE. Do not enter in this if block in this case.
338 if ( tagName == currentName )
339 addElement( currentNode, currentNode.parent );
340 else if ( tagName in CKEDITOR.dtd.$listItem )
341 {
342 parser.onTagOpen( 'ul', {} );
343 addPoint = currentNode;
344 reApply = true;
345 }
346 else
347 {
348 addElement( currentNode, currentNode.parent );
349
350 // The current element is an inline element, which
351 // cannot hold the new one. Put it in the pending list,
352 // and try adding the new one after it.
353 pendingInline.unshift( currentNode );
354 reApply = true;
355 }
356
357 if ( addPoint )
358 currentNode = addPoint;
359 // Try adding it to the return point, or the parent element.
360 else
361 currentNode = currentNode.returnPoint || currentNode.parent;
362
363 if ( reApply )
364 {
365 parser.onTagOpen.apply( this, arguments );
366 return;
367 }
368 }
369
370 checkPending( tagName );
371 checkPendingBrs( tagName );
372
373 element.parent = currentNode;
374 element.returnPoint = returnPoint;
375 returnPoint = 0;
376
377 if ( element.isEmpty )
378 addElement( element );
379 else
380 currentNode = element;
381 };
382
383 parser.onTagClose = function( tagName )
384 {
385 // Check if there is any pending tag to be closed.
386 for ( var i = pendingInline.length - 1 ; i >= 0 ; i-- )
387 {
388 // If found, just remove it from the list.
389 if ( tagName == pendingInline[ i ].name )
390 {
391 pendingInline.splice( i, 1 );
392 return;
393 }
394 }
395
396 var pendingAdd = [],
397 newPendingInline = [],
398 candidate = currentNode;
399
400 while ( candidate.type && candidate.name != tagName )
401 {
402 // If this is an inline element, add it to the pending list, if we're
403 // really closing one of the parents element later, they will continue
404 // after it.
405 if ( !candidate._.isBlockLike )
406 newPendingInline.unshift( candidate );
407
408 // This node should be added to it's parent at this point. But,
409 // it should happen only if the closing tag is really closing
410 // one of the nodes. So, for now, we just cache it.
411 pendingAdd.push( candidate );
412
413 candidate = candidate.parent;
414 }
415
416 if ( candidate.type )
417 {
418 // Add all elements that have been found in the above loop.
419 for ( i = 0 ; i < pendingAdd.length ; i++ )
420 {
421 var node = pendingAdd[ i ];
422 addElement( node, node.parent );
423 }
424
425 currentNode = candidate;
426
427
428 addElement( candidate, candidate.parent );
429
430 // The parent should start receiving new nodes now, except if
431 // addElement changed the currentNode.
432 if ( candidate == currentNode )
433 currentNode = currentNode.parent;
434
435 pendingInline = pendingInline.concat( newPendingInline );
436 }
437 };
438
439 parser.onText = function( text )
440 {
441 var currentDtd = CKEDITOR.dtd[ currentNode.name ];
442 if ( !currentDtd || currentDtd[ '#' ] )
443 {
444 checkPendingBrs();
445 checkPending();
446
447 text.replace(/([\r\n])|[^\r\n]*/g, function( piece, lineBreak )
448 {
449 if ( lineBreak !== undefined && lineBreak.length )
450 pendingBrs++;
451 else if ( piece.length )
452 {
453 var lastIndex = 0;
454
455 // Create smiley from text emotion.
456 piece.replace( smileyRegExp, function( match, index )
457 {
458 addElement( new CKEDITOR.htmlParser.text( piece.substring( lastIndex, index ) ), currentNode );
459 addElement( new CKEDITOR.htmlParser.element( 'smiley', { 'desc': smileyReverseMap[ match ] } ), currentNode );
460 lastIndex = index + match.length;
461 });
462
463 if ( lastIndex != piece.length )
464 addElement( new CKEDITOR.htmlParser.text( piece.substring( lastIndex, piece.length ) ), currentNode );
465 }
466 });
467 }
468 };
469
470 // Parse it.
471 parser.parse( CKEDITOR.tools.htmlEncode( source ) );
472
473 // Close all hanging nodes.
474 while ( currentNode.type )
475 {
476 var parent = currentNode.parent,
477 node = currentNode;
478
479 addElement( node, parent );
480 currentNode = parent;
481 }
482
483 return fragment;
484 };
485
486 CKEDITOR.htmlParser.BBCodeWriter = CKEDITOR.tools.createClass(
487 {
488 $ : function()
489 {
490 this._ =
491 {
492 output : [],
493 rules : []
494 };
495
496 // List and list item.
497 this.setRules( 'list',
498 {
499 breakBeforeOpen : 1,
500 breakAfterOpen : 1,
501 breakBeforeClose : 1,
502 breakAfterClose : 1
503 } );
504
505 this.setRules( '*',
506 {
507 breakBeforeOpen : 1,
508 breakAfterOpen : 0,
509 breakBeforeClose : 1,
510 breakAfterClose : 0
511 } );
512
513 this.setRules( 'quote',
514 {
515 breakBeforeOpen : 1,
516 breakAfterOpen : 0,
517 breakBeforeClose : 0,
518 breakAfterClose : 1
519 } );
520 },
521
522 proto :
523 {
524 /**
525 * Sets formatting rules for a given tag. The possible rules are:
526 * <ul>
527 * <li><b>breakBeforeOpen</b>: break line before the opener tag for this element.</li>
528 * <li><b>breakAfterOpen</b>: break line after the opener tag for this element.</li>
529 * <li><b>breakBeforeClose</b>: break line before the closer tag for this element.</li>
530 * <li><b>breakAfterClose</b>: break line after the closer tag for this element.</li>
531 * </ul>
532 *
533 * All rules default to "false". Each call to the function overrides
534 * already present rules, leaving the undefined untouched.
535 *
536 * @param {String} tagName The tag name to which set the rules.
537 * @param {Object} rules An object containing the element rules.
538 * @example
539 * // Break line before and after "img" tags.
540 * writer.setRules( 'list',
541 * {
542 * breakBeforeOpen : true
543 * breakAfterOpen : true
544 * });
545 */
546 setRules : function( tagName, rules )
547 {
548 var currentRules = this._.rules[ tagName ];
549
550 if ( currentRules )
551 CKEDITOR.tools.extend( currentRules, rules, true );
552 else
553 this._.rules[ tagName ] = rules;
554 },
555
556 getRule : function( tagName, ruleName )
557 {
558 return this._.rules[ tagName ] && this._.rules[ tagName ][ ruleName ];
559 },
560
561 openTag : function( tag, attributes )
562 {
563 if ( tag in bbcodeMap )
564 {
565 if ( this.getRule( tag, 'breakBeforeOpen' ) )
566 this.lineBreak( 1 );
567
568 this.write( '[', tag );
569 var option = attributes.option;
570 option && this.write( '=', option );
571 this.write( ']' );
572
573 if ( this.getRule( tag, 'breakAfterOpen' ) )
574 this.lineBreak( 1 );
575 }
576 else if ( tag == 'br' )
577 this._.output.push( '\n' );
578 },
579
580 openTagClose : function() { },
581 attribute : function() { },
582
583 closeTag : function( tag )
584 {
585 if ( tag in bbcodeMap )
586 {
587 if ( this.getRule( tag, 'breakBeforeClose' ) )
588 this.lineBreak( 1 );
589
590 tag != '*' && this.write( '[/', tag, ']' );
591
592 if ( this.getRule( tag, 'breakAfterClose' ) )
593 this.lineBreak( 1 );
594 }
595 },
596
597 text : function( text )
598 {
599 this.write( text );
600 },
601
602 /**
603 * Writes a comment.
604 * @param {String} comment The comment text.
605 * @example
606 * // Writes "&lt;!-- My comment --&gt;".
607 * writer.comment( ' My comment ' );
608 */
609 comment : function() {},
610
611 /*
612 * Output line-break for formatting.
613 */
614 lineBreak : function()
615 {
616 // Avoid line break when:
617 // 1) Previous tag already put one.
618 // 2) We're at output start.
619 if ( !this._.hasLineBreak && this._.output.length )
620 {
621 this.write( '\n' );
622 this._.hasLineBreak = 1;
623 }
624 },
625
626 write : function()
627 {
628 this._.hasLineBreak = 0;
629 var data = Array.prototype.join.call( arguments, '' );
630 this._.output.push( data );
631 },
632
633 reset : function()
634 {
635 this._.output = [];
636 this._.hasLineBreak = 0;
637 },
638
639 getHtml : function( reset )
640 {
641 var bbcode = this._.output.join( '' );
642
643 if ( reset )
644 this.reset();
645
646 return decodeHtml ( bbcode );
647 }
648 }
649 });
650
651 var BBCodeWriter = new CKEDITOR.htmlParser.BBCodeWriter();
652
653 CKEDITOR.plugins.add( 'bbcode',
654 {
655 requires : [ 'htmldataprocessor', 'entities' ],
656 beforeInit : function( editor )
657 {
658 // Adapt some critical editor configuration for better support
659 // of BBCode environment.
660 var config = editor.config;
661 CKEDITOR.tools.extend( config,
662 {
663 enterMode : CKEDITOR.ENTER_BR,
664 basicEntities: false,
665 entities : false,
666 fillEmptyBlocks : false
667 }, true );
668 },
669 init : function( editor )
670 {
671 var config = editor.config;
672
673 function BBCodeToHtml( code )
674 {
675 var fragment = CKEDITOR.htmlParser.fragment.fromBBCode( code ),
676 writer = new CKEDITOR.htmlParser.basicWriter();
677
678 fragment.writeHtml( writer, dataFilter );
679 return writer.getHtml( true );
680 }
681
682 var dataFilter = new CKEDITOR.htmlParser.filter();
683 dataFilter.addRules(
684 {
685 elements :
686 {
687 'blockquote' : function( element )
688 {
689 var quoted = new CKEDITOR.htmlParser.element( 'div' );
690 quoted.children = element.children;
691 element.children = [ quoted ];
692 var citeText = element.attributes.cite;
693 if ( citeText )
694 {
695 var cite = new CKEDITOR.htmlParser.element( 'cite' );
696 cite.add( new CKEDITOR.htmlParser.text( citeText.replace( /^"|"$/g, '' ) ) );
697 delete element.attributes.cite;
698 element.children.unshift( cite );
699 }
700 },
701 'span' : function( element )
702 {
703 var bbcode;
704 if ( ( bbcode = element.attributes.bbcode ) )
705 {
706 if ( bbcode == 'img' )
707 {
708 element.name = 'img';
709 element.attributes.src = element.children[ 0 ].value;
710 element.children = [];
711 }
712 else if ( bbcode == 'email' )
713 {
714 element.name = 'a';
715 element.attributes.href = 'mailto:' + element.children[ 0 ].value;
716 }
717
718 delete element.attributes.bbcode;
719 }
720 },
721 'ol' : function ( element )
722 {
723 if ( element.attributes.listType )
724 {
725 if ( element.attributes.listType != 'decimal' )
726 element.attributes.style = 'list-style-type:' + element.attributes.listType;
727 }
728 else
729 element.name = 'ul';
730
731 delete element.attributes.listType;
732 },
733 a : function( element )
734 {
735 if ( !element.attributes.href )
736 element.attributes.href = element.children[ 0 ].value;
737 },
738 'smiley' : function( element )
739 {
740 element.name = 'img';
741
742 var description = element.attributes.desc,
743 image = config.smiley_images[ CKEDITOR.tools.indexOf( config.smiley_descriptions, description ) ],
744 src = CKEDITOR.tools.htmlEncode( config.smiley_path + image );
745
746 element.attributes =
747 {
748 src : src,
749 'data-cke-saved-src' : src,
750 title : description,
751 alt : description
752 };
753 }
754 }
755 } );
756
757 editor.dataProcessor.htmlFilter.addRules(
758 {
759 elements :
760 {
761 $ : function( element )
762 {
763 var attributes = element.attributes,
764 style = parseStyleText( attributes.style ),
765 value;
766
767 var tagName = element.name;
768 if ( tagName in convertMap )
769 tagName = convertMap[ tagName ];
770 else if ( tagName == 'span' )
771 {
772 if ( ( value = style.color ) )
773 {
774 tagName = 'color';
775 value = RGBToHex( value );
776 }
777 else if ( ( value = style[ 'font-size' ] ) )
778 {
779 var percentValue = value.match( /(\d+)%$/ );
780 if ( percentValue )
781 {
782 value = percentValue[ 1 ];
783 tagName = 'size';
784 }
785 }
786 }
787 else if ( tagName == 'ol' || tagName == 'ul' )
788 {
789 if ( ( value = style[ 'list-style-type'] ) )
790 {
791 switch ( value )
792 {
793 case 'lower-alpha':
794 value = 'a';
795 break;
796 case 'upper-alpha':
797 value = 'A';
798 break;
799 }
800 }
801 else if ( tagName == 'ol' )
802 value = 1;
803
804 tagName = 'list';
805 }
806 else if ( tagName == 'blockquote' )
807 {
808 try
809 {
810 var cite = element.children[ 0 ],
811 quoted = element.children[ 1 ],
812 citeText = cite.name == 'cite' && cite.children[ 0 ].value;
813
814 if ( citeText )
815 {
816 value = '"' + citeText + '"';
817 element.children = quoted.children;
818 }
819
820 }
821 catch( er )
822 {
823 }
824
825 tagName = 'quote';
826 }
827 else if ( tagName == 'a' )
828 {
829 if ( ( value = attributes.href ) )
830 {
831 if ( value.indexOf( 'mailto:' ) !== -1 )
832 {
833 tagName = 'email';
834 // [email] should have a single text child with email address.
835 element.children = [ new CKEDITOR.htmlParser.text( value.replace( 'mailto:', '' ) ) ];
836 value = '';
837 }
838 else
839 {
840 var singleton = element.children.length == 1 && element.children[ 0 ];
841 if ( singleton
842 && singleton.type == CKEDITOR.NODE_TEXT
843 && singleton.value == value )
844 value = '';
845
846 tagName = 'url';
847 }
848 }
849 }
850 else if ( tagName == 'img' )
851 {
852 element.isEmpty = 0;
853
854 // Translate smiley (image) to text emotion.
855 var src = attributes[ 'data-cke-saved-src' ];
856 if ( src && src.indexOf( editor.config.smiley_path ) != -1 )
857 return new CKEDITOR.htmlParser.text( smileyMap[ attributes.alt ] );
858 else
859 element.children = [ new CKEDITOR.htmlParser.text( src ) ];
860 }
861
862 element.name = tagName;
863 value && ( element.attributes.option = value );
864
865 return null;
866 },
867
868 // Remove any bogus br from the end of a pseudo block,
869 // e.g. <div>some text<br /><p>paragraph</p></div>
870 br : function( element )
871 {
872 var next = element.next;
873 if ( next && next.name in blockLikeTags )
874 return false;
875 }
876 }
877 }, 1 );
878
879 editor.dataProcessor.writer = BBCodeWriter;
880
881 editor.on( 'beforeSetMode', function( evt )
882 {
883 evt.removeListener();
884 var wysiwyg = editor._.modes[ 'wysiwyg' ];
885 wysiwyg.loadData = CKEDITOR.tools.override( wysiwyg.loadData, function( org )
886 {
887 return function( data )
888 {
889 return ( org.call( this, BBCodeToHtml( data ) ) );
890 };
891 } );
892 } );
893 },
894
895 afterInit : function( editor )
896 {
897 var filters;
898 if ( editor._.elementsPath )
899 {
900 // Eliminate irrelevant elements from displaying, e.g body and p.
901 if ( ( filters = editor._.elementsPath.filters ) )
902 filters.push( function( element )
903 {
904 var htmlName = element.getName(),
905 name = tagnameMap[ htmlName ] || false;
906
907 // Specialized anchor presents as email.
908 if ( name == 'link' && element.getAttribute( 'href' ).indexOf( 'mailto:' ) === 0 )
909 name = 'email';
910 // Styled span could be either size or color.
911 else if ( htmlName == 'span' )
912 {
913 if ( element.getStyle( 'font-size' ) )
914 name = 'size';
915 else if ( element.getStyle( 'color' ) )
916 name = 'color';
917 }
918 else if ( name == 'img' )
919 {
920 var src = element.data( 'cke-saved-src' );
921 if ( src && src.indexOf( editor.config.smiley_path ) === 0 )
922 name = 'smiley';
923 }
924
925 return name;
926 });
927 }
928 }
929 } );
930
931 })();