일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 코테
- data_structure
- MainScheduler
- 오픈채팅방
- SwiftUI
- DiffableDataSource
- gitflow
- 프로그래머스
- MethodSwilzzling
- MainScheduler.asyncInstance
- DynamicMemberLookup
- GCD
- RaceCondition
- MainScheduler.Instance
- 등굣길
- DispatchQueue
- swift
- 청년취업사관학교
- GIT
- IOS
- combine
- cleanarchitecture
- CoreBluetooth
- rxswift
- SeSAC
- SRP
- leetcode
- DependencyInjection
- 명품cppProgramming c++
- Realm
- Today
- Total
Do.
XML Parser 본문
서론(무시해도 됨)
Swift Foundation에 있는 XML Parser는 말 그대로 XML parsing이 가능한 클래스이다.
공공 데이터 포털의 REST API는 JSON으로 제공하는 것도 있지만 XML로 제공하는게 더 많은 것 같다.(체감)
JSON의 경우 JSON Encoder와 Decoder를 이용하면 정말 손쉽고 간편하게 사용할 수 있는데(URL Session과 함께)
XMLParser 클래스는 좀 순서가 다르다 XMLParser 클래스를 생성하고 .parse() 메소드로 파싱을 시작하는데 데이터를 분석하는 것은 Delegate를 통해서 한다. XML Parser의 사용법을 알아보자
사용법
UIKit 기준으로 설명
func updateBusInformation(_ requestModel: StationBusListModel) {
let baseURLString = "http://ws.bus.go.kr/api/rest/arrive/getArrInfoByRoute?"
guard let serviceKey = Bundle.main.infoDictionary?["StationInfoKey"] as? String else { fatalError("api key not found!")}
guard let url = URL(string: "\(baseURLString)serviceKey=\(serviceKey)&stId=\(requestModel.stId)&busRouteId=\(requestModel.busRouteId)&ord=\(requestModel.staOrd)") else { fatalError("url convert error")}
//======= XML Parser
guard let xmlParser = XMLParser(contentsOf: url) else { return }
xmlParser.delegate = self
xmlParser.parse()
//======
tableView.reloadData()
}
제작중인 출근을 부탁해의 XML parsing에 해당하는 코드로 url을 만들어서 XMLparser 초기화 한다.
parse 메소드를 통해 파싱을 시작하는데 미리 몇가지 작업해야 할 부분이 있다.
var currentElement = ""
var xmlDictionary: [String: String] = [:]
View Controller 의 프로퍼티로 생성한 것인데 이 것들의 역할은 델리게이트에서 자세히 설명하겠다
extension StationDetailViewController: XMLParserDelegate {
//1
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
currentElement = elementName
if elementName == XMLKey.itemList.rawValue {
xmlDictionary.removeAll()
}
}
//2
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
if elementName == XMLKey.itemList.rawValue {
let route = StationBusListModel()
XMLKey.allCases.forEach { key in
if let value = xmlDictionary[key.rawValue] {
do {
try route.codingKeys(key.rawValue, value)
} catch {
print(error)
}
}
}
routes.append(route)
}
}
//3
func parser(_ parser: XMLParser, foundCharacters string: String) {
if let key = XMLKey.init(rawValue: currentElement) {
xmlDictionary[key.rawValue] = string
}
}
}
코드가 단번에 길어져서 어지러울 수 있는데 차례대로 설명하겠다.
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:])
- didStartElement로 <name> 과 같은 속성을 검출한다. 나머지 파라메터들은 확인을 해봤는데 제대로 된 값을 넘겨 받은 적이 없어서 다른 사용법이 있는지 알아봐야 할 것 같다.
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?)
- 말 그대로 didEndElement </name> 닫는 속성을 검출한다.
func parser(_ parser: XMLParser, foundCharacters string: String)
- <name>GookBob</name> 에서 GookBob에 해당하는 내용을 string 프로퍼티로 넘겨 받는다.
구조가 눈에 들어왔을지 모르겠는데 다시 설명하자면
- 속성을 연다.
- 값을 확인한다.
- 속성을 닫는다.
요 3개 과정을 Delegate를 통해서 전달한다.
그럼 이 메소들 간에서 어떻게 값을 객체에 전달할 수 있을까. 여기서 앞에서 선언한 뷰 컨트롤러 클래스의 두 프로퍼티를 활용한다.
var currentElement = ""
var xmlDictionary: [String: String] = [:]
이 녀석들인데 의사코드로 작성하자면
- 속성을 연다. currentElement에 현재 속성 이름을 저장한다. name이라고 하겠다. xmlDictionary에 있는 값을 초기화 한다.
- 값을 확인한다. GookBob이 들어있다. xmlDictionary에 name을 키로 GookBob을 값으로 저장한다.
- 속성을 닫는다. xmlDictionary있는 값을 객체를 생성하는데 사용한다.
요 3과정으로 XML 문서를 다 읽을 때 까지 반복 한 후 parsing 과정을 종료한다. 다시 전체 코드를 보면
fileprivate enum XMLKey: String, CaseIterable {
case itemList = "itemList"
case adirection = "adirection"
case busRouteId = "busRouteId"
case rtNm = "rtNm"
case stId = "stId"
case stNm = "stNm"
case staOrd = "staOrd"
}
extension StationDetailViewController: XMLParserDelegate {
//1
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
currentElement = elementName
if elementName == XMLKey.itemList.rawValue {
xmlDictionary.removeAll()
}
}
//2
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
if elementName == XMLKey.itemList.rawValue {
let route = StationBusListModel()
XMLKey.allCases.forEach { key in
if let value = xmlDictionary[key.rawValue] {
do {
try route.codingKeys(key.rawValue, value)
} catch {
print(error)
}
}
}
routes.append(route)
}
}
//3
func parser(_ parser: XMLParser, foundCharacters string: String) {
if let key = XMLKey.init(rawValue: currentElement) {
xmlDictionary[key.rawValue] = string
}
}
}
이 경우 들어오는 속성들을 일일이 문자로 작성하기에는 너무 불편해서 열거형을 사용했다.
대안
내용을 보면 알겠지만 코딩 하는데도 준비된 JSON 인코더, 디코더에 비해서 상당히 불편하고 Session의 response를 받을 수 있는지도 모르겠다. XMLParser는 iOS 2.0 objective-C 기반의 클래스로 기술적으로 오래되었다고 볼 수 있다. 이를 친절하게 JSON Codable 처럼 라이브러리를 제작해 준 개발자가 있으므로 링크를 첨부한다. https://github.com/ShawnMoore/XMLParsing
'iOS' 카테고리의 다른 글
디버그와 브레이크 포인트 (0) | 2022.02.09 |
---|---|
함수 반환형 (0) | 2022.02.09 |
표준 라이브러리의 고차함수 (0) | 2022.02.09 |
Swift - Class와 Object의 차이점 (0) | 2022.02.09 |
Swift - Float, Double을 Int로 바꾸기, 반올림, 올림, 내림 (0) | 2022.02.09 |