home *** CD-ROM | disk | FTP | other *** search
/ PC World 2005 July & August / PCWorld_2005-07-08_cd.bin / komunikace / netscape / nsb-install-8-0.exe / chrome / toolkit.jar / content / global / viewPartialSource.js < prev    next >
Text File  |  2004-11-25  |  17KB  |  471 lines

  1.  
  2. var gDebug = 0;
  3. var gLineCount = 0;
  4. var gStartTargetLine = 0;
  5. var gEndTargetLine = 0;
  6. var gTargetNode = null;
  7.  
  8. var gEntityConverter = null;
  9. var gWrapLongLines = false;
  10. const gViewSourceCSS = 'resource://gre/res/viewsource.css';
  11. const NS_XHTML = 'http://www.w3.org/1999/xhtml';
  12.  
  13. // These are markers used to delimit the selection during processing. They
  14. // are removed from the final rendering, but we pick space-like characters for
  15. // safety (and futhermore, these are known to be mapped to a 0-length string
  16. // in transliterate.properties). It is okay to set start=end, we use findNext()
  17. // U+200B ZERO WIDTH SPACE
  18. const MARK_SELECTION_START = '\u200B\u200B\u200B\u200B\u200B';
  19. const MARK_SELECTION_END = '\u200B\u200B\u200B\u200B\u200B';
  20.  
  21. function onLoadViewPartialSource()
  22. {
  23.   // check the view_source.wrap_long_lines pref and set the menuitem's checked attribute accordingly
  24.   if (gPrefs) {
  25.     try {
  26.       var wraplonglinesPrefValue = gPrefs.getBoolPref('view_source.wrap_long_lines');
  27.       if (wraplonglinesPrefValue) {
  28.         document.getElementById('menu_wrapLongLines').setAttribute('checked', 'true');
  29.         gWrapLongLines = true;
  30.       }
  31.     } catch (e) { }
  32.     try {
  33.       document.getElementById("menu_highlightSyntax").setAttribute("checked", gPrefs.getBoolPref("view_source.syntax_highlight"));
  34.     } catch (e) {
  35.     }
  36.   } else {
  37.     document.getElementById("menu_highlightSyntax").setAttribute("hidden", "true");
  38.   }
  39.   
  40.   initFindBar();
  41.  
  42.   // disable menu items that don't work since the selection is munged and
  43.   // the editor doesn't work for MathML
  44.   document.getElementById('cmd_savePage').setAttribute('disabled', 'true');
  45.   // we don't support external editors
  46.   //document.getElementById('cmd_editPage').setAttribute('disabled', 'true');
  47.  
  48.   if (window.arguments[3] == 'selection')
  49.     viewPartialSourceForSelection(window.arguments[2]);
  50.   else
  51.     viewPartialSourceForFragment(window.arguments[2], window.arguments[3]);
  52.  
  53.   window._content.focus();
  54. }
  55.  
  56. function onUnloadViewPartialSource()
  57. {
  58.   uninitFindBar();
  59. }
  60.  
  61. ////////////////////////////////////////////////////////////////////////////////
  62. // view-source of a selection with the special effect of remapping the selection
  63. // to the underlying view-source output
  64. function viewPartialSourceForSelection(selection)
  65. {
  66.   var range = selection.getRangeAt(0);
  67.   var ancestorContainer = range.commonAncestorContainer;
  68.   var doc = ancestorContainer.ownerDocument;
  69.  
  70.   var startContainer = range.startContainer;
  71.   var endContainer = range.endContainer;
  72.   var startOffset = range.startOffset;
  73.   var endOffset = range.endOffset;
  74.  
  75.   // let the ancestor be an element
  76.   if (ancestorContainer.nodeType == Node.TEXT_NODE ||
  77.       ancestorContainer.nodeType == Node.CDATA_SECTION_NODE)
  78.     ancestorContainer = ancestorContainer.parentNode;
  79.  
  80.   // for selectAll, let's use the entire document, including <html>...</html>
  81.   // @see DocumentViewerImpl::SelectAll() for how selectAll is implemented
  82.   try {
  83.     if (ancestorContainer == doc.body)
  84.       ancestorContainer = doc.documentElement;
  85.   } catch (e) { }
  86.  
  87.   // each path is a "child sequence" (a.k.a. "tumbler") that
  88.   // descends from the ancestor down to the boundary point
  89.   var startPath = getPath(ancestorContainer, startContainer);
  90.   var endPath = getPath(ancestorContainer, endContainer);
  91.  
  92.   // clone the fragment of interest and reset everything to be relative to it
  93.   // note: it is with the clone that we operate from now on
  94.   ancestorContainer = ancestorContainer.cloneNode(true);
  95.   startContainer = ancestorContainer;
  96.   endContainer = ancestorContainer;
  97.   var i;
  98.   for (i = startPath ? startPath.length-1 : -1; i >= 0; i--) {
  99.     startContainer = startContainer.childNodes.item(startPath[i]);
  100.   }
  101.   for (i = endPath ? endPath.length-1 : -1; i >= 0; i--) {
  102.     endContainer = endContainer.childNodes.item(endPath[i]);
  103.   }
  104.  
  105.   // add special markers to record the extent of the selection
  106.   // note: |startOffset| and |endOffset| are interpreted either as
  107.   // offsets in the text data or as child indices (see the Range spec)
  108.   // (here, munging the end point first to keep the start point safe...)
  109.   var tmpNode;
  110.   if (endContainer.nodeType == Node.TEXT_NODE ||
  111.       endContainer.nodeType == Node.CDATA_SECTION_NODE) {
  112.     // do some extra tweaks to try to avoid the view-source output to look like
  113.     // ...<tag>]... or ...]</tag>... (where ']' marks the end of the selection).
  114.     // To get a neat output, the idea here is to remap the end point from:
  115.     // 1. ...<tag>]...   to   ...]<tag>...
  116.     // 2. ...]</tag>...  to   ...</tag>]...
  117.     if ((endOffset > 0 && endOffset < endContainer.data.length) ||
  118.         !endContainer.parentNode || !endContainer.parentNode.parentNode)
  119.       endContainer.insertData(endOffset, MARK_SELECTION_END);
  120.     else {
  121.       tmpNode = doc.createTextNode(MARK_SELECTION_END);
  122.       endContainer = endContainer.parentNode;
  123.       if (endOffset == 0)
  124.         endContainer.parentNode.insertBefore(tmpNode, endContainer);
  125.       else
  126.         endContainer.parentNode.insertBefore(tmpNode, endContainer.nextSibling);
  127.     }
  128.   }
  129.   else {
  130.     tmpNode = doc.createTextNode(MARK_SELECTION_END);
  131.     endContainer.insertBefore(tmpNode, endContainer.childNodes.item(endOffset));
  132.   }
  133.  
  134.   if (startContainer.nodeType == Node.TEXT_NODE ||
  135.       startContainer.nodeType == Node.CDATA_SECTION_NODE) {
  136.     // do some extra tweaks to try to avoid the view-source output to look like
  137.     // ...<tag>[... or ...[</tag>... (where '[' marks the start of the selection).
  138.     // To get a neat output, the idea here is to remap the start point from:
  139.     // 1. ...<tag>[...   to   ...[<tag>...
  140.     // 2. ...[</tag>...  to   ...</tag>[...
  141.     if ((startOffset > 0 && startOffset < startContainer.data.length) ||
  142.         !startContainer.parentNode || !startContainer.parentNode.parentNode ||
  143.         startContainer != startContainer.parentNode.lastChild)
  144.       startContainer.insertData(startOffset, MARK_SELECTION_START);
  145.     else {
  146.       tmpNode = doc.createTextNode(MARK_SELECTION_START);
  147.       startContainer = startContainer.parentNode;
  148.       if (startOffset == 0)
  149.         startContainer.parentNode.insertBefore(tmpNode, startContainer);
  150.       else
  151.         startContainer.parentNode.insertBefore(tmpNode, startContainer.nextSibling);
  152.     }
  153.   }
  154.   else {
  155.     tmpNode = doc.createTextNode(MARK_SELECTION_START);
  156.     startContainer.insertBefore(tmpNode, startContainer.childNodes.item(startOffset));
  157.   }
  158.  
  159.   // now extract and display the syntax highlighted source
  160.   tmpNode = doc.createElementNS(NS_XHTML, 'div');
  161.   tmpNode.appendChild(ancestorContainer);
  162.  
  163.   // the load is aynchronous and so we will wait until the view-source DOM is done
  164.   // before drawing the selection.
  165.   window.document.getElementById("appcontent").addEventListener("load", drawSelection, true);
  166.  
  167.   // all our content is held by the data:URI and URIs are internally stored as utf-8 (see nsIURI.idl)
  168.   var loadFlags = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_BYPASS_CACHE;
  169.   getBrowser().webNavigation
  170.               .loadURI("view-source:data:text/html;charset=utf-8," + encodeURIComponent(tmpNode.innerHTML),
  171.                        loadFlags, null, null, null);
  172. }
  173.  
  174. ////////////////////////////////////////////////////////////////////////////////
  175. // helper to get a path like FIXptr, but with an array instead of the "tumbler" notation
  176. // see FIXptr: http://lists.w3.org/Archives/Public/www-xml-linking-comments/2001AprJun/att-0074/01-NOTE-FIXptr-20010425.htm
  177. function getPath(ancestor, node)
  178. {
  179.   var n = node;
  180.   var p = n.parentNode;
  181.   if (n == ancestor || !p)
  182.     return null;
  183.   var path = new Array();
  184.   if (!path)
  185.     return null;
  186.   do {
  187.     for (var i = 0; i < p.childNodes.length; i++) {
  188.       if (p.childNodes.item(i) == n) {
  189.         path.push(i);
  190.         break;
  191.       }
  192.     }
  193.     n = p;
  194.     p = n.parentNode;
  195.   } while (n != ancestor && p);
  196.   return path;
  197. }
  198.  
  199. ////////////////////////////////////////////////////////////////////////////////
  200. // using special markers left in the serialized source, this helper makes the
  201. // underlying markup of the selected fragment to automatically appear as selected
  202. // on the inflated view-source DOM
  203. function drawSelection()
  204. {
  205.   // find the special selection markers that we added earlier, and
  206.   // draw the selection between the two...
  207.   var findService = null;
  208.   try {
  209.     // get the find service which stores the global find state
  210.     findService = Components.classes["@mozilla.org/find/find_service;1"]
  211.                             .getService(Components.interfaces.nsIFindService);
  212.   } catch(e) { }
  213.   if (!findService)
  214.     return;
  215.  
  216.   // cache the current global find state
  217.   var matchCase     = findService.matchCase;
  218.   var entireWord    = findService.entireWord;
  219.   var wrapFind      = findService.wrapFind;
  220.   var findBackwards = findService.findBackwards;
  221.   var searchString  = findService.searchString;
  222.   var replaceString = findService.replaceString;
  223.  
  224.   // setup our find instance
  225.   var findInst = getBrowser().webBrowserFind;
  226.   findInst.matchCase = true;
  227.   findInst.entireWord = false;
  228.   findInst.wrapFind = true;
  229.   findInst.findBackwards = false;
  230.  
  231.   // ...lookup the start mark
  232.   findInst.searchString = MARK_SELECTION_START;
  233.   var startLength = MARK_SELECTION_START.length;
  234.   findInst.findNext();
  235.  
  236.   var contentWindow = getBrowser().contentDocument.defaultView;
  237.   var selection = contentWindow.getSelection();
  238.   var range = selection.getRangeAt(0);
  239.  
  240.   var startContainer = range.startContainer;
  241.   var startOffset = range.startOffset;
  242.  
  243.   // ...lookup the end mark
  244.   findInst.searchString = MARK_SELECTION_END;
  245.   var endLength = MARK_SELECTION_END.length;
  246.   findInst.findNext();
  247.  
  248.   var endContainer = selection.anchorNode;
  249.   var endOffset = selection.anchorOffset;
  250.  
  251.   // reset the selection that find has left
  252.   selection.removeAllRanges();
  253.  
  254.   // delete the special markers now...
  255.   endContainer.deleteData(endOffset, endLength);
  256.   startContainer.deleteData(startOffset, startLength);
  257.   if (startContainer == endContainer)
  258.     endOffset -= startLength; // has shrunk if on same text node...
  259.   range.setEnd(endContainer, endOffset);
  260.  
  261.   // show the selection and scroll it into view
  262.   selection.addRange(range);
  263.   // the default behavior of the selection is to scroll at the end of
  264.   // the selection, whereas in this situation, it is more user-friendly
  265.   // to scroll at the beginning. So we override the default behavior here
  266.   try {
  267.     getBrowser().docShell
  268.                 .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
  269.                 .getInterface(Components.interfaces.nsISelectionDisplay)
  270.                 .QueryInterface(Components.interfaces.nsISelectionController)
  271.                 .scrollSelectionIntoView(Components.interfaces.nsISelectionController.SELECTION_NORMAL,
  272.                                          Components.interfaces.nsISelectionController.SELECTION_ANCHOR_REGION,
  273.                                          true);
  274.   }
  275.   catch(e) { }
  276.  
  277.   // restore the current find state
  278.   findService.matchCase     = matchCase;
  279.   findService.entireWord    = entireWord;
  280.   findService.wrapFind      = wrapFind;
  281.   findService.findBackwards = findBackwards;
  282.   findService.searchString  = searchString;
  283.   findService.replaceString = replaceString;
  284.  
  285.   findInst.matchCase     = matchCase;
  286.   findInst.entireWord    = entireWord;
  287.   findInst.wrapFind      = wrapFind;
  288.   findInst.findBackwards = findBackwards;
  289.   findInst.searchString  = searchString;
  290. }
  291.  
  292. ////////////////////////////////////////////////////////////////////////////////
  293. // special handler for markups such as MathML where reformatting the output is
  294. // helpful
  295. function viewPartialSourceForFragment(node, context)
  296. {
  297.   gTargetNode = node;
  298.   if (gTargetNode && gTargetNode.nodeType == Node.TEXT_NODE)
  299.     gTargetNode = gTargetNode.parentNode;
  300.  
  301.   // walk up the tree to the top-level element (e.g., <math>, <svg>)
  302.   var topTag;
  303.   if (context == 'mathml')
  304.     topTag = 'math';
  305.   else
  306.     throw 'not reached';
  307.   var topNode = gTargetNode;
  308.   while (topNode && topNode.localName != topTag)
  309.     topNode = topNode.parentNode;
  310.   if (!topNode)
  311.     return;
  312.  
  313.   // serialize (note: the main window overrides the title set here)
  314.   var wrapClass = gWrapLongLines ? ' class="wrap"' : '';
  315.   var source =
  316.     '<html>'
  317.   + '<head><title>Mozilla</title>'
  318.   + '<link rel="stylesheet" type="text/css" href="' + gViewSourceCSS + '">'
  319.   + '<style type="text/css">'
  320.   + '#target { border: dashed 1px; background-color: lightyellow; }'
  321.   + '</style>'
  322.   + '</head>'
  323.   + '<body id="viewsource"' + wrapClass
  324.   +        ' onload="document.getElementById(\'target\').scrollIntoView(true)">'
  325.   + '<pre>'
  326.   + getOuterMarkup(topNode, 0)
  327.   + '</pre></body></html>'
  328.   ; // end
  329.  
  330.   // display
  331.   var doc = getBrowser().contentDocument;
  332.   doc.open("text/html", "replace");
  333.   doc.write(source);
  334.   doc.close();
  335. }
  336.  
  337. ////////////////////////////////////////////////////////////////////////////////
  338. function getInnerMarkup(node, indent) {
  339.   var str = '';
  340.   for (var i = 0; i < node.childNodes.length; i++) {
  341.     str += getOuterMarkup(node.childNodes.item(i), indent);
  342.   }
  343.   return str;
  344. }
  345.  
  346. ////////////////////////////////////////////////////////////////////////////////
  347. function getOuterMarkup(node, indent) {
  348.   var newline = '';
  349.   var padding = '';
  350.   var str = '';
  351.   if (node == gTargetNode) {
  352.     gStartTargetLine = gLineCount;
  353.     str += '</pre><pre id="target">';
  354.   }
  355.  
  356.   switch (node.nodeType) {
  357.   case Node.ELEMENT_NODE: // Element
  358.     // to avoid the wide gap problem, '\n' is not emitted on the first
  359.     // line and the lines before & after the <pre id="target">...</pre>
  360.     if (gLineCount > 0 &&
  361.         gLineCount != gStartTargetLine &&
  362.         gLineCount != gEndTargetLine) {
  363.       newline = '\n';
  364.     }
  365.     gLineCount++;
  366.     if (gDebug) {
  367.       newline += gLineCount;
  368.     }
  369.     for (var k = 0; k < indent; k++) {
  370.       padding += ' ';
  371.     }
  372.     str += newline + padding
  373.         +  '<<span class="start-tag">' + node.nodeName + '</span>';
  374.     for (var i = 0; i < node.attributes.length; i++) {
  375.       var attr = node.attributes.item(i);
  376.       if (!gDebug && attr.nodeName.match(/^[-_]moz/)) {
  377.         continue;
  378.       }
  379.       str += ' <span class="attribute-name">'
  380.           +  attr.nodeName
  381.           +  '</span>=<span class="attribute-value">"'
  382.           +  unicodeTOentity(attr.nodeValue)
  383.           +  '"</span>';
  384.     }
  385.     if (!node.hasChildNodes()) {
  386.       str += '/>';
  387.     }
  388.     else {
  389.       str += '>';
  390.       var oldLine = gLineCount;
  391.       str += getInnerMarkup(node, indent + 2);
  392.       if (oldLine == gLineCount) {
  393.         newline = '';
  394.         padding = '';
  395.       }
  396.       else {
  397.         newline = (gLineCount == gEndTargetLine) ? '' : '\n';
  398.         gLineCount++;
  399.         if (gDebug) {
  400.           newline += gLineCount;
  401.         }
  402.       }
  403.       str += newline + padding
  404.           +  '</<span class="end-tag">' + node.nodeName + '</span>>';
  405.     }
  406.     break;
  407.   case Node.TEXT_NODE: // Text
  408.     var tmp = node.nodeValue;
  409.     tmp = tmp.replace(/(\n|\r|\t)+/g, " ");
  410.     tmp = tmp.replace(/^ +/, "");
  411.     tmp = tmp.replace(/ +$/, "");
  412.     if (tmp.length != 0) {
  413.       str += '<span class="text">' + unicodeTOentity(tmp) + '</span>';
  414.     }
  415.     break;
  416.   default:
  417.     break;
  418.   }
  419.  
  420.   if (node == gTargetNode) {
  421.     gEndTargetLine = gLineCount;
  422.     str += '</pre><pre>';
  423.   }
  424.   return str;
  425. }
  426.  
  427. ////////////////////////////////////////////////////////////////////////////////
  428. function unicodeTOentity(text)
  429. {
  430.   const charTable = {
  431.     '&': '&<span class="entity">amp;</span>',
  432.     '<': '&<span class="entity">lt;</span>',
  433.     '>': '&<span class="entity">gt;</span>',
  434.     '"': '&<span class="entity">quot;</span>'
  435.   };
  436.  
  437.   function charTableLookup(letter) {
  438.     return charTable[letter];
  439.   }
  440.  
  441.   function convertEntity(letter) {
  442.     try {
  443.       var unichar = gEntityConverter.ConvertToEntity(letter, entityVersion);
  444.       var entity = unichar.substring(1); // extract '&'
  445.       return '&<span class="entity">' + entity + '</span>';
  446.     } catch (ex) {
  447.       return letter;
  448.     }
  449.   }
  450.  
  451.   if (!gEntityConverter) {
  452.     try {
  453.       gEntityConverter =
  454.         Components.classes["@mozilla.org/intl/entityconverter;1"]
  455.                   .createInstance(Components.interfaces.nsIEntityConverter);
  456.     } catch(e) { }
  457.   }
  458.  
  459.   const entityVersion = Components.interfaces.nsIEntityConverter.entityW3C;
  460.  
  461.   var str = text;
  462.  
  463.   // replace chars in our charTable
  464.   str = str.replace(/[<>&"]/g, charTableLookup);
  465.  
  466.   // replace chars > 0x7f via nsIEntityConverter
  467.   str = str.replace(/[^\0-\u007f]/g, convertEntity);
  468.  
  469.   return str;
  470. }
  471.