Skip to content

Commit 1d87b15

Browse files
fix: Fix multilanguage properties (#119)
Co-authored-by: Ahmed Rizk <rizkengin@gmail.com>
1 parent e0a6747 commit 1d87b15

File tree

5 files changed

+153
-60
lines changed

5 files changed

+153
-60
lines changed

src/VirtoCommerce.CatalogCsvImportModule.Data/Services/CsvCatalogImporter.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -760,8 +760,8 @@ private async Task LoadProductDependencies(IEnumerable<CsvProduct> csvProducts,
760760
propertyValue.PropertyId = inheritedProperty.Id;
761761
}
762762

763-
//Try to split the one value to multiple values for Multivalue properties
764-
if (inheritedProperty.Multivalue)
763+
//Try to split the one value to multiple values for Multivalue/Multilanguage properties
764+
if (inheritedProperty.Multivalue || inheritedProperty.Multilanguage)
765765
{
766766
var parsedValues = new List<PropertyValue>();
767767

@@ -776,7 +776,7 @@ private async Task LoadProductDependencies(IEnumerable<CsvProduct> csvProducts,
776776
else if (property.Values.Count > 1)
777777
{
778778
var propertyValue = property.Values.First();
779-
propertyValue.Value = string.Join(CsvReaderExtension.Delimiter, property.Values.Select(x => x.Value));
779+
propertyValue.Value = property.Values.Join();
780780
property.Values = new List<PropertyValue> { propertyValue };
781781
}
782782
}

src/VirtoCommerce.CatalogCsvImportModule.Data/Services/CsvProductMap.cs

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -77,31 +77,7 @@ public virtual void Initialize(CsvProductMappingConfiguration mappingCfg)
7777
csvPropertyMap.UsingExpression<ICollection<Property>>(null, properties =>
7878
{
7979
var property = properties.FirstOrDefault(x => x.Name == propertyCsvColumn && x.Values.Any());
80-
var propertyValues = Array.Empty<string>();
81-
if (property != null)
82-
{
83-
if (property.Dictionary)
84-
{
85-
propertyValues = property.Values
86-
?.Where(x => !string.IsNullOrEmpty(x.Alias))
87-
.Select(x => x.Alias)
88-
.Distinct()
89-
.ToArray();
90-
}
91-
else if (property.Multilanguage)
92-
{
93-
propertyValues = property.Values.Select(v => string.Join(null, v.LanguageCode, CsvReaderExtension.InnerDelimiter, v.Value)).ToArray();
94-
}
95-
else
96-
{
97-
propertyValues = property.Values
98-
?.Where(x => x.Value != null || x.Alias != null)
99-
.Select(x => x.Alias ?? x.Value.ToString())
100-
.ToArray();
101-
}
102-
}
103-
104-
return string.Join(mappingCfg.Delimiter, propertyValues);
80+
return property.JoinValues();
10581
});
10682

10783
MemberMaps.Add(csvPropertyMap);
Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System;
21
using System.Collections.Generic;
32
using System.Linq;
43
using CsvHelper;
@@ -10,22 +9,65 @@ public static class CsvReaderExtension
109
{
1110
public static string Delimiter { get; set; } = ";";
1211
public static string InnerDelimiter { get; set; } = "__";
12+
1313
public static IEnumerable<PropertyValue> GetPropertiesByColumn(this IReaderRow reader, string columnName)
1414
{
1515
var columnValue = reader.GetField<string>(columnName);
16+
1617
foreach (var value in columnValue.Trim().Split(Delimiter))
1718
{
18-
var multilanguage = value.Contains(InnerDelimiter);
19-
var splitedValues = value.Split(InnerDelimiter);
20-
var languageCode = splitedValues.First();
21-
var propertyValue = multilanguage ? splitedValues.Last() : value;
19+
const int multilanguagePartsCount = 2;
20+
var valueParts = value.Split(InnerDelimiter, multilanguagePartsCount);
21+
var multilanguage = valueParts.Length == multilanguagePartsCount;
22+
2223
yield return new PropertyValue
2324
{
2425
PropertyName = columnName,
25-
Value = propertyValue,
26-
LanguageCode = multilanguage ? languageCode : string.Empty
26+
Value = multilanguage ? valueParts.Last() : value,
27+
LanguageCode = multilanguage ? valueParts.First() : string.Empty,
2728
};
2829
}
2930
}
31+
32+
public static string JoinValues(this Property property)
33+
{
34+
if (property?.Values is null)
35+
{
36+
return string.Empty;
37+
}
38+
39+
IEnumerable<string> values;
40+
41+
if (property.Dictionary)
42+
{
43+
values = property.Values
44+
.Where(x => !string.IsNullOrEmpty(x.Alias))
45+
.Select(x => x.Alias)
46+
.Distinct();
47+
}
48+
else if (property.Multilanguage)
49+
{
50+
values = property.Values
51+
.Select(x => $"{x.LanguageCode}{InnerDelimiter}{x.Value}");
52+
}
53+
else
54+
{
55+
values = property.Values
56+
.Where(x => x.Value != null || x.Alias != null)
57+
.Select(x => x.Alias ?? x.Value?.ToString());
58+
}
59+
60+
return JoinCsvValues(values);
61+
}
62+
63+
public static string Join(this IList<PropertyValue> propertyValues)
64+
{
65+
return JoinCsvValues(propertyValues.Select(x => x.Value?.ToString()));
66+
}
67+
68+
public static string JoinCsvValues(IEnumerable<string> values)
69+
{
70+
return string.Join(Delimiter, values);
71+
}
3072
}
3173
}

tests/VirtoCommerce.CatalogCsvImportModule.Test/ImporterTests.cs

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,56 @@ public void ExtractFileNameFromUrl_ValidUrls_ReturnsExpectedFileName(string url,
8282
Assert.Equal(expectedFileName, result);
8383
}
8484

85+
[Theory]
86+
[InlineData(",")]
87+
[InlineData(";")]
88+
public async Task DoImport_NewProductMultilanguageProperty_ValuesCreated(string delimiter)
89+
{
90+
//Arrange
91+
var product = GetCsvProductBase();
92+
product.Properties = new List<Property>
93+
{
94+
new CsvProperty
95+
{
96+
Name = "CatalogProductProperty_Multilanguage",
97+
Multilanguage = true,
98+
Values = new List<PropertyValue>
99+
{
100+
new()
101+
{
102+
PropertyName = "CatalogProductProperty_Multilanguage",
103+
LanguageCode = "en-US",
104+
Value = "value-en",
105+
ValueType = PropertyValueType.ShortText,
106+
},
107+
new()
108+
{
109+
PropertyName = "CatalogProductProperty_Multilanguage",
110+
LanguageCode = "de-DE",
111+
Value = "value-de",
112+
ValueType = PropertyValueType.ShortText,
113+
},
114+
}
115+
},
116+
};
117+
118+
var target = GetImporter();
119+
120+
var progressInfo = new ExportImportProgressInfo();
121+
122+
//Act
123+
await target.DoImport([product], GetCsvImportInfo(delimiter), progressInfo, _ => { });
124+
125+
//Assert
126+
Action<PropertyValue>[] inspectors =
127+
[
128+
x => Assert.True(x.PropertyName == "CatalogProductProperty_Multilanguage" && x.LanguageCode == "en-US" && (string)x.Value == "value-en"),
129+
x => Assert.True(x.PropertyName == "CatalogProductProperty_Multilanguage" && x.LanguageCode == "de-DE" && (string)x.Value == "value-de"),
130+
];
131+
Assert.Collection(product.Properties.SelectMany(x => x.Values), inspectors);
132+
Assert.Empty(progressInfo.Errors);
133+
}
134+
85135
[Fact]
86136
public async Task DoImport_NewProductMultivalueDictionaryProperties_PropertyValuesCreated()
87137
{
@@ -1770,6 +1820,16 @@ private static Catalog CreateCatalog()
17701820
ValueType = PropertyValueType.ShortText
17711821
};
17721822

1823+
var catalogProductProperty9 = new Property
1824+
{
1825+
Name = "CatalogProductProperty_Multilanguage",
1826+
Id = "CatalogProductProperty_Multilanguage",
1827+
CatalogId = catalog.Id,
1828+
Multilanguage = true,
1829+
Type = PropertyType.Product,
1830+
ValueType = PropertyValueType.ShortText,
1831+
};
1832+
17731833
catalog.Properties.Add(catalogProductProperty);
17741834
catalog.Properties.Add(catalogProductProperty2);
17751835

@@ -1782,6 +1842,8 @@ private static Catalog CreateCatalog()
17821842
catalog.Properties.Add(catalogProductProperty7);
17831843
catalog.Properties.Add(catalogProductProperty8);
17841844

1845+
catalog.Properties.Add(catalogProductProperty9);
1846+
17851847
return catalog;
17861848
}
17871849

