- PB 4 compatible
 - EnableExplicit compatible
 - Debugger compatible (no ASM, no hacks)
 - Threadsafe in all cases
 - 100% native PureBasic code
 
This code has a testing-unit running when compiled with debugger-support.
(it tests itself for beeing fully operational)
This code is threadsafe, but the objects themselves aren't!
(It's easier to implement this in the calling application)
Docs:
This Code is OOP-code. This means you create objects with which you can work:
Code: Select all
object.ExStringField = ExStringField(String$[,Separators$[,SeparatorsSeparator$[,flags]]])String$: The string that should be seperated
Separator$: One or more separators which are separated by the SeparatorsSeparator$ (default: Space and Tabulator)
SeparatorsSeparator$: The string that separates the separators (default: "|" )
flags: #ExStringField_Flag_NoEmptyFields and/or #ExStringField_Flag_IgnoreDoublequotes
Then you can call following methods on those objects:
Code: Select all
Result$ = object\GetNextField()Code: Select all
Result$ = object\GetRemainingFields()Code: Select all
Result = object\IsNextField()Code: Select all
object\Destroy()Code: Select all
; Author: DaDummy
; EnableExplicit
; This one may also consist of multiple chars but is treated as a single separator
#ExStringField_DefaultSeparatorsSeparator = "|"
; Flags
#ExStringField_Flag_NoEmptyFields      = %00000001
#ExStringField_Flag_IgnoreDoublequotes = %00000010
Structure ExStringField_Properties
  VTable.l
  String.s
  Separators.s
  SeparatorsSeparator.s
  pos.l
  len.l
  sCount.l
  flags.l
EndStructure
Interface ExStringField ; methods
  destroy()
  GetNextField.s()
  GetRemainingFields.s()
  IsNextField.l()
EndInterface
Declare   ExStringField_Destroy(*This.ExStringField_Properties)
Declare.s ExStringField_GetNextField(*This.ExStringField_Properties)       ; Returns the next field or "" if no more fields are availble
Declare.s ExStringField_GetRemainingFields(*This.ExStringField_Properties) ; Returns the rest of the string beginning after the current separator with all following separators intact
Declare.l ExStringField_IsNextField(*This.ExStringField_Properties)        ; Returns True if the end of the String isn't reached
; Constructor
Procedure.l ExStringField(String.s, Separators.s = (" " + #ExStringField_DefaultSeparatorsSeparator + #TAB$), SeparatorsSeparator.s = #ExStringField_DefaultSeparatorsSeparator, flags.l = 0)
  Protected i.l, count.l, *This.ExStringField_Properties
  
  ; Create object
  *This = AllocateMemory(SizeOf(ExStringField_Properties))
  
  ; Always look out for memory problems
  If *This = #Null
    ProcedureReturn #Null
  EndIf
  
  ; Set properties
  With *This
    \VTable              = AllocateMemory(SizeOf(ExStringField))
    \String              = String
    \Separators          = Separators
    \SeparatorsSeparator = SeparatorsSeparator
    \pos                 = 1
    \len                 = Len(String)+1
    \flags               = flags
  EndWith
  
  ; Always look out for memory problems
  If *This\VTable = #Null
    ; Free strings ;)
    With *This
      \String              = ""
      \Separators          = ""
      \SeparatorsSeparator = ""
    EndWith
    
    ; Free memory
    FreeMemory(*This)
    
    ProcedureReturn #Null 
  EndIf
  
  ; Define Methods
  PokeL(*This\VTable + OffsetOf(ExStringField\destroy           ()), @ExStringField_Destroy           ())
  PokeL(*This\VTable + OffsetOf(ExStringField\GetNextField      ()), @ExStringField_GetNextField      ())
  PokeL(*This\VTable + OffsetOf(ExStringField\GetRemainingFields()), @ExStringField_GetRemainingFields())
  PokeL(*This\VTable + OffsetOf(ExStringField\IsNextField       ()), @ExStringField_IsNextField       ())
  
  ; Correct handling for an empty SeparatorsSeparator
  If *This\SeparatorsSeparator = ""
    *This\SeparatorsSeparator = #ExStringField_DefaultSeparatorsSeparator
  EndIf
  
  ; Remove empty separators inside the Separator string
  While FindString(*This\Separators, *This\SeparatorsSeparator+*This\SeparatorsSeparator, 1)
    *This\Separators = ReplaceString(*This\Separators, *This\SeparatorsSeparator+*This\SeparatorsSeparator, SeparatorsSeparator)
  Wend
  ; Remove empty separators at the beginning of the Separator string
  While Left(*This\Separators, 1) = *This\SeparatorsSeparator
    *This\Separators = Right(*This\Separators, Len(*This\Separators)-1)
  Wend
  ; Remove empty separators at the end of the Separator string
  While Right(*This\Separators, 1) = *This\SeparatorsSeparator
    *This\Separators = Left(*This\Separators, Len(*This\Separators)-1)
  Wend
  
  ; Correct handling for an empty Separator
  If *This\Separators = ""
    *This\Separators = (" " + *This\SeparatorsSeparator + #TAB$)
  EndIf
  
  ; Count separators
  *This\sCount = CountString(*This\Separators, SeparatorsSeparator)+1
  
  ProcedureReturn *This
EndProcedure
; Destructor
Procedure   ExStringField_Destroy(*This.ExStringField_Properties)
  ; Free strings ;)
  With *This
    \String              = ""
    \Separators          = ""
    \SeparatorsSeparator = ""
  EndWith
  
  ; Free memory
  FreeMemory(*This\VTable)
  FreeMemory(*This       )
EndProcedure
Procedure.s ExStringField_GetNextField(*This.ExStringField_Properties)       ; Returns the next field or "" if no more fields are availble
  Protected oldPos.l, midPos.l, curPos.l, i.l
  Protected cSeparator.s, retVal.s
  
  Repeat
    If *This\pos = *This\len
      ProcedureReturn ""
    EndIf
    
    oldPos = *This\pos
    midPos = 0
    ; Fields beginning with a doublequote have a second one at their end
    If *This\flags & #ExStringField_Flag_IgnoreDoublequotes = 0 And Mid(*This\String, oldPos, 1) = #DOUBLEQUOTE$
      *This\pos = FindString(*This\String, #DOUBLEQUOTE$, oldPos)
      If *This\pos = 0
        *This\pos = *This\len
      EndIf
    Else
      For i = 1 To *This\sCount
        cSeparator = StringField(*This\Separators, i, *This\SeparatorsSeparator)
        curPos = FindString(*This\String, cSeparator, oldPos)
        If midPos = 0 Or (midPos > curPos And curPos <> 0)
          midPos    = curPos
          *This\pos = curPos + Len(cSeparator)
        EndIf
      Next
      If midPos = 0
        midPos    = *This\len
        *This\pos = *This\len
      EndIf
    EndIf
  Until *This\flags & #ExStringField_Flag_NoEmptyFields = 0 Or midPos-oldPos > 0
  
  retVal = Mid(*This\String, oldPos, midPos-oldPos)
  
  ProcedureReturn retVal
EndProcedure
Procedure.s ExStringField_GetRemainingFields(*This.ExStringField_Properties) ; Returns the rest of the string beginning after the current separator with all following separators intact
  Protected len.l
  Protected retVal.s
  
  If *This\pos = *This\len
    ProcedureReturn ""
  EndIf
  
  len       = *This\len-*This\pos
  *This\pos + len
  
  retVal = Right(*This\String, len)
  
  ProcedureReturn retVal
EndProcedure
Procedure.l ExStringField_IsNextField(*This.ExStringField_Properties)        ; Returns True if the end of the String isn't reached
  If *This\pos = *This\len
    ProcedureReturn #False
  Else
    ProcedureReturn #True
  EndIf
EndProcedure
CompilerIf #PB_Compiler_Debugger
Define.s sval1, sval2
Define.l lval1, lval2
Define.s text, sep, sepsep
Define.ExStringField object
Macro AssertS(val1, val2)
  sval1 = val1
  sval2 = val2
  If sval1 <> sval2
    Debug Str(#PB_Compiler_Line)+": "+Chr('"')+sval1+Chr('"')+"<>"+Chr('"')+sval2+Chr('"')
  EndIf
EndMacro
Macro AssertL(val1, val2)
  lval1 = val1
  lval2 = val2
  If lval1 <> lval2
    Debug Str(#PB_Compiler_Line)+": "+Chr('"')+Str(lval1)+Chr('"')+"<>"+Chr('"')+Str(lval2)+Chr('"')
  EndIf
EndMacro
text   = ""
object = ExStringField(text)
AssertL(object\IsNextField(), #False)
object\destroy()
text   = "a ab  abc a abcd"
object = ExStringField(text)
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "a")
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "ab")
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "")
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "abc")
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "a")
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "abcd")
AssertL(object\IsNextField(), #False)
object\destroy()
text   = "a ab  abc a abcd"
object = ExStringField(text)
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "a")
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "ab")
AssertL(object\IsNextField(), #True)
AssertS(object\GetRemainingFields(), " abc a abcd")
AssertL(object\IsNextField(), #False)
object\destroy()
text   = "a ab //abc a//abcd"
sep    = " |//"
object = ExStringField(text,sep)
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "a")
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "ab")
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "")
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "abc")
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "a")
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "abcd")
AssertL(object\IsNextField(), #False)
object\destroy()
text   = "a ab //abc a//abcd"
sep    = " |//"
object = ExStringField(text,sep)
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "a")
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "ab")
AssertL(object\IsNextField(), #True)
AssertS(object\GetRemainingFields(), "//abc a//abcd")
AssertL(object\IsNextField(), #False)
object\destroy()
text   = "a ab //abc a//abcd"
sep    = " \//"
sepsep = "\"
object = ExStringField(text,sep,sepsep)
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "a")
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "ab")
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "")
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "abc")
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "a")
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "abcd")
AssertL(object\IsNextField(), #False)
object\destroy()
text   = "a ab //abc a//abcd"
sep    = " \//"
sepsep = "\"
object = ExStringField(text,sep,sepsep)
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "a")
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "ab")
AssertL(object\IsNextField(), #True)
AssertS(object\GetRemainingFields(), "//abc a//abcd")
AssertL(object\IsNextField(), #False)
object\destroy()
text   = "a ab //abc a//abcd"
sep    = " \//"
sepsep = "\"
object = ExStringField(text,sep,sepsep,#ExStringField_Flag_NoEmptyFields)
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "a")
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "ab")
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "abc")
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "a")
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "abcd")
AssertL(object\IsNextField(), #False)
object\destroy()
text   = "a ab //abc a//abcd"
sep    = " \//"
sepsep = "\"
object = ExStringField(text,sep,sepsep,#ExStringField_Flag_NoEmptyFields)
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "a")
AssertL(object\IsNextField(), #True)
AssertS(object\GetNextField(), "ab")
AssertL(object\IsNextField(), #True)
AssertS(object\GetRemainingFields(), "//abc a//abcd")
AssertL(object\IsNextField(), #False)
object\destroy()
CompilerEndIfEdit (04/24/2007 14:09): code is now always threadsafe
Edit (05/10/2007 14:51): Added some more Assert-checks and added the IgnoreDoublequotes flag


