
    j5j9                        d Z ddlmZ ddlZddlZddlZddlZddlmZ ddl	m
Z
mZ ddlmZ ddlmZmZmZmZmZ e
d   Z ej,                  e      Zej2                  Z G d	 d
e      Z G d d      Zy)zParser for hOCR format files.

This module provides functionality to parse hOCR files (HTML-based OCR format)
and convert them to the engine-agnostic OcrElement tree structure.

For details of the hOCR format, see:
http://kba.github.io/hocr-spec/1.2/
    )annotationsN)Path)Literalcast)ElementTree)BaselineBoundingBoxFontInfoOcrClass
OcrElementltrrtlc                      e Zd ZdZy)HocrParseErrorzError while parsing hOCR file.N)__name__
__module____qualname____doc__     X/var/www/html/qr/venv/lib/python3.12/site-packages/ocrmypdf/hocrtransform/hocr_parser.pyr   r   (   s    (r   r   c                     e Zd ZdZ ej
                  dej                        Z ej
                  dej                        Z ej
                  dej                        Z	 ej
                  dej                        Z
 ej
                  dej                        Z ej
                  dej                        Z ej
                  dej                        Z ej
                  d	ej                        Zdd
ZddZdddZddZd dZ	 	 	 	 	 	 	 	 	 	 d!dZd"dZd#dZed$d       Zd%dZd&dZd'dZd'dZd(dZd'dZd)dZy)*
HocrParsera  Parser for hOCR format files.

    Converts hOCR XML/HTML files into OcrElement trees.

    The hOCR format uses HTML with special class attributes (ocr_page, ocr_line,
    ocrx_word, etc.) and a title attribute containing properties like bbox,
    baseline, and confidence scores.
    z
        bbox \s+
        (\d+) \s+   # left: uint
        (\d+) \s+   # top: uint
        (\d+) \s+   # right: uint
        (\d+)       # bottom: uint
        z
        baseline \s+
        ([\-\+]?\d*\.?\d*) \s+  # slope: +/- decimal float
        ([\-\+]?\d+)            # intercept: +/- int
        zV
        textangle \s+
        ([\-\+]?\d*\.?\d*)  # angle: +/- decimal float
        zG
        x_wconf \s+
        (\d+)  # confidence: uint (0-100)
        zE
        x_fsize \s+
        (\d*\.?\d+)  # font size: float
        za
        x_font \s+
        ([^\s;]+)  # font name: non-whitespace, non-semicolon string
        z@
        ppageno \s+
        (\d+)  # page number: uint
        zb
        scan_res \s+
        (\d+) \s+  # x resolution
        (\d+)      # y resolution
        c                   t        |      | _        	 t        j                  t	        j
                  |            | _        | j                  j                         j                  }t        j                  d|      }|r|j                  d      | _        yd| _        y# t        j                  $ r}t        d|       |d}~ww xY w)zInitialize the parser with an hOCR file.

        Args:
            hocr_file: Path to the hOCR file to parse

        Raises:
            HocrParseError: If the file cannot be parsed
        zFailed to parse hOCR file: Nz
({.*})html    )r   
_hocr_pathETparseosfspath_tree
ParseErrorr   getroottagrematchgroup_xmlns)self	hocr_fileeroot_tagmatchess        r   __init__zHocrParser.__init__|   s     y/	K"))I"67DJ
 ::%%'++((=(3*1gmmA&r }} 	K #>qc!BCJ	Ks   -B C-B<<Cc                    | j                   j                  | j                  dd            }|t        d      | j	                  |      S )zParse the hOCR file and return an OcrElement tree.

        Returns:
            The root OcrElement (ocr_page) containing the document structure

        Raises:
            HocrParseError: If no ocr_page element is found
        divocr_pagez&No ocr_page element found in hOCR file)r#   find_xpathr   _parse_page)r+   page_divs     r   r    zHocrParser.parse   sD     ::??4;;uj#AB !IJJ))r   Nc                >    d| j                    | }|r	|d| dz  }|S )zBuild an XPath expression for finding elements.

        Args:
            html_tag: HTML tag name (e.g., 'div', 'span', 'p')
            html_class: Optional class attribute to match

        Returns:
            XPath expression string
        z.//z	[@class='z'])r*   )r+   html_tag
