Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 36 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,34 @@ Think of it as your Swiss Army knife for Swift development - all the tools you n

SwiftDevKit aims to be your go-to toolkit for common development tasks, offering a wide range of functionalities from data conversion to code generation. Our goal is to provide a comprehensive, well-tested, and professionally crafted SDK that brings the power of web-based developer tools natively to Swift.

## Planned Features
## Features

- 🛠 Comprehensive utility functions
- 📦 Modular architecture
- 💻 Native Swift implementation
### Text Processing

SwiftDevKit provides powerful text processing tools for common string manipulation tasks:

#### String Transformations
- Case transformations (toTitleCase, toCamelCase, toSnakeCase, toKebabCase)
- Smart string truncation with customizable length and ellipsis
- Whitespace handling (removeExcessWhitespace)

#### String Distance Calculations
- Levenshtein distance for measuring edit distance between strings
- Jaro-Winkler distance for name matching and fuzzy string comparison
- Hamming distance for strings of equal length

#### String Extraction
- Extract numbers (with optional negative number handling)
- Extract words with minimum length filtering
- Extract sentences from text
- Extract URLs with custom scheme filtering
- Extract email addresses
- Extract hashtags and mentions (social media style)
- Extract dates from text

### More Features Coming Soon
- 🔄 Data conversion tools
- 🔐 Cryptography utilities
- 📝 Text processing tools
- 🎨 Development helpers
- 📱 Platform-specific optimizations

Expand Down Expand Up @@ -56,14 +76,17 @@ pod 'SwiftDevKit'
## Development Roadmap

1. 🏗 Core Infrastructure (In Progress)
- Setting up development tools
- Establishing CI/CD pipeline
- Code quality tools integration

2. 🧰 Core Features (Planned)
- Data Conversion utilities
- Text Processing tools
- Development helpers
- Setting up development tools ✅
- Establishing CI/CD pipeline ✅
- Code quality tools integration ✅

2. 🧰 Core Features (In Progress)
- Text Processing tools ✅
- Case transformations
- String truncation
- Whitespace handling
- Data Conversion utilities (Planned)
- Development helpers (Planned)

3. 🔒 Advanced Features (Planned)
- Cryptography utilities
Expand Down
202 changes: 202 additions & 0 deletions Sources/SwiftDevKit/TextProcessing/String+Extraction.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
// String+Extraction.swift
// SwiftDevKit
//
// Copyright (c) 2025 owdax and The SwiftDevKit Contributors
// MIT License - https://opensource.org/licenses/MIT

import Foundation

public extension String {
/// Extracts all numbers from the string.
///
/// Example:
/// ```swift
/// let text = "The price is $19.99 and quantity is 42"
/// let numbers = text.extractNumbers() // ["19.99", "42"]
/// ```
///
/// - Parameter includeNegative: Whether to include negative numbers (default: true)
/// - Returns: An array of strings containing the extracted numbers
func extractNumbers(includeNegative: Bool = true) -> [String] {
let pattern = includeNegative ?
#"-?\d+\.?\d*"# : // Match numbers with optional negative sign
#"(?<![0-9.-])\d+\.?\d*"# // Match numbers not preceded by digits, dots, or minus
guard let regex = try? NSRegularExpression(pattern: pattern) else { return [] }

let range = NSRange(startIndex..., in: self)
return regex.matches(in: self, range: range)
.compactMap { match in
guard let range = Range(match.range, in: self) else { return nil }
return String(self[range])
}
}

/// Extracts all words from the string.
///
/// Example:
/// ```swift
/// let text = "Hello, World! How are you?"
/// let words = text.extractWords() // ["Hello", "World", "How", "are", "you"]
/// ```
///
/// - Parameter minLength: Minimum length for a word to be included (default: 1)
/// - Returns: An array of strings containing the extracted words
func extractWords(minLength: Int = 1) -> [String] {
components(separatedBy: .punctuationCharacters)
.joined()
.components(separatedBy: .whitespaces)
.filter { $0.count >= minLength && !$0.isEmpty }
}

/// Extracts all sentences from the string.
///
/// Example:
/// ```swift
/// let text = "Hello! How are you? I'm doing great."
/// let sentences = text.extractSentences() // ["Hello!", "How are you?", "I'm doing great."]
/// ```
///
/// - Returns: An array of strings containing the extracted sentences
func extractSentences() -> [String] {
components(separatedBy: CharacterSet(charactersIn: ".!?"))
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { !$0.isEmpty }
}

/// Extracts all URLs from the string.
///
/// Example:
/// ```swift
/// let text = "Visit https://www.example.com or http://test.com"
/// let urls = text.extractURLs() // ["https://www.example.com", "http://test.com"]
/// ```
///
/// - Parameter schemes: Array of URL schemes to match (default: ["http", "https"])
/// - Returns: An array of strings containing the extracted URLs
func extractURLs(schemes: [String] = ["http", "https"]) -> [String] {
let pattern = schemes
.map { #"\b\#($0)://[^\s<>\"]+[\w]"# }
.joined(separator: "|")

guard let regex = try? NSRegularExpression(pattern: pattern) else { return [] }

let range = NSRange(startIndex..., in: self)
return regex.matches(in: self, range: range)
.compactMap { match in
guard let range = Range(match.range, in: self) else { return nil }
return String(self[range])
}
}

/// Extracts all email addresses from the string.
///
/// Example:
/// ```swift
/// let text = "Contact us at info@example.com or support@test.com"
/// let emails = text.extractEmails() // ["info@example.com", "support@test.com"]
/// ```
///
/// - Returns: An array of strings containing the extracted email addresses
func extractEmails() -> [String] {
let pattern = #"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,64}"#
guard let regex = try? NSRegularExpression(pattern: pattern) else { return [] }

let range = NSRange(startIndex..., in: self)
return regex.matches(in: self, range: range)
.compactMap { match in
guard let range = Range(match.range, in: self) else { return nil }
return String(self[range])
}
}

/// Extracts all hashtags from the string.
///
/// Example:
/// ```swift
/// let text = "Check out #SwiftDev and #iOS15 features!"
/// let hashtags = text.extractHashtags() // ["#SwiftDev", "#iOS15"]
/// ```
///
/// - Parameter includeHash: Whether to include the # symbol (default: true)
/// - Returns: An array of strings containing the extracted hashtags
func extractHashtags(includeHash: Bool = true) -> [String] {
let pattern = #"#[a-zA-Z][a-zA-Z0-9_]*"#
guard let regex = try? NSRegularExpression(pattern: pattern) else { return [] }

let range = NSRange(startIndex..., in: self)
let matches = regex.matches(in: self, range: range)
.compactMap { (match: NSTextCheckingResult) -> String? in
guard let range = Range(match.range, in: self) else { return nil }
return String(self[range])
}
return matches.map { (str: String) -> String in includeHash ? str : String(str.dropFirst()) }
}

/// Extracts all mentions (@username) from the string.
///
/// Example:
/// ```swift
/// let text = "Thanks @john and @jane_doe!"
/// let mentions = text.extractMentions() // ["@john", "@jane_doe"]
/// ```
///
/// - Parameter includeAt: Whether to include the @ symbol (default: true)
/// - Returns: An array of strings containing the extracted mentions
func extractMentions(includeAt: Bool = true) -> [String] {
let pattern = #"@[a-zA-Z][a-zA-Z0-9_]*"#
guard let regex = try? NSRegularExpression(pattern: pattern) else { return [] }

let range = NSRange(startIndex..., in: self)
let matches = regex.matches(in: self, range: range)
.compactMap { (match: NSTextCheckingResult) -> String? in
guard let range = Range(match.range, in: self) else { return nil }
return String(self[range])
}
return matches.map { (str: String) -> String in includeAt ? str : String(str.dropFirst()) }
}

/// Extracts all dates from the string.
///
/// Example:
/// ```swift
/// let text = "Meeting on 2024-01-16 and party on 01/20/2024"
/// let dates = text.extractDates() // ["2024-01-16", "01/20/2024"]
/// ```
///
/// - Returns: An array of strings containing the extracted valid dates
func extractDates() -> [String] {
let patterns = [
#"\d{4}-\d{2}-\d{2}"#, // YYYY-MM-DD
#"\d{2}/\d{2}/\d{4}"#, // MM/DD/YYYY
#"\d{2}\.\d{2}\.\d{4}"#, // DD.MM.YYYY
#"[A-Za-z]+ \d{1,2}, \d{4}"#, // Month DD, YYYY
]

let combinedPattern = patterns.joined(separator: "|")
guard let regex = try? NSRegularExpression(pattern: combinedPattern) else { return [] }

let range = NSRange(startIndex..., in: self)
let dateFormatter = DateFormatter()
dateFormatter.timeZone = TimeZone(identifier: "UTC")

return regex.matches(in: self, range: range)
.compactMap { match in
guard let range = Range(match.range, in: self) else { return nil }
return String(self[range])
}
.filter { dateString in
// Try different date formats
let formats = [
"yyyy-MM-dd", // YYYY-MM-DD
"MM/dd/yyyy", // MM/DD/YYYY
"dd.MM.yyyy", // DD.MM.YYYY
"MMMM d, yyyy", // Month DD, YYYY
]

return formats.contains { format in
dateFormatter.dateFormat = format
return dateFormatter.date(from: dateString) != nil
}
}
}
}
Loading
Loading