diff --new-file --recursive --suppress-common-lines --exclude changes.txt --exclude diff.txt releases/unplug-2.036/source/chrome/content/common.js releases/unplug-2.037/source/chrome/content/common.js 135,136c135,136 < version : 2.036, < codename : "sprawl", --- > version : 2.037, > codename : "newyork", 139c139 < revision : 201011142341, --- > revision : 201011212112, diff --new-file --recursive --suppress-common-lines --exclude changes.txt --exclude diff.txt releases/unplug-2.036/source/chrome/content/config/config.js releases/unplug-2.037/source/chrome/content/config/config.js 32a33 > set_text(); 34c35,40 < goto_tab_requested() --- > goto_tab_requested(); > } > > function set_text() { > document.getElementById("dmethod-saveas").setAttribute("label", UnPlug2.str("dmethod.saveas")) > document.getElementById("dmethod-openover").setAttribute("label", UnPlug2.str("dmethod.open-over")) diff --new-file --recursive --suppress-common-lines --exclude changes.txt --exclude diff.txt releases/unplug-2.036/source/chrome/content/config/config.xul releases/unplug-2.037/source/chrome/content/config/config.xul 114,116c114,116 < < < --- > > > diff --new-file --recursive --suppress-common-lines --exclude changes.txt --exclude diff.txt releases/unplug-2.036/source/chrome/content/display/download.js releases/unplug-2.037/source/chrome/content/display/download.js 0a1,335 > UnPlug2DownloadMethods = { > _button_lookup : {}, > _button_names : [], > > add_button : (function(name, data) { > this._button_names.push(name); > this._button_lookup[name] = data; > }), > > finish : (function () { > var lookup = this._button_lookup; > > // fix for config > var prefer = UnPlug2.get_pref("downloader"); > switch (prefer) { > case "saveas" : > lookup["saveas"].obscurity = -50; > break; > case "auto": > // use add-ons if possible > lookup["flashgot"].obscurity = -50; > lookup["dta"].obscurity = -40; > lookup["saveas"].obscurity = -30; > break; > case "openover": > lookup["open-over"].obscurity = -50; > break; > default: > UnPlug2.log("UnPlug2DownloadMethods preference of " + prefer + " is not supported"); > break; > } > > // sort _button_names > this._button_names.sort(function (a, b) { > var aobs = lookup[a].obscurity; > var bobs = lookup[b].obscurity; > return aobs - bobs; > }); > }), > > /* button_names: > * returns the button names, in order of preference (ie, the most obscure last) > */ > button_names : (function () { > return this._button_names; > }), > getinfo : (function (name) { > return this._button_lookup[name]; > }), > callback : (function (name, result) { > var that = this; > return (function (evt) { > try { > that.exec(name, result); > } catch (e) { > UnPlug2.log("Error in UnPlug2DownloadMethods for " + name + " " + result.toSource() + " with error " + e); > } > evt.stopPropagation(); > }); > }), > exec : (function (name, result) { > var data = this._button_lookup[name]; > if (!data) { > throw "Unknown button name " + name; > } > if (!data.avail(result)) { > throw "Cannot use DownloadMethod " + name + " with " + result.toSource(); > } > if (data.exec_fp) { > // if a method implements exec_fp, it wishes to use the "normal" file-picker code > // and we'll pass them the appropriate file object in the arguments > // TODO move this _save_as_box code here > var file = this._save_as_box(result.details.name, result.details.file_ext); > if (!file) { > return; > } > data.exec_fp(result, file); > } else { > data.exec(result); > } > }), > > /** > * Displays save-as box > * return { file : nsIFile?, fileURL : nsIFileURL? }, or null for cancel. > */ > _save_as_box : (function (name, ext) { > // make string, strip whitespace > name = (name || "no name").replace(RegExp("(^\\s|\\s$)", "g"), ""); > ext = (ext || "").replace(RegExp("(^\\s+|\\s+$)", "g"), ""); > > // look for .ext in name > if (!ext) { > var ext_re = RegExp("\\.(\\w{1,5})$"); > var ext_match = ext_re.exec(name); > if (ext_match) { > ext = ext_match[1]; > name = name.replace(ext_re, ""); > } else { > ext = "flv"; // fallback > } > } > > // replace bad characters with "_" > name = name.replace(RegExp("[\\*\\\\/\\?\\<\\>~#\\|`\\$\\&;:%\"'\x00-\x1f]+", "g"), "_"); > ext = ext.replace(RegExp("[^\\w\\s]+", "g"), "_"); > > var nsIFilePicker = Components.interfaces.nsIFilePicker; > var filepicker = Components.classes["@mozilla.org/filepicker;1"] > .createInstance(nsIFilePicker); > filepicker.init(window, "Save as", nsIFilePicker.modeSave); > > // default directory > var path = UnPlug2.get_pref("savepath"); > if (!path) { > path = Components.classes["@mozilla.org/download-manager;1"] > .getService(Components.interfaces.nsIDownloadManager) > .defaultDownloadsDirectory.path; > } > if (path) { > var f = Components.classes["@mozilla.org/file/local;1"] > .createInstance(Components.interfaces.nsILocalFile); > f.initWithPath(path); > if (f.exists() && f.isDirectory()) { > filepicker.displayDirectory = f; > } > } > > // default file name > filepicker.defaultString = name + "." + ext; > //filepicker.defaultExtention = ext; > > var ret = filepicker.show(); > if (ret != nsIFilePicker.returnOK && ret != nsIFilePicker.returnReplace) > return null; // cancelled > UnPlug2.set_pref("savepath", filepicker.file.parent.path); > return { "file" : filepicker.file, "fileURL" : filepicker.fileURL }; > }) > } > > UnPlug2DownloadMethods.add_button("saveas", { > avail : (function (res) { > return res.download.url && ( > res.download.url.indexOf("http://") == 0 > || res.download.url.indexOf("https://") == 0 > || res.download.url.indexOf("ftp://") == 0); > }), > exec_fp : (function (res, file) { > var io_service = Components.classes["@mozilla.org/network/io-service;1"] > .getService(Components.interfaces.nsIIOService) > var nsiurl = io_service.newURI(res.download.url, null, null); > var nsireferer = nsiurl; > try { > nsireferer = io_service.newURI(res.download.referer, null, null); > } catch(e) { > // pass > } > > var persistArgs = { > source : nsiurl, > contentType : "application/octet-stream", > target : file.fileURL, > postData : null, > bypassCache : false > }; > > // var persist = makeWebBrowserPersist(); > var persist = Components.classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]. > createInstance(Components.interfaces.nsIWebBrowserPersist); > > // Calculate persist flags. > const nsIWBP = Components.interfaces.nsIWebBrowserPersist; > persist.persistFlags = ( > nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES | > nsIWBP.PERSIST_FLAGS_FROM_CACHE | > nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION ); > > // Create download and initiate it (below) > var tr = Components.classes["@mozilla.org/transfer;1"].createInstance(Components.interfaces.nsITransfer); > > tr.init(persistArgs.source, persistArgs.target, "", null, null, null, persist); > > persist.progressListener = tr; > persist.saveURI(persistArgs.source, null, nsireferer, persistArgs.postData, null, persistArgs.target); > }), > obscurity : 0, > css : "saveas", > group : "main" > }); > > UnPlug2DownloadMethods.add_button("dta", { > avail : (function (res) { > if (!window.opener.DTA_AddingFunctions && !window.DTA) { > return false; > } > if (!res.download.url) { > return false; > } > if (res.download.url.indexOf("http://") != 0 && res.download.url.indexOf("https://") != 0) { > return false; > } > return true; > }), > exec_fp : (function (res, fileobj) { > var file = fileobj.file; > if (file.leafName.indexOf("*") >= 0) { > // we use the renaming mask, which treats *name*, etc as special. > throw "Filename contains star"; > } > > // call String() explicitly as DTA alters string > link = { > "url" : res.download.url, // string > "postData" : null, > "referrer" : String(res.download.referer || ""), // an object with toURL > "dirSave" : String(file.parent.path), // an object with addFinalSlash > "fileName" : String(file.leafName), // string > "description" : String(file.leafName) } // string > UnPlug2.log("Hello DTA, I'm sending you: " + link.toSource()); > if (window.DTA) { > // DTA 2.0 > DTA.sendLinksToManager(window, true, [link]); > } else { > // DTA 1.0 > window.opener.DTA_AddingFunctions.sendToDown(true, [link]); > } > }), > obscurity : 25, > css : "dta", > group : "main" > }); > > UnPlug2DownloadMethods.add_button("flashgot", { > avail : (function (res) { > if (! Components.classes["@maone.net/flashgot-service;1"]) { > // flashgot not installed > return false; > } > // flashgot is installed > return (res.download.url && ( > res.download.url.indexOf("http://") == 0 > || res.download.url.indexOf("https://") == 0)) > }), > exec : (function (res) { > var flashgot_service = Components.classes["@maone.net/flashgot-service;1"] > .getService(Components.interfaces.nsISupports) > .wrappedJSObject; > var name = res.details.name + "." + res.details.file_ext; > var links=[{ > href: res.download.url, > description: name, > fname : name, > // XXX can we set the save-as thingy? > noRedir: false }]; > links.referrer = res.download.referer || null; > links.document = window.document; // origWindow XXX TODO should not be from chrome > links.browserWindow = flashgot_service.getBrowserWindow(links.document); > flashgot_service.download(links); > flashgot_service.DMS[flashgot_service.defaultDM].download(links, flashgot_service.OP_ONE) > }), > obscurity : 20, > css : "flashgot", > group : "main" > }); > > UnPlug2DownloadMethods.add_button("rtmpdump", { > avail : (function (res) { > return res.download.url && ( > res.download.url.indexOf("rtmp://") == 0 > || res.download.url.indexOf("rtmpe://") == 0); > }), > exec : (function (res) { > alert("Sorry, this feature is not available yet"); > }), > obscurity : 50, > css : "extern rtmpdump", > group : "special" > }); > > UnPlug2DownloadMethods.add_button("open-tab", { > avail : (function (res) { > return (res.download.url ? true : false); > }), > exec : (function (res) { > var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"] > .getService(Components.interfaces.nsIWindowMediator); > var gbrowser = wm.getMostRecentWindow("navigator:browser").gBrowser; > var t = gbrowser.addTab(res.download.url); > gbrowser.selectedTab = t; > }), > obscurity : 100, > css : "open open-tab", > group : "open" > }); > > UnPlug2DownloadMethods.add_button("open-new", { > avail : (function (res) { > return (res.download.url ? true : false); > }), > exec : (function (res) { > window.open(res.download.url); > }), > obscurity : 100, > css : "open open-new", > group : "open" > }); > > UnPlug2DownloadMethods.add_button("open-over", { > avail : (function (res) { > return (res.download.url ? true : false); > }), > exec : (function (res) { > UnPlug2SearchPage._win.location = res.download.url; > }), > obscurity : 110, > css : "open open-over", > group : "open" > }); > > UnPlug2DownloadMethods.add_button("copyurl", { > avail : (function (res) { > return (res.download.url ? true : false); > }), > exec : (function (res) { > var clipboard = Components.classes["@mozilla.org/widget/clipboardhelper;1"] > .getService(Components.interfaces.nsIClipboardHelper); > clipboard.copyString(res.download.url); > }), > obscurity : 200, > css : "copyurl", > group : "copy" > }); > > UnPlug2DownloadMethods.finish(); > diff --new-file --recursive --suppress-common-lines --exclude changes.txt --exclude diff.txt releases/unplug-2.036/source/chrome/content/display/pop/pop.js releases/unplug-2.037/source/chrome/content/display/pop/pop.js 32,47d31 < // parent window's getBrowser() and global window namespace < this._gbrowser = args.gbrowser; < this._flashgot = args.flashgotserv; < < // firefox < this._download_mgr = Components.classes["@mozilla.org/download-manager;1"] < .getService(Components.interfaces.nsIDownloadManager); < < // io service < this._io_service = Components.classes["@mozilla.org/network/io-service;1"] < .getService(Components.interfaces.nsIIOService) < < // clipboard < this._clipboard = Components.classes["@mozilla.org/widget/clipboardhelper;1"] < .getService(Components.interfaces.nsIClipboardHelper); < 210,237c194,220 < // variables for use in the callbaks (closures) < var uid = result.uid; < var download = result.download; < var that = UnPlug2SearchPage; < < var getwidget = (function (wname) { < var l = reselem.getElementsByTagName("menuitem") < for (var i = 0; i < l.length; ++i) { < if (l[i].className && l[i].className.split(" ").indexOf(wname) >= 0) { < return l[i]; < } < } < var l = reselem.getElementsByTagName("toolbarbutton") < for (var i = 0; i < l.length; ++i) { < if (l[i].className && l[i].className.split(" ").indexOf(wname) >= 0) { < return l[i]; < } < } < return null; < }); < < var buttons = ["copyurl", "saveas", "dta", "flashgot", "special", "opentab", "opennew", "openover", "config", "fallback"] < // what's available < var available_buttons = []; < for (var i = 0; i < buttons.length; ++i) { < var wname = buttons[i]; < if (that.widgets[wname].avail(result)) { < available_buttons.push(wname); --- > var popup = reselem.getElementsByTagName("menupopup")[0]; > var button_names = UnPlug2DownloadMethods.button_names(); > var prev_elem_group = null; > var avail_elements = []; > > // we want to replace the old callbacks with new callbacks > while (popup.firstChild) { > popup.removeChild(popup.firstChild); > } > for (var i = 0; i < button_names.length; ++i) { > var name = button_names[i]; > var info = UnPlug2DownloadMethods.getinfo(name); > if (info.avail(result)) { > if (prev_elem_group != info.group && avail_elements.length != 0) { > var spacer = document.createElement("menuseparator"); > popup.appendChild(spacer); > } > prev_elem_group = info.group; > avail_elements.push(name); > var elem = document.createElement("menuitem"); > prev_elem_is_spacer = false; > elem.setAttribute("accesskey", UnPlug2.str("dmethod." + name + ".a")) > elem.setAttribute("label", UnPlug2.str("dmethod." + name)); > elem.setAttribute("tooltiptext", UnPlug2.str("dmethod." + name + ".tip")); > elem.className = "menuitem-iconic " + info.css; > elem.addEventListener("command", UnPlug2DownloadMethods.callback(name, result), false); > popup.appendChild(elem); 240,247c223,233 < // what's best of those available (for main button action) < var best_downloader = null; < for (var i = 0; i < that.preferred_downloaders.length; ++i) { < var wname = that.preferred_downloaders[i]; < if (available_buttons.indexOf(wname) >= 0) { < best_downloader = wname; < break; < } --- > > var copy_button = reselem.getElementsByTagName("toolbarbutton")[0]; > var copy_info = UnPlug2DownloadMethods.getinfo("copyurl"); > if (copy_info && copy_info.avail(result)) { > copy_button.addEventListener("command", UnPlug2DownloadMethods.callback("copyurl", result), false); > copy_button.setAttribute("label", UnPlug2.str("dmethod.copyurl")); > copy_button.setAttribute("accesskey", UnPlug2.str("dmethod.copyurl.a")); > copy_button.setAttribute("tooltiptext", UnPlug2.str("dmethod.copyurl.tip")); > copy_button.setAttribute("disabled", false); > } else { > copy_button.setAttribute("disabled", true); 249,269c235,246 < // hook up events and enable < for (var i = 0; i < available_buttons.length; ++i) { < var wname = available_buttons[i]; < var w = getwidget(wname); < // use closure to get correct scoping < var function_function = (function (that, uid, wname) { < return (function (evt) { < that.widgetresponse(uid, wname, wname == "config" ? "downloader" : null); < evt.stopPropagation(); < }); < }); < if (w) { < w.addEventListener("command", function_function(that, uid, wname), false); < w.setAttribute("disabled", false); < } < if (best_downloader == wname) { < // also hook up main button < var main = getwidget("big-download-button"); < main.addEventListener("command", function_function(that, uid, wname), false); < main.className = "big-download-button menuitem-iconic " + wname; < } --- > > var main_button = reselem.getElementsByTagName("toolbarbutton")[1]; > if (avail_elements.length == 0) { > main_button.setAttribute("disabled", true); > main_button.className = "menuitem-icon unavailable" > main_button.setAttribute("tooltiptext", UnPlug2.str("dmethod.unavailable.tip")); > } else { > var name = avail_elements[0]; > var info = UnPlug2DownloadMethods.getinfo(name); > main_button.className = "menuitem-iconic " + info.css; > main_button.addEventListener("command", UnPlug2DownloadMethods.callback(name, result), false); > main_button.setAttribute("tooltiptext", UnPlug2.str("dmethod." + name + ".tip")); 365,463d341 < _download_ff2_version : function (url, file, referer) { < var nsiurl = this._io_service.newURI(url, null, null); < var nsireferer = nsiurl; < try { < nsireferer = this._io_service.newURI(referer, null, null); < } catch(e) { < // pass < } < < var persistArgs = { < source : nsiurl, < contentType : "application/octet-stream", < target : file, < postData : null, < bypassCache : false < }; < < // var persist = makeWebBrowserPersist(); < var persist = Components.classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]. < createInstance(Components.interfaces.nsIWebBrowserPersist); < < // Calculate persist flags. < const nsIWBP = Components.interfaces.nsIWebBrowserPersist; < persist.persistFlags = ( < nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES | < nsIWBP.PERSIST_FLAGS_FROM_CACHE | < nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION ); < < // Create download and initiate it (below) < var tr = Components.classes["@mozilla.org/transfer;1"].createInstance(Components.interfaces.nsITransfer); < < tr.init(persistArgs.source, persistArgs.target, "", null, null, null, persist); < < persist.progressListener = tr; < persist.saveURI(persistArgs.source, null, nsireferer, persistArgs.postData, null, persistArgs.target); < }, < < /** < * return file or null. < */ < _save_as_box : function (name, ext) { < // make string, strip whitespace < name = (name || "no name").replace(RegExp("(^\\s|\\s$)", "g"), ""); < ext = (ext || "").replace(RegExp("(^\\s+|\\s+$)", "g"), ""); < < // look for .ext in name < if (!ext) { < var ext_re = RegExp("\\.(\\w{1,5})$"); < var ext_match = ext_re.exec(name); < if (ext_match) { < ext = ext_match[1]; < name = name.replace(ext_re, ""); < } else { < ext = "flv"; // fallback < } < } < < // replace bad characters with "_" < name = name.replace(RegExp("[\\*\\\\/\\?\\<\\>~#\\|`\\$\\&;:%\"'\x00-\x1f]+", "g"), "_"); < ext = ext.replace(RegExp("[^\\w\\s]+", "g"), "_"); < < var nsIFilePicker = Components.interfaces.nsIFilePicker; < var filepicker = Components.classes["@mozilla.org/filepicker;1"] < .createInstance(nsIFilePicker); < < filepicker.defaultString = name + "." + ext; < //filepicker.defaultExtention = ext; < filepicker.init(window, "Save as", nsIFilePicker.modeSave); < var ret = filepicker.show(); < < if (ret != nsIFilePicker.returnOK && ret != nsIFilePicker.returnReplace) < return null; // cancelled < < return { "file" : filepicker.file, "fileURL" : filepicker.fileURL }; < }, < < /* < * save String url into a nsIFile file < */ < _download_with_downloadmgr : function (source_url, file) { < var persist = Components.classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"] < .createInstance(Components.interfaces.nsIWebBrowserPersist); < < var ioFile = this._io_service.newFileURI(file); < var obj_URI = this._io_service.newURI(source_url, null, null); < < persist.progressListener = this._download_mgr.addDownload( < 0, // aDownloadType < obj_URI, // source < ioFile, // target < "", // aDisplayName < null, //aMIMEInfo < Date.now(), // aStartTim < null, // aTempFile, < persist); // aCancelable < < persist.saveURI(obj_URI, null, null, null, "", ioFile); < }, < 518,665d395 < /* < * For callbacks from unplug_result button presses < */ < widgets : { < "special" : { < avail : function (res) { return res.download.url && ( < res.download.url.indexOf("rtmp://") == 0 < || res.download.url.indexOf("rtmpe://") == 0); < }, < exec : function (res, data) { < alert("Sorry, this feature is not available yet"); < } < }, < "copyurl" : { < avail : function (res) { return (res.download.url ? true : false); }, < exec : function (res, data) { < UnPlug2SearchPage._clipboard.copyString(res.download.url); < } < }, < "saveas" : { < avail : function (res) { return res.download.url && ( < res.download.url.indexOf("http://") == 0 < || res.download.url.indexOf("https://") == 0 < || res.download.url.indexOf("ftp://") == 0); < }, < exec : function (res, data) { < var file = UnPlug2SearchPage._save_as_box(res.details.name, res.details.file_ext); < if (!file) { < return; < } < < // UnPlug2SearchPage._download_with_downloadmgr(res.download.url, file.file); < UnPlug2SearchPage._download_ff2_version(res.download.url, file.fileURL, res.download.referer); < } < }, < "opentab" : { < avail : function (res) { return (res.download.url ? true : false); }, < exec : function (res, data) { < var t = UnPlug2SearchPage._gbrowser.addTab(res.download.url); < UnPlug2SearchPage._gbrowser.selectedTab = t; < } < }, < "opennew" : { < avail : function (res) { return (res.download.url ? true : false); }, < exec : function (res, data) { < window.open(res.download.url); < } < }, < "openover" : { < avail : function (res) { return (res.download.url ? true : false); }, < exec : function (res, data) { < UnPlug2SearchPage._win.location = res.download.url; < } < }, < "dta" : { < avail : function (res) { < if (!window.opener.DTA_AddingFunctions && !window.DTA) { < return false; < } < if (!res.download.url) { < return false; < } < if (res.download.url.indexOf("http://") != 0 && res.download.url.indexOf("https://") != 0) { < return false; < } < return true; < }, < exec : function (res, data) { < var fileobj = UnPlug2SearchPage._save_as_box(res.details.name, res.details.file_ext); < if (!fileobj) { < return; < } < < var file = fileobj.file; < if (file.leafName.indexOf("*") >= 0) { < // we use the renaming mask, which treats *name*, etc as special. < throw "Filename contains star"; < } < < // call String() explicitly as DTA alters string < link = { < "url" : res.download.url, // string < "postData" : null, < "referrer" : String(res.download.referer || ""), // an object with toURL < "dirSave" : String(file.parent.path), // an object with addFinalSlash < "fileName" : String(file.leafName), // string < "description" : String(file.leafName) } // string < UnPlug2.log("Hello DTA, I'm sending you: " + link.toSource()); < if (window.DTA) { < // DTA 2.0 < DTA.sendLinksToManager(window, true, [link]); < } else { < // DTA 1.0 < window.opener.DTA_AddingFunctions.sendToDown(true, [link]); < } < } < }, < "flashgot" : { < avail : function (res) { < if (UnPlug2SearchPage._flashgot) { < // flashgot installed < return (res.download.url && ( < res.download.url.indexOf("http://") == 0 < || res.download.url.indexOf("https://") == 0)) < } else { < // flashgot not installed < return false; < } < }, < exec : function (res, data) { < var fg = UnPlug2SearchPage._flashgot; < fg.download([res.download.url], fg.OP_ONE); < } < }, < "fallback" : { < avail : function (res) { return true; }, < exec : function (res, data) { < alert(UnPlug2.str("cannot_download_this_kind")); < } < }, < "config" : { < avail : function (res) { return true; }, < exec : function (res, data) { < UnPlug2SearchPage.configure(data); < } < } < }, < < widgetresponse : function (reference, widgetname, widgetdata) { < try { < var result = this.results[reference]; < if (!UnPlug2SearchPage.widgets[widgetname].avail(result)) { < throw ("Widget " + widgetname + " not available for result " + result.toSource()); < } < UnPlug2SearchPage.widgets[widgetname].exec(result, widgetdata); < } catch(e) { < UnPlug2.log("widgetresponse: " + e); < } < }, < < widgetavailable : function (result, widgetname) { < try { < return UnPlug2SearchPage.widgets[widgetname].avail(result); < } catch(e) { < UnPlug2.log("widgetavailable: " + e); < } < }, < diff --new-file --recursive --suppress-common-lines --exclude changes.txt --exclude diff.txt releases/unplug-2.036/source/chrome/content/display/pop/pop.xul releases/unplug-2.037/source/chrome/content/display/pop/pop.xul 9,10d8 < < %dtdresult; 60a59 >