1 /* -*- Mode: javascript; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 *
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
9 *
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
14 *
15 * The Original Code is Mozilla Calendar code.
16 *
17 * The Initial Developer of the Original Code is
18 * ArentJan Banck <ajbanck@planet.nl>.
19 * Portions created by the Initial Developer are Copyright (C) 2002
20 * the Initial Developer. All Rights Reserved.
21 *
22 * Contributor(s):
23 * Michiel van Leeuwen <mvl@exedo.nl>
24 * Daniel Boelzle <daniel.boelzle@sun.com>
25 *
26 * Alternatively, the contents of this file may be used under the terms of
27 * either the GNU General Public License Version 2 or later (the "GPL"), or
28 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
37 *
38 * ***** END LICENSE BLOCK ***** */
39
40 // Export
41
42 function calHtmlExporter() {
43 this.wrappedJSObject = this;
44 }
45
46 calHtmlExporter.prototype.QueryInterface =
47 function QueryInterface(aIID) {
48 if (!aIID.equals(Components.interfaces.nsISupports) &&
49 !aIID.equals(Components.interfaces.calIExporter)) {
50 throw Components.results.NS_ERROR_NO_INTERFACE;
51 }
52
53 return this;
54 };
55
56 calHtmlExporter.prototype.getFileTypes =
57 function getFileTypes(aCount) {
58 aCount.value = 1;
59 var wildmat = '*.html; *.htm';
60 var label = calGetString("calendar", 'filterHtml', [wildmat]);
61 return([{defaultExtension:'html',
62 extensionFilter: wildmat,
63 description: label}]);
64 };
65
66 // not prototype.export. export is reserved.
67 calHtmlExporter.prototype.exportToStream =
68 function html_exportToStream(aStream, aCount, aItems, aTitle) {
69 var dateFormatter =
70 Components.classes["@mozilla.org/calendar/datetime-formatter;1"]
71 .getService(Components.interfaces.calIDateTimeFormatter);
72
73 var documentTitle = aTitle;
74 if (!documentTitle) {
75 documentTitle = calGetString("calendar", "HTMLTitle");
76 }
77
78 var html =
79 <html>
80 <head>
81 <title>{documentTitle}</title>
82 <meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/>
83 <style type='text/css'/>
84 </head>
85 <body>
86 <!-- Note on the use of the summarykey class: this is a
87 special class, because in the default style, it is hidden.
88 The div is still included for those that want a different
89 style, where the key is visible -->
90 </body>
91 </html>;
92 // XXX The html comment above won't propagate to the resulting html.
93 // Should fix that, one day.
94
95 // Using this way to create the styles, because { and } are special chars
96 // in e4x. They have to be escaped, which doesn't improve readability
97 html.head.style = ".vevent {border: 1px solid black; padding: 0px; margin-bottom: 10px;}\n";
98 html.head.style += "div.key {font-style: italic; margin-left: 3px;}\n";
99 html.head.style += "div.value {margin-left: 20px;}\n";
100 html.head.style += "abbr {border: none;}\n";
101 html.head.style += ".summarykey {display: none;}\n";
102 html.head.style += "div.summary {background: lightgray; font-weight: bold; margin: 0px; padding: 3px;}\n";
103
104 // Sort aItems
105 function sortFunc(a, b) {
106 var start_a = calGetStartDate(a);
107 if (!start_a) {
108 return -1;
109 }
110 var start_b = calGetStartDate(b);
111 if (!start_b) {
112 return 1;
113 }
114 return start_a.compare(start_b);
115 }
116 aItems.sort(sortFunc);
117
118 var prefixTitle = calGetString("calendar", "htmlPrefixTitle");
119 var prefixWhen = calGetString("calendar", "htmlPrefixWhen");
120 var prefixLocation = calGetString("calendar", "htmlPrefixLocation");
121 var prefixDescription = calGetString("calendar", "htmlPrefixDescription");
122 var defaultTimezone = calendarDefaultTimezone();
123
124 for (var pos = 0; pos < aItems.length; ++pos) {
125 var item = aItems[pos];
126
127 // Put properties of the event in a definition list
128 // Use hCalendar classes as bonus
129 var ev = <div class='vevent'/>;
130
131 // Title
132 ev.appendChild(
133 <div>
134 <div class='key summarykey'>{prefixTitle}</div>
135 <div class='value summary'>{item.title}</div>
136 </div>
137 );
138
139 var startDate = calGetStartDate(item);
140 var endDate = calGetEndDate(item);
141 if (startDate) {
142 startDate = startDate.getInTimezone(defaultTimezone);
143 }
144 if (endDate) {
145 endDate = endDate.getInTimezone(defaultTimezone);
146 }
147
148 // Start and end
149 var startstr = new Object();
150 var endstr = new Object();
151 if (startDate && endDate)
152 dateFormatter.formatInterval(startDate, endDate, startstr, endstr);
153 else {
154 startstr.value = "";
155 endstr.value = "";
156 }
157
158 // Include the end date anyway, even when empty, because the dtend
159 // class should be there, for hCalendar goodness.
160 var seperator = "";
161 if (endstr.value) {
162 seperator = " - ";
163 }
164
165 ev.appendChild(
166 <div>
167 <div class='key'>{prefixWhen}</div>
168 <div class='value'>
169 <abbr class='dtstart' title={startDate ? startDate.icalString : "none"}>{startstr.value}</abbr>
170 {seperator}
171 <abbr class='dtend' title={endDate ? endDate.icalString : "none"}>{endstr.value}</abbr>
172 </div>
173 </div>
174 );
175
176
177 // Location
178 if (item.getProperty('LOCATION')) {
179 ev.appendChild(
180 <div>
181 <div class='key'>{prefixLocation}</div>
182 <div class='value location'>{item.getProperty('LOCATION')}</div>
183 </div>
184 );
185 }
186
187 // Description, inside a pre to preserve formating when needed.
188 var desc = item.getProperty('DESCRIPTION');
189 if (desc && desc.length > 0) {
190 var usePre = false;
191 if (desc.indexOf("\n ") >= 0 || desc.indexOf("\n\t") >= 0 ||
192 desc.indexOf(" ") == 0 || desc.indexOf("\t") == 0)
193 // (RegExp /^[ \t]/ doesn't work.)
194 // contains indented preformatted text after beginning or newline
195 // so preserve indentation with PRE.
196 usePre = true;
197
198 var descnode =
199 <div>
200 <div class='key'>{prefixDescription}</div>
201 <div class='value'/>
202 </div>;
203
204 if (usePre) {
205 descnode.div[1] = <pre class='description'>{desc}</pre>;
206 } else {
207 var lines = desc.split('\n');
208 for (var i in lines) {
209 descnode.div[1].appendChild(lines[i]);
210 // Add a new line, except after the last line
211 if (i != (lines.length-1)) {
212 descnode.div[1].appendChild(<br/>);
213 }
214 }
215 }
216 ev.appendChild(descnode);
217 }
218 html.body.appendChild(ev);
219 }
220
221 // Convert the javascript string to an array of bytes, using the
222 // utf8 encoder
223 var convStream = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
224 .getService(Components.interfaces.nsIConverterOutputStream);
225 convStream.init(aStream, 'UTF-8', 0, 0x0000);
226
227 var str = html.toXMLString()
228 convStream.writeString(str);
229 return;
230 };