feedparser-rs 0.5.0__tar.gz → 0.5.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/Cargo.lock +11 -11
  2. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/Cargo.toml +1 -1
  3. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/PKG-INFO +1 -1
  4. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/examples/podcast_feed.rs +11 -17
  5. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/lib.rs +5 -5
  6. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/limits.rs +48 -0
  7. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/namespace/content.rs +46 -8
  8. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/namespace/dublin_core.rs +14 -9
  9. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/namespace/georss.rs +320 -18
  10. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/namespace/media_rss.rs +53 -2
  11. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/namespace/mod.rs +5 -0
  12. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/namespace/slash.rs +2 -0
  13. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/namespace/syndication.rs +8 -7
  14. feedparser_rs-0.5.2/crates/feedparser-rs-core/src/parser/atom.rs +3686 -0
  15. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/parser/common.rs +278 -25
  16. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/parser/detect.rs +32 -3
  17. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/parser/json.rs +459 -32
  18. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/parser/mod.rs +55 -3
  19. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/parser/rss.rs +4134 -1575
  20. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/parser/rss10.rs +237 -64
  21. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/types/common.rs +132 -5
  22. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/types/entry.rs +26 -2
  23. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/types/feed.rs +43 -2
  24. feedparser_rs-0.5.2/crates/feedparser-rs-core/src/types/mod.rs +25 -0
  25. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/types/podcast.rs +327 -127
  26. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/types/version.rs +6 -3
  27. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/util/date.rs +66 -0
  28. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/util/text.rs +3 -0
  29. feedparser_rs-0.5.2/crates/feedparser-rs-core/tests/fixtures/rss10/rss10_channel_selfclosing_refs.xml +26 -0
  30. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/tests/integration_tests.rs +125 -2
  31. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/tests/namespace_edge_cases.rs +5 -1
  32. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/tests/namespace_integration.rs +211 -1
  33. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/tests/test_author_detail.rs +2 -2
  34. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/tests/test_georss_cc_namespaces.rs +200 -0
  35. feedparser_rs-0.5.2/crates/feedparser-rs-core/tests/test_media_namespace.rs +277 -0
  36. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/tests/test_podcast_namespace.rs +24 -20
  37. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/tests/test_rss10.rs +26 -2
  38. feedparser_rs-0.5.2/crates/feedparser-rs-core/tests/test_rss_optional_elements.rs +293 -0
  39. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/src/lib.rs +7 -0
  40. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/src/limits.rs +15 -9
  41. feedparser_rs-0.5.2/crates/feedparser-rs-py/src/types/common.rs +1069 -0
  42. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/src/types/entry.rs +170 -10
  43. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/src/types/feed_meta.rs +243 -6
  44. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/src/types/geo.rs +21 -3
  45. feedparser_rs-0.5.2/crates/feedparser-rs-py/src/types/media.rs +535 -0
  46. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/src/types/podcast.rs +392 -15
  47. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/src/types/syndication.rs +3 -3
  48. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/tests/test_basic.py +11 -8
  49. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/tests/test_bindings.py +56 -23
  50. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/tests/test_compat.py +10 -8
  51. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/tests/test_itunes_flat_keys.py +36 -6
  52. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/tests/test_syndication.py +37 -2
  53. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/uv.lock +21 -1
  54. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/pyproject.toml +2 -2
  55. feedparser_rs-0.5.0/crates/feedparser-rs-core/src/parser/atom.rs +0 -1843
  56. feedparser_rs-0.5.0/crates/feedparser-rs-core/src/types/mod.rs +0 -22
  57. feedparser_rs-0.5.0/crates/feedparser-rs-py/src/types/common.rs +0 -492
  58. feedparser_rs-0.5.0/crates/feedparser-rs-py/src/types/media.rs +0 -164
  59. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/README.md +0 -0
  60. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/Cargo.toml +0 -0
  61. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/README.md +0 -0
  62. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/benches/parsing.rs +0 -0
  63. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/benches/types.rs +0 -0
  64. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/examples/error_handling.rs +0 -0
  65. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/examples/feeds/malformed_feed.xml +0 -0
  66. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/examples/feeds/sample_atom.xml +0 -0
  67. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/examples/feeds/sample_podcast.xml +0 -0
  68. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/examples/feeds/sample_rss.xml +0 -0
  69. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/examples/parse_file.rs +0 -0
  70. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/examples/parse_url.rs +0 -0
  71. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/examples/profile_memory.rs +0 -0
  72. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/compat/mod.rs +0 -0
  73. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/error.rs +0 -0
  74. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/http/client.rs +0 -0
  75. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/http/mod.rs +0 -0
  76. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/http/response.rs +0 -0
  77. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/http/validation.rs +0 -0
  78. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/namespace/cc.rs +0 -0
  79. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/namespace/threading.rs +0 -0
  80. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/options.rs +0 -0
  81. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/parser/namespace_detection.rs +0 -0
  82. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/types/generics.rs +0 -0
  83. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/types/thread.rs +0 -0
  84. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/util/base_url.rs +0 -0
  85. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/util/encoding.rs +0 -0
  86. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/util/mod.rs +0 -0
  87. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/src/util/sanitize.rs +0 -0
  88. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/tests/encoding_integration.rs +0 -0
  89. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/tests/guid_entity_tests.rs +0 -0
  90. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/tests/http_integration.rs +0 -0
  91. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/tests/issue45_test.rs +0 -0
  92. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/tests/test_adversarial_input.rs +0 -0
  93. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/tests/test_entry_author_precedence.rs +0 -0
  94. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/tests/test_parser_limits.rs +0 -0
  95. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/tests/test_slash_wfw_namespaces.rs +0 -0
  96. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/tests/test_thr_namespace.rs +0 -0
  97. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/tests/test_threading_namespace.rs +0 -0
  98. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/tests/test_timezone_preservation.rs +0 -0
  99. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/tests/test_truncated_feeds.rs +0 -0
  100. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/tests/test_url_resolution.rs +0 -0
  101. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-core/tests/test_url_security.rs +0 -0
  102. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/Cargo.toml +0 -0
  103. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/README.md +0 -0
  104. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/src/error.rs +0 -0
  105. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/src/types/compat.rs +0 -0
  106. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/src/types/datetime.rs +0 -0
  107. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/src/types/mod.rs +0 -0
  108. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/src/types/parsed_feed.rs +0 -0
  109. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/src/types/thread.rs +0 -0
  110. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/tests/test_dict_methods.py +0 -0
  111. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/tests/test_guid_entities.py +0 -0
  112. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/tests/test_integration.py +0 -0
  113. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/crates/feedparser-rs-py/tests/test_subscriptable.py +0 -0
  114. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/python/feedparser_rs/__init__.py +0 -0
  115. {feedparser_rs-0.5.0 → feedparser_rs-0.5.2}/python/feedparser_rs/py.typed +0 -0
