{"id":18031729,"url":"https://github.com/dagronf/dsfregex","last_synced_at":"2025-03-27T05:30:58.494Z","repository":{"id":63907300,"uuid":"250735177","full_name":"dagronf/DSFRegex","owner":"dagronf","description":"A Swift regex class abstracting away the complexities of NSRegularExpression and Swift Strings","archived":false,"fork":false,"pushed_at":"2024-04-06T20:41:21.000Z","size":113,"stargazers_count":7,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-23T04:31:39.246Z","etag":null,"topics":["foundation","nsregularexpression","regex","regular-expression","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dagronf.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-03-28T07:09:07.000Z","updated_at":"2024-04-03T00:43:53.000Z","dependencies_parsed_at":"2022-11-28T22:54:06.969Z","dependency_job_id":null,"html_url":"https://github.com/dagronf/DSFRegex","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dagronf%2FDSFRegex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dagronf%2FDSFRegex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dagronf%2FDSFRegex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dagronf%2FDSFRegex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dagronf","download_url":"https://codeload.github.com/dagronf/DSFRegex/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245791341,"owners_count":20672665,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["foundation","nsregularexpression","regex","regular-expression","swift"],"created_at":"2024-10-30T10:10:35.992Z","updated_at":"2025-03-27T05:30:57.409Z","avatar_url":"https://github.com/dagronf.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DSFRegex\n\nA Swift regex class abstracting away some of the complexities of `NSRegularExpression`\n\n![tag](https://img.shields.io/github/v/tag/dagronf/DSFRegex)\n![swift versions](https://img.shields.io/badge/Swift-5.3+-orange.svg)\n![Platform support](https://img.shields.io/badge/platform-ios%20%7C%20osx%20%7C%20tvos%20%7C%20watchos%20%7C%20macCatalyst%20%7C%20linux-lightgrey.svg?style=flat-square)\n[![License MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](https://github.com/dagronf/DSFRegex/blob/master/LICENSE) \n![Build](https://img.shields.io/github/actions/workflow/status/dagronf/DSFRegex/swift.yml)\n\n## Why?\n\nEvery time I have to use `NSRegularExpression` in Swift I make the same mistakes over and over regarding ranges and range conversions between `NSRange` and `Range\u003cString.Index\u003e`.\n\nAlso, pulling content out using capture groups is tedious and a little error-prone. I wanted to abstract away from of the things that I kept stuffing up.\n\n## TL;DR - Show me something!\n\n```swift\nlet inputText: String = \u003csome text to match against\u003e\n\n// Build the regex to match against (in this case, \u003cnumber\u003e\\t\u003cstring\u003e)\n// This regex has two capture groups, one for the number and one for the string.\nlet regex = try DSFRegex(#\"(\\d*)\\t\\\"([^\\\"]+)\\\"\"#)\n\n// Retrieve ALL the matches for the supplied text\nlet searchResult = regex.matches(for: inputText)\n\n// Loop over each of the matches found, and print them out\nsearchResult.forEach { match in \n   let foundStr = inputText[match.range]          // The text of the entire match\n   let numberVal = inputText[match.captures[0]]   // Retrieve the first capture group text.\n   let stringVal = inputText[match.captures[1]]   // Retrieve the second capture group text.\n\n   Swift.print(\"Number is \\(numberVal), String is \\(stringVal)\")\n}\n```\n\nThe basic structure of a 'matches' result is as follows\n\n```\nMatches\n  \u003e matches: An array of regex matches\n    \u003e range: A match range. This range specifies the match range within the original text being searched\n    \u003e captures: An array of capture groups\n       \u003e A capture range. This range represents the range of a capture within the original text being searched\n```\n\n## Usage\n\nAll ranges provided back to the caller (and conversely, when passing ranges to the regex object) are in the range of the Swift `String` passed in for the match. \n\nThis is important, as `NSRegularExpression` uses `NSString` and the code points and character range information are different between `NSString` and `String`, especially when dealing with characters in the high Unicode ranges such as emoji 🇦🇲 👨‍👩‍👦.\n\n### Creation\n\nYou create a regex matching object using the constructor and a regex pattern. If the regex is badly formatted or cannot be compiled, this constructor will throw.\n\n```swift\n// Match against dummy phone numbers XXXX-YYY-ZZZ\nlet phoneNumberRegex = try DSFRegex(#\"(\\d{4})-(\\d{3})-(\\d{3})\"#)\n```\n\n### Matching \n\nTo check whether a string matches against the regex, use the `hasMatch` method.\n\n```swift\nlet hasAMatch = phoneNumberRegex.hasMatch(\"0499-999-999\")   // true\nlet noMatch = phoneNumberRegex.hasMatch(\"0499 999 999\")     // false\n```\n\nIf you want to extract all the match information, use the `matches` method.\n\n```swift\nlet result = phoneNumberRegex.matches(for: \"0499-999-999 0491-111-444 4324-222-123\")\nresult.forEach { match in \n   let matchText = result.text(for: match.element)\n   Swift.print(\"Match `\\(matchText)`\")\n   for capture in match.captures {\n      let captureText = result.text(for: capture)\n      Swift.print(\" - `\\(captureText)`\")\n   }\n}\n```\n\n### Enumeration\n\nIf you have a large input text or a complex regex that will take a while to process or you have constrained memory conditions you can choose to enumerate the match results rather than process everything up front.\n\nThe enumeration method allows you to stop processing at any time or any point in the process (eg. if you have limited time constraints, or are looking for a specific match within a text).\n\n```swift\n/// Find all email addresses within a text\nlet inputString = \"… some input string …\"\nlet emailRegex = try DSFRegex(\"… some regex …\")\nemailRegex.enumerateMatches(in: inputString) { (match) -\u003e Bool in\n\n   // Extract match information\n   let matchRange = match.range\n   let matchText = inputString[match.range]\n   Swift.print(\"Found '\\(matchText)' at range \\(matchRange)\")\n   \n   // Continue processing\n   return true\n}\n```\n\n### String search cursor\n\nA string search cursor is useful when you are searching sporadically within a string, say in response to a user clicking on the 'next' button.  The cursor keeps track of the current match, and is used when locating the next match in the string.\n\n```swift\nvar searchCursor: DSFRegex.Cursor?\nvar content: String\n\n@IBAction func startSearch(_ sender: Any) {\n   let regex = DSFRegex(... some pattern ...)\n   \n   // Find the first match in the string\n   self.searchCursor = self.content.firstMatch(for: regex)\n   \n   self.displayForCurrentSearch()\n}\n\n@IBAction func nextSearchResult(_ sender: Any) {\n   if let previous = self.searchCursor {\n   \t   // Find the next match in the string from the \n      self.searchCursor = self.content.nextMatch(for: previous)\n   }\n   self.displayForCurrentSearch()\n}\n\ninternal func displayForCurrentSearch() {\n   // Update the UI reflecting the search result found in self.searchCursor\n   ...\n}\n```\n\n### Matching string replacement\n\nReturns a new string containing matching regular expressions replaced with a template string.\n\n```swift\n// Redact email addresses within the text\nlet emailRegex = try DSFRegex(\"… some regex …\")\nlet redacted = emailRegex.stringByReplacingMatches(\n    in: inputString,\n    withTemplate: NSRegularExpression.escapedTemplate(for: \"\u003cREDACTED-EMAIL-ADDRESS\u003e\")\n)\n```\n\n## Classes\n\n### DSFRegex\n\nThe primary class used to perform a regex match.\n\n#### DSFRegex.Matches\n\nAn object that contains all of the results of the regex matched against a text. It also provides a number of methods to help extract text from a match and/or capture object.\n\n#### DSFRegex.Match\n\nA single match object. Stores the range of the match within the original string.  If capture groups were defined within the regex also contains an array of the capture group objects.\n\n#### DSFRegex.Capture\n\nA capture represents a single range matching a capture within a regex result.  Each `match` may contain 0 or more captures depending on the captures available in the regex\n\n#### DSFRegex.Cursor\n\nAn incremental cursor object used when searching via the `String` extension.\n\n## Integration\n\n### Cocoapods\n\n`pod 'DSFRegex', :git =\u003e 'https://github.com/dagronf/DSFRegex/'`\n\n### Swift package manager\n\nAdd `https://github.com/dagronf/DSFRegex` to your project.\n\n### Direct\n\nCopy the files in the `Sources/DSFRegex` into your project\n\n## Examples\n\nFor more examples and usage, you can find a series of tests in the `Tests` folder.\n\n### Phone number matching\n\n```swift\nlet phoneNumberRegex = try DSFRegex(#\"(\\d{4})-(\\d{3})-(\\d{3})\"#)\nlet results = phoneNumberRegex.matches(for: \"4499-999-999 3491-111-444 4324-222-123\")\n\n// results.numberOfMatches == 3\n// results.text(match: 0) == \"4499-999-999\"\n// results.text(match: 1) == \"3491-111-444\"\n// results.text(match: 2) == \"4324-222-123\"\n\n// Just retrieve the text for each of the matches\nlet textMatches = results.textMatching()  // == [\"4499-999-999\", \"3491-111-444, \"4324-222-123\"]\n\n```\n\nIf you're only interested in the first match, use\n\n```swift\nlet first = phoneNumberRegex.firstMatch(in: \"4499-999-999 3491-111-444 4324-222-123\")\n```\n\n### Data extraction\n\n```swift\nlet allMatches = phoneNumberRegex.matches(for: \"0499-999-999 0491-111-444 4324-222-123\")\nfor match in allMatches.matches.enumerated() {\n   let matchText = allMatches.text(for: match.element)\n   Swift.print(\"Match (\\(match.offset)) -\u003e `\\(matchText)`\")\n   for capture in match.element.capture.enumerated() {\n      let captureText = allMatches.text(for: capture.element)\n      Swift.print(\"  Capture (\\(capture.offset)) -\u003e `\\(captureText)`\")\n   }\n}\n```\n\nThe output :-\n\n```\nMatch (0) -\u003e `0499-999-888`\n  Capture (0) -\u003e `0499`\n  Capture (1) -\u003e `999`\n  Capture (2) -\u003e `888`\nMatch (1) -\u003e `0491-111-444`\n  Capture (0) -\u003e `0491`\n  Capture (1) -\u003e `111`\n  Capture (2) -\u003e `444`\nMatch (2) -\u003e `4324-222-123`\n  Capture (0) -\u003e `4324`\n  Capture (1) -\u003e `222`\n  Capture (2) -\u003e `123`\n```\n\n### Print just the first two email addresses in a text\n\n```swift\n/// Find all email addresses within a text\nlet emailRegex = try DSFRegex(\"… some regex …\")\nlet inputString = \"This is a test.\\n noodles@compuserve4.nginix.com and sillytest32@gmail.com, grubby@supernoodle.org lives here\"\n\nvar count = 0\nemailRegex.enumerateMatches(in: inputString) { (match) -\u003e Bool in\n   \n   count += 1\n\n   // Extract match information\n   let matchRange = match.range\n   let nsRange = NSRange(matchRange, in: inputString)\n   let matchText = inputString[match.range]\n   Swift.print(\"\\(count) - Found '\\(matchText)' at range \\(nsRange)\")\n\n   // Stop processing if we've found more than two\n   return count \u003c 2\n}\n```\n\nOutput :-\n\n```\n1 - Found 'noodles@compuserve4.nginix.com' at range {17, 30}\n2 - Found 'sillytest32@gmail.com' at range {52, 21}\n```\n\n# License\n\n```\nMIT License\n\nCopyright (c) 2024 Darren Ford\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdagronf%2Fdsfregex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdagronf%2Fdsfregex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdagronf%2Fdsfregex/lists"}