1
0
mirror of https://github.com/squidfunk/mkdocs-material.git synced 2024-11-28 01:10:58 +01:00

Fixed search plugin crashing on nested headlines

This commit is contained in:
squidfunk 2023-01-08 09:39:05 +01:00
parent c4d61cdc41
commit 81e7b8c7fc
2 changed files with 76 additions and 28 deletions

View File

@ -266,6 +266,10 @@ class Element:
self.tag = tag self.tag = tag
self.attrs = attrs self.attrs = attrs
# String representation
def __repr__(self):
return self.tag
# Support comparison (compare by tag only) # Support comparison (compare by tag only)
def __eq__(self, other): def __eq__(self, other):
if other is Element: if other is Element:
@ -291,12 +295,22 @@ class Section:
""" """
# Initialize HTML section # Initialize HTML section
def __init__(self, el): def __init__(self, el, depth = 0):
self.el = el self.el = el
self.depth = depth
# Initialize section data
self.text = [] self.text = []
self.title = [] self.title = []
self.id = None self.id = None
# String representation
def __repr__(self):
if self.id:
return "#".join([self.el.tag, self.id])
else:
return self.el.tag
# Check whether the section should be excluded # Check whether the section should be excluded
def is_excluded(self): def is_excluded(self):
return self.el.is_excluded() return self.el.is_excluded()
@ -350,15 +364,16 @@ class Parser(HTMLParser):
# Handle headings # Handle headings
if tag in ([f"h{x}" for x in range(1, 7)]): if tag in ([f"h{x}" for x in range(1, 7)]):
depth = len(self.context)
if "id" in attrs: if "id" in attrs:
# Ensure top-level section # Ensure top-level section
if tag != "h1" and not self.data: if tag != "h1" and not self.data:
self.section = Section(Element("hx")) self.section = Section(Element("hx"), depth)
self.data.append(self.section) self.data.append(self.section)
# Set identifier, if not first section # Set identifier, if not first section
self.section = Section(el) self.section = Section(el, depth)
if self.data: if self.data:
self.section.id = attrs["id"] self.section.id = attrs["id"]
@ -398,6 +413,20 @@ class Parser(HTMLParser):
if not self.context or self.context[-1] != tag: if not self.context or self.context[-1] != tag:
return return
# Check whether we're exiting the current context, which happens when
# a headline is nested in another element. In that case, we close the
# current section, continuing to append data to the previous section,
# which could also be a nested section see https://bit.ly/3IxxIJZ
if self.section.depth > len(self.context):
for section in reversed(self.data):
if section.depth and section.depth <= len(self.context):
# Set depth to 0 in order to denote that the current section
# is exited and must not be considered again.
self.section.depth = 0
self.section = section
break
# Remove element from skip list # Remove element from skip list
el = self.context.pop() el = self.context.pop()
if el in self.skip: if el in self.skip:
@ -407,18 +436,13 @@ class Parser(HTMLParser):
# Render closing tag if kept # Render closing tag if kept
if not self.skip.intersection(self.context): if not self.skip.intersection(self.context):
if tag in self.keep: if tag in self.keep:
# Check whether we're inside the section title
data = self.section.text data = self.section.text
if self.section.el in reversed(self.context): if self.section.el in self.context:
data = self.section.title data = self.section.title
# Remove element if empty (or only whitespace)
if data[-1] == f"<{tag}>":
del data[-1:]
elif data[-1].isspace() and data[-2] == f"<{tag}>":
del data[-2:]
# Append to section title or text # Append to section title or text
else:
data.append(f"</{tag}>") data.append(f"</{tag}>")
# Called for the text contents of each tag # Called for the text contents of each tag
@ -439,7 +463,7 @@ class Parser(HTMLParser):
self.data.append(self.section) self.data.append(self.section)
# Handle section headline # Handle section headline
if self.section.el in reversed(self.context): if self.section.el in self.context:
permalink = False permalink = False
for el in self.context: for el in self.context:
if el.tag == "a" and el.attrs.get("class") == "headerlink": if el.tag == "a" and el.attrs.get("class") == "headerlink":

