NEW: Learn to build the incredible iOS 15 Weather app today! >>

XML Parser

Forums > Swift

Hi @twostraws, could you please help me parse the XML constant string? I need to find every attribute of the tag Link and put it in my data model.

I've used your XMLParser class as coded below, but it can not even find the root element, which is DownloadMultiplo.

import Foundation

class XMLNode {
    let tag: String
    var data: String
    let attributes: [String: String]
    var childNodes: [XMLNode]

    init(tag: String, data: String, attributes: [String: String], childNodes: [XMLNode]) {
        self.tag = tag
        self.data = data
        self.attributes = attributes
        self.childNodes = childNodes
    }

    func getAttribute(_ name: String) -> String? {
        attributes[name]
    }

    func getElementsByTagName(_ name: String) -> [XMLNode] {
        var results = [XMLNode]()

        for node in childNodes {
            if node.tag == name {
                results.append(node)
            }

            results += node.getElementsByTagName(name)
        }

        return results
    }
}

class MicroDOM: NSObject, XMLParserDelegate {
    private let parser: XMLParser
    private var stack = [XMLNode]()
    private var tree: XMLNode?

    init(data: Data) {
        parser = XMLParser(data: data)
        super.init()
        parser.delegate = self
    }

    func parse() -> XMLNode? {
        parser.parse()

        guard parser.parserError == nil else {
            return nil
        }

        return tree
    }

    func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String: String] = [:]) {
        let node = XMLNode(tag: elementName, data: "", attributes: attributeDict, childNodes: [])
        stack.append(node)
    }

    func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
        let lastElement = stack.removeLast()

        if let last = stack.last {
            last.childNodes += [lastElement]
        } else {
            tree = lastElement
        }
    }

    func parser(_ parser: XMLParser, foundCharacters string: String) {
        stack.last?.data = string
    }
}

let string = """
<DownloadMultiplo DataSolicitada="23/11/2021 00:00" TipoDocumento="IPE" DataConsulta="23/11/2021 11:56">
<Link url="http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919084" Documento="IPE" ccvm="24830" DataRef="11/11/2021 10:30:59" FrmDtRef="dd/mm/aaaa hh:mm" Categoria="Reunião da Administração" Tipo="Conselho de Administração" Especie="Ata" Situacao="Cancelado"/>
<Link url="http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919521" Documento="IPE" ccvm="23493" DataRef="22/11/2021 12:00:59" FrmDtRef="dd/mm/aaaa hh:mm" Categoria="Reunião da Administração" Tipo="Conselho de Administração" Especie="Ata" Situacao="Liberado"/>
<Link url="http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919522" Documento="IPE" ccvm="23493" DataRef="22/11/2021 11:45:59" FrmDtRef="dd/mm/aaaa hh:mm" Categoria="Reunião da Administração" Tipo="Conselho Fiscal" Especie="Ata" Situacao="Liberado"/>
<Link url="http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919523" Documento="IPE" ccvm="54461" DataRef="23/11/2021 23:59:59" FrmDtRef="dd/mm/aaaa" Categoria="Comunicado ao Mercado" Tipo="Outros Comunicados Não Considerados Fatos Relevantes" Especie=" " Situacao="Liberado"/>
<Link url="http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919524" Documento="IPE" ccvm="51209" DataRef="23/11/2021 23:59:59" FrmDtRef="dd/mm/aaaa" Categoria="Comunicado ao Mercado" Tipo="Outros Comunicados Não Considerados Fatos Relevantes" Especie=" " Situacao="Liberado"/>
<Link url="http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919525" Documento="IPE" ccvm="50563" DataRef="23/11/2021 23:59:59" FrmDtRef="dd/mm/aaaa" Categoria="Comunicado ao Mercado" Tipo="Outros Comunicados Não Considerados Fatos Relevantes" Especie=" " Situacao="Liberado"/>
<Link url="http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919526" Documento="IPE" ccvm="52140" DataRef="23/11/2021 23:59:59" FrmDtRef="dd/mm/aaaa" Categoria="Comunicado ao Mercado" Tipo="Outros Comunicados Não Considerados Fatos Relevantes" Especie=" " Situacao="Liberado"/>
<Link url="http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919527" Documento="IPE" ccvm="53961" DataRef="23/11/2021 23:59:59" FrmDtRef="dd/mm/aaaa" Categoria="Comunicado ao Mercado" Tipo="Outros Comunicados Não Considerados Fatos Relevantes" Especie=" " Situacao="Liberado"/>
<Link url="http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919528" Documento="IPE" ccvm="52167" DataRef="23/11/2021 23:59:59" FrmDtRef="dd/mm/aaaa" Categoria="Comunicado ao Mercado" Tipo="Outros Comunicados Não Considerados Fatos Relevantes" Especie=" " Situacao="Liberado"/>
</DownloadMultiplo>
"""

let dom = MicroDOM(data: Data(string.utf8))
let tree = dom.parse()
print(tree?.tag ?? "N/A")

It prints out N/A

   

It's because your XML is not valid. Specifically, the URLs in your Link elements.

http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919084

The parser chokes on &prot, which it tries to read as an XML entity because of the & but then there is no closing ; to end the entity.

You can see this if you add the following delegate method to the MicroDOM class:

func parser(_ parser: XMLParser, parseErrorOccurred parseError: Error) {
    let error = parseError as NSError
    print(error.userInfo["NSXMLParserErrorMessage"]!)
    print("line: \(error.userInfo["NSXMLParserErrorLineNumber"]!)")
    print("column: \(error.userInfo["NSXMLParserErrorColumn"]!)")
}

Prints:

EntityRef: expecting ';'

line: 2
column: 86

I'm honestly not sure how to solve this with the given XML. I tried replacing the raw & with an entity &amp;, which is what you are supposed to do, but the parser resolves &amp;prot to &prot and we're back where we started. Same with &amp;amp;prot. I'm not sure this is even allowed in XML attributes.

Do you control this XML? Ampersands are allowed within CDATA sections, so something like this works:

<Link Documento="IPE" ccvm="24830" DataRef="11/11/2021 10:30:59" FrmDtRef="dd/mm/aaaa hh:mm" Categoria="Reunião da Administração" Tipo="Conselho de Administração" Especie="Ata" Situacao="Cancelado">
<![CDATA[http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919084]]>
</Link>

I reformatted your XML and altered the root-level code to show some results and it looked good:

let string = """
    <DownloadMultiplo DataSolicitada="23/11/2021 00:00" TipoDocumento="IPE" DataConsulta="23/11/2021 11:56">
    <Link Documento="IPE" ccvm="24830" DataRef="11/11/2021 10:30:59" FrmDtRef="dd/mm/aaaa hh:mm" Categoria="Reunião da Administração" Tipo="Conselho de Administração" Especie="Ata" Situacao="Cancelado"><![CDATA[http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919084]]></Link>
    <Link Documento="IPE" ccvm="23493" DataRef="22/11/2021 12:00:59" FrmDtRef="dd/mm/aaaa hh:mm" Categoria="Reunião da Administração" Tipo="Conselho de Administração" Especie="Ata" Situacao="Liberado"><![CDATA[http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919521]]></Link>
    <Link Documento="IPE" ccvm="23493" DataRef="22/11/2021 11:45:59" FrmDtRef="dd/mm/aaaa hh:mm" Categoria="Reunião da Administração" Tipo="Conselho Fiscal" Especie="Ata" Situacao="Liberado"><![CDATA[http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919522]]></Link>
    <Link Documento="IPE" ccvm="54461" DataRef="23/11/2021 23:59:59" FrmDtRef="dd/mm/aaaa" Categoria="Comunicado ao Mercado" Tipo="Outros Comunicados Não Considerados Fatos Relevantes" Especie=" " Situacao="Liberado"><![CDATA[http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919523]]></Link>
    <Link Documento="IPE" ccvm="51209" DataRef="23/11/2021 23:59:59" FrmDtRef="dd/mm/aaaa" Categoria="Comunicado ao Mercado" Tipo="Outros Comunicados Não Considerados Fatos Relevantes" Especie=" " Situacao="Liberado"><![CDATA[http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919524]]></Link>
    <Link Documento="IPE" ccvm="50563" DataRef="23/11/2021 23:59:59" FrmDtRef="dd/mm/aaaa" Categoria="Comunicado ao Mercado" Tipo="Outros Comunicados Não Considerados Fatos Relevantes" Especie=" " Situacao="Liberado"><![CDATA[http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919525]]></Link>
    <Link Documento="IPE" ccvm="52140" DataRef="23/11/2021 23:59:59" FrmDtRef="dd/mm/aaaa" Categoria="Comunicado ao Mercado" Tipo="Outros Comunicados Não Considerados Fatos Relevantes" Especie=" " Situacao="Liberado"><![CDATA[http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919526]]></Link>
    <Link Documento="IPE" ccvm="53961" DataRef="23/11/2021 23:59:59" FrmDtRef="dd/mm/aaaa" Categoria="Comunicado ao Mercado" Tipo="Outros Comunicados Não Considerados Fatos Relevantes" Especie=" " Situacao="Liberado"><![CDATA[http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919527]]></Link>
    <Link Documento="IPE" ccvm="52167" DataRef="23/11/2021 23:59:59" FrmDtRef="dd/mm/aaaa" Categoria="Comunicado ao Mercado" Tipo="Outros Comunicados Não Considerados Fatos Relevantes" Especie=" " Situacao="Liberado"><![CDATA[http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919528]]></Link>
</DownloadMultiplo>
"""

let dom = MicroDOM(data: Data(string.utf8))
if let tree = dom.parse() {
    tree.childNodes.forEach {
        print($0.data)
    }
}

Printed:

http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919084
http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919521
http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919522
http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919523
http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919524
http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919525
http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919526
http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919527
http://siteempresas.bovespa.com.br/DWL/FormDetalheDownload.asp?site=C&prot=919528

   

Actually I do not control the XML format, it comes from a CVM (the Brazilian version of the SEC) API.

It is strange because the Python XML parser works just fine with the original format.

I'll try to parse it with Python, convert the data to a JSON format and then access it with Swift with my own API.

Thanks a lot for your help!

   

Python's parser must be more lenient than Swift's.

I tried several online XML syntax checkers and they all choked at the same spot for the same reason.

   

Hacking with Swift is sponsored by Essential Developer

SPONSORED Learn the most up-to-date techniques and strategies for testing new and legacy Swift code in this free practical course for iOS devs who want to become complete Senior iOS Developers.

Learn more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.