Index: libcfa/src/containers/string.cfa
===================================================================
--- libcfa/src/containers/string.cfa	(revision 09264872a331b4bed71c918508e0ab499161d1fc)
+++ libcfa/src/containers/string.cfa	(revision 7e1dbd72b91978bc0a2745abacb829909bdfc517)
@@ -10,6 +10,6 @@
 // Created On       : Fri Sep 03 11:00:00 2021
 // Last Modified By : Peter A. Buhr
-// Last Modified On : Thu Aug 24 11:27:57 2023
-// Update Count     : 89
+// Last Modified On : Mon Aug 28 19:00:45 2023
+// Update Count     : 146
 //
 
@@ -118,33 +118,114 @@
 }
 
-void getline( ifstream & in, string & str, const char delimit = '\n' ) {
-	// .---------------,
-	// | | | | |...|0|0| check and guard
-	// `---------------'
-	enum { gwd = 256 + 2, wd = gwd - 1 };				// guarded and unguarded width
+static void readstr( ifstream & is, _Istream_str f, char fmtstr[], char cstr[] ) {
+	int check = f.rwd - 1;
+
+	if ( ! f.flags.rwd ) cstr[check] = '\0';			// insert sentinel
+	int len = fmt( is, fmtstr, cstr );
+	// fprintf( stderr, "KK %s %zd %d %c %s\n", fmtstr, len, check, cstr[check], cstr );
+
+	if ( ! f.flags.rwd && cstr[check] != '\0' )			// sentinel overwritten ?
+		throw (cstring_length){ &cstring_length_vt };
+
+	if ( f.flags.delimit ) {							// getline ?
+		if ( len == 0 ) cstr[0] = '\0';					// empty read => argument unchanged => set empty
+		if ( ! eof( is ) ) fmt( is, "%*c" );			// ignore delimiter
+	} //if
+} // readstr
+
+ifstream & ?|?( ifstream & is, _Istream_str f ) {
+	// skip, same as for char *
+	if ( ! &f.s ) {
+		// fprintf( stderr,  "skip %s %d\n", f.scanset, f.wd );
+		if ( f.rwd == -1 ) fmt( is, f.scanset, "" ); // no input arguments
+		else for ( f.rwd ) fmt( is, "%*c" );
+		return is;
+	} // if
+
+	enum { gwd = 16 + 2, wd = gwd - 1 };				// guarded and unguarded width
 	char cstr[gwd];										// read in chunks
 	bool cont = false;;
 
+	if ( f.rwd == -1 ) f.rwd = wd;
+	const char * scanset = f.scanset;;
+	if ( f.flags.delimit ) scanset = f.delimit;			// getline ?
+
+	size_t len = 0;
+	if ( scanset ) len = strlen( scanset );
+	char fmtstr[len + 16];
+	int start = 1;
+	fmtstr[0] = '%';
+	if ( f.flags.ignore ) { fmtstr[1] = '*'; start += 1; }
+	if ( f.rwd != -1 ) { start += sprintf( &fmtstr[start], "%d", f.rwd ); }
+
+	if ( ! scanset ) {
+		// %s, %*s, %ws, %*ws
+		fmtstr[start] = 's'; fmtstr[start + 1] = '\0';
+		// printf( "cstr %s\n", fmtstr );
+	} else {
+		// incl %[xxx],  %*[xxx],  %w[xxx],  %*w[xxx]
+		// excl %[^xxx], %*[^xxx], %w[^xxx], %*w[^xxx]
+		fmtstr[start] = '['; start += 1;
+		if ( f.flags.inex ) { fmtstr[start] = '^'; start += 1; }
+		strcpy( &fmtstr[start], scanset );				// copy includes '\0'
+		len += start;
+		fmtstr[len] = ']'; fmtstr[len + 1] = '\0';
+		// printf( "incl/excl %s\n", fmtstr );
+	} // if
+
 	cstr[wd] = '\0';									// guard null terminate string
 	try {
-		in | getline( wdi( wd, cstr ), delimit );		// must have room for string terminator
-		if ( eof( in  ) ) return;						// do not change argument
+		readstr( is, f, fmtstr, cstr );
 	} catch( cstring_length * ) {
-		cont = true;									// continue not allowed
+		cont = true;
 	} finally {
-		str = cstr;										// ok to initialize string
+		f.s = cstr;										// ok to initialize string
 	} // try
 	for ( ; cont; )  {									// overflow read ?
 		cont = false;
 		try {
-			in | getline( wdi( wd, cstr ), delimit );	// must have room for string terminator
-			if ( eof( in  ) ) return;
+			readstr( is, f, fmtstr, cstr );
 		} catch( cstring_length * ) {
 			cont = true;								// continue not allowed
 		} finally {
-			str += cstr;								// build string chunk at a time
+			f.s += cstr;								// build string chunk at a time
 		} // try
 	} // for
-}
+	return is;
+} // ?|?
+
+void ?|?( ifstream & in, _Istream_str f ) {
+    (ifstream &)(in | f); ends( in );
+}
+
+// void getline( ifstream & in, string & str, const char delimit = '\n' ) {
+// 	// .---------------,
+// 	// | | | | |...|0|0| check and guard
+// 	// `---------------'
+// 	enum { gwd = 128 + 2, wd = gwd - 1 };				// guarded and unguarded width
+// 	char cstr[gwd];										// read in chunks
+// 	bool cont = false;;
+
+// 	cstr[wd] = '\0';									// guard null terminate string
+// 	try {
+// 		in | getline( wdi( wd, cstr ), delimit );		// must have room for string terminator
+// 		if ( eof( in  ) ) return;						// do not change argument
+// 	} catch( cstring_length * ) {
+// 		cont = true;
+// 	} finally {
+// 		str = cstr;										// ok to initialize string
+// 	} // try
+// 	for ( ; cont; )  {									// overflow read ?
+// 		cont = false;
+// 		try {
+// 			in | getline( wdi( wd, cstr ), delimit );	// must have room for string terminator
+// 			if ( eof( in  ) ) return;
+// 		} catch( cstring_length * ) {
+// 			cont = true;								// continue not allowed
+// 		} finally {
+// 			str += cstr;								// build string chunk at a time
+// 		} // try
+// 	} // for
+// }
 
 ////////////////////////////////////////////////////////