View File

@ -266,6 +266,10 @@ class Element:
self.tag = tag self.tag = tag
self.attrs = attrs self.attrs = attrs
# String representation
def __repr__(self):
return self.tag
# Support comparison (compare by tag only) # Support comparison (compare by tag only)
def __eq__(self, other): def __eq__(self, other):
if other is Element: if other is Element:
@ -291,12 +295,22 @@ class Section:
""" """
# Initialize HTML section # Initialize HTML section
def __init__(self, el): def __init__(self, el, depth = 0):
self.el = el self.el = el
self.depth = depth
# Initialize section data
self.text = [] self.text = []
self.title = [] self.title = []
self.id = None self.id = None
# String representation
def __repr__(self):
if self.id:
return "#".join([self.el.tag, self.id])
else:
return self.el.tag
# Check whether the section should be excluded # Check whether the section should be excluded
def is_excluded(self): def is_excluded(self):
return self.el.is_excluded() return self.el.is_excluded()
@ -350,15 +364,16 @@ class Parser(HTMLParser):
# Handle headings # Handle headings
if tag in ([f"h{x}" for x in range(1, 7)]): if tag in ([f"h{x}" for x in range(1, 7)]):
depth = len(self.context)
if "id" in attrs: if "id" in attrs:
# Ensure top-level section # Ensure top-level section
if tag != "h1" and not self.data: if tag != "h1" and not self.data:
self.section = Section(Element("hx")) self.section = Section(Element("hx"), depth)
self.data.append(self.section) self.data.append(self.section)
# Set identifier, if not first section # Set identifier, if not first section
self.section = Section(el) self.section = Section(el, depth)
if self.data: if self.data:
self.section.id = attrs["id"] self.section.id = attrs["id"]
@ -398,6 +413,20 @@ class Parser(HTMLParser):
if not self.context or self.context[-1] != tag: if not self.context or self.context[-1] != tag:
return return
# Check whether we're exiting the current context, which happens when
# a headline is nested in another element. In that case, we close the
# current section, continuing to append data to the previous section,
# which could also be a nested section see https://bit.ly/3IxxIJZ
if self.section.depth > len(self.context):
for section in reversed(self.data):
if section.depth and section.depth <= len(self.context):
# Set depth to 0 in order to denote that the current section
# is exited and must not be considered again.
self.section.depth = 0
self.section = section
break
# Remove element from skip list # Remove element from skip list
el = self.context.pop() el = self.context.pop()
if el in self.skip: if el in self.skip:
@ -407,18 +436,13 @@ class Parser(HTMLParser):
# Render closing tag if kept # Render closing tag if kept
if not self.skip.intersection(self.context): if not self.skip.intersection(self.context):
if tag in self.keep: if tag in self.keep:
# Check whether we're inside the section title
data = self.section.text data = self.section.text
if self.section.el in reversed(self.context): if self.section.el in self.context:
data = self.section.title data = self.section.title
# Remove element if empty (or only whitespace)
if data[-1] == f"<{tag}>":
del data[-1:]
elif data[-1].isspace() and data[-2] == f"<{tag}>":
del data[-2:]
# Append to section title or text # Append to section title or text
else:
data.append(f"</{tag}>") data.append(f"</{tag}>")
# Called for the text contents of each tag # Called for the text contents of each tag
@ -439,7 +463,7 @@ class Parser(HTMLParser):
self.data.append(self.section) self.data.append(self.section)
# Handle section headline # Handle section headline
if self.section.el in reversed(self.context): if self.section.el in self.context:
permalink = False permalink = False
for el in self.context: for el in self.context:
if el.tag == "a" and el.attrs.get("class") == "headerlink": if el.tag == "a" and el.attrs.get("class") == "headerlink":