feedparser-rs 0.4.7__tar.gz → 0.5.0__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.
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/Cargo.lock +3 -3
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/Cargo.toml +1 -1
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/PKG-INFO +1 -1
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/examples/parse_file.rs +1 -1
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/examples/podcast_feed.rs +8 -4
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/lib.rs +3 -3
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/namespace/dublin_core.rs +39 -15
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/namespace/georss.rs +23 -23
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/namespace/media_rss.rs +3 -3
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/namespace/mod.rs +21 -0
- feedparser_rs-0.5.0/crates/feedparser-rs-core/src/namespace/slash.rs +117 -0
- feedparser_rs-0.5.0/crates/feedparser-rs-core/src/namespace/threading.rs +264 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/parser/atom.rs +879 -40
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/parser/common.rs +380 -26
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/parser/json.rs +204 -10
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/parser/mod.rs +36 -20
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/parser/namespace_detection.rs +6 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/parser/rss.rs +1124 -87
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/parser/rss10.rs +141 -9
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/types/common.rs +114 -32
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/types/entry.rs +67 -12
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/types/feed.rs +27 -18
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/types/mod.rs +2 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/types/podcast.rs +8 -0
- feedparser_rs-0.5.0/crates/feedparser-rs-core/src/types/thread.rs +113 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/util/date.rs +99 -8
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/util/encoding.rs +25 -14
- feedparser_rs-0.5.0/crates/feedparser-rs-core/src/util/text.rs +174 -0
- feedparser_rs-0.5.0/crates/feedparser-rs-core/tests/encoding_integration.rs +97 -0
- feedparser_rs-0.5.0/crates/feedparser-rs-core/tests/guid_entity_tests.rs +113 -0
- feedparser_rs-0.5.0/crates/feedparser-rs-core/tests/integration_tests.rs +844 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/tests/issue45_test.rs +1 -1
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/tests/namespace_edge_cases.rs +19 -18
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/tests/namespace_integration.rs +150 -16
- feedparser_rs-0.5.0/crates/feedparser-rs-core/tests/test_adversarial_input.rs +120 -0
- feedparser_rs-0.5.0/crates/feedparser-rs-core/tests/test_author_detail.rs +122 -0
- feedparser_rs-0.5.0/crates/feedparser-rs-core/tests/test_entry_author_precedence.rs +65 -0
- feedparser_rs-0.5.0/crates/feedparser-rs-core/tests/test_georss_cc_namespaces.rs +261 -0
- feedparser_rs-0.5.0/crates/feedparser-rs-core/tests/test_parser_limits.rs +527 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/tests/test_rss10.rs +11 -10
- feedparser_rs-0.5.0/crates/feedparser-rs-core/tests/test_slash_wfw_namespaces.rs +153 -0
- feedparser_rs-0.5.0/crates/feedparser-rs-core/tests/test_thr_namespace.rs +59 -0
- feedparser_rs-0.5.0/crates/feedparser-rs-core/tests/test_threading_namespace.rs +191 -0
- feedparser_rs-0.5.0/crates/feedparser-rs-core/tests/test_timezone_preservation.rs +239 -0
- feedparser_rs-0.5.0/crates/feedparser-rs-core/tests/test_truncated_feeds.rs +77 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-py/src/lib.rs +1 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-py/src/types/common.rs +120 -18
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-py/src/types/entry.rs +360 -41
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-py/src/types/feed_meta.rs +199 -40
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-py/src/types/media.rs +58 -10
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-py/src/types/mod.rs +1 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-py/src/types/parsed_feed.rs +14 -0
- feedparser_rs-0.5.0/crates/feedparser-rs-py/src/types/thread.rs +103 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-py/tests/test_basic.py +120 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-py/tests/test_bindings.py +129 -42
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-py/tests/test_compat.py +2 -2
- feedparser_rs-0.5.0/crates/feedparser-rs-py/tests/test_dict_methods.py +191 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-py/tests/test_integration.py +1 -1
- feedparser_rs-0.5.0/crates/feedparser-rs-py/tests/test_itunes_flat_keys.py +84 -0
- feedparser_rs-0.5.0/crates/feedparser-rs-py/tests/test_subscriptable.py +196 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-py/uv.lock +1 -1
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/pyproject.toml +1 -1
- feedparser_rs-0.4.7/crates/feedparser-rs-core/src/util/text.rs +0 -53
- feedparser_rs-0.4.7/crates/feedparser-rs-core/tests/integration_tests.rs +0 -296
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/README.md +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/Cargo.toml +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/README.md +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/benches/parsing.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/benches/types.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/examples/error_handling.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/examples/feeds/malformed_feed.xml +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/examples/feeds/sample_atom.xml +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/examples/feeds/sample_podcast.xml +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/examples/feeds/sample_rss.xml +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/examples/parse_url.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/examples/profile_memory.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/compat/mod.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/error.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/http/client.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/http/mod.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/http/response.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/http/validation.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/limits.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/namespace/cc.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/namespace/content.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/namespace/syndication.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/options.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/parser/detect.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/types/generics.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/types/version.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/util/base_url.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/util/mod.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/util/sanitize.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/tests/http_integration.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/tests/test_podcast_namespace.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/tests/test_url_resolution.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/tests/test_url_security.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-py/Cargo.toml +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-py/README.md +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-py/src/error.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-py/src/limits.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-py/src/types/compat.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-py/src/types/datetime.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-py/src/types/geo.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-py/src/types/podcast.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-py/src/types/syndication.rs +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-py/tests/test_guid_entities.py +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-py/tests/test_syndication.py +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/python/feedparser_rs/__init__.py +0 -0
- {feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/python/feedparser_rs/py.typed +0 -0
|
@@ -600,7 +600,7 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
|
|
600
600
|
|
|
601
601
|
[[package]]
|
|
602
602
|
name = "feedparser-rs"
|
|
603
|
-
version = "0.
|
|
603
|
+
version = "0.5.0"
|
|
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.
|
|
626
|
+
version = "0.5.0"
|
|
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.
|
|
636
|
+
version = "0.5.0"
|
|
637
637
|
dependencies = [
|
|
638
638
|
"chrono",
|
|
639
639
|
"feedparser-rs",
|
{feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/examples/parse_file.rs
RENAMED
|
@@ -109,7 +109,7 @@ fn parse_rss_example() -> Result<(), Box<dyn std::error::Error>> {
|
|
|
109
109
|
if let Some(enclosure_type) = &enc.enclosure_type {
|
|
110
110
|
println!(" Type: {enclosure_type}");
|
|
111
111
|
}
|
|
112
|
-
if let Some(length) = enc.length {
|
|
112
|
+
if let Some(length) = &enc.length {
|
|
113
113
|
println!(" Size: {length} bytes");
|
|
114
114
|
}
|
|
115
115
|
}
|
{feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/examples/podcast_feed.rs
RENAMED
|
@@ -176,10 +176,14 @@ fn display_episodes(feed: &feedparser_rs::ParsedFeed) {
|
|
|
176
176
|
if let Some(enclosure_type) = &enc.enclosure_type {
|
|
177
177
|
println!(" Type: {enclosure_type}");
|
|
178
178
|
}
|
|
179
|
-
if let Some(length) = enc.length {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
179
|
+
if let Some(length) = &enc.length {
|
|
180
|
+
if let Ok(bytes) = length.parse::<u64>() {
|
|
181
|
+
#[allow(clippy::cast_precision_loss)]
|
|
182
|
+
let mb = bytes as f64 / 1_048_576.0;
|
|
183
|
+
println!(" Size: {mb:.2} MB ({bytes} bytes)");
|
|
184
|
+
} else {
|
|
185
|
+
println!(" Size: {length} bytes");
|
|
186
|
+
}
|
|
183
187
|
}
|
|
184
188
|
}
|
|
185
189
|
}
|
|
@@ -188,9 +188,9 @@ 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,
|
|
192
|
-
ItunesEntryMeta, ItunesFeedMeta, ItunesOwner, LimitedCollectionExt, Link,
|
|
193
|
-
MediaThumbnail, MimeType, ParsedFeed, Person, PodcastChapters, PodcastEntryMeta,
|
|
191
|
+
Content, Email, Enclosure, Entry, FeedMeta, FeedVersion, Generator, Image, InReplyTo,
|
|
192
|
+
ItunesCategory, ItunesEntryMeta, ItunesFeedMeta, ItunesOwner, LimitedCollectionExt, Link,
|
|
193
|
+
MediaContent, MediaThumbnail, MimeType, ParsedFeed, Person, PodcastChapters, PodcastEntryMeta,
|
|
194
194
|
PodcastFunding, PodcastMeta, PodcastPerson, PodcastSoundbite, PodcastTranscript, PodcastValue,
|
|
195
195
|
PodcastValueRecipient, Source, Tag, TextConstruct, TextType, Url, parse_duration,
|
|
196
196
|
parse_explicit,
|
{feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/namespace/dublin_core.rs
RENAMED
|
@@ -32,10 +32,8 @@ pub const DC_NAMESPACE: &str = "http://purl.org/dc/elements/1.1/";
|
|
|
32
32
|
pub fn handle_feed_element(element: &str, text: &str, feed: &mut FeedMeta) {
|
|
33
33
|
match element {
|
|
34
34
|
"creator" => {
|
|
35
|
-
// dc:creator → author (
|
|
36
|
-
|
|
37
|
-
feed.author = Some(text.into());
|
|
38
|
-
}
|
|
35
|
+
// dc:creator → author (always overrides)
|
|
36
|
+
feed.author = Some(text.into());
|
|
39
37
|
// Store in dc_creator field
|
|
40
38
|
feed.dc_creator = Some(text.into());
|
|
41
39
|
// Also add to authors list
|
|
@@ -47,6 +45,7 @@ pub fn handle_feed_element(element: &str, text: &str, feed: &mut FeedMeta) {
|
|
|
47
45
|
&& feed.updated.is_none()
|
|
48
46
|
{
|
|
49
47
|
feed.updated = Some(dt);
|
|
48
|
+
feed.updated_str = Some(text.to_string());
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
"subject" => {
|
|
@@ -111,18 +110,16 @@ pub fn handle_feed_element(element: &str, text: &str, feed: &mut FeedMeta) {
|
|
|
111
110
|
pub fn handle_entry_element(element: &str, text: &str, entry: &mut Entry) {
|
|
112
111
|
match element {
|
|
113
112
|
"creator" => {
|
|
114
|
-
|
|
115
|
-
entry.author = Some(text.into());
|
|
116
|
-
}
|
|
113
|
+
// dc:creator takes precedence over <author>; reconciled at </item>
|
|
117
114
|
entry.dc_creator = Some(text.into());
|
|
118
115
|
entry.authors.push(Person::from_name(text));
|
|
119
116
|
}
|
|
120
117
|
"date" => {
|
|
121
118
|
if let Some(dt) = parse_date(text) {
|
|
122
119
|
entry.dc_date = Some(dt);
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
entry.
|
|
120
|
+
if entry.updated.is_none() {
|
|
121
|
+
entry.updated = Some(dt);
|
|
122
|
+
entry.updated_str = Some(text.to_string());
|
|
126
123
|
}
|
|
127
124
|
}
|
|
128
125
|
}
|
|
@@ -149,6 +146,9 @@ pub fn handle_entry_element(element: &str, text: &str, entry: &mut Entry) {
|
|
|
149
146
|
entry.contributors.push(Person::from_name(text));
|
|
150
147
|
}
|
|
151
148
|
"rights" => {
|
|
149
|
+
if entry.rights.is_none() {
|
|
150
|
+
entry.rights = Some(text.to_string());
|
|
151
|
+
}
|
|
152
152
|
entry.dc_rights = Some(text.to_string());
|
|
153
153
|
}
|
|
154
154
|
_ => {
|
|
@@ -177,8 +177,8 @@ mod tests {
|
|
|
177
177
|
handle_feed_element("creator", "Alice", &mut feed);
|
|
178
178
|
handle_feed_element("creator", "Bob", &mut feed);
|
|
179
179
|
|
|
180
|
-
//
|
|
181
|
-
assert_eq!(feed.author.as_deref(), Some("
|
|
180
|
+
// Last dc:creator wins (always overrides)
|
|
181
|
+
assert_eq!(feed.author.as_deref(), Some("Bob"));
|
|
182
182
|
// Both are in authors list
|
|
183
183
|
assert_eq!(feed.authors.len(), 2);
|
|
184
184
|
}
|
|
@@ -232,16 +232,40 @@ mod tests {
|
|
|
232
232
|
handle_entry_element("subject", "Tech", &mut entry);
|
|
233
233
|
handle_entry_element("description", "Entry summary", &mut entry);
|
|
234
234
|
|
|
235
|
-
|
|
235
|
+
// dc:creator is stored in dc_creator; entry.author is reconciled at </item> in rss.rs
|
|
236
|
+
assert_eq!(entry.dc_creator.as_deref(), Some("Jane Doe"));
|
|
236
237
|
assert_eq!(entry.tags.len(), 1);
|
|
237
238
|
assert_eq!(entry.summary.as_deref(), Some("Entry summary"));
|
|
238
239
|
}
|
|
239
240
|
|
|
240
241
|
#[test]
|
|
241
|
-
fn
|
|
242
|
+
fn test_entry_updated_from_dc_date() {
|
|
242
243
|
let mut entry = Entry::default();
|
|
243
244
|
handle_entry_element("date", "2024-01-15T10:30:00Z", &mut entry);
|
|
244
245
|
|
|
245
|
-
assert!(entry.
|
|
246
|
+
assert!(entry.updated.is_some());
|
|
247
|
+
assert!(entry.published.is_none());
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
#[test]
|
|
251
|
+
fn test_entry_dc_rights_sets_both_fields() {
|
|
252
|
+
let mut entry = Entry::default();
|
|
253
|
+
handle_entry_element("rights", "Copyright 2024 Example", &mut entry);
|
|
254
|
+
|
|
255
|
+
assert_eq!(entry.rights.as_deref(), Some("Copyright 2024 Example"));
|
|
256
|
+
assert_eq!(entry.dc_rights.as_deref(), Some("Copyright 2024 Example"));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
#[test]
|
|
260
|
+
fn test_entry_dc_rights_does_not_overwrite_rights() {
|
|
261
|
+
let mut entry = Entry {
|
|
262
|
+
rights: Some("Atom rights".to_string()),
|
|
263
|
+
..Default::default()
|
|
264
|
+
};
|
|
265
|
+
handle_entry_element("rights", "DC rights", &mut entry);
|
|
266
|
+
|
|
267
|
+
// dc:rights should not overwrite rights already set (e.g. by Atom <rights>)
|
|
268
|
+
assert_eq!(entry.rights.as_deref(), Some("Atom rights"));
|
|
269
|
+
assert_eq!(entry.dc_rights.as_deref(), Some("DC rights"));
|
|
246
270
|
}
|
|
247
271
|
}
|
{feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/namespace/georss.rs
RENAMED
|
@@ -178,25 +178,25 @@ pub fn handle_entry_element(
|
|
|
178
178
|
match tag {
|
|
179
179
|
b"point" => {
|
|
180
180
|
if let Some(loc) = parse_point(text) {
|
|
181
|
-
entry.
|
|
181
|
+
entry.r#where = Some(Box::new(loc));
|
|
182
182
|
}
|
|
183
183
|
true
|
|
184
184
|
}
|
|
185
185
|
b"line" => {
|
|
186
186
|
if let Some(loc) = parse_line(text) {
|
|
187
|
-
entry.
|
|
187
|
+
entry.r#where = Some(Box::new(loc));
|
|
188
188
|
}
|
|
189
189
|
true
|
|
190
190
|
}
|
|
191
191
|
b"polygon" => {
|
|
192
192
|
if let Some(loc) = parse_polygon(text) {
|
|
193
|
-
entry.
|
|
193
|
+
entry.r#where = Some(Box::new(loc));
|
|
194
194
|
}
|
|
195
195
|
true
|
|
196
196
|
}
|
|
197
197
|
b"box" => {
|
|
198
198
|
if let Some(loc) = parse_box(text) {
|
|
199
|
-
entry.
|
|
199
|
+
entry.r#where = Some(Box::new(loc));
|
|
200
200
|
}
|
|
201
201
|
true
|
|
202
202
|
}
|
|
@@ -225,25 +225,25 @@ pub fn handle_feed_element(
|
|
|
225
225
|
match tag {
|
|
226
226
|
b"point" => {
|
|
227
227
|
if let Some(loc) = parse_point(text) {
|
|
228
|
-
feed.
|
|
228
|
+
feed.r#where = Some(Box::new(loc));
|
|
229
229
|
}
|
|
230
230
|
true
|
|
231
231
|
}
|
|
232
232
|
b"line" => {
|
|
233
233
|
if let Some(loc) = parse_line(text) {
|
|
234
|
-
feed.
|
|
234
|
+
feed.r#where = Some(Box::new(loc));
|
|
235
235
|
}
|
|
236
236
|
true
|
|
237
237
|
}
|
|
238
238
|
b"polygon" => {
|
|
239
239
|
if let Some(loc) = parse_polygon(text) {
|
|
240
|
-
feed.
|
|
240
|
+
feed.r#where = Some(Box::new(loc));
|
|
241
241
|
}
|
|
242
242
|
true
|
|
243
243
|
}
|
|
244
244
|
b"box" => {
|
|
245
245
|
if let Some(loc) = parse_box(text) {
|
|
246
|
-
feed.
|
|
246
|
+
feed.r#where = Some(Box::new(loc));
|
|
247
247
|
}
|
|
248
248
|
true
|
|
249
249
|
}
|
|
@@ -426,9 +426,9 @@ mod tests {
|
|
|
426
426
|
|
|
427
427
|
let handled = handle_entry_element(b"point", "45.256 -71.92", &mut entry, &limits);
|
|
428
428
|
assert!(handled);
|
|
429
|
-
assert!(entry.
|
|
429
|
+
assert!(entry.r#where.is_some());
|
|
430
430
|
|
|
431
|
-
let geo = entry.
|
|
431
|
+
let geo = entry.r#where.as_ref().unwrap();
|
|
432
432
|
assert_eq!(geo.geo_type, GeoType::Point);
|
|
433
433
|
assert_eq!(geo.coordinates[0], (45.256, -71.92));
|
|
434
434
|
}
|
|
@@ -441,8 +441,8 @@ mod tests {
|
|
|
441
441
|
let handled =
|
|
442
442
|
handle_entry_element(b"line", "45.256 -71.92 46.0 -72.0", &mut entry, &limits);
|
|
443
443
|
assert!(handled);
|
|
444
|
-
assert!(entry.
|
|
445
|
-
assert_eq!(entry.
|
|
444
|
+
assert!(entry.r#where.is_some());
|
|
445
|
+
assert_eq!(entry.r#where.as_ref().unwrap().geo_type, GeoType::Line);
|
|
446
446
|
}
|
|
447
447
|
|
|
448
448
|
#[test]
|
|
@@ -452,7 +452,7 @@ mod tests {
|
|
|
452
452
|
|
|
453
453
|
let handled = handle_entry_element(b"unknown", "data", &mut entry, &limits);
|
|
454
454
|
assert!(!handled);
|
|
455
|
-
assert!(entry.
|
|
455
|
+
assert!(entry.r#where.is_none());
|
|
456
456
|
}
|
|
457
457
|
|
|
458
458
|
#[test]
|
|
@@ -487,9 +487,9 @@ mod tests {
|
|
|
487
487
|
|
|
488
488
|
let handled = handle_feed_element(b"point", "45.256 -71.92", &mut feed, &limits);
|
|
489
489
|
assert!(handled);
|
|
490
|
-
assert!(feed.
|
|
490
|
+
assert!(feed.r#where.is_some());
|
|
491
491
|
|
|
492
|
-
let geo = feed.
|
|
492
|
+
let geo = feed.r#where.as_ref().unwrap();
|
|
493
493
|
assert_eq!(geo.geo_type, GeoType::Point);
|
|
494
494
|
assert_eq!(geo.coordinates[0], (45.256, -71.92));
|
|
495
495
|
}
|
|
@@ -501,8 +501,8 @@ mod tests {
|
|
|
501
501
|
|
|
502
502
|
let handled = handle_feed_element(b"line", "45.256 -71.92 46.0 -72.0", &mut feed, &limits);
|
|
503
503
|
assert!(handled);
|
|
504
|
-
assert!(feed.
|
|
505
|
-
assert_eq!(feed.
|
|
504
|
+
assert!(feed.r#where.is_some());
|
|
505
|
+
assert_eq!(feed.r#where.as_ref().unwrap().geo_type, GeoType::Line);
|
|
506
506
|
}
|
|
507
507
|
|
|
508
508
|
#[test]
|
|
@@ -517,8 +517,8 @@ mod tests {
|
|
|
517
517
|
&limits,
|
|
518
518
|
);
|
|
519
519
|
assert!(handled);
|
|
520
|
-
assert!(feed.
|
|
521
|
-
assert_eq!(feed.
|
|
520
|
+
assert!(feed.r#where.is_some());
|
|
521
|
+
assert_eq!(feed.r#where.as_ref().unwrap().geo_type, GeoType::Polygon);
|
|
522
522
|
}
|
|
523
523
|
|
|
524
524
|
#[test]
|
|
@@ -528,8 +528,8 @@ mod tests {
|
|
|
528
528
|
|
|
529
529
|
let handled = handle_feed_element(b"box", "45.0 -72.0 46.0 -71.0", &mut feed, &limits);
|
|
530
530
|
assert!(handled);
|
|
531
|
-
assert!(feed.
|
|
532
|
-
assert_eq!(feed.
|
|
531
|
+
assert!(feed.r#where.is_some());
|
|
532
|
+
assert_eq!(feed.r#where.as_ref().unwrap().geo_type, GeoType::Box);
|
|
533
533
|
}
|
|
534
534
|
|
|
535
535
|
#[test]
|
|
@@ -539,7 +539,7 @@ mod tests {
|
|
|
539
539
|
|
|
540
540
|
let handled = handle_feed_element(b"unknown", "data", &mut feed, &limits);
|
|
541
541
|
assert!(!handled);
|
|
542
|
-
assert!(feed.
|
|
542
|
+
assert!(feed.r#where.is_none());
|
|
543
543
|
}
|
|
544
544
|
|
|
545
545
|
#[test]
|
|
@@ -549,6 +549,6 @@ mod tests {
|
|
|
549
549
|
|
|
550
550
|
let handled = handle_feed_element(b"point", "invalid data", &mut feed, &limits);
|
|
551
551
|
assert!(handled);
|
|
552
|
-
assert!(feed.
|
|
552
|
+
assert!(feed.r#where.is_none());
|
|
553
553
|
}
|
|
554
554
|
}
|
{feedparser_rs-0.4.7 → feedparser_rs-0.5.0}/crates/feedparser-rs-core/src/namespace/media_rss.rs
RENAMED
|
@@ -198,13 +198,13 @@ pub fn handle_entry_element(element: &str, text: &str, entry: &mut Entry) {
|
|
|
198
198
|
/// let enclosure = media_content_to_enclosure(&content);
|
|
199
199
|
/// assert_eq!(enclosure.url, "https://example.com/video.mp4");
|
|
200
200
|
/// assert_eq!(enclosure.enclosure_type.as_deref(), Some("video/mp4"));
|
|
201
|
-
/// assert_eq!(enclosure.length, Some(
|
|
201
|
+
/// assert_eq!(enclosure.length.as_deref(), Some("1024000"));
|
|
202
202
|
/// ```
|
|
203
203
|
pub fn media_content_to_enclosure(content: &MediaContent) -> Enclosure {
|
|
204
204
|
Enclosure {
|
|
205
205
|
url: content.url.clone().into(),
|
|
206
206
|
enclosure_type: content.type_.as_ref().map(|t| t.clone().into()),
|
|
207
|
-
length: content.file_size,
|
|
207
|
+
length: content.file_size.map(|v| v.to_string()),
|
|
208
208
|
}
|
|
209
209
|
}
|
|
210
210
|
|
|
@@ -412,7 +412,7 @@ mod tests {
|
|
|
412
412
|
|
|
413
413
|
assert_eq!(enclosure.url, "https://example.com/video.mp4");
|
|
414
414
|
assert_eq!(enclosure.enclosure_type.as_deref(), Some("video/mp4"));
|
|
415
|
-
assert_eq!(enclosure.length, Some(
|
|
415
|
+
assert_eq!(enclosure.length.as_deref(), Some("1024000"));
|
|
416
416
|
}
|
|
417
417
|
|
|
418
418
|
#[test]
|
|
@@ -8,6 +8,8 @@
|
|
|
8
8
|
/// - **Media RSS** (`media:`) - Multimedia content
|
|
9
9
|
/// - **GeoRSS** (`georss:`) - Geographic location data
|
|
10
10
|
/// - **Creative Commons** (`cc:`) - License information
|
|
11
|
+
/// - **Slash** (`slash:`) - Comment count
|
|
12
|
+
/// - **WFW** (`wfw:`) - Comment RSS URL
|
|
11
13
|
///
|
|
12
14
|
/// # Usage
|
|
13
15
|
///
|
|
@@ -35,8 +37,12 @@ pub mod dublin_core;
|
|
|
35
37
|
pub mod georss;
|
|
36
38
|
/// Media RSS specification
|
|
37
39
|
pub mod media_rss;
|
|
40
|
+
/// Slash and WFW namespace (comment count + comment RSS URL)
|
|
41
|
+
pub mod slash;
|
|
38
42
|
/// Syndication Module for RSS 1.0
|
|
39
43
|
pub mod syndication;
|
|
44
|
+
/// Atom Threading Extensions (RFC 4685)
|
|
45
|
+
pub mod threading;
|
|
40
46
|
|
|
41
47
|
/// Common namespace URIs used in feeds
|
|
42
48
|
pub mod namespaces {
|
|
@@ -75,6 +81,15 @@ pub mod namespaces {
|
|
|
75
81
|
|
|
76
82
|
/// Creative Commons (legacy Userland)
|
|
77
83
|
pub const CREATIVE_COMMONS: &str = "http://backend.userland.com/creativeCommonsRssModule";
|
|
84
|
+
|
|
85
|
+
/// Atom Threading Extensions (RFC 4685)
|
|
86
|
+
pub const THREADING: &str = "http://purl.org/syndication/thread/1.0";
|
|
87
|
+
|
|
88
|
+
/// Slash namespace
|
|
89
|
+
pub const SLASH: &str = "http://purl.org/rss/1.0/modules/slash/";
|
|
90
|
+
|
|
91
|
+
/// WFW (Well-Formed Web) namespace
|
|
92
|
+
pub const WFW: &str = "http://wellformedweb.org/CommentAPI/";
|
|
78
93
|
}
|
|
79
94
|
|
|
80
95
|
/// Get namespace URI for a common prefix
|
|
@@ -99,6 +114,9 @@ pub fn get_namespace_uri(prefix: &str) -> Option<&'static str> {
|
|
|
99
114
|
"georss" => Some(namespaces::GEORSS),
|
|
100
115
|
"cc" => Some(namespaces::CC),
|
|
101
116
|
"creativeCommons" => Some(namespaces::CREATIVE_COMMONS),
|
|
117
|
+
"thr" => Some(namespaces::THREADING),
|
|
118
|
+
"slash" => Some(namespaces::SLASH),
|
|
119
|
+
"wfw" => Some(namespaces::WFW),
|
|
102
120
|
_ => None,
|
|
103
121
|
}
|
|
104
122
|
}
|
|
@@ -125,6 +143,9 @@ pub fn get_namespace_prefix(uri: &str) -> Option<&'static str> {
|
|
|
125
143
|
namespaces::GEORSS => Some("georss"),
|
|
126
144
|
namespaces::CC => Some("cc"),
|
|
127
145
|
namespaces::CREATIVE_COMMONS => Some("creativeCommons"),
|
|
146
|
+
namespaces::THREADING => Some("thr"),
|
|
147
|
+
namespaces::SLASH => Some("slash"),
|
|
148
|
+
namespaces::WFW => Some("wfw"),
|
|
128
149
|
_ => None,
|
|
129
150
|
}
|
|
130
151
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/// Slash namespace — `http://purl.org/rss/1.0/modules/slash/`
|
|
2
|
+
/// WFW (Well-Formed Web) namespace — `http://wellformedweb.org/CommentAPI/`
|
|
3
|
+
///
|
|
4
|
+
/// These namespaces are commonly used together in `WordPress`, `Blogger`, and
|
|
5
|
+
/// `Movable Type` feeds to expose comment metadata.
|
|
6
|
+
///
|
|
7
|
+
/// - `slash:comments` — integer count of comments on an entry
|
|
8
|
+
/// - `wfw:commentRss` — URL of the entry's comment RSS feed
|
|
9
|
+
use crate::types::Entry;
|
|
10
|
+
|
|
11
|
+
/// Slash namespace URI
|
|
12
|
+
pub const SLASH_NAMESPACE: &str = "http://purl.org/rss/1.0/modules/slash/";
|
|
13
|
+
|
|
14
|
+
/// WFW namespace URI
|
|
15
|
+
pub const WFW_NAMESPACE: &str = "http://wellformedweb.org/CommentAPI/";
|
|
16
|
+
|
|
17
|
+
/// Handle a Slash namespace element at entry level
|
|
18
|
+
///
|
|
19
|
+
/// # Arguments
|
|
20
|
+
///
|
|
21
|
+
/// * `element` - Local name of the element (without namespace prefix)
|
|
22
|
+
/// * `text` - Text content of the element
|
|
23
|
+
/// * `entry` - Entry to update
|
|
24
|
+
pub fn handle_slash_entry_element(element: &str, text: &str, entry: &mut Entry) {
|
|
25
|
+
if element == "comments" {
|
|
26
|
+
// Ignore non-integer values silently (bozo pattern: keep parsing)
|
|
27
|
+
entry.slash_comments = text.trim().parse::<u32>().ok();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/// Handle a WFW namespace element at entry level
|
|
32
|
+
///
|
|
33
|
+
/// # Arguments
|
|
34
|
+
///
|
|
35
|
+
/// * `element` - Local name of the element (without namespace prefix)
|
|
36
|
+
/// * `text` - Text content of the element
|
|
37
|
+
/// * `entry` - Entry to update
|
|
38
|
+
pub fn handle_wfw_entry_element(element: &str, text: &str, entry: &mut Entry) {
|
|
39
|
+
if element == "commentRss" || element == "commentrss" {
|
|
40
|
+
entry.wfw_comment_rss = Some(text.trim().to_string());
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
#[cfg(test)]
|
|
45
|
+
mod tests {
|
|
46
|
+
use super::*;
|
|
47
|
+
|
|
48
|
+
#[test]
|
|
49
|
+
fn test_slash_comments_integer() {
|
|
50
|
+
let mut entry = Entry::default();
|
|
51
|
+
handle_slash_entry_element("comments", "42", &mut entry);
|
|
52
|
+
assert_eq!(entry.slash_comments, Some(42));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#[test]
|
|
56
|
+
fn test_slash_comments_with_whitespace() {
|
|
57
|
+
let mut entry = Entry::default();
|
|
58
|
+
handle_slash_entry_element("comments", " 7 ", &mut entry);
|
|
59
|
+
assert_eq!(entry.slash_comments, Some(7));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
#[test]
|
|
63
|
+
fn test_slash_comments_zero() {
|
|
64
|
+
let mut entry = Entry::default();
|
|
65
|
+
handle_slash_entry_element("comments", "0", &mut entry);
|
|
66
|
+
assert_eq!(entry.slash_comments, Some(0));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#[test]
|
|
70
|
+
fn test_slash_comments_invalid_is_ignored() {
|
|
71
|
+
let mut entry = Entry::default();
|
|
72
|
+
handle_slash_entry_element("comments", "not-a-number", &mut entry);
|
|
73
|
+
assert_eq!(entry.slash_comments, None);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
#[test]
|
|
77
|
+
fn test_slash_unknown_element_ignored() {
|
|
78
|
+
let mut entry = Entry::default();
|
|
79
|
+
handle_slash_entry_element("section", "tech", &mut entry);
|
|
80
|
+
assert_eq!(entry.slash_comments, None);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#[test]
|
|
84
|
+
fn test_wfw_comment_rss() {
|
|
85
|
+
let mut entry = Entry::default();
|
|
86
|
+
handle_wfw_entry_element(
|
|
87
|
+
"commentRss",
|
|
88
|
+
"https://example.com/post/comments/feed/",
|
|
89
|
+
&mut entry,
|
|
90
|
+
);
|
|
91
|
+
assert_eq!(
|
|
92
|
+
entry.wfw_comment_rss.as_deref(),
|
|
93
|
+
Some("https://example.com/post/comments/feed/")
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
#[test]
|
|
98
|
+
fn test_wfw_comment_rss_lowercase_variant() {
|
|
99
|
+
let mut entry = Entry::default();
|
|
100
|
+
handle_wfw_entry_element(
|
|
101
|
+
"commentrss",
|
|
102
|
+
"https://example.com/post/comments/feed/",
|
|
103
|
+
&mut entry,
|
|
104
|
+
);
|
|
105
|
+
assert_eq!(
|
|
106
|
+
entry.wfw_comment_rss.as_deref(),
|
|
107
|
+
Some("https://example.com/post/comments/feed/")
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#[test]
|
|
112
|
+
fn test_wfw_unknown_element_ignored() {
|
|
113
|
+
let mut entry = Entry::default();
|
|
114
|
+
handle_wfw_entry_element("other", "value", &mut entry);
|
|
115
|
+
assert_eq!(entry.wfw_comment_rss, None);
|
|
116
|
+
}
|
|
117
|
+
}
|