Search code examples
sql-servert-sqlcamelcasing

Add spaces to Camelcase with Caps in it


I have an external app passing strings in camelcase. Users are looking to report on the string and want it formatted as a sentence. The string may have words in it with all caps that I need to add spaces around.

built out a function based on this question here: Is there any SQL Server built-in function to convert string in camel case? however, it does not work with the all caps words. Below is my modified version.


DECLARE @String    NVARCHAR(MAX) = 'ThisIsASentenceWithCAPTIInIt'
    --, @Len       INT         --  = LEN(@String)
      , @Delimiter CHAR(1)       = ' '
      , @Iterator  INT           = 2; --Don't put space to left of first even if it's a capital

WHILE @Iterator <= LEN(@String)
-- Iterate through string
    BEGIN
        -- Check if current character is Uppercase (lowercase = 0)
        IF PATINDEX('[ABCDEFGHIJKLMNOPQRSTUVWXYZ]', SUBSTRING(@String, @Iterator, 1) COLLATE Latin1_General_CS_AI) <> 0
        -- Do this if capital
            BEGIN
                -- check if the previous character is lowercase, if it is then add a space before the current character.
                IF(
                   (PATINDEX('[ABCDEFGHIJKLMNOPQRSTUVWXYZ]', SUBSTRING(@String, @Iterator - 1, 1) COLLATE Latin1_General_CS_AI) = 0
                    AND SUBSTRING(@String, @Iterator - 1, 1) <> ' '
                   )
                   OR PATINDEX('[ABCDEFGHIJKLMNOPQRSTUVWXYZ]', SUBSTRING(@String, @Iterator + 1, 1) COLLATE Latin1_General_CS_AI) = 0)
                    BEGIN
                        SET @String = STUFF(@String, @Iterator, 0, @Delimiter);
                        SET @Iterator+=1;
                END;
                -- check if the next character is lowercase, if it is then add a space before the current character.
                SET @Iterator+=1;
        END;
        ---- Don't care about current character being lowercase. Just continue iterating
        SET @Iterator+=1;
    END;

SELECT @String;

Example of what I get from the app - "ThisIsASentenceWithCAPTIInIt"

What I want to pass back - "This Is A Sentence With CAPTI In It"

What I am getting back from my modified version - "This Is ASentence With CAPTIIn It"


Solution

  • This is my suggestion:

    DECLARE @s VARCHAR(100)='ThisIsASentenceWithCAPTIInIt';
    
    WITH cte AS
    (
        SELECT 1 AS Position
              ,@s AS Original
              ,CAST(SUBSTRING(@s,1,1) AS VARCHAR(MAX)) AS GrowingString
        UNION ALL
        SELECT cte.Position+1
              ,cte.Original
              ,CONCAT(cte.GrowingString 
              ,CurrentLetter
              ,CASE WHEN CurrentCapit=0 AND NextCapit=1 THEN ' ' ELSE 
                    CASE WHEN CurrentCapit=1 AND NextCapit=1 AND ThirdCapit=0 THEN ' ' ELSE '' END END ) AS GrowingString
        FROM cte
        CROSS APPLY(SELECT SUBSTRING(@s,cte.Position+1,1) CurrentLetter
                          ,SUBSTRING(@s,cte.Position+2,1) NextLetter
                          ,SUBSTRING(@s,cte.Position+3,1)ThirdLetter) A
        CROSS APPLY(SELECT CASE WHEN ASCII(CurrentLetter) BETWEEN 65 AND 90 THEN 1 ELSE 0 END CurrentCapit
                          ,CASE WHEN ASCII(NextLetter) BETWEEN 65 AND 90 THEN 1 ELSE 0 END NextCapit
                          ,CASE WHEN ASCII(ThirdLetter) BETWEEN 65 AND 90 THEN 1 ELSE 0 END ThirdCapit) B
        WHERE cte.Position < LEN(@s)
    )
    SELECT TOP 1 GrowingString
    FROM cte
    ORDER BY Position DESC;
    

    The idea in short:

    We use a recursive CTE. The simple approach will just read the string character by character and will return the whole string in GrowingString at the end. This would be just the same as the original string.

    The tricky part is: Where to insert spaces?

    If the current letter is not capitalized and the next is, we need a space. That's for sure. Furthermore, the embedded CASE will test if the current and the next letter is capitalized but the third one. In this case we need the space too.