report.js (9710B)
1 /* 2 ***** BEGIN LICENSE BLOCK ***** 3 4 Copyright © 2009 Center for History and New Media 5 George Mason University, Fairfax, Virginia, USA 6 http://zotero.org 7 8 This file is part of Zotero. 9 10 Zotero is free software: you can redistribute it and/or modify 11 it under the terms of the GNU Affero General Public License as published by 12 the Free Software Foundation, either version 3 of the License, or 13 (at your option) any later version. 14 15 Zotero is distributed in the hope that it will be useful, 16 but WITHOUT ANY WARRANTY; without even the implied warranty of 17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 GNU Affero General Public License for more details. 19 20 You should have received a copy of the GNU Affero General Public License 21 along with Zotero. If not, see <http://www.gnu.org/licenses/>. 22 23 ***** END LICENSE BLOCK ***** 24 */ 25 26 27 Zotero.Report = {}; 28 29 Zotero.Report.HTML = new function () { 30 let domParser = Components.classes["@mozilla.org/xmlextras/domparser;1"] 31 .createInstance(Components.interfaces.nsIDOMParser); 32 33 this.listGenerator = function* (items, combineChildItems) { 34 yield '<!DOCTYPE html>\n' 35 + '<html>\n' 36 + ' <head>\n' 37 + ' <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\n' 38 + ' <title>' + Zotero.getString('report.title.default') + '</title>\n' 39 + ' <link rel="stylesheet" type="text/css" href="zotero://report/detail.css"/>\n' 40 + ' <link rel="stylesheet" type="text/css" media="screen,projection" href="zotero://report/detail_screen.css"/>\n' 41 + ' <link rel="stylesheet" type="text/css" media="print" href="zotero://report/detail_print.css"/>\n' 42 + ' </head>\n' 43 + ' <body>\n' 44 + ' <ul class="report' + (combineChildItems ? ' combineChildItems' : '') + '">'; 45 46 for (let i=0; i<items.length; i++) { 47 let obj = items[i]; 48 49 let content = '\n\t\t\t<li id="item_' + obj.key + '" class="item ' + obj.itemType + '">\n'; 50 51 if (obj.title) { 52 // Top-level item matched search, so display title 53 if (obj.reportSearchMatch) { 54 content += '\t\t\t<h2>' + escapeXML(obj.title) + '</h2>\n'; 55 } 56 // Non-matching parent, so display "Parent Item: [Title]" 57 else { 58 content += '\t\t\t<h2 class="parentItem">' + escapeXML(Zotero.getString('report.parentItem')) 59 + ' <span class="title">' + escapeXML(obj.title) + '</span></h2>\n'; 60 } 61 } 62 63 // If parent matches search, display parent item metadata table and tags 64 if (obj.reportSearchMatch) { 65 content += _generateMetadataTable(obj); 66 67 content += _generateTagsList(obj); 68 69 // Independent note 70 if (obj['note']) { 71 content += '\n\t\t\t'; 72 content += getNoteHTML(obj.note); 73 } 74 } 75 76 // Children 77 if (obj.reportChildren) { 78 // Child notes 79 if (obj.reportChildren.notes.length) { 80 // Only display "Notes:" header if parent matches search 81 if (obj.reportSearchMatch) { 82 content += '\t\t\t\t<h3 class="notes">' + escapeXML(Zotero.getString('report.notes')) + '</h3>\n'; 83 } 84 content += '\t\t\t\t<ul class="notes">\n'; 85 for (let note of obj.reportChildren.notes) { 86 content += '\t\t\t\t\t<li id="item_' + note.key + '">\n'; 87 88 content += getNoteHTML(note.note); 89 90 // Child note tags 91 content += _generateTagsList(note); 92 93 content += '\t\t\t\t\t</li>\n'; 94 } 95 content += '\t\t\t\t</ul>\n'; 96 } 97 98 // Chid attachments 99 content += _generateAttachmentsList(obj.reportChildren); 100 } 101 102 // Related items 103 if (obj.reportSearchMatch && Zotero.Relations.relatedItemPredicate in obj.relations) { 104 content += '\t\t\t\t<h3 class="related">' + escapeXML(Zotero.getString('itemFields.related')) + '</h3>\n'; 105 content += '\t\t\t\t<ul class="related">\n'; 106 var rels = obj.relations[Zotero.Relations.relatedItemPredicate]; 107 // TEMP 108 if (!Array.isArray(rels)) { 109 rels = [rels]; 110 } 111 for (let i=0; i<rels.length; i++) { 112 let rel = rels[i]; 113 let relItem = yield Zotero.URI.getURIItem(rel); 114 if (relItem) { 115 content += '\t\t\t\t\t<li id="item_' + relItem.key + '">'; 116 content += escapeXML(relItem.getDisplayTitle()); 117 content += '</li>\n'; 118 } 119 } 120 content += '\t\t\t\t</ul>\n'; 121 } 122 123 124 content += '\t\t\t</li>\n\n'; 125 126 yield content; 127 } 128 129 yield '\t\t</ul>\n\t</body>\n</html>'; 130 }; 131 132 133 function _generateMetadataTable(obj) { 134 var table = false; 135 var content = '\t\t\t\t<table>\n'; 136 137 // Item type 138 content += '\t\t\t\t\t<tr>\n'; 139 content += '\t\t\t\t\t\t<th>' 140 + escapeXML(Zotero.getString('itemFields.itemType')) 141 + '</th>\n'; 142 content += '\t\t\t\t\t\t<td>' + escapeXML(Zotero.ItemTypes.getLocalizedString(obj.itemType)) + '</td>\n'; 143 content += '\t\t\t\t\t</tr>\n'; 144 145 // Creators 146 if (obj['creators']) { 147 table = true; 148 var displayText; 149 150 for (let creator of obj['creators']) { 151 // One field 152 if (creator.name !== undefined) { 153 displayText = creator.name; 154 } 155 // Two field 156 else { 157 displayText = (creator.firstName + ' ' + creator.lastName).trim(); 158 } 159 160 content += '\t\t\t\t\t<tr>\n'; 161 content += '\t\t\t\t\t\t<th class="' + creator.creatorType + '">' 162 + escapeXML(Zotero.getString('creatorTypes.' + creator.creatorType)) 163 + '</th>\n'; 164 content += '\t\t\t\t\t\t<td>' + escapeXML(displayText) + '</td>\n'; 165 content += '\t\t\t\t\t</tr>\n'; 166 } 167 } 168 169 // Move dateAdded and dateModified to the end of the objay 170 var da = obj['dateAdded']; 171 var dm = obj['dateModified']; 172 delete obj['dateAdded']; 173 delete obj['dateModified']; 174 obj['dateAdded'] = da; 175 obj['dateModified'] = dm; 176 177 for (var i in obj) { 178 // Skip certain fields 179 switch (i) { 180 case 'reportSearchMatch': 181 case 'reportChildren': 182 183 case 'key': 184 case 'version': 185 case 'itemType': 186 case 'title': 187 case 'creators': 188 case 'note': 189 case 'collections': 190 case 'relations': 191 case 'tags': 192 case 'deleted': 193 case 'parentItem': 194 195 case 'charset': 196 case 'contentType': 197 case 'linkMode': 198 case 'path': 199 continue; 200 } 201 202 try { 203 var localizedFieldName = Zotero.ItemFields.getLocalizedString(obj.itemType, i); 204 } 205 // Skip fields we don't have a localized string for 206 catch (e) { 207 Zotero.debug('Localized string not available for ' + 'itemFields.' + i, 2); 208 continue; 209 } 210 211 obj[i] = (obj[i] + '').trim(); 212 213 // Skip empty fields 214 if (!obj[i]) { 215 continue; 216 } 217 218 table = true; 219 var fieldText; 220 221 if (i == 'url' && obj[i].match(/^https?:\/\//)) { 222 fieldText = '<a href="' + escapeXML(obj[i]) + '">' + escapeXML(obj[i]) + '</a>'; 223 } 224 // Hyperlink DOI 225 else if (i == 'DOI') { 226 fieldText = '<a href="' + escapeXML('http://doi.org/' + obj[i]) + '">' 227 + escapeXML(obj[i]) + '</a>'; 228 } 229 // Remove SQL date from multipart dates 230 // (e.g. '2006-00-00 Summer 2006' becomes 'Summer 2006') 231 else if (i=='date') { 232 fieldText = escapeXML(Zotero.Date.multipartToStr(obj[i])); 233 } 234 // Convert dates to local format 235 else if (i=='accessDate' || i=='dateAdded' || i=='dateModified') { 236 var date = Zotero.Date.isoToDate(obj[i], true) 237 fieldText = escapeXML(date.toLocaleString()); 238 } 239 else { 240 fieldText = escapeXML(obj[i]); 241 } 242 243 content += '\t\t\t\t\t<tr>\n\t\t\t\t\t<th>' + escapeXML(localizedFieldName) 244 + '</th>\n\t\t\t\t\t\t<td>' + fieldText + '</td>\n\t\t\t\t\t</tr>\n'; 245 } 246 247 content += '\t\t\t\t</table>\n'; 248 249 return table ? content : ''; 250 } 251 252 253 function _generateTagsList(obj) { 254 var content = ''; 255 if (obj.tags && obj.tags.length) { 256 var str = Zotero.getString('report.tags'); 257 content += '\t\t\t\t<h3 class="tags">' + escapeXML(str) + '</h3>\n'; 258 content += '\t\t\t\t<ul class="tags">\n'; 259 for (let i=0; i<obj.tags.length; i++) { 260 content += '\t\t\t\t\t<li>' + escapeXML(obj.tags[i].tag) + '</li>\n'; 261 } 262 content += '\t\t\t\t</ul>\n'; 263 } 264 return content; 265 } 266 267 268 function _generateAttachmentsList(obj) { 269 var content = ''; 270 if (obj.attachments && obj.attachments.length) { 271 content += '\t\t\t\t<h3 class="attachments">' + escapeXML(Zotero.getString('itemFields.attachments')) + '</h3>\n'; 272 content += '\t\t\t\t<ul class="attachments">\n'; 273 for (let i=0; i<obj.attachments.length; i++) { 274 let attachment = obj.attachments[i]; 275 276 content += '\t\t\t\t\t<li id="item_' + attachment.key + '">'; 277 if (attachment.title !== undefined) { 278 content += escapeXML(attachment.title); 279 } 280 281 // Attachment tags 282 content += _generateTagsList(attachment); 283 284 // Attachment note 285 if (attachment.note) { 286 content += '\t\t\t\t\t\t<div class="note">'; 287 content += getNoteHTML(attachment.note); 288 content += '\t\t\t\t\t</div>'; 289 } 290 291 content += '\t\t\t\t\t</li>\n'; 292 } 293 content += '\t\t\t\t</ul>\n'; 294 } 295 return content; 296 } 297 298 299 function getNoteHTML(note) { 300 // If HTML tag or entity, parse as HTML 301 if (note.match(/(<(p|ul|ol|div|a|br|b|i|u|strong|em( >))|&[a-z]+;|&#[0-9]+;)/)) { 302 let doc = domParser.parseFromString('<div>' 303 + note 304 // Strip control characters (for notes that were 305 // added before item.setNote() started doing this) 306 .replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, "") 307 + '</div>', "text/html"); 308 return doc.body.innerHTML + '\n'; 309 } 310 // Otherwise, treat as plain text 311 return '<p class="plaintext">' + escapeXML(note) + '</p>\n'; 312 } 313 314 315 var escapeXML = function (str) { 316 str = str.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\ud800-\udfff\ufffe\uffff]/g, '\u2B1A'); 317 return Zotero.Utilities.htmlSpecialChars(str); 318 } 319 }