dragon-item-parser: introduce item parser library
Some checks failed
Dragon Item Parser CI / build-and-test (push) Successful in 1m3s
pipeline / lint-and-format (push) Failing after 4m8s
pipeline / build-and-push-images (push) Has been skipped

This commit is contained in:
2026-04-25 19:48:06 +02:00
parent a98e3c6589
commit e82ad73de1
17 changed files with 7689 additions and 5022 deletions

View File

@@ -0,0 +1,202 @@
/**
* Item stats parsed from CommunityDragon item description HTML
*/
export interface ItemStats {
// Offensive stats
attackDamage?: number
abilityPower?: number
attackSpeed?: number
criticalStrikeChance?: number
lifeSteal?: number
omnivamp?: number
physicalVamp?: number
spellVamp?: number
// Defensive stats
health?: number
armor?: number
magicResist?: number
// Resource stats
mana?: number
baseManaRegen?: number
baseHealthRegen?: number
// Movement stats
moveSpeed?: number
// Ability stats
abilityHaste?: number
// Penetration stats (usually percentages)
armorPenetration?: number
magicPenetration?: number
lethality?: number
magicPenetrationFlat?: number
// Other percentage stats
healAndShieldPower?: number
tenacity?: number
slowResist?: number
}
/**
* Stat name mappings from CDragon description text to stat keys
*/
const STAT_MAPPINGS: Record<string, keyof ItemStats> = {
'Attack Damage': 'attackDamage',
'Ability Power': 'abilityPower',
'Attack Speed': 'attackSpeed',
'Critical Strike Chance': 'criticalStrikeChance',
'Life Steal': 'lifeSteal',
Omnivamp: 'omnivamp',
'Physical Vamp': 'physicalVamp',
'Spell Vamp': 'spellVamp',
Health: 'health',
Armor: 'armor',
'Magic Resist': 'magicResist',
Mana: 'mana',
'Base Mana Regen': 'baseManaRegen',
'Base Health Regen': 'baseHealthRegen',
'Move Speed': 'moveSpeed',
'Ability Haste': 'abilityHaste',
'Armor Penetration': 'armorPenetration',
'Magic Penetration': 'magicPenetration',
Lethality: 'lethality',
'Heal and Shield Power': 'healAndShieldPower',
Tenacity: 'tenacity',
'Slow Resist': 'slowResist'
}
/**
* Parse a stat value string to a number
* Handles both flat values (e.g., "25") and percentages (e.g., "25%")
*/
function parseStatValue(valueStr: string): { value: number; isPercentage: boolean } {
const trimmed = valueStr.trim()
const isPercentage = trimmed.includes('%')
const numStr = trimmed.replace('%', '').replace(',', '').trim()
const value = parseFloat(numStr)
return { value, isPercentage }
}
/**
* Extract stats section from the description HTML
*/
function extractStatsSection(description: string): string | null {
const statsMatch = description.match(/<stats>(.*?)<\/stats>/s)
return statsMatch ? statsMatch[1] : null
}
/**
* Parse individual stat lines from the stats section
* Format: <attention> value </attention> statName
*/
function parseStatLines(
statsSection: string
): Array<{ value: number; isPercentage: boolean; statName: string }> {
const results: Array<{ value: number; isPercentage: boolean; statName: string }> = []
// Match patterns like: <attention> 25</attention> Move Speed
// or: <attention> 25%</attention> Attack Speed
const statRegex = /<attention>\s*([^<]+)<\/attention>\s*([^<]+)/g
let match
while ((match = statRegex.exec(statsSection)) !== null) {
const valueStr = match[1]
const statName = match[2].trim()
const { value, isPercentage } = parseStatValue(valueStr)
if (!isNaN(value) && statName) {
results.push({ value, isPercentage, statName })
}
}
return results
}
/**
* Parse item stats from CDragon description HTML
*
* @param description - The HTML description string from CDragon items.json
* @returns Parsed ItemStats object with all recognized stats
*
* @example
* ```ts
* const stats = parseItemStats(
* '<mainText><stats><attention> 25</attention> Move Speed</stats><br><br></mainText>'
* )
* // Returns: { moveSpeed: 25 }
* ```
*/
export function parseItemStats(description: string): ItemStats {
const stats: ItemStats = {}
if (!description) {
return stats
}
const statsSection = extractStatsSection(description)
if (!statsSection) {
return stats
}
const statLines = parseStatLines(statsSection)
for (const { value, statName } of statLines) {
const statKey = STAT_MAPPINGS[statName]
if (statKey) {
// For percentage stats that are stored as decimals (e.g., 25% -> 25)
// We store the percentage value as-is for consistency with game data
stats[statKey] = value
}
}
return stats
}
/**
* Item data structure from CDragon
*/
export interface CDragonItem {
id: number
name: string
description: string
active?: boolean
inStore?: boolean
from?: number[]
to?: number[]
categories?: string[]
maxStacks?: number
requiredChampion?: string
requiredAlly?: string
price?: number
priceTotal?: number
iconPath: string
}
/**
* Item with parsed stats
*/
export interface ItemWithStats extends CDragonItem {
stats: ItemStats
}
/**
* Parse a CDragon item and add parsed stats
*/
export function parseItem(item: CDragonItem): ItemWithStats {
return {
...item,
stats: parseItemStats(item.description)
}
}
/**
* Parse an array of CDragon items and add parsed stats
*/
export function parseItems(items: CDragonItem[]): ItemWithStats[] {
return items.map(parseItem)
}