4 changed files with 417 additions and 0 deletions
@ -0,0 +1,247 @@ |
|||||
|
import os |
||||
|
import re # find and replace |
||||
|
import shutil # move tmp file for overwrite |
||||
|
|
||||
|
cantoDir = os.path.abspath(os.getcwd()) |
||||
|
|
||||
|
def getXMLFileName(xmlCantoFile): |
||||
|
xmlCanto = xmlCantoFile.split('.')[0] + '.xml' |
||||
|
return xmlCanto |
||||
|
|
||||
|
def addWordTags(cantoFile): |
||||
|
""" |
||||
|
- [x] Parse filename to create xml file |
||||
|
- [x] Open file as one huge string (keep track of newlines) |
||||
|
- [x] Replace every space with a dummy char not used in the text, like `~` |
||||
|
- [ ] Traverse the big file-string from beginning to end |
||||
|
- [x] Define a "word-chars" group: letters, apostrofes |
||||
|
- [x] If you encounter a char that's a word-char |
||||
|
put a `<w>` in front of it and move to the next. |
||||
|
- [x] If the next char is also a word-char, move on |
||||
|
to the next because we're still in a word. |
||||
|
- [x] If the next char is not a word-char, |
||||
|
put a `</w>` in front of it and move to the next. |
||||
|
- [x] Replace all `~` with spaces and get back the newlines |
||||
|
- [x] Write to XML file |
||||
|
""" |
||||
|
xmlCanto = getXMLFileName(cantoFile) |
||||
|
with open(xmlCanto, 'w') as xmlCanto: |
||||
|
with open(cantoFile, 'r') as txtCanto: |
||||
|
originalText = txtCanto.read() |
||||
|
# Prepare canto for traversing |
||||
|
plainText = traversePreparation(originalText, '\n', '+', False) |
||||
|
plainText = traversePreparation(plainText, ' ', '~', False) |
||||
|
|
||||
|
# Define variables |
||||
|
wordChars = '[a-zA-ZÀ-ÿ0-9‘’]' |
||||
|
openWord = '<w>' |
||||
|
closeWord = '</w>' |
||||
|
insideWord = False |
||||
|
|
||||
|
# Traverse canto |
||||
|
for char in plainText: |
||||
|
if re.match(wordChars, char): |
||||
|
if insideWord == False: |
||||
|
xmlCanto.write(openWord) |
||||
|
xmlCanto.write(char) |
||||
|
insideWord = True |
||||
|
else: |
||||
|
xmlCanto.write(char) |
||||
|
else: |
||||
|
if char == '~': |
||||
|
if insideWord == True: |
||||
|
xmlCanto.write(closeWord + ' ') |
||||
|
insideWord = False |
||||
|
else: |
||||
|
xmlCanto.write(' ') |
||||
|
elif char == '+': |
||||
|
if insideWord == True: |
||||
|
xmlCanto.write(closeWord + '\n') # line ends with word |
||||
|
insideWord = False |
||||
|
else: |
||||
|
xmlCanto.write('\n') # line ends with punctuation |
||||
|
elif insideWord == True: |
||||
|
xmlCanto.write(closeWord + char) # punctuation right after to word |
||||
|
insideWord = False |
||||
|
else: |
||||
|
xmlCanto.write(char) # punctuation after punctuation |
||||
|
txtCanto.close() |
||||
|
xmlCanto.close() |
||||
|
|
||||
|
def traversePreparation(cantoText, find, replace, negate): |
||||
|
""" |
||||
|
Substitute find with replace. |
||||
|
If negate is True, substitute replace with find. |
||||
|
""" |
||||
|
if negate == False: |
||||
|
replacedText = cantoText.replace(find, replace) |
||||
|
else: |
||||
|
replacedText = cantoText.replace(replace, find) |
||||
|
return replacedText |
||||
|
|
||||
|
def addLineTags(xmlCantoFile): |
||||
|
""" |
||||
|
add <l l_num="n"> to the beginning of each line number n |
||||
|
add </l> to the end of each line |
||||
|
""" |
||||
|
xmlCanto = getXMLFileName(xmlCantoFile) |
||||
|
tmpFile = '.' + xmlCantoFile + '.tmp' |
||||
|
with open(xmlCanto, 'r') as xmlCanto: |
||||
|
with open(tmpFile, 'w') as tmpCanto: |
||||
|
i = 1 |
||||
|
for line in xmlCanto: |
||||
|
# replace '\n' in line with ' </l>\n' |
||||
|
newLine = re.sub('\n', ' </l>\n', line) |
||||
|
newLine = '<l l_num="' + str(i) + '"> ' + newLine |
||||
|
i += 1 |
||||
|
tmpCanto.write(newLine) |
||||
|
closeCopyRemoveTmp(tmpFile, tmpCanto, xmlCantoFile, xmlCanto) |
||||
|
|
||||
|
def addTercetTags(xmlCantoFile): |
||||
|
""" |
||||
|
add <tercet t_num="n"> ... </tercet> around each group of three lines until the end |
||||
|
""" |
||||
|
xmlCanto = getXMLFileName(xmlCantoFile) |
||||
|
tmpFile = '.' + xmlCantoFile + '.tmp' |
||||
|
with open(xmlCanto, 'r') as xmlCanto: |
||||
|
lastLine = xmlCanto.readlines()[-1] |
||||
|
lastLineNum = int(lastLine.split('"')[1]) |
||||
|
xmlCanto.seek(0) |
||||
|
with open(tmpFile, 'w') as tmpCanto: |
||||
|
i = 0 |
||||
|
j = 2 |
||||
|
for line in xmlCanto: |
||||
|
#print(line) |
||||
|
i += 1 |
||||
|
#print('new i: ' + str(i)) |
||||
|
if i == 1: |
||||
|
newLine = '<tercet t_num="1">\n' + line |
||||
|
tmpCanto.write(newLine) |
||||
|
#print('first line newline: ' + newLine) |
||||
|
elif i % 3 == 0: |
||||
|
tercetNum = str(j) |
||||
|
newLineSub = '\n</tercet>\n<tercet t_num="' + tercetNum + '">\n' |
||||
|
newLine = re.sub('\n', newLineSub, line) |
||||
|
tmpCanto.write(newLine) |
||||
|
j += 1 |
||||
|
#print('every 3 lines newline: ' + newLine) |
||||
|
elif i == lastLineNum: |
||||
|
newLine = re.sub('\n', '\n</tercet>', line) |
||||
|
tmpCanto.write(newLine) |
||||
|
#print('last line newline: ' + newLine) |
||||
|
else: |
||||
|
tmpCanto.write(line) |
||||
|
closeCopyRemoveTmp(tmpFile, tmpCanto, xmlCantoFile, xmlCanto) |
||||
|
|
||||
|
def wrapTags(tag, xmlCantoFile): |
||||
|
""" |
||||
|
used to add <tercets>, <canto>, and <text> tags around <canto> body |
||||
|
""" |
||||
|
xmlCanto = getXMLFileName(xmlCantoFile) |
||||
|
if tag == 'TEI': |
||||
|
openTag = '<TEI xmlns="http://www.tei-c.org/ns/1.0">\n' |
||||
|
else: |
||||
|
openTag = '<' + tag + '>\n' |
||||
|
closeTag = '\n</' + tag + '>' |
||||
|
with open(xmlCanto, 'r') as xmlCantoContents: |
||||
|
contents = xmlCantoContents.read() |
||||
|
with open(xmlCanto, 'w') as xmlCantoContents: |
||||
|
xmlCantoContents.write(openTag) |
||||
|
xmlCantoContents.write(contents) |
||||
|
xmlCantoContents.write(closeTag) |
||||
|
xmlCantoContents.close() |
||||
|
|
||||
|
def addTEIHeaderTags(xmlCantoFile): |
||||
|
""" |
||||
|
The following is the minimum required TEI header |
||||
|
<TEI xmlns="http://www.tei-c.org/ns/1.0"> |
||||
|
<fileDesc> |
||||
|
<titleStmt> |
||||
|
<title> |
||||
|
</title> |
||||
|
<author> |
||||
|
</author> |
||||
|
<respStmt> |
||||
|
</respStmt> |
||||
|
</titleStmt> |
||||
|
<publicationStmt> |
||||
|
<publisher> |
||||
|
</publisher> |
||||
|
</publicationStmt> |
||||
|
<sourceDesc> |
||||
|
<citation> |
||||
|
</citation> |
||||
|
</sourceDesc> |
||||
|
</fileDesc> |
||||
|
<text> |
||||
|
""" |
||||
|
xmlCanto = getXMLFileName(xmlCantoFile) |
||||
|
|
||||
|
def addSpaces(xmlCantoFile): |
||||
|
""" |
||||
|
Add a given number of spaces before each opening and closing tag. |
||||
|
User specifies the number of spaces only once, in a series of lists |
||||
|
which hold strings of each tag that needs spaces before it. This |
||||
|
function parses the names of the list to determine the number of |
||||
|
spaces to add before each tag (e.g. list2 => 2 spaces). It replaces |
||||
|
the tags in the document with the spaces affixed. |
||||
|
""" |
||||
|
# XML is hierarchical, so each "level" needs its own number of spaces |
||||
|
levelDict = { |
||||
|
'0' : ['TEI'], |
||||
|
'1' : ['teiHeader', 'text' ], |
||||
|
'2' : ['fileDesc' , 'canto' ], |
||||
|
'3' : ['titleStmt', 'tercets'], |
||||
|
'4' : [ 'tercet' ], |
||||
|
'5' : [ 'l' ] |
||||
|
} |
||||
|
|
||||
|
#levelDictLen = int(sorted(levelDict.keys())[-1]) + 1 # number of levels. add 1 to make range() work below |
||||
|
tags = ['TEI', 'teiHeader', 'text', 'fileDesc', 'canto', 'titleStmt', 'tercets', 'tercet', 'l' ] |
||||
|
|
||||
|
xmlCanto = getXMLFileName(xmlCantoFile) |
||||
|
tmpFile = '.' + xmlCantoFile + '.tmp' |
||||
|
|
||||
|
with open(xmlCantoFile, 'r') as xmlCanto: |
||||
|
with open(tmpFile, 'w') as tmpCanto: |
||||
|
for line in xmlCanto: |
||||
|
for tag in tags: |
||||
|
# need open tag regex b/c we don't know if tag has attributes, |
||||
|
# and if we did, we might not know their value |
||||
|
openTagRegex = '(<' + tag + ')(>|\s.*>)' # match <tag> and <tag attr="value"> but not <tags> |
||||
|
closeTag = '</' + tag + '>' |
||||
|
for level in levelDict.keys(): |
||||
|
if tag in levelDict[level]: |
||||
|
spaces = ' ' * int(level) |
||||
|
if re.search(openTagRegex, line): |
||||
|
newLine = re.sub(openTagRegex, spaces + r"\1\2", line) |
||||
|
tmpCanto.write(newLine) |
||||
|
elif closeTag in line: |
||||
|
if tag != 'l': # don't add spaces to closing line tags |
||||
|
newLine = re.sub(closeTag, spaces + closeTag, line) |
||||
|
tmpCanto.write(newLine) |
||||
|
closeCopyRemoveTmp(tmpFile, tmpCanto, xmlCantoFile, xmlCanto) |
||||
|
|
||||
|
def closeCopyRemoveTmp(tmpFile, tmpCanto, xmlCantoFile, xmlCanto): |
||||
|
tmpCanto.close() |
||||
|
xmlCanto.close() |
||||
|
shutil.copy(tmpFile, xmlCantoFile) |
||||
|
os.remove(tmpFile) |
||||
|
|
||||
|
def main(): |
||||
|
for cantoFile in os.listdir(cantoDir): |
||||
|
xmlCantoName = getXMLFileName(cantoFile) |
||||
|
if re.search('\.txt$', cantoFile): |
||||
|
addWordTags(cantoFile) |
||||
|
addLineTags(xmlCantoName) |
||||
|
addTercetTags(xmlCantoName) |
||||
|
wrapTags('tercets', xmlCantoName) |
||||
|
wrapTags('canto', xmlCantoName) |
||||
|
wrapTags('text', xmlCantoName) |
||||
|
#addTEIHeaderTags() |
||||
|
wrapTags('TEI', xmlCantoName) |
||||
|
addSpaces(xmlCantoName) |
||||
|
os.remove(cantoFile) # don't delete txt files while debugging so you don't have to keep copying them |
||||
|
|
||||
|
|
||||
|
main() |
||||
@ -0,0 +1,24 @@ |
|||||
|
import shutil |
||||
|
import os |
||||
|
import re |
||||
|
|
||||
|
cantoDir = os.path.abspath(os.getcwd()) |
||||
|
|
||||
|
def main(): |
||||
|
""" |
||||
|
Take txt file from `dante-visualized` project and remove blank lines |
||||
|
""" |
||||
|
for txtFile in os.listdir(cantoDir): |
||||
|
if re.search('\.txt$', txtFile): |
||||
|
tmpFile = '.' + txtFile+ '.tmp' |
||||
|
with open(txtFile, 'r') as txt: |
||||
|
with open(tmpFile, 'w') as tmp: |
||||
|
for line in txt: |
||||
|
if line.rstrip(): |
||||
|
tmp.write(line) |
||||
|
tmp.close() |
||||
|
txt.close() |
||||
|
shutil.copy(tmpFile, txtFile) |
||||
|
os.remove(tmpFile) |
||||
|
|
||||
|
main() |
||||
@ -0,0 +1,22 @@ |
|||||
|
import re |
||||
|
import os |
||||
|
|
||||
|
canto_dir = os.path.abspath(os.getcwd()) |
||||
|
canto_files = os.listdir(canto_dir) |
||||
|
|
||||
|
def rename_canto(canto): |
||||
|
""" |
||||
|
Take txt canto files from `dante-visualized` and change the name styling |
||||
|
""" |
||||
|
# parse canto name |
||||
|
# put it back together |
||||
|
print('canto name: ' + canto) |
||||
|
new_canto = re.sub('\_','-',canto) |
||||
|
new_canto = re.sub('Canto[IVX]*-','',new_canto) |
||||
|
new_canto = new_canto.lower() |
||||
|
print('--> new canto name: ' + new_canto) |
||||
|
os.rename(canto,new_canto) |
||||
|
|
||||
|
for canto in canto_files: |
||||
|
if canto != 'rename-txt.py' and canto != '.rename-txt.py.swp': |
||||
|
rename_canto(canto) |
||||
@ -0,0 +1,124 @@ |
|||||
|
import re |
||||
|
import os |
||||
|
import xml.etree.ElementTree as ET |
||||
|
|
||||
|
""" |
||||
|
This script takes xml canto files and counts the number of unique words as |
||||
|
defined by the value of `<w>` nodes. |
||||
|
|
||||
|
8 files are generated: csv and html of |
||||
|
- sorted numerically and alphabetically |
||||
|
- sorted numerically and alphabetically with blacklist applied |
||||
|
""" |
||||
|
|
||||
|
cantoDir = os.path.abspath(os.getcwd()) |
||||
|
|
||||
|
blacklist = ('e', 'che', 'la', 'di', 'a', 'per', '’l', 'mi', 'tu', 'con', |
||||
|
'lo', 'sì', 'sua', 'li', 'del', 'si', 'nel', 'era', 'ch’i’', 'è', |
||||
|
'là', 'le', 'de', 'quella', 'poi', 'ma', 'il', 'già', '’n', 'una', |
||||
|
'tanto', 'se’', 'quel', 'qual', 'perché', 'in', 'come', 'al', 'da', |
||||
|
'un', 'i', 'ti', 'a', 'per', 'con', 'lo', 'al', 'poi', 'E', 'il') |
||||
|
|
||||
|
def createWordCountDict(cantoFile): |
||||
|
""" |
||||
|
for node in xpathQueryAllWordNodes: |
||||
|
if xpathValue(node) in wordCountDict(keys): |
||||
|
wordCountDict[node] += 1 |
||||
|
else: |
||||
|
wordCountDict[xpathValue(node)] = 1 |
||||
|
""" |
||||
|
wordCountDict = dict() |
||||
|
tree = ET.parse(cantoFile) |
||||
|
root = tree.getroot() |
||||
|
# TODO |
||||
|
words = root.findall(".//{http://www.tei-c.org/ns/1.0}w") |
||||
|
for word in words: |
||||
|
if word.text in wordCountDict: |
||||
|
wordCountDict[word.text] += 1 |
||||
|
else: |
||||
|
wordCountDict[word.text] = 1 |
||||
|
return wordCountDict |
||||
|
|
||||
|
def numSort(wordCountDict): |
||||
|
# Need reverse = True to sort greatest to least |
||||
|
wordCountDictNumSorted = dict(sorted(wordCountDict.items(), key=lambda item: item[1], reverse = True)) |
||||
|
return wordCountDictNumSorted |
||||
|
|
||||
|
def alphaSort(wordCountDict): |
||||
|
# Capital letters come first with this implementation |
||||
|
wordCountDictAlphaSorted = dict(sorted(wordCountDict.items())) |
||||
|
return wordCountDictAlphaSorted |
||||
|
|
||||
|
def blackSort(wordCountDictSorted): |
||||
|
sortedCopy = dict(wordCountDictSorted) |
||||
|
for word in wordCountDictSorted: |
||||
|
if word in blacklist: |
||||
|
del sortedCopy[word] |
||||
|
return sortedCopy |
||||
|
|
||||
|
def dictToCsv(sortedDict, sortType, cantoFile): |
||||
|
""" |
||||
|
write a sorted dict to a CSV file for that canto |
||||
|
name it based on sorting type (numerical or alphabetical) |
||||
|
abbreviate sortType as `num` or `alpha` |
||||
|
""" |
||||
|
csvCanto = cantoFile.split('.')[0] + '-' + sortType + '.csv' |
||||
|
cantoNum = str(int(cantoFile.split('-')[0])) |
||||
|
canticle = cantoFile.split('-')[1] |
||||
|
|
||||
|
with open(csvCanto, 'w') as csvCanto: |
||||
|
cantoInformation = canticle + ',' + cantoNum + '\n' |
||||
|
csvCanto.write(cantoInformation) |
||||
|
for word, count in sortedDict.items(): |
||||
|
csvInput = word + ',' + str(count) + '\n' |
||||
|
csvCanto.write(csvInput) |
||||
|
csvCanto.close() |
||||
|
|
||||
|
def dictToHtml(sortedDict, sortType, cantoFile): |
||||
|
htmlCanto = cantoFile.split('.')[0] + '-' + sortType + '.html' |
||||
|
with open(htmlCanto, 'w') as htmlCanto: |
||||
|
cantoNum = str(int(cantoFile.split('-')[0])) |
||||
|
canticle = cantoFile.split('-')[1].title() |
||||
|
if sortType == 'num': |
||||
|
sortInformation = 'Words sorted by most to least occurrences.' |
||||
|
elif sortType == 'num-black': |
||||
|
sortInformation = 'Words sorted by most to least occurrences, with blacklist applied.' |
||||
|
elif sortType == 'alpha': |
||||
|
sortInformation = 'Words sorted alphabetically.' |
||||
|
elif sortType == 'alpha-black': |
||||
|
sortInformation = 'Words sorted alphabetically, with blacklist applied.' |
||||
|
cantoHeaders = '<h1>' + canticle + ' ' + cantoNum + '</h1>\n<h3>' + sortInformation + '</h3>\n' |
||||
|
boilerplate = '<html>\n' + cantoHeaders + '<table>\n<tr>\n<th>Word</th><th>Count</th>\n</tr>\n' |
||||
|
boilerclose = '</table>\n</html>' |
||||
|
htmlCanto.write(boilerplate) |
||||
|
for word, count in sortedDict.items(): |
||||
|
htmlInput = '<tr>\n<td>' + word + '</td><td>' + str(count) + '</td>\n</tr>\n' |
||||
|
htmlCanto.write(htmlInput) |
||||
|
htmlCanto.write(boilerclose) |
||||
|
htmlCanto.close() |
||||
|
|
||||
|
def main(): |
||||
|
for canto in os.listdir(cantoDir): |
||||
|
if re.search('\.xml$', canto): |
||||
|
cantoFile = canto |
||||
|
wordCountDict = createWordCountDict(cantoFile) |
||||
|
|
||||
|
# create sorted dicts |
||||
|
wordCountDictNumSorted = numSort(wordCountDict) |
||||
|
wordCountDictNumSortedBlack = blackSort(wordCountDictNumSorted) |
||||
|
wordCountDictAlphaSorted = alphaSort(wordCountDict) |
||||
|
wordCountDictAlphaSortedBlack = blackSort(wordCountDictAlphaSorted) |
||||
|
|
||||
|
# create output files |
||||
|
dictToCsv(wordCountDictNumSorted, 'num', cantoFile) # `num-full`: sort numerically with no blacklist |
||||
|
dictToCsv(wordCountDictNumSortedBlack, 'num-black', cantoFile) # `num`: sort numerically with blacklist |
||||
|
dictToCsv(wordCountDictAlphaSorted, 'alpha', cantoFile) |
||||
|
dictToCsv(wordCountDictAlphaSortedBlack, 'alpha-black', cantoFile) |
||||
|
|
||||
|
dictToHtml(wordCountDictNumSorted, 'num', cantoFile) |
||||
|
dictToHtml(wordCountDictNumSortedBlack, 'num-black', cantoFile) |
||||
|
dictToHtml(wordCountDictAlphaSorted, 'alpha', cantoFile) |
||||
|
dictToHtml(wordCountDictAlphaSortedBlack, 'alpha-black', cantoFile) |
||||
|
|
||||
|
|
||||
|
main() |
||||
Loading…
Reference in new issue