From 3591ca6c0ab7fe37271ba65f10660cd8b8386b21 Mon Sep 17 00:00:00 2001 From: Stephan Partzsch Date: Wed, 6 Mar 2024 21:40:38 +0100 Subject: [PATCH] Fixes support for ordered list with other numbers instead of "1. " (https://github.com/SimonFairbairn/SwiftyMarkdown/pull/111). Done by https://github.com/starfall-9000 --- .../SwiftyMarkdown/SwiftyLineProcessor.swift | 236 +++++++++--------- 1 file changed, 119 insertions(+), 117 deletions(-) diff --git a/Sources/SwiftyMarkdown/SwiftyLineProcessor.swift b/Sources/SwiftyMarkdown/SwiftyLineProcessor.swift index b6acadd..44b2b83 100644 --- a/Sources/SwiftyMarkdown/SwiftyLineProcessor.swift +++ b/Sources/SwiftyMarkdown/SwiftyLineProcessor.swift @@ -10,25 +10,25 @@ import Foundation import os.log extension OSLog { - private static var subsystem = "SwiftyLineProcessor" - static let swiftyLineProcessorPerformance = OSLog(subsystem: subsystem, category: "Swifty Line Processor Performance") + private static var subsystem = "SwiftyLineProcessor" + static let swiftyLineProcessorPerformance = OSLog(subsystem: subsystem, category: "Swifty Line Processor Performance") } public protocol LineStyling { - var shouldTokeniseLine : Bool { get } + var shouldTokeniseLine: Bool { get } func styleIfFoundStyleAffectsPreviousLine() -> LineStyling? } -public struct SwiftyLine : CustomStringConvertible { - public let line : String - public let lineStyle : LineStyling +public struct SwiftyLine: CustomStringConvertible { + public let line: String + public let lineStyle: LineStyling public var description: String { return self.line } } -extension SwiftyLine : Equatable { - public static func == ( _ lhs : SwiftyLine, _ rhs : SwiftyLine ) -> Bool { +extension SwiftyLine: Equatable { + public static func == (_ lhs: SwiftyLine, _ rhs: SwiftyLine) -> Bool { return lhs.line == rhs.line } } @@ -44,23 +44,23 @@ public enum Remove { public enum ChangeApplication { case current case previous - case untilClose + case untilClose } public struct FrontMatterRule { - let openTag : String - let closeTag : String - let keyValueSeparator : Character + let openTag: String + let closeTag: String + let keyValueSeparator: Character } public struct LineRule { - let token : String - let removeFrom : Remove - let type : LineStyling - let shouldTrim : Bool - let changeAppliesTo : ChangeApplication + let token: String + let removeFrom: Remove + let type: LineStyling + let shouldTrim: Bool + let changeAppliesTo: ChangeApplication - public init(token : String, type : LineStyling, removeFrom : Remove = .leading, shouldTrim : Bool = true, changeAppliesTo : ChangeApplication = .current ) { + public init(token: String, type: LineStyling, removeFrom: Remove = .leading, shouldTrim: Bool = true, changeAppliesTo: ChangeApplication = .current) { self.token = token self.type = type self.removeFrom = removeFrom @@ -70,25 +70,24 @@ public struct LineRule { } public class SwiftyLineProcessor { - - public var processEmptyStrings : LineStyling? - public internal(set) var frontMatterAttributes : [String : String] = [:] + public var processEmptyStrings: LineStyling? + public internal(set) var frontMatterAttributes: [String: String] = [:] - var closeToken : String? = nil - let defaultType : LineStyling + var closeToken: String? + let defaultType: LineStyling - let lineRules : [LineRule] - let frontMatterRules : [FrontMatterRule] + let lineRules: [LineRule] + let frontMatterRules: [FrontMatterRule] - let perfomanceLog = PerformanceLog(with: "SwiftyLineProcessorPerformanceLogging", identifier: "Line Processor", log: OSLog.swiftyLineProcessorPerformance) + let perfomanceLog = PerformanceLog(with: "SwiftyLineProcessorPerformanceLogging", identifier: "Line Processor", log: OSLog.swiftyLineProcessorPerformance) - public init( rules : [LineRule], defaultRule: LineStyling, frontMatterRules : [FrontMatterRule] = []) { + public init(rules: [LineRule], defaultRule: LineStyling, frontMatterRules: [FrontMatterRule] = []) { self.lineRules = rules self.defaultType = defaultRule - self.frontMatterRules = frontMatterRules + self.frontMatterRules = frontMatterRules } - func findLeadingLineElement( _ element : LineRule, in string : String ) -> String { + func findLeadingLineElement(_ element: LineRule, in string: String) -> String { var output = string if let range = output.index(output.startIndex, offsetBy: element.token.count, limitedBy: output.endIndex), output[output.startIndex.. String { + func findTrailingLineElement(_ element: LineRule, in string: String) -> String { var output = string let token = element.token.trimmingCharacters(in: .whitespaces) if let range = output.index(output.endIndex, offsetBy: -(token.count), limitedBy: output.startIndex), output[range.. SwiftyLine? { + func processLineLevelAttributes(_ text: String) -> SwiftyLine? { if text.isEmpty, let style = processEmptyStrings { return SwiftyLine(line: "", lineStyle: style) } - let previousLines = lineRules.filter({ $0.changeAppliesTo == .previous }) + let previousLines = self.lineRules.filter { $0.changeAppliesTo == .previous } - for element in lineRules { + for element in self.lineRules { guard element.token.count > 0 else { continue } - var output : String = (element.shouldTrim) ? text.trimmingCharacters(in: .whitespaces) : text + var output: String = (element.shouldTrim) ? text.trimmingCharacters(in: .whitespaces) : text let unprocessed = output - if let hasToken = self.closeToken, unprocessed != hasToken { - return nil - } + if let hasToken = self.closeToken, unprocessed != hasToken { + return nil + } - if !text.contains(element.token) { - continue - } + if element.token == "1. " { + // replace all text format "2. ", "3. ", ..etc with format "1. " + output = processOrderListRegex(output) + } + + if !text.contains(element.token) && element.token != "1. " { + continue + } switch element.removeFrom { case .leading: - output = findLeadingLineElement(element, in: output) + output = self.findLeadingLineElement(element, in: output) case .trailing: - output = findTrailingLineElement(element, in: output) + output = self.findTrailingLineElement(element, in: output) case .both: - output = findLeadingLineElement(element, in: output) - output = findTrailingLineElement(element, in: output) - case .entireLine: - let maybeOutput = output.replacingOccurrences(of: element.token, with: "") - output = ( maybeOutput.isEmpty ) ? maybeOutput : output + output = self.findLeadingLineElement(element, in: output) + output = self.findTrailingLineElement(element, in: output) + case .entireLine: + let maybeOutput = output.replacingOccurrences(of: element.token, with: "") + output = (maybeOutput.isEmpty) ? maybeOutput : output default: break } @@ -147,88 +150,82 @@ public class SwiftyLineProcessor { guard unprocessed != output else { continue } - if element.changeAppliesTo == .untilClose { - self.closeToken = (self.closeToken == nil) ? element.token : nil - return nil - } + if element.changeAppliesTo == .untilClose { + self.closeToken = (self.closeToken == nil) ? element.token : nil + return nil + } - - output = (element.shouldTrim) ? output.trimmingCharacters(in: .whitespaces) : output return SwiftyLine(line: output, lineStyle: element.type) - } - for element in previousLines { - let output = (element.shouldTrim) ? text.trimmingCharacters(in: .whitespaces) : text - let charSet = CharacterSet(charactersIn: element.token ) - if output.unicodeScalars.allSatisfy({ charSet.contains($0) }) { - return SwiftyLine(line: "", lineStyle: element.type) - } - } + for element in previousLines { + let output = (element.shouldTrim) ? text.trimmingCharacters(in: .whitespaces) : text + let charSet = CharacterSet(charactersIn: element.token) + if output.unicodeScalars.allSatisfy({ charSet.contains($0) }) { + return SwiftyLine(line: "", lineStyle: element.type) + } + } - return SwiftyLine(line: text.trimmingCharacters(in: .whitespaces), lineStyle: defaultType) + return SwiftyLine(line: text.trimmingCharacters(in: .whitespaces), lineStyle: self.defaultType) } - func processFrontMatter( _ strings : [String] ) -> [String] { - guard let firstString = strings.first?.trimmingCharacters(in: .whitespacesAndNewlines) else { - return strings - } - var rulesToApply : FrontMatterRule? = nil - for matter in self.frontMatterRules { - if firstString == matter.openTag { - rulesToApply = matter - break - } - } - guard let existentRules = rulesToApply else { - return strings - } - var outputString = strings - // Remove the first line, which is the front matter opening tag - let _ = outputString.removeFirst() - var closeFound = false - while !closeFound { - let nextString = outputString.removeFirst() - if nextString == existentRules.closeTag { - closeFound = true - continue - } - var keyValue = nextString.components(separatedBy: "\(existentRules.keyValueSeparator)") - if keyValue.count < 2 { - continue - } - let key = keyValue.removeFirst() - let value = keyValue.joined() - self.frontMatterAttributes[key] = value - } - while outputString.first?.isEmpty ?? false { - outputString.removeFirst() - } - return outputString - } + func processFrontMatter(_ strings: [String]) -> [String] { + guard let firstString = strings.first?.trimmingCharacters(in: .whitespacesAndNewlines) else { + return strings + } + var rulesToApply: FrontMatterRule? + for matter in self.frontMatterRules { + if firstString == matter.openTag { + rulesToApply = matter + break + } + } + guard let existentRules = rulesToApply else { + return strings + } + var outputString = strings + // Remove the first line, which is the front matter opening tag + _ = outputString.removeFirst() + var closeFound = false + while !closeFound { + let nextString = outputString.removeFirst() + if nextString == existentRules.closeTag { + closeFound = true + continue + } + var keyValue = nextString.components(separatedBy: "\(existentRules.keyValueSeparator)") + if keyValue.count < 2 { + continue + } + let key = keyValue.removeFirst() + let value = keyValue.joined() + self.frontMatterAttributes[key] = value + } + while outputString.first?.isEmpty ?? false { + outputString.removeFirst() + } + return outputString + } - public func process( _ string : String ) -> [SwiftyLine] { - var foundAttributes : [SwiftyLine] = [] + public func process(_ string: String) -> [SwiftyLine] { + var foundAttributes: [SwiftyLine] = [] + self.perfomanceLog.start() - self.perfomanceLog.start() + var lines = string.components(separatedBy: CharacterSet.newlines) + lines = self.processFrontMatter(lines) - var lines = string.components(separatedBy: CharacterSet.newlines) - lines = self.processFrontMatter(lines) + self.perfomanceLog.tag(with: "(Front matter completed)") - self.perfomanceLog.tag(with: "(Front matter completed)") - - - for heading in lines { - - if processEmptyStrings == nil && heading.isEmpty { + for heading in lines { + if self.processEmptyStrings == nil, heading.isEmpty { continue } - guard let input = processLineLevelAttributes(String(heading)) else { - continue - } + guard let input = processLineLevelAttributes(String(heading)) else { + continue + } if let existentPrevious = input.lineStyle.styleIfFoundStyleAffectsPreviousLine(), foundAttributes.count > 0 { if let idx = foundAttributes.firstIndex(of: foundAttributes.last!) { @@ -239,11 +236,16 @@ public class SwiftyLineProcessor { } foundAttributes.append(input) - self.perfomanceLog.tag(with: "(line completed: \(heading)") + self.perfomanceLog.tag(with: "(line completed: \(heading)") } return foundAttributes } - } - +func processOrderListRegex(_ text: String) -> String { + let regex = try? NSRegularExpression(pattern: "^[0-9]+. ", options: .caseInsensitive) + let range = NSMakeRange(0, text.count) + let result = regex?.stringByReplacingMatches(in: text, options: [], range: range, + withTemplate: "1. ") + return result ?? text +}