File size: 9,492 Bytes
ba6e49b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
import gradio as gr
import os
import tempfile
import shutil
from pathlib import Path
import time

# Import our modules
from ocr_strategies import OCRFactory
from core_cleaner import XMLCleanerCore
from visualizer import XMLTreeVisualizer, BoundingBoxVisualizer

# Initialize Logic Classes
cleaner_core = XMLCleanerCore()
tree_viz = XMLTreeVisualizer()
bbox_viz = BoundingBoxVisualizer()

def process_pipeline(image_file, xml_file, ocr_choice, visible_text_input, progress=gr.Progress()):
    # 1. Validation
    if xml_file is None:
        raise gr.Error("Please upload XML file.")
    
    # Check if we need image (only if visible text is not provided)
    use_ocr = not (visible_text_input and visible_text_input.strip())
    if use_ocr and image_file is None:
        raise gr.Error("Please upload Image file when using OCR, or provide visible text manually.")
        
    start_time = time.time()
    
    # 2. Setup Paths (Safe Temp Files)
    temp_dir = Path(tempfile.gettempdir())
    unique_id = str(int(time.time()))
    
    # Paths for outputs
    cleaned_xml_path = temp_dir / f"cleaned_{unique_id}.xml"
    
    img_viz_before = temp_dir / f"bbox_before_{unique_id}.png"
    img_viz_after = temp_dir / f"bbox_after_{unique_id}.png"
    tree_viz_before = temp_dir / f"tree_before_{unique_id}.png"
    tree_viz_after = temp_dir / f"tree_after_{unique_id}.png"
    
    # 3. Text Extraction Stage (OCR or Manual Input)
    text_source = None
    if visible_text_input and visible_text_input.strip():
        # Use provided visible text - NO OCR NEEDED
        progress(0.2, desc="Using provided visible text (OCR skipped)...")
        # Convert input text to set of strings (split by newlines or commas)
        lines = [line.strip() for line in visible_text_input.replace(',', '\n').split('\n') if line.strip()]
        visible_text = {line.lower().strip() for line in lines if line.strip()}
        text_source = "Manual Input"
    else:
        # Use OCR - image is required here
        progress(0.2, desc="Running OCR on image...")
        ocr_engine = OCRFactory.get_strategy(ocr_choice)
        visible_text = ocr_engine.extract_text(image_file)
        text_source = ocr_choice
    
    # 4. XML Parsing & Detection
    progress(0.4, desc="Parsing XML...")
    tree, root, parent_map = cleaner_core.parse_xml(xml_file)
    
    progress(0.5, desc="Detecting Stale Elements...")
    active, stale = cleaner_core.find_active_and_stale(root, visible_text)
    
    # 5. Pruning
    progress(0.6, desc="Pruning Tree...")
    removed_count = 0
    if stale:
        removed_count = cleaner_core.prune_stale_subtrees(root, active, stale, parent_map)
    
    # Save Cleaned XML
    tree.write(str(cleaned_xml_path))
    
    # 6. Visualization Generation
    progress(0.7, desc="Generating Visualizations...")
    
    # Bounding Boxes (only if image is provided)
    if image_file is not None:
        bbox_viz.visualize(image_file, xml_file, str(img_viz_before))
        bbox_viz.visualize(image_file, str(cleaned_xml_path), str(img_viz_after))
    else:
        # Create placeholder images or skip
        img_viz_before = None
        img_viz_after = None
    
    # Trees
    progress(0.8, desc="Drawing Trees (This might take a moment)...")
    # Before: no highlights
    tree_viz.visualize(xml_file, str(tree_viz_before), visible_text=None, active_elements=None)
    # After: highlight active elements (OCR matched nodes)
    active_elements_set = set(active) if active else set()
    tree_viz.visualize(str(cleaned_xml_path), str(tree_viz_after), visible_text, active_elements_set)
    
    # 7. Stats
    total_time = time.time() - start_time
    stats_md = f"""
    ### 📊 Process Statistics
    
    | Metric | Result |
    | :--- | :--- |
    | **Text Source** | {text_source} |
    | **Elements Removed** | `{removed_count}` |
    | **Active Elements** | `{len(active)}` |
    | **Stale Elements** | `{len(stale)}` |
    | **Processing Time** | `{total_time:.2f}s` |
    """
    
    ocr_text_display = "\n".join(sorted(list(visible_text)))
    
    progress(1.0, desc="Done!")
    
    return (
        str(tree_viz_before),
        str(tree_viz_after),
        str(img_viz_before) if img_viz_before else None,
        str(img_viz_after) if img_viz_after else None,
        stats_md,
        ocr_text_display,
        str(cleaned_xml_path)
    )

