home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 September / PCWorld_2008-09_cd.bin / komunikace / kmeleon / K-Meleon1.1.3en-US.exe / chrome / newsfox.jar / content / newsfox / file.js < prev    next >
Text File  |  2007-10-07  |  34KB  |  1,040 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is NewsFox.
  15.  *
  16.  * The Initial Developer of the Original Code is
  17.  * Andy Frank <andy@andyfrank.com>.
  18.  * Portions created by the Initial Developer are Copyright (C) 2005-2007
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Andrey Gromyko <andrey@gromyko.name>
  23.  *   Ron Pruitt <wa84it@gmail.com>
  24.  *
  25.  * Alternatively, the contents of this file may be used under the terms of
  26.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  27.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28.  * in which case the provisions of the GPL or the LGPL are applicable instead
  29.  * of those above. If you wish to allow use of your version of this file only
  30.  * under the terms of either the GPL or the LGPL, and not to allow others to
  31.  * use your version of this file under the terms of the MPL, indicate your
  32.  * decision by deleting the provisions above and replace them with the notice
  33.  * and other provisions required by the LGPL or the GPL. If you do not delete
  34.  * the provisions above, a recipient may use your version of this file under
  35.  * the terms of any one of the MPL, the GPL or the LGPL.
  36.  *
  37.  * ***** END LICENSE BLOCK ***** */
  38.  
  39. var scKeyList = new Array();
  40.  
  41. ////////////////////////////////////////////////////////////////
  42. // Text view
  43. ////////////////////////////////////////////////////////////////
  44.  
  45. function getTextView(art)
  46. {
  47.     if (gOptions.copyToClip)
  48.     {
  49.         var trans = Components.classes["@mozilla.org/widget/transferable;1"]
  50.             .createInstance(Components.interfaces.nsITransferable);
  51.       trans.addDataFlavor("text/html");
  52.       trans.addDataFlavor("text/unicode");
  53.  
  54.         var htmlStr = Components.classes["@mozilla.org/supports-string;1"]
  55.             .createInstance(Components.interfaces.nsISupportsString);
  56.          htmlStr.data = art.body;
  57.  
  58.         trans.setTransferData("text/html",htmlStr, htmlStr.toString().length);
  59.     trans.setTransferData("text/unicode",htmlStr, htmlStr.toString().length);
  60.  
  61.         var clipId = Components.interfaces.nsIClipboard;
  62.         var clipB = Components.classes["@mozilla.org/widget/clipboard;1"]
  63.             .getService(clipId);
  64.       clipB.setData(trans,null,clipId.kGlobalClipboard);
  65.     }
  66.  
  67.     var iframe = document.getElementById("buildContent");
  68.     var doc = iframe.contentDocument;
  69.     iframe.docShell.allowJavascript = false;
  70.  
  71.     while (doc.body.childNodes.length > 0) doc.body.removeChild(doc.body.childNodes[0]);
  72.  
  73.   var div = doc.createElement("div");
  74.     div.setAttribute("id","newsfox-box");
  75.         var s = doc.createElement("span");
  76.         s.setAttribute("class","newsfox-mail");
  77.         var a = doc.createElement("a");
  78.         a.setAttribute("href",makeMailto(art.title,art.link));
  79.             var img = doc.createElement("img");
  80.             img.setAttribute("src","chrome://newsfox/skin/images/mail.png");
  81.             img.setAttribute("style","border: 0px; padding-right: 7px;");
  82.         a.appendChild(img);
  83.         s.appendChild(a);
  84.     div.appendChild(s);
  85.         var b = getXhtmlBody(art.title,"b",doc);
  86.         b.setAttribute("class","newsfox-title");
  87.         b.appendChild(doc.createElement("br"));
  88.     div.appendChild(b);
  89.         var p = doc.createElement("span");
  90.         p.setAttribute("class","newsfox-category");
  91.         p.innerHTML = "";
  92.       if (art.category != "") p.innerHTML += emphsrch(art.category) + ": ";
  93.     div.appendChild(p);
  94.         var p2 = doc.createElement("span");
  95.         p2.setAttribute("class","newsfox-date");
  96.         p2.innerHTML = displayDate(art.date, LONG_DATE_STYLE) + "<br/>";
  97.     div.appendChild(p2);
  98.     if (art.link)
  99.     {
  100.       var a = doc.createElement("a");
  101.         a.setAttribute("href", art.link);
  102.         a.setAttribute("target", "_blank");
  103.         a.setAttribute("class","newsfox-link");
  104.         a.innerHTML = art.link + "<br/>";
  105.         div.appendChild(a);
  106.     }
  107.  
  108.     if (art.enclosures.length > 0)
  109.     {
  110.         var s = doc.createElement("span");
  111.         s.setAttribute("class","newsfox-enclosures");
  112.         for (var i=0; i<art.enclosures.length; i++)
  113.         {
  114.             var artenc = art.enclosures[i];
  115.             var nobr = doc.createElement("span");  // xhtml equivalent of <nobr>
  116.             nobr.setAttribute("style","white-space: nowrap;");
  117.             var a = doc.createElement("a");
  118.             a.setAttribute("class","newsfox-encl");
  119.             a.setAttribute("href", artenc.url);
  120.             a.setAttribute("target","_blank");
  121.             var img = doc.createElement("img");
  122.             var imgsrc;
  123.             var encltype = artenc.type.substring(0,5);
  124.             if (encltype == "video")
  125.                 imgsrc = "chrome://newsfox/skin/images/encl-video.png";
  126.             else if (encltype == "audio")
  127.                 imgsrc = "chrome://newsfox/skin/images/encl-audio.png";
  128.             else
  129.                 imgsrc = "chrome://newsfox/skin/images/encl-other.png";
  130.             img.setAttribute("src",imgsrc);
  131.             img.setAttribute("style","border: 0px; padding-right: 2px;");
  132.             var p = doc.createElement("span");
  133.             p.innerHTML = artenc.type;
  134.             a.appendChild(img);
  135.             a.appendChild(p);
  136.             nobr.appendChild(a);
  137.             var p = doc.createElement("span");
  138.             var sz = Math.round(artenc.length/1024);
  139.             if (sz == 0 || isNaN(sz)) sz = "----";
  140.             sz += "Kb";
  141.             p.innerHTML = ": " + sz + "    ";
  142.             nobr.appendChild(p);
  143.             s.appendChild(nobr);
  144.         }
  145.         s.appendChild(doc.createElement("br"));
  146.         div.appendChild(s);
  147.     }
  148.  
  149.     var p = getXhtmlBody(art.body,"p",doc);
  150.  
  151.     doc.body.appendChild(div);
  152.     doc.body.appendChild(p);
  153. }
  154.  
  155. function makeMailto(title,link)
  156. {
  157.     var uri = encodeURI("mailto:?subject=" + entityDecode(title) + "&body=" + link);
  158.     uri = uri.replace(/\?/g,"%3F");
  159.     uri = uri.replace(/=/g,"%3D");
  160.     uri = uri.replace(/&/g,"%26");
  161.     uri = uri.replace("%3Fsubject%3D","?subject=");
  162.     uri = uri.replace("%26body%3D","&body=");
  163.     return(uri);
  164. }
  165.  
  166. function getXhtmlBody(body,tag,doc)
  167. {
  168.     if (body.substring(0,7) == "<xhtml>")
  169.     {
  170.       var p = doc.createElementNS("http://www.w3.org/1999/xhtml",tag);
  171.         var body2 = body.replace(/^<xhtml>|<\/xhtml>$/g,"");
  172.         var xmlBody = new DOMParser().parseFromString(emphsrch(body2),"text/xml");
  173.         // Atom specification guarantees a single <div> element, now a <span>
  174.         p.appendChild(xmlBody.childNodes[0]);
  175.     }
  176.     else
  177.     {
  178.         var p = doc.createElement(tag);
  179.         p.innerHTML = emphsrch(body);
  180.     }
  181.     return p;
  182. }
  183.  
  184. function emphsrch(html)
  185. {
  186.     if (gCollect.type == 3)  // search
  187.     {
  188.         var st = gCollect.getSrchText();
  189.         for (var i=0; i<st.length; i++)
  190.             html = hilite(st[i],html);
  191.     }
  192.     return html;
  193. }
  194.  
  195. function hilite(srchtext, html)
  196. {
  197.     var srchdat = gFdGroup[gCollect.grpindex].srchdat;
  198.     var textflags = srchdat.textflags;
  199.     if (srchtext.length > 0)
  200.     {
  201.         var caseSen = ((textflags & 0x04) == 0);
  202.         var text = html;
  203.         var repltext = srchtext;
  204.         if (!caseSen)
  205.         {
  206.             text = text.toLowerCase();
  207.             repltext = repltext.toLowerCase();
  208.         }
  209.         var todo = new Array();
  210.         var startpos = 0;
  211.         var newpos = text.indexOf(repltext,startpos);
  212.         while (newpos > -1)
  213.         {
  214.             // check if in <tag> or in html
  215.             if (text.lastIndexOf(">",newpos) >= text.lastIndexOf("<",newpos))
  216.                 todo.push(newpos);
  217.             startpos = newpos + 1;
  218.             newpos = text.indexOf(repltext,startpos);
  219.         }
  220.         var pos;
  221.         var len = srchtext.length;
  222.         for (var i=todo.length-1; i>=0; i--)
  223.         {
  224.             pos = todo[i];
  225.             html = html.substring(0,pos)+"<span class='srch'>"+html.substring(pos,pos+len)+"</span>"+html.substring(pos+len);
  226.         }
  227.     }
  228.     return html;
  229. }
  230.  
  231. function resetIframe()
  232. {
  233.     var iframe = document.getElementById("buildContent");
  234.     var doc = iframe.contentDocument;
  235.  
  236. // doesn't work, need to keep node with name='html' if it exists
  237. //    while (doc.childNodes.length > 0) doc.removeChild(doc.childNodes[0]);
  238.  
  239.     var docHEAD = null;
  240.     for (var i=0; i<doc.childNodes.length; i++)
  241.         if (doc.childNodes[i].localName == "HTML")
  242.         {
  243.             var docHTML = doc.childNodes[i];
  244.             for (i=0; i<docHTML.childNodes.length; i++)
  245.                 if (docHTML.childNodes[i].localName == "HEAD")
  246.                 {
  247.                     docHEAD = docHTML.childNodes[i];
  248.                     while (docHEAD.childNodes.length > 0)
  249.                         docHEAD.removeChild(docHEAD.childNodes[0]);
  250.                 }
  251.                 else if (docHTML.childNodes[i].localName == "BODY")
  252.                 {
  253.                     var docBODY = docHTML.childNodes[i];
  254.                     while (docBODY.childNodes.length > 0)
  255.                         docBODY.removeChild(docBODY.childNodes[0]);
  256.                 }
  257.                 else
  258.                     docHTML.removeChild(docHTML.childNodes[i]);
  259.         }
  260.  
  261.     var meta = doc.createElement("meta");
  262.     meta.setAttribute("http-equiv", "content-type");
  263.     meta.setAttribute("content", "text/html");
  264.     meta.setAttribute("charset", "utf-8");
  265.  
  266.     var sty = doc.createElement("style");
  267.     sty.setAttribute("type","text/css");
  268.     sty.innerHTML = getCss();
  269.  
  270.     docHEAD.appendChild(meta);
  271.     docHEAD.appendChild(sty);
  272. }
  273.  
  274. ////////////////////////////////////////////////////////////////
  275. // CSS file
  276. ////////////////////////////////////////////////////////////////
  277.  
  278. function getCss()
  279. {
  280.     var file = getProfileDir();
  281.     file.append("textview.css");
  282.   var inputStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance( Components.interfaces.nsIFileInputStream );
  283.   inputStream.init( file,0x01,00004,null);
  284.   var scInputStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance( Components.interfaces.nsIScriptableInputStream );
  285.   scInputStream.init(inputStream);
  286.   var output = scInputStream.read(-1);
  287.   scInputStream.close();
  288.   inputStream.close();
  289.     return output;
  290. }
  291.  
  292. function makeCss()
  293. {
  294.     var file, out;
  295.     file = getProfileDir();
  296.     file.append("textview.css");
  297.     if (!file.exists())
  298.     {
  299.         out = openOutputStream(file, 0x02 | 0x08 | 0x20);
  300.         println(out, "body { font:10pt Verdana,sans-serif; background:white; }\n");
  301.         println(out, "#newsfox-box { background: #e3dfd9; padding:10px; overflow:hidden; }\n");
  302.         println(out, ".srch { color: red; font-weight: bold; }\n");
  303.         out.close();
  304.     }
  305.     else
  306.     {
  307.         var cssTxt = getCss();
  308.         var saveIt = false;
  309.         if (cssTxt.indexOf(".srch") == -1)
  310.         {
  311.             cssTxt += "\n.srch { color: red; font-weight: bold; }\n";
  312.             saveIt = true;
  313.         }
  314.         if (saveIt)
  315.         {
  316.             file.remove(false);
  317.             out = openOutputStream(file, 0x02 | 0x08 | 0x20);
  318.             println(out, cssTxt);
  319.             out.close();
  320.         }
  321.     }
  322. }
  323.  
  324. ////////////////////////////////////////////////////////////////
  325. // Keyboard shortcuts
  326. ////////////////////////////////////////////////////////////////
  327.  
  328. function updateShortcuts(index)
  329. {
  330.     var keySet = document.getElementById("shortcut-keys");
  331.     while (keySet.firstChild != null) keySet.removeChild(keySet.firstChild);
  332.  
  333.     for (var i=0; i<scKeyList.length; i++)
  334.     {
  335.         var idkey = scKeyList[i];
  336.         idkey.setAttribute("modifiers", idkey.getAttribute("mod"+index));
  337.         idkey.setAttribute("key", idkey.getAttribute("key"+index));
  338.         if (index != 0) keySet.appendChild(idkey);
  339.     }
  340.  
  341.     var feedTree = document.getElementById("newsfox.feedTree");
  342.     if (index != 0)
  343.         feedTree.setAttribute("disableKeyNavigation", true);
  344.     else
  345.         feedTree.removeAttribute("disableKeyNavigation");
  346.  
  347.     makeAccel();
  348. }
  349.  
  350. function makeAccel()
  351. {
  352.     var file, out;
  353.     file = getProfileDir();
  354.     file.append("accel.xml");
  355.     if (file.exists()) file.remove(false);
  356.  
  357.     out = openOutputStream(file, 0x02 | 0x08 | 0x20);
  358.     println(out, "<?xml version=\"1.0\"?>\n<body>\n");
  359.     const NF_SB = document.getElementById("newsfox-string-bundle");
  360.   var feedKeyNav = NF_SB.getString('feedKeyNav');
  361.     println(out, "\t<info val=\"" + feedKeyNav + "\" />");
  362.     var feedTree = document.getElementById("newsfox.feedTree");
  363.     var dKNvalue = "true";
  364.     if (feedTree.getAttribute("disableKeyNavigation") != "true") dKNvalue = "false";
  365.     println(out, "\t<feedtree disableKeyNavigation=\"" + dKNvalue + "\"/>\n");
  366.  
  367.     var keySet = document.getElementById("shortcut-keys");
  368.     for (var i=keySet.firstChild; i != null; i=i.nextSibling)
  369.     {
  370.         println(out, "\t<key id=\"" + i.id + "\" modifiers=\"" + i.getAttribute("modifiers") + "\" key=\"" + i.getAttribute("key") + "\"/>");
  371.     }
  372.     println(out, "</body>");
  373.     out.close();
  374. }
  375.  
  376. function getAccel()
  377. {
  378.     var file = getProfileDir();
  379.     file.append("accel.xml");
  380.  
  381.     if (!file.exists())
  382.         makeAccel();
  383.     else
  384.     {
  385.     var xml = document.implementation.createDocument("","",null);
  386.     xml.async = false;
  387.     xml.load("file:///" + file.path);
  388.  
  389.         var keyNav = xml.getElementsByTagName("feedtree");
  390.         if (keyNav.length != 0)
  391.         {
  392.             var feedTree = document.getElementById("newsfox.feedTree");
  393.             if (keyNav[0].getAttribute("disableKeyNavigation") != "false")
  394.                 feedTree.setAttribute("disableKeyNavigation", true);
  395.             else
  396.                 feedTree.removeAttribute("disableKeyNavigation");
  397.         }
  398.  
  399.     var kids = xml.getElementsByTagName("key");
  400.     for (var i=0; i<kids.length; i++)
  401.     {
  402.             var idkey = document.getElementById(kids[i].getAttribute("id"));
  403.             idkey.setAttribute("modifiers", kids[i].getAttribute("modifiers"));
  404.             idkey.setAttribute("key", kids[i].getAttribute("key"));
  405.         }
  406.     }
  407.     
  408.     var keySet = document.getElementById("shortcut-keys");
  409.     var idkey = keySet.firstChild;
  410.     while (idkey != null)
  411.     {
  412.         scKeyList.push(idkey);
  413.         var mods = idkey.getAttribute("modifiers");
  414.         var keys = idkey.getAttribute("key");
  415.         var tmp = idkey.nextSibling;
  416.         if (mods=="" && keys=="") keySet.removeChild(idkey);
  417.         idkey = tmp;
  418.     }
  419. }
  420.  
  421. ////////////////////////////////////////////////////////////////
  422. // File Util
  423. ////////////////////////////////////////////////////////////////
  424.  
  425. function openOutputStream(file, flags)
  426. {
  427.   var out = Components.classes["@mozilla.org/network/file-output-stream;1"]
  428.     .createInstance(Components.interfaces.nsIFileOutputStream);
  429.   out.init(file, flags, 0664, 0);
  430.   return out;
  431. }
  432.  
  433. function print(out, data)
  434. {
  435.   out.write(data, data.length);
  436. }
  437.  
  438. function println(out, data)
  439. {
  440.   data += "\n";
  441.   out.write(data, data.length);
  442. }
  443.  
  444. ////////////////////////////////////////////////////////////////
  445. // Create new feed
  446. ////////////////////////////////////////////////////////////////
  447.  
  448. function createNewFeed(model, url, isExcluded, rmArtfile)
  449. {
  450.     var feed;
  451.   try
  452.   {
  453.     var uid  = model.makeUniqueUid(url); //makeUid(model, url);
  454.     feed = new Feed();
  455.     feed.uid = uid;
  456.     feed.url = url;
  457.     feed.defaultName = uid;
  458.     feed.exclude = isExcluded;
  459.     model.add(feed, isExcluded);
  460.         if (rmArtfile) deleteFeedFromDisk(feed);
  461.     if (!isExcluded)
  462.       {
  463.         var feedtree = document.getElementById("newsfox.feedTree");
  464.         if (gFdGroup[0].expanded == true) feedtree.view.toggleOpenState(0);
  465.         var index = model.size();
  466.         gFdGroup[0].list.push(index-1);
  467.         feedtree.view.toggleOpenState(0);
  468.       }
  469.   }
  470.   catch (err) 
  471.   { 
  472.     var msg = "createNewFeed(): [" + uid + "] " + err
  473.     alert(msg); 
  474.   }
  475.   return feed; 
  476. }
  477.  
  478. ////////////////////////////////////////////////////////////////
  479. // Load feeds from disk
  480. ////////////////////////////////////////////////////////////////
  481.  
  482. function loadIndices()
  483. {
  484.   try
  485.   {
  486.     var file = getProfileDir();
  487.     file.append("master_index.xml");
  488.  
  489.     var xml = document.implementation.createDocument("","",null);
  490.     xml.async = false;
  491.     xml.load("file:///" + file.path);
  492.  
  493.     // Get file version
  494.     var root = xml.getElementsByTagName("newsfox-index")[0];
  495.     var version = root.getAttribute("version");
  496.  
  497.     var fdgpidxstr = xml.getElementsByTagName("fdgpidx")[0].childNodes[0].nodeValue;
  498.     var tmp = fdgpidxstr.split(",");
  499.     for (var i=0; i < tmp.length; i++)
  500.       tmp[i] = parseInt(tmp[i]);
  501.     gIdx.fdgp = tmp;
  502.  
  503.     var feedidxstr = xml.getElementsByTagName("feedidx")[0].childNodes[0].nodeValue;
  504.     tmp = feedidxstr.split(",");
  505.     for (i=0; i < tmp.length; i++)
  506.       tmp[i] = parseInt(tmp[i]);
  507.     gIdx.feed = tmp;
  508.  
  509.     var catgidxstr = xml.getElementsByTagName("catgidx")[0].childNodes[0].nodeValue;
  510.     tmp = catgidxstr.split(",");
  511.     for (i=0; i < tmp.length; i++)
  512.     {
  513.       tmp[i] = parseInt(tmp[i]);
  514.       if (tmp[i] > 0) 
  515.       {
  516.     var feed = gFmodel.get(gIdx.feed[i]);
  517.     loadFeed(feed);
  518.       }
  519.     }
  520.     gIdx.catg = tmp;
  521.  
  522.     var openidxstr = xml.getElementsByTagName("openidx")[0].childNodes[0].nodeValue;
  523.     tmp = openidxstr.split(",");
  524.     for (i=0; i < tmp.length; i++)
  525.       tmp[i] = (tmp[i] == 1);
  526.     gIdx.open = tmp;
  527.   }
  528.   catch (err) { alert("loadIndices(): " + err); }
  529. }
  530.  
  531. function loadGroupModel()
  532. {
  533.   try
  534.   {
  535.     var file = getProfileDir();
  536.     file.append("master_group.xml");
  537.  
  538.     var xml = document.implementation.createDocument("","",null);
  539.     xml.async = false;
  540.     xml.load("file:///" + file.path);
  541.  
  542.     // Get file version
  543.     var root = xml.getElementsByTagName("newsfox-grouplist")[0];
  544.     var version = root.getAttribute("version");
  545.  
  546.     var tmp = new Array();
  547.     var kids = xml.getElementsByTagName("group");
  548.     for (var i=0; i<kids.length; i++)
  549.     {
  550.       gFdGroup[i] = new FeedGroup();
  551. //check empty string
  552.       gFdGroup[i].title = child(kids[i], "title").childNodes[0].nodeValue;
  553.       gFdGroup[i].expanded = child(kids[i], "expanded").childNodes[0].nodeValue;
  554.       gFdGroup[i].expanded = (gFdGroup[i].expanded == "true");
  555.       var v = child(kids[i], "list");
  556.       if (v != null && v.childNodes.length > 0)
  557.       {
  558.     var tmpstr = v.childNodes[0].nodeValue;
  559.     var tmp = tmpstr.split(",");
  560.     for (var j=0; j < tmp.length; j++)
  561.       gFdGroup[i].list[j] = parseInt(tmp[j]);
  562.       }
  563.       v = child(kids[i], "search");
  564.       if (v != null && v.childNodes.length > 0) 
  565.         gFdGroup[i].search = (v.childNodes[0].nodeValue == "true");
  566.             if (gFdGroup[i].search)
  567.             {
  568.                 v = child(kids[i], "flagged");
  569.                 if (v != null && v.childNodes.length > 0) gFdGroup[i].srchdat.flagged = parseInt(v.childNodes[0].nodeValue);
  570.                 v = child(kids[i], "unread");
  571.                 if (v != null && v.childNodes.length > 0) gFdGroup[i].srchdat.unread = parseInt(v.childNodes[0].nodeValue);
  572.                 gFdGroup[i].srchdat.text = child(kids[i], "text").childNodes[0].nodeValue;
  573.                 v = child(kids[i], "textflags");
  574.                 if (v != null && v.childNodes.length > 0) gFdGroup[i].srchdat.textflags = parseInt(v.childNodes[0].nodeValue);
  575.                 v = child(kids[i], "startTime");
  576.                 if (v != null && v.childNodes.length > 0) gFdGroup[i].srchdat.startTime = parseInt(v.childNodes[0].nodeValue);
  577.                 v = child(kids[i], "endTime");
  578.                 if (v != null && v.childNodes.length > 0) gFdGroup[i].srchdat.endTime = parseInt(v.childNodes[0].nodeValue);
  579.             }
  580.  
  581.             // Version 1.1 encoded strings, version 1.2 uses UTF-8 encoding
  582.             if (version == 1.1)
  583.             {
  584.                 gFdGroup[i].title = entityDecode(gFdGroup[i].title);
  585.                 if (gFdGroup[i].search)
  586.                     gFdGroup[i].srchdat.text = entityDecode(gFdGroup[i].srchdat.text);
  587.             }
  588.  
  589.     }
  590.     gFdGroup.length = kids.length;
  591.   }
  592.   catch (err) { alert("loadGroupModel(): " + err); }
  593. }
  594.  
  595. function loadFeedModel()
  596. {
  597.   gFmodel = new FdModel();
  598.   try
  599.   {
  600.     var file = getProfileDir();
  601.     file.append("master.xml");
  602.  
  603.     var xml = document.implementation.createDocument("","",null);
  604.     xml.async = false;
  605.     xml.load("file:///" + file.path);
  606.  
  607.     // Get file version
  608.     var root = xml.getElementsByTagName("newsfox-feedlist")[0];
  609.     var version = root.getAttribute("version");
  610.  
  611.         var iconfile, leafName;
  612.     var kids = xml.getElementsByTagName("feed");
  613.     for (var i=0; i<kids.length; i++)
  614.     {
  615.       var feed = new Feed();
  616.             feed.exclude = (kids[i].getAttribute("exclude") == "true");
  617.       feed.uid = child(kids[i], "uid").childNodes[0].nodeValue;
  618.       feed.url = child(kids[i], "url").childNodes[0].nodeValue;
  619.       feed.defaultName = feed.uid; 
  620.       var v = child(kids[i], "dname");
  621.       if (v != null) feed.defaultName = v.childNodes[0].nodeValue;
  622.  
  623.       v = child(kids[i], "home");
  624.       if (v != null && v.childNodes.length > 0) feed.homepage = v.childNodes[0].nodeValue;
  625.  
  626.             iconfile = getProfileDir();
  627.             leafName = feed.uid + ".ico";
  628.             iconfile.append(leafName);
  629.             if (!gOptions.favicons  || !iconfile.exists() || !isImg(iconfile))
  630.                 feed.icon.src = ICON_OK;
  631.             else
  632.                 feed.icon.src = "file:///" + iconfile.path;
  633.  
  634.       v = child(kids[i], "style");
  635.       if (v != null && v.childNodes.length > 0) feed.style = parseInt(v.childNodes[0].nodeValue);
  636.  
  637.       v = child(kids[i], "deleteOld");
  638.       if (v != null && v.childNodes.length > 0) 
  639.         feed.deleteOld = (v.childNodes[0].nodeValue == "true");
  640.  
  641.       v = child(kids[i], "daysToKeep");
  642.       if (v != null && v.childNodes.length > 0)
  643.                 feed.daysToKeep = parseInt(v.childNodes[0].nodeValue);
  644.  
  645.       v = child(kids[i], "autoCheck");
  646.       if (v != null && v.childNodes.length > 0)
  647.         feed.autoCheck = (v.childNodes[0].nodeValue == "true");
  648.       else feed.autoCheck = true;
  649.  
  650.       v = child(kids[i], "customName");
  651.       if (v != null && v.childNodes.length > 0) feed.customName = v.childNodes[0].nodeValue;
  652.  
  653.       v = child(kids[i], "dontDeleteUnread");
  654.       if (v != null && v.childNodes.length > 0) feed.dontDeleteUnread = (v.childNodes[0].nodeValue == "true");
  655.  
  656.       var elem = child(kids[i], "flags");
  657.       if (elem != null && elem.childNodes.length > 0)
  658.       {
  659.         var flags = elem.childNodes[0].nodeValue;
  660.         for (var j=0; j<flags.length; j++)
  661.           feed.flags.push(parseInt(flags.charAt(j)));
  662.       }
  663.  
  664.             // Version 1.1-2 encoded strings, version 1.3 uses UTF-8 encoding
  665.             if (version == 1.1 || version == 1.2)
  666.             {
  667.                 feed.defaultName = entityDecode(feed.defaultName);
  668.                 feed.customName =    entityDecode(feed.customName);
  669.             }
  670.  
  671.       // Version 1.0 tried to encode body text. Version 1.1
  672.       // just uses CDATA, so decode not necessary
  673.       if (version == 1.0)
  674.       {
  675.         feed.url = decodeHTML(feed.url);
  676.         feed.defaultName = decodeHTML(feed.defaultName);
  677.         feed.homepage = decodeHTML(feed.homepage);
  678.       }
  679.  
  680.       gFmodel.add(feed);
  681.     }
  682.   }
  683.   catch (err) 
  684.   { 
  685.     var msg = "loadFeedModel(): " + "[" + feed.uid + "," + elem + "]\n" + err; 
  686.     alert(msg);
  687.   }
  688. }
  689.  
  690. function loadFeed(feed)
  691. {
  692.     if (feed.loaded) return;
  693.   try
  694.   {
  695.     var file = getProfileDir();
  696.     file.append(feed.uid + ".xml");
  697.     checkFeedFile(file);
  698.  
  699.     var xml = document.implementation.createDocument("","",null);
  700.     xml.async = false;
  701.     xml.load("file:///" + file.path);
  702.  
  703.     // Get file version
  704.     var root = xml.getElementsByTagName("newsfox-feed")[0];
  705.     var version = root.getAttribute("version");
  706.  
  707.         var flags = feed.flags;
  708.         feed.flags = new Array();
  709.     var kids = xml.getElementsByTagName("article");
  710.     for (var i=0; i<kids.length; i++)
  711.     {
  712.             var id = child(kids[i], "id");
  713.             // versions up until 1.2 used link as id
  714.             if (id || version <= 1.2)
  715.             {
  716.                 if (id) id = id.childNodes[0];
  717.                 var link = child(kids[i], "link");
  718.                 if (link) link = link.childNodes[0];
  719.           var title = child(kids[i], "title").childNodes[0];
  720.           var date  = child(kids[i], "date").childNodes[0];
  721.           var body  = child(kids[i], "body").childNodes[0];
  722.           // AG: added category
  723.           var category  = child(kids[i], "category");
  724.           if (category != null) category = category.childNodes[0];
  725.                 var encls = new Array();
  726.                 var enc = kids[i].getElementsByTagName("enclosure");
  727.                 for (var j=0; j<enc.length; j++)
  728.                     encls.push(newEncl(enc[j],"url"));
  729.     
  730.           var art = new Article();
  731.           art.link  = (link  == null) ? null : link.nodeValue;
  732.           art.title = (title == null) ? "" : title.nodeValue;
  733.           art.body  = (body  == null) ? "" : body.nodeValue;
  734.           art.date  = new Date();
  735.           if (date != null) art.date.setTime(Date.parse(date.nodeValue));
  736.           // AG: added category
  737.           art.category  = (category  == null) ? "" : category.nodeValue;
  738.                 art.id = (id) ? id.nodeValue : null;
  739.                 art.enclosures = encls;
  740.     
  741.                 // Version 1.1-2 encoded strings, version 1.3 uses UTF-8 encoding
  742.                 if (version == 1.1 || version == 1.2)
  743.                 {
  744.               art.category = entityDecode(art.category);
  745.                     art.id = (art.id) ? art.id : art.link;
  746.                 }
  747.     
  748.           // Version 1.0 tried to encode body text. Version 1.1
  749.           // just uses CDATA, so decode not necessary
  750.           if (version == 1.0)
  751.           {
  752.             art.link  = decodeHTML(art.link);
  753.             art.title = decodeHTML(art.title);
  754.             art.body  = decodeHTML(art.body);
  755.           }
  756.     
  757.           // Add feed
  758.           feed.add(art,flags[i]);
  759.             }
  760.     }
  761.     feed.sortCategories();
  762.  
  763.         // load deleted articles
  764.     var kids = xml.getElementsByTagName("deletedarticle");
  765.     for (var i=0; i<kids.length; i++)
  766.     {
  767.       var link  = child(kids[i], "link").childNodes[0];
  768.       var date  = child(kids[i], "date").childNodes[0];
  769.  
  770.       var art = new Article();
  771.       art.link  = (link  == null) ? "" : link.nodeValue;
  772.       art.date  = new Date();
  773.       if (date != null) art.date.setTime(Date.parse(date.nodeValue));
  774.             var id = child(kids[i], "id");
  775.             if (id != null) id = id.childNodes[0];
  776.             art.id = (id) ? id.nodeValue : art.link;
  777.  
  778.       feed.deletedAdd(art);
  779.     }
  780.         // some articles deleted
  781.         if (flags.length != feed.flags.length) saveFeed(feed);
  782.         feed.loaded = true;
  783.   }
  784.   catch (err) { alert("loadFeed(): " + feed.uid + "\n\n" + err); }
  785. }
  786.  
  787. function checkFeedFile(file)
  788. {
  789.   try
  790.   {
  791.     if (!file.exists())
  792.     {
  793.       var out = openOutputStream(file, 0x02 | 0x08 | 0x20);
  794.             println(out, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
  795.       println(out, "<newsfox-feed version=\"1.3\">");
  796.       println(out, "</newsfox-feed>");
  797.       out.close();
  798.     }
  799.   }
  800.   catch (err) { alert(err); }
  801. }
  802.  
  803. ////////////////////////////////////////////////////////////////
  804. // Save feeds to disk
  805. ////////////////////////////////////////////////////////////////
  806.  
  807. function saveIndices()
  808. {
  809.   try
  810.   {
  811.     var file = getProfileDir();
  812.     file.append("master_index.xml");
  813.  
  814.     var out = openOutputStream(file, 0x02 | 0x08 | 0x20);
  815.         println(out, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
  816.     println(out, "<newsfox-index version=\"1.0\">");
  817.     var fdgpidxstr = gIdx.fdgp.join();
  818.     println(out, "  <fdgpidx>" + fdgpidxstr + "</fdgpidx>");
  819.     var feedidxstr = gIdx.feed.join();
  820.     println(out, "  <feedidx>" + feedidxstr + "</feedidx>");
  821.     var catgidxstr = gIdx.catg.join();
  822.     println(out, "  <catgidx>" + catgidxstr + "</catgidx>");
  823.     var tmp = new Array();
  824.     for (var i=0; i<gIdx.open.length; i++)
  825.       tmp[i] = 1*(gIdx.open[i] == true);
  826.     var openidxstr = tmp.join();
  827.     println(out, "  <openidx>" + openidxstr + "</openidx>");
  828.     println(out, "</newsfox-index>");
  829.     out.close();
  830.   }
  831.   catch (err) { alert("saveIndices(): " + err); }
  832. }
  833.  
  834. function saveGroupModel()
  835. {
  836.   try
  837.   {
  838.     var file = getProfileDir();
  839.     file.append("master_group.xml");
  840.  
  841.     var out = openOutputStream(file, 0x02 | 0x08 | 0x20);
  842.         println(out, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
  843.     println(out, "<newsfox-grouplist version=\"1.2\">");
  844.     for (var i=0; i<gFdGroup.length; i++)
  845.     {
  846.       var grpstr = gFdGroup[i].list.join();
  847.       println(out, " <group>");
  848.       println(out, "  <title><![CDATA[" + toUTF8(gFdGroup[i].title) + "]]></title>");
  849.       println(out, "  <expanded>" + gFdGroup[i].expanded + "</expanded>");
  850.       println(out, "  <search>" + gFdGroup[i].search + "</search>");
  851.             if (gFdGroup[i].search)
  852.             {
  853.                 println(out, "  <flagged>" + gFdGroup[i].srchdat.flagged + "</flagged>");
  854.                 println(out, "  <unread>" + gFdGroup[i].srchdat.unread + "</unread>");
  855.                 println(out, "  <text><![CDATA[" + toUTF8(gFdGroup[i].srchdat.text) + "]]></text>");
  856.                 println(out, "  <textflags>" + gFdGroup[i].srchdat.textflags + "</textflags>");
  857.                 println(out, "  <startTime>" + gFdGroup[i].srchdat.startTime + "</startTime>");
  858.                 println(out, "  <endTime>" + gFdGroup[i].srchdat.endTime + "</endTime>");
  859.             }
  860.       println(out, "  <list>" + grpstr + "</list>");
  861.       println(out, " </group>");
  862.     }
  863.     println(out, "</newsfox-grouplist>");
  864.     out.close();
  865.   }
  866.   catch (err) { alert("saveGroupModel(): " + err); }
  867. }
  868.  
  869. function saveFeedModel()
  870. {
  871.   try
  872.   {
  873.     var file = getProfileDir();
  874.     file.append("master.xml");
  875.  
  876.     var out = openOutputStream(file, 0x02 | 0x08 | 0x20);
  877.         println(out, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
  878.     println(out, "<newsfox-feedlist version=\"1.3\">");
  879.     for (var i=0; i<gFmodel.sizeTotal(); i++)
  880.     {
  881.       var feed = gFmodel.get(i);
  882.       println(out, " <feed exclude=\"" + feed.exclude + "\">");
  883.       println(out, "  <uid>" + toUTF8(feed.uid) + "</uid>");
  884.       println(out, "  <url><![CDATA[" + toUTF8(feed.url) + "]]></url>");
  885.       println(out, "  <dname><![CDATA[" + toUTF8(feed.defaultName) + "]]></dname>");
  886.       if (feed.homepage != null)
  887.         println(out, "  <home><![CDATA[" + toUTF8(feed.homepage) + "]]></home>");
  888.             // Only save if property overrides global setting
  889.       if (feed.style != 0)
  890.         println(out, "  <style>" + feed.style + "</style>");
  891.       println(out, "  <deleteOld>" + feed.deleteOld + "</deleteOld>");
  892.       println(out, "  <daysToKeep>" + feed.daysToKeep + "</daysToKeep>");
  893.       println(out, "  <autoCheck>" + feed.autoCheck + "</autoCheck>");
  894.             if( null != feed.customName )
  895.           println(out, "  <customName><![CDATA[" + toUTF8(feed.customName) + "]]></customName>");
  896.       println(out, "  <dontDeleteUnread>" + feed.dontDeleteUnread + "</dontDeleteUnread>");
  897.       print(out, "  <flags>");
  898.             var fdflag;
  899.       for (var j=0; j<feed.flags.length; j++)
  900.             {
  901.                 fdflag = 1*((feed.flags[j] & 0x01)!=0) + 4*((feed.flags[j] & 0x04)!=0);
  902. //RPdebug                if (fdflag != feed.flags[j]) alert(feed.getDisplayName() + "  j= " + j + "  feed.flags[j]= " + feed.flags[j]);
  903.         print(out, "" + fdflag);
  904.             }
  905.       println(out, "</flags>");
  906.       println(out, " </feed>");
  907.     }
  908.     println(out, "</newsfox-feedlist>");
  909.     out.close();
  910.   }
  911.   catch (err) { alert("saveFeedModel(): [" + feed.uid + "] " + err); }
  912. }
  913.  
  914. function saveFeed(feed)
  915. {
  916.   var file = getProfileDir();
  917.   file.append(feed.uid + ".xml");
  918.  
  919.   var out = openOutputStream(file, 0x02 | 0x08 | 0x20);
  920.     println(out, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
  921.   println(out, "<newsfox-feed version=\"1.3\">");
  922.   for (var i=0; i<feed.size(); i++)
  923.   {
  924.     var art = feed.get(i);
  925.     println(out, " <article>");
  926.     if (art.link) println(out, "  <link><![CDATA[" + toUTF8(art.link) + "]]></link>");
  927.     println(out, "  <title><![CDATA[" + toUTF8(art.title) + "]]></title>");
  928.     println(out, "  <date>" + toUTF8(art.date) + "</date>");
  929.     println(out, "  <body><![CDATA[" + toUTF8(art.body) + "]]></body>");
  930.     // AG: added category
  931.     println(out, "  <category><![CDATA[" + toUTF8(art.category) + "]]></category>");
  932.         if (art.id) println(out, "  <id><![CDATA[" + toUTF8(art.id) + "]]></id>");
  933.         for (var j=0; j<art.enclosures.length; j++)
  934.         {
  935.             var enc = art.enclosures[j];
  936.             println(out, '  <enclosure url="' + toUTF8(encodeHTML(enc.url)) + '" type="' + toUTF8(encodeHTML(enc.type)) + '" length="' + toUTF8(encodeHTML(enc.length)) + '"/>');
  937.         }
  938.     println(out, " </article>");
  939.   }
  940.   for (var i=0; i<feed.deletedsize(); i++)
  941.   {
  942.     var art = feed.deletedget(i);
  943.     println(out, " <deletedarticle>");
  944.     println(out, "  <link><![CDATA[" + toUTF8(art.link) + "]]></link>");
  945.     println(out, "  <date>" + toUTF8(art.date) + "</date>");
  946.         println(out, "  <id><![CDATA[" + toUTF8(art.id) + "]]></id>");
  947.     println(out, " </deletedarticle>");
  948.   }
  949.   println(out, "</newsfox-feed>");
  950.   out.close();
  951. }
  952.  
  953. ////////////////////////////////////////////////////////////////
  954. // Delete feed
  955. ////////////////////////////////////////////////////////////////
  956.  
  957. function deleteFeedFromDisk(feed)
  958. {
  959.   try
  960.   {
  961.     var file = getProfileDir();
  962.     file.append(feed.uid + ".xml");
  963.     if (file.exists()) file.remove(false);
  964.     var file = getProfileDir();
  965.     file.append(feed.uid + ".ico");
  966.     if (file.exists()) file.remove(false);
  967.   }
  968.   catch (err) {} // TODO sometimes icon file locked, file now checked on creation as well - alert("deleteFeedFromDisk(): [" + feed.uid + "] " + err); }
  969. }
  970.  
  971. ////////////////////////////////////////////////////////////////
  972. // Util
  973. ////////////////////////////////////////////////////////////////
  974.  
  975. function child(element, tagName)
  976. {
  977.   var kids = element.childNodes;
  978.   for (var i=0; i<kids.length; i++)
  979.     if (kids[i].nodeName == tagName) 
  980.       return kids[i];
  981.   return null;
  982. }
  983.  
  984. ////////////////////////////////////////////////////////////////
  985. // Encode/Decode
  986. ////////////////////////////////////////////////////////////////
  987.  
  988. /**
  989.  * Return XML-friendly HTML encoding.
  990.  */
  991. function encodeHTML(s) 
  992. {
  993.     if (!s) return "";
  994.   s = s.replace(new RegExp('&','gi'), '&');
  995.   s = s.replace(new RegExp('<','gi'), '<');
  996.   s = s.replace(new RegExp('>','gi'), '>');
  997.   s = s.replace(new RegExp('"','gi'), '"');
  998.   return s;
  999. }
  1000.  
  1001. /**
  1002.  * Return original HTML from encoding.
  1003.  */
  1004. function decodeHTML(s)
  1005. {
  1006.   s = s.replace(new RegExp('&'  ,'gi'), '&');
  1007.   s = s.replace(new RegExp('<'   ,'gi'), '<');
  1008.   s = s.replace(new RegExp('>'   ,'gi'), '>');
  1009.   s = s.replace(new RegExp('"' ,'gi'), '"');
  1010.   s = s.replace(new RegExp('´','gi'), '┤');
  1011.   return s;
  1012. }
  1013.  
  1014. function toUTF8(inVal)
  1015. {
  1016.     var uC = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
  1017.       .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
  1018.     uC.charset = "UTF-8";
  1019.     return uC.ConvertFromUnicode(inVal);
  1020. }
  1021.  
  1022. // from Sage project
  1023. function entityDecode(aStr) {
  1024.  var formatConverter = Components.classes["@mozilla.org/widget/htmlformatconverter;1"].createInstance(Components.interfaces.nsIFormatConverter);
  1025.  var fromStr = Components.classes["@mozilla.org/supports-string;1"].createInstance(Components.interfaces.nsISupportsString);
  1026.  fromStr.data = aStr;
  1027.  var toStr = {value: null};
  1028.  
  1029.  try {
  1030.   formatConverter.convert("text/html", fromStr, fromStr.toString().length, "text/unicode", toStr, {});
  1031.  } catch(e) {
  1032.   return aStr;
  1033.  }
  1034.  if (toStr.value) {
  1035.   toStr = toStr.value.QueryInterface(Components.interfaces.nsISupportsString);
  1036.   return toStr.toString();
  1037.  }
  1038.  return aStr;
  1039. }
  1040.