Skip to main content
mikejlkemp
Known Participant
May 17, 2022
Answered

Find and Replace function for Bridge

  • May 17, 2022
  • 3 replies
  • 7993 views

Hi there. I used to have this incredible Find and Replace function for Bridge. It meant I could strip out a given part of a caption in the description field and replace it. I used it to strip out credit information. It used to look like this. I recently had to wipe and reinstall my Mac and I have lost it. Does anyone know where I can get this and how to load it back into Bridge? Thank you!

This topic has been closed for replies.
Correct answer gregreser
Looking at Paul Riggott's original Find Replace In Description script, I noticed that it runs this command when it loads: app.document.chooseMenuItem("PurgeCacheForSelected");
 
I'm guessing that there was a problem with metadata edits not always displaying and that purging the cache solved that. Ineed, I experienced the same blank Description as @mikejlkemp after running Find Replace In Description. I then manually purged the cache and the edited Description displayed.
 
Riggott's script used a simple XMP write method:  md.description =  . It might be that this does not work well in Bridge 2022. I changed the script to use the xmp.serialize method and that seems to work better.
 

 

#target bridge
if( BridgeTalk.appName == "bridge" ) { 
ReplaceDescription = new MenuElement("command", "Find and Replace Description/Caption", "at the end of tools");
}
 // Load the XMP Script library
