GetExtensionPart fails with a space

Just starting out? Need help? Post your questions and find answers here.
BarryG
Addict
Addict
Posts: 4123
Joined: Thu Apr 18, 2019 8:17 am

GetExtensionPart fails with a space

Post by BarryG »

Just discovered this problem today:

Code: Select all

Debug GetExtensionPart("c:\test\file.john doe") ; Returns "" instead of "john doe"
Yes, extensions don't normally have a space in them, but my app won't work if they do. :( Is this a bug?
Last edited by BarryG on Wed Jul 30, 2025 11:13 am, edited 1 time in total.
Fred
Administrator
Administrator
Posts: 18153
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: GetExtensionPart fails with a space

Post by Fred »

Windows doesn't support this, you can check in Explorer, the column 'type' will display nothing if you have a space.
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 4946
Joined: Sun Apr 12, 2009 6:27 am

Re: GetExtensionPart fails with a space

Post by RASHAD »

Hi BarryG
More simple (I think)

Code: Select all

Procedure.s MyGetExtensionPart(txt$)
  txt$ = ReplaceString(txt$," ","_")
  ext$ = GetExtensionPart(txt$)
  ext$ = ReplaceString(ext$,"_"," ")
  ProcedureReturn ext$
EndProcedure

Debug MyGetExtensionPart("c:\test\file.john doe") ; Returns "" instead of "john doe"
Egypt my love
BarryG
Addict
Addict
Posts: 4123
Joined: Thu Apr 18, 2019 8:17 am

Re: GetExtensionPart fails with a space

Post by BarryG »

Nice, Rashad! I will use a variation of that. :) (Can't use underscores in case they're already in the ext).
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 4946
Joined: Sun Apr 12, 2009 6:27 am

Re: GetExtensionPart fails with a space

Post by RASHAD »

How about :mrgreen:

Code: Select all

Procedure.s MyGetExtensionPart(txt$)
  txt$ = ReplaceString(txt$," ","*#?")
  ext$ = GetExtensionPart(txt$)
  ext$ = ReplaceString(ext$,"*#?"," ")
  ProcedureReturn ext$
EndProcedure

Debug MyGetExtensionPart("c:\test\file.john doe") ; Returns "" instead of "john doe"

Egypt my love
BarryG
Addict
Addict
Posts: 4123
Joined: Thu Apr 18, 2019 8:17 am

Re: GetExtensionPart fails with a space

Post by BarryG »

I ended up using the below, since I assume most file extensions don't have Chr(1) in them. :)

Note that I use GetExtensionPart() first, as it's probably faster (for my bulk file renaming). I only fall into the ReplaceString() parts if actually needed.

Code: Select all

Procedure.s GetFileExtension(file$) ; https://www.purebasic.fr/english/viewtopic.php?p=643514#p643514
  ext$=GetExtensionPart(file$)
  If ext$=""
    ext$=ReplaceString(file$," ",Chr(1))
    ext$=ReplaceString(GetExtensionPart(ext$),Chr(1)," ")
  EndIf
  ProcedureReturn ext$
EndProcedure

file$="C:\File Path\Name.John Doe"
Debug GetExtensionPart(file$) ; ""
Debug GetFileExtension(file$) ; John Doe
User avatar
NicTheQuick
Addict
Addict
Posts: 1503
Joined: Sun Jun 22, 2003 7:43 pm
Location: Germany, Saarbrücken
Contact:

Re: GetExtensionPart fails with a space

Post by NicTheQuick »

Here are my ideas. I don't like the ReplaceString method.

Code: Select all

Procedure.s MyGetExtensionPart1(path.s)
	path = GetFilePart(path)
	If Not FindString(path, "."):
		ProcedureReturn ""
	EndIf
	ProcedureReturn ReverseString(StringField(ReverseString(path), 1, "."))
EndProcedure
Procedure.s MyGetExtensionPart2(text.s)
	Protected dotCount.i = CountString(text, ".")
	If Not dotCount
		ProcedureReturn ""
	EndIf
	ProcedureReturn StringField(text, dotCount + 1, ".")
