iT邦幫忙

2024 iThome 鐵人賽

DAY 25
0

昨天我們已經完成掃描 QRCode 並顯示購物清單的功能,但是當遇到不同編碼的 QRCode 資料時(例如 Big5 和 Base64),會出現問題。今天我們的目標就是處理這些不同的編碼並將其正確轉換為 UTF-8,讓 App 可以正確顯示商品明細。

目標

今天的主要目標是:

  • 支援 Big5 編碼:將掃描到的 Big5 編碼文字轉換為 UTF-8。
  • 支援 Base64 編碼:將 Base64 編碼的商品資料解碼並轉換為 UTF-8。

主要實作

Big5 編碼處理

Big5 是傳統的中文編碼方式,主要用於台灣。在掃描到發票 QRCode 的資料後,如果是 Big5 編碼,我們需要將其轉換為 UTF-8,這樣才能正確顯示商品名稱等資訊。

首先,我們來實作一個解碼 Big5 的函數:

func decodeBig5String(_ input: String) -> String {
    let cfEncoding = CFStringEncoding(CFStringEncodings.big5.rawValue)
    let encoding = String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(cfEncoding))

    if let data = input.data(using: encoding) {
        return String(data: data, encoding: .utf8) ?? input
    }
    
    return input  // 如果無法解碼,返回原始字串
}

這段程式碼利用了 CFStringEncodings.big5 來處理 Big5 編碼。透過 CFStringConvertEncodingToNSStringEncoding,我們可以將 Big5 轉換為能夠處理的字串編碼,並最終將其轉換為 UTF-8 進行顯示。

參考資料:

Base64 編碼處理

Base64 通常用於對二進制資料進行編碼,而在發票的 QRCode 資料中,有時候商品資訊可能會以 Base64 編碼的形式存在。

我們來實作一個解碼 Base64 的函數:

func decodeBase64String(_ input: String) -> String {
    if let data = Data(base64Encoded: input) {
        return String(data: data, encoding: .utf8) ?? input
    }
    return input  // 如果無法解碼,返回原始字串
}

這裡我們使用了 Data(base64Encoded:) 將 Base64 編碼轉換為二進制資料,接著再將其轉換為 UTF-8 字串。這樣我們就能正確解碼 Base64 資料。

參考資料:How can I encode/decode a string to Base64 in Swift?

更新 QRCode 解析邏輯

接著,我們需要更新 QRCode 的解析邏輯,判斷發票資訊的編碼類型,並根據不同的編碼進行解碼操作。

// 解析 QRCode 的函數
func parseQRCode(_ qrCode: String) -> [ScannedItem] {
    var items: [ScannedItem] = []
    
    // 檢查 QRCode 是否為右邊的格式(以 ** 開頭)
    if qrCode.hasPrefix("**") {
        let rightPart = String(qrCode.dropFirst(2))
        let encodingType = getEncodingType(from: qrCode)
        items = parseProductDetails(from: rightPart, encodingType: encodingType)
    } else if qrCode.count > 95 {
        // 左邊的 QRCode 從第 95 個字元開始解析商品資訊
        let leftPart = String(qrCode.dropFirst(95))
        
        // 取得編碼參數 (第 3 個欄位)
        let encodingType = getEncodingType(from: qrCode)
        items = parseProductDetails(from: leftPart, encodingType: encodingType)
    }
    
    return items
}

// 判斷編碼類型的輔助函數
func getEncodingType(from qrCode: String) -> String.Encoding {
    let components = qrCode.components(separatedBy: ":")
    
    if components.count >= 3, let encodingParam = Int(components[4]) {
        switch encodingParam {
        case 0:
            return String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.big5.rawValue)))
        case 1:
            return .utf8  // UTF-8 編碼
        case 2:
            return .ascii  // Base64 編碼(會進行進一步處理)
        default:
            return .utf8  // 預設為 UTF-8 編碼
        }
    }
    
    return .utf8  // 預設為 UTF-8 編碼
}

// 解析商品明細的輔助函數
func parseProductDetails(from details: String, encodingType: String.Encoding) -> [ScannedItem] {
    var items: [ScannedItem] = []
    
    // 根據編碼類型處理商品名稱
    let decodedDetails = decodeString(details, encodingType: encodingType)
    
    // 使用 ":" 分隔商品資訊
    let components = decodedDetails.components(separatedBy: ":")
    
    // 每 3 個為一組:品名:數量:單價
    for i in stride(from: 0, to: components.count, by: 3) {
        if i + 2 < components.count {
            let name = components[i]  // 商品名稱
            if let count = Int(components[i + 1]),  // 數量
               let price = Double(components[i + 2]) {  // 單價
                let item = ScannedItem(name: name, quantity: count, price: price)
                items.append(item)
            }
        }
    }
    
    
    return items
}

// 根據編碼類型解碼字串
func decodeString(_ input: String, encodingType: String.Encoding) -> String {
    if encodingType == .ascii, let data = Data(base64Encoded: input) {
        // Base64 解碼
        return String(data: data, encoding: .utf8) ?? input
    } else if encodingType == String.Encoding(rawValue: CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.big5.rawValue))) {
        // Big5 解碼
        let big5Encoding = CFStringConvertEncodingToNSStringEncoding(CFStringEncoding(CFStringEncodings.big5.rawValue))
        if let data = input.data(using: String.Encoding(rawValue: big5Encoding)),
           let decodedString = String(data: data, encoding: String.Encoding(rawValue: big5Encoding)) {
            return decodedString
        }
    } else if let data = input.data(using: encodingType), let decodedString = String(data: data, encoding: encodingType) {
        // 其他編碼類型解碼 (如 UTF-8)
        return decodedString
    }
    
    return input  // 如果無法解碼,返回原字串
}

在這裡,我們根據 QRCode 中的編碼參數,動態選擇不同的解碼方式,讓 App 可以解讀不同的編碼資訊。

總結

雖然我們今天加上對於 Big5 和 Base64 編碼的支援,並成功將它們轉換為 UTF-8,將掃描到的發票 QRCode 資料顯示出來,但其實還是有些問題。

最主要的問題是 QRCode 容量有限。以 電子發票證明聯一維及二維條碼規格說明 的範例就可以得知,在 Base64 編碼顯示商品名稱時,後續的數量與價格有可能會被放置到右邊的 QRCode,導致解析失敗。又或者 QRCode 無法將所有商品都列出等等,造成資料的不完整。而且以規格說明文件來看,商品明細並無強制規定要全部列出。

這些問題也許未來可以透過串接電子發票整合服務平台提供的 API 查詢發票和優化掃描 QRCode 的程式可以解決,本次練習就不考慮這些因素了。

明天我們將繼續實作將取得的商品列表存入Core Data中,敬請期待~


上一篇
Day 24: 掃描 QRCode 並顯示列表
下一篇
Day 26: SwiftUI 編輯與儲存掃描到的消費清單
系列文
用 SwiftUI 掌控家庭日用品庫存30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言