Is there a way in Swift that I can add an element to an XML document after passing it through a function?
For example, if I pass in the text of <original>
into a function, can I append it after into the XML file and save it as a new XML document?
input XML
<original>item-id-01</original>
custom function
func doThings(_ string: String) -> String { ... }
doThings("item-id-01")
output XML
<original>item-id-01</original>
<destination>output from custom function</destination>
I’m trying to parse an XML file without using a library but all the tutorials I’ve seen only seem to only show how to append one element into an array, and display that into the UI.
That is, most examples:
<book> --> struct Book {
<title>My book</title> --> var title: String
<author>Dr. ABC</author> --> var author: String
</book> --> }
The XMLParser
and XMLParserDelegate
will search for the elementName == "book"
and then append that to an array of var books: [Book]
. This seems to be the popular use of XML parsing since it was the way to get data before JSON was the standard.
However, I want to be able to pass in my XML file:
<?xml version="1.0" encoding="UTF-8"?>
<data version="1.2">
<file file-location="Local/Folder/file-a.txt" original="192.168.0.1" destination="192.168.0.2">
<header>
<tool id="com.apple.TextEdit" name="TextEdit" version="14.0" build="123ABC"/>
</header>
<body>
<item id="item-id-01" xml:space="preserve">
<original>item-id-01</original>
<note>Notes: no notes recorded</note>
</item>
</body>
</file>
<file file-location="Local/Folder/file-b.txt" original="192.168.10.1" destination="192.168.10.2">
<header>
<tool id="com.apple.TextEdit" name="TextEdit" version="14.0" build="123ABC"/>
</header>
<body>
<item id="item-id-02" xml:space="preserve">
<original>item-id-02</original>
<note>Notes: no notes recorded</note>
</item>
</body>
</file>
</data>
Get all the <original>
elements, pass them into the doThings()
function, and have an XML document that looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<data version="1.2">
<file file-location="Local/Folder/file-a.txt" original="192.168.0.1" destination="192.168.0.2">
<header>
<tool id="com.apple.TextEdit" name="TextEdit" version="14.0" build="123ABC"/>
</header>
<body>
<item id="item-id-01" xml:space="preserve">
<original>item-id-01</original>
<destination>output from custom function</destination> // <--- custom text
<note>Notes: no notes recorded</note>
</item>
</body>
</file>
<file file-location="Local/Folder/file-b.txt" original="192.168.10.1" destination="192.168.10.2">
<header>
<tool id="com.apple.TextEdit" name="TextEdit" version="14.0" build="123ABC"/>
</header>
<body>
<item id="item-id-02" xml:space="preserve">
<original>item-id-02</original>
<destination>output from custom function</destination> // <--- custom text
<note>Notes: no notes recorded</note>
</item>
</body>
</file>
</data>
When I use the XMLParserDelegate, I’m able to extract all the <original>
text into an array of Item()
models:
struct Item {
var original: String
var destination: String?
var note: String
}
Then compactMap
the Item.original
into a String array, so I can pass it to my function:
let items: [Item] = []
let original: [String] = []
init() {
items = XMLParser.getDataFrom(url: ...)
orignal = items.compactMap({ $0.original })
}
func doThings(_ string: [String]) -> [String] { ... }
let output = doThings(original)
But how would I inject into the original XML document and essentially "Save As.." into a new file?
Things I tried
-
adding the entire XML document into their own struct models and looping through all of them to rebuild
- this didn’t work for me as I couldn’t get the attributes into the model and output it as it originally was
- eg.
<file file-location="Local/Folder/file-a.txt" original="192.168.0.1" destination="192.168.0.2">
into
struct File { var header: Header var body: Body var atributes: [String : String] }
-
convert the XML to a string, find all the
</original>
and replace it with</original><destination>loopValue[index]</destination>
-
this didn’t work because if there already was a
<destination>
in the original XML document, then I would end up with</original><destination>loopValue[index]</destination><destination>.. original document data</destination>
Am I not using the correct API or is there a good way to achieve this outcome?
2
Answers
If you insist on
the only option you are left is
libxml2
.libxml2
is a low levelC
Api, so not really much fun in using it.For
macOs
there would be the alternative ofXMLDocument
but forios
you are stuck withXMLParser
.Here is a rudimentary version of how to read and update the xml using XMLDocument. Note that we are only dealing with classes here so everything is by reference which makes the update part a bit simpler.
Then use
document.xmlData
ordocument.xmlString
to extract and a save the modified xmlAnd you shouldn’t use
try!
everywhere as I have done here 🙂