vex-match-parser
Version:
A TypeScript library for parsing VEX Robotics match schedules from PDF files. Works in both Node.js and browser environments.
240 lines (184 loc) • 6.39 kB
Markdown
A TypeScript library for parsing VEX Robotics match schedules from PDF files. **Works in both Node.js and browser environments**.
- ✅ **Universal**: Works in Node.js and browsers
- ✅ Supports **3 VEX competition types**: VEXIQ, V5, and VEXGO
- ✅ Parses both **Qualification (Q)** and **Practice (P)** matches
- ✅ Automatic competition type detection
- ✅ Full TypeScript support with type definitions
- ✅ Simple, clean API
```bash
npm install vex-match-parser
```
```typescript
import { parseVEXMatchSchedule } from 'vex-match-parser';
// Parse from file path
const schedule = await parseVEXMatchSchedule('./match-schedule.pdf');
console.log(schedule.type); // 'VEXIQ' | 'V5' | 'VEXGO'
console.log(schedule.matchType); // 'Qualification' | 'Practice'
console.log(schedule.event); // '南宁WRC-IQ初中'
console.log(schedule.matches); // Array of matches
```
```html
<!DOCTYPE html>
<html>
<head>
<title>VEX Match Parser Demo</title>
</head>
<body>
<input type="file" id="pdfInput" accept=".pdf" />
<div id="result"></div>
<script type="module">
import { parseVEXMatchScheduleFromFile } from 'vex-match-parser';
document.getElementById('pdfInput').addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
try {
const schedule = await parseVEXMatchScheduleFromFile(file);
document.getElementById('result').innerHTML = `
<h2>${schedule.event}</h2>
<p>Competition: ${schedule.type}</p>
<p>Matches: ${schedule.matches.length}</p>
<pre>${JSON.stringify(schedule.matches[0], null, 2)}</pre>
`;
} catch (error) {
console.error('Error parsing PDF:', error);
}
});
</script>
</body>
</html>
```
```typescript
import { parseVEXMatchSchedule } from 'vex-match-parser';
// From fetch
const response = await fetch('/path/to/schedule.pdf');
const arrayBuffer = await response.arrayBuffer();
const schedule = await parseVEXMatchSchedule(arrayBuffer, 'schedule.pdf');
// From File
const file = document.querySelector('input[type="file"]').files[0];
const buffer = await file.arrayBuffer();
const schedule2 = await parseVEXMatchSchedule(buffer, file.name);
```
```tsx
import { parseVEXMatchScheduleFromFile } from 'vex-match-parser';
import { useState } from 'react';
function MatchScheduleUploader() {
const [schedule, setSchedule] = useState(null);
const [loading, setLoading] = useState(false);
const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
setLoading(true);
try {
const parsedSchedule = await parseVEXMatchScheduleFromFile(file);
setSchedule(parsedSchedule);
} catch (error) {
console.error('Error:', error);
} finally {
setLoading(false);
}
};
return (
<div>
<input type="file" onChange={handleFileUpload} accept=".pdf" />
{loading && <p>Loading...</p>}
{schedule && (
<div>
<h2>{schedule.event}</h2>
<p>Type: {schedule.type}</p>
<p>Total Matches: {schedule.matches.length}</p>
</div>
)}
</div>
);
}
```
```typescript
import { Match, MatchSchedule, CompetitionType } from 'vex-match-parser';
// Competition types
type CompetitionType = 'VEXIQ' | 'V5' | 'VEXGO';
type MatchType = 'Qualification' | 'Practice';
// Match schedule structure
interface MatchSchedule {
title: string; // "Qualification Match List"
matchType: MatchType; // "Qualification" or "Practice"
event: string; // Event name
division: string; // Division name
type: CompetitionType; // VEXIQ/V5/VEXGO
matches: Match[]; // Array of matches
filename: string; // Original PDF filename
}
// Match types (union type)
type Match = VEXIQMatch | V5Match | VEXGOMatch;
// VEXIQ/VEXGO Match (2 teams)
interface VEXIQMatch {
type: 'VEXIQ';
matchNumber: string; // "Q1", "P5", etc.
matchType: MatchType;
field: string; // "A", "B", etc.
time: string; // "周四 10:00 AM"
team1: string; // "7645B"
team2: string; // "7323W"
}
// V5 Match (4 teams, red vs blue alliance)
interface V5Match {
type: 'V5';
matchNumber: string;
matchType: MatchType;
field: string;
time: string;
red1: string; // "23456W"
red2: string; // "66666B"
blue1: string; // "1023K"
blue2: string; // "900K"
}
```
Main parsing function. Works in both Node.js and browser.
**Parameters:**
- `data`: `string | ArrayBuffer | Uint8Array`
- Node.js: File path as string
- Browser: ArrayBuffer or Uint8Array
- `filename?`: `string` (optional) - Original filename for type detection
**Returns:** `Promise<MatchSchedule>`
Browser-specific helper for File objects.
**Parameters:**
- `file`: `File` - File object from `<input type="file">`
**Returns:** `Promise<MatchSchedule>`
The parser uses a three-layer detection system to automatically identify the competition type:
1. **Filename detection**: Checks if filename contains "V5", "IQ", or "GO"
2. **Header detection**: Checks the PDF title for competition type
3. **Team count detection**: V5 has 4 teams per match, IQ/GO have 2 teams
The parser expects VEX match schedule PDFs with the following structure:
- **Title**: `Qualification Match List` or `Practice Match List`
- **Subtitle**: Event name and division (e.g., `南宁WRC-IQ初中 - Default Division`)
- **Table columns**:
- VEXIQ/VEXGO: `Match | Field | Time | Team 1 | Team 2`
- V5: `Match | Field | Time | Red 1 | Red 2 | Blue 1 | Blue 2`
## Development
```bash
# Install dependencies
npm install
# Build
npm run build
# Test
npm test
```
## Browser Compatibility
- Modern browsers with ES modules support
- Requires support for: `ArrayBuffer`, `Uint8Array`, `File` API
- Works with: Chrome 63+, Firefox 60+, Safari 11.1+, Edge 79+
## License
MIT