initial commit

This commit is contained in:
2026-03-22 05:17:01 +00:00
parent 9a41fa4ef2
commit ef6e94958a
155 changed files with 43839 additions and 0 deletions

View File

@@ -0,0 +1,90 @@
# Data Template Reference
## Dungeon Data File Structure
Each dungeon is a TypeScript file in `src/data/` that exports a `DungeonData` object.
```typescript
import type { DungeonData } from "@/types/dungeon";
export const dungeonSlug: DungeonData = {
id: "dungeon-slug",
name: "Dungeon Name",
descriptionHtml: `Brief layout with <span class="wing-badge wing-arcane">Wing</span> badges if applicable`,
headerImage: "/assets/dungeon_name_header.jpg",
icon: "/assets/dungeon_dungeon_name.jpg",
sections: [
{
type: "trash",
data: {
header: "Area Name — Trash Before BossName",
content: [
{ type: "quickRef", data: { html: `<strong>Environment:</strong> Brief description.` } },
{
type: "mob",
data: {
name: "Mob Name (Immune to CC)",
nameHtml: `Mob Name <span style="color: var(--accent-red); font-size: 12px;">(Immune to CC)</span>`,
tankRelevant: true,
abilitiesHtml: [
`<a href="https://www.wowhead.com/beta/spell=123456/ability-name">Ability Name</a> — TANK: What to do.`,
],
},
},
],
},
},
{
type: "boss",
data: {
bossNumber: "Boss 1 · Area/Wing",
name: "Boss Name",
subtitle: "One-line fight summary",
image: "/assets/boss_name_boss_fight.jpg",
abilities: [
{
name: "Ability Name",
wowheadUrl: "https://www.wowhead.com/beta/spell=123456/ability-name",
role: "tank",
importance: "tank-important",
descriptionHtml: `<ul><li><span class="warn">Warning.</span></li></ul>`,
},
{
name: "Ability Name",
wowheadUrl: "https://www.wowhead.com/beta/spell=123457/ability-name",
role: "everyone",
importance: "everyone-important",
descriptionHtml: `<ul><li>Description.</li></ul>`,
},
],
},
},
{ type: "divider" },
],
};
```
## Registering in index.ts
```typescript
import { dungeonSlug } from "./dungeon-slug";
// In dungeonList (alphabetical):
{ id: "dungeon-slug", name: "Dungeon Name", icon: "/assets/dungeon_dungeon_name.jpg" },
// In dungeonDataMap:
"dungeon-slug": dungeonSlug,
```
## Roles & Importance
| Role | Importance | Visual |
|------|-----------|--------|
| `"tank"` | `"tank-important"` | Blue left border + badge |
| `"everyone"` | `"everyone-important"` | Amber left border + badge |
| `"healer"` | `"healer-important"` | Green left border + badge |
| `"dps"` | `null` | Red badge, no border |
## HTML Classes: `warn` (red), `tip` (green), `wing-badge wing-arcane/void/light`
## Wowhead: `<a href="https://www.wowhead.com/beta/spell={ID}/{slug}">Name</a>`

View File

