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
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!
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