{- | extensible and reusable replacement functions Run replacement with your preferred content types e.g. "Data.Text" (implemented), from search results with non-PCRE regex or non-regex libs === how to use: value replacement: >>> replace (Just [(4,3)::PosLen]) "4567" ("abc 123 def"::Text) "abc 4567 def" 'GroupReplacer' : replace with a function @ replacer::GroupReplacer Text replacer = defaultReplacer 1 tweak1 -- 1: group 1 match. where tweak1 str1 = case str1 of "123" -> "[1-2-3]" otherwise -> traceShow str1 "?" @ >>> replace (Just ([(4,3)]::[PosLen])) replacer ("abc 123 def"::Text) "abc [1-2-3] def" -} module Text.Regex.Do.Replace.Open (Replace(..), defaultReplacer, getGroup, replaceMatch, boundsOk) where import Text.Regex.Base.RegexLike as R import Data.Array as A import Prelude as P import Text.Regex.Do.Type.Do import Text.Regex.Do.Match.Result as R import Text.Regex.Do.Type.Convert import Text.Regex.Do.Type.Extract class Replace f repl body where replace::(Extract' body, ToArray arr) => f arr -> repl -> body -> body instance Replace Maybe b b where replace Nothing repl0 body0 = body0 replace (Just ma0) repl0 body0 = firstGroup lpl1 (repl0, body0) where lpl1 = A.elems $ toArray ma0 instance Replace [] b b where replace [] _ body0 = body0 replace ma0 repl0 body0 = let lpl1 = R.poslen $ toArray <$> ma0::[[PosLen]] foldFn1 lpl1 acc1 = firstGroup lpl1 (repl0,acc1) in P.foldr foldFn1 body0 lpl1 instance Replace Maybe (GroupReplacer b) b where replace Nothing _ body0 = body0 replace (Just ma0) (GroupReplacer repl0) body0 = let a1 = ReplaceAcc { acc = body0, pos_adj = 0 } in acc $ repl0 (toArray ma0) a1 instance Replace [] (GroupReplacer b) b where replace [] _ body0 = body0 replace ma0 (GroupReplacer repl0) body0 = let acc1 = ReplaceAcc { acc = body0, pos_adj = 0 } in acc $ P.foldl (flip repl0) acc1 $ toArray <$> ma0 firstGroup::Extract' a => [PosLen] -> (a,a) -> a firstGroup (pl0:_) r1@(new0,a0) = acc $ replaceMatch pl0 (new0, acc1) where acc1 = ReplaceAcc { acc = a0, pos_adj = 0 } -- dynamic {- | Replaces specified (by idx) group match with value provided by (a -> a) fn. Works for one common simple use case 'GroupReplacer' can also be used with multi-group regex another custom dynamic replacer could e.g. inspect all group matches before looking up a replacement. -} defaultReplacer::Extract' a => Int -- ^ group idx. 0: full match, groups: 1.. see 'MatchArray' -> (a -> a) -- ^ (group match -> replacement) lookup -> GroupReplacer a defaultReplacer idx0 tweak0 = GroupReplacer fn1 where fn1 (ma0::MatchArray) acc0 = if boundsOk ma0 idx0 then maybe acc0 fn1 mval1 else acc0 where pl1 = ma0 A.! idx0 :: (R.MatchOffset, R.MatchLength) mval1 = getGroup acc0 ma0 idx0 fn1 str1 = replaceMatch pl1 (str2, acc0) where str2 = tweak0 str1 {- | check if specified group index is within 'MatchArray' bounds for use within 'GroupReplacer' -} boundsOk::MatchArray -> Int -> Bool boundsOk ma0 = inRange (bounds ma0) {- | get group content safely: * non-existing group idx will not error but return 'Nothing' * adjust for previous replacements length see 'defaultReplacer' source for use example -} getGroup::R.Extract a => ReplaceAcc a -> MatchArray -> Int -> Maybe a getGroup acc0 ma0 idx0 = if not (boundsOk ma0 idx0) then Nothing -- safety catch else Just val1 where pl1 = ma0 A.! idx0 :: (R.MatchOffset, R.MatchLength) pl2 = adjustPoslen pl1 acc0 val1 = extract pl2 $ acc acc0 {- | replace group match while adjusting for previous replacements length see 'defaultReplacer' source for use example -} replaceMatch::Extract' a => PosLen -- ^ replaceable, unadjusted -> (a, ReplaceAcc a) -- ^ (new val, acc passed to 'GroupReplacer') -> ReplaceAcc a -- ^ new acc replaceMatch pl0@(_,l0) (new0, acc0) = ReplaceAcc { acc = acc1, pos_adj = pos_adj acc0 + l1 - l0 } where pl1 = adjustPoslen pl0 acc0 prefix1 = prefix pl1 $ acc acc0 suffix1 = suffix pl1 $ acc acc0 acc1 = concat' [prefix1, new0, suffix1] l1 = len' new0 adjustPoslen::PosLen -> ReplaceAcc a -> PosLen adjustPoslen (p0,l0) acc0 = (p0 + pos_adj acc0, l0)