@@ -500,9 +500,9 @@ dependencies = [
500
500
 
501
501
  [[package]]
502
502
  name = "ctor"
503
- version = "0.6.3"
503
+ version = "0.8.0"
504
504
  source = "registry+https://github.com/rust-lang/crates.io-index"
505
- checksum = "424e0138278faeb2b401f174ad17e715c829512d74f3d1e81eb43365c2e0590e"
505
+ checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98"
506
506
  dependencies = [
507
507
  "ctor-proc-macro",
508
508
  "dtor",
@@ -558,9 +558,9 @@ dependencies = [
558
558
 
559
559
  [[package]]
560
560
  name = "dtor"
561
- version = "0.1.1"
561
+ version = "0.3.0"
562
562
  source = "registry+https://github.com/rust-lang/crates.io-index"
563
- checksum = "404d02eeb088a82cfd873006cb713fe411306c7d182c344905e101fb1167d301"
563
+ checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4"
564
564
  dependencies = [
565
565
  "dtor-proc-macro",
566
566
  ]
@@ -600,7 +600,7 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
600
600
 
601
601
  [[package]]
602
602
  name = "feedparser-rs"
603
- version = "0.5.0"
603
+ version = "0.5.2"
604
604
  dependencies = [
605
605
  "ammonia",
606
606
  "chrono",
@@ -623,7 +623,7 @@ dependencies = [
623
623
 
624
624
  [[package]]
625
625
  name = "feedparser-rs-node"
626
- version = "0.5.0"
626
+ version = "0.5.2"
627
627
  dependencies = [
628
628
  "feedparser-rs",
629
629
  "napi",
@@ -633,7 +633,7 @@ dependencies = [
633
633
 
634
634
  [[package]]
635
635
  name = "feedparser-rs-py"
636
- version = "0.5.0"
636
+ version = "0.5.2"
637
637
  dependencies = [
638
638
  "chrono",
639
639
  "feedparser-rs",
@@ -1330,9 +1330,9 @@ dependencies = [
1330
1330
 
1331
1331
  [[package]]
1332
1332
  name = "napi"
1333
- version = "3.8.3"
1333
+ version = "3.8.4"
1334
1334
  source = "registry+https://github.com/rust-lang/crates.io-index"
1335
- checksum = "e6944d0bf100571cd6e1a98a316cdca262deb6fccf8d93f5ae1502ca3fc88bd3"
1335
+ checksum = "fb7848c221fb7bb789e02f01875287ebb1e078b92a6566a34de01ef8806e7c2b"
1336
1336
  dependencies = [
1337
1337
  "anyhow",
1338
1338
  "bitflags",
@@ -1352,9 +1352,9 @@ checksum = "d376940fd5b723c6893cd1ee3f33abbfd86acb1cd1ec079f3ab04a2a3bc4d3b1"
1352
1352
 
1353
1353
  [[package]]
1354
1354
  name = "napi-derive"
1355
- version = "3.5.2"
1355
+ version = "3.5.3"
1356
1356
  source = "registry+https://github.com/rust-lang/crates.io-index"
1357
- checksum = "2c914b5e420182bfb73504e0607592cdb8e2e21437d450883077669fb72a114d"
1357
+ checksum = "60867ff9a6f76e82350e0c3420cb0736f5866091b61d7d8a024baa54b0ec17dd"
1358
1358
  dependencies = [
1359
1359
  "convert_case",
1360
1360
  "ctor",
@@ -3,7 +3,7 @@ members = ["crates/feedparser-rs-core", "crates/feedparser-rs-py"]
3
3
  resolver = "2"
4
4
 
5
5
  [workspace.package]
6
- version = "0.5.0"
6
+ version = "0.5.2"
7
7
  edition = "2024"
8
8
  rust-version = "1.88.0"
9
9
  authors = ["bug-ops"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: feedparser-rs
3
- Version: 0.5.0
3
+ Version: 0.5.2
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -94,7 +94,11 @@ fn display_podcast_metadata(feed: &feedparser_rs::ParsedFeed) {
94
94
  println!(" Type: {podcast_type}");
95
95
  }
96
96
 
97
- if itunes.complete == Some(true) {
97
+ if itunes
98
+ .complete
99
+ .as_deref()
100
+ .is_some_and(|v| v.eq_ignore_ascii_case("yes"))
101
+ {
98
102
  println!(" Status: Complete (no more episodes will be released)");
99
103
  }
100
104
  }
@@ -192,25 +196,15 @@ fn display_episodes(feed: &feedparser_rs::ParsedFeed) {
192
196
  if let Some(itunes) = &entry.itunes {
193
197
  println!(" iTunes:");
194
198
 
195
- if let Some(duration) = itunes.duration {
196
- println!(" Duration: {duration} seconds");
197
-
198
- // Convert to human-readable format
199
- let hours = duration / 3600;
200
- let minutes = (duration % 3600) / 60;
201
- let seconds = duration % 60;
202
- if hours > 0 {
203
- println!(" ({hours:02}:{minutes:02}:{seconds:02})");
204
- } else {
205
- println!(" ({minutes}:{seconds:02})");
206
- }
199
+ if let Some(duration) = &itunes.duration {
200
+ println!(" Duration: {duration}");
207
201
  }
208
202
 
209
- if let Some(episode_num) = itunes.episode {
203
+ if let Some(episode_num) = &itunes.episode {
210
204
  println!(" Episode Number: {episode_num}");
211
205
  }
212
206
 
213
- if let Some(season) = itunes.season {
207
+ if let Some(season) = &itunes.season {
214
208
  println!(" Season: {season}");
215
209
  }
216
210
 
@@ -262,9 +256,9 @@ fn display_episodes(feed: &feedparser_rs::ParsedFeed) {
262
256
  }
263
257
 
264
258
  // Guest information
265
- if !podcast.person.is_empty() {
259
+ if !podcast.persons.is_empty() {
266
260
  println!(" People:");
267
- for person in &podcast.person {
261
+ for person in &podcast.persons {
268
262
  print!(" - {}", person.name);
269
263
  if let Some(role) = &person.role {
270
264
  print!(" ({role})");
@@ -188,12 +188,12 @@ pub use limits::{LimitError, ParserLimits};
188
188
  pub use options::ParseOptions;
189
189
  pub use parser::{detect_format, parse, parse_with_limits};
190
190
  pub use types::{
191
- Content, Email, Enclosure, Entry, FeedMeta, FeedVersion, Generator, Image, InReplyTo,
191
+ Cloud, Content, Email, Enclosure, Entry, FeedMeta, FeedVersion, Generator, Image, InReplyTo,
192
192
  ItunesCategory, ItunesEntryMeta, ItunesFeedMeta, ItunesOwner, LimitedCollectionExt, Link,
193
- MediaContent, MediaThumbnail, MimeType, ParsedFeed, Person, PodcastChapters, PodcastEntryMeta,
194
- PodcastFunding, PodcastMeta, PodcastPerson, PodcastSoundbite, PodcastTranscript, PodcastValue,
195
- PodcastValueRecipient, Source, Tag, TextConstruct, TextType, Url, parse_duration,
196
- parse_explicit,
193
+ MediaContent, MediaCopyright, MediaCredit, MediaRating, MediaThumbnail, MimeType, ParsedFeed,
194
+ Person, PodcastChapters, PodcastEntryMeta, PodcastFunding, PodcastMeta, PodcastPerson,
195
+ PodcastSoundbite, PodcastTranscript, PodcastValue, PodcastValueRecipient, Source, Tag,
196
+ TextConstruct, TextInput, TextType, Url, parse_explicit,
197
197
  };
198
198
 
199
199
  pub use namespace::syndication::{SyndicationMeta, UpdatePeriod};
@@ -143,6 +143,36 @@ pub struct ParserLimits {
143
143
  ///
144
144
  /// Default: 20 recipients
145
145
  pub max_value_recipients: usize,
146
+
147
+ /// Maximum number of alternate enclosures per entry
148
+ ///
149
+ /// Default: 20
150
+ pub max_podcast_alternate_enclosures: usize,
151
+
152
+ /// Maximum number of sources per alternate enclosure
153
+ ///
154
+ /// Default: 10
155
+ pub max_podcast_alternate_enclosure_sources: usize,
156
+
157
+ /// Maximum number of podroll entries per feed
158
+ ///
159
+ /// Default: 50
160
+ pub max_podcast_podroll: usize,
161
+
162
+ /// Maximum number of socialInteract elements per entry
163
+ ///
164
+ /// Default: 20
165
+ pub max_podcast_social_interact: usize,
166
+
167
+ /// Maximum number of txt records per feed or entry
168
+ ///
169
+ /// Default: 20
170
+ pub max_podcast_txt: usize,
171
+
172
+ /// Maximum number of follow links per feed or entry
173
+ ///
174
+ /// Default: 20
175
+ pub max_podcast_follow: usize,
146
176
  }
147
177
 
148
178
  impl Default for ParserLimits {
@@ -170,6 +200,12 @@ impl Default for ParserLimits {
170
200
  max_podcast_funding: 20,
171
201
  max_podcast_persons: 50,
172
202
  max_value_recipients: 20,
203
+ max_podcast_alternate_enclosures: 20,
204
+ max_podcast_alternate_enclosure_sources: 10,
205
+ max_podcast_podroll: 50,
206
+ max_podcast_social_interact: 20,
207
+ max_podcast_txt: 20,
208
+ max_podcast_follow: 20,
173
209
  }
174
210
  }
175
211
  }
@@ -209,6 +245,12 @@ impl ParserLimits {
209
245
  max_podcast_funding: 5,
210
246
  max_podcast_persons: 10,
211
247
  max_value_recipients: 5,
248
+ max_podcast_alternate_enclosures: 5,
249
+ max_podcast_alternate_enclosure_sources: 3,
250
+ max_podcast_podroll: 10,
251
+ max_podcast_social_interact: 5,
252
+ max_podcast_txt: 5,
253
+ max_podcast_follow: 5,
212
254
  }
213
255
  }
214
256
 
@@ -246,6 +288,12 @@ impl ParserLimits {
246
288
  max_podcast_funding: 50,
247
289
  max_podcast_persons: 200,
248
290
  max_value_recipients: 50,
291
+ max_podcast_alternate_enclosures: 100,
292
+ max_podcast_alternate_enclosure_sources: 50,
293
+ max_podcast_podroll: 200,
294
+ max_podcast_social_interact: 100,
295
+ max_podcast_txt: 100,
296
+ max_podcast_follow: 100,
249
297
  }
250
298
  }
251
299
 
@@ -21,14 +21,21 @@ pub const CONTENT_NAMESPACE: &str = "http://purl.org/rss/1.0/modules/content/";
21
21
  /// * `element` - Local name of the element (without namespace prefix)
22
22
  /// * `text` - Text content of the element
23
23
  /// * `entry` - Entry to update
24
- pub fn handle_entry_element(element: &str, text: &str, entry: &mut Entry) {
24
+ pub fn handle_entry_element(
25
+ element: &str,
26
+ text: &str,
27
+ entry: &mut Entry,
28
+ lang: Option<&str>,
29
+ base: Option<&str>,
30
+ ) {
25
31
  if element == "encoded" {
26
32
  // content:encoded → add to entry.content as HTML
27
33
  entry.content.push(Content {
28
34
  value: text.to_string(),
29
35
  content_type: Some("text/html".into()),
30
- language: None,
31
- base: None,
36
+ language: lang.filter(|s| !s.is_empty()).map(Into::into),
37
+ base: base.filter(|s| !s.is_empty()).map(ToString::to_string),
38
+ src: None,
32
39
  });
33
40
  }
34
41
  }
@@ -42,7 +49,7 @@ mod tests {
42
49
  let mut entry = Entry::default();
43
50
  let html = r"<p>Full HTML content with <strong>formatting</strong>.</p>";
44
51
 
45
- handle_entry_element("encoded", html, &mut entry);
52
+ handle_entry_element("encoded", html, &mut entry, None, None);
46
53
 
47
54
  assert_eq!(entry.content.len(), 1);
48
55
  assert_eq!(entry.content[0].value, html);
@@ -53,8 +60,8 @@ mod tests {
53
60
  fn test_multiple_content_encoded() {
54
61
  let mut entry = Entry::default();
55
62
 
56
- handle_entry_element("encoded", "<p>First content</p>", &mut entry);
57
- handle_entry_element("encoded", "<p>Second content</p>", &mut entry);
63
+ handle_entry_element("encoded", "<p>First content</p>", &mut entry, None, None);
64
+ handle_entry_element("encoded", "<p>Second content</p>", &mut entry, None, None);
58
65
 
59
66
  assert_eq!(entry.content.len(), 2);
60
67
  }
@@ -65,7 +72,7 @@ mod tests {
65
72
  // CDATA markers are typically stripped by XML parser before we see it
66
73
  let html = r"<p>Content from <![CDATA[...]]></p>";
67
74
 
68
- handle_entry_element("encoded", html, &mut entry);
75
+ handle_entry_element("encoded", html, &mut entry, None, None);
69
76
 
70
77
  assert!(!entry.content.is_empty());
71
78
  }
@@ -74,8 +81,39 @@ mod tests {
74
81
  fn test_ignore_unknown_elements() {
75
82
  let mut entry = Entry::default();
76
83
 
77
- handle_entry_element("unknown", "test", &mut entry);
84
+ handle_entry_element("unknown", "test", &mut entry, None, None);
78
85
 
79
86
  assert!(entry.content.is_empty());
80
87
  }
88
+
89
+ #[test]
90
+ fn test_content_encoded_with_lang_and_base() {
91
+ let mut entry = Entry::default();
92
+ let html = "<p>Content</p>";
93
+
94
+ handle_entry_element(
95
+ "encoded",
96
+ html,
97
+ &mut entry,
98
+ Some("en"),
99
+ Some("http://example.com/"),
100
+ );
101
+
102
+ assert_eq!(entry.content.len(), 1);
103
+ assert_eq!(entry.content[0].language.as_deref(), Some("en"));
104
+ assert_eq!(
105
+ entry.content[0].base.as_deref(),
106
+ Some("http://example.com/")
107
+ );
108
+ }
109
+
110
+ #[test]
111
+ fn test_content_encoded_empty_lang_treated_as_none() {
112
+ let mut entry = Entry::default();
113
+
114
+ handle_entry_element("encoded", "<p>Test</p>", &mut entry, Some(""), None);
115
+
116
+ assert_eq!(entry.content.len(), 1);
117
+ assert!(entry.content[0].language.is_none());
118
+ }
81
119
  }
@@ -40,10 +40,12 @@ pub fn handle_feed_element(element: &str, text: &str, feed: &mut FeedMeta) {
40
40
  feed.authors.push(Person::from_name(text));
41
41
  }
42
42
  "date" => {
43
- // dc:date updated (if not already set)
44
- if let Some(dt) = parse_date(text)
45
- && feed.updated.is_none()
46
- {
43
+ if let Some(dt) = parse_date(text) {
44
+ if feed.published.is_none() {
45
+ feed.published = Some(dt);
46
+ feed.published_str = Some(text.to_string());
47
+ }
48
+ // dc:date → updated (always overrides pubDate promotion)
47
49
  feed.updated = Some(dt);
48
50
  feed.updated_str = Some(text.to_string());
49
51
  }
@@ -117,10 +119,13 @@ pub fn handle_entry_element(element: &str, text: &str, entry: &mut Entry) {
117
119
  "date" => {
118
120
  if let Some(dt) = parse_date(text) {
119
121
  entry.dc_date = Some(dt);
120
- if entry.updated.is_none() {
121
- entry.updated = Some(dt);
122
- entry.updated_str = Some(text.to_string());
122
+ if entry.published.is_none() {
123
+ entry.published = Some(dt);
124
+ entry.published_str = Some(text.to_string());
123
125
  }
126
+ // dc:date is more authoritative than pubDate for updated; always override
127
+ entry.updated = Some(dt);
128
+ entry.updated_str = Some(text.to_string());
124
129
  }
125
130
  }
126
131
  "subject" => {
@@ -239,12 +244,12 @@ mod tests {
239
244
  }
240
245
 
241
246
  #[test]
242
- fn test_entry_updated_from_dc_date() {
247
+ fn test_entry_updated_and_published_from_dc_date() {
243
248
  let mut entry = Entry::default();
244
249
  handle_entry_element("date", "2024-01-15T10:30:00Z", &mut entry);
245
250
 
246
251
  assert!(entry.updated.is_some());
247
- assert!(entry.published.is_none());
252
+ assert!(entry.published.is_some());
248
253
  }
249
254
 
250
255
  #[test]