Skip to content

Commit 493eda3

Browse files
committed
feat(v2): Change custom namespace to '_custom' and add Atom support
- Change namespace key from 'rss' to '_custom' to avoid conflicts - '_custom' clearly indicates non-namespaced custom elements - Add custom element support to Atom parser for both feed and entry levels - Update all tests and test data to use '_custom' namespace - Add GetCustomValue helper method for Feed type - Add comprehensive Atom custom element test coverage The '_custom' namespace is less likely to conflict with actual XML namespaces and clearly indicates its special purpose for non-namespaced elements across all feed formats.
1 parent 934f418 commit 493eda3

17 files changed

+298
-37
lines changed

atom/parser.go

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -161,10 +161,36 @@ func (ap *Parser) parseRoot(p *xpp.XMLPullParser) (*Feed, error) {
161161
}
162162
atom.Entries = append(atom.Entries, result)
163163
} else {
164-
err := p.Skip()
165-
if err != nil {
166-
return nil, err
167-
}
164+
// For non-standard Atom feed elements, add them to extensions
165+
// under a special "_custom" namespace prefix
166+
customExt := ext.Extension{
167+
Name: p.Name,
168+
Attrs: make(map[string]string),
169+
}
170+
171+
// Copy attributes
172+
for _, attr := range p.Attrs {
173+
customExt.Attrs[attr.Name.Local] = attr.Value
174+
}
175+
176+
// Parse the text content
177+
result, err := shared.ParseText(p)
178+
if err != nil {
179+
p.Skip()
180+
continue
181+
}
182+
customExt.Value = result
183+
184+
// Initialize extensions map if needed
185+
if extensions == nil {
186+
extensions = make(ext.Extensions)
187+
}
188+
if extensions["_custom"] == nil {
189+
extensions["_custom"] = make(map[string][]ext.Extension)
190+
}
191+
192+
// Add to extensions
193+
extensions["_custom"][p.Name] = append(extensions["_custom"][p.Name], customExt)
168194
}
169195
}
170196
}
@@ -314,10 +340,36 @@ func (ap *Parser) parseEntry(p *xpp.XMLPullParser) (*Entry, error) {
314340
}
315341
entry.Content = result
316342
} else {
317-
err := p.Skip()
318-
if err != nil {
319-
return nil, err
320-
}
343+
// For non-standard Atom entry elements, add them to extensions
344+
// under a special "_custom" namespace prefix
345+
customExt := ext.Extension{
346+
Name: p.Name,
347+
Attrs: make(map[string]string),
348+
}
349+
350+
// Copy attributes
351+
for _, attr := range p.Attrs {
352+
customExt.Attrs[attr.Name.Local] = attr.Value
353+
}
354+
355+
// Parse the text content
356+
result, err := shared.ParseText(p)
357+
if err != nil {
358+
p.Skip()
359+
continue
360+
}
361+
customExt.Value = result
362+
363+
// Initialize extensions map if needed
364+
if extensions == nil {
365+
extensions = make(ext.Extensions)
366+
}
367+
if extensions["_custom"] == nil {
368+
extensions["_custom"] = make(map[string][]ext.Extension)
369+
}
370+
371+
// Add to extensions
372+
extensions["_custom"][p.Name] = append(extensions["_custom"][p.Name], customExt)
321373
}
322374
}
323375
}

extension_helpers.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,15 @@ func (i *Item) GetExtensionValue(namespace, element string) string {
5656
return exts[0].Value
5757
}
5858

59-
// GetCustomValue retrieves the text value of a non-namespaced custom RSS element.
59+
// GetCustomValue retrieves the text value of a non-namespaced custom element.
6060
// This is a convenience method that replaces the previous Item.Custom[key] access pattern.
6161
// Returns empty string if the element is not found.
6262
func (i *Item) GetCustomValue(element string) string {
63-
return i.GetExtensionValue("rss", element)
63+
return i.GetExtensionValue("_custom", element)
64+
}
65+
66+
// GetCustomValue retrieves the text value of a non-namespaced custom element at the feed level.
67+
// Returns empty string if the element is not found.
68+
func (f *Feed) GetCustomValue(element string) string {
69+
return f.GetExtensionValue("_custom", element)
6470
}

