completed parser for reparier-cafe jena
Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
@@ -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 <a href="https://datatracker.ietf.org/doc/html/rfc5545#section-3.8.1.6">iCalendar spec</a>
|
||||
* @return the formatted coords
|
||||
|
||||
@@ -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<Appointment> 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<List<Appointment>> parse(Container<InputStream> input) {
|
||||
private Container<List<Appointment>> parse(Container<InputStream> input) {
|
||||
if (input.optional().isEmpty()) return transform(input);
|
||||
InputStream stream = input.optional().get();
|
||||
var scanner = new Scanner(stream);
|
||||
var lines = new ArrayList<String>();
|
||||
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<List<Appointment>> parseLines(ArrayList<String> lines) {
|
||||
var events = new ArrayList<Appointment>();
|
||||
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<Appointment> parseEvent(ArrayList<String> lines) {
|
||||
String summary = null;
|
||||
String description = null;
|
||||
Container<LocalDateTime> 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<LocalDateTime> 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<String> tagsFrom(String description);
|
||||
|
||||
public abstract String url();
|
||||
}
|
||||
|
||||
@@ -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<LocalDateTime> 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);
|
||||
}
|
||||
}
|
||||
|
||||
+24
-2
@@ -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<String> tagsFrom(String description) {
|
||||
var tags = new HashSet<String>();
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user