@@ -0,0 +1,145 @@
---
name: parse-dungeon
description: Parses a method.gg dungeon HTML file and adds it to the M+ dungeon reference webapp. Use when the user wants to add a new dungeon, parse a dungeon HTML file, or generate guide content from a dungeon file in the dungeons/ directory.
---
# Parse Dungeon Guide
Extracts boss info, trash mechanics, and assets from a method.gg dungeon HTML file, then adds the dungeon as a new TypeScript data file in the React webapp.
## Project Structure
This is a **React + Vite + shadcn/ui** app. Dungeon content lives in typed data files, not raw HTML.
- `src/data/*.ts` — one file per dungeon (e.g., `nexus-point-xenas.ts`)
- `src/data/index.ts` — ordered dungeon list + data map exports
- `src/types/dungeon.ts` — TypeScript types for all data structures
- `src/components/dungeon/` — React components that render dungeon data
- `public/assets/` — all images (dungeon headers, boss screenshots, dungeon icons)
- `parse_dungeon.py` — Python script to extract raw data from method.gg HTML
## Step 1: Parse the HTML
Run the extraction script against the target dungeon file:
```bash
source ~/.zshrc && poetry run python parse_dungeon.py dungeons/<filename>.html
```
If the script doesn't accept a CLI argument yet, update `parse_dungeon.py` to accept one via `sys.argv[1]`.
The script extracts:
- **Section content** — full text of each `guide-section-title` section
- **Boss images** — `img.section-boss` saved to `public/assets/`
- **Mob icons** — `div.mob-icon img` saved to `public/assets/`
- **Dungeon header image** — `img.boss--render` saved to `public/assets/`
- **Spell links** — all wowhead spell references with context
**Important:** Images must be saved to `public/assets/`, not `assets/`.
## Step 2: Identify content
From the parsed output, identify:
1. **Bosses** — sections ending in "Boss Fight" (e.g., "Vexamus Boss Fight")
2. **Trash sections** — sections ending in "Trash" (e.g., "Vexamus Trash")
3. **Mob names**`h4.mob-name` elements within trash sections
4. **Role-specific mechanics** — identify abilities by role:
- **Tank:** tank, taunt, face away, defensive, positioning, aggro, kite, knockback, damage taken increase, swap, active mitigation
- **Healer:** heavy damage, healing absorb, dispel, disease, poison, curse, magic debuff, raid damage, heal check, ticking DoT
- **DPS:** interrupt, CC, burn target, add priority, enrage timer, soothe, purge, DPS check
- **Everyone:** dodge, soak, spread, stack, move, kill order, frontal, AoE avoidance
## Step 3: Create the data file
Create a new TypeScript file at `src/data/<dungeon-slug>.ts` following the types in `src/types/dungeon.ts`.
Reference an existing dungeon data file (e.g., `src/data/nexus-point-xenas.ts`) for the exact structure.
The file must:
- Import and conform to `DungeonData` from `@/types/dungeon`
- Export a named constant (camelCase of slug, e.g., `pitOfSaron`)
- Use HTML strings for rich content (Wowhead links, `<span class="warn">`, etc.) via template literals
- Image paths use `/assets/` prefix (served from `public/assets/`)
Key rules:
- **Brevity is critical.** 1-2 sentences max per ability. No paragraphs.
- **Include all role-relevant mechanics.** Every boss should have cards for each role that has a distinct responsibility.
- **Use role tags:** `tank`, `healer`, `dps`, `everyone` to classify each ability card.
- **Role-importance mapping:**
- `role: "tank"``importance: "tank-important"` (sky-blue left border)
- `role: "healer"``importance: "healer-important"` (green left border)
- `role: "everyone"``importance: "everyone-important"` or `null` (amber left border or none)
- `role: "dps"``importance: null` (red badge, no left border)
- **Ability card order per boss:** tank first, then healer, then everyone, then dps.
- **Ability descriptions use bullet points** — HTML `<ul><li>...</li></ul>` in the `descriptionHtml` field.
- **Mark CC-immune mobs** with `nameHtml` containing a red "(Immune to CC)" span.
- **Highlight warnings** with `<span class="warn">` and tips with `<span class="tip">`.
- **Environment/quick-ref** content uses `{ type: "quickRef", data: { html: "..." } }` in trash section content arrays.
## Step 4: Register the dungeon
Update `src/data/index.ts`:
1. Add the import for the new dungeon data file
2. If replacing a disabled entry in `dungeonList`, remove the `disabled: true` flag
3. Add the dungeon to `dungeonDataMap`
## Step 5: Verify
```bash
make dev
```
- The new dungeon tab appears in the sidebar and is clickable
- All boss images render correctly from `public/assets/`
- Every boss has a section with abilities
- Tank-critical abilities are not missed
- Wowhead tooltip links work (hover to verify)
- Text is concise — cut any filler words
## Data Structure Reference
See `src/types/dungeon.ts` for full types. Key structures:
```typescript
// Dungeon data file exports:
export const dungeonSlug: DungeonData = {
id: "dungeon-slug", // URL hash
name: "Dungeon Name",
descriptionHtml: "...", // May contain wing badge spans
headerImage: "/assets/dungeon_header.jpg",
icon: "/assets/dungeon_icon.jpg",
sections: [
{ type: "trash", data: { header: "...", content: [...] } },
{ type: "boss", data: { bossNumber: "Boss 1 · Wing", name: "...", ... } },
{ type: "divider" },
// repeat...
],
};
```
## Wowhead Spell Links
All ability names must be wowhead links in the HTML strings.
Format: `<a href="https://www.wowhead.com/beta/spell={ID}/{slug}">Ability Name</a>`
- The spell ID comes from the parser output (SPELLS WITH CONTEXT section).
- The slug is the spell name lowercased with hyphens.
- The React app handles tooltip initialization automatically via `useWowheadTooltips` hook.
## Writing Style
- **Max 1-2 sentences per ability point.** No paragraphs.
- **Use bullet points** — `<ul><li>...</li></ul>` in descriptionHtml.
- **Lead with the ability name** as a wowhead link, followed by a dash and the description.
- **Ability order:** tank, healer, everyone, dps in each boss's abilities array.
- **Tank actions** should be explicit: "Face away", "Use defensive", "Pick up adds", "Position at edge".
- **Healer actions** should be explicit: "Dispel immediately", "Heavy healing required", "Heal absorb — heal through before next cast".
- **DPS actions** should be explicit: "Interrupt on cooldown", "Burn adds before X", "Soothe enrage".
- **Use `warn` for dangers** any role MUST react to.
- **Use `tip` for helpful tricks.**
- **Include all role-relevant mechanics.** Tag each ability with the most specific role responsible for handling it.
- Every boss should have at minimum one `tank-important` card if there is ANY tank-specific mechanic.
- Include healer and DPS cards where mechanics specifically require those roles to act.

7
.dockerignore Normal file
View File

@@ -0,0 +1,7 @@
node_modules
dist
.git
.claude
dungeons
*.md
Makefile

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.DS_Store
__pycache__/
*.pyc
node_modules/
dist/

59
CLAUDE.md Normal file
View File

