Skip to content

Commit

Permalink
Merge pull request #14 from absdf15/main
Browse files Browse the repository at this point in the history
refactor(renderer): refactor the parser method
  • Loading branch information
fu050409 authored Mar 7, 2024
2 parents 6badde6 + ab35216 commit f0cf051
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 119 deletions.
205 changes: 121 additions & 84 deletions src/diceutils/renderer/_models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from cProfile import label
from pathlib import Path
from typing import Dict, List, Any, Optional
from enum import Enum
Expand All @@ -9,34 +10,41 @@ class Element(metaclass=abc.ABCMeta):
def __init__(self, _type: str, content: str) -> None:
self.type: str = _type
self.content: str = content
self._tag: str = "act"
self._label: str = "act"

def __repr__(self) -> str:
return f"Element(type={self.type}, content={self.content})"
return f"Element(type={self.type!r}, content={self.content!r}, label={self._label!r})"

@property
def tag(self) -> str:
return self._tag
return self._label

def set_tag(self, tag: str):
self.set_label(tag)

@property
def label(self) -> str:
return self._label

def set_label(self, label: str):
self._label = label


class Text(Element):
def __init__(self, text, tag: Optional[str] = None):
def __init__(self, text, label: Optional[str] = None):
super().__init__("text", text)
self._tag = tag or self._tag
self._label = label or self._label

def __repr__(self) -> str:
return f"Text(type={self.type!r}, content={self.content!r}, tag={self._tag!r})"

def set_tag(self, tag: str):
self._tag = tag
return f"Text(type={self.type!r}, content={self.content!r}, label={self._label!r})"


class Image(Element):
def __init__(self, url) -> None:
super().__init__("image", url)

def __repr__(self) -> str:
return f"Image(type={self.type!r}, url={self.content!r})"
return f"Image(type={self.type!r}, url={self.content!r}, label={self._label!r})"


class Role(Enum):
Expand All @@ -48,12 +56,12 @@ class Role(Enum):

class Message:
def __init__(
self,
user_code: str,
role: Role,
nickname: str,
date: str,
elements: List[Element] = [],
self,
user_code: str,
role: Role,
nickname: str,
date: str,
elements: List[Element] = [],
):
self.user_code = user_code
self.nickname = nickname
Expand All @@ -73,12 +81,12 @@ def append(self, message: Message):
return super().append(message)

