0 votes

I have a colleague with a published NT, but no dictionary. They are interested in creating one, but would like to be spared as much ‘heavy lifting’ as possible. Is there a way to get the ParaTExt Wordlist to FLEx (e.g., ‘Export to XML’)? Is this recommended? Are there other recommendations? Thanks for any advice.

Paratext by (639 points)

8 Answers

+1 vote
Best answer

Paul - there are lots of thoughts on this. Some people say that you should
not build a dictionary off of translated text. Having said that you could
export the wordlist to xml and then try and import it into FLEx. However
this would give you a mess. You would have multiple different tenses and
forms. I’d suggest you look at the Rapid Word Collection site:
http://rapidwords.net/

anon848905
Americas Area Language Technology Coordinator
[Email Removed]
[Phone Removed]Office at JAARS)
[Phone Removed]Cell)
Skype name: anon848905

by (9.8k points)

Thanks anon848905. We’re well aware of RWC (I’ve gathered ~ 4,000 words this way and helped a few colleagues get started with it as well). The particular colleagues in question have spent over twenty years working in the language and are looking for a way to get wordforms into FLEx so they could start using the Bulk Edit tools available there. Rapid Word Collection Lite? I guess I’m trying to spare them the drudgery of typing in what will likely be over 5,000 stems. Right now this is the hurdle that prevents them moving ahead… I’ll keep looking for other options.

What if you extracted the word list from Paratext and created a text
document (or maybe 50 documents of 100 words each) and then gave each
document to FLEx and added to the dictionary only the wordforms that
belong there? (Depending on the language, you could take the time to set
up some parsing, so that “acted” would be parsed as “act” + “-ed” etc.)

Here’s an excerpt from my ParaTExt Wordlist exported to XML:

I don’t think it would be difficult to do a few Find / Replace passes to make it useful:
Find: <item word=" -> \lx[space]
Find: spelling=“Correct” -> [nothing]
I’m not sure what to do with the morphs, though… Maybe that would have to be done in FLEx.

I’ll run this past them and see if it’s the start they’re looking for. Thanks.

Ummm… Can’t one ‘paste’ into this forum? I pasted a section of text in this window and it showed up while composing, but it doesn’t appear once I’ve posted…

0 votes

You probably pasted text that was interpreted in a special way by the Markdown formatter or that was invalid and was thus removed.
Make sure to pay attention to the preview pane when composing.

by [Expert]
(16.6k points)

Let me try again, removing the leading < from each line:

item word=“abu” spelling=“Correct” morph=“abu” />
item word=“abua” spelling=“Correct” morph=“abu +a” />
item word=“abughami” spelling=“Correct” morph=“abu +ghami” />
item word=“abughamu” spelling=“Correct” morph=“abu +ghamu” />
item word=“abughinia” spelling=“Unknown” morph=“abu +ghini +a” />
item word=“abughita” spelling=“Correct” morph=“abu +ghita” />
item word=“abugho” spelling=“Correct” morph=“abu +gho” />
item word=“abui” spelling=“Correct” morph=“abu +i” />
item word=“abukoira” spelling=“Correct” morph=“abu +ko +ira” />
item word=“abukolu” spelling=“Unknown” morph=“abu +kolu” />
item word=“abura” spelling=“Correct” morph=“abu +ra” />
item word=“aburara” spelling=“Unknown” morph=“abu +ra +ra” />
item word=“abutughami” spelling=“Correct” morph=“abu +tu +ghami” />
item word=“abutuira” spelling=“Correct” morph=“abu +tu +ira” />
item word=“abuu” spelling=“Correct” morph=“abu +u” />

0 votes

So I’ve done a Find & Replace to change < item word=" to \lx[space]. Is there a regular expression I could use to get rid of everything following the word, i.e., from the first " to > at the end:

\lx abu" spelling=“Correct” morph=“abu” />

by (639 points)
0 votes