@@ -1863,12 +1925,15 @@ private Category CreateCategory(CsvProduct existingProduct)
18631925
return category;
18641926
}
18651927

1866-
private CsvImportInfo GetCsvImportInfo()
1928+
private CsvImportInfo GetCsvImportInfo(string delimiter = ";")
18671929
{
1930+
var configuration = CsvProductMappingConfiguration.GetDefaultConfiguration();
1931+
configuration.Delimiter = delimiter;
1932+
18681933
return new CsvImportInfo
18691934
{
18701935
CatalogId = _catalog.Id,
1871-
Configuration = CsvProductMappingConfiguration.GetDefaultConfiguration(),
1936+
Configuration = configuration,
18721937
};
18731938
}
18741939
}

tests/VirtoCommerce.CatalogCsvImportModule.Test/MappingTests.cs

Lines changed: 33 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,10 @@ public void CsvHeadersExportTest_DefaultConfiguration_HeadersAreSame()
245245
}
246246
}
247247

248-
[Fact]
249-
public void CsvProductMapTest_DictionaryMultilanguage_OnlyOneAliasExported()
248+
[Theory]
249+
[InlineData(",")]
250+
[InlineData(";")]
251+
public void CsvProductMapTest_DictionaryMultilanguage_OnlyOneAliasExported(string delimiter)
250252
{
251253
//Arrange
252254
var product = GetProduct();
@@ -267,16 +269,18 @@ public void CsvProductMapTest_DictionaryMultilanguage_OnlyOneAliasExported()
267269
};
268270