html_classxpaths       r   r5   zHocrParser._xpath   s3     dkk]8*-yB//Er   c                d   |j                   j                  dd      }| j                  |      }|t        d      | j	                  |      }| j                  |      }t        t        j                  |||      }|j                  | j                  dd            D ]1  }| j                  |      }||j                  j                  |       3 |j                  sU|j                  | j                  dd            D ]1  }	| j                  |	      }
|
|j                  j                  |
       3 |S )	zParse an ocr_page element.

        Args:
            page_elem: The XML element with class="ocr_page"

        Returns:
            OcrElement representing the page
        titler   zocr_page missing bbox)	ocr_classbboxpage_numberdpipocr_parspan	ocrx_word)attribget_parse_bboxr   _parse_ppageno_parse_scan_resr   r   PAGEiterfindr5   _parse_paragraphchildrenappend_parse_word)r+   	page_elemr=   r?   r@   rA   pagepar_elem	paragraph	word_elemwords              r   r6   zHocrParser._parse_page   s%      $$Wb1&< !899 ))%0""5)mm#	
 "**4;;sI+FG 	0H--h7I$$$Y/	0 }}&//FK0PQ /	''	2#MM((./
 r   c                D   |j                   j                  dd      }| j                  |      }|j                   j                  d      }|dv rt        t        |      nd}|j                   j                  d      }t        t        j                  |||      }h d}|j                  | j                  d	            D ]U  }	|	j                   j                  d
d      }
|
|v s$| j                  |	|
||      }|;|j                  j                  |       W |j                  sy|S )zParse an ocr_par element.

        Args:
            par_elem: The XML element with class="ocr_par"

        Returns:
            OcrElement representing the paragraph, or None if empty
        r=   r   dirr   Nlang)r>   r?   	directionlanguage>   ocr_line
ocr_footer
ocr_headerocr_captionocr_textfloatrD   class)rF   rG   rH   r   TextDirectionr   r   	PARAGRAPHrL   r5   _parse_linerN   rO   )r+   rS   r=   r?   dir_attrrZ   r[   rT   line_classes	span_elem
elem_classlines               r   rM   zHocrParser._parse_paragraph   s    ##GR0& ??&&u--5-GD)T 	 ??&&v.((	
	
 "**4;;v+>? 	4I"))--gr:J\)''	:y(S#&&--d3	4 !!r   c                2   |j                   j                  dd      }| j                  |      }|y| j                  |      }| j	                  |      }|j                   j                  d      }	|	dv rt        t        |	      }
n|}
|j                   j                  d      xs |}t        |||||
|      }|j                  | j                  dd	            D ]1  }| j                  |      }||j                  j                  |       3 |j                  sy|S )
a  Parse a line element (ocr_line, ocr_header, etc.).

        Args:
            line_elem: The XML element representing the line
            ocr_class: The hOCR class of the line
            parent_direction: Text direction inherited from parent
            parent_language: Language inherited from parent

        Returns:
            OcrElement representing the line, or None if empty
        r=   r   NrX   r   rY   )r>   r?   baseline	textanglerZ   r[   rD   rE   )rF   rG   rH   _parse_baseline_parse_textangler   rb   r   rL   r5   rP   rN   rO   )r+   	line_elemr>   parent_directionparent_languager=   r?   rk   rl   re   rZ   r[   ri   rU   rV   s                  r   rd   zHocrParser._parse_line	  s   $   $$Wb1&<''.))%0	 ##''.~%.2=(.KI(I##''/B?
 #++DKK,LM 	+I##I.D$$T*	+ }}r   c                4   |j                   j                  dd      }| j                  |      }| j                  |      }| j	                  |      }|sy| j                  |      }||dz  }| j                  |      }t        t        j                  ||||      S )zParse an ocrx_word element.

        Args:
            word_elem: The XML element with class="ocrx_word"

        Returns:
            OcrElement representing the word, or None if empty
        r=   r   Ng      Y@)r>   r?   text
confidencefont)
rF   rG   rH   _get_element_text_normalize_text_parse_x_wconf_parse_font_infor   r   WORD)r+   rU   r=   r?   rs   rt   ru   s          r   rP   zHocrParser._parse_wordB  s       $$Wb1& %%i0##D) ((/
!#e+J $$U+mm!
 	
r   c                    |j                   |j                   nd}|D ]  }|| j                  |      z  } ||j                  |j                  z  }|S dz  }|S )zGet the full text content of an element including children.

        Args:
            element: XML element

        Returns:
            Combined text content
        r   )rs   rv   tail)r+   elementrs   childs       r   rv   zHocrParser._get_element_texte  sh      '||7w||R 	2ED**511D	2 8@ ?A@r   c                J    t        j                  d|       j                         S )zNormalize text using NFKC normalization.

        This splits ligatures and combines diacritics.

        Args:
            text: Raw text

        Returns:
            Normalized text, stripped of leading/trailing whitespace
        NFKC)unicodedata	normalizestrip)rs   s    r   rw   zHocrParser._normalize_textt  s      $$VT288::r   c           
     <   | j                   j                  |      }|sy	 t        t        |j	                  d            t        |j	                  d            t        |j	                  d            t        |j	                  d                  S # t
        $ r Y yw xY w)zParse a bbox from an hOCR title attribute.

        Args:
            title: The title attribute value

        Returns:
            BoundingBox or None if not found
        Nr            )lefttoprightbottom)_bbox_patternsearchr	   floatr)   
