• Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
    Dedicated community for Japanese speakers
  • 한국 커뮤니티
    Dedicated community for Korean speakers
Exit
1

RegExp search and replace, keep original text formatting

Engaged ,
Aug 06, 2016 Aug 06, 2016

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);

find_and_replace_regexp.jpg

Thanks!

TOPICS
Scripting

Views

5.7K

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines

correct answers 1 Correct answer

Community Expert , Aug 11, 2016 Aug 11, 2016

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

Votes

Translate

Translate
Adobe
Valorous Hero ,
Aug 06, 2016 Aug 06, 2016

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();

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Engaged ,
Aug 06, 2016 Aug 06, 2016

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

  • Select char n
    • If (char n == n + 1) add the char n + 1 to the selected area
    • else use the search/replace to the selection
      • Remove selection
  • Start the cycle again from the current position

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.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Engaged ,
Aug 08, 2016 Aug 08, 2016

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;
    }
  }
}

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Valorous Hero ,
Aug 09, 2016 Aug 09, 2016

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:
2016-08-09 08_56_04-Untitled-6_ @ 100% (CMYK_Preview).png
After:
2016-08-09 08_56_25-Untitled-6_ @ 100% (CMYK_Preview).png

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Engaged ,
Aug 09, 2016 Aug 09, 2016

Copy link to clipboard

Copied

From the search area excluded zones with different formatting. Of course it needs correcting.

The algorithm is like this:

  1. Find match.
  2. Take the formatting of the first character of the match.
  3. Make replacement.
  4. Format (to previously saved formatting).
  5. And so on.

Search in MSWord or InDesign behaves the nearly same.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Valorous Hero ,
Aug 09, 2016 Aug 09, 2016

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.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Valorous Hero ,
Aug 09, 2016 Aug 09, 2016

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.select(true);

      }

    }

    s.textRange.textSelection[0].contents = newWord;

    s.textRange.deSelect();

  };

};

test();
2016-08-09 17_22_33-Untitled-1_ @ 172.74% (CMYK_Preview).png

2016-08-09 17_23_12-Untitled-1_ @ 172.74% (CMYK_Preview).png

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 09, 2016 Aug 09, 2016

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Engaged ,
Aug 11, 2016 Aug 11, 2016

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 ();

  }

}

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 11, 2016 Aug 11, 2016

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Engaged ,
Aug 12, 2016 Aug 12, 2016

Copy link to clipboard

Copied

pixxxel schubser, it's great!

Just one thing:

aCon.contents = aCon.contents.replace(s, replacer);

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 12, 2016 Aug 12, 2016

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!

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Engaged ,
Aug 12, 2016 Aug 12, 2016

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'?

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 12, 2016 Aug 12, 2016

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Valorous Hero ,
Aug 12, 2016 Aug 12, 2016

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.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Engaged ,
Aug 12, 2016 Aug 12, 2016

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.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 12, 2016 Aug 12, 2016

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Valorous Hero ,
Aug 13, 2016 Aug 13, 2016

Copy link to clipboard

Copied

Don't worry, in another 5 years I shall advance my regex to new heights

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Engaged ,
Aug 13, 2016 Aug 13, 2016

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.

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Valorous Hero ,
Aug 12, 2016 Aug 12, 2016

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 (??)

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Engaged ,
Aug 21, 2016 Aug 21, 2016

Copy link to clipboard

Copied

During using of the script I found two interesting points.

  1. Target: insert something at the beginning or end of the paragraph. A regular expression can be /^/gmi or /$/gmi. The loop becomes infinite in the first match.
    Fixed using setting the value of the RegExp.lastIntex property (refer to code below).
  2. Can not find a match with the latest sign of the end of the text (in Illustrator it appears as #). Finds a match with all the marks the end of lines, except the last. I am using the pattern /$/gmi. Although in ExtendScript or Chrome it works.

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

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 21, 2016 Aug 21, 2016

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) {};

    }

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Engaged ,
Aug 21, 2016 Aug 21, 2016

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)?

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Aug 21, 2016 Aug 21, 2016

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

http://indesign-faq.de/files/2009/10/grep-intro.pdf

Votes

Translate

Translate

Report

Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines