@joebigelow / wix / commits / 923d3edd

Modularize XmlConfig/@ElementId and add tests for XmlConfig

Fixes wixtoolset/issues#4698 Closes wixtoolset/issues#5447

Rob Mensching committed Jan 9, 2021 at 01:37 UTC 923d3edd21da412b0fd6a09dfd3391913fe18c56
10 files changed +175 -77
src/ca/XmlConfig.cpp
+61 -40
@@ -30,10 +30,10 @@ enum eXmlPreserveDate
30 };
31
32 LPCWSTR vcsXmlConfigQuery =
33 - L"SELECT `Wix4XmlConfig`.`Wix4XmlConfig`, `Wix4XmlConfig`.`File`, `Wix4XmlConfig`.`ElementPath`, `Wix4XmlConfig`.`VerifyPath`, `Wix4XmlConfig`.`Name`, "
33 + L"SELECT `Wix4XmlConfig`.`Wix4XmlConfig`, `Wix4XmlConfig`.`File`, `Wix4XmlConfig`.`ElementId`, `Wix4XmlConfig`.`ElementPath`, `Wix4XmlConfig`.`VerifyPath`, `Wix4XmlConfig`.`Name`, "
34 L"`Wix4XmlConfig`.`Value`, `Wix4XmlConfig`.`Flags`, `Wix4XmlConfig`.`Component_`, `Component`.`Attributes` "
35 L"FROM `Wix4XmlConfig`,`Component` WHERE `Wix4XmlConfig`.`Component_`=`Component`.`Component` ORDER BY `File`, `Sequence`";
36 -enum eXmlConfigQuery { xfqXmlConfig = 1, xfqFile, xfqElementPath, xfqVerifyPath, xfqName, xfqValue, xfqXmlFlags, xfqComponent, xfqCompAttributes };
36 +enum eXmlConfigQuery { xfqXmlConfig = 1, xfqFile, xfqElementId, xfqElementPath, xfqVerifyPath, xfqName, xfqValue, xfqXmlFlags, xfqComponent, xfqCompAttributes };
37
38 struct XML_CONFIG_CHANGE
39 {
@@ -44,6 +44,7 @@ struct XML_CONFIG_CHANGE
44 INSTALLSTATE isAction;
45
46 WCHAR wzFile[MAX_PATH];
47 + LPWSTR pwzElementId;
48 LPWSTR pwzElementPath;
49 LPWSTR pwzVerifyPath;
50 WCHAR wzName[MAX_DARWIN_COLUMN];
@@ -72,26 +73,32 @@ static HRESULT FreeXmlConfigChangeList(
73 pxfcDelete = pxfcList;
74 pxfcList = pxfcList->pxfcNext;
75
76 + if (pxfcDelete->pwzElementId)
77 + {
78 + hr = MemFree(pxfcDelete->pwzElementId);
79 + ExitOnFailure(hr, "failed to free xml config element id in change list item");
80 + }
81 +
82 if (pxfcDelete->pwzElementPath)
83 {
84 hr = MemFree(pxfcDelete->pwzElementPath);
78 - ExitOnFailure(hr, "failed to free xml file element path in change list item");
85 + ExitOnFailure(hr, "failed to free xml config element path in change list item");
86 }
87
88 if (pxfcDelete->pwzVerifyPath)
89 {
90 hr = MemFree(pxfcDelete->pwzVerifyPath);
84 - ExitOnFailure(hr, "failed to free xml file verify path in change list item");
91 + ExitOnFailure(hr, "failed to free xml config verify path in change list item");
92 }
93
94 if (pxfcDelete->pwzValue)
95 {
96 hr = MemFree(pxfcDelete->pwzValue);
90 - ExitOnFailure(hr, "failed to free xml file value in change list item");
97 + ExitOnFailure(hr, "failed to free xml config value in change list item");
98 }
99
100 hr = MemFree(pxfcDelete);
94 - ExitOnFailure(hr, "failed to free xml file change list item");
101 + ExitOnFailure(hr, "failed to free xml config change list item");
102 }
103
104 LExit:
@@ -191,6 +198,10 @@ static HRESULT ReadXmlConfigTable(
198 hr = WcaGetRecordInteger(hRec, xfqXmlFlags, &(*ppxfcTail)->iXmlFlags);
199 ExitOnFailure(hr, "failed to get Wix4XmlConfig flags for Wix4XmlConfig: %ls", (*ppxfcTail)->wzId);
200
201 + // Get the Element Id
202 + hr = WcaGetRecordFormattedString(hRec, xfqElementId, &(*ppxfcTail)->pwzElementId);
203 + ExitOnFailure(hr, "failed to get Element Id for Wix4XmlConfig: %ls", (*ppxfcTail)->wzId);
204 +
205 // Get the Element Path
206 hr = WcaGetRecordFormattedString(hRec, xfqElementPath, &(*ppxfcTail)->pwzElementPath);
207 ExitOnFailure(hr, "failed to get Element Path for Wix4XmlConfig: %ls", (*ppxfcTail)->wzId);
@@ -259,45 +270,55 @@ static HRESULT ProcessChanges(
270 pxfcCheck = *ppxfcHead;
271 while (pxfcCheck)
272 {
262 - if (0 == lstrcmpW(pxfc->pwzElementPath, pxfcCheck->wzId) && 0 == pxfc->iXmlFlags
263 - && XMLCONFIG_CREATE & pxfcCheck->iXmlFlags && XMLCONFIG_ELEMENT & pxfcCheck->iXmlFlags)
273 + if (pxfc->pwzElementId)
274 {
265 - // We found a match. First, take it out of the current list
266 - if (pxfc->pxfcPrev)
275 + if (0 == lstrcmpW(pxfc->pwzElementId, pxfcCheck->wzId)
276 + && 0 == pxfc->iXmlFlags
277 + && XMLCONFIG_CREATE & pxfcCheck->iXmlFlags
278 + && XMLCONFIG_ELEMENT & pxfcCheck->iXmlFlags)
279 {
268 - pxfc->pxfcPrev->pxfcNext = pxfc->pxfcNext;
269 - }
270 - else // it was the head. Update the head
271 - {
272 - *ppxfcHead = pxfc->pxfcNext;
273 - }
280 + // We found a match. First, take it out of the current list
281 + if (pxfc->pxfcPrev)
282 + {
283 + pxfc->pxfcPrev->pxfcNext = pxfc->pxfcNext;
284 + }
285 + else // it was the head. Update the head
286 + {
287 + *ppxfcHead = pxfc->pxfcNext;
288 + }
289
275 - if (pxfc->pxfcNext)
276 - {
277 - pxfc->pxfcNext->pxfcPrev = pxfc->pxfcPrev;
278 - }
290 + if (pxfc->pxfcNext)
291 + {
292 + pxfc->pxfcNext->pxfcPrev = pxfc->pxfcPrev;
293 + }
294
280 - pxfc->pxfcNext = NULL;
281 - pxfc->pxfcPrev = NULL;
295 + pxfc->pxfcNext = NULL;
296 + pxfc->pxfcPrev = NULL;
297
283 - // Now, add this node to the end of the matched node's additional changes list
284 - if (!pxfcCheck->pxfcAdditionalChanges)
285 - {
286 - pxfcCheck->pxfcAdditionalChanges = pxfc;
287 - pxfcCheck->cAdditionalChanges = 1;
298 + // Now, add this node to the end of the matched node's additional changes list
299 + if (!pxfcCheck->pxfcAdditionalChanges)
300 + {
301 + pxfcCheck->pxfcAdditionalChanges = pxfc;
302 + pxfcCheck->cAdditionalChanges = 1;
303 + }
304 + else
305 + {
306 + pxfcLast = pxfcCheck->pxfcAdditionalChanges;
307 + cAdditionalChanges = 1;
308 + while (pxfcLast->pxfcNext)
309 + {
310 + pxfcLast = pxfcLast->pxfcNext;
311 + ++cAdditionalChanges;
312 + }
313 + pxfcLast->pxfcNext = pxfc;
314 + pxfc->pxfcPrev = pxfcLast;
315 + pxfcCheck->cAdditionalChanges = ++cAdditionalChanges;
316 + }
317 }
318 else
319 {
291 - pxfcLast = pxfcCheck->pxfcAdditionalChanges;
292 - cAdditionalChanges = 1;
293 - while (pxfcLast->pxfcNext)
294 - {
295 - pxfcLast = pxfcLast->pxfcNext;
296 - ++cAdditionalChanges;
297 - }
298 - pxfcLast->pxfcNext = pxfc;
299 - pxfc->pxfcPrev = pxfcLast;
300 - pxfcCheck->cAdditionalChanges = ++cAdditionalChanges;
320 + hr = E_NOTFOUND;
321 + ExitOnRootFailure(hr, "failed to find matching ElementId: %ls", pxfc->pwzElementId);
322 }
323 }
324
@@ -381,9 +402,10 @@ static HRESULT WriteChangeData(
402
403 HRESULT hr = S_OK;
404 XML_CONFIG_CHANGE* pxfcAdditionalChanges = NULL;
405 + LPCWSTR wzElementPath = pxfc->pwzElementId ? pxfc->pwzElementId : pxfc->pwzElementPath;
406
385 - hr = WcaWriteStringToCaData(pxfc->pwzElementPath, ppwzCustomActionData);
386 - ExitOnFailure(hr, "failed to write ElementPath to custom action data: %ls", pxfc->pwzElementPath);
407 + hr = WcaWriteStringToCaData(wzElementPath, ppwzCustomActionData);
408 + ExitOnFailure(hr, "failed to write ElementPath to custom action data: %ls", wzElementPath);
409
410 hr = WcaWriteStringToCaData(pxfc->pwzVerifyPath, ppwzCustomActionData);
411 ExitOnFailure(hr, "failed to write VerifyPath to custom action data: %ls", pxfc->pwzVerifyPath);
@@ -1111,4 +1133,3 @@ LExit:
1133 }
1134 return WcaFinalize(er);
1135 }
1114 -
src/test/WixToolsetTest.Util/TestData/XmlConfig/Package.en-us.wxl new
+11
@@ -0,0 +1,11 @@
1 +<?xml version="1.0" encoding="utf-8"?>
2 +
3 +<!--
4 +This file contains the declaration of all the localizable strings.
5 +-->
6 +<WixLocalization xmlns="http://wixtoolset.org/schemas/v4/wxl" Culture="en-US">
7 +
8 + <String Id="DowngradeError">A newer version of [ProductName] is already installed.</String>
9 + <String Id="FeatureTitle">MsiPackage</String>
10 +
11 +</WixLocalization>
src/test/WixToolsetTest.Util/TestData/XmlConfig/Package.wxs new
+27
@@ -0,0 +1,27 @@
1 +<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
2 + <Package Name="MsiPackage" Language="1033" Version="1.0.0.0" Manufacturer="Example Corporation" UpgradeCode="047730a5-30fe-4a62-a520-da9381b8226a">
3 + <MajorUpgrade DowngradeErrorMessage="!(loc.DowngradeError)" />
4 +
5 + <Feature Id="ProductFeature" Title="!(loc.FeatureTitle)">
6 + <Component Id="Del" Directory="INSTALLFOLDER" Guid="3613414c-11f5-40fa-a1f1-a0ba722a6895">
7 + <util:XmlConfig
8 + Id="DelElement"
9 + File="[INSTALLFOLDER]my.xml"
10 + Action="delete"
11 + Node="element"
12 + VerifyPath="xxx"
13 + ElementPath="//root/sub"
14 + On="install"
15 + Sequence="1" />
16 + </Component>
17 + </Feature>
18 + </Package>
19 +
20 + <Fragment>
21 + <Directory Id="TARGETDIR" Name="SourceDir">
22 + <Directory Id="ProgramFilesFolder">
23 + <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
24 + </Directory>
25 + </Directory>
26 + </Fragment>
27 +</Wix>
src/test/WixToolsetTest.Util/TestData/XmlConfigModule/Module.wxs new
+34
@@ -0,0 +1,34 @@
1 +<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs" xmlns:util="http://wixtoolset.org/schemas/v4/wxs/util">
2 + <Module Id="XmlConfigModule" Language="1033" Version="1.0.0.0" Guid="047730a5-30fe-4a62-a520-da9381b8226a">
3 +
4 + <Component Id="Parent" Directory="INSTALLFOLDER">
5 + <File Id="my.xml" Source="my.xml" />
6 + <util:XmlConfig
7 + Id="AddElement"
8 + File="[my.xml]"
9 + Action="create"
10 + Node="element"
11 + VerifyPath="xxx"
12 + ElementPath="//root/sub"
13 + On="install"
14 + Sequence="1" />
15 + </Component>
16 +
17 + <Component Id="Child" Directory="INSTALLFOLDER" Guid="4613414c-11f5-40fa-a1f1-a0ba722a6895">
18 + <util:XmlConfig
19 + Id="ChildElement"
20 + File="[my.xml]"
21 + VerifyPath="xxx"
22 + ElementId="AddElement"
23 + Sequence="1" />
24 + </Component>
25 + </Module>
26 +
27 + <Fragment>
28 + <Directory Id="TARGETDIR" Name="SourceDir">
29 + <Directory Id="ProgramFilesFolder">
30 + <Directory Id="INSTALLFOLDER" Name="MsiPackage" />
31 + </Directory>
32 + </Directory>
33 + </Fragment>
34 +</Wix>
src/test/WixToolsetTest.Util/TestData/XmlConfigModule/my.xml new
+1
@@ -0,0 +1 @@
1 +This is my.xml file.
src/test/WixToolsetTest.Util/UtilExtensionFixture.cs
+27
@@ -172,6 +172,33 @@ namespace WixToolsetTest.Util
172 }, results.OrderBy(s => s).ToArray());
173 }
174
175 + [Fact]
176 + public void CanBuildWithXmlConfig()
177 + {
178 + var folder = TestData.Get(@"TestData", "XmlConfig");
179 + var build = new Builder(folder, typeof(UtilExtensionFactory), new[] { folder });
180 +
181 + var results = build.BuildAndQuery(BuildX64, "Wix4XmlConfig");
182 + WixAssert.CompareLineByLine(new[]
183 + {
184 + "Wix4XmlConfig:DelElement\t[INSTALLFOLDER]my.xml\t\t//root/sub\txxx\t\t\t289\tDel\t1",
185 + }, results.OrderBy(s => s).ToArray());
186 + }
187 +
188 + [Fact]
189 + public void CanBuildModuleWithXmlConfig()
190 + {
191 + var folder = TestData.Get(@"TestData", "XmlConfigModule");
192 + var build = new Builder(folder, typeof(UtilExtensionFactory), new[] { folder });
193 +
194 + var results = build.BuildAndQuery(BuildX64, "Wix4XmlConfig");
195 + WixAssert.CompareLineByLine(new[]
196 + {
197 + "Wix4XmlConfig:AddElement.047730A5_30FE_4A62_A520_DA9381B8226A\t[my.xml.047730A5_30FE_4A62_A520_DA9381B8226A]\t\t//root/sub\txxx\t\t\t273\tParent.047730A5_30FE_4A62_A520_DA9381B8226A\t1",
198 + "Wix4XmlConfig:ChildElement.047730A5_30FE_4A62_A520_DA9381B8226A\t[my.xml.047730A5_30FE_4A62_A520_DA9381B8226A]\tAddElement.047730A5_30FE_4A62_A520_DA9381B8226A\t\txxx\t\t\t0\tChild.047730A5_30FE_4A62_A520_DA9381B8226A\t1",
199 + }, results.OrderBy(s => s).ToArray());
200 + }
201 +
202 [Fact]
203 public void CanBuildBundleWithSearches()
204 {
src/test/WixToolsetTest.Util/WixToolsetTest.Util.csproj
+1 -35
@@ -12,41 +12,7 @@
12 </PropertyGroup>
13
14 <ItemGroup>
15 - <Content Include="TestData\.Data\burn.exe" CopyToOutputDirectory="PreserveNewest" />
16 - <Content Include="TestData\BundleWithSearches\data\fakeba.dll" CopyToOutputDirectory="PreserveNewest" />
17 - <Content Include="TestData\BundleWithSearches\data\MsiPackage\Shared.dll" CopyToOutputDirectory="PreserveNewest" />
18 - <Content Include="TestData\BundleWithSearches\data\MsiPackage\test.txt" CopyToOutputDirectory="PreserveNewest" />
19 - <Content Include="TestData\BundleWithSearches\data\test.msi" CopyToOutputDirectory="PreserveNewest" />
20 - <Content Include="TestData\BundleWithSearches\Bundle.en-us.wxl" CopyToOutputDirectory="PreserveNewest" />
21 - <Content Include="TestData\BundleWithSearches\Bundle.wxs" CopyToOutputDirectory="PreserveNewest" />
22 - <Content Include="TestData\UsingFileShare\example.txt" CopyToOutputDirectory="PreserveNewest" />
23 - <Content Include="TestData\UsingFileShare\Package.en-us.wxl" CopyToOutputDirectory="PreserveNewest" />
24 - <Content Include="TestData\UsingFileShare\Package.wxs" CopyToOutputDirectory="PreserveNewest" />
25 - <Content Include="TestData\UsingFileShare\PackageComponents.wxs" CopyToOutputDirectory="PreserveNewest" />
26 - <Content Include="TestData\CloseApplication\example.txt" CopyToOutputDirectory="PreserveNewest" />
27 - <Content Include="TestData\CloseApplication\Package.en-us.wxl" CopyToOutputDirectory="PreserveNewest" />
28 - <Content Include="TestData\CloseApplication\Package.wxs" CopyToOutputDirectory="PreserveNewest" />
29 - <Content Include="TestData\CloseApplication\PackageComponents.wxs" CopyToOutputDirectory="PreserveNewest" />
30 - <Content Include="TestData\InternetShortcut\Package.ico" CopyToOutputDirectory="PreserveNewest" />
31 - <Content Include="TestData\InternetShortcut\Package.en-us.wxl" CopyToOutputDirectory="PreserveNewest" />
32 - <Content Include="TestData\InternetShortcut\Package.wxs" CopyToOutputDirectory="PreserveNewest" />
33 - <Content Include="TestData\InternetShortcut\PackageComponents.wxs" CopyToOutputDirectory="PreserveNewest" />
34 - <Content Include="TestData\InternetShortcutModule\Package.ico" CopyToOutputDirectory="PreserveNewest" />
35 - <Content Include="TestData\InternetShortcutModule\Package.en-us.wxl" CopyToOutputDirectory="PreserveNewest" />
36 - <Content Include="TestData\InternetShortcutModule\Package.wxs" CopyToOutputDirectory="PreserveNewest" />
37 - <Content Include="TestData\InternetShortcutModule\PackageComponents.wxs" CopyToOutputDirectory="PreserveNewest" />
38 - <Content Include="TestData\PermissionEx\example.txt" CopyToOutputDirectory="PreserveNewest" />
39 - <Content Include="TestData\PermissionEx\Package.en-us.wxl" CopyToOutputDirectory="PreserveNewest" />
40 - <Content Include="TestData\PermissionEx\Package.wxs" CopyToOutputDirectory="PreserveNewest" />
41 - <Content Include="TestData\PermissionEx\PackageComponents.wxs" CopyToOutputDirectory="PreserveNewest" />
42 - <Content Include="TestData\EventManifest\example.txt" CopyToOutputDirectory="PreserveNewest" />
43 - <Content Include="TestData\EventManifest\Package.en-us.wxl" CopyToOutputDirectory="PreserveNewest" />
44 - <Content Include="TestData\EventManifest\Package.wxs" CopyToOutputDirectory="PreserveNewest" />
45 - <Content Include="TestData\EventManifest\PackageComponents.wxs" CopyToOutputDirectory="PreserveNewest" />
46 - <Content Include="TestData\Queries\example.txt" CopyToOutputDirectory="PreserveNewest" />
47 - <Content Include="TestData\Queries\Package.en-us.wxl" CopyToOutputDirectory="PreserveNewest" />
48 - <Content Include="TestData\Queries\Package.wxs" CopyToOutputDirectory="PreserveNewest" />
49 - <Content Include="TestData\Queries\PackageComponents.wxs" CopyToOutputDirectory="PreserveNewest" />
15 + <Content Include="TestData\**" CopyToOutputDirectory="PreserveNewest" />
16 </ItemGroup>
17
18 <ItemGroup>
src/wixext/Symbols/XmlConfigSymbol.cs
+8
@@ -12,6 +12,7 @@ namespace WixToolset.Util
12 new[]
13 {
14 new IntermediateFieldDefinition(nameof(XmlConfigSymbolFields.File), IntermediateFieldType.String),
15 + new IntermediateFieldDefinition(nameof(XmlConfigSymbolFields.ElementId), IntermediateFieldType.String),
16 new IntermediateFieldDefinition(nameof(XmlConfigSymbolFields.ElementPath), IntermediateFieldType.String),
17 new IntermediateFieldDefinition(nameof(XmlConfigSymbolFields.VerifyPath), IntermediateFieldType.String),
18 new IntermediateFieldDefinition(nameof(XmlConfigSymbolFields.Name), IntermediateFieldType.String),
@@ -31,6 +32,7 @@ namespace WixToolset.Util.Symbols
32 public enum XmlConfigSymbolFields
33 {
34 File,
35 + ElementId,
36 ElementPath,
37 VerifyPath,
38 Name,
@@ -58,6 +60,12 @@ namespace WixToolset.Util.Symbols
60 set => this.Set((int)XmlConfigSymbolFields.File, value);
61 }
62
63 + public string ElementId
64 + {
65 + get => this.Fields[(int)XmlConfigSymbolFields.ElementId].AsString();
66 + set => this.Set((int)XmlConfigSymbolFields.ElementId, value);
67 + }
68 +
69 public string ElementPath
70 {
71 get => this.Fields[(int)XmlConfigSymbolFields.ElementPath].AsString();
src/wixext/UtilCompiler.cs
+3 -1
@@ -3706,13 +3706,15 @@ namespace WixToolset.Util
3706 var symbol = section.AddSymbol(new XmlConfigSymbol(sourceLineNumbers, id)
3707 {
3708 File = file,
3709 - ElementPath = elementId ?? elementPath,
3709 + ElementId = elementId,
3710 + ElementPath = elementPath,
3711 VerifyPath = verifyPath,
3712 Name = name,
3713 Value = value,
3714 Flags = flags,
3715 ComponentRef = componentId,
3716 });
3717 +
3718 if (CompilerConstants.IntegerNotSet != sequence)
3719 {
3720 symbol.Sequence = sequence;
src/wixext/UtilTableDefinitions.cs
+2 -1
@@ -253,7 +253,8 @@ namespace WixToolset.Util
253 {
254 new ColumnDefinition("Wix4XmlConfig", ColumnType.String, 72, primaryKey: true, nullable: false, ColumnCategory.Identifier, description: "Primary key, non-localized token.", modularizeType: ColumnModularizeType.Column),
255 new ColumnDefinition("File", ColumnType.Localized, 255, primaryKey: false, nullable: false, ColumnCategory.Formatted, description: "The .XML file in which to write the information", modularizeType: ColumnModularizeType.Property),
256 - new ColumnDefinition("ElementPath", ColumnType.Localized, 0, primaryKey: false, nullable: false, ColumnCategory.Formatted, description: "The XPATH query for an element to modify or add children to. Can also be a foreign key reference to another Wix4XmlConfig row if no attributes are set and the row referenced is a create element row.", modularizeType: ColumnModularizeType.Property),
256 + new ColumnDefinition("ElementId", ColumnType.String, 0, primaryKey: false, nullable: true, ColumnCategory.Identifier, keyTable: "Wix4XmlConfig", keyColumn: 1, description: "A foreign key reference to another Wix4XmlConfig row if no attributes are set and the row referenced is a create element row.", modularizeType: ColumnModularizeType.Column),
257 + new ColumnDefinition("ElementPath", ColumnType.Localized, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "The XPATH query for an element to modify or add children to. Must be null if ElementId is provided", modularizeType: ColumnModularizeType.Property),
258 new ColumnDefinition("VerifyPath", ColumnType.Localized, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "The XPATH query run from ElementPath to verify whether a repair is necessary. Also used to uninstall.", modularizeType: ColumnModularizeType.Property),
259 new ColumnDefinition("Name", ColumnType.Localized, 255, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "The .XML file node to set/add in the element.", modularizeType: ColumnModularizeType.Property),
260 new ColumnDefinition("Value", ColumnType.Localized, 0, primaryKey: false, nullable: true, ColumnCategory.Formatted, description: "The value to be written.", modularizeType: ColumnModularizeType.Property),