|
|
|
@ -4,7 +4,6 @@ package de.srsoftware.cal.importer;
@@ -4,7 +4,6 @@ package de.srsoftware.cal.importer;
|
|
|
|
|
import static de.srsoftware.tools.Optionals.nullable; |
|
|
|
|
import static de.srsoftware.tools.TagFilter.*; |
|
|
|
|
import static java.util.Optional.empty; |
|
|
|
|
import static java.util.function.Predicate.not; |
|
|
|
|
|
|
|
|
|
import de.srsoftware.cal.api.*; |
|
|
|
|
import de.srsoftware.tools.*; |
|
|
|
@ -38,6 +37,52 @@ public class JenaRosenkeller implements Importer {
@@ -38,6 +37,52 @@ public class JenaRosenkeller implements Importer {
|
|
|
|
|
return "Events von der Seite rosenkeller.org importieren"; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static List<Attachment> extractAttachments(Tag appointmentTag) { |
|
|
|
|
return appointmentTag //
|
|
|
|
|
.find(ofType("img")) |
|
|
|
|
.stream() |
|
|
|
|
.map(tag -> tag.get("src")) |
|
|
|
|
.filter(Objects::nonNull) |
|
|
|
|
.map(Payload::of) |
|
|
|
|
.map(JenaRosenkeller::url) |
|
|
|
|
.map(JenaRosenkeller::toAttachment) |
|
|
|
|
.map(Result::optional) |
|
|
|
|
.flatMap(Optional::stream) |
|
|
|
|
.toList(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static Optional<String> extractDescription(Tag appointmentTag) { |
|
|
|
|
return appointmentTag.find(attributeHas("class", "tribe-events-single-event-description")).stream().flatMap(tag -> tag.inner(2).stream()).findAny(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static List<Link> extractLinks(Tag appointmentTag) { |
|
|
|
|
var links = new ArrayList<Link>(); |
|
|
|
|
appointmentTag //
|
|
|
|
|
.find(attributeStartsWith("id", "post-")) |
|
|
|
|
.stream() |
|
|
|
|
.flatMap(tag -> tag.find(ofType("a")).stream()) |
|
|
|
|
.forEach(anchor -> { |
|
|
|
|
var href = anchor.get("href"); |
|
|
|
|
if (href == null) return; |
|
|
|
|
if (!href.contains("://")) href = BASE_URL + "href"; |
|
|
|
|
var text = anchor.inner(0).orElse(href); |
|
|
|
|
Payload.of(href).map(JenaRosenkeller::url).optional().map(url -> new Link(url, text)).ifPresent(links::add); |
|
|
|
|
}); |
|
|
|
|
return links; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static Optional<LocalDateTime> extractStart(Tag appointmentTag) { |
|
|
|
|
return appointmentTag.find(attributeEquals("class", "tribe-event-date-start")).stream().flatMap(tag -> tag.inner(0).stream()).flatMap(txt -> toDateTime(txt).stream()).findAny(); |
|
|
|
|
} |
|
|
|
|
private static Optional<String> extractTitle(Tag appointmentTag) { |
|
|
|
|
return appointmentTag |
|
|
|
|
.find(attributeEndsWith("class", "single-event-title")) //
|
|
|
|
|
.stream() |
|
|
|
|
.flatMap(tag -> tag.inner(2).stream()) |
|
|
|
|
.findAny(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
public Stream<Appointment> fetch() { |
|
|
|
|
var url = Payload.of(BASE_URL + "/de/programm"); |
|
|
|
@ -53,29 +98,6 @@ public class JenaRosenkeller implements Importer {
@@ -53,29 +98,6 @@ public class JenaRosenkeller implements Importer {
|
|
|
|
|
.flatMap(result -> result.optional().stream()); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static Result<Appointment> loadEvent(Result<URL> urlResult) { |
|
|
|
|
var link = urlResult.optional().map(url -> new Link(url, "Event-Seite")).orElse(null); |
|
|
|
|
return urlResult //
|
|
|
|
|
.map(JenaRosenkeller::open) |
|
|
|
|
.map(JenaRosenkeller::preload) |
|
|
|
|
.map(JenaRosenkeller::parse) |
|
|
|
|
.map(JenaRosenkeller::getEventDiv) |
|
|
|
|
.map(tagResult -> parseEvent(tagResult, link)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static Result<Tag> getEventDiv(Result<Tag> pageResult) { |
|
|
|
|
switch (pageResult) { |
|
|
|
|
case Payload<Tag> payload: |
|
|
|
|
List<Tag> list = payload.get().find(attributeEquals("id", APPOINTMENT_TAG_ID)); |
|
|
|
|
if (list.size() == 1) return Payload.of(list.getFirst()); |
|
|
|
|
return Error.format("Could not find tag with id \"%s\"", APPOINTMENT_TAG_ID); |
|
|
|
|
case Error<Tag> err: |
|
|
|
|
return err.transform(); |
|
|
|
|
default: |
|
|
|
|
return Error.format("Invalid parameter: %s", pageResult.getClass().getSimpleName()); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static Result<List<String>> findEventUrls(Result<Tag> tagResult) { |
|
|
|
|
return switch (tagResult) { |
|
|
|
|
case Payload<Tag> payload -> { |
|
|
|
@ -94,29 +116,31 @@ public class JenaRosenkeller implements Importer {
@@ -94,29 +116,31 @@ public class JenaRosenkeller implements Importer {
|
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static Result<Tag> parse(Result<InputStream> inputStream) { |
|
|
|
|
return switch (inputStream) { |
|
|
|
|
case Payload<InputStream> payload -> XMLParser.parse(payload.get()); |
|
|
|
|
case Error<InputStream> error -> error.transform(); |
|
|
|
|
default -> Error.of("Invalid parameter: %s".formatted(inputStream.getClass().getSimpleName())); |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static Result<InputStream> preload(Result<InputStream> inputStream) { |
|
|
|
|
switch (inputStream){ |
|
|
|
|
case Payload<InputStream> payload: |
|
|
|
|
try { |
|
|
|
|
return Payload.of(XMLParser.preload(payload.get())); |
|
|
|
|
} catch (IOException e) { |
|
|
|
|
return Error.of("Failed to buffer data from %s".formatted(payload), e); |
|
|
|
|
} |
|
|
|
|
case Error<InputStream> error: |
|
|
|
|
return error.transform(); |
|
|
|
|
private static Result<Tag> getEventDiv(Result<Tag> pageResult) { |
|
|
|
|
switch (pageResult) { |
|
|
|
|
case Payload<Tag> payload: |
|
|
|
|
List<Tag> list = payload.get().find(attributeEquals("id", APPOINTMENT_TAG_ID)); |
|
|
|
|
if (list.size() == 1) return Payload.of(list.getFirst()); |
|
|
|
|
return Error.format("Could not find tag with id \"%s\"", APPOINTMENT_TAG_ID); |
|
|
|
|
case Error<Tag> err: |
|
|
|
|
return err.transform(); |
|
|
|
|
default: |
|
|
|
|
return Error.format("Invalid parameter: %s", inputStream.getClass().getSimpleName()); |
|
|
|
|
return Error.format("Invalid parameter: %s", pageResult.getClass().getSimpleName()); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static Result<Appointment> loadEvent(Result<URL> urlResult) { |
|
|
|
|
var link = urlResult.optional().map(url -> new Link(url, "Event-Seite")).orElse(null); |
|
|
|
|
return urlResult //
|
|
|
|
|
.map(JenaRosenkeller::open) |
|
|
|
|
.map(JenaRosenkeller::preload) |
|
|
|
|
.map(JenaRosenkeller::parse) |
|
|
|
|
.map(JenaRosenkeller::getEventDiv) |
|
|
|
|
.map(tagResult -> parseEvent(tagResult, link)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static Result<InputStream> open(Result<URL> url) { |
|
|
|
|
switch (url) { |
|
|
|
|
case Payload<URL> payload: |
|
|
|
@ -132,6 +156,15 @@ public class JenaRosenkeller implements Importer {
@@ -132,6 +156,15 @@ public class JenaRosenkeller implements Importer {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static Result<Tag> parse(Result<InputStream> inputStream) { |
|
|
|
|
return switch (inputStream) { |
|
|
|
|
case Payload<InputStream> payload -> XMLParser.parse(payload.get()); |
|
|
|
|
case Error<InputStream> error -> error.transform(); |
|
|
|
|
default -> Error.of("Invalid parameter: %s".formatted(inputStream.getClass().getSimpleName())); |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static Result<Appointment> parseEvent(Result<Tag> domResult, Link eventPage) { |
|
|
|
|
switch (domResult) { |
|
|
|
|
case Payload<Tag> payload: |
|
|
|
@ -153,20 +186,23 @@ public class JenaRosenkeller implements Importer {
@@ -153,20 +186,23 @@ public class JenaRosenkeller implements Importer {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static List<Attachment> extractAttachments(Tag appointmentTag) { |
|
|
|
|
return appointmentTag //
|
|
|
|
|
.find(ofType("img")) |
|
|
|
|
.stream() |
|
|
|
|
.map(tag -> tag.get("src")) |
|
|
|
|
.filter(Objects::nonNull) |
|
|
|
|
.map(Payload::of) |
|
|
|
|
.map(JenaRosenkeller::url) |
|
|
|
|
.map(JenaRosenkeller::toAttachment) |
|
|
|
|
.map(Result::optional) |
|
|
|
|
.flatMap(Optional::stream) |
|
|
|
|
.toList(); |
|
|
|
|
|
|
|
|
|
private static Result<InputStream> preload(Result<InputStream> inputStream) { |
|
|
|
|
switch (inputStream) { |
|
|
|
|
case Payload<InputStream> payload: |
|
|
|
|
try { |
|
|
|
|
return Payload.of(XMLParser.preload(payload.get())); |
|
|
|
|
} catch (IOException e) { |
|
|
|
|
return Error.of("Failed to buffer data from %s".formatted(payload), e); |
|
|
|
|
} |
|
|
|
|
case Error<InputStream> error: |
|
|
|
|
return error.transform(); |
|
|
|
|
default: |
|
|
|
|
return Error.format("Invalid parameter: %s", inputStream.getClass().getSimpleName()); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private static Result<Attachment> toAttachment(Result<URL> urlResult) { |
|
|
|
|
switch (urlResult) { |
|
|
|
|
case Payload<URL> payload: |
|
|
|
@ -183,33 +219,6 @@ public class JenaRosenkeller implements Importer {
@@ -183,33 +219,6 @@ public class JenaRosenkeller implements Importer {
|
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static List<Link> extractLinks(Tag appointmentTag) { |
|
|
|
|
var links = new ArrayList<Link>(); |
|
|
|
|
appointmentTag //
|
|
|
|
|
.find(attributeStartsWith("id", "post-")) |
|
|
|
|
.stream() |
|
|
|
|
.flatMap(tag -> tag.find(ofType("a")).stream()) |
|
|
|
|
.forEach(anchor -> { |
|
|
|
|
var href = anchor.get("href"); |
|
|
|
|
if (href == null) return; |
|
|
|
|
if (!href.contains("://")) href = BASE_URL + "href"; |
|
|
|
|
var text = anchor.inner(0).orElse(href); |
|
|
|
|
Payload.of(href).map(JenaRosenkeller::url).optional().map(url -> new Link(url, text)).ifPresent(links::add); |
|
|
|
|
}); |
|
|
|
|
return links; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static Result<Link> toLink(Result<URL> urlResult, Optional<String> description) { |
|
|
|
|
return switch (urlResult) { |
|
|
|
|
case Payload<URL> payload -> Payload.of(new Link(payload.get(),description.orElse(payload.toString()))); |
|
|
|
|
case Error<URL> err -> err.transform(); |
|
|
|
|
default -> Error.format("Invalid parameter: %s", urlResult.getClass().getSimpleName()); |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static Optional<LocalDateTime> extractStart(Tag appointmentTag) { |
|
|
|
|
return appointmentTag.find(attributeEquals("class", "tribe-event-date-start")).stream().flatMap(tag -> tag.inner(0).stream()).flatMap(txt -> toDateTime(txt).stream()).findAny(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static Optional<LocalDateTime> toDateTime(String text) { |
|
|
|
|
var match = DATE_PATTERN.matcher(text); |
|
|
|
@ -246,17 +255,6 @@ public class JenaRosenkeller implements Importer {
@@ -246,17 +255,6 @@ public class JenaRosenkeller implements Importer {
|
|
|
|
|
return empty(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static Optional<String> extractDescription(Tag appointmentTag) { |
|
|
|
|
return appointmentTag.find(attributeHas("class", "tribe-events-single-event-description")).stream().flatMap(tag -> tag.inner(2).stream()).findAny(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static Optional<String> extractTitle(Tag appointmentTag) { |
|
|
|
|
return appointmentTag |
|
|
|
|
.find(attributeEndsWith("class", "single-event-title")) //
|
|
|
|
|
.stream() |
|
|
|
|
.flatMap(tag -> tag.inner(2).stream()) |
|
|
|
|
.findAny(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private static Result<URL> url(Result<String> urls) { |
|
|
|
|
switch (urls) { |
|
|
|
|