@@ -0,0 +1,59 @@
# Midnight S1 Tank Guide
WoW Midnight Season 1 M+ tanking quick-reference webapp. Built to be read at a glance on a second monitor during gameplay.
## Stack
React 19 + Vite + TypeScript + Tailwind v4 + shadcn/ui. No router — hash-based dungeon switching.
## Commands
- `make dev` — start dev server on port 8000
- `make build` — production build
- `source ~/.zshrc` before any node/npm/npx command (lazy nvm)
## Design Rules
- **Dark only.** Deep abyss navy (`#050810`). No light mode, no toggle.
- **Frosted glass cards.** Use `.frost` class (backdrop-blur + semi-transparent bg). Never plain bg.
- **Square edges.** All radii are 0. No rounded corners anywhere.
- **Colours that pop.** Cyan (`#22d3ee`), sky blue (`#38bdf8`), purple (`#a78bfa`), amber (`#fbbf24`), red (`#f87171`), green (`#34d399`).
- **Readable at a glance.** Body text `text-base` minimum. Headers bump up. Bullet spacing `mb-2.5`.
- **Centre-aligned content.** Main container `max-w-7xl mx-auto`. 2-col grid on `lg:`.
- **Font:** Geist Variable.
## Role Colours
- Tank: `#38bdf8` (sky blue) — `text-tank`, `border-l-tank`
- Healer: `#34d399` (green) — `text-healer`
- DPS: `#f87171` (red) — `text-dps`
- Everyone: `#fbbf24` (amber) — `text-everyone`
## Project Layout
```
src/
data/*.ts — one file per dungeon, typed DungeonData exports
data/index.ts — dungeonList (sidebar order) + dungeonDataMap
types/dungeon.ts — all shared types
components/
dungeon/ — DungeonPage, BossHeader, AbilityCard, MobCard, TrashSection, etc.
layout/ — AppSidebar
ui/ — shadcn primitives (badge, button, card, etc.)
index.css — theme vars, .frost utility, wowhead overrides
public/assets/ — all images (dungeon headers, boss screenshots, icons)
dungeons/ — raw method.gg HTML source files
```
## Adding a Dungeon
Use `/parse-dungeon` skill. It runs `parse_dungeon.py` against a `dungeons/*.html` file, creates `src/data/<slug>.ts`, and registers it in `src/data/index.ts`.
## Content Rules
- HTML strings in data files (wowhead links, `<span class="warn">`, `<span class="tip">`).
- Ability card order per boss: tank first, then healer, then everyone, then dps.
- Include healer and DPS-specific mechanics — not just tank-focused content.
- DPS cards use `role: "dps"` with `importance: null` (red badge, no left border).
- Max 1-2 sentences per ability. No paragraphs.
- CC-immune mobs get red "(Immune to CC)" span in `nameHtml`.

11
Caddyfile Normal file
View File

@@ -0,0 +1,11 @@
:8080 {
root * /srv
file_server
try_files {path} /index.html
header {
X-Content-Type-Options nosniff
X-Frame-Options DENY
Referrer-Policy strict-origin-when-cross-origin
}
encode gzip
}

14
Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
# ── Build stage ──────────────────────────────────────────
FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts
COPY . .
RUN npm run build
# ── Runtime stage ────────────────────────────────────────
FROM caddy:2-alpine
COPY Caddyfile /etc/caddy/Caddyfile
COPY --from=build /app/dist /srv
USER nobody
EXPOSE 8080

19
Makefile Normal file
View File

@@ -0,0 +1,19 @@
.PHONY: dev stop build install extract
PORT ?= 8000
dev:
npx vite --port $(PORT) --host &
@echo "Dev server running at http://localhost:$(PORT)"
stop:
@-pkill -f "vite --port $(PORT)" 2>/dev/null && echo "Stopped dev server" || echo "No dev server running"
build:
npm run build
install:
npm install
extract:
node scripts/extract-data.mjs

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
assets/dungeon_skyreach.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
assets/l_ura_boss_fight.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
assets/skyreach_header.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

25
components.json Normal file
View File

@@ -0,0 +1,25 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "base-nova",
"rsc": false,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/index.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"rtl": false,
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"menuColor": "default",
"menuAccent": "subtle",
"registries": {}
}

6
docker-compose.yml Normal file
View File

@@ -0,0 +1,6 @@
services:
app:
build: .
ports:
- "8080:8080"
restart: unless-stopped

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

3413
dungeons/pit-of-saron.html Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

3413
dungeons/skyreach.html Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

23
eslint.config.js Normal file
View File

@@ -0,0 +1,23 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
},
])

15
index.html Normal file
View File

@@ -0,0 +1,15 @@
<!doctype html>
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Midnight S1 — Tank Guide</title>
<script>var wowhead_tooltips = {"colorlinks": false, "iconizelinks": true, "iconSize": "small", "renamelinks": false}</script>
<script src="https://wow.zamimg.com/js/tooltips.js"></script>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

2471
index.original.html Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,58 @@
---
created: 11-03-2026
---
# midnight_season_1_tanking_summaries.md
## download dungeon pages
```
monolith https://www.method.gg/guides/dungeons/nexus-point-xenas -o nexus-point.html
```
## prompt
```
I want to create a simple webapp that I can use while playing World of Warcraft
The goal should be a simple reference guide for each dungeon that i can use while tanking
There are 3 roles:
Tank - The role is to keep aggro and position enemies for the group to kill
DPS - Kill things
Healer - Keep everyone alive
The webapp should be dark, with dark navy blue themes. Colours/accents should pop and be bright but pleasant to look at.
The goal is for me (as a tank):
- I should know for each boss what I need to do
- This could be positioning
- Or enemy attacks I need to look out for
- Or defensives/abilities I need to use
Basically anything relating to tanking that I need to know in order to successfully complete the dungeon
There should be at the top of the page a selection for each dungeon. When clicked it should take me to the dungeon page
It should show me the bosses, and then key abilities/things I need ot pay attention to
The goal here is brevity. I need to know exactly what in the least amount of words WITHOUT SKIPPING ANYTHING IMPORTANT. We shouldn't write paragraphs when 1-2 sentences would do. It is imperative we do not miss important things as a tank I need to do. This is going ot be the main reference of what to do in the dungeons
The format should be big so it's easy to see at a glance (with cards) You should use your best judgement to make it easy to read while playing on a second screen and look nice. The app should look modern and sleek.
For each dungeon it should all be on page, with headers for bosses
You should use the assets contained within each dungeon file
Let's start with @dungeons/nexus-point-xenas.html
All the information you need is in there
You should
1. Identify the bosses in the dungeon
2. Identify any assets you need
3. Extract any and all relevant tanking information for bosses
```

8115
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

42
package.json Normal file
View File

