B
    fcU                 @   s   d Z ddlZddlZddlmZmZ ddlmZmZ dd Z	dd Z
G d	d
 d
eZG dd dZG dd dZdddZedZdd ZdddZd ddZdd ZdS )!z
    babel.messages.pofile
    ~~~~~~~~~~~~~~~~~~~~~

    Reading and writing of files in the ``gettext`` PO (portable object)
    format.

    :copyright: (c) 2013-2022 by the Babel Team.
    :license: BSD, see LICENSE for more details.
    N)CatalogMessage)wraptext_cmpc             C   s"   dd }t d|| dd S )zReverse `escape` the given string.

    >>> print(unescape('"Say:\\n  \\"hello, world!\\"\\n"'))
    Say:
      "hello, world!"
    <BLANKLINE>

    :param string: the string to unescape
    c             S   s2   |  d}|dkrdS |dkr"dS |dkr.dS |S )N   n
t	r)group)matchm r   4lib/python3.7/site-packages/babel/messages/pofile.pyreplace_escapes   s    
z!unescape.<locals>.replace_escapesz\\([\\trn"])r   )recompilesub)stringr   r   r   r   unescape   s    

r   c             C   sF   d| kr:|   }| dr&|dd }tt|}d|S t| S dS )a  Reverse the normalization done by the `normalize` function.

    >>> print(denormalize(r'''""
    ... "Say:\n"
    ... "  \"hello, world!\"\n"'''))
    Say:
      "hello, world!"
    <BLANKLINE>

    >>> print(denormalize(r'''""
    ... "Say:\n"
    ... "  \"Lorem ipsum dolor sit "
    ... "amet, consectetur adipisicing"
    ... " elit, \"\n"'''))
    Say:
      "Lorem ipsum dolor sit amet, consectetur adipisicing elit, "
    <BLANKLINE>

    :param string: the string to denormalize
    r   z""r   N )
splitlines
startswithmapr   join)r   Zescaped_lineslinesr   r   r   denormalize*   s    


r   c                   s    e Zd ZdZ fddZ  ZS )PoFileErrorzDException thrown by PoParser when an invalid po file is encountered.c                s,   t  | d|  || _|| _|| _d S )Nz on )super__init__cataloglinelineno)selfmessager#   r$   r%   )	__class__r   r   r"   K   s    zPoFileError.__init__)__name__
__module____qualname____doc__r"   __classcell__r   r   )r(   r   r    I   s   r    c               @   sl   e Zd Zdd Zdd Zdd Zdd Zd	d
 Zdd Zdd Z	dd Z
dd Zdd Zdd Zdd ZdS )_NormalizedStringc             G   s"   g | _ x|D ]}| | qW d S )N)_strsappend)r&   argsargr   r   r   r"   T   s    
z_NormalizedString.__init__c             C   s   | j |  d S )N)r/   r0   strip)r&   sr   r   r   r0   Y   s    z_NormalizedString.appendc             C   s   d tt| jS )Nr   )r   r   r   r/   )r&   r   r   r   r   \   s    z_NormalizedString.denormalizec             C   s
   t | jS )N)boolr/   )r&   r   r   r   __bool___   s    z_NormalizedString.__bool__c             C   s   t j| jS )N)oslinesepr   r/   )r&   r   r   r   __repr__b   s    z_NormalizedString.__repr__c             C   s   |sdS t t| t|S )Nr   )r   str)r&   otherr   r   r   __cmp__e   s    z_NormalizedString.__cmp__c             C   s   |  |dkS )Nr   )r<   )r&   r;   r   r   r   __gt__k   s    z_NormalizedString.__gt__c             C   s   |  |dk S )Nr   )r<   )r&   r;   r   r   r   __lt__n   s    z_NormalizedString.__lt__c             C   s   |  |dkS )Nr   )r<   )r&   r;   r   r   r   __ge__q   s    z_NormalizedString.__ge__c             C   s   |  |dkS )Nr   )r<   )r&   r;   r   r   r   __le__t   s    z_NormalizedString.__le__c             C   s   |  |dkS )Nr   )r<   )r&   r;   r   r   r   __eq__w   s    z_NormalizedString.__eq__c             C   s   |  |dkS )Nr   )r<   )r&   r;   r   r   r   __ne__z   s    z_NormalizedString.__ne__N)r)   r*   r+   r"   r0   r   r6   r9   r<   r=   r>   r?   r@   rA   rB   r   r   r   r   r.   R   s   r.   c               @   sr   e Zd ZdZddddgZdddZd	d
 Zdd Zdd ZdddZ	dddZ
