xmltv2html 0.6.1 → 0.7.0

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.
Files changed (4) hide show
  1. data/ChangeLog +128 -0
  2. data/bin/xmltv2html.rb +402 -323
  3. data/xmltv2htmlrc +31 -33
  4. metadata +3 -3
data/bin/xmltv2html.rb CHANGED
@@ -1,9 +1,10 @@
1
+ #!/usr/bin/ruby -w
2
+ # With Ruby 1.8.3, yaml gives a lot of warnings... :-(( Fixed with 1.8.4
1
3
  #!/usr/bin/env ruby
2
- #!/usr/bin/ruby -w # With Ruby 1.8.3, yaml gives a lot of warnings... :-((
3
4
  #
4
5
  # xmltv2html.rb - A Ruby script to transform the XMLTV output into HTML.
5
6
  #
6
- # Version : 0.6.1
7
+ # Version : 0.7.0
7
8
  # Author : Kurt V. Hindenburg <public@kurt.hindenburg.name>
8
9
  #
9
10
  # Copyright (C) 2003, 2004, 2005 Kurt V. Hindenburg
@@ -38,8 +39,6 @@ file and a CSS file.
38
39
  = OPTIONS
39
40
  : -c, --configfile=FILE
40
41
  Configuration file to use.
41
- : --noconfigfile
42
- Do not use any configuration file.
43
42
  : --urlprev=URL
44
43
  URL for previous link.
45
44
  : --urlnext=URL
@@ -63,7 +62,6 @@ Written by Kurt V. Hindenburg <public@kurt.hindenburg.name>
63
62
  = SEE ALSO
