diff --git a/app/Umbraco/Archetype.Tests/Serialization/Base/RegressionTestBase.cs b/app/Umbraco/Archetype.Tests/Serialization/Base/RegressionTestBase.cs index d06383d..db9b84b 100644 --- a/app/Umbraco/Archetype.Tests/Serialization/Base/RegressionTestBase.cs +++ b/app/Umbraco/Archetype.Tests/Serialization/Base/RegressionTestBase.cs @@ -1,20 +1,12 @@ -using System; -using System.Collections; -using System.Collections.Generic; +using System.Collections; using System.Linq; using System.Reflection; -using Archetype.PropertyEditors; using Archetype.Serialization; using Moq; using Newtonsoft.Json; -using Newtonsoft.Json.Linq; using NUnit.Framework; using Umbraco.Core; -using Umbraco.Core.Models; -using Umbraco.Core.Models.Editors; -using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence; -using Umbraco.Core.PropertyEditors; using Umbraco.Core.Services; namespace Archetype.Tests.Serialization.Base @@ -93,59 +85,11 @@ namespace Archetype.Tests.Serialization.Base AssertAreEqual(model, resultfromArchetypeJson); } - protected void CompoundModel_WithEscapedJson_Regression_Battery(T model) - { - var propGuid = new Guid(); - var dtd = new DataTypeDefinition(-1, String.Empty) {Id = 0}; - - var dataTypeService = new Mock(); - dataTypeService.Setup(dts => dts.GetDataTypeDefinitionById(Guid.Parse(propGuid.ToString()))).Returns(dtd); - dataTypeService.Setup(dts => dts.GetPreValuesCollectionByDataTypeId(dtd.Id)).Returns(new PreValueCollection(new Dictionary())); - - var propValueEditor = new Mock(new PropertyValueEditor()); - propValueEditor.Setup(pe => pe.GetPropertyEditor(dtd)).Returns(new ArchetypePropertyEditor()); - - var archetype = ConvertModelToArchetype(model); - - foreach (var prop in archetype.Fieldsets.SelectMany(fs => fs.Properties)) - { - prop.DataTypeGuid = propGuid.ToString(); - if (prop.Value.ToString().Contains("fieldsets")) - prop.PropertyEditorAlias = Constants.PropertyEditorAlias; - } - - var archetypeJson = JsonConvert.SerializeObject(archetype); - Assert.IsNotNullOrEmpty(archetypeJson); - - EnsureUmbracoContext(dataTypeService); - - var propEditor = new ArchetypePropertyEditor.ArchetypePropertyValueEditor(new PropertyValueEditor()); - var convertedJson = propEditor.ConvertEditorToDb(new ContentPropertyData(JObject.FromObject(archetype), - new PreValueCollection(new Dictionary()), new Dictionary()), - archetypeJson); - - Assert.IsNotNull(convertedJson); - - var resultfromArchetypeJson = ConvertArchetypeJsonToModel(archetypeJson); - var resultfromConvertedJson = ConvertArchetypeJsonToModel(convertedJson.ToString().DelintArchetypeJson()); - - Assert.IsInstanceOf(resultfromArchetypeJson); - AssertAreEqual(model, resultfromArchetypeJson); - - Assert.IsInstanceOf(resultfromConvertedJson); - AssertAreEqual(model, resultfromConvertedJson); - } - protected void NestedModel_Regression_Battery(T model) { CompoundModel_Regression_Battery(model); } - protected void NestedModel_WithEscapedJson_Regression_Battery(T model) - { - CompoundModel_WithEscapedJson_Regression_Battery(model); - } - protected void ComplexModel_Regression_Battery(T model) { CompoundModel_Regression_Battery(model); diff --git a/app/Umbraco/Archetype.Tests/Serialization/Delinter/ArchetypeJsonDelinterTest.cs b/app/Umbraco/Archetype.Tests/Serialization/Delinter/ArchetypeJsonDelinterTest.cs index 7c92ba2..dcb02a4 100644 --- a/app/Umbraco/Archetype.Tests/Serialization/Delinter/ArchetypeJsonDelinterTest.cs +++ b/app/Umbraco/Archetype.Tests/Serialization/Delinter/ArchetypeJsonDelinterTest.cs @@ -49,7 +49,46 @@ namespace Archetype.Tests.Serialization.Delinter _nestedClass.Nested.SlideShow = _referenceModel; _nestedClass.Nested.Nested = GetNestedClass(); - } + } + + [Test] + public void Delinter_RemoveNewLine_ByPasses_Values() + { + const string _JSON_WITH_RESERVED_CHARS = @"{ + ""alias"": ""captions"", + ""value"": ""{ \""fieldsets\"": [ +{ + \""properties\"": + [ +{ \""alias\"": \""textstring\"" , \""value\"":\""test1 \r\n test1\r\n\""}], \""alias\"":\""textstringArray\"" },{\""properties\"": [ { \""alias\"":\""textstring\"",\""value\"":\""test2\r\ntest2\""}],\""alias\"":\""textstringArray\""},{\""properties\"":[{\""alias\"":\""textstring\"",\""value\"":\""test3b \""quote\"" \\r\\r\\rtest3\""}],\""alias\"":\""textstringArray\"" } ] }"" +}"; + + var referenceModel = new Captions() + { + new TextString() {Text = @"test1 + test1 +"}, + new TextString() {Text = @"test2 +test2"}, + new TextString() {Text = @"test3b ""quote"" \r\r\rtest3"} + }; + + var delintedJson = _JSON_WITH_RESERVED_CHARS.DelintArchetypeJson(); + var actualModel = ConvertArchetypeJsonToModel(delintedJson); + + var t = JToken.Parse(delintedJson).ToString(Formatting.None); + + Assert.IsInstanceOf(actualModel); + Assert.IsNotNull(actualModel); + + Assert.AreEqual(3, actualModel.Count); + + foreach (var refItem in referenceModel) + { + var index = referenceModel.IndexOf(refItem); + AssertAreEqual(refItem, actualModel.ElementAt(index)); + } + } [Test] public void ReferenceJsonAndModel_Equal_ExpectedJsonAndModel() diff --git a/app/Umbraco/Archetype.Tests/Serialization/Regression/ArchetypeJsonConverterTest.cs b/app/Umbraco/Archetype.Tests/Serialization/Regression/ArchetypeJsonConverterTest.cs index 4246963..83f4384 100644 --- a/app/Umbraco/Archetype.Tests/Serialization/Regression/ArchetypeJsonConverterTest.cs +++ b/app/Umbraco/Archetype.Tests/Serialization/Regression/ArchetypeJsonConverterTest.cs @@ -67,13 +67,6 @@ namespace Archetype.Tests.Serialization.Regression CompoundModel_Regression_Battery(compoundModel); } - [Test] - public void CompoundModel_WithEscapedJson_Regression_Battery() - { - var compoundModel = _testHelper.GetModel(); - CompoundModel_WithEscapedJson_Regression_Battery(compoundModel); - } - [Test] public void CompoundModelWithMixedFieldsetVariant1_Regression_Battery() { @@ -81,13 +74,6 @@ namespace Archetype.Tests.Serialization.Regression CompoundModel_Regression_Battery(compoundModel); } - [Test] - public void CompoundModelWithMixedFieldsetVariant1_WithEscapedJson_Regression_Battery() - { - var compoundModel = _testHelper.GetModel(); - CompoundModel_WithEscapedJson_Regression_Battery(compoundModel); - } - [Test] public void CompoundModelWithMixedFieldsetVariant2_Regression_Battery() { @@ -95,13 +81,6 @@ namespace Archetype.Tests.Serialization.Regression CompoundModel_Regression_Battery(compoundModel); } - [Test] - public void CompoundModelWithMixedFieldsetVariant2_WithEscapedJson_Regression_Battery() - { - var compoundModel = _testHelper.GetModel(); - CompoundModel_WithEscapedJson_Regression_Battery(compoundModel); - } - [Test] public void CompoundModelWithFieldset_Regression_Battery() { @@ -109,13 +88,6 @@ namespace Archetype.Tests.Serialization.Regression CompoundModel_Regression_Battery(compoundModel); } - [Test] - public void CompoundModelWithFieldset_WithEscapedJson_Regression_Battery() - { - var compoundModel = _testHelper.GetModel(); - CompoundModel_WithEscapedJson_Regression_Battery(compoundModel); - } - [Test] public void CompoundModelWithList_Regression_Battery() { @@ -123,13 +95,6 @@ namespace Archetype.Tests.Serialization.Regression CompoundModel_Regression_Battery(compoundModelWithList); } - [Test] - public void CompoundModelWithList_WithEscapedJson_Regression_Battery() - { - var compoundModel = _testHelper.GetModel(); - CompoundModel_WithEscapedJson_Regression_Battery(compoundModel); - } - [Test] public void CompoundModelWithMixedFieldsetWithList_Regression_Battery() { @@ -137,13 +102,6 @@ namespace Archetype.Tests.Serialization.Regression CompoundModel_Regression_Battery(compoundModelWithList); } - [Test] - public void CompoundModelWithMixedFieldsetWithList_WithEscapedJson_Regression_Battery() - { - var compoundModel = _testHelper.GetModel(); - CompoundModel_WithEscapedJson_Regression_Battery(compoundModel); - } - [Test] public void CompoundModelWithFieldsetWithList_Regression_Battery() { @@ -151,13 +109,6 @@ namespace Archetype.Tests.Serialization.Regression CompoundModel_Regression_Battery(compoundModelWithList); } - [Test] - public void CompoundModelWithFieldsetWithList_WithEscapedJson_Regression_Battery() - { - var compoundModel = _testHelper.GetModel(); - CompoundModel_WithEscapedJson_Regression_Battery(compoundModel); - } - [Test] public void NestedModel_Regression_Battery() { @@ -165,13 +116,6 @@ namespace Archetype.Tests.Serialization.Regression NestedModel_Regression_Battery(nestedModel); } - [Test] - public void NestedModel_WithEscapedJson_Regression_Battery() - { - var nestedModel = _testHelper.GetModel(); - NestedModel_WithEscapedJson_Regression_Battery(nestedModel); - } - [Test] public void NestedModelWithMixedFieldset_Regression_Battery() { @@ -179,13 +123,6 @@ namespace Archetype.Tests.Serialization.Regression NestedModel_Regression_Battery(nestedModel); } - [Test] - public void NestedModelWithMixedFieldset_WithEscapedJson_Regression_Battery() - { - var nestedModel = _testHelper.GetModel(); - NestedModel_WithEscapedJson_Regression_Battery(nestedModel); - } - [Test] public void NestedModelWithFieldset_Regression_Battery() { @@ -193,13 +130,6 @@ namespace Archetype.Tests.Serialization.Regression NestedModel_Regression_Battery(nestedModel); } - [Test] - public void NestedModelWithFieldset_WithEscapedJson_Regression_Battery() - { - var nestedModel = _testHelper.GetModel(); - NestedModel_WithEscapedJson_Regression_Battery(nestedModel); - } - [Test] public void ComplexModel_Regression_Battery() { diff --git a/app/Umbraco/Archetype.Tests/Serialization/Regression/JsonTestStrings.cs b/app/Umbraco/Archetype.Tests/Serialization/Regression/JsonTestStrings.cs index 72a503a..df58e5d 100644 --- a/app/Umbraco/Archetype.Tests/Serialization/Regression/JsonTestStrings.cs +++ b/app/Umbraco/Archetype.Tests/Serialization/Regression/JsonTestStrings.cs @@ -9,7 +9,7 @@ ""properties"": [ { ""alias"": ""text"", - ""value"": ""Test Text"" + ""value"": ""Test \r\nText \""quote\"" \r\n"" }, { ""alias"": ""integer"", diff --git a/app/Umbraco/Archetype.Tests/Serialization/Regression/SerializationTestHelper.cs b/app/Umbraco/Archetype.Tests/Serialization/Regression/SerializationTestHelper.cs index fc4f8b9..d8e8a54 100644 --- a/app/Umbraco/Archetype.Tests/Serialization/Regression/SerializationTestHelper.cs +++ b/app/Umbraco/Archetype.Tests/Serialization/Regression/SerializationTestHelper.cs @@ -27,7 +27,9 @@ namespace Archetype.Tests.Serialization.Regression DateTwo = null, Id = 123, NullableId = null, - Text = "Test Text" + Text = @"Test +Text ""quote"" +" }; } diff --git a/app/Umbraco/Umbraco.Archetype/PropertyEditors/ArcheTypePropertyEditor.cs b/app/Umbraco/Umbraco.Archetype/PropertyEditors/ArcheTypePropertyEditor.cs index b5b21ba..a4d3e98 100644 --- a/app/Umbraco/Umbraco.Archetype/PropertyEditors/ArcheTypePropertyEditor.cs +++ b/app/Umbraco/Umbraco.Archetype/PropertyEditors/ArcheTypePropertyEditor.cs @@ -126,8 +126,8 @@ namespace Archetype.PropertyEditors { var dtd = ApplicationContext.Current.Services.DataTypeService.GetDataTypeDefinitionById(Guid.Parse(propDef.DataTypeGuid)); var preValues = ApplicationContext.Current.Services.DataTypeService.GetPreValuesCollectionByDataTypeId(dtd.Id); - var propData = new ContentPropertyData(propDef.Value, preValues, new Dictionary()); - var propEditor = GetPropertyEditor(dtd); + var propData = new ContentPropertyData(propDef.Value, preValues, new Dictionary()); + var propEditor = PropertyEditorResolver.Current.GetByAlias(dtd.PropertyEditorAlias); propDef.Value = propEditor.ValueEditor.ConvertEditorToDb(propData, propDef.Value); } catch (Exception ex) diff --git a/app/Umbraco/Umbraco.Archetype/Serialization/ArchetypeJsonConverter.cs b/app/Umbraco/Umbraco.Archetype/Serialization/ArchetypeJsonConverter.cs index ffb6be5..ed50204 100644 --- a/app/Umbraco/Umbraco.Archetype/Serialization/ArchetypeJsonConverter.cs +++ b/app/Umbraco/Umbraco.Archetype/Serialization/ArchetypeJsonConverter.cs @@ -38,22 +38,9 @@ namespace Archetype.Serialization var jToken = JToken.ReadFrom(reader); if (jToken == null) - return null; - - var obj = Activator.CreateInstance(objectType); + return null; - if (null != obj as IEnumerable) - { - JToken enumerableToken; - - if (TryParseJTokenAsEnumerable(jToken, out enumerableToken) - || TryParseJTokenAsEnumerable(jToken["value"], out enumerableToken)) - return DeserializeEnumerableObject(obj, enumerableToken); - } - - return null == jToken as JArray - ? DeserializeObject(obj, jToken) - : PopulateProperties(obj, jToken); + return DeserializeJson(objectType, GetDelintedJToken(jToken)); } public override bool CanConvert(Type objectType) @@ -63,17 +50,48 @@ namespace Archetype.Serialization #endregion - #region private methods - deserialization + #region private methods - deserialization + + private JToken GetDelintedJToken(JToken jToken) + { + return JToken.Parse(jToken.ToString(Formatting.None).DelintArchetypeJson()); + } + + private object DeserializeJson(Type objectType, JToken jToken) + { + + var obj = Activator.CreateInstance(objectType); + + if (null != obj as IEnumerable) + { + JToken enumerableToken; + + if (TryParseJTokenAsEnumerable(jToken, out enumerableToken) + || (jToken.SelectToken("value") != null && TryParseJTokenAsEnumerable(jToken["value"], out enumerableToken))) + return DeserializeEnumerableObject(obj, enumerableToken); + } + + return null == jToken as JArray + ? DeserializeObject(obj, jToken) + : PopulateProperties(obj, jToken); + } private object DeserializeEnumerableObject(object obj, JToken jToken) { - var model = obj as IEnumerable; - - var itemType = model.GetType().BaseType.GetGenericArguments().First(); - foreach (var fs in jToken["fieldsets"].Where(fs => fs["alias"].ToString().Equals(GetFieldsetName(itemType)))) - { - var item = JsonConvert.DeserializeObject( - fs["properties"].ToString().DelintArchetypeJson(), itemType, this); + var model = obj as IEnumerable; + var fsToken = jToken; + + var itemType = model.GetType().GetGenericArguments().FirstOrDefault(); + + if (itemType == null) + { + itemType = model.GetType().BaseType.GetGenericArguments().First(); + fsToken = fsToken["fieldsets"]; + } + + foreach (var fs in fsToken.Where(fs => fs["alias"].ToString().Equals(GetFieldsetName(itemType)))) + { + var item = DeserializeJson(itemType, fs["properties"]); obj.GetType().GetMethod("Add").Invoke(obj, new[] { item }); } @@ -208,15 +226,12 @@ namespace Archetype.Serialization private object GetPropertyValue(PropertyInfo propertyInfo, JToken propJToken) { return IsTypeArchetypeDatatype(propertyInfo.PropertyType) - ? JsonConvert.DeserializeObject(propJToken.ToString().DelintArchetypeJson(), propertyInfo.PropertyType, - this) + ? DeserializeJson(propertyInfo.PropertyType, propJToken) : IsTypeIEnumerableArchetypeDatatype(propertyInfo.PropertyType) - ? JsonConvert.DeserializeObject(propJToken["value"].SelectToken("fieldsets").ToString(), propertyInfo.PropertyType, - this) + ? DeserializeJson(propertyInfo.PropertyType, propJToken["value"].SelectToken("fieldsets")) : GetDeserializedPropertyValue(propJToken, propertyInfo.PropertyType); } - private JToken ParseJTokenFromItem(JToken jToken, string itemAlias) { return (jToken.SelectToken("alias") != null && jToken["alias"].ToString().Equals(itemAlias)) @@ -266,8 +281,14 @@ namespace Archetype.Serialization resultToken = null; //To Do: Strange newtonsoft behaviour - var fsToken = jToken.Parent == null ? jToken["fieldsets"] : jToken.Parent.Children().First(); - var jTokenEnumerable = jToken != null && fsToken != null && fsToken.Any(); + var fsToken = jToken.Parent != null + ? jToken.Parent.Children().First() is JArray + ? jToken.Parent.Children().First() + : jToken.Parent.Children().First()["fieldsets"] + : jToken.SelectToken("fieldsets"); + + var jTokenEnumerable = jToken != null + && fsToken != null && fsToken is JArray; if (jTokenEnumerable) resultToken = jToken; diff --git a/app/Umbraco/Umbraco.Archetype/Serialization/ArchetypeJsonDelinter.cs b/app/Umbraco/Umbraco.Archetype/Serialization/ArchetypeJsonDelinter.cs index ef7b758..ea2c55c 100644 --- a/app/Umbraco/Umbraco.Archetype/Serialization/ArchetypeJsonDelinter.cs +++ b/app/Umbraco/Umbraco.Archetype/Serialization/ArchetypeJsonDelinter.cs @@ -1,24 +1,24 @@ using System; +using System.Linq; using System.Collections.Generic; using System.Text.RegularExpressions; namespace Archetype.Serialization { public enum DelinterStep - { + { RemoveNewLine = 0, - LabelFieldsets, - LabelProperties, - LabelAlias, - LabelValue, + RemoveWhiteSpace, + UnescapeLabels, + UnescapeAlias, UnescapeValues, - FixNestedFieldsets, - RemoveWhiteSpace + FixNestedFieldsets } public enum DelinterAction { Remove = 0, + RemoveWhiteSpace, UnescapeLabels, UnescapeValues, FixNestedFieldsets @@ -26,9 +26,9 @@ namespace Archetype.Serialization public class ArchetypeJsonDelinter { - public IDictionary Pipeline { get; set; } - public IDictionary Tokens { get; set; } - public IDictionary> Actions { get; set; } + public IDictionary Pipeline { get; private set; } + public IDictionary Tokens { get; private set; } + public IDictionary> Actions { get; private set; } public ArchetypeJsonDelinter() { @@ -42,11 +42,7 @@ namespace Archetype.Serialization foreach (var step in Pipeline.Keys) { var action = Pipeline[step]; - while (Tokens[step].IsMatch(buffer)) - { - buffer = Tokens[step] - .Replace(buffer, match => Actions[action](match)); - } + buffer = Actions[action](buffer, Tokens[step]); } return buffer; @@ -56,35 +52,54 @@ namespace Archetype.Serialization { Tokens = new Dictionary { - {DelinterStep.RemoveNewLine, new Regex(@"\\r|\\n|[\r\n]", RegexOptions.Multiline)}, - {DelinterStep.LabelFieldsets, new Regex(@"\s*\\+""(fieldsets)\\+"":\s*")}, - {DelinterStep.LabelProperties, new Regex(@"\s*\\+""(properties)\\+"":\s*")}, - {DelinterStep.LabelAlias, new Regex(@"\s*\\+""(alias)\\+"":\s*")}, - {DelinterStep.LabelValue, new Regex(@"\s*\\+""(value)\\+"":\s*")}, - {DelinterStep.UnescapeValues, new Regex(@"""(alias|value)"":\\+""(.*?)\\+""")}, - {DelinterStep.FixNestedFieldsets, new Regex(@"""(value)"":\s*?""({""fieldsets"".+?})""")}, - {DelinterStep.RemoveWhiteSpace, new Regex(@"\s+(?=([^""]*""[^""]*"")*[^""]*$)")} + {DelinterStep.RemoveNewLine, new Regex(@"(?<=\,){0,1}(\\+r\\+n)(?=\\*?""(alias|value|properties|fieldsets)\\*?"":\{*?(\\*|""|\[))|(\r|\n)+|(\\+r\\+n)(?=\s*?(\{|\}|\]))|(\\+r\\n+)(?=\s+\\*?""(alias|value|properties|fieldsets))")}, + {DelinterStep.RemoveWhiteSpace, new Regex(@"(""\S+?"":)(\s+?)("")|([\{\[\}\],]|(?> + + Actions = new Dictionary> { - {DelinterAction.Remove, match => String.Empty}, - {DelinterAction.UnescapeLabels, match => String.Format(@"""{0}"":", match.Groups[1])}, - {DelinterAction.UnescapeValues, match => String.Format(@"""{0}"":""{1}""", match.Groups[1], match.Groups[2])}, - {DelinterAction.FixNestedFieldsets, match => String.Format(@"""{0}"":{1}", match.Groups[1], match.Groups[2])} + {DelinterAction.Remove, (input, pattern) => + RecursiveReplace(input, pattern, match => String.Empty) + }, + {DelinterAction.RemoveWhiteSpace, (input, pattern) => + RecursiveReplace(input.Trim(), pattern, match => + String.Format("{0}{1}{2}{3}", match.Groups[1].Value, + match.Groups[3].Value, match.Groups[4].Value, match.Groups[6].Value)) + }, + {DelinterAction.UnescapeLabels, (input, pattern) => + RecursiveReplace(input, pattern, match => String.Format(@"""{0}"":", match.Groups[1].Value)) + }, + {DelinterAction.UnescapeValues, (input, pattern) => + RecursiveReplace(input, pattern, match => String.Format(@"""{0}"":""{1}""", match.Groups[1].Value, match.Groups[2].Value)) + }, + {DelinterAction.FixNestedFieldsets, (input, pattern) => + RecursiveReplace(input, pattern, match => String.Format(@"""{0}"":{{{1}}}", match.Groups[1].Value, match.Groups[2].Value)) + } }; Pipeline = new Dictionary { - {DelinterStep.RemoveNewLine, DelinterAction.Remove}, - {DelinterStep.LabelFieldsets, DelinterAction.UnescapeLabels}, - {DelinterStep.LabelProperties, DelinterAction.UnescapeLabels}, - {DelinterStep.LabelAlias, DelinterAction.UnescapeLabels}, - {DelinterStep.LabelValue, DelinterAction.UnescapeLabels}, + {DelinterStep.RemoveNewLine, DelinterAction.Remove}, + {DelinterStep.UnescapeLabels, DelinterAction.UnescapeLabels}, + {DelinterStep.UnescapeAlias, DelinterAction.UnescapeValues}, {DelinterStep.UnescapeValues, DelinterAction.UnescapeValues}, {DelinterStep.FixNestedFieldsets, DelinterAction.FixNestedFieldsets}, - {DelinterStep.RemoveWhiteSpace, DelinterAction.Remove} + {DelinterStep.RemoveWhiteSpace, DelinterAction.RemoveWhiteSpace}, }; } + + private string RecursiveReplace(string input, Regex pattern, Func matchFunc) + { + var buffer = input; + while (pattern.IsMatch(buffer)) + { + buffer = pattern.Replace(buffer, match => matchFunc(match)); + } + return buffer; + } } }