File size: 21,978 Bytes
f52d4df
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0002c06
 
 
f52d4df
0002c06
 
 
 
 
 
 
f52d4df
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0002c06
 
 
 
 
 
 
 
 
 
f52d4df
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0002c06
f52d4df
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0002c06
 
 
f52d4df
 
 
 
 
 
 
 
 
 
0002c06
 
 
 
 
 
 
f52d4df
 
 
 
 
0002c06
 
 
 
 
f52d4df
0002c06
f52d4df
 
0002c06
f52d4df
 
 
0002c06
 
f52d4df
 
 
 
 
0002c06
 
 
f52d4df
 
 
 
 
 
 
 
 
0002c06
f52d4df
 
 
 
 
0002c06
 
f52d4df
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0002c06
f52d4df
0002c06
 
f52d4df
 
0002c06
f52d4df
 
 
 
 
 
0002c06
f52d4df
0002c06
 
f52d4df
 
 
 
0002c06
f52d4df
 
0002c06
f52d4df
 
0002c06
 
f52d4df
 
0002c06
f52d4df
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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
# import pdfplumber
# import pandas as pd
# import re
# from typing import List, Dict, Tuple, Any

# class PDFSectionExtractor:
#     def __init__(self, pdf_path: str):
#         """Initialize with path to PDF file."""
#         self.pdf_path = pdf_path
#         self.tables = []
#         self.table_names = {}  # Store table names and their content
        
#     def is_level_three_section(self, section_number: str) -> bool:
#         """Check if the section number is a level three section (e.g., 2.1.1)."""
#         return len(section_number.split('.')) == 3

#     def find_table_names(self, text: str) -> List[Dict[str, str]]:
#         """Extract table names from text."""
#         table_pattern = r'\*\*Table\s+(\d+):\s+([^*]+)\*\*'
#         return [(match.group(1), match.group(2).strip()) 
#                 for match in re.finditer(table_pattern, text)]

#     def clean_content(self, content: str) -> str:
#         """Remove table references and names from content."""
#         # Remove table references
#         content = re.sub(r'\*\*Table\s+\d+:\s+[^*]+\*\*', '', content)
#         # Remove any empty lines created
#         content = '\n'.join(line for line in content.split('\n') if line.strip())
#         return content
        
#     def merge_split_tables(self, tables: List[List]) -> List[List]:
#         """Merge tables that are split across pages."""
#         merged_tables = []
#         current_table = None
        
#         for table in tables:
#             if not table:
#                 continue
                
#             if current_table is None:
#                 current_table = table
#             else:
#                 # Check if this table is a continuation
#                 # Compare the number of columns
#                 if len(table[0]) == len(current_table[0]):
#                     current_table.extend(table)
#                 else:
#                     merged_tables.append(current_table)
#                     current_table = table
        
#         if current_table:
#             merged_tables.append(current_table)
            
#         return merged_tables

#     def extract_tables(self) -> List[Dict[str, Any]]:
#         """Extract tables with their names and merge split tables."""
#         tables_data = []
#         current_section = None
#         current_table_data = None
#         current_table_name = None
        
#         with pdfplumber.open(self.pdf_path) as pdf:
#             for page in pdf.pages:
#                 text = page.extract_text(x_tolerance=1,y_tolerance=0) or ''
                
#                 # Find table names in the text
#                 table_names = self.find_table_names(text)
#                 tables = page.extract_tables()
                
#                 # Process each table found
#                 if tables:
#                     tables = self.merge_split_tables(tables)
                    
#                     for i, table in enumerate(tables):
#                         table_name = None
#                         if i < len(table_names):
#                             table_num, name = table_names[i]
#                             table_name = f"Table {table_num}: {name}"
                        
#                         if table:  # Check if table has content
#                             df = pd.DataFrame(table)
#                             # Clean the DataFrame
#                             df = df.dropna(how='all').dropna(axis=1, how='all')
#                             # Replace None with empty string
#                             df = df.fillna('')
                            
#                             tables_data.append({
#                                 'name': table_name,
#                                 'data': df
#                             })
        
#         return tables_data

#     def extract_sections(self) -> List[Dict[str, str]]:
#         """Extract sections from PDF with content, excluding tables."""
#         sections = []
#         current_section = None
#         current_content = []
#         section_pattern = r'^(\d+\.(?:\d+)?(?:\.\d+)?)\s+(.+)$'
        
#         with pdfplumber.open(self.pdf_path) as pdf:
#             for page in pdf.pages:
#                 text = page.extract_text(x_tolerance=1)
#                 if not text:
#                     continue
                    
#                 lines = text.split('\n')
                
#                 for line in lines:
#                     match = re.match(section_pattern, line.strip())
                    
