Skip to content

Commit 180cfcf

Browse files
committed
Fix capitalization and improve removeStyleTags
1 parent de106ac commit 180cfcf

File tree

9 files changed

+109
-72
lines changed

9 files changed

+109
-72
lines changed

.changeset/sour-mirrors-care.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"scan-chart": patch
3+
---
4+
5+
Fix capitalization and improve removeStyleTags

readme.md

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ This has been validated on 40,000 charts, including some that were deliberately
99
/**
1010
* Scans `files` as a chart folder, and returns a `ScannedChart` object.
1111
*/
12-
function scanChartFolder(files: { filename: string; data: Uint8Array }[]): ScannedChart
12+
function scanChartFolder(files: { fileName: string; data: Uint8Array }[]): ScannedChart
1313
function parseChartFile(data: Uint8Array, format: 'chart' | 'mid', iniChartModifiers: IniChartModifiers): ParsedChart
1414
function calculateTrackHash(parsedChart: ParsedChart, instrument: Instrument, difficulty: Difficulty): { hash: string, bchart: Uint8Array }
1515

@@ -310,38 +310,30 @@ type NoteType =
310310
| 'greenTomOrCymbalMarker'
311311

312312
type FolderIssueType =
313-
| 'noMetadata' // This chart doesn't have "song.ini"
314-
| 'invalidIni' // .ini file is not named "song.ini"
315-
| 'invalidMetadata' // "song.ini" doesn't have a "[Song]" section
316-
| 'badIniLine' // This line in "song.ini" couldn't be parsed
317-
| 'multipleIniFiles' // This chart has multiple .ini files
318-
| 'noAlbumArt' // This chart doesn't have album art
319-
| 'albumArtSize' // This chart's album art is not 500x500 or 512x512
320-
| 'badAlbumArt' // This chart's album art couldn't be parsed
321-
| 'multipleAlbumArt' // This chart has multiple album art files
322-
| 'noAudio' // This chart doesn't have an audio file
323-
| 'invalidAudio' // Audio file is not a valid audio stem name
324-
| 'badAudio' // This chart's audio couldn't be parsed
325-
| 'multipleAudio' // This chart has multiple audio files of the same stem
326-
| 'noChart' // This chart doesn't have "notes.chart"/"notes.mid"
327-
| 'invalidChart' // .chart/.mid file is not named "notes.chart"/"notes.mid"
328-
| 'badChart' // This chart's .chart/.mid file couldn't be parsed
329-
| 'multipleChart' // This chart has multiple .chart/.mid files
330-
| 'badVideo' // This chart has a video background that will not work on Linux
331-
| 'multipleVideo' // This chart has multiple video background files
313+
| 'noMetadata' // This chart doesn't have "song.ini"
314+
| 'invalidIni' // .ini file is not named "song.ini"
315+
| 'invalidMetadata' // "song.ini" doesn't have a "[Song]" section
316+
| 'badIniLine' // This line in "song.ini" couldn't be parsed
317+
| 'multipleIniFiles' // This chart has multiple .ini files
318+
| 'noAlbumArt' // This chart doesn't have album art
319+
| 'albumArtSize' // This chart's album art is not 500x500 or 512x512
320+
| 'badAlbumArt' // This chart's album art couldn't be parsed
321+
| 'multipleAlbumArt' // This chart has multiple album art files
322+
| 'noAudio' // This chart doesn't have an audio file
323+
| 'invalidAudio' // Audio file is not a valid audio stem name
324+
| 'badAudio' // This chart's audio couldn't be parsed
325+
| 'multipleAudio' // This chart has multiple audio files of the same stem
326+
| 'noChart' // This chart doesn't have "notes.chart"/"notes.mid"
327+
| 'invalidChart' // .chart/.mid file is not named "notes.chart"/"notes.mid"
328+
| 'badChart' // This chart's .chart/.mid file couldn't be parsed
329+
| 'multipleChart' // This chart has multiple .chart/.mid files
330+
| 'badVideo' // This chart has a video background that will not work on Linux
331+
| 'multipleVideo' // This chart has multiple video background files
332332

