diff --git a/de.srsoftware.cal.api/src/main/java/de/srsoftware/cal/api/Coords.java b/de.srsoftware.cal.api/src/main/java/de/srsoftware/cal/api/Coords.java
index 7cf24b7..7b0883a 100644
--- a/de.srsoftware.cal.api/src/main/java/de/srsoftware/cal/api/Coords.java
+++ b/de.srsoftware.cal.api/src/main/java/de/srsoftware/cal/api/Coords.java
@@ -10,6 +10,11 @@ import org.json.JSONObject;
* @param longitude the longitude
*/
public record Coords(double latitude,double longitude) {
+ public static Coords from(String geo) {
+ var parts = geo.split(";");
+ return new Coords(Double.parseDouble(parts[0]), Double.parseDouble(parts[1]));
+ }
+
/**
* get a string representing this coords in the ICAL format, see iCalendar spec
* @return the formatted coords
diff --git a/de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/IcalImporter.java b/de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/IcalImporter.java
index b817e74..669db1a 100644
--- a/de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/IcalImporter.java
+++ b/de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/IcalImporter.java
@@ -3,19 +3,27 @@ package de.srsoftware.cal;
import static de.srsoftware.tools.container.Container.transform;
import static de.srsoftware.tools.container.Error.error;
+import static java.lang.System.Logger.Level.WARNING;
+import static java.lang.System.getLogger;
import de.srsoftware.cal.api.Appointment;
+import de.srsoftware.cal.api.Coords;
import de.srsoftware.cal.api.Importer;
import de.srsoftware.tools.container.Container;
import de.srsoftware.tools.container.Payload;
import java.io.IOException;
import java.io.InputStream;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.Scanner;
import java.util.stream.Stream;
public abstract class IcalImporter implements Importer {
+ private static final System.Logger LOG = getLogger(IcalImporter.class.getSimpleName());
+
@Override
public Stream fetch() throws IOException {
var programPage = Payload.of(url()).map(Util::url);
@@ -23,27 +31,90 @@ public abstract class IcalImporter implements Importer {
return programPage
.map(Util::open)
.map(Util::preload)
- .map(IcalImporter::parse)
+ .map(this::parse)
.stream();
}
- private static Container> parse(Container input) {
+ private Container> parse(Container input) {
if (input.optional().isEmpty()) return transform(input);
InputStream stream = input.optional().get();
var scanner = new Scanner(stream);
+ var lines = new ArrayList();
while (scanner.hasNextLine()){
var line = scanner.nextLine();
- System.out.println(line);
+ if (line.startsWith(" ")) line = lines.removeLast() + line.substring(1);
+ lines.add(line);
}
scanner.close();
try {
stream.close();
} catch (IOException e) {
- throw new RuntimeException(e);
+ return error(e.getMessage());
}
- return error("Not implemented");
+ return parseLines(lines);
}
+ private Container> parseLines(ArrayList lines) {
+ var events = new ArrayList();
+ while (!lines.isEmpty()){
+ var line = lines.removeFirst();
+ if (line.equals("BEGIN:VEVENT")){
+ lines.addFirst(line);
+ parseEvent(lines).optional().ifPresent(events::add);
+ }
+ }
+ return Payload.of(events);
+ }
+
+ private Container parseEvent(ArrayList lines) {
+ String summary = null;
+ String description = null;
+ Container dtstart = error("No start date found"), dtend = error("No end date found");
+ String location = null;
+ String geo = null;
+ while (!lines.isEmpty()){
+ var line = lines.removeFirst();
+ if (line.equals("BEGIN:VEVENT")) continue;
+ if (line.startsWith("DTSTAMP:")) continue;
+ if (line.startsWith("UID:")) continue;
+ if (line.startsWith("X-APPLE-")) continue;
+ if (line.equals("END:VEVENT")) break;
+
+ if (line.startsWith("SUMMARY:")) {
+ summary = line.substring(8);
+ continue;
+ }
+ if (line.startsWith("DESCRIPTION:")) {
+ description = line.substring(12);
+ continue;
+ }
+ if (line.startsWith("DTSTART:")) {
+ dtstart = Util.parseIcalDateTime(line.substring(8));
+ continue;
+ }
+ if (line.startsWith("DTEND:")) {
+ dtend = Util.parseIcalDateTime(line.substring(6));
+ continue;
+ }
+ if (line.startsWith("LOCATION:")) {
+ location = line.substring(9);
+ continue;
+ }
+ if (line.startsWith("GEO:")) {
+ geo = line.substring(4);
+ continue;
+ }
+ LOG.log(WARNING,"Unknown entry in ICAL file: {0}",line);
+ }
+ description = description == null ? null : description.replace("\\n","\n").replace("\\, ",", ");
+ if (!(dtstart instanceof Payload start)) return transform(dtstart);
+ var appointment = new BaseAppointment(0,summary,description,start.get(),dtend.optional().orElse(null),location);
+ if (geo != null) appointment.coords(Coords.from(geo));
+ appointment.tags(tagsFrom(description));
+ return Payload.of(appointment);
+ }
+
+ public abstract Collection tagsFrom(String description);
public abstract String url();
}
diff --git a/de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/Util.java b/de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/Util.java
index 133e241..dd0bdee 100644
--- a/de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/Util.java
+++ b/de.srsoftware.cal.base/src/main/java/de/srsoftware/cal/Util.java
@@ -47,6 +47,7 @@ public class Util {
public static final Pattern GERMAN_DATE_WITHOUT_YEAR = Pattern.compile("(\\d\\d?)\\.(\\d\\d?)");
public static final Pattern GERMAN_DATE_PATTERN_LONG = Pattern.compile("(\\d\\d?)\\.?\\s*(\\w+)\\s+(\\d{4})\\D");
public static final Pattern GERMAN_TIME_PATTERN = Pattern.compile("(\\d\\d?):(\\d\\d?)(:(\\d\\d?))?\\D");
+ public static final Pattern ICAL_DATE = Pattern.compile("(\\d{4})(\\d{2})(\\d{2})T(\\d{2})(\\d{2})(\\d{2})");
private static final Pattern BG_IMAGE_URL = Pattern.compile("background(-image)?:\\surl\\('?([^)]+)'?\\)");
private static final System.Logger LOG = System.getLogger(Util.class.getSimpleName());
@@ -268,4 +269,18 @@ public class Util {
}
return error("Failed to parse date from %s",string);
}
+
+ public static Container parseIcalDateTime(String s) {
+ var matcher = ICAL_DATE.matcher(s);
+ if (matcher.find()){
+ int year = Integer.parseInt(matcher.group(1));
+ int mon = Integer.parseInt(matcher.group(2));
+ int day = Integer.parseInt(matcher.group(3));
+ int hour = Integer.parseInt(matcher.group(4));
+ int min = Integer.parseInt(matcher.group(5));
+ int sec = Integer.parseInt(matcher.group(6));
+ return Payload.of(LocalDateTime.of(year,mon,day,hour,min,sec));
+ }
+ return error("{0} does not match format {1}",s, ICAL_DATE);
+ }
}
diff --git a/de.srsoftware.cal.importer/src/main/java/de/srsoftware/cal/importer/jena/ReparierCafe.java b/de.srsoftware.cal.importer/src/main/java/de/srsoftware/cal/importer/jena/ReparierCafe.java
index a411fa2..b90422b 100644
--- a/de.srsoftware.cal.importer/src/main/java/de/srsoftware/cal/importer/jena/ReparierCafe.java
+++ b/de.srsoftware.cal.importer/src/main/java/de/srsoftware/cal/importer/jena/ReparierCafe.java
@@ -3,14 +3,36 @@ package de.srsoftware.cal.importer.jena;
import de.srsoftware.cal.IcalImporter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+
public class ReparierCafe extends IcalImporter {
+
@Override
- public String url() {
- return "https://www.reparatur-initiativen.de/events/ical/2553.ics";
+ public Collection tagsFrom(String description) {
+ var tags = new HashSet();
+ tags.add("Jena");
+ tags.add("Repariercafe");
+ tags.add("Reparier-Café");
+ var lines = description.split("\n");
+ for (var line : lines){
+ if (line.startsWith("Kategorien:")) {
+ var kategorien = line.substring(11).trim().split(" ");
+ for (var kat : kategorien) tags.add(kat);
+ }
+ }
+ return tags;
}
@Override
public String description() {
return "Reparier-Café Jena";
}
+
+ @Override
+ public String url() {
+ return "https://www.reparatur-initiativen.de/events/ical/2553.ics";
+ }
}