Embarcadero IDE uses a template plist file to be used for building iOS applications.
The template file is a simple XML file, however it contains illegal placeholder symbols which Embarcadero resolves during deployment. (e.g. \<%VersionInfoPListKeys%\>
)
Also due to limitations in the IDE, i have to manually edit the template file, to add custom entries which reference the bundle id.
Since Embarcadero does not support xcode's variables inside plist files such as $(PRODUCT_BUNDLE_IDENTIFIER)
, I have to manually edit the file using a powershell script.
However, due to those placeholder symbols, powershell cannot convert the contents of the file to an XML object.
Therefore I tried replacing those % characters with valid symbols, and before saving the file, replace the original symbols back.
I tried the following: ($plistPath and $bundleId are launch parameters)
Loading in the plist:
$plist_raw = ([System.IO.File]::ReadAllText($plistPath)) -replace "<%", "<_" -replace "%>", "_/>"
$plist = [xml]::new()
$plist.PreserveWhitespace = $true
$plist.LoadXml($plist_raw)
Adding / replacing an array node to the plist:
$arrayKey = "some_key"
$keyNode = (Select-Xml -Xml $plist -XPath "//dict/key" | Where-Object { $_.Node.InnerText -eq $arrayKey } | Select-Object -First 1).Node
if ($keyNode) {
$plist.plist.dict.RemoveChild($keyNode.NextSibling)
$plist.plist.dict.RemoveChild($keyNode)
}
$arrayNode = $plist.CreateElement('array')
$arrayKeyNode = $plist.CreateElement('key')
$arrayKeyNode.InnerText = $arrayKey
$plist.plist.dict.AppendChild($arrayKeyNode)
$plist.plist.dict.AppendChild($arrayNode)
$value1 = "$bundleId.foo"
$value2 = "$bundleId.bar"
foreach ($item in @($value1, $value2)) {
$newItem = $plist.CreateElement('string')
$newItem.InnerText = $item
$arrayNode.AppendChild($newItem)
Write-Host "Identifier '$item' has been added to the array '$arrayKey'."
}
saving the plist:
($plist.OuterXml -replace "<_", "<%" -replace "_/>", "%>" -replace "_ />", "%>") | Set-Content $plistPath
The problem is, if I do this, the array node is appended to the file as a single line and has no formatting. It also fails to delete the existing array node.
Input file: "info.plist.TemplateiOS.xml"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"[]>
<plist version="1.0">
<dict>
<%VersionInfoPListKeys%>
<%ExtraInfoPListKeys%>
<%StoryboardInfoPListKey%>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
</array>
</dict>
</plist>
The output I expect:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"[]>
<plist version="1.0">
<dict>
<%VersionInfoPListKeys%>
<%ExtraInfoPListKeys%>
<%StoryboardInfoPListKey%>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
</array>
<key>some_key</key>
<array>
<string>some_bundleId.foo</string>
<string>some_bundleId.bar</string>
</array>
</dict>
</plist>
The output I get:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"[]>
<plist version="1.0">
<dict>
<%VersionInfoPListKeys%>
<%ExtraInfoPListKeys%>
<%StoryboardInfoPListKey%>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>processing</string>
</array>
<array>
<string>some_bundleId.foo</string>
<string>some_bundleId.bar</string>
</array>
<key>some_key</key><array><string>some_bundleId.foo</string><string>some_bundleId.bar</string></array></dict>
</plist>
notice it only deleted the key and not the array, then put the new nodes below it.
If I use Get-Content
to load the plist, it properly appends / deletes the nodes, but then the PreserveWhitespace
property has no effect and the output is written into a single line.
To clarify, the script above is supposed to add a key "some_key" and an array with 2 strings. if that key is already present, it should overwrite it. The second output shown below is when that key was already present and it failed to delete the array. it does work when used Get-Content so it must be a side effect of the lacking formatting.
I suggest not trying to preserve the original pretty-printing, but modifying the document as desired and then pretty-printing the modified document anew, as a whole:
# Creates a pretty-printed string representation of your [xml] document, $plist.
# .Dispose() calls omitted for brevity.
$xmlWriter = [System.Xml.XmlTextWriter] ($stringWriter = [System.IO.StringWriter]::new())
$xmlWriter.Formatting = 'indented'; $xmlWriter.Indentation = 2
$plist.WriteContentTo($xmlWriter)
$prettyPrintedXml =
$stringWriter.ToString()
The above uses a System.Xml.XmlTextWriter
instance that targets a System.IO.TextWriter
instance to create a pretty-printed string representation of the [xml]
(System.Xml.XmlDocument
) instance stored in $plist
.
To put it all together in the context of your code:
$plist_raw = (Get-Content $plistPath -Raw) -replace '<%', '<_' -replace '%>', '_/>'
$plist = [xml]::new()
$plist.LoadXml($plist_raw)
$arrayKey = 'some_key'
$keyNode = $plist.SelectSingleNode("//dict/key[.='$arrayKey']")
$null = if ($keyNode) {
$plist.plist.dict.RemoveChild($keyNode.NextSibling)
$plist.plist.dict.RemoveChild($keyNode)
}
$arrayNode = $plist.CreateElement('array')
$arrayKeyNode = $plist.CreateElement('key')
$arrayKeyNode.InnerText = $arrayKey
$null = $plist.plist.dict.AppendChild($arrayKeyNode)
$null = $plist.plist.dict.AppendChild($arrayNode)
$value1 = "$bundleId.foo"
$value2 = "$bundleId.bar"
foreach ($item in @($value1, $value2)) {
$newItem = $plist.CreateElement('string')
$newItem.InnerText = $item
$null = $arrayNode.AppendChild($newItem)
Write-Host "Identifier '$item' has been added to the array '$arrayKey'."
}
# Create a pretty-printed string representation of the modified document.
$xmlWriter = [System.Xml.XmlTextWriter] ($stringWriter = [System.IO.StringWriter]::new())
$xmlWriter.Formatting = 'indented'; $xmlWriter.Indentation = 2
$plist.WriteContentTo($xmlWriter)
$prettyPrintedXml = $stringWriter.ToString()
$stringWriter.Dispose()
$xmlWriter.Dispose()
# Restore the templates and save back to the input file.
$prettyPrintedXml -replace '<_', '<%' -replace '_ ?/>', '%>' |
Set-Content $plistPath
Note:
Get-Content
-Raw
is used as the PowerShell-idiomatic way to read a text file in full.
.PreserveWhitespace = $true
is no longer necessary, due to the renewed pretty-printing performed later.
Since you already have an [xml]
instance, it is simpler to call .SelectSingleNode()
on it (instead of calling Select-Xml
) to perform your XPath query, and to build the desired filtering directly into the XPath query.
$null = ...
is prepended to those method calls whose return value isn't captured in a variable, so as to prevent unwanted implicit output from them.