import java.util.HashMap; import java.util.ArrayList; import java.util.List; import java.util.Calendar; import java.util.Locale; import java.util.Iterator; import java.util.TimeZone; import java.io.PrintWriter; import javax.xml.stream.XMLStreamReader; import javax.xml.stream.XMLStreamException; /** * This class stores the incoming data from XML. * It is needed because of the stream nature of the XML parser. **/ public class CelcatParser { private final HashMap weeks; private final Group group; private String day; private String week; private String start_time; private String end_time; private String uid_prefix; private Event event; private DataType data_type; private ListType current_list_type; private int event_count; public static void parse ( final XMLStreamReader stream_reader, final Group group, final String uid_prefix ) throws XMLStreamException { final CelcatParser parser; parser = new CelcatParser(group, uid_prefix); while (stream_reader.hasNext()) { switch (stream_reader.next()) { case XMLStreamReader.START_ELEMENT: parser.handle_new_element(stream_reader); break; case XMLStreamReader.CDATA: case XMLStreamReader.CHARACTERS: parser.handle_data(stream_reader.getText().trim()); break; case XMLStreamReader.END_DOCUMENT: parser.flush(); break; default: break; } } } private CelcatParser ( final Group group, final String uid_prefix ) { this.group = group; this.uid_prefix = uid_prefix; event = null; event_count = 0; weeks = new HashMap(); } /** * Called when entering an new element. **/ private void handle_new_element (final XMLStreamReader stream_reader) { final DataType new_data_type; new_data_type = DataType.get(stream_reader.getLocalName()); switch (new_data_type) { case EVENT: /** We're done handling the previous event. **/ flush(); break; case SPAN: /** This contains information about the weeks. **/ weeks.put ( Integer.parseInt ( stream_reader.getAttributeValue(null, "rawix") ), get_calendar_from_span ( stream_reader.getAttributeValue(null, "date") ) ); break; case ROOM: /** * Multiple rooms may be concerned, hence the List. * Any following ITEM elements should be considered as describing * a room for this event. **/ current_list_type = ListType.ROOMS; break; case GROUP: /** * Multiple groups may be concerned, hence the List. * Any following ITEM elements should be considered as describing * a group for this event. **/ current_list_type = ListType.GROUPS; break; case ITEM: if (data_type == DataType.MODULE) { /** * The XML file will consider that the event may concern * multiple modules. It makes sense, but I've never seen * it happen and it complicates the process. * Not updating the data_type here will make the next * data be considered as the Module's name instead of * something to add to a list. **/ /* [XXX][Improvement] Handle it. */ return; } break; default: break; } data_type = new_data_type; } private void flush () { week = null; start_time = null; end_time = null; if (event == null) { /** * This is the first event, we don't have anything to write. **/ event = new Event(); event.set_uid(uid_prefix + (event_count++)); return; } group.add_event(event); event = new Event(); event.set_uid(uid_prefix + (event_count++)); current_list_type = ListType.IGNORED; } /** * Called when finding actual data. **/ private void handle_data (final String data) { if (data.equals("")) { /** * Data was garbage. * It happens sometimes, I don't know why. **/ /* [XXX][Improvement] Prevent this from happening. */ return; } /** * Uses the previously set "data_type" to know where the data belongs. **/ switch (data_type) { case MODULE: event.set_name(data); break; case STARTTIME: if ((week == null) || (day == null)) { start_time = data; } else { event.set_start_time ( get_calendar ( week_to_calendar(week), day, data ) ); } break; case ENDTIME: if ((week == null) || (day == null)) { end_time = data; } else { event.set_end_time ( get_calendar ( week_to_calendar(week), day, data ) ); } break; case NOTES: if (event != null) { event.set_description("[Notes] " + data); } break; case RAWWEEKS: week = data; if ((start_time != null) && (day != null)) { event.set_start_time ( get_calendar ( week_to_calendar(week), day, start_time ) ); } if ((end_time != null) && (day != null)) { event.set_end_time ( get_calendar ( week_to_calendar(week), day, end_time ) ); } break; case DAY: day = data; if ((start_time != null) && (week != null)) { event.set_start_time ( get_calendar ( week_to_calendar(week), day, start_time ) ); } if ((end_time != null) && (week != null)) { event.set_end_time ( get_calendar ( week_to_calendar(week), day, end_time ) ); } break; case CATEGORY: event.set_category(data); break; case ITEM: switch (current_list_type) { case ROOMS: event.add_location(data); break; case GROUPS: event.add_attendee(data); break; default: break; } break; } } /** * Retrieves the relevant data from a week description. **/ private Calendar get_calendar_from_span (final String spandata) { final String[] data; final Calendar result; data = spandata.split("/"); result = java.util.Calendar.getInstance ( TimeZone.getTimeZone("Europe/Paris"), Locale.FRENCH ); if (data.length != 3) { Error.ERROR.from_thread ( group.get_abbreviation(), ("Invalid date format for week:" + spandata) ); return null; } result.set(java.util.Calendar.DAY_OF_MONTH, Integer.parseInt(data[0])); /* Java... don't ask... */ result.set(java.util.Calendar.MONTH, Integer.parseInt(data[1]) - 1); result.set(java.util.Calendar.YEAR, Integer.parseInt(data[2])); return result; } /** * Retrieves the week relevant to the event being processed. **/ private Calendar week_to_calendar ( final String week ) { final java.util.Calendar result; int pos; for (pos = 0; week.charAt(pos) != 'Y'; ++pos); result = weeks.get(pos + 1); if (result == null) { Error.ERROR.from_thread ( group.get_abbreviation(), "An event appeared before its week description did." ); } return result; } private java.util.Calendar get_calendar ( final Calendar week, final String day, final String hourmin ) { final String[] time; final java.util.Calendar result; time = hourmin.split(":"); result = (java.util.Calendar) week.clone(); result.set(java.util.Calendar.HOUR_OF_DAY, Integer.parseInt(time[0])); result.set(java.util.Calendar.MINUTE, Integer.parseInt(time[1])); result.add(java.util.Calendar.DAY_OF_YEAR, Integer.parseInt(day)); return result; } private static enum ListType { IGNORED, ROOMS, GROUPS; } /** * This enum is used to sort what is relevant in the XML file. * Sadly, for justified reasons, Java does not permit access to the static * members of an enum from its constructor, hence the "static"'s redundancy. **/ private static enum DataType { IRRELEVANT, EVENT, DAY, STARTTIME, ENDTIME, CATEGORY, RAWWEEKS, ITEM, MODULE, ROOM, SPAN, NOTES, GROUP; private static final HashMap FROM_STRING; static { FROM_STRING = new HashMap(); FROM_STRING.put("irrelevant", IRRELEVANT); FROM_STRING.put("event", EVENT); FROM_STRING.put("day", DAY); FROM_STRING.put("starttime", STARTTIME); FROM_STRING.put("endtime", ENDTIME); FROM_STRING.put("category", CATEGORY); FROM_STRING.put("item", ITEM); FROM_STRING.put("module", MODULE); FROM_STRING.put("room", ROOM); FROM_STRING.put("group", GROUP); FROM_STRING.put("rawweeks", RAWWEEKS); FROM_STRING.put("span", SPAN); FROM_STRING.put("notes", NOTES); } public static DataType get (final String e) { final DataType result; result = FROM_STRING.get(e); if (result == null) { return IRRELEVANT; } return result; } } }