find text element in MXD with wildcard?

1187
28
Jump to solution
11-13-2018 02:16 PM
JaredPilbeam2
MVP Regular Contributor

I have a working script that searches for a text element in an MXD and replaces it with new text. I'm using it to update the "print date: mo./day/year". But, this method is sort of limited in that it only finds that exact string. In my map document folder, the dates of these maps differ from each other. So, how can I make the script replace an open-ended string? 

I've tried:

oldText = "Print Date: "

and

oldText = "Print Date: *"

As the script is here, "oldText = Print Date: 9.6.2016" will be replaced with "newText = Print Date: 11/13/2018". But, dates on other maps could be anything. If I could replace the old with dynamic text date that would be even better.

arcpy.env.workspace = Workspace = r"path\to\TestFolder"
Output = r"path\to\TestFolder2"
oldText = "Print Date: 9.6.2016"
oldList = oldText.split(', ')
newText = "Print Date: 11/13/2018"
newList = newText.split(', ')

# list the mxds of the workspace folder
for mxdname in arcpy.ListFiles("*.mxd"):
    print "checking document: {}".format(mxdname)
# set the variable
    mxd = arcpy.mapping.MapDocument(Workspace + "\\" + mxdname)
# replace elements that occur in the map document
    for elm in arcpy.mapping.ListLayoutElements(mxd, "TEXT_ELEMENT"):
        counter = 0
        for text in oldList:
            if text in elm.text:
                elm.text = elm.text.replace(text, newList[counter])
                print '{} changed'.format(elm.text)
                counter = counter + 1
            else:
                counter = counter + 1

    for elm in arcpy.mapping.ListLayoutElements(mxd, ""):
        counter = 0
        for text in oldList:
            if text in mxd.title:
                mxd.title = elm.text.replace(text, newList[counter])
                counter = counter + 1
            else:
                counter = counter + 1
# move the mxd.saveACopy outside of the if loop so a copy is saved even it does not meet the condition of the if loops
    mxd.save() #(os.path.join(Output + "\\" + mxdname))
# do not include this delete statement inside the above loop or it will delete the mxd object inside the loop. Make sure to dedent.
del mxd‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
0 Kudos
1 Solution

Accepted Solutions
RandyBurton
MVP Regular Contributor

There are a couple of ways that you can go.  If all the date text contains the address info, you could replace the entire contents with the similar contents that include dynamic text for the date.

If the print date line is separated by new line codes \n, you can split your old text, replace the part with the print date and rejoin the text.  It appears that your text might follow this pattern.  If so, you can try something like this:

import arcpy, glob, os

path = r"C:\Directory\with\mxds"

os.chdir(path)
for file in glob.glob("*.mxd"):
    print "Processing: {}".format(file)

    mxd = arcpy.mapping.MapDocument(file)
    
    for elm in arcpy.mapping.ListLayoutElements(mxd,"TEXT_ELEMENT"):
        if ' DATE' in elm.text.upper():
            txt = elm.text.split('\n')
            for i, t in enumerate(txt):
                if ' DATE' in t.upper():
                    # txt = "Print date: <dyn type=\"date\" format=\"\"/>"
                    # format date with periods, no leading zero in month
                    txt[i] = "Print date: <dyn type=\"date\" format=\"M.dd.yyyy\"/>"
            elm.text = '\n'.join(txt)
            elm.name = 'DateBox'
        print elm.text, elm.name

    mxd.save()
    del mxd‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

If the date text that you want replaced is on the same line with other text ( such as:  Print Date: 11/28/2018  By: JP ) then you may need to consider an alternative like regular expressions if a pattern in the text cannot be found.   There is a discussion on stackoverflow ( Extracting date from a string in Python ) that would be of interest if you need another option.

View solution in original post

28 Replies
DanPatterson_Retired
MVP Esteemed Contributor

perhaps, it could be handled in a similar fashion

oldText = "Print Date: 9.6.2016"

newdate = "12.12.2018"

if "Print Date" in oldText:
    new_fixed = "{}: {}".format(oldText.split(":")[0], newdate)
    

new_fixed

'Print Date: 12.12.2018'
JaredPilbeam2
MVP Regular Contributor

Dan,

I like that approach. The thing is, "oldText" could be "Print Date:", "Plot Date:", "Plot date", etc... I wasn't the originator of the maps. I've only been updating them.

