@ -1,6 +1,130 @@
import Foundation
import UIKit
// MARK: - v C a r d 版 本 转 换 工 具
struct VCardConverter {
// / 将 v C a r d 2 . 1 格 式 转 换 为 3 . 0 格 式
static func convertVCard21To30 ( _ vcard21 : String ) -> String {
let lines = vcard21 . components ( separatedBy : . newlines )
var vcard30 = " BEGIN:VCARD \n VERSION:3.0 \n "
for line in lines {
let trimmedLine = line . trimmingCharacters ( in : . whitespaces )
if trimmedLine . isEmpty || trimmedLine . hasPrefix ( " BEGIN: " ) || trimmedLine . hasPrefix ( " VERSION: " ) || trimmedLine . hasPrefix ( " END: " ) {
continue
}
// 处 理 N 字 段 ( 姓 名 )
if trimmedLine . hasPrefix ( " N: " ) {
let nameValue = String ( trimmedLine . dropFirst ( 2 ) )
let nameParts = nameValue . components ( separatedBy : " ; " )
if nameParts . count >= 2 {
let lastName = nameParts [ 0 ]
let firstName = nameParts [ 1 ]
vcard30 += " N: \( lastName ) ; \( firstName ) ;;; \n "
vcard30 += " FN: \( firstName ) \( lastName ) \n "
}
}
// 处 理 T E L 字 段 ( 电 话 )
else if trimmedLine . hasPrefix ( " TEL " ) {
let telValue = String ( trimmedLine . dropFirst ( 3 ) )
if telValue . hasPrefix ( " ;TYPE= " ) {
let typeStart = telValue . firstIndex ( of : " = " ) !
let typeEnd = telValue . firstIndex ( of : " : " ) ? ? telValue . endIndex
let type = String ( telValue [ telValue . index ( after : typeStart ) . . < typeEnd ] )
let number = String ( telValue [ telValue . index ( after : typeEnd ) . . . ] )
vcard30 += " TEL;TYPE= \( type ) : \( number ) \n "
} else if telValue . hasPrefix ( " : " ) {
let number = String ( telValue . dropFirst ( ) )
vcard30 += " TEL: \( number ) \n "
}
}
// 处 理 E M A I L 字 段 ( 邮 箱 )
else if trimmedLine . hasPrefix ( " EMAIL " ) {
let emailValue = String ( trimmedLine . dropFirst ( 5 ) )
if emailValue . hasPrefix ( " ;TYPE= " ) {
let typeStart = emailValue . firstIndex ( of : " = " ) !
let typeEnd = emailValue . firstIndex ( of : " : " ) ? ? emailValue . endIndex
let type = String ( emailValue [ emailValue . index ( after : typeStart ) . . < typeEnd ] )
let email = String ( emailValue [ emailValue . index ( after : typeEnd ) . . . ] )
vcard30 += " EMAIL;TYPE= \( type ) : \( email ) \n "
} else if emailValue . hasPrefix ( " : " ) {
let email = String ( emailValue . dropFirst ( ) )
vcard30 += " EMAIL: \( email ) \n "
}
}
// 处 理 A D R 字 段 ( 地 址 )
else if trimmedLine . hasPrefix ( " ADR " ) {
let adrValue = String ( trimmedLine . dropFirst ( 3 ) )
if adrValue . hasPrefix ( " ;TYPE= " ) {
let typeStart = adrValue . firstIndex ( of : " = " ) !
let typeEnd = adrValue . firstIndex ( of : " : " ) ? ? adrValue . endIndex
let type = String ( adrValue [ adrValue . index ( after : typeStart ) . . < typeEnd ] )
let address = String ( adrValue [ adrValue . index ( after : typeEnd ) . . . ] )
vcard30 += " ADR;TYPE= \( type ) : \( address ) \n "
} else if adrValue . hasPrefix ( " : " ) {
let address = String ( adrValue . dropFirst ( ) )
vcard30 += " ADR: \( address ) \n "
}
}
// 处 理 其 他 字 段
else if trimmedLine . contains ( " : " ) {
let colonIndex = trimmedLine . firstIndex ( of : " : " ) !
let fieldName = String ( trimmedLine [ . . < colonIndex ] )
let fieldValue = String ( trimmedLine [ trimmedLine . index ( after : colonIndex ) . . . ] )
// 跳 过 已 处 理 的 字 段
if ! [ " N " , " TEL " , " EMAIL " , " ADR " ] . contains ( fieldName ) {
vcard30 += " \( fieldName ) : \( fieldValue ) \n "
}
}
}
vcard30 += " END:VCARD "
return vcard30
}
// / 检 测 v C a r d 版 本
static func detectVCardVersion ( _ vcard : String ) -> String {
let lines = vcard . components ( separatedBy : . newlines )
for line in lines {
if line . hasPrefix ( " VERSION: " ) {
return String ( line . dropFirst ( 8 ) )
}
}
return " 3.0 " // 默 认 版 本
}
// / 标 准 化 v C a r d 为 3 . 0 版 本
static func normalizeVCard ( _ vcard : String ) -> String {
let version = detectVCardVersion ( vcard )
if version = = " 2.1 " {
return convertVCard21To30 ( vcard )
}
return vcard
}
// / 测 试 v C a r d 版 本 转 换 功 能
static func testVCardConversion ( ) {
let vcard21 = " " "
BEGIN : VCARD
VERSION : 2.1
N : Surname ; Givenname ; ; ;
FN : Givenname Surname
TEL ; TYPE = WORK : 123 - 456 - 7890
EMAIL ; TYPE = PREF : test @ example . com
END : VCARD
" " "
let converted = convertVCard21To30 ( vcard21 )
print ( " Original vCard 2.1: " )
print ( vcard21 )
print ( " \n Converted to vCard 3.0: " )
print ( converted )
}
}
// MARK: - 二 维 码 解 析 器
class QRCodeParser {
@ -177,27 +301,71 @@ class QRCodeParser {
// MARK: - 解 析 v C a r d
private static func parseVCard ( _ content : String ) -> ParsedQRData {
let lines = content . components ( separatedBy : . newlines )
// 标 准 化 v C a r d 为 3 . 0 版 本
let normalizedVCard = VCardConverter . normalizeVCard ( content )
let lines = normalizedVCard . components ( separatedBy : . newlines )
var name = " "
var phone = " "
var email = " "
var company = " "
var title = " "
var address = " "
var website = " "
for line in lines {
if line . hasPrefix ( " FN: " ) {
name = String ( line . dropFirst ( 3 ) )
} else if line . hasPrefix ( " TEL: " ) {
phone = String ( line . dropFirst ( 4 ) )
} else if line . hasPrefix ( " EMAIL: " ) {
email = String ( line . dropFirst ( 6 ) )
let trimmedLine = line . trimmingCharacters ( in : . whitespaces )
if trimmedLine . hasPrefix ( " FN: " ) {
name = String ( trimmedLine . dropFirst ( 3 ) )
} else if trimmedLine . hasPrefix ( " TEL " ) {
let telValue = String ( trimmedLine . dropFirst ( 3 ) )
if telValue . contains ( " : " ) {
let number = telValue . components ( separatedBy : " : " ) . last ? ? " "
phone = number
}
} else if trimmedLine . hasPrefix ( " EMAIL " ) {
let emailValue = String ( trimmedLine . dropFirst ( 5 ) )
if emailValue . contains ( " : " ) {
let emailAddress = emailValue . components ( separatedBy : " : " ) . last ? ? " "
email = emailAddress
}
} else if trimmedLine . hasPrefix ( " ORG: " ) {
company = String ( trimmedLine . dropFirst ( 4 ) )
} else if trimmedLine . hasPrefix ( " TITLE: " ) {
title = String ( trimmedLine . dropFirst ( 6 ) )
} else if trimmedLine . hasPrefix ( " ADR " ) {
let adrValue = String ( trimmedLine . dropFirst ( 3 ) )
if adrValue . contains ( " : " ) {
let addressParts = adrValue . components ( separatedBy : " : " )
if addressParts . count > 1 {
let addressComponents = addressParts [ 1 ] . components ( separatedBy : " ; " )
if addressComponents . count >= 3 {
address = " \( addressComponents [ 2 ] ) \( addressComponents [ 1 ] ) "
}
}
}
} else if trimmedLine . hasPrefix ( " URL: " ) {
website = String ( trimmedLine . dropFirst ( 4 ) )
}
}
let title = " 联系人信息 "
let subtitle = " 姓名: \( name ) \n 电话: \( phone ) \n 邮箱: \( email ) "
var subtitle = " "
if ! name . isEmpty { subtitle += " 姓名: \( name ) \n " }
if ! phone . isEmpty { subtitle += " 电话: \( phone ) \n " }
if ! email . isEmpty { subtitle += " 邮箱: \( email ) \n " }
if ! company . isEmpty { subtitle += " 公司: \( company ) \n " }
if ! title . isEmpty { subtitle += " 职位: \( title ) \n " }
if ! address . isEmpty { subtitle += " 地址: \( address ) \n " }
if ! website . isEmpty { subtitle += " 网站: \( website ) \n " }
// 移 除 最 后 一 个 换 行 符
if subtitle . hasSuffix ( " \n " ) {
subtitle = String ( subtitle . dropLast ( ) )
}
return ParsedQRData (
type : . vcard ,
title : title ,
title : " 联系人信息 " ,
subtitle : subtitle ,
icon : " person.crop.rectangle "
)