Skip to content
This repository has been archived by the owner on Jan 4, 2024. It is now read-only.

Commit

Permalink
chore: rewrite HTML parsing with lxml
Browse files Browse the repository at this point in the history
  • Loading branch information
soulless-viewer committed Mar 17, 2023
1 parent 7de21ad commit 4c9b1fb
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 85 deletions.
60 changes: 59 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<video style="width:50%" muted="" controls="" alt="type:video">
<source src="video.mp4" type="video/mp4">
</video>
```

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
<video style="width: 100%" muted="" controls="" alt="type:video">
<source src="video.mp4" type="video/mp4">
</video>
```

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
<video alt="type:video" style="width: 100%">
<source src="video.mp4" type="video/mp4">
</video>
```

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
<video style="width:50%" muted="" controls="" alt="type:video">
<source src="another-video.mp4" type="video/mp4">
</video>
```

### Marker

By default, the string `type:video` is used as a **marker** in the Markdown syntax.
Expand Down Expand Up @@ -239,6 +297,6 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI

## Did you like it?

<a href="https://www.buymeacoffee.com/soulless.viewer">
<a href="https://www.buymeacoffee.com/soulless.viewer">
<img height="50em" src="https://cdn.buymeacoffee.com/buttons/v2/default-blue.png" alt="soulless.viewer" />
</a>
146 changes: 64 additions & 82 deletions mkdocs_video/plugin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import re
import mkdocs
import lxml.html
from mkdocs.config import config_options
from mkdocs.exceptions import ConfigurationError

Expand All @@ -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'<video style="{style}"'
f'{" loop" if video_loop else ""}'
f'{" muted" if video_muted else ""}'
f'{" controls" if video_controls else ""}'
f'{" autoplay" if video_autoplay else ""}'
'>'
f'<source src="{src}" type="{video_type}" />'
'</video>'
) if is_video else (
f'<iframe src="{src}" style="{style}" frameborder="0"'
'allowfullscreen>'
'</iframe>'
)

return f'<div class="video-container">{tag}</div>'


def find_marked_tags(self, content):
'''
Find image tag with marked alternative name
return: list
'''

mark = self.config["mark"]

return re.findall(
r'<img alt="' + mark + '" src="[^\s]*"\s*\/>',
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
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
lxml>=4.7.0
mkdocs>=1.1.0,<2
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setup(
name="mkdocs-video",
version="1.4.0",
version="1.5.0",
author="Mikalai Lisitsa",
author_email="[email protected]",
url="https://github.com/soulless-viewer/mkdocs-video",
Expand All @@ -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',
Expand Down

0 comments on commit 4c9b1fb

Please sign in to comment.