diff --git a/Nustache.Compilation.Tests/Compiled_Templates_Support.cs b/Nustache.Compilation.Tests/Compiled_Templates_Support.cs index 066d965..5059121 100644 --- a/Nustache.Compilation.Tests/Compiled_Templates_Support.cs +++ b/Nustache.Compilation.Tests/Compiled_Templates_Support.cs @@ -25,6 +25,20 @@ public class SubObject public bool TestBool { get; set; } public SubObject Sub { get; set; } + + public override string ToString() + { + return "SubObject"; + } + } + + public class TestObjectWithToString + { + public string TestString { get; set; } + public override string ToString() + { + return ""; + } } [TestFixture] @@ -273,6 +287,24 @@ public void Missing_Properties() compiled(new TestObject { TestString = "Hello", TestBool = true }); } + [Test] + public void Member_ToString() + { + var template = Template("A template with {{Sub}}"); + var compiled = template.Compile(null); + var result = compiled(new TestObject { Sub = new SubObject() }); + Assert.AreEqual("A template with SubObject", result); + } + + [Test] + public void Model_With_ToString() + { + var template = Template("A template with {{TestString}}"); + var compiled = template.Compile(null); + var result = compiled(new TestObjectWithToString { TestString = "Hello"}); + Assert.AreEqual("A template with Hello", result); + } + private Func Compiled(string text) where T : class { return Template(text).Compile(null); diff --git a/Nustache.Compilation/CompilePartVisitor.cs b/Nustache.Compilation/CompilePartVisitor.cs index 1844940..f6e59e0 100644 --- a/Nustache.Compilation/CompilePartVisitor.cs +++ b/Nustache.Compilation/CompilePartVisitor.cs @@ -114,8 +114,8 @@ public void Visit(TemplateInclude include) public void Visit(VariableReference variable) { var getter = context.CompiledGetter(variable.Path); - getter = CompoundExpression.NullCheck(getter, ""); - getter = Expression.Call(getter, context.TargetType.GetMethod("ToString")); + var returnIfNotNull = Expression.Call(getter, getter.Type.GetMethod("ToString", new Type[0])); + getter = CompoundExpression.NullCheck(getter, "", returnIfNotNull); if (variable.Escaped) { diff --git a/Nustache.Core.Tests/Describe_ValueGetter.cs b/Nustache.Core.Tests/Describe_ValueGetter.cs index e3cde06..f0d7b96 100644 --- a/Nustache.Core.Tests/Describe_ValueGetter.cs +++ b/Nustache.Core.Tests/Describe_ValueGetter.cs @@ -153,6 +153,45 @@ public void It_gets_XmlNode_multiple_child_element_values_as_a_list() Assert.AreEqual("text2", elements[1].InnerText); } + [Test] + public void It_gets_single_string_values_as_string_by_Index_from_XmlNodeList() + { + XmlDocument target = new XmlDocument(); + target.LoadXml(@" + text1 + text2 + text3 +"); + var value = (string)ValueGetter.GetValue(target.DocumentElement.ChildNodes, "1"); + Assert.AreEqual("text2", value); + } + + [Test] + public void It_gets_single_node_values_as_node_by_Index_from_XmlNodeList() + { + XmlDocument target = new XmlDocument(); + target.LoadXml(@" + text1 + text2 + text3 +"); + var value = (XmlNode)ValueGetter.GetValue(target.DocumentElement.ChildNodes, "1"); + Assert.AreEqual("text2", value.InnerXml); + } + + [Test] + public void It_gets_single_CDATA_values_as_string_by_Index_from_XmlNodeList() + { + XmlDocument target = new XmlDocument(); + target.LoadXml(@" + + + +"); + var value = (string)ValueGetter.GetValue(target.DocumentElement.ChildNodes, "1"); + Assert.AreEqual("text2", value); + } + [Test] public void It_gets_ListValueByIndex_values_from_array() { diff --git a/Nustache.Core.Tests/RenderContext_Behaviour_Support.cs b/Nustache.Core.Tests/RenderContext_Behaviour_Support.cs index 5cdc2c9..49f0c99 100644 --- a/Nustache.Core.Tests/RenderContext_Behaviour_Support.cs +++ b/Nustache.Core.Tests/RenderContext_Behaviour_Support.cs @@ -54,6 +54,12 @@ public void It_does_not_throw_an_exception_when_there_is_no_data_and_the_render_ Assert.AreEqual("beforeafter", result); } - + [Test] + public void Use_custom_encoder() + { + var result = Render.StringToString("before{{foo}}after", new {foo = string.Empty}, + new RenderContextBehaviour {HtmlEncoder = text => "middle"}); + Assert.AreEqual("beforemiddleafter", result); + } } } \ No newline at end of file diff --git a/Nustache.Core/RenderContext.cs b/Nustache.Core/RenderContext.cs index ad9c508..0b314a7 100644 --- a/Nustache.Core/RenderContext.cs +++ b/Nustache.Core/RenderContext.cs @@ -30,6 +30,11 @@ public class RenderContext public string ActiveStartDelimiter { get; set; } public string ActiveEndDelimiter { get; set; } + public Encoders.HtmlEncoder HtmlEncoder + { + get { return _renderContextBehaviour.HtmlEncoder ?? Encoders.HtmlEncode; } + } + public RenderContext(Section section, object data, TextWriter writer, TemplateLocator templateLocator, RenderContextBehaviour renderContextBehaviour = null) { _sectionStack.Push(section); diff --git a/Nustache.Core/RenderContextBehaviour.cs b/Nustache.Core/RenderContextBehaviour.cs index 8c0985c..3d64f77 100644 --- a/Nustache.Core/RenderContextBehaviour.cs +++ b/Nustache.Core/RenderContextBehaviour.cs @@ -6,6 +6,8 @@ public class RenderContextBehaviour public bool RaiseExceptionOnDataContextMiss { get; set; } public bool RaiseExceptionOnEmptyStringValue { get; set; } + public Encoders.HtmlEncoder HtmlEncoder { get; set; } + public static RenderContextBehaviour GetDefaultRenderContextBehaviour() { return new RenderContextBehaviour diff --git a/Nustache.Core/ValueGetter.cs b/Nustache.Core/ValueGetter.cs index edbc1fd..42aca65 100644 --- a/Nustache.Core/ValueGetter.cs +++ b/Nustache.Core/ValueGetter.cs @@ -122,6 +122,38 @@ private bool TryGetStringByAttributeName(string attributeName) } } + internal class XmlNodeListIndexGetter : ValueGetter + { + private readonly XmlNodeList _target; + private readonly int _index; + private object _foundSingleValue; + + public XmlNodeListIndexGetter(XmlNodeList target, int index) + { + _target = target; + _index = index; + } + + private object GetNodeValue(XmlNode node) + { + if (node.ChildNodes.Count == 1 + && (node.ChildNodes[0].NodeType == XmlNodeType.Text || node.ChildNodes[0].NodeType == XmlNodeType.CDATA) + ) + { + return node.ChildNodes[0].Value; + } + else + { + return node; + } + } + + public override object GetValue() + { + return GetNodeValue(_target[_index]); + } + } + internal class PropertyDescriptorValueGetter : ValueGetter { private readonly object _target; diff --git a/Nustache.Core/ValueGetterFactory.cs b/Nustache.Core/ValueGetterFactory.cs index 0ce4690..a8e614a 100644 --- a/Nustache.Core/ValueGetterFactory.cs +++ b/Nustache.Core/ValueGetterFactory.cs @@ -82,6 +82,7 @@ public static class ValueGetterFactories private static readonly ValueGetterFactoryCollection _factories = new ValueGetterFactoryCollection { new XmlNodeValueGetterFactory(), + new XmlNodeListIndexGetterFactory(), new PropertyDescriptorValueGetterFactory(), new GenericDictionaryValueGetterFactory(), new DataRowGetterFactory(), @@ -112,6 +113,32 @@ public override ValueGetter GetValueGetter(object target, Type targetType, strin } } + internal class XmlNodeListIndexGetterFactory : ValueGetterFactory + { + public override ValueGetter GetValueGetter(object target, Type targetType, string name) + { + if (target is XmlNodeList) + { + var listTarget = target as XmlNodeList; + int arrayIndex; + bool parseSuccess = Int32.TryParse(name, out arrayIndex); + + /* + * There is an index as per the success of the parse, it is not greater than the count + * (minus one since index is zero referenced) or less than zero. + */ + if (parseSuccess && + !(arrayIndex > (listTarget.Count - 1)) && + !(arrayIndex < 0)) + { + return new XmlNodeListIndexGetter(listTarget, arrayIndex); + } + } + + return null; + } + } + internal class PropertyDescriptorValueGetterFactory : ValueGetterFactory { public override ValueGetter GetValueGetter(object target, Type targetType, string name) diff --git a/Nustache.Core/VariableReference.cs b/Nustache.Core/VariableReference.cs index d205721..c9ae271 100644 --- a/Nustache.Core/VariableReference.cs +++ b/Nustache.Core/VariableReference.cs @@ -46,7 +46,7 @@ public override void Render(RenderContext context) var lambdaResult = lambda().ToString(); lambdaResult = _escaped - ? Encoders.HtmlEncode(lambdaResult.ToString()) + ? context.HtmlEncoder(lambdaResult.ToString()) : lambdaResult.ToString(); using (System.IO.TextReader sr = new System.IO.StringReader(lambdaResult)) @@ -73,7 +73,7 @@ public override void Render(RenderContext context) else if (value != null) { context.Write(_escaped - ? Encoders.HtmlEncode(value.ToString()) + ? context.HtmlEncoder(value.ToString()) : value.ToString()); } }