Swift实现XML压缩:移除换行与行前空白,保留节点内部空格
在Swift中实现XML压缩(保留节点内部空格)
嘿,这个问题我之前处理XML的时候也踩过坑!直接用replacingOccurrences(of:)替换空格或者换行肯定不行,一不小心就会把节点内容里的有效空格(比如<Value1>company name</Value1>里的空格)给删掉。这里有两个实用的方案,你可以根据自己的需求选:
方案一:正则表达式快速处理
如果你的XML结构比较简单,没有CDATA、注释这类特殊内容,正则表达式是最快的实现方式。核心思路是只删除标签之间的格式化空白,而保留节点内容里的空格。
代码示例
func compressXML(_ xmlString: String) -> String { // 匹配 ">" 后接任意空白字符(换行、制表符、空格)再到 "<" 的部分 let pattern = "(>\\s+<)" guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return xmlString } let range = NSRange(xmlString.startIndex..., in: xmlString) // 把匹配到的空白部分替换成 "><",直接连接标签 let compressed = regex.stringByReplacingMatches(in: xmlString, range: range, withTemplate: "><") // 额外处理XML声明后的空白(比如<?xml...?>后面的换行) let cleanedDeclaration = compressed.replacingOccurrences(of: "\\?>\\s+<", with: "?>", options: .regularExpression) return cleanedDeclaration } // 使用示例 let originalXML = """ <?xml version="1.0" encoding="UTF-8" standalone="yes"?> <RootNode attribute1="1.0" attribute2="client" attribute3="apiKey"> <Node> <SubNode> <Value1>company name</Value1> <Value2>userName</Value2> <Value3>password</Value3> </SubNode> </Node> <OtherNode attribute="datatype" attribute="method"/> </RootNode> """ let compressedXML = compressXML(originalXML) print(compressedXML) // 输出:<?xml version="1.0" encoding="UTF-8" standalone="yes"?><RootNode attribute1="1.0" attribute2="client" attribute3="apiKey"><Node><SubNode><Value1>company name</Value1><Value2>userName</Value2><Value3>password</Value3></SubNode></Node><OtherNode attribute="datatype" attribute="method"/></RootNode>
方案二:XML解析器实现(更健壮)
如果你的XML包含CDATA、注释,或者结构复杂到正则容易出错的程度,用Foundation自带的XMLParser是最稳妥的选择。解析器会准确区分标签、属性、内容的边界,完全不用担心误删有效内容。
代码示例
import Foundation class XMLCompressor: NSObject, XMLParserDelegate { private var resultString = "" private var currentElementContent = "" func compress(xmlString: String) -> String? { resultString = "" currentElementContent = "" guard let data = xmlString.data(using: .utf8) else { return nil } let parser = XMLParser(data: data) parser.delegate = self guard parser.parse() else { print("XML解析失败:\(parser.parserError?.localizedDescription ?? "未知错误")") return nil } return resultString } // 处理XML声明(比如<?xml...?>) func parser(_ parser: XMLParser, foundProcessingInstructionWithTarget target: String, data: String?) { resultString += "<?\(target) \(data ?? "")?>" } // 开始解析标签 func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) { // 先把之前收集的元素内容(如果有)添加到结果,只去掉前后的格式化空白 if !currentElementContent.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { resultString += currentElementContent.trimmingCharacters(in: .whitespacesAndNewlines) currentElementContent = "" } // 拼接开始标签和属性 var startTag = "<\(elementName)" for (key, value) in attributeDict { startTag += " \(key)=\"\(value)\"" } startTag += ">" resultString += startTag } // 收集元素内部的内容 func parser(_ parser: XMLParser, foundCharacters string: String) { currentElementContent += string } // 结束解析标签 func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) { // 处理元素内容 if !currentElementContent.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { resultString += currentElementContent.trimmingCharacters(in: .whitespacesAndNewlines) currentElementContent = "" } // 添加结束标签 resultString += "</\(elementName)>" } // 可选:处理CDATA块,原封不动保留内容 func parser(_ parser: XMLParser, foundCDATA CDATABlock: Data) { if let cdataString = String(data: CDATABlock, encoding: .utf8) { resultString += "<![CDATA[\(cdataString)]]>" } } } // 使用示例 let compressor = XMLCompressor() if let compressedXML = compressor.compress(xmlString: originalXML) { print(compressedXML) }
方案对比
- 正则方案:代码简洁,执行速度快,但仅适用于结构简单的XML,无法处理CDATA、注释等特殊情况。
- 解析器方案:完全兼容所有合法XML,能正确处理CDATA、注释、特殊字符,代码量稍大但稳定性拉满。
内容的提问来源于stack exchange,提问作者Vladimir
相关产品推荐
相关产品推荐

