File size: 4,900 Bytes
2cf467c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from bs4 import BeautifulSoup, NavigableString
from modules.infographics_generator.chat_utils import safe_save_json, load_txt

def parse_element_with_style_and_bbox(driver, web_element):
    computed_style = driver.execute_script("""
        const elem = arguments[0];
        const styles = window.getComputedStyle(elem);
        const style_dict = {};
        for (let i = 0; i < styles.length; i++) {
            const prop = styles[i];
            style_dict[prop] = styles.getPropertyValue(prop);
        }
        return style_dict;
    """, web_element)

    # only keep color-related attributes in style
    color_attributes = ['fill', 'stroke', 'opacity', 'fill-opacity', 'stroke-opacity', 'stroke-width']
    computed_style = {k: v for k, v in computed_style.items() if k in color_attributes}

    bbox = driver.execute_script("""
        const elem = arguments[0];
        try {
            const box = elem.getBoundingClientRect();
            return {x: box.x, y: box.y, width: box.width, height: box.height};
        } catch (e) {
            return null;
        }
    """, web_element)
    
    svg_bbox = driver.execute_script("""
        const elem = arguments[0];
        try {
            const box = elem.getBBox();
            return {x: box.x, y: box.y, width: box.width, height: box.height};
        } catch (e) {
            return null;
        }
    """, web_element)

    return computed_style, bbox, svg_bbox

def parse_svg_tree(driver, bs_element: BeautifulSoup, selenium_element):
    tag_name = bs_element.name
    assert not isinstance(bs_element, NavigableString), "bs_element should not be NavigableString"
    
    attributes = dict(bs_element.attrs)
    computed_style, bbox, svg_bbox = None, None, None
    if selenium_element:
        computed_style, bbox, svg_bbox = parse_element_with_style_and_bbox(driver, selenium_element)

    node_info = {
        "tag": tag_name,
        "attributes": attributes,
        "computed_style": computed_style,
        "bounding_box": bbox,
        "svg_bounding_box": svg_bbox,
        "html": str(bs_element),
        "children": []
    }

    if len(bs_element.find_all(recursive=False)) > 5000:
        print(f"--- {bs_element.name} has too many children: {len(bs_element.find_all(recursive=False))}")
        return node_info

    for child in bs_element.find_all(recursive=False):
        siblings = child.find_previous_siblings(child.name)
        index = len(siblings) + 1
        child_sele = selenium_element.find_element(By.XPATH, f'./*[local-name()="{child.name}"][{index}]')
        child_info = parse_svg_tree(driver, child, child_sele)
        if child_info:
            node_info["children"].append(child_info)
    if not node_info["children"]:
        # judge if has text
        text_content = bs_element.text.strip()
        if text_content:
            node_info["text"] = text_content


    return node_info

def parse_tree_from_html(driver: webdriver.Chrome, html_path: str, save_svg=True):
    driver.get(f'file://{html_path}')
    
    time.sleep(0.2)

    svg_element = driver.find_element("css selector", "svg")
    svg_content = svg_element.get_attribute('outerHTML')    
    if save_svg:
        svg_file_path = html_path.replace('.html', '_extracted.svg')
        with open(svg_file_path, 'w', encoding='utf-8') as f:
            f.write(svg_content)
    
    soup = BeautifulSoup(svg_content, "xml")
    svg_root = soup.find('svg')

    tree_data = parse_svg_tree(driver, svg_root, svg_element)
    safe_save_json(tree_data, html_path.replace('.html', '.json'))
    return tree_data

def convert_svg_to_html(svg_path: str, html_path: str):
    svg_content = load_txt(svg_path)
    html = f"""<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>Infographic Chart</title>
        <style>
            html, body {{
                margin: 0;
                padding: 0;
                background: #ffffff;
            }}
            svg {{
                display: block;
            }}
        </style>
    </head>
    <body>
        <div id="chart-container">
          {svg_content}
        </div>
    </body>
</html>
"""
    with open(html_path, 'w', encoding='utf-8') as f:
        f.write(html)
    return html

def convert_g_to_html(g_str, gw, gh, html_path: str):
    html = f"""<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>Infographic Chart</title>
    </head>
    <body>
        <div id="chart-container">
            <svg width="{gw}" height="{gh}">
                <g transform="translate({gw / 2}, {gh / 2})">
                    {g_str}
                </g>
            </svg>
        </div>
    </body>
</html>
"""
    with open(html_path, 'w', encoding='utf-8') as f:
        f.write(html)
    return html