dd Zdd Zdd Zdd ZdS )PoFileParserzSupport class to  read messages from a ``gettext`` PO (portable object) file
    and add them to a `Catalog`

    See `read_po` for simple cases.
    msgidmsgstrmsgctxtmsgid_pluralFc             C   s*   || _ || _d| _d| _|| _|   d S )Nr   )r#   ignore_obsoletecounteroffsetabort_invalid_reset_message_state)r&   r#   rH   rK   r   r   r   r"      s    zPoFileParser.__init__c             C   sF   g | _ g | _g | _g | _g | _g | _d | _d| _d| _d| _	d| _
d S )NF)messagestranslations	locationsflagsuser_commentsauto_commentscontextobsoletein_msgid	in_msgstr
in_msgctxt)r&   r   r   r   rL      s    z!PoFileParser._reset_message_statec          
   C   s@  | j   t| jdkr.tdd | jD }n| jd  }t|ttfrdd t| j	j
D }x<| j D ]2\}}|| j	j
kr| d| jd qh| ||< qhW t|}n| j d d  }| jr| j }nd	}t||t| jt| j| j| j| jd |d
}| jr| js&|| j	j|< n
|| j	|< |  jd7  _|   d	S )z
        Add a message to the catalog based on the current parser state and
        clear the state ready to process the next message.
        r   c             s   s   | ]}|  V  qd S )N)r   ).0r   r   r   r   	<genexpr>   s    z,PoFileParser._add_message.<locals>.<genexpr>r   c             S   s   g | ]}d qS )r   r   )rX   _r   r   r   
<listcomp>   s    z-PoFileParser._add_message.<locals>.<listcomp>r   z5msg has more translations than num_plurals of catalogN)r%   rS   )rN   sortlenrM   tupler   
isinstancelistranger#   num_plurals_invalid_pofilerJ   rS   r   rO   setrP   rR   rQ   rT   rH   rI   rL   )r&   rD   r   idxtranslationrF   r'   r   r   r   _add_message   s2    


zPoFileParser._add_messagec             C   s   | j r|   d S )N)rM   rg   )r&   r   r   r   _finish_current_message   s    z$PoFileParser._finish_current_messagec             C   s*   | dr| || n| ||| d S )N")r   !_process_string_continuation_line_process_keyword_line)r&   r%   r$   rT   r   r   r   _process_message_line   s    
z"PoFileParser._process_message_linec          	   C   sH  xt| j D ]X}y0||r:|t| dkr:|t|d  }P W q tk
r^   | ||d Y qX qW | ||d d S |dkr|   || _|dkr|| _|dkrd| _d| _	| j
t| n|d	kr*d| _	d| _|d
r|dd  dd\}}| jt|t|g n| jdt|g n|dkrDd| _t|| _d S )N) [z$Keyword must be followed by a stringz0Start of line didn't match any expected keyword.)rD   rF   rD   )rD   rG   FTrE   rn   r   ]r   rF   )	_keywordsr   r]   
IndexErrorrc   rh   rT   rJ   rW   rU   rM   r0   r.   rV   splitrN   intrS   )r&   r%   r$   rT   keywordr2   re   msgr   r   r   rk      s8    

z"PoFileParser._process_keyword_linec             C   sV   | j r| jd }n6| jr(| jd d }n | jr6| j}n| ||d d S || d S )Nr   r   z<Got line starting with " but not in msgid, msgstr or msgctxt)rU   rM   rV   rN   rW   rS   rc   r0   )r&   r$   r%   r4   r   r   r   rj      s    z.PoFileParser._process_string_continuation_linec          	   C   s>  |    |dd  drx|dd    D ]p}|d}|dkryt||d d  }W n tk
rt   w0Y nX | j|d | |f q0| j|d f q0W n|dd  drx|dd   dD ]}| j	|
  qW nP|dd  dr"|dd  
 }|r:| j| n| j|dd  
  d S )Nr   :   r   ,.)rh   r   lstriprr   rfindrs   
ValueErrorrO   r0   rP   r3   rR   rQ   )r&   r$   locationposr%   flagcommentr   r   r   _process_comment  s&    
zPoFileParser._process_commentc             C   s   xt |D ]\}}| }t|ts2|| jj}|s8q
|dr~|dd drr| j||dd 	 dd q| 
| q
| || q
W |   | js| js| js| jr| jtd | jd	tdg |   dS )
z
        Reads from the file-like object `fileobj` and adds any po file
        units found in it to the `Catalog` supplied to the constructor.
        #r   N~rw   T)rT   z""r   )	enumerater3   r_   r:   decoder#   charsetr   rl   rz   r   rh   rI   rP   rQ   rR   rM   r0   r.   rN   rg   )r&   fileobjr%   r$   r   r   r   parse  s     

