skip to Main Content

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


  1. 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.

    Login or Signup to reply.
  2. 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 🙂

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search