I created a drag'n'drop form for a layer with a number of Text Edit widgets which I set as 'multi-line'. I also ticked 'HTML' (don't ask me why!). I gave the project to a colleague to input and edit data. Now these fields have a full HMTL document in each field of each record - in this case the data was a single word [Dry] but what is stored is:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
p, li { white-space: pre-wrap; }
</style></head><body style=" font-family:'.AppleSystemUIFont'; font-size:13pt; font-weight:400; font-style:normal;">
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Dry</p></body></html>

Is there a way I can remove the HTML tags? When viewing the attribute table I can't see the data, just the HTML tags. And this additional 'bloat' is stored in the GPKG file for each value.

I have just worked out how to 'hide' the tags when creating a report / print layout (with a text label I need to render as HTML), but of course it over-rides any styling that I might have wanted to use!

I did this form formatting in error and would like a way of undoing the error. I have tried pasting the attribute table to excel and other text editors but the dataset gets messed up when there are new-lines within a record.

I'm using QGIS 3.26.0

2 Answers 2


A way to solve this would be to create a custom function from the function editor, in order to remove the tags.

For example you could create a regex ( such as the one from https://stackoverflow.com/questions/9662346/python-code-to-remove-html-tags-from-a-string#12982689 )

And in the function editor you would have

import re
CLEANR = re.compile('<.*?>') 

from qgis.core import *
from qgis.gui import *

@qgsfunction(args='auto', group='Custom')
def cleanhtml(raw_html,*args):
  cleantext = re.sub(CLEANR, '', raw_html)
  return cleantext

Then you can use cleanhtml in the expression builder to remove most, you may need a trim and to tweak it a little. In your example some elements still remain, I would suggest to split the string by newline and fetch the last element of the array. But it depends on your other inputs if the pattern is constant.

  • Thanks for this suggestion. Unfortunately this produces an error in the function editor: NameError: name 're' is not defined To get this to work I can use the inbuilt function : regexp_replace( [FieldName],'<.*?>','') So this takes me a step forward - I would just need to strip out the {styles}, which are constant. However, this is only a partial solution in that I need to run the calculator for each field and update them individually... I have 30 fields. Maybe I need a function in python to iterate over each field? As usual, I know what I want to do, but don't have the language!
    – SeeWhy
    Commented Sep 5, 2022 at 9:42
  • try to add on "import re" as the first line to be able to run Al rl's snippet Commented Sep 6, 2022 at 8:44
  • I thought the 'import re ' was presetn as I did ctrl+a, I'll add it to the snippet, my bad. Edit, the start of the code block hid the import re, moved it down a line. Thanks @MortenSickel
    – Al rl
    Commented Sep 7, 2022 at 0:03

So I figured out how to iterate through the fields using the python console after a lot of iterations myself! The actual cleaning comes from the previous answer (thanks Al rl) and I worked out the process with the help of lots of different answers to various questions and pygis cookbook...

In case this is useful (the code is probably not very streamlined, as I'm new to python):

import re
layer = iface.activeLayer()
CLEANR = re.compile('<.*?>') # the all important replacement expression
#pre = re.compile('p, li { white-space: pre-wrap; }') #other style junk
for feature in layer.getFeatures():
    fid = feature.id() # get the row number
    for x in range(count): #loop through the fields in each record
        raw = feature[x] # the raw value of each field
        if type(raw) is str: # check that the field is a string
            value = re.sub(CLEANR, '', raw) # strip out <..> tags
            #cleaned = re.sub(pre, '', value)
            layer.changeAttributeValue(fid, x, value) # update value in this field for the given row [or swap for 'cleaned']
            print ( fid, x, raw, value)

And obviously I've commented a few things out here...

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.