@NDL_DH – Yes, as I previously wrote I don't know why the formatting is lost or how to avoid that. More accomplished scripters may be able to answer.
You can use the Edit > Find and Replace Text command. ScriptingListener can also capture the use of this as well and it doesn't remove the formatting in my simple tests.
The issue is that it uses strings as input/output, I can't work out how to "inject" variables or regular expressions. Therefore it would need repetition with hard-coded values: find/replace two spaces with one space. Repeat again for three spaces and replace with one space etc. Not the end of the world, but it could be better...
Edit:
findReplaceText(" ", " ", true, false, false, false, false); // 2 spaces
findReplaceText(" ", " ", true, false, false, false, false); // 3 spaces
findReplaceText(" ", " ", true, false, false, false, false); // 4 spaces
findReplaceText(" ", " ", true, false, false, false, false); // 5 spaces
function findReplaceText(find, replace, checkAll, forward, caseSensitive, wholeWord, ignoreAccents) {
function s2t(s) {
return app.stringIDToTypeID(s);
}
var descriptor = new ActionDescriptor();
var descriptor2 = new ActionDescriptor();
var reference = new ActionReference();
reference.putProperty( s2t( "property" ), s2t( "replace" ));
reference.putEnumerated( s2t( "textLayer" ), s2t( "ordinal" ), s2t( "allEnum" ));
descriptor.putReference( s2t( "null" ), reference );
descriptor2.putString( s2t( "find" ), find );
descriptor2.putString( s2t( "replace" ), replace );
descriptor2.putBoolean( s2t( "checkAll" ), checkAll );
descriptor2.putBoolean( s2t( "forward" ), forward );
descriptor2.putBoolean( s2t( "caseSensitive" ), caseSensitive );
descriptor2.putBoolean( s2t( "wholeWord" ), wholeWord );
descriptor2.putBoolean( s2t( "ignoreAccents" ), ignoreAccents );
descriptor.putObject( s2t( "using" ), s2t( "findReplace" ), descriptor2 );
executeAction( s2t( "replace" ), descriptor, DialogModes.NO );
}