wegen/sosci2word/sosci2word.py

266 lines
8.4 KiB
Python
Raw Normal View History

2023-05-19 20:41:19 +02:00
#!/usr/bin/python3
#
# sosci2word - Einlesen eines SoSci-Fragebogens und umwandeln in ein Word-Dokument
#
# (c) 2023 Hermann Voßeler
# This code is free software and can be used under the terms of the MIT License.
#
import sys
2023-05-19 20:41:19 +02:00
from xml.etree import ElementTree
2023-05-20 00:16:05 +02:00
from docx import Document
from demo import document
2023-05-19 20:41:19 +02:00
#-----------------------SETUP
SOSCI_XML='ThesisFragebogen.2023-04-19.xml'
WORD_TEMPLATE='Thesis-Fragebogen.docx'
WORD_OUTPUT='Thesis-Fragebogen-import.docx'
#-----------------------SETUP
def convertSosci2Word():
# Schritt-1 : alle Frage-Definitionen aufsammeln
questions = collectQuestionDefinitions()
# Schritt-2 : die Fragen in der Reihenfolge vom Questionaire
questionaire = buildQuestionaire(questions)
## TODO
2023-05-20 00:16:05 +02:00
renderContentToWord(questionaire)
## TODO
class QuestionElement:
def __init__(self, questionID, question):
self.qid = questionID
self.title = question.find('title').text
self.desc = question.find('description').text
def dumpBasis(self, type):
return "##--%s--##\ndes=%s\ntitle=%s" % (type, self.desc, self.title)
def dump(self):
return self.dumpBasis("Basis")
2023-05-20 00:16:05 +02:00
def render(self, document):
pass # macht nichts in der Basisklasse
class LikertItem(QuestionElement):
def __init__(self, questionID, question):
QuestionElement.__init__(self, questionID, question)
self.labels = []
for entry in question.find('labels').findall('li'):
code = int(entry.get('i'))
text = entry.text
self.labels.append((code,text))
self.items = []
for item in question.findall('item'):
code = int(getSpecificID(item))
text = item.find('text').text
self.items.append((code,text))
def dump(self):
return ( self.dumpBasis('Likert')
+ "\nLabels: " + str(list("(%d,%s)" % (c,t) for c,t in self.labels))
+ "\nItems: " + str(list("(%d,%s)" % (c,t) for c,t in self.items))
)
2023-05-20 00:16:05 +02:00
def render(self, document):
questionText = self.desc + ": " + self.title
document.add_paragraph(questionText, style='Fragebogen-Item')
#
# Tabelle mit den Antwortmöglichkeiten
cntLabels = len(self.labels)
cntItems = len(self.items)
table = document.add_table(rows=cntItems+1, cols=cntLabels+1)
# gesammte Tabelle mit dem korrekten Style auszeichnen
for r in table.rows:
for c in r.cells:
c.paragraphs[0].style = 'Frabebogen-Wert'
# alle Inhalts-Spalten ausfüllen
# in die Kopfzeile kommt das richtige Label
# in die Inhaltsfelder ein Symbol für das Antwort-Kästchen
for c in range(1, cntLabels+1):
table.cell(0,c).paragraphs[0].text = self.labels[c-1][1]
for r in range(1, cntItems+1):
table.cell(r,c).paragraphs[0].text = '□'
#
for r in range(1, cntItems+1):
table.cell(r,0).paragraphs[0].text = self.items[r-1][1]
class SelectItem(QuestionElement):
def __init__(self, questionID, question):
QuestionElement.__init__(self, questionID, question)
self.items = []
for item in question.findall('item'):
code = int(getSpecificID(item))
text = item.find('text').text
self.items.append((code,text))
def dump(self):
return ( self.dumpBasis('Select')
+ "\nItems: " + str(list("(%d,%s)" % (c,t) for c,t in self.items))
)
2023-05-20 00:16:05 +02:00
def render(self, document):
questionText = self.title
document.add_paragraph(questionText, style='Fragebogen-Item')
#
# Tabelle mit den Antwortmöglichkeiten
cntItems = len(self.items)
table = document.add_table(rows=cntItems, cols=2)
# gesammte Tabelle mit dem korrekten Style auszeichnen
for r in table.rows:
for c in r.cells:
c.paragraphs[0].style = 'Frabebogen-Wert'
# alle Inhalts-Spalten ausfüllen
# in die Inhaltsfelder links der Fragetext, rechts der Code-Wert
for r in range(0, cntItems):
table.cell(r,0).paragraphs[0].text = self.items[r][1] # text
table.cell(r,1).paragraphs[0].text = str(self.items[r][0]) # code
class TextElement(QuestionElement):
def __init__(self, questionID, question):
QuestionElement.__init__(self, questionID, question)
lead = question.find('lead')
if lead == None:
self.text = ""
else:
self.text = lead.text
def dump(self):
return ( self.dumpBasis('Text')
+ "\nText: " + self.text
)
2023-05-20 00:16:05 +02:00
def render(self, document):
document.add_paragraph(self.desc, style='Überschrift-Anhang 3')
if self.text != None:
document.add_paragraph(self.text, style='Body Text')
def collectQuestionDefinitions():
#
questions = {}
2023-05-19 20:41:19 +02:00
#
print("Extracting Question Definitions...")
2023-05-19 20:41:19 +02:00
root = ElementTree.parse(SOSCI_XML).getroot()
for section in root.findall('section'):
id_prefix = getSpecificID(section)
for question in section.findall('question'):
questionType = question.find('class').text
id_suffix = getSpecificID(question)
id_suffix = int(id_suffix)
questionID = "%s%02d" % (id_prefix, id_suffix)
if questionType == 'scale':
element = LikertItem(questionID, question)
elif questionType == 'select' or questionType == 'dropdown':
element = SelectItem(questionID, question)
elif questionType == 'TextContent':
element = TextElement(questionID, question)
elif questionType == 'checkbox':
pass # ignorieren wir
else:
__err("unbekannter QuestionType:"+questionType)
questions[questionID] = element
# Rückgabe: ein Dictionary mit allen bekannten Question-Elementen (keyed by ID)
return questions
# Hilfsmittel für SoSci-XML: extrahiert ein ID-Element, das eingeschachtelt in <attributes.specific> liegt
def getSpecificID(element):
attribspecific = element.find('attributes.specific')
return next(attr.text for attr in attribspecific.findall('attr') if attr.get('id') == 'id')
2023-05-19 20:41:19 +02:00
def buildQuestionaire(questions):
#
print("Building Questionaire Pages...")
#
root = ElementTree.parse(SOSCI_XML).getroot()
quest = root.find('questionnaire').find('attributes.specific')
quest = next(a for a in quest.findall('attr') if a.get('id') == 'content')
quest = quest.text
questionaire = []
questRoot = ElementTree.fromstring(quest)
for page in questRoot.findall('page'):
if page.get('ident') == 'I':
continue # die erste Seite enthält den Einführungstext. Den fügen wir später manuell ein
#
info = page.find('info').text
questionsOnThisPage = []
for quest in page.findall('question'):
questionID = quest.get('id')
element = questions[questionID]
questionsOnThisPage.append(element)
questionaire.append((info, questionsOnThisPage))
#
return questionaire
2023-05-19 20:41:19 +02:00
2023-05-20 00:16:05 +02:00
def renderContentToWord(questionaire):
#
print('Integrate content in Word file "%s"....' % WORD_TEMPLATE)
document = Document('Thesis-Fragebogen.docx')
#
renderQuestionaireTitle(document)
for pageInfo, elements in questionaire:
renderPageTitle(document, pageInfo)
for element in elements:
print(element.dump())
element.render(document)
print('Writing modified Word file as "%s"....' % WORD_OUTPUT)
document.save(WORD_OUTPUT)
#
# erzeugt ein neues Hauptkapitel im Anhang für den Fragebogen
def renderQuestionaireTitle(document):
document.add_paragraph('Fragebogen (TODO Import)', style='Überschrift-Anhang 1')
# erzeugt einen Abschnitt im Anhang für die jeweilige Seite des Fragebogens
def renderPageTitle(document, title):
document.add_paragraph(title, style='Überschrift-Anhang 2')
# ---- Hilfsfunktionen
2023-05-19 20:41:19 +02:00
def __err(text):
print('---FAILURE----------------------')
print(text)
print('abort.')
sys.exit(255)
2023-05-19 20:41:19 +02:00
if __name__=='__main__':
convertSosci2Word()