Select to view content in your preferred language

Labeling using ampersand (&) symbol in text via Python

6044
23
Jump to solution
02-02-2023 06:55 AM
PeteJordan
Frequent Contributor

  I've got a number of different labeling scripts I use and they all at least contain one NAME type field I display.  On the rare occasion, I have names that contain the "&(such as "Rough & Ready").  

  I know the ampersand is a special character and when I use it in my script it starts displaying the other fileds parameters in the output text.

 

Here is an example of one of my simple labeling codes in Python:

def FindLabel([Name_1],[Width_1]):

str_list = []
if [Name_1] not in (None, "<NULL>","<Null>", "", "0"):
str_list.append([Name_1])

if [Width_1] not in (None, "<NULL>","<Null>", "", "0", " ","Varies"):
Width_1_str = '<ITA>Width = {}\'</ITA>'.format("<ITA>" + [Width_1] + "</ITA>")
str_list.append(Width_1_str)

label_str = '\n'.join(str_list)
return(label_str)

 I have found an example using VB and somehow incorporating "&","&amp;" into the script, but I have played around with adding variations of that in my code, but I can't seem to get it to work without errors.

  Any ideas how to simply allow & into my Name_1 field in this example?

Thanks

0 Kudos
23 Replies
PeteJordan
Frequent Contributor

  Thanks so much, and I actually used the code block in the past and just forgot how to use it, so thanks for that refresher for my future questions on the forum.

  I liked how you explained everything and that made it much easier to understand why you changed what you did.  Now I can update my document with this and make it a lot more inclusive to the types of naming conventions we get.

  With the Width, yes we can get just numeric values or such things like "8FT" or "Varies", so I always have to treat that as a text field (I forgot to include the "varies" back into the code, but that's an easy fix).

  Thanks so much for this help...

0 Kudos
PeteJordan
Frequent Contributor

 @RhettZufelt , I did run into an issue though.  As I mentioned before, the WIDTH can actually be a string in some cases or numeric in other cases.

  So right now this code works if the WIDTH is a String, but it fails if it's numeric and WIDTH=0.  So I've tried to add in just a 0 into the code, but it doesn't seem to ignore that.  I've also tried to use the None as well without success.

(Note I had to change the name of the Width_1 to WDTH_TMP to make it easier.

 

Here's what I'm trying without success if the value is 0 and WIDTH is actually a numeric field.

 

 

  if str(WDTH_TMP).strip() not in (None,"<NULL>","<Null>","0",0):

 

 

 

 

0 Kudos
RhettZufelt
MVP Notable Contributor

Apperantly, python evaluates a zero as emtpy, but not None/Null.

the If WDTH_TMP:  comparison will evaluate to false if NAME1 is numeric and = 0 so is bailing out before the next comparison.

RhettZufelt_0-1675703313444.png

If you change it to 'is not None', it shouldn't have this issue:

    if NAME1 is not None::                                        # checks to see if value or Null
        if '&' in NAME1:                             # as string value can print as None, but doesn't work with "not in"
            NAME1=NAME1.replace("&","&amp;")

        if str(NAME1).strip not in ("<NULL>","<Null>","0"):   
            str_list.append(NAME1)

R_

 

0 Kudos
PeteJordan
Frequent Contributor

  Though the problem now is I can't exclude some of the text variables that might show up if that field is a Text rather than Numeric.  I have gotten this version of the code to work without any issues.  The only problem of course is the labeling features such as font size and Itallics aren't included, but I can deal with that as the "&' in the NAME or ALT_NAME is more important.

  Thanks for your help though on this again...

def FindLabel([NAME],[ALT_NAME],[ROUTE],[WIDTH]):

  str_list = []
  if [NAME] not in (None, "<NULL>", "", "0"):
    str_list.append([NAME])

  if [ALT_NAME] not in (None,"<NULL>", "", "0"):
    altname_str = '({})'.format([ALT_NAME])
    str_list.append(altname_str)    

  if [ROUTE] not in (None,"<NULL>", "", "0"):
    route_str = 'RT# {}'.format([ROUTE])
    str_list.append(route_str)  
    
  if [WIDTH] not in (None, "<NULL>", "", "0"):
    width_str = 'Width = {}'.format([WIDTH])
    str_list.append(width_str)    

  label_str = '\n'.join(str_list)
  return(label_str)

  

0 Kudos
RhettZufelt
MVP Notable Contributor

Can you give example : " I can't exclude some of the text variables that might show up if that field is a Text rather than Numeric."   I'm not sure what you are getting at here.

Normally one standardizes the schema (text in text fields, numbers in numeric) to avoid this type of issue.

R_

0 Kudos
PeteJordan
Frequent Contributor

  Well for the Width the original code had 

 

    if WDTH_TMP:
        if str(WDTH_TMP).strip() not in ("<NULL>","<Null>","0","Varies"):
            width_str = "<ITA>Width = {}'</ITA>".format(str(WDTH_TMP))
            str_list.append(width_str)

as I need to exclude 0 if it was a numeric field, or exclude text values that show up as "<NULL>, "0", "<Null>" and "Varies".  These have been populated in the Width field if the Width was a text field and ones with those values, I didn't want to display at all as a lable if they contained those values...

0 Kudos
RhettZufelt
MVP Notable Contributor

OK, since you don't seem to have control of text vs numeric data, lets cast everything to text first.

while at it, might as well .strip() any spaces on either end of the text string.

Now we have varibles (with"1" appended to the end) that are strings with no extra white spaces.

I also changed from variable.format() to f'{variable}' format as it is easier to visualize and keep track of.

Since you don't  have control over the data given, lets compare ROUTE1 and WIDTH1 upper case.  This way, doesn't matter if the field says "Varies", "varies", "VARIES", etc. will be filtered out.

Now, as far as null values, difficult figureing a way to query them out when then can be text or numeric without actually putting "None" in the label field.

The following seems to work regardless of the input format (text vs number):

def FindLabel([NAME],[ALT_NAME],[ROUTE],[WIDTH]):

  NAME1 = str([NAME]).strip()           # assign variable to string with whitespace stripped
  ALT_NAME1 = str([ALT_NAME]).strip()   # assign variable to string with whitespace stripped
  ROUTE1 = str([ROUTE]).strip()         # assign variable to string with whitespace stripped
  WIDTH1 = str([WIDTH]).strip()          # assign variable to string with whitespace stripped
  
  
  str_list = []
  if [NAME]:                                    #Make sure is not empty or can append 'None' to label
    if NAME1 not in ("<NULL>", "", "0"):        # since already string, test if in(values)
      str_list.append(NAME1)                    # append to list if not in the filter list

  if [ALT_NAME]:
     if ALT_NAME1 not in ("<NULL>", "", "0"):
       altname_str = f'({ALT_NAME1})'            # I changed to f'strings as easier to visualize than .format
       str_list.append(altname_str)    

  if [ROUTE]:
     if ROUTE1.upper() not in ("<NULL>", "", "0", "VARIES"):  # Cast compare variable to upper case to test for "VARIES"
       route_str = f'RT# {ROUTE1}'                                 # does not sound like you have much control over data
       str_list.append(route_str)                                  # so casting to upper case will catch other spellings/Case
    
  if [WIDTH]:
     if WIDTH1.upper() not in ("<NULL>", "", "0", "VARIES"):
       width_str = f'Width = {WIDTH1}'
       str_list.append(width_str)    

  label_str = '\n'.join(str_list)
  return(label_str)

R_

 

0 Kudos
PeteJordan
Frequent Contributor

Yes that works the same with the code I just posted earlier and allows the use of the "&".  The issue is that it won't allow me to work with Itallics or font changes and that was the issue.  I can get away using the code I had, or the one you posted just fine, but I won't have the ability to work with that functionality.  

  Again, it's more just visually appeasing, and not something I really need, but it would be nice to see if it could work with that sort of thing.  

  Here's the example of what I added to your code for just the Width (note I had to rename your WIDTH1 to another name as some data comes in with a Width1 as the name, and searching replacing doesn't work as well like that.) so I just renamed it to WDTH_TMP

 

	if WDTH_TMP:
        if str(WDTH_TMP).strip() not in (None,"<NULL>","<Null>","","0",0):
            width_str = "<ITA>Width = {}'</ITA>".format(str(WDTH_TMP))
            str_list.append(width_str)

 

Here's the full code using your example and the itallics and font size changes where having a "&" in the name, still causes that issue displaying the code for itallics, etc as the original issue had.  Removing those of course works fine, but I don't get this added visual possibilities is all...

(I will be out on vacation for a week so after tomorrow, I'll repond and check out any other suggestions next week.)

 

0 Kudos
RhettZufelt
MVP Notable Contributor

If you use text formatting tags anywhere in the labelstring, you need to put the replace function back in there to replace any '&' with &amp; as below:

 

def FindLabel([NAME],[ALT_NAME],[ROUTE],[WIDTH]):

  NAME1 = str([NAME]).strip()           # assign variable to string with whitespace stripped
  ALT_NAME1 = str([ALT_NAME]).strip()   # assign variable to string with whitespace stripped
  ROUTE1 = str([ROUTE]).strip()         # assign variable to string with whitespace stripped
  WIDTH1 = str([WIDTH]).strip()          # assign variable to string with whitespace stripped
            
  str_list = []
  if [NAME]:                                    #Make sure is not empty since or can append 'None' to label
     if '&' in NAME1:
            NAME1=NAME1.replace("&","&amp;")
            
     if NAME1 not in ("<NULL>", "", "0"):        # since already string, test if in(values)
      str_list.append(NAME1)                    # append to list if not in the filter list

  if [ALT_NAME]:
     if '&' in ALT_NAME1:
            ALT_NAME1=ALT_NAME1.replace("&","&amp;")
            
     if ALT_NAME1 not in ("<NULL>", "", "0"):
       altname_str = f"<CLR red = '255'><BOL><FNT name = 'Arial' style = 'Italic' size = '10'>({ALT_NAME1})</FNT></BOL></CLR>"       # I changed to f'strings as easier to visualize than .format
       str_list.append(altname_str)    

  if [ROUTE]:
     if ROUTE1.upper() not in ("<NULL>", "", "0", "VARIES"):  # Cast compare variable to upper case to test for "VARIES"
       route_str = f'RT# {ROUTE1}'                                 # does not sound like you have much control over data
       str_list.append(route_str)                                  # so casting to upper case will catch other spellings/Case
    
  if [WIDTH]:
     if WIDTH1.upper() not in ("<NULL>", "", "0", "VARIES"):
       width_str = f"<UND><BOL>Width = {WIDTH1}'</BOL></UND>"
       str_list.append(width_str)    

  label_str = '\n'.join(str_list)
  return(label_str)

 

This example shows changing font size, color, and making bold.  the <ITA> tag, even though Pro's documentation says it works, DOES NOT WORK in either of my versions of Pro (2.9.3 or 3.0.3).  However, if you look at line 21, I call out the font as Arial Italic so it displays as such.

RhettZufelt_0-1675730425786.png

Maybe you could contact tech support and see if there is some trick to getting the <ITA> tag to work, but suspect it is a bug.  Otherwise, if you want italics, you will need to find a font that lets you declare it as above.

R_

0 Kudos
PeteJordan
Frequent Contributor

Very odd.  So I have tried your code and I get it to work, all except when there is only the NAME being displayed.  Then it fails with the '&' and prints out the following:

"TEST & TEST" will show up as "TEST &amp: TRUE"

  Now if I add an ALT_NAME with or without the '&' it displays both perfectly fine.  Or if I add a ROUTE or WIDTH (other than what is in the exclusion) it works perfectly fine.  So just the NAME with the '&' fails.  This is getting pretty close to the fix though.

  On another note, being on 3.0.3 myself.  I did try with the <ITA> just to see if I get the same issues you did, and I am getting it working for me the exact way you used <BOL>.  So odd you aren't getting that working like I am...

  

 

0 Kudos