# 02. Kỹ thuật Bóc tách Dữ liệu Vật phẩm từ Clipboard (Clipboard Item Parser)

> **Mục tiêu**: Phân tích cú pháp văn bản thẻ vật phẩm (Item Tooltip Text) trong POE2 thành cấu trúc dữ liệu JSON chuẩn hóa phục vụ tra cứu thị trường.  
> **Cơ chế Kích hoạt**: Tổ hợp phím tắt `Ctrl + C` khi di chuột lên vật phẩm trong game.

---

## 1. Cấu trúc Chuẩn của Thẻ Văn bản Vật phẩm POE2 (v0.5.5)

Khi người chơi nhấn `Ctrl + C` lên một vật phẩm, client Path of Exile 2 tạo ra một chuỗi văn bản phân tách bằng các dòng gạch ngang `--------`:

```text
Item Class: Quarterstaves
Rarity: Rare
Gale Weaver
Steelpoint Quarterstaff
--------
Quarterstaff
Physical Damage: 45-92 (augmented)
Elemental Damage: 28-56 (Cold)
Critical Strike Chance: 7.50%
Attacks per Second: 1.45
Quality: +20% (augmented)
--------
Requirements:
Level: 58
Dex: 110
Int: 85
--------
Sockets: S-S-S
--------
Item Level: 74
--------
18% increased Elemental Damage with Weapons (implicit)
--------
+85% increased Physical Damage
Adds 28 to 56 Cold Damage
+24% to Global Critical Strike Multiplier
+38% to Fire Resistance
+120 to maximum Spirit
--------
Corrupted
```

---

## 2. Máy Trạng thái Phân tích Cú pháp (Parsing State Machine)

Để xử lý văn bản một cách linh hoạt, không bị lỗi khi cấu trúc trang bị thay đổi:

```mermaid
stateDiagram-v2
    [*] --> ReadHeader: Dòng đầu tiên
    ReadHeader --> ParseProperties: Đọc Item Class & Rarity
    ParseProperties --> ParseRequirements: Gặp dòng gạch ngang "--------"
    ParseRequirements --> ParseSockets: Gặp "Requirements:"
    ParseSockets --> ParseItemLevel: Gặp "Sockets:"
    ParseItemLevel --> ParseImplicitMods: Gặp "Item Level:"
    ParseImplicitMods --> ParseExplicitMods: Gặp các dòng có nhãn "(implicit)"
    ParseExplicitMods --> ParseStatusFlags: Đọc toàn bộ các dòng thuộc tính
    ParseStatusFlags --> [*]: Gặp "Corrupted", "Mirrored" hoặc Hết file
```

---

## 3. Mã nguồn Triển khai Mẫu: Python POE2ItemParser

Dưới đây là module hoàn chỉnh có khả năng bóc tách chính xác toàn bộ các dòng thuộc tính của POE2:

```python
"""
POE2 Clipboard Item Parser (v0.5.5)
Extracts item properties, tiers, and affixes from clipboard text.
"""

import re
from typing import Dict, List, Optional
from pydantic import BaseModel, Field

class ParsedItem(BaseModel):
    item_class: str = Field(default="", description="Loại trang bị: Bows, Crossbows, Quarterstaves...")
    rarity: str = Field(default="Normal", description="Normal, Magic, Rare, Unique, Currency, Gem")
    name: str = Field(default="", description="Tên riêng của trang bị (nếu là Rare/Unique)")
    base_type: str = Field(default="", description="Loại trang bị gốc: Steelpoint Quarterstaff...")
    quality: int = 0
    item_level: int = 0
    physical_dps: float = 0.0
    elemental_dps: float = 0.0
    total_dps: float = 0.0
    implicit_mods: List[str] = []
    explicit_mods: List[str] = []
    is_corrupted: bool = False
    raw_properties: Dict[str, str] = {}

class POE2ItemParser:
    RE_CLASS = re.compile(r"^Item Class:\s*(.+)$")
    RE_RARITY = re.compile(r"^Rarity:\s*(.+)$")
    RE_QUALITY = re.compile(r"^Quality:\s*\+(\d+)%")
    RE_ILVL = re.compile(r"^Item Level:\s*(\d+)$")
    RE_PHYS_DMG = re.compile(r"^Physical Damage:\s*(\d+)-(\d+)")
    RE_APS = re.compile(r"^Attacks per Second:\s*([\d\.]+)")

    @classmethod
    def parse(cls, raw_text: str) -> Optional[ParsedItem]:
        if not raw_text or "Item Class:" not in raw_text:
            return None

        lines = [line.strip() for line in raw_text.strip().splitlines()]
        sections = [sec.strip().splitlines() for sec in raw_text.split("--------")]

        item = ParsedItem()

        # 1. Bóc tách Header (Section 0)
        header_lines = sections[0]
        for line in header_lines:
            m_class = cls.RE_CLASS.match(line)
            if m_class: item.item_class = m_class.group(1).strip()
            
            m_rarity = cls.RE_RARITY.match(line)
            if m_rarity: item.rarity = m_rarity.group(1).strip()

        if item.rarity in ["Rare", "Unique"] and len(header_lines) >= 4:
            item.name = header_lines[2].strip()
            item.base_type = header_lines[3].strip()
        elif len(header_lines) >= 3:
            item.base_type = header_lines[2].strip()

        # 2. Bóc tách các phần còn lại
        min_phys, max_phys, aps = 0, 0, 1.0
        for section in sections[1:]:
            for line in section:
                # Kiểm tra Item Level
                m_ilvl = cls.RE_ILVL.match(line)
                if m_ilvl:
                    item.item_level = int(m_ilvl.group(1))
                    continue

                # Kiểm tra Quality
                m_qual = cls.RE_QUALITY.match(line)
                if m_qual:
                    item.quality = int(m_qual.group(1))
                    continue

                # Kiểm tra Sát thương vật lý
                m_phys = cls.RE_PHYS_DMG.match(line)
                if m_phys:
                    min_phys, max_phys = int(m_phys.group(1)), int(m_phys.group(2))
                    continue

                # Kiểm tra Tốc độ đánh
                m_aps = cls.RE_APS.match(line)
                if m_aps:
                    aps = float(m_aps.group(1))
                    continue

                # Kiểm tra cờ trạng thái
                if line == "Corrupted":
                    item.is_corrupted = True
                    continue

                # Phân loại Implicit vs Explicit Mods
                if "(implicit)" in line:
                    clean_mod = line.replace("(implicit)", "").strip()
                    item.implicit_mods.append(clean_mod)
                elif not any(line.startswith(k) for k in ["Requirements:", "Level:", "Dex:", "Int:", "Str:", "Sockets:"]):
                    item.explicit_mods.append(line)

        # Tính toán sát thương DPS
        if max_phys > 0 and aps > 0:
            item.physical_dps = round(((min_phys + max_phys) / 2.0) * aps, 1)
            item.total_dps = item.physical_dps

        return item
```