def add_message(
self,
user_code: str,
role: Role,
nickname: str,
date: str,
content: List[Dict[str, Any]],
self,
user_code: str,
role: Role,
nickname: str,
date: str,
content: List[Dict[str, Any]],
):
elements = []
try:
Expand All @@ -100,105 +108,134 @@ def add_message(
class ExportConfig:
def __init__(self):
self.first_line_indent = True # 首行缩进
self.dice_command_filter = False # 骰子指令过滤
self.external_comment_filter = False # 场外发言过滤
self.display_dice_command = True # 指令显示
self.display_external_comment = True # 场外发言显示
self.display_image = True # 图片显示
self.display_datetime = True # 时间显示
self.display_account = True # 帐号显示
self.display_year_month_day = False # 年月日显示


class Renderer(metaclass=abc.ABCMeta):
_PLAYER_ACTION_LABEL = "act" # 玩家行动
_PLAYER_SPEECH_LABEL = "speak" # 玩家发言
_DICE_COMMAND_LABEL = "command" # 骰娘指令
_EXTERNAL_COMMENT_LABEL = "outside" # 场外发言
_INVALID_OPERATION_LABEL = "invalid" # 非法操作或无效操作产生的数据

@staticmethod
def split_and_label(text: str) -> Dict[str, str]:
ACTION_LABEL = Renderer._PLAYER_ACTION_LABEL
SPEECH_LABEL = Renderer._PLAYER_SPEECH_LABEL

result_dict = {}
inside_quote = False
current_text = ""
current_label = "act"
current_label = ACTION_LABEL

for c in text:
if c in "“”\"":
if inside_quote:
current_text = f"“{current_text}”"
if inside_quote:
result_dict[current_text] = current_label
current_text = ""
current_label = "act"
current_label = ACTION_LABEL
inside_quote = False
else:
else:
if current_text:
result_dict[current_text] = current_label
current_text = ""
current_label = "speak"
inside_quote = True
current_label = SPEECH_LABEL
inside_quote = True
else:
current_text += c

if current_text:
final_label = "speak" if inside_quote else "act"
if final_label == "speak":
current_text = f"{current_text}"
final_label = SPEECH_LABEL if inside_quote else ACTION_LABEL
result_dict[current_text] = final_label

return result_dict

@staticmethod
def parse_message(message: Message, config: ExportConfig) -> Optional[Message]:

def set_labels(elements: List[Element], new_elements: List[Element], first_label: str, other_label: str):
if (elements_len := len(elements)) == 0:
assert False, "Empty element list"

first_elements = elements[0]

first_elements.set_label(first_label)
new_elements.append(first_elements)

if elements_len != 1:
for element in elements[1:]:
element.set_label(other_label)
new_elements.append(element)

elements = message.elements

if (ele_len := len(elements)) == 0:
element_len = len(elements)

if element_len == 0:
return None

if config.dice_command_filter and message.role == -1:
sender_role = message.role

first_ele = elements[0]
first_ele_content = first_ele.content.strip()

is_command = first_ele_content.startswith(('.', '。', '/'))

is_first_ele_img = isinstance(first_ele, Image)
is_first_ele_text = isinstance(first_ele, Text)

is_external_comment = is_first_ele_text and first_ele_content.startswith(('(', '('))

if sender_role == Role.DICER and not config.display_dice_command:
return None

ele_iter = iter(elements)
first_ele = next(ele_iter)
is_image = isinstance(first_ele, Image)

if ele_len == 1 and is_image:
return message if config.display_image else None

is_text = isinstance(first_ele, Text)
is_external_comment = first_ele.content.startswith(("(", "("))

if config.external_comment_filter and is_text and is_external_comment:
if is_external_comment and not config.display_external_comment:
return None

is_command = first_ele.content.startswith((".", "。", "/"))

if config.dice_command_filter and is_text and is_command:

if is_command and not config.display_dice_command:
return None

if element_len == 1 and is_first_ele_img:
return message if config.display_image else None

new_elements = []
if is_text:
if is_external_comment:
first_ele.set_tag("outside")
for ele in ele_iter:
if isinstance(ele, Text):
ele.set_tag("outside")
return message
elif is_command:
first_ele.set_tag("command")
for ele in ele_iter:
if isinstance(ele, Text):
ele.set_tag("command")
return message
else:
for text, tag in Renderer.split_and_label(first_ele.content).items():
new_elements.append(Text(text, tag))
elif is_image:
new_elements.append(first_ele)
if is_first_ele_text:
if is_command:
set_labels(
elements=elements,
new_elements=new_elements,
first_label=Renderer._DICE_COMMAND_LABEL,
other_label=Renderer._INVALID_OPERATION_LABEL
)
elif is_external_comment:
set_labels(
elements=elements,
new_elements=new_elements,
first_label=Renderer._EXTERNAL_COMMENT_LABEL,
other_label=Renderer._EXTERNAL_COMMENT_LABEL
)
elif is_first_ele_img:
if config.display_image:
new_elements.append(first_ele)
else:
assert False, "Encountered an unsupported element type."

for ele in ele_iter:
if isinstance(ele, Text):
for text, tag in Renderer.split_and_label(ele.content).items():
new_elements.append(Text(text, tag))
elif isinstance(ele, Image):
new_elements.append(Image(ele.content))
else:
assert False, "Encountered an unsupported element type."
assert False, "Encountered an unsupported element type"

if not (is_external_comment or is_command):
for element in elements:
if isinstance(element, Text):
labeled_text = Renderer.split_and_label(element.content)
for text, label in labeled_text.items():
element_obj = Text(text, label)
new_elements.append(element_obj)
elif isinstance(element, Image):
new_elements.append(element)
else:
assert False, "Encountered an unsupported element type."

message.elements = new_elements
return message
Expand All @@ -209,9 +246,9 @@ def render_message(self, message: Message) -> None:

@staticmethod
def render(
messages: Messages,
renderer: "Renderer",
config: Optional[ExportConfig] = None,
messages: Messages,
renderer: "Renderer",
config: Optional[ExportConfig] = None,
) -> "Renderer":
for message in messages:
if message := renderer.parse_message(message, config or ExportConfig()):
Expand Down
87 changes: 52 additions & 35 deletions src/diceutils/renderer/docx.py
Original file line number Diff line number Diff line change
@@ -1,35 +1,52 @@
# class DocxRenderer(Renderer):
# document: DocumentType

# def __init__(self) -> None:
# self.document = Document()
# self.document.styles["Normal"].font.name = "SimSun"

# def export(self) -> None:
# self.document.save("a.docx")

# def render_message(self, message: Message) -> None:
# paragraph = self.document.add_paragraph()
# for element in message.elements:
# if element.type == "text":
# if element.tag == "act":
# run = paragraph.add_run(element.content)
# run.bold = True
# run.font.name = "SimSun"
# elif element.tag == "outside":
# paragraph.add_run("(")
# content = element.content.strip("()()")
# run = paragraph.add_run(content)
# run.font.color.rgb = (207, 210, 210)
# run.font.name = "SimSun"
# elif element.tag == "speak":
# content = element.content.strip('“”""')
# paragraph.add_run("“")
# run = paragraph.add_run(content)
# run.font.name = "Microsoft YaHei"
# paragraph.add_run("”")
# else:
# # self.document.add_picture()
# ...

# return
from typing import Optional
from diceutils.renderer._models import Renderer, Message

from pathlib import Path

from docx.enum.style import WD_STYLE_TYPE
from docx import Document
from docx.shared import Pt
from docx.oxml.ns import qn


class DocxRenderer(Renderer):

def __init__(self) -> None:
self.document = Document()
self._add_chinese_font_style("微软雅黑")
self._add_chinese_font_style("SimSun", "SimSun-ExtB")

def _add_chinese_font_style(self, style_name: str, font_name: Optional[str] = None, font_size: float = 11.0):
font_name = font_name or style_name
style_song = self.document.styles.add_style(style_name, WD_STYLE_TYPE.CHARACTER)
style_song.font.name = font_name
style_song.font.size = Pt(font_size)
style_song.element.rPr.rFonts.set(qn('w:eastAsia'), font_name)

def export(self, filename: str) -> Path:
filepath = Path("./exports/" + filename + ".docx").resolve()

filepath.parent.mkdir(parents=True, exist_ok=True)
self.document.save(str(filepath))

return filepath

def render_message(self, message: Message) -> None:
paragraph = self.document.add_paragraph()
for element in message.elements:
if element.type == "text":
if element.tag == "act":
run = paragraph.add_run(element.content, style="SimSun")

elif element.tag == "outside":
content = element.content.strip("()()")
run = paragraph.add_run(content, style="SimSun")

elif element.tag == "speak":
run = paragraph.add_run(f"“{element.content}”", style="微软雅黑")

else:
# self.document.add_picture()
...

return

0 comments on commit f0cf051

Please sign in to comment.