EndProcedure


Debug MyGetExtensionPart1("c:\test\file.john doe")
Debug MyGetExtensionPart2("c:\test\file.john doe")
Debug MyGetExtensionPart1("john doe")
Debug MyGetExtensionPart2("john doe")
Btw: Would be nice if StringField() could work with negative indexes.
The english grammar is freeware, you can use it freely - But it's not Open Source, i.e. you can not change it or publish it in altered way.
Little John
Addict
Addict
Posts: 4777
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: GetExtensionPart fails with a space

Post by Little John »

NicTheQuick wrote: Wed Jul 30, 2025 11:38 am

Code: Select all

Procedure.s MyGetExtensionPart2(text.s)
	Protected dotCount.i = CountString(text, ".")
	If Not dotCount
		ProcedureReturn ""
	EndIf
	ProcedureReturn StringField(text, dotCount + 1, ".")
EndProcedure
This is elegant, but not bullet-proof. Try this:

Code: Select all

Debug GetExtensionPart("c:\test\.foo")
Debug MyGetExtensionPart2("c:\test\.foo")
I have extended your code a little bit:

Code: Select all

Procedure.s MyGetExtensionPart2 (path$)
   Protected filePart$ = GetFilePart(path$)
   Protected dotCount.i = CountString(filePart$, ".")
   
   If dotCount = 0 Or (dotCount = 1 And Asc(filePart$) = '.')
      ProcedureReturn ""
   Else   
      ProcedureReturn StringField(filePart$, dotCount+1, ".")
   EndIf
EndProcedure


; -- Demo
Debug GetExtensionPart("c:\test.it\demo.txt")          ; -> "txt"
Debug MyGetExtensionPart2("c:\test.it\demo.txt")       ; -> "txt"
Debug "----------------"
Debug GetExtensionPart("c:\test.it\demo")              ; -> ""
Debug MyGetExtensionPart2("c:\test.it\demo")           ; -> ""
Debug "----------------"
Debug GetExtensionPart("c:\test.it\.foo")              ; -> ""
Debug MyGetExtensionPart2("c:\test.it\.foo")           ; -> ""
Debug "----------------"
Debug GetExtensionPart("c:\test.it\file.john doe")     ; -> ""
Debug MyGetExtensionPart2("c:\test.it\file.john doe")  ; -> "john doe"
Last edited by Little John on Wed Jul 30, 2025 6:45 pm, edited 1 time in total.
Wolfram
Enthusiast
Enthusiast
Posts: 604
Joined: Thu May 30, 2013 4:39 pm

Re: GetExtensionPart fails with a space

Post by Wolfram »

Code: Select all


Procedure.s MyGetExtensionPart3(*p)
  
  *str.string = @*p
  length = StringByteLength(*str\s)

  *char.character = *p +length
  
  
  Repeat
    
    *char -SizeOf(character)
    
    If *char\c = 46
      *char +SizeOf(character)
      *str = @*char
      
      ProcedureReturn *str\s
      
    EndIf
   
  Until *char = *str
  
  ProcedureReturn ""
EndProcedure


Debug MyGetExtensionPart3(@"c:\test\file.john doe") ; -> john doe
macOS Catalina 10.15.7
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 4946
Joined: Sun Apr 12, 2009 6:27 am

Re: GetExtensionPart fails with a space

Post by RASHAD »

I like the game :D

Code: Select all

Procedure.s MyGetExtensionPart(path.s)
  pos = FindString(path,".") 
  extn = Len(path)-pos
  ProcedureReturn Right(path,extn)
EndProcedure

Debug MyGetExtensionPart("c:\test\file.john doe")
Egypt my love
User avatar
NicTheQuick
Addict
Addict
Posts: 1503
Joined: Sun Jun 22, 2003 7:43 pm
Location: Germany, Saarbrücken
Contact:

Re: GetExtensionPart fails with a space

Post by NicTheQuick »

RASHAD wrote: Wed Jul 30, 2025 4:16 pm I like the game :D

