diff --git a/src/main/java/com/apptasticsoftware/rssreader/AbstractRssReader.java b/src/main/java/com/apptasticsoftware/rssreader/AbstractRssReader.java index 446390f..201688a 100644 --- a/src/main/java/com/apptasticsoftware/rssreader/AbstractRssReader.java +++ b/src/main/java/com/apptasticsoftware/rssreader/AbstractRssReader.java @@ -70,6 +70,7 @@ public abstract class AbstractRssReader { private final Map headers = new HashMap<>(); private final HashMap> channelTags = new HashMap<>(); private final HashMap>> channelAttributes = new HashMap<>(); + private final HashMap> onItemTags = new HashMap<>(); private final HashMap> itemTags = new HashMap<>(); private final HashMap>> itemAttributes = new HashMap<>(); private final Set collectChildNodesForTag = Set.of("content", "summary"); @@ -204,6 +205,8 @@ protected void registerItemTags() { itemTags.putIfAbsent("comments", Item::setComments); itemTags.putIfAbsent("dc:creator", (i, v) -> Mapper.mapIfEmpty(v, i::getAuthor, i::setAuthor)); itemTags.putIfAbsent("dc:date", (i, v) -> Mapper.mapIfEmpty(v, i::getPubDate, i::setPubDate)); + + onItemTags.put("enclosure", (i) -> i.addEnclosure(new Enclosure())); } /** @@ -214,9 +217,9 @@ protected void registerItemAttributes() { itemAttributes.computeIfAbsent("guid", k -> new HashMap<>()).putIfAbsent("isPermaLink", (i, v) -> i.setIsPermaLink(Boolean.parseBoolean(v)) ); var enclosureAttributes = itemAttributes.computeIfAbsent("enclosure", k -> new HashMap<>()); - enclosureAttributes.putIfAbsent("url", (i, v) -> createIfNull(i::getEnclosure, i::setEnclosure, Enclosure::new).setUrl(v)); - enclosureAttributes.putIfAbsent("type", (i, v) -> createIfNull(i::getEnclosure, i::setEnclosure, Enclosure::new).setType(v)); - enclosureAttributes.putIfAbsent("length", (i, v) -> createIfNullOptional(i::getEnclosure, i::setEnclosure, Enclosure::new).ifPresent(e -> mapLong(v, e::setLength))); + enclosureAttributes.putIfAbsent("url", (i, v) -> i.getEnclosure().ifPresent(a -> a.setUrl(v))); + enclosureAttributes.putIfAbsent("type", (i, v) -> i.getEnclosure().ifPresent(a -> a.setType(v))); + enclosureAttributes.putIfAbsent("length", (i, v) -> i.getEnclosure().ifPresent(e -> mapLong(v, e::setLength))); } /** @@ -671,6 +674,8 @@ void parseAttributes() { mapChannelAttributes(elementFullPath); } else if (isItemPart) { + onItemTags.computeIfPresent(nsTagName, (k, f) -> { f.accept(item); return f; }); + onItemTags.computeIfPresent(getElementFullPath(), (k, f) -> { f.accept(item); return f; }); // Map item attributes mapItemAttributes(nsTagName); mapItemAttributes(elementFullPath); diff --git a/src/main/java/com/apptasticsoftware/rssreader/Item.java b/src/main/java/com/apptasticsoftware/rssreader/Item.java index bd1b711..9f26b34 100644 --- a/src/main/java/com/apptasticsoftware/rssreader/Item.java +++ b/src/main/java/com/apptasticsoftware/rssreader/Item.java @@ -47,6 +47,7 @@ public class Item implements Comparable { private String pubDate; private String comments; private Enclosure enclosure; + private final List enclosures = new ArrayList<>(); private Channel channel; private final DateTimeParser dateTimeParser; @@ -288,7 +289,25 @@ public Optional getEnclosure() { * @param enclosure enclosure */ public void setEnclosure(Enclosure enclosure) { + addEnclosure(enclosure); + } + + /** + * Get enclosures for item. + * Use this method if multiple enclosures exist per item. + * @return list of enclosures + */ + public List getEnclosures() { + return Collections.unmodifiableList(enclosures); + } + + /** + * Add enclosure for item. + * @param enclosure enclosure + */ + public void addEnclosure(Enclosure enclosure) { this.enclosure = enclosure; + enclosures.add(enclosure); } /** @@ -323,14 +342,14 @@ public boolean equals(Object o) { Objects.equals(getIsPermaLink(), item.getIsPermaLink()) && Objects.equals(getPubDate(), item.getPubDate()) && Objects.equals(getComments(), item.getComments()) && - Objects.equals(getEnclosure(), item.getEnclosure()) && + getEnclosures().equals(item.getEnclosures()) && Objects.equals(getChannel(), item.getChannel()); } @Override public int hashCode() { return Objects.hash(getTitle(), getDescription(), getLink(), getAuthor(), getCategories(), - getGuid(), getIsPermaLink(), getPubDate(), getComments(), getEnclosure(), getChannel()); + getGuid(), getIsPermaLink(), getPubDate(), getComments(), getEnclosures(), getChannel()); } /** diff --git a/src/test/java/com/apptasticsoftware/integrationtest/RssReaderIntegrationTest.java b/src/test/java/com/apptasticsoftware/integrationtest/RssReaderIntegrationTest.java index 99c8374..5308ab6 100644 --- a/src/test/java/com/apptasticsoftware/integrationtest/RssReaderIntegrationTest.java +++ b/src/test/java/com/apptasticsoftware/integrationtest/RssReaderIntegrationTest.java @@ -657,6 +657,33 @@ void testMultipleCategories() { assertTrue(item.getCategory().isPresent()); } + @Test + void testMultipleEnclosures() { + var list = new RssReader().read(fromFile("multiple-enclosures.xml")).collect(Collectors.toList()); + + assertEquals(2, list.size()); + var item = list.get(0); + assertEquals(3, item.getEnclosures().size()); + assertEquals("https://url1", item.getEnclosures().get(0).getUrl()); + assertEquals("image/jpeg", item.getEnclosures().get(0).getType()); + assertEquals(1L, item.getEnclosures().get(0).getLength().orElse(-1L)); + assertEquals("https://url2", item.getEnclosures().get(1).getUrl()); + assertEquals("image/png", item.getEnclosures().get(1).getType()); + assertEquals(2L, item.getEnclosures().get(1).getLength().orElse(-1L)); + assertEquals("https://url3", item.getEnclosures().get(2).getUrl()); + assertEquals("image/gif", item.getEnclosures().get(2).getType()); + assertEquals(3L, item.getEnclosures().get(2).getLength().orElse(-1L)); + + item = list.get(1); + assertEquals(2, item.getEnclosures().size()); + assertEquals("https://url4", item.getEnclosures().get(0).getUrl()); + assertEquals("image/svg", item.getEnclosures().get(0).getType()); + assertEquals(4L, item.getEnclosures().get(0).getLength().orElse(-1L)); + assertEquals("https://url5", item.getEnclosures().get(1).getUrl()); + assertEquals("image/webp", item.getEnclosures().get(1).getType()); + assertEquals(5L, item.getEnclosures().get(1).getLength().orElse(-1L)); + } + @Test void testImageBadWidthHeight() { var list = new RssReader().read(fromFile("bad-image-width-height.xml")).collect(Collectors.toList()); diff --git a/src/test/java/com/apptasticsoftware/rssreader/RssReaderTest.java b/src/test/java/com/apptasticsoftware/rssreader/RssReaderTest.java index a574c4d..b4070ce 100644 --- a/src/test/java/com/apptasticsoftware/rssreader/RssReaderTest.java +++ b/src/test/java/com/apptasticsoftware/rssreader/RssReaderTest.java @@ -507,7 +507,7 @@ void enclosureEqualsTest() { @Test void equalsContract() { EqualsVerifier.simple().forClass(Channel.class).withIgnoredFields("dateTimeParser").withIgnoredFields("category").withNonnullFields("categories").verify(); - EqualsVerifier.simple().forClass(Item.class).withIgnoredFields("defaultComparator").withIgnoredFields("dateTimeParser").withIgnoredFields("category").withNonnullFields("categories").verify(); + EqualsVerifier.simple().forClass(Item.class).withIgnoredFields("defaultComparator").withIgnoredFields("dateTimeParser").withIgnoredFields("category").withNonnullFields("categories").withIgnoredFields("enclosure").withNonnullFields("enclosures").verify(); EqualsVerifier.simple().forClass(Enclosure.class).verify(); EqualsVerifier.simple().forClass(Image.class).verify(); } diff --git a/src/test/java/com/apptasticsoftware/rssreader/module/itunes/ItunesRssReaderTest.java b/src/test/java/com/apptasticsoftware/rssreader/module/itunes/ItunesRssReaderTest.java index b0010f5..a87f98f 100644 --- a/src/test/java/com/apptasticsoftware/rssreader/module/itunes/ItunesRssReaderTest.java +++ b/src/test/java/com/apptasticsoftware/rssreader/module/itunes/ItunesRssReaderTest.java @@ -33,7 +33,7 @@ void readItunesPodcastFeed2() throws IOException { @Test void equalsContract() { EqualsVerifier.simple().forClass(ItunesChannel.class).withIgnoredFields("dateTimeParser").withIgnoredFields("category").withNonnullFields("categories").withNonnullFields("itunesCategories").verify(); - EqualsVerifier.simple().forClass(ItunesItem.class).withIgnoredFields("defaultComparator").withIgnoredFields("dateTimeParser").withIgnoredFields("category").withNonnullFields("categories").verify(); + EqualsVerifier.simple().forClass(ItunesItem.class).withIgnoredFields("defaultComparator").withIgnoredFields("dateTimeParser").withIgnoredFields("category").withNonnullFields("categories").withIgnoredFields("enclosure").withNonnullFields("enclosures").verify(); EqualsVerifier.simple().forClass(ItunesOwner.class).verify(); } diff --git a/src/test/resources/multiple-enclosures.xml b/src/test/resources/multiple-enclosures.xml new file mode 100644 index 0000000..bb81c11 --- /dev/null +++ b/src/test/resources/multiple-enclosures.xml @@ -0,0 +1,39 @@ + + + + + + World of Tanks News | World of Tanks + https://worldoftanks.eu/en/news/ + The latest news, updates, specials, and events for World of Tanks, the team-based, MMO tank battle game from Wargaming. Everything about WoT in one place. + en + Fri, 02 Feb 2024 11:05:26 GMT + + https://worldoftanks.eu/static/5.132.2_d0fd33/portalnews/img/news.png + World of Tanks News | World of Tanks + https://worldoftanks.eu/en/news/ + + + A Warrior's Path: Unleash the Power of Japanese Tanks With Special Bundles! + https://worldoftanks.eu/en/news/specials/a-warriors-path-sale-feb-2024/ + Embark on an epic journey with the A Warrior's Path event, featuring exclusive bundle sales for the newly introduced Japanese heavy tanks. + https://worldoftanks.eu/en/news/specials/a-warriors-path-sale-feb-2024/ + Thu, 01 Feb 2024 09:00:00 GMT + Specials + + + + + + WoT Monthly: Valentine's Day and More in February 2024 + https://worldoftanks.eu/en/news/general-news/wot-monthly-february-2024/ + A quick look at World of Tanks events during the month of love! + https://worldoftanks.eu/en/news/general-news/wot-monthly-february-2024/ + Wed, 31 Jan 2024 14:00:00 GMT + General News + + + + + + \ No newline at end of file