265 lines
8.4 KiB
Python
265 lines
8.4 KiB
Python
#!/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
|
|
from xml.etree import ElementTree
|
|
from docx import Document
|
|
from demo import document
|
|
|
|
|
|
|
|
#-----------------------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
|
|
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")
|
|
|
|
|
|
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))
|
|
)
|
|
|
|
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))
|
|
)
|
|
|
|
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
|
|
)
|
|
|
|
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 = {}
|
|
#
|
|
print("Extracting Question Definitions...")
|
|
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')
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
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
|
|
|
|
def __err(text):
|
|
print('---FAILURE----------------------')
|
|
print(text)
|
|
print('abort.')
|
|
sys.exit(255)
|
|
|
|
|
|
|
|
|
|
if __name__=='__main__':
|
|
convertSosci2Word()
|