From 311e4cb7da10a3f6ce736200b1dbd8086b61c4b6 Mon Sep 17 00:00:00 2001 From: Benedikt Seeger <benedikt.seeger@ptb.de> Date: Mon, 17 Jun 2024 09:33:06 +0200 Subject: [PATCH] added first version of working xml paser --- main.py | 173 +++++++++++++++++++++++++++++++++----------------------- 1 file changed, 103 insertions(+), 70 deletions(-) diff --git a/main.py b/main.py index be32efa..195ea64 100644 --- a/main.py +++ b/main.py @@ -20,13 +20,16 @@ import math import bokehCssPTB from urllib.parse import quote from dsiUnits import dsiUnit -from bokeh.plotting import curdoc,figure +from bokeh.plotting import curdoc, figure from bokeh.layouts import column, row -from bokeh.models import FileInput, Div, CustomJS, Button, TabPanel, Tabs, Dropdown, TextInput, Button, MathText, Label, Arrow, NormalHead,CheckboxGroup +from bokeh.models import FileInput, Div, CustomJS, Button, TabPanel, Tabs, TextInput, Label, Arrow, NormalHead, CheckboxGroup from bokeh.palettes import Category10 from bokeh.events import ValueSubmit import numpy as np -colors=Category10[10] +from base64 import b64decode +import XMLUnitExtractor + +colors = Category10[10] VERSION = "0.1.0" import socket @@ -65,18 +68,18 @@ If there was an error with the calculation, please fill out the table above. Fee *Free text comment*""" -labelOffsetX=[0.0,0.0,-0.02,0.07,0.02,-0.07] -labelOffsetY=[-0.02,0.09,0,0.07,0,0.07] -class dsiparserInput(): +labelOffsetX = [0.0, 0.0, -0.02, 0.07, 0.02, -0.07] +labelOffsetY = [-0.02, 0.09, 0, 0.07, 0, 0.07] - def __init__(self,defaultInput="",additionalComparisonCallbacks=[]): - self.additionalComparisonCallbacks=additionalComparisonCallbacks +class dsiparserInput(): + def __init__(self, defaultInput="", additionalComparisonCallbacks=[]): + self.additionalComparisonCallbacks = additionalComparisonCallbacks self.dsiInput = TextInput(value=defaultInput, title="DSI unit string:", width=500) self.dsiInput.on_event(ValueSubmit, self.parseInput) self.dsiSubmitButton = Button(label="Convert", button_type="primary") self.dsiSubmitButton.on_click(self.parseInput) - self.inputRow = row(children = [self.dsiInput, self.dsiSubmitButton], css_classes = ["textInputRow"]) - self.results = column(children = []) + self.inputRow = row(children=[self.dsiInput, self.dsiSubmitButton], css_classes=["textInputRow"]) + self.results = column(children=[]) self.widget = column(children=[self.inputRow, self.results], css_classes=["doubleColumn"]) self.valideUnit = False self.dsiTree = None @@ -199,49 +202,47 @@ class dsiCompGraphGen: y2 = quant2['coords'][1] name2 = quant2['name'] - if not name1+'_'+name2 in self.arrows: + if not name1 + '_' + name2 in self.arrows: nh = NormalHead(fill_color=colors[colIDX], fill_alpha=0.5, line_color=colors[colIDX]) - self.arrows[name1+'_'+name2]=Arrow(end=nh, line_color=colors[colIDX], line_dash=[15, 5],x_start=x1, y_start=y1, x_end=x2, y_end=y2) - self.plot.add_layout(self.arrows[name1+'_'+name2]) - scale12, baseUnit = quant1['baseUnit'].isScalablyEqualTo(quant2['baseUnit'],complete=complete)# TODO remove this un neescary reclaculation - #isSpecial, Latex = format_special_scale_factor(scale12) + self.arrows[name1 + '_' + name2] = Arrow(end=nh, line_color=colors[colIDX], line_dash=[15, 5], x_start=x1, y_start=y1, x_end=x2, y_end=y2) + self.plot.add_layout(self.arrows[name1 + '_' + name2]) + scale12, baseUnit = quant1['baseUnit'].isScalablyEqualTo(quant2['baseUnit'], complete=complete) isSpecial = False - Latex="" + Latex = "" if isSpecial: - text="{:.4g}".format(scale12)+" = "+Latex + text = "{:.4g}".format(scale12) + " = " + Latex else: - text="{:.4g}".format(scale12) + text = "{:.4g}".format(scale12) if not name1 + '_' + name2 in self.scalFactorLables: - angle_deg = -1*np.arctan2(y2 - y1, x2 - x1) - if abs(angle_deg)>np.pi/8: - angle_deg+=-np.pi/2 - self.scalFactorLables[name1 + '_' + name2]=Label(x=np.abs(x1-x2)/2+np.min([x1,x2])+labelOffsetX[colIDX], y=np.abs(y1-y2)/2+np.min([y1,y2])+labelOffsetY[colIDX], text=text, text_font_size="24px", text_baseline=unitToDraw['text_baseLine'], text_align=unitToDraw['text_align'],text_color=colors[colIDX],angle=angle_deg) + angle_deg = -1 * np.arctan2(y2 - y1, x2 - x1) + if abs(angle_deg) > np.pi / 8: + angle_deg += -np.pi / 2 + self.scalFactorLables[name1 + '_' + name2] = Label(x=np.abs(x1 - x2) / 2 + np.min([x1, x2]) + labelOffsetX[colIDX], y=np.abs(y1 - y2) / 2 + np.min([y1, y2]) + labelOffsetY[colIDX], text=text, text_font_size="24px", text_baseline=unitToDraw['text_baseLine'], text_align=unitToDraw['text_align'], text_color=colors[colIDX], angle=angle_deg) self.plot.add_layout(self.scalFactorLables[name1 + '_' + name2]) else: - self.scalFactorLables[name1 + '_' + name2].text=text + self.scalFactorLables[name1 + '_' + name2].text = text if not name2 + '_' + name1 in self.arrows: - nh = NormalHead(fill_color=colors[colIDX+1], fill_alpha=0.5, line_color=colors[colIDX+1]) - self.arrows[name2+'_'+name1]=Arrow(end=nh, line_color=colors[colIDX+1], line_dash=[15, 5], x_start=x2, y_start=y2+0.05, x_end=x1, y_end=y1+0.05) - self.plot.add_layout(self.arrows[name2+'_'+name1]) - - scale21, baseUnit = quant2['baseUnit'].isScalablyEqualTo(quant1['baseUnit'],complete=complete) - #isSpecial, Latex = format_special_scale_factor(scale21) - isSpecial=False - Latex="" + nh = NormalHead(fill_color=colors[colIDX + 1], fill_alpha=0.5, line_color=colors[colIDX + 1]) + self.arrows[name2 + '_' + name1] = Arrow(end=nh, line_color=colors[colIDX + 1], line_dash=[15, 5], x_start=x2, y_start=y2 + 0.05, x_end=x1, y_end=y1 + 0.05) + self.plot.add_layout(self.arrows[name2 + '_' + name1]) + + scale21, baseUnit = quant2['baseUnit'].isScalablyEqualTo(quant1['baseUnit'], complete=complete) + isSpecial = False + Latex = "" if isSpecial: - text="{:.4g}".format(scale21)+" = "+Latex + text = "{:.4g}".format(scale21) + " = " + Latex else: - text="{:.4g}".format(scale21) + text = "{:.4g}".format(scale21) if not name2 + '_' + name1 in self.scalFactorLables: - angle_deg = -1*np.arctan2(y2 - y1, x2 - x1) - if abs(angle_deg)>np.pi/8: - angle_deg+=-np.pi/2 - self.scalFactorLables[name2 + '_' + name1]=Label(x=np.abs(x1-x2)/2+np.min([x1,x2])+labelOffsetX[colIDX+1], y=np.abs(y1-y2)/2+np.min([y1,y2])+labelOffsetY[colIDX+1], text=text, text_font_size="24px", text_baseline=unitToDraw['text_baseLine'], text_align=unitToDraw['text_align'],text_color=colors[colIDX+1],angle=angle_deg) + angle_deg = -1 * np.arctan2(y2 - y1, x2 - x1) + if abs(angle_deg) > np.pi / 8: + angle_deg += -np.pi / 2 + self.scalFactorLables[name2 + '_' + name1] = Label(x=np.abs(x1 - x2) / 2 + np.min([x1, x2]) + labelOffsetX[colIDX + 1], y=np.abs(y1 - y2) / 2 + np.min([y1, y2]) + labelOffsetY[colIDX + 1], text=text, text_font_size="24px", text_baseline=unitToDraw['text_baseLine'], text_align=unitToDraw['text_align'], text_color=colors[colIDX + 1], angle=angle_deg) self.plot.add_layout(self.scalFactorLables[name2 + '_' + name1]) else: - self.scalFactorLables[name2+ '_' + name1].text=text - colIDX+=2 + self.scalFactorLables[name2 + '_' + name1].text = text + colIDX += 2 class page(): def __init__(self): @@ -249,62 +250,95 @@ class page(): curdoc().title = "DSI to Latex" curdoc().add_root(bokehCssPTB.getStyleDiv()) curdoc().theme = bokehCssPTB.getTheme() - self.dsiInput1 = dsiparserInput(defaultInput="\\milli\\newton\\metre",additionalComparisonCallbacks=[self.clearComparison,self.tryComparison]) - self.dsiInput2 = dsiparserInput(defaultInput="\\kilo\\joule",additionalComparisonCallbacks=[self.clearComparison,self.tryComparison]) - self.inputs=row([self.dsiInput1.widget, self.dsiInput2.widget]) - curdoc().add_root(self.inputs) + # Tab 1: DSI Parser and Comparator + self.dsiInput1 = dsiparserInput(defaultInput="\\milli\\newton\\metre", additionalComparisonCallbacks=[self.clearComparison, self.tryComparison]) + self.dsiInput2 = dsiparserInput(defaultInput="\\kilo\\joule", additionalComparisonCallbacks=[self.clearComparison, self.tryComparison]) + self.inputs = row([self.dsiInput1.widget, self.dsiInput2.widget]) self.comapreButton = Button(label="Compare", button_type="primary") self.comapreButton.on_click(self.compare) LABELS = ['Complete Comparison'] self.completeComCBGPR = CheckboxGroup(labels=LABELS, active=[]) - self.compaReresult = Div(text = "", css_classes = ["msg-positive"],visible=False) - self.compareRow = row(children = [self.comapreButton,self.completeComCBGPR,self.compaReresult], css_classes = ["textInputRow"]) - curdoc().add_root(self.compareRow) - self.dsiCompGraphGen=dsiCompGraphGen(self.dsiInput1,self.dsiInput2) - curdoc().add_root(self.dsiCompGraphGen.widget) + self.compaReresult = Div(text="", css_classes=["msg-positive"], visible=False) + self.compareRow = row(children=[self.comapreButton, self.completeComCBGPR, self.compaReresult], css_classes=["textInputRow"]) + self.dsiCompGraphGen = dsiCompGraphGen(self.dsiInput1, self.dsiInput2) self.createIssueButton = Button(label="Report conversion error", disabled=True) - # self.createIssueButton.on_click(self.createIssueUrl) - curdoc().add_root(self.dsiCompGraphGen.widget) - curdoc().add_root(self.createIssueButton) + tab1_layout = column(self.inputs, self.compareRow, self.dsiCompGraphGen.widget, self.createIssueButton) + tab1 = TabPanel(child=tab1_layout, title="DSI Parser and Comparator") + + # Tab 2: XML Unit Validator + self.upload_widget = FileInput(accept=".xml") + self.upload_widget.on_change('value', self.process_xml) + self.valid_units_message = Div(text="No valid units found", css_classes=["msg-negative"]) + self.invalid_units_column = column() + tab2_layout = column(self.upload_widget, self.valid_units_message, self.invalid_units_column) + tab2 = TabPanel(child=tab2_layout, title="XML Unit Validator") + + tabs = Tabs(tabs=[tab1, tab2]) + curdoc().add_root(tabs) + + def process_xml(self, attr, old, new): + decoded = b64decode(new).decode('utf-8') + result = XMLUnitExtractor.parse_and_process(decoded) + valid_units = result['valid_units'] + invalid_units = result['invalid_units'] + + # Update valid units message + if valid_units: + self.valid_units_message.text = f"{len(valid_units)} valid units found" + self.valid_units_message.css_classes = ["msg-positive"] + else: + self.valid_units_message.text = "No valid units found" + self.valid_units_message.css_classes = ["msg-negative"] + + # Update invalid units column + self.invalid_units_column.children = [] + for line_num, details in invalid_units.items(): + warnings_div = column([Div(text=warning) for warning in details.get("warnings", [])]) + parsedunit=dsiUnit(details["unit"]) + unit_div = row([ + Div(text=str(line_num)), + Div(text=details["unit"]), + Div(text=parsedunit.toLatex()), + warnings_div + ]) + self.invalid_units_column.children.append(unit_div) def compare(self): self.dsiInput1.parseInput() self.dsiInput2.parseInput() - completeConversion=self.completeComCBGPR.active==[0] + completeConversion = self.completeComCBGPR.active == [0] try: - scalfactor,baseUnit=self.dsiInput1.dsiTree.isScalablyEqualTo(self.dsiInput2.dsiTree,complete=completeConversion) + scalfactor, baseUnit = self.dsiInput1.dsiTree.isScalablyEqualTo(self.dsiInput2.dsiTree, complete=completeConversion) if not math.isnan(scalfactor): - self.compaReresult.text = "The two units are equal up to a scaling factor of "+str(scalfactor)+" and a base unit of "+str(baseUnit) - self.compaReresult.css_classes=["msg-positive"] + self.compaReresult.text = "The two units are equal up to a scaling factor of " + str(scalfactor) + " and a base unit of " + str(baseUnit) + self.compaReresult.css_classes = ["msg-positive"] else: self.compaReresult.text = "The two units are not equal" self.compaReresult.css_classes = ["msg-negative"] if self.dsiInput1.valideUnit and self.dsiInput2.valideUnit: - self.dsiCompGraphGen.reDraw(self.dsiInput1.dsiTree,self.dsiInput2.dsiTree,complete=completeConversion) + self.dsiCompGraphGen.reDraw(self.dsiInput1.dsiTree, self.dsiInput2.dsiTree, complete=completeConversion) else: self.dsiCompGraphGen.flush() except AttributeError as Ae: - warnings.warn("AttributeError: "+str(Ae)) + warnings.warn("AttributeError: " + str(Ae)) self.compaReresult.text = "The two units are not equal" self.compaReresult.css_classes = ["msg-negative"] self.dsiCompGraphGen.flush() self.compaReresult.visible = True - self.createIssueButton.disabled=False - self.createIssueButton.button_type="danger" - self.createIssueButton.js_on_event("button_click",CustomJS(code=f"window.open('{self.createIssueUrl()}', '_blank');")) + self.createIssueButton.disabled = False + self.createIssueButton.button_type = "danger" + self.createIssueButton.js_on_event("button_click", CustomJS(code=f"window.open('{self.createIssueUrl()}', '_blank');")) def createIssueUrl(self): - issueArgs=[self.dsiInput1.dsiInput.value,str(self.dsiInput1.dsiTree),self.dsiInput2.dsiInput.value,str(self.dsiInput2.dsiTree)] - comGenAtrssFroIssue=['baseUnit', 'scalfactorAB', 'scalfactorBA', 'scalfactorABase', 'scalfactorBaseA', 'scalfactorBBase', 'scalfactorBaseB'] + issueArgs = [self.dsiInput1.dsiInput.value, str(self.dsiInput1.dsiTree), self.dsiInput2.dsiInput.value, str(self.dsiInput2.dsiTree)] + comGenAtrssFroIssue = ['baseUnit', 'scalfactorAB', 'scalfactorBA', 'scalfactorABase', 'scalfactorBaseA', 'scalfactorBBase', 'scalfactorBaseB'] for comGenAtrss in comGenAtrssFroIssue: try: - issueArgs.append(str(getattr(self.dsiCompGraphGen,comGenAtrss))) + issueArgs.append(str(getattr(self.dsiCompGraphGen, comGenAtrss))) except AttributeError as Ae: - issueArgs.append("AttributeError: "+str(Ae)) - #quantitiesToAdd=[self.dsiInput1.dsiInput.value,str(self.dsiInput1.dsiTree),self.dsiInput2.dsiInput.value,str(self.dsiInput2.dsiTree),str(self.dsiCompGraphGen.baseUnit),self.dsiCompGraphGen.scalfactorAB,self.dsiCompGraphGen.scalfactorBA,self.dsiCompGraphGen.scalfactorABase,self.dsiCompGraphGen.scalfactorBaseA,self.dsiCompGraphGen.scalfactorBBase,self.dsiCompGraphGen.scalfactorBaseB] - #issueTemplate=open('./issue.md').read() #TODO add file inculde instead of the str.... - filledResult=issueTemplate.format(*issueArgs) + issueArgs.append("AttributeError: " + str(Ae)) + filledResult = issueTemplate.format(*issueArgs) filledTitle = f'Unexpected comparison result: {self.dsiInput1.dsiInput.value} to {self.dsiInput2.dsiInput.value}' issueUrl = r'https://gitlab1.ptb.de/digitaldynamicmeasurement/dsi-parser-frontend/-/issues/new?' title = quote(filledTitle) @@ -315,13 +349,12 @@ class page(): def clearComparison(self): self.dsiCompGraphGen.flush() self.compaReresult.text = "" - self.createIssueButton.disabled=True - self.createIssueButton.button_type="primary" + self.createIssueButton.disabled = True + self.createIssueButton.button_type = "primary" self.compaReresult.visible = False def tryComparison(self): if self.dsiInput1.valideUnit and self.dsiInput2.valideUnit: self.compare() - thisPage = page() -- GitLab