code: function consume_opening_or_closing_tag (proc_stack) {
    proc_stack.tSkip();
    let is_closing = proc_stack.tSkipIf("/");
    let tag_name = proc_stack.tAcceptUntilSet(TAG_NAME_DELIMITER).toLocaleLowerCase();
    if (!/^[a-z0-9]+(-[a-z0-9]+)*(:[a-z0-9]+(-[a-z0-9]+)*)?$/.test(tag_name)) {
      throw new SyntaxError(`Invalid HTML tag name "${tag_name}"`);
    }
  
    if (!is_closing && /^zc-/.test(tag_name)) {
      return consume_directive_tag(proc_stack, tag_name);
    }
  
    let parts = []; // Parts are needed for attribute value directives
  
    let html = "<" + (is_closing ? "/" : "") + tag_name;
    let done = false;
    while (!done) {
      html += proc_stack.tAcceptUntilSet(ATTR_VAL_START_OR_TAG_END_DELIMITER);
      let c = proc_stack.tAccept();
      html += c;
  
      switch (c) {
      case "\"":
        parts.push(html);
        Array.prototype.push.apply(parts, consume_char_data_with_entity_directives(proc_stack, false, "\""));
        html = proc_stack.tAccept();
        break;
  
      case ">":
        done = true;
        break;
  
      default:
        throw new Error(`INTERR Invalid char after ATTR_VAL_START_OR_TAG_END_DELIMITER`);
      }
    }
  
    parts.push(html);
  
    return parts;
  }
docs: Called on match `<`