Copy link to clipboard
Copied
I want to make an action for the Find and Change option, but that doesn't work.
For example I want an action for: find 'Space' change to 'Nothing'
Is there another way to automate this?
Copy link to clipboard
Copied
Hi @Adriaan_Metz Another way to automate this would be to use a script. I have previously written a script that performs a find/change grep (a bit like in Indesign) which you could use for this task. Here it is below, and I've configured the settings object to search for a space and replace with nothing (empty string) with no UI showing. You can run this script from an action and assign a keyboard shortcut, or choose it from the scripts menu as usual for scripts. Let me know if it helps.
- Mark
/**
* @file Find Change RegExp.js
*
* Perform a find/change operation on textRanges of target object(s).
*
* Notes
* - `findWhat` is a RegExp and is always global.
*
* @author m1b
* @version 2025-02-09
* @discussion https://community.adobe.com/t5/illustrator-discussions/illustrator-jsx-to-delete-lt-b-gt-lt-b-gt-tags-in-paragraph-text-an-keep-the-differents-font-style/m-p/14269543
*/
(function () {
if (0 === app.documents.length)
return alert('Please open a document and try again.');
var doc = app.activeDocument;
var settings = {
// what to search (can be a document, a story, a text frame etc.)
target: doc.selection,
// find using RegExp
findWhat: / /,
// replacement string
changeTo: '',
// whether to search lock and/or hidden texts
doNotSearchLockedOrHidden: true,
// whether to show a UI
showUI: false,
// whether to show results after script is finished
showResult: true,
};
// show UI
if (
settings.showUI
&& 2 === ui(settings)
)
// user cancelled
return;
if (
undefined != settings.findWhat
&& 'String' === settings.findWhat.constructor.name
)
// convert to RegExp
settings.findWhat = new RegExp(settings.findWhat, 'g');
if (
undefined == settings.findWhat
|| settings.findWhat.constructor.name !== 'RegExp'
)
return alert('Sorry, "' + (settings.findWhat || 'findWhat') + '" was an invalid RegExp.');
// findWhat regex must be global!
if (!settings.findWhat.global)
settings.findWhat = new RegExp(settings.findWhat.source, 'g'
+ (settings.findWhat.ignoreCase ? 'i' : '')
+ (settings.findWhat.multiline ? 'm' : ''));
var textRanges = getTexts(settings.target, settings.doNotSearchLockedOrHidden),
counter = 0;
for (var i = textRanges.length - 1; i >= 0; i--) {
if (
settings.doNotSearchLockedOrHidden
&& isLockedOrHidden(textRanges[i].story.textFrames[0])
)
// ignore this text
continue;
// do the find/change
counter += findChangeRegExp(textRanges[i], settings.findWhat, settings.changeTo);
}
if (settings.showResult) {
app.redraw();
alert('Performed ' + counter + ' find/change operations.');
}
})();
/**
* Returns array of TextRanges, given `target`.
* @author m1b
* @version 2025-02-04
* @param {TextRange|TextFrame|Document|GroupItem} target - the item to search for texts.
* @returns {Array<TextRange>}
*/
function getTexts(target) {
var texts = [];
if (target == undefined)
throw Error('findChange: bad `text` supplied. Expected [TextRange] or [TextFrame]');
if ('Document' === target.constructor.name)
target = asArray(target.stories);
if (
(
// to bypass bug in TextFrames object:
undefined != target.typename
&& 'TextFrames' === target.typename
)
|| 'Stories' === target.constructor.name
)
target = asArray(target);
if ('Array' === target.constructor.name) {
// handle array of text frames
for (var i = target.length - 1; i >= 0; i--)
texts = texts.concat(getTexts(target[i]));
}
else if (
'GroupItem' === target.constructor.name
|| 'Layer' === target.constructor.name
) {
// handle the group's items
for (var i = target.pageItems.length - 1; i >= 0; i--)
texts = texts.concat(getTexts(target.pageItems[i]));
}
if (
'Story' === target.constructor.name
|| 'TextFrame' === target.constructor.name
)
target = target.textRange;
if ('TextRange' === target.constructor.name)
texts.push(target)
return texts;
};
/**
* Performs a Find/Change operation on `text` and
* returns a count of how many changes were made.
* @author m1b
* @version 2023-12-01
* @param {TextRange} text - the text to search.
* @param {RegExp} findWhat - the RegExp to find with.
* @param {String} [changeTo] - the change-to string.
* @param {Boolean} [adjustSelection] - whether to adjust the selection (default: true when `text` has textSelection).
* @returns {Number}
*/
function findChangeRegExp(text, findWhat, changeTo, adjustSelection) {
var counter = 0;
if (
undefined == findWhat
|| 'RegExp' !== findWhat.constructor.name
)
throw TypeError('findChange: Bad `findWhat` supplied.');
// reset findWhat index
findWhat.lastIndex = 0;
if (changeTo == undefined)
changeTo = '';
var contents = text.contents,
selectLength = 0,
selectStart,
selectEnd,
match,
found = [];
if (
text.hasOwnProperty('textSelection')
&& text.textSelection.length > 0
) {
// record the selection for later
selectStart = text.textSelection[0].start;
selectEnd = text.textSelection[0].end;
selectLength = selectEnd - selectStart;
}
while (match = findWhat.exec(contents))
found.push({
start: match.index,
end: findWhat.lastIndex,
str: contents.slice(match.index, findWhat.lastIndex),
});
counter = found.length;
// process each found, going backwards
var s, e;
while (match = found.pop()) {
// remove the found text, except first character
for (e = match.end - 1, s = match.start; s < e; e--) {
text.characters[e].remove();
selectEnd--;
}
var replacement = match.str.replace(findWhat, changeTo);
if (replacement) {
// set the contents of the first character
text.characters[e].contents = replacement;
selectEnd += replacement.length - match.str.length;
}
else {
// no contents to replace with
text.characters[e].remove();
selectEnd--;
}
}
if (
!adjustSelection
&& selectLength > 0
) {
// adjust the selection
app.selection = [];
for (var s = selectStart; s < selectEnd; s++)
text.story.characters[s].select(true);
}
return counter;
};
/**
* Returns a collection as an Array.
* @param {Collection} objs - the collection object.
* @returns {Array<*>}
*/
function asArray(objs) {
var arr = [];
try {
for (var i = 0; i < objs.length; i++)
arr.push(objs[i]);
} catch (error) { }
return arr;
};
/**
* Returns true if item
* is locked or hidden.
* @param {PageItem} item - an Illustrator PageItem.
* @returns {Boolean}
*/
function isLockedOrHidden(item) {
return (
(
item.locked != undefined
&& item.locked == true
)
|| (
item.hidden != undefined
&& item.hidden == true
)
|| (
item.layer != undefined
&& (
item.layer.locked == true
|| item.layer.visible == false
)
)
);
};
/**
* Shows UI suitable for Find/Change
* @param {Object} settings
* @returns {1|2} - ScriptUI result code.
*/
function ui(settings) {
var targets = [];
if (app.activeDocument.selection.length > 0)
targets.push(
{ label: 'Selection', get: function () { return app.activeDocument.selection } }
);
targets.push(
{ label: 'Document', get: function () { return app.activeDocument } },
);
var labelWidth = 100,
columnWidth = 200;
var w = new Window("dialog { text:'Find/Change RegExp' }"),
fields = w.add("Group { orientation: 'column', alignment: ['fill','fill'] }"),
findWhatGroup = fields.add("Group { orientation: 'row', alignment: ['fill','fill'] }"),
findWhatLabel = findWhatGroup.add("StaticText { text: 'Find what:', justify: 'right' }"),
findWhatField = findWhatGroup.add("EditText { text:'', alignment:['fill','top'] }"),
changeToGroup = fields.add("Group { orientation: 'row', alignment: ['fill','fill'] }"),
changeToLabel = changeToGroup.add("StaticText { text: 'Change to:', justify: 'right' }"),
changeToField = changeToGroup.add("EditText { text:'', alignment:['fill','top'] }"),
targetMenuGroup = w.add("Group { orientation: 'row', alignment: ['fill','fill'] }"),
targetLabel = targetMenuGroup.add("StaticText { text: 'Target', justify: 'right' }"),
targetMenu = targetMenuGroup.add("Dropdownlist {alignment:['right','center'] }"),
checkBoxes = w.add("group {orientation:'row', alignment:['fill','bottom'], alignChildren:'left', margins:[5,0,5,0] }"),
ignoreCaseCheckBox = checkBoxes.add("Checkbox { text:'Ignore case', alignment:'left', value:false }"),
doNotSearchLockedOrHiddenCheckBox = checkBoxes.add("Checkbox { text:'Ignore locked/hidden text', alignment:'left', value:false }"),
bottomUI = w.add("group {orientation:'row', alignment:['fill','fill'], margins:[0,20,0,0] }"),
buttons = bottomUI.add("group {orientation:'row', alignment:['right','center'], alignChildren:'right' }"),
cancelButton = buttons.add("Button { text: 'Cancel', properties: {name:'cancel'} }"),
okayButton = buttons.add("Button { text:'Change', enabled: true, properties: {name:'ok'} }");
targetLabel.preferredSize.width = labelWidth;
targetMenu.preferredSize.width = columnWidth;
findWhatLabel.preferredSize.width = labelWidth;
findWhatField.preferredSize.width = columnWidth;
findWhatField.text = settings.findWhat.source;
changeToLabel.preferredSize.width = labelWidth;
changeToField.preferredSize.width = columnWidth;
changeToField.text = settings.changeTo;
ignoreCaseCheckBox.value = settings.findWhat.ignoreCase;
doNotSearchLockedOrHiddenCheckBox.value = settings.doNotSearchLockedOrHidden;
var targetTypes = [];
for (var i = 0; i < targets.length; i++)
targetTypes.push(targets[i].label);
buildMenu(targetMenu, targetTypes, settings.targetType);
okayButton.onClick = function () { return update() && w.close(1) };
cancelButton.onClick = function () { w.close(2) };
w.center();
return w.show();
/**
* Builds dropdown menu items.
*/
function buildMenu(menu, arr, index) {
for (var i = 0; i < arr.length; i++)
menu.add('item', arr[i]);
menu.selection = [index || 0];
};
/**
* Updates settings.
* @returns {Boolean} - valid RegExp?
*/
function update() {
settings.target = targets[targetMenu.selection.index].get();
settings.changeTo = changeToField.text;
settings.findText = findWhatField.text;
settings.ignoreCase = !!ignoreCaseCheckBox.value;
settings.doNotSearchLockedOrHidden = !!doNotSearchLockedOrHiddenCheckBox.value;
try {
settings.findWhat = RegExp(findWhatField.text, 'g' + (ignoreCaseCheckBox.value ? 'i' : ''));
} catch (error) {
settings.findWhat = undefined;
}
return settings.findWhat !== undefined;
};
};
Copy link to clipboard
Copied
Thank you for your solution, but scripting is not my cup off tea...
Copy link to clipboard
Copied
Okay, all good, but just checking ... are you saying that (a) you're not interested in a solution that involves a script, or are you saying that (b) you need help using a script because you are inexperienced?
Copy link to clipboard
Copied
No, I want to learn, but I didn't do anything with scripting sofar. So if you can help or if you know a tutorial where it will be explaned easily for me, it's welcom