Use arcpy to read MXD Label Expressions then write value to field in featureclass

1966
8
Jump to solution
03-17-2017 07:01 AM
DarrisFriend
Occasional Contributor

Label Expressions are not supported in Runtime Content. I have a MXD with over 50 layers, grouped by utility, i.e electric, gas, water, fiber, etc. Each layer for each utility has a different label expression. Using arcpy can I read the label expression's Function (the parser is VBScript) and write the value of the Function to a field in that feature class I created in a seperate process?

I started with this example (#1) LabelClass—Help | ArcGIS for Desktop 

Here is one result of the above sample, can I read the Function into arcpy and write the return value into an existing field? 

@

Layer name: TransformerBankTraffic
Class Name: Default
Expression: Function FindLabel ( [Subtype], [DeviceID], [RatedKVA_A], [RatedKVA_B], [RatedKVA_C], [RatedKVA_Spare] )
blnHasText = False

sPrefix = ucase(left([DeviceID] & " ", 3))
if (sPrefix <> "NON") and (sPrefix <> "UNK") and (sPrefix <> " ") then
blnHasText = True
strLabel = [DeviceID]
end if

if ([subtype] = 5) or ([subtype] = 9) or ([subtype] = 7) then
if blnHasText then
strLabel = strLabel & vbcrlf
else
blnHasText = True
end if

if UCASE([RatedKVA_A]) = "25 KVA" then
strLabel = strlabel & "75 KVA-ABC"
else
if UCASE([RatedKVA_A]) = "37.5 KVA" then
strLabel = strlabel & "112.5 KVA-ABC"
else
if UCASE([RatedKVA_A]) = "50 KVA" then
strLabel = strlabel & "150 KVA-ABC"
else
if UCASE([RatedKVA_A]) = "75 KVA" then
strLabel = strlabel & "225 KVA-ABC"
else
if UCASE([RatedKVA_A]) = "100 KVA" then
strLabel = strlabel & "300 KVA-ABC"
else
if UCASE([RatedKVA_A]) = "167 KVA" then
strLabel = strlabel & "500 KVA-ABC"
else
if UCASE([RatedKVA_A]) = "250 KVA" then
strLabel = strlabel & "750 KVA-ABC"
else
if UCASE([RatedKVA_A]) = "333 KVA" then
strLabel = strlabel & "1000 KVA-ABC"
else
if UCASE([RatedKVA_A]) = "500 KVA" then
strLabel = strlabel & "1500 KVA-ABC"
else
if UCASE([RatedKVA_A]) = "667 KVA" then
strLabel = strlabel & "2000 KVA-ABC"
else
if UCASE([RatedKVA_A]) = "833 KVA" then
strLabel = strlabel & "2500 KVA-ABC"
else
if UCASE([RatedKVA_A]) = "1000 KVA" then
strLabel = strlabel & "3000 KVA-ABC"
else
if UCASE([RatedKVA_A]) = "1250 KVA" then
strLabel = strlabel & "3750 KVA-ABC"
end if
end if
end if
end if
end if
end if
end if
end if
end if
end if
end if
end if
end if

else
if UCASE([RatedKVA_A]) <> "NONE" then
if blnHasText then
strLabel = strLabel & vbcrlf
else
blnHasText = True
end if
strLabel = strlabel & [RatedKVA_A] & "-A"
end if

if UCASE([RatedKVA_B]) <> "NONE" then
if blnHasText then
strLabel = strLabel & vbcrlf
else
blnHasText = True
end if
strLabel = strLabel & [RatedKVA_B] & "-B"
end if

if UCASE([RatedKVA_C]) <> "NONE" then
if blnHasText then
strLabel = strLabel & vbcrlf
else
blnHasText = True
end if
strLabel = strLabel & [RatedKVA_C] & "-C"
end if

if UCASE([RatedKVA_Spare]) <> "NONE" then
if blnHasText then
strLabel = strLabel & vbcrlf
else
blnHasText = True
end if
strLabel = strLabel & [RatedKVA_Spare] & "-Spare"
end if
end if
FindLabel = strLabel
End Function

0 Kudos
1 Solution

Accepted Solutions
RandyBurton
MVP Regular Contributor

I've used this code in the Python window inside ArcMap.  You may need to adjust around lines 40-41 if the VB code from the label expression isn't well formatted.

mxd = arcpy.mapping.MapDocument("CURRENT") # reference document open in ArcMap

for lyr in arcpy.mapping.ListLayers(mxd):
    if lyr.supports("LABELCLASSES"):
        print "\n\nProcessing Layer: " + lyr.name

        # check for label class expression
        lblExp = ""
        lblType = "Other"
        for lblClass in lyr.labelClasses:
            lblExp = lblClass.expression 
            if len(lblExp):
                if "Function FindLabel" in lblExp:
                    lblType = "VB"
                    print "VB expression found"
                elif "def FindLabel" in lblExp:
                    lblType = "PYTHON_9.3" # assuming later version of ArcMap
                    print "Python expression found"
                elif "function FindLabel" in lblExp:
                    lblType = "JScript" # Field Calculator does not use JScript
                    print "JScript expression found"

        if lblType == "VB": # just working with VB for now
            add_field = True # plan to add field for TextLabel
            layerFields = arcpy.ListFields(lyr)
            for fld in layerFields:
                if fld.name in ['TextLabel']:
                    add_field = False  # no need to add field
            if add_field:
                print "Adding field: TextLabel"
                arcpy.AddField_management(in_table=lyr,
                                          field_name="TextLabel",
                                          field_type="TEXT",
                                          field_length="50",
                                          field_alias="Text Label",
                                          field_is_nullable="NULLABLE",
                                          field_is_required="NON_REQUIRED")

            # delete first and last line of VB code block
            lblExp = lblExp.strip()
            calcExp = lblExp[lblExp.find('\n')+1:lblExp.rfind('\n')].strip()
            print calcExp

            arcpy.CalculateField_management(in_table=lyr,
                                field="TextLabel",
                                expression="FindLabel",
                                expression_type="VB",
                                code_block=calcExp)

View solution in original post

8 Replies
DanPatterson_Retired
MVP Esteemed Contributor

you might want to try to format your code using code blocks... and that code, does it do anything? or are you looking for a translation?

0 Kudos
DarrisFriend
Occasional Contributor

I think the solution is to pass labelClass.expression to calculatefield_managment

RandyBurton
MVP Regular Contributor

The code used in Label Expressions is slightly different than code used in the Field Calculator.  You will need to make adjustments.  

For VB code:

''' VB Function used in Label Class Expression

Function FindLabel ( [Prefix], [NAME], [Sufix] )
  FindLabel = [Prefix] & " " & [NAME] & " " & [Sufix] 
End Function

Note: Drop first and last line of VB script for code block
For Expression, you will use name of function
'''
arcpy.CalculateField_management(in_table=myTable,
                                field=labelField,
                                expression="FindLabel",
                                expression_type="VB",
                                code_block='FindLabel = [Prefix] & " " & [NAME] & " " & [Sufix]\n')‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

For Python code (in case you need to go that route):

''' Python Function used in Label Class Expression:

def FindLabel ( [Prefix], [NAME], [Sufix] ):
  return [Prefix] + " " +  [NAME] + " " + [Sufix]

Note: For expression, replace square brackets with exclamation marks in first row.
For def code, remove remove brackets from around field names
'''
arcpy.CalculateField_management(in_table=myTable,
                                field=labelField,
                                expression="FindLabel( !Prefix!,!NAME!, !Sufix! )",
                                expression_type="PYTHON_9.3",
                                code_block='def FindLabel ( Prefix, NAME, Sufix ):\n  return Prefix + " " +  NAME + " " + Sufix\n')
0 Kudos
RandyBurton
MVP Regular Contributor

I've used this code in the Python window inside ArcMap.  You may need to adjust around lines 40-41 if the VB code from the label expression isn't well formatted.

mxd = arcpy.mapping.MapDocument("CURRENT") # reference document open in ArcMap

for lyr in arcpy.mapping.ListLayers(mxd):
    if lyr.supports("LABELCLASSES"):
        print "\n\nProcessing Layer: " + lyr.name

        # check for label class expression
        lblExp = ""
        lblType = "Other"
        for lblClass in lyr.labelClasses:
            lblExp = lblClass.expression 
            if len(lblExp):
                if "Function FindLabel" in lblExp:
                    lblType = "VB"
                    print "VB expression found"
                elif "def FindLabel" in lblExp:
                    lblType = "PYTHON_9.3" # assuming later version of ArcMap
                    print "Python expression found"
                elif "function FindLabel" in lblExp:
                    lblType = "JScript" # Field Calculator does not use JScript
                    print "JScript expression found"

        if lblType == "VB": # just working with VB for now
            add_field = True # plan to add field for TextLabel
            layerFields = arcpy.ListFields(lyr)
            for fld in layerFields:
                if fld.name in ['TextLabel']:
                    add_field = False  # no need to add field
            if add_field:
                print "Adding field: TextLabel"
                arcpy.AddField_management(in_table=lyr,
                                          field_name="TextLabel",
                                          field_type="TEXT",
                                          field_length="50",
                                          field_alias="Text Label",
                                          field_is_nullable="NULLABLE",
                                          field_is_required="NON_REQUIRED")

            # delete first and last line of VB code block
            lblExp = lblExp.strip()
            calcExp = lblExp[lblExp.find('\n')+1:lblExp.rfind('\n')].strip()
            print calcExp

            arcpy.CalculateField_management(in_table=lyr,
                                field="TextLabel",
                                expression="FindLabel",
                                expression_type="VB",
                                code_block=calcExp)
DarrisFriend
Occasional Contributor

Thanks for putting it together. I was reading about CalculateField_management on my way home from work Friday. I was delighted to see I can pass in the expression, but I was wasn't aware of the need to remove the first and last VB code block. Your solution will need some adjustment to work for my needs, for example calc the field value if arcpy.mapping.ListLayers(mxd).showLabels = True, but I can do that. I also want it run from ArcCatalog or maybe stand alone. Our data is versioned so it also needs to disconnect users to add the new field in production. I can disconnect users ahead of running it but I think it would be handy if this can do it all. When I have time I will make an Add-In out of this for others to use.

Nice job and thanks for being persistent over the weekend. I was going to work on this Monday but your help put me a day ahead of schedule.

0 Kudos
DarrisFriend
Occasional Contributor

After some modifications to your solution I discovered another obstacle. For fields that use Coded Value Domains (CVD) the returned value is the code and not the description. For example this is the expression [FeederID]  & " " & [PhaseDesignation] where the field named PhaseDesignation has a different CVD based on the Subtype chosen. In the attached screen shot you can see the results in StandardLabel showing the coded value and not the description value from PhaseDesignation, such as "ABC" which is three phase or "B" which is single phase. When the feature is labeled in ArcMap the descriptive value is labeled on the feature.

Anyway, I am looking over the arcpy documentation for a solution today.

0 Kudos
RandyBurton
MVP Regular Contributor

Perhaps creating a dictionary for the domain might help.  There is some discussion in this old thread: How can I get the description value of a field that has a domain?  Then you might be able to replace domain descriptions used in the VB code block with the equivalent domain code. 

0 Kudos
DarrisFriend
Occasional Contributor

Thanks, I considered that as well. I worked on it yesterday. The issue is that every layer will have a different code block. Writing code to address every possibility would be complex. The time it takes to do that I might as well review each layer in the MXD and add the label I want manually to the new label field. I posted a request to ideas.esri.com asking for an additional optional parameter in CalculateField_managment to address a problem such as this. It could be useful to calc the description value rather than the coded value for other tasks as well.

CalculateField_management should allow Domain Description value 

0 Kudos