ValueErrorr+   r=   r(   s      r   rH   zHocrParser._parse_bbox  s     ""))%0	5;;q>*%++a.)EKKN+U[[^,	   		s   A.B 	BBc                    | j                   j                  |      }|sy	 t        |j                  d      rt	        |j                  d            ndt	        |j                  d                  S # t
        $ r Y yw xY w)zParse baseline from an hOCR title attribute.

        Args:
            title: The title attribute value

        Returns:
            Baseline or None if not found
        Nr   g        r   )slope	intercept)_baseline_patternr   r   r)   r   r   r   s      r   rm   zHocrParser._parse_baseline  sr     &&--e4	/4{{1~eEKKN+3A/   		s   AA0 0	A<;A<c                    | j                   j                  |      }|sy	 t        |j                  d            S # t        $ r Y yw xY w)zParse textangle from an hOCR title attribute.

        Args:
            title: The title attribute value

        Returns:
            Angle in degrees or None if not found
        Nr   )_textangle_patternr   r   r)   r   r   s      r   rn   zHocrParser._parse_textangle  sJ     ''..u5	Q(( 		   : 	AAc                    | j                   j                  |      }|sy	 t        |j                  d            S # t        $ r Y yw xY w)zParse word confidence from an hOCR title attribute.

        Args:
            title: The title attribute value

        Returns:
            Confidence (0-100) or None if not found
        Nr   )_x_wconf_patternr   r   r)   r   r   s      r   rx   zHocrParser._parse_x_wconf  sJ     %%,,U3	Q(( 		r   c                    | j                   j                  |      }|sy	 t        |j                  d            S # t        $ r Y yw xY w)zParse physical page number from an hOCR title attribute.

        Args:
            title: The title attribute value

        Returns:
            Page number or None if not found
        Nr   )_ppageno_patternr   intr)   r   r   s      r   rI   zHocrParser._parse_ppageno  sJ     %%,,U3	u{{1~&& 		r   c                    | j                   j                  |      }|sy	 t        |j                  d            S # t        $ r Y yw xY w)zParse scan resolution (DPI) from an hOCR title attribute.

        Args:
            title: The title attribute value

        Returns:
            DPI (using first value if x and y differ) or None if not found
        Nr   )_scan_res_patternr   r   r)   r   r   s      r   rJ   zHocrParser._parse_scan_res  sJ     &&--e4	Q(( 		r   c                    | j                   j                  |      }| j                  j                  |      }|s|syt        |r|j	                  d      nd|rt        |j	                  d                  S d      S )zParse font information from an hOCR title attribute.

        Args:
            title: The title attribute value

        Returns:
            FontInfo or None if no font info found
        Nr   )namesize)_x_font_patternr   _x_fsize_patternr
   r)   r   )r+   r=   
font_match
size_matchs       r   ry   zHocrParser._parse_font_info  sy     ))007
**11%8
*(2!!!$/9z''*+
 	
?C
 	
r   )r,   z
str | Path)returnr   )N)r9   strr:   
str | Noner   r   )rQ   Elementr   r   )rS   r   r   OcrElement | None)
ro   r   r>   r   rp   zTextDirection | Nonerq   r   r   r   )rU   r   r   r   )r}   r   r   r   )rs   r   r   r   )r=   r   r   zBoundingBox | None)r=   r   r   zBaseline | None)r=   r   r   zfloat | None)r=   r   r   z
int | None)r=   r   r   zFontInfo | None) r   r   r   r   r'   compileVERBOSEr   r   r   r   r   r   r   r   r0   r    r5   r6   rM   rd   rP   rv   staticmethodrw   rH   rm   rn   rx   rI   rJ   ry   r   r   r   r   r   ,   s    BJJ	 	

	M #

	
 	

 $	 	

 "rzz	 	

 "rzz	 	

 !bjj	 	

O "rzz	 	

 #

	
 	

:(* (T.`77 7 /	7
 $7 
7r!
F ; ;.*$$$&
r   r   )r   
__future__r   loggingr!   r'   r   pathlibr   typingr   r   	xml.etreer   r   ocrmypdf.models.ocr_elementr   r	   r
   r   r   rb   	getLoggerr   logr   	Exceptionr   r   r   r   r   <module>r      sm    #  	 	     '  %g!
**)Y )]
 ]
r   