Does the parent have an implied closing tag?

@param string $html

@return boolean
541141
public function isClosingElementImplied(string $html): bool
    {
        $parent = $this->getParent();
        if ($parent === null || !($parent instanceof self)) {
            return false;
        }

        $name = $this->parseElementName($html);
        $parentName = $parent->getName();

        // HEAD: no closing tag.
        if ($name === 'body' && $parentName === 'head') {
            return true;
        }

        // P
        if ($parentName === 'p' && \in_array($name, self::NO_CHILDREN_OF_P, true)) {
            return true;
        }

        // LI
        if ($parentName === 'li' && $name === 'li') {
            return true;
        }

        // DT and DD
        if (($parentName === 'dt' || $parentName === 'dd') && ($name === 'dt' || $name === 'dd')) {
            return true;
        }

        // RP and RT
        if (($parentName === 'rp' || $parentName === 'rt') && ($name === 'rp' || $name === 'rt')) {
            return true;
        }

        return false;
    }
839540
protected function outputClosingTag()
    {
        echo '</', htmlspecialchars($this->_tag), '>';
        if ($this->_tag == 'html' || $this->_tag == 'head' || $this->_tag == 'body') {
            echo "\n";
        }
    }
859827
protected function getHtmlFrag($html)
    {
        if (!($html = (string) $html)) {
            return []; // Nothing to do.
        }
        if (preg_match('/(?P<all>(?P<open_tag>\<html(?:\s+[^>]*?)?\>)(?P<contents>.*?)(?P<closing_tag>\<\/html\>))/uis', $html, $html_frag)) {
            return $this->removeNumericKeysDeep($html_frag);
        }
        return [];
    }
952958
public function hasTag(string $tag): bool
    {
        return is_array($this->tags) && array_key_exists($tag, $this->tags);
    }
689697
private function cleanup($html)
    {
        $html = trim($html);

        // special replacements (not properly handled by washtml class)
        $html_search = array(
            // space(s) between <NOBR>
            '/(<\/nobr>)(\s+)(<nobr>)/i',
            // PHP bug #32547 workaround: remove title tag
            '/<title[^>]*>[^<]*<\/title>/i',
            // remove <!doctype> before BOM (#1490291)
            '/<\!doctype[^>]+>[^<]*/im',
            // byte-order mark (only outlook?)
            '/^(\0\0\xFE\xFF|\xFF\xFE\0\0|\xFE\xFF|\xFF\xFE|\xEF\xBB\xBF)/',
            // washtml/DOMDocument cannot handle xml namespaces
            '/<html\s[^>]+>/i',
        );

        $html_replace = array(
            '\\1'.' &nbsp; '.'\\3',
            '',
            '',
            '',
            '<html>',
        );

        $html = preg_replace($html_search, $html_replace, trim($html));

        // Replace all of those weird MS Word quotes and other high characters
        $badwordchars = array(
            "\xe2\x80\x98", // left single quote
            "\xe2\x80\x99", // right single quote
            "\xe2\x80\x9c", // left double quote
            "\xe2\x80\x9d", // right double quote
            "\xe2\x80\x94", // em dash
            "\xe2\x80\xa6"  // elipses
        );

        $fixedwordchars = array(
            "'",
            "'",
            '"',
            '"',
            '&mdash;',
            '...'
        );

        $html = str_replace($badwordchars, $fixedwordchars, $html);

        // PCRE errors handling (#1486856), should we use something like for every preg_* use?
        if ($html === null && ($preg_error = preg_last_error()) != PREG_NO_ERROR) {
            $errstr = "Could not clean up HTML message! PCRE Error: $preg_error.";

            if ($preg_error == PREG_BACKTRACK_LIMIT_ERROR) {
                $errstr .= " Consider raising pcre.backtrack_limit!";
            }
            if ($preg_error == PREG_RECURSION_LIMIT_ERROR) {
                $errstr .= " Consider raising pcre.recursion_limit!";
            }

            rcube::raise_error(array('code' => 620, 'type' => 'php',
                'line' => __LINE__, 'file' => __FILE__,
                'message' => $errstr), true, false);

            return '';
        }

        // FIXME: HTML comments handling could be better. The code below can break comments (#6464),
        //        we should probably do not modify content inside comments at all.

        // fix (unknown/malformed) HTML tags before "wash"
        $html = preg_replace_callback('/(<(?!\!)[\/]*)([^\s>]+)([^>]*)/', array($this, 'html_tag_callback'), $html);

        // Remove invalid HTML comments (#1487759)
        // Note: We don't want to remove valid comments, conditional comments
        // and MSOutlook comments (<!-->)
        $html = preg_replace('/<!--[a-zA-Z0-9]+>/', '', $html);

        // fix broken nested lists
        self::fix_broken_lists($html);

        // turn relative into absolute urls
        $html = self::resolve_base($html);

        return $html;
    }