#                     if match:
#                         if current_section:
#                             content_text = '\n'.join(current_content)
#                             content_text = self.clean_content(content_text)
                            
#                             if self.is_level_three_section(current_section[0]):
#                                 full_content = current_section[1] + '\n' + content_text
#                                 sections.append({
#                                     'section_number': current_section[0],
#                                     'section_name': '',
#                                     'content': full_content
#                                 })
#                             else:
#                                 sections.append({
#                                     'section_number': current_section[0],
#                                     'section_name': current_section[1],
#                                     'content': content_text
#                                 })
                        
#                         current_section = (match.group(1), match.group(2))
#                         current_content = []
#                     elif current_section:
#                         current_content.append(line.strip())
        
#         # Handle the last section
#         if current_section:
#             content_text = '\n'.join(current_content)
#             content_text = self.clean_content(content_text)
            
#             if self.is_level_three_section(current_section[0]):
#                 full_content = current_section[1] + '\n' + content_text
#                 sections.append({
#                     'section_number': current_section[0],
#                     'section_name': '',
#                     'content': full_content
#                 })
#             else:
#                 sections.append({
#                     'section_number': current_section[0],
#                     'section_name': current_section[1],
#                     'content': content_text
#                 })
                
#         return sections

# def convert_pdf_to_excel(pdf_path: str, excel_path: str):
#     """Convert PDF with sections and tables to Excel file."""
#     try:
#         extractor = PDFSectionExtractor(pdf_path)
#         sections = extractor.extract_sections()
#         tables = extractor.extract_tables()
        
#         with pd.ExcelWriter(excel_path, engine='openpyxl') as writer:
#             # Write sections to main sheet
#             df_sections = pd.DataFrame(sections)
#             df_sections.to_excel(writer, index=False, sheet_name='Sections')
            
#             # Auto-adjust sections sheet
#             worksheet = writer.sheets['Sections']
#             for idx, col in enumerate(['A', 'B', 'C']):
#                 worksheet.column_dimensions[col].width = 15 if idx < 2 else 50
            
#             # Write tables to separate sheets
#             for i, table_info in enumerate(tables, 1):
#                 if table_info['name']:
#                     sheet_name = table_info['name'][:31]  # Excel sheet name length limit
#                 else:
#                     sheet_name = f'Table_{i}'
                
#                 # Write table data
#                 table_info['data'].to_excel(writer, sheet_name=sheet_name, index=False)
                
#                 # Auto-adjust table sheet
#                 worksheet = writer.sheets[sheet_name]
#                 for column in worksheet.columns:
#                     max_length = 0
#                     column = [cell for cell in column]
#                     for cell in column:
#                         try:
#                             if len(str(cell.value)) > max_length:
#                                 max_length = len(cell.value)
#                         except:
#                             pass
#                     adjusted_width = (max_length + 2)
#                     worksheet.column_dimensions[column[0].column_letter].width = adjusted_width
        
#         return True
    
#     except Exception as e:
#         print(f"Error converting PDF to Excel: {str(e)}")
#         return False

# if __name__ == "__main__":
#     pdf_path = "/Users/aakanksha.n/Desktop/pdf_to_excel/[00 12 10] 26251-100-3DR-S04-00001_002.docx.pdf"
#     excel_path = "/Users/aakanksha.n/Desktop/pdf_to_excel/format4.xlsx"
    
#     success = convert_pdf_to_excel(pdf_path, excel_path)
#     if success:
#         print("Successfully converted PDF to Excel!")
#     else:
#         print("Failed to convert PDF to Excel.")



import pdfplumber
import pandas as pd
import re
import os
from typing import List, Dict, Tuple, Any