# --- Gradio UI Layout ---
custom_css = """
.container { max-width: 1100px; margin: auto; }
.header { text-align: center; margin-bottom: 20px; }
.stat-box { border: 1px solid #ddd; padding: 10px; border-radius: 8px; background: #f9f9f9; }
"""

with gr.Blocks() as app:
    
    with gr.Row():
        gr.Markdown(
            """
            # 🌳 XML Cleaner & Visualizer Studio
            **Optimize Mobile UI XMLs** by removing invisible/stale nodes using OCR-based or manual text input for sibling pruning.
            """, 
            elem_classes="header"
        )
        
    with gr.Row():
        # --- Left Panel: Inputs ---
        with gr.Column(scale=1, variant="panel"):
            gr.Markdown("### 1. Upload Data")
            img_input = gr.Image(type="filepath", label="Screenshot (PNG/JPG)")
            gr.Markdown("*Optional if visible text is provided below*")
            xml_input = gr.File(label="XML Layout Dump", file_types=[".xml"])
            
            gr.Markdown("### 2. Visible Text (Optional)")
            visible_text_input = gr.TextArea(
                label="Visible Text",
                placeholder="Enter visible text from the screenshot (one per line or comma-separated). Leave empty to use OCR.",
                lines=5,
                info="If provided, this text will be used instead of OCR. Otherwise, OCR will be used automatically."
            )
            
            # Status indicator for text input mode
            text_input_status = gr.Markdown("", visible=False)
            
            gr.Markdown("### 3. Settings")
            ocr_selector = gr.Dropdown(
                choices=["EasyOCR (Best Accuracy)", "Tesseract (Fast & Free)"],
                value="EasyOCR (Best Accuracy)",
                label="OCR Engine (Fallback)",
                info="Used only if visible text is not provided above.",
                interactive=True
            )
            
            btn_run = gr.Button("✨ Run Analysis & Clean", variant="primary", size="lg")

        # --- Right Panel: Outputs ---
        with gr.Column(scale=2):
            gr.Markdown("### 4. Analysis Results")
            
            # Stats Area
            stats_output = gr.Markdown()
            
            # Visualization Tabs
            with gr.Tabs():
                with gr.TabItem("🌳 Tree Structure"):
                    gr.Markdown("*Left: Original XML | Right: Cleaned XML (Active Nodes Highlighted)*")
                    with gr.Row():
                        out_tree_before = gr.Image(label="Before Pruning", type="filepath")
                        out_tree_after = gr.Image(label="After Pruning", type="filepath")
                
                with gr.TabItem("🖼️ Bounding Boxes"):
                    gr.Markdown("*Visualizing XML bounds on the screenshot*")
                    with gr.Row():
                        out_bbox_before = gr.Image(label="Original Bounds", type="filepath")
                        out_bbox_after = gr.Image(label="Cleaned Bounds", type="filepath")
                
                with gr.TabItem("📝 OCR Data"):
                    out_ocr_text = gr.TextArea(label="Detected Text", lines=10, interactive=False)
            
            # Download
            gr.Markdown("### 5. Export")
            out_file = gr.File(label="Download Cleaned XML")

    # Function to toggle OCR selector and image input based on visible text input
    def toggle_ocr_selector(visible_text):
        """Disable OCR selector if visible text is provided, enable if empty"""
        if visible_text and visible_text.strip():
            return (
                gr.update(
                    label="OCR Engine (Disabled - Using Manual Text)",
                    info="⚠️ OCR is disabled because visible text is provided above.",
                    interactive=False
                ),
                gr.update(value="✅ **Using Manual Text Input** - OCR is disabled. Image is optional.", visible=True),
                gr.update(label="Screenshot (PNG/JPG) - Optional")
            )
        else:
            return (
                gr.update(
                    label="OCR Engine",
                    info="Select OCR engine to extract visible text from the screenshot.",
                    interactive=True
                ),
                gr.update(value="", visible=False),
                gr.update(label="Screenshot (PNG/JPG) - Required")
            )
    
    # Wire Interactions
    # Update OCR selector and image input when visible text changes
    visible_text_input.change(
        fn=toggle_ocr_selector,
        inputs=[visible_text_input],
        outputs=[ocr_selector, text_input_status, img_input]
    )
    
    btn_run.click(
        fn=process_pipeline,
        inputs=[img_input, xml_input, ocr_selector, visible_text_input],
        outputs=[
            out_tree_before, out_tree_after, 
            out_bbox_before, out_bbox_after, 
            stats_output, out_ocr_text, out_file
        ]
    )

if __name__ == "__main__":
    app.launch(css=custom_css, theme=gr.themes.Soft())