Notes › Life Essays › Upper-Casing .ß Adds One Slot to the Whole Line: Three Tiers Measured Line by Line on an 18-Line Sample

Upper-Casing .ß Adds One Slot to the Whole Line: Three Tiers Measured Line by Line on an 18-Line Sample

Upper-casing .ß turns one suffix code unit into two, so the whole line grows by 1 unit while its UTF-8 byte count stays put; an 18-line sample list pasted as 20 segments and run through all three tiers on the suffix case page.

When the suffixes are a mess, run all three tiers

At the end of the month I tidied the download folder and the photo backup directory, and the worst part was the suffixes: JPG, jpg and Jpg mixed on one layer. I pasted whole file names into the suffix case page and ran each of the three tiers once. The sample is a list I wrote myself: 20 segments pasted in, 18 lines valid, and every name on it is fictional.

The page itself measures 211 lines and 8526 bytes, convertExt sits on lines 88 to 113, and all three tiers share one and the same indexed string build. Every number quoted here comes from the pure function return values of the suffix case conversion page.

For some suffixes the whole line gets longer after the change

The suffix of clip.ß is ß, one code unit; the upper tier hands back SS, two units, so the whole string grows from 6 units to 7, while the UTF-8 byte count stays 7 on both sides.

dot.İ moves only under lower: İ becomes an i plus a combining point, the suffix goes from 1 unit to 2, the whole string from 5 units to 6 and from 6 bytes to 7. The upper tier folds it back to the original character, the line does not move, and it is booked as unchanged.

mix.İß is the line where the three tiers disagree: suffix 2 units, whole string 6 units and 8 bytes; upper gives 7 units still 8 bytes, lower gives 7 units and 9 bytes, and title does not stir at all.

Against my 18-line valid list: the upper tier lengthens 2 lines, the lower tier lengthens 2 lines and the title tier lengthens 1 line.

A name with a dot in it does not have to have a suffix

The names look like this: a., photo., .., ..., D:\dir., .bashrc., archive.tar.. The last dot sits at the very end in every one of them, at positions dot=1, 5, 1, 2, 6, 7, 11. Line 92 only blocks dot<=0, so all of these pass; on line 97 substring(dot+1) cuts out an empty string of length 0; the !r.ext on line 138 then files them together with the names carrying no dot at all in one and the same cell, so the no-suffix counter and the unchanged counter both tick up at once. Running those 7 entries through 3 tiers is 21 calls, and every return value equals its input character for character. I wrote the last-dot rule up separately in an article of its own and will not retell it here.

namedotsuffix cut outresult across the three tiers
photo.5empty stringall three tiers return the input
D:\dir.6empty stringall three tiers return the input
.bashrc.7empty stringall three tiers return the input
archive.tar.11empty stringall three tiers return the input
archive.tar.gz10gzgz changes tier as usual

Leading indentation is eaten away quietly

The outer whitespace is trimmed twice, once at each of two places: once on line 134 inside process, and again on line 89 inside convertExt with the same regex. So a line that arrived with 4 leading spaces and 1 trailing tab is 22 units long at the source, yet by the time it reaches the return value before is already 17 units.

Fed to convertExt alone, the 13-unit input " photo.JPG " comes back with before at 9 units; a space inside a word never gets trimmed: the 20-unit " spaced name .TXT " leaves 16 units and the space in front of the dot stays where it was. The before column, the after column and the copied text are, at the function return layer, one and the same trimmed string.

How many lines you paste is not how many you processed

The list goes in as 20 segments, split(/\r?\n/) yields 20 segments, and only 18 lines reach the table: one whitespace-only segment and one empty segment are skipped outright on line 135. Three tiers, four counters:

tiertotal filesconvertedunchangedno suffix
upper187114
lower188104
title188104

Converted plus unchanged equals total files, and on this list that holds for all three tiers: 7+11=18 and 8+10=18. The line reading 18 file names processed reports total, not the 20 pasted segments.