64
63
  * ((<"xmltv2html.rb home page - http://xmltv2html.rubyforge.org/"|URL:http://xmltv2html.rubyforge.org/>))
65
64
  * ((<"xmltv home page - http://sourceforge.net/projects/xmltv"|URL:http://sourceforge.net/projects/xmltv>))
66
- * ((<"REXML home page - http://www.germane-software.com/software/rexml"|URL:http://www.germane-software.com/software/rexml>))
67
65
  = BUGS
68
66
  * Please process the xmltv.xml file through tv_sort ((*before*)) using xmltv2html.rb
69
67
 
@@ -75,10 +73,10 @@ require 'rexml/document'
75
73
  require 'singleton'
76
74
  require 'time'
77
75
  require 'set'
76
+ require 'ostruct'
78
77
 
79
-
80
- XMLTV2HTML_VERSION="0.6.1"
81
- XMLTV2HTML_DATE="Dec 21, 2005"
78
+ XMLTV2HTML_VERSION="0.7.0"
79
+ XMLTV2HTML_DATE="Feb 13, 2006"
82
80
 
83
81
  class Time
84
82
  class << self
@@ -95,9 +93,8 @@ class Time
95
93
  end
96
94
 
97
95
  ### object.<method>
98
-
99
96
  def round_to_interval
100
- self - (self.min % @@options['time_divisor']) * 60
97
+ self - (self.min % @@options[:time_divisor]) * 60
101
98
  end
102
99
 
103
100
  end
@@ -113,79 +110,55 @@ module XMLTV2HTML
113
110
  exit(1)
114
111
  end
115
112
 
116
- def test
117
- # info "The West Wing=", @@options['favorites'].include?('The West Wing')
118
- # info "EXTRA=", @@options['favorites'].include?('EXTRA')
119
- # info "Lay & Order=", @@options['favorites'].include?("Law & Order")
120
- # info "Categories=", @@options['categories'].class
121
- # info "Categories=", @@options['categories']
122
- # info "start_time=",@@options['start_time']
123
- # info "stop_time=",@@options['stop_time']
124
- end
125
-
126
113
  class ConfReader
127
114
  def initialize
128
115
  @@options = self # set Module variable
129
116
  @cmd_line_options = nil
130
117
  clear
131
118
  set_defaults
132
- # info "default config_file=",@@options['config_file']
133
119
  parse_command_line_options_for_config_file
134
- # info "cmd_line config_file=",@@options['config_file']
135
- # info "cmd_line start_time=",@@options['start_time']
136
- # info "cmd_line stop_time=",@@options['stop_time']
137
-
138
120
 
139
- read_conf if @@options['use_config_file']
140
- # info "start_time=",@@options['start_time']
141
- # info "stop_time=",@@options['stop_time']
121
+ read_conf if @@options[:use_config_file]
142
122
  parse_command_line_options
143
- # info "start_time=",@@options['start_time']
144
- # info "stop_time=",@@options['stop_time']
145
- # info "config_file=",@@options['config_file']
146
123
 
147
124
  # convert favorites_list to Set for faster lookup
148
- @@options['favorites'] = Set.new @@options['favorites_list' ]
149
-
150
- test
125
+ @@options[:favorites] = Set.new @@options[:favorites_list]
151
126
  end
152
127
 
153
128
  # Don't edit these, use an external xmltv2htmlrc file.
154
129
  def set_defaults
155
130
  @tree = {
156
- 'verbose' => false,
157
- 'times_interval' => 4,
158
- 'channels_interval' => 4,
159
- 'time_format_12' => true,
160
- 'use_favorites' => false,
161
- 'output_favorites' => false,
162
- 'favorites_list' => [],
163
- 'css_filename' => 'xmltv2html.css',
164
- 'categories' => {},
165
- 'output_date_in_time' => false,
166
- 'date_format' => '%a %d',
167
- 'use_programme_popup' => true,
168
- 'programme_popup_method' => "DHTML",
169
- 'popup_title_format' => "%T",
170
- 'popup_body_format' => "%S %P<br />%D<br />Rating: %R",
171
- 'popup_title_color' => "#FFFFFF",
172
- 'popup_title_font' => "",
173
- 'popup_title_font_size' => "2",
174
- 'popup_title_background_color' => "#000099",
175
- 'popup_body_color' => "#000000",
176
- 'popup_body_font' => "",
177
- 'popup_body_font_size' => "1",
178
- 'popup_body_background_color' => "#E8E8FF",
179
- 'popup_body_width' => "200",
180
- 'output_links' => false,
181
- 'start_time' => "",
182
- 'stop_time' => "",
183
- 'start_date' => "",
184
- 'stop_date' => "",
185
- 'time_divisor' => 10, # Divide programmes' times in X minute slots
186
- 'total_hours' => 0,
187
- 'config_file' => "",
188
- 'use_config_file' => true,
131
+ :verbose => false,
132
+ :times_interval => 4,
133
+ :channels_interval => 4,
134
+ :time_format_12 => true,
135
+ :use_favorites => false,
136
+ :output_favorites => false,
137
+ :favorites_list => [],
138
+ :css_filename => 'xmltv2html.css',
139
+ :categories => {},
140
+ :output_date_in_time => false,
141
+ :date_format => '%a %d',
142
+ :use_programme_popup => true,
143
+ :programme_popup_method => "DHTML",
144
+ :popup_title_format => "%T",
145
+ :popup_body_format => "%S %P<br />%D<br />Rating: %R",
146
+ :popup_title_color => "#FFFFFF",
147
+ :popup_title_font => "",
148
+ :popup_title_font_size => "2",
149
+ :popup_title_background_color => "#000099",
150
+ :popup_body_color => "#000000",
151
+ :popup_body_font => "",
152
+ :popup_body_font_size => "1",
153
+ :popup_body_background_color => "#E8E8FF",
154
+ :popup_body_width => "200",
155
+ :output_links => false,
156
+ :start_time => "",
157
+ :stop_time => "",
158
+ :time_divisor => 10, # Divide programmes' times in X minute slots
159
+ :total_hours => 0,
160
+ :config_file => "",
161
+ :use_config_file => true,
189
162
  }
190
163
  end
191
164
 
@@ -202,8 +175,8 @@ module XMLTV2HTML
202
175
  end
203
176
 
204
177
  def read_conf
205
- return if @@options['config_file'].empty?
206
- f=File.expand_path(@@options['config_file'])
178
+ return if @@options[:config_file].empty?
179
+ f=File.expand_path(@@options[:config_file])
207
180
  if not File.exists?(f) or not File.stat(f).readable_real?
208
181
  info "* Unable to read #{f}\n"
209
182
  return
@@ -211,12 +184,12 @@ module XMLTV2HTML
211
184
  new_xmltv2htmlrc_format = false
212
185
  File.open(f, "r") do |aFile|
213
186
  first_line = aFile.gets
214
- new_xmltv2htmlrc_format = true if first_line =~ /^#config_version: 1/
187
+ new_xmltv2htmlrc_format = true if first_line =~ /^#config_version: 2/
215
188
  end
216
189
  if new_xmltv2htmlrc_format == false
217
- info "\nWhoa! You are using an old xmltv2htmlrc format file (v0.5.x)."
190
+ info "\nWhoa! You are using an old xmltv2htmlrc format file (< v0.7.x)."
218
191
  info "Please update your xmltv2htmlrc using the sample file provided"
219
- info "with xmltv2html-0.6.0+."
192
+ info "with xmltv2html-0.7.0+."
220
193
  die "\n"
221
194
  end
222
195
  info "^ Reading #{f}\n"
@@ -229,12 +202,11 @@ module XMLTV2HTML
229
202
  ARGV.each do
230
203
  |opts|
231
204
  if opts =~ /--configfile=/
232
- @@options['config_file'] = opts.split(/=/)[1]
205
+ @@options[:config_file] = opts.split(/=/)[1]
233
206
  end
234
207
  end
235
208
  end
236
209
 
237
- # TODO: This has to be a better way to do this...
238
210
  def parse_command_line_options
239
211
  ARGV.options do
240
212
  |opts|
@@ -245,17 +217,17 @@ module XMLTV2HTML
245
217
  opts.on_tail("common options:")
246
218
 
247
219
  opts.on("--configfile=FILE", String, "config file to use") {
248
- |@@options['config_file']|}
220
+ |@@options[:config_file]|}
249
221
 
250
222
  opts.on("--starttime=YYYYMMDDHHMM", String, "Start time") {
251
- |@@options['start_time']|}
223
+ |@@options[:start_time]|}
252
224
  opts.on("--stoptime=YYYYMMDDHHMM", String, "Stop time") {
253
- |@@options['stop_time']|}
225
+ |@@options[:stop_time]|}
254
226
 
255
227
  opts.on("--urlprev=URL", String, "URL for previous link") {
256
- |@@options['url_prev']|}
228
+ |@@options[:url_prev]|}
257
229
  opts.on("--urlnext=URL", String, "URL for next link") {
258
- |@@options['url_next']|}
230
+ |@@options[:url_next]|}
259
231
 
260
232
  # no argument, shows at tail
261
233
  opts.on_tail("-h", "--help", "show this message") {puts opts; exit}
@@ -282,86 +254,103 @@ EOF
282
254
 
283
255
  end
284
256
 
285
- class ProgrammeTime
286
- attr_reader(:fullStartTime, :fullStopTime)
287
-
288
- def initialize(start, stop)
289
- @fullStartTime = Time.parse_xmltv_time(start)
290
- @fullStopTime = Time.parse_xmltv_time(stop)
291
-
292
- #$stderr.print "#{@fullStartTime}; "
293
- @fullStartTime = @fullStartTime.round_to_interval
294
- #$stderr.print "after #{@fullStartTime} \n"
295
- @fullStopTime = @fullStopTime.round_to_interval
296
-
297
- @start_date = nil
298
- @stop_date = nil
299
- end
300
-
301
- def returnParsedTime(t)
302
- begin
303
- pt = Time.parse(t)
304
- rescue ArgumentError
305
- die "Unable to parse #{t}\n\n"
306
- end
307
- pt
308
- end
309
-
310
- def calculateStartSlot
311
- return if @@options['start_date'] == nil # 1st pass
312
- @start_date = returnParsedTime(@@options['start_date']) if not @start_date
313
-
314
- start_diff_min = ((@fullStartTime - @start_date) / 60).to_i
315
- # $stderr.print ", diff #{start_diff_min}"
316
- start_slot = start_diff_min / @@options['time_divisor']
317
- # $stderr.print "starting slot #{start_slot}\n"
318
- start_slot
319
- end
320
- def calculateStopSlot
321
- return if @@options['stop_date'] == nil # 1st pass
322
-
323
- @start_date = returnParsedTime(@@options['start_date']) if not @start_date
324
- stop_diff_min = ((@fullStopTime - @start_date) / 60).to_i
325
- stop_slot = stop_diff_min / @@options['time_divisor']
326
- # $stderr.print " stoping slot #{stop_slot}\n"
327
-
328
- # (@fullStopTime.hour * 60 / $timeDivisor) + (@fullStopTime.min / $timeDivisor)
329
- stop_slot
330
- end
331
-
332
- end
333
-
334
257
  class Programme
335
258
  attr_reader :title, :subtitle, :span, :times, :desc, :rating, :category
336
259
  attr_reader :previouslyShown, :spanSlots, :startSlot
337
- attr_accessor :popupIndex
260
+ attr_accessor :startTimeStr, :stopTimeStr
261
+ attr_accessor :startTime, :stopTime, :popupIndex
338
262
 
339
263
  def initialize(title, subtitle, channel, start, stop, desc, rating, cats, rerun)
340
264
  @title = title
341
265
  @subtitle = subtitle
342
266
  @channelid = channel
267
+ @startTimeStr = start
268
+ @stopTimeStr = stop
343
269
  @desc = desc
344
270
  @rating = rating
345
271
  @category = cats
346
272
  @previouslyShown = rerun
347
273
  @popupIndex = -1
274
+ @startTime = nil
275
+ @stopTime = nil
276
+ @startSlot = 0
277
+ @stopSlot = -1
278
+
279
+ @title.gsub!(/[\"\'\`]/,'') # Remove "'`
280
+ @title = @title.unpack("U*").pack("C*")
281
+
282
+ @subtitle = "" if not @subtitle
283
+ @subtitle.gsub!(/[\"\'\`]/,'') # Remove "'`
284
+
285
+ @desc = "" if not @desc
286
+ @desc.gsub!(/[\"\'\`]/,'') # Remove "'`
287
+ @desc = @desc.unpack("U*").pack("C*")
348
288
 
349
- @times = ProgrammeTime.new(start, stop)
289
+ @rating = "" if not @rating
290
+ @rating.gsub!(/[\"\'\`]/,'') # Remove "'`
291
+
292
+ # @times = ProgrammeTime.new(start, stop)
293
+ setStartTime(start) if start
294
+ setStopTime(stop) if stop
350
295
  end
351
296
 
352
- # Slot interval = 12 (60 minutes / 5 minutes)
353
- def calculateSlots
354
- @startSlot = @times.calculateStartSlot
355
- @stopSlot = @times.calculateStopSlot
297
+ # Change start time
298
+ def setStartTime(start)
299
+ #$stderr.print "setStartTime=#{start}\n"
300
+ case start
301
+ when String
302
+ begin
303
+ @startTimeStr = start
304
+ @startTime = Time.parse_xmltv_time(@startTimeStr)
305
+ end
306
+ when Time
307
+ begin
308
+ @startTime = start
309
+ @startTimeStr = start.strftime("%Y%m%d%H%M%S")
310
+ end
311
+ end
312
+ end
356
313
 
357
- return if not @startSlot
358
- return if not @stopSlot
314
+ # Change stop time
315
+ def setStopTime(stop)
316
+ case stop
317
+ when String
318
+ begin
319
+ @stopTimeStr = stop
320
+ @stopTime = Time.parse_xmltv_time(@stopTimeStr)
321
+ end
322
+ when Time
323
+ begin
324
+ @stopTime = stop
325
+ @stopTimeStr = stop.strftime("%Y%m%d%H%M%S")
326
+ end
327
+ end
328
+ end
329
+
330
+ def calculateProgrammeSlots
331
+
332
+ listing_start_time = Time.parse_xmltv_time(@@options[:start_time])
333
+
334
+ start_diff_min = ((@startTime - listing_start_time) / 60).to_i
335
+ @startSlot = start_diff_min / @@options[:time_divisor]
336
+
337
+ stop_diff_min = ((@stopTime - listing_start_time) / 60).to_i
338
+ @stopSlot = stop_diff_min / @@options[:time_divisor]
359
339
 
360
340
  @spanSlots = @stopSlot - @startSlot
341
+
342
+ #$stderr.print "startSlot=#{@startSlot}, stopSlot=#{@stopSlot}, spanSlots=#{@spanSlots}\n"
343
+
361
344
  end
362
345
 
363
- def to_s
364
- @title
346
+ def calculateTimeData
347
+
348
+ @startTime = Time.parse_xmltv_time(@startTimeStr)
349
+ @stopTime = Time.parse_xmltv_time(@stopTimeStr)
350
+
351
+ setStartTime @startTime.round_to_interval
352
+ setStopTime @stopTime.round_to_interval
353
+
365
354
  end
366
355
 
367
356
  end
@@ -395,7 +384,8 @@ end
395
384
 
396
385
  class Channel
397
386
  attr_reader(:name, :id, :fullname)
398
- attr_reader(:programmes)
387
+ attr_accessor(:programmes)
388
+ # attr_reader(:programmes)
399
389
  attr_accessor(:totalSpan)
400
390
 
401
391
  def initialize(id, fn)
@@ -417,39 +407,69 @@ class Channel
417
407
  end
418
408
 
419
409
  def number_of_programmes
420
- @programmes[@id].size
410
+ return @programmes[@id].size if @programmes[@id]
411
+ 0
421
412
  end
422
413
 
423
414
  def programme_at(i)
424
- @programmes[@id][i]
415
+ @programmes[@id][i] if @programmes[@id]
416
+ end
417
+
418
+ def addDummyProgramme(index, start, stop)
419
+ info "\n * Adding dummy programme: #{start}, #{stop}\n" +
420
+ " This could indicate a problem with your xmltv grabber.\n\n"
421
+
422
+ @programmes[@id][index,0] = Programme.new("No Data", "", @id,
423
+ start, stop, "", "",
424
+ "", false)
425
+ @programmes[@id][index].calculateTimeData
426
+ @programmes[@id][index].calculateProgrammeSlots
425
427
  end
426
428
 
427
429
  # Verify that each show's STOP date is the next show's START date
428
- # Should not be needed if tv_sort was used.
429
- # TODO: remove this once we can verify tv_sort was used on input data
430
+ # tv_sort doesn't fix all the start/stop issues...
430
431
  def verifyStopDate
431
- # @programmeList.each_index { |si|
432
- # s = @programmeList[si]
433
- # next_show = @programmeList[si.succ]
434
- # next if next_show == nil
435
- # next if s.times.fullStopTime == next_show.times.fullStartTime
436
- #
437
- # die "\n * A programme's stop time does not match the next \n" +
438
- # " * programme's start time. \n" +
439
- # " * Use tv_sort from the xmltv distribution to correct!\n" +
440
- # " * Exiting...\n\n" if !stop
441
- # }
442
- end
432
+ dummy_record = OpenStruct.new
433
+ dummy_progs = []
434
+
435
+ @programmes[@id].each_index { |si|
436
+ s = @programmes[@id][si]
437
+ next_show = @programmes[@id][si.succ]
438
+ next if next_show == nil
439
+ next if s.stopTime == next_show.startTime
440
+
441
+ if s.stopTime > next_show.startTime
442
+ die "\n * Whoa...this is bad!\n" +
443
+ " * Previous programme's stop time #{s.stopTime}, next\n" +
444
+ " * programme's start time #{next_show.startTime}\n\n"
445
+ end
443
446
 
444
- def calc_programme_slots
447
+ dummy_record.index = si + 1
448
+ dummy_record.start = s.stopTime
449
+ dummy_record.stop = next_show.startTime
450
+ dummy_progs << dummy_record
445
451
 
446
- @programmes[@id].each { |p|
447
- p.calculateSlots
452
+ addDummyProgramme(si + 1, s.stopTime, next_show.startTime)
453
+
454
+ # info " * Using tv_sort from the xmltv may correct this!\n" +
455
+ # " * Currently xmltv2html is unable to continue...\n\n" +
456
+ # " * Previous programme's stop time #{s.stopTime}, next\n" +
457
+ # " * programme's start time #{next_show.startTime}\n\n" +
458
+ # " * Previous programme's stop time #{s.stopTimeStr}, next\n" +
459
+ # " * programme's start time #{next_show.startTimeStr}\n\n"
448
460
  }
449
461
  end
450
462
 
463
+
464
+ def calc_programme_slots
465
+ # $stderr.print "\n# programmes=#{number_of_programmes}\n\n"
466
+ # @programmes[@id].each { |p|
467
+ # p.calculateSlots
468
+ # }
469
+ end
470
+
451
471
  def calculateSlots(slist)
452
- tinterval = @@options['channels_interval'] * 60 / @@options['time_divisor']
472
+ tinterval = @@options[:channels_interval] * 60 / @@options[:time_divisor]
453
473
  left = []
454
474
  right = slist.dup
455
475
  index = 0
@@ -503,8 +523,8 @@ class Channel
503
523
  times_counter = 1
504
524
  sindex = 0
505
525
  total = 0
506
- if @@options['channels_interval'] > 0
507
- tinterval = @@options['channels_interval'] * 60 / @@options['time_divisor']
526
+ if @@options[:channels_interval] > 0
527
+ tinterval = @@options[:channels_interval] * 60 / @@options[:time_divisor]
508
528
  else
509
529
  tinterval = 9999
510
530
  end
@@ -512,19 +532,40 @@ class Channel
512
532
 
513
533
  slist = Array.new
514
534
 
515
-
516
535
  s = @programmes[@id].first # 1st programme for this channel
536
+ # $stderr.print "### programmes=#{@programmes[@id].size}\n"
517
537
 
518
- if s.startSlot() > 0 # Missing programme at start
519
- dummy_span = s.startSlot() - span_counter
538
+ # $stderr.print "#{s.class}, startSlot=#{s.startSlot}\n"
539
+ # $stderr.print "startSlot=#{s.startTime},#{s.startTimeStr}\n"
540
+ if s.startSlot > 0 # Missing programme at start
541
+ dummy_span = s.startSlot - span_counter
520
542
  slist.push "D"+dummy_span.to_s
521
543
  end
522
544
 
523
545
  @programmes[@id].each { |s|
524
- slist.push "P"+s.spanSlots.to_s
546
+ # if s.title == "No Data" # Where we had to add dummy in middle
547
+ # slist.push "D"+s.spanSlots.to_s
548
+ # $stderr.print "push dummy...slots=#{s.spanSlots.to_s}\n"
549
+ # else
550
+ slist.push "P"+s.spanSlots.to_s # if s.spanSlots
551
+ # end
525
552
  }
526
553
 
527
- if @@options['channels_interval'] > 0
554
+ slist.each { |entry|
555
+ span = entry.slice(1..-1).to_i
556
+ total += span
557
+ }
558
+
559
+ dinterval = @@options[:total_hours] * 60 / @@options[:time_divisor]
560
+ #$stderr.print "totalhours=#{@@options[:total_hours]}\n"
561
+ #$stderr.print "total=#{total}, dinterval=#{dinterval}\n"
562
+ if total < dinterval # Not enough programmes' data at end
563
+ slist.push("D"+(dinterval - total).to_s)
564
+ end
565
+ #$stderr.print slist,"\n"
566
+
567
+ total = 0
568
+ if @@options[:channels_interval] > 0
528
569
  sl = calculateSlots(slist)
529
570
  while sl != slist
530
571
  slist = sl.dup
@@ -536,23 +577,11 @@ class Channel
536
577
  span = entry.slice(1..-1).to_i
537
578
  l.push entry
538
579
  total += span
539
- l.push("C0") if (total % tinterval) == 0 and @@options['channels_interval'] > 0
580
+ l.push("C0") if (total % tinterval) == 0 and @@options[:channels_interval] > 0
540
581
  }
541
582
 
542
583
  slist = l.unshift("C0") # Add left-most channel
543
- # Add left-most channel if not already there
544
- # slist = l
545
- # slist = l.unshift("C0") if l[0] != "C0"
546
- # slist = l.unshift("C0") if l[0, 1] != "C0"
547
- # $stderr.print slist[0, 3],"\n"
548
- # $stderr.print l[0, 3],"\n"
549
-
550
- dinterval = @@options['total_hours'] * 60 / @@options['time_divisor']
551
- if total < dinterval # Not enough programmes' data at end
552
- slist.push("D"+(dinterval - total).to_s)
553
- slist.push("C0") if dinterval % tinterval == 0
554
- end
555
-
584
+ #$stderr.print slist,"\n"
556
585
  slist
557
586
  end
558
587
 
@@ -572,7 +601,6 @@ class Channels < Hash
572
601
  end
573
602
 
574
603
  def calc_programmes_slots
575
-
576
604
  each { |id, c|
577
605
  c.calc_programme_slots
578
606
  }
@@ -608,8 +636,9 @@ class XmlTV
608
636
  @top_title = ""
609
637
  @HTML_title = ""
610
638
  @top_title += @srcInfoName + " :: " if @srcInfoName
611
- t1 = Time.parse(@firstShowStartDate)
612
- t2 = Time.parse(@lastShowStopDate)
639
+ t1 = Time.parse(@@options[:start_time])
640
+ t2 = Time.parse(@@options[:stop_time])
641
+ # t2 = Time.parse(@lastShowStopDate)
613
642
 
614
643
  @time_first = t1.strftime("%a %b %d %I:%M %p")
615
644
  @time_last = t2.strftime("%a %b %d %I:%M %p %Z")
@@ -642,6 +671,9 @@ class XmlTV
642
671
  }
643
672
  channels[id] = Channel.new(id, fn)
644
673
  }
674
+ if channels.size < 1
675
+ die "No channels in the input file!"
676
+ end
645
677
  end
646
678
 
647
679
  def parseProgrammes
@@ -653,60 +685,13 @@ class XmlTV
653
685
  rating=""
654
686
  cats = nil
655
687
  rerun = false
656
- ndesc = ""
657
688
  start = element.attributes["start"]
658
689
  stop = element.attributes["stop"]
659
- die "\n * No stop attribute in this programme...\n" +
660
- " * Use tv_sort from the xmltv distribution to correct!\n" +
661
- " * Exiting...\n\n" if !stop
690
+ dstart = ""
691
+ dstop= ""
662
692
 
663
693
  dstart = start[0..13]
664
- dstop = stop[0..13]
665
- ext = start[14..-1]
666
-
667
- # info "start_time=",@@options['start_time']
668
- # info "stop_time=",@@options['stop_time']
669
- # info "empty?" ,@@options['start_time'].empty?
670
- if not @@options['start_time'].empty?
671
- nstart = @@options['start_time'].clone
672
- nstart[10..11] = @@options['time_divisor'].to_s
673
-
674
- # If programme ends before the desired start time...
675
- # Adjust for the time_divisor (round_to_interval)
676
- if dstop < nstart
677
- # info "Delete programme - Desired start : #{@@options['start_time']}, "
678
- # $stderr.print "Delete programme - Desired start : #{$params.start_time}, "
679
- # $stderr.print "programme stop : #{dstop}\n"
680
- # $stderr.print "Old start time = #{$params.start_time}, new=#{nstart}\n"
681
- next
682
- end
683
- end
684
- # If programme starts after the desired stop time...
685
- if (not @@options['stop_time'].empty?) and (dstart >= @@options['stop_time'])
686
- # $stderr.print "Delete programme - Desired stop : #{$params.stop_time}, "
687
- # $stderr.print "programme start : #{dstart}\n"
688
- # info "programme start : #{dstart}\n"
689
- next
690
- end
691
-
692
- # If programme starts before the desired start time, change start
693
- if (not @@options['start_time'].empty?) and (dstart < @@options['start_time'])
694
- # info "Change Start - new start : "
695
- # $stderr.print "Change Start - new start : #{$params.start_time}, "
696
- # $stderr.print "old start: #{dstart}\n"
697
- # ndesc = "(" + start[8..9] + ":" + start[10..11] + ") "
698
-
699
- start = @@options['start_time'] + ext
700
- dstart = start[0..13]
701
- end
702
-
703
-
704
- # If programme ends after the desired stop time, change stop
705
- if (not @@options['stop_time'].empty?) and (dstop > @@options['stop_time'])
706
- stop = @@options['stop_time'] + ext
707
- # info "Change Stop - new stopt : "
708
- dstop = stop[0..13]
709
- end
694
+ dstop = stop[0..13] if stop
710
695
 
711
696
  @firstShowStartDate = dstart if @firstShowStartDate > dstart
712
697
  @lastShowStartDate = dstart if @lastShowStartDate < dstart
@@ -726,34 +711,126 @@ class XmlTV
726
711
  # Check to see if user want to use special CSS class for category
727
712
  # FIXME: What happens when more than 1 category is triggered?
728
713
  if e.name == "category"
729
- if @@options['categories'].has_key?(e.text())
730
- cats = @@options['categories'][e.text()];
714
+ if @@options[:categories].has_key?(e.text())
715
+ cats = @@options[:categories][e.text()];
731
716
  end
732
717
  end
733
718
  }
734
-
735
- title.gsub!(/[\"\'\`]/,'') # Remove "'`
736
- title = title.unpack("U*").pack("C*")
737
719
 
738
- subtitle = "" if not subtitle
739
- subtitle.gsub!(/[\"\'\`]/,'') # Remove "'`
740
-
741
- desc = "" if not desc
742
- desc.gsub!(/[\"\'\`]/,'') # Remove "'`
743
- desc = desc.unpack("U*").pack("C*")
744
- desc = ndesc + desc if ndesc
745
-
746
- rating = "" if not rating
747
- rating.gsub!(/[\"\'\`]/,'') # Remove "'`
748
- #$stderr.print "title=#{title}, desc=#{desc}, rating=#{rating}\n"
749
-
750
- p = Programme.new(
751
- title, subtitle, channel, start, stop, desc, rating, cats, rerun)
752
- # plist[channel] = p
753
- channels[channel] << p
720
+ channels[channel] << Programme.new(title, subtitle, channel,
721
+ start, stop, desc, rating,
722
+ cats, rerun)
723
+ }
724
+
725
+ channels.each { |id, c|
726
+ if c.number_of_programmes < 1
727
+ die "No programmes for channel #{id} in the input file!"
728
+ end
754
729
  }
755
730
  end
756
731
 
732
+ # Set :start_time and :stop_time if needed.
733
+ # Adjust start/stop_time to round to next hour
734
+ # Add stop time, should be very last programme stop time.
735
+ # Correct start, stop times due to --starttime, --stoptime
736
+ # Remove programmes that are too short
737
+ # Remove programmes which stop before --starttime
738
+ # Remove programmes which start after --stoptime
739
+ def filterProgrammes
740
+ # $stderr.print "@firstShowStartDate=#{@firstShowStartDate}\n"
741
+ # $stderr.print "@lastShowStartDate =#{@lastShowStartDate}\n"
742
+ # $stderr.print "@lastShowStopDate =#{@lastShowStopDate}\n"
743
+
744
+ # Set if --starttime= nor --stoptime= not given.
745
+ if (@@options[:start_time].empty?)
746
+ @@options[:start_time] = @firstShowStartDate
747
+ end
748
+ if (@@options[:stop_time].empty?)
749
+ @@options[:stop_time] = @lastShowStopDate
750
+ end
751
+
752
+ # Force start/stop time on hour
753
+ @@options[:start_time][10,4] = "0000"
754
+
755
+ if @@options[:stop_time][10,2] != "00"
756
+ hour = (@@options[:stop_time][8,2]).to_i + 1
757
+ if hour > 23 # Next day
758
+ pstop = Time.parse(@@options[:stop_time]) + (3600)
759
+ @@options[:stop_time] = pstop.strftime("%Y%m%d%H%M%S")
760
+ @@options[:stop_time][12,2] = "00"
761
+ end
762
+ # FIXME: Below still needed?
763
+ @@options[:stop_time][8,2] = hour.to_s.rjust(2).sub(/\s/,'0') if hour < 10
764
+ end
765
+ @@options[:stop_time][10,4] = "0000"
766
+
767
+
768
+ channels = Channels.instance
769
+
770
+ channels.each { |id, c|
771
+ programmes = c.programmes
772
+ # $stderr.print "\n# programmes=#{c.number_of_programmes}\n\n"
773
+
774
+ programmes.each { | id, p|
775
+ delete_indicies = []
776
+ p.each_with_index { |programme, index|
777
+
778
+ if !programme.stopTimeStr
779
+ programme.setStopTime @@options[:stop_time]
780
+ end
781
+
782
+ dstart = programme.startTimeStr
783
+ dstop = programme.stopTimeStr
784
+
785
+ # If programme ends before the desired start time...drop it.
786
+ nstart = @@options[:start_time].clone
787
+ nstart[10..11] = @@options[:time_divisor].to_s
788
+ if dstop < nstart
789
+ # $stderr.print "#{index}, programme ends #{dstop} before start #{nstart}\n"
790
+ # p.delete programme
791
+ # p = p.compact
792
+ delete_indicies << index
793
+ next
794
+ end
795
+
796
+ # If programme starts after the desired stop time...drop it.
797
+ if (dstart >= @@options[:stop_time])
798
+ # $stderr.print "#{index}, programme starts #{dstart} after stoptime #{@@options[:stop_time]}\n"
799
+ # p.delete programme
800
+ # p = p.compact
801
+ delete_indicies << index
802
+ next
803
+ end
804
+
805
+ # If programme starts before the desired start time, change it.
806
+ if (dstart < @@options[:start_time])
807
+ programme.setStartTime @@options[:start_time]
808
+ end
809
+
810
+ # If programme stops after the desired stop time, change it.
811
+ if (dstop > @@options[:stop_time])
812
+ programme.setStopTime @@options[:stop_time]
813
+ end
814
+
815
+ programme.calculateTimeData
816
+ programme.calculateProgrammeSlots
817
+ if programme.spanSlots < 1
818
+ # $stderr.print "span slot=#{programme.spanSlots}\n"
819
+ delete_indicies << index
820
+ next
821
+ end
822
+ }
823
+
824
+ # Delete programme's in reverse order
825
+ delete_indicies.reverse_each { |i|
826
+ c.programmes[id].delete_at i
827
+ c.programmes[id].compact!
828
+ }
829
+ c.verifyStopDate
830
+ # $stderr.print "# programmes=#{c.number_of_programmes}\n"
831
+ }
832
+ }
833
+ end
757
834
 
758
835
  end
759
836
 
@@ -772,33 +849,22 @@ class XMLTV2HTML2
772
849
  @xml.parseChannels
773
850
  @xml.parseProgrammes
774
851
 
852
+ @xml.filterProgrammes
775
853
 
776
- @@options['start_date'] = @xml.firstShowStartDate
777
- @@options['stop_date'] = @xml.lastShowStopDate
854
+ #$stderr.print "start_time=#{@@options[:start_time]}\n"
855
+ #$stderr.print "stop_time= #{@@options[:stop_time]}\n"
778
856
 
779
- # Force start/stop time on hour
780
- @@options['start_date'][10,4] = "0000"
781
-
782
- # info "start_date=",@@options['start_date']
783
- # info "stop_date= ",@@options['stop_date']
784
- if @@options['stop_date'][10,2] != "00"
785
- hour = (@@options['stop_date'][8,2]).to_i + 1
786
- if hour > 23
787
- $stderr.print "yuck #{hour}\n"
788
- end
789
- @@options['stop_date'][8,2] = hour.to_s.rjust(2).sub(/\s/,'0')
790
- end
791
- @@options['stop_date'][10,4] = "0000"
857
+ pstart = Time.parse(@@options[:start_time])
792
858
 
793
- pstart = Time.parse(@@options['start_date'])
794
- pstop = Time.parse(@@options['stop_date'])
795
- @@options['total_hours'] = ((pstop - pstart) / 3600).to_i
859
+ pstop = Time.parse(@@options[:stop_time])
860
+ @@options[:total_hours] = ((pstop - pstart) / 3600).to_i
796
861
 
862
+ #$stderr.print " hours #{@@options[:total_hours]}\n"
797
863
  channels = Channels.instance
798
864
  channels.calc_programmes_slots
799
865
 
800
- fdate = @@options['start_date'][0,8]
801
- ldate = @@options['stop_date'][0,8]
866
+ fdate = @@options[:start_time][0,8]
867
+ ldate = @@options[:stop_time][0,8]
802
868
 
803
869
  @xml.setTitle(@dates)
804
870
 
@@ -824,7 +890,7 @@ class XMLTV2HTML2
824
890
 
825
891
  @out.doctype
826
892
  @out.header(@xml.HTML_title)
827
- generatePopups if @@options['use_programme_popup']
893
+ generatePopups if @@options[:use_programme_popup]
828
894
  @out.text_before_table(@xml.top_title)
829
895
  @out.table_start
830
896
  @out.table_times
@@ -836,6 +902,7 @@ class XMLTV2HTML2
836
902
  @out.outputChannelBegin
837
903
  slot_list = c.createSlotList
838
904
  next unless slot_list
905
+
839
906
  i = 0
840
907
  pi = -1
841
908
  slot_list.each { |entry|
@@ -848,14 +915,26 @@ class XMLTV2HTML2
848
915
  sindex += 1
849
916
  pi += 1
850
917
  prog = c.programme_at(pi)
918
+
919
+ # Where we had to add dummy in middle
920
+ if prog.title == "No Data"
921
+ @out.outputDummyProgramme(span)
922
+ next
923
+ end
924
+
851
925
  @out.outputProgramme(prog, sindex, span)
852
- # @out.outputProgramme(c.programme_at(pi), sindex, span)
853
- if @@options['use_favorites'] and @@options['output_favorites'] and @@options['favorites'].include?(prog.title)
926
+ if @@options[:use_favorites] and @@options[:output_favorites] and @@options[:favorites].include?(prog.title)
854
927
  #$stderr.print "found a fav #{prog.title}\n"
855
928
  favorites_list << prog
856
929
  end
857
930
  when "Q" # Use previous Programme's info
858
- @out.outputProgramme(c.programme_at(pi), sindex, span)
931
+ prog = c.programme_at(pi)
932
+ # Where we had to add dummy in middle
933
+ if prog.title == "No Data"
934
+ @out.outputDummyProgramme(span)
935
+ next
936
+ end
937
+ @out.outputProgramme(prog, sindex, span)
859
938
  when "C"
860
939
  @out.outputChannel(c)
861
940
  else
@@ -864,8 +943,8 @@ class XMLTV2HTML2
864
943
  i += 1
865
944
  }
866
945
  @out.outputChannelEnd
867
- if @@options['times_interval'] > 0 and
868
- ((times_counter % @@options['times_interval']) == 0)
946
+ if @@options[:times_interval] > 0 and
947
+ ((times_counter % @@options[:times_interval]) == 0)
869
948
  @out.table_times
870
949
  end
871
950
  times_counter += 1
@@ -907,11 +986,11 @@ class Html
907
986
  print '<title>'
908
987
  print title if title
909
988
  print '</title>'; nl
910
- print '<link rel="stylesheet" href="', @@options['css_filename']
989
+ print '<link rel="stylesheet" href="', @@options[:css_filename]
911
990
  print '" type="text/css">'; nl
912
991
 
913
- if @@options['use_programme_popup']
914
- if @@options['programme_popup_method'] == "DHTML"
992
+ if @@options[:use_programme_popup]
993
+ if @@options[:programme_popup_method] == "DHTML"
915
994
  print '<script language="JavaScript1.2" src="popup.js" type="text/javascript"></script>'; nl
916
995
  else
917
996
  print '<script language="JavaScript1.2" type="text/javascript">'
@@ -936,17 +1015,17 @@ class Html
936
1015
 
937
1016
  # Styles
938
1017
  print 'Style[1]=['
939
- print '"', @@options['popup_title_color'], '",'
940
- print '"', @@options['popup_body_color'], '",'
941
- print '"', @@options['popup_title_background_color'], '",'
942
- print '"', @@options['popup_body_background_color'], '",'
1018
+ print '"', @@options[:popup_title_color], '",'
1019
+ print '"', @@options[:popup_body_color], '",'
1020
+ print '"', @@options[:popup_title_background_color], '",'
1021
+ print '"', @@options[:popup_body_background_color], '",'
943
1022
  print '"","","","",'
944
- print '"', @@options['popup_title_font'], '",'
945
- print '"', @@options['popup_body_font'], '",'
1023
+ print '"', @@options[:popup_title_font], '",'
1024
+ print '"', @@options[:popup_body_font], '",'
946
1025
  print '"center","",'
947
- print '"', @@options['popup_title_font_size'], '",'
948
- print '"', @@options['popup_body_font_size'], '",'
949
- print @@options['popup_body_width'], ','
1026
+ print '"', @@options[:popup_title_font_size], '",'
1027
+ print '"', @@options[:popup_body_font_size], '",'
1028
+ print @@options[:popup_body_width], ','
950
1029
  print '"",'
951
1030
  print '3,' # Border width
952
1031
  print '10,' # Padding around body text
@@ -961,9 +1040,9 @@ class Html
961
1040
  def outputPopupDescs(c, cindex)
962
1041
  # The descriptions go here...Text[#]=["title","text"]
963
1042
  c.programmes[c.id()].each { |s|
964
- title = @@options['popup_title_format'].sub(/\%T/, s.title)
1043
+ title = @@options[:popup_title_format].sub(/\%T/, s.title)
965
1044
  title.sub!(/\%R/, s.rating)
966
- desc = @@options['popup_body_format'].gsub(/%T/, s.title)
1045
+ desc = @@options[:popup_body_format].gsub(/%T/, s.title)
967
1046
  desc.sub!(/\%S/, s.subtitle)
968
1047
  if (s.previouslyShown) # rerun
969
1048
  desc.sub!(/\%P/, "(R)")
@@ -993,32 +1072,32 @@ class Html
993
1072
  print text
994
1073
  print '</h3></center>'; nl
995
1074
  end
996
- if @@options['url_prev']
1075
+ if @@options[:url_prev]
997
1076
  print '<a class="links" href="'
998
- print @@options['url_prev']
1077
+ print @@options[:url_prev]
999
1078
  print '"><<< Previous</a> | '
1000
1079
  end
1001
- if @@options['url_next']
1080
+ if @@options[:url_next]
1002
1081
  print '<a class="links" href="'
1003
- print @@options['url_next']
1082
+ print @@options[:url_next]
1004
1083
  print '">Next >>></a>'
1005
1084
  end
1006
1085
  end
1007
1086
 
1008
1087
  def text_after_table
1009
- if @@options['url_prev']
1088
+ if @@options[:url_prev]
1010
1089
  print '<a class="links" href="'
1011
- print @@options['url_prev']
1090
+ print @@options[:url_prev]
1012
1091
  print '"><<< Previous</a> | '
1013
1092
  end
1014
- if @@options['url_next']
1093
+ if @@options[:url_next]
1015
1094
  print '<a class="links" href="'
1016
- print @@options['url_next']
1095
+ print @@options[:url_next]
1017
1096
  print '">Next >>></a>'
1018
1097
  end
1019
1098
  print '<br />'
1020
1099
  outputInfo
1021
- outputLinks if @@options['output_links']
1100
+ outputLinks if @@options[:output_links]
1022
1101
  end
1023
1102
 
1024
1103
  def table_start
@@ -1036,7 +1115,7 @@ class Html
1036
1115
  def outputDate
1037
1116
  days = @hours/24
1038
1117
  colspan = 96
1039
- colspan += 96 / @@options['channels_interval'] if @@options['channels_interval'] > 0
1118
+ colspan += 96 / @@options[:channels_interval] if @@options[:channels_interval] > 0
1040
1119
 
1041
1120
  print '<tr class="date">'; nl
1042
1121
  (0 .. days-1).each { |d|
@@ -1046,12 +1125,12 @@ class Html
1046
1125
  end
1047
1126
 
1048
1127
  def table_times
1049
- intervals = 60 / @@options['time_divisor']
1128
+ intervals = 60 / @@options[:time_divisor]
1050
1129
 
1051
1130
 
1052
1131
  # Need hour to start.... we'll force it to be on an hour later
1053
- starting_day = @@options['start_date'][6,2].to_i
1054
- starting_hour = @@options['start_date'][8,2].to_i
1132
+ starting_day = @@options[:start_time][6,2].to_i
1133
+ starting_hour = @@options[:start_time][8,2].to_i
1055
1134
  # $stderr.print "Day to start: #{starting_day}\n"
1056
1135
  # $stderr.print "Hour to start: #{starting_hour}\n"
1057
1136
  # $stderr.print "Total hours: #{$params.total_hours}\n"
@@ -1060,13 +1139,13 @@ class Html
1060
1139
  output_channel_space
1061
1140
 
1062
1141
  cs = 0
1063
- (starting_hour .. starting_hour + @@options['total_hours'] - 1).each { |h|
1142
+ (starting_hour .. starting_hour + @@options[:total_hours] - 1).each { |h|
1064
1143
  (0 .. intervals-1).each { |hh|
1065
1144
  print '<td>';
1066
- printf "%02d", hh * @@options['time_divisor']; print '</td>';
1145
+ printf "%02d", hh * @@options[:time_divisor]; print '</td>';
1067
1146
  }
1068
1147
  cs += 1
1069
- output_channel_space if @@options['channels_interval'] > 0 and cs % @@options['channels_interval'] == 0
1148
+ output_channel_space if @@options[:channels_interval] > 0 and cs % @@options[:channels_interval] == 0
1070
1149
  }
1071
1150
  print '</tr>'; nl
1072
1151
 
@@ -1074,17 +1153,17 @@ class Html
1074
1153
  output_channel_space
1075
1154
  days = @hours/24
1076
1155
  cs = 0
1077
- cdate = Time.parse(@@options['start_date'])
1156
+ cdate = Time.parse(@@options[:start_time])
1078
1157
 
1079
- (starting_hour .. starting_hour + @@options['total_hours'] - 1).each { |hi|
1158
+ (starting_hour .. starting_hour + @@options[:total_hours] - 1).each { |hi|
1080
1159
  h = hi % 24
1081
1160
  print '<td colspan="',intervals,'" class="time">';
1082
1161
 
1083
- if @@options['output_date_in_time']
1162
+ if @@options[:output_date_in_time]
1084
1163
  nl; print '<table width="100%" border="0" cellpadding="0">'; nl
1085
1164
  print '<tr><td align="left" class="time">';
1086
1165
  end
1087
- if @@options['time_format_12']
1166
+ if @@options[:time_format_12]
1088
1167
  if h < 12
1089
1168
  out = "#{h} am"
1090
1169
  else
@@ -1095,16 +1174,16 @@ class Html
1095
1174
  out = sprintf "%02d", h
1096
1175
  end
1097
1176
  print out
1098
- if @@options['output_date_in_time']
1177
+ if @@options[:output_date_in_time]
1099
1178
  print '<td align="right" class="date_in_time">';
1100
- print cdate.strftime(@@options['date_format'])
1179
+ print cdate.strftime(@@options[:date_format])
1101
1180
  print '</td></tr>'; nl; print '</table></td>'; nl
1102
1181
  else
1103
1182
  print '</td>';
1104
1183
  end
1105
1184
  cs += 1
1106
1185
  cdate += 3600 # Add 1 hour
1107
- output_channel_space if @@options['channels_interval'] > 0 and cs % @@options['channels_interval'] == 0
1186
+ output_channel_space if @@options[:channels_interval] > 0 and cs % @@options[:channels_interval] == 0
1108
1187
  }
1109
1188
  print '</tr>'; nl
1110
1189
  end
@@ -1125,7 +1204,7 @@ class Html
1125
1204
  end
1126
1205
 
1127
1206
  def outputProgramme(s, index, slots)
1128
- return if s.spanSlots == 0 # Invalid programme - too short
1207
+ return if !s or s.spanSlots == 0 # Invalid programme - too short
1129
1208
  if slots > 0
1130
1209
  print '<td colspan="', slots, '" class="'
1131
1210
  else
@@ -1133,7 +1212,7 @@ class Html
1133
1212
  end
1134
1213
 
1135
1214
  # Favorites override category.
1136
- if @@options['use_favorites'] and @@options['favorites'].include?(s.title)
1215
+ if @@options[:use_favorites] and @@options[:favorites].include?(s.title)
1137
1216
  print 'favorite">'
1138
1217
  elsif s.category
1139
1218
  print s.category,'">'
@@ -1141,9 +1220,9 @@ class Html
1141
1220
  print 'programme">'
1142
1221
  end
1143
1222
  # Styles : 12=right; 1=center; 2=left; 3=float
1144
- if @@options['use_programme_popup']
1223
+ if @@options[:use_programme_popup]
1145
1224
  if s.popupIndex >= 0
1146
- if @@options['programme_popup_method'] == "DHTML"
1225
+ if @@options[:programme_popup_method] == "DHTML"
1147
1226
  print '<a onmouseover="stm(Text[', s.popupIndex
1148
1227
  print '],Style[1])" onmouseout="htm()">'; nl
1149
1228
  else
@@ -1182,11 +1261,11 @@ class Html
1182
1261
  outputProgramme(p, p.popupIndex, 1)
1183
1262
 
1184
1263
  print '<td align="center">'; nl
1185
- print p.times.fullStartTime.strftime("%a %b %d %I:%M %p")
1264
+ print p.startTime.strftime("%a %b %d %I:%M %p")
1186
1265
  print '</td>'; nl
1187
1266
 
1188
1267
  print '<td align="center">'; nl
1189
- print p.times.fullStopTime.strftime("%a %b %d %I:%M %p")
1268
+ print p.stopTime.strftime("%a %b %d %I:%M %p")
1190
1269
  print '</td>'; nl
1191
1270
 
1192
1271
  print '</tr>'; nl