transformed JenaRosenkeller importer to use Result-driven pipeline

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
This commit is contained in:
2024-12-19 16:23:30 +01:00
parent a17b322001
commit ed8cef3738
8 changed files with 272 additions and 69 deletions

View File

@@ -1,59 +1,192 @@
/* © SRSoftware 2024 */
package de.srsoftware.cal.importer;
import static de.srsoftware.tools.Optionals.nullable;
import static java.util.Optional.empty;
import static java.util.function.Predicate.not;
import de.srsoftware.cal.api.Appointment;
import de.srsoftware.cal.api.Importer;
import de.srsoftware.cal.api.*;
import de.srsoftware.tools.Error;
import de.srsoftware.tools.Payload;
import de.srsoftware.tools.Result;
import de.srsoftware.tools.Tag;
import de.srsoftware.tools.XMLParser;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
public class JenaRosenkeller implements Importer {
private static class EmptyAppointment implements Appointment {
@Override
public Set<Attachment> attachments() {
return Set.of();
}
@Override
public Optional<Coords> coords() {
return empty();
}
@Override
public String description() {
return null;
}
@Override
public Optional<LocalDateTime> end() {
return empty();
}
@Override
public long id() {
return 0;
}
@Override
public String location() {
return null;
}
@Override
public LocalDateTime start() {
return null;
}
@Override
public Set<String> tags() {
return Set.of();
}
@Override
public String title() {
return null;
}
@Override
public String toString() {
return "%s (%s)".formatted(title(), EmptyAppointment.class.getSimpleName());
}
@Override
public Set<Link> urls() {
return Set.of();
}
}
@Override
public String description() {
return "Events von der Seite rosenkeller.org importieren";
}
@Override
public List<Appointment> fetch() throws IOException {
var url = "https://rosenkeller.org/de/programm";
var listUrl = url(url);
if (listUrl.isEmpty()) throw new RuntimeException("Failed to fetch %s".formatted(url));
var input = listUrl.get().openConnection().getInputStream();
input = XMLParser.preload(input);
var result = XMLParser.parse(input);
input.close();
List<Appointment> appointments = nullable(result) //
.filter(o -> o instanceof Payload<Tag>)
.map(o -> (Payload<Tag>)o)
.map(Payload::get)
.stream()
.flatMap(JenaRosenkeller::findEventUrls)
.map(JenaRosenkeller::url)
.filter(Optional::isPresent)
.map(Optional::get)
.map(JenaRosenkeller::loadEvent)
.filter(Optional::isPresent)
.map(Optional::get)
.toList();
System.out.println(appointments);
return appointments;
public Stream<Result<Appointment>> fetch() throws IOException {
var url = Payload.of("https://rosenkeller.org/de/programm");
Stream<Result<String>> stream = url(url)
.map(JenaRosenkeller::open) //
.map(JenaRosenkeller::preload)
.map(JenaRosenkeller::parse)
.map(JenaRosenkeller::findEventUrls)
.stream();
return stream //
.map(JenaRosenkeller::url)
.map(JenaRosenkeller::open)
.map(JenaRosenkeller::preload)
.map(JenaRosenkeller::parse)
.map(JenaRosenkeller::loadEvent);
}
private static Optional<Appointment> loadEvent(URL url) {
private static Result<List<String>> findEventUrls(Result<Tag> tagResult) {
return switch (tagResult) {
case Payload<Tag> payload -> {
List<String> urls = payload // find tag with event-id
.get()
.find("id", val -> val.startsWith("event-"))
.stream()
.map(t -> t.find("class", "ect-event-url"::equals))
.flatMap(List::stream)
.map(t -> t.get("href"))
.toList();
yield Payload.of(urls);
}
case Error<Tag> error -> error.transform();
default -> Error.format("Invalid parameter: %s", tagResult.getClass().getSimpleName());
};
}
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();
default:
return Error.format("Invalid parameter: %s", inputStream.getClass().getSimpleName());
}
}
private static Result<InputStream> open(Result<URL> url) {
switch (url) {
case Payload<URL> payload:
try {
return Payload.of(payload.get().openConnection().getInputStream());
} catch (IOException e) {
return Error.of("Failed to open %s".formatted(payload), e);
}
case Error<URL> error:
return error.transform();
default:
return Error.format("Invalid parameter: %s", url.getClass().getSimpleName());
}
}
private static Result<Appointment> loadEvent(Result<Tag> domResult) {
switch (domResult) {
case Payload<Tag> payload:
var tag = payload.get();
Optional<String> title = tag.find("class", s -> s.endsWith("single-event-title")) //
.stream()
.map(Tag::children)
.filter(not(List::isEmpty))
.map(List::getFirst)
.map(Tag::toString)
.findAny();
if (title.isPresent()) {
var appointment = new EmptyAppointment() {
@Override
public String title() {
return title.get();
}
};
return Payload.of(appointment);
}
return Error.of("Could not find appointment title");
case Error<Tag> err:
return err.transform();
default:
return Error.format("Invalid parameter: %s", domResult.getClass().getSimpleName());
}
}
private static Optional<Appointment> nope(URL url) {
try {
var input = url.openConnection().getInputStream();
input = XMLParser.preload(input);
@@ -76,16 +209,19 @@ public class JenaRosenkeller implements Importer {
}
}
private static Stream<String> findEventUrls(Tag tag) {
return tag.find("id", val -> val.startsWith("event-")).stream().map(t -> t.find("class", "ect-event-url" ::equals)).flatMap(List::stream).map(t -> t.get("href"));
}
private static Optional<URL> url(String url) {
try {
return Optional.of(new URI(url).toURL());
} catch (MalformedURLException | URISyntaxException e) {
e.printStackTrace();
return empty();
private static Result<URL> url(Result<String> urls) {
switch (urls) {
case Payload<String> payload:
var url = payload.get();
try {
return Payload.of(new URI(url).toURL());
} catch (MalformedURLException | URISyntaxException e) {
return Error.of("Failed to create URL of %s".formatted(url), e);
}
case Error<String> err:
return err.transform();
default:
return Error.format("Invalid parameter: %s", urls.getClass().getSimpleName());
}
}
}