Once I handed a batch of images to a server whose static rules only listed lowercase extensions, and photo.JPG plus document.PDF fell outside that rule as a whole batch. To normalise them to lowercase I pasted in 20-odd names and ran a first pass, and the results split three ways: half the names were changed, a few came back exactly as before, and archive.tar.gz only had its tail rewritten. Missed conversions like this keep happening, so it is worth understanding the cause first.
Set the scale first: the three modes change a different number of rows, 20, 19 and 20 of the same list, or 74.1%, 70.4% and 74.1%. Which mode you pick is not a matter of taste.
Which dot starts the extension: the last one, and its index must be above 0
The logic is only two lines: var dot = filename.lastIndexOf('.'); and then if (dot <= 0), which returns the string unchanged and records the extension as empty. dot is an index, not a count: with no dot in the line it is -1, and when the dot is the very first character it is 0. Both cases are blocked from conversion by that check.
| Input | Last dot | Extension taken | UPPER | lower | Title |
|---|---|---|---|---|---|
photo.JPG | 5 | JPG | unchanged | photo.jpg | photo.Jpg |
archive.tar.gz | 11 | gz | archive.tar.GZ | unchanged | archive.tar.Gz |
.profile.bak | 8 | bak | .profile.BAK | unchanged | .profile.Bak |
.gitignore | 0 | empty | unchanged | unchanged | unchanged |
LICENSE | -1 | empty | unchanged | unchanged | unchanged |
file. | 4 | empty | unchanged | unchanged | unchanged |
data.JSONL | 4 | JSONL | unchanged | data.jsonl | data.Jsonl |
C:\Program Files\my.app\run | 19 | app\run | directory segment taken as suffix | unchanged | also changed |
The seven no-suffix rows break down neatly: 3 lines with no dot at all (LICENSE, index, C:\path\to\file), 2 where the dot sits at index 0 (.gitignore, .), and 2 where the extension after the split is an empty string (file., ..). 3 + 2 + 2 = 7, matching the number in the stats bar. The trailing dot is not removed either: file. still outputs file..
Figure 1 | Index of the last dot, with bars scaled to the index; the three pale bars all fall on the no-conversion side.
The three modes differ that much in rows changed, and no-suffix is not a fourth category
Figure 2 | Of the 28 lines in the list, 27 are valid, and bars are scaled to row counts; no-suffix is 7 in all three modes.
The counting rules need stating first: converted plus unchanged equals the total file count, and 11 + 16 = 27 holds. No-suffix rows are added to both counters, so they are a subset of unchanged and adding all four numbers gives more than the total. I pasted 28 lines but the table has only 27 rows; the missing one is the blank line, which never reaches the table. I lined these numbers up by running the same list through Extension Case Converter in all three modes; the page says it processed 27 filenames, the two duplicate photo.JPG rows are both counted, and the tool does not deduplicate.
Title mode is the one most often mistaken for a prettier lowercase: it actually rewrites every letter of the extension except the first. document.PDF becomes document.Pdf and data.JSONL becomes data.Jsonl. If acronyms such as PDF, JPEG and MP3 must stay as they are, only UPPER does that; when the extension is a single character, Title and UPPER are equivalent (a.b.c.D still outputs a.b.c.D). The base name, everything before the last dot, is never touched. The case of the whole line is a different matter, and to normalise it you can run the text through a case converter first.
Paste a whole path and the directory name is what gets changed
The check looks for dots only and knows nothing about backslashes. C:\path\to\file has no dot and passes through untouched; a directory name with a dot gives the game away: in C:\Program Files\my.app\run the dot is at 19, the extension it takes is app\run, and UPPER turns it into C:\Program Files\my.APP\RUN, so the directory changes and the extension does not. That is why I now paste bare filenames only and send whole paths through a filename extractor first. Chinese and full-width characters are unaffected: 年度报告.docx becomes 年度报告.DOCX, and final inside 报告 2024.final.docx stays put.
Length is not an issue either: a 135-code-unit name is not truncated, and 400 lines in produce 400 table rows, because the source has no line or byte limit.
Three places where the pasted text does not match what you expect
- Leading and trailing whitespace on each line is stripped, trimmed once at each end; the space after
note.txtdoes not come back, while spaces in the middle survive (mid dle.TXTunchanged). - Blank lines, whitespace-only lines and tab-only lines disappear entirely and leave no gap; row order is unchanged, so a
z.txtin first place stays in first place. - Reserved Windows names and illegal characters are not validated at all:
CON.txtstill becomesCON.TXT, andPRN.logandnul.mdbehave the same; evena<b>|".txtconverts, and the angle brackets in the table are shown after escaping. - Paste CRLF and the trailing CR is eaten when lines are split; what you copy back always has LF endings and CRLF is never written back.
Two more things you have to watch yourself. First, the UI does not tell you which rows were left alone: the rendering code has a before-and-after comparison, but both branches emit the same fc-after cell, and in practice all three modes show fc-after 27 times and fc-same 0 times. Converted and unchanged rows look identical, and only the stats tell them apart. Second, this page only processes text: it never touches disk and uploads nothing, since the script makes 0 network or local-storage calls. The rename step is yours to wire up with rename or mv.
Q: Why did archive.tar.gz only change halfway?
Because only the last dot counts: dot = 11, so the split yields gz, while tar sits before it as part of the base name. All three modes act on that last segment only, and there is no switch to convert tar as well.
Q: Why are LICENSE and .gitignore left untouched?
Same early-return check: dot <= 0. LICENSE has no dot at all, so dot is -1, and .gitignore has its dot at index 0. Both are recorded as no-suffix yet still counted in the unchanged column, which is why the stats show them. The check looks only at where the last dot is, not at the start: .profile.bak has dot at 8, so it converts to .profile.BAK normally.
Q: How should I choose between the three modes?
Choose by what you need to keep. If URLs and server conventions need lowercase, use lower, which touches only 9 rows in this list. If acronyms such as JPG and PDF must stay as they are, only UPPER will do. Title looks tidy, at the cost of splitting PDF into Pdf. Before a full run, put in a few representative names (one double extension, one leading dot, one with no dot) and try each mode; that is cheaper than checking the whole batch afterwards.