extension_helpers_test.go

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ func TestItemGetExtension(t *testing.T) {
1515
{Name: "creator", Value: "John Doe"},
1616
},
1717
},
18-
"rss": {
18+
"_custom": {
1919
"customField": []ext.Extension{
2020
{Name: "customField", Value: "Custom Value", Attrs: map[string]string{"id": "123"}},
2121
},
@@ -28,8 +28,8 @@ func TestItemGetExtension(t *testing.T) {
2828
assert.Len(t, creators, 1)
2929
assert.Equal(t, "John Doe", creators[0].Value)
3030

31-
// Test getting custom RSS element
32-
custom := item.GetExtension("rss", "customField")
31+
// Test getting custom element
32+
custom := item.GetExtension("_custom", "customField")
3333
assert.Len(t, custom, 1)
3434
assert.Equal(t, "Custom Value", custom[0].Value)
3535
assert.Equal(t, "123", custom[0].Attrs["id"])
@@ -47,7 +47,7 @@ func TestItemGetExtensionValue(t *testing.T) {
4747
{Name: "creator", Value: "John Doe"},
4848
},
4949
},
50-
"rss": {
50+
"_custom": {
5151
"customField": []ext.Extension{
5252
{Name: "customField", Value: "Custom Value"},
5353
},
@@ -57,7 +57,7 @@ func TestItemGetExtensionValue(t *testing.T) {
5757

5858
// Test getting existing value
5959
assert.Equal(t, "John Doe", item.GetExtensionValue("dc", "creator"))
60-
assert.Equal(t, "Custom Value", item.GetExtensionValue("rss", "customField"))
60+
assert.Equal(t, "Custom Value", item.GetExtensionValue("_custom", "customField"))
6161

6262
// Test getting non-existent value
6363
assert.Equal(t, "", item.GetExtensionValue("missing", "field"))
@@ -66,7 +66,7 @@ func TestItemGetExtensionValue(t *testing.T) {
6666
func TestItemGetCustomValue(t *testing.T) {
6767
item := &Item{
6868
Extensions: ext.Extensions{
69-
"rss": {
69+
"_custom": {
7070
"customField": []ext.Extension{
7171
{Name: "customField", Value: "Custom Value"},
7272
},
@@ -93,7 +93,7 @@ func TestFeedGetExtension(t *testing.T) {
9393
{Name: "updatePeriod", Value: "hourly"},
9494
},
9595
},
96-
"rss": {
96+
"_custom": {
9797
"customFeedData": []ext.Extension{
9898
{Name: "customFeedData", Value: "Feed Custom Value"},
9999
},
@@ -106,16 +106,38 @@ func TestFeedGetExtension(t *testing.T) {
106106
assert.Len(t, period, 1)
107107
assert.Equal(t, "hourly", period[0].Value)
108108

109-
// Test getting custom RSS element
110-
custom := feed.GetExtension("rss", "customFeedData")
109+
// Test getting custom element
110+
custom := feed.GetExtension("_custom", "customFeedData")
111111
assert.Len(t, custom, 1)
112112
assert.Equal(t, "Feed Custom Value", custom[0].Value)
113113
}
114114

115+
func TestFeedGetCustomValue(t *testing.T) {
116+
feed := &Feed{
117+
Extensions: ext.Extensions{
118+
"_custom": {
119+
"customFeedId": []ext.Extension{
120+
{Name: "customFeedId", Value: "feed-123"},
121+
},
122+
"updateFrequency": []ext.Extension{
123+
{Name: "updateFrequency", Value: "hourly"},
124+
},
125+
},
126+
},
127+
}
128+
129+
// Test getting custom values at feed level
130+
assert.Equal(t, "feed-123", feed.GetCustomValue("customFeedId"))
131+
assert.Equal(t, "hourly", feed.GetCustomValue("updateFrequency"))
132+
133+
// Test getting non-existent custom value
134+
assert.Equal(t, "", feed.GetCustomValue("missing"))
135+
}
136+
115137
func TestMultipleExtensionsWithSameName(t *testing.T) {
116138
item := &Item{
117139
Extensions: ext.Extensions{
118-
"rss": {
140+
"_custom": {
119141
"tag": []ext.Extension{
120142
{Name: "tag", Value: "First"},
121143
{Name: "tag", Value: "Second"},
@@ -126,12 +148,12 @@ func TestMultipleExtensionsWithSameName(t *testing.T) {
126148
}
127149

128150
// Test getting all tags
129-
tags := item.GetExtension("rss", "tag")
151+
tags := item.GetExtension("_custom", "tag")
130152
assert.Len(t, tags, 3)
131153
assert.Equal(t, "First", tags[0].Value)
132154
assert.Equal(t, "Second", tags[1].Value)
133155
assert.Equal(t, "Third", tags[2].Value)
134156

135157
// GetExtensionValue returns the first one
136-
assert.Equal(t, "First", item.GetExtensionValue("rss", "tag"))
158+
assert.Equal(t, "First", item.GetExtensionValue("_custom", "tag"))
137159
}

rss/parser.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ func (rp *Parser) parseChannel(p *xpp.XMLPullParser) (rss *Feed, err error) {
283283
p.Skip()
284284
} else {
285285
// For non-standard RSS channel elements, add them to extensions
286-
// under a special "rss" namespace prefix
286+
// under a special "_custom" namespace prefix
287287
customExt := ext.Extension{
288288
Name: p.Name,
289289
Attrs: make(map[string]string),
@@ -306,12 +306,12 @@ func (rp *Parser) parseChannel(p *xpp.XMLPullParser) (rss *Feed, err error) {
306306
if extensions == nil {
307307
extensions = make(ext.Extensions)
308308
}
309-
if extensions["rss"] == nil {
310-
extensions["rss"] = make(map[string][]ext.Extension)
309+
if extensions["_custom"] == nil {
310+
extensions["_custom"] = make(map[string][]ext.Extension)
311311
}
312312

313313
// Add to extensions
314-
extensions["rss"][p.Name] = append(extensions["rss"][p.Name], customExt)
314+
extensions["_custom"][p.Name] = append(extensions["_custom"][p.Name], customExt)
315315
}
316316
}
317317
}
@@ -453,7 +453,7 @@ func (rp *Parser) parseItem(p *xpp.XMLPullParser) (item *Item, err error) {
453453
categories = append(categories, result)
454454
} else {
455455
// For non-standard RSS elements, add them to extensions
456-
// under a special "rss" namespace prefix
456+
// under a special "_custom" namespace prefix
457457
customExt := ext.Extension{
458458
Name: p.Name,
459459
Attrs: make(map[string]string),
@@ -475,12 +475,12 @@ func (rp *Parser) parseItem(p *xpp.XMLPullParser) (item *Item, err error) {
475475
if extensions == nil {
476476
extensions = make(ext.Extensions)
477477
}
478-
if extensions["rss"] == nil {
479-
extensions["rss"] = make(map[string][]ext.Extension)
478+
if extensions["_custom"] == nil {
479+
extensions["_custom"] = make(map[string][]ext.Extension)
480480
}
481481

482482
// Add to extensions
483-
extensions["rss"][p.Name] = append(extensions["rss"][p.Name], customExt)
483+
extensions["_custom"][p.Name] = append(extensions["_custom"][p.Name], customExt)
484484
}
485485
}
486486
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"title": "Test Feed",
3+
"id": "urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6",
4+
"updated": "2003-12-13T18:30:02Z",
5+
"updatedParsed": "2003-12-13T18:30:02Z",
6+
"extensions": {
7+
"_custom": {
8+
"customFeedId": [
9+
{
10+
"name": "customFeedId",
11+
"value": "feed-123",
12+
"attrs": {}
13+
}
14+
],
15+
"updateInterval": [
16+
{
17+
"name": "updateInterval",
18+
"value": "30",
19+
"attrs": {
20+
"units": "minutes"
21+
}
22+
}
23+
],
24+
"customMetadata": [
25+
{
26+
"name": "customMetadata",
27+
"value": "Some metadata",
28+
"attrs": {
29+
"type": "internal"
30+
}
31+
}
32+
]
33+
}
34+
},
35+
"entries": [],
36+
"version": "1.0"
37+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<!--
2+
Description: atom10 feed with custom elements at feed level
3+
-->
4+
<?xml version="1.0" encoding="utf-8"?>
5+
<feed xmlns="http://www.w3.org/2005/Atom">
6+
<title>Test Feed</title>
7+
<id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>
8+
<updated>2003-12-13T18:30:02Z</updated>
9+
<customFeedId>feed-123</customFeedId>
10+
<updateInterval units="minutes">30</updateInterval>
11+
<customMetadata type="internal">Some metadata</customMetadata>
12+
</feed>
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"title": "Test Feed",
3+
"id": "urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6",
4+
"updated": "2003-12-13T18:30:02Z",
5+
"updatedParsed": "2003-12-13T18:30:02Z",
6+
"entries": [
7+
{
8+
"title": "Test Entry",
9+
"id": "urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a",
10+
"updated": "2003-12-13T18:30:02Z",
11+
"updatedParsed": "2003-12-13T18:30:02Z",
12+
"extensions": {
13+
"_custom": {
14+
"customId": [
15+
{
16+
"name": "customId",
17+
"value": "entry-123",
18+
"attrs": {}
19+
}
20+
],
21+
"priority": [
22+
{
23+
"name": "priority",
24+
"value": "1",
25+
"attrs": {
26+
"level": "high"
27+
}
28+
}
29+
],
30+
"customTag": [
31+
{
32+
"name": "customTag",
33+
"value": "Custom Value",
34+
"attrs": {}
35+
}
36+
]
37+
}
38+
}
39+
}
40+
],
41+
"version": "1.0"
42+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<!--
2+
Description: atom10 feed with custom elements at entry level
3+
-->
4+
<?xml version="1.0" encoding="utf-8"?>
5+
<feed xmlns="http://www.w3.org/2005/Atom">
6+
<title>Test Feed</title>
7+
<id>urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6</id>
8+
<updated>2003-12-13T18:30:02Z</updated>
9+
10+
<entry>
11+
<title>Test Entry</title>
12+
<id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id>
13+
<updated>2003-12-13T18:30:02Z</updated>
14+
<customId>entry-123</customId>
15+
<priority level="high">1</priority>
16+
<customTag>Custom Value</customTag>
17+
</entry>
18+
</feed>

0 commit comments

Comments
 (0)