The following should work:
Find: ".+?>
Replace: (nothing)

P.S. I strongly recommend this website to help create regular expressions. It’s the best one I’ve found. :smile:

by [Expert]
(16.6k points)

Worked perfectly. Thank you! I think this will be enough to get my colleagues started. :wink:

0 votes

There is a utility that will extract glosses from Paratext Interlinear as either an SFM file or an Excel Spreadsheet. Might try that. http://lingtransoft.info/apps/extract-paratext-interlinear-glosses

by [Expert]
(2.9k points)
0 votes

I found another utility for viewing the glosses in the Paratext Interlinearizer. http://lingtransoft.info/apps/glossy

by [Expert]
(2.9k points)

Glossy can be used to extract the glosses in a way that could be imported into FLEx, but it’s not exactly set up for that at the moment. It’s for interactive lexicon browsing. I think I could figure out a way to produce an SFM file if folk were interested.

Has there been any progress on getting Glossy to extract the glosses from PT Interlineariser and import them into something like an SFM file? I have tried dragging the file lexicon.xml onto ParatextLexiconSetup program. But when I run the ParatextLexicon program, I don’t know what to enter in the slot labeled ‘SFM File’. I see a dialogue box entitled ‘Convert Paratext Lexicon to SFM’. Then it prompts you to enter file names for two fields:
Paratext Lexicon, and
SMF File.
I can easily enter the lexicon.xml file. But I don’t know what to put in the SFM file. Is there a list of different SFM formatting styles to choose from?

anon088806,

The SFM File is the name of the output file of the selected lexicon.xml. You can put whatever full path name you want. This will become the desired sfm file.

kent_schroeder

Hi kent_schroeder,

Thanks so much! I had no idea the solution could be that simple… Much appreciated! anon088806

0 votes

Seems we could use a utility to work with the Paratext wordlist file. Anyone out there interested in writing one?

by [Expert]
(2.9k points)

What do you want the tool do?

kent_schroeder
Software Developer/Language Technology Consultant
SIL – Africa-Focused Shared Services

Nairobi, Kenya

kent_schroeder, it seems that they want to harvest words from the wordlist tool for making a lexicon in Fieldworks. The interlinearizer data is better since it also has the gloss, but the wordlist tool would most likely have more words. If they vernacular words could be stripped out of the html file and marked with the \lx marker then that file could be imported into an existing Flex database. The user then would have to go through each entry and adjust the surface form to be a true lexeme and enter the gloss and part of speech himself.

kent_schroeder, can you give your software the ability to convert the Biblicaltermsxyz.xml file to standard format? Someone in another post is wanting to do that.

0 votes

Here is a script I’ve written for python3 (I use Wasta Linux 18.04). Note that I have hard-coded the path for the input and output in the code…

#!/usr/bin/python3
# run with python3 convertLexicon2SFM.py3

# BEST TO IMPORT INTO TEXT AND WORDS - not into the dictionary itself.
#   -Import into Standard Format Words and Glosses

#
#  MODIFIED AND ONLY MILDLY TESTED!!!!  It only includes words that 1.) have glosses
# and 2.) are marked as correctly spelled and 3.) exist in the text.
#

import codecs
from lxml import etree
xmldoc = etree.parse("/home/justin/Desktop/Lexicon.xml")
#Paratext Wordlist Export as XML 
wordlist = etree.parse("/home/justin/Desktop/wordlist.xml")
outfile=codecs.open("/home/justin/Desktop/PT7_dictionary_py3.sfm", mode="w", encoding='utf-8')
outfile.write ("\_sh v3.0  400  MDF\n\_DateStampHasFourDigitYear\n\n")
#correctWords=spellings.getroot().findall("Status")
correctWords=wordlist.getroot().findall("item")
wordlistTotal=len(correctWords)
approvedWords=[]
#for index,word in reversed( list( enumerate(correctWords) ) ) :
for index,word in reversed( list( enumerate(correctWords) ) ) :
  if word.attrib['spelling'] == "Correct" :
  #if word.attrib['State'] == "W" :
    del correctWords[index]
    approvedWords.append(word.attrib['word'])
    #approvedWords.append(word.attrib['Word'])

