1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
16 * The Original Code is ChatZilla.
18 * The Initial Developer of the Original Code is
19 * New Dimensions Consulting, Inc.
20 * Portions created by the Initial Developer are Copyright (C) 1999
21 * the Initial Developer. All Rights Reserved.
24 * Robert Ginda, rginda@ndcico.com, original author
25 * Samuel Sieb, samuel@sieb.net, MIRC color codes
27 * Alternatively, the contents of this file may be used under the terms of
28 * either the GNU General Public License Version 2 or later (the "GPL"), or
29 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
39 * ***** END LICENSE BLOCK ***** */
41 /* This file contains the munger functions and rules used by ChatZilla.
42 * It's generally a bad idea to call munger functions inside ChatZilla for
43 * anything but munging (chat) output.
48 /* linkRE: the general URL linkifier regular expression:
50 * - start with whitespace, non-word, or begining-of-line
52 * - EITHER scheme (word + hyphen), colon, then lots of non-whitespace
53 * - OR "www" followed by at least 2 sets of:
54 * - "." plus some non-whitespace, non-"." characters
55 * - must end match with a word-break
56 * - include a "/" or "=" beyond break if present
57 * - end with whitespace, non-word, or end-of-line
60 /(?:\s|\W|^)((?:(\w[\w-]+):[^\s]+|www(\.[^.\s]+){2,})\b[\/=]?)(?=\s|\W|$)/;
62 const LOW_PRIORITY = 5;
63 const NORMAL_PRIORITY = 10;
64 const HIGH_PRIORITY = 15;
65 const HIGHER_PRIORITY = 20;
67 var munger = client.munger = new CMunger(insertHyphenatedWord);
68 // Special internal munger!
69 munger.addRule(".inline-buttons", /(\[\[.*?\]\])/, insertInlineButton,
71 munger.addRule("quote", /(``|'')/, insertQuote,
72 NORMAL_PRIORITY, NORMAL_PRIORITY);
73 munger.addRule("bold", /(?:[\s(\[]|^)(\*[^*()]*\*)(?:[\s\]).,;!\?]|$)/,
74 "chatzilla-bold", NORMAL_PRIORITY, NORMAL_PRIORITY);
75 munger.addRule("underline", /(?:[\s(\[]|^)(\_[^_()]*\_)(?:[\s\]).,;!\?]|$)/,
76 "chatzilla-underline", NORMAL_PRIORITY, NORMAL_PRIORITY);
77 munger.addRule("italic", /(?:\s|^)(\/[^\/()]*\/)(?:[\s.,]|$)/,
78 "chatzilla-italic", NORMAL_PRIORITY, NORMAL_PRIORITY);
79 /* allow () chars inside |code()| blocks */
80 munger.addRule("teletype", /(?:\s|^)(\|[^|]*\|)(?:[\s.,]|$)/,
81 "chatzilla-teletype", NORMAL_PRIORITY, NORMAL_PRIORITY);
82 munger.addRule(".mirc-colors", /(\x03((\d{1,2})(,\d{1,2}|)|))/,
83 mircChangeColor, NORMAL_PRIORITY, NORMAL_PRIORITY);
84 munger.addRule(".mirc-bold", /(\x02)/, mircToggleBold,
85 NORMAL_PRIORITY, NORMAL_PRIORITY);
86 munger.addRule(".mirc-underline", /(\x1f)/, mircToggleUnder,
87 NORMAL_PRIORITY, NORMAL_PRIORITY);
88 munger.addRule(".mirc-color-reset", /(\x0f)/, mircResetColor,
89 NORMAL_PRIORITY, NORMAL_PRIORITY);
90 munger.addRule(".mirc-reverse", /(\x16)/, mircReverseColor,
91 NORMAL_PRIORITY, NORMAL_PRIORITY);
92 munger.addRule("ctrl-char", /([\x01-\x1f])/, showCtrlChar,
93 NORMAL_PRIORITY, NORMAL_PRIORITY);
94 munger.addRule("link", client.linkRE, insertLink, NORMAL_PRIORITY, HIGH_PRIORITY);
96 // This has a higher starting priority so as to get it to match before the
97 // normal link, which won't know about mailto and then fail.
98 munger.addRule(".mailto",
99 /(?:\s|\W|^)((mailto:)?[^:;\\<>\[\]()\'\"\s\u201d]+@[^.<>\[\]()\'\"\s\u201d]+\.[^<>\[\]()\'\"\s\u201d]+)/i,
100 insertMailToLink, NORMAL_PRIORITY, HIGHER_PRIORITY, false);
101 munger.addRule("bugzilla-link",
102 /(?:\s|\W|^)(bug\s+(?:#?\d+|#[^\s,]{1,20}))/i,
103 insertBugzillaLink, NORMAL_PRIORITY, NORMAL_PRIORITY);
104 munger.addRule("channel-link",
105 /(?:\s|\W|^)[@%+]?(#[^<>,\[\](){}\"\s\u201d]*[^:,.<>\[\](){}\'\"\s\u201d])/i,
106 insertChannelLink, NORMAL_PRIORITY, NORMAL_PRIORITY);
107 munger.addRule("talkback-link", /(?:\W|^)(TB\d{8,}[A-Z]?)(?:\W|$)/,
108 insertTalkbackLink, NORMAL_PRIORITY, NORMAL_PRIORITY);
110 munger.addRule("face",
111 /((^|\s)(?:[>]?[B8=:;(xX][~']?[-^v"]?(?:[)|(PpSs0oO\?\[\]\/\\]|D+)|>[-^v]?\)|[oO9][._][oO9])(\s|$))/,
112 insertSmiley, NORMAL_PRIORITY, NORMAL_PRIORITY);
113 munger.addRule("rheet", /(?:\s|\W|^)(rhee+t\!*)(?:\s|$)/i, insertRheet, 10, 10);
114 munger.addRule("word-hyphenator",
115 new RegExp ("(\\S{" + client.MAX_WORD_DISPLAY + ",})"),
116 insertHyphenatedWord, LOW_PRIORITY, NORMAL_PRIORITY);
118 client.enableColors = client.prefs["munger.colorCodes"];
119 var branch = client.prefManager.prefBranch;
120 for (var entry in munger.entries)
122 if (!isinstance(munger.entries[entry], Object))
125 for (var rule in munger.entries[entry])
132 munger.entries[entry][rule].enabled =
133 branch.getBoolPref("munger." + rule);
143 function insertLink(matchText, containerTag, data, mungerEntry)
149 ary = matchText.match(/([.,?]+)$/);
152 linkText = RegExp.leftContext;
157 linkText = matchText;
160 var ary = linkText.match(/^(\w[\w-]+):/);
163 if (!("schemes" in client))
165 var pfx = "@mozilla.org/network/protocol;1?name=";
166 var len = pfx.length;
168 client.schemes = new Object();
169 for (var c in Components.classes)
171 if (c.indexOf(pfx) == 0)
172 client.schemes[c.substr(len)] = true;
176 if (!(ary[1] in client.schemes))
178 mungerEntry.enabled = false;
179 client.munger.munge(matchText, containerTag, data);
180 mungerEntry.enabled = true;
188 href = "http://" + linkText;
191 /* This gives callers to the munger control over URLs being logged; the
192 * channel topic munger uses this, as well as the "is important" checker.
193 * If either of |dontLogURLs| or |noStateChange| is present and true, we
196 if ((!("dontLogURLs" in data) || !data.dontLogURLs) &&
197 (!("noStateChange" in data) || !data.noStateChange))
199 var max = client.prefs["urls.store.max"];
200 if (client.prefs["urls.list"].unshift(href) > max)
201 client.prefs["urls.list"].pop();
202 client.prefs["urls.list"].update();
205 var anchor = document.createElementNS("http://www.w3.org/1999/xhtml",
207 var mircRE = /\x1f|\x02|\x0f|\x16|\x03([0-9]{1,2}(,[0-9]{1,2})?)?/g;
208 anchor.setAttribute("href", href.replace(mircRE, ""));
210 // Carry over formatting.
211 var otherFormatting = calcClass(data);
213 anchor.setAttribute("class", "chatzilla-link " + otherFormatting);
215 anchor.setAttribute("class", "chatzilla-link");
217 anchor.setAttribute("target", "_content");
218 mungerEntry.enabled = false;
220 client.munger.munge(linkText, anchor, data);
221 mungerEntry.enabled = true;
223 containerTag.appendChild(anchor);
225 insertHyphenatedWord(trailing, containerTag, data);
229 function insertMailToLink(matchText, containerTag, eventData, mungerEntry)
231 if (("inLink" in eventData) && eventData.inLink)
233 mungerEntry.enabled = false;
234 client.munger.munge(matchText, containerTag, eventData);
235 mungerEntry.enabled = true;
241 if (matchText.indexOf("mailto:") != 0)
242 href = "mailto:" + matchText;
246 var anchor = document.createElementNS("http://www.w3.org/1999/xhtml",
248 var mircRE = /\x1f|\x02|\x0f|\x16|\x03([0-9]{1,2}(,[0-9]{1,2})?)?/g;
249 anchor.setAttribute("href", href.replace(mircRE, ""));
251 // Carry over formatting.
252 var otherFormatting = calcClass(eventData);
254 anchor.setAttribute("class", "chatzilla-link " + otherFormatting);
256 anchor.setAttribute("class", "chatzilla-link");
258 //anchor.setAttribute ("target", "_content");
259 mungerEntry.enabled = false;
260 eventData.inLink = true;
261 client.munger.munge(matchText, anchor, eventData);
262 mungerEntry.enabled = true;
263 delete eventData.inLink;
264 containerTag.appendChild(anchor);
268 function insertChannelLink(matchText, containerTag, eventData, mungerEntry)
270 if (("inLink" in eventData) && eventData.inLink)
272 mungerEntry.enabled = false;
273 client.munger.munge(matchText, containerTag, eventData);
274 mungerEntry.enabled = true;
279 /^#(include|error|define|if|ifdef|else|elsif|endif|\d+)$/i;
281 if (!("network" in eventData) || !eventData.network ||
282 matchText.search(bogusChannels) != -1)
284 containerTag.appendChild(document.createTextNode(matchText));
288 var encodedMatchText = fromUnicode(matchText, eventData.sourceObject);
289 var anchor = document.createElementNS("http://www.w3.org/1999/xhtml",
291 anchor.setAttribute("href", eventData.network.getURL() +
292 ecmaEscape(encodedMatchText));
294 // Carry over formatting.
295 var otherFormatting = calcClass(eventData);
297 anchor.setAttribute("class", "chatzilla-link " + otherFormatting);
299 anchor.setAttribute("class", "chatzilla-link");
301 mungerEntry.enabled = false;
302 eventData.inLink = true;
303 client.munger.munge(matchText, anchor, eventData);
304 mungerEntry.enabled = true;
305 delete eventData.inLink;
306 containerTag.appendChild(anchor);
309 function insertTalkbackLink(matchText, containerTag, eventData, mungerEntry)
311 if (("inLink" in eventData) && eventData.inLink)
313 mungerEntry.enabled = false;
314 client.munger.munge(matchText, containerTag, eventData);
315 mungerEntry.enabled = true;
319 var anchor = document.createElementNS("http://www.w3.org/1999/xhtml",
322 anchor.setAttribute("href", "http://talkback-public.mozilla.org/" +
323 "search/start.jsp?search=2&type=iid&id=" + matchText);
325 // Carry over formatting.
326 var otherFormatting = calcClass(eventData);
328 anchor.setAttribute("class", "chatzilla-link " + otherFormatting);
330 anchor.setAttribute("class", "chatzilla-link");
332 mungerEntry.enabled = false;
333 client.munger.munge(matchText, anchor, eventData);
334 mungerEntry.enabled = true;
335 containerTag.appendChild(anchor);
338 function insertBugzillaLink (matchText, containerTag, eventData, mungerEntry)
340 if (("inLink" in eventData) && eventData.inLink)
342 mungerEntry.enabled = false;
343 client.munger.munge(matchText, containerTag, eventData);
344 mungerEntry.enabled = true;
349 if (eventData.channel)
350 bugURL = eventData.channel.prefs["bugURL"];
351 else if (eventData.network)
352 bugURL = eventData.network.prefs["bugURL"];
354 bugURL = client.prefs["bugURL"];
356 if (bugURL.length > 0)
358 var idOrAlias = matchText.match(/bug\s+#?(\d+|[^\s,]{1,20})/i)[1];
359 var anchor = document.createElementNS("http://www.w3.org/1999/xhtml",
362 anchor.setAttribute("href", bugURL.replace("%s", idOrAlias));
363 // Carry over formatting.
364 var otherFormatting = calcClass(eventData);
366 anchor.setAttribute("class", "chatzilla-link " + otherFormatting);
368 anchor.setAttribute("class", "chatzilla-link");
370 anchor.setAttribute("target", "_content");
371 mungerEntry.enabled = false;
372 eventData.inLink = true;
373 client.munger.munge(matchText, anchor, eventData);
374 mungerEntry.enabled = true;
375 delete eventData.inLink;
376 containerTag.appendChild(anchor);
380 mungerEntry.enabled = false;
381 client.munger.munge(matchText, containerTag, eventData);
382 mungerEntry.enabled = true;
386 function insertRheet(matchText, containerTag, eventData, mungerEntry)
388 if (("inLink" in eventData) && eventData.inLink)
390 mungerEntry.enabled = false;
391 client.munger.munge(matchText, containerTag, eventData);
392 mungerEntry.enabled = true;
396 var anchor = document.createElementNS("http://www.w3.org/1999/xhtml",
398 anchor.setAttribute("href",
399 "http://ftp.mozilla.org/pub/mozilla.org/mozilla/libraries/bonus-tracks/rheet.wav");
400 anchor.setAttribute("class", "chatzilla-rheet chatzilla-link");
401 //anchor.setAttribute ("target", "_content");
402 insertHyphenatedWord(matchText, anchor, eventData);
403 containerTag.appendChild(anchor);
406 function insertQuote (matchText, containerTag)
408 if (matchText == "``")
409 containerTag.appendChild(document.createTextNode("\u201c"));
411 containerTag.appendChild(document.createTextNode("\u201d"));
414 function insertSmiley(emoticon, containerTag)
418 if (emoticon.search(/\>[-^v]?\)/) != -1)
420 else if (emoticon.search(/\>[=:;][-^v]?[(|]/) != -1)
422 else if (emoticon.search(/[=:;][-^v]?[Ss\\\/]/) != -1)
423 type = "face-confused";
424 else if (emoticon.search(/[B8][-^v]?[)\]]/) != -1)
426 else if (emoticon.search(/[=:;][~'][-^v]?\(/) != -1)
428 else if (emoticon.search(/o[._]O/) != -1)
430 else if (emoticon.search(/O[._]o/) != -1)
431 type = "face-dizzy-back";
432 else if (emoticon.search(/o[._]o|O[._]O/) != -1)
434 else if (emoticon.search(/\>[=:;][-^v]?D/) != -1)
436 else if (emoticon.search(/[=:;][-^v]?DD/) != -1)
438 else if (emoticon.search(/[=:;][-^v]?D/) != -1)
440 else if (emoticon.search(/\([-^v]?D|[xX][-^v]?D/) != -1)
442 else if (emoticon.search(/[=:;][-^v]?\|/) != -1)
443 type = "face-normal";
444 else if (emoticon.search(/[=:;][-^v]?\?/) != -1)
445 type = "face-question";
446 else if (emoticon.search(/[=:;]"[)\]]/) != -1)
448 else if (emoticon.search(/9[._]9/) != -1)
449 type = "face-rolleyes";
450 else if (emoticon.search(/[=:;][-^v]?[(\[]/) != -1)
452 else if (emoticon.search(/[=:][-^v]?[)\]]/) != -1)
454 else if (emoticon.search(/[=:;][-^v]?[0oO]/) != -1)
455 type = "face-surprised";
456 else if (emoticon.search(/[=:;][-^v]?[pP]/) != -1)
457 type = "face-tongue";
458 else if (emoticon.search(/;[-^v]?[)\]]/) != -1)
463 // We didn't actually match anything, so it'll be a too-generic match
464 // from the munger RegExp.
465 containerTag.appendChild(document.createTextNode(emoticon));
469 var span = document.createElementNS ("http://www.w3.org/1999/xhtml",
472 /* create a span to hold the emoticon text */
473 span.setAttribute ("class", "chatzilla-emote-txt");
474 span.setAttribute ("type", type);
475 span.appendChild (document.createTextNode (emoticon));
476 containerTag.appendChild (span);
478 /* create an empty span after the text. this span will have an image added
479 * after it with a chatzilla-emote:after css rule. using
480 * chatzilla-emote-txt:after is not good enough because it does not allow us
481 * to turn off the emoticon text, but keep the image. ie.
482 * chatzilla-emote-txt { display: none; } turns off
483 * chatzilla-emote-txt:after as well.*/
484 span = document.createElementNS ("http://www.w3.org/1999/xhtml",
486 span.setAttribute ("class", "chatzilla-emote");
487 span.setAttribute ("type", type);
488 span.setAttribute ("title", emoticon);
489 containerTag.appendChild (span);
493 function mircChangeColor (colorInfo, containerTag, data)
495 /* If colors are disabled, the caller doesn't want colors specifically, or
496 * the caller doesn't want any state-changing effects, we drop out.
498 if (!client.enableColors ||
499 (("noMircColors" in data) && data.noMircColors) ||
500 (("noStateChange" in data) && data.noStateChange))
505 var ary = colorInfo.match (/.(\d{1,2}|)(,(\d{1,2})|)/);
507 // Do we have a BG color specified...?
508 if (!arrayHasElementAt(ary, 1) || !ary[1])
511 delete data.currFgColor;
512 delete data.currBgColor;
516 var fgColor = String(Number(ary[1]) % 16);
518 if (fgColor.length == 1)
519 data.currFgColor = "0" + fgColor;
521 data.currFgColor = fgColor;
523 // Do we have a BG color specified...?
524 if (arrayHasElementAt(ary, 3) && ary[3])
526 var bgColor = String(Number(ary[3]) % 16);
528 if (bgColor.length == 1)
529 data.currBgColor = "0" + bgColor;
531 data.currBgColor = bgColor;
534 data.hasColorInfo = true;
537 function mircToggleBold (colorInfo, containerTag, data)
539 if (!client.enableColors ||
540 (("noMircColors" in data) && data.noMircColors) ||
541 (("noStateChange" in data) && data.noStateChange))
546 if ("isBold" in data)
550 data.hasColorInfo = true;
553 function mircToggleUnder (colorInfo, containerTag, data)
555 if (!client.enableColors ||
556 (("noMircColors" in data) && data.noMircColors) ||
557 (("noStateChange" in data) && data.noStateChange))
562 if ("isUnderline" in data)
563 delete data.isUnderline;
565 data.isUnderline = true;
566 data.hasColorInfo = true;
569 function mircResetColor (text, containerTag, data)
571 if (!client.enableColors ||
572 (("noMircColors" in data) && data.noMircColors) ||
573 (("noStateChange" in data) && data.noStateChange) ||
574 !("hasColorInfo" in data))
579 delete data.currFgColor;
580 delete data.currBgColor;
582 delete data.isUnderline;
583 delete data.hasColorInfo;
586 function mircReverseColor (text, containerTag, data)
588 if (!client.enableColors ||
589 (("noMircColors" in data) && data.noMircColors) ||
590 (("noStateChange" in data) && data.noStateChange))
595 var tempColor = ("currFgColor" in data ? data.currFgColor : "");
597 if ("currBgColor" in data)
598 data.currFgColor = data.currBgColor;
600 delete data.currFgColor;
602 data.currBgColor = tempColor;
604 delete data.currBgColor;
605 data.hasColorInfo = true;
608 function showCtrlChar(c, containerTag)
610 var span = document.createElementNS ("http://www.w3.org/1999/xhtml",
612 span.setAttribute ("class", "chatzilla-control-char");
615 containerTag.appendChild(document.createTextNode(c));
619 var ctrlStr = c.charCodeAt(0).toString(16);
620 if (ctrlStr.length < 2)
621 ctrlStr = "0" + ctrlStr;
622 span.appendChild (document.createTextNode ("0x" + ctrlStr));
623 containerTag.appendChild (span);
626 function insertHyphenatedWord(longWord, containerTag, data)
628 var wordParts = splitLongWord(longWord, client.MAX_WORD_DISPLAY);
630 if (data && ("hasColorInfo" in data))
631 newClass = calcClass(data);
633 for (var i = 0; i < wordParts.length; ++i)
637 var wbr = document.createElementNS("http://www.w3.org/1999/xhtml",
639 containerTag.appendChild(wbr);
644 var newTag = document.createElementNS(NS_XHTML, "html:span");
645 newTag.setAttribute("class", newClass);
646 newTag.appendChild(document.createTextNode(wordParts[i]));
647 containerTag.appendChild(newTag);
651 delete data.hasColorInfo;
652 containerTag.appendChild(document.createTextNode(wordParts[i]));
657 function insertInlineButton(text, containerTag, data)
659 var ary = text.match(/\[\[([^\]]+)\]\[([^\]]+)\]\[([^\]]+)\]\]/);
663 containerTag.appendChild(document.createTextNode(text));
669 var command = ary[3];
671 var link = document.createElementNS("http://www.w3.org/1999/xhtml", "a");
672 link.setAttribute("href", "x-cz-command:" + encodeURI(command));
673 link.setAttribute("title", title);
674 link.setAttribute("class", "chatzilla-link");
675 link.appendChild(document.createTextNode(label));
677 containerTag.appendChild(document.createTextNode("["));
678 containerTag.appendChild(link);
679 containerTag.appendChild(document.createTextNode("]"));
682 function calcClass(data)
685 if ("hasColorInfo" in data)
687 if ("currFgColor" in data)
688 className += " chatzilla-fg" + data.currFgColor;
689 if ("currBgColor" in data)
690 className += " chatzilla-bg" + data.currBgColor;
691 if ("isBold" in data)
692 className += " chatzilla-bold";
693 if ("isUnderline" in data)
694 className += " chatzilla-underline";