"""
HTML要素の構築と操作を補助するモジュール。
このモジュールは、HTMLのタグ、属性、コンテンツ、子要素をプログラム的に管理し、
HTML文字列を生成するためのクラスを提供します。
`tkHTMLElement` クラスは個々のHTML要素を表し、その属性や子要素を操作できます。
`tkHTMLDocument` クラスは、これらの要素を生成し、より複雑なHTML構造(例: テーブル)を
簡単に構築するためのファクトリメソッドを提供します。
関連リンク:
:doc:`tkHTMLDocument_usage`
"""
import re
import html
[ドキュメント]
class tkHTMLElement:
"""
単一のHTML要素を表すクラス。
タグ名、コンテンツ、属性、子要素を管理し、それらからHTML文字列を構築します。
コンテンツのエスケープ処理も行います。
"""
def __init__(self, tag: str, content: str = "", attributes: dict = None, escape: bool = True):
"""
tkHTMLElementの新しいインスタンスを初期化します。
要素のタグ、コンテンツ、属性を設定し、必要に応じてコンテンツをエスケープします。
:param tag: str: HTML要素のタグ名(例: "div", "p", "a")。
:param content: str | Any: 要素のテキストコンテンツ。デフォルトは空文字列。
文字列でない場合は文字列に変換されます。
:param attributes: dict | None: 要素の属性をキーと値の辞書で指定します。
例: {"class": "my-class", "id": "my-id"}。デフォルトはNone。
:param escape: bool: コンテンツと属性値をHTMLエスケープするかどうかを決定します。
'script'タグの場合、またはFalseが指定された場合、エスケープは行われません。
デフォルトはTrue。
:returns: None
"""
self.escape = escape
self.tag = tag
# 初期コンテンツをエスケープ
if tag == "script" or self.escape is False:
self._content = content
else:
if type(content) is str:
self._content = html.escape(content)
else:
self._content = html.escape(str(content))
self.attributes = attributes or {} # 属性は辞書で管理
self.children = [] # 子要素のリスト
[ドキュメント]
@classmethod
def create_element(cls, tag: str, content: str = "", attributes: dict = None, escape: bool = True) -> 'tkHTMLElement':
"""
HTML要素の新しいインスタンスを生成します。
このクラスメソッドは、指定されたタグ、コンテンツ、属性を持つHTML要素を
簡単に作成するためのファクトリとして機能します。
:param tag: str: HTML要素のタグ名。
:param content: str: 要素のテキストコンテンツ。デフォルトは空文字列。
:param attributes: dict | None: 要素の属性をキーと値の辞書で指定します。デフォルトはNone。
:param escape: bool: コンテンツをHTMLエスケープするかどうか。デフォルトはTrue。
:returns: tkHTMLElement: 新しく生成されたtkHTMLElementのインスタンス。
"""
return cls(tag, content, attributes)
@property
def contentText(self) -> str:
"""
要素のテキストコンテンツを取得します。
このプロパティは、HTMLエスケープされた生テキストコンテンツを返します。
setterを使用すると、新しいテキストコンテンツを設定できます。
:returns: str: 現在の要素のテキストコンテンツ。
"""
return self._content
@contentText.setter
def contentText(self, new_content: str):
"""
要素のテキストコンテンツを更新します。
:param new_content: str: 設定する新しいテキストコンテンツ。
:returns: None
"""
self._content = new_content
@property
def innerHTML(self) -> str:
"""
現在の要素の子要素を含むHTMLコンテンツを構築して返します。
要素自身のコンテンツと、すべての子要素が構築されたHTML文字列を結合します。
子要素はインデントされて整形されます。
:returns: str: 要素の内部HTML表現(子要素を含む)。
"""
children_html = "\n".join(" " + child.build_element() for child in self.children) # 子要素にインデント
if self.children:
html = f'{self._content}\n{children_html}'
else:
html = self._content
html = re.sub(r'^\s*\n', '', html)
return html
@property
def textContent(self) -> str:
"""
要素とそのすべての子要素からテキストコンテンツを再帰的に収集し、結合して返します。
各要素のコンテンツは改行で区切られます。
HTMLタグは含まれません。
:returns: str: 要素ツリー全体の結合されたテキストコンテンツ。
"""
def gather_text_with_newlines(element):
texts = [element._content]
for child in element.children:
texts.append(gather_text_with_newlines(child)) # 再帰的に子要素を処理
return "\n".join(filter(None, texts)) # 改行で結合し、空文字を除外
return gather_text_with_newlines(self).strip()
@textContent.setter
def textContent(self, new_content: str):
"""
テキストコンテンツを一括更新し、子要素をリセットします。
新しいコンテンツはHTMLエスケープされます。
:param new_content: str: 設定する新しいテキストコンテンツ。
:returns: None
"""
self._content = html.escape(new_content) # 新しいコンテンツをエスケープ
self.children.clear() # 子要素をリセット
@property
def outerHTML(self) -> str:
"""
要素全体のHTML表現を構築して返します。
これは `build_element()` メソッドのラッパーです。
:returns: str: 要素とその子要素すべてを含む完全なHTML文字列。
"""
return self.build_element()
[ドキュメント]
def append_child(self, *child_elements: 'tkHTMLElement'):
"""
指定された子要素を現在の要素に追加します。
複数の子要素を一度に追加できます。
:param child_elements: tkHTMLElement: 追加する一つ以上のtkHTMLElementインスタンス。
:returns: None
"""
for e in child_elements:
self.children.append(e)
[ドキュメント]
def build_element(self) -> str:
"""
現在のHTML要素の完全なHTML文字列を構築して返します。
属性の整形、空要素の処理、子要素のインデント、コンテンツの配置など、
要素の構造とコンテンツに基づいて適切なHTMLを生成します。
:returns: str: 構築されたHTML要素の文字列表現。
"""
attributes_str = ""
for key, value in self.attributes.items():
if value is True:
attributes_str += f" {key}"
elif value is not False and value is not None:
str_value = value if self.tag == "script" or self.escape is False else html.escape(value)
attributes_str += f' {key}="{str_value}"'
if re.match(r'^\s+$', attributes_str):
attributes_str = ""
# if attributes_str != "":
# attributes_str = f" {attributes_str}"
# 空要素(例: <br>, <img>, <input>)は終わりタグを省略
void_elements = {"br", "img", "input", "hr", "meta", "link"}
if self.tag in void_elements:
return f'<{self.tag}{attributes_str} />'
children_html = "\n".join(" " + child.build_element() for child in self.children) # 子要素にインデントを追加
if self.children:
# 子要素がある場合、閉じタグはインデント無し
if self._content == "":
content = ""
else:
content = f"\n {self._content}"
return f'<{self.tag}{attributes_str}>{content}\n{children_html.rstrip()}\n</{self.tag}>'
else:
# 子要素がない場合、通常の処理
content = self._content.rstrip()
if len(content) > 0 and content[0] == '\n':
content = content[1:]
if "\n" in content:
# if self.tag == "script" and content != "":
return f'<{self.tag}{attributes_str}>\n{content}\n</{self.tag}>'
else:
return f'<{self.tag}{attributes_str}>{content}</{self.tag}>'
"""
def create_table(self, attributes={"border": "1"}, header=[], data_list=[]):
table_structure = TableStructure(self) # tkHTMLDocument を渡す
table_structure.table.set_attributes(attributes)
# ヘッダーを追加
table_structure.add_header(header)
# データを追加
for row in data_list:
table_structure.add_row(row)
return table_structure
class tkHTMLDocument:
def create_table(self, attributes={"border": "1"}, header=[], data_list=[]):
table_structure = TableStructure(self) # tkHTMLDocument を渡す
table_structure.table.set_attributes(attributes)
table_structure.add_header(header)
for row in data_list:
table_structure.add_row(row)
return table_structure
"""
[ドキュメント]
class TableStructure:
"""
HTMLテーブル構造を簡素化して構築するためのヘルパークラス。
HTMLテーブルの `<table>`, `<thead>`, `<tbody>`, `<tr>`, `<th>`, `<td>` 要素を管理し、
ヘッダーや行データを簡単に追加できるインターフェースを提供します。
"""
def __init__(self, document: 'tkHTMLDocument' = None):
"""
TableStructureの新しいインスタンスを初期化します。
テーブル、ヘッダー、ボディの基本構造を作成します。
`tkHTMLDocument` インスタンスが指定されない場合、新しいインスタンスを内部で作成します。
:param document: tkHTMLDocument | None: HTML要素を生成するために使用するtkHTMLDocumentインスタンス。
デフォルトはNone。
:returns: None
"""
# 注意: 既存コードの `tk_document` はこのスコープで未定義であり、
# 実行時に `NameError` を引き起こす可能性があります。
# このDocstringは現状のコードの動作を説明しており、コードの変更は行いません。
self.document = tk_document if tk_document else tkHTMLDocument()
self.table = self.tk_document.create_element("table")
self.thead = self.tk_document.create_element("thead")
self.tr_head = self.tk_document.create_element("tr")
self.tbody = self.tk_document.create_element("tbody")
self.table.append_child(self.thead)
self.thead.append_child(self.tr_head)
self.table.append_child(self.tbody)
[ドキュメント]
def add_row(self, row_data: list[str]):
"""
テーブルのボディに行データセルを追加します。
:param row_data: list[str]: 行の各データセルのテキストコンテンツのリスト。
:returns: None
"""
tr = self.tk_document.create_element("tr")
self.tbody.append_child(tr)
for data in row_data:
td = self.tk_document.create_element("td", data)
tr.append_child(td)
[ドキュメント]
class tkHTMLDocument:
"""
HTMLドキュメント全体の管理と要素生成のためのファクトリクラス。
個々のHTML要素を生成し、より複雑なHTML構造(例: テーブル)を構築するための
便利なメソッドを提供します。
"""
def __init__(self):
"""
tkHTMLDocumentの新しいインスタンスを初期化します。
:returns: None
"""
pass
[ドキュメント]
def create_element(self, tag: str, content: str = "", attributes: dict = None, escape: bool = True) -> tkHTMLElement:
"""
指定されたタグ、コンテンツ、属性を持つ新しいtkHTMLElementインスタンスを生成します。
このメソッドは、`tkHTMLElement` のコンストラクタをラップし、
`tkHTMLDocument` インスタンスから直接要素を生成できるようにします。
:param tag: str: HTML要素のタグ名。
:param content: str: 要素のテキストコンテンツ。デフォルトは空文字列。
:param attributes: dict | None: 要素の属性をキーと値の辞書で指定します。デフォルトはNone。
:param escape: bool: コンテンツと属性値をHTMLエスケープするかどうかを決定します。
デフォルトはTrue。
:returns: tkHTMLElement: 新しく生成されたtkHTMLElementのインスタンス。
"""
return tkHTMLElement(tag, content, attributes=attributes, escape=escape)
[ドキュメント]
def create_table(self, attributes: dict = {"border": "1"}, header: list[str] = [], data_list: list[list[str]] = []) -> tuple[tkHTMLElement, tkHTMLElement, tkHTMLElement, tkHTMLElement]:
"""
HTMLテーブルを生成し、ヘッダーとデータ行を追加します。
このメソッドは、`<table>`, `<thead>`, `<tr>`, `<th>`, `<tbody>`, `<td>` 要素を
自動的に構築し、指定されたヘッダーとデータでテーブルを初期化します。
:param attributes: dict: テーブル要素に適用する属性の辞書。デフォルトは{"border": "1"}。
:param header: list[str]: テーブルのヘッダーテキストのリスト。
:param data_list: list[list[str]]: テーブルの各行データを含むリストのリスト。
:returns: tuple[tkHTMLElement, tkHTMLElement, tkHTMLElement, tkHTMLElement]:
生成されたテーブル、thead、tr_head、tbody要素のタプル。
"""
# 注意: 既存コードの `document.create_element` はグローバル変数 `document` を参照しており、
# `self.create_element` を使用すべきですが、絶対遵守ルールに基づきコードの変更は行いません。
table = self.create_element("table", attributes=attributes)
thead = document.create_element("thead")
table.append_child(thead)
tr_head = document.create_element("tr")
thead.append_child(tr_head)
for e in header:
th = document.create_element("th", e)
tr_head.append_child(th)
tbody = document.create_element("tbody")
table.append_child(tbody)
for data in data_list:
tr = document.create_element("tr")
tbody.append_child(tr)
for e in data:
td = document.create_element("td", e)
tr.append_child(td)
return table, thead, tr_head, tbody
document = tkHTMLDocument()
if __name__ == "__main__":
details = document.create_element("details", attributes={"open": "true"})
summary = document.create_element("summary", "クリックして開閉")
content = document.create_element("div", "ここに詳細情報を書きます!")
details.append_child(summary)
details.append_child(content)
print("textContent:", details.textContent)
print("InnerHTML:", details.innerHTML) # 子要素のHTMLのみ
print("OuterHTML:", details.outerHTML) # 要素全体のHTML