itemList = xmldoc.getroot().findall("Entries/item")
for item in itemList :
  Lexeme=next( item.iter("Lexeme") )
  if (Lexeme.get('Type') == 'Word') and (Lexeme.get('Form') not in approvedWords) :
    print ( 'unused', Lexeme.get('Type'), Lexeme.get('Form'), "wordlist", wordlistTotal, "incorrect", len(correctWords) )
    continue
  print ( 'good', Lexeme.get('Type'), Lexeme.get('Form'), 'count', approvedWords.count(Lexeme.get('Form')), 'unglossed remaining', len(approvedWords) )
  if approvedWords.count(Lexeme.get('Form')) :
    approvedWords.remove( Lexeme.get('Form') )
  outfile.write ("\n\\lx ")
  if Lexeme.get("Type") == "Suffix" :
    outfile.write ("-")
    #outfile.write ("-", end='')
  outfile.write ("%s" % Lexeme.get("Form"))
  #outfile.write ("%s" % Lexeme.get("Form"), end='')
  if Lexeme.get("Type") == "Prefix" :
    outfile.write ("-")
    #outfile.write ("-", end='')
  outfile.write ("\n")
  outfile.write   ("\\co_Eng %s\n" % next( item.iter("Lexeme") ).get("Type"))
  entryList = item.iter("Gloss")
  sense=1
  for element in entryList :
    if element.get("Language") == "English" :
      outfile.write ("\\sn %s\n" % sense)
      outfile.write ("\\ge %s\n" %  element.text )
      sense+=1
    if element.get("Language") == "Korean" :
      outfile.write ("\\sn %s\n" % sense)
      outfile.write ("\\g_Kor %s\n" %  element.text )
      sense+=1

for Lexeme in approvedWords :
  outfile.write ("\n\\lx %s\n" % Lexeme)
  print ("adding unglossed words", len(approvedWords), Lexeme )

outfile.close()
by (105 points)
Greetings,

I've taken the above Python script to make it general where there will be a prompt for the word list and the lexicon if there is one and output an SMF file to import into FLEx

#!/usr/bin/python3
"""
wordlist_to_sfm.py

Exports a Paratext word list to SFM (Standard Format Markers) format
for import into FLEx (FieldWorks Language Explorer).

Usage:
    python wordlist_to_sfm.py

The script will prompt for:
  - A Paratext word list XML file (e.g. exported from Paratext's word list tool)
  - Optionally, a Paratext Lexicon XML file to supply English glosses
  - An output file path for the resulting .sfm file

Only words marked as "Correct" in the word list are exported by default.
This can be changed by editing the INCLUDE_* settings below.

Output format is MDF (Multi-Dictionary Formatter), the standard SFM dialect used by FLEx for lexicon import.
"""

import codecs
import os
import xml.etree.ElementTree as etree

# --- Settings ----------------------------------------------------------------
# Control which spelling categories are included in the export.
# Set to True to include words in that category, False to exclude them.

INCLUDE_CORRECT   = True   # Words the translator has approved
INCLUDE_UNKNOWN   = False  # Words not yet reviewed
INCLUDE_INCORRECT = False  # Words flagged as misspellings

# Set to True to write the word's corpus frequency as a comment field (\co_count)
INCLUDE_COUNT     = True
# -----------------------------------------------------------------------------

def prompt_path(prompt_text, default):
    """Show a prompt with an optional default value; return the user's input or the default."""
    display = f" [{default}]" if default else ""
    value = input(f"{prompt_text}{display}: ").strip()
return value if value else default