if( !xmpLib){
	if( Folder.fs == "Windows" ){
		var pathToLib = Folder.startup.fsName + "/AdobeXMPScript.dll";
	}
	else{
		var pathToLib = Folder.startup.fsName + "/AdobeXMPScript.framework";
	}
	var libfile = new File( pathToLib );
	var xmpLib = new ExternalObject("lib:" + pathToLib );
}
ReplaceDescription.onSelect = function () { 
var win = new Window( 'dialog', 'Find & Replace' ); 
//g = win.graphics;
//var myBrush = g.newBrush(g.BrushType.SOLID_COLOR, [0.99, 0.99, 0.99, 1]);
//g.backgroundColor = myBrush;
win.orientation='column';
win.p1= win.add("panel", undefined, undefined, {borderStyle:"black"}); 
win.p1.preferredSize=[380,100];
win.g1 = win.p1.add('group');
win.g1.orientation = "row";
win.title = win.g1.add('statictext',undefined,'Description/Caption Editor');
win.title.alignment="fill";
var g = win.title.graphics;
g.font = ScriptUI.newFont("Georgia","BOLDITALIC",22);
win.p6= win.p1.add("panel", undefined, undefined, {borderStyle:"black"}); //Replace
win.p6.preferredSize=[380,100];
win.g600 =win.p6.add('group');
win.g600.orientation = "row";
win.g600.alignment='fill';
win.g600.st1 = win.g600.add('statictext',undefined,'Replace');
win.g600.st1.preferredSize=[75,20];
win.g600.et1 = win.g600.add('edittext');
win.g600.et1.preferredSize=[200,20];
win.g610 =win.p6.add('group');
win.g610.orientation = "row";
win.g610.alignment='fill';
win.g610.st1 = win.g610.add('statictext',undefined,'With');
win.g610.st1.helpTip="Leave this field blank if you want to remove the characters";
win.g610.st1.preferredSize=[75,20];
win.g610.et1 = win.g610.add('edittext');
win.g610.et1.preferredSize=[200,20];
win.g620 =win.p6.add('group');
win.g620.orientation = "row";
win.g620.alignment='fill';
win.g620.cb1 = win.g620.add('checkbox',undefined,'Global');
win.g620.cb1.helpTip="Replace all occurrences of";
win.g620.cb2 = win.g620.add('checkbox',undefined,'Case Insensitive');
win.g620.cb2.value=true;
win.g620.cb3 = win.g620.add('checkbox',undefined,'Remove any ()[]');
win.g620.cb3.value=false;
win.g1000 =win.p1.add('group');
win.g1000.orientation = "row";
win.g1000.alignment='center';
win.g1000.bu1 = win.g1000.add('button',undefined,'Process');
win.g1000.bu1.preferredSize=[170,30];
win.g1000.bu2 = win.g1000.add('button',undefined,'Cancel');
win.g1000.bu2.preferredSize=[170,30];
win.g1000.bu1.onClick=function(){
if(win.g600.et1.text == ''){
    alert("No replace value has been entered!");
    return;
    }
 win.close(0);
var sels = app.document.selections;
for(var a in sels){
var thumb = sels[a];
// Get the metadata object 
md = thumb.synchronousMetadata;
    // Get the XMP packet as a string and create the XMPMeta object
    var xmp = new XMPMeta(md.serialize());

if(win.g620.cb1.value && !win.g620.cb2.value) var patt = new RegExp (win.g600.et1.text.toString(),"g");
if(!win.g620.cb1.value && win.g620.cb2.value) var patt = new RegExp (win.g600.et1.text.toString(),"i");
if(win.g620.cb1.value && win.g620.cb2.value) var patt = new RegExp (win.g600.et1.text.toString(),"gi");
if(!win.g620.cb1.value && !win.g620.cb2.value) var patt = new RegExp (win.g600.et1.text.toString());

        // Read existing dc:description property
        var Caption = xmp.getArrayItem(XMPConst.NS_DC,"description", 1);
        // Change existing dc:description property based on user input
        var result=patt.test(Caption.toString());
        if(result == true){
            var newCaption = Caption.toString().replace(patt,win.g610.et1.text.toString());
            if(win.g620.cb3.value)  newCaption = newCaption.replace(/["'\(\)]/g, "");
            }
		// Delete the dc:description property
		xmp.deleteProperty(XMPConst.NS_DC, "description");	
		// Populate the new dc:description property and set lang to x-default
		xmp.appendArrayItem(XMPConst.NS_DC, "description", newCaption, 0, XMPConst.ALIAS_TO_ALT_TEXT);
		xmp.setQualifier(XMPConst.NS_DC, "description[1]", "http://www.w3.org/XML/1998/namespace", "lang", "x-default");
        
/*
    // Paul Riggott's original write method
md.namespace =  "http://purl.org/dc/elements/1.1/";
var Caption = md.description ? md.description[0] : "";
if(Caption == "") continue;
var result=patt.test(Caption.toString());
if(result == true){
    var newCaption = Caption.replace(patt,win.g610.et1.text.toString());
    if(win.g620.cb3.value)  newCaption = newCaption.replace(/["'\(\)]/g, "");
    md.description='';
    md.description = newCaption;
        }
 */
 	// Write the packet back to the selected file
	var updatedPacket = xmp.serialize(XMPConst.SERIALIZE_OMIT_PACKET_WRAPPER | XMPConst.SERIALIZE_USE_COMPACT_FORMAT);
	thumb.metadata = new Metadata(updatedPacket);
    }
}
win.show();
//app.document.chooseMenuItem("PurgeCacheForSelected");
};

 

 

3 replies

PECourtejoie
Community Expert
Community Expert
October 6, 2022

I wanted to thank you all scripters for the great work you do for the community!

Stephen Marsh
Community Expert
Community Expert
May 21, 2022

OK, my MacBook is back! @mikejlkemp it looks like there may be a solution offered by @gregreser ? Have you tested it yet? If it works, please mark his reply as the correct answer. If you need further assistance, please let the forum know.

mikejlkemp
Known Participant
May 21, 2022

Hi Stephen, good news about your Macbook! Afraid the solution isn't quite there, although great of @gregreser to help. This is what it is doing:

 

When you use the script on one caption, the caption disappears completely, but then if you navigate away to another program and go back, the caption comes back, minus the part chosen to remove.

 

If you then do more than one picture, again the captions completely disappear (which is a bit worrying at the time) but then if you navigate away as previously, the caption is still gone. But if you quite bridge, and restart, the captions come back, minus their removed parts.

 

So, it's a bit clunky and doesn't quite instill much confidence. So need somehow to get this working without having to quit and restart Bridge. 

 

Cheers,

 

Mike

Inspiring
November 19, 2024

@Shlomit Heymann my new add-find-replace script is ready for testing. It is working well for me, but plase consider it still in development and only work with test copy files. The script is called "Add-Replace Metadata" and will be in the Tools menu.

 

Click the "?" buttom for instructions. I hope they are clear, let me know.

 

I added a feature to add the file name. If you enter [FILENAME] in the Add text box, the actual file name will be added. You have to add any punctuation and space you want before or after the filename. In your case, you would enter ", [FILENAME]" (quotes aren't necessary).

The script window will stay open after it runs and you can do additional changes.

 

I have to say that adding and replacing metadata via a script makes me nervous because things could go wrong and there is no undo option. You might consider exporting all of your metadata to a text file as a backup. I can help with that, if you are interested.

 

#target bridge

/*
Terms and Conditions of Use:
MIT License

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/

//Create a menu item within Adobe Bridge
if (BridgeTalk.appName == 'bridge') {
    var addReplaceLbl = "Add-Replace Metadata";
    var addReplaceVers = "2023-04-26";
    addReplaceMenuItem = MenuElement.create("command", addReplaceLbl, "at the end of tools");
}
 
// when the menu item is clicked, run the script
addReplaceMenuItem.onSelect = function () {
    addReplace();
    }

function addReplace(){
    // Load the XMP Script library
    if ( ExternalObject.AdobeXMPScript == undefined ) {
        ExternalObject.AdobeXMPScript = new ExternalObject( "lib:AdobeXMPScript" );
        }
    // Register XMP namespace
    if (NS_IPTC_EXT == undefined){
        var NS_IPTC_EXT = "http://iptc.org/std/Iptc4xmpExt/2008-02-29/"; 
        XMPMeta.registerNamespace (NS_IPTC_EXT, "Iptc4xmpExt");
        }
        
    // Main window
    // =======
    var addReplace = new Window("palette"); 
        addReplace.text = addReplaceLbl+" ("+addReplaceVers+")"; 
        addReplace.orientation = "column"; 
        addReplace.alignChildren = ["center","top"]; 
        addReplace.spacing = 10; 
        addReplace.margins = 16; 

    var fieldGrp = addReplace.add("group", undefined); 
        fieldGrp.orientation = "row"; 
        fieldGrp.alignChildren = ["left","center"]; 
        fieldGrp.spacing = 10; 
        fieldGrp.margins = 0;

    //var fieldLabel = fieldGrp.add("statictext", undefined, "Edit"); 

    var fieldsArr = [
    {label:'Title',xmpType:'langAlt',namespace:XMPConst.NS_DC,prefix:'dc',path:'title'},      
    {label:'Creator',xmpType:'seq',namespace:XMPConst.NS_DC,prefix:'dc',path:'creator'}, 
    {label:'Headline',xmpType:'text',namespace:XMPConst.NS_PHOTOSHOP,prefix:'photoshop',path:'Headline'},
    {label:'Description',xmpType:'langAlt',namespace:XMPConst.NS_DC,prefix:'dc',path:'description'},
    {label:'Keywords',xmpType:'bag',namespace:XMPConst.NS_DC,prefix:'dc',path:'subject'},
    {label:'Copyright Notice',xmpType:'langAlt',namespace:XMPConst.NS_DC,prefix:'dc',path:'rights'},
    {label:'Rights Usage Terms',xmpType:'langAlt',namespace:XMPConst.NS_XMP_RIGHTS,prefix:'xmpRights',path:'UsageTerms'}
    ];
    // {label:'Person Shown',xmpType:'bag',namespace:NS_IPTC_EXT,prefix:'Iptc4xmpExt',path:'PersonInImage'}
        
    // default property values (first item in fieldsArr). these will change if the dropdown selection is changed by the user
    var selIndex = 0;
    var selLabel = "Title";
    var selXmpType = "langAlt";
    var selNamespace = XMPConst.NS_DC;
    var selPrefix = "dc";
    var selPath = "title";

    var fieldDd = fieldGrp.add("dropdownlist", undefined, undefined);
        fieldDd.minimumSize = [200,25];
        fieldDd.maximumSize = [200,25];
    for(var L1 = 0; L1 < fieldsArr.length; L1++){
        fieldDd.add('item', fieldsArr[L1].label)
        }    
    fieldDd.selection = 0;

    var group1 = addReplace.add("group", undefined); 
        group1.orientation = "row"; 
        group1.alignChildren = ["left","center"]; 
        group1.spacing = 10; 
        group1.margins = 0; 

    var radiobutton1 = group1.add("radiobutton", undefined, undefined); 
        radiobutton1.text = "Add"; 
        radiobutton1.value = true; 

    var radiobutton2 = group1.add("radiobutton", undefined, undefined); 
        radiobutton2.text = "Replace"; 
    
    info1Btn = group1.add('button', undefined, "?");
        info1Btn.minimumSize = [32,25];
        info1Btn.maximumSize = [32,25];
        info1Btn.helpTip = "Instructions";
        info1Btn.onClick = showinfo1Win;

    var panel1 = addReplace.add("panel", undefined, undefined); 
        panel1.orientation = "column"; 
        panel1.alignChildren = ["center","top"]; 
        panel1.spacing = 2; 
        panel1.margins = 10; 

    var group2 = panel1.add("group", undefined); 
        group2.orientation = "row"; 
        group2.alignChildren = ["left","center"]; 
        group2.spacing = 10; 
        group2.margins = 0; 

    var statictext2 = group2.add("statictext", undefined, undefined); 
        statictext2.text = "Add"; 
        statictext2.minimumSize = [60,20];
        statictext2.maximumSize = [60,20];
        statictext2.justify = "right"; 
     
    // var addPositionGrp
    var addPositionGrp = group2.add("group", undefined);
        addPositionGrp.minimumSize = [120,22];
        addPositionGrp.maximumSize = [120,22];
    var addPositionDd = addPositionGrp.add("dropdownlist", undefined, undefined, {items: [" to begining"," to end"]});
        addPositionDd.minimumSize = [100,22];
        addPositionDd.maximumSize = [100,22];
        addPositionDd.selection = 0;

    var edittext1 = group2.add("edittext"); 
        edittext1.minimumSize = [250,22];
        edittext1.maximumSize = [250,22];
        
    var infoTextTxt = 
    'Add\n'+
    '   - Select "to beginning" or "to end"\n'+
    '   - Enter text in the Add box\n'+
    '   Title, Headline, Description:\n'+
    '      - Enter exact text to be added, including spaces and punctuation\n'+
    '      - Enter [FILENAME] to add the file name (without file extension)\n'+
    '   Creator, Keywords:\n'+
    '      - Commas are treated as text\n'+
    '      - Separate multiple values with semicolons (;)\n\n'+
    'Replace\n'+
    '   - Replace box: Enter text, including spaces and punctuation, to be replaced\n'+
    '   - With box: Enter replacement text. Leave blank to delete the Replace text\n\n'+
    'Note:\n'+
    'Creator and Keyword entries are stored in XMP metadata as lists of individual items\n'+
    '      Sue\n'+
    '      Mark Johnson\n'+
    '      Cutler, Michelle\n'+
    'Bridge displays multiple entries separated by a semicolon and space (; )\n'+
    'Bridge displays quotes around entries containing a comma (,)\n'+
    '      Sue; Mark Johnson; "Cutler, Michelle"'
    
    var info1WinLabel = "About adding/replacing";

    function showinfo1Win(){
       var info1Win = new Window('palette', info1WinLabel);  
       info1WinText = info1Win.add('statictext', undefined, infoTextTxt, {multiline:true});
       if (Folder.fs == "Windows"){
        info1WinText.minimumSize = [440,360];
        info1WinText.maximumSize = [440,360];
        }
    else{
        info1WinText.minimumSize = [520,460];
        info1WinText.maximumSize = [520,460];
        }               
            info1Win.cancelBtn = info1Win.add('button', undefined, 'Close');
            info1Win.cancelBtn.onClick =  function(){ 
            info1Win.hide();
            info1Win.close();
            addReplace.active = true;
        }
       info1Win.show();
       info1Win.active = true;
    }
     
    var replaceOpts = panel1.add("group", undefined);
        replaceOpts.orientation = "row"; 
        replaceOpts.alignChildren = ["left","center"]; 
        replaceOpts.spacing = 10; 
        replaceOpts.margins = 0; 
        replaceOpts.indent = 70;
        replaceOpts.minimumSize = [0,0];
        replaceOpts.maximumSize = [0,0];
        exactCB = replaceOpts.add("checkbox", undefined, "Match Case"); 
        
    var statictext3 = panel1.add("statictext", undefined, "commas are treated as text, not separators"); 
        statictext3.minimumSize = [400,25];
        statictext3.maximumSize = [400,25];
        statictext3.justify = 'center';
        
    var group3 = panel1.add("group", undefined); 
        group3.orientation = "row"; 
        group3.alignChildren = ["left","center"]; 
        group3.spacing = 10; 
        group3.margins = 0; 

    var statictext4 = group3.add("statictext", undefined, undefined); 
        statictext4.text = "With"; 
        statictext4.minimumSize = [60,20];
        statictext4.maximumSize = [60,20]; 
        statictext4.justify = "right"; 
    var group3spc = group3.add("statictext", undefined); 
        group3spc.minimumSize = [0,0];
        group3spc.maximumSize = [0,0];
    var edittext2 = group3.add("edittext", undefined); 
        edittext2.minimumSize = [250,22];
        edittext2.maximumSize = [250,22];
        
    var statictext5 = panel1.add("statictext", undefined, undefined); 
        statictext5.text = "leave blank to delete"; 
        statictext5.indent = 70;

    var statictext6 = addReplace.add("statictext", undefined, undefined, {multiline: true}); 
        statictext6.minimumSize = [300,30]; 
        statictext6.maximumSize = [300,30]; 
        statictext6.justify = "center"; 
    
    var group4 = addReplace.add("group", undefined); 
        group4.orientation = "row"; 
        group4.alignChildren = ["left","top"]; 
        group4.spacing = 40; 
        group4.margins = 0; 

    var ok = group4.add("button", undefined, undefined); 
        ok.text = "Add"; 
        ok.minimumSize = [150,30];
        ok.maximumSize = [150,30];

    var cancel = group4.add("button", undefined, undefined); 
        cancel.text = "Close"; 

    // set  default view to Add name
    statictext2.text = "Add"; 
    group3.visible = false;
    statictext5.visible=false;
    exactCB.value = true;
    // apply saved preferences
    try{
        if(app.preferences.mDeluxe_addReplace_fieldDd){ // edit which field
            fieldDd.selection = app.preferences.mDeluxe_addReplace_fieldDd;
            selIndex = app.preferences.mDeluxe_addReplace_fieldDd;
            selXmpType = fieldsArr[selIndex].xmpType;
            selLabel = fieldsArr[selIndex].label;
            selNamespace = fieldsArr[selIndex].namespace;
            selPath = fieldsArr[selIndex].path;
            }
        
        if(app.preferences.mDeluxe_addReplace_radiobutton1 == true){ // Add
            radiobutton1.value = true;
            group2.visible = true;
            statictext2.text = "Add"; 
            group3.visible = false;
            if(selXmpType == "bag"|| selXmpType == "seq"){
                statictext3.visible = true;
                statictext3.text = 'commas are treated as text, not separators';
                }
            if(selXmpType == "text" || selXmpType == "langAlt"){
                statictext3.visible = true;
                statictext3.text = 'enter exact text to be added, including spaces and punctuation';
                }
        replaceOpts.minimumSize = [0,0];
        replaceOpts.maximumSize = [0,0];
        addPositionGrp.minimumSize = [100,22];
        addPositionGrp.maximumSize = [100,22];
        statictext5.visible=false;
        ok.text = "Add"
            };
        if(app.preferences.mDeluxe_addReplace_radiobutton2 == true){ // Replace
            radiobutton2.value = true;
            group2.visible = true;
            statictext2.text = "Replace";
            group3.visible = true;
            statictext3.visible = false;
            replaceOpts.minimumSize = [400,25];
            replaceOpts.maximumSize = [400,25];
            addPositionGrp.minimumSize = [0,0];
            addPositionGrp.maximumSize = [0,0];
            statictext5.visible=true;
            ok.text = "Replace"
            };
        if(app.preferences.mDeluxe_addReplace_addPositionDd){ // add to beginning or end
            addPositionDd.selection = app.preferences.mDeluxe_addReplace_addPositionDd;
            }

        if(app.preferences.mDeluxe_addReplace_exactCB == true){ // match case
            exactCB.value = true; //app.preferences.mDeluxe_addReplace_exactCB;
            }
        else{
            exactCB.value = false;
            }
        }
        catch(e){}
    
    addReplace.show();

    // Dialog functions 
    if(app.document.selections.length == 0){
        ok.enabled = false;
        statictext6.text = "No file(s) selected. 'Close' then try again"
        }; 
    
    fieldDd.onChange=function(){ // when a field is selected, load XMP variables for read/write
        selIndex = fieldDd.selection.index;
        selLabel = fieldsArr[selIndex].label;
        selXmpType = fieldsArr[selIndex].xmpType;
        selNamespace = fieldsArr[selIndex].namespace;
        selPath = fieldsArr[selIndex].path;
        if(selXmpType == "bag" && radiobutton1.value == true || selXmpType == "seq" && radiobutton1.value == true){
            statictext3.visible = true;
            statictext3.text = 'commas are treated as text, not separators';
            }
        if(selXmpType == "text" && radiobutton1.value == true || selXmpType == "langAlt" && radiobutton1.value == true){
            statictext3.visible = true;
            statictext3.text = 'enter exact text to be added, including spaces and punctuation';
            }
        }
    
    radiobutton1.onClick = function(){
        group2.visible = true;
        statictext2.text = "Add"; 
        edittext1.text = "";
        edittext2.text = "";
        group3.visible = false;
        if(selXmpType == "bag" || selXmpType == "seq"){
            statictext3.visible = true;
            statictext3.text = 'commas are treated as text, not separators';
            }
        else{
            statictext3.visible = true;
            statictext3.text = 'enter exact text to be added, including spaces and punctuation';
            }
        replaceOpts.minimumSize = [0,0];
        replaceOpts.maximumSize = [0,0];
        addPositionGrp.minimumSize = [100,22];
        addPositionGrp.maximumSize = [100,22];
        statictext5.visible=false;
        ok.text = "Add"
        addReplace.layout.layout (true);
        }

    radiobutton2.onClick = function(){
        group2.visible = true;
        statictext2.text = "Replace";
        edittext1.text = "";
        edittext2.text = "";
        group3.visible = true;
        statictext3.visible = false;
        replaceOpts.minimumSize = [400,25];
        replaceOpts.maximumSize = [400,25];
        addPositionGrp.minimumSize = [0,0];
        addPositionGrp.maximumSize = [0,0];
        statictext5.visible=true;
        ok.text = "Replace"
        addReplace.layout.layout (true);
        }

    edittext1.addEventListener ("focus", function (e){statictext6.text = ""}); // clear result text when edittext1 is clicked
     
     var okToRun = true; // used for testForSeparator() - only used for text and langAlt functions
    
    function testForSeparator(){ // test for the presence of a separator before or after Add text input 
        if(addPositionDd.selection.index == 0){ // add to beginning
            var regExSeparators = new RegExp('[!@#\\$%\\^\\&*\\)\\(\/ +=.,:;_-]$'); // The line ends with a space or special character
            }
        if(addPositionDd.selection.index == 1){ // add to end
            var regExSeparators = new RegExp('^[!@#\\$%\\^\\&*\\)\\(\/ +=.,:;_-]'); // The line begins  with a space or special character
        }
        if(regExSeparators.test(edittext1.text.toString()) == false){          
            var showConfirm = Window.confirm("There is no space or punctuation to separate added text from existing text.\n\nContinue?", true, "No Separator")
            if( showConfirm == false){
                okToRun = false;
                }
            else{ 
                okToRun = true;
                }
            }
        else{
            okToRun = true;
            }
        } // CLOSE function testForSeparator()

    ok.onClick = function processChange(){
        var addText = edittext1.text.toString().split('\"').join(""); // remove double quotes
        if(selXmpType == 'bag' || selXmpType == 'seq'){ 
           var Replace = edittext1.text.toString().replace(/; |;|"/g, ""); // remove double quotes and semi-colons
            }
        else{
            var Replace = edittext1.text.toString();// don't remove quotes for text or lang alt  .replace(/"/g); // remove double quotes
            }
        var ReplaceEscaped = Replace.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); // escape special characters so they are treated as strings in regex
        var With = edittext2.text.toString().split('\"').join(""); // remove double quotes
        var addKey = radiobutton1.value;
        var items = app.document.selections;
        var errorLog = [];
        if(items.length > 1){
            var fileTxt = "files";
            }
        else{
            var fileTxt = "file";
            }
        
        if(Replace.length == 0){
            Window.alert("No value entered");
            }
        if(Replace.length > 0 && items.length > 0){
            var addedTo = 0;
            var matchFound = 0;
            /// write changes to XMP
            for (var i = 0; i < items.length; i++){
                if(!items[i].container && items[i].hasMetadata){ //exclude folders and only proccess files with metadata  
                    var file=new Thumbnail(items[i]);
                    try{
                        /* old method
                        var xmpFile = new XMPFile(file.path, XMPConst.UNKNOWN,XMPConst.OPEN_FOR_UPDATE);  // TODO handle RAW files?
                        var xmp = xmpFile.getXMP();
                        */
                        var ftMeta = items[i].synchronousMetadata; // Get the metadata object - wait for  valid values
                        var xmp = new XMPMeta(ftMeta.serialize()); // Get the XMP packet as a string and create the XMPMeta object
                        }
                    catch(e){
                    // Window.alert("Problem getting xmp data:-\r"  + e.message);
                      errorLog.push("Problem getting xmp data: " + e.message);
                      return;
                    }  
                if(selXmpType == 'text'){                      
                    var keyTxt = " "+selLabel; // used for completed message
                   
                    if(addKey){  // if Add is selected
                        testForSeparator();
                        if (okToRun == true){    
                            addedTo++;
                            var splitKeys = 1;
                            var New = "";
                            // not needed?    var  regex = new RegExp('FILENAME');
                            // not needed?   if(regex.test(edittext1.text.toString()) == true){ // if Add text conatins [FILENAME], add the filename wiithout the extension
                            var filenameNoExt = decodeURI(items[i].spec.name).replace(/\..+$/,""); // filename without extension - used for [FILENAME] feature
                            addText = edittext1.text.toString().replace("[FILENAME]", filenameNoExt); // replace [FILENAME] with real filename in Add text
                            // }
                            var prop = xmp.getProperty(selNamespace, selPath); // get current text value
                            if(prop){
                                if(addPositionDd.selection.index == 0){ // add to beginning
                                    New = addText+prop;
                                    }
                                else{ // add to end
                                    New = prop+addText;
                                    }
                                };
                            else{New = Replace};
                            xmp.deleteProperty(selNamespace, selPath);
                            xmp.setProperty(selNamespace, selPath, New);
                            statictext6.text = keyTxt+" text added to "+addedTo+" "+fileTxt; 
                            } // CLOSE if (okToRun)
                        } // CLOSE if(addKey)
                   
                    if(!addKey){ // if Replace is selected
                        var Lookup = xmp.getProperty(selNamespace, selPath); 
                        var result = "";
                        if(exactCB.value == false){var patt = new RegExp (ReplaceEscaped,"gi")}; // do not match case
                        if(exactCB.value == true){var patt = new RegExp (ReplaceEscaped,"g")}; // Match Case
                        if(Lookup){ // only run regex test if xmp property exists
                            result = patt.test(Lookup.toString());
                            }
                        if(result == true){
                        matchFound++;
                        var New = Lookup.toString().replace(patt,With);
                        xmp.deleteProperty(selNamespace, selPath);
                        xmp.setProperty(selNamespace, selPath, New);
                        } // CLOSE if(result == true)
                        if(matchFound > 1){var editedTxt = "files"};
                        else{var editedTxt = "file";}
                        if(matchFound == 0){
                            statictext6.text = "'"+Replace+ "' not found. No replacement made"; 
                            }
                        else{
                            statictext6.text = keyTxt+" text edited in "+matchFound+" "+editedTxt;
                            }
                        } // CLOSE  if(tmpCount >0 && !addKey)            
                    } // CLOSE if(selXmpType == 'text')
                                
                if(selXmpType == 'langAlt'){
                        var keyTxt = " "+selLabel; // used for completed message
                        
                        if(addKey){ // if Add is selected             
                            testForSeparator();
                            if (okToRun == true){ 
                                addedTo++;
                                var splitKeys = 1;
                                var New = "";
                                // not needed?    var  regex = new RegExp('FILENAME');
                                // not needed?   if(regex.test(edittext1.text.toString()) == true){ // if Add text conatins [FILENAME], add the filename wiithout the extension
                                var filenameNoExt = decodeURI(items[i].spec.name).replace(/\..+$/,""); // filename without extension - used for [FILENAME] feature
                                addText = edittext1.text.toString().replace("[FILENAME]", filenameNoExt); // replace [FILENAME] with real filename in Add text
                                // }              
                                var prop = xmp.getArrayItem(selNamespace, selPath, 1); // get current text value
                                if(prop){
                                    if(addPositionDd.selection.index == 0){ // add to beginning
                                        New = addText+prop;
                                        }
                                    else{ // add to end
                                        New = prop+addText;
                                        }
                                    };
                                else{New = Replace};
                                xmp.deleteProperty(selNamespace, selPath);
                                xmp.setLocalizedText(selNamespace, selPath, null, "x-default", New);
                                statictext6.text = keyTxt+" text added to "+addedTo+" "+fileTxt; 
                                } // CLOSE if (okToRun)
                            } // CLOSE if(addKey)
                        
                        if(!addKey){ // if Replace is selected
                            var Lookup = xmp.getArrayItem(selNamespace, selPath, 1); // get current text value 
                            var result = "";
                            if(exactCB.value == false){var patt = new RegExp (ReplaceEscaped,"gi")}; // do not match case
                            if(exactCB.value == true){var patt = new RegExp (ReplaceEscaped,"g")}; // Match Case
                            if(Lookup){ // only run regex test if xmp property exists
                                result = patt.test(Lookup.toString());
                                }
                            if(result == true){
                            matchFound++;
                            var New = Lookup.toString().replace(patt,With);
                            xmp.deleteProperty(selNamespace, selPath);
                            xmp.setLocalizedText(selNamespace, selPath, null, "x-default", New);
                            } // CLOSE if(result == true)
                            if(matchFound > 1){var editedTxt = "files"};
                            else{var editedTxt = "file";}
                            if(matchFound == 0){
                                statictext6.text = "'"+Replace+ "' not found. No replacement made"; 
                                }
                            else{
                                statictext6.text = keyTxt+" text edited in "+matchFound+" "+editedTxt;
                                }
                            } // CLOSE  if(tmpCount >0 && !addKey)
                    } // CLOSE if(selXmpType == 'langAlt')
                
                if(selXmpType == 'bag' || selXmpType == 'seq'){       
                    var  regex = new RegExp('FILENAME');
                    if(regex.test(edittext1.text.toString()) == true){
                        Window.alert("[FILENAME] add does not work for this field"); // alert if Add includes "[FILENAME]", do not process
                        return;
                        }
                
                    if(selXmpType == 'bag'){var xmpConstArray = XMPConst.ARRAY_IS_UNORDERED;}
                    if(selXmpType == 'seq'){var xmpConstArray = XMPConst.ARRAY_IS_ORDERED;}
                    try{
                        var tmpCount =  xmp.countArrayItems(selNamespace, selPath);
                        }
                    catch(e){
                        errorLog.push("Cannot get bag or seq array count \r" + e.message);
                        }
                     // if Add is selected
                    if(addKey){     
                        addedTo++;
                        var splitKeys = addText.split('; ').join(';').split (';'); // split text separated by semicolon into an array (handle either "; " or ";")
                        var keyTxt = " "+selLabel; // used for completed message
                        if(splitKeys.length > 1){keyTxt = " "+selLabel+"s"}; // used for completed message
                        var prop = []; //get existing array items
                        var newProp= []; // array for existing and added items
                        for (var L2 = 1; L2 <= (tmpCount); L2++){
                            prop.push(xmp.getArrayItem(selNamespace, selPath, L2));
                            }
                        if(addPositionDd.selection.index == 0){ // add to beginning
                                newProp = splitKeys.concat(prop);
                                }
                            else{ // add to end
                                newProp = prop.concat(splitKeys);
                                }
                        xmp.deleteProperty(selNamespace, selPath); // Delete the array if it exists                              
                        for (var L1 = 1; L1 < (newProp.length + 1); L1++){ // write each item in newProp array
                            xmp.appendArrayItem(selNamespace, selPath, newProp[L1 - 1], 0, xmpConstArray);
                            }       
                        statictext6.text = splitKeys.length+keyTxt+" added to "+addedTo+" "+fileTxt; 
                        } // CLOSE if(addKey)
                    // if Replace is selected
                    if(tmpCount >0 && !addKey){
                        var matchFound = 0;
                        for (var a =0;a<tmpCount;a++){
                        var Lookup = xmp.getArrayItem(selNamespace, selPath, a+1);
                        var result = "";
                        if(exactCB.value == false){var patt = new RegExp (ReplaceEscaped,"gi")}; // do not match case
                        if(exactCB.value == true){var patt = new RegExp (ReplaceEscaped,"g")}; // Match Case
                        if(Lookup){ // only run regex test if xmp property exists
                            result = patt.test(Lookup.toString());
                            }
                        if(result == true){
                            matchFound++;
                            var New = Lookup.toString().replace(patt,With);
                            if(!New){ // if the replace text matches all the text in the array item and with text  is empty, delete the array item
                                xmp.deleteArrayItem(selNamespace, selPath, a+1);
                                }
                            else{
                                xmp.setArrayItem(selNamespace, selPath, a+1,New); // if the Replace text matches the existing value, replace the current array item with the new text
                                }
                            } // CLOSE if(result == true)
                        }
                        if(matchFound == 0){
                            statictext6.text = "'"+Replace+ "' not found. No replacement made"; 
                            }
                        else{
                            var matchTxt = " replacement";
                            if(matchFound > 1){matchTxt = " replacements"};
                            statictext6.text = matchFound+matchTxt+" made to "+items.length+" "+fileTxt; 
                            }
                        } // CLOSE  if(tmpCount >0 && !addKey)          
                    } // CLOSE if(selXmpType == 'bag' || selXmpType == 'seq')
                var updatedPacket = xmp.serialize(XMPConst.SERIALIZE_OMIT_PACKET_WRAPPER | XMPConst.SERIALIZE_USE_COMPACT_FORMAT); // // Write the packet back to the selected file
                items[i].metadata = new Metadata(updatedPacket); // Write the packet back to the selected file
                }
            } // CLOSE if(item[i].hasMetadata)
        } // CLOSE for (var i = 0; i < items.length; i++)
    savePrefs();
    } // CLOSE if(Replace.length > 0 && items.length > 0)
    cancel.onClick = function(){
        savePrefs();
        addReplace.close();
        }

    function savePrefs(){ // save  settings to preferences
        app.preferences.mDeluxe_addReplace_radiobutton1 = radiobutton1.value; // Add
        app.preferences.mDeluxe_addReplace_radiobutton2 = radiobutton2.value; // Replace
        app.preferences.mDeluxe_addReplace_addPositionDd = addPositionDd.selection.index; // beginning or end
        app.preferences.mDeluxe_addReplace_fieldDd = fieldDd.selection.index; // which field
        app.preferences.mDeluxe_addReplace_exactCB = exactCB.value; // match case
        } 

    }// CLOSE function addReplace()

 


Thanks @gregreser !! This is an absolutely amazing super-script and works perfectly (Bridge 14.1., Win 11 Pro). Martin

gregreser
Legend
May 17, 2022

I think that script was created by Paul Riggott. You can find his scripts at https://github.com/Paul-Riggott/PS-Scripts 

The one you want is Find and Replace.jsx It looks like it has been updated from the version you have. it now allows you to edit metadata in many different fields (your version only changed Description/Caption).

mikejlkemp
Known Participant
May 17, 2022

Thank you very much for that Greg, that hopefully will do it! Do you know how I input that script into Bridge to make it work? (apologies, I am no expert.) Thanks again, Mike

gregreser
Legend
May 17, 2022

No problem. Here's how you install it:

 

On Github, click the green Code button, then click Download ZIP

 

Unzip this and look for the file Find and Replace.jsx

You will copy this file in the Bridge Startup Scripts folder. To find this folder:

In Bridge, go to Preferences > Startup Scripts > Reveal My Startup Scripts

 

Copy Find and Replace.jsx in this folder.

Restart Bridge and confirm you want to load the script.

It should now appear in the Tools menu.

 

Let me know how it goes.