zPoFileParser.parsec             C   sJ   t |tst| jr$t|| j||td| td|d t| d S )NzWARNING:z!WARNING: Problem on line {0}: {1}r   )	r_   r:   AssertionErrorrK   r    r#   printformatrepr)r&   r$   r%   ru   r   r   r   rc   <  s
    
zPoFileParser._invalid_pofileN)FF)F)F)r)   r*   r+   r,   rp   r"   rL   rg   rh   rl   rk   rj   r   r   rc   r   r   r   r   rC      s   
#

)rC   Fc             C   s*   t |||d}t|||d}||  |S )a  Read messages from a ``gettext`` PO (portable object) file from the given
    file-like object and return a `Catalog`.

    >>> from datetime import datetime
    >>> from io import StringIO
    >>> buf = StringIO('''
    ... #: main.py:1
    ... #, fuzzy, python-format
    ... msgid "foo %(name)s"
    ... msgstr "quux %(name)s"
    ...
    ... # A user comment
    ... #. An auto comment
    ... #: main.py:3
    ... msgid "bar"
    ... msgid_plural "baz"
    ... msgstr[0] "bar"
    ... msgstr[1] "baaz"
    ... ''')
    >>> catalog = read_po(buf)
    >>> catalog.revision_date = datetime(2007, 4, 1)

    >>> for message in catalog:
    ...     if message.id:
    ...         print((message.id, message.string))
    ...         print(' ', (message.locations, sorted(list(message.flags))))
    ...         print(' ', (message.user_comments, message.auto_comments))
    (u'foo %(name)s', u'quux %(name)s')
      ([(u'main.py', 1)], [u'fuzzy', u'python-format'])
      ([], [])
    ((u'bar', u'baz'), (u'bar', u'baaz'))
      ([(u'main.py', 3)], [])
      ([u'A user comment'], [u'An auto comment'])

    .. versionadded:: 1.0
       Added support for explicit charset argument.

    :param fileobj: the file-like object to read the PO file from
    :param locale: the locale identifier or `Locale` object, or `None`
                   if the catalog is not bound to a locale (which basically
                   means it's a template)
    :param domain: the message domain
    :param ignore_obsolete: whether to ignore obsolete messages in the input
    :param charset: the character set of the catalog.
    :param abort_invalid: abort read if po file is invalid
    )localedomainr   )rK   )r   rC   r   )r   r   r   rH   r   rK   r#   parserr   r   r   read_poG  s    /
r   zL(\s+|[^\s\w]*\w+[a-zA-Z]-(?=\w+[a-zA-Z])|(?<=[\w\!\"\'\&\.\,\?])-{2,}(?=\w))c             C   s0   d|  dd dd dd dd	 d
d S )zEscape the given string so that it can be included in double-quoted
    strings in ``PO`` files.

    >>> escape('''Say:
    ...   "hello, world!"
    ... ''')
    '"Say:\\n  \\"hello, world!\\"\\n"'

    :param string: the string to escape
    z"%s"\z\\r
   z\tr   z\rr   z\nri   z\")replace)r   r   r   r   escape  s
    r   r   L   c       
         s<  |r|dkrt  }g }x| dD ]}t t|| |krt|}|  x|rg }d}xX|rt t|d d | }	||	 |k r||  ||	7 }q^|s||  P q^W |d| qPW q$|| q$W n
| d}t |dkrt| S |r |d s |d= |d  d7  < dd fd	d
|D  S )a  Convert a string into a format that is appropriate for .po files.

    >>> print(normalize('''Say:
    ...   "hello, world!"
    ... ''', width=None))
    ""
    "Say:\n"
    "  \"hello, world!\"\n"

    >>> print(normalize('''Say:
    ...   "Lorem ipsum dolor sit amet, consectetur adipisicing elit, "
    ... ''', width=32))
    ""
    "Say:\n"
    "  \"Lorem ipsum dolor sit "
    "amet, consectetur adipisicing"
    " elit, \"\n"

    :param string: the string to normalize
    :param prefix: a string that should be prepended to every line
    :param width: the maximum line width; use `None`, 0, or a negative number
                  to completely disable line wrapping
    r   Trw   r   r   r   r   z""
c                s   g | ]} t | qS r   )r   )rX   r$   )prefixr   r   r[     s    znormalize.<locals>.<listcomp>)	r]   r   r   WORD_SEPrr   reverser0   popr   )
r   r   widthZ	prefixlenr   r$   Zchunksbufsizelr   )r   r   	normalize  s6    


r   Tc
          	      s  d#fdd	 fddd$fdd	}