The row number in the table is not the paste position either: feed in first.JPG, an empty line, second.TAR.gz, a whitespace-only line, third.7z and a trailing break, those 6 segments, and split yields 6 segments, total files 3, valid original line numbers 1,3,5, while the # column of the table (idx+1 on line 158) runs 1,2,3 straight down. Switching tiers only reruns automatically while the results are already on screen (lines 82 to 84), and line 131 clears the old results first each time, so the four counters are three independent sets rather than a running total.

The 7 lines the example button loads (lines 190 to 198) are a ready-made comparison. On one and the same text upper converts only 1 line, lower converts 5 and title converts 6, so the converted share climbs from 14.3% all the way to 85.7%.

What the table shows is not what the clipboard carries

Every table cell passes through escHtml (lines 116 to 118): first &, then <, then >, the quotes last, four steps run together. So the after of all <>&".LOG, 12 units long, is a 27-unit entity string inside the table; a<b>.TXT goes from 8 units to 14; quote".TXT goes from 10 units to 15.

Copying takes the other road (line 185): it takes only the after field of each line and joins them with a single line break, no row-number column, no before column, no escHtml. That 12-unit name above still leaves as its 12-unit original.

Whole-string length bars: units and bytes clip.ß input 6 units, 7 bytes clip.SS upper 7 units, bytes still 7 dot.i plus dot lower 6 units, bytes 6 to 7

Figure 1: the lengthening is booked in units, the bytes keep a separate ledger, and the two never agree.

This page only produces text; actually renaming things is the job of the batch rename page, and this article does not write a word on its behalf.

The copy mentions neither blank lines nor lengthening

I checked the tool listing copy item by item, 14 checks in all: 6 agree, 7 agree in part, 1 conflicts. The five lines of copy never say blank line, yet the gap between 20 pasted segments and total 18 is exactly what the skip-blank-lines behaviour pays for. There is no upper limit on name length: 200 x characters plus .JPG, 204 units in all, still comes back as the whole string. An empty box, a run of nothing but line breaks, 4 spaces, and the run that splits into 5 segments of nothing but blank and whitespace lines are all stopped by the guard on line 126, which only raises the prompt asking for file names; that reading on line 153 of no valid data cannot be reached from the function layer, and clicking copy after clearing the results only raises nothing to copy.

FAQ

Q: the file name did not change by a single character, so why is one line booked as converted and another as unchanged?

The test looks only at whether the returned string equals its input character for character. The upper tier on word.ς returns word.Σ: the length is untouched and only the code point moved, so that one is converted; the upper tier on dot.İ and the upper tier on roman.Ⅷ fold back to the original character, the line does not move, and the bookkeeping says unchanged.

Q: where does that one extra character come from?

From the case mapping's own return value: upper gives SS for ß, lower gives an i plus one combining point for İ, so 1 unit goes in and 2 come out. This page builds the string by index and applies no length guard to what comes back, which is why the upper tier on clip.ß takes the whole string from 6 units to 7 while the UTF-8 byte count holds at 7.

Q: why does a dot at the very end count as no suffix?

While dot is greater than 0 the substring cuts from dot+1, which comes out empty, and !r.ext merges it into no suffix and unchanged. Two names both 11 characters long disagree: archive.tar. reports no suffix while archive.tar.gz converts gz as usual, and the only difference is whether anything at all follows the last dot.

Q: is the row on the screen the same string as the one on the clipboard?

When the name carries no &, <, > or quote it is the same string; when it does they differ, because all <>&".LOG is a 27-unit entity run inside the table and a 12-unit original on the clipboard. What the screen actually renders went unmeasured, so the ledger is kept against the copied text.

📎 Related articles

Which three of the four switches default to on: collapse rules, first-seen representatives and the dedupe short circuit 2026-11-08 It Took Three Presses to Reach Grandma: 36 Kinship Rules and 11,110 Chains Measured 2026-11-07 How Many Problems Fit on One A4 Worksheet Page: Font Size, Margins and the Info Bar Rewrite the Pagination 2026-11-03 The Same 23 Bytes at Four Error-Correction Levels: How Far This Page's Own QR Encoder Goes 2026-10-30 Type 137 ms as the frame delay and every frame holds 140 ms: tick steps, 4096-byte padding, canvas from the first image 2026-10-30