269271
//Act
270-
var importedCsvProduct = ExportAndImportProduct(product);
272+
var importedCsvProduct = ExportAndImportProduct(product, delimiter);
271273

272274
//Assert
273275
importedCsvProduct.Properties.Should().HaveCount(1);
274276
importedCsvProduct.Properties.First().Values.Should().HaveCount(1);
275277
importedCsvProduct.Properties.First().Values.First().Value.ToString().Should().BeEquivalentTo("A");
276278
}
277279

278-
[Fact]
279-
public void CsvProductMapTest_DictionaryMultivalue_OnlyUniqAliasesExported()
280+
[Theory]
281+
[InlineData(",")]
282+
[InlineData(";")]
283+
public void CsvProductMapTest_DictionaryMultivalue_OnlyUniqAliasesExported(string delimiter)
280284
{
281285
//Arrange
282286
var product = GetProduct();
@@ -300,7 +304,7 @@ public void CsvProductMapTest_DictionaryMultivalue_OnlyUniqAliasesExported()
300304
};
301305

302306
//Act
303-
var importedCsvProduct = ExportAndImportProduct(product);
307+
var importedCsvProduct = ExportAndImportProduct(product, delimiter);
304308

305309
//Assert
306310
importedCsvProduct.Properties.Should().HaveCount(1);
@@ -310,8 +314,10 @@ public void CsvProductMapTest_DictionaryMultivalue_OnlyUniqAliasesExported()
310314
}
311315

