Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ELM/Haskell : use Regular expression(Regex) to search a String and render Html view

what I am trying to do is to parse string using regular expression and get Html element as output so the function signature should be as following:

parse : String -> Html Msg

before we dive in the code let us take an example how the code should behave in order to have a clear idea, given a the below string as below :

input : hello (!BOLD!) I Am a bold text (!BOLD!)bla bla la

expected output : div [ ][ text "hello", b [ ][ text "I Am a bold text" ] , text "bla la la"] ]

in order to achieve this goal, I have used the regex library provided in ELM package

replace : HowMany -> Regex -> (Match -> String) -> String -> String

on top of the above function, i created the 2 function listed here after :

myReplace   str expression =
  replace (All)  ( regex expression )   matchToString  str
 --myreplace "hello (!BOLD!) bold (!BOLD!) " "(!BOLD!)"  == "hello b[][] hello"  

with the below helper function, this function take a Match and specify the start and the end for the regular expression

matchToString : Match -> String
matchToString match =
  case  match.number `rem` 2  of
    0 ->"]" -- mtaches the close bracket
    _ -> "B [][" --mtaches the open bracket

but what I want to get is : div [][text "hello", b[][text "bold"]]

how could I improve my code and write the full parser? or how could I achieve the same purpose in haskell?

reference : elm regex source

like image 208
khaled omar Avatar asked Feb 02 '26 23:02

khaled omar


1 Answers

Regular Expressions start to lose their power and become overly complex in cases like this. Instead, I recommend looking into Parser Combinators which are much more powerful while being easier to maintain and reason about.

For this example I'll be using the Bogdanp/elm-combine package.

Here we'll build up a parser that takes a string and assumes it's unstyled until it hits (!BOLD!) and will remain bold until it finds another (!BOLD!) entry. The output will be a list of tuples of characters and whether they are Unstyled or Bold. caveat: there are probably more concise combinators to achieve this but I'm relatively new to the art

import Html exposing (..)
import Html.Attributes exposing (..)
import Combine exposing (..)
import Combine.Char exposing (..)
import Combine.Infix exposing (..)
import String
import List.Extra exposing (groupWhile)

type Style
  = Unstyled
  | Bold

styleParser : Bool -> Parser (List (Char, Style))
styleParser bolded =
  let
    style = if bolded then Bold else Unstyled
  in
    (end `andThen` always (succeed []))
      <|> (string "(!BOLD!)" `andThen` \_ -> styleParser (not bolded))
      <|> (anyChar
           `andThen` \c -> styleParser bolded
           `andThen` \cs -> (succeed ((c, style) :: cs)))

The result of that parser for the example "a(!BOLD!)b(!BOLD!)c" would contain the list [('a', Unstyled), ('b', Bold), ('c', Unstyled)], so we need to do some mapping and folding in order to turn that into a list of Html msg values:

htmlParser : Parser (List (Html msg))
htmlParser =
  styleParser False
    `andThen` (succeed << foldStyledHtml)

foldStyledHtml : List (Char, Style) -> List (Html msg)
foldStyledHtml chars =
  let
    foldSingleStyledHtml =
      List.foldr (\(c, s) (cs, _) -> (c :: cs, s)) ([], Unstyled)
        >> \(chars, style) ->
          let str = String.fromList chars
          in case style of
              Unstyled -> text str
              Bold -> b [] [ text str ]
  in
    groupWhile (\a b -> snd a == snd b) chars
      |> List.map foldSingleStyledHtml

You can then output some example text using the following:

main =
  case parse htmlParser testInput of
    (Ok htmls, _) -> div [] htmls
    (Err err, _) -> div [ style [("color", "red")] ] [ text <| toString <| err]

I've posted the full source of this in a gist. You'll also need the package elm-community/list-extra. Hope this helps!

like image 97
Chad Gilbert Avatar answered Feb 04 '26 16:02

Chad Gilbert



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!