1
\$\begingroup\$

I have a word document that contains alphabetical bullet points along with the key: value that I am trying to input into a dictionary using docx.

Example:

This is my document
   a. First name: Jeff
   b. Last name: Smith
   c. Position: Manager
   d. Birthdate: 01/01/1990
   e. Address: 123 Main Street, Smithville, Maine

Output:

my_dict = {'First name': 'Jeff',
           'Last name': 'Smith',
            .....}
def get_the_deets(path):
    doc = Document(path)
    parse_list = ['a','b','c','d','e','f','g','h','i','j','k','l']
    return {i.text.strip(f' {p}.').split(':')[0]:i.text.strip(f' {p}.').split(':')[1].strip() 
        for p in parse_list for i in doc.paragraphs if i.text.strip().startswith(f'{p}')}

path = r'path_to_doc'
get_the_deets(path)

This works the way I want it to, but I am looking for criticism as how to clean up my code, as I assume there are more efficient ways of handling this task.

Steps I took:

  • create a list of values I am looking for
  • Iterate through each paragraph using doc.paragraph
  • Remove whitespace from the the ends of each line
  • if the item I am looking for is the first character, split the rest of the line on the :
  • assign the first index of the split to a key, second index to a value
  • add strip to the key and value to clean up whitespace and unnecessary characters.
\$\endgroup\$
0

2 Answers 2

2
\$\begingroup\$

This is a classic job for a regex.

@AJNeufeld wrote the bugs beautifully, but I believe there are 2 possible major improvements that can be done.

First option

If you wish to keep your way of parsing, I suggest using a loop like so:

def get_the_deets(path):
    doc = Document(path)
    result = {}
    parse_list = {'a','b','c','d','e','f','g','h','i','j','k','l'}
    for para in doc.paragraphs:
        text = para.text
        # Validate the text
        if (text.startswith("   ") and len(text) >= 6 and
                text[3] in parse_list and text[4] == '.' and ":" in text):
            text = text[6:]
            field, _, value = text.partition(": ")
            result[field] = value

The validation is a bit more clear, and will ensure there won't be any bugs. It is true that the code is longer, but first and foremost, it is correct, not repetitive, and is clearer than doing this entire thing inside a one liner.

Second option

Use a regex. It'll be much simpler:

import re
from docx import Document
# 1. A few spaces (important to make sure these are values)
# 2. the [a-l] items only
# 3. the field: value
FIELD_RE = re.compile(r'   [a-l]\. (?P<field>[^:\n]+): (?P<value>.+)')

def get_the_deets(path):
    doc = Document(path)
    result = {}
    for para in doc.paragraphs:
        match = FIELD_RE.match(para.text)
        if match:
            result[match.group('field')] = match.group('value')
\$\endgroup\$
1
\$\begingroup\$

Bugs

It is interesting you truncated your example output after 2 dictionary entries. Perhaps you noticed you had a bug with the 5th line?

>>> i_text = "   e. Address: 123 Main Street, Smithville, Maine"
>>> p = 'e'
>>> {i_text.strip(f' {p}.').split(':')[0]:i_text.strip(f' {p}.').split(':')[1].strip()}
{'Address': '123 Main Street, Smithville, Main'}

Maine has been shortened to Main in the resulting dictionary.

Why? The first strip() in i_text.strip(f' {p}.').split(':')[1].strip() is removing any of the characters , e and . at either end of the text.

You don't mind stripping off spaces at the right end, probably can live with stripping off the period from the end of the values, but stripping the e should be unacceptable.

Similarly, the following should also be unacceptable:

  • a. First name: RobertaRobert
  • b. Last name: WebbWe
  • c. Position: MedicMedi

If you ever have a line starting with a lowercase letter up to l that doesn't contain a :, your split(':')[1] will raise an IndexError. This makes your code very fragile.

\$\endgroup\$
1
  • \$\begingroup\$ Thank you for your response. I'm still learning so your post brought up some things I didn't think about. I actually did not notice the bug because I used dummy data for the post as I did not want to use the real data, but I am glad the bug was found. Also ,the form will always have a ':' because there are forced user input boxes. \$\endgroup\$
    – Brendan S
    Jan 9, 2023 at 16:41

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.