Code: Select all

Procedure.s MyGetExtensionPart(path.s)
  pos = FindString(path,".") 
  extn = Len(path)-pos
  ProcedureReturn Right(path,extn)
EndProcedure

Debug MyGetExtensionPart("c:\test\file.john doe")
It's not that easy. Try paths with multiple dots. :D
Also you could even simplify it using `Mid()` instead of `Right()`. :wink:
The english grammar is freeware, you can use it freely - But it's not Open Source, i.e. you can not change it or publish it in altered way.
User avatar
mk-soft
Always Here
Always Here
Posts: 6202
Joined: Fri May 12, 2006 6:51 pm
Location: Germany

Re: GetExtensionPart fails with a space

Post by mk-soft »

Fred wrote: Wed Jul 30, 2025 10:58 am Windows doesn't support this, you can check in Explorer, the column 'type' will display nothing if you have a space.
I have to agree with Fred. Thus, this is not an extension and thus your functions are an error ...
My Projects ThreadToGUI / OOP-BaseClass / EventDesigner V3
PB v3.30 / v5.75 - OS Mac Mini OSX 10.xx - VM Window Pro / Linux Ubuntu
Downloads on my Webspace / OneDrive
Little John
Addict
Addict
Posts: 4777
Joined: Thu Jun 07, 2007 3:25 pm
Location: Berlin, Germany

Re: GetExtensionPart fails with a space

Post by Little John »

mk-soft wrote: Wed Jul 30, 2025 6:39 pm
Fred wrote: Wed Jul 30, 2025 10:58 am Windows doesn't support this, you can check in Explorer, the column 'type' will display nothing if you have a space.
I have to agree with Fred.
And what about Linux and macOS?
RASHAD
PureBasic Expert
PureBasic Expert
Posts: 4946
Joined: Sun Apr 12, 2009 6:27 am

Re: GetExtensionPart fails with a space

Post by RASHAD »

Code: Select all

Procedure.s MyGetExtensionPart(path.s)
  For i = 1 To 20
    ext.s = Right(path,i)
    If Left(ext,1) = "."
      ext = Right(ext,i-1)
      Break
    EndIf  
  Next
ProcedureReturn ext
EndProcedure

path.s = "c:\test\file.file.file.john doe"
Debug MyGetExtensionPart(path)
Egypt my love
SMaag
Enthusiast
Enthusiast
Posts: 302
Joined: Sat Jan 14, 2023 6:55 pm
Location: Bavaria/Germany

Re: GetExtensionPart fails with a space

Post by SMaag »

"c:\test\file.john doe" is not allowed and an error!

But it is possible to get correct "john doe" with FindCharRevers()

Code: Select all

EnableExplicit 

Prototype.i FindCharReverse(String$, cSearch.c, *outLength.Integer=0)
Global FindCharReverse.FindCharReverse                  ; define Prototype-Handler for FindCharReverse

