Repository navigation
perf: speed up _unescape_special_chars for large documents #748
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
fb5899e
4bf3ddb
ed6330a
58cd6cb
be9bb4e
a13e621
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2377,18 +2377,29 @@ def _encode_email_address(self, addr: str) -> str: | |
|
|
||
| def _unescape_special_chars(self, text: str) -> str: | ||
| # Swap back in all the special characters we've hidden. | ||
| hashmap = tuple(self._escape_table.items()) + tuple(self._code_table.items()) | ||
| # html_blocks table is in format {hash: item} compared to usual {item: hash} | ||
| hashmap += tuple(tuple(reversed(i)) for i in self.html_blocks.items()) | ||
| replacements = {} | ||
| for ch, hash in hashmap: | ||
| if 'md5-' not in text: | ||
| return text | ||
| # Build hash→original mapping directly without intermediate tuple. | ||
| # html_blocks is stored inverted ({hash: item}) compared to the others. | ||
| replacements: dict[str, str] = {} | ||
| for ch, hash in self._escape_table.items(): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would it be perhaps faster and simpler if instead of building a val = (
self._escape_table.get(match[0])
or self._code_table.get(match[0])
or self.html_blocks.get(match[0])
)Maybe more memory efficient?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That works for
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah yes, forgot about that. In that case building the map is probably the best way |
||
| replacements.setdefault(hash, ch) | ||
| for ch, hash in self._code_table.items(): | ||
| replacements.setdefault(hash, ch) | ||
| for hash, item in self.html_blocks.items(): | ||
| replacements.setdefault(hash, item) | ||
| # Scan once per nesting level. Use a flag instead of a full-string | ||
| # comparison to detect whether any replacement was made this pass. | ||
| while True: | ||
| orig_text = text | ||
| # Scan once per nesting level instead of once for every stored hash. | ||
| text = re.sub(r'md5-[0-9a-f]{32}', | ||
| lambda match: replacements.get(match[0], match[0]), text) | ||
| if text == orig_text: | ||
| replaced = [False] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting but odd pattern. Would
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, switched to |
||
| def _sub(match: re.Match, _rep: dict = replacements) -> str: | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, it doesn't. It uses
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch. It's defined once before the loop now, and only the flag is reset each pass (4bf3ddb). |
||
| val = _rep.get(match[0]) | ||
| if val is not None: | ||
| replaced[0] = True | ||
| return val | ||
| return match[0] | ||
| text = re.sub(r'md5-[0-9a-f]{32}', _sub, text) | ||
| if not replaced[0]: | ||
| break | ||
| return text | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick but I would remove the
→char in the comment in favour of plain text like->.I don't think it will cause any issues (python source is default utf-8) but I just think it can't be typed from a keyboard so should it be in a comment?
It also displays oddly in VSCode, really small and almost looks like an underline:


As opposed to this which is bigger and more legible:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done in 4bf3ddb, it's plain
->now.