最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

python - How do I create clickable hyperlinks from markdown link format in a Tkinter Text widget? - Stack Overflow

programmeradmin2浏览0评论

I'm working on a Tkinter application where I display text in a Text widget. The incoming text sometimes contains markdown-style links in the following format:

[CNN Travel](.html)

Here, the source name ("CNN Travel") is immediately followed by a URL in brackets. I want to make the source name clickable so that clicking it opens the corresponding link in the default web browser.

I already have code in place that processes text for bold formatting using ** markers. Below is a simplified version of my current code that processes the text stream and formats bold text:

stream = some_function()  

full_response = ""
buffer = ""
bold = False
for chunk in stream:
    if chunk != "None":
        full_response += chunk
        buffer += chunk
        if "**" in buffer:
            while "**" in buffer:
                print("exist bold")
                pre, _, post = buffer.partition("**")
                append_to_chat_log(message=pre, bold=bold)
                bold = not bold
                buffer = post
        else:     
            append_to_chat_log(message=buffer, bold=bold)
            buffer = ""

def insert_with_formatting(message):
    chat_log.tag_configure("bold", font=('Helvetica', font_size, 'bold'))
    bold_pattern = repile(r'\*\*(.*?)\*\*')
    cursor = 0

    for match in bold_pattern.finditer(message):
        start, end = match.span()
        # Insert text before bold part
        chat_log.insert("end", message[cursor:start])
        # Insert bold text
        chat_log.insert("end", match.group(1), "bold")
        cursor = end
    
    # Insert any remaining text after the last bold part
    chat_log.insert("end", message[cursor:])

def append_to_chat_log(sender=None, message=None, bold=False):
    chat_log.config(state=tk.NORMAL)
    if sender:
        chat_log.insert("end", f"{sender}\n\n", "sender")
    if message and bold:
        print("append bold")
        chat_log.tag_configure("bold", font=('Helvetica', font_size, 'bold'))
        chat_log.insert("end", message, "bold")
    
    if not bold and message:
        if "**" in message:
            print("if message and not bold")
            insert_with_formatting(message)
        else:
            chat_log.insert("end", message)
    
    chat_log.tag_config("sender", font=('Helvetica', font_size, 'bold'), foreground=TEXT_COLOR)
    chat_log.config(state=tk.DISABLED)
    chat_log.see("end")
    chat_log.update()

My Question: How can I modify this (or add additional code) to detect markdown-style links (e.g., [CNN Travel](.html)), and make the source name clickable? When clicked, it should open the associated URL in the browser.

I've looked into ways to create hyperlinks in Tkinter's Text widget but haven't been able to integrate it with my current text formatting logic. Any help on how to implement clickable hyperlinks in this context would be greatly appreciated.

I'm working on a Tkinter application where I display text in a Text widget. The incoming text sometimes contains markdown-style links in the following format:

