4
* Copyright (c) 2001-2004 by Andy Balaam and the FreeGuide contributors
6
* Released under the GNU General Public License
7
* with ABSOLUTELY NO WARRANTY.
9
* See the file COPYING for more information.
11
package freeguide.gui.viewer;
14
import freeguide.lib.fgspecific.*;
15
import freeguide.lib.general.*;
20
import java.util.regex.*;
21
import javax.xml.parsers.*;
22
import org.xml.sax.helpers.*;
26
* XMLTVLoader Loads the required XMLTV files for a given date into a Vector of
27
* programmes and stores other relevant details e.g. channel details
30
*@created 28 June 2003
34
public class ViewerFrameXMLTVLoader extends DefaultHandler implements ChannelSetInterface {
37
* Loads the programme data from a file and stores it in a class structure
38
* ready for display on the screen.
40
*@param nowDate The date and time for which to load programmes
42
public void loadProgrammeData( Calendar nowDate ) {
44
thereAreEarlyProgs = false;
45
thereAreLateProgs = false;
47
// Find out the span of time for this day (using day_start_time)
48
// and alter the date if we're actually asking for a time that falls
49
// on the previous date
50
updateDaySpan( nowDate );
52
setupHasDataStuff( nowDate );
54
// Prepare the vectors that will contain the parsed data
55
programmes = new Vector();
56
channelIDs = new Vector();
57
channelNames = new Vector();
59
// Now date is the actual date we want (not the time) so we can work out
60
// what days we need to ask for from the grabber
62
// Get a reference to yesterday's date (day before "date")
63
Calendar yesterday = (Calendar) date.clone();
64
yesterday.add(Calendar.DAY_OF_YEAR, -1);
66
// Similarly get tomorrow
67
Calendar tomorrow = (Calendar) date.clone();
68
tomorrow.add(Calendar.DAY_OF_YEAR, 1);
73
//FreeGuide.log.info( "grabber_start_time=" + grabber_start_time +
74
// " day_start_time=" + day_start_time );
76
// See whether we need to grab yesterday or tomorrow
77
if ( grabber_start_time.before( day_start_time,
81
// grabber grabs midnight to midnight and we want to see 02:00 today
82
// to 02:00 tomorrow: need to grab today and tomorrow
84
date1str = ViewerFrame.fileDateFormat.format(date.getTime());
85
date2str = ViewerFrame.fileDateFormat.format(
88
//FreeGuide.log.info( "Getting tomorrow data" );
90
} else if( grabber_start_time.after( day_start_time,
94
// grabber grabs 06:00 today to 06:00 tomorrow and we want to see
95
// midnight to midnight: need to grab today and yesterday
97
date1str = ViewerFrame.fileDateFormat.format(
99
date2str = ViewerFrame.fileDateFormat.format(
102
//FreeGuide.log.info( "Getting yesterday data" );
106
// The days are perfectly matched so only parse one day.
109
date2str = ViewerFrame.fileDateFormat.format(
117
if( date1str != null ) {
118
day1Filename = working_directory + fs + "tv-" +
124
day2Filename = working_directory + fs + "tv-" +
127
String unprocFilename = working_directory + fs + "tv-unprocessed.xmltv";
133
if( day1Filename != null ) {
134
day1File = new File(day1Filename);
139
day2File = new File(day2Filename);
140
unprocFile = new File(unprocFilename);
142
// Parse any files that exist
145
//ParserExceptions etc
147
//DefaultHandler handler = new FreeGuideSAXHandler( this );
149
SAXParserFactory factory = SAXParserFactory.newInstance();
151
SAXParser saxParser = factory.newSAXParser();
153
boolean day1FileExists;
154
boolean day2FileExists;
156
if( day1File != null ) {
157
day1FileExists = day1File.exists();
159
day1FileExists = false;
162
day2FileExists = day2File.exists();
164
// If either day file exists (or both), parse it/them. Otherwise
165
// get the unproc. listings if they exist.
167
if (day1FileExists) {
169
//FreeGuide.log.info( "Parsing " + day1Filename );
171
saxParser.parse(day1Filename, this);
175
if (day2FileExists) {
177
//FreeGuide.log.info( "Parsing " + day2Filename );
179
saxParser.parse(day2Filename, this);
183
if ((!day1FileExists) && (!day2FileExists) && unprocFile.exists()) {
185
// The grabber must not be able to split into days,
186
// so we'll deal with the unprocessed data.
187
saxParser.parse(unprocFilename, this);
190
} catch (ParserConfigurationException e) {
192
// FIXME - error dialog!
193
} catch (SAXException e) {
195
// FIXME - error dialog!
196
} catch (java.io.IOException e) {
198
// FIXME - error dialog!
206
* Returns true if there was "enough" data for today and false if some was
209
*@return true if there was enough data for today
211
public boolean hasData() {
213
return (thereAreEarlyProgs && thereAreLateProgs);
217
// ----------------------------------------------------------------------
220
* Alter date to reflect if we are in another day's data (i.e. before the
221
* day start time) and set earliest and latest to the right values
222
* according to the day start time
224
*@param nowDate Description of the Parameter
226
public void updateDaySpan( Calendar nowDate ) {
228
day_start_time = FreeGuide.prefs.misc.getTime(
229
"day_start_time", new Time(6, 0));
231
grabber_start_time = FreeGuide.prefs.misc.getTime(
232
"grabber_start_time", new Time(0, 0));
234
working_directory = FreeGuide.prefs.performSubstitutions(
235
FreeGuide.prefs.misc.get("working_directory"));
237
//earliest = GregorianCalendar.getInstance();
238
//latest = GregorianCalendar.getInstance();
240
date = (Calendar) nowDate.clone();
242
// If we're before the day start time we actually want the previous day.
243
//FreeGuideTime nowTime = new FreeGuideTime( date );
245
//if( nowTime.before( day_start_time, new FreeGuideTime( 0, 0 ) ) ) {
247
// If we need to adjust because our day start is before our grabber's
248
//if( day_start_time.before(
249
// grabber_start_time, new FreeGuideTime( 0, 0 ) ) ) {
251
// Set the time to the previous day, 1 hour after the day start time
252
// date.add( Calendar.DATE, -1 );
253
// date.set( Calendar.HOUR, day_start_time.getHours() + 1 );
257
// Set earliest to the start time on the date
258
earliest = (Calendar) date.clone();
260
day_start_time.adjustCalendar(earliest);
262
// Set latest to the start time on the day after the date
263
latest = (Calendar) date.clone();
265
latest.add(Calendar.DAY_OF_YEAR, 1);
267
day_start_time.adjustCalendar(latest);
273
* Description of the Method
275
*@param nowDate Description of the Parameter
277
private void setupHasDataStuff(Calendar nowDate) {
279
// There must be a programme crossing over both of these times in order
280
// for the day to be "covered" i.e. we don't need to download more.
281
hasDataEarliest = (Calendar) earliest.clone();
282
hasDataLatest = (Calendar) latest.clone();
284
// If it's today then hasDataEarliest is now-ish
285
if( dayIsToday(nowDate) ) {
287
hasDataEarliest.setTimeInMillis( nowDate.getTimeInMillis() );
291
// Now add an hour's grace to start time
292
hasDataEarliest.add(Calendar.HOUR, 1);
294
// and remove an hour from the end time
295
hasDataLatest.add(Calendar.HOUR, -1);
301
* Description of the Method
303
*@param nowDate Description of the Parameter
304
*@return Description of the Return Value
306
private boolean dayIsToday( Calendar iviewedDateTime ) {
308
// First copy the datetime we were given because we may have to alter it
309
Calendar viewedDateTime = GregorianCalendar.getInstance();
310
viewedDateTime.setTimeInMillis( iviewedDateTime.getTimeInMillis() );
312
// Now find the time of the datetime we were given
313
Time viewedTime = new Time( viewedDateTime );
314
if( viewedTime.before( day_start_time, new Time(0, 0) ) ) {
316
viewedDateTime.add( Calendar.DATE, -1 );
320
// If we're before the day start time then go to the previous day
322
Calendar nowDateTime = GregorianCalendar.getInstance();
323
Time nowTime = new Time( nowDateTime );
325
// If we're before the day start time then go to the previous day
326
if( nowTime.before( day_start_time, new Time(0, 0) ) ) {
328
nowDateTime.add( Calendar.DATE, -1 );
332
// Now check whether the dates are equal.
333
return ( viewedDateTime.get(Calendar.YEAR)
334
== nowDateTime.get(Calendar.YEAR)
335
&& viewedDateTime.get(Calendar.DAY_OF_YEAR)
336
== nowDateTime.get(Calendar.DAY_OF_YEAR) );
341
// ----------------------------------------------------------------------
344
* Description of the Method
346
public void startDocument() {
347
saxLoc = new String();
348
channelIcons = new Hashtable();
355
* Description of the Method
357
public void endDocument() {
365
* Description of the Method
367
*@param namespaceURI Description of the Parameter
368
*@param sName Description of the Parameter
369
*@param name Description of the Parameter
370
*@param attrs Description of the Parameter
372
public void startElement(String namespaceURI, String sName, String name,
375
saxLoc += ":" + name;
379
//FreeGuide.log.info( saxLoc );
381
if (saxLoc.equals(":tv:programme")) {
383
currentProgramme = new Programme();
385
// Prepare GregorianCalendars for start and end
386
Calendar start = GregorianCalendar.getInstance();
387
Calendar end = GregorianCalendar.getInstance();
389
// Assume it has a channel
390
String channelID = attrs.getValue("channel");
391
currentProgramme.setChannelID(channelID);
392
currentProgramme.addToChannelName(getChannelName(channelID));
396
// Assume is has a start time
397
start = parseDate(attrs.getValue("start"));
399
// Don't assume it has an end time
400
if (attrs.getIndex("stop") == -1
401
|| attrs.getValue("stop").equals("+0100")) {
402
// Also hack around bug in de grabber
404
// Give it a fake end time, half an hour after the start
405
end.setTimeInMillis(start.getTimeInMillis());
406
end.add(Calendar.MINUTE, 30);
410
end = parseDate(attrs.getValue("stop"));
411
//Watch out for missing end dates!
412
if (end.before(start)) {
413
// Give it a fake end time, half an hour after the start
414
end.setTimeInMillis(start.getTimeInMillis());
415
end.add(Calendar.MINUTE, 30);
419
} catch (java.text.ParseException e) {
421
currentProgramme = null;
426
if( start.before(hasDataEarliest) ) {
428
thereAreEarlyProgs = true;
432
if( end.after(hasDataLatest) ) {
434
thereAreLateProgs = true;
438
currentProgramme.setStart(start);
439
currentProgramme.setEnd(end);
441
} else if (saxLoc.equals(":tv:channel")) {
443
String id = attrs.getValue("id");
447
} else if (saxLoc.equals(":tv:channel:icon")) {
449
String URL = attrs.getValue("src");
451
channelIcons.put(tmpChannelID,URL);
453
} else if (saxLoc.equals(":tv:programme:previously-shown")) {
455
if (currentProgramme != null) {
456
currentProgramme.setPreviouslyShown(true);
459
} else if (saxLoc.equals(":tv:programme:rating")) {
461
if (currentProgramme != null) {
462
String ratingsystem = attrs.getValue("system");
463
if (ratingsystem != null && ratingsystem.equalsIgnoreCase("MPAA")) {
464
currentProgramme.setIsMovie(true);
468
} else if (saxLoc.equals(":tv:programme:subtitles")) {
470
if (currentProgramme != null) {
472
currentProgramme.setSubtitled( true );
476
} else if (saxLoc.equals(":tv:programme:icon")) {
478
if (currentProgramme != null && attrs.getValue("src") != null)
479
currentProgramme.setIconURL(attrs.getValue("src"));
481
} else if ( saxLoc.equals(":tv:programme:desc")
482
|| saxLoc.equals(":tv:programme:title")
483
|| saxLoc.equals(":tv:programme:sub-title")
484
|| saxLoc.equals(":tv:programme:category")
485
|| saxLoc.startsWith(":tv:programme:rating")
486
|| saxLoc.equals(":tv:programme:star-rating")
487
|| saxLoc.equals(":tv:programme:star-rating:value")
488
|| saxLoc.equals(":tv:programme:url") )
491
// Do nothing - dealt with in endElement
493
} else if( saxLoc.matches( ":tv:programme:[^:]*" ) ) {
495
//FreeGuide.log.info( saxLoc );
497
// Remember any unrecognised data
498
if (currentProgramme != null && attrs.getLength() > 0) {
500
currentProgramme.startElement( name, attrs );
513
* Description of the Method
515
*@param strDate Description of the Parameter
516
*@return Description of the Return Value
517
*@exception java.text.ParseException Description of the Exception
519
private Calendar parseDate(String strDate)
520
throws java.text.ParseException {
522
Calendar ans = GregorianCalendar.getInstance();
524
// First check for a time without any timezone or seconds
525
if( strDate.matches( "\\A\\d{12}\\z" ) ) {
528
new SimpleDateFormat( "yyyyMMddHHmm" ).parse( strDate ) );
530
// Now try without timezone or seconds
531
} else if( strDate.matches( "\\A\\d{14}\\z" ) ) {
534
new SimpleDateFormat( "yyyyMMddHHmmss" ).parse( strDate ) );
541
new SimpleDateFormat("yyyyMMddHHmmss z").parse( strDate ) );
543
} catch (java.text.ParseException g) {
546
new SimpleDateFormat("yyyyMMddHHmmss Z").parse( strDate ) );
556
* Description of the Method
558
*@param namespaceURI Description of the Parameter
559
*@param sName Description of the Parameter
560
*@param name Description of the Parameter
562
public void endElement(String namespaceURI, String sName, String name) {
564
//FreeGuide.log.info(name);
566
if (saxLoc.equals(":tv:programme")) {
568
if (currentProgramme.getEnd().after(earliest) &&
569
currentProgramme.getStart().before(latest))
572
if( programmeNotAlreadyEntered( currentProgramme ) ) {
573
programmes.add(currentProgramme);
577
currentProgramme = null;
579
} else if (saxLoc.equals(":tv:programme:title")) {
581
if (currentProgramme != null) {
582
currentProgramme.setTitle( data );
585
} else if (saxLoc.equals(":tv:programme:sub-title")) {
587
if (currentProgramme != null) {
588
currentProgramme.setSubTitle( data );
590
} else if (saxLoc.equals(":tv:programme:desc")) {
592
if (currentProgramme != null) {
593
currentProgramme.addDesc( data );
595
} else if (saxLoc.equals(":tv:programme:category")) {
597
if (currentProgramme != null) {
598
currentProgramme.addCategory( data );
599
if( data.equalsIgnoreCase("Film")
600
|| data.equalsIgnoreCase("CINE") )
602
currentProgramme.setIsMovie(true);
605
} else if (saxLoc.equals(":tv:programme:star-rating:value")) {
607
if (currentProgramme != null) {
608
currentProgramme.setStarRating( data );
611
//} else if (saxLoc.equals(":tv:programme:episode-num")) {
612
// FIXME - fill in here
614
} else if (saxLoc.equals(":tv:programme:url")) {
616
if (currentProgramme != null) {
620
currentProgramme.setLink(new URL(data));
622
} catch(java.net.MalformedURLException e) {
627
} else if( saxLoc.equals(":tv:programme:subtitles")
628
|| saxLoc.equals(":tv:programme:previously-shown")
629
|| saxLoc.startsWith(":tv:programme:rating")
630
|| saxLoc.equals(":tv:programme:star-rating")
631
|| saxLoc.equals(":tv:programme:icon") )
634
// Do nothing - dealt with in startElement or elsewhere
636
} else if( saxLoc.equals(":tv:channel:display-name") ) {
638
// Remember the name of the channel we're looking at
639
addChannelName(tmpChannelID, data);
641
} else if( saxLoc.matches( ":tv:programme:[^:]*" ) ) {
642
// Ending an unknown main tag
644
if( currentProgramme != null ) {
645
currentProgramme.endElement( name, "", data );
650
Pattern patt = Pattern.compile( ":tv:programme:([^:]+):(.+)" );
651
Matcher mat = patt.matcher( saxLoc );
653
// If we're looking at an unknown tag of a programme
654
if (mat.matches() && currentProgramme != null && data != null && !data.equals("")) {
655
// Ending an unknown subtag
657
String mainTag = mat.group( 1 );
659
currentProgramme.endElement( mainTag, mat.group(2), data );
665
if (saxLoc.endsWith(name)) {
667
saxLoc = saxLoc.substring(0, saxLoc.length() - (name.length() + 1));
674
//FreeGuide.log.info("endElement END");
678
private boolean programmeNotAlreadyEntered( Programme programme ) {
680
Iterator iter = programmes.iterator();
682
while( iter.hasNext() ) {
684
Programme prog = (Programme)(iter.next());
686
if( prog.equals( programme ) ) {
697
* Description of the Method
699
*@param ch Description of the Parameter
700
*@param start Description of the Parameter
701
*@param length Description of the Parameter
703
public void characters(char[] ch, int start, int length) {
704
data += new String(ch, start, length);
710
// -----------------------------------------------------------------------
713
* Gets the channelIDs attribute of the XMLTVLoader object
715
*@return The channelIDs value
717
public Vector getChannelIDs() {
723
* Gets the channelNames attribute of the XMLTVLoader object
725
*@return The channelNames value
727
public Vector getChannelNames() {
733
* Adds a feature to the ChannelName attribute of the XMLTVLoader object
735
*@param channelID The feature to be added to the ChannelName attribute
736
*@param channelName The feature to be added to the ChannelName attribute
738
private void addChannelName(String channelID, String channelName) {
740
int i = channelIDs.indexOf(channelID);
744
channelIDs.add(channelID);
745
channelNames.add(channelName);
752
* Gets the channelName attribute of the XMLTVLoader object
754
*@param i Description of the Parameter
755
*@return The channelName value
757
public String getChannelName(int i) {
759
return (String) channelNames.get(i);
764
* Gets the noChannels attribute of the XMLTVLoader object
766
*@return The noChannels value
768
public int getNoChannels() {
770
return channelIDs.size();
775
* Gets the channelNo attribute of the XMLTVLoader object
777
*@param channelID Description of the Parameter
778
*@return The channelNo value
780
public int getChannelNo(String channelID) {
782
return channelIDs.indexOf(channelID);
787
* Sets the channelSetName attribute of the XMLTVLoader object
789
*@param name The new channelSetName value
791
public void setChannelSetName(String name) {
797
* Gets the channelSetName attribute of the XMLTVLoader object
799
*@return The channelSetName value
801
public String getChannelSetName() {
802
return ViewerFrame.CHANNEL_SET_ALL_CHANNELS;
807
* Returns the name of the channel whose ID is supplied.
809
*@param channelID Description of the Parameter
810
*@return The channelName value
812
public String getChannelName(String channelID) {
814
int ch = channelIDs.indexOf(channelID);
818
addChannelName(channelID, channelID);
823
String chName = (String) channelNames.get(ch);
825
if (chName == null) {
838
* to get the Icon URL of the channel
839
* @param channelID the Id of the channel to get URL for
840
* @return the URL as a string;
842
public String getChannelIcon(String channelID){
843
if (channelID == null || channelIcons == null) return null;
844
return (String)channelIcons.get(channelID);
849
* Description of the Method
851
private void parseError() {
852
FreeGuide.log.severe("ViewerFrame - Error parsing XML.");
857
// ---------------------------------------------------------------
859
private String saxLoc = "";
860
// Holds our current pos in the XML hierarchy
861
private String tmpChannelID;
864
private Programme currentProgramme;
865
// The programme we're loading in now
867
String fs = System.getProperty("file.separator");
869
private Time day_start_time;
871
private Time grabber_start_time;
873
private String working_directory;
876
* Description of the Field
878
public Vector programmes;
879
// Vector of loaded FreeGuideProgrammes
880
private Vector channelIDs;
881
// The IDs of the channels
882
private Vector channelNames;
883
// The names of the channels
884
private Hashtable channelIcons;
887
* Description of the Field
889
public Calendar date;
890
// The actual date we want (YMD, ignore time)
893
* Description of the Field
895
public Calendar earliest;
896
// The time of the start of this day
898
* Description of the Field
900
public Calendar latest;
901
// The time of the end of this day
903
private boolean thereAreEarlyProgs;
904
// 2 flags which indicate whether or
905
private boolean thereAreLateProgs;
906
// not there are programmes at the
907
// beg. and end of today.
909
private Calendar hasDataEarliest;
910
// The start of the day in terms of
911
// Whether there is enough data today
912
private Calendar hasDataLatest;