Skip to content

Commit

Permalink
Fixes support for ordered list with other numbers instead of "1. " (S…
Browse files Browse the repository at this point in the history
  • Loading branch information
StephanPartzsch committed Mar 6, 2024
1 parent 3a591ed commit 3591ca6
Showing 1 changed file with 119 additions and 117 deletions.
236 changes: 119 additions & 117 deletions Sources/SwiftyMarkdown/SwiftyLineProcessor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
Expand All @@ -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
Expand All @@ -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..<range] == element.token {
output.removeSubrange(output.startIndex..<range)
Expand All @@ -97,138 +96,136 @@ public class SwiftyLineProcessor {
return output
}

func findTrailingLineElement( _ element : LineRule, in string : String ) -> 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..<output.endIndex] == token {
output.removeSubrange(range..<output.endIndex)
return output

}
return output
}

func processLineLevelAttributes( _ text : String ) -> 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
}
// Only if the output has changed in some way
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!) {
Expand All @@ -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
}

0 comments on commit 3591ca6

Please sign in to comment.