iT邦幫忙

2021 iThome 鐵人賽

DAY 9
0
Mobile Development

使用 Swift 和公開資訊,打造投資理財的 Apps系列 第 9

D9-用 Swift 和公開資訊,打造投資理財的 Apps { 台股申購實作.2 -讀取Big5碼的csv}

現在開發者寫程式,最方便的一點,就是不會的地方,可以問 Google

在 Google 中輸入 Swift big5 to utf8 你會找到許多前人和你遇到一樣的問題。

下面這個聯結就是 Big5 轉 UTF8 的範例文章。

http://supermingblog.blogspot.com/2015/04/ios-big5-to-utf8-or-utf8-to-big5.html

我參考了前人的程式碼,擴充了 String

import Foundation

extension String {
    
    static func dataWtihBig5(data: Data) -> Self? {
        
        let big5Encoding = CFStringEncodings.big5_HKSCS_1999.rawValue
        
        let convertEncodingBig5 = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(big5Encoding))
        
        return String(data: data, encoding: String.Encoding(rawValue: convertEncodingBig5))
    }
}

然後在 StockSubscriptionManager 那邊,使用這個方法讓 Data 轉成 String,再餵給 CSVAdapter。

試著印出 csv.header 和其中一個 row,雖然怪怪的,但已經能讀到中文了。

接下來繼續解那個怪怪的 header。

https://ithelp.ithome.com.tw/upload/images/20210918/20140622NbOJbsfKvF.png

下方是使用 csv 檔的圖,用 Spreadsheet, Excel, Number 打開,可以看到,在 header 上方還有一行,"公開申購公告-抽籤日程表"。就是這一行,讓 CSV 在 parse 的時候沒有得到我們想要的結果。但這個套件沒有從第 n 行開始讀取的 api,那我們就要在 csv string 讀取之前,把第 1 行拿掉。

https://ithelp.ithome.com.tw/upload/images/20210918/201406221Jge5OWe3k.png

從第 n 行開始讀取這個職責,我歸類在 CSVAdapter 裡,所以 extension 一個 static func, CSVAdapter 可以得到去除掉第 n 行之前的 string

extension CSVAdapter {
    
    /// 在公開資訊站拿到的 csv 檔中,是有一些檔案的 headers 不是從 line = 0開始,而是 line = 1 或其他行,用這個方以去掉
    static func removeLine(_ rawString: String, at line: Int, separator: String = "\n") -> String {
        
        let separatedString = rawString.components(separatedBy: separator)

        let removedString = separatedString.suffix(from: line)
        
        let joinedString = removedString.joined(separator: separator)
        
        return joinedString
    }
}

接下來,再小輻修改 StockSubscriptionManager 就可以了

//
//  StockSubscriptionManager.swift
//  ITIronMan
//
//  Created by Marvin on 2021/9/4.
//

import Foundation

/// 這個類別專門處理股票申購資訊
class StockSubscriptionManager {
    
    private lazy var alamofireAdapter: AlamofireAdapter = {
        return AlamofireAdapter()
    }()
    
    func requestStockSubscriptionInfo(year: Int, completion: @escaping (([StockSubscriptionInfo], Error?) -> Void)) {
        
        let urlString = "https://www.twse.com.tw/announcement/publicForm?response=csv&yy=\(year)"
        
        alamofireAdapter.request(urlString, method: .get) { data, response, error in
            
            if let error = error {
                completion([], error)
                return
            }
            
            var subscriptionList = [StockSubscriptionInfo]()
            
            if let data = data,
               let string = String.dataWtihBig5(data: data) {
                
                let trimmedString = CSVAdapter.removeLine(string, at: 1)
                
                if let csv = try? CSVAdapter(rawString: trimmedString) {
                 
                    for each in csv.namedRows {
                        
                        let stockCode = each["證券代號"] ?? ""
                        let stockName = each["證券名稱"] ?? ""
                        let subscriptionStartString = each["申購開始日"] ?? ""
                        let subscriptionEndString = each["申購結束日"] ?? ""
                        let subscriptionOccurString = each["抽籤日期"] ?? ""
                        let price = each["承銷價(元)"] ?? ""
                        let actualPrice = each["實際承銷價(元)"] ?? ""
                        let stockDeliveringDateString = each["撥券日期(上市、上櫃日期)"] ?? ""
                        let stockCountString = each["申購股數"] ?? ""
                        let totalApplyCountString = each["實際承銷股數"] ?? ""
                        let subscriptionRateString = each["中籤率(%)"] ?? ""
                        
                        let subscription = StockSubscriptionInfo(
                            stockCode: stockCode,
                            stockName: stockName,
                            subscriptionStartString: subscriptionStartString,
                            subscriptionEndString: subscriptionEndString,
                            subscriptionOccurString: subscriptionOccurString,
                            price: price,
                            actualPrice: actualPrice,
                            stockDeliveringDateString: stockDeliveringDateString,
                            stockCountString: stockCountString,
                            totalApplyCountString: totalApplyCountString,
                            subscriptionRateString: subscriptionRateString)
                        
                        subscriptionList.append(subscription)
                    }
                }
            }
               
            completion(subscriptionList, error)
        }
    }
}

台股申購 ViewController 所擁有的 model,只要呼叫年份,就完成了 model 的功能,將得到的申購列表回傳 VC 的部分,則會放在下一篇。

//
//  StockSubscriptionModel.swift
//  ITIronMan
//
//  Created by Marvin on 2021/9/4.
//

import Foundation

/// 股票申購 VC 所需的 Model
class StockSubscriptionModel {
    
    private lazy var manager: StockSubscriptionManager = {
        return StockSubscriptionManager()
    }()
    
    func requestStockSubscription() {
        
        let year = 2021
        manager.requestStockSubscriptionInfo(year: year) { subscriptionList, error in
            
            // TODO: - 傳出 list 給 vc
        }
    }
}

上一篇
D8 - 用 Swift 和公開資訊,打造投資理財的 Apps { 台股申購資訊實作.1 - 取得公開申購公告csv檔 }
下一篇
D10- 用 Swift 和公開資訊,打造投資理財的 Apps { 台股申購實作.3-讓申購資訊放進可以清楚理解的 TableView }
系列文
使用 Swift 和公開資訊,打造投資理財的 Apps37
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言