csv2frab/main.go

254 lines
6.4 KiB
Go

// TODO: docstring
package main
import (
"encoding/csv"
"fmt"
"log"
"os"
"strconv"
"text/template"
"time"
)
var timeCSVFormat = "2.1.06 15:04" // input format
var timeXMLDateFormat = "2006-01-02" // frab xml format for dates
var timeXMLTimeFormat = "15:04" // frab xml format for times
const csvColTitle = 0
const csvColRoom = 1
const csvColStart = 2
const csvColEnd = 3
const csvColSpeaker = 4
const csvColLang = 5
type frabSchedule struct {
Conf conference
Days []day
}
type conference struct {
Title string
Start string // date (yyyy-mm-dd)
End string // date (yyyy-mm-dd)
Days int
}
type day struct {
Index int
Date string // date (yyyy-mm-dd)
Start string // timedate (RFC3339)
End string // timedate (RFC3339)
Rooms []room
}
type room struct {
Name string
Events []event
}
type event struct {
ID int
GUID string // autogen UUID
Room string
Title string
Date string // timedate (RFC3339)
Start string // time (hh:mm)
Duration string // time (hh:mm)
Language string
Persons []person
}
type person struct {
ID int
Name string
}
func fmtDuration(d time.Duration) string {
d = d.Round(time.Minute)
h := d / time.Hour
d -= h * time.Hour
m := d / time.Minute
return fmt.Sprintf("%02d:%02d", h, m)
}
func main() {
fmt.Println("Conference Schedule: CSV to Frab (Infobeamer) Converter")
/*
* Data Input
*/
// read CSV contents
csvFile, err := os.Open("schedule.csv")
if err != nil {
log.Fatal("Error opening CSV file:\n", err)
}
csvReader := csv.NewReader(csvFile)
csvReader.Comma = ';' // MS excel default export format
records, err := csvReader.ReadAll()
if err != nil {
log.Fatal("Error reading CSV file:\n", err)
}
// omit title row
csvRecords := records[1:]
/*
* Data Conversion
*/
roomsList := make(map[time.Time]map[string]int) // map of day's start time to map of room names
var conferenceStart time.Time
var conferenceEnd time.Time
loc, _ := time.LoadLocation("Europe/Berlin")
for idx, row := range csvRecords {
// parse event times
eventStart, err := time.ParseInLocation(timeCSVFormat, row[csvColStart], loc)
if err != nil {
log.Fatal("Error parsing start date in CSV file, line ", idx+1, ":\n", err)
}
eventEnd, err := time.ParseInLocation(timeCSVFormat, row[csvColEnd], loc)
if err != nil {
log.Fatal("Error parsing end date in CSV file, line ", idx+1, ":\n", err)
}
// create unique list of the rooms for each day
dayStart := eventStart.Truncate(24 * time.Hour)
if roomsList[dayStart] == nil {
roomsList[dayStart] = make(map[string]int)
}
roomsList[dayStart][row[csvColRoom]] = 1
// get conference start and end date
if idx == 0 {
conferenceStart = eventStart
conferenceEnd = eventEnd
}
if eventStart.Before(conferenceStart) {
conferenceStart = eventStart
}
if eventEnd.After(conferenceEnd) {
conferenceEnd = eventEnd
}
}
conferenceStart = conferenceStart.Truncate(24 * time.Hour)
conferenceEnd = conferenceEnd.Truncate(24 * time.Hour).Add(24 * time.Hour)
// convert csv entries into event and sort in days
events := make(map[int][]event) // map of day index to day's events
eventID := 100
speakerID := 1
confDays := make(map[int]day) // map of day index to day structure
for idx, row := range csvRecords {
eventStart, err := time.ParseInLocation(timeCSVFormat, row[csvColStart], loc)
if err != nil {
log.Fatal("Error parsing start date in CSV file, line ", idx+1, ":\n", err)
}
eventEnd, err := time.ParseInLocation(timeCSVFormat, row[csvColEnd], loc)
if err != nil {
log.Fatal("Error parsing end date in CSV file, line ", idx+1, ":\n", err)
}
// create event struct and sort into day map
var eventSpeaker []person
if row[csvColSpeaker] != "" {
eventSpeaker = append(eventSpeaker, person{speakerID, row[csvColSpeaker]})
}
dayIdx := int(eventStart.Sub(conferenceStart).Hours()/24) + 1
eventGUID := strconv.Itoa(dayIdx) + "-" + strconv.Itoa(eventID)
eventDateStr := eventStart.Format(time.RFC3339)
eventStartStr := eventStart.Format(timeXMLTimeFormat)
eventDurationStr := fmtDuration(eventEnd.Sub(eventStart))
events[dayIdx] = append(events[dayIdx], event{eventID, eventGUID, row[csvColRoom], row[csvColTitle], eventDateStr, eventStartStr, eventDurationStr, row[csvColLang], eventSpeaker})
// create days
if _, ok := confDays[dayIdx]; !ok {
dayDateStr := eventStart.Format(timeXMLDateFormat)
dayStart := eventStart.Truncate(24 * time.Hour)
dayEnd := dayStart.Add(24 * time.Hour)
dayStartStr := dayStart.Format(time.RFC3339)
dayEndStr := dayEnd.Format(time.RFC3339)
var dayRooms []room
for roomName := range roomsList[dayStart] {
dayRooms = append(dayRooms, room{roomName, nil})
}
confDays[dayIdx] = day{dayIdx, dayDateStr, dayStartStr, dayEndStr, dayRooms}
}
eventID++
}
// sort events to rooms and day structures
for dayIdx, eventDay := range events {
for _, event := range eventDay {
for roomIdx, roomName := range confDays[dayIdx].Rooms {
if roomName.Name == event.Room {
confDays[dayIdx].Rooms[roomIdx].Events = append(confDays[dayIdx].Rooms[roomIdx].Events, event)
}
}
}
}
// new conference
confDurationDays := int(conferenceEnd.Sub(conferenceStart).Hours() / 24)
conf := conference{"Wälderhaus Veranstaltung", conferenceStart.Format(time.RFC3339), conferenceEnd.Format(time.RFC3339), confDurationDays}
var days []day
for _, d := range confDays {
days = append(days, d)
}
sched := frabSchedule{conf, days}
/*
* Data Export
*/
// open output file
xmlOutput, err := os.Create("schedule.xml")
defer xmlOutput.Close()
if err != nil {
log.Fatal("Error opening XML file for output:\n", err)
}
t := template.New("Frab XML Template")
t, err = t.ParseFiles("tmpl/schedule.xml")
if err != nil {
log.Fatal("Error parsing XML template:\n", err)
}
err = t.ExecuteTemplate(xmlOutput, "schedule.xml", sched)
if err != nil {
log.Println("Error executing XML template:\n", err)
}
/*
* Print Summary
*/
fmt.Println("- Conference Start:", conferenceStart)
fmt.Println("- Conference End :", conferenceEnd)
fmt.Println("- Days :", confDurationDays)
fmt.Println("- Events :", eventID-100)
// fmt.Println("Dump days:")
// for _, day := range days {
// fmt.Println("Day", day.Index)
// for _, room := range day.Rooms {
// fmt.Println(" Room", room.Name)
// for _, event := range room.Events {
// fmt.Println(" Event", event)
// }
// }
// }
fmt.Println("done")
}