/* LICENCE see end of file */ /* --->>> Version number <<<--- Don't forget to update the version number in the private section of the class! */ #ifndef DUMMY // Mandatory Preprocessor Items ================================= // Meta items --------------------------------------------------------------- #ifndef _10_SPLIT_TEXT_IS_STANDALONE #define _10_SPLIT_TEXT_IS_STANDALONE true // If running as a standalone #endif // _10_SPLIT_TEXT_IS_STANDALONE // REQUIRED IN GLOBAL SPACE ================================================! String textToSplit; // Reserved long String to avoid heap fragmentation #endif // Mandatory Preprocessor Items #include "shared_includes.h" #include "shared_globals.h" class SplitText { /** Split text into 'words' * - Use protected or non-protected delimiters * - Protected delimiters are accepted as pairs only * - Delimiters may be of one or of two bytes length * - Two-byte delimiters are to be composed of one kind of char only */ #ifndef DUMMY // private: =================================================== private: /***************** FILE AND VERSION ****************/ /* ! ! ! ! ! ! ! UPDATE VERSION NO ! ! ! ! ! ! ! */ static constexpr char FILE_AND_VERSION[8] = "10.0047"; // Identifiers are listed in alphabetical order /* curPos */ int curPos ; /* delimiter */ String delimiter ; /* delimiterChar */ char delimiterChar ; /* delimiterLen */ int delimiterLen ; /* freshJobDataReceived */ bool freshJobDataReceived ; /* startPos */ int startPos ; /* endPos */ int endPos ; /* preserveDelimiter */ bool preserveDelimiter ; /* textToSplitLen */ int textToSplitLen ; #endif // private: #ifndef DUMMY // public: ==================================================== public: #ifndef DUMMY // initialize() =============================================== static void initialize() { textToSplit = ""; } #endif // initialize() #ifndef DUMMY // freshSplitData() =========================================== void freshSplitData( String text, String delimiter, bool preserveDelimiter) { /** Receives a fresh split-job */ // Set freshJobDataReceived here. Set to false in getNextSplitPositions() freshJobDataReceived = true; // Populate textToSplit with parameter text textToSplit = text; // Disambiguate identical names of class member-values and parameters this->delimiter = delimiter; this->preserveDelimiter = preserveDelimiter; // Dependables delimiterChar = delimiter.charAt(0); delimiterLen = delimiter.length(); textToSplitLen = textToSplit.length(); #ifndef DUMMY // Validate delimiters passed /* * 0) See for delimiter not found or text is empty ------------------- */ bool runTests = true; if ((textToSplitLen == 0 || textToSplit.indexOf(delimiter) == -1) && delimiterLen != 0) { // No further tests are required runTests = false; } /* * 1.0) Error if delimiter is empty ---------------------------------- */ if (runTests && delimiter == "") { // error condition errorType = 1; // WARN #if _10_SPLIT_TEXT_IS_STANDALONE Serial << endl; Serial << "Error: " << __FILE__ << " " << FILE_AND_VERSION << " line " << __LINE__ << endl; Serial << "Test: Delimiter is empty!" << endl; #endif // _10_SPLIT_TEXT_IS_STANDALONE } /* * 1.1) Error if protected delimiters are spaces --------------------- */ if (runTests && (delimiterChar == ' ' || delimiterChar == ' ') && preserveDelimiter) { // error condition errorType = 1; // WARN #if _10_SPLIT_TEXT_IS_STANDALONE Serial << endl; Serial << "Error: " << __FILE__ << " " << FILE_AND_VERSION << " line " << __LINE__ << endl; Serial << "Test: Space(s) as delimiter is not allowed!" << endl; #endif // _10_SPLIT_TEXT_IS_STANDALONE } /* * 1.2) Error if delimiterLen is greater 2 --------------------------- */ if (runTests && delimiterLen > 2) { // error condition errorType = 1; // WARN #if _10_SPLIT_TEXT_IS_STANDALONE Serial << endl; Serial << "Error: " << __FILE__ << " " << FILE_AND_VERSION << " line " << __LINE__ << endl; Serial << "Test: Delimiter is greater than 2!" << endl; #endif // _10_SPLIT_TEXT_IS_STANDALONE } /* * 1.3) Error if delimiters are composed of different characters ----- */ if (runTests && delimiterLen == 2) { if (delimiterChar != delimiter.charAt(1)) { // error condition errorType = 1; // WARN #if _10_SPLIT_TEXT_IS_STANDALONE Serial << endl; Serial << "Error: " << __FILE__ << " " << FILE_AND_VERSION << " line " << __LINE__ << endl; Serial << "Test: Delimiter is composed of different characters!" << endl; #endif // _10_SPLIT_TEXT_IS_STANDALONE } } /* * 1.4) Error on delimiter sequence length --------------------------- */ // delimiterLen = 1 if (runTests && delimiterLen == 1) { // Test for a 'double' delimiter if (textToSplit.indexOf(delimiter + delimiter) > -1) { // error condition errorType = 1; // WARN #if _10_SPLIT_TEXT_IS_STANDALONE Serial << endl; Serial << "Error: " << __FILE__ << " " << FILE_AND_VERSION << " line " << __LINE__ << endl; Serial << "Test: Invalid delimiter length found!" << endl; #endif // _10_SPLIT_TEXT_IS_STANDALONE } } // delimiterLen = 2 else if (runTests && delimiterLen == 2) { // Test for a 'triple' delimiterChar if (textToSplit.indexOf(delimiter + delimiterChar) > -1) { // error condition errorType = 1; // WARN #if _10_SPLIT_TEXT_IS_STANDALONE Serial << endl; Serial << "Error: " << __FILE__ << " " << FILE_AND_VERSION << " line " << __LINE__ << endl; Serial << "Test: Invalid delimiter length found!" << endl; #endif // _10_SPLIT_TEXT_IS_STANDALONE } } /* * 1.5) Error if protected delimiters are not paired ----------------- */ if (runTests && preserveDelimiter) { int i = 0; int delimiterCount = 0; i = textToSplit.indexOf(delimiter); if (i == -1) { // Check is passed (no occurrence of delimiters) } else { for (; i < textToSplitLen; i++) { if (textToSplit.charAt(i) == delimiterChar) { delimiterCount++; } } } // If delimiterCount modulo 2 is unequal 0 if (delimiterCount % 2 != 0) { // error condition errorType = 1; // WARN #if _10_SPLIT_TEXT_IS_STANDALONE Serial << endl; Serial << "Error: " << __FILE__ << " " << FILE_AND_VERSION << " line " << __LINE__ << endl; Serial << "Test: Protected delimiters not paired!" << endl; #endif // _10_SPLIT_TEXT_IS_STANDALONE } } #endif // Validate delimiters passed } #endif // freshSplitData() #ifndef DUMMY // getNextSplitPositions() ==================================== std::pair getNextSplitPositions() { /** Return startPos and endPos of next 'word', * indicating start and end position in textToSplit * Plain and protected delimiters are not accepted to be mixed, * two phases of calls may be required. */ if (freshJobDataReceived) { curPos = 0; startPos = 0; endPos = 0; freshJobDataReceived = false; } // Approach A: Use .indexOf() to find delimiter positions /* * 1.0) Case of preserved delimiters --------------------------------- */ if (preserveDelimiter) { // No definition of starting position - loop starts at actual curPos for (; curPos < textToSplitLen; curPos++) { /* * 1.1) Delimiter is first character ----------------------------- */ if (textToSplit.charAt(curPos) == delimiterChar) { startPos = curPos; endPos = textToSplit.indexOf(delimiter, curPos + 1) + delimiterLen; if (delimiterLen == 2) { curPos++; endPos++; } else { curPos = endPos; } return std::make_pair(startPos, endPos); } /* * 1.2) Section starting 'plain' --------------------------------- */ else { startPos = curPos; endPos = textToSplit.indexOf(delimiter, curPos); // Chars to the right still are existing if (endPos > -1) { curPos = endPos; } // The end of textToSplit has been reached else { endPos = textToSplitLen; curPos = textToSplitLen; } return std::make_pair(startPos, endPos); } } } /* * 2.0) Case of 'plain' delimiters ----------------------------------- */ else { // No definition of starting position - loop starts at actual curPos for (; curPos < textToSplitLen; curPos++) { /* * 2.1) Delimiter is first character ----------------------------- */ if (textToSplit.charAt(curPos) == delimiterChar) { startPos = curPos + delimiterLen; curPos = startPos; /* * 2.2) Section starting 'plain' --------------------------------- */ } else { startPos = curPos; } endPos = textToSplit.indexOf(delimiter, curPos); // Chars to the right still are existing if (endPos > -1) { curPos = endPos + 1; return std::make_pair(startPos, endPos); } // The end of textToSplit has been reached else { curPos = textToSplitLen; return std::make_pair(startPos, textToSplitLen); } } } // Approach B: Count positions between delimiters // Not yet tried // No more words are available return std::make_pair(-1, -1); } #endif // getNextSplitPositions() #endif // public: }; // Instantiation of class ===================================================== SplitText splitText; #if _10_SPLIT_TEXT_IS_STANDALONE // setup() and loop() void setup() { #include "shared_setup.h" // slpitText() calls ====================================================== // Parameter format: text, delimiter, bool (protected=true) #ifndef DUMMY // Tests freshSplitData() // Trigger 1.0) Error if delimiter is empty splitText.freshSplitData("Any text", "", false); splitText.freshSplitData("Any text", "", true); // Trigger 1.1) Error if protected delimiters are spaces splitText.freshSplitData("Any text", " ", true); splitText.freshSplitData("Any text", " ", false); // Trigger 1.2) Error if delimiter is greater than two splitText.freshSplitData("Any text", "xxx", false); splitText.freshSplitData("Any text", "xxx", true); // Trigger 1.3) Error if delimiters are composed of different characters splitText.freshSplitData("Any text", "xy", false); splitText.freshSplitData("Any text", "xy", true); // Trigger 1.4) Error on existence of delimiters of invalid length // Case 1: delimiterLen = 1 splitText.freshSplitData("Text, error exxample", "x", false); splitText.freshSplitData("Text, error exxample", "x", true); // Case 2: delimiterLen = 2 splitText.freshSplitData("Text, error exxxample", "xx", false); splitText.freshSplitData("Text, error exxxample", "xx", true); splitText.freshSplitData("Text, error exxxxample", "xx", false); splitText.freshSplitData("Text, error exxxxample", "xx", true); // Trigger 1.5) Error if protected delimiters are not paired splitText.freshSplitData("Text, error example, a lone 'x'", "x", true); #endif // Tests freshSplitData() #ifndef DUMMY // Tests getNextSplitPositions() std::pair indexPositions; String tmpWord; #ifndef DUMMY // Test for 1.1) Delimiter is first character splitText.freshSplitData( "|Mein| erster Test |für| textToSplit().", "|", // Delimiter true // Protected ); Serial << endl; while (true) { indexPositions = splitText.getNextSplitPositions(); if (indexPositions.first == -1) break; tmpWord = textToSplit.substring(indexPositions.first, indexPositions.second); Serial << tmpWord << endl; } #endif // Test for 1.1) Delimiter is first character #ifndef DUMMY // Test for 1.2) Section starting 'plain' splitText.freshSplitData( "Mein |zweiter| Test für |textToSplit().|", "|", // Delimiter true // Protected ); Serial << endl; while (true) { indexPositions = splitText.getNextSplitPositions(); if (indexPositions.first == -1) break; tmpWord = textToSplit.substring(indexPositions.first, indexPositions.second); Serial << tmpWord << endl; } #endif // Test for 1.2) Section starting 'plain' #ifndef DUMMY // Test for 2.1) Delimiter is first character splitText.freshSplitData( " Mein dritter Test für textToSplit().", " ", // Delimiter false // Protected ); Serial << endl; while (true) { indexPositions = splitText.getNextSplitPositions(); if (indexPositions.first == -1) break; tmpWord = textToSplit.substring(indexPositions.first, indexPositions.second); Serial << tmpWord << endl; } #endif // Test for 2.1) Delimiter is first character #ifndef DUMMY // Test for 2.2) Section starting 'plain' splitText.freshSplitData( "Mein vierter Test für textToSplit().", " ", // Delimiter false // Protected ); Serial << endl; while (true) { indexPositions = splitText.getNextSplitPositions(); if (indexPositions.first == -1) break; tmpWord = textToSplit.substring(indexPositions.first, indexPositions.second); Serial << tmpWord << endl; } #endif // Test for 2.2) Section starting 'plain' #endif // Test getNextSplitPositions() } void loop() { } #endif // setup() and loop() /*** History *** * 2025-06-18: * - Added licence (#046) * - Corrected various logical pitfalls (#045) * 2025-06-17: * - Corrected logic of curPos assignment (#045) * - Added std::pair getNextSplitPositions() (#044) * 2025-06-16 (#042-043): * - Moved regarding items in setup() to shared_setup.h * - Moved regarding includes to shared_includes.h * - Ordered globally required items and removed tests on textToSplit * - Ordered globally required items and removed tests on textToSplit * 2025-06-15 (#040-42): * - Moved textToSplit to global scope, accessible for all * - Corrected endless loops, simplified error and invalid checks logic * - Basic setup, including freshSplitData() */ /***** LICENCE ***** Fermenting Box Control © 2024 by Uli / ulisblog.info / ulrich-hauser.de ======================================================================= including its header-files and custom-libraries is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International CC BY-NC-SA 4.0 To view a copy of this license, visit https://creativecommons.org/licenses/by-nc-sa/4.0// */