Procedure.i _FindCharReverse(*String.Character, cSearch.c, *outLength.Integer=0)
; ============================================================================
; NAME: FindCharReverse
; DESC: !PointerVersion! use it as ProtoType FindCharReverse()
; DESC: Finds the first psoition of Char from reverse
; DESC: 
; VAR(*String.Character) : Pointer to the String
; VAR(cSearch) : Character to find
; VAR(*outLength.Integer): Optional a Pointer to an Int to receive the Length
; RET.i : Character Position 1..n of cSearch or 0 if no Char was found
; ============================================================================ 
  
  CompilerIf (#PB_Compiler_Backend = #PB_Backend_Asm) And #PB_Compiler_64Bit And #PB_Compiler_Unicode
    
    ; Used Registers:
    ;   RAX : Pointer *String
    ;   RCX : operating Register and Bool: 1 if NullChar was found
    ;   RDX : operating Register
    ;   R8  : *pCharFound -> NoOfChars
    ;   R9  : operating Register
    
    ;   XMM0 : the 4 Chars
    ;   XMM1 : cSearch shuffeled to all Words
    ;   XMM2 : 0 to search for EndOfString
    ;   XMM3 : the 4 Chars Backup
    
    ; If you use XMM4..XMM7 you have to backup it first
    ;   XMM4 : 
    ;   XMM5 : 
    ;   XMM6 :
    ;   XMM7 :
   
    ; ----------------------------------------------------------------------
    ; Check *String-Pointer and MOV it to RAX as operating register
    ; ----------------------------------------------------------------------
    !MOV RAX, [p.p_String]    ; load String address
    !Test RAX, RAX            ; If *String = 0
    !JZ .Return               ; Exit    
    !SUB RAX, 8               ; Sub 8 to start with Add 8 in the Loop     
    ; ----------------------------------------------------------------------
    ; Setup start parameter for registers 
    ; ----------------------------------------------------------------------     
    ; your indiviual setup parameters
    !MOVZX RDX, WORD [p.v_cSearch] ; ZeroExpanded load of 1 Word = load Char
    !MOVQ XMM1, RDX
    !PSHUFLW XMM1, XMM1, 0    ; Shuffle/Copy Word0 to all Words 
    
    ; here are the standard setup parameters
    !XOR RCX, RCX             ; operating Register and BOOL for EndOfStringFound
    !XOR R8, R8               ; *pCharFound = 0
    !PXOR XMM2, XMM2          ; XMM2 = 0 ; Mask to search for NullChar = EndOfString         
    ; ----------------------------------------------------------------------
    ; Main Loop
    ; ----------------------------------------------------------------------     
    !.Loop:
      !ADD RAX, 8                     ; *String + 8 => NextChars    
      !MOVQ XMM0, [RAX]               ; load 4 Chars to XMM0  
      !MOVQ XMM3, [RAX]               ; load 4 Chars to XMM3
      !PCMPEQW XMM0, XMM2             ; Compare with 0
      !MOVQ RDX, XMM0                 ; RDX CompareResult contains FFFF for each NullChar 
      !TEST RDX, RDX                     ; If 0 : No NullChar found
      !JZ .EndIf                   ; JumpIfEqual 0 => JumpToEndif if Not EndOfString  
      ; If EndOfStringFound  
        ; Caclulate the Bytepostion of EndOfString [0..3] using Bitscan
        !BSF RDX, RDX                 ; BitSanForward => No of the LSB   
        !SHR RDX, 3                   ; BitNo to ByteNo
        !ADD RAX, RDX                 ; Actual StringPointer + OffsetOf_NullChar
        !MOV RCX, RDX                 ; Save ByteOfsett of NullChar in RCX
        !SUB RAX, [p.p_String]        ; RAX *EndOfString - *String
        !SHR RAX, 1                   ; NoOfBytes to NoOfWord => Len(String)
        ; ------------------------------------------------------------
        ; check for Return of Length and and move it to *outLength 
        ; ------------------------------------------------------------
        !MOV RDX, [p.p_outLength]
        !TEST RDX, RDX
        !JZ @f                        ; If *outLength
          !MOV [RDX], RAX             ;   *outLength = Len()
        !@@:                          ; Endif
        ; ------------------------------------------------------------      
        
        ; If a Nullchar was found Then create a Bitmask for setting all Chars after the NullChar to 00h 
        ; In RCX ist the Backup of the ByteOffset of NullChahr
        !CMP RCX, 6                   ; If NullChar is the last Char : Byte[7,6]=Word[3]
        !JGE @f                       ;  => we don't have to eliminate chars from testing
          ; If WordPos(EndOfString) <> 3  ; Word3 if EndOfString is in Bit 48..63 = Word 3
          !SHL RCX, 3                   ; ByteNo to BitNo
          !NEG RCX                      ; RCX = -LSB 
          !ADD RCX, 63                  ; RCX = (63-LSB)
          !XOR RDX, RDX                 ; RDX = 0
          !BTS RDX, 63                  ; set Bit 63 => RDX = 8000000000000000h
          !SAR RDX, CL                  ; Do an arithmetic Shift Right (63-LSB) : EndOfString=Word2 => Mask $FFFF.FFFF.0000.0000, Word1 $FFFF.FFFF.FFFF.0000
          !NOT RDX                      ; Now invert our Mask so we get a Mask to fileter out all Chars after EndOfString $0000.0000.FFFF.FFFF or $0000.0000.0000.FFFF
          !MOVQ XMM0, RDX               ; Now move this Mask to XMM0, the operating Register
          !PAND XMM3, XMM0              ; XMM3 the CharBackup AND Mask => we select only Chars up to EndOfString 
        !@@:
        
        !MOV RCX, 1                     ; BOOL EndOfStringFound = #TRUE
      !.EndIf:                        ; Endif ; EndOfStringFound    
      
      ; ------------------------------------------------------------
      ; Start of function individual code! Do not use RCX here!
      ; ------------------------------------------------------------
      ; Count number of found Chars
      !MOVQ XMM0, XMM3              ; Load the 4 Chars to operating Register
      !PCMPEQW XMM0, XMM1           ; Compare the 4 Chars with cSearch
      !MOVQ RDX, XMM0               ; CompareResult to RDX
      !TEST RDX, RDX
      !JZ @f                          ; Jump to Endif if cSearch not found
        ; !BSF RDX, RDX                 ; BitScan finds the LSB Bitposition
         ; because we need Character from right, we have to use BSR, not BSF
         !BSR RDX, RDX               ; BitScan finds the MSB Bitposition
    	 !SHR RDX, 3                   ; BitNo => ByteNO
        !MOV R8, RAX                  ; R8 = *pCharFound
        !ADD R8, RDX                  ; ADD ByteNo to *String
      !@@: 
      ; ------------------------------------------------------------
      
      !TEST RCX, RCX                ; Check BOOL EndOfStringFound      
    !JZ .Loop                       ; Continue Loop if Not EndOfStringFound
    
    ; ----------------------------------------------------------------------
    ; Handle Return value an POP-Registers
    ; ----------------------------------------------------------------------         
    
    !XOR RAX, RAX             ; RAX = 0
    !TEST R8, R8              ; TEST *pCharFound = 0
    !JZ .Return               ; If *pCharFound = 0 Then .Return 0
    ; Else Return NoOfChars
    !MOV RAX, R8              ; RAX = *pCharFound
    !SUB RAX, [p.p_String]    ; RAX = *pCharFound - *String
    !SHR RAX, 1               ; Bytes -> Chars
    !INC RAX
    !.Return:
    
    ProcedureReturn   ; RAX
   
  CompilerElse
    
    ; ----------------------------------------------------------------------
    ;  PB-Standard-Code
    ; ----------------------------------------------------------------------         
    
    Protected *pChar.Character = *String
    Protected *LastChar
   
    If Not *pChar
      ProcedureReturn 0
    EndIf
    
    While *pChar\c
      If *pChar\c = cSearch
        *LastChar = *pChar
      EndIf
      *pChar + SizeOf(Character)
    Wend
          
    If *outLength
      *outLength\i = (*pChar - *String) / SizeOf(Character)
    EndIf
    
    If *LastChar=0 
      ProcedureReturn 0
    Else
      ProcedureReturn (*LastChar - *String) / SizeOf(Character) + 1
    EndIf
        
  CompilerEndIf
  
EndProcedure
FindCharReverse = @_FindCharReverse()   ; Bind ProcedureAddress to the PrototypeHandler


Procedure.s GetFileExtension(File$)
  Protected CharPos
  CharPos = FindCharReverse(File$, '.')
  If CharPos  
    ProcedureReturn PeekS(@File$+CharPos*2)
  EndIf
  ProcedureReturn #Null$
EndProcedure

  
Define File1$ = "c:\test\file.john doe"
Define File2$ = "c:\test\files.list.dat"

Debug File1$
Debug GetFileExtension(File1$)
Debug ""
Debug File2$
Debug GetFileExtension(File2$)


  
  
  
Last edited by SMaag on Thu Jul 31, 2025 11:17 am, edited 1 time in total.
Post Reply