333333
type MetadataIssueType =
334-
| 'noName' // Metadata is missing the "name" property
335-
| 'noArtist' // Metadata is missing the "artist" property
336-
| 'noAlbum' // Metadata is missing the "album" property
337-
| 'noGenre' // Metadata is missing the "genre" property
338-
| 'noYear' // Metadata is missing the "year" property
339-
| 'noCharter' // Metadata is missing the "charter" property
340-
| 'missingInstrumentDiff' // Metadata is missing a "diff_" property
341-
| 'extraInstrumentDiff' // Metadata contains a "diff_" property for an uncharted instrument
342-
| 'nonzeroDelay' // Metadata contains a "delay" property that is not zero
343-
| 'drumsSetTo4And5Lane' // Metadata indicates the drum chart is both 4-lane and 5-lane
344-
| 'nonzeroOffset' // Chart file contains an "Offset" property that is not zero
334+
| 'missingValue' // Metadata is missing a required value
335+
| 'invalidValue' // Metadata property was set to an unsupported value
336+
| 'extraValue' // Metadata contains a property that should not be included
345337

346338
interface ParsedChart {
347339
resolution: number

src/audio/audio-scanner.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { getBasename, hasAudioExtension, hasAudioName } from '../utils'
55

66
// TODO: use _max_threads
77
// eslint-disable-next-line @typescript-eslint/no-unused-vars
8-
export function scanAudio(files: { filename: string; data: Uint8Array }[], _max_threads: number) {
8+
export function scanAudio(files: { fileName: string; data: Uint8Array }[], _max_threads: number) {
99
const folderIssues: { folderIssue: FolderIssueType; description: string }[] = []
1010

1111
const findAudioDataResult = findAudioData(files)
@@ -31,20 +31,20 @@ export function scanAudio(files: { filename: string; data: Uint8Array }[], _max_
3131
/**
3232
* @returns the audio file(s) in this chart, or `[]` if none were found.
3333
*/
34-
function findAudioData(files: { filename: string; data: Uint8Array }[]) {
34+
function findAudioData(files: { fileName: string; data: Uint8Array }[]) {
3535
const folderIssues: { folderIssue: FolderIssueType; description: string }[] = []
3636
const audioData: Uint8Array[] = []
3737
const stemNames: string[] = []
3838

3939
for (const file of files) {
40-
if (hasAudioExtension(file.filename)) {
41-
if (hasAudioName(file.filename)) {
42-
stemNames.push(getBasename(file.filename))
43-
if (!['preview', 'crowd'].includes(getBasename(file.filename).toLowerCase())) {
40+
if (hasAudioExtension(file.fileName)) {
41+
if (hasAudioName(file.fileName)) {
42+
stemNames.push(getBasename(file.fileName))
43+
if (!['preview', 'crowd'].includes(getBasename(file.fileName).toLowerCase())) {
4444
audioData.push(file.data)
4545
}
4646
} else {
47-
folderIssues.push({ folderIssue: 'invalidAudio', description: `"${file.filename}" is not a valid audio stem name.` })
47+
folderIssues.push({ folderIssue: 'invalidAudio', description: `"${file.fileName}" is not a valid audio stem name.` })
4848
}
4949
}
5050
}

src/chart/chart-scanner.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const MIN_SUSTAIN_GAP_MS = 40
1515
const MIN_SUSTAIN_MS = 100
1616
const NPS_GROUP_SIZE_MS = 1000
1717

18-
export function scanChart(files: { filename: string; data: Uint8Array }[], iniChartModifiers: IniChartModifiers) {
18+
export function scanChart(files: { fileName: string; data: Uint8Array }[], iniChartModifiers: IniChartModifiers) {
1919
const { chartData, format, folderIssues } = findChartData(files)
2020

2121
if (chartData) {
@@ -105,19 +105,19 @@ export function scanChart(files: { filename: string; data: Uint8Array }[], iniCh
105105
return { chartHash: null, notesData: null, metadata: null, folderIssues }
106106
}
107107

108-
function findChartData(files: { filename: string; data: Uint8Array }[]) {
108+
function findChartData(files: { fileName: string; data: Uint8Array }[]) {
109109
const folderIssues: { folderIssue: FolderIssueType; description: string }[] = []
110110

111111
const chartFiles = _.chain(files)
112-
.filter(f => hasChartExtension(f.filename))
113-
.orderBy([f => hasChartName(f.filename), f => getExtension(f.filename).toLowerCase() === '.mid'], ['desc', 'desc'])
112+
.filter(f => hasChartExtension(f.fileName))
113+
.orderBy([f => hasChartName(f.fileName), f => getExtension(f.fileName).toLowerCase() === '.mid'], ['desc', 'desc'])
114114
.value()
115115

116116
for (const file of chartFiles) {
117-
if (!hasChartName(file.filename)) {
117+
if (!hasChartName(file.fileName)) {
118118
folderIssues.push({
119119
folderIssue: 'invalidChart',
120-
description: `"${file.filename}" is not named "notes${getExtension(file.filename).toLowerCase()}".`,
120+
description: `"${file.fileName}" is not named "notes${getExtension(file.fileName).toLowerCase()}".`,
121121
})
122122
}
123123
}
@@ -132,7 +132,7 @@ function findChartData(files: { filename: string; data: Uint8Array }[]) {
132132
} else {
133133
return {
134134
chartData: chartFiles[0].data,
135-
format: (getExtension(chartFiles[0].filename).toLowerCase() === '.mid' ? 'mid' : 'chart') as 'mid' | 'chart',
135+
format: (getExtension(chartFiles[0].fileName).toLowerCase() === '.mid' ? 'mid' : 'chart') as 'mid' | 'chart',
136136
folderIssues,
137137
}
138138
}

src/image/image-scanner.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { load } from 'exifreader'
33
import { FolderIssueType } from '../interfaces'
44
import { hasAlbumName } from '../utils'
55

6-
export function scanImage(files: { filename: string; data: Uint8Array }[]) {
6+
export function scanImage(files: { fileName: string; data: Uint8Array }[]) {
77
const folderIssues: { folderIssue: FolderIssueType; description: string }[] = []
88

99
const findAlbumDataResult = findAlbumData(files)
@@ -21,13 +21,13 @@ export function scanImage(files: { filename: string; data: Uint8Array }[]) {
2121
/**
2222
* @returns the album art file data in this chart, or `null` if one wasn't found.
2323
*/
24-
function findAlbumData(files: { filename: string; data: Uint8Array }[]) {
24+
function findAlbumData(files: { fileName: string; data: Uint8Array }[]) {
2525
const folderIssues: { folderIssue: FolderIssueType; description: string }[] = []
2626
let albumCount = 0
2727
let lastAlbumData: Uint8Array | null = null
2828

2929
for (const file of files) {
30-
if (hasAlbumName(file.filename)) {
30+
if (hasAlbumName(file.fileName)) {
3131
albumCount++
3232
lastAlbumData = file.data
3333
}

src/index.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export { calculateTrackHash } from './chart/track-hasher'
1717
/**
1818
* Scans `files` as a chart folder, and returns a `ScannedChart` object.
1919
*/
20-
export function scanChartFolder(files: { filename: string; data: Uint8Array }[]): ScannedChart {
20+
export function scanChartFolder(files: { fileName: string; data: Uint8Array }[]): ScannedChart {
2121
const chart: RequireMatchingProps<Subset<ScannedChart>, 'folderIssues' | 'metadataIssues' | 'playable'> = {
2222
folderIssues: [],
2323
metadataIssues: [],
@@ -119,10 +119,10 @@ export function scanChartFolder(files: { filename: string; data: Uint8Array }[])
119119
return chart as ScannedChart
120120
}
121121

122-
function getChartMD5(files: { filename: string; data: Uint8Array }[]) {
122+
function getChartMD5(files: { fileName: string; data: Uint8Array }[]) {
123123
const hash = md5.create()
124-
for (const file of _.orderBy(files, f => f.filename)) {
125-
hash.update(file.filename)
124+
for (const file of _.orderBy(files, f => f.fileName)) {
125+
hash.update(file.fileName)
126126
hash.update(file.data)
127127
}
128128
return hash.hex()

src/ini/ini-scanner.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ const integerProperties: MetaNumberKey[] = [
8585
]
8686
const requiredProperties: MetaStringKey[] = ['name', 'artist', 'album', 'genre', 'year', 'charter']
8787

88-
export function scanIni(files: { filename: string; data: Uint8Array }[]) {
88+
export function scanIni(files: { fileName: string; data: Uint8Array }[]) {
8989
const folderIssues: { folderIssue: FolderIssueType; description: string }[] = []
9090

9191
const findIniDataResult = findIniData(files)
@@ -110,7 +110,7 @@ export function scanIni(files: { filename: string; data: Uint8Array }[]) {
110110
/**
111111
* @returns the .ini file data in this chart, or `null` if one wasn't found.
112112
*/
113-
function findIniData(files: { filename: string; data: Uint8Array }[]): {
113+
function findIniData(files: { fileName: string; data: Uint8Array }[]): {
114114
iniData: Uint8Array | null
115115
folderIssues: { folderIssue: FolderIssueType; description: string }[]
116116
} {
@@ -120,11 +120,11 @@ function findIniData(files: { filename: string; data: Uint8Array }[]): {
120120
let lastIniData: Uint8Array | null = null
121121

122122
for (const file of files) {
123-
if (hasIniExtension(file.filename)) {
123+
if (hasIniExtension(file.fileName)) {
124124
iniCount++
125125
lastIniData = file.data
126-
if (!hasIniName(file.filename)) {
127-
folderIssues.push({ folderIssue: 'invalidIni', description: `"${file.filename}" is not named "song.ini".` })
126+
if (!hasIniName(file.fileName)) {
127+
folderIssues.push({ folderIssue: 'invalidIni', description: `"${file.fileName}" is not named "song.ini".` })
128128
} else {
129129
bestIniData = file.data
130130
}

src/utils.ts

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export function getEncoding(buffer: Uint8Array) {
4444
}
4545

4646
/**
47-
* @returns true if the list of filename `extensions` appears to be intended as a chart folder.
47+
* @returns true if the list of fileName `extensions` appears to be intended as a chart folder.
4848
*/
4949
export function appearsToBeChartFolder(extensions: string[]) {
5050
const ext = extensions.map(extension => extension.toLowerCase())
@@ -83,7 +83,7 @@ export function hasIniExtension(name: string) {
8383
}
8484

8585
/**
86-
* @returns `true` if `name` is a valid ini filename.
86+
* @returns `true` if `name` is a valid ini fileName.
8787
*/
8888
export function hasIniName(name: string) {
8989
return name === 'song.ini'
@@ -97,7 +97,7 @@ export function hasChartExtension(name: string) {
9797
}
9898

9999
/**
100-
* @returns `true` if `name` is a valid chart filename.
100+
* @returns `true` if `name` is a valid chart fileName.
101101
*/
102102
export function hasChartName(name: string) {
103103
return ['notes.chart', 'notes.mid'].includes(name)
@@ -111,7 +111,7 @@ export function hasAudioExtension(name: string) {
111111
}
112112

113113
/**
114-
* @returns `true` if `name` has a valid chart audio filename.
114+
* @returns `true` if `name` has a valid chart audio fileName.
115115
*/
116116
export function hasAudioName(name: string) {
117117
return (
@@ -136,26 +136,67 @@ export function hasAudioName(name: string) {
136136
}
137137

138138
/**
139-
* @returns `true` if `name` is a valid album filename.
139+
* @returns `true` if `name` is a valid album fileName.
140140
*/
141141
export function hasAlbumName(name: string) {
142142
return ['album.jpg', 'album.jpeg', 'album.png'].includes(name)
143143
}
144144

145145
/**
146-
* @returns `true` if `name` is a valid video filename.
146+
* @returns `true` if `name` is a valid video fileName.
147147
*/
148148
export function hasVideoName(name: string) {
149149
return getBasename(name) === 'video' && ['.mp4', '.avi', '.webm', '.vp8', '.ogv', '.mpeg'].includes(getExtension(name))
150150
}
151151

152152
/**
153-
* @returns `true` if `name` is a video filename that is not supported on Linux.
153+
* @returns `true` if `name` is a video fileName that is not supported on Linux.
154154
*/
155155
export function hasBadVideoName(name: string) {
156156
return getBasename(name) === 'video' && ['.mp4', '.avi', '.mpeg'].includes(getExtension(name))
157157
}
158158

159+
const allowedTags = [
160+
'align',
161+
'allcaps',
162+
'alpha',
163+
'b',
164+
'br',
165+
'color',
166+
'cspace',
167+
'font',
168+
'font-weight',
169+
'gradient',
170+
'i',
171+
'indent',
172+
'line-height',
173+
'line-indent',
174+
'link',
175+
'lowercase',
176+
'margin',
177+
'mark',
178+
'mspace',
179+
'nobr',
180+
'noparse',
181+
'page',
182+
'pos',
183+
'rotate',
184+
's',
185+
'size',
186+
'smallcaps',
187+
'space',
188+
'sprite',
189+
'strikethrough',
190+
'style',
191+
'sub',
192+
'sup',
193+
'u',
194+
'uppercase',
195+
'voffset',
196+
'width',
197+
'#',
198+
]
199+
const tagPattern = allowedTags.map(tag => `\\b${tag}\\b`).join('|')
159200
/**
160201
* @returns `text` with all style tags removed. (e.g. "<color=#AEFFFF>Aren Eternal</color> & Geo" -> "Aren Eternal & Geo")
161202
*/
@@ -164,9 +205,8 @@ export function removeStyleTags(text: string) {
164205
let newText = text
165206
do {
166207
oldText = newText
167-
newText = newText.replace(/<\s*[^>]+>(.*?)<\s*\/\s*[^>]+>/g, '$1')
168-
newText = newText.replace(/<\s*\/\s*[^>]+>(.*?)<\s*[^>]+>/g, '$1')
169-
} while (newText != oldText)
208+
newText = newText.replace(new RegExp(`<\\s*\\/?\\s*(?:${tagPattern})[^>]*>`, 'gi'), '').trim()
209+
} while (newText !== oldText)
170210
return newText
171211
}
172212

src/video/video-scanner.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { FolderIssueType } from '../interfaces'
22
import { hasBadVideoName, hasVideoName } from '../utils'
33

4-
export function scanVideo(files: { filename: string; data: Uint8Array }[]) {
4+
export function scanVideo(files: { fileName: string; data: Uint8Array }[]) {
55
const folderIssues: { folderIssue: FolderIssueType; description: string }[] = []
66

77
const findVideoDataResult = findVideoData(files)
@@ -10,20 +10,20 @@ export function scanVideo(files: { filename: string; data: Uint8Array }[]) {
1010
return { hasVideoBackground: !!findVideoDataResult.videoData, folderIssues }
1111
}
1212

13-
function findVideoData(files: { filename: string; data: Uint8Array }[]) {
13+
function findVideoData(files: { fileName: string; data: Uint8Array }[]) {
1414
const folderIssues: { folderIssue: FolderIssueType; description: string }[] = []
1515
let videoCount = 0
1616
let bestVideoData: Uint8Array | null = null
1717
let lastVideoData: Uint8Array | null = null
1818

1919
for (const file of files) {
20-
if (hasVideoName(file.filename)) {
20+
if (hasVideoName(file.fileName)) {
2121
videoCount++
2222
lastVideoData = file.data
23-
if (hasBadVideoName(file.filename)) {
23+
if (hasBadVideoName(file.fileName)) {
2424
folderIssues.push({
2525
folderIssue: 'badVideo',
26-
description: `"${file.filename}" will not work on Linux and should be converted to .webm.`,
26+
description: `"${file.fileName}" will not work on Linux and should be converted to .webm.`,
2727
})
2828
} else {
2929
bestVideoData = file.data

0 commit comments

Comments
 (0)