diff --git a/main.py b/main.py
index be32efae02cf8ec5b4e5664fe9579f972c5f0ee4..195ea641f89b0d325774a1103b94a8f3a89e53c2 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()