diff --git a/src/Attributes/BasePropertyAttribute.cs b/src/Attributes/BasePropertyAttribute.cs
index 1f104b2..2a82c7c 100644
--- a/src/Attributes/BasePropertyAttribute.cs
+++ b/src/Attributes/BasePropertyAttribute.cs
@@ -13,14 +13,14 @@ public abstract class BasePropertyAttribute : Attribute
public string Namespace { get; set; }
- public Uri Url { get; private set; }
+ public Uri Uri { get; private set; }
public string AttributeName { get; set; }
- public string NamespaceUrl
+ public string NamespaceUri
{
- get => Url?.ToString();
- set => Url = new Uri(value);
+ get => Uri?.ToString();
+ set => Uri = new Uri(value);
}
public Type Writer { get; set; }
diff --git a/src/Attributes/XmlNamespaceAttribute.cs b/src/Attributes/XmlNamespaceAttribute.cs
new file mode 100644
index 0000000..274cac8
--- /dev/null
+++ b/src/Attributes/XmlNamespaceAttribute.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace dng.Syndication.Attributes
+{
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
+ public class XmlNamespaceAttribute : Attribute
+ {
+ public string Prefix { get; }
+ public Uri Uri { get; }
+
+ public XmlNamespaceAttribute(string prefix, string uri)
+ {
+ Prefix = prefix;
+ Uri = new Uri(uri);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Feed.cs b/src/Feed.cs
index 3749bc2..866d6ef 100644
--- a/src/Feed.cs
+++ b/src/Feed.cs
@@ -9,8 +9,6 @@ namespace dng.Syndication
{
public class Feed : IFeed
{
-
-
///
/// Name for the feed.
///
@@ -29,8 +27,8 @@ public class Feed : IFeed
/// Defines the hyperlink to the channel
///
[AtomProperty("id", Required = true)]
- [AtomProperty("link", NamespaceUrl = Namespaces.AtomNamespace, Writer = typeof(AtomLinkElementWriter))]
- [Rss20Property("link", NamespaceUrl = Namespaces.AtomNamespace, Writer = typeof(AtomLinkElementWriter))]
+ [AtomProperty("link", NamespaceUri = Namespaces.AtomNamespace, Writer = typeof(AtomLinkElementWriter))]
+ [Rss20Property("link", NamespaceUri = Namespaces.AtomNamespace, Writer = typeof(AtomLinkElementWriter))]
[Rss20Property("link", Required = true, Writer = typeof(Rcf3986UriElementFormatter))]
public Uri Link { get; set; }
diff --git a/src/Generators/AtomGenerator.cs b/src/Generators/AtomGenerator.cs
index 5f3a4d0..b8a70e5 100644
--- a/src/Generators/AtomGenerator.cs
+++ b/src/Generators/AtomGenerator.cs
@@ -1,4 +1,7 @@
-using System.Text;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
using System.Xml.Linq;
using dng.Syndication.Attributes;
using dng.Syndication.Enums;
@@ -18,6 +21,8 @@ public override string Process()
var rootElement = new XElement(atomNamespace + "feed");
+ AddFeedNamespaces(rootElement);
+
var declaration = new XDeclaration("1.0", Encoding.UTF8.HeaderName, null);
var doc = new XDocument(declaration, rootElement);
diff --git a/src/Generators/Generator.cs b/src/Generators/Generator.cs
index 83bf793..ddcb94d 100644
--- a/src/Generators/Generator.cs
+++ b/src/Generators/Generator.cs
@@ -34,13 +34,30 @@ public override string ToString()
public abstract FeedType FeedType { get; }
- protected virtual XElement ParseProperties(TObject obj, XElement parentElement = null, XNamespace @namespace = null)
+ protected virtual void AddFeedNamespaces(XElement element)
+ {
+ var namespaceAttributes =
+ Feed.GetType().GetCustomAttributes(typeof(XmlNamespaceAttribute), false)
+ .Cast().ToList();
+
+ if (!namespaceAttributes.Any())
+ {
+ return;
+ }
+
+ foreach (var attribute in namespaceAttributes)
+ {
+ element.SetAttributeValue(XNamespace.Xmlns + attribute.Prefix, attribute.Uri);
+ }
+ }
+
+ protected virtual XElement ParseProperties(TObject obj, XElement parentElement, XNamespace @namespace = null)
where TObject : class
{
return ParsePropertiesInternal(obj, (object) null, parentElement, @namespace);
}
- internal XElement ParsePropertiesInternal(TObject obj, TParent parentobj, XElement parentElement = null, XNamespace @namespace = null)
+ internal XElement ParsePropertiesInternal(TObject obj, TParent parentobj, XElement parentElement, XNamespace @namespace = null)
where TObject : class
where TParent : class
{
@@ -52,7 +69,6 @@ internal XElement ParsePropertiesInternal(TObject obj, TParent
property.GetCustomAttributes(typeof(TAttribute), false)
.Cast().ToList();
-
if (!propertyAttributes.Any())
{
continue;
@@ -150,9 +166,9 @@ private static XName GetElementName(TAttribute attribute, XNamespace @namespace)
{
XName result = attribute.Name;
- if (!string.IsNullOrWhiteSpace(attribute.NamespaceUrl))
+ if (!string.IsNullOrWhiteSpace(attribute.NamespaceUri))
{
- result = XNamespace.Get(attribute.NamespaceUrl) + attribute.Name;
+ result = XNamespace.Get(attribute.NamespaceUri) + attribute.Name;
}
else if (@namespace != null)
{
diff --git a/src/Generators/Rss20Generator.cs b/src/Generators/Rss20Generator.cs
index b5c7914..11cc1aa 100644
--- a/src/Generators/Rss20Generator.cs
+++ b/src/Generators/Rss20Generator.cs
@@ -26,6 +26,8 @@ public override string Process()
rootElement.Add(new XAttribute(XNamespace.Xmlns + "atom", atomNamespace));
+ AddFeedNamespaces(rootElement);
+
var parentElement = new XElement("channel");
rootElement.Add(ParseProperties(Feed, parentElement));
diff --git a/tests/AtomGeneratorTests.cs b/tests/AtomGeneratorTests.cs
index 2abeab5..75120b7 100644
--- a/tests/AtomGeneratorTests.cs
+++ b/tests/AtomGeneratorTests.cs
@@ -1,13 +1,24 @@
using System;
using System.Collections.Generic;
using System.Globalization;
+using dng.Syndication.Attributes;
using dng.Syndication.Generators;
+using dng.Syndication.Writers;
using Xunit;
namespace dng.Syndication.Tests
{
public class AtomGeneratorTests
{
+ private const string DOTNETGEEK_ATOM_NAMESPACE = "http://www.dotnetgeek.com/Atom/2018";
+
+ [XmlNamespace("dng", DOTNETGEEK_ATOM_NAMESPACE)]
+ private class CustomAtomFeed : Feed
+ {
+ [AtomProperty("property", NamespaceUri = DOTNETGEEK_ATOM_NAMESPACE)]
+ public string MyProperty { get; set; }
+ }
+
private Feed CreateSimpleFeed()
{
var feed = new Feed
@@ -38,6 +49,39 @@ private Feed CreateSimpleFeed()
}
};
+ return feed;
+ }
+
+ private CustomAtomFeed CreateCustomFeed()
+ {
+ var feed = new CustomAtomFeed
+ {
+ Title = FeedContent.Text("dotnetgeek feed"),
+ Author = new Author
+ {
+ Name = "Daniel",
+ Email = "email@email.em"
+ },
+ Copyright = "2016 @ www.dotnetgeek.com",
+ Description = FeedContent.Text("Dotnet relevant thinks"),
+ Generator = "dng.Syndication",
+ Language = CultureInfo.GetCultureInfo("de"),
+ UpdatedDate = new DateTime(2016, 08, 16),
+ Link = new Uri("http://www.dotnetgeek.de/rss"),
+ FeedEntries = new List
+ {
+ new FeedEntry
+ {
+ Title = FeedContent.Plain("First Entry"),
+ Content = FeedContent.Plain("Content"),
+ Link = new Uri("http://www.dotnetgeek.com/first-entry"),
+ Summary = FeedContent.Plain("summary"),
+ PublishDate = new DateTime(2016, 08, 16),
+ Updated = new DateTime(2016, 08, 16),
+ }
+ },
+ MyProperty = "my_custom_atom_value"
+ };
return feed;
}
@@ -107,6 +151,38 @@ public void Create_a_simple_atom_feed_with_logo()
Assert.Equal(expected, feedXml);
}
+
+ [Fact]
+ public void Create_a_custom_atom_feed()
+ {
+ var atomGenerator = new AtomGenerator(CreateCustomFeed());
+ var feedXml = atomGenerator.Process();
+
+ const string expected = "" +
+ "" +
+ "dotnetgeek feed" +
+ "Danielemail@email.em" +
+ "http://www.dotnetgeek.de/rss" +
+ "" +
+ "2016-08-16T00:00:00Z" +
+ "2016 @ www.dotnetgeek.com" +
+ "dng.Syndication" +
+ "Dotnet relevant thinks" +
+ "" +
+ "First Entry" +
+ "" +
+ "http://www.dotnetgeek.com/first-entry" +
+ "summary" +
+ "Content" +
+ "Danielemail@email.em" +
+ "2016-08-16T00:00:00Z" +
+ "2016-08-16T00:00:00Z" +
+ "" +
+ "my_custom_atom_value" +
+ "";
+
+ Assert.Equal(expected, feedXml);
+ }
}
}
diff --git a/tests/Rss20GeneratorTests.cs b/tests/Rss20GeneratorTests.cs
index 8887255..90dc881 100644
--- a/tests/Rss20GeneratorTests.cs
+++ b/tests/Rss20GeneratorTests.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
+using dng.Syndication.Attributes;
using dng.Syndication.Generators;
using Xunit;
@@ -10,6 +11,15 @@ public class Rss20GeneratorTests
{
private readonly string _dateTimeOffset = new DateTime(2016, 08, 16).ToString("zzzz").Replace(":", "");
+ private const string DOTNETGEEK_RSS_NAMESPACE = "http://www.dotnetgeek.com/Rss/2018";
+
+ [XmlNamespace("dng", DOTNETGEEK_RSS_NAMESPACE)]
+ private class CustomRssFeed : Feed
+ {
+ [Rss20Property("property", NamespaceUri = DOTNETGEEK_RSS_NAMESPACE)]
+ public string MyProperty { get; set; }
+ }
+
private Feed CreateSimpleFeed()
{
var feed = new Feed
@@ -36,6 +46,35 @@ private Feed CreateSimpleFeed()
}
};
+ return feed;
+ }
+
+ private CustomRssFeed CreateCustomFeed()
+ {
+ var feed = new CustomRssFeed
+ {
+ Title = FeedContent.Plain("dotnetgeek feed"),
+ Author = new Author("Daniel", "email@email.em"),
+ Copyright = "2016 @ www.dotnetgeek.com",
+ Description = FeedContent.Plain("Dotnet relevant topics"),
+ Generator = "dng.Syndication",
+ Language = CultureInfo.GetCultureInfo("de"),
+ UpdatedDate = new DateTime(2016, 08, 16),
+ Link = new Uri("http://www.dotnetgeek.de/rss"),
+ FeedEntries = new List
+ {
+ new FeedEntry
+ {
+ Title = FeedContent.Plain("First Entry"),
+ Content = FeedContent.Plain("Content"),
+ Link = new Uri("http://www.dotnetgeek.com/first-entry"),
+ Summary = FeedContent.Plain("summary"),
+ PublishDate = new DateTime(2016, 08, 16),
+ Updated = new DateTime(2016, 08, 16)
+ }
+ },
+ MyProperty = "my_custom_rss_value"
+ };
return feed;
}
@@ -103,8 +142,35 @@ public void Create_a_feed_with_image()
Assert.Equal(expected, feedXml);
}
-
-
+
+ [Fact]
+ public void Create_a_custom_feed()
+ {
+ var rss20Generator = new Rss20Generator(CreateCustomFeed());
+ var feedXml = rss20Generator.Process();
+
+ var expected = "" +
+ "" +
+ "dotnetgeek feed" +
+ "" +
+ "http://www.dotnetgeek.de/rss" +
+ $"Tue, 16 Aug 2016 00:00:00 {_dateTimeOffset}" +
+ "de" +
+ "2016 @ www.dotnetgeek.com" +
+ "dng.Syndication" +
+ "Dotnet relevant topics" +
+ "- " +
+ "First Entry" +
+ "http://www.dotnetgeek.com/first-entry" +
+ "http://www.dotnetgeek.com/first-entry" +
+ "Content" +
+ $"Tue, 16 Aug 2016 00:00:00 {_dateTimeOffset}" +
+ "
" +
+ "my_custom_rss_value" +
+ "";
+
+ Assert.Equal(expected, feedXml);
+ }
}
}