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
        }
    }
}

<<:  grep - 3 Regex搭配浅谈

>>:  [Day 18] 针对网页的单元测试(四)

Day 19 ( 中级 ) 阵列点灯 ( 动画 )

阵列点灯 ( 动画 ) 教学原文参考:阵列点灯 ( 动画 ) 这篇文章延续「阵列点灯 ( 显示图形 ...

【Day13】:EXTI外部中断/事件控制器

外部中断 由於各种外设都可以有中断,今天我们就来实际使用一种比较简单的中断-外部中断,但却是非常重要...

【Day15】:STM32辗压Arduino的功能—TIM(下)

TIMER+NVIC中断 今天我们来使用Timer的中断功能吧! 设定与昨天大致相同,只是我们现在需...

R语言-2&3-记忆体

在电脑中的资料单位 1个0或1 =>一个位元(bit) 8个0或1 =>一个位元组(By...

Day09 Platform Channel - BasicMessageChannel

如同前面介绍的,Flutter 定义了三种不同型别的Platform Channel 在platfo...