doc2dict 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: doc2dict
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Requires-Python: >=3.8
@@ -0,0 +1,7 @@
1
+ from .html.html_parser import html_reduction
2
+ from .html.visualizer import format_list_html
3
+ from .xml.parser import xml2dict
4
+ from .txt.parser import txt2dict
5
+ from .dict2dict import dict2dict
6
+
7
+ __all__ = ['html_reduction', 'format_list_html', 'xml2dict', 'txt2dict','dict2dict']
@@ -0,0 +1,50 @@
1
+ def dict2dict(data):
2
+ result = {}
3
+
4
+ def process_item(item):
5
+ # If item is a string, return it directly
6
+ if isinstance(item, str):
7
+ return item.strip()
8
+
9
+ # If item is not a dict, return string version
10
+ if not isinstance(item, dict):
11
+ return str(item).strip()
12
+
13
+ # Base case: if there's no further content, return the item itself
14
+ if 'content' not in item:
15
+ return item
16
+
17
+ # If there's a text key, use it as the dict key, otherwise use the type
18
+ key = item.get('text', item.get('type', ''))
19
+
20
+ # Process the content
21
+ if isinstance(item['content'], list):
22
+ # Check if content contains dictionaries with type/text
23
+ if any(isinstance(x, dict) and ('type' in x or 'text' in x) for x in item['content']):
24
+ nested_result = {}
25
+ for content_item in item['content']:
26
+ if isinstance(content_item, dict):
27
+ nested_key = content_item.get('text', content_item.get('type', ''))
28
+ nested_result[nested_key] = process_item(content_item)
29
+ return nested_result
30
+ # If content items are simple values (strings/numbers), join with newlines and strip
31
+ else:
32
+ return '\n'.join(str(x) for x in item['content']).strip()
33
+ else:
34
+ return str(item['content']).strip()
35
+
36
+ # Handle case where data itself might be a string
37
+ if isinstance(data, str):
38
+ return data.strip()
39
+
40
+ # Handle case where content is a list directly
41
+ if isinstance(data.get('content', []), list):
42
+ for item in data['content']:
43
+ if isinstance(item, dict):
44
+ key = item.get('text', item.get('type', ''))
45
+ result[key] = process_item(item)
46
+ else:
47
+ # If we have a string in content, use it as both key and value
48
+ result[str(item).strip()] = str(item).strip()
49
+
50
+ return result
File without changes
@@ -0,0 +1,294 @@
1
+ import re
2
+
3
+ def iterwalk(root, include_text):
4
+ def walk(node, inherited_styles=None, inherited_text_style=None):
5
+ current_styles = dict(inherited_styles or {})
6
+ node_styles = get_node_styles(node)
7
+ current_styles.update(node_styles)
8
+
9
+ if current_styles.get('display', [''])[0] == 'none':
10
+ return
11
+
12
+ text_content = node.text_content
13
+ if text_content:
14
+ text_transform = current_styles.get('text-transform', [''])[0]
15
+ if text_transform == 'uppercase':
16
+ text_content = text_content.upper()
17
+ elif text_transform == 'lowercase':
18
+ text_content = text_content.lower()
19
+ elif text_transform == 'capitalize':
20
+ text_content = text_content.capitalize()
21
+
22
+ text_style = get_text_style(node, current_styles, inherited_text_style)
23
+
24
+ yield ("start", node, current_styles, text_content, text_style)
25
+ for child in node.iter(include_text=include_text):
26
+ if child is not node:
27
+ yield from walk(child, current_styles, text_style)
28
+ yield ("end", node, current_styles, text_content, text_style)
29
+
30
+ for node in root.iter(include_text=include_text):
31
+ if node is not root:
32
+ yield from walk(node)
33
+
34
+ class Ctx:
35
+ def __init__(self, base=16.0):
36
+ self.base = base # base font size
37
+ self.vh = 900 # viewport height
38
+ self.vw = 1600 # viewport width
39
+
40
+ class Box:
41
+ def __init__(self):
42
+ self.mt = self.mb = self.pt = self.pb = self.bt = self.bb = 0 # margins, padding, borders
43
+ self.ch = 0 # content height
44
+
45
+ @property
46
+ def height(self):
47
+ return self.mt + self.pt + self.bt + self.ch + self.mb + self.pb + self.bb
48
+
49
+ def parse_val(val, ctx, parent):
50
+ """Parse CSS values with unit support"""
51
+ if not val or val == 'auto' or val == 'none': return 0
52
+
53
+ try:
54
+ m = re.match(r'^(-?\d*\.?\d+)(\D*)$', val.strip())
55
+ if not m: return 0
56
+
57
+ n, u = float(m.group(1)), m.group(2)
58
+
59
+ units = {
60
+ '': n,
61
+ 'px': n,
62
+ 'em': n * parent,
63
+ 'rem': n * ctx.base,
64
+ '%': (n/100) * parent,
65
+ 'vh': (n/100) * ctx.vh,
66
+ 'vw': (n/100) * ctx.vw,
67
+ 'pt': n * 1.333333,
68
+ 'pc': n * 16,
69
+ 'in': n * 96,
70
+ 'cm': n * 37.795276,
71
+ 'mm': n * 3.779528
72
+ }
73
+
74
+ return units.get(u, 0)
75
+ except:
76
+ return 0
77
+
78
+ def parse_border(val, ctx, parent):
79
+ """Parse border values"""
80
+ if not val: return 0
81
+ return next((parse_val(p, ctx, parent) for p in val.split() if re.match(r'^\d', p)), 0)
82
+
83
+ def calc_line_height(fs, lh):
84
+ """Calculate line height"""
85
+ if not lh: return fs # Default to font size
86
+
87
+ try:
88
+ if isinstance(lh, (int, float)):
89
+ return fs * float(lh) # multiplier
90
+ elif lh.endswith('px'):
91
+ return float(lh[:-2])
92
+ elif lh.endswith('%'):
93
+ return fs * (float(lh[:-1])/100)
94
+ return fs # Default to font size for any other value
95
+ except:
96
+ return fs
97
+
98
+ class StyleChain:
99
+ def __init__(self, ctx):
100
+ self.ctx = ctx
101
+ self.chain = []
102
+
103
+ def push(self, s): self.chain.append(s)
104
+ def pop(self): self.chain.pop() if self.chain else None
105
+
106
+ def get(self, prop, default=''):
107
+ """Get computed style"""
108
+ for s in reversed(self.chain):
109
+ if prop in s: return s[prop][0]
110
+ return default
111
+
112
+ def font_size(self):
113
+ """Calculate font size through chain"""
114
+ size = self.ctx.base
115
+ for s in self.chain:
116
+ if 'font-size' in s:
117
+ size = parse_val(s['font-size'][0], self.ctx, size)
118
+ return size
119
+
120
+ def get_box_metrics(styles, chain):
121
+ """Calculate box model metrics"""
122
+ ctx = chain.ctx
123
+ fs = chain.font_size()
124
+ box = Box()
125
+
126
+ # Get box properties
127
+ props = {
128
+ 'margin-top': 'mt', 'margin-bottom': 'mb',
129
+ 'padding-top': 'pt', 'padding-bottom': 'pb'
130
+ }
131
+
132
+ for css_prop, box_prop in props.items():
133
+ if css_prop in styles:
134
+ setattr(box, box_prop, parse_val(styles[css_prop][0], ctx, fs))
135
+
136
+ # Borders
137
+ if 'border-top' in styles: box.bt = parse_border(styles['border-top'][0], ctx, fs)
138
+ if 'border-bottom' in styles: box.bb = parse_border(styles['border-bottom'][0], ctx, fs)
139
+
140
+ # Content height
141
+ box.ch = calc_line_height(fs, chain.get('line-height'))
142
+
143
+ # Handle border-box
144
+ if chain.get('box-sizing') == 'border-box':
145
+ extra = box.pt + box.pb + box.bt + box.bb
146
+ box.ch = max(0, box.ch - extra)
147
+
148
+ return box
149
+
150
+ def process_height(node, chain):
151
+ """Process element height"""
152
+ styles = get_node_styles(node)
153
+ chain.push(styles)
154
+
155
+ try:
156
+ box = get_box_metrics(styles, chain)
157
+ fs = chain.font_size()
158
+
159
+ return {
160
+ 'font_size': round(fs, 2),
161
+ 'content_height': round(box.ch, 2),
162
+ 'total_height': round(box.ch, 2), # Only use content height
163
+ 'box': {
164
+ 'mt': 0, 'mb': 0,
165
+ 'pt': 0, 'pb': 0,
166
+ 'bt': 0, 'bb': 0
167
+ }
168
+ }
169
+ finally:
170
+ chain.pop()
171
+
172
+ def standardize_css(style):
173
+ if not style:
174
+ return {}
175
+ return {
176
+ k.strip(): v.strip().split()
177
+ for k, v in (pair.split(':') for pair in style.split(';') if ':' in pair)
178
+ if k.strip() and v.strip()
179
+ }
180
+
181
+ def get_node_styles(node):
182
+ style = node.attributes.get('style')
183
+ if not style:
184
+ return {}
185
+ return standardize_css(style)
186
+
187
+ def get_text_style(node, styles, inherited_style=None):
188
+ text_style = inherited_style.copy() if inherited_style else {
189
+ 'bold': False,
190
+ 'italic': False,
191
+ 'underline': False
192
+ }
193
+
194
+ if node.tag in {'b', 'strong'}: text_style['bold'] = True
195
+ if node.tag in {'i', 'em'}: text_style['italic'] = True
196
+ if node.tag == 'u': text_style['underline'] = True
197
+
198
+ font_weight = styles.get('font-weight', [''])[0]
199
+ if font_weight in {'bold', '700', '800', '900'}:
200
+ text_style['bold'] = True
201
+
202
+ if styles.get('font-style', [''])[0] == 'italic':
203
+ text_style['italic'] = True
204
+
205
+ if 'underline' in styles.get('text-decoration', [''])[0]:
206
+ text_style['underline'] = True
207
+
208
+ return text_style
209
+
210
+ def is_inline(node, styles):
211
+ if node.tag in {'span', 'a', 'strong', 'em', 'b', 'i', 'u'}:
212
+ return True
213
+ return styles.get('display', [''])[0] == 'inline'
214
+
215
+ def html_reduction(tree):
216
+ """Main processing function"""
217
+ ctx = Ctx()
218
+ chain = StyleChain(ctx)
219
+ blocks = {'p', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'article', 'section', 'li'}
220
+ skip = {'script', 'style', 'noscript'}
221
+ lines = []
222
+ cur_line = []
223
+ row_items = []
224
+ in_row = False
225
+
226
+ def flush():
227
+ if cur_line:
228
+ items = []
229
+ for text, style, node in cur_line:
230
+ if text.strip():
231
+ metrics = process_height(node, chain)
232
+ items.append({
233
+ "text": text.strip(),
234
+ "bold": style.get('bold', False),
235
+ "italic": style.get('italic', False),
236
+ "underline": style.get('underline', False),
237
+ "height": metrics['total_height'],
238
+ "font_size": metrics['font_size']
239
+ })
240
+ if items: lines.append(items)
241
+ cur_line.clear()
242
+
243
+ for event, node, styles, text, style in iterwalk(tree.body, include_text=True):
244
+ if node.tag in skip: continue
245
+
246
+ if event == "start": chain.push(styles)
247
+
248
+ if node.tag == 'tr':
249
+ if event == "start":
250
+ in_row = True
251
+ row_items = []
252
+ elif event == "end":
253
+ if row_items:
254
+ lines.append([{
255
+ "text": t,
256
+ "bold": s['bold'],
257
+ "italic": s['italic'],
258
+ "underline": s['underline'],
259
+ "height": process_height(n, chain)['total_height'],
260
+ "font_size": process_height(n, chain)['font_size']
261
+ } for t, s, n in row_items])
262
+ in_row = False
263
+ row_items = []
264
+
265
+ if in_row and event == "start" and text:
266
+ t = text.strip()
267
+ if t: row_items.append((t, style, node))
268
+ continue
269
+
270
+ if (node.tag in blocks or styles.get('display', [''])[0] == 'flex') and not is_inline(node, styles):
271
+ if event == "start":
272
+ flush()
273
+ if text:
274
+ t = text.strip()
275
+ if t:
276
+ metrics = process_height(node, chain)
277
+ lines.append([{
278
+ "text": t,
279
+ "bold": style['bold'],
280
+ "italic": style['italic'],
281
+ "underline": style['underline'],
282
+ "height": metrics['total_height'],
283
+ "font_size": metrics['font_size']
284
+ }])
285
+ elif event == "end":
286
+ flush()
287
+ elif event == "start" and text:
288
+ t = text.strip()
289
+ if t: cur_line.append((t, style, node))
290
+
291
+ if event == "end": chain.pop()
292
+
293
+ flush()
294
+ return lines
@@ -0,0 +1,100 @@
1
+ import webbrowser
2
+ import os
3
+
4
+ def format_list_html(nested_list):
5
+ # Simplified color scheme
6
+ single_line_color = '#E6F3FF' # Blue hue
7
+ multi_first_color = '#E6FFE6' # Green hue
8
+ multi_rest_color = '#FFE6E6' # Red hue
9
+
10
+ html_content = """
11
+ <html>
12
+ <head>
13
+ <style>
14
+ body {
15
+ font-family: Arial, sans-serif;
16
+ line-height: 1.6;
17
+ margin: 20px;
18
+ }
19
+ .row {
20
+ margin-bottom: 20px;
21
+ display: flex;
22
+ flex-wrap: wrap;
23
+ }
24
+ .item {
25
+ padding: 10px 15px;
26
+ border-radius: 5px;
27
+ margin-right: 15px;
28
+ margin-bottom: 10px;
29
+ display: inline-block;
30
+ }
31
+ </style>
32
+ </head>
33
+ <body>
34
+ """
35
+
36
+ def get_styled_text(item):
37
+ """Helper function to apply text styling"""
38
+ if not isinstance(item, dict):
39
+ return str(item)
40
+
41
+ height = str(item.get('height', ''))
42
+ text = item.get('text', '')
43
+ style_properties = []
44
+
45
+ if item.get('bold', False):
46
+ style_properties.append('font-weight: bold')
47
+ if item.get('italic', False):
48
+ style_properties.append('font-style: italic')
49
+ if item.get('underline', False):
50
+ style_properties.append('text-decoration: underline')
51
+ if height: # Change height to font-size
52
+ style_properties.append(f'font-size: {height}')
53
+
54
+ if style_properties:
55
+ return f"<span style='{'; '.join(style_properties)}'>{text}</span>"
56
+ return text
57
+
58
+ for items in nested_list:
59
+ if isinstance(items, list):
60
+ first_indent = items[0].get('indent', 0) if isinstance(items[0], dict) else 0
61
+ html_content += f"<div class='row' style='padding-left: {first_indent}em'>\n"
62
+
63
+ for i, item in enumerate(items):
64
+ # Single item -> blue
65
+ if len(items) == 1:
66
+ color = single_line_color
67
+ # Multiple items: first -> green, rest -> red
68
+ else:
69
+ color = multi_first_color if i == 0 else multi_rest_color
70
+
71
+ styled_text = get_styled_text(item)
72
+ html_content += f" <span class='item' style='background-color: {color}'>{styled_text}</span>\n"
73
+
74
+ html_content += "</div>\n"
75
+ else:
76
+ if isinstance(items, dict):
77
+ indent = items.get('indent', 0)
78
+ styled_text = get_styled_text(items)
79
+ height = items.get('height', '')
80
+ height_style = f'height: {height};' if height else ''
81
+ html_content += f"<div class='row' style='padding-left: {indent}em'><span class='item' style='background-color: {single_line_color}; {height_style}'>{styled_text}</span></div>\n"
82
+ else:
83
+ styled_text = str(items)
84
+ indent = 0
85
+ html_content += f"<div class='row' style='padding-left: {indent}em'><span class='item' style='background-color: {single_line_color}'>{styled_text}</span></div>\n"
86
+
87
+ html_content += """
88
+ </body>
89
+ </html>
90
+ """
91
+
92
+ # Write HTML content to a temporary file
93
+ with open('temp.html', 'w', encoding='utf-8') as f:
94
+ f.write(html_content)
95
+
96
+ # Get the absolute path of the file
97
+ file_path = os.path.abspath('temp.html')
98
+
99
+ # Open the HTML file in the default web browser
100
+ webbrowser.open('file://' + file_path)
File without changes
@@ -0,0 +1,21 @@
1
+ from doc2dict.mapping import DocumentProcessor
2
+
3
+ def txt2dict(content,mapping_dict):
4
+ # Split by double newlines to get paragraphs
5
+ paragraphs = content.split('\n\n')
6
+
7
+ # Process each paragraph
8
+ lines = []
9
+ for paragraph in paragraphs:
10
+ if paragraph.strip(): # Skip empty paragraphs
11
+ processed = paragraph.strip()
12
+ processed = processed.split('\n')
13
+ # Add double newlines at start of each paragraph
14
+ processed[0] = '\n\n' + processed[0]
15
+ # Add single newline to other lines
16
+ processed[1:] = ['\n' + line for line in processed[1:]]
17
+ lines.extend(processed)
18
+
19
+ processor = DocumentProcessor(mapping_dict)
20
+ result = processor.process(lines)
21
+ return result
File without changes
@@ -0,0 +1,12 @@
1
+ import xmltodict
2
+ from ..mapping import JSONTransformer
3
+
4
+ def xml2dict(content, mapping_dict=None):
5
+ data = xmltodict.parse(content)
6
+
7
+ if mapping_dict is None:
8
+ return data
9
+
10
+ transformer = JSONTransformer(mapping_dict)
11
+ transformed_data = transformer.transform(data)
12
+ return transformed_data
@@ -1,4 +1,4 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: doc2dict
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Requires-Python: >=3.8
@@ -0,0 +1,16 @@
1
+ setup.py
2
+ doc2dict/__init__.py
3
+ doc2dict/dict2dict.py
4
+ doc2dict/mapping.py
5
+ doc2dict.egg-info/PKG-INFO
6
+ doc2dict.egg-info/SOURCES.txt
7
+ doc2dict.egg-info/dependency_links.txt
8
+ doc2dict.egg-info/requires.txt
9
+ doc2dict.egg-info/top_level.txt
10
+ doc2dict/html/__init__.py
11
+ doc2dict/html/html_parser.py
12
+ doc2dict/html/visualizer.py
13
+ doc2dict/txt/__init__.py
14
+ doc2dict/txt/parser.py
15
+ doc2dict/xml/__init__.py
16
+ doc2dict/xml/parser.py
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="doc2dict",
5
- version="0.2.0",
5
+ version="0.2.2",
6
6
  packages=find_packages(),
7
7
  install_requires=['selectolax','xmltodict'
8
8
  ],
@@ -1,4 +0,0 @@
1
- from .html.html_parser import html_reduction
2
- from .html.visualizer import format_list_html
3
- from .xml.parser import xml2dict
4
- from .txt.parser import txt2dict
@@ -1,8 +0,0 @@
1
- setup.py
2
- doc2dict/__init__.py
3
- doc2dict/mapping.py
4
- doc2dict.egg-info/PKG-INFO
5
- doc2dict.egg-info/SOURCES.txt
6
- doc2dict.egg-info/dependency_links.txt
7
- doc2dict.egg-info/requires.txt
8
- doc2dict.egg-info/top_level.txt
File without changes
File without changes