3 HXM@sdZddlmZddlZddlZddlmZmZddlm Z ddl m Z ddZ d d Z Gd d d eZGd ddeZdddZejdZddZdddZd ddZddZdS)!z babel.messages.pofile ~~~~~~~~~~~~~~~~~~~~~ Reading and writing of files in the ``gettext`` PO (portable object) format. :copyright: (c) 2013 by the Babel Team. :license: BSD, see LICENSE for more details. )print_functionN)CatalogMessage)wraptext) text_typecCs"dd}tjdj||ddS)zReverse `escape` the given string. >>> print(unescape('"Say:\\n \\"hello, world!\\"\\n"')) Say: "hello, world!" :param string: the string to unescape cSs2|jd}|dkrdS|dkr"dS|dkr.dS|S)Nn t r )group)matchmr/usr/lib/python3.6/pofile.pyreplace_escapes s z!unescape..replace_escapesz \\([\\trn"])r)recompilesub)stringrrrrunescapes  rcCsFd|kr:|j}|jdr&|dd}tt|}dj|St|SdS)aReverse the normalization done by the `normalize` function. >>> print(denormalize(r'''"" ... "Say:\n" ... " \"hello, world!\"\n"''')) Say: "hello, world!" >>> print(denormalize(r'''"" ... "Say:\n" ... " \"Lorem ipsum dolor sit " ... "amet, consectetur adipisicing" ... " elit, \"\n"''')) Say: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, " :param string: the string to denormalize r z""rN) splitlines startswithmaprjoin)rZ escaped_lineslinesrrr denormalize-s    r c@s,eZdZddZddZddZddZd S) _NormalizedStringcGs"g|_x|D]}|j|q WdS)N)_strsappend)selfargsargrrr__init__Ns z_NormalizedString.__init__cCs|jj|jdS)N)r"r#strip)r$srrrr#Ssz_NormalizedString.appendcCsdjtt|jS)Nr)rrrr")r$rrrr Vsz_NormalizedString.denormalizecCs t|jS)N)boolr")r$rrr __nonzero__Ysz_NormalizedString.__nonzero__N)__name__ __module__ __qualname__r'r#r r+rrrrr!Lsr!c@sreZdZdZddddgZdddZd d Zd d Zd dZdddZ dddZ ddZ ddZ ddZ ddZdS) PoFileParserzSupport class to read messages from a ``gettext`` PO (portable object) file and add them to a `Catalog` See `read_po` for simple cases. msgidmsgstrmsgctxt msgid_pluralFcCs$||_||_d|_d|_|jdS)Nr)catalogignore_obsoletecounteroffset_reset_message_state)r$r4r5rrrr'ks zPoFileParser.__init__cCsFg|_g|_g|_g|_g|_g|_d|_d|_d|_d|_ d|_ dS)NF) messages translations locationsflags user_comments auto_commentscontextobsoletein_msgid in_msgstr in_msgctxt)r$rrrr8rsz!PoFileParser._reset_message_statec Cs@|jjt|jdkr.tdd|jD}n|jdj}t|ttfrddt|j j D}x<|jD]2\}}||j j kr|j d|j dqh|j||<qhWt|}n|jddj}|j r|j j}nd}t||t|jt|j|j|j|j d|d }|jr|js&||j j|<n ||j |<|jd7_|jdS) z Add a message to the catalog based on the current parser state and clear the state ready to process the next message. rcSsg|] }|jqSr)r ).0rrrr sz-PoFileParser._add_message..rcSsg|]}dqS)rr)rD_rrrrEsrz5msg has more translations than num_plurals of catalogN)linenor?)r:sortlenr9tupler isinstancelistranger4 num_plurals_invalid_pofiler7r?rr;setr<r>r=r@r5r6r8)r$r0ridxZ translationr2messagerrr _add_messages2     zPoFileParser._add_messagecCs|jr|jdS)N)r9rS)r$rrr_finish_current_messagesz$PoFileParser._finish_current_messagecCs*|jdr|j||n|j|||dS)N")r!_process_string_continuation_line_process_keyword_line)r$rGliner@rrr_process_message_lines z"PoFileParser._process_message_linecCsxL|jD]0}|j|r|t|d kr|t|d}PqW|j||ddS|dkr^|j||_|dkrr||_|dkrd|_d|_|j j t |n|d krd|_d|_ |jdr|d dj d d \}}|jj t|t |gn|jj d t |gn|dkrd|_t ||_dS)N [z0Start of line didn't match any expected keyword.r0r2r3FTr1r]r)rZr[)r0r2)r0r3) _keywordsrrIrOrTr@r7rCrAr9r#r!rBsplitr:intr?)r$rGrXr@keywordr&rQmsgrrrrWs2    z"PoFileParser._process_keyword_linecCsV|jr|jd}n6|jr(|jdd}n |jr6|j}n|j||ddS|j|dS)Nrz|j|ddjdrx|ddjjD]p}|jd}|dkryt||dd}Wntk rtw0YnX|jj|d||fq0|jj|dfq0Wn|ddjdrx|ddjjdD]}|j j|j qWnP|ddjdr"|ddj }|r:|j j|n|j j|ddj dS)Nr:r,.) rTrlstripr^rfindr_ ValueErrorr;r#r<r(r>r=)r$rXlocationposrGflagcommentrrr_process_comments& zPoFileParser._process_commentcCsxt|D]\}}|j}t|ts2|j|jj}|s8q |jdr~|ddjdrr|j||ddj ddq|j |q |j||q W|j |j r|j s|js|jr|jjtd|jjd tdg|jdS) z Reads from the file-like object `fileobj` and adds any po file units found in it to the `Catalog` supplied to the constructor. #rN~rcT)r@z""r) enumerater(rKrdecoder4charsetrrYrfrmrTr6r<r=r>r9r#r!r:rS)r$fileobjrGrXrrrparses    zPoFileParser.parsecCs"td|tdj|d|dS)NzWARNING:z!WARNING: Problem on line {0}: {1}r)printformat)r$rXrGrarrrrOs zPoFileParser._invalid_pofileN)F)F)F)r,r-r.__doc__r]r'r8rSrTrYrWrVrmrtrOrrrrr/]s  #  & r/FcCs&t|||d}t||}|j||S)aTRead messages from a ``gettext`` PO (portable object) file from the given file-like object and return a `Catalog`. >>> from datetime import datetime >>> from babel._compat 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. )localedomainrr)rr/rt)rsrxryr5rrr4parserrrrread_pos.  r{zL(\s+|[^\s\w]*\w+[a-zA-Z]-(?=\w+[a-zA-Z])|(?<=[\w\!\"\'\&\.\,\?])-{2,}(?=\w))cCs0d|jddjddjddjdd jd 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\nrUz\")replace)rrrrescapeVs r~rLc s>|r|dkrt}g}x|jdD]}tt|||krtj|}|jx|rg}d}xX|rtt|d d|} || |kr|j|j|| 7}q^|s|j|jPq^W|jdj|qPWq$|j|q$Wn |jd}t|dkrt|S|r"|d  r"|d =|d d7<ddjfdd |DS)aConvert 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 rTrcrrr z"" csg|]}t|qSr)r~)rDrX)prefixrrrEsznormalize..rrrr) rIrr~WORD_SEPr^reverser#popr) rrwidthZ prefixlenrrXZchunksZbufsizelr)rr normalizehs6      rTc sTd fdd fddd!fdd } d"fdd } d } |rLd } n|rTd } xt| d D]} | js|rvqdj}rdkrg}x"|jD]}|t|dd7}qWdj|}|dx| jD] }| |qWx| jD]}| |ddqW|srg}xZt| j D]L\}}|rB| rB|j d|j t j d|fn|j d|j t j dqW| dj|dd| jrddjdgt| j| jr|r| d| jdddt| jdkr| d| jddd| | dqdW|sPxJtjj| d D]4} x| jD]}| |q$W| | dddqWd S)#aWrite 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',)) >>> catalog.add((u'bar', u'baz'), locations=[('main.py', 3)]) >>> from babel._compat 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 "" #: main.py:3 msgid "bar" msgid_plural "baz" msgstr[0] "" msgstr[1] "" :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 rcst||dS)N)rr)r)keyr)rrr _normalizeszwrite_po.._normalizecs&t|tr|jjd}j|dS)Nbackslashreplace)rKrencoderrwrite)text)r4rsrr_writes zwrite_po.._writecsBrdkr}nd}x&t||D]}d||jfq"WdS)Nrrz#%s %s )rr()rlrZ_widthrX)rrrr_write_comments  z write_po.._write_commentcst|jttfr|jr.d||j|fd||jd|fd||jd|fxtjD]D}y|j|}Wntk rd}YnXd||||fqrWnT|jrڈd||j|fd||j|fd||jpd|fdS) Nz %smsgctxt %s z %smsgid %s rz%smsgid_plural %s rrz%smsgstr[%d] %s z %smsgstr %s ) rKidrLrJr?rMrNr IndexError)rRrrQr)rrr4rr_write_messages( z write_po.._write_messageNrRri)sort_byrz# )rZsubsequent_indentr re)rz%s:%d/z%srZrbz#%s z, zmsgid %s|rzmsgid_plural %sz#~ )r)r)r)_sort_messagesrZheader_commentrrrr=r>sortedr;r#r}ossepr<Z previous_idrIr@values)rsr4rZ no_locationZ omit_headerZ sort_outputZ sort_by_filer5Zinclude_previousZinclude_linenorrrrRZcomment_headerrrXrlZlocsfilenamerGr)rrr4rsrrwrite_posf.             rcCs6t|}|dkr|jn|dkr2|jddd|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] rRricSs|jS)N)r;)rrrrGsz _sort_messages..)r)rLrH)r9rrrrr9s  r)NNFN)rr)rFFFFFFT)rwZ __future__rrrZbabel.messages.catalogrrZ babel.utilrZ babel._compatrrr objectr!r/r{rrr~rrrrrrr s(   ? 4  =