Burgers & Bytes
June 19, 2024

From Markdown syntax hyperlink to HTML link

Jun 19, 2024  •  4   • 785 
Table of contents

Scenario

This blogs scenario is regarding a plain multi line text input in which no rich text was allowed. Requirement was just one exception to have the ability to have clickable hyperlinks.

In the Dataverse table no html is stored only plain text, so using a markdown syntax notation was the solution.

Markdown syntax for a hyperlink is square brackets followed by parentheses. The square brackets hold the text, the parentheses hold the link.

`[Link text Here](https://link-url-here.org)`

One link as input

In the textinput you can add your text and add a link using Markdown syntax. In case there is just one link, the Match is quite straightforward to make.

One Link

MatchAll and regular expression

Use MatchAll and a regular expression \[(.*?)\]\((.*?)\). This regular expression is used to find patterns of Markdown links within the string.

Input

This example, the input string is:

On this website you can learn new things [Microsoft](learn.microsoft.com).

The MatchAll returns a table where each row corresponds to a match, and each match has columns: FullMatch for the link, one for StartMatch as the text position and one Submatches as table.

MatchAll The submatches look like this, in which the link text and actually link are in two separate rows: Submatches

Output

Output string is:

"On this website you can learn new things <a href=\"https://learn.microsoft.com\">Microsoft</a>."

This output can be used in a HTML element to have the clickable link to an external of internal location.

PowerFx

Complete code which is behind the OnSelect property of the button:

ClearCollect(
    colMatchOneLink,
    MatchAll(
        txtInputOneLink.Value,
        "\[(.*?)\]\((.*?)\)"
    )
);
Set(
    varOutPutHTMLOneLink,
    Substitute(
        txtInputOneLink.Value,
        First(colMatchOneLink).FullMatch,
        Concatenate(
            "<a href=""" & If(
                StartsWith(
                    Last(First(colMatchOneLink).SubMatches).Value,
                    "https://"
                ),
                Last(First(colMatchOneLink).SubMatches).Value,
                "https://" & Last(First(colMatchOneLink).SubMatches).Value
            ) & """>" & First(First(colMatchOneLink).SubMatches).Value & "</a>"
        )
    )
);  

Read some more detailed explanation about the reason why ‘https://’ is added.

Multiple links as input

The one link input approach asked for more… Multiple links in the input string. Therefor the MatchAll wasn’t the challenge. The MatchAll just results in multiple rows. The challenge regarding replacing the multiple links into one output string. Due to the fact the ForAll in PowerFx can’t update a variable inside the loop, we need to come up with a different approach.

Mulitple links

As you can see in the animation while modifying the text, it is still in Markdown syntax for the enduser. Both the plain text and the html formatted text is stored into the collection.

PowerFx

On the OnSelect of the Save button, the following PowerFx is executed:

UpdateContext({varTextInput: txtInputPrompt.Value});

// Extract all matches and their positions
ClearCollect(
    colTextMatch,
    MatchAll(varTextInput,"\[(.*?)\]\((.*?)\)")
);

// Convert matches to HTML links and calculate their lengths
ClearCollect(
    colExtraColumns,
    AddColumns(colTextMatch,
        Link,
        "<a href=""" & If(
            StartsWith(Last(SubMatches).Value,"https://"),
            Last(SubMatches).Value,
            "https://" & Last(SubMatches).Value
        ) & """>" & First(SubMatches).Value & "</a>",

        wordLength,
        Len(ThisRecord.FullMatch)
    )
);

// Initialize the result collection
Clear(colResults);
// Iterate through the matches and build the result collection
ForAll(
    colExtraColumns As Result,
    With(
        {
            matchStart: Result.StartMatch,
            matchLength: Result.wordLength,
            previousEnd: If(CountRows(colResults) = 0, 1, Last(colResults).end + 1)
        },
        Collect(
            colResults,
            {
                type: "text",
                content: Mid(varTextInput,previousEnd,matchStart - previousEnd),
                start: previousEnd,
                end: matchStart - 1
            },
            {
                type: "link",
                content: Result.Link,
                start: matchStart,
                end: matchStart + matchLength - 1
            }
        )
    )
);

// Append any remaining text after the last match
If(
    Last(colResults).end < Len(varTextInput),
    Collect(
        colResults,
        {
            type: "text",
            content: Mid(varTextInput,Last(colResults).end + 1,Len(varTextInput) - Last(colResults).end),
            start: Last(colResults).end + 1,
            end: Len(varTextInput)
        }
    )
);
// Concatenate all values from the 'content' column into one string
UpdateContext({varOutputHTML: Concat(colResults,content)});

Clear(colResults);
Clear(colTextMatch);
Clear(colExtraColumns);

If(varCommentModify,   
    //Update current response
     Patch(colAnswers, galResponses.Selected,
          { 
        Name: User().FullName,
        Date: Now(),
        Answer: varTextInput,
        Response: varOutputHTML
    }),
    //Collect new response 
    Collect(colAnswers,
        { 
            Name: User().FullName,
            Date: Now(),
            Answer: varTextInput,
            Response: varOutputHTML
        }
    )
);

Set(varCommentModify, false);

Reset(txtInputPrompt);

The formula matches the links in the text input and creates a collection in which the Markdown syntax links and converted HTML links are stored:

Collection Columns

Setup results

To setup the colResults it iterates through each match to construct a result collection that separates text and HTML links. It uses text positions to split the input text into multiple rows.

colResults

Concatenate into one string

The last step concatenate all values from the ‘content’ column into one string UpdateContext({varOutputHTML: Concat(colResults,content)});

To have the ability to have a thread of messages the results are stored into a collection. In this collection both plain input text as the HTML output is stored. The HTML output is used to display the text and the plain text is used when user wants to modify their answer.

Hyperlink error

The HTML link may produce an error such as:

This authoring.eu-il108.gateway.prod.island.powerapps.com page can’t be found

This error happens when the link doesn’t start with ‘https://’. To avoid this issue, ensure the link starts with ‘https://’. If it doesn’t, add ‘https://’ to the link.

comments powered by Disqus
Empowering productivity - one blog at a time!