[CNN Travel](https://www.cnn/travel/best-destinations-to-visit-2025/index.html)

Here, the source name ("CNN Travel") is immediately followed by a URL in brackets. I want to make the source name clickable so that clicking it opens the corresponding link in the default web browser.

I already have code in place that processes text for bold formatting using ** markers. Below is a simplified version of my current code that processes the text stream and formats bold text:

stream = some_function()  

full_response = ""
buffer = ""
bold = False
for chunk in stream:
    if chunk != "None":
        full_response += chunk
        buffer += chunk
        if "**" in buffer:
            while "**" in buffer:
                print("exist bold")
                pre, _, post = buffer.partition("**")
                append_to_chat_log(message=pre, bold=bold)
                bold = not bold
                buffer = post
        else:     
            append_to_chat_log(message=buffer, bold=bold)
            buffer = ""

def insert_with_formatting(message):
    chat_log.tag_configure("bold", font=('Helvetica', font_size, 'bold'))
    bold_pattern = repile(r'\*\*(.*?)\*\*')
    cursor = 0

    for match in bold_pattern.finditer(message):
        start, end = match.span()
        # Insert text before bold part
        chat_log.insert("end", message[cursor:start])
        # Insert bold text
        chat_log.insert("end", match.group(1), "bold")
        cursor = end
    
    # Insert any remaining text after the last bold part
    chat_log.insert("end", message[cursor:])

def append_to_chat_log(sender=None, message=None, bold=False):
    chat_log.config(state=tk.NORMAL)
    if sender:
        chat_log.insert("end", f"{sender}\n\n", "sender")
    if message and bold:
        print("append bold")
        chat_log.tag_configure("bold", font=('Helvetica', font_size, 'bold'))
        chat_log.insert("end", message, "bold")
    
    if not bold and message:
        if "**" in message:
            print("if message and not bold")
            insert_with_formatting(message)
        else:
            chat_log.insert("end", message)
    
    chat_log.tag_config("sender", font=('Helvetica', font_size, 'bold'), foreground=TEXT_COLOR)
    chat_log.config(state=tk.DISABLED)
    chat_log.see("end")
    chat_log.update()

My Question: How can I modify this (or add additional code) to detect markdown-style links (e.g., [CNN Travel](https://www.cnn/travel/best-destinations-to-visit-2025/index.html)), and make the source name clickable? When clicked, it should open the associated URL in the browser.

I've looked into ways to create hyperlinks in Tkinter's Text widget but haven't been able to integrate it with my current text formatting logic. Any help on how to implement clickable hyperlinks in this context would be greatly appreciated.

Share Improve this question edited Mar 31 at 5:18 user29255210 asked Mar 31 at 4:35 user29255210user29255210 133 bronze badges 3
  • Stackoverflow is not a code writing service but the volounteers here may be willing to help you to fix your code. What have you tried already and how well did it work? What did it not do. – AdrianHHH Commented Mar 31 at 8:19
  • it should be simple to detect link with regex because it uses [...](...). And you could keep it in dictionary as {name: url}, To make it clickable you would have to assign tag to CNN Travel and use it to get url from dictionary. – furas Commented Mar 31 at 9:18
  • The best solution might be to learn how to use regular expressions. With regular expressions you can easily detect and parse a hyperlink in the text. Then, search on this site to look for other questions about creating hyperlinks in a text widget. – Bryan Oakley Commented Mar 31 at 12:48
Add a comment  | 

1 Answer 1

Reset to default 0

I love regular expressions and they are very useful, for sure. But in quite a lot of situations, I think that using a proper parser is a better choice. You'll probably have new input cases and finally have problems with regular expressions.

I'll explain why with a solution using a regular expression.

Cases of markdown links

  • The usual one: [Visit example](https://www.example)
  • A link with a title: [⬆️top](#top "Go back to the top of the page")
  • Not really valid, but you could accept it: [](https://www.google)

The regular expression (first try)

To make it more readable, use the x modifier so that you can use spaces and comments and write the regular expression on several lines:

regex = r"""
    \[                   # Opening bracket.
    (?P<text>[^\]]*)     # Capture the link text.
    \]\(                 # Closing bracket and opening parenthesis.
    (?P<url>[^"\s)]+)    # Capture the URL.
    (?:                  # Optional group for the title attribute.
      \s+                # Spaces between the URL and the title.
      "(?P<title>[^"]*)" # Capture the optional title value.
    )?
    \)                   # Closing parenthesis.
    """

I used named capturing groups with (?P<group_name>) instead of indexed capturing groups that one usually create with ().

I made the text group accept an empty value. You could then add some logic in your code to use the url group instead.

Test it here: https://regex101/r/HbwBhy/1

Minor problem: don't match images

  • In Markdown, an image is very similar, as it has an exclamation point before the brackets, like this:

    • ![alt value](/path/to/image.png)
    • ![Butterfly](/path/to/butterfly.png "A butterfly on a flower")

    This can be solved by adding a negative lookbehind just before the opening bracket, to not match the "!" char: (?<!!)

    regex = r"""
      (?<!!)               # Negative lookbehind to avoid matching images.
      \[                   # Opening bracket.
      (?P<text>[^\]]*)     # Capture the link text.
      \]\(                 # Closing bracket and opening parenthesis.
      (?P<url>[^"\s)]+)    # Capture the URL.
      (?:                  # Optional group for the title attribute.
        \s+                # Spaces between the URL and the title.
        "(?P<title>[^"]*)" # Capture the optional title value.
      )?
      \)                   # Closing parenthesis.
      """
    

    See it working here: https://regex101/r/HbwBhy/2

More complicated problems

  • As you can see in my regex101 example above, you may have a link inside a block of code:

    
    [This link is ok to match](#ok)
    
    But not in this block of RAW text:
    
        Here, **nothing** should be matched, not even the bold or the
        following [link](https://www.example/should-not-be-matched)
    
    

    This will also be the case for fenced code blocks

  • The same problem occurs for inline code (between backticks):

    Image syntax is `[link text](https://www.example/path/to/page)`
    

    Just fet solving it with negative lookbehinds and lookaheads. This is why a parser is the best choice.

Regex or Markdown parser?

If you think that your "Markdown-like" input will never have blocks of code, ok, well, try using the regular expression.

You can generate the Python code directly from regex101.

But if you want to be sure, use a proper Markdown parser and you could traverse its AST, if it has it available.

I'm not a Python developer, but maybe this library could help you:

https://github/miyuchina/mistletoe

发布评论

评论列表(0)

  1. 暂无评论