xmltv2html 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/bin/xmltv2html.rb ADDED
@@ -0,0 +1,1155 @@
1
+ #!/usr/bin/ruby -w
2
+ #
3
+ # xmltv2html.rb - A Ruby script to tranform the XMLTV output into HTML.
4
+ #
5
+ # Version : 0.5.3
6
+ # Author : Kurt V. Hindenburg <public@kurt.hindenburg.name>
7
+ #
8
+ # Copyright (C) 2003, 2004, 2005 Kurt V. Hindenburg
9
+ #
10
+ # This program is free software; you can redistribute it and/or modify
11
+ # it under the terms of the GNU General Public License as published by
12
+ # the Free Software Foundation; either version 2 of the License, or
13
+ # (at your option) any later version.
14
+ #
15
+ # This program 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 General Public License for more details.
19
+ #
20
+ # You should have received a copy of the GNU General Public License
21
+ # along with this program; if not, write to the Free Software
22
+ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23
+ #
24
+
25
+ =begin
26
+
27
+ = NAME
28
+ = SYNOPSIS
29
+ xmltv2html.rb [arg1 ... argN] < xmltv.xml > listing.html
30
+ xmltv2html.rb -h|--help|-v|--version
31
+ = DESCRIPTION
32
+ ((*xmltv2html*)) is a script that transforms the output from xmltv into HTML.
33
+ The HTML output has the times horizontally and the shows vertically. The
34
+ shows' information is displayed via a DHTML popup window. Virtually every
35
+ aspect of the HTML output can be customized using a configuration
36
+ file and a CSS file.
37
+ = OPTIONS
38
+ : -c, --configfile=FILE
39
+ Configuration file to use.
40
+ : --noconfigfile
41
+ Do not use any configuration file.
42
+ : --urlprev=URL
43
+ URL for previous link.
44
+ : --urlnext=URL
45
+ URL for next link.
46
+ : -h or --help
47
+ Display usage information.
48
+ : -v or --version
49
+ Display version information.
50
+ = AUTHOR
51
+ Written by Kurt V. Hindenburg <public@kurt.hindenburg.name>
52
+ = COPYRIGHT
53
+ Copyright (C) 2003-2005 Kurt V. Hindenburg
54
+
55
+ This is free software; see the source for copying conditions.
56
+ There is NO warranty; not even for MERCHANTABILITY or FITNESS
57
+ FOR A PARTICULAR PURPOSE.
58
+ = FILES
59
+ * popup.js - The show's information is displayed using this JavaScript DHTML popup window script.
60
+ * xmltv2html.css - An optional CSS file used in the HTML output.
61
+ * xmltv2htmlrc - An optional configuration file.
62
+ = SEE ALSO
63
+ * ((<"xmltv2html.rb home page - http://kurt.hindenburg.name/projects/xmltv2html"|URL:http://kurt.hindenburg.name/projects/xmltv2html>))
64
+ * ((<"xmltv home page - http://sourceforge.net/projects/xmltv"|URL:http://sourceforge.net/projects/xmltv>))
65
+ * ((<"REXML home page - http://www.germane-software.com/software/rexml"|URL:http://www.germane-software.com/software/rexml>))
66
+ = BUGS
67
+ * Please process the xmltv.xml file through tv_sort ((*before*)) using xmltv2html.rb
68
+
69
+ =end
70
+
71
+ require "optparse"
72
+ require "rexml/document"
73
+ require 'singleton'
74
+ require 'time'
75
+
76
+ XMLTV2HTML_VERSION="0.5.3"
77
+ XMLTV2HTML_DATE="Jun 20, 2005"
78
+
79
+ # die - print message to stderr and exit
80
+ def die(*args)
81
+ $stderr.print args
82
+ exit(1)
83
+ end
84
+
85
+ class Config
86
+ attr_reader(:times_interval, :channels_interval, :time_format_12)
87
+ attr_reader(:use_config_file, :verbose, :output_links)
88
+ attr_reader(:date_format, :categories)
89
+ attr_reader(:use_favorites, :favorites)
90
+ attr_reader(:output_date_in_time, :css_filename)
91
+ attr_reader(:use_programme_popup, :programme_popup_method)
92
+ attr_reader(:popup_title_format, :popup_body_format)
93
+ attr_reader(:popup_title_background_color, :popup_title_color)
94
+ attr_reader(:popup_title_font_size, :popup_body_font_size)
95
+ attr_reader(:popup_body_background_color, :popup_body_color)
96
+ attr_reader(:popup_body_width)
97
+ attr_reader(:popup_title_font, :popup_body_font)
98
+ attr_reader(:time_divisor)
99
+ attr_accessor(:start_date, :stop_date)
100
+ attr_accessor(:days, :total_hours, :total_span)
101
+ attr_accessor(:url_next, :url_prev)
102
+
103
+ def initialize
104
+ @opts = {}
105
+ @keys = %w{ times_interval chanels_interval time_format_12 }
106
+ @favorites = Hash.new
107
+ @use_config_file = true
108
+ @config_file="xmltv2htmlrc"
109
+ @total_hours = 0
110
+ @total_span = 0
111
+ @time_divisor = 10; # Divide programmes' times in X minute slots
112
+ @url_prev = nil
113
+ @url_next = nil
114
+
115
+ setDefaults
116
+ handleOpts
117
+ readConfigFile if @config_file and @use_config_file
118
+
119
+ # Convert favorite_list to Hash for fast lookup.
120
+ @favorite_list.each { |f|
121
+ f.gsub!(/&/,'&amp;')
122
+ @favorites[f] = true
123
+ }
124
+ @days = 1
125
+ end
126
+
127
+ def setDefaults
128
+ ### BEGIN CONFIGURATION
129
+ #
130
+
131
+ # Will output info to STDERR
132
+ @verbose = true
133
+
134
+ # Number of channels to repeat times horizontally; 0 to disable
135
+ @times_interval = 4
136
+
137
+ # Number of hours to repeat channels vertically; 0 to disable
138
+ @channels_interval = 4
139
+
140
+ # true = 12 hour format; false = 24 hour format
141
+ @time_format_12 = true
142
+
143
+ # Use channel favorites?
144
+ @use_favorites = true
145
+
146
+ # List favorite channels below (seperate by a ',')
147
+ # The titles must be exact, not regular expressions.
148
+ @favorite_list = [
149
+ "CSI: Crime Scene Investigation",
150
+ "Law & Order: Special Victims Unit", "Angel", "Law & Order",
151
+ "The Shield", "Law & Order: Criminal Intent",
152
+ "Charmed", "The West Wing"
153
+ ]
154
+
155
+ # Name of CSS file
156
+ @css_filename = 'xmltv2html.css'
157
+
158
+ # Categories - use specified CSS class for given category.
159
+ # You must give the exact category for this to match. Look
160
+ # at the xml data file for a list of categories.
161
+ # Put the corresponding CSS class in your .css file
162
+ # .sports-event
163
+ # {
164
+ # color: red;
165
+ # background-color: white;
166
+ # }
167
+ @categories = {
168
+ "Sports event" => "sports-event",
169
+ "News" => "news"
170
+ }
171
+
172
+ # If true, will display date on the hour cell
173
+ # (helpful for multiple days).
174
+ # Color, etc in CSS file
175
+ @output_date_in_time = true
176
+
177
+ # The format to display the date
178
+ # %m = month (01..12); %d = day (01..31); %b = Abbr month (Jan)
179
+ # %a = Abbr weekday (Sun)
180
+ # @date_format = '%m-%d'
181
+ # @date_format = '%b %m-%d'
182
+ @date_format = '%a %d'
183
+
184
+ # Should the programme's description 'popup' on mouseover?
185
+ # This will enlarge the output by the size of all the programme's
186
+ # descriptions.
187
+ @use_programme_popup = true
188
+
189
+ # What method to use for popup? DHTML or STATUSBAR
190
+ # Some browsers disable pages from changing the statusbar.
191
+ @programme_popup_method = "DHTML" # "STATUSBAR"
192
+
193
+ # Title is the top line of popup; body is the below part.
194
+ # Define the popup: %T = title, %S = sub-title
195
+ # %D = description, %R = rating
196
+ # %P = previously-shown; will display (R)
197
+ # Example: @popup_title_format = "%T"
198
+ # Example: @popup_body_format = "%D<br />%R"
199
+ # DHTML: You can put *some* CSS/HTML stuff here as well...
200
+ # STATUSBAR: NO HTML
201
+ @popup_title_format = "%T"
202
+ @popup_body_format = "%S %P<br />%D<br />Rating: %R"
203
+
204
+ # Attributes for the DHTML popup
205
+ @popup_title_color = "#FFFFFF"
206
+ @popup_title_font = "" # "Utopia"
207
+ @popup_title_font_size = "2" # FONT SIZE=
208
+ @popup_title_background_color = "#000099"
209
+ @popup_body_color = "#000000"
210
+ @popup_body_font = "" # "Utopia"
211
+ @popup_body_font_size = "1" # FONT SIZE=
212
+ @popup_body_background_color = "#E8E8FF"
213
+ @popup_body_width = "200" # pixels
214
+
215
+ # xmltv and xmltv2html home page links
216
+ @output_links = true
217
+
218
+ ### END CONFIGURATION
219
+ end
220
+
221
+ def readConfigFile
222
+ f=File.expand_path(@config_file)
223
+ return if not File.exists?(f)
224
+ return if not File.stat(f).readable_real?
225
+ begin
226
+ eval File.new(f).read
227
+ $stderr.print "^ Reading configuration file #{f}\n" if @verbose
228
+ rescue
229
+ warn("\nAn error occurred while reading #{f}; please fix!\n")
230
+ exit
231
+ end
232
+ end
233
+
234
+ def handleOpts
235
+
236
+ ARGV.options do
237
+ |opts|
238
+ opts.banner = "Usage: #{File.basename($0)} < xmltv.xml > tv.html\n"
239
+
240
+ # separater
241
+ opts.on_tail
242
+ opts.on_tail("common options:")
243
+
244
+ opts.on("-c", "--configfile=FILE", String, "config file to use") {
245
+ |@config_file|}
246
+ opts.on("--noconfigfile","do NOT use any config file") {
247
+ @use_config_file = false}
248
+
249
+ opts.on("--urlprev=URL", String, "URL for previous link") { |@url_prev|}
250
+ opts.on("--urlnext=URL", String, "URL for next link") { |@url_next|}
251
+
252
+ # no argument, shows at tail
253
+ opts.on_tail("-h", "--help", "show this message") {puts opts; exit}
254
+
255
+ opts.on_head("specific options:")
256
+
257
+ # no argument
258
+ opts.on_tail("-v", "--version", "show version") do
259
+ print "xmltv2html.rb v#{XMLTV2HTML_VERSION} (#{XMLTV2HTML_DATE})\n\n"
260
+ print "Copyright (C) 2003, 2004, 2005 Kurt V. Hindenburg\n"
261
+ print "This is free software; see the source for copying conditions. There is NO\n"
262
+ print " warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"
263
+ print "\npublic@kurt.hindenburg.name\n"
264
+
265
+ exit
266
+ end
267
+ begin
268
+ opts.parse!
269
+ rescue
270
+ die "\n * Invalid parameters given!\n\n"
271
+ end
272
+ end
273
+ end
274
+ end
275
+
276
+ class Time
277
+
278
+ class << self
279
+ ### Time.<method>
280
+
281
+ # time string in format 'yyyymmddhhmmss (+/-xxxx)'
282
+ def parse_xmltv_time(str)
283
+ begin
284
+ Time.parse(str.split(' ')[0])
285
+ rescue ArgumentError
286
+ die "Unable to parse #{str}\n\n"
287
+ end
288
+ end
289
+ end
290
+
291
+ ### object.<method>
292
+
293
+ # TODO: cache $params.time_divisor
294
+ def round_to_interval
295
+ self - (self.min % $params.time_divisor) * 60
296
+ end
297
+
298
+ end
299
+
300
+ class ProgrammeTime
301
+ attr_reader(:fullStartTime, :fullStopTime)
302
+
303
+ def initialize(start, stop)
304
+ @fullStartTime = Time.parse_xmltv_time(start)
305
+ @fullStopTime = Time.parse_xmltv_time(stop)
306
+
307
+ #$stderr.print "#{@fullStartTime}; "
308
+ @fullStartTime = @fullStartTime.round_to_interval
309
+ #$stderr.print "after #{@fullStartTime} \n"
310
+ @fullStopTime = @fullStopTime.round_to_interval
311
+
312
+ @start_date = nil
313
+ @stop_date = nil
314
+ end
315
+
316
+ def returnParsedTime(t)
317
+ begin
318
+ pt = Time.parse(t)
319
+ rescue ArgumentError
320
+ die "Unable to parse #{t}\n\n"
321
+ end
322
+ pt
323
+ end
324
+
325
+ def calculateStartSlot
326
+ return if $params.start_date == nil # 1st pass
327
+ @start_date = returnParsedTime($params.start_date) if not @start_date
328
+
329
+ start_diff_min = ((@fullStartTime - @start_date) / 60).to_i
330
+ # $stderr.print ", diff #{start_diff_min}"
331
+ start_slot = start_diff_min / $params.time_divisor
332
+ # $stderr.print "starting slot #{start_slot}\n"
333
+ start_slot
334
+ end
335
+ def calculateStopSlot
336
+ return if $params.stop_date == nil # 1st pass
337
+
338
+ @start_date = returnParsedTime($params.start_date) if not @start_date
339
+ stop_diff_min = ((@fullStopTime - @start_date) / 60).to_i
340
+ stop_slot = stop_diff_min / $params.time_divisor
341
+ # $stderr.print " stoping slot #{stop_slot}\n"
342
+
343
+ # (@fullStopTime.hour * 60 / $timeDivisor) + (@fullStopTime.min / $timeDivisor)
344
+ stop_slot
345
+ end
346
+
347
+ end
348
+
349
+ class Channel
350
+ attr_reader(:programmeList, :name, :id, :fullname)
351
+ attr_accessor(:totalSpan)
352
+
353
+ def initialize(id, fn)
354
+ @id = id
355
+ @fullname = fn
356
+ @number = ""
357
+ @name = @fullname
358
+ @totalSpan = 0 # 24 hours * 4 = 96
359
+ @programmeList = nil
360
+ end
361
+
362
+ def <=>(o)
363
+ fullname.to_i <=> o.fullname.to_i
364
+ end
365
+
366
+ def setProgrammes(l)
367
+ @programmeList = l
368
+ end
369
+
370
+ # Verify that each show's STOP date is the next show's START date
371
+ # Should not be needed if tv_sort was used.
372
+ # TODO: remove this once we can verify tv_sort was used on input data
373
+ def verifyStopDate
374
+ @programmeList.each_index { |si|
375
+ s = @programmeList[si]
376
+ next_show = @programmeList[si.succ]
377
+ next if next_show == nil
378
+ next if s.times.fullStopTime == next_show.times.fullStartTime
379
+
380
+ die "\n * A programme's stop time does not match the next \n" +
381
+ " * programme's start time. \n" +
382
+ " * Use tv_sort from the xmltv distribution to correct!\n" +
383
+ " * Exiting...\n\n" if !stop
384
+ }
385
+ end
386
+
387
+ def calculateSlots(slist)
388
+ tinterval = $params.channels_interval * 60 / $params.time_divisor
389
+ left = []
390
+ right = slist.dup
391
+ index = 0
392
+ total = 0
393
+ slist.each { |e|
394
+ cmd = e.slice(0..0).to_s
395
+ span = e.slice(1..-1).to_i
396
+ if total + span > tinterval
397
+ # $stderr.print "removing #{e} ::: "
398
+ prev = tinterval - total
399
+ # $stderr.print "adding prev #{prev}; "
400
+ nxt = total + span - tinterval
401
+ # $stderr.print "adding next #{nxt}\n"
402
+ # exit if nxt + prev != span
403
+
404
+ if cmd == "D" # Dummy data
405
+ left.push "D"+prev.to_s
406
+ left.push "D"+nxt.to_s
407
+ else
408
+ if cmd =="Q" # The removed was a Q
409
+ left.push "Q"+prev.to_s
410
+ else
411
+ left.push "P"+prev.to_s
412
+ end
413
+ left.push "Q"+nxt.to_s
414
+ end
415
+ right.shift
416
+ break
417
+ elsif total + span == tinterval
418
+ total = 0
419
+ left.push e
420
+ right.shift
421
+ else
422
+ left.push e
423
+ right.shift
424
+ total += span
425
+ end
426
+ index += 1
427
+ }
428
+ left = left.concat(right)
429
+ # left.each { |i| $stderr.print "#{i}*" }; $stderr.print "\n"
430
+ left
431
+ end
432
+
433
+ # Create a slot list to account for displaying channel info
434
+ # $params.channel_interval = # of hours to display channel info
435
+ # Handle empty programmes at start/end of channel.
436
+ def createSlotList
437
+ l = Array.new
438
+ span_counter = 0
439
+ times_counter = 1
440
+ sindex = 0
441
+ total = 0
442
+ if $params.channels_interval > 0
443
+ tinterval = $params.channels_interval * 60 / $params.time_divisor
444
+ else
445
+ tinterval = 9999
446
+ end
447
+ ci = tinterval
448
+
449
+ slist = Array.new
450
+
451
+ s = @programmeList[0]
452
+ if s.startSlot() > 0 # Missing programme at start
453
+ dummy_span = s.startSlot() - span_counter
454
+ slist.push "D"+dummy_span.to_s
455
+ end
456
+
457
+ @programmeList.each { |s|
458
+ slist.push "P"+s.spanSlots.to_s
459
+ }
460
+
461
+ if $params.channels_interval > 0
462
+ sl = calculateSlots(slist)
463
+ while sl != slist
464
+ slist = sl.dup
465
+ sl = calculateSlots(sl)
466
+ end
467
+ end
468
+
469
+ slist.each { |entry|
470
+ span = entry.slice(1..-1).to_i
471
+ l.push entry
472
+ total += span
473
+ l.push("C0") if (total % tinterval) == 0 and $params.channels_interval > 0
474
+ }
475
+
476
+ slist = l.unshift("C0") # Add left-most channel
477
+
478
+ dinterval = $params.total_hours * 60 / $params.time_divisor
479
+ if total < dinterval # Not enough programmes' data at end
480
+ slist.push("D"+(dinterval - total).to_s)
481
+ slist.push("C0") if dinterval % tinterval == 0
482
+ end
483
+
484
+ slist
485
+ end
486
+
487
+ end
488
+
489
+ class Programme
490
+ attr_reader :title, :subtitle, :span, :times, :desc, :rating, :category
491
+ attr_reader :previouslyShown, :spanSlots, :startSlot
492
+ attr_accessor :popupIndex
493
+
494
+ def initialize(title, subtitle, channel, start, stop, desc, rating, cats, rerun)
495
+ @title = title
496
+ @title.gsub!(/&/,'&amp;')
497
+ @subtitle = subtitle
498
+ @channelid = channel
499
+ @desc = desc
500
+ @rating = rating
501
+ @category = cats
502
+ @previouslyShown = rerun
503
+ @popupIndex = -1
504
+
505
+ @times = ProgrammeTime.new(start, stop)
506
+ end
507
+
508
+ # Slot interval = 12 (60 minutes / 5 minutes)
509
+ def calculateSlots
510
+ @startSlot = @times.calculateStartSlot
511
+ @stopSlot = @times.calculateStopSlot
512
+
513
+ return if not @startSlot
514
+ return if not @stopSlot
515
+
516
+ @spanSlots = @stopSlot - @startSlot
517
+ end
518
+
519
+ end
520
+
521
+ # [channel id] -> Channel
522
+ class Channels < Hash
523
+ include Singleton
524
+
525
+ attr_accessor(:output_total_hours)
526
+
527
+ def initialize
528
+ @output_total_hours = 0 # Set to hours displayed
529
+ @output_start_hour = 0 # Start of hours displayed
530
+ @output_stop_hour = 0 # Stop hours displayed
531
+ end
532
+
533
+ end
534
+
535
+ # channel id => [show1, show2, ...]
536
+ class Programmes < Hash
537
+ include Singleton
538
+
539
+ def []=(id, programme)
540
+ case programme
541
+ when Programme
542
+ begin
543
+ if empty? or not has_key?(id)
544
+ super id, Array.new << programme
545
+ else
546
+ super id, (fetch(id) << programme)
547
+ end
548
+ end
549
+ when Array
550
+ begin
551
+ delete id
552
+ super id, programme
553
+ end
554
+ else
555
+ $stderr.print "^^^ Programmes []= Unknown class=#{programme.class}\n"
556
+ end
557
+ end
558
+ end
559
+
560
+
561
+ class XmlTV
562
+ attr_reader :srcInfoName
563
+ attr_reader :genInfoName
564
+ attr_reader :HTML_title # Title of HTML page
565
+ attr_reader :top_title # Title of HTML page
566
+ attr_accessor(:firstShowStartDate, :lastShowStartDate, :lastShowStopDate)
567
+
568
+ def initialize
569
+ @firstShowStartDate = "99999999999999"
570
+ @lastShowStartDate = "0"
571
+ @lastShowStopDate = "0"
572
+
573
+ file = $stdin
574
+ @doc = REXML::Document.new file
575
+ @doc.elements.each("tv") { |e| # Should only be one
576
+ # The date here is the date/time that the user obtained the tv
577
+ # listings, NOT the date/time of the actual shows.
578
+ # @date = e.attributes["date"]
579
+ @srcInfoName = e.attributes["source-info-name"]
580
+ @genInfoName = e.attributes["generator-info-name"]
581
+ @HTML_title = ""
582
+ }
583
+ end
584
+
585
+ def setTitle(dates)
586
+ @top_title = ""
587
+ @HTML_title = ""
588
+ @top_title += @srcInfoName + " :: " if @srcInfoName
589
+ t1 = Time.parse(@firstShowStartDate)
590
+ t2 = Time.parse(@lastShowStopDate)
591
+
592
+ @time_first = t1.strftime("%a %b %d %I:%M %p")
593
+ @time_last = t2.strftime("%a %b %d %I:%M %p %Z")
594
+
595
+ if (t1.hour > 19) and (t2.hour < 05)
596
+ t3 = t1 + (5 * 3600)
597
+ @top_title += t3.strftime("%A %b %d")
598
+ @HTML_title = @top_title
599
+
600
+ @top_title += "<br />"
601
+ @top_title += "<span style=\"font-size: smaller\">"
602
+ @top_title += @time_first + " - " + @time_last + "</span>"
603
+ else
604
+ @top_title += @time_first + " - " + @time_last
605
+ @HTML_title = @top_title
606
+ end
607
+
608
+ end
609
+
610
+ def parseChannels
611
+ channels = Channels.instance
612
+ @doc.elements.each("tv/channel") { |element|
613
+ id = element.attributes["id"]
614
+ fn = ""
615
+ element.each_element { |e|
616
+ if e.name == "display-name" # Use 1st entry
617
+ fn = e.text()
618
+ # $stderr.print "#{id}---#{fn}\n"
619
+ break
620
+ end
621
+ }
622
+ # $stderr.print "Using : #{id}---#{fn}\n"
623
+ channels[id] = Channel.new(id, fn)
624
+ }
625
+ end
626
+
627
+ def parseProgrammes
628
+ plist = Programmes.instance
629
+
630
+ @doc.elements.each("tv/programme") { |element|
631
+ title=""
632
+ subtitle=""
633
+ desc=""
634
+ rating=""
635
+ cats = nil
636
+ rerun = false
637
+ start = element.attributes["start"]
638
+ stop = element.attributes["stop"]
639
+ die "\n * No stop attribute in this programme...\n" +
640
+ " * Use tv_sort from the xmltv distribution to correct!\n" +
641
+ " * Exiting...\n\n" if !stop
642
+
643
+ dstart = start[0..13]
644
+ @firstShowStartDate = dstart if @firstShowStartDate > dstart
645
+ @lastShowStartDate = dstart if @lastShowStartDate < dstart
646
+ dstop = stop[0..13]
647
+ @lastShowStopDate = dstop if @lastShowStopDate < dstop
648
+ #$stderr.print "Start Date=#{dstart}, firstShowStartDate=#{firstShowStartDate}\n"
649
+
650
+ channel = element.attributes["channel"]
651
+ element.each_element {|e|
652
+ #$stderr.print e.text(),"\n"
653
+ title = e.text() if e.name == "title"
654
+ subtitle = e.text() if e.name == "sub-title"
655
+ desc=e.text() if e.name == "desc"
656
+ rerun=true if e.name == "previously-shown"
657
+ if e.name == "rating"
658
+ rv = e.elements[1]
659
+ rating = rv.text() if rv.name == "value"
660
+ end
661
+
662
+ # Check to see if user want to use special CSS class for category
663
+ # FIXME: What happens when more than 1 category is triggered?
664
+ if e.name == "category"
665
+ if $params.categories.has_key?(e.text())
666
+ # $stderr.print "found #{e.text()}, using #{$params.categories[e.text()]}\n"
667
+ cats = $params.categories[e.text()];
668
+ end
669
+ end
670
+ }
671
+
672
+ title.gsub!(/[\"\'\`]/,'') # Remove "'`
673
+ title = title.unpack("U*").pack("C*")
674
+
675
+ subtitle = "" if not subtitle
676
+ subtitle.gsub!(/[\"\'\`]/,'') # Remove "'`
677
+
678
+ desc = "" if not desc
679
+ desc.gsub!(/[\"\'\`]/,'') # Remove "'`
680
+ desc = desc.unpack("U*").pack("C*")
681
+
682
+ rating = "" if not rating
683
+ rating.gsub!(/[\"\'\`]/,'') # Remove "'`
684
+ #$stderr.print "title=#{title}, desc=#{desc}, rating=#{rating}\n"
685
+
686
+ plist[channel] = Programme.new(
687
+ title, subtitle, channel, start, stop, desc, rating, cats, rerun)
688
+ }
689
+ end
690
+ end
691
+
692
+ class XMLTV2HTML
693
+ attr_accessor(:dates)
694
+
695
+ def initialize
696
+ @xml = XmlTV.new
697
+ @out = Html.new
698
+
699
+ @dates = Array.new
700
+ end
701
+
702
+ def parseXML
703
+
704
+ @xml.parseChannels
705
+ @xml.parseProgrammes
706
+
707
+ $params.start_date = @xml.firstShowStartDate
708
+ $params.stop_date = @xml.lastShowStopDate
709
+ # $stderr.print "Starting listings at #{$params.start_date}\n"
710
+ # $stderr.print "Stopping listings at #{$params.stop_date}\n"
711
+
712
+ # Force start/stop time on hour
713
+ $params.start_date[10,4] = "0000"
714
+
715
+ if $params.stop_date[10,2] != "00"
716
+ hour = ($params.stop_date[8,2]).to_i + 1
717
+ if hour > 23
718
+ $stderr.print "yuck #{hour}\n"
719
+ end
720
+ $params.stop_date[8,2] = hour.to_s.rjust(2).sub(/\s/,'0')
721
+ end
722
+ $params.stop_date[10,4] = "0000"
723
+ # $stderr.print "Starting listings at #{$params.start_date}\n"
724
+ # $stderr.print "Stopping listings at #{$params.stop_date}\n"
725
+
726
+ pstart = Time.parse($params.start_date)
727
+ pstop = Time.parse($params.stop_date)
728
+ $params.total_hours = ((pstop - pstart) / 3600).to_i
729
+
730
+ # Traverse all channels' programmes and calculate their slots
731
+ # Delete any programme with span < 1
732
+ programmes = Programmes.instance
733
+ channels = Channels.instance
734
+
735
+ programmes.each { |id, programs|
736
+ total = 0
737
+ programs.each { |p|
738
+ p.calculateSlots
739
+ if p.spanSlots < 1
740
+ $stderr.print "Deleting #{p} span=#{p.spanSlots}\n"
741
+ delete p
742
+ else
743
+ total += p.spanSlots
744
+ end
745
+ }
746
+ # $stderr.print "Total span = #{total}\n"
747
+ channels[id].totalSpan = total
748
+ channels[id].setProgrammes programmes[id]
749
+ $params.total_span = total if total > $params.total_span
750
+ }
751
+
752
+ fdate = $params.start_date[0,8]
753
+ ldate = $params.stop_date[0,8]
754
+
755
+ @xml.setTitle(@dates)
756
+
757
+ end
758
+
759
+ def generatePopups
760
+ i = 0
761
+ @out.outputPopupHTMLBegin
762
+
763
+ channels = Channels.instance
764
+ channels.each { |id, c|
765
+ i = @out.outputPopupDescs(c, i)
766
+ }
767
+ @out.outputPopupHTMLEnd
768
+ end
769
+
770
+ def generateHTML
771
+ sindex =-1
772
+ times_counter = 1
773
+
774
+ @out.dates = @dates
775
+
776
+ @out.doctype
777
+ @out.header(@xml.HTML_title)
778
+ generatePopups if $params.use_programme_popup
779
+ @out.text_before_table(@xml.top_title)
780
+ @out.table_start
781
+ @out.table_times
782
+
783
+ channels = Channels.instance
784
+ sorted_channels = channels.sort { |a, b| a[1]<=>b[1] }
785
+
786
+ sorted_channels.each { |id, c|
787
+ @out.outputChannelBegin
788
+ slot_list = c.createSlotList
789
+ i = 0
790
+ pi = -1
791
+ slot_list.each { |entry|
792
+ cmd = entry.slice(0..0).to_s
793
+ span = entry.slice(1..-1).to_i
794
+ case cmd
795
+ when "D"
796
+ # $stderr.print "Dummy #{entry}\n"
797
+ @out.outputDummyProgramme(span)
798
+ when "P" # Programme
799
+ sindex += 1
800
+ pi += 1
801
+ # p = c.programmeList[pi]
802
+ # $stderr.print "#{entry} #{p}\n"
803
+ @out.outputProgramme(c.programmeList[pi], sindex, span)
804
+ when "Q" # Use previous Programme's info
805
+ p = c.programmeList[pi]
806
+ # $stderr.print "Programme- Use previous programme #{entry}\n"
807
+ # p = c.programmeList[pi-1]
808
+ @out.outputProgramme(c.programmeList[pi], sindex, span)
809
+ # @out.outputProgramme(p, sindex, span)
810
+ when "C"
811
+ # $stderr.print "Channel space #{cmd}\n"
812
+ @out.outputChannel(c)
813
+ else
814
+ $stderr.print "Unknown slot entry #{entry}\n" ; exit
815
+ $stderr.print "Using : #{id}---#{fn}\n"
816
+ end
817
+ i += 1
818
+ }
819
+ @out.outputChannelEnd
820
+ @out.table_times if times_counter % $params.times_interval == 0
821
+ times_counter += 1
822
+ }
823
+
824
+ @out.table_end
825
+ @out.text_after_table
826
+ @out.footer
827
+ end
828
+
829
+ end
830
+
831
+ class Html
832
+ attr_accessor(:hours, :dates)
833
+
834
+ def initialize
835
+ @hours = 0 # hours displayed
836
+ end
837
+ def nl
838
+ print "\n"
839
+ end
840
+ def nb
841
+ print "&nbsp;"
842
+ end
843
+ def dots
844
+ print "../"
845
+ end
846
+ def doctype
847
+ print '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" >'
848
+ nl
849
+ end
850
+ def header(title)
851
+ print '<html><head>'; nl
852
+ print '<meta http-equiv="Content-type" content="text/html; charset=ISO-8859-1">'; nl
853
+ print '<meta http-equiv="Content-language" content="en">'; nl
854
+ print '<title>'
855
+ print title if title
856
+ print '</title>'; nl
857
+ print '<link rel="stylesheet" href="', $params.css_filename
858
+ print '" type="text/css">'; nl
859
+
860
+ if $params.use_programme_popup
861
+ if $params.programme_popup_method == "DHTML"
862
+ print '<script language="JavaScript1.2" src="popup.js" type="text/javascript"></script>'; nl
863
+ else
864
+ print '<script language="JavaScript1.2" type="text/javascript">'
865
+ # April 02, 2004 : This doesn't work with Konqueor3.2.1
866
+ # nor firefox0.8
867
+ print 'window.defaultStatus = " "'
868
+ print '</script>'; nl
869
+ end
870
+ end
871
+ print '</head>'; nl; print '<body>'; nl
872
+ end
873
+
874
+ def outputPopupHTMLBegin
875
+ print '<div id="TipLayer" style="position: absolute; z-index: 1000; visibility: hidden; left: 0pt; top: -800px;"><table width="200" border="0" cellpadding="2" cellspacing="0" bgcolor="#000099"><tbody><tr><td><table width="100%" border="0" cellpadding="2" cellspacing="0" bgcolor="#e8e8ff"><tbody><tr><td valign="top"><font size="1" face="Verdana,Arial,Helvetica" color="black">This is only text</font></td></tr></tbody></table></td></tr></tbody></table></div>'; nl
876
+
877
+ print '<script language="JavaScript1.2" type="text/javascript">'; nl
878
+
879
+ end
880
+ def outputPopupHTMLEnd
881
+
882
+ print 'var FiltersEnabled = 0 // if your not going to use transitions or filters in any of the tips set this to 0'; nl
883
+
884
+ # Styles
885
+ print 'Style[1]=['
886
+ print '"', $params.popup_title_color, '",'
887
+ print '"', $params.popup_body_color, '",'
888
+ print '"', $params.popup_title_background_color, '",'
889
+ print '"', $params.popup_body_background_color, '",'
890
+ print '"","","","",'
891
+ print '"', $params.popup_title_font, '",'
892
+ print '"', $params.popup_body_font, '",'
893
+ print '"center","",'
894
+ print '"', $params.popup_title_font_size, '",'
895
+ print '"', $params.popup_body_font_size, '",'
896
+ print $params.popup_body_width, ','
897
+ print '"",'
898
+ print '3,' # Border width
899
+ print '10,' # Padding around body text
900
+ print '20, 20, "", "", "", "", ""]'
901
+ print nl
902
+
903
+ print 'applyCssFilter()'; nl
904
+ print '</script>'; nl
905
+ nl
906
+
907
+ end
908
+
909
+ def outputPopupDescs(c, cindex)
910
+ # The descriptions go here...Text[#]=["title","text"]
911
+ id = c.id()
912
+ c.programmeList.each { |s|
913
+ title = $params.popup_title_format.sub(/\%T/, s.title)
914
+ title.sub!(/\%R/, s.rating)
915
+ desc = $params.popup_body_format.gsub(/%T/, s.title)
916
+ desc.sub!(/\%S/, s.subtitle)
917
+ if (s.previouslyShown) # rerun
918
+ desc.sub!(/\%P/, "(R)")
919
+ else
920
+ desc.sub!(/\%P/, "")
921
+ end
922
+ desc.sub!(/%D/, s.desc)
923
+ desc.sub!(/%R/, s.rating)
924
+ if s.desc != "" or s.rating != ""
925
+ print 'Text[',cindex,']=["',title,'","',desc,'"]'; nl
926
+ s.popupIndex = cindex
927
+ cindex += 1
928
+ end
929
+ }
930
+ nl
931
+ cindex
932
+ end
933
+
934
+ def footer
935
+ print '</body></html>'
936
+ nl
937
+ end
938
+
939
+ def text_before_table(text)
940
+ if text
941
+ print '<center><h3>'
942
+ print text
943
+ print '</h3></center>'; nl
944
+ end
945
+ if $params.url_prev
946
+ print '<a class="links" href="'
947
+ print $params.url_prev
948
+ print '"><<< Previous</a> | '
949
+ end
950
+ if $params.url_next
951
+ print '<a class="links" href="'
952
+ print $params.url_next
953
+ print '">Next >>></a>'
954
+ end
955
+ end
956
+
957
+ def text_after_table
958
+ if $params.url_prev
959
+ print '<a class="links" href="'
960
+ print $params.url_prev
961
+ print '"><<< Previous</a> | '
962
+ end
963
+ if $params.url_next
964
+ print '<a class="links" href="'
965
+ print $params.url_next
966
+ print '">Next >>></a>'
967
+ end
968
+ print '<br />'
969
+ outputInfo
970
+ outputLinks if $params.output_links
971
+ end
972
+
973
+ def table_start
974
+ print '<table width="100%" border="3" cellpadding="3">'; nl
975
+ end
976
+ def table_end
977
+ print '</table>'; nl
978
+ end
979
+
980
+ # Space for channel names
981
+ def output_channel_space
982
+ print '<td>'; nb; print '</td>'; nl
983
+ end
984
+
985
+ def outputDate
986
+ days = @hours/24
987
+ colspan = 96
988
+ colspan += 96 / $params.channels_interval if $params.channels_interval > 0
989
+
990
+ print '<tr class="date">'; nl
991
+ (0 .. days-1).each { |d|
992
+ print '<td colspan="',colspan,'" align="center">','date here','</td>'; nl
993
+ }
994
+ print '</tr>'; nl
995
+ end
996
+
997
+ def table_times
998
+ intervals = 60 / $params.time_divisor
999
+
1000
+ # $stderr.print "Starting time #{$params.start_date}\n"
1001
+ # $stderr.print "Stoping time #{$params.stop_date}\n"
1002
+
1003
+ # Need hour to start.... we'll force it to be on an hour later
1004
+ starting_day = $params.start_date[6,2].to_i
1005
+ starting_hour = $params.start_date[8,2].to_i
1006
+ # $stderr.print "Day to start: #{starting_day}\n"
1007
+ # $stderr.print "Hour to start: #{starting_hour}\n"
1008
+ # $stderr.print "Total hours: #{$params.total_hours}\n"
1009
+
1010
+ print '<tr class="time">'; nl
1011
+ output_channel_space
1012
+
1013
+ cs = 0
1014
+ (starting_hour .. starting_hour + $params.total_hours - 1).each { |h|
1015
+ (0 .. intervals-1).each { |hh|
1016
+ print '<td>';
1017
+ printf "%02d", hh * $params.time_divisor; print '</td>';
1018
+ }
1019
+ cs += 1
1020
+ output_channel_space if $params.channels_interval > 0 and cs % $params.channels_interval == 0
1021
+ }
1022
+ print '</tr>'; nl
1023
+
1024
+ print '<tr class="time">'; nl
1025
+ output_channel_space
1026
+ days = @hours/24
1027
+ cs = 0
1028
+ cdate = Time.parse($params.start_date)
1029
+
1030
+ (starting_hour .. starting_hour + $params.total_hours - 1).each { |hi|
1031
+ h = hi % 24
1032
+ print '<td colspan="',intervals,'" class="time">';
1033
+
1034
+ if $params.output_date_in_time
1035
+ nl; print '<table width="100%" border="0" cellpadding="0">'; nl
1036
+ print '<tr><td align="left" class="time">';
1037
+ end
1038
+ if $params.time_format_12
1039
+ if h < 12
1040
+ out = "#{h} am"
1041
+ else
1042
+ out = (h - 12).to_s + " pm"
1043
+ end
1044
+ out.gsub!(/^0/, '12')
1045
+ else
1046
+ out = sprintf "%02d", h
1047
+ end
1048
+ print out
1049
+ if $params.output_date_in_time
1050
+ print '<td align="right" class="date_in_time">';
1051
+ print cdate.strftime($params.date_format)
1052
+ print '</td></tr>'; nl; print '</table></td>'; nl
1053
+ else
1054
+ print '</td>';
1055
+ end
1056
+ cs += 1
1057
+ cdate += 3600 # Add 1 hour
1058
+ output_channel_space if $params.channels_interval > 0 and cs % $params.channels_interval == 0
1059
+ }
1060
+ print '</tr>'; nl
1061
+ end
1062
+
1063
+ def outputChannelBegin
1064
+ print '<tr class="channel">'; nl
1065
+ end
1066
+ def outputChannelEnd
1067
+ print '</tr>'; nl
1068
+ end
1069
+ def outputChannel(c)
1070
+ print '<td class="channel">';
1071
+ print c.fullname; print '</td>'; nl
1072
+ end
1073
+
1074
+ def outputDummyProgramme(span)
1075
+ print '<td colspan="',span,'" class="dummy">No Data</td>'
1076
+ end
1077
+
1078
+ def outputProgramme(s, index, slots)
1079
+ if slots > 0
1080
+ print '<td colspan="', slots, '" class="'
1081
+ else
1082
+ print '<td colspan="',s.spanSlots,'" class="'
1083
+ end
1084
+
1085
+ # Favorites override category.
1086
+ if $params.use_favorites and $params.favorites.has_key?(s.title)
1087
+ print 'favorite">'
1088
+ elsif s.category
1089
+ print s.category,'">'
1090
+ else
1091
+ print 'programme">'
1092
+ end
1093
+ # Styles : 12=right; 1=center; 2=left; 3=float
1094
+ if $params.use_programme_popup
1095
+ if s.popupIndex >= 0
1096
+ if $params.programme_popup_method == "DHTML"
1097
+ print '<a onmouseover="stm(Text[', s.popupIndex
1098
+ print '],Style[1])" onmouseout="htm()">'; nl
1099
+ else
1100
+ print '<a language="Javascript" '
1101
+ print 'onmouseover="window.status=Text[', s.popupIndex
1102
+ print '];return true"'
1103
+ # print ' OnMouseout="window.status=\' \';"'
1104
+ print '>'; nl
1105
+ end
1106
+
1107
+ print s.title ; print '</a>'; nl
1108
+ else
1109
+ print s.title ; nl
1110
+ end
1111
+ else
1112
+ print s.title ; nl
1113
+ end
1114
+ print '</td>'; nl
1115
+ end
1116
+
1117
+ def outputInfo
1118
+ print '<br>'
1119
+ print '<span class="info">'
1120
+ nl
1121
+ print "Generated by xmltv2html.rb v#{XMLTV2HTML_VERSION} (#{XMLTV2HTML_DATE})"
1122
+ print " on #{Time.now}"
1123
+ nl
1124
+ print '</span>'
1125
+ print '<br>'
1126
+ end
1127
+
1128
+ def outputLinks
1129
+ nl
1130
+ # print 'Links: '
1131
+ print '<a class="links" href="http://sourceforge.net/projects/xmltv">xmltv</a>'
1132
+ nl
1133
+ print '<a class="links" href="http://kurt.hindenburg.name">xmltv2html</a>'
1134
+ nl
1135
+ end
1136
+
1137
+ end
1138
+
1139
+ $params = Config.new
1140
+ xmltv2html = XMLTV2HTML.new
1141
+
1142
+ time1 = Time.new
1143
+ xmltv2html.parseXML
1144
+ time2 = Time.new
1145
+ $stderr.print "^ Parsing XML took #{time2-time1} seconds.\n" if $params.verbose
1146
+
1147
+ time3 = Time.new
1148
+ xmltv2html.generateHTML
1149
+ time4 = Time.new
1150
+ $stderr.print "^ Generating HTML took #{time4-time3} seconds.\n" if $params.verbose
1151
+
1152
+ exit
1153
+
1154
+
1155
+ # vim:set ts=3 sw=3 expandtab: