Is there a high-level API for doing search-and-replace with regexes in Haskell? In particular, I'm looking at the Text.Regex.TDFA
or Text.Regex.Posix
packages. I'd really like something of type:
f :: Regex -> (ResultInfo -> m String) -> String -> m String
so, for example, to replace "dog" with "cat" you could write
runIdentity . f "dog" (return . const "cat") -- :: String -> String
or do more advanced things with the monad, like counting occurrences, etc.
Haskell documentation for this is pretty lacking. Some low-level API notes are here.
How about the Text.Regex.subRegex
in package regex-compat
?
Prelude> import Text.Regex (mkRegex, subRegex) Prelude> :t mkRegex mkRegex :: String -> Regex Prelude> :t subRegex subRegex :: Regex -> String -> String -> String Prelude> subRegex (mkRegex "foo") "foobar" "123" "123bar"
I don't know of any existing function that creates this functionality, but I think that I'd end up using something like the AllMatches [] (MatchOffset, MatchLength)
instance of RegexContent
to simulate it:
replaceAll :: RegexLike r String => r -> (String -> String) -> String -> String
replaceAll re f s = start end
where (_, end, start) = foldl' go (0, s, id) $ getAllMatches $ match re s
go (ind,read,write) (off,len) =
let (skip, start) = splitAt (off - ind) read
(matched, remaining) = splitAt len matched
in (off + len, remaining, write . (skip++) . (f matched ++))
replaceAllM :: (Monad m, RegexLike r String) => r -> (String -> m String) -> String -> m String
replaceAllM re f s = do
let go (ind,read,write) (off,len) = do
let (skip, start) = splitAt (off - ind) read
let (matched, remaining) = splitAt len matched
replacement <- f matched
return (off + len, remaining, write . (skip++) . (replacement++))
(_, end, start) <- foldM go (0, s, return) $ getAllMatches $ match re s
start end
Based on @rampion's answer, but with the typo fixed so it doesn't just <<loop>>
:
replaceAll :: Regex -> (String -> String) -> String -> String
replaceAll re f s = start end
where (_, end, start) = foldl' go (0, s, id) $ getAllMatches $ match re s
go (ind,read,write) (off,len) =
let (skip, start) = splitAt (off - ind) read
(matched, remaining) = splitAt len start
in (off + len, remaining, write . (skip++) . (f matched ++))
You can use replaceAll
from the Data.Text.ICU.Replace module.
Prelude> :set -XOverloadedStrings
Prelude> import Data.Text.ICU.Replace
Prelude Data.Text.ICU.Replace> replaceAll "cat" "dog" "Bailey is a cat, and Max is a cat too."
"Bailey is a dog, and Max is a dog too."
maybe this approach fit you.
import Data.Array (elems)
import Text.Regex.TDFA ((=~), MatchArray)
replaceAll :: String -> String -> String -> String
replaceAll regex new_str str =
let parts = concat $ map elems $ (str =~ regex :: [MatchArray])
in foldl (replace' new_str) str (reverse parts)
where
replace' :: [a] -> [a] -> (Int, Int) -> [a]
replace' new list (shift, l) =
let (pre, post) = splitAt shift list
in pre ++ new ++ (drop l post)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With