d% fdd		}d
}|rLd}n|rTd}xt |dD ]}|js|rvqdj}rdkrg }x"| D ]}|t|dd7 }qW d|}|d  x|jD ]}|
| qW x|jD ]}|
|dd qW |sg }yt|j	dd d}W n t
k
r6   |j	}Y nX x\|D ]T\}}|rj|	rjd|tjd|f }nd|tjd }||kr>|| q>W |
d|dd |jrΈdddgt|j   |jr |r |
d |jd  dd t|jd kr |
d! |jd   dd || d qdW |sxJt j |dD ]4}x|jD ]}|
| qZW ||d"d d qNW d
S )&a  Write a ``gettext`` PO (portable object) template file for a given
    message catalog to the provided file-like object.

    >>> catalog = Catalog()
    >>> catalog.add(u'foo %(name)s', locations=[('main.py', 1)],
    ...             flags=('fuzzy',))
    <Message...>
    >>> catalog.add((u'bar', u'baz'), locations=[('main.py', 3)])
    <Message...>
    >>> from io import BytesIO
    >>> buf = BytesIO()
    >>> write_po(buf, catalog, omit_header=True)
    >>> print(buf.getvalue().decode("utf8"))
    #: main.py:1
    #, fuzzy, python-format
    msgid "foo %(name)s"
    msgstr ""
    <BLANKLINE>
    #: main.py:3
    msgid "bar"
    msgid_plural "baz"
    msgstr[0] ""
    msgstr[1] ""
    <BLANKLINE>
    <BLANKLINE>

    :param fileobj: the file-like object to write to
    :param catalog: the `Catalog` instance
    :param width: the maximum line width for the generated output; use `None`,
                  0, or a negative number to completely disable line wrapping
    :param no_location: do not emit a location comment for every message
    :param omit_header: do not include the ``msgid ""`` entry at the top of the
                        output
    :param sort_output: whether to sort the messages in the output by msgid
    :param sort_by_file: whether to sort the messages in the output by their
                         locations
    :param ignore_obsolete: whether to ignore obsolete messages and not include
                            them in the output; by default they are included as
                            comments
    :param include_previous: include the old msgid as a comment when
                             updating the catalog
    :param include_lineno: include line number in the location comment
    r   c                s   t | | dS )N)r   r   )r   )keyr   )r   r   r   
_normalize   s    zwrite_po.<locals>._normalizec                s&   t | tr|  jd} |  d S )Nbackslashreplace)r_   r:   encoder   write)text)r#   r   r   r   _write  s    
zwrite_po.<locals>._writec                sB   rdkr}nd}x&t | |D ]} d|| f  q"W d S )Nr   r   z#%s %s
)r   r3   )r   r   _widthr$   )r   r   r   r   _write_comment  s
    z write_po.<locals>._write_commentc          	      s  t | jttfr| jr.d| | j|f  d| | jd |f  d| | jd |f  xtjD ]D}y| j| }W n tk
r   d}Y nX d|| ||f  qrW nT| jrڈd| | j|f  d| | j|f  d| | jpd|f  d S )	Nz%smsgctxt %s
z%smsgid %s
r   z%smsgid_plural %s
r   r   z%smsgstr[%d] %s
z%smsgstr %s
)	r_   idr`   r^   rS   ra   rb   r   rq   )r'   r   re   r   )r   r   r#   r   r   _write_message  s(    
z write_po.<locals>._write_messageNr'   r}   )sort_byr   z# )r   subsequent_indentr   ry   )r   c             S   s"   | d t | d tr| d pdfS )Nr   r   r   )r_   rs   )xr   r   r   <lambda>N      zwrite_po.<locals>.<lambda>)r   z%s:%d/z%srm   rv   z#%s
z, zmsgid %s|r   zmsgid_plural %sz#~ )r   )r   )r   )_sort_messagesr   Zheader_commentr   r   r   rQ   rR   sortedrO   	TypeErrorr   r7   sepr0   rP   Zprevious_idr]   rT   values)r   r#   r   Zno_locationZomit_headerZsort_outputZsort_by_filerH   Zinclude_previousZinclude_linenor   r   r   r'   Zcomment_headerr   r$   r   ZlocsrO   filenamer%   r}   r   )r   r   r#   r   r   r   write_po  st    .


r   c             C   s6   t | } |dkr|   n|dkr2| jdd d | S )z
    Sort the given message iterable by the given criteria.

    Always returns a list.

    :param messages: An iterable of Messages.
    :param sort_by: Sort by which criteria? Options are `message` and `location`.
    :return: list[Message]
    r'   r}   c             S   s   | j S )N)rO   )r   r   r   r   r     r   z _sort_messages.<locals>.<lambda>)r   )r`   r\   )rM   r   r   r   r   r   s  s    

r   )NNFNF)r   r   )r   FFFFFFT)r,   r7   r   Zbabel.messages.catalogr   r   Z
babel.utilr   r   r   r   	Exceptionr    r.   rC   r   r   r   r   r   r   r   r   r   r   r   <module>
   s&   	- I
5

=  
  