title: A better way to split a string into an array of strings in C/C++ using whitespace as a delimiter
body: <p>Sorry, my C/C++ is not that good, but the following existing code looks like garbage even to me. It also has a bug - fails when str = "07/02/2010" terminated by '\0' - . I think that instead of fixing a bug, it might as well be rewritten. In Python it is just <code>'kas\nhjkfh kjsdjkasf'.split()</code>. I know this is C-ish code, but it cannot be that complicated to split a string! Sticking to the same signature, and without using extra libraries, how can I improve it - make it short and sweet? I can tell that this code smells, for instance because of the else clause all the way at the end.</p>
  
  <p>LINE THAT FAILS:</p>
  
  <pre><code>_tcsncpy_s(
      s.GetBuffer((int) (nIndex-nLast)),
      nIndex-nLast,
      psz+nLast,
      (size_t) (nIndex-nLast)
  );
  </code></pre>
  
  <p>With the string "07/02/2010" terminated by '\0' it will try to write 11 characters into a buffer that is only 10 characters long.</p>
  
  <p>FULL FUNCTION:</p>
  
  <pre><code>#define 
  
  // This will return the text string as a string array
  // This function is called from SetControlText to parse the
  // text string into an array of CStrings that the control
  // Gadgets will attempt to interpret
  
  BOOL CLVGridDateTimeCtrl::ParseTextWithCurrentFormat(const CString&amp; str, const CGXStyle* pOldStyle, CStringArray&amp; strArray )
  {
      // Unused:
      pOldStyle;
  
      // we assume that the significant segments are seperated by space
  
      // Please change m_strDelim to add other delimiters
  
      CString s;
  
      LPCTSTR psz = (LPCTSTR) str;
  
      BOOL bLastCharSpace = FALSE;
      DWORD size = str.GetLength()+1;
  
      // (newline will start a new row, tab delimiter will
      // move to the next column).
      // parse buffer (DBCS aware)
      for (DWORD nIndex = 0, nLast = 0; nIndex &lt; size; nIndex += _tclen(psz+nIndex))
      {
          // check for a delimiter
          if (psz[nIndex] == _T('\0') || _tcschr(_T("\r\n"), psz[nIndex]) || _tcschr(_T(" "), psz[nIndex])
              ||!_tcscspn(&amp;psz[nIndex], (LPCTSTR)m_strDelim))
          {
              s.ReleaseBuffer();
              s.Empty();
              // abort parsing the string if next char
              // is an end-of-string
              if (psz[nIndex] == _T('\0'))
              {
                  if (psz[nIndex] == _T('\r') &amp;&amp; psz[nIndex+1] == _T('\n'))
                      nIndex++;
  
                  _tcsncpy_s(s.GetBuffer((int) (nIndex-nLast)),
                      nIndex-nLast,
                              psz+nLast,
                              (size_t) (nIndex-nLast));
                  CString temStr = s;
                  strArray.Add(temStr);
                  temStr.Empty();
                  break;
              }
  
              else if (_tcscspn(&amp;psz[nIndex], (LPCTSTR)m_strDelim) == 0 &amp;&amp; !bLastCharSpace)
              {
                  if (psz[nIndex] == _T('\r') &amp;&amp; psz[nIndex+1] == _T('\n'))
                      nIndex++;
  
                  _tcsncpy_s(s.GetBuffer((int) (nIndex-nLast)),
                      nIndex-nLast,
                              psz+nLast,
                              (size_t) (nIndex-nLast));
                  CString temStr = s;
                  strArray.Add(temStr);
                  temStr.Empty();
                  bLastCharSpace = TRUE;
                  // abort parsing the string if next char
                  // is an end-of-string
                  if (psz[nIndex+1] == _T('\0'))
                      break;
  
              }
              // Now, that the value has been copied to the cell,
              // let's check if we should jump to a new row.
              else if (_tcschr(_T(" "), psz[nIndex]) &amp;&amp; !bLastCharSpace)
              {
                  if (psz[nIndex] == _T('\r') &amp;&amp; psz[nIndex+1] == _T('\n'))
                      nIndex++;
  
                  _tcsncpy_s(s.GetBuffer((int) (nIndex-nLast)),
                      nIndex-nLast,
                              psz+nLast,
                              (size_t) (nIndex-nLast));
                  CString temStr = s;
                  strArray.Add(temStr);
                  temStr.Empty();
                  bLastCharSpace = TRUE;
                  // abort parsing the string if next char
                  // is an end-of-string
                  if (psz[nIndex+1] == _T('\0'))
                      break;
              }
  
              nLast = nIndex + _tclen(psz+nIndex);
  
  
          }
          else
          {   
              // nLast = nIndex + _tclen(psz+nIndex);
              bLastCharSpace = FALSE;
          }
      }
      if (strArray.GetSize())
          return TRUE;
      else
          return FALSE;
  }
  </code></pre>
  
  <p><strong><em>EDIT:</em></strong>
  <code>m_strDelim = _T(",");</code> and this member variable is used in this function only. I suppose I see the point of tokenization now - it tries to parse a date and time ... wait, there is more! Here is the code which calls this function below. Please help me improve this as well. Some of my co-workers claim that C# makes them no more productive than C++. I used to feel like an idiot for not being able to say the same about me.</p>
  
  <pre><code>// SetControlText will attempt to convert the text to a valid date first with
  // the help of COleDateTime and then with the help of the Date control and the
  // current format
  
  BOOL CLVGridDateTimeCtrl::ConvertControlTextToValue(CString&amp; str, ROWCOL nRow, ROWCOL nCol, const CGXStyle* pOldStyle)
  {
      CGXStyle* pStyle = NULL;
      BOOL bSuccess = FALSE;
  
      if (pOldStyle == NULL)
      {
          pStyle = Grid()-&gt;CreateStyle();
          Grid()-&gt;ComposeStyleRowCol(nRow, nCol, pStyle);
          pOldStyle = pStyle;
      }
  
      // allow only valid input
      {
          // First do this
          CLVDateTime dt;
  
          if (str.IsEmpty())
          {
              ;
              // if (Grid()-&gt;IsCurrentCell(nRow, nCol))
              //  Reset();
              bSuccess = TRUE;
          }
          else if (dt.ParseDateTime(str,CLVGlobals::IsUSDateFormat()) &amp;&amp; (DATE) dt != 0)
          {
              SetDateTime(dt);
              if (m_bDateValueAsNumber)
                  str.Format(_T("%g"), (DATE) dt);
              else
                  str = dt.Format();
              bSuccess = TRUE;
          }
          else
          {
              // parse the string using the current format
              CStringArray strArray;
              if (!ParseTextWithCurrentFormat(str, pOldStyle, strArray))
                  return FALSE;
  
              UpdateNullStatus(m_TextCtrlWnd);
  
              SetFormat(m_TextCtrlWnd, *pOldStyle);
  
              int nArrIndex = 0;
              for(int i=0; i&lt;m_TextCtrlWnd.m_gadgets.GetSize(); i++)
              {
                  int val = m_TextCtrlWnd.m_gadgets[i]-&gt;GetValue();   
                  // s.Empty();
                  if(m_TextCtrlWnd.m_gadgets[i]-&gt;IsKindOf(RUNTIME_CLASS(SECDTNumericGadget)))
                  {
                      // TRACE(_T("The value %s\n"), strArray[nArrIndex]);
                      ((CLVDTNumericGadget*)m_TextCtrlWnd.m_gadgets[i])-&gt;m_nNewValue = _ttoi(strArray[nArrIndex]);    
                      nArrIndex++;
                      if (nArrIndex&gt;strArray.GetUpperBound())
                              break;
                  }
                  else if(m_TextCtrlWnd.m_gadgets[i]-&gt;IsKindOf(RUNTIME_CLASS(SECDTListGadget)) &amp;&amp; val!=-1)
                  {
                      int nIndex = ((CLVDTListGadget*)m_TextCtrlWnd.m_gadgets[i])-&gt;FindMatch(strArray[nArrIndex], ((CLVDTListGadget*)m_TextCtrlWnd.m_gadgets[i])-&gt;GetValue()+1);
                      if (nIndex!=-1)
                      {
                          // TRACE(_T("The value %s\n"), strArray[nArrIndex]);
                          ((CLVDTListGadget*)m_TextCtrlWnd.m_gadgets[i])-&gt;SetValue(nIndex);
                          nArrIndex++;
                          if (nArrIndex&gt;strArray.GetUpperBound())
                              break;
                      }
  
                  }
  
                  CLVDBValue dbDate = m_TextCtrlWnd.GetDateTime();
                  if (dbDate.IsNull())
                      str = _T("");
                  else
                  {
                      CLVDateTime dt = (CLVDateTime)dbDate;
                      if (m_bDateValueAsNumber)
                          str.Format(_T("%g"), (DATE) dt);
                      else
                          str = dt.Format();
                  }
              }
              bSuccess = TRUE;
          }
      }
  
      if (pStyle)
          Grid()-&gt;RecycleStyle(pStyle);
  
      return bSuccess;
  }
  </code></pre>