33from rich .panel import Panel
44from rich .table import Table
55import re
6+ import xml .etree .ElementTree as ET
67
78def validate_workflow (workflow_file , console ):
89 workflow_path = Path (workflow_file )
@@ -22,15 +23,34 @@ def validate_workflow(workflow_file, console):
2223 errors .append ("File is empty" )
2324 return show_results (console , errors , warnings , info )
2425
26+ # strict XML syntax check
27+ try :
28+ ET .fromstring (content )
29+ except ET .ParseError as e :
30+ errors .append (f"Invalid XML: { str (e )} " )
31+ return show_results (console , errors , warnings , info )
32+
2533 try :
2634 soup = BeautifulSoup (content , 'xml' )
2735 except Exception as e :
2836 errors .append (f"Invalid XML: { str (e )} " )
2937 return show_results (console , errors , warnings , info )
3038
31- if not soup .find ('graphml' ):
39+ root = soup .find ('graphml' )
40+ if not root :
3241 errors .append ("Not a valid GraphML file - missing <graphml> root element" )
3342 return show_results (console , errors , warnings , info )
43+
44+ # check the graph attributes
45+ graph = soup .find ('graph' )
46+ if not graph :
47+ errors .append ("Missing <graph> element" )
48+ else :
49+ edgedefault = graph .get ('edgedefault' )
50+ if not edgedefault :
51+ errors .append ("Graph missing required 'edgedefault' attribute" )
52+ elif edgedefault not in ['directed' , 'undirected' ]:
53+ errors .append (f"Invalid edgedefault value '{ edgedefault } ' (must be 'directed' or 'undirected')" )
3454
3555 nodes = soup .find_all ('node' )
3656 edges = soup .find_all ('edge' )
@@ -47,8 +67,19 @@ def validate_workflow(workflow_file, console):
4767
4868 node_labels = []
4969 for node in nodes :
70+ #check the node id
71+ node_id = node .get ('id' )
72+ if not node_id :
73+ errors .append ("Node missing required 'id' attribute" )
74+ #skip further checks for this node to avoid noise
75+ continue
76+
5077 try :
78+ #robust find: try with namespace prefix first, then without
5179 label_tag = node .find ('y:NodeLabel' )
80+ if not label_tag :
81+ label_tag = node .find ('NodeLabel' )
82+
5283 if label_tag and label_tag .text :
5384 label = label_tag .text .strip ()
5485 node_labels .append (label )
@@ -60,13 +91,13 @@ def validate_workflow(workflow_file, console):
6091 if len (parts ) != 2 :
6192 warnings .append (f"Node '{ label } ' has invalid format" )
6293 else :
63- node_id , filename = parts
94+ nodeId_part , filename = parts
6495 if not filename :
6596 errors .append (f"Node '{ label } ' has no filename" )
6697 elif not any (filename .endswith (ext ) for ext in ['.py' , '.cpp' , '.m' , '.v' , '.java' ]):
6798 warnings .append (f"Node '{ label } ' has unusual file extension" )
6899 else :
69- warnings .append (f"Node { node . get ( 'id' , 'unknown' ) } has no label" )
100+ warnings .append (f"Node { node_id } has no label" )
70101 except Exception as e :
71102 warnings .append (f"Error parsing node: { str (e )} " )
72103
@@ -91,6 +122,9 @@ def validate_workflow(workflow_file, console):
91122 for edge in edges :
92123 try :
93124 label_tag = edge .find ('y:EdgeLabel' )
125+ if not label_tag :
126+ label_tag = edge .find ('EdgeLabel' )
127+
94128 if label_tag and label_tag .text :
95129 if edge_label_regex .match (label_tag .text .strip ()):
96130 zmq_edges += 1
0 commit comments