312316

313-
[Fact]
314-
public void CsvProductMapTest_Multilanguage_AllValueExported()
317+
[Theory]
318+
[InlineData(",")]
319+
[InlineData(";")]
320+
public void CsvProductMapTest_Multilanguage_AllValueExported(string delimiter)
315321
{
316322
//Arrange
317323
var product = GetProduct();
@@ -332,7 +338,7 @@ public void CsvProductMapTest_Multilanguage_AllValueExported()
332338
};
333339

334340
//Act
335-
var importedCsvProduct = ExportAndImportProduct(product);
341+
var importedCsvProduct = ExportAndImportProduct(product, delimiter);
336342

337343
//Assert
338344
importedCsvProduct.Properties.Should().HaveCount(1);
@@ -387,22 +393,26 @@ private CatalogProduct GetProduct()
387393
.Create();
388394
}
389395

390-
private CsvProduct ExportAndImportProduct(CatalogProduct product)
396+
private static CsvProduct ExportAndImportProduct(CatalogProduct product, string delimiter)
391397
{
392-
var exportInfo = new CsvExportInfo { Configuration = CsvProductMappingConfiguration.GetDefaultConfiguration() };
398+
var configuration = CsvProductMappingConfiguration.GetDefaultConfiguration();
399+
configuration.Delimiter = delimiter;
400+
configuration.PropertyCsvColumns = product.Properties.Select(x => x.Name).Distinct().ToArray();
401+
402+
var csvProductMap = CsvProductMap.Create(configuration);
403+
393404
using (var stream = new MemoryStream())
394405
{
395-
var streamWriter = new StreamWriter(stream, Encoding.UTF8, 1024, true) { AutoFlush = true };
396-
exportInfo.Configuration.PropertyCsvColumns = product.Properties.Select(x => x.Name).Distinct().ToArray();
406+
var streamWriter = new StreamWriter(stream, Encoding.UTF8, 1024, leaveOpen: true) { AutoFlush = true };
407+
397408
var writerConfig = new CsvConfiguration(CultureInfo.InvariantCulture)
398409
{
399-
Delimiter = exportInfo.Configuration.Delimiter
410+
Delimiter = configuration.Delimiter,
400411
};
401412

402413
using (var csvWriter = new CsvWriter(streamWriter, writerConfig))
403414
{
404-
csvWriter.Context.RegisterClassMap(CsvProductMap.Create(exportInfo.Configuration));
405-
415+
csvWriter.Context.RegisterClassMap(csvProductMap);
406416
csvWriter.WriteHeader<CsvProduct>();
407417
csvWriter.NextRecord();
408418
var csvProduct = CsvProduct.Create(product, null, null, null, null);
@@ -413,18 +423,18 @@ private CsvProduct ExportAndImportProduct(CatalogProduct product)
413423

414424
var readerConfig = new CsvConfiguration(CultureInfo.InvariantCulture)
415425
{
416-
Delimiter = exportInfo.Configuration.Delimiter,
426+
Delimiter = configuration.Delimiter,
417427
TrimOptions = TrimOptions.Trim,
418-
MissingFieldFound = args => { }
428+
MissingFieldFound = _ => { },
419429
};
420-
using (var reader = new CsvReader(new StreamReader(stream, Encoding.UTF8), readerConfig))
430+
431+
using (var csvReader = new CsvReader(new StreamReader(stream, Encoding.UTF8), readerConfig))
421432
{
422-
reader.Context.RegisterClassMap(CsvProductMap.Create(exportInfo.Configuration));
423-
reader.Read();
424-
return reader.GetRecord<CsvProduct>();
433+
csvReader.Context.RegisterClassMap(csvProductMap);
434+
csvReader.Read();
435+
return csvReader.GetRecord<CsvProduct>();
425436
}
426437
}
427-
428438
}
429439
}
430440
}

0 commit comments

Comments
 (0)