From 4c9b1fba49919ffc19bcd2c20ef868c26576e384 Mon Sep 17 00:00:00 2001 From: soulless-viewer <> Date: Fri, 17 Mar 2023 18:33:24 +0100 Subject: [PATCH] chore: rewrite HTML parsing with lxml --- README.md | 60 ++++++++++++++++- mkdocs_video/plugin.py | 146 ++++++++++++++++++----------------------- requirements.txt | 1 + setup.py | 5 +- 4 files changed, 127 insertions(+), 85 deletions(-) diff --git a/README.md b/README.md index 15a0f30..1da7b93 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,64 @@ You can also use relative paths for videos stored together with your content ## Configuration +The following parameters can be used to change the functionality and appearance of video elements in the final HTML. Keep in mind that the plugin configuration parameters are applied globally to all relevant [marked](#marker) elements. To fine-tune each video element, you can use the [Attribute Lists](https://python-markdown.github.io/extensions/attr_list/) extension. + +When using this plugin and the mentioned extension together, the following rules apply *(with an illustrative examples)*: + +0. *[Let's assume we have this plugin configuration]* + ```yaml + # mkdocs.yml + markdown_extensions: + - attr_list + plugins: + - mkdocs-video: + is_video: True + video_muted: True + video_controls: True + css_style: + width: "50%" + ``` + +1. The plugin attributes are used globally by default + ```markdown + ![type:video](video.mp4) + ``` + ```html + + ``` + +2. The extension attributes will override the corresponding plugin attributes, but the rest will remain by default. + ```markdown + ![type:video](video.mp4){: style='width: 100%'} + ``` + ```html + + ``` + +3. The plugin attributes can be disabled for specific video element by adding `disable-global-config` attribute. + ```markdown + ![type:video](video.mp4){: disable-global-config style='width: 100%'} + ``` + ```html + + ``` + +4. The extension attribute `src` will override video source... Do what you want with this info 🙃. + ```markdown + ![type:video](video.mp4){: src='another-video.mp4'} + ``` + ```html + + ``` + ### Marker By default, the string `type:video` is used as a **marker** in the Markdown syntax. @@ -239,6 +297,6 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI ## Did you like it? - + soulless.viewer diff --git a/mkdocs_video/plugin.py b/mkdocs_video/plugin.py index a5f783b..d1b8a75 100644 --- a/mkdocs_video/plugin.py +++ b/mkdocs_video/plugin.py @@ -1,5 +1,5 @@ -import re import mkdocs +import lxml.html from mkdocs.config import config_options from mkdocs.exceptions import ConfigurationError @@ -13,97 +13,79 @@ class Plugin(mkdocs.plugins.BasePlugin): ("video_loop", config_options.Type(bool, default=False)), ("video_controls", config_options.Type(bool, default=True)), ("video_autoplay", config_options.Type(bool, default=False)), - ("css_style", config_options.Type(dict, default={ - "position": "relative", - "width": "100%", - "height": "22.172vw" - })) + ("css_style", config_options.Type( + dict, + default={ + "position": "relative", + "width": "100%", + "height": "22.172vw" + } + )) ) def on_page_content(self, html, page, config, files): - # Separate tags by strings to simplify the use of regex - content = html - content = re.sub(r'>\s*<', '>\n<', content) - - tags = self.find_marked_tags(content) - + content = lxml.html.fromstring(html) + tags = content.xpath(f'//img[@alt="{self.config["mark"]}" and @src]') for tag in tags: - src = self.get_tag_src(tag) - if src is None: + if not tag.attrib.get("src"): continue - repl_tag = self.create_repl_tag(src) - esc_tag = re.sub(r'\/', "\\\\/", tag) - html = re.sub(esc_tag, repl_tag, html) - - return html - - - def get_tag_src(self, tag): - ''' - Get value of the src attribute - - return: str - ''' - - result = re.search( - r'src=\"[^\s]*\"', - tag - ) - - return result[0][5:-1] if result is not None else None + tag.getparent().replace(tag, self.create_repl_tag(tag)) + return lxml.html.tostring(content, encoding="unicode") - def create_repl_tag(self, src): - ''' + def create_repl_tag(self, tag): + """ Сreate a replacement tag with the specified source and style. return: str - ''' - - style = self.config["css_style"] - style = "; ".join( - ["{}: {}".format(str(atr), str(style[atr])) for atr in style] - ) + """ is_video = self.config["is_video"] - video_loop = self.config["video_loop"] - video_muted = self.config["video_muted"] - video_controls = self.config["video_controls"] - video_autoplay = self.config["video_autoplay"] - video_type = self.config['video_type'].lower().strip() - if " " in video_type or "/" in video_type: - raise ConfigurationError("Unsupported video type") - video_type = f"video/{video_type}" - - tag = ( - f'' - ) if is_video else ( - f'' - ) - - return f'
{tag}
' - - - def find_marked_tags(self, content): - ''' - Find image tag with marked alternative name - - return: list - ''' - - mark = self.config["mark"] - - return re.findall( - r'' + mark + '', - content - ) + repl_tag = lxml.html.Element("video" if is_video else "iframe") + + # Basic config if global is disabled + if is_video: + repl_subtag = lxml.html.Element("source") + repl_subtag.set("src", tag.attrib["src"]) + video_type = self.config["video_type"].lower().strip() + if any(i in video_type for i in [" ", "/"]): + raise ConfigurationError("Unsupported video type") + video_type = f"video/{video_type}" + repl_subtag.set("type", video_type) + repl_tag.append(repl_subtag) + else: + repl_tag.set("src", tag.attrib["src"]) + + # Extended config if global is enabled + if "disable-global-config" not in tag.attrib: + css_style = ";".join( + [f"{k}:{v}" for k, v in self.config["css_style"].items()] + ) + repl_tag.set("style", css_style) + + if is_video: + if self.config["video_loop"]: + repl_tag.set("loop") + if self.config["video_muted"]: + repl_tag.set("muted") + if self.config["video_controls"]: + repl_tag.set("controls") + if self.config["video_autoplay"]: + repl_tag.set("autoplay") + else: + repl_tag.set("frameborder", "0") + repl_tag.set("allowfullscreen") + else: + tag.attrib.pop("disable-global-config") + + # Duplicate everything from original tag (except 2) + for attr, val in tag.attrib.items(): + if "src" != attr: + repl_tag.set(attr, val if val else None) + + div = lxml.html.Element("div") + div.set("class", "video-container") + div.append(repl_tag) + + return div diff --git a/requirements.txt b/requirements.txt index b4e1d73..6014e56 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ +lxml>=4.7.0 mkdocs>=1.1.0,<2 diff --git a/setup.py b/setup.py index 504fab4..9c457f2 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name="mkdocs-video", - version="1.4.0", + version="1.5.0", author="Mikalai Lisitsa", author_email="mikalai.lisitsa@gmail.com", url="https://github.com/soulless-viewer/mkdocs-video", @@ -15,7 +15,8 @@ license='MIT', packages=find_packages(), install_requires=[ - "mkdocs>=1.1.0,<2" + "mkdocs>=1.1.0,<2", + "lxml>=4.7.0" ], include_package_data=True, python_requires='>=3.6',