Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/markdown_it/rules_core/linkify.py: 13%
89 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:07 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:07 +0000
1import re
3from ..common.utils import arrayReplaceAt
4from ..token import Token
5from .state_core import StateCore
7LINK_OPEN_RE = re.compile(r"^<a[>\s]", flags=re.IGNORECASE)
8LINK_CLOSE_RE = re.compile(r"^</a\s*>", flags=re.IGNORECASE)
10HTTP_RE = re.compile(r"^http://")
11MAILTO_RE = re.compile(r"^mailto:")
12TEST_MAILTO_RE = re.compile(r"^mailto:", flags=re.IGNORECASE)
15def isLinkOpen(string: str) -> bool:
16 return bool(LINK_OPEN_RE.search(string))
19def isLinkClose(string: str) -> bool:
20 return bool(LINK_CLOSE_RE.search(string))
23def linkify(state: StateCore) -> None:
24 blockTokens = state.tokens
26 if not state.md.options.linkify:
27 return
29 if not state.md.linkify:
30 raise ModuleNotFoundError("Linkify enabled but not installed.")
32 for j in range(len(blockTokens)):
33 if blockTokens[j].type != "inline" or not state.md.linkify.pretest(
34 blockTokens[j].content
35 ):
36 continue
38 tokens = blockTokens[j].children
40 htmlLinkLevel = 0
42 # We scan from the end, to keep position when new tags added.
43 # Use reversed logic in links start/end match
44 assert tokens is not None
45 i = len(tokens)
46 while i >= 1:
47 i -= 1
48 assert isinstance(tokens, list)
49 currentToken = tokens[i]
51 # Skip content of markdown links
52 if currentToken.type == "link_close":
53 i -= 1
54 while (
55 tokens[i].level != currentToken.level
56 and tokens[i].type != "link_open"
57 ):
58 i -= 1
59 continue
61 # Skip content of html tag links
62 if currentToken.type == "html_inline":
63 if isLinkOpen(currentToken.content) and htmlLinkLevel > 0:
64 htmlLinkLevel -= 1
65 if isLinkClose(currentToken.content):
66 htmlLinkLevel += 1
67 if htmlLinkLevel > 0:
68 continue
70 if currentToken.type == "text" and state.md.linkify.test(
71 currentToken.content
72 ):
73 text = currentToken.content
74 links = state.md.linkify.match(text)
76 # Now split string to nodes
77 nodes = []
78 level = currentToken.level
79 lastPos = 0
81 for ln in range(len(links)):
82 url = links[ln].url
83 fullUrl = state.md.normalizeLink(url)
84 if not state.md.validateLink(fullUrl):
85 continue
87 urlText = links[ln].text
89 # Linkifier might send raw hostnames like "example.com", where url
90 # starts with domain name. So we prepend http:// in those cases,
91 # and remove it afterwards.
92 if not links[ln].schema:
93 urlText = HTTP_RE.sub(
94 "", state.md.normalizeLinkText("http://" + urlText)
95 )
96 elif links[ln].schema == "mailto:" and TEST_MAILTO_RE.search(
97 urlText
98 ):
99 urlText = MAILTO_RE.sub(
100 "", state.md.normalizeLinkText("mailto:" + urlText)
101 )
102 else:
103 urlText = state.md.normalizeLinkText(urlText)
105 pos = links[ln].index
107 if pos > lastPos:
108 token = Token("text", "", 0)
109 token.content = text[lastPos:pos]
110 token.level = level
111 nodes.append(token)
113 token = Token("link_open", "a", 1)
114 token.attrs = {"href": fullUrl}
115 token.level = level
116 level += 1
117 token.markup = "linkify"
118 token.info = "auto"
119 nodes.append(token)
121 token = Token("text", "", 0)
122 token.content = urlText
123 token.level = level
124 nodes.append(token)
126 token = Token("link_close", "a", -1)
127 level -= 1
128 token.level = level
129 token.markup = "linkify"
130 token.info = "auto"
131 nodes.append(token)
133 lastPos = links[ln].last_index
135 if lastPos < len(text):
136 token = Token("text", "", 0)
137 token.content = text[lastPos:]
138 token.level = level
139 nodes.append(token)
141 blockTokens[j].children = tokens = arrayReplaceAt(tokens, i, nodes)