@@ -63,6 +63,26 @@ function parseSetEntry(entry: string): Set<string> {
6363 return set ;
6464}
6565
66+ const ansiEscapeCode = '[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-PRZcf-nqry=><]' ,
67+ zeroWidthCharacterExceptNewline =
68+ '\u0000-\u0009\u000B-\u0019\u001b\u180e\u009b\u00ad\u200b\u2028\u2029\ufeff\ufe00-\ufe0f' ;
69+
70+ const zeroWidthCharactersExceptNewline = new RegExp (
71+ // eslint-disable-next-line no-misleading-character-class
72+ '(?:' + ansiEscapeCode + ')|[' + zeroWidthCharacterExceptNewline + ']' ,
73+ 'g'
74+ ) ;
75+
76+ function nonVisibleCharChecker ( entry : string | undefined ) {
77+ if ( ! entry ) {
78+ return null ;
79+ }
80+
81+ zeroWidthCharactersExceptNewline . lastIndex = 0 ;
82+ const nonVisibleCharCheck = zeroWidthCharactersExceptNewline . exec ( entry ) ;
83+ return nonVisibleCharCheck ;
84+ }
85+
6686const ZOD_TYPE_NAMES = [
6787 'ZodNumber' ,
6888 'ZodString' ,
@@ -506,17 +526,40 @@ export namespace Zod3 {
506526 ) ;
507527 }
508528
529+ let rowNumber = 1 ;
530+
531+ const regexResultSubject = nonVisibleCharChecker ( dataLines [ 0 ] [ 0 ] ) ;
532+ const regexResultDate = nonVisibleCharChecker ( dataLines [ 0 ] [ 1 ] ) ;
533+
534+ if ( regexResultSubject !== null ) {
535+ const charCode = regexResultSubject [ 0 ] . charCodeAt ( 0 ) . toString ( 16 ) . toUpperCase ( ) . padStart ( 4 , '0' ) ;
536+ return reject (
537+ new UploadError ( {
538+ en : `Subject ID at row ${ rowNumber } contains non-visible character(s) (U+${ charCode } )` ,
539+ fr : `L'ID du sujet à la ligne ${ rowNumber } contient des caractères non visible(s) (U+${ charCode } )`
540+ } )
541+ ) ;
542+ }
543+ if ( regexResultDate !== null ) {
544+ const charCode = regexResultDate [ 0 ] . charCodeAt ( 0 ) . toString ( 16 ) . toUpperCase ( ) . padStart ( 4 , '0' ) ;
545+ return reject (
546+ new UploadError ( {
547+ en : `Date at row ${ rowNumber } contains non-visible character(s) (U+${ charCode } )` ,
548+ fr : `Date à la ligne ${ rowNumber } contient des caractères non visible(s) (U+${ charCode } )`
549+ } )
550+ ) ;
551+ }
552+
509553 //remove sample data if included remove any mongolian vowel separators
510554 if (
511- dataLines [ 0 ] [ 0 ] ?. replace ( / [ \u200B - \u200D \uFEFF \u180E ] / g , '' ) . trim ( ) === INTERNAL_HEADERS_SAMPLE_DATA [ 0 ] &&
512- dataLines [ 0 ] [ 1 ] ?. replace ( / [ \u200B - \u200D \uFEFF \u180E ] / g , '' ) . trim ( ) === INTERNAL_HEADERS_SAMPLE_DATA [ 1 ]
555+ dataLines [ 0 ] [ 0 ] ?. trim ( ) === INTERNAL_HEADERS_SAMPLE_DATA [ 0 ] &&
556+ dataLines [ 0 ] [ 1 ] ?. trim ( ) === INTERNAL_HEADERS_SAMPLE_DATA [ 1 ]
513557 ) {
514558 dataLines . shift ( ) ;
515559 }
516560
517561 const result : FormTypes . Data [ ] = [ ] ;
518562
519- let rowNumber = 1 ;
520563 for ( const elements of dataLines ) {
521564 const jsonLine : { [ key : string ] : unknown } = { } ;
522565 for ( let i = 0 ; i < headers . length ; i ++ ) {
@@ -525,6 +568,19 @@ export namespace Zod3 {
525568 if ( rawValue === '\n' ) {
526569 continue ;
527570 }
571+
572+ //Check for non visible char in every row, return error if present
573+ const nonVisibleChars = nonVisibleCharChecker ( rawValue ) ;
574+ if ( nonVisibleChars !== null ) {
575+ const charCode = nonVisibleChars [ 0 ] . charCodeAt ( 0 ) . toString ( 16 ) . toUpperCase ( ) . padStart ( 4 , '0' ) ;
576+ return reject (
577+ new UploadError ( {
578+ en : `Value at row ${ rowNumber } and column '${ key } ' contains non-visible character(s) (U+${ charCode } )` ,
579+ fr : `La valeur à la ligne ${ rowNumber } et colonne '${ key } ' contient des caractère(s) non visibles (U+${ charCode } )`
580+ } )
581+ ) ;
582+ }
583+
528584 if ( shape [ key ] === undefined ) {
529585 return reject (
530586 new UploadError ( {
@@ -541,7 +597,7 @@ export namespace Zod3 {
541597 return reject (
542598 new UploadError ( {
543599 en : `${ error . description . en } at column name: '${ key } ' and row number '${ rowNumber } '` ,
544- fr : `${ error . description . fr } au nom de colonne : '${ key } ' et numéro de ligne '${ rowNumber } `
600+ fr : `${ error . description . fr } au nom de colonne : '${ key } ' et numéro de ligne '${ rowNumber } ' `
545601 } )
546602 ) ;
547603 }
@@ -560,7 +616,8 @@ export namespace Zod3 {
560616 console . error ( `Failed to parse data: ${ JSON . stringify ( jsonLine ) } ` ) ;
561617 return reject (
562618 new UploadError ( {
563- en : 'Schema parsing failed: refer to the browser console for further details'
619+ en : 'Schema parsing failed: refer to the browser console for further details' ,
620+ fr : `Échec de l'analyse du schéma : reportez-vous à la console du navigateur pour plus de détails`
564621 } )
565622 ) ;
566623 }
@@ -824,24 +881,58 @@ export namespace Zod4 {
824881 }
825882
826883 //remove sample data if included (account for old mongolian vowel separator templates)
884+ // Return an error if non-visible characters are found
885+
886+ let rowNumber = 1 ;
887+
888+ const regexResultSubject = nonVisibleCharChecker ( dataLines [ 0 ] [ 0 ] ) ;
889+ const regexResultDate = nonVisibleCharChecker ( dataLines [ 0 ] [ 1 ] ) ;
890+
891+ if ( regexResultSubject !== null ) {
892+ const charCode = regexResultSubject [ 0 ] . charCodeAt ( 0 ) . toString ( 16 ) . toUpperCase ( ) . padStart ( 4 , '0' ) ;
893+ return reject (
894+ new UploadError ( {
895+ en : `Subject ID at row ${ rowNumber } contains non-visible characters (U+${ charCode } )` ,
896+ fr : `L'ID du sujet à la ligne ${ rowNumber } contient des caractères non visibles (U+${ charCode } )`
897+ } )
898+ ) ;
899+ }
900+ if ( regexResultDate !== null ) {
901+ const charCode = regexResultDate [ 0 ] . charCodeAt ( 0 ) . toString ( 16 ) . toUpperCase ( ) . padStart ( 4 , '0' ) ;
902+ return reject (
903+ new UploadError ( {
904+ en : `Date at row ${ rowNumber } contains non-visible characters (U+${ charCode } )` ,
905+ fr : `Date à la ligne ${ rowNumber } contient des caractères non visibles (U+${ charCode } )`
906+ } )
907+ ) ;
908+ }
827909
828910 if (
829- dataLines [ 0 ] [ 0 ] ?. replace ( / [ \u200B - \u200D \uFEFF \u180E ] / g , '' ) . trim ( ) === INTERNAL_HEADERS_SAMPLE_DATA [ 0 ] &&
830- dataLines [ 0 ] [ 1 ] ?. replace ( / [ \u200B - \u200D \uFEFF \u180E ] / g , '' ) . trim ( ) === INTERNAL_HEADERS_SAMPLE_DATA [ 1 ]
911+ dataLines [ 0 ] [ 0 ] ?. trim ( ) === INTERNAL_HEADERS_SAMPLE_DATA [ 0 ] &&
912+ dataLines [ 0 ] [ 1 ] ?. trim ( ) === INTERNAL_HEADERS_SAMPLE_DATA [ 1 ]
831913 ) {
832914 dataLines . shift ( ) ;
833915 }
834916
835917 const result : FormTypes . Data [ ] = [ ] ;
836918
837- let rowNumber = 1 ;
838919 for ( const elements of dataLines ) {
839920 const jsonLine : { [ key : string ] : unknown } = { } ;
840921 for ( let i = 0 ; i < headers . length ; i ++ ) {
841922 const key = headers [ i ] ! . trim ( ) ;
842- const rawValue = elements [ i ] ! . trim ( ) ;
843- if ( rawValue === '\n' ) {
844- continue ;
923+ const cell = elements [ i ] ;
924+ const rawValue = cell == null ? '' : cell . trim ( ) ;
925+ if ( rawValue === '\n' ) continue ;
926+ // Return error if any non‑visible character is present
927+ const nonVisibleChars = nonVisibleCharChecker ( rawValue ) ;
928+ if ( nonVisibleChars !== null ) {
929+ const charCode = nonVisibleChars [ 0 ] . charCodeAt ( 0 ) . toString ( 16 ) . toUpperCase ( ) . padStart ( 4 , '0' ) ;
930+ return reject (
931+ new UploadError ( {
932+ en : `Value at row ${ rowNumber } and column '${ key } ' contains non-visible characters (U+${ charCode } )` ,
933+ fr : `La valeur à la ligne ${ rowNumber } et colonne '${ key } ' contient des caractères non visibles (U+${ charCode } )`
934+ } )
935+ ) ;
845936 }
846937 if ( shape [ key ] === undefined ) {
847938 return reject (
@@ -859,7 +950,7 @@ export namespace Zod4 {
859950 return reject (
860951 new UploadError ( {
861952 en : `${ error . description . en } at column name: '${ key } ' and row number '${ rowNumber } '` ,
862- fr : `${ error . description . fr } au nom de colonne : '${ key } ' et numéro de ligne '${ rowNumber } `
953+ fr : `${ error . description . fr } au nom de colonne : '${ key } ' et numéro de ligne '${ rowNumber } ' `
863954 } )
864955 ) ;
865956 }
0 commit comments