diff --git a/src/HtmlAgilityPack.Shared/HtmlNode.Encapsulator.cs b/src/HtmlAgilityPack.Shared/HtmlNode.Encapsulator.cs
index af4e87bd..aaa90909 100644
--- a/src/HtmlAgilityPack.Shared/HtmlNode.Encapsulator.cs
+++ b/src/HtmlAgilityPack.Shared/HtmlNode.Encapsulator.cs
@@ -11,7 +11,6 @@
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
-using System.Xml;
using System.Xml.XPath;
namespace HtmlAgilityPack
@@ -201,6 +200,7 @@ public object GetEncapsulatedData(Type targetType, HtmlDocument htmlDocument = n
}
else // It target attribute of HTMLTag
{
+ ThrowIfNodeReturnTypeIsExplicitlySetWhenAttributeNameIsGiven(xPathAttribute);
result = htmlNode.GetAttributeValue(xPathAttribute.AttributeName, null);
}
@@ -329,6 +329,8 @@ public object GetEncapsulatedData(Type targetType, HtmlDocument htmlDocument = n
}
else // It target attribute
{
+ ThrowIfNodeReturnTypeIsExplicitlySetWhenAttributeNameIsGiven(xPathAttribute);
+
foreach (HtmlNode node in nodeCollection)
{
string nodeAttributeValue = node.GetAttributeValue(xPathAttribute.AttributeName, null);
@@ -386,6 +388,15 @@ public object GetEncapsulatedData(Type targetType, HtmlDocument htmlDocument = n
}
#endregion targetObject_NOTDefined_XPath
}
+
+
+ private static void ThrowIfNodeReturnTypeIsExplicitlySetWhenAttributeNameIsGiven(XPathAttribute xPathAttr)
+ {
+ if (xPathAttr.IsNodeReturnTypeExplicitlySet && !string.IsNullOrEmpty(xPathAttr.AttributeName))
+ {
+ throw new InvalidNodeReturnTypeException("Specifying a ReturnType value not allowed for XPathAttribute annotations targeting element attributes");
+ }
+ }
}
@@ -749,7 +760,7 @@ internal static string GetHtmlForEncapsulation(HtmlNode node, ReturnType returnT
case ReturnType.OuterHtml:
return node.OuterHtml;
default:
- throw new IndexOutOfRangeException("Unhandled ReturnType : " + returnType.ToString());
+ throw new InvalidNodeReturnTypeException(string.Format("Invalid ReturnType value {0}", returnType));
};
}
}
@@ -852,20 +863,6 @@ public XPathAttribute(string xpathString, string attributeName)
AttributeName = attributeName;
_nodeReturnType = ReturnType.InnerText;
}
-
-
- ///
- /// Specify Xpath and Attribute to find related Html Node and its attribute value.
- ///
- ///
- ///
- /// Specify you want the output include html text too.
- public XPathAttribute(string xpathString, string attributeName, ReturnType nodeReturnType)
- {
- XPath = xpathString;
- AttributeName = attributeName;
- NodeReturnType = nodeReturnType;
- }
}
@@ -955,6 +952,26 @@ public MissingXPathException(string message) : base(message) { }
public MissingXPathException(string message, Exception inner) : base(message, inner) { }
}
+
+ ///
+ /// Exception that occurs when an XPathAttribute annotation has an invalid ReturnType specified.
+ ///
+ public class InvalidNodeReturnTypeException : Exception
+ {
+ ///
+ ///
+ ///
+ ///
+ public InvalidNodeReturnTypeException(string message)
+ : base(message) { }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public InvalidNodeReturnTypeException(string message, Exception inner) : base(message, inner) { }
+ }
}
#if FX20
diff --git a/src/Tests/HtmlAgilityPack.Tests.NetStandard2_0/HtmlNode_GetEncapsulatedData_NodeReturnType_Tests.cs b/src/Tests/HtmlAgilityPack.Tests.NetStandard2_0/HtmlNode_GetEncapsulatedData_NodeReturnType_Tests.cs
new file mode 100644
index 00000000..b0d4a68a
--- /dev/null
+++ b/src/Tests/HtmlAgilityPack.Tests.NetStandard2_0/HtmlNode_GetEncapsulatedData_NodeReturnType_Tests.cs
@@ -0,0 +1,387 @@
+using System;
+using System.Collections.Generic;
+using Xunit;
+
+namespace HtmlAgilityPack.Tests.NetStandard2_0
+{
+ public class HtmlNode_GetEncapsulatedData_NodeReturnType_Tests
+ {
+ [Theory]
+ [InlineData(typeof(Model_InvalidReturnTypeOnElementWithStringProperty))]
+ [InlineData(typeof(Model_InvalidReturnTypeAsNamedArgOnElementWithStringProperty))]
+ public void InvalidReturnTypeOnElementWithStringProperty(Type modelType)
+ {
+ const string html =
+ @"
";
+
+ var doc = new HtmlDocument();
+ doc.LoadHtml(html);
+
+ var ex = Assert.Throws(
+ () => doc.DocumentNode.GetEncapsulatedData(modelType)
+ );
+
+ Assert.Equal("Invalid ReturnType value 1234", ex.Message);
+ }
+
+ [HasXPath]
+ class Model_InvalidReturnTypeOnElementWithStringProperty
+ {
+ [XPath("//div/a", (ReturnType) 1234)]
+ public string? StringValue { get; set; }
+ }
+
+ [HasXPath]
+ class Model_InvalidReturnTypeAsNamedArgOnElementWithStringProperty
+ {
+ [XPath("//div/a", NodeReturnType = (ReturnType) 1234)]
+ public string? StringValue { get; set; }
+ }
+
+
+ [Fact]
+ public void ImplicitReturnTypeOnElementWithStringProperty()
+ {
+ const string html =
+ @"";
+
+ var doc = new HtmlDocument();
+ doc.LoadHtml(html);
+ var model = doc.DocumentNode.GetEncapsulatedData();
+
+ Assert.NotNull(model);
+ Assert.Equal("Link 1", model.StringValue);
+ }
+
+ [HasXPath]
+ class Model_ImplicitReturnTypeOnElementWithStringProperty
+ {
+ [XPath("//div/a")]
+ public string? StringValue { get; set; }
+ }
+
+
+ [Theory]
+ [InlineData(typeof(Model_ReturnTypeInnerTextOnElementWithStringProperty))]
+ [InlineData(typeof(Model_ReturnTypeInnerTextAsNamedArgOnElementWithStringProperty))]
+ public void ReturnTypeInnerTextOnElementWithStringProperty(Type modelType)
+ {
+ const string html =
+ @"";
+
+ var doc = new HtmlDocument();
+ doc.LoadHtml(html);
+ var model = (IModelWithStringProperty) doc.DocumentNode.GetEncapsulatedData(modelType);
+
+ Assert.NotNull(model);
+ Assert.Equal("Link 1", model.StringValue);
+ }
+
+ [HasXPath]
+ class Model_ReturnTypeInnerTextOnElementWithStringProperty : IModelWithStringProperty
+ {
+ [XPath("//div/a", ReturnType.InnerText)]
+ public string? StringValue { get; set; }
+ }
+
+ [HasXPath]
+ class Model_ReturnTypeInnerTextAsNamedArgOnElementWithStringProperty : IModelWithStringProperty
+ {
+ [XPath("//div/a", NodeReturnType = ReturnType.InnerText)]
+ public string? StringValue { get; set; }
+ }
+
+
+ [Theory]
+ [InlineData(typeof(Model_ReturnTypeInnerHTMLOnElementWithStringProperty))]
+ [InlineData(typeof(Model_ReturnTypeInnerHTMLAsNamedArgOnElementWithStringProperty))]
+ public void ReturnTypeInnerHTMLOnElementWithStringProperty(Type modelType)
+ {
+ const string html =
+ @"";
+
+ var doc = new HtmlDocument();
+ doc.LoadHtml(html);
+ var model = (IModelWithStringProperty) doc.DocumentNode.GetEncapsulatedData(modelType);
+
+ Assert.NotNull(model);
+ Assert.Equal("Link 1
", model.StringValue);
+ }
+
+ [HasXPath]
+ class Model_ReturnTypeInnerHTMLOnElementWithStringProperty : IModelWithStringProperty
+ {
+ [XPath("//div/a", ReturnType.InnerHtml)]
+ public string? StringValue { get; set; }
+ }
+
+ [HasXPath]
+ class Model_ReturnTypeInnerHTMLAsNamedArgOnElementWithStringProperty : IModelWithStringProperty
+ {
+ [XPath("//div/a", NodeReturnType = ReturnType.InnerHtml)]
+ public string? StringValue { get; set; }
+ }
+
+
+ [Theory]
+ [InlineData(typeof(Model_ReturnTypeOuterHTMLOnElementWithStringProperty))]
+ [InlineData(typeof(Model_ReturnTypeOuterHTMLAsNamedArgOnElementWithStringProperty))]
+ public void ReturnTypeOuterHTMLOnElementWithStringProperty(Type modelType)
+ {
+ const string html =
+ @"";
+
+ var doc = new HtmlDocument();
+ doc.LoadHtml(html);
+ var model = (IModelWithStringProperty) doc.DocumentNode.GetEncapsulatedData(modelType);
+
+ Assert.NotNull(model);
+ Assert.Equal("Link 1
", model.StringValue);
+ }
+
+ [HasXPath]
+ class Model_ReturnTypeOuterHTMLOnElementWithStringProperty : IModelWithStringProperty
+ {
+ [XPath("//div/a", ReturnType.OuterHtml)]
+ public string? StringValue { get; set; }
+ }
+
+ [HasXPath]
+ class Model_ReturnTypeOuterHTMLAsNamedArgOnElementWithStringProperty : IModelWithStringProperty
+ {
+ [XPath("//div/a", NodeReturnType = ReturnType.OuterHtml)]
+ public string? StringValue { get; set; }
+ }
+
+
+ [Fact]
+ public void ImplicitReturnTypeOnAttributeWithStringProperty()
+ {
+ const string html =
+ @"";
+
+ var doc = new HtmlDocument();
+ doc.LoadHtml(html);
+ var model = doc.DocumentNode.GetEncapsulatedData();
+
+ Assert.NotNull(model);
+ Assert.Equal("link1.html", model.Value);
+ }
+
+ [HasXPath]
+ class Model_ImplicitReturnTypeOnAttributeWithStringProperty
+ {
+ [XPath("//div/a", "href")]
+ public string? Value { get; set; }
+ }
+
+
+ [Theory]
+ [InlineData(typeof(Model_ExplicitReturnTypeOnAttributeWithStringProperty))]
+ [InlineData(typeof(Model_ExplicitInvalidReturnTypeOnAttributeWithStringProperty))]
+ public void ExplicitReturnTypeOnAttributeWithStringProperty(Type modelType)
+ {
+ const string html =
+ @"";
+
+ var doc = new HtmlDocument();
+ doc.LoadHtml(html);
+
+ var ex = Assert.Throws(
+ () => doc.DocumentNode.GetEncapsulatedData(modelType)
+ );
+
+ Assert.Equal("Specifying a ReturnType value not allowed for XPathAttribute annotations targeting element attributes", ex.Message);
+ }
+
+ [HasXPath]
+ class Model_ExplicitReturnTypeOnAttributeWithStringProperty
+ {
+ [XPath("//div/a", "href", NodeReturnType = ReturnType.InnerText)]
+ public string? StringValue { get; set; }
+ }
+
+ [HasXPath]
+ class Model_ExplicitInvalidReturnTypeOnAttributeWithStringProperty
+ {
+ [XPath("//div/a", "href", NodeReturnType = (ReturnType)567)]
+ public string? StringValue { get; set; }
+ }
+
+
+ [Fact]
+ public void ImplicitReturnTypeOnElementSequenceWithStringListProperty()
+ {
+ const string html =
+ @"";
+
+
+ var doc = new HtmlDocument();
+ doc.LoadHtml(html);
+ var model = doc.DocumentNode.GetEncapsulatedData();
+
+ Assert.NotNull(model);
+
+ string[] expected = new[] { "Link 1", "Link 2", "Link 3" };
+ Assert.Equal(expected, model.StringListValue);
+ }
+
+ [HasXPath]
+ class Model_ImplicitReturnTypeOnElementSequenceWithStringListProperty
+ {
+ [XPath("//div/a")]
+ public List? StringListValue { get; set; }
+ }
+
+
+ [Theory]
+ [InlineData(typeof(Model_ReturnTypeInnerTextOnElementSequenceWithStringListProperty))]
+ [InlineData(typeof(Model_ReturnTypeInnerTextAsNamedArgOnElementSequenceWithStringListProperty))]
+ public void ReturnTypeInnerTextOnElementSequenceWithStringListProperty(Type modelType)
+ {
+ const string html =
+ @"";
+
+ var doc = new HtmlDocument();
+ doc.LoadHtml(html);
+ var model = (IModelWithStringListProperty) doc.DocumentNode.GetEncapsulatedData(modelType);
+
+ Assert.NotNull(model);
+
+ string[] expected = new[] { "Link 1", "Link 2", "Link 3" };
+ Assert.Equal(expected, model.StringListValue);
+ }
+
+ [HasXPath]
+ class Model_ReturnTypeInnerTextOnElementSequenceWithStringListProperty : IModelWithStringListProperty
+ {
+ [XPath("//div/a", ReturnType.InnerText)]
+ public List? StringListValue { get; set; }
+ }
+
+ [HasXPath]
+ class Model_ReturnTypeInnerTextAsNamedArgOnElementSequenceWithStringListProperty : IModelWithStringListProperty
+ {
+ [XPath("//div/a", NodeReturnType = ReturnType.InnerText)]
+ public List? StringListValue { get; set; }
+ }
+
+
+ [Theory]
+ [InlineData(typeof(Model_ReturnTypeInnerHTMLOnElementSequenceWithStringListProperty))]
+ [InlineData(typeof(Model_ReturnTypeInnerHTMLAsNamedArgOnElementSequenceWithStringListProperty))]
+ public void ReturnTypeInnerHTMLOnElementSequenceWithStringListProperty(Type modelType)
+ {
+ const string html =
+ @"";
+
+ var doc = new HtmlDocument();
+ doc.LoadHtml(html);
+ var model = (IModelWithStringListProperty) doc.DocumentNode.GetEncapsulatedData(modelType);
+
+ Assert.NotNull(model);
+
+ string[] expected = new[]
+ {
+ "Link 1
",
+ "Link 2
",
+ "Link 3
"
+ };
+ Assert.Equal(expected, model.StringListValue);
+ }
+
+ [HasXPath]
+ class Model_ReturnTypeInnerHTMLOnElementSequenceWithStringListProperty : IModelWithStringListProperty
+ {
+ [XPath("//div/a", ReturnType.InnerHtml)]
+ public List? StringListValue { get; set; }
+ }
+
+ [HasXPath]
+ class Model_ReturnTypeInnerHTMLAsNamedArgOnElementSequenceWithStringListProperty : IModelWithStringListProperty
+ {
+ [XPath("//div/a", NodeReturnType = ReturnType.InnerHtml)]
+ public List? StringListValue { get; set; }
+ }
+
+
+ [Theory]
+ [InlineData(typeof(Model_ReturnTypeOuterHTMLOnElementSequenceWithStringListProperty))]
+ [InlineData(typeof(Model_ReturnTypeOuterHTMLAsNamedArgOnElementSequenceWithStringListProperty))]
+ public void ReturnTypeOuterHTMLOnElementSequenceWithStringListProperty(Type modelType)
+ {
+ const string html =
+ @"";
+
+ var doc = new HtmlDocument();
+ doc.LoadHtml(html);
+ var model = (IModelWithStringListProperty) doc.DocumentNode.GetEncapsulatedData(modelType);
+
+ Assert.NotNull(model);
+
+ string[] expected = new[]
+ {
+ "Link 1
",
+ "Link 2
",
+ "Link 3
"
+ };
+ Assert.Equal(expected, model.StringListValue);
+ }
+
+ [HasXPath]
+ class Model_ReturnTypeOuterHTMLOnElementSequenceWithStringListProperty : IModelWithStringListProperty
+ {
+ [XPath("//div/a", ReturnType.OuterHtml)]
+ public List? StringListValue { get; set; }
+ }
+
+ [HasXPath]
+ class Model_ReturnTypeOuterHTMLAsNamedArgOnElementSequenceWithStringListProperty : IModelWithStringListProperty
+ {
+ [XPath("//div/a", NodeReturnType = ReturnType.OuterHtml)]
+ public List? StringListValue { get; set; }
+ }
+
+
+
+ interface IModelWithStringProperty
+ {
+ string? StringValue { get; set; }
+ }
+
+ interface IModelWithStringListProperty
+ {
+ List? StringListValue { get; set; }
+ }
+ }
+}