Python-docx: How to Insert HTML into Docx?

Created on 21 Jan 2017  ·  8Comments  ·  Source: python-openxml/python-docx

Is it possible to insert HTML into a Document using python-docx with styling applied?
The only thing I need to work are italics.

For example how to insert "Today is \Saturday\" with Saturday actually being inserted with italics?

Thanks!

Most helpful comment

I prepare something like this... This is enough for me..

from HTMLParser import HTMLParser
from htmlentitydefs import name2codepoint
from docx import Document

.....
document = Document()
document_html_parser = DocumentHTMLParser(document)

document_html_parser.add_paragraph_and_feed(html_code)
.....
class DocumentHTMLParser(HTMLParser):
    def __init__(self, document):
        HTMLParser.__init__(self)
        self.document = document
        self.paragraph = self.document.add_paragraph()
        self.run = self.paragraph.add_run()

    def add_paragraph_and_feed(self, html):
        self.paragraph = self.document.add_paragraph()
        self.run = self.paragraph.add_run()
        self.feed(html)

    def handle_starttag(self, tag, attrs):
        self.run = self.paragraph.add_run()
        if tag == "i":
            self.run.italic = True
        if tag == "b":
            self.run.bold = True
        if tag == "u":
            self.run.underline = True
        if tag in ["br", "ul", "ol"]:
            self.run.add_break()
        if tag == "li":
            self.run.add_text(u'● ')
        if tag == "p":
            self.run.add_break()
            # self.run.add_break()
            # self.run.add_tab()

    def handle_endtag(self, tag):
        if tag in ["br", "li", "ul", "ol"]:
            self.run.add_break()
        self.run = self.paragraph.add_run()

    def handle_data(self, data):
        self.run.add_text(data)

    def handle_entityref(self, name):
        c = unichr(name2codepoint[name])
        self.run.add_text(c)

    def handle_charref(self, name):
        if name.startswith('x'):
            c = unichr(int(name[1:], 16))
        else:
            c = unichr(int(name))
        self.run.add_text(c)


All 8 comments

I had a similar issue however our HTML is way more complex and I couldn't find any direct method, so we ended up translating HTML fragments into a run map and then something like:
par = doc.add_paragraph()
for run_item in run_map:
run = par.add_run(run_item["text_fragment"])
if run_item["type"] == "italic":
run.style.italic = True
Something like that, see the docs around run topic for exact methods.

Wow thank you for the reply! Any way you could provide the link for that documentation?
Thanks,

Have a look at docx.text.run
This is an extract from what i did in a similar case (simplified for italics only):

import re
from docx import Document


test_items = [
    "Trailing <i>tag</i>",
    "<i>Leading</i> tag",
    "This <i>time a tag</i> is in the middle",
]

class HTMLHelper(object):
    """ Translates some html into word runs. """
    def __init__(self):
        self.get_tags = re.compile("(<[a-z,A-Z]+>)(.*?)(</[a-z,A-Z]+>)")

    def html_to_run_map(self, html_fragment):
        """ breakes an html fragment into a run map """
        ptr = 0
        run_map = []
        for match in self.get_tags.finditer(html_fragment):
            if match.start() > ptr:
                text = html_fragment[ptr:match.start()]
                if len(text) > 0:
                    run_map.append((text, "plain_text"))
            run_map.append((match.group(2), match.group(1)))
            ptr = match.end()
        if ptr < len(html_fragment) - 1:
            run_map.append((html_fragment[ptr:], "plain_text"))
        return run_map

    def insert_runs_from_html_map(self, paragraph, run_map):
        """ inserts some runs into a paragraph object. """
        for run_item in run_map:
            run = paragraph.add_run(run_item[0])
            if run_item[1] == "<i>":
                run.italic = True


doc = Document()
html_helper = HTMLHelper()
for test_item in test_items:
    run_map = html_helper.html_to_run_map(test_item)
    print "------------------------------\nTest item:", test_item
    print "\nRun map: ", run_map
    par = doc.add_paragraph()
    print "\n XML before:\n", par._element.xml
    html_helper.insert_runs_from_html_map(par, run_map)
    print "\n XML after:\n", par._element.xml
doc.save("test_run_mapping.docx")

hope it helps

Thank you so much for spending time to do that! It looks great. I am going to try to implement it now.

I am getting a type error while trying to make the run fragments.

if match.start > ptr:
TypeError: unorderable types: builtin_function_or_method() > int()]

Any Ideas on how to fix this?

I would expect it to be sorted by now, but for the sake of consistency:
it should be if match.start() > ptr: - simply missing ()

@electron378
its realy good sample but i wish you share full sample, not just for italic.
your sample doesnt work with alone br tag and double tag recursively inside each other immediately :(

I prepare something like this... This is enough for me..

from HTMLParser import HTMLParser
from htmlentitydefs import name2codepoint
from docx import Document

.....
document = Document()
document_html_parser = DocumentHTMLParser(document)

document_html_parser.add_paragraph_and_feed(html_code)
.....
class DocumentHTMLParser(HTMLParser):
    def __init__(self, document):
        HTMLParser.__init__(self)
        self.document = document
        self.paragraph = self.document.add_paragraph()
        self.run = self.paragraph.add_run()

    def add_paragraph_and_feed(self, html):
        self.paragraph = self.document.add_paragraph()
        self.run = self.paragraph.add_run()
        self.feed(html)

    def handle_starttag(self, tag, attrs):
        self.run = self.paragraph.add_run()
        if tag == "i":
            self.run.italic = True
        if tag == "b":
            self.run.bold = True
        if tag == "u":
            self.run.underline = True
        if tag in ["br", "ul", "ol"]:
            self.run.add_break()
        if tag == "li":
            self.run.add_text(u'● ')
        if tag == "p":
            self.run.add_break()
            # self.run.add_break()
            # self.run.add_tab()

    def handle_endtag(self, tag):
        if tag in ["br", "li", "ul", "ol"]:
            self.run.add_break()
        self.run = self.paragraph.add_run()

    def handle_data(self, data):
        self.run.add_text(data)

    def handle_entityref(self, name):
        c = unichr(name2codepoint[name])
        self.run.add_text(c)

    def handle_charref(self, name):
        if name.startswith('x'):
            c = unichr(int(name[1:], 16))
        else:
            c = unichr(int(name))
        self.run.add_text(c)


Was this page helpful?
0 / 5 - 0 ratings

Related issues

kociak picture kociak  ·  5Comments

madphysicist picture madphysicist  ·  3Comments

chcourses picture chcourses  ·  3Comments

bendaojiu picture bendaojiu  ·  5Comments

ronbarak picture ronbarak  ·  3Comments