diff --git a/src/Orchard.Tests/Orchard.Framework.Tests.csproj b/src/Orchard.Tests/Orchard.Framework.Tests.csproj index c433d1879..7b3df98cf 100644 --- a/src/Orchard.Tests/Orchard.Framework.Tests.csproj +++ b/src/Orchard.Tests/Orchard.Framework.Tests.csproj @@ -320,6 +320,7 @@ + diff --git a/src/Orchard.Tests/Services/YamlParserTests.cs b/src/Orchard.Tests/Services/YamlParserTests.cs new file mode 100644 index 000000000..19ece43fa --- /dev/null +++ b/src/Orchard.Tests/Services/YamlParserTests.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using NUnit.Framework; +using Newtonsoft.Json.Linq; +using Orchard.Services; + +namespace Orchard.Tests.Services { + + [TestFixture] + public class YamlParserTests { + + [Test] + public void ShouldConvertYamlToWellknowType() { + var parser = new YamlParser(); + var yaml = SampleYamlDocument; + + var order = parser.Deserialize(yaml); + + Assert.AreEqual("Nikola", order.Customer.FirstName); + Assert.AreEqual(2, order.Items.Count); + } + + [Test] + public void ShouldConvertYamlToDynamic() + { + var parser = new YamlParser(); + var yaml = SampleYamlDocument; + + var order = parser.Deserialize(yaml); + + Assert.AreEqual("Nikola", (string)order.Customer.FirstName); + Assert.AreEqual(2, (int)order.Items.Count); + } + + public class Order { + public DateTime Date { get; set; } + public Customer Customer { get; set; } + public IList Items { get; set; } + } + + public class OrderItem { + public string Product { get; set; } + public int Quantity { get; set; } + public decimal Price { get; set; } + } + + public class Customer { + public string FirstName { get; set; } + public string LastName { get; set; } + + } + + private const string SampleYamlDocument = +@" +Date: 1916-04-01 +Customer: + FirstName: Nikola + LastName: Tesla +Items: + - Product: Bulb + Quantity: 1 + Price: 1.46 + + - Product: Wire + Quantity: 1 + Price: 0.32 +"; + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Layouts/Providers/SnippetElementHarvester.cs b/src/Orchard.Web/Modules/Orchard.Layouts/Providers/SnippetElementHarvester.cs index 8cab0b828..1333ef9c5 100644 --- a/src/Orchard.Web/Modules/Orchard.Layouts/Providers/SnippetElementHarvester.cs +++ b/src/Orchard.Web/Modules/Orchard.Layouts/Providers/SnippetElementHarvester.cs @@ -17,13 +17,14 @@ using Orchard.Layouts.Services; using Orchard.Layouts.Shapes; using Orchard.Layouts.ViewModels; using Orchard.Localization; +using Orchard.Services; using Orchard.Themes.Services; using Orchard.Tokens; using Orchard.Utility.Extensions; namespace Orchard.Layouts.Providers { [OrchardFeature("Orchard.Layouts.Snippets")] - public class SnippetElementHarvester : Component, IElementHarvester { + public class SnippetElementHarvester : IElementHarvester { private const string SnippetShapeSuffix = "Snippet"; private readonly Work _shapeFactory; private readonly Work _siteThemeService; @@ -33,6 +34,7 @@ namespace Orchard.Layouts.Providers { private readonly Work _currentThemeShapeBindingResolver; private readonly Work _tokenizer; private readonly IWorkContextAccessor _wca; + private readonly Work _yamlParser; public SnippetElementHarvester( IWorkContextAccessor workContextAccessor, @@ -42,7 +44,8 @@ namespace Orchard.Layouts.Providers { Work elementFactory, Work shapeDisplay, Work tokenizer, - Work currentThemeShapeBindingResolver) { + Work currentThemeShapeBindingResolver, + Work yamlParser) { _shapeFactory = shapeFactory; _siteThemeService = siteThemeService; @@ -51,6 +54,7 @@ namespace Orchard.Layouts.Providers { _shapeDisplay = shapeDisplay; _tokenizer = tokenizer; _currentThemeShapeBindingResolver = currentThemeShapeBindingResolver; + _yamlParser = yamlParser; _wca = workContextAccessor; } @@ -65,12 +69,13 @@ namespace Orchard.Layouts.Providers { var shapeType = shapeDescriptor.Value.ShapeType; var elementName = GetDisplayName(shapeDescriptor.Value.BindingSource); var closureDescriptor = shapeDescriptor; - yield return new ElementDescriptor(elementType, shapeType, T(elementName), T("An element that renders the {0} shape.", shapeType), snippetElement.Category) { - Displaying = displayContext => Displaying(displayContext, closureDescriptor.Value), + var snippetDescriptor = ParseSnippetDescriptor(shapeDescriptor.Value.BindingSource); + yield return new ElementDescriptor(elementType, shapeType, new LocalizedString(elementName), new LocalizedString(String.Format("An element that renders the {0} shape.", shapeType)), snippetElement.Category) { + Displaying = displayContext => Displaying(displayContext, closureDescriptor.Value, snippetDescriptor), ToolboxIcon = "\uf10c", - EnableEditorDialog = HasSnippetFields(shapeDescriptor.Value), - Editor = ctx => Editor(DescribeSnippet(shapeType, snippetElement), ctx), - UpdateEditor = ctx => UpdateEditor(DescribeSnippet(shapeType, snippetElement), ctx) + EnableEditorDialog = snippetDescriptor != null || HasSnippetFields(shapeDescriptor.Value), + Editor = ctx => Editor(snippetDescriptor ?? DescribeSnippet(shapeType, snippetElement), ctx), + UpdateEditor = ctx => UpdateEditor(snippetDescriptor ?? DescribeSnippet(shapeType, snippetElement), ctx) }; } } @@ -113,22 +118,53 @@ namespace Orchard.Layouts.Providers { context.EditorResult.Add(snippetEditorShape); } - private void Displaying(ElementDisplayingContext context, ShapeDescriptor shapeDescriptor) { + private void Displaying(ElementDisplayingContext context, ShapeDescriptor shapeDescriptor, SnippetDescriptor snippetDescriptor) { var shapeType = shapeDescriptor.ShapeType; var shape = (dynamic)_shapeFactory.Value.Create(shapeType); shape.Element = context.Element; + if (snippetDescriptor != null) { + foreach (var fieldDescriptor in snippetDescriptor.Fields) { + var value = context.Element.Data.Get(fieldDescriptor.Name); + shape.Properties[fieldDescriptor.Name] = value; + } + } + ElementShapes.AddTokenizers(shape, _tokenizer.Value); context.ElementShape.Snippet = shape; } private string GetDisplayName(string bindingSource) { - var fileName = Path.GetFileNameWithoutExtension(bindingSource); + var fileName = Path.GetFileNameWithoutExtension(bindingSource) ?? ""; var lastIndex = fileName.IndexOf(SnippetShapeSuffix, StringComparison.OrdinalIgnoreCase); return fileName.Substring(0, lastIndex).CamelFriendly(); } + private SnippetDescriptor ParseSnippetDescriptor(string bindingSource) { + var physicalSourcePath = _wca.GetContext().HttpContext.Server.MapPath(bindingSource); + var paramsFileName = Path.Combine(Path.GetDirectoryName(physicalSourcePath) ?? "", Path.GetFileNameWithoutExtension(physicalSourcePath) + ".txt"); + + if (!File.Exists(paramsFileName)) + return null; + + var yaml = File.ReadAllText(paramsFileName); + var snippetConfig = _yamlParser.Value.Deserialize(yaml); + var fieldsConfig = snippetConfig.Fields != null ? snippetConfig.Fields.Children : new dynamic[0]; + var descriptor = new SnippetDescriptor(); + + foreach (var fieldConfig in fieldsConfig) { + descriptor.Fields.Add(new SnippetFieldDescriptor { + Name = (string)fieldConfig.Name, + Type = (string)fieldConfig.Type, + DisplayName = new LocalizedString((string)fieldConfig.DisplayName), + Description = new LocalizedString((string)fieldConfig.Description) + }); + } + + return descriptor; + } + private SnippetDescriptor DescribeSnippet(string shapeType, Snippet element) { var shape = (dynamic)_shapeFactory.Value.Create(shapeType); shape.Element = element; diff --git a/src/Orchard.Web/Web.config b/src/Orchard.Web/Web.config index 2106f9674..cc10d9dab 100644 --- a/src/Orchard.Web/Web.config +++ b/src/Orchard.Web/Web.config @@ -225,7 +225,11 @@ - + + + + + diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 5cb38eb1d..be276337b 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -166,6 +166,14 @@ + + ..\packages\YamlDotNet.3.8.0\lib\net35\YamlDotNet.dll + True + + + ..\packages\YamlDotNet.Dynamic.3.2.3\lib\net40\YamlDotNet.Dynamic.dll + True + @@ -419,7 +427,9 @@ + + diff --git a/src/Orchard/Services/IYamlParser.cs b/src/Orchard/Services/IYamlParser.cs new file mode 100644 index 000000000..cf9ea5f2c --- /dev/null +++ b/src/Orchard/Services/IYamlParser.cs @@ -0,0 +1,21 @@ +namespace Orchard.Services { + /// + /// Provides methods to deserialize objects from YAML documents. + /// + public interface IYamlParser : IDependency { + /// + /// Deserializes a YAML document to a dynamic object. + /// + /// The YAML document to deserialize. + /// The deserialized object. + dynamic Deserialize(string yaml); + + /// + /// Deserializes a YAML document to a specific object. + /// + /// The type of the object to deserialize. + /// The YAML document to deserialize. + /// The deserialized object. + T Deserialize(string yaml); + } +} diff --git a/src/Orchard/Services/YamlParser.cs b/src/Orchard/Services/YamlParser.cs new file mode 100644 index 000000000..b783aad5c --- /dev/null +++ b/src/Orchard/Services/YamlParser.cs @@ -0,0 +1,19 @@ +using System.IO; +using YamlDotNet.Dynamic; +using YamlDotNet.Serialization; +using YamlDotNet.Serialization.NamingConventions; + +namespace Orchard.Services { + public class YamlParser : IYamlParser { + public dynamic Deserialize(string yaml) { + return new DynamicYaml(yaml); + } + + public T Deserialize(string yaml) { + var deserializer = new Deserializer(namingConvention: new PascalCaseNamingConvention(), ignoreUnmatched: true); + using (var reader = new StringReader(yaml)) { + return deserializer.Deserialize(reader); + } + } + } +} \ No newline at end of file diff --git a/src/Orchard/packages.config b/src/Orchard/packages.config index 2583dd2b6..f5f57f9e4 100644 --- a/src/Orchard/packages.config +++ b/src/Orchard/packages.config @@ -17,4 +17,6 @@ + + \ No newline at end of file