diff --git a/texla/PageTree/Babel.py b/texla/PageTree/Babel.py index 927e5d4..e12dc9c 100644 --- a/texla/PageTree/Babel.py +++ b/texla/PageTree/Babel.py @@ -1,82 +1,81 @@ import logging class Babel: #Label babel def __init__(self): self.refs = {} self.anchors = {} def add_label(self, label, anchor): ''' This method adds a label and its anchor to the babel. The label is unique but can be overwritten. ''' if label in self.anchors: - logging.warn("Babel @ label: {} already present".format(label)) + logging.warning("Babel @ label: {} already present".format(label)) #saving the anchor that has to be unique self.anchors[label] = anchor logging.info("Babel @ Adding label: \"{}\" to anchor: {}". format(label, anchor)) def add_reference(self, label, ref): ''' This method adds a reference to the label. A reference is a page or in general an object with .text properties. The babel will fix the reference of the registered objects. ''' #we don't check if label exist because #the process is asynchronous" if label not in self.refs: self.refs[label] = [] self.refs[label].append(ref) logging.info("Babel @ Adding ref: {}, to label: \"{}\"". format(ref, label)) def move_anchor(self, oldanc, newanc): '''This function replace the references to oldanc with newanc, both as anchor and ref. It is used mainly when a page is moved''' new_anchors = {} for label, anc in self.anchors.items(): if anc == oldanc: new_anchors[label] = newanc self.anchors.update(new_anchors) new_refs = {} for label, ref in self.refs.items(): if ref == oldanc: new_refs[label] = newanc self.refs.update(new_refs) def fix_references(self): ''' This method will fix the reference in the objects saved under self.refs. The text {{ref:label}} in the objects' .text properties will be replaces by a url made of [url|title]. The url and the title MUST be properties of the anchor saved. ''' #iterating over anchor to fix only the right labels #and ignoring the missing ones for label in self.anchors: obj = self.anchors[label] title = obj.title url = obj.url - replace_string = "" - if url == None and title == None: + if url is None and title is None: continue - elif url == None and title != None: + elif url is None and title is not None: replace_string = title - elif url != None and title == None: + elif url is not None and title is None: replace_string = "[["+ url + "]]" else: replace_string = "[[{}|{}]]".format(url,title) #checking if the babel has refs if label not in self.refs: continue #iterating over all refs for ref in self.refs[label]: logging.info("Babel @ Fixing ref to label: \"{}\", in page: {}". format(label,ref.title)) logging.debug("From page: {}, to page: {}".format(ref, obj)) ref.text = ref.text.replace("{{ref:"+ label +"}}", replace_string) diff --git a/texla/PageTree/Page.py b/texla/PageTree/Page.py index 3927dfb..d2aa2a4 100644 --- a/texla/PageTree/Page.py +++ b/texla/PageTree/Page.py @@ -1,218 +1,218 @@ # -*- coding: utf-8 -*- import re import logging from ..Parser.Blocks.Utilities import * class Page(): - ''' Class that manages the pages content. + """ Class that manages the pages content. Data members of Page -self.id = randon id of the page -self.title is the title normalized for urls -self.subpages contains the list of the subpages objects -self.level memorize the level of the page.(root=-1)) -self.url contains the unique internal url of the page -self.type is 'root',part,chapter,section,subsection,subsubection,paragraph. - -self.keywords is a dictionary with localized keywords for output''' + -self.keywords is a dictionary with localized keywords for output""" def __init__(self,title, page_type, level, keywords ): self.id = utility.get_random_string(8) self.title = title self.type = page_type self.keywords = keywords #contains the page text self.text = '' self.collapsed = False #list of subpages objects self.subpages = [] self.parent = None self.level = level def addText(self,text): self.text = text.strip() def addSubpage(self, page, after=None): - '''This methods add a subpage + """This methods add a subpage refreshing the levels of ALL subpages. - It sets also the parent of the subpage''' + It sets also the parent of the subpage""" if after: i = self.subpages.index(after) self.subpages.insert(i+1, page) else: self.subpages.append(page) #setting level page.refresh_level(self.level+1) #setting parent page.parent = self def addSubpages(self, pages, after=None): - '''This function add a list of subpages, - setting levels and parent page.''' + """This function add a list of subpages, + setting levels and parent page.""" if after: i = self.subpages.index(after) +1 self.subpages = self.subpages[:i] + pages +\ self.subpages[i:] else: self.subpages += pages #setting level and parent of subpages for p in pages: p.refresh_level(self.level+1) p.parent = self def addSubpage_top(self, page): - '''This function add a subpage before all the others.''' + """This function add a subpage before all the others.""" self.subpages.insert(0,page) page.parent = self def removeSubpage(self, page): - '''This function removes the subpage - recursively looking also at subpages''' + """This function removes the subpage + recursively looking also at subpages""" if page in self.subpages: self.subpages.remove(page) page.parent = None else: for p in self.subpages: p.removeSubpage(page) def isSubpage(self,page): return page in self.subpages def get_subpages(self): - '''This function returns a list with all the subpages + """This function returns a list with all the subpages of the current page walking the subtree KEEPING THE ORDER (REALLY IMPORTANT): -subpage: --subsub1 --subsub2: ---subsubsub -subpage2... - => [subpage, subsub1, subsub2, subsubsub, subpage2, ...]''' + => [subpage, subsub1, subsub2, subsubsub, subpage2, ...]""" subpag = [] for p in self.subpages: subpag.append(p) subpag += p.get_subpages() return subpag def refresh_level(self, new_level): - '''This function change the level of this page - refreshing recursively all the subpages''' + """This function change the level of this page + refreshing recursively all the subpages""" self.level = new_level for p in self.subpages: p.refresh_level(self.level+1) def after_render(self): - '''This function does some fixes after rendering''' + """This function does some fixes after rendering""" #first of all the text is fixed self.fix_text_characters() #check math inside titles self.math_inside_title = self.is_math_inside_title() def collapseSubpagesText(self, level=0): - ''' This method insert the text of subpages in this - page and returns the complete text.''' + """ This method insert the text of subpages in this + page and returns the complete text.""" for subpage in self.subpages: t = subpage.collapseSubpagesText(level+1) #add text self.text+= '\n'+t if level == 0: if '' in self.text: #added refs tags to show footnotes self.text+='\n{{Notes}}' else: #Creation of current page title tit = '\n'+'='+'='*(level)+self.title+ \ '='*(level)+'=' self.text = tit+ "\n"+ self.text #return the text return self.text def collapseURL(self, base_url): - '''This functions creates the url of the page + """This functions creates the url of the page checking if it is collapsed. Then it continues - with the subpages''' + with the subpages""" if self.collapsed: self.url = base_url + '#' + self.title for p in self.subpages: p.collapseURL(base_url) else: if base_url != '': self.url = base_url + '/' + self.title else: self.url = self.title for p in self.subpages: p.collapseURL(self.url) def create_pagenumbers(self, parent, current): - '''This function creates the pagenumber string appending to the + """This function creates the pagenumber string appending to the parent number its position in the pages of the same level. Then - the process continues with subpages''' + the process continues with subpages""" if parent != None: self.pagenumber = parent+ ".{}".format(current) else: self.pagenumber = str(current) i = 1 for page in self.subpages: page.create_pagenumbers(self.pagenumber, i) i += 1 def fix_text_characters(self): - '''Utility function to fix apostrophes and other characters - inside the text of the page''' + """Utility function to fix apostrophes and other characters + inside the text of the page""" #fix for double apostrophes quotes s = re.findall(u'(\`\`)\s?(.*?)\s?(\'\')', self.text, re.DOTALL) for item in s: self.text = self.text.replace(item[0],'"') self.text = self.text.replace(item[2],'"') s2 = re.findall(u'(\‘\‘)\s?(.*?)\s?(\’\’)', self.text, re.DOTALL) for item2 in s2: self.text = self.text.replace(item2[0],'"') self.text = self.text.replace(item2[2],'"') #apostrophe fixed self.text = self.text.replace(u'’',u"'") self.text = self.text.replace(u'`',u"'") def is_math_inside_title(self): - '''This function checkes if there is math inside titles''' + """This function checkes if there is math inside titles""" mre = re.search(r'(?2: s['is_page'] = False else: s['is_page'] = True s['text'] = self.text[:100] s['children'] = [] for page in self.subpages: s['children'].append(page.get_json_dictionary(pages)) return s def get_str(self): a = ' [C]' if self.collapsed else '' return '('+ str(self.level)+')'+'---'*self.level +\ '> '+ self.title + a def __str__(self): s =[] s.append('title='+self.title) if hasattr(self, "url"): s.append('url='+self.url) s.append('subpages='+str(self.subpages)) s.append('level='+str(self.level)) s.append('collapsed='+ str(self.collapsed)) return ' '.join(s) diff --git a/texla/PageTree/PageTree.py b/texla/PageTree/PageTree.py index 88e0c26..96102b6 100644 --- a/texla/PageTree/PageTree.py +++ b/texla/PageTree/PageTree.py @@ -1,323 +1,323 @@ from .Page import Page from .Babel import Babel from .TheoremsManager import * import re, os, json class PageTree(): def __init__(self, configs): self.configs = configs self.doc_title = configs['doc_title'] self.keywords = configs['keywords'] self.output_path = configs['output_path'] #pages Data {id: page} self.pages = {} #id : titles self.titles = {} #label manager self.babel = Babel() #theorems manager self.theorems_manager = TheoremsManager(self.pages) #urls (they are created after collapsing). #it's a dictionary id:url self.urls = {} #ROOT PAGE ro = Page(self.doc_title, 'root', -1, self.keywords) self.root_id = ro.id self.root_page = ro self.pages[self.root_id] = ro self.titles[self.root_id] = ro.title #indexes self.pageid_stack = [ro.id] self.current_page_id = self.root_id #the anchor is the object to be referentiated by labels self.current_anchor = ro def createPage(self, title, page_type): - '''This method creates a new page and enters - in his enviroment setting current variables''' + """This method creates a new page and enters + in his enviroment setting current variables""" title = self.get_normalized_title(title) #finding level level = len(self.pageid_stack) - 1 #create new page p = Page(title, page_type, level, self.keywords) #add page to pages index self.pages[p.id] = p self.titles[p.id] = p.title #adding the page as subpage of the current page self.pages[self.current_page_id].addSubpage(p) #updates current self.pageid_stack.append(p.id) self.current_page_id = p.id self.current_anchor = p def exitPage(self): - '''Return to the parent page enviroment''' + """Return to the parent page enviroment""" self.current_page_id = self.pageid_stack[-2] self.pageid_stack.pop() self.current_anchor = self.pages[self.current_page_id] def addText(self, text): self.pages[self.current_page_id].addText(text) def addLabel(self, label): - '''adding label to the babel with the current - page as the anchor''' + """adding label to the babel with the current + page as the anchor""" self.babel.add_label(label, self.current_anchor) def addReference(self, label): - '''adding the current_anchor as a reference for the - requesting label''' + """adding the current_anchor as a reference for the + requesting label""" self.babel.add_reference(label, self.current_anchor) def addTheorem(self, id, th_type): - '''Adding a theorem also as anchor''' + """Adding a theorem also as anchor""" th = Theorem(id,self.current_anchor, th_type) self.theorems_manager.addTheorem(th) #setting current anchor on the theorem self.current_anchor = th def exitTheorem(self): - '''Removing the anchor from the theorem and setting it - to the last used page''' + """Removing the anchor from the theorem and setting it + to the last used page""" self.current_anchor = self.pages[self.current_page_id] @staticmethod def get_normalized_title(title): - '''Function that removes bad symbols from title''' + """Function that removes bad symbols from title""" title = title.replace('$', '') title = title.replace('{','') title = title.replace('}','') title = title.replace('\\mathcal','') title = title.replace('\\mathbf','') title = title.replace('\\mathbb','') title = title.replace('\\ensuremath','') title = title.replace(';', ' ') title = title.replace('&', 'e') title = title.replace('\\', '') title = title.replace('/', '_') title = title.replace('>', 'gt') title = title.replace('<', 'lt') return title def get_tree_json(self): - '''This function return the json tree''' + """This function return the json tree""" return self.root_page.get_json_dictionary(self.pages) def get_tree_debug(self): - '''This function prints the tree for debug''' + """This function prints the tree for debug""" s = [] for p in self.root_page.get_subpages(): s.append(p.get_str()) return('\n'.join(s)) def after_render(self): - '''This function does some fixes after rendering''' + """This function does some fixes after rendering""" for page in self.pages.values(): page.after_render() def change_title(self, page_id, title): self.pages[page_id].title = title def remove_page_from_tree(self, page, parent=None): - '''This function remove a page from the tree, + """This function remove a page from the tree, but doesn't delete it. The page remains in the self.pages dictionary but not in the subpages of the pages in the tree. If a parent page is passed the research for the removal - starts from that page with performance improvements''' + starts from that page with performance improvements""" if parent: parent.removeSubpage(page) else: self.root_page.removeSubpage(page) def move_page_references(self, oldpage, newpage): - '''This function fixes the reference in TheoremsManager - and Babel when a page is moved''' + """This function fixes the reference in TheoremsManager + and Babel when a page is moved""" #we need to fix anchors in Babel self.babel.move_anchor(oldpage, newpage) #we need also to fix the theorems page self.theorems_manager.move_theorems_page(oldpage, newpage) def collapse_tree(self, content_level, max_page_level): - '''This function contains all the tree collapsing + """This function contains all the tree collapsing procedures in the order: 1) Mark the pages for the content collapsing without actually move the text 2) Fix the tree order with collapsing of page level (N.B.: it needs the collasped status of pages) 3) Fix the urls now that the level if fixed. 4) Create the pagenumber of every page, after the movement in the tree. 5) The theorems are fixed adding the right numbering. 6) Fix references to labels: the Babel will change pages content so this has to be done after the url fixing but before the actual text collapsing. 7) Finally collapse the pages text to the - right content level''' + right content level""" self.collapse_content_level(content_level) self.collapse_page_level(max_page_level) self.collapse_urls() self.create_pagenumbers() self.theorems_manager.fix_theorems() #ask the babel to fix the refs to labels in all the pages self.babel.fix_references() #collapse the text in the right pages self.collapse_content_level_text(content_level) def collapse_content_level(self, max_level): - '''This function marks pages with level higher than - choosen level to be collapsed. It DOESN'T move the text.''' + """This function marks pages with level higher than + choosen level to be collapsed. It DOESN'T move the text.""" for p in self.pages.values(): if p.level > max_level: p.collapsed = True def collapse_content_level_text(self, max_level): - '''This function collapses the content + """This function collapses the content of the pages at the choosen level. The content of the pages with level higher than max_level is moved up to the tree to the page with the max_level, creating titles in the page text. The pages touched - are marked as collapsed=True.''' + are marked as collapsed=True.""" for p in self.pages.values(): if p.level == max_level: p.collapseSubpagesText() def collapse_page_level(self, max_level): - '''This function fixes the level of the pages + """This function fixes the level of the pages in the index according to a max_level. Pages with a level higher than the max_level are moved up in the tree till the max_level. The order related to parent pages is mantained. The PageTree is rewrited, hierarchy and levels are fixed. Moreover the level=0 is a special level and it's content is moved to an intro page, because level=0 pages must contain the index of their subpages. - ''' + """ #PAGES LEVEL = 0 #If they contain text we have to create a new page #called introduction (localized) for p in [x for x in self.pages.values() if x.level==0]: if len(p.text)>0: #creating new page for text inside text page. p_intro = Page(self.keywords['intro'], 'section',1, self.keywords) p_intro.text = p.text #saving the intro page self.pages[p_intro.id] = p_intro self.titles[p_intro.id] = p_intro.title p.addSubpage_top(p_intro) #erasing text from section page p.text = '' #fixing page references self.move_page_references(p, p_intro) #Now we move pages according to the max_level. #pages not collapsed and with higher level then #the max_level are moved as subpages of the #nearest max_level page. for p in [x for x in self.pages.values() if x.level==max_level]: parent_page = p.parent #list of subpages to move at the right level subpages_to_add = [] #now we are cycling on the pages with level>max_level for sp in p.get_subpages(): if not sp.collapsed: #removing page from the tree acting #directly on the parent page sp.parent.removeSubpage(sp) #saving the page for the movement subpages_to_add.append(sp) #adding the list of moved subpages to the parent_page #so getting the right level. parent_page.addSubpages(subpages_to_add, p) ###NB: remember that the subpages level #is AUTOMATICALLY refreshed for all pages added. def collapse_urls(self): - '''This function creates the urls of the pages, + """This function creates the urls of the pages, checking is they are collapsed or not. If they are collapsed the url is parent_page#title. - Then the references are resolved to urls throught labes''' + Then the references are resolved to urls throught labes""" self.root_page.collapseURL(self.configs['base_path']) def create_pagenumbers(self): - '''Every page will have a pagenumber like 1.2.1''' + """Every page will have a pagenumber like 1.2.1""" self.root_page.pagenumber = "0" i = 1 for pages in self.root_page.subpages: pages.create_pagenumbers(None, i ) i += 1 def create_indexes(self, export_book_page=False): - '''This function create sections index and - book total index''' + """This function create sections index and + book total index""" self.create_sections_index() self.create_book_index(export_book_page=False) def create_sections_index(self): - '''This function create the index for the - sections (level=0) pages''' + """This function create the index for the + sections (level=0) pages""" for page in self.pages.values(): if page.level == 0: index = [] for p in page.get_subpages(): if not p.collapsed: if len(p.text) >0: index.append('*'*p.level+ \ '[[' + p.url + '|' + p.title + ']]') else: index.append('*'*p.level+ p.title ) #adding section category index.append("[[Category:CourseLevelTwo]]") page.text = '\n'.join(index) def create_book_index(self, export_book_page=False): - '''This function create the book total index - and the book export page index''' + """This function create the book total index + and the book export page index""" base_page = self.root_page #book export: link book_url = self.doc_title.replace(' ','_') #creating root index index = ["{{CourseRoot|"] if export_book_page: book_export_index = ['{{libro_salvato | setting-papersize = a4\ | setting-toc = auto | setting-columns = 1}}'] #book export: setting title book_export_index.append('==' + self.doc_title + '==') for page in self.root_page.subpages: if page.level == 0: index.append('{{CourseLevelTwo|'+page.title +'}}') if export_book_page: #book export index for chapters book_export_index.append(';' + page.title) #creating index for book for p in page.get_subpages(): if not p.collapsed: if len(p.text) > 0: book_export_index.append( ':[[' + p.url + '|' + p.title + ']]') #closing section index.append('\n{{ForceBreak}}\n') #adding course categories index.append("}}\n") index.append("[["+ self.configs["keywords"]["category"] +":Structure]]") index.append("[[Category:CourseRoot]]") base_page.text += '\n'.join(index) #creating book export page if export_book_page: #adding category to book page book_export_index.append("[["+self.configs["keywords"]["book_category"]+ "|"+self.doc_title +"]]") book_template = self.configs["keywords"]["book_template"] book_title = book_template + '_' + book_url book_export_page = Page(book_title, 'root', -1,None) book_export_page.url = self.configs['base_path']+ \ book_template + '/' + self.doc_title #inserting index text book_export_page.addText(u'\n'.join(book_export_index)) #the export book page is inserted in the pages dict and index self.pages[book_template + '/' + self.doc_title] = book_export_page diff --git a/texla/PageTree/TheoremsManager.py b/texla/PageTree/TheoremsManager.py index c1a0510..7f3fef1 100644 --- a/texla/PageTree/TheoremsManager.py +++ b/texla/PageTree/TheoremsManager.py @@ -1,92 +1,92 @@ import logging class TheoremsManager: def __init__(self, pages_dict): #refernce to the pages dictionary self.pages = pages_dict self.pages_ths = {} def addTheorem(self, theorem): page = theorem.page if page not in self.pages_ths: self.pages_ths[page] = [] self.pages_ths[page].append(theorem) def move_theorems_page(self, oldpage, newpage): - '''This function moves a theorem to a different page - to maintain the right anchor in case of moved page.''' + """This function moves a theorem to a different page + to maintain the right anchor in case of moved page.""" if oldpage in self.pages_ths: self.pages_ths[newpage] = self.pages_ths[oldpage] self.pages_ths.pop(oldpage) def fix_theorems(self): - '''This function fixes the theorems calculating their + """This function fixes the theorems calculating their number and substituing it the placeholder in the text. - Moreover it fixes the data needed by the label manager.''' + Moreover it fixes the data needed by the label manager.""" for chapter in [x for x in self.pages.values() if x.level == 0]: chapter_number = chapter.pagenumber th_numbering = {} pages_to_check = self.get_subpages_ordered(chapter) for pag in pages_to_check: for th in self.pages_ths[pag]: if th.th_type in th_numbering: number = th_numbering[th.th_type] +1 else: number = 1 th_numbering[th.th_type] = number th.fixNumber(chapter_number+"."+str(number)) th.fixUrl() def get_subpages_ordered(self, page): pages = [] for subp in page.subpages: if subp in self.pages_ths: pages.append(subp) pages += self.get_subpages_ordered(subp) return pages class Theorem: def __init__(self, id, page, th_type): self.id = id self.page = page #creating a text value. If the Theorem is used #as reference the text of the parent page is used. self.text = page.text self.th_type = th_type self.number = 0 def fixNumber(self, number): - '''This method fix the number of the theorem + """This method fix the number of the theorem inside its page text replacing the string {{thnum:id}}. - The number is also appended to the title''' + The number is also appended to the title""" self.page.text = self.page.text.replace( "{{thnum:"+ self.id + "}}", str(number)) #creating title for label management. Only the number as latex. self.title = str(number) self.number = number logging.info("Theorem @ Fixing number {} of theorem: {}". format(number, self)) def fixUrl(self): - '''The theorem url is setted to the page url. - N.B.: to be called after pages' urls fixing''' + """The theorem url is setted to the page url. + N.B.: to be called after pages' urls fixing""" #getting not collapsed url current_page = self.page while(True): if current_page.collapsed: current_page = current_page.parent else: break self.url = current_page.url + "#" + self.title.replace(".", "_") #replacing anchor in the text self.page.text = self.page.text.replace( "{{thanchor:"+ self.id + "}}", self.title.replace(".","_")) logging.debug("Theorem @ Fixing URL of theorem {}".format(self)) def __str__(self): return "Theorem. Type: {}, Page: {}, Number: {}".format( self.th_type, self.page.title, self.number ) diff --git a/texla/Parser/Parser.py b/texla/Parser/Parser.py index 89b4690..0a68851 100644 --- a/texla/Parser/Parser.py +++ b/texla/Parser/Parser.py @@ -1,457 +1,457 @@ import re import logging from . import PreParser from . import Blocks from .TreeExplorer import TreeExplorer from .Blocks.Utilities import * from .Blocks.DocumentBlock import DocumentBlock from ..Exceptions.TexlaExceptions import * '''Commands that changes directly the subsequent letter''' letters_commands = ("'","`",'"','~','^','=','.') special_characters = ('%','&','$','{','}','#','_',' ', '\n') class Parser: def __init__(self, configs): self.configs = configs self.doc_data = {} self.root_block = None self.tree_explorer = None def parse(self,tex): """Entry point for parsing. The DocumentBlock is created and all the parse chain is started from parse_sections. The function returns the root_block, which contains all the parsed tree blocks.""" #preparsing tex, doc_data = PreParser.preparse(tex, self.configs['input_path']) self.doc_data = doc_data logging.info('\033[0;34m############### STARTING PARSING ###############\033[0m') #Getting content of document r_doc = re.compile(r'\\begin(?P\[.*?\])?{document}'+ r'(?P.*?)\\end{document}', re.DOTALL) m_doc = r_doc.search(tex) #getting content content = m_doc.group("content") logging.debug('Parser @ got content of Document') #creating root block self.root_block = DocumentBlock(self.doc_data['title'],{}) #creating the TreeExplorer self.tree_explorer = TreeExplorer(self.root_block) #beginning of parsing: creation of root block options = {} #for now we don't have options blocks = self.parse_sections(content, -1, self.root_block,options) self.root_block.add_children_blocks(blocks) #updating the tree_explorer self.tree_explorer.update_blocks_register() return self.root_block def parse_sections(self, tex, level, parent_block, options): """ This parser function search for sections splitting inside tex. The level of sections searched is indicated by sec_level option. The function calls the parser_hooks of every section block. When all sections levels are searched the control pass to parse_instructions(). It returns a list of blocks parsed as tuples. """ try: pblocks = [] #check if the level is greater than subparagraph if (level+1) < (len(utility.section_level)-1): #getting level key from utility to create regex level_key = utility.section_level[level+1] sec_re = re.compile(r'\\'+ level_key + r'(?![a-zA-Z])') #the tex is splitted by the section key toks = sec_re.split(tex) #the first token is the tex outside sectioning #the text is STRIPED to avoid null strings outside_sec = toks.pop(0).strip() if outside_sec != '': #this tex has to be parser but with a sectioning #level greater than one pblocks+=self.parse_sections(outside_sec, level+1, parent_block, options) #now the sections found are processed for tok in toks: if tok.startswith('*'): star = True tok = tok[1:].strip() else: star = False tok = tok.strip() #we insert the level in the options sec_params = { 'sec_level' : (level +1), 'level_key' : level_key, 'star' : star} #the tuple with the result is saved pblocks.append(self.call_parser_hook(level_key, 'env', tok, parent_block, sec_params)) logging.info('Block @ %s%s', "\t"*pblocks[-1].tree_depth, str(pblocks[-1])) else: #if we have searched for all section levels #we continue with instructions #First we STRIP the tex and check if tex is not void new_tex = tex.strip() if len(new_tex) > 0: pblocks += self.parse_instructions(new_tex, parent_block, options) #found block are returned to main cycle return pblocks except ParserError as err: raise except: raise ParserError(tex, parent_block, "Error in parse_sections") def parse_instructions(self, tex, parent_block, options): """This function is the MAIN ENTRY POINT for parsing. It scan the tex from left to right. It searches for \\ or $. When an instruction is found (a pattern starting with \\ or $), the right parser function is called. These functions take care to parse the command, create the block calling parser_hooks, and to return the block and the tex left to parse. Then the remaining tex starts a new cycle in parse_instructions() recursively. It returnes a list of parsed blocks. The categories of instrucions parsed are: -math: starts with $, $$ or \[ \( -environments: (start with \begin) -letters commands: they are special commands listed in letters_commands. They are parsed separately -normal commands: like \cmd{text} """ #printing the current tex for debug #logging.debug('CURRENT-TEX: ' + tex[:40]) #list of blocks parsed pblocks = [] #checking if tex is void if len(tex) == 0: return pblocks #searching for comands \cmd, envs \begin or math symb = {} left_tex = '' slash = tex.find('\\') dollar = tex.find('$') graph = tex.find('{') if slash == -1 and dollar == -1 and graph==-1: #we check if the string is only space or \n - if (len(tex.strip())): + if len(tex.strip()): #it's plain text pblocks.append(self.parse_plain_text(tex, parent_block)) return pblocks #searching the first symbol if slash != -1: symb[slash] = 'slash' if dollar != -1: symb[dollar] = 'dollar' if graph != -1: symb[graph] = 'group' #getting the first occurence first_index = sorted(symb)[0] first_symb = symb[first_index] #creating block text with before_tex before_tex = tex[:first_index] #tex to parse tex_tp = tex[first_index:] #creating a plain text block - if (len(before_tex.strip())): + if len(before_tex.strip()): pblocks.append(self.parse_plain_text(before_tex, parent_block)) #what's the first token: slash, dollar, group if first_symb == 'slash': #we check if it's a math command like \[ or \( if tex_tp[1] in ('[','('): block, left_tex = self.parse_math( tex_tp, parent_block, options) #now we check if it's an environment elif tex_tp[1:6] == 'begin': block, left_tex = self.parse_enviroment( tex_tp, parent_block, options) #we check if we have letters commands elif tex_tp[1] in letters_commands: block, left_tex = self.parse_letter_command( tex_tp, parent_block, options) #we check if we have special characters elif tex_tp[1] in special_characters: block, left_tex = self.parse_special_character( tex_tp, parent_block, options) else: #finally we have a normal command block, left_tex = self.parse_command( tex_tp, parent_block, options) #block saved pblocks.append(block) elif first_symb == 'dollar': #we have to parse math block, left_tex = self.parse_math(tex_tp, parent_block,options) pblocks.append(block) elif first_symb == 'group': #we have a group {...} syntax block, left_tex = self.parse_commands_group( tex_tp, parent_block, options) pblocks.append(block) #left_tex is parsed with another cycle pblocks += self.parse_instructions( left_tex, parent_block, options) #all the blocks parsed are returned return pblocks def parse_enviroment(self, tex, parent_block, options): """ This function handles the parsing of environments. It parses the name of the environment and if it's starred. Then EnvironmentParser.get_environment() is used to extract the complete environment, handling nested envs. The content is sent to parser_hook for the specific parsing. The parser_hook decides also if the content of the env must be parsed recursively. A new block is created and returned with the tex remained to parse. """ #we search for the first enviroment re_env1 = re.compile(r'\\begin(?: *)\{(?: *)(?P\w*?)'+\ r'(?P[*]?)(?: *)\}') match = re_env1.match(tex) - if not match == None: + if not match is None: env = match.group('env') star = True if match.group('star')!='' else False env_tot = env + '\*' if star else env #now we extract the env greedy s,e,content = EnvironmentParser.get_environment( tex,env_tot) #the name of catched env is inserted in params #with the star param env_params = {'env':env, 'star':star} #we can call the parser hooks. #N.B.: the tex passed to parser hook is the CONTENT STRIPPED #of the environment, without \begin{} and \end{} part. #The strip is necessary to parse possible options. block = self.call_parser_hook(env,'env', content.strip(), parent_block, env_params) logging.info('Block @ %s%s', "\t"*block.tree_depth, str(block)) #we return the block and left_tex return (block, tex[e:]) else: #it's an error logging.error('PARSER.parse_enviroment @ env NOT FOUND') def parse_math(self, tex, parent_block, options): """ This function handles the parsing of math commands: $..$, $$..$$, \[..\], \(..\). The matched math is inserted in "display_math" or "inline_math" block. The function returnes the block and left_tex. """ #firt we have to check the double dollar if tex.startswith("$$"): i = tex.find("$$", 2) content = tex[2:i] left_tex = tex[i+2:] env = "displaymath" elif tex.startswith("$"): i = tex.find("$", 1) content = tex[1:i] left_tex = tex[i+1:] env = "inlinemath" elif tex.startswith("\\["): i = tex.find("\\]", 2) content = tex[2:i] left_tex = tex[i+2:] env = "displaymath" elif tex.startswith("\\("): i = tex.find("\\)", 2) content = tex[2:i] left_tex = tex[i+2:] env = "inlinemath" params = {'env': env} block = self.call_parser_hook(env, 'env', content, parent_block, params) logging.info('Block @ %s%s', "\t"*block.tree_depth, str(block)) return (block, left_tex) def parse_command(self, tex, parent_block, options): """ This function handles the parsing of normal commands. It catches the command's name and if it's starred. Removed the \cmd part, the tex is passed to the right parser_hook that manages the real parsing of commands options. The parser_hook decides also if the content of the command must be parsed recursively. It returns the block and the left tex that must be parsed by another cycle of parse_instructions() """ #regex to catch commands re_cmd = re.compile(r"\\(?:(?P[a-zA-Z]+)"+\ r"(?P[*]?)|(?P\\))", re.DOTALL) match = re_cmd.match(tex) - if match!=None: + if not match is None: #managing match. #checking if the part of the match with the regular #command is present--> math.group != None!!! if match.group('cmd') != None: matched_cmd = match.group('cmd') star = True if match.group('star')!='' else False #we insert the matched options in the dict for hooks params = {'cmd':matched_cmd, 'star':star} #the text passed to hooks is STRIPPED to remove #useless spaces. #N.B the matched part is not sent to hook tex_to_parse = tex[match.end():].strip() #the matched command is parsed by the parser_hook #and the remaining tex is returned as the second element of #a list. The first element is the parsed Block. block, left_tex = self.call_parser_hook(matched_cmd, 'cmd', tex_to_parse, parent_block,params) logging.info('Block @ %s%s', "\t"*block.tree_depth, str(block)) else: #we have a \\ command matched_cmd = '\\' tex_to_parse = tex[match.end():].strip() #we insert the matched options in the dict for hooks params = {'cmd':'\\', 'star':False} #check if we have \\* if tex_to_parse.startswith('*'): params['star'] = True tex_to_parse = tex_to_parse[1:] #parser_hook call block, left_tex = self.call_parser_hook(matched_cmd, 'cmd', tex_to_parse, parent_block,params) logging.info('Block @ %s%s', "\t"*block.tree_depth, str(block)) return (block, left_tex) else: #it's an error logging.error('PARSER.parse_command @ command NOT FOUND: {}'. format(tex[0:10])) def parse_commands_group(self, tex, parent_block, options): """ This function handles the group of commands created with the syntax {...}. It's used for the formatting commands. """ block, left_tex = self.call_parser_hook( 'commands_group', 'env', tex, parent_block, {'env':'commands_group'}) return (block, left_tex) def parse_letter_command(self, tex, parent_block,options): """' This function handles special commands for accented or modified letters. They are special commands because they don't need a {} and they act directly on the next letter. Examples: \'a: accented letter \`a: grave accent \~a \=a \^a other changes on the letter The function parse that commands and call parser_hook as the normal parse_command() function. Althought, the letter influenced by the command is inserted in a {} so that special command could be treated like normal commands with hooks. It returns the block and the left tex to parse. """ #first of all we get the command cmd = tex[1] params = {'cmd':cmd, 'star':False} #then it is a letter command #check if the letter is inside a {} r = re.compile(r'\\' + cmd + r'\s*\{(.*?)\}') match = r.match(tex) if match != None: tex_to_parse = tex[2:].strip() block, left_tex = self.call_parser_hook(cmd, 'cmd', tex_to_parse, parent_block,params) else: #we have to catch the next letter re_letter = re.compile(r'\\' + cmd + r'\s*(?P\w)') letter_m = re_letter.match(tex) letter = letter_m.group('letter') #adding parenthesis to standardize parser_hook tex_to_parse = '{'+letter + '}'+ \ tex[letter_m.end():] block, left_tex = self.call_parser_hook(cmd, 'cmd', tex_to_parse, parent_block, params) logging.info('Block @ %s%s', "\t"*block.tree_depth, str(block)) return (block, left_tex) def parse_special_character(self, tex, parent_block,options): """ This function parse special commands like \% or \&. The mechanism is the same ad special_commands, but options are not searched. """ cmd = tex[1] if cmd in [' ','\n']: #we change the name of the command cmd = "mandatory_space" params = {'cmd':cmd, 'star':False} block, left_tex = self.call_parser_hook(cmd, 'cmd', tex[2:], parent_block, params) logging.info('Block @ %s%s', "\t"*block.tree_depth, str(block)) return (block, left_tex) def parse_plain_text(self, tex, parent_block): """ This function create the block for plain text. It doesn't return any left tex. """ params = {'env':'text'} block = self.call_parser_hook('text','env', tex, parent_block,params) logging.info('Block @ %s%s', "\t"*block.tree_depth, str(block)) return block def call_parser_hook(self, hook, type, tex, parent_block, params={}): """ This function checks if the required parser_hook is avaiable, if not it calls th default hook. The function ask for type of call (env or cmd) to be able of asking the right default hooks, in case the hook in not avaiable. Params is a dictionary of options for the parser. It usually contains che env or cmd parsed and if it's starred. It returns directly the output of parser_hook. """ if hook in Blocks.parser_hooks: return Blocks.parser_hooks[hook](self, tex, parent_block, params) else: #default hook is called if type == 'cmd': return Blocks.parser_hooks['default_cmd']( self, tex, parent_block, params) elif type == 'env': return Blocks.parser_hooks['default_env']( self, tex, parent_block, params) diff --git a/texla/Parser/TreeExplorer.py b/texla/Parser/TreeExplorer.py index f1ada15..f582588 100644 --- a/texla/Parser/TreeExplorer.py +++ b/texla/Parser/TreeExplorer.py @@ -1,110 +1,110 @@ import logging class TreeExplorer: """ The TreeExplorer class is an utility to navigate and extract information from the tree of parsed blocks. For example it is useful to extract the tree of the parents of a block for debugging reasons. It is useful also in rendering to localize blocks inside the document. """ def __init__(self, root_block): """ The constructor needs a root_block to begin the tree""" self.root_block = root_block self.blocks = {'@': root_block} #registering blocks by id self.register_blocks(root_block.ch_blocks) @staticmethod def create_tree_from_children(block): #first of all we need the root_block current = block while True: if current.parent_block is None: root_block = current break current = current.parent_block #now we can return a new TreeExplorer #constructed from the found root. return TreeExplorer(root_block) def register_blocks(self, blocks): """This methods reads all the blocks tree from the root_block and created a dictionary with id:block""" for block in blocks: self.blocks[block.id] = block if block.N_chblocks > 0: self.register_blocks(block.ch_blocks) def update_blocks_register(self): """This methods update the blocks' ids register recalling register_blocks with the root_block""" self.register_blocks(self.root_block.ch_blocks) def get_parents_list(self, block): """This method returns the list of the parent blocks of the requested block """ if isinstance(block, str): block = self.blocks[block] parents = [] current = block while True: if current == self.root_block: break parents.append(current.parent_block) current = current.parent_block parents.reverse() return parents def get_parents_list_ids(self, block): parents = self.get_parents_list(block) return [x.id for x in parents] def get_block(self, blockid): return self.blocks.get(blockid) def print_tree(self, block, filter_list=None): - '''This methods prints a beautified tree starting + """This methods prints a beautified tree starting from block parameter and his children. If filter_list is present only the block with the id in the list - are printed. It returns a list of output strings''' + are printed. It returns a list of output strings""" output = [] if filter_list is None or block.id in filter_list: lstr = ". "* (block.tree_depth+1) output.append(lstr+ ". "+ " "+"_"*40 ) output.append(lstr+ "#"+"---"+ ">|ID : {}".format(block.id)) output.append(lstr+ ". "+ " |block_name : {}". format(block.block_name)) output.append(lstr+ ". "+ " |attributes: ") for at,attr in block.attributes.items(): output.append(lstr+ ". " + " | - "+ "{} : {}". format(at, attr)) output.append(lstr+ ". ."+"\u203E"*40+"\n") output = "\n".join(output) #iterating on the block children for bl in block.ch_blocks: output += self.print_tree(bl, filter_list) return output def print_tree_to_blocks(self, blocks): - '''This methods print the tree of parents + """This methods print the tree of parents of the list of blocks passed as parameter. First of all it gets all the parents ids and - then prints the tree using the list as filter.''' + then prints the tree using the list as filter.""" fl = [] for bl in blocks: fl+= self.get_parents_list_ids(bl) if isinstance(bl, str): fl.append(bl) else: fl.append(bl.id) return self.print_tree(self.root_block, filter_list=fl) def print_tree_to_block(self, block): return self.print_tree_to_blocks([block]) def print_all_tree(self): return self.print_tree(self.root_block) \ No newline at end of file diff --git a/texla/Renderers/MediaWikiRenderer.py b/texla/Renderers/MediaWikiRenderer.py index 1af4084..7fdf65e 100644 --- a/texla/Renderers/MediaWikiRenderer.py +++ b/texla/Renderers/MediaWikiRenderer.py @@ -1,548 +1,548 @@ import logging from .Renderer import Renderer from ..PageTree.PageTree import * class MediaWikiRenderer(Renderer): def __init__(self, configs): super().__init__() self.configs = configs self.doc_title = configs['doc_title'] #saving the hooks self.render_hooks = { #root 'root-block': self.r_document, 'default': self.default, #text 'par': self.r_par, 'newpage': self.r_newpage, 'newline': self.r_newline, '\\': self.r_newline, 'text': self.r_text, 'clearpage': self.r_newpage, 'cleardoublepage': self.r_newpage, #formatting 'emph': self.r_textit, 'textbf': self.r_textbf, 'textit': self.r_textit, 'textsc': self.r_textsc, 'textsuperscript': self.r_superscript, 'textsubscript': self.r_subscript, 'underline': self.r_underline, 'uline': self.r_underline, '%': self.r_special_character, '&': self.r_special_character, '$': self.r_special_character, '{': self.r_special_character, '}': self.r_special_character, '#': self.r_special_character, '_': self.r_special_character, 'dots': self.r_dots, 'ldots': self.r_dots, 'flushright': self.r_flushright, 'flushleft': self.r_flushleft, 'center': self.r_center, 'centerline': self.r_center, 'abstract': self.r_abstract, 'linebreak': self.r_break, 'pagebreak': self.r_break, 'nolinebreak': self.r_break, 'nopagebreak': self.r_break, 'verbatim': self.r_verbatim, 'verb': self.r_verb, #spaces 'vspace': self.r_vspace, 'mandatory_space': self.r_mandatory_space, #theorems 'theorem' : self.r_theorem, 'proof' : self.r_proof, #sectioning 'part': self.sectioning, 'chapter': self.sectioning, 'section': self.sectioning, 'subsection': self.sectioning, 'subsubsection': self.sectioning, 'paragraph': self.sectioning, 'subparagraph': self.sectioning, #math 'displaymath': self.r_display_math, 'inlinemath': self.r_inline_math, 'ensuremath': self.r_inline_math, 'equation': self.r_display_math, 'eqnarray': self.r_align, 'multline': self.r_align, 'align': self.r_align, 'alignat': self.r_align, 'gather': self.r_gather, #lists 'itemize': self.r_itemize, 'enumerate': self.r_enumerate, 'description': self.r_description, #quotes 'quotation': self.r_quotes, 'quote': self.r_quotes, 'verse': self.r_verse, 'footnote': self.r_footnote, #labels 'label': self.r_label, 'ref': self.r_ref, 'vref': self.r_ref, 'pageref': self.r_ref, 'eqref': self.r_ref, #accents "accented_letter": self.r_accented_letter, } #register plugins self.register_plugins(configs["plugins"]) #tree object self.tree = PageTree(configs) #parameter for list formatting self.list_level = '' #parameters for theorem handling self.in_theorem = False self.theorem_number = 0 self.th_numbering = {} ######################################## #STARTING POINT def start_rendering(self, root_block): - '''starting rendering from root-block''' + """starting rendering from root-block""" #start rendering of base class super(MediaWikiRenderer, self).start_rendering(root_block) self.render_block(root_block) #after rendering self.tree.after_render() #end rendering of base class super(MediaWikiRenderer, self).end_rendering() ####### ROOT BLOCK def r_document(self, block): #we trigger the rendering of content text = self.render_children_blocks(block) #text is the tex outside sections self.tree.addText(text) #returning the text to respect the interface return text ######################################## #DEFAULT def default(self, block): #we don't print anything return '' ######################################### #TEXT def r_text(self, block): text = block.attributes['text'] return text def r_newline(self, block): return '\n' def r_newpage(self, block): return '\n\n' def r_par(self, block): return '\n\n' ######################################### #SECTIONING def sectioning(self, block): title = block.attributes['title'] section_name = block.attributes['section_name'] #remove the \n insiede title title = re.sub('\\n*', '', title) #creation of the new page self.tree.createPage(title, section_name) #content processing text = self.render_children_blocks(block) #adding text to current page self.tree.addText(text) #exiting the section self.tree.exitPage() return '' ######################################### #MATH def r_display_math(self, block): s = block.attributes['content'] #rendering labels self.render_blocks(block.labels) return '' + s + '' def r_inline_math(self, block): s = block.attributes['content'] #rendering labels self.render_blocks(block.labels) return '' + s + '' def r_align(self, block): s = block.attributes['content'] #rendering labels self.render_blocks(block.labels) return '\\begin{align}' +\ s + '\end{align}' def r_gather(self, block): s = block.attributes['content'] output = [] for eq in s.split("\\\\"): eq = eq.replace("\n","").strip() output.append('' +\ eq + '') #rendering labels self.render_blocks(block.labels) return '\n'.join(output) ######################################### #LABELS and refs def r_label(self, block): label = block.attributes['label'] self.tree.addLabel(label) return '' def r_ref(self, block): ref = block.attributes['ref'] #saving ref in Babel of PageTree self.tree.addReference(ref) return "{{ref:"+ ref+ "}}" ######################################### #FORMATTING def r_special_character(self, block): return block.attributes['character'] def r_dots(self, block): return '...' def r_textbf(self, block): s = [] s.append("\'\'\'") s.append(self.render_children_blocks(block)) s.append("\'\'\'") return ''.join(s) def r_textit(self, block): s = [] s.append("\'\'") s.append(self.render_children_blocks(block)) s.append("\'\'") return ''.join(s) def r_textsc(self, block): return self.render_children_blocks(block).upper() def r_superscript(self, block): s = [] s.append('') s.append(self.render_children_blocks(block)) s.append('') return ''.join(s) def r_subscript(self, block): s = [] s.append('') s.append(self.render_children_blocks(block)) s.append('') return ''.join(s) def r_underline(self, block): s = [] s.append('{{Sottolineato|') s.append(self.render_children_blocks(block)) s.append('}}') return ''.join(s) def r_abstract(self, block): s = [] s.append('{{Abstract|') s.append(self.render_children_blocks(block)) s.append('}}') return ''.join(s) def r_break(self, block): return '' def r_vspace(self,block): return '\n\n' def r_mandatory_space(self,block): return ' ' def r_verbatim(self, block): return '
' + block.attributes['content'] +'
' def r_verb(self, block): return '' + block.attributes['content'] +'' ######################################### #ALIGNMENT def r_center(self, block): s = [] s.append('{{Center|') s.append(self.render_children_blocks(block)) s.append('}}') return ''.join(s) def r_flushleft(self, block): s = [] s.append('{{Flushleft|') s.append(self.render_children_blocks(block)) s.append('}}') return ''.join(s) def r_flushright(self, block): s = [] s.append('{{Flushright|') s.append(self.render_children_blocks(block)) s.append('}}') return ''.join(s) ######################################### #LISTS def r_itemize(self, block): self.list_level += '*' s = ['\n'] for item in block.ch_blocks: s.append(self.list_level) s.append(self.render_children_blocks(item)) s.append("\n") self.list_level = self.list_level[:-1] return ''.join(s) def r_enumerate(self, block): self.list_level += '#' s = ['\n'] for item in block.ch_blocks: s.append(self.list_level) s.append(self.render_children_blocks(item).strip()) s.append("\n") self.list_level = self.list_level[:-1] return ''.join(s) def r_description(self, block): s = ['\n'] for item in block.ch_blocks: s.append(';') s.append(item.attributes['word']) s.append(':') s.append(self.render_children_blocks(item)) s.append("\n") return ''.join(s) ######################################### #QUOTES def r_quotes(self, block): s = [] s.append('
') s.append(self.render_children_blocks(block)) s.append('
') return ''.join(s) def r_verse(self, block): s = [] s.append('
') s.append('\n'.join(self.render_children_blocks(block).split('//'))) s.append('
') return ''.join(s) def r_footnote(self, block): s = [] s.append("") s.append(self.render_children_blocks(block)) s.append("") return ''.join(s) ######################################### #Theorems def r_theorem(self, block): #the label in theorems is not working for now th_definition = block.attributes['definition'] th_title = '' if block.attributes['title'] != None: th_title +=" "+ block.attributes['title'] s = [] #adding the theorem to the tree self.theorem_number += 1 self.tree.addTheorem(str(self.theorem_number), th_definition) #checking if the Environment template is used environ = False if self.configs['lang'] =='it': if th_definition.lower() == 'teorema': #adding content to page through a template s.append("\n{{InizioTeorema|titolo=" + \ th_title+"|number={{thnum:"+ str(self.theorem_number)+"}}"+\ "|anchor={{thanchor:"+ str(self.theorem_number) +"}}}}") s.append(self.render_children_blocks(block)) s.append("{{FineTeorema}}\n") elif th_definition.lower() == 'definizione': s.append("\n{{InizioDefinizione|titolo=" + \ th_title+"|number={{thnum:"+ str(self.theorem_number)+"}}"+\ "|anchor={{thanchor:"+ str(self.theorem_number) +"}}}}") s.append(self.render_children_blocks(block)) s.append("{{FineDefinizione}}\n") elif th_definition.lower() == 'proposizione': s.append("\n{{InizioProposizione|titolo=" + \ th_title+"|number={{thnum:"+ str(self.theorem_number)+"}}"+\ "|anchor={{thanchor:"+ str(self.theorem_number) +"}}}}") s.append(self.render_children_blocks(block)) s.append("{{FineProposizione}}\n") elif th_definition.lower() == 'lemma': s.append("\n{{InizioLemma|title=" + \ th_title+"|number={{thnum:"+ str(self.theorem_number)+"}}"+\ "|anchor={{thanchor:"+ str(self.theorem_number) +"}}}}") s.append(self.render_children_blocks(block)) s.append("{{FineLemma}}\n") elif th_definition.lower() == 'corollario': s.append("\n{{InizioCorollario|titolo=" + \ th_title+"|number={{thnum:"+ str(self.theorem_number)+"}}"+\ "|anchor={{thanchor:"+ str(self.theorem_number) +"}}}}") s.append(self.render_children_blocks(block)) s.append("{{FineCorollario}}\n") elif th_definition.lower()[:-2] == 'eserciz': s.append("\n{{InizioEsercizio|titolo=" + \ th_title+"|number={{thnum:"+ str(self.theorem_number)+"}}"+\ "|anchor={{thanchor:"+ str(self.theorem_number) +"}}}}") s.append(self.render_children_blocks(block)) s.append("{{FineEsercizio}}\n") elif th_definition.lower()[:-1] == 'osservazion': s.append("\n{{InizioOsservazione|titolo=" + \ th_title+"|number={{thnum:"+ str(self.theorem_number)+"}}"+\ "|anchor={{thanchor:"+ str(self.theorem_number) +"}}}}") s.append(self.render_children_blocks(block)) s.append("{{FineOsservazione}}\n") elif th_definition.lower()[:-2] == 'esemp': s.append("\n{{InizioEsempio|titolo=" + \ th_title+"|number={{thnum:"+ str(self.theorem_number)+"}}"+\ "|anchor={{thanchor:"+ str(self.theorem_number) +"}}}}") s.append(self.render_children_blocks(block)) s.append("{{FineEsempio}}\n") elif th_definition.lower() == 'dimostrazione': s.append("\n{{InizioDimostrazione|titolo=" + \ th_title+"|number={{thnum:"+ str(self.theorem_number)+"}}"+\ "|anchor={{thanchor:"+ str(self.theorem_number) +"}}}}") s.append(self.render_children_blocks(block)) s.append("{{FineDimostrazione}}\n") else: s.append("\n{{Environment|name="+ th_definition + \ "|title=" + th_title +\ "|content=") s.append(self.render_children_blocks(block)) s.append("}}\n") elif self.configs['lang'] =='en': if th_definition.lower() == 'theorem': #adding content to page through a template s.append("\n{{BeginTheorem|title=" + \ th_title+"|number={{thnum:"+ str(self.theorem_number)+"}}"+\ "|anchor={{thanchor:"+ str(self.theorem_number) +"}}}}") s.append(self.render_children_blocks(block)) s.append("{{EndTheorem}}\n") elif th_definition.lower() == 'definition': s.append("\n{{BeginDefinition|title=" + \ th_title+"|number={{thnum:"+ str(self.theorem_number)+"}}"+\ "|anchor={{thanchor:"+ str(self.theorem_number) +"}}}}") s.append(self.render_children_blocks(block)) s.append("{{EndDefinition}}\n") elif th_definition.lower() == 'proposition': s.append("\n{{BeginProposition|title=" + \ th_title+"|number={{thnum:"+ str(self.theorem_number)+"}}"+\ "|anchor={{thanchor:"+ str(self.theorem_number) +"}}}}") s.append(self.render_children_blocks(block)) s.append("{{EndProposition}}\n") elif th_definition.lower() == 'lemma': s.append("\n{{BeginLemma|title=" + \ th_title+"|number={{thnum:"+ str(self.theorem_number)+"}}"+\ "|anchor={{thanchor:"+ str(self.theorem_number) +"}}}}") s.append(self.render_children_blocks(block)) s.append("{{EndLemma}}\n") elif th_definition.lower() == 'corollary': s.append("\n{{BeginCorollary|title=" + \ th_title+"|number={{thnum:"+ str(self.theorem_number)+"}}"+\ "|anchor={{thanchor:"+ str(self.theorem_number) +"}}}}") s.append(self.render_children_blocks(block)) s.append("{{EndCorollary}}\n") elif th_definition.lower() == 'exercise': s.append("\n{{BeginExercise|title=" + \ th_title+"|number={{thnum:"+ str(self.theorem_number)+"}}"+\ "|anchor={{thanchor:"+ str(self.theorem_number) +"}}}}") s.append(self.render_children_blocks(block)) s.append("{{EndExercise}}\n") elif th_definition.lower() == 'observation': s.append("\n{{BeginObservation|title=" + \ th_title+"|number={{thnum:"+ str(self.theorem_number)+"}}"+\ "|anchor={{thanchor:"+ str(self.theorem_number) +"}}}}") s.append(self.render_children_blocks(block)) s.append("{{EndObservation}}\n") elif th_definition.lower() == 'remark': s.append("\n{{BeginRemark|title=" + \ th_title+"|number={{thnum:"+ str(self.theorem_number)+"}}"+\ "|anchor={{thanchor:"+ str(self.theorem_number) +"}}}}") s.append(self.render_children_blocks(block)) s.append("{{EndRemark}}\n") elif th_definition.lower() == 'example': s.append("\n{{BeginExample|title=" + \ th_title+"|number={{thnum:"+ str(self.theorem_number)+"}}"+\ "|anchor={{thanchor:"+ str(self.theorem_number) +"}}}}") s.append(self.render_children_blocks(block)) s.append("{{EndExample}}\n") elif th_definition.lower() == 'demonstration': s.append("\n{{BeginDemonstration|title=" + \ th_title+"|number={{thnum:"+ str(self.theorem_number)+"}}"+\ "|anchor={{thanchor:"+ str(self.theorem_number) +"}}}}") s.append(self.render_children_blocks(block)) s.append("{{EndDemonstration}}\n") else: s.append("\n{{Environment|name="+ th_definition + \ "|title=" + th_title +\ "|content=") s.append(self.render_children_blocks(block)) s.append("}}\n") #exit from theorem ambient self.tree.exitTheorem() return '\n'.join(s) def r_proof(self, block): s=[] if self.configs['lang'] == 'it': if block.title !=None: s.append('\n{{InizioDimostrazione|titolo='+\ block.attributes['title']+ "}}") s.append(self.render_children_blocks(block)) s.append("{{FineDimostrazione}}\n") else: s.append('\n{{InizioDimostrazione}}') s.append(self.render_children_blocks(block)) s.append("{{FineDimostrazione}}\n") elif self.configs['lang'] == 'en': if block.title !=None: s.append('\n{{BeginProof|title='+\ block.attributes['title']+"}}") s.append(self.render_children_blocks(block)) s.append("{{EndProof}}\n") else: s.append('\n{{BeginProof}}') s.append(self.render_children_blocks(block)) s.append("{{EndProof}}\n") return '\n'.join(s) ######################################### #ACCENTED letters def r_accented_letter(self, block): if block.attributes["accent_type"] in ['"',"'","`"]: return block.attributes["letter"]+\ block.attributes["accent_type"] else: return block.attributes["letter"] diff --git a/texla/Renderers/Renderer.py b/texla/Renderers/Renderer.py index 31f6a13..1d56299 100644 --- a/texla/Renderers/Renderer.py +++ b/texla/Renderers/Renderer.py @@ -1,184 +1,185 @@ from ..Parser import Blocks from ..Parser.TreeExplorer import TreeExplorer import logging import importlib class Renderer(): def __init__(self): #hooks implemented directly by the Renderer class. self.render_hooks = {} #plugins hooks self.pre_render_hooks = {} self.post_render_hooks = {} self.start_hooks = [] self.end_hooks = [] self.loaded_plugins = {} self.used_tags = {} + self.tree_explorer = None def register_plugins(self, plugins): for plugin in plugins: module = importlib.import_module("..plugins"+'.'+ plugin, __name__) if hasattr(module, "plugin_render_hooks"): self.loaded_plugins[plugin] = module self.register_render_plugin_hooks(module.plugin_render_hooks) logging.info("Renderer.register_plugins "\ "@ Loaded plugin: {}".format(plugin)) logging.debug("Plugin {} render hooks: {}".format( plugin, list(module.plugin_render_hooks.keys()))) if hasattr(module, "plugin_lifecycle_hooks"): self.register_lifecyle_plugin_hooks(module.plugin_lifecycle_hooks) logging.info("Plugin {} lifecycle hooks: {}".format( plugin, list(module.plugin_lifecycle_hooks.keys()))) def register_render_plugin_hooks(self, hooks): '''This function registers the hooks for renderer plugins. The plugins can define hooks for pre and post render actions. The pre hook receives the block before the rendering and can only return the block itself, modified. The post hook receive the block and the text from the renderer: it has to return the final text only. The keyword ALL creates a hooks for all the blocks. Note that it is always called after all the other hooks.''' for bl in hooks: if "pre" in hooks[bl]: self.register_pre_renderer_hook(bl, hooks[bl]["pre"]) if "post" in hooks[bl]: self.register_post_renderer_hook(bl, hooks[bl]["post"]) #checking ALL keyword if "ALL" in hooks: if "pre" in hooks["ALL"]: self.register_pre_renderer_hook(bl, hooks["ALL"]["pre"]) if "post" in hooks["ALL"]: self.register_post_renderer_hook(bl, hooks["ALL"]["post"]) def register_lifecyle_plugin_hooks(self, hooks): ''' This function registers the hooks for the renderer lifecycle. Plugins can register hooks for the start and end actions. The start hook is called with the root_block of the chain. The end hook is called without arguments. These hooks must be used only to signal the actions to the plugins.''' if "start" in hooks: self.register_start_hook(hooks["start"]) if "end" in hooks: self.register_end_hook(hooks["end"]) def register_pre_renderer_hook(self, block, hook): if block not in self.pre_render_hooks: self.pre_render_hooks[block] = [] self.pre_render_hooks[block].append(hook) def register_post_renderer_hook(self, block, hook): if block not in self.post_render_hooks: self.post_render_hooks[block] = [] self.post_render_hooks[block].append(hook) def register_start_hook(self, hook): self.start_hooks.append(hook) def register_end_hook(self, hook): self.end_hooks.append(hook) def start_rendering(self, root_block): '''This function create a TreeExplorer instance and passes it to the plugin that has the variable needs_tree_explorer=True. Then it starts the plugins''' self.tree_explorer = TreeExplorer(root_block) #passing the tree_explorer for pl in self.loaded_plugins.values(): if hasattr(pl, "needs_tree_explorer"): if pl.needs_tree_explorer: logging.debug("Renderer @ Inserting "\ "TreeExplorer into plugin {}".format(pl)) pl.tree_explorer = self.tree_explorer #starting the plugins for hook in self.start_hooks: hook(root_block) def end_rendering(self): #ending plugins for hook in self.end_hooks: hook() def render_children_blocks(self, block, collapse=True): '''This is one of the most important funciont of the rendering process. This function takes all the children blocks of a block and get they rendering output. If collapsed=True it returns a unique string, otherwise it returns a list of tuples with[(block_name, output)] ''' output = [] for bl in block.ch_blocks: #it's not necessary checking for renderer_hook #because default hook is mandatory output.append((bl.block_name, self.render_block(bl))) logging.debug('Render.ch_blocks @ %s', output) if collapse: return ''.join([x[1] for x in output]) else: return output def render_block(self, bl): '''This function calls the right render_hook for the block. If there isn't an hook it calld the default, that is mandatory''' output = "" ######### pre hooks ############ #hooks executed in the order of inserction #They receive the block and they can only modify the block object if bl.block_name in self.pre_render_hooks: for prehook in self.pre_render_hooks[bl.block_name]: #calling prehook with the block prehook(bl) #calling after the others the ALL hooks if "ALL" in self.pre_render_hooks: for prehook in self.pre_render_hooks["ALL"]: #calling prehook with the block prehook(bl) ######## rendering ############# if bl.block_name in self.render_hooks: self.used_tag('ok | ' + bl.block_name) logging.debug('Render @ block: ' + bl.block_name) output = self.render_hooks[bl.block_name](bl) else: #default hook is mandatory self.used_tag('default | ' + bl.block_name) logging.debug('Render @ block: default@' + bl.block_name) output = self.render_hooks['default'](bl) ######## post hooks ########### #hooks executed in the order of inserction. #They receive the block and text. They have to return the #output text, that is passed to the next posthook if bl.block_name in self.post_render_hooks: for posthook in self.post_render_hooks[bl.block_name]: #calling posthook with the block and output output = posthook(bl, output) #calling ALL hooks after the others if "ALL" in self.post_render_hooks: for posthook in self.post_render_hooks["ALL"]: #calling posthook with the block and output output = posthook(bl, output) #final output return output def render_blocks(self, bls, collapse=False): '''This function renderes a list of blocks. It's the same as render_children_blocks but with a generic list''' output = [] for bl in bls: output.append((bl.block_name,self.render_block(bl))) if collapse: return ''.join([x[1] for x in output]) else: return output #Utils for debug def used_tag(self, tag): if tag in self.used_tags: self.used_tags[tag] += 1 else: self.used_tags[tag] = 1