Index: libcfa/src/containers/string.hfa
===================================================================
--- libcfa/src/containers/string.hfa	(revision 09264872a331b4bed71c918508e0ab499161d1fc)
+++ libcfa/src/containers/string.hfa	(revision 7e1dbd72b91978bc0a2745abacb829909bdfc517)
@@ -10,6 +10,6 @@
 // Created On       : Fri Sep 03 11:00:00 2021
 // Last Modified By : Peter A. Buhr
-// Last Modified On : Wed Aug 23 11:16:14 2023
-// Update Count     : 6
+// Last Modified On : Mon Aug 28 18:32:59 2023
+// Update Count     : 40
 //
 
@@ -57,4 +57,44 @@
 ifstream & ?|?(ifstream & in, string & s);
 void ?|?( ifstream & in, string & this );
+
+
+struct _Istream_str {
+	string & s;
+	union {
+		const char * scanset;
+		char delimit[2];
+	};
+	int rwd;											// read width
+	union {
+		unsigned char all;
+		struct {
+			unsigned char ignore:1;						// do not change input argument
+			unsigned char inex:1;						// include/exclude characters in scanset
+			unsigned char delimit:1;					// delimit character
+			unsigned char rwd:1;						// read width
+		} flags;
+	};
+}; // _Istream_str
+
+static inline {
+	// read width does not include null terminator
+	_Istream_str wdi( unsigned int rwd, string & s ) { return (_Istream_str)@{ s, {0p}, rwd, {.flags.rwd : true} }; }
+	_Istream_str skip( const char scanset[] ) { return (_Istream_str)@{ *0p, {scanset}, -1, {.all : 0} }; }
+	_Istream_str skip( unsigned int wd ) { return (_Istream_str)@{ *0p, {0p}, wd, {.all : 0} }; }
+	_Istream_str getline( string & s, const char delimit = '\n' ) {
+		return (_Istream_str)@{ s, {.delimit : { delimit, '\0' } }, -1, {.flags.delimit : true, .flags.inex : true} };
+	}
+	_Istream_str & getline( _Istream_str & fmt, const char delimit = '\n' ) {
+		fmt.delimit[0] = delimit; fmt.delimit[1] = '\0'; fmt.flags.delimit = true; fmt.flags.inex = true; return fmt;
+	}
+	_Istream_str incl( const char scanset[], string & s ) { return (_Istream_str)@{ s, {scanset}, -1, {.flags.inex : false} }; }
+	_Istream_str & incl( const char scanset[], _Istream_str & fmt ) { fmt.scanset = scanset; fmt.flags.inex = false; return fmt; }
+	_Istream_str excl( const char scanset[], string & s ) { return (_Istream_str)@{ s, {scanset}, -1, {.flags.inex : true} }; }
+	_Istream_str & excl( const char scanset[], _Istream_str & fmt ) { fmt.scanset = scanset; fmt.flags.inex = true; return fmt; }
+	_Istream_str ignore( string & s ) { return (_Istream_str)@{ s, {0p}, -1, {.flags.ignore : true} }; }
+	_Istream_str & ignore( _Istream_str & fmt ) { fmt.flags.ignore = true; return fmt; }
+} // distribution
+ifstream & ?|?( ifstream & is, _Istream_str f );
+void ?|?( ifstream & is, _Istream_str t );
 void getline( ifstream & in, string & this, const char delimit = '\n' );
 
