Apply document-specific fixes to clean up severe OCR and layout conversion issues seen in certain MDR and MDCG guidance PDFs (e.g. 2019-16).
(content: str)
| 591 | return content, n1 + n2 |
| 592 | |
| 593 | def apply_specific_fixes(content: str) -> tuple[str, int]: |
| 594 | """ |
| 595 | Apply document-specific fixes to clean up severe OCR and layout conversion issues |
| 596 | seen in certain MDR and MDCG guidance PDFs (e.g. 2019-16). |
| 597 | """ |
| 598 | modified = 0 |
| 599 | orig = content |
| 600 | |
| 601 | # 1. Figure 1 diagram textual elements in MDCG 2019-16 |
| 602 | def repl_fig1(m): |
| 603 | images = re.findall(r'(?:<!-- image -->|!\[.*?\]\(.*?\))', m.group(0)) |
| 604 | return '\n\n'.join(images) + '\n\n' |
| 605 | |
| 606 | content = re.sub( |
| 607 | r'General safety and performance requirements with focus on cybersecurity.*?1nт 1\. Cahraanetar enmmlenmonta\n+', |
| 608 | repl_fig1, |
| 609 | content, |
| 610 | flags=re.DOTALL |
| 611 | ) |
| 612 | |
| 613 | # 2. Fix the "Table 1: I" orphan header |
| 614 | content = re.sub( |
| 615 | r'^Table 1:\s*I\n+Correspondence table between sections, relevant for this guidance, in MDR Annex I and IVDR Annex', |
| 616 | 'Table 1: Correspondence table between sections, relevant for this guidance, in MDR Annex I and IVDR Annex I', |
| 617 | content, |
| 618 | flags=re.MULTILINE |
| 619 | ) |
| 620 | |
| 621 | # 3. Numbered lists with weird bullets e.g. "9. • A description..." |
| 622 | content = re.sub( |
| 623 | r'^(\d+\.)\s*(?:-\s*|[•➢o]\s+)+(.*)$', |
| 624 | r'\1 \2', |
| 625 | content, |
| 626 | flags=re.MULTILINE |
| 627 | ) |
| 628 | |
| 629 | # Missing GDPR in MDCG-2019-16 abbreviations |
| 630 | content = content.replace( |
| 631 | "Field Safety Corrective actions\n\n\nGeneral Data Protection Regulation", |
| 632 | "Field Safety Corrective actions\n\nGDPR\n\nGeneral Data Protection Regulation" |
| 633 | ) |
| 634 | # the extra hyphen before IEC/TR |
| 635 | content = content.replace("\n- IEC/TR\n", "\nIEC/TR\n") |
| 636 | |
| 637 | # 4. Turn spaced abbreviation items into table |
| 638 | abbr_block_match = re.search(r'(### 1\.5\.\s*Abbreviations\n+)(.*?)(?=\n\||\n#)', content, re.DOTALL) |
| 639 | if not abbr_block_match: |
| 640 | abbr_block_match = re.search(r'(## 1\.5\.\s*Abbreviations\n+)(.*?)(?=\n\||\n#)', content, re.DOTALL) |
| 641 | |
| 642 | if abbr_block_match: |
| 643 | prefix = abbr_block_match.group(1) |
| 644 | block = abbr_block_match.group(2) |
| 645 | lines = [l.strip() for l in block.split('\n') if l.strip()] |
| 646 | out_table = ["| Abbreviation | Meaning |", "| --- | --- |"] |
| 647 | |
| 648 | # Only do this if it looks like alternating items without pipes |
| 649 | if all('|' not in l for l in lines) and len(lines) % 2 == 0 and len(lines) > 2: |
| 650 | for i in range(0, len(lines)-1, 2): |