So, with that, could I create a list with all these possibilities? I'm basically wondering how to formulate that.

0 Kudos
RandyBurton
MVP Regular Contributor

Using Dan's example, try the comparison with just "DATE" and use .upper():

if "DATE" in oldText.upper():
    new_fixed = "{}: {}".format(oldText.split(":")[0], newdate)
0 Kudos
RandyBurton
MVP Regular Contributor

I place a "revision" at the bottom of my maps using dynamic text.  The format is easy to figure out:

Rev: <dyn type="date" format="yyMMdd"/><dyn type="time" format="HHmm"/>‍‍‍‍‍

Working with dynamic text

Another method I like to use is naming the  text boxes when you create them.  This helps in finding a specific one and makes it easy to replace text.  Set the 'Element Name' on 'Size and Position' tab in Desktop version.

for elm in arcpy.mapping.ListLayoutElements(mxd,"TEXT_ELEMENT"):
    if elm.name == "PrintDate": # text element named 'PrintDate'
        elm.text = "Print date: {}".format(someDate)
        # elm.text = "Print date: <dyn type=\"date\" format=\"\"/>"‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍
JaredPilbeam2
MVP Regular Contributor

Randy,

Are you aware of a way to name the text boxes with Arcpy? That's a good idea. Sad thing is I'd have to retroactively do that at this point.

0 Kudos
RandyBurton
MVP Regular Contributor

You could process all your MXDs in a directory using os.walk or similar.  If you only wanted to rename textboxes with the word "Date" in the text, you could use something like:

import arcpy, glob, os

path = r"C:\Directory\to\search"

os.chdir(path)
for file in glob.glob("*.mxd"):
    print "Processing: {}".format(file)

    mxd = arcpy.mapping.MapDocument(file)
    
    for elm in arcpy.mapping.ListLayoutElements(mxd,"TEXT_ELEMENT"):
        if "DATE" in elm.text.upper():
            elm.name = 'DateBox'
        print elm.text, elm.name

    mxd.save() # or mxd.saveACopy('newname')
    del mxd‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

This assumes that only one box will contain the word "Date". To take this further, you could check for other text and name the box accordingly (if a dictionary key is found in the text, set the name to the paired value, for example).

XanderBakker
Esri Esteemed Contributor

I always name the element for easy access using arcpy.mapping.ListLayoutElement...

JaredPilbeam2
MVP Regular Contributor

Randy,

Thanks for that. I tried your script verbatim (besides the path variable) and for some reason it won't save (nothing's open, tried saveACopy('test'), unindented, quit python, restarted, etc...). But, as far as the printout goes, it doesn't look like it changed the date because it's showing the same old date.

Using yours and Dan's help from before, I put together this script:

import arcpy, os
from arcpy import env

arcpy.env.workspace = ws = r"\\gisfile\GISmaps\AtlasMaps\ATLAS_MAPS_18\Test Folder"
oldText = "Print Date:9.23.2016"
newdate = "Print date: <dyn type=\"date\" format=\"\"/>" #i want the date text dynamic

# list the mxds of the workspace folder
for mxdname in arcpy.ListFiles("*.mxd"):
    print "checking document: {}".format(mxdname)

    mxd = arcpy.mapping.MapDocument(ws + "\\" + mxdname)
    for elm in arcpy.mapping.ListLayoutElements(mxd, "TEXT_ELEMENT"):
        if "Print Date" in oldText.upper():
            new_fixed = "{}: {}".format(oldText.split(":")[0], newdate)
            print "+++date changed+++"

mxd.save()
del mxd
‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

However, it doesn't print the second statement or change the text in the MXD. I'm guessing it might have to do with the contents of the text box? This is the actual text box I've been testing on with all it's contents as it is in the MXD. As I said before this "Print Date..." line will have different content in different MXDs; where I'm eventually headed. Also, I've never named any elements in any of the maps. So, I don't see how I'd use this to access the text.

0 Kudos
RandyBurton
MVP Regular Contributor

I just took a quick look at your code.  You need to make at least one change.  For the comparison, it can help to convert case, so that it does not prevent a match.  You can either convert your oldText to upper case for comparison, or remove the .upper() and do a case sensitive match:

# change:
       if "Print Date" in oldText.upper():

# to:
       if "PRINT DATE" in oldText.upper():

# or:
       if "Print Date" in oldText:
0 Kudos