@@ -0,0 +1,42 @@
{
"name": "midnight-s1-tank-guide",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@base-ui/react": "^1.3.0",
"@fontsource-variable/geist": "^5.2.8",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.577.0",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"shadcn": "^4.0.5",
"tailwind-merge": "^3.5.0",
"tw-animate-css": "^1.4.0"
},
"devDependencies": {
"@eslint/js": "^9.39.4",
"@tailwindcss/vite": "^4.2.1",
"@types/jsdom": "^28.0.0",
"@types/node": "^24.12.0",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^4.5.0",
"eslint": "^9.39.4",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.4.0",
"jsdom": "^28.1.0",
"tailwindcss": "^4.2.1",
"typescript": "~5.9.3",
"typescript-eslint": "^8.56.1",
"vite": "^6.0.0"
}
}

175
parse_dungeon.py Normal file
View File

@@ -0,0 +1,175 @@
"""Parse dungeon HTML files from method.gg to extract boss info, abilities, and assets.
Usage: poetry run python parse_dungeon.py dungeons/<filename>.html
"""
import base64
import os
import re
import sys
from lxml import etree
def slugify(text: str) -> str:
return re.sub(r"[^a-zA-Z0-9]+", "_", text).strip("_").lower()
def save_base64_image(src: str, filename: str) -> str | None:
"""Extract base64 image data and save to file. Returns filename if saved."""
if not src.startswith("data:image/"):
return None
match = re.match(r"data:image/(\w+);base64,(.*)", src)
if not match:
return None
ext = match.group(1)
if ext == "jpeg":
ext = "jpg"
data = base64.b64decode(match.group(2))
full_path = f"assets/{filename}.{ext}"
with open(full_path, "wb") as f:
f.write(data)
print(f" Saved: {full_path} ({len(data)} bytes)")
return full_path
def main(html_file: str) -> None:
# Derive dungeon slug from filename
dungeon_slug = os.path.splitext(os.path.basename(html_file))[0]
dungeon_slug_clean = slugify(dungeon_slug)
parser = etree.HTMLParser()
tree = etree.parse(html_file, parser)
root = tree.getroot()
os.makedirs("assets", exist_ok=True)
# ── Dungeon title ──
titles = root.xpath('//h1[contains(@class, "main-title")]')
for t in titles:
print("=== DUNGEON TITLE ===")
print(etree.tostring(t, method="text", encoding="unicode").strip())
# ── Guide sections ──
sections = root.xpath('//div[contains(@class, "guide-section-title")]/h2')
print("\n=== GUIDE SECTIONS ===")
for s in sections:
print(f" {etree.tostring(s, method='text', encoding='unicode').strip()}")
# ── Full section content ──
print("\n" + "=" * 80)
print("=== FULL SECTION CONTENT ===")
print("=" * 80)
guide_sections = root.xpath('//div[contains(@class, "guide-section-title")]')
for section_title_div in guide_sections:
h2 = section_title_div.find(".//h2")
if h2 is None:
continue
title = etree.tostring(h2, method="text", encoding="unicode").strip()
parent = section_title_div.getparent()
if parent is None:
continue
full_text = etree.tostring(parent, method="text", encoding="unicode").strip()
full_text = re.sub(r"\n\s*\n", "\n\n", full_text)
full_text = re.sub(r" +", " ", full_text)
print(f"\n{'' * 60}")
print(f"SECTION: {title}")
print(f"{'' * 60}")
print(full_text[:3000])
if len(full_text) > 3000:
print(f"... [truncated, total {len(full_text)} chars]")
# ── Boss images ──
print("\n" + "=" * 80)
print("=== EXTRACTING BOSS IMAGES ===")
print("=" * 80)
boss_images = root.xpath('//img[contains(@class, "section-boss")]')
for i, img in enumerate(boss_images):
src = img.get("src", "")
ancestor = img
section_name = f"boss_{i}"
while ancestor is not None:
ancestor = ancestor.getparent()
if ancestor is not None:
title_el = ancestor.find('.//div[@class="guide-section-title"]//h2')
if title_el is not None:
section_name = slugify(
etree.tostring(title_el, method="text", encoding="unicode").strip()
)
break
save_base64_image(src, section_name)
# ── Dungeon header image ──
header_imgs = root.xpath('//img[contains(@class, "boss--render")]')
for img in header_imgs:
src = img.get("src", "")
save_base64_image(src, f"{dungeon_slug_clean}_header")
# ── Mob icons ──
mob_icons = root.xpath('//div[contains(@class, "mob-icon")]/img')
for i, icon_img in enumerate(mob_icons):
src = icon_img.get("src", "")
mob_header_div = icon_img.getparent().getparent()
name_el = (
mob_header_div.find('.//h4[@class="mob-name"]')
if mob_header_div is not None
else None
)
if name_el is not None:
mob_name = etree.tostring(name_el, method="text", encoding="unicode").strip()
mob_name_clean = slugify(mob_name)
else:
mob_name_clean = f"mob_{i}"
save_base64_image(src, f"icon_{mob_name_clean}")
# ── Wowhead spell links ──
print("\n" + "=" * 80)
print("=== SPELLS WITH CONTEXT ===")
print("=" * 80)
spell_links = root.xpath('//a[contains(@href, "wowhead.com") and contains(@href, "spell")]')
for link in spell_links:
href = link.get("href", "")
spell_name = etree.tostring(link, method="text", encoding="unicode").strip()
parent = link.getparent()
if parent is not None:
context = etree.tostring(parent, method="text", encoding="unicode").strip()
context = re.sub(r"\s+", " ", context)[:300]
else:
context = ""
spell_id_match = re.search(r"spell=(\d+)", href)
spell_id = spell_id_match.group(1) if spell_id_match else "unknown"
print(f"\n Spell: {spell_name} (ID: {spell_id})")
print(f" Context: {context}")
# ── Dungeon selector images ──
print("\n" + "=" * 80)
print("=== DUNGEON SELECTOR IMAGES ===")
print("=" * 80)
dungeon_links = root.xpath('//a[contains(@class, "boss-guide-link")]')
for link in dungeon_links:
img = link.find(".//img")
if img is not None:
alt = img.get("alt", "")
src = img.get("src", "")
dungeon_name = re.sub(r" Mythic\+ Guide", "", alt)
dungeon_name_clean = slugify(dungeon_name)
saved = save_base64_image(src, f"dungeon_{dungeon_name_clean}")
if saved:
print(f" {alt}")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: poetry run python parse_dungeon.py dungeons/<filename>.html")
sys.exit(1)
main(sys.argv[1])