def get_user_inputs():
    """Ask the user for the three file paths needed to run the export."""
    print("Paratext Word List to SFM Exporter")
    print("-----------------------------------\n")

    # Word list is required - keep asking until the file is found
    wordlist_path = prompt_path("Word list file (XML)", "Notsi-WL.xml")
    while not os.path.exists(wordlist_path):
        print(f"  Cannot find '{wordlist_path}'. Please check the path and try again.")
        wordlist_path = prompt_path("Word list file (XML)", "Notsi-WL.xml")

    # Lexicon is optional - skip silently if not found
    lexicon_path = prompt_path("Lexicon file for glosses (press Enter to skip)", "")
    if lexicon_path and not os.path.exists(lexicon_path):
        print(f"  Cannot find '{lexicon_path}'. Continuing without glosses.")
        lexicon_path = ""

    output_path = prompt_path("Output file name", "wordlist_for_flex.sfm")
    return wordlist_path, lexicon_path, output_path

def load_lexicon_glosses(lexicon_path):
    """
    Read English glosses from a Paratext Lexicon XML file.

    Returns a dictionary keyed by lexeme form, where each value contains the lexeme type (Word, Prefix, Suffix) and a list of English gloss strings.
    Returns an empty dictionary if no lexicon path is provided.
    """
    glosses = {}
    if not lexicon_path:
        print("No lexicon provided - entries will be exported without glosses.")
        return glosses

    print(f"Reading lexicon from: {lexicon_path}")
    with open(lexicon_path, "rb") as f:
        data = f.read()
    # Strip any BOM variants (standard UTF-8 BOM, or double-encoded BOM)
    for bom in (b"\xef\xbb\xbf", b"\xc3\xaf\xc2\xbb\xc2\xbf"):
        if data.startswith(bom):
            data = data[len(bom):]
            break
    xmldoc = etree.fromstring(data)

    for item in xmldoc.findall("Entries/item"):
        lexeme = next(item.iter("Lexeme"), None)
        if lexeme is None:
            continue

        form     = lexeme.get("Form", "")
        lex_type = lexeme.get("Type", "Word")  # Word, Prefix, or Suffix

        # Collect all English glosses for this entry (there may be more than one sense)
        entry_glosses = [
            gloss.text
            for gloss in item.iter("Gloss")
            if gloss.get("Language") == "en" and gloss.text
        ]

        if form and entry_glosses:
            glosses[form] = {"type": lex_type, "glosses": entry_glosses}

    print(f"  Found {len(glosses)} entries with English glosses.")
    return glosses

def build_approved_list(wordlist_path):
    """
    Read the Paratext word list XML and return words that pass the INCLUDE_* filters.

    Returns a list of approved word forms and a metadata dictionary containing each word's corpus count, hyphenation, and morphology breakdown.
    """
    wordlist  = etree.parse(wordlist_path)
    all_items = wordlist.getroot().findall("item")
    print(f"Reading word list from: {wordlist_path}")
    print(f"  {len(all_items)} words found in list.")

    approved = []
    metadata = {}

    for item in all_items:
        spelling = item.attrib.get("spelling", "Unknown")
        word     = item.attrib.get("word", "")

        include = (
            (spelling == "Correct"   and INCLUDE_CORRECT)   or
            (spelling == "Unknown"   and INCLUDE_UNKNOWN)   or
            (spelling == "Incorrect" and INCLUDE_INCORRECT)
        )

        if include and word:
            approved.append(word)
            metadata[word] = {
                "count":          item.attrib.get("count", "0"),
                "hyphenation":    item.attrib.get("hyphenation", ""),
                "morphology":     item.attrib.get("morphology", ""),
                "morph_approved": item.attrib.get("morphologyApproved", "False"),
                "specificcase":   item.attrib.get("specificcase", ""),
            }

    print(f"  {len(approved)} words marked as correct and ready to export.")
    return approved, metadata