class PDFSectionExtractor:
    def __init__(self, pdf_path: str):
        """Initialize with path to PDF file."""
        self.pdf_path = pdf_path
        self.tables = []
        self.table_names = {}
        self.table_content_markers = set()
        self.table_content_lines = set()
        self.table_headers = set()
        self.document_name = None  # Set to None to identify when it's missing
        self.document_id = None

    def extract_document_info_from_pdf(self) -> Tuple[str, str]:
        """Extract document name and ID from the first page of PDF."""
        with pdfplumber.open(self.pdf_path) as pdf:
            if not pdf.pages:
                return "", ""

            first_page_text = pdf.pages[0].extract_text()

            if not first_page_text:
                return "", ""

            doc_name_pattern = r'[dD]ocument\s*[nN]ame:\s*(.*?)(?:\n|$)'
            doc_id_pattern = r'[dD]ocument\s*[iI][dD]:\s*(.*?)(?:\n|$)'


            doc_name_match = re.search(doc_name_pattern, first_page_text)
            doc_id_match = re.search(doc_id_pattern, first_page_text)
            print(f" extract document info function{doc_name_match}")

            doc_name = doc_name_match.group(1).strip() if doc_name_match else ""
            doc_id = doc_id_match.group(1).strip() if doc_id_match else ""

            # print(f"Document name from pdf inside function{doc_name},{doc_id}")

            return doc_name, doc_id

    def get_document_info(self) -> Tuple[str, str]:
        """
        Get document info from the PDF content.
        If missing, fall back to filename for document ID only.
        """
        if self.document_name is None or self.document_id is None:
            # Try extracting from PDF
            doc_name, doc_id = self.extract_document_info_from_pdf()
            self.document_name = doc_name
            self.document_id = doc_id
            # print(f"Entering if one,{self.document_name}")

        # Fallback only if document ID or name is missing
        if not self.document_name or not self.document_id:
            doc_name = os.path.basename(self.pdf_path)
            doc_id_match = re.search(r'\[\d{2}\s+\d{2}\s+\d{2}\]\s+(\d+)', doc_name)
            if not self.document_id:
                self.document_id = doc_id_match.group(1) if doc_id_match else ""
            if not self.document_name:
                self.document_name = doc_name

        # print(f"final{self.document_name},{self.document_id}")
        return self.document_name, self.document_id

    def is_level_three_section(self, section_number: str) -> bool:
        """Check if the section number is a level three section (e.g., 2.1.1)."""
        return len(section_number.split('.')) == 3

    def find_table_names(self, text: str) -> List[Dict[str, str]]:
        """Extract table names from text."""
        table_pattern = r'\*\*Table\s+(\d+):\s+([^*]+)\*\*'
        return [(match.group(1), match.group(2).strip()) 
                for match in re.finditer(table_pattern, text)]

    def store_table_headers(self, table: List[List[str]]):
        """Store table headers."""
        if table and table[0]:
            header_row = table[0]
            for header in header_row:
                if header:
                    header_text = self.fix_table_cell_spacing(header)
                    if header_text:
                        self.table_headers.add(header_text)

    def store_table_content(self, table: List[List[str]]):
        """Store all content from table cells, including headers."""
        if not table:
            return

        # Store headers separately
        self.store_table_headers(table)

        # Store all cell content
        for row in table:
            for cell in row:
                if cell:
                    cell_text = self.fix_table_cell_spacing(cell)
                    if cell_text:
                        self.table_content_lines.add(cell_text)

    def fix_table_cell_spacing(self, cell_text: str) -> str:
        """Fix spacing issues within table cells."""
        if not isinstance(cell_text, str):
            return str(cell_text)

        text = str(cell_text).strip()
        text = re.sub(r',(?=\S)', ', ', text)
        text = re.sub(r'(\d)([a-zA-Z])', r'\1 \2', text)
        text = re.sub(r'([a-zA-Z])(\d)', r'\1 \2', text)
        text = re.sub(r'([a-z])([A-Z])', r'\1 \2', text)
        text = re.sub(r'\s+', ' ', text)
        return text.strip()

    def contains_table_content(self, line: str) -> bool:
        """Check if a line contains any stored table content or headers."""
        line = self.fix_table_cell_spacing(line)
        return (any(table_line in line for table_line in self.table_content_lines) or
                any(header in line for header in self.table_headers))

    def is_table_content(self, line: str) -> bool:
        """Identify if a line is part of a table."""
        if any(marker in line for marker in ['|', '+', '─', 'β”‚', 'β”Œ', '┐', 'β””', 'β”˜', 'β”œ', '─', '┬', 'β”΄']):
            return True
        if re.search(r'\S+\s{2,}\S+', line):
            return True
        if re.match(r'^[\s\-+|=_]{3,}$', line):
            return True
        return False

    def filter_table_content(self, content: str) -> str:
        """Remove lines that contain table content."""
        lines = content.split('\n')
        filtered_lines = []

        for line in lines:
            line = line.strip()
            if line and not self.contains_table_content(line):
                filtered_lines.append(line)

        return '\n'.join(filtered_lines)

    def process_table(self, table: List[List[str]], table_num: int) -> pd.DataFrame:
        """Process table data with proper spacing and ensure unique column names."""
        if not table:
            return pd.DataFrame()

        # Store all table content including headers
        self.store_table_content(table)

        # Create DataFrame with default column names
        df = pd.DataFrame(table)
        if df.empty:
            return df

        # Get the header row (first row)
        header = df.iloc[0].tolist()

        # Create unique column names
        unique_cols = []
        col_count = {}

        for col in header:
            col = str(col) if col else "Unnamed"
            if col in col_count:
                col_count[col] += 1
                unique_cols.append(f"{col}_{col_count[col]}")
            else:
                col_count[col] = 0
                unique_cols.append(col)

        # Set the unique column names
        df.columns = unique_cols

        # Remove the header row since we used it for column names
        df = df.iloc[1:].reset_index(drop=True)

        # Fix spacing in cells
        for col in df.columns:
            df[col] = df[col].astype(str).apply(self.fix_table_cell_spacing)

        # Add metadata
        df.insert(0, 'Document_Name', self.document_name)
        df.insert(1, 'Document_ID', self.document_id)
        df.insert(2, 'Table_Number', table_num)

        return df

    def extract_tables(self) -> List[pd.DataFrame]:
        """Extract tables from PDF and return a list of DataFrames."""
        tables = []
        table_counter = 1

        # Ensure we have document info before processing tables
        self.get_document_info()

        with pdfplumber.open(self.pdf_path) as pdf:
            for page in pdf.pages:
                detected_tables = page.extract_tables()

                for table in detected_tables:
                    if table:
                        df = self.process_table(table, table_counter)
                        if not df.empty:
                            tables.append(df)
                            table_counter += 1

        return tables

    def extract_sections(self) -> List[Dict[str, str]]:
        """Extract sections from PDF with content, excluding tables."""
        sections = []
        current_section = None
        current_content = []
        section_pattern = r'^(\d+\.(?:\d+)?(?:\.\d+)?)\s+(.+)$'

        # Ensure we have document info before processing sections
        doc_name, doc_id = self.get_document_info()
        table_counter = 1

        with pdfplumber.open(self.pdf_path) as pdf:
            for page in pdf.pages:
                text = page.extract_text(x_tolerance=1)
                if not text:
                    continue

                lines = text.split('\n')
                table_found = False

                for line in lines:
                    line = line.strip()
                    match = re.match(section_pattern, line)

                    if match:
                        if current_section:
                            content_text = self.process_content(current_content)
                            content_text = self.filter_table_content(content_text)
                            sections.append(self.create_section_dict(
                                doc_name, doc_id, current_section, content_text))

                        current_section = (match.group(1), match.group(2))
                        current_content = []
                    elif current_section:
                        if self.is_table_content(line):
                            if not table_found:
                                current_content.append(f"[Table {table_counter} is available in Table_{table_counter} sheet]")
                                table_counter += 1
                                table_found = True
                        else:
                            current_content.append(line)
                            table_found = False

        if current_section:
            content_text = self.process_content(current_content)
            content_text = self.filter_table_content(content_text)
            sections.append(self.create_section_dict(
                doc_name, doc_id, current_section, content_text))

        return sections

    def process_content(self, content_lines: List[str]) -> str:
        """Process content lines and remove duplicate table references."""
        processed_lines = []
        last_line_was_table_ref = False

        for line in content_lines:  # Fixed: using content_lines parameter
            line = line.strip()
            if '[Table' in line:
                if not last_line_was_table_ref:
                    processed_lines.append(line)
                    last_line_was_table_ref = True
            else:
                processed_lines.append(line)
                last_line_was_table_ref = False

        return '\n'.join(processed_lines)

    def create_section_dict(self, doc_name: str, doc_id: str, 
                            section_tuple: Tuple[str, str], content: str) -> Dict[str, str]:
        """Create a dictionary for section data."""
        section_number, section_name = section_tuple

        if self.is_level_three_section(section_number):
            return {
                'document_name': doc_name,
                'document_id': doc_id,
                'section_number': section_number,
                'section_name': '',
                'content': f"{section_name}\n{content}"
            }
        else:
            return {
                'document_name': doc_name,
                'document_id': doc_id,
                'section_number': section_number,
                'section_name': section_name,
                'content': content
            }

def convert_pdf_to_excel(pdf_path: str, excel_path: str):
    """Convert PDF with sections and tables to Excel file with separate sheets for each table."""
    try:
        extractor = PDFSectionExtractor(pdf_path)

        # Extract and process tables
        tables = extractor.extract_tables()

        # Extract sections
        sections = extractor.extract_sections()
        df_sections = pd.DataFrame(sections)

        # Write to Excel file
        with pd.ExcelWriter(excel_path, engine='openpyxl') as writer:
            # Write sections sheet
            df_sections.to_excel(writer, index=False, sheet_name='Sections')
            
            # Write each table to its own sheet
            for i, table_df in enumerate(tables, 1):
                sheet_name = f'Table_{i}'
                table_df.to_excel(writer, index=False, sheet_name=sheet_name)
        
        print(f"Successfully saved PDF data to {excel_path}")
        print(f"Created {len(tables)} table sheets")
    except Exception as e:
        print(f"Error occurred: {e}")
        raise

if __name__ == "__main__":
    pdf_path = "[00 12 10] 26251-100-3DR-S04-00001_002.docx.pdf"
    excel_path = "format.xlsx"
    
    convert_pdf_to_excel(pdf_path, excel_path)