Source code
Revision control
Copy as Markdown
Other Tools
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
"""Gettext PO(T) parser
Parses gettext po and pot files.
"""
import re
from .base import CAN_SKIP, Entity, BadEntity, Parser
class PoEntityMixin:
@property
def val(self):
return self.stringlist_val if self.stringlist_val else self.stringlist_key[0]
@property
def key(self):
return self.stringlist_key
@property
def localized(self):
# gettext denotes a non-localized string by an empty value
return bool(self.stringlist_val)
def __repr__(self):
return self.key[0]
class PoEntity(PoEntityMixin, Entity):
pass
# Unescape and concat a string list
def eval_stringlist(lines):
return "".join(
(
line.replace(r"\\", "\\")
.replace(r"\t", "\t")
.replace(r"\r", "\r")
.replace(r"\n", "\n")
.replace(r"\"", '"')
)
for line in lines
)
class PoParser(Parser):
# gettext l10n fallback at runtime, don't merge en-US strings
capabilities = CAN_SKIP
reKey = re.compile("msgctxt|msgid")
reValue = re.compile("(?P<white>[ \t\r\n]*)(?P<cmd>msgstr)")
reComment = re.compile(r"(?:#.*?\n)+")
# string list item:
# leading whitespace
# `"`
# escaped quotes etc, not quote, newline, backslash
# `"`
reListItem = re.compile(r'[ \t\r\n]*"((?:\\[\\trn"]|[^"\n\\])*)"')
def __init__(self):
super().__init__()
def createEntity(self, ctx, m, current_comment, white_space):
start = cursor = m.start()
id_start = cursor
try:
msgctxt, cursor = self._parse_string_list(ctx, cursor, "msgctxt")
m = self.reWhitespace.match(ctx.contents, cursor)
if m:
cursor = m.end()
except BadEntity:
# no msgctxt is OK
msgctxt = None
if id_start is None:
id_start = cursor
msgid, cursor = self._parse_string_list(ctx, cursor, "msgid")
id_end = cursor
m = self.reWhitespace.match(ctx.contents, cursor)
if m:
cursor = m.end()
val_start = cursor
msgstr, cursor = self._parse_string_list(ctx, cursor, "msgstr")
e = PoEntity(
ctx,
current_comment,
white_space,
(start, cursor),
(id_start, id_end),
(val_start, cursor),
)
e.stringlist_key = (msgid, msgctxt)
e.stringlist_val = msgstr
return e
def _parse_string_list(self, ctx, cursor, key):
if not ctx.contents.startswith(key, cursor):
raise BadEntity
cursor += len(key)
frags = []
while True:
m = self.reListItem.match(ctx.contents, cursor)
if not m:
break
frags.append(m.group(1))
cursor = m.end()
if not frags:
raise BadEntity
return eval_stringlist(frags), cursor