202532
protected function _tag_close_UL($param)
    {
        if ($this->_isForOneLine) {
            return false;
        }

        $this->_tag_close_TABLE($param);

        $this->_listeDelLevel();

        if (!$this->_subPart) {
            if (!count($this->_defList)) {
                $this->_tag_open_BR(array());
            }
        }

        return true;
    }
201682
public function readability_html( $html )
	{
		$data = $this->post_call('/readability', ['html' => $html ] );

		return ( !empty($data['readability']) ) ? $data['readability'] : null;
	}
701693
@Override
  protected void visitHtmlCloseTagNode(HtmlCloseTagNode node) {
    // This case occurs in the case where we encounter the end of a keyed element. If the open tag
    // mapped to this close tag contains a key node, pop the keyCounterStack to return
    // to the state before entering the keyed node.
    if (node.getTaggedPairs().size() == 1) {
      HtmlOpenTagNode openTag = (HtmlOpenTagNode) node.getTaggedPairs().get(0);
      if (openTag.getKeyNode() != null) {
        keyCounterStack.pop();
        getJsCodeBuilder().append(INCREMENTAL_DOM_POP_MANUAL_KEY.call());
      }
    }

    if (!node.getTagName().isDefinitelyVoid()) {
      emitClose(getTagNameCodeChunk(node.getTagName()));
    }
  }
1118414
private void checkCloseTagChildren(HtmlCloseTagNode closeTag) {
    HtmlAttributeNode phNameAttribute =
        closeTag.getDirectAttributeNamed(MessagePlaceholders.PHNAME_ATTR);
    HtmlAttributeNode phExAttribute =
        closeTag.getDirectAttributeNamed(MessagePlaceholders.PHEX_ATTR);
    // the child at index 0 is the tag name
    for (int i = 1; i < closeTag.numChildren(); i++) {
      StandaloneNode child = closeTag.getChild(i);
      if (child == phNameAttribute || child == phExAttribute) {
        continue; // the phname and phex attributes are validated later and allowed in close nodes
      }
      errorReporter.report(child.getSourceLocation(), UNEXPECTED_CLOSE_TAG_CONTENT);
    }
  }
683681
private function tag_has_desc(string $tag){
    return in_array('description', array_values(
      \is_array($this->tags[$tag]) ?
        $this->tags[$tag] :
        $this->tags[$this->tags[$tag]]
    ));
  }
682367
public function end_tag($tag) {
        // TODO: check the tag name is valid

        if ($this->level == 0) { // Nothing to end, already at level 0
            throw new xml_writer_exception('xml_writer_end_tag_no_match');
        }

        $pre = $this->lastwastext ? '' : "\n" . str_repeat(' ', ($this->level - 1) * 2); // Indent
        $tag = $this->casefolding ? strtoupper($tag) : $tag; // Follow casefolding

        $lastopentag = array_pop($this->opentags);

        if ($tag != $lastopentag) {
            $a = new stdclass();
            $a->lastopen = $lastopentag;
            $a->tag = $tag;
            throw new xml_writer_exception('xml_writer_end_tag_no_match', $a);
        }

        // Send to xml_output
        $this->write($pre . '</' . $tag . '>');

        $this->level--;
        $this->lastwastext = false;
    }
