Create OPMLParserTests.
This commit is contained in:
parent
f63af89e31
commit
4349dd26ff
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1530"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "OPMLParser"
|
||||
BuildableName = "OPMLParser"
|
||||
BlueprintName = "OPMLParser"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "OPMLParser"
|
||||
BuildableName = "OPMLParser"
|
||||
BlueprintName = "OPMLParser"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1530"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "OPMLParserTests"
|
||||
BuildableName = "OPMLParserTests"
|
||||
BlueprintName = "OPMLParserTests"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
@ -0,0 +1,117 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1530"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "OPMLParser"
|
||||
BuildableName = "OPMLParser"
|
||||
BlueprintName = "OPMLParser"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "Parser"
|
||||
BuildableName = "Parser"
|
||||
BlueprintName = "Parser"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "SAX"
|
||||
BuildableName = "SAX"
|
||||
BlueprintName = "SAX"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "OPMLParserTests"
|
||||
BuildableName = "OPMLParserTests"
|
||||
BlueprintName = "OPMLParserTests"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "ParserTests"
|
||||
BuildableName = "ParserTests"
|
||||
BlueprintName = "ParserTests"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "OPMLParser"
|
||||
BuildableName = "OPMLParser"
|
||||
BlueprintName = "OPMLParser"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
@ -0,0 +1,67 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1530"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES"
|
||||
buildArchitectures = "Automatic">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "SAX"
|
||||
BuildableName = "SAX"
|
||||
BlueprintName = "SAX"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
shouldAutocreateTestPlan = "YES">
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "SAX"
|
||||
BuildableName = "SAX"
|
||||
BlueprintName = "SAX"
|
||||
ReferencedContainer = "container:">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
@ -15,13 +15,26 @@ let package = Package(
|
||||
.library(
|
||||
name: "SAX",
|
||||
type: .dynamic,
|
||||
targets: ["SAX"])
|
||||
targets: ["SAX"]),
|
||||
.library(
|
||||
name: "OPMLParser",
|
||||
type: .dynamic,
|
||||
targets: ["OPMLParser"])
|
||||
|
||||
],
|
||||
dependencies: [
|
||||
],
|
||||
targets: [
|
||||
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
|
||||
// Targets can depend on other targets in this package, and on products in packages this package depends on.
|
||||
.target(
|
||||
name: "OPMLParser",
|
||||
dependencies: [
|
||||
"SAX"
|
||||
],
|
||||
swiftSettings: [
|
||||
.enableExperimentalFeature("StrictConcurrency")
|
||||
]),
|
||||
.target(
|
||||
name: "Parser",
|
||||
dependencies: [
|
||||
@ -41,6 +54,10 @@ let package = Package(
|
||||
dependencies: ["Parser"],
|
||||
exclude: ["Info.plist"],
|
||||
resources: [.copy("Resources")]),
|
||||
.testTarget(
|
||||
name: "OPMLParserTests",
|
||||
dependencies: ["OPMLParser"],
|
||||
resources: [.copy("Resources")]),
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SAX
|
||||
|
||||
public struct OPMLFeedSpecifier: Sendable {
|
||||
|
@ -27,8 +27,9 @@ public class OPMLItem {
|
||||
|
||||
if let feedURL = attributes?.opml_xmlUrl {
|
||||
self.feedSpecifier = OPMLFeedSpecifier(title: self.titleFromAttributes, feedDescription: attributes?.opml_description, homePageURL: attributes?.opml_htmlUrl, feedURL: feedURL)
|
||||
} else {
|
||||
self.feedSpecifier = nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func add(_ item: OPMLItem) {
|
@ -6,13 +6,16 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SAX
|
||||
|
||||
public final class OPMLParser {
|
||||
|
||||
private let url: String
|
||||
private let data: Data
|
||||
private let parserData: ParserData
|
||||
private var data: Data {
|
||||
parserData.data
|
||||
}
|
||||
|
||||
private let opmlDocument: OPMLDocument
|
||||
private var opmlDocument: OPMLDocument?
|
||||
|
||||
private var itemStack = [OPMLItem]()
|
||||
private var currentItem: OPMLItem? {
|
||||
@ -28,26 +31,26 @@ public final class OPMLParser {
|
||||
public static func document(with parserData: ParserData) -> OPMLDocument? {
|
||||
|
||||
let opmlParser = OPMLParser(parserData)
|
||||
return opmlParser.parse()
|
||||
opmlParser.parse()
|
||||
return opmlParser.opmlDocument
|
||||
}
|
||||
|
||||
init(_ parserData: ParserData) {
|
||||
|
||||
self.url = parserData.url
|
||||
self.data = parserData.data
|
||||
self.opmlDocument = OPMLDocument(url: parserData.url)
|
||||
self.parserData = parserData
|
||||
}
|
||||
}
|
||||
|
||||
private extension OPMLParser {
|
||||
|
||||
func parse() -> OPMLDocument? {
|
||||
func parse() {
|
||||
|
||||
guard canParseData() else {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
pushItem(opmlDocument)
|
||||
opmlDocument = OPMLDocument(url: parserData.url)
|
||||
push(opmlDocument!)
|
||||
|
||||
let saxParser = SAXParser(delegate: self, data: data)
|
||||
saxParser.parse()
|
||||
@ -70,20 +73,20 @@ private extension OPMLParser {
|
||||
return
|
||||
}
|
||||
|
||||
itemStack.dropLast()
|
||||
_ = itemStack.dropLast()
|
||||
}
|
||||
}
|
||||
|
||||
extension OPMLParser: SAXParserDelegate {
|
||||
|
||||
func saxParser(_ saxParser: SAXParser, xmlStartElement localName: XMLPointer, prefix: XMLPointer?, uri: XMLPointer?, namespaceCount: Int, namespaces: UnsafePointer<XMLPointer?>?, attributeCount: Int, attributesDefaultedCount: Int, attributes: UnsafePointer<XMLPointer?>?) {
|
||||
public func saxParser(_ saxParser: SAXParser, xmlStartElement localName: XMLPointer, prefix: XMLPointer?, uri: XMLPointer?, namespaceCount: Int, namespaces: UnsafePointer<XMLPointer?>?, attributeCount: Int, attributesDefaultedCount: Int, attributes: UnsafePointer<XMLPointer?>?) {
|
||||
|
||||
if SAXEqualStrings(localName, XMLKey.title) {
|
||||
if SAXEqualTags(localName, XMLKey.title) {
|
||||
saxParser.beginStoringCharacters()
|
||||
return
|
||||
}
|
||||
|
||||
if !SAXEqualStrings(localName, XMLKey.outline) {
|
||||
if !SAXEqualTags(localName, XMLKey.outline) {
|
||||
return
|
||||
}
|
||||
|
||||
@ -94,21 +97,21 @@ extension OPMLParser: SAXParserDelegate {
|
||||
push(item)
|
||||
}
|
||||
|
||||
func saxParser(_ saxParser: SAXParser, xmlEndElement localName: XMLPointer, prefix: XMLPointer?, uri: XMLPointer?) {
|
||||
public func saxParser(_ saxParser: SAXParser, xmlEndElement localName: XMLPointer, prefix: XMLPointer?, uri: XMLPointer?) {
|
||||
|
||||
if SAXEqualStrings(localname, XMLKey.title) {
|
||||
if SAXEqualTags(localName, XMLKey.title) {
|
||||
if let item = currentItem as? OPMLDocument {
|
||||
item.title = saxParser.currentStringWithTrimmedWhitespace
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if SAXEqualStrings(localName, XMLKey.outline) {
|
||||
if SAXEqualTags(localName, XMLKey.outline) {
|
||||
popItem()
|
||||
}
|
||||
}
|
||||
|
||||
func saxParser(_: SAXParser, xmlCharactersFound: XMLPointer, count: Int) {
|
||||
public func saxParser(_: SAXParser, xmlCharactersFound: XMLPointer, count: Int) {
|
||||
|
||||
// Nothing to do, but method is required.
|
||||
}
|
@ -7,7 +7,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Data {
|
||||
public extension Data {
|
||||
|
||||
/// Return true if the data contains a given String.
|
||||
///
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Dictionary where Key == String, Value == String {
|
||||
public extension Dictionary where Key == String, Value == String {
|
||||
|
||||
func object(forCaseInsensitiveKey key: String) -> String? {
|
||||
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
public extension String {
|
||||
|
||||
var nilIfEmptyOrWhitespace: String? {
|
||||
return self.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? nil : self
|
||||
|
@ -9,7 +9,7 @@ import Foundation
|
||||
|
||||
public struct HTMLTag: Sendable {
|
||||
|
||||
public enum TagType {
|
||||
public enum TagType: Sendable {
|
||||
case link
|
||||
case meta
|
||||
}
|
@ -9,8 +9,8 @@ import Foundation
|
||||
|
||||
public struct ParserData: Sendable {
|
||||
|
||||
let url: String
|
||||
let data: Data
|
||||
public let url: String
|
||||
public let data: Data
|
||||
|
||||
public init(url: String, data: Data) {
|
||||
self.url = url
|
||||
|
@ -8,9 +8,9 @@
|
||||
import Foundation
|
||||
import libxml2
|
||||
|
||||
typealias XMLPointer = UnsafePointer<xmlChar>
|
||||
public typealias XMLPointer = UnsafePointer<xmlChar>
|
||||
|
||||
protocol SAXParserDelegate {
|
||||
public protocol SAXParserDelegate {
|
||||
|
||||
func saxParser(_: SAXParser, xmlStartElement: XMLPointer, prefix: XMLPointer?, uri: XMLPointer?, namespaceCount: Int, namespaces: UnsafePointer<XMLPointer?>?, attributeCount: Int, attributesDefaultedCount: Int, attributes: UnsafePointer<XMLPointer?>?)
|
||||
|
||||
@ -19,11 +19,11 @@ protocol SAXParserDelegate {
|
||||
func saxParser(_: SAXParser, xmlCharactersFound: XMLPointer, count: Int)
|
||||
}
|
||||
|
||||
final class SAXParser {
|
||||
public final class SAXParser {
|
||||
|
||||
fileprivate let delegate: SAXParserDelegate
|
||||
|
||||
var currentCharacters: Data? { // UTF-8 encoded
|
||||
public var currentCharacters: Data? { // UTF-8 encoded
|
||||
|
||||
guard storingCharacters else {
|
||||
return nil
|
||||
@ -33,7 +33,7 @@ final class SAXParser {
|
||||
|
||||
// Conveniences to get string version of currentCharacters
|
||||
|
||||
var currentString: String? {
|
||||
public var currentString: String? {
|
||||
|
||||
guard let d = currentCharacters, !d.isEmpty else {
|
||||
return nil
|
||||
@ -41,7 +41,7 @@ final class SAXParser {
|
||||
return String(data: d, encoding: .utf8)
|
||||
}
|
||||
|
||||
var currentStringWithTrimmedWhitespace: String? {
|
||||
public var currentStringWithTrimmedWhitespace: String? {
|
||||
|
||||
guard let s = currentString else {
|
||||
return nil
|
||||
@ -53,13 +53,13 @@ final class SAXParser {
|
||||
private var storingCharacters = false
|
||||
private var characters = Data()
|
||||
|
||||
init(delegate: SAXParserDelegate, data: Data) {
|
||||
public init(delegate: SAXParserDelegate, data: Data) {
|
||||
|
||||
self.delegate = delegate
|
||||
self.data = data
|
||||
}
|
||||
|
||||
func parse() {
|
||||
public func parse() {
|
||||
|
||||
guard !data.isEmpty else {
|
||||
return
|
||||
@ -69,7 +69,7 @@ final class SAXParser {
|
||||
xmlCtxtUseOptions(context, Int32(XML_PARSE_RECOVER.rawValue | XML_PARSE_NOENT.rawValue))
|
||||
|
||||
data.withUnsafeBytes { bufferPointer in
|
||||
if let bytes = bufferPointer.bindMemory(to: xmlChar.self).baseAddress {
|
||||
if let bytes = bufferPointer.bindMemory(to: CChar.self).baseAddress {
|
||||
xmlParseChunk(context, bytes, Int32(data.count), 0)
|
||||
}
|
||||
}
|
||||
@ -79,7 +79,7 @@ final class SAXParser {
|
||||
}
|
||||
|
||||
/// Delegate can call from xmlStartElement. Characters will be available in xmlEndElement as currentCharacters property. Storing characters is stopped after each xmlEndElement.
|
||||
func beginStoringCharacters() {
|
||||
public func beginStoringCharacters() {
|
||||
|
||||
storingCharacters = true
|
||||
characters.count = 0
|
||||
@ -91,7 +91,7 @@ final class SAXParser {
|
||||
characters.count = 0
|
||||
}
|
||||
|
||||
func attributesDictionary(_ attributes: UnsafePointer<XMLPointer?>?, attributeCount: Int) -> [String: String]? {
|
||||
public func attributesDictionary(_ attributes: UnsafePointer<XMLPointer?>?, attributeCount: Int) -> [String: String]? {
|
||||
|
||||
guard attributeCount > 0, let attributes else {
|
||||
return nil
|
||||
@ -154,7 +154,7 @@ private extension SAXParser {
|
||||
}
|
||||
}
|
||||
|
||||
private func startElement(_ context: UnsafeMutableRawPointer?, name: XMLPointer?, prefix: XMLPointer?, URI: XMLPointer?, nb_namespaces: CInt, namespaces: UnsafePointer<XMLPointer?>?, nb_attributes: CInt, nb_defaulted: CInt, attributes: UnsafeMutablePointer<XMLPointer?>?) {
|
||||
private func startElement(_ context: UnsafeMutableRawPointer?, name: XMLPointer?, prefix: XMLPointer?, URI: XMLPointer?, nb_namespaces: CInt, namespaces: UnsafeMutablePointer<XMLPointer?>?, nb_attributes: CInt, nb_defaulted: CInt, attributes: UnsafeMutablePointer<XMLPointer?>?) {
|
||||
|
||||
guard let context, let name else {
|
||||
return
|
||||
@ -194,8 +194,9 @@ nonisolated(unsafe) private var saxHandlerStruct: xmlSAXHandler = {
|
||||
var handler = xmlSAXHandler()
|
||||
|
||||
handler.characters = charactersFound
|
||||
handler.startElement = startElement
|
||||
handler.endElement = endElement
|
||||
handler.startElementNs = startElement
|
||||
handler.endElementNs = endElement
|
||||
handler.initialized = XML_SAX2_MAGIC
|
||||
|
||||
return handler
|
||||
}()
|
||||
|
@ -8,11 +8,26 @@
|
||||
import Foundation
|
||||
import libxml2
|
||||
|
||||
func SAXEqualStrings(_ s1: XMLPointer, _ s2: XMLPointer, length: Int? = nil) -> Bool {
|
||||
public func SAXEqualTags(_ localName: XMLPointer, _ tag: ContiguousArray<Int8>) -> Bool {
|
||||
|
||||
if let length {
|
||||
return xmlStrncmp(s1, s2, Int32(length)) == 0
|
||||
return tag.withUnsafeBufferPointer { bufferPointer in
|
||||
|
||||
let tagCount = tag.count
|
||||
|
||||
for i in 0..<tagCount {
|
||||
|
||||
let localNameCharacter = localName[i]
|
||||
if localNameCharacter == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
let tagCharacter = UInt8(tag[i])
|
||||
if localNameCharacter != tagCharacter {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// localName might actually be longer — make sure it’s the same length as tag.
|
||||
return localName[tagCount] == 0
|
||||
}
|
||||
|
||||
return xmlStrEqual(s1, s2) != 0
|
||||
}
|
||||
|
@ -7,8 +7,8 @@
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import Parser
|
||||
import ParserObjC
|
||||
import SAX
|
||||
@testable import OPMLParser
|
||||
|
||||
class OPMLTests: XCTestCase {
|
||||
|
||||
@ -18,21 +18,23 @@ class OPMLTests: XCTestCase {
|
||||
|
||||
// 0.002 sec on my 2012 iMac.
|
||||
self.measure {
|
||||
let _ = try! RSOPMLParser.parseOPML(with: self.subsData)
|
||||
let _ = OPMLParser.document(with: self.subsData)
|
||||
}
|
||||
}
|
||||
|
||||
func testNotOPML() {
|
||||
|
||||
let d = parserData("DaringFireball", "rss", "http://daringfireball.net/")
|
||||
XCTAssertThrowsError(try RSOPMLParser.parseOPML(with: d))
|
||||
XCTAssertNil(OPMLParser.document(with: d))
|
||||
}
|
||||
|
||||
func testSubsStructure() {
|
||||
let opmlDocument = try! RSOPMLParser.parseOPML(with: subsData)
|
||||
XCTAssertEqual("Subs", opmlDocument.title)
|
||||
XCTAssertEqual("http://example.org/", opmlDocument.url)
|
||||
recursivelyCheckOPMLStructure(opmlDocument)
|
||||
let opmlDocument = OPMLParser.document(with: subsData)
|
||||
XCTAssertNotNil(opmlDocument)
|
||||
|
||||
XCTAssertEqual("Subs", opmlDocument!.title)
|
||||
XCTAssertEqual("http://example.org/", opmlDocument!.url)
|
||||
recursivelyCheckOPMLStructure(opmlDocument!)
|
||||
}
|
||||
|
||||
|
||||
@ -42,23 +44,23 @@ class OPMLTests: XCTestCase {
|
||||
// which appears to be true with OPML generated by The Old Reader.
|
||||
|
||||
let d = parserData("SubsNoTitleAttributes", "opml", "http://example.org/")
|
||||
let opmlDocument = try! RSOPMLParser.parseOPML(with: d)
|
||||
recursivelyCheckOPMLStructure(opmlDocument)
|
||||
let opmlDocument = OPMLParser.document(with: d)
|
||||
recursivelyCheckOPMLStructure(opmlDocument!)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension OPMLTests {
|
||||
|
||||
func recursivelyCheckOPMLStructure(_ item: RSOPMLItem) {
|
||||
func recursivelyCheckOPMLStructure(_ item: OPMLItem) {
|
||||
let feedSpecifier = item.feedSpecifier
|
||||
if !(item is RSOPMLDocument) {
|
||||
XCTAssertNotNil((item.attributes! as NSDictionary).opml_text)
|
||||
if !(item is OPMLDocument) {
|
||||
XCTAssertNotNil(item.attributes!.opml_text)
|
||||
}
|
||||
|
||||
// If it has no children, it should have a feed specifier. The converse is also true.
|
||||
var isFolder = item.children != nil && item.children!.count > 0
|
||||
if !isFolder && (item.attributes! as NSDictionary).opml_title == "Skip" {
|
||||
var isFolder = item.items != nil && item.items!.count > 0
|
||||
if !isFolder && item.attributes?.opml_title == "Skip" {
|
||||
isFolder = true
|
||||
}
|
||||
|
||||
@ -70,10 +72,17 @@ private extension OPMLTests {
|
||||
XCTAssertNil(feedSpecifier)
|
||||
}
|
||||
|
||||
if item.children != nil && item.children!.count > 0 {
|
||||
for oneItem in item.children! {
|
||||
if item.items != nil && item.items!.count > 0 {
|
||||
for oneItem in item.items! {
|
||||
recursivelyCheckOPMLStructure(oneItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func parserData(_ filename: String, _ fileExtension: String, _ url: String) -> ParserData {
|
||||
let filename = "Resources/\(filename)"
|
||||
let path = Bundle.module.path(forResource: filename, ofType: fileExtension)!
|
||||
let data = try! Data(contentsOf: URL(fileURLWithPath: path))
|
||||
return ParserData(url: url, data: data)
|
||||
}
|
2278
Modules/Parser/Tests/OPMLParserTests/Resources/DaringFireball.rss
Executable file
2278
Modules/Parser/Tests/OPMLParserTests/Resources/DaringFireball.rss
Executable file
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user