Procházet zdrojové kódy

feat(atom): inherit feed-level xml:lang on entries

Per the XML specification, xml:lang applies to the whole subtree it is
declared on, so an Atom entry without its own xml:lang attribute takes
the language declared on the feed element. The parser previously left
such entries with an empty language, and only the web UI compensated
with a template-level fallback; API consumers reading entries.language
saw an empty value even when the feed declared one.

Apply the fallback in the Atom 1.0 and 0.3 adapters so the inherited
value is persisted and exposed everywhere.

Known limitation: an explicit xml:lang="" on an entry, which the spec
defines as undefining the language for that subtree, cannot be
distinguished from an absent attribute with a plain string field and
therefore inherits as well.
Fred před 1 dnem
rodič
revize
ab8f7f9eb4

+ 6 - 0
internal/reader/atom/atom_03_adapter.go

@@ -52,7 +52,13 @@ func (a *atom03Adapter) buildFeed(baseURL string) *model.Feed {
 	for _, atomEntry := range a.atomFeed.Entries {
 		entry := model.NewEntry()
 
+		// Populate the entry language. xml:lang applies to the whole
+		// subtree it is declared on, so an entry without its own
+		// xml:lang inherits the feed-level value.
 		entry.Language = model.NormalizeLanguage(atomEntry.Language)
+		if entry.Language == "" {
+			entry.Language = model.NormalizeLanguage(a.atomFeed.Language)
+		}
 
 		// Populate the entry URL.
 		entry.URL = atomEntry.Links.originalLink()

+ 15 - 4
internal/reader/atom/atom_03_test.go

@@ -301,13 +301,20 @@ func TestParseAtom03WithLanguage(t *testing.T) {
 		<title>dive into mark</title>
 		<link rel="alternate" type="text/html" href="http://diveintomark.org/"/>
 		<modified>2003-12-13T18:30:02Z</modified>
-		<entry xml:lang="fr-CA" foo:lang="zz">
+		<entry xml:lang="pt-BR" foo:lang="zz">
 			<title>Atom 0.3 snapshot</title>
 			<link rel="alternate" type="text/html" href="http://diveintomark.org/2003/12/13/atom03"/>
 			<id>tag:diveintomark.org,2003:3.2397</id>
 			<issued>2003-12-13T08:29:29-04:00</issued>
 			<modified>2003-12-13T18:30:02Z</modified>
 		</entry>
+		<entry>
+			<title>Atom 0.3 second snapshot</title>
+			<link rel="alternate" type="text/html" href="http://diveintomark.org/2003/12/14/atom03"/>
+			<id>tag:diveintomark.org,2003:3.2398</id>
+			<issued>2003-12-14T08:29:29-04:00</issued>
+			<modified>2003-12-14T18:30:02Z</modified>
+		</entry>
 	</feed>`
 
 	feed, err := Parse("http://diveintomark.org/atom.xml", bytes.NewReader([]byte(data)), "0.3")
@@ -319,11 +326,15 @@ func TestParseAtom03WithLanguage(t *testing.T) {
 		t.Errorf("Incorrect language, got: %q", feed.Language)
 	}
 
-	if len(feed.Entries) != 1 {
-		t.Fatalf("Expected 1 entry, got: %d", len(feed.Entries))
+	if len(feed.Entries) != 2 {
+		t.Fatalf("Expected 2 entries, got: %d", len(feed.Entries))
 	}
 
-	if feed.Entries[0].Language != "fr-ca" {
+	if feed.Entries[0].Language != "pt-br" {
 		t.Errorf("Incorrect entry language, got: %q", feed.Entries[0].Language)
 	}
+
+	if feed.Entries[1].Language != "fr-ca" {
+		t.Errorf("Expected entry to inherit feed language, got: %q", feed.Entries[1].Language)
+	}
 }

+ 6 - 0
internal/reader/atom/atom_10_adapter.go

@@ -111,7 +111,13 @@ func (a *atom10Adapter) populateEntries(siteURL string) model.Entries {
 			}
 		}
 
+		// Populate the entry language. xml:lang applies to the whole
+		// subtree it is declared on, so an entry without its own
+		// xml:lang inherits the feed-level value.
 		entry.Language = model.NormalizeLanguage(atomEntry.Language)
+		if entry.Language == "" {
+			entry.Language = model.NormalizeLanguage(a.atomFeed.Language)
+		}
 
 		// Populate the entry author.
 		authors := atomEntry.Authors.personNames()

+ 30 - 1
internal/reader/atom/atom_10_test.go

@@ -1933,7 +1933,7 @@ func TestParseEntryWithLanguage(t *testing.T) {
 	}
 }
 
-func TestParseEntryWithoutLanguage(t *testing.T) {
+func TestParseEntryWithoutLanguageInheritsFeedLanguage(t *testing.T) {
 	data := `<?xml version="1.0" encoding="utf-8"?>
 	<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
 		<title>Example Feed</title>
@@ -1957,6 +1957,35 @@ func TestParseEntryWithoutLanguage(t *testing.T) {
 		t.Fatalf("Expected 1 entry, got: %d", len(feed.Entries))
 	}
 
+	if feed.Entries[0].Language != "en" {
+		t.Errorf("Expected entry to inherit feed language, got: %q", feed.Entries[0].Language)
+	}
+}
+
+func TestParseEntryWithoutAnyLanguage(t *testing.T) {
+	data := `<?xml version="1.0" encoding="utf-8"?>
+	<feed xmlns="http://www.w3.org/2005/Atom">
+		<title>Example Feed</title>
+		<link href="http://example.org/"/>
+		<updated>2003-12-13T18:30:02Z</updated>
+		<id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>
+		<entry>
+			<title>Hello</title>
+			<link href="http://example.org/2003/12/13/hello"/>
+			<id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
+			<updated>2003-12-13T18:30:02Z</updated>
+		</entry>
+	</feed>`
+
+	feed, err := Parse("http://example.org/feed.xml", bytes.NewReader([]byte(data)), "10")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if len(feed.Entries) != 1 {
+		t.Fatalf("Expected 1 entry, got: %d", len(feed.Entries))
+	}
+
 	if feed.Entries[0].Language != "" {
 		t.Errorf("Expected empty entry language, got: %q", feed.Entries[0].Language)
 	}