home *** CD-ROM | disk | FTP | other *** search
/ ftp.swcp.com / ftp.swcp.com.zip / ftp.swcp.com / mac / mozilla-macos9-1.3.1.sea.bin / Mozilla1.3.1 / Chrome / comm.jar / content / editor / editorUtilities.js < prev    next >
Text File  |  2003-06-08  |  28KB  |  1,097 lines

  1. /*
  2.  * The contents of this file are subject to the Netscape Public
  3.  * License Version 1.1 (the "License"); you may not use this file
  4.  * except in compliance with the License. You may obtain a copy of
  5.  * the License at http://www.mozilla.org/NPL/
  6.  *
  7.  * Software distributed under the License is distributed on an "AS
  8.  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
  9.  * implied. See the License for the specific language governing
  10.  * rights and limitations under the License.
  11.  *
  12.  * The Original Code is Mozilla Communicator client code, released
  13.  * March 31, 1998.
  14.  *
  15.  * The Initial Developer of the Original Code is Netscape
  16.  * Communications Corporation. Portions created by Netscape are
  17.  * Copyright (C) 1998-1999 Netscape Communications Corporation. All
  18.  * Rights Reserved.
  19.  *
  20.  * Contributor(s):
  21.  *   Pete Collins
  22.  *   Brian King
  23.  *   Daniel Glazman <glazman@netscape.com>
  24.  */
  25.  
  26. /**** NAMESPACES ****/
  27. const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
  28.  
  29. // Each editor window must include this file
  30. // Variables  shared by all dialogs:
  31.  
  32. // Object to attach commonly-used widgets (all dialogs should use this)
  33. var gDialog = {};
  34.  
  35. // Bummer! Can't get at enums from nsIDocumentEncoder.h
  36. // http://lxr.mozilla.org/seamonkey/source/content/base/public/nsIDocumentEncoder.h#111
  37. var gStringBundle;
  38. var gIOService;
  39. var gPrefsService;
  40. var gPrefsBranch;
  41. var gFilePickerDirectory;
  42.  
  43. var gOS = "";
  44. const gWin = "Win";
  45. const gUNIX = "UNIX";
  46. const gMac = "Mac";
  47.  
  48. const kWebComposerWindowID = "editorWindow";
  49. const kMailComposerWindowID = "msgcomposeWindow";
  50.  
  51. var gIsHTMLEditor;
  52. /************* Message dialogs ***************/
  53.  
  54. function AlertWithTitle(title, message, parentWindow)
  55. {
  56.   if (!parentWindow)
  57.     parentWindow = window;
  58.  
  59.   var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService();
  60.   promptService = promptService.QueryInterface(Components.interfaces.nsIPromptService);
  61.  
  62.   if (promptService)
  63.   {
  64.     if (!title)
  65.       title = GetString("Alert");
  66.  
  67.     // "window" is the calling dialog window
  68.     promptService.alert(parentWindow, title, message);
  69.   }
  70. }
  71.  
  72. // Optional: Caller may supply text to substitue for "Ok" and/or "Cancel"
  73. function ConfirmWithTitle(title, message, okButtonText, cancelButtonText)
  74. {
  75.   var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"].getService();
  76.   promptService = promptService.QueryInterface(Components.interfaces.nsIPromptService);
  77.  
  78.   if (promptService)
  79.   {
  80.     var okFlag = okButtonText ? promptService.BUTTON_TITLE_IS_STRING : promptService.BUTTON_TITLE_OK;
  81.     var cancelFlag = cancelButtonText ? promptService.BUTTON_TITLE_IS_STRING : promptService.BUTTON_TITLE_CANCEL;
  82.  
  83.     return promptService.confirmEx(window, title, message,
  84.                             (okFlag * promptService.BUTTON_POS_0) +
  85.                             (cancelFlag * promptService.BUTTON_POS_1),
  86.                             okButtonText, cancelButtonText, null, null, {value:0}) == 0;
  87.   }
  88.   return false;
  89. }
  90.  
  91. /************* String Utilities ***************/
  92.  
  93. function GetString(name)
  94. {
  95.   if (!gStringBundle)
  96.   {
  97.     try {
  98.       var strBundleService =
  99.           Components.classes["@mozilla.org/intl/stringbundle;1"].getService(); 
  100.       strBundleService = 
  101.           strBundleService.QueryInterface(Components.interfaces.nsIStringBundleService);
  102.  
  103.       gStringBundle = strBundleService.createBundle("chrome://editor/locale/editor.properties"); 
  104.  
  105.     } catch (ex) {}
  106.   }
  107.   if (gStringBundle)
  108.   {
  109.     try {
  110.       return gStringBundle.GetStringFromName(name);
  111.     } catch (e) {}
  112.   }
  113.   return null;
  114. }
  115.  
  116. function TrimStringLeft(string)
  117. {
  118.   if(!string) return "";
  119.   return string.replace(/^\s+/, "");
  120. }
  121.  
  122. function TrimStringRight(string)
  123. {
  124.   if (!string) return "";
  125.   return string.replace(/\s+$/, '');
  126. }
  127.  
  128. // Remove whitespace from both ends of a string
  129. function TrimString(string)
  130. {
  131.   if (!string) return "";
  132.   return string.replace(/(^\s+)|(\s+$)/g, '')
  133. }
  134.  
  135. function IsWhitespace(string)
  136. {
  137.   return /^\s/.test(string);
  138. }
  139.  
  140. function TruncateStringAtWordEnd(string, maxLength, addEllipses)
  141. {
  142.   // Return empty if string is null, undefined, or the empty string
  143.   if (!string)
  144.     return "";
  145.  
  146.   // We assume they probably don't want whitespace at the beginning
  147.   string = string.replace(/^\s+/, '');
  148.   if (string.length <= maxLength)
  149.     return string;
  150.  
  151.   // We need to truncate the string to maxLength or fewer chars
  152.   if (addEllipses)
  153.     maxLength -= 3;
  154.   string = string.replace(RegExp("(.{0," + maxLength + "})\\s.*"), "$1")
  155.  
  156.   if (string.length > maxLength)
  157.     string = string.slice(0, maxLength);
  158.  
  159.   if (addEllipses)
  160.     string += "...";
  161.   return string;
  162. }
  163.  
  164. // Replace all whitespace characters with supplied character
  165. // E.g.: Use charReplace = " ", to "unwrap" the string by removing line-end chars
  166. //       Use charReplace = "_" when you don't want spaces (like in a URL)
  167. function ReplaceWhitespace(string, charReplace)
  168. {
  169.   return string.replace(/(^\s+)|(\s+$)/g,'').replace(/\s+/g,charReplace)
  170. }
  171.  
  172. // Replace whitespace with "_" and allow only HTML CDATA
  173. //   characters: "a"-"z","A"-"Z","0"-"9", "_", ":", "-", ".",
  174. //   and characters above ASCII 127
  175. function ConvertToCDATAString(string)
  176. {
  177.   return string.replace(/\s+/g,"_").replace(/[^a-zA-Z0-9_\.\-\:\u0080-\uFFFF]+/g,'');
  178. }
  179.  
  180. function GetSelectionAsText()
  181. {
  182.   try {
  183.     return GetCurrentEditor().outputToString("text/plain", 1); // OutputSelectionOnly
  184.   } catch (e) {}
  185.  
  186.   return "";
  187. }
  188.  
  189.  
  190. /************* Get Current Editor and associated interfaces or info ***************/
  191. const nsIPlaintextEditor = Components.interfaces.nsIPlaintextEditor;
  192. const nsIHTMLEditor = Components.interfaces.nsIHTMLEditor;
  193. const nsITableEditor = Components.interfaces.nsITableEditor;
  194. const nsIEditorStyleSheets = Components.interfaces.nsIEditorStyleSheets;
  195. const nsIEditingSession = Components.interfaces.nsIEditingSession;
  196.  
  197. function GetCurrentEditor()
  198. {
  199.   // Get the active editor from the <editor> tag
  200.   // XXX This will probably change if we support > 1 editor in main Composer window
  201.   //      (e.g. a plaintext editor for HTMLSource)
  202.  
  203.   // For dialogs: Search up parent chain to find top window with editor
  204.   var editor;
  205.   try {
  206.     var editorElement = GetCurrentEditorElement();
  207.     editor = editorElement.getEditor(editorElement.contentWindow);
  208.  
  209.     // Do QIs now so editor users won't have to figure out which interface to use
  210.     // Using "instanceof" does the QI for us.
  211.     editor instanceof Components.interfaces.nsIPlaintextEditor;
  212.     editor instanceof Components.interfaces.nsIHTMLEditor;
  213.   } catch (e) { dump (e)+"\n"; }
  214.  
  215.   return editor;
  216. }
  217.  
  218. function GetCurrentTableEditor()
  219. {
  220.   var editor = GetCurrentEditor();
  221.   return (editor && (editor instanceof nsITableEditor)) ? editor : null;
  222. }
  223.  
  224. function GetCurrentEditorElement()
  225. {
  226.   var tmpWindow = window;
  227.   
  228.   do {
  229.     // Get the <editor> element(s)
  230.     var editorList = tmpWindow.document.getElementsByTagName("editor");
  231.  
  232.     // This will change if we support > 1 editor element
  233.     if (editorList.item(0))
  234.       return editorList.item(0);
  235.  
  236.     tmpWindow = tmpWindow.opener;
  237.   } 
  238.   while (tmpWindow);
  239.  
  240.   return null;
  241. }
  242.  
  243. function GetCurrentEditingSession()
  244. {
  245.   try {
  246.     return GetCurrentEditorElement().editingSession;
  247.   } catch (e) { dump (e)+"\n"; }
  248.  
  249.   return null;
  250. }
  251.  
  252. function GetCurrentCommandManager()
  253. {
  254.   try {
  255.     return GetCurrentEditorElement().commandManager;
  256.   } catch (e) { dump (e)+"\n"; }
  257.  
  258.   return null;
  259. }
  260.  
  261. function GetCurrentEditorType()
  262. {
  263.   try {
  264.     return GetCurrentEditorElement().editortype;
  265.   } catch (e) { dump (e)+"\n"; }
  266.  
  267.   return "";
  268. }
  269.  
  270. function IsHTMLEditor()
  271. {
  272.   // We don't have an editorElement, just return false
  273.   if (!GetCurrentEditorElement())
  274.     return false;
  275.  
  276.   var editortype = GetCurrentEditorType();
  277.   switch (editortype)
  278.   {
  279.       case "html":
  280.       case "htmlmail":
  281.         return true;
  282.  
  283.       case "text":
  284.       case "textmail":
  285.         return false
  286.  
  287.       default:
  288.         dump("INVALID EDITOR TYPE: " + editortype + "\n");
  289.         break;
  290.   }
  291.   return false;
  292. }
  293.  
  294. function PageIsEmptyAndUntouched()
  295. {
  296.   return IsDocumentEmpty() && !IsDocumentModified()
  297.          && !gHTMLSourceChanged;
  298. }
  299.  
  300. function IsInHTMLSourceMode()
  301. {
  302.   return (gEditorDisplayMode == kDisplayModeSource);
  303. }
  304.  
  305. // are we editing HTML (i.e. neither in HTML source mode, nor editing a text file)
  306. function IsEditingRenderedHTML()
  307. {
  308.   return IsHTMLEditor() && !IsInHTMLSourceMode();
  309. }
  310.  
  311. function IsWebComposer()
  312. {
  313.   return document.documentElement.id == "editorWindow";
  314. }
  315.  
  316. function IsDocumentEditable()
  317. {
  318.   try {
  319.     return GetCurrentEditor().isDocumentEditable;
  320.   } catch (e) {}
  321.   return false;
  322. }
  323.  
  324. function IsDocumentEmpty()
  325. {
  326.   try {
  327.     return GetCurrentEditor().documentIsEmpty;
  328.   } catch (e) {}
  329.   return false;
  330. }
  331.  
  332. function IsDocumentModified()
  333. {
  334.   try {
  335.     return GetCurrentEditor().documentModified;
  336.   } catch (e) {}
  337.   return false;
  338. }
  339.  
  340. function newCommandParams()
  341. {
  342.   try {
  343.     return Components.classes["@mozilla.org/embedcomp/command-params;1"].createInstance(Components.interfaces.nsICommandParams);
  344.   }
  345.   catch(e) { dump("error thrown in newCommandParams: "+e+"\n"); }
  346.   return null;
  347. }
  348.  
  349. /************* General editing command utilities ***************/
  350.  
  351. function GetDocumentTitle()
  352. {
  353.   try {
  354.     var domhtmldoc = GetCurrentEditor().document.QueryInterface(Components.interfaces.nsIDOMHTMLDocument);
  355.     
  356.     // See bug 77315 for details on this wacky code! 
  357.     // (It seems DOM hasn't consistently returned title string, 
  358.     /// but "[xpconnect wrapped HTMLObjectElement]" instead! )
  359.     return Components.lookupMethod(domhtmldoc, 'title').call(domhtmldoc);
  360.   } catch (e) {}
  361.  
  362.   return "";
  363. }
  364.  
  365. function SetDocumentTitle(title)
  366. {
  367.  
  368.   try {
  369.     GetCurrentEditor().setDocumentTitle(title);
  370.  
  371.     // Update window title (doesn't work if called from a dialog)
  372.     if ("UpdateWindowTitle" in window)
  373.       window.UpdateWindowTitle();
  374.   } catch (e) {}
  375. }
  376.  
  377. var gAtomService;
  378. function GetAtomService()
  379. {
  380.   gAtomService = Components.classes["@mozilla.org/atom-service;1"].getService(Components.interfaces.nsIAtomService);
  381. }
  382.  
  383. function EditorGetTextProperty(property, attribute, value, firstHas, anyHas, allHas)
  384. {
  385.   try {
  386.     if (!gAtomService) GetAtomService();
  387.     var propAtom = gAtomService.getAtom(property);
  388.  
  389.     GetCurrentEditor().getInlineProperty(propAtom, attribute, value,
  390.                                          firstHas, anyHas, allHas);
  391.   }
  392.   catch(e) {}
  393. }
  394.  
  395. function EditorSetTextProperty(property, attribute, value)
  396. {
  397.   try {
  398.     if (!gAtomService) GetAtomService();
  399.     var propAtom = gAtomService.getAtom(property);
  400.  
  401.     GetCurrentEditor().setInlineProperty(propAtom, attribute, value);
  402.     if ("gContentWindow" in window)
  403.       window.gContentWindow.focus();
  404.   }
  405.   catch(e) {}
  406. }
  407.  
  408. function EditorRemoveTextProperty(property, attribute)
  409. {
  410.   try {
  411.     if (!gAtomService) GetAtomService();
  412.     var propAtom = gAtomService.getAtom(property);
  413.  
  414.     GetCurrentEditor().removeInlineProperty(propAtom, attribute);
  415.     if ("gContentWindow" in window)
  416.       window.gContentWindow.focus();
  417.   }
  418.   catch(e) {}
  419. }
  420.  
  421. /************* Element enbabling/disabling ***************/
  422.  
  423. // this function takes an elementID and a flag
  424. // if the element can be found by ID, then it is either enabled (by removing "disabled" attr)
  425. // or disabled (setAttribute) as specified in the "doEnable" parameter
  426. function SetElementEnabledById(elementID, doEnable)
  427. {
  428.   SetElementEnabled(document.getElementById(elementID), doEnable);
  429. }
  430.  
  431. function SetElementEnabled(element, doEnable)
  432. {
  433.   if ( element )
  434.   {
  435.     if ( doEnable )
  436.       element.removeAttribute("disabled");
  437.     else
  438.       element.setAttribute("disabled", "true");
  439.   }
  440.   else
  441.   {
  442.     dump("Element  not found in SetElementEnabled\n");
  443.   }
  444. }
  445.  
  446. /************* Services / Prefs ***************/
  447.  
  448. function GetIOService()
  449. {
  450.   if (gIOService)
  451.     return gIOService;
  452.  
  453.   gIOService = Components.classes["@mozilla.org/network/io-service;1"]
  454.                .getService(Components.interfaces.nsIIOService);
  455.  
  456.   return gIOService;
  457. }
  458.  
  459. function GetFileProtocolHandler()
  460. {
  461.   var ios = GetIOService();
  462.   var handler = ios.getProtocolHandler("file");
  463.   return handler.QueryInterface(Components.interfaces.nsIFileProtocolHandler);
  464. }
  465.  
  466. function GetPrefsService()
  467. {
  468.   if (gPrefsService)
  469.     return gPrefsService;
  470.  
  471.   try {
  472.     gPrefsService = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
  473.   }
  474.   catch(ex) {
  475.     dump("failed to get prefs service!\n");
  476.   }
  477.  
  478.   return gPrefsService;
  479. }
  480.  
  481. function GetPrefs()
  482. {
  483.   if (gPrefsBranch)
  484.     return gPrefsBranch;
  485.  
  486.   try {
  487.     var prefService = GetPrefsService();
  488.     if (prefService)
  489.       gPrefsBranch = prefService.getBranch(null);
  490.  
  491.     if (gPrefsBranch)
  492.       return gPrefsBranch;
  493.     else
  494.       dump("failed to get root prefs!\n");
  495.   }
  496.   catch(ex) {
  497.     dump("failed to get root prefs!\n");
  498.   }
  499.   return null;
  500. }
  501.  
  502. function GetStringPref(name)
  503. {
  504.   try {
  505.     return GetPrefs().getComplexValue(name, Components.interfaces.nsISupportsString).data;
  506.   } catch (e) {}
  507.   return "";
  508. }
  509.  
  510. function GetBoolPref(name)
  511. {
  512.   try {
  513.     return GetPrefs().getBoolPref(name);
  514.   } catch (e) {}
  515.   return false;
  516. }
  517.  
  518. function SetUnicharPref(aPrefName, aPrefValue)
  519. {
  520.   var prefs = GetPrefs();
  521.   if (prefs)
  522.   {
  523.     try {
  524.       var str = Components.classes["@mozilla.org/supports-string;1"]
  525.                           .createInstance(Components.interfaces.nsISupportsString);
  526.       str.data = aPrefValue;
  527.       prefs.setComplexValue(aPrefName, Components.interfaces.nsISupportsString, str);
  528.     }
  529.     catch(e) {}
  530.   }
  531. }
  532.  
  533. function GetUnicharPref(aPrefName, aDefVal)
  534. {
  535.   var prefs = GetPrefs();
  536.   if (prefs)
  537.   {
  538.     try {
  539.       return prefs.getComplexValue(aPrefName, Components.interfaces.nsISupportsString).data;
  540.     }
  541.     catch(e) {}
  542.   }
  543.   return "";
  544. }
  545.  
  546. // Set initial directory for a filepicker from URLs saved in prefs
  547. function SetFilePickerDirectory(filePicker, fileType)
  548. {
  549.   if (filePicker)
  550.   {
  551.     try {
  552.       var prefBranch = GetPrefs();
  553.       if (prefBranch)
  554.       {
  555.         // Save current directory so we can reset it in SaveFilePickerDirectory
  556.         gFilePickerDirectory = filePicker.displayDirectory;
  557.  
  558.         var location = prefBranch.getComplexValue("editor.lastFileLocation."+fileType, Components.interfaces.nsILocalFile);
  559.         if (location)
  560.           filePicker.displayDirectory = location;
  561.       }
  562.     }
  563.     catch(e) {}
  564.   }
  565. }
  566.  
  567. // Save the directory of the selected file to prefs
  568. function SaveFilePickerDirectory(filePicker, fileType)
  569. {
  570.   if (filePicker && filePicker.file)
  571.   {
  572.     try {
  573.       var prefBranch = GetPrefs();
  574.  
  575.       var fileDir;
  576.       if (filePicker.file.parent)
  577.         fileDir = filePicker.file.parent.QueryInterface(Components.interfaces.nsILocalFile);
  578.  
  579.       if (prefBranch)
  580.        prefBranch.setComplexValue("editor.lastFileLocation."+fileType, Components.interfaces.nsILocalFile, fileDir);
  581.     
  582.       var prefsService = GetPrefsService();
  583.         prefsService.savePrefFile(null);
  584.     } catch (e) {}
  585.   }
  586.  
  587.   // Restore the directory used before SetFilePickerDirectory was called;
  588.   // This reduces interference with Browser and other module directory defaults
  589.   if (gFilePickerDirectory)
  590.     filePicker.displayDirectory = gFilePickerDirectory;
  591.  
  592.   gFilePickerDirectory = null;
  593. }
  594.  
  595. function GetDefaultBrowserColors()
  596. {
  597.   var prefs = GetPrefs();
  598.   var colors = { TextColor:0, BackgroundColor:0, LinkColor:0, VisitedLinkColor:0 };
  599.   var useSysColors = false;
  600.   try { useSysColors = prefs.getBoolPref("browser.display.use_system_colors"); } catch (e) {}
  601.  
  602.   if (!useSysColors)
  603.   {
  604.     try { colors.TextColor = prefs.getCharPref("browser.display.foreground_color"); } catch (e) {}
  605.  
  606.     try { colors.BackgroundColor = prefs.getCharPref("browser.display.background_color"); } catch (e) {}
  607.   }
  608.   // Use OS colors for text and background if explicitly asked or pref is not set
  609.   if (!colors.TextColor)
  610.     colors.TextColor = "windowtext";
  611.  
  612.   if (!colors.BackgroundColor)
  613.     colors.BackgroundColor = "window";
  614.  
  615.   colors.LinkColor = prefs.getCharPref("browser.anchor_color");
  616.   colors.VisitedLinkColor = prefs.getCharPref("browser.visited_color");
  617.  
  618.   return colors;
  619. }
  620.  
  621. /************* URL handling ***************/
  622.  
  623. function TextIsURI(selectedText)
  624. {
  625.   return selectedText && /^http:\/\/|^https:\/\/|^file:\/\/|\
  626.     ^ftp:\/\/|^about:|^mailto:|^news:|^snews:|^telnet:|^ldap:|\
  627.     ^ldaps:|^gopher:|^finger:|^javascript:/i.test(selectedText);
  628. }
  629.  
  630. function IsUrlAboutBlank(urlString)
  631. {
  632.   return (urlString == "about:blank");
  633. }
  634.  
  635. function MakeRelativeUrl(url)
  636. {
  637.   var inputUrl = TrimString(url);
  638.   if (!inputUrl)
  639.     return inputUrl;
  640.  
  641.   // Get the filespec relative to current document's location
  642.   // NOTE: Can't do this if file isn't saved yet!
  643.   var docUrl = GetDocumentBaseUrl();
  644.   var docScheme = GetScheme(docUrl);
  645.  
  646.   // Can't relativize if no doc scheme (page hasn't been saved)
  647.   if (!docScheme)
  648.     return inputUrl;
  649.  
  650.   var urlScheme = GetScheme(inputUrl);
  651.  
  652.   // Do nothing if not the same scheme or url is already relativized
  653.   if (docScheme != urlScheme)
  654.     return inputUrl;
  655.  
  656.   var IOService = GetIOService();
  657.   if (!IOService)
  658.     return inputUrl;
  659.  
  660.   // Host must be the same
  661.   var docHost = GetHost(docUrl);
  662.   var urlHost = GetHost(inputUrl);
  663.   if (docHost != urlHost)
  664.     return inputUrl;
  665.  
  666.  
  667.   // Get just the file path part of the urls
  668.   // XXX Should we use GetCurrentEditor().documentCharacterSet for 2nd param ?
  669.   var docPath = IOService.newURI(docUrl, GetCurrentEditor().documentCharacterSet, null).path;
  670.   var urlPath = IOService.newURI(inputUrl, GetCurrentEditor().documentCharacterSet, null).path;
  671.  
  672.   // We only return "urlPath", so we can convert
  673.   //  the entire docPath for case-insensitive comparisons
  674.   var os = GetOS();
  675.   var doCaseInsensitive = (docScheme == "file" && os == gWin);
  676.   if (doCaseInsensitive)
  677.     docPath = docPath.toLowerCase();
  678.  
  679.   // Get document filename before we start chopping up the docPath
  680.   var docFilename = GetFilename(docPath);
  681.  
  682.   // Both url and doc paths now begin with "/"
  683.   // Look for shared dirs starting after that
  684.   urlPath = urlPath.slice(1);
  685.   docPath = docPath.slice(1);
  686.  
  687.   var firstDirTest = true;
  688.   var nextDocSlash = 0;
  689.   var done = false;
  690.  
  691.   // Remove all matching subdirs common to both doc and input urls
  692.   do {
  693.     nextDocSlash = docPath.indexOf("\/");
  694.     var nextUrlSlash = urlPath.indexOf("\/");
  695.  
  696.     if (nextUrlSlash == -1)
  697.     {
  698.       // We're done matching and all dirs in url
  699.       // what's left is the filename
  700.       done = true;
  701.  
  702.       // Remove filename for named anchors in the same file
  703.       if (nextDocSlash == -1 && docFilename)
  704.       { 
  705.         var anchorIndex = urlPath.indexOf("#");
  706.         if (anchorIndex > 0)
  707.         {
  708.           var urlFilename = doCaseInsensitive ? urlPath.toLowerCase() : urlPath;
  709.         
  710.           if (urlFilename.indexOf(docFilename) == 0)
  711.             urlPath = urlPath.slice(anchorIndex);
  712.         }
  713.       }
  714.     }
  715.     else if (nextDocSlash >= 0)
  716.     {
  717.       // Test for matching subdir
  718.       var docDir = docPath.slice(0, nextDocSlash);
  719.       var urlDir = urlPath.slice(0, nextUrlSlash);
  720.       if (doCaseInsensitive)
  721.         urlDir = urlDir.toLowerCase();
  722.  
  723.       if (urlDir == docDir)
  724.       {
  725.  
  726.         // Remove matching dir+"/" from each path
  727.         //  and continue to next dir
  728.         docPath = docPath.slice(nextDocSlash+1);
  729.         urlPath = urlPath.slice(nextUrlSlash+1);
  730.       }
  731.       else
  732.       {
  733.         // No match, we're done
  734.         done = true;
  735.  
  736.         // Be sure we are on the same local drive or volume 
  737.         //   (the first "dir" in the path) because we can't 
  738.         //   relativize to different drives/volumes.
  739.         // UNIX doesn't have volumes, so we must not do this else
  740.         //  the first directory will be misinterpreted as a volume name
  741.         if (firstDirTest && docScheme == "file" && os != gUNIX)
  742.           return inputUrl;
  743.       }
  744.     }
  745.     else  // No more doc dirs left, we're done
  746.       done = true;
  747.  
  748.     firstDirTest = false;
  749.   }
  750.   while (!done);
  751.  
  752.   // Add "../" for each dir left in docPath
  753.   while (nextDocSlash > 0)
  754.   {
  755.     urlPath = "../" + urlPath;
  756.     nextDocSlash = docPath.indexOf("\/", nextDocSlash+1);
  757.   }
  758.   return urlPath;
  759. }
  760.  
  761. function MakeAbsoluteUrl(url)
  762. {
  763.   var resultUrl = TrimString(url);
  764.   if (!resultUrl)
  765.     return resultUrl;
  766.  
  767.   // Check if URL is already absolute, i.e., it has a scheme
  768.   var urlScheme = GetScheme(resultUrl);
  769.  
  770.   if (urlScheme)
  771.     return resultUrl;
  772.  
  773.   var docUrl = GetDocumentBaseUrl();
  774.   var docScheme = GetScheme(docUrl);
  775.  
  776.   // Can't relativize if no doc scheme (page hasn't been saved)
  777.   if (!docScheme)
  778.     return resultUrl;
  779.  
  780.   var  IOService = GetIOService();
  781.   if (!IOService)
  782.     return resultUrl;
  783.   
  784.   // Make a URI object to use its "resolve" method
  785.   var absoluteUrl = resultUrl;
  786.   var docUri = IOService.newURI(docUrl, GetCurrentEditor().documentCharacterSet, null);
  787.  
  788.   try {
  789.     absoluteUrl = docUri.resolve(resultUrl);
  790.     // This is deprecated and buggy! 
  791.     // If used, we must make it a path for the parent directory (remove filename)
  792.     //absoluteUrl = IOService.resolveRelativePath(resultUrl, docUrl);
  793.   } catch (e) {}
  794.  
  795.   return absoluteUrl;
  796. }
  797.  
  798. // Get the HREF of the page's <base> tag or the document location
  799. // returns empty string if no base href and document hasn't been saved yet
  800. function GetDocumentBaseUrl()
  801. {
  802.   try {
  803.     var docUrl;
  804.  
  805.     // if document supplies a <base> tag, use that URL instead 
  806.     var baseList = GetCurrentEditor().document.getElementsByTagName("base");
  807.     if (baseList)
  808.     {
  809.       var base = baseList.item(0);
  810.       if (base)
  811.         docUrl = base.getAttribute("href");
  812.     }
  813.     if (!docUrl)
  814.       docUrl = GetDocumentUrl();
  815.  
  816.     if (!IsUrlAboutBlank(docUrl))
  817.       return docUrl;
  818.   } catch (e) {}
  819.   return "";
  820. }
  821.  
  822. function GetDocumentUrl()
  823. {
  824.   try {
  825.     var aDOMHTMLDoc = GetCurrentEditor().document.QueryInterface(Components.interfaces.nsIDOMHTMLDocument);
  826.     return aDOMHTMLDoc.URL;
  827.   }
  828.   catch (e) {}
  829.   return "";
  830. }
  831.  
  832. // Extract the scheme (e.g., 'file', 'http') from a URL string
  833. function GetScheme(urlspec)
  834. {
  835.   var resultUrl = TrimString(urlspec);
  836.   // Unsaved document URL has no acceptable scheme yet
  837.   if (!resultUrl || IsUrlAboutBlank(resultUrl))
  838.     return "";
  839.  
  840.   if (/^internal-gopher-/.test(resultUrl))
  841.     return "internal-gopher-";
  842.  
  843.   var IOService = GetIOService();
  844.   if (!IOService)
  845.     return "";
  846.  
  847.   var scheme = "";
  848.   try {
  849.     // This fails if there's no scheme
  850.     scheme = IOService.extractScheme(resultUrl);
  851.   } catch (e) {}
  852.  
  853.   return scheme ? scheme.toLowerCase() : "";
  854. }
  855.  
  856. function GetHost(urlspec)
  857. {
  858.   if (!urlspec)
  859.     return "";
  860.  
  861.   var IOService = GetIOService();
  862.   if (!IOService)
  863.     return "";
  864.  
  865.   var host = "";
  866.   try {
  867.     host = IOService.newURI(urlspec, null, null).host;
  868.    } catch (e) {}
  869.  
  870.   return host;
  871. }
  872.  
  873. function GetUsername(urlspec)
  874. {
  875.   if (!urlspec)
  876.     return "";
  877.  
  878.   var IOService = GetIOService();
  879.   if (!IOService)
  880.     return "";
  881.  
  882.   var username = "";
  883.   try {
  884.     username = IOService.newURI(urlspec, null, null).username;
  885.   } catch (e) {}
  886.  
  887.   return username;
  888. }
  889.  
  890. function GetFilename(urlspec)
  891. {
  892.   if (!urlspec || IsUrlAboutBlank(urlspec))
  893.     return "";
  894.  
  895.   var IOService = GetIOService();
  896.   if (!IOService)
  897.     return "";
  898.  
  899.   var filename;
  900.  
  901.   try {
  902.     var uri = IOService.newURI(urlspec, null, null);
  903.     if (uri)
  904.     {
  905.       var url = uri.QueryInterface(Components.interfaces.nsIURL);
  906.       if (url)
  907.         filename = url.fileName;
  908.     }
  909.   } catch (e) {}
  910.  
  911.   return filename ? filename : "";
  912. }
  913.  
  914. // Return the url without username and password
  915. // Optional output objects return extracted username and password strings
  916. // This uses just string routines via nsIIOServices
  917. function StripUsernamePassword(urlspec, usernameObj, passwordObj)
  918. {
  919.   urlspec = TrimString(urlspec);
  920.   if (!urlspec || IsUrlAboutBlank(urlspec))
  921.     return urlspec;
  922.  
  923.   if (usernameObj)
  924.     usernameObj.value = "";
  925.   if (passwordObj)
  926.     passwordObj.value = "";
  927.  
  928.   // "@" must exist else we will never detect username or password
  929.   var atIndex = urlspec.indexOf("@");
  930.   if (atIndex > 0)
  931.   {
  932.     try {
  933.       var IOService = GetIOService();
  934.       if (!IOService)
  935.         return urlspec;
  936.  
  937.       var uri = IOService.newURI(urlspec, null, null);
  938.       var username = uri.username;
  939.       var password = uri.password;
  940.  
  941.       if (usernameObj && username)
  942.         usernameObj.value = username;
  943.       if (passwordObj && password)
  944.         passwordObj.value = password;
  945.       if (username)
  946.       {
  947.         var usernameStart = urlspec.indexOf(username);
  948.         if (usernameStart != -1)
  949.           return urlspec.slice(0, usernameStart) + urlspec.slice(atIndex+1);
  950.       }
  951.     } catch (e) {}
  952.   }
  953.   return urlspec;
  954. }
  955.  
  956. function StripPassword(urlspec, passwordObj)
  957. {
  958.   urlspec = TrimString(urlspec);
  959.   if (!urlspec || IsUrlAboutBlank(urlspec))
  960.     return urlspec;
  961.  
  962.   if (passwordObj)
  963.     passwordObj.value = "";
  964.  
  965.   // "@" must exist else we will never detect password
  966.   var atIndex = urlspec.indexOf("@");
  967.   if (atIndex > 0)
  968.   {
  969.     try {
  970.       var IOService = GetIOService();
  971.       if (!IOService)
  972.         return urlspec;
  973.  
  974.       var password = IOService.newURI(urlspec, null, null).password;
  975.  
  976.       if (passwordObj && password)
  977.         passwordObj.value = password;
  978.       if (password)
  979.       {
  980.         // Find last ":" before "@"
  981.         var colon = urlspec.lastIndexOf(":", atIndex);
  982.         if (colon != -1)
  983.         {
  984.           // Include the "@"
  985.           return urlspec.slice(0, colon) + urlspec.slice(atIndex);
  986.         }
  987.       }
  988.     } catch (e) {}
  989.   }
  990.   return urlspec;
  991. }
  992.  
  993. // Version to use when you have an nsIURI object
  994. function StripUsernamePasswordFromURI(uri)
  995. {
  996.   var urlspec = "";
  997.   if (uri)
  998.   {
  999.     try {
  1000.       urlspec = uri.spec;
  1001.       var userPass = uri.userPass;
  1002.       if (userPass)
  1003.       {
  1004.         start = urlspec.indexOf(userPass);
  1005.         urlspec = urlspec.slice(0, start) + urlspec.slice(start+userPass.length+1);
  1006.       }
  1007.     } catch (e) {}    
  1008.   }
  1009.   return urlspec;
  1010. }
  1011.  
  1012. function InsertUsernameIntoUrl(urlspec, username)
  1013. {
  1014.   if (!urlspec || !username)
  1015.     return urlspec;
  1016.  
  1017.   try {
  1018.     var ioService = GetIOService();
  1019.     var URI = ioService.newURI(urlspec, GetCurrentEditor().documentCharacterSet, null);
  1020.     URI.username = username;
  1021.     return URI.spec;
  1022.   } catch (e) {}
  1023.  
  1024.   return urlspec;
  1025. }
  1026.  
  1027. function GetOS()
  1028. {
  1029.   if (gOS)
  1030.     return gOS;
  1031.  
  1032.   var platform = navigator.platform.toLowerCase();
  1033.  
  1034.   if (platform.indexOf("win") != -1)
  1035.     gOS = gWin;
  1036.   else if (platform.indexOf("mac") != -1)
  1037.     gOS = gMac;
  1038.   else if (platform.indexOf("unix") != -1 || platform.indexOf("linux") != -1 || platform.indexOf("sun") != -1)
  1039.     gOS = gUNIX;
  1040.   else
  1041.     gOS = "";
  1042.   // Add other tests?
  1043.  
  1044.   return gOS;
  1045. }
  1046.  
  1047. function ConvertRGBColorIntoHEXColor(color)
  1048. {
  1049.   if ( /rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/.test(color) ) {
  1050.     var r = Number(RegExp.$1).toString(16);
  1051.     if (r.length == 1) r = "0"+r;
  1052.     var g = Number(RegExp.$2).toString(16);
  1053.     if (g.length == 1) g = "0"+g;
  1054.     var b = Number(RegExp.$3).toString(16);
  1055.     if (b.length == 1) b = "0"+b;
  1056.     return "#"+r+g+b;
  1057.   }
  1058.   else
  1059.   {
  1060.     return color;
  1061.   }
  1062. }
  1063.  
  1064. /************* CSS ***************/
  1065.  
  1066. function GetHTMLOrCSSStyleValue(element, attrName, cssPropertyName)
  1067. {
  1068.   var prefs = GetPrefs();
  1069.   var IsCSSPrefChecked = prefs.getBoolPref("editor.use_css");
  1070.   var value;
  1071.   if (IsCSSPrefChecked && IsHTMLEditor())
  1072.     value = element.style.getPropertyValue(cssPropertyName);
  1073.  
  1074.   if (!value)
  1075.     value = element.getAttribute(attrName);
  1076.  
  1077.   if (!value)
  1078.     return "";
  1079.  
  1080.   return value;
  1081. }
  1082.  
  1083. /************* Miscellaneous ***************/
  1084. // Clone simple JS objects
  1085. function Clone(obj) 
  1086.   var clone = {};
  1087.   for (var i in obj)
  1088.   {
  1089.     if( typeof obj[i] == 'object')
  1090.       clone[i] = Clone(obj[i]);
  1091.     else
  1092.       clone[i] = obj[i];
  1093.   }
  1094.   return clone;
  1095. }
  1096.