#!/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 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()