private function newTagDOMNode()

in src/parser/html/PhutilHTMLParser.php [121:226]


  private function newTagDOMNode(array $part) {
    if (!$part['tag']) {
      return null;
    }

    $raw_content = $part['content'];
    $content = $raw_content;

    $content = trim($content);
    $content_len = strlen($content);

    // If the tag content begins with "/", like "</td>", strip the slash
    // off and mark this as a closing tag.
    $is_close = false;
    if ($content_len > 0 && $content[0] === '/') {
      $is_close = true;
      $content = substr($content, 1);
      $content = trim($content);
      $content_len = strlen($content);
    }

    // If the tag content ends with "/", like "<td />", strip the slash off
    // and mark this as self-closing.
    $self_close = false;
    if ($content_len > 0 && $content[$content_len - 1] === '/') {
      $self_close = true;
      $content = substr($content, 0, $content_len - 1);
      $content = trim($content);
      $content_len = strlen($content);
    }

    // If this tag is both a closing tag and a self-closing tag, it is
    // not formatted correctly. Treat it as content.
    if ($self_close && $is_close) {
      return null;
    }

    // Now, split the rest of the tag into the tag name and tag attributes.
    $pieces = preg_split('/\s+/', $content, 2);
    $tag_name = $pieces[0];

    if (count($pieces) > 1) {
      $attributes = $pieces[1];
    } else {
      $attributes = '';
    }

    // If there's no tag name, this tag is not valid. Treat it as content.
    if (!strlen($tag_name)) {
      return null;
    }

    // If this is a closing tag with attributes, it's not valid. Treat it
    // as content.
    if ($is_close && strlen($attributes)) {
      return null;
    }

    $tag_name = phutil_utf8_strtolower($tag_name);

    // If we find a valid closing tag, try to find a matching tag on the stack.
    // If we find a matching tag, close it.
    // If we do not find a matching tag, treat the closing tag as content.
    if ($is_close) {
      $cursor = $this->getCursor();

      while ($cursor) {
        if ($cursor->getTagName() === $tag_name) {
          // Add this raw content to the raw content of the tag we're closing.
          $cursor->setRawTail('<'.$raw_content.'>');

          $parent = $cursor->getParentNode();
          $this->setCursor($parent);

          return true;
        }
        $cursor = $cursor->getParentNode();
      }

      return null;
    }

    if (strlen($attributes)) {
      $attribute_map = $this->parseAttributes($attributes);
      // If the attributes can't be parsed, treat the tag as content.
      if ($attribute_map === null) {
        return null;
      }
    } else {
      $attribute_map = array();
    }

    $node = id(new PhutilDOMNode())
      ->setTagName($tag_name)
      ->setAttributes($attribute_map)
      ->setRawHead('<'.$raw_content.'>');

    $cursor = $this->getCursor();
    $cursor->appendChild($node);

    if (!$self_close) {
      $this->setCursor($node);
    }

    return $node;
  }