Copy link to clipboard
Copied
Hello!
Required to make search and replace in a text frame using RegExp.
And at that keep original character formatting of the text frame.
How it's possible?
Bad example – it changes the formating of the entire text to the format of the first character:
var reg = /a/gmi;
var replacer = '*';
var fr = activeDocument.textFrames[0];
fr.contents = fr.contents.replace (reg , replacer);
Thanks!
I didn't tested your code yet.
But why you didn't play with my code snippet and use something like this:
// regex_changeContentsOfWordOrString_RemainFormatting.jsx
// regards pixxxel schubser
var s = /arguments/gi;
var replacer = "other string", result;
var atf = activeDocument.textFrames[0];
while (result = s.exec(atf.contents)) {
try {
aCon = atf.characters[result.index];
aCon.length = result[0].length;
aCon.contents = replacer;
} catch (e) {};
}
Try it and have fun
Copy link to clipboard
Copied
This one's a lot more tricky when wanting to keep formatting: you have got to select the stretch of text you want using one of the selection commands, however they have it in the textRange object, or one of the object's descendants. Then, when your text range is selected you can replace just the contents of that range, which keeps the styling. This is exactly the same method where if you highlight the text manually and type in replacement text, it will keep the styling. However for reasons unknown, this method would always produce 2 ranges within the selection: the bulk of the range from 1st character to 2nd-to-last character and another range with just the last character. Not sure why it occurs, but it's a mild expected annoyance.
Here, I actually looked it back up for you, meditate on this following scripture and the righteous path shall be revealed to thee:
#target illustrator-20
function test(){
var doc = app.activeDocument;
var s = doc.textFrames[0];
s.textRange.characters[0].select();
s.textRange.characters[1].select(true);
};
test();
Copy link to clipboard
Copied
Silly-V, many thanks for the thought!
If I understand you correctly, the sequence of actions can be something like this:
Define attributes for comparison
It seems that there is one problem: from the search is excluded the boundary between the two blocks with different formatting...
But, in any case, this is the best solution available.
And I forgot to clarify one obvious point (just in case): search for a single character (as in my first post) does not present any problems - the replacement can be carried out in a loop over the elements TextFrame.textRanges .
The whole point in more complex regexp.
Copy link to clipboard
Copied
This is character by character splits the selected frame to the blocks with the same formatting and then search/replace in these blocks. "The same formatting" is checked on the 4th parameters (font family, size, style, color). The boundaries between the blocks falls out of the search area. It works slowly.
function repl (reStr, replacer) {
var re = new RegExp (reStr, 'gmi');
var frm = selection[0];
var a, b,
i = 0,
txtOrig, txtModif;
frm.textRange.characters.select ();
for (; ; i++) {
try {
a = frm.textRange.characters;
b = frm.textRange.characters[i + 1];
} catch (e) {
_replRng ();
break;
}
if (_cmprTxtAttr (a, b)) {
b.select (true);
continue;
} else {
_replRng ();
i += txtModif.length - txtOrig.length;
frm.textRange.characters[i + 1].select ();
continue;
}
}
function _replRng () { // nothing return; only modify global vars
if (frm.textSelection.length > 1) {
txtOrig = frm.textSelection[0].contents + frm.textSelection[1].contents;
txtModif = txtOrig.replace (re, replacer);
frm.textSelection[1].contents = txtModif.slice (-1);
frm.textSelection[0].contents = txtModif.slice (0, -1);
} else {
txtOrig = frm.textSelection[0].contents;
txtModif = txtOrig.replace (re, replacer);
frm.textSelection[0].contents = txtModif;
}
}
function _cmprTxtAttr (a, b) {
return __compareFill (a, b) && __compareFont (a, b);
function __compareFill (a, b) {
var aCol = a.characterAttributes.fillColor;
var bCol = b.characterAttributes.fillColor;
if (aCol.typename != bCol.typename) return false;
if (aCol.typename == 'RGBColor') {
if (aCol.red != bCol.red || aCol.green != bCol.green || aCol.blue != bCol.blue) {
return false;
}
} else if (aCol.typename == 'CMYKColor') {
if (aCol.cyan != bCol.cyan || aCol.magenta != bCol.magenta ||
aCol.yellow != bCol.yellow || aCol.black != bCol.black) {
return false;
}
}
return true;
}
function __compareFont (a, b) {
var aFnt = a.characterAttributes;
var bFnt = b.characterAttributes;
if (aFnt.size != bFnt.size) return false;
if (aFnt.textFont.style != bFnt.textFont.style) return false;
if (aFnt.textFont.family != bFnt.textFont.family) return false;
return true;
}
}
}
Copy link to clipboard
Copied
Sorry, not sure how this works, I tested with a simple sentence and all it does is select the last part of text after the last match.
repl("bird", "puppy-dog");
Before:
After:
Copy link to clipboard
Copied
From the search area excluded zones with different formatting. Of course it needs correcting.
The algorithm is like this:
Search in MSWord or InDesign behaves the nearly same.
Copy link to clipboard
Copied
Why do you need to take the formatting of the first character? If your routine will highlight the entire match word and the style is consistent, the style will be preserved by default, as if you did it manually by highlighting and typing new characters.
If you replace the contents of the .textSelection (which is an 'array of textRange') of a textRange it should work out just fine.
Copy link to clipboard
Copied
Yea it's a tricky one- I got one to work finally. And yea for those matches where the style is different within the match, maybe do the format comparison then.
#target illustrator
function test(){
var doc = app.activeDocument;
var s = doc.textFrames[0];
var oldWord = "bird";
var oldWordRx = new RegExp(oldWord, "g");
var newWord = "puppy-dog";
var textString = s.contents;
var matches = textString.match(oldWordRx);
if(!matches){return;}
var indexArr;
for(var i=0, ln = matches.length; i<ln; i++){
oldWordRx.exec(s.contents);
indexArr = [oldWordRx.lastIndex - oldWord.length, oldWordRx.lastIndex];
for(var j=0; j<s.textRange.characters.length; j++){
if(j >= indexArr[0] && j <= indexArr[1]){
s.textRange.characters
}
}
s.textRange.textSelection[0].contents = newWord;
s.textRange.deSelect();
};
};
test();
Copy link to clipboard
Copied
o-marat schrieb:
Hello!
Required to make search and replace in a text frame using RegExp.
And at that keep original character formatting of the text frame.
How it's possible?
Going this way
// regex_changeContentsOfSingleCharacterRemainFormatting.jsx
// regards pixxxel schubser
var s = /a/gi;
var replacer = "*";
var result;
var atf = activeDocument.textFrames[0];
while (result = s.exec(atf.contents)) {
try {
atf.characters[result.index].contents = replacer;
} catch (e) {};
}
Have fun
Copy link to clipboard
Copied
Now it is working as it should. Thank you all for your help!
function replaceKeepFormatting (reg, replacer, txtFrame) {
var result;
while (result = reg.exec (txtFrame.contents)) {
for (var i = 0; i < result[0].length; i++) {
txtFrame.characters[result.index + i].select (true);
}
if (txtFrame.textSelection.length == 2) {
var str = (txtFrame.textSelection[0].contents + txtFrame.textSelection[1].contents).replace (reg, replacer)
txtFrame.textSelection[1].contents = str.slice (-1);
txtFrame.textSelection[0].contents = str.slice (0, -1);
} else {
txtFrame.textSelection[0].contents = (txtFrame.textSelection[0].contents).replace (reg, replacer);
}
result.lastIndex += replacer.length - result[0].length;
txtFrame.textRange.deSelect ();
}
}
Copy link to clipboard
Copied
I didn't tested your code yet.
But why you didn't play with my code snippet and use something like this:
// regex_changeContentsOfWordOrString_RemainFormatting.jsx
// regards pixxxel schubser
var s = /arguments/gi;
var replacer = "other string", result;
var atf = activeDocument.textFrames[0];
while (result = s.exec(atf.contents)) {
try {
aCon = atf.characters[result.index];
aCon.length = result[0].length;
aCon.contents = replacer;
} catch (e) {};
}
Try it and have fun
Copy link to clipboard
Copied
Copy link to clipboard
Copied
Hi o-marat​,
Hmmh? I wondering? Why?
This is not necessary.
Because of:
aCon.contents === [TextRange] === "arguments"
This part of my code:aCon.contents = replacer;
aCon.contents = replacer;
means:
[TextRange] === "arguments" will be overwritten with
replacer === "other string"
The same base exists for you:
aCon.contents === [TextRange] === "arguments"
This part of your code:aCon.contents = aCon.contents.replace(s, replacer);
aCon.contents = aCon.contents.replace(s, replacer);
means:
[TextRange] === "arguments" will be overwritten with
[TextRange] === "arguments" .replace((what) === s === /arguments/gi; , (with) === replacer === "other string")
Do you see, what I mean?
You have a String with contents "arguments".
In this String "arguments" you looking for the String "arguments" with the Regex /arguments/gi; and replace the finding with the Replacer "other string".
Very confusing!
Copy link to clipboard
Copied
But how to use the "capturing groups" in this case?
If the regex is /lacto (acid)/gi and replacer is 'sulphuric $1'?
Copy link to clipboard
Copied
You are the only one who know what you really want.
But you give informations rarely and only in little parts, step by step.
1) At first you want to replace one character.
2) You marked as correct the answer in which was replaced the whole word.
3) And now you ask for "capturing groups".
These are absolutly different requirements. But you can do all with the "way to go" I gave you before.
If your knowledge base in grep/regex is good enough (and if I understand you right), you only have to change the regex.
If not – do the same, change only the regex (or better: the lines #3 and #4 in the code in my answer #10) like this:
var s = /lacto(?= acid)/gi;
var replacer = "sulphuric", result;
Have fun
Copy link to clipboard
Copied
Hey pixxxel: so apparently you don't have to select the text at all? Your length of textRange which can be set, sets the length - I see that now!
Well, to be honest I started using the selections to replace characters because I did not know you could do this. However, an added benefit to my experimentation with that was the technique I learned where character styles can be used to mimic Indesign's data-merge field functionality by separating a text range from the content for the purposes of dynamic data population.
Copy link to clipboard
Copied
Hi, pixxxel schubser. I'm sorry for the confusion. I wrote the question clearly, but attached very simple illustration. (and, quite possibly, I made some mistakes when translating into English).
Of course, I want to use all the power of regular expressions! not just search/replace of one character
I think your post #10 this is the best correct answer at this point in time, and post#14 excellent complements it.
Copy link to clipboard
Copied
You're welcome.
Don't worry – my english isn't the best too (my german is much more better)
.
But yes, I also thinking, the code in my post #10 is a good choice.
And yes, Silly-V​ is a very good coder, but I have a small advantage with regex/grep.
And like he said: "… so apparently you don't have to select the text at all? Your length of textRange which can be set, sets the length - I see that now! …"
is a good and quick way. And it's also faster as to loop through all the characters and so on …
One note: Please read my post #12 again. This post shows how a few lines work and perhaps it can help you better to understand a little better.
Regards
pixxxel schubser
Copy link to clipboard
Copied
Don't worry, in another 5 years I shall advance my regex to new heights
Copy link to clipboard
Copied
Silly-V, thank you very much for your help! You really helped me to deal with how to work with textSelection.
Copy link to clipboard
Copied
Hmm, my code will screw up definitely, if the replacer will contain, or the resulting replacement will contain a new replacer match.
Oh, gasp - it will actually ​indefinitely screw up because it can create an infinite loop, and keep growing the text until a crash happens!
Just kidding! I'm silly - the code won't screw up indefinitely, but it can have possibility of replacing a wrong item if there is a replacee inside the replacer and if the new replaced text can overlap the starting original index of the next match (??)
Copy link to clipboard
Copied
During using of the script I found two interesting points.
var s = /^/gmi,
replacer = " — ",
atf = selection[0],
result;
while (result = s.exec (atf.contents)) {
try {
// when the pattern is /$/gmi or /^/gmi the loop becomes infinite
if (result[0].length == 0) {
s.lastIndex += replacer.length + 1; // enforce set the value of the RegExp.lastIndex property
}
aCon = atf.characters[result.index];
aCon.length = result[0].length;
aCon.contents = replacer;
} catch (e) {
}
}
I would welcome any additions and explanations
Copy link to clipboard
Copied
"^" and "$" gives you the positions or insertion points (without any contents). Not easy to handles with exec (and not good to use) in illustrator.
How about (e.g. for the beginning of a line) :
var replacer = "- ", result;
var atf = activeDocument.textFrames[0];
atf.characters[0].contents = replacer + atf.characters[0].contents;
var s = /\r(?=.)/gi;
replacer = "\r" + replacer;
while (result = s.exec(atf.contents)) {
try {
aCon = atf.characters[result.index];
aCon.length = result[0].length;
aCon.contents = replacer;
} catch (e) {};
}
Copy link to clipboard
Copied
pixxxel schubser​, once again, thank you very much!
Offtop: what are your favorite resources for in-depth study of regular expressions (on-line and books)?
Copy link to clipboard
Copied
You will laugh:
InDesign Grep and a lot of time (using since ID CS3)
This is the nearest for Illustrator. But at the end InDesign Grep is much more powerful. (E.g.: In Adobe ExtendScript U cannot use an lookbehind (?<=) or (?<!) or every letter [\l\u] )
Her is a good (german) reference with useful wildcards (written by Gerald Singelmann