1312925
public function parentTag(): ?string
    {
        switch ($this->attributes['list']) {
            case Options::ATTRIBUTE_LIST_ORDERED:
                return Options::HTML_TAG_LIST_ORDERED;
                break;
            case Options::ATTRIBUTE_LIST_BULLET:
                return Options::HTML_TAG_LIST_UNORDERED;
                break;

            default:
                return Options::HTML_TAG_LIST_UNORDERED;
                break;
        }
    }
497543
public static function repair($html, $use_tidy = true)
    {
        if (function_exists('tidy_repair_string') && $use_tidy) {
            $TidyConfig = [
                'indent' => true,
                'output-xhtml' => true,
                'show-body-only' => true,
                'wrap' => false,
            ];

            return tidy_repair_string($html, $TidyConfig, 'utf8');
        } else {
            $arr_single_tags = ['meta', 'img', 'br', 'link', 'area'];

            // Put all opened tags into an array
            preg_match_all("#<([a-z]+)( .*)?(?!/)>#iU", $html, $result);
            $openedtags = $result[1];

            // Put all closed tags into an array
            preg_match_all("#</([a-z]+)>#iU", $html, $result);

            $closedtags = $result[1];
            $len_opened = count($openedtags);

            // All tags are closed
            if (count($closedtags) == $len_opened) {
                return $html;
            }

            $openedtags = array_reverse($openedtags);

            // Close tags
            for ($i = 0; $i < $len_opened; $i++) {
                if (!in_array($openedtags[$i], $closedtags)) {
                    if (!in_array($openedtags[$i], $arr_single_tags)) {
                        $html .= "</" . $openedtags[$i] . ">";
                    }
                } else {
                    unset($closedtags[array_search($openedtags[$i], $closedtags)]);
                }
            }

            return $html;
        }
    }
561632
private function hasOnlyTags(string $html): bool
    {
        $html = preg_replace('/<[^>]*>/isu', '', $html);
        $html = str_replace('&nbsp;', '', $html);
        $html = trim($html);

        return strlen($html) === 0;
    }
1262534
protected function closeAncestor(Tag $tag)
	{
		if (!empty($this->openTags))
		{
			$tagName   = $tag->getName();
			$tagConfig = $this->tagsConfig[$tagName];

			if (!empty($tagConfig['rules']['closeAncestor']))
			{
				$i = count($this->openTags);

				while (--$i >= 0)
				{
					$ancestor     = $this->openTags[$i];
					$ancestorName = $ancestor->getName();

					if (isset($tagConfig['rules']['closeAncestor'][$ancestorName]))
					{
						++$this->currentFixingCost;

						// We have to close this ancestor. First we reinsert this tag...
						$this->tagStack[] = $tag;

						// ...then we add a new end tag for it with a better priority
						$this->addMagicEndTag($ancestor, $tag->getPos(), $tag->getSortPriority() - 1);

						return true;
					}
				}
			}
		}

		return false;
	}
72708
def handle_endtag(self, tag):
        
        if tag in PARENT_ELEMENTS:
            self.current_parent_element['tag'] = ''
            self.current_parent_element['attrs'] = ''

        if tag == 'li':
            self.parsing_li = True
        if tag != 'br':
            self.cleaned_html += '</{}>'.format(tag)
1282640
public function setHtml($Html)
    {
        if (! is_object($Html) || ! $Html instanceof HtmlObject) {
            throw new \InvalidArgumentException(
                'The HTML Object must be a valid HTML Object'
            );
        }
        $this->Html = $Html;

        return true;
    }