162
poetry.lock generated Normal file
View File

@@ -0,0 +1,162 @@
# This file is automatically @generated by Poetry 2.2.1 and should not be changed by hand.
[[package]]
name = "lxml"
version = "6.0.2"
description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API."
optional = false
python-versions = ">=3.8"
groups = ["main"]
files = [
{file = "lxml-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e77dd455b9a16bbd2a5036a63ddbd479c19572af81b624e79ef422f929eef388"},
{file = "lxml-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5d444858b9f07cefff6455b983aea9a67f7462ba1f6cbe4a21e8bf6791bf2153"},
{file = "lxml-6.0.2-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f952dacaa552f3bb8834908dddd500ba7d508e6ea6eb8c52eb2d28f48ca06a31"},
{file = "lxml-6.0.2-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:71695772df6acea9f3c0e59e44ba8ac50c4f125217e84aab21074a1a55e7e5c9"},
{file = "lxml-6.0.2-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:17f68764f35fd78d7c4cc4ef209a184c38b65440378013d24b8aecd327c3e0c8"},
{file = "lxml-6.0.2-cp310-cp310-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:058027e261afed589eddcfe530fcc6f3402d7fd7e89bfd0532df82ebc1563dba"},
{file = "lxml-6.0.2-cp310-cp310-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8ffaeec5dfea5881d4c9d8913a32d10cfe3923495386106e4a24d45300ef79c"},
{file = "lxml-6.0.2-cp310-cp310-manylinux_2_31_armv7l.whl", hash = "sha256:f2e3b1a6bb38de0bc713edd4d612969dd250ca8b724be8d460001a387507021c"},
{file = "lxml-6.0.2-cp310-cp310-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d6690ec5ec1cce0385cb20896b16be35247ac8c2046e493d03232f1c2414d321"},
{file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2a50c3c1d11cad0ebebbac357a97b26aa79d2bcaf46f256551152aa85d3a4d1"},
{file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:3efe1b21c7801ffa29a1112fab3b0f643628c30472d507f39544fd48e9549e34"},
{file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:59c45e125140b2c4b33920d21d83681940ca29f0b83f8629ea1a2196dc8cfe6a"},
{file = "lxml-6.0.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:452b899faa64f1805943ec1c0c9ebeaece01a1af83e130b69cdefeda180bb42c"},
{file = "lxml-6.0.2-cp310-cp310-win32.whl", hash = "sha256:1e786a464c191ca43b133906c6903a7e4d56bef376b75d97ccbb8ec5cf1f0a4b"},
{file = "lxml-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:dacf3c64ef3f7440e3167aa4b49aa9e0fb99e0aa4f9ff03795640bf94531bcb0"},
{file = "lxml-6.0.2-cp310-cp310-win_arm64.whl", hash = "sha256:45f93e6f75123f88d7f0cfd90f2d05f441b808562bf0bc01070a00f53f5028b5"},
{file = "lxml-6.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:13e35cbc684aadf05d8711a5d1b5857c92e5e580efa9a0d2be197199c8def607"},
{file = "lxml-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b1675e096e17c6fe9c0e8c81434f5736c0739ff9ac6123c87c2d452f48fc938"},
{file = "lxml-6.0.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:8ac6e5811ae2870953390452e3476694196f98d447573234592d30488147404d"},
{file = "lxml-6.0.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5aa0fc67ae19d7a64c3fe725dc9a1bb11f80e01f78289d05c6f62545affec438"},
{file = "lxml-6.0.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:de496365750cc472b4e7902a485d3f152ecf57bd3ba03ddd5578ed8ceb4c5964"},
{file = "lxml-6.0.2-cp311-cp311-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:200069a593c5e40b8f6fc0d84d86d970ba43138c3e68619ffa234bc9bb806a4d"},
{file = "lxml-6.0.2-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7d2de809c2ee3b888b59f995625385f74629707c9355e0ff856445cdcae682b7"},
{file = "lxml-6.0.2-cp311-cp311-manylinux_2_31_armv7l.whl", hash = "sha256:b2c3da8d93cf5db60e8858c17684c47d01fee6405e554fb55018dd85fc23b178"},
{file = "lxml-6.0.2-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:442de7530296ef5e188373a1ea5789a46ce90c4847e597856570439621d9c553"},
{file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:2593c77efde7bfea7f6389f1ab249b15ed4aa5bc5cb5131faa3b843c429fbedb"},
{file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:3e3cb08855967a20f553ff32d147e14329b3ae70ced6edc2f282b94afbc74b2a"},
{file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:2ed6c667fcbb8c19c6791bbf40b7268ef8ddf5a96940ba9404b9f9a304832f6c"},
{file = "lxml-6.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b8f18914faec94132e5b91e69d76a5c1d7b0c73e2489ea8929c4aaa10b76bbf7"},
{file = "lxml-6.0.2-cp311-cp311-win32.whl", hash = "sha256:6605c604e6daa9e0d7f0a2137bdc47a2e93b59c60a65466353e37f8272f47c46"},
{file = "lxml-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e5867f2651016a3afd8dd2c8238baa66f1e2802f44bc17e236f547ace6647078"},
{file = "lxml-6.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:4197fb2534ee05fd3e7afaab5d8bfd6c2e186f65ea7f9cd6a82809c887bd1285"},
{file = "lxml-6.0.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a59f5448ba2ceccd06995c95ea59a7674a10de0810f2ce90c9006f3cbc044456"},
{file = "lxml-6.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e8113639f3296706fbac34a30813929e29247718e88173ad849f57ca59754924"},
{file = "lxml-6.0.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:a8bef9b9825fa8bc816a6e641bb67219489229ebc648be422af695f6e7a4fa7f"},
{file = "lxml-6.0.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:65ea18d710fd14e0186c2f973dc60bb52039a275f82d3c44a0e42b43440ea534"},
{file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c371aa98126a0d4c739ca93ceffa0fd7a5d732e3ac66a46e74339acd4d334564"},
{file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:700efd30c0fa1a3581d80a748157397559396090a51d306ea59a70020223d16f"},
{file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c33e66d44fe60e72397b487ee92e01da0d09ba2d66df8eae42d77b6d06e5eba0"},
{file = "lxml-6.0.2-cp312-cp312-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:90a345bbeaf9d0587a3aaffb7006aa39ccb6ff0e96a57286c0cb2fd1520ea192"},
{file = "lxml-6.0.2-cp312-cp312-manylinux_2_31_armv7l.whl", hash = "sha256:064fdadaf7a21af3ed1dcaa106b854077fbeada827c18f72aec9346847cd65d0"},
{file = "lxml-6.0.2-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:fbc74f42c3525ac4ffa4b89cbdd00057b6196bcefe8bce794abd42d33a018092"},
{file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6ddff43f702905a4e32bc24f3f2e2edfe0f8fde3277d481bffb709a4cced7a1f"},
{file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:6da5185951d72e6f5352166e3da7b0dc27aa70bd1090b0eb3f7f7212b53f1bb8"},
{file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:57a86e1ebb4020a38d295c04fc79603c7899e0df71588043eb218722dabc087f"},
{file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:2047d8234fe735ab77802ce5f2297e410ff40f5238aec569ad7c8e163d7b19a6"},
{file = "lxml-6.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6f91fd2b2ea15a6800c8e24418c0775a1694eefc011392da73bc6cef2623b322"},
{file = "lxml-6.0.2-cp312-cp312-win32.whl", hash = "sha256:3ae2ce7d6fedfb3414a2b6c5e20b249c4c607f72cb8d2bb7cc9c6ec7c6f4e849"},
{file = "lxml-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:72c87e5ee4e58a8354fb9c7c84cbf95a1c8236c127a5d1b7683f04bed8361e1f"},
{file = "lxml-6.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:61cb10eeb95570153e0c0e554f58df92ecf5109f75eacad4a95baa709e26c3d6"},
{file = "lxml-6.0.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:9b33d21594afab46f37ae58dfadd06636f154923c4e8a4d754b0127554eb2e77"},
{file = "lxml-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c8963287d7a4c5c9a432ff487c52e9c5618667179c18a204bdedb27310f022f"},
{file = "lxml-6.0.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:1941354d92699fb5ffe6ed7b32f9649e43c2feb4b97205f75866f7d21aa91452"},
{file = "lxml-6.0.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb2f6ca0ae2d983ded09357b84af659c954722bbf04dea98030064996d156048"},
{file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:eb2a12d704f180a902d7fa778c6d71f36ceb7b0d317f34cdc76a5d05aa1dd1df"},
{file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:6ec0e3f745021bfed19c456647f0298d60a24c9ff86d9d051f52b509663feeb1"},
{file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:846ae9a12d54e368933b9759052d6206a9e8b250291109c48e350c1f1f49d916"},
{file = "lxml-6.0.2-cp313-cp313-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef9266d2aa545d7374938fb5c484531ef5a2ec7f2d573e62f8ce722c735685fd"},
{file = "lxml-6.0.2-cp313-cp313-manylinux_2_31_armv7l.whl", hash = "sha256:4077b7c79f31755df33b795dc12119cb557a0106bfdab0d2c2d97bd3cf3dffa6"},
{file = "lxml-6.0.2-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a7c5d5e5f1081955358533be077166ee97ed2571d6a66bdba6ec2f609a715d1a"},
{file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8f8d0cbd0674ee89863a523e6994ac25fd5be9c8486acfc3e5ccea679bad2679"},
{file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:2cbcbf6d6e924c28f04a43f3b6f6e272312a090f269eff68a2982e13e5d57659"},
{file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dfb874cfa53340009af6bdd7e54ebc0d21012a60a4e65d927c2e477112e63484"},
{file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:fb8dae0b6b8b7f9e96c26fdd8121522ce5de9bb5538010870bd538683d30e9a2"},
{file = "lxml-6.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:358d9adae670b63e95bc59747c72f4dc97c9ec58881d4627fe0120da0f90d314"},
{file = "lxml-6.0.2-cp313-cp313-win32.whl", hash = "sha256:e8cd2415f372e7e5a789d743d133ae474290a90b9023197fd78f32e2dc6873e2"},
{file = "lxml-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:b30d46379644fbfc3ab81f8f82ae4de55179414651f110a1514f0b1f8f6cb2d7"},
{file = "lxml-6.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:13dcecc9946dca97b11b7c40d29fba63b55ab4170d3c0cf8c0c164343b9bfdcf"},
{file = "lxml-6.0.2-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:b0c732aa23de8f8aec23f4b580d1e52905ef468afb4abeafd3fec77042abb6fe"},
{file = "lxml-6.0.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4468e3b83e10e0317a89a33d28f7aeba1caa4d1a6fd457d115dd4ffe90c5931d"},
{file = "lxml-6.0.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:abd44571493973bad4598a3be7e1d807ed45aa2adaf7ab92ab7c62609569b17d"},
{file = "lxml-6.0.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:370cd78d5855cfbffd57c422851f7d3864e6ae72d0da615fca4dad8c45d375a5"},
{file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:901e3b4219fa04ef766885fb40fa516a71662a4c61b80c94d25336b4934b71c0"},
{file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:a4bf42d2e4cf52c28cc1812d62426b9503cdb0c87a6de81442626aa7d69707ba"},
{file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b2c7fdaa4d7c3d886a42534adec7cfac73860b89b4e5298752f60aa5984641a0"},
{file = "lxml-6.0.2-cp314-cp314-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:98a5e1660dc7de2200b00d53fa00bcd3c35a3608c305d45a7bbcaf29fa16e83d"},
{file = "lxml-6.0.2-cp314-cp314-manylinux_2_31_armv7l.whl", hash = "sha256:dc051506c30b609238d79eda75ee9cab3e520570ec8219844a72a46020901e37"},
{file = "lxml-6.0.2-cp314-cp314-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:8799481bbdd212470d17513a54d568f44416db01250f49449647b5ab5b5dccb9"},
{file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9261bb77c2dab42f3ecd9103951aeca2c40277701eb7e912c545c1b16e0e4917"},
{file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_armv7l.whl", hash = "sha256:65ac4a01aba353cfa6d5725b95d7aed6356ddc0a3cd734de00124d285b04b64f"},
{file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:b22a07cbb82fea98f8a2fd814f3d1811ff9ed76d0fc6abc84eb21527596e7cc8"},
{file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:d759cdd7f3e055d6bc8d9bec3ad905227b2e4c785dc16c372eb5b5e83123f48a"},
{file = "lxml-6.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:945da35a48d193d27c188037a05fec5492937f66fb1958c24fc761fb9d40d43c"},
{file = "lxml-6.0.2-cp314-cp314-win32.whl", hash = "sha256:be3aaa60da67e6153eb15715cc2e19091af5dc75faef8b8a585aea372507384b"},
{file = "lxml-6.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:fa25afbadead523f7001caf0c2382afd272c315a033a7b06336da2637d92d6ed"},
{file = "lxml-6.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:063eccf89df5b24e361b123e257e437f9e9878f425ee9aae3144c77faf6da6d8"},
{file = "lxml-6.0.2-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:6162a86d86893d63084faaf4ff937b3daea233e3682fb4474db07395794fa80d"},
{file = "lxml-6.0.2-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:414aaa94e974e23a3e92e7ca5b97d10c0cf37b6481f50911032c69eeb3991bba"},
{file = "lxml-6.0.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:48461bd21625458dd01e14e2c38dd0aea69addc3c4f960c30d9f59d7f93be601"},
{file = "lxml-6.0.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:25fcc59afc57d527cfc78a58f40ab4c9b8fd096a9a3f964d2781ffb6eb33f4ed"},
{file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5179c60288204e6ddde3f774a93350177e08876eaf3ab78aa3a3649d43eb7d37"},
{file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:967aab75434de148ec80597b75062d8123cadf2943fb4281f385141e18b21338"},
{file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:d100fcc8930d697c6561156c6810ab4a508fb264c8b6779e6e61e2ed5e7558f9"},
{file = "lxml-6.0.2-cp314-cp314t-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2ca59e7e13e5981175b8b3e4ab84d7da57993eeff53c07764dcebda0d0e64ecd"},
{file = "lxml-6.0.2-cp314-cp314t-manylinux_2_31_armv7l.whl", hash = "sha256:957448ac63a42e2e49531b9d6c0fa449a1970dbc32467aaad46f11545be9af1d"},
{file = "lxml-6.0.2-cp314-cp314t-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b7fc49c37f1786284b12af63152fe1d0990722497e2d5817acfe7a877522f9a9"},
{file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e19e0643cc936a22e837f79d01a550678da8377d7d801a14487c10c34ee49c7e"},
{file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_armv7l.whl", hash = "sha256:1db01e5cf14345628e0cbe71067204db658e2fb8e51e7f33631f5f4735fefd8d"},
{file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:875c6b5ab39ad5291588aed6925fac99d0097af0dd62f33c7b43736043d4a2ec"},
{file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:cdcbed9ad19da81c480dfd6dd161886db6096083c9938ead313d94b30aadf272"},
{file = "lxml-6.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:80dadc234ebc532e09be1975ff538d154a7fa61ea5031c03d25178855544728f"},
{file = "lxml-6.0.2-cp314-cp314t-win32.whl", hash = "sha256:da08e7bb297b04e893d91087df19638dc7a6bb858a954b0cc2b9f5053c922312"},
{file = "lxml-6.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:252a22982dca42f6155125ac76d3432e548a7625d56f5a273ee78a5057216eca"},
{file = "lxml-6.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:bb4c1847b303835d89d785a18801a883436cdfd5dc3d62947f9c49e24f0f5a2c"},
{file = "lxml-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a656ca105115f6b766bba324f23a67914d9c728dafec57638e2b92a9dcd76c62"},
{file = "lxml-6.0.2-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c54d83a2188a10ebdba573f16bd97135d06c9ef60c3dc495315c7a28c80a263f"},
{file = "lxml-6.0.2-cp38-cp38-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:1ea99340b3c729beea786f78c38f60f4795622f36e305d9c9be402201efdc3b7"},
{file = "lxml-6.0.2-cp38-cp38-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:af85529ae8d2a453feee4c780d9406a5e3b17cee0dd75c18bd31adcd584debc3"},
{file = "lxml-6.0.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:fe659f6b5d10fb5a17f00a50eb903eb277a71ee35df4615db573c069bcf967ac"},
{file = "lxml-6.0.2-cp38-cp38-win32.whl", hash = "sha256:5921d924aa5468c939d95c9814fa9f9b5935a6ff4e679e26aaf2951f74043512"},
{file = "lxml-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:0aa7070978f893954008ab73bb9e3c24a7c56c054e00566a21b553dc18105fca"},
{file = "lxml-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2c8458c2cdd29589a8367c09c8f030f1d202be673f0ca224ec18590b3b9fb694"},
{file = "lxml-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3fee0851639d06276e6b387f1c190eb9d7f06f7f53514e966b26bae46481ec90"},
{file = "lxml-6.0.2-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:b2142a376b40b6736dfc214fd2902409e9e3857eff554fed2d3c60f097e62a62"},
{file = "lxml-6.0.2-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6b5b39cc7e2998f968f05309e666103b53e2edd01df8dc51b90d734c0825444"},
{file = "lxml-6.0.2-cp39-cp39-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4aec24d6b72ee457ec665344a29acb2d35937d5192faebe429ea02633151aad"},
{file = "lxml-6.0.2-cp39-cp39-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:b42f4d86b451c2f9d06ffb4f8bbc776e04df3ba070b9fe2657804b1b40277c48"},
{file = "lxml-6.0.2-cp39-cp39-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cdaefac66e8b8f30e37a9b4768a391e1f8a16a7526d5bc77a7928408ef68e93"},
{file = "lxml-6.0.2-cp39-cp39-manylinux_2_31_armv7l.whl", hash = "sha256:b738f7e648735714bbb82bdfd030203360cfeab7f6e8a34772b3c8c8b820568c"},
{file = "lxml-6.0.2-cp39-cp39-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:daf42de090d59db025af61ce6bdb2521f0f102ea0e6ea310f13c17610a97da4c"},
{file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:66328dabea70b5ba7e53d94aa774b733cf66686535f3bc9250a7aab53a91caaf"},
{file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:e237b807d68a61fc3b1e845407e27e5eb8ef69bc93fe8505337c1acb4ee300b6"},
{file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_riscv64.whl", hash = "sha256:ac02dc29fd397608f8eb15ac1610ae2f2f0154b03f631e6d724d9e2ad4ee2c84"},
{file = "lxml-6.0.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:817ef43a0c0b4a77bd166dc9a09a555394105ff3374777ad41f453526e37f9cb"},
{file = "lxml-6.0.2-cp39-cp39-win32.whl", hash = "sha256:bc532422ff26b304cfb62b328826bd995c96154ffd2bac4544f37dbb95ecaa8f"},
{file = "lxml-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:995e783eb0374c120f528f807443ad5a83a656a8624c467ea73781fc5f8a8304"},
{file = "lxml-6.0.2-cp39-cp39-win_arm64.whl", hash = "sha256:08b9d5e803c2e4725ae9e8559ee880e5328ed61aa0935244e0515d7d9dbec0aa"},
{file = "lxml-6.0.2-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e748d4cf8fef2526bb2a589a417eba0c8674e29ffcb570ce2ceca44f1e567bf6"},
{file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:4ddb1049fa0579d0cbd00503ad8c58b9ab34d1254c77bc6a5576d96ec7853dba"},
{file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:cb233f9c95f83707dae461b12b720c1af9c28c2d19208e1be03387222151daf5"},
{file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bc456d04db0515ce3320d714a1eac7a97774ff0849e7718b492d957da4631dd4"},
{file = "lxml-6.0.2-pp310-pypy310_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2613e67de13d619fd283d58bda40bff0ee07739f624ffee8b13b631abf33083d"},
{file = "lxml-6.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:24a8e756c982c001ca8d59e87c80c4d9dcd4d9b44a4cbeb8d9be4482c514d41d"},
{file = "lxml-6.0.2-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1c06035eafa8404b5cf475bb37a9f6088b0aca288d4ccc9d69389750d5543700"},
{file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c7d13103045de1bdd6fe5d61802565f1a3537d70cd3abf596aa0af62761921ee"},
{file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0a3c150a95fbe5ac91de323aa756219ef9cf7fde5a3f00e2281e30f33fa5fa4f"},
{file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:60fa43be34f78bebb27812ed90f1925ec99560b0fa1decdb7d12b84d857d31e9"},
{file = "lxml-6.0.2-pp311-pypy311_pp73-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21c73b476d3cfe836be731225ec3421fa2f048d84f6df6a8e70433dff1376d5a"},
{file = "lxml-6.0.2-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:27220da5be049e936c3aca06f174e8827ca6445a4353a1995584311487fc4e3e"},
{file = "lxml-6.0.2.tar.gz", hash = "sha256:cd79f3367bd74b317dda655dc8fcfa304d9eb6e4fb06b7168c5cf27f96e0cd62"},
]
[package.extras]
cssselect = ["cssselect (>=0.7)"]
html-clean = ["lxml_html_clean"]
html5 = ["html5lib"]
htmlsoup = ["BeautifulSoup4"]
[metadata]
lock-version = "2.1"
python-versions = "^3.12"
content-hash = "51fcc3287807d80972696b287267c9510262d473f6a5328d44c478a16dbc851e"

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Some files were not shown because too many files have changed in this diff Show More