def write_sfm_entry(outfile, word, lex_type, glosses, meta, include_count):
    """
    Write a single SFM lexicon entry to the output file.

    MDF conventions followed:
      \\lx  - lexeme (headword); affixes get a hyphen on the bound side
      \\sn  - sense number (written once per gloss)
      \\ge  - English gloss for that sense
      \\mr  - morphology string (when approved by the translator)
      \\co_* - comment fields for metadata FLEx doesn't have a standard marker for
    """
    # Write the headword, adding hyphens to show affix attachment points
    outfile.write("\n\\lx ")
    if lex_type == "Suffix":
        outfile.write("-")
    outfile.write(word)
    if lex_type == "Prefix":
        outfile.write("-")
    outfile.write("\n")

    # Record affix type as a comment so it survives the FLEx import
    if lex_type != "Word":
        outfile.write(f"\\co_type {lex_type}\n")

    # Corpus frequency - useful for prioritising entries during dictionary work
if include_count and meta:
        outfile.write(f"\\co_count {meta['count']}\n")

    # Morphology: use the standard \\mr marker if the analysis has been approved,
    # otherwise store it as a comment to avoid importing unverified data
    if meta and meta["morphology"]:
        marker = "\\mr" if meta["morph_approved"] == "True" else "\\co_morph"
        outfile.write(f"{marker} {meta['morphology']}\n")

    # Write one sense block per gloss
    for sense_num, gloss_text in enumerate(glosses, start=1):
        outfile.write(f"\\sn {sense_num}\n")
        outfile.write(f"\\ge {gloss_text}\n")

def main():
    """
    Main export routine.

    Two-pass approach:
      Pass 1 - write entries that have lexicon glosses (richer data first)
      Pass 2 - write remaining approved words that had no lexicon entry
    This ensures glossed entries are not duplicated.
    """
    wordlist_path, lexicon_path, output_path = get_user_inputs()
    print()

    approved_words, metadata = build_approved_list(wordlist_path)
    lexicon_glosses           = load_lexicon_glosses(lexicon_path)

    # Open output file as UTF-8 and write the MDF header
    outfile = codecs.open(output_path, mode="w", encoding="utf-8")
    outfile.write("\\_sh v3.0  400  MDF\n\\_DateStampHasFourDigitYear\n\n")

    remaining    = list(approved_words)  # Words still to be written after pass 1
    with_glosses = 0

    # Pass 1: entries that appear in both the lexicon and the approved word list
    for form, lex_data in lexicon_glosses.items():
        if form not in approved_words:
            print(f"  Skipping '{form}' (in lexicon but not marked as correct in word list)")
            continue
        if form in remaining:
            remaining.remove(form)
        write_sfm_entry(outfile, word=form, lex_type=lex_data["type"],
                        glosses=lex_data["glosses"], meta=metadata.get(form),
                        include_count=INCLUDE_COUNT)
        with_glosses += 1

    # Pass 2: approved words with no lexicon entry - exported without glosses
    for word in remaining:
        write_sfm_entry(outfile, word=word, lex_type="Word", glosses=[],
                        meta=metadata.get(word), include_count=INCLUDE_COUNT)

    total = with_glosses + len(remaining)
    outfile.close()
    print(f"\nExport complete.")
    print(f"  Total entries written : {total}")
    print(f"  With glosses          : {with_glosses}")
    print(f"  Without glosses       : {len(remaining)}")
    print(f"  Output file           : {output_path}")

if __name__ == "__main__":
    main()

Related questions

0 votes
6 answers
Paratext Oct 12, 2021 asked by bbryson (105 points)
0 votes
3 answers
Paratext Nov 15, 2017 asked by SIL LSS PNG (411 points)
0 votes
1 answer
Welcome to Support Bible, where you can ask questions and receive answers from other members of the community.
I urge you, brothers and sisters, to watch out for those who cause divisions and put obstacles in your way that are contrary to the teaching you have learned. Keep away from them.
Romans 16:17
3,032 questions
5,987 answers
5,653 comments
2,019 users