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?
CodePudding user response:
If you insist on
without using a library
the only option you are left is libxml2
. libxml2
is a low level C
Api, so not really much fun in using it.
For macOs
there would be the alternative of XMLDocument
but for ios
you are stuck with XMLParser
.
CodePudding user response:
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.
let document = try! XMLDocument(xmlString: xmlString) //Change this to a suitable init
let nodes = try! document.nodes(forXPath: "/data/file/body/item")
for node in nodes {
if var item = node as? XMLElement {
let element = XMLNode.element(withName: "destination", stringValue: "some string value") as! XMLNode
item.addChild(element)
}
}
Then use document.xmlData
or document.xmlString
to extract and a save the modified xml
And you shouldn't use try!
everywhere as I have done here :)