This is a rewrite of the check_po script.
A few checks have been improved and a new check was added. The new check is for XML special characters. svn: r7583
This commit is contained in:
		
							
								
								
									
										570
									
								
								po/check_po
									
									
									
									
									
								
							
							
						
						
									
										570
									
								
								po/check_po
									
									
									
									
									
								
							@@ -1,8 +1,8 @@
 | 
				
			|||||||
#! /usr/bin/env python
 | 
					#! /usr/bin/env python
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# Gramps - a GTK+/GNOME based genealogy program
 | 
					# check_po - a gramps tool to check validity of po files
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# Copyright (C) 2000-2006  Donald N. Allingham
 | 
					# Copyright (C) 2006-2006  Kees Bakker
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# This program is free software; you can redistribute it and/or modify
 | 
					# This program is free software; you can redistribute it and/or modify
 | 
				
			||||||
# it under the terms of the GNU General Public License as published by
 | 
					# it under the terms of the GNU General Public License as published by
 | 
				
			||||||
@@ -18,25 +18,11 @@
 | 
				
			|||||||
# along with this program; if not, write to the Free Software
 | 
					# along with this program; if not, write to the Free Software
 | 
				
			||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 | 
					# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# $Id: check_po,v 1.1.2.6 2006/04/22 18:30:33 rshura Exp $
 | 
					# $Id:$
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
import re
 | 
					import re
 | 
				
			||||||
 | 
					
 | 
				
			||||||
f = open('gramps.pot')
 | 
					 | 
				
			||||||
template_total = 0
 | 
					 | 
				
			||||||
for line in f.xreadlines():
 | 
					 | 
				
			||||||
    try:
 | 
					 | 
				
			||||||
        if (line.split()[0] == 'msgid'):
 | 
					 | 
				
			||||||
            template_total += 1
 | 
					 | 
				
			||||||
    except:
 | 
					 | 
				
			||||||
        pass
 | 
					 | 
				
			||||||
f.close()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
NONE   = 0
 | 
					 | 
				
			||||||
MSGID  = 1
 | 
					 | 
				
			||||||
MSGSTR = 2
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
all_total = {}
 | 
					all_total = {}
 | 
				
			||||||
all_fuzzy = {}
 | 
					all_fuzzy = {}
 | 
				
			||||||
all_untranslated = {}
 | 
					all_untranslated = {}
 | 
				
			||||||
@@ -47,170 +33,400 @@ all_context = {}
 | 
				
			|||||||
all_coverage = {}
 | 
					all_coverage = {}
 | 
				
			||||||
all_template_coverage = {}
 | 
					all_template_coverage = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
def strip_quotes(st):
 | 
					def strip_quotes(st):
 | 
				
			||||||
    if len(st.strip()) > 2:
 | 
						st = st.strip()
 | 
				
			||||||
        return st.strip()[1:-1]
 | 
						if len(st) >= 2 and st[0] == '"' and st[len(st)-1] == '"':
 | 
				
			||||||
    else:
 | 
							st = st.strip()[1:-1]
 | 
				
			||||||
        return ""
 | 
						return st
 | 
				
			||||||
 | 
					
 | 
				
			||||||
args = sys.argv
 | 
					class Msgid:
 | 
				
			||||||
while len(args) > 1:
 | 
						fuzzy_pat = re.compile( 'fuzzy' )
 | 
				
			||||||
    args = args[1:]
 | 
						tips_xml_pat = re.compile( r'tips\.xml' )
 | 
				
			||||||
 | 
						def __init__( self, lineno ):
 | 
				
			||||||
 | 
							self._msgid = []
 | 
				
			||||||
 | 
							self._msgstr = []
 | 
				
			||||||
 | 
							self._cmnt = []
 | 
				
			||||||
 | 
							self.lineno = lineno
 | 
				
			||||||
 | 
							self.is_fuzzy = 0
 | 
				
			||||||
 | 
							self.has_sfmt_mismatch = 0
 | 
				
			||||||
 | 
							self.has_named_sfmt_mismatch = 0
 | 
				
			||||||
 | 
							self.has_fmt_missing_sd = 0
 | 
				
			||||||
 | 
							self.has_context_error = 0
 | 
				
			||||||
 | 
							self.has_named_fmt_mismatch = 0
 | 
				
			||||||
 | 
							self.has_xml_error = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    f = open(args[0],"r")
 | 
						def diag( self ):
 | 
				
			||||||
    
 | 
							if 0:
 | 
				
			||||||
    mode = NONE
 | 
								print "lineno: %d" % self.lineno
 | 
				
			||||||
    fuzzy = False
 | 
								sys.stdout.write( ''.join( self._msgid ) )
 | 
				
			||||||
    fuzzy_count = 0
 | 
								sys.stdout.write( ''.join( self._msgstr ) )
 | 
				
			||||||
    string_map = {}
 | 
							else:
 | 
				
			||||||
    current_msgid = ""
 | 
								# Compatible with the old check_po
 | 
				
			||||||
    current_msgstr = ""
 | 
								print "%d '%s' : '%s'" % ( self.lineno, self.msgid(), self.msgstr() )
 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    for line in f.xreadlines():
 | 
					 | 
				
			||||||
        data = line.split(None,1)
 | 
					 | 
				
			||||||
        if mode == NONE:
 | 
					 | 
				
			||||||
            if len(data) > 0 and data[0] == "msgid":
 | 
					 | 
				
			||||||
                mode = MSGID
 | 
					 | 
				
			||||||
                if len(data) > 1:
 | 
					 | 
				
			||||||
                    current_msgid = strip_quotes(data[1])
 | 
					 | 
				
			||||||
            elif (len(data) > 0) and (data[0] == "#,") \
 | 
					 | 
				
			||||||
                     and (data[1] == 'fuzzy\n'):
 | 
					 | 
				
			||||||
                fuzzy = True
 | 
					 | 
				
			||||||
        elif mode == MSGID:
 | 
					 | 
				
			||||||
            if data[0][0] == '"':
 | 
					 | 
				
			||||||
                current_msgid += strip_quotes(line)
 | 
					 | 
				
			||||||
            elif data[0] == "msgstr":
 | 
					 | 
				
			||||||
                mode = MSGSTR
 | 
					 | 
				
			||||||
                if len(data) > 1:
 | 
					 | 
				
			||||||
                    current_msgstr = strip_quotes(data[1])
 | 
					 | 
				
			||||||
        elif mode == MSGSTR:
 | 
					 | 
				
			||||||
            if line == "" or line[0] == "#":
 | 
					 | 
				
			||||||
                mode = NONE
 | 
					 | 
				
			||||||
                if fuzzy:
 | 
					 | 
				
			||||||
                    fuzzy = False
 | 
					 | 
				
			||||||
                    fuzzy_count += 1
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    string_map[current_msgid] = current_msgstr
 | 
					 | 
				
			||||||
            elif len(data) > 0 and data[0][0] == '"':
 | 
					 | 
				
			||||||
                current_msgstr += strip_quotes(line)
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    f.close()
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    named = re.compile('%\((\w+)\)\d*s')
 | 
					 | 
				
			||||||
    bnamed = re.compile('%\((\w+)\)\d*[^sd]')
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    total = len(string_map) + fuzzy_count
 | 
					 | 
				
			||||||
    untranslated = 0
 | 
					 | 
				
			||||||
    percent_s = 0
 | 
					 | 
				
			||||||
    percent_s_list = []
 | 
					 | 
				
			||||||
    named_s = 0
 | 
					 | 
				
			||||||
    named_s_list = []
 | 
					 | 
				
			||||||
    bnamed_s = 0
 | 
					 | 
				
			||||||
    bnamed_s_list = []
 | 
					 | 
				
			||||||
    context = 0
 | 
					 | 
				
			||||||
    context_list = []
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    for (msgid,msgstr) in string_map.items():
 | 
					 | 
				
			||||||
        if msgstr == "":
 | 
					 | 
				
			||||||
            untranslated += 1
 | 
					 | 
				
			||||||
            continue
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
        cnt1 = msgid.count('%s')
 | 
					 | 
				
			||||||
        cnt2 = msgstr.count('%s')
 | 
					 | 
				
			||||||
        if cnt1 != cnt2:
 | 
					 | 
				
			||||||
            percent_s += 1
 | 
					 | 
				
			||||||
            percent_s_list.append(msgid)
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
        list1 = named.findall(msgid)
 | 
					 | 
				
			||||||
        list2 = named.findall(msgstr)
 | 
					 | 
				
			||||||
        if len(list1) != len(list2):
 | 
					 | 
				
			||||||
            percent_s += 1
 | 
					 | 
				
			||||||
            percent_s_list.append(msgid)
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
        list1.sort()
 | 
					 | 
				
			||||||
        list2.sort()
 | 
					 | 
				
			||||||
        if list1 != list2:
 | 
					 | 
				
			||||||
            named_s += 1
 | 
					 | 
				
			||||||
            named_s_list.append(msgid)
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
        match = bnamed.match(msgstr)
 | 
					 | 
				
			||||||
        if match:
 | 
					 | 
				
			||||||
            bnamed_s +=1
 | 
					 | 
				
			||||||
            bnamed_s_list.append(msgstr)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        has_context1 = (msgid.count('|') > 0)
 | 
						def msgid( self ):
 | 
				
			||||||
        has_context2 = (msgstr.count('|') > 0)
 | 
							if not self._msgid:
 | 
				
			||||||
        if has_context1 and has_context2 and (msgid != msgstr):
 | 
								return None
 | 
				
			||||||
            context += 1
 | 
							txt = ''
 | 
				
			||||||
            context_list.append(msgid)
 | 
							for l in self._msgid:
 | 
				
			||||||
        
 | 
								l = re.sub( r'msgid\s+', '', l )
 | 
				
			||||||
    
 | 
								l = strip_quotes( l )
 | 
				
			||||||
    coverage = (1.0 - (float(untranslated)/float(total))) * 100
 | 
								txt += l
 | 
				
			||||||
    template_coverage = coverage * float(total) / float(template_total)
 | 
							return txt
 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    print "File:            %s" % args[0]
 | 
					 | 
				
			||||||
    print "Template total:  %d" % template_total
 | 
					 | 
				
			||||||
    print "PO total:        %d" % total
 | 
					 | 
				
			||||||
    all_total[args[0]] = total
 | 
					 | 
				
			||||||
    print "Fuzzy:           %d" % fuzzy_count
 | 
					 | 
				
			||||||
    all_fuzzy[args[0]] = fuzzy_count
 | 
					 | 
				
			||||||
    print "Untranslated:    %d" % untranslated
 | 
					 | 
				
			||||||
    all_untranslated[args[0]] = untranslated
 | 
					 | 
				
			||||||
    print "%%s mismatches:   %d" % percent_s
 | 
					 | 
				
			||||||
    all_percent_s[args[0]] = percent_s
 | 
					 | 
				
			||||||
    print "%%()s mismatches: %d" % named_s
 | 
					 | 
				
			||||||
    all_named_s[args[0]] = named_s
 | 
					 | 
				
			||||||
    print "%%() missing s/d: %d" % bnamed_s
 | 
					 | 
				
			||||||
    all_bnamed_s[args[0]] = bnamed_s
 | 
					 | 
				
			||||||
    print "Runaway context: %d" % context
 | 
					 | 
				
			||||||
    all_context[args[0]] = context
 | 
					 | 
				
			||||||
    print "PO Coverage:     %5.2f%%" % coverage
 | 
					 | 
				
			||||||
    all_coverage[args[0]] = coverage
 | 
					 | 
				
			||||||
    print "Template Coverage:  %5.2f%%" % template_coverage
 | 
					 | 
				
			||||||
    all_template_coverage[args[0]] = coverage
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    if percent_s:
 | 
					 | 
				
			||||||
        print "\n-------- %s mismatches --------------"
 | 
					 | 
				
			||||||
        for i in percent_s_list:
 | 
					 | 
				
			||||||
            print "'%s' : '%s'" % (i, string_map[i])
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    if named_s:
 | 
					 | 
				
			||||||
        print "\n-------- %()s mismatches ------------"
 | 
					 | 
				
			||||||
        for i in named_s_list:
 | 
					 | 
				
			||||||
            print "'%s' : '%s'" % (i, string_map[i])
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    if bnamed_s:
 | 
					 | 
				
			||||||
        print "\n-------- %() missing s or d ---------"
 | 
					 | 
				
			||||||
        for i in bnamed_s_list:
 | 
					 | 
				
			||||||
            print "'%s' : '%s'" % (i, string_map[i])
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if context:
 | 
						def add_msgid( self, line ):
 | 
				
			||||||
        print "\n-------- Runaway context in translation  ---------"
 | 
							self._msgid.append( line )
 | 
				
			||||||
        for i in context_list:
 | 
					 | 
				
			||||||
            print "'%s' : '%s'" % (i, string_map[i])
 | 
					 | 
				
			||||||
    print ""
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
if len(sys.argv) > 2:
 | 
						def msgstr( self ):
 | 
				
			||||||
    print "\n\nFile \tTotal \tFuzzy \tUntranslated \t%s mismatch \t%()s mismatch \tmissing s/d \tcontext \tCoverage"
 | 
							if not self._msgstr:
 | 
				
			||||||
    for pofile in sys.argv[1:]:
 | 
								return None
 | 
				
			||||||
        print "%s \t%5d \t%7d \t%7d \t%7d \t%7d \t%7d \t%7d \t%3.2f%% \t%3.2f%%" %\
 | 
							txt = ''
 | 
				
			||||||
           (pofile,
 | 
							for l in self._msgstr:
 | 
				
			||||||
            all_total[pofile],
 | 
								l = re.sub( r'msgstr\s+', '', l )
 | 
				
			||||||
            all_fuzzy[pofile],
 | 
								l = strip_quotes( l )
 | 
				
			||||||
            all_untranslated[pofile],
 | 
								txt += l
 | 
				
			||||||
            all_percent_s[pofile],
 | 
							return txt
 | 
				
			||||||
            all_named_s[pofile],
 | 
					 | 
				
			||||||
            all_bnamed_s[pofile],
 | 
					 | 
				
			||||||
            all_context[pofile],
 | 
					 | 
				
			||||||
            all_coverage[pofile],
 | 
					 | 
				
			||||||
            all_template_coverage[pofile]
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
f = open("used_strings.txt","w")
 | 
						def add_msgstr( self, line ):
 | 
				
			||||||
keys = string_map.keys()
 | 
							self._msgstr.append( line )
 | 
				
			||||||
keys.sort()
 | 
					
 | 
				
			||||||
for i in keys:
 | 
						def add_cmnt( self, line ):
 | 
				
			||||||
    f.write(i + "\n")
 | 
							self._cmnt.append( line )
 | 
				
			||||||
f.close()
 | 
							if not self.is_fuzzy and self.fuzzy_pat.search( line ):
 | 
				
			||||||
 | 
								self.is_fuzzy = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def is_tips_xml( self ):
 | 
				
			||||||
 | 
							for c in self._cmnt:
 | 
				
			||||||
 | 
								if self.tips_xml_pat.search( c ):
 | 
				
			||||||
 | 
									return 1
 | 
				
			||||||
 | 
							return 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def set_sfmt_mismatch( self ):
 | 
				
			||||||
 | 
							self.has_sfmt_mismatch = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def set_named_fmt_mismatch( self ):
 | 
				
			||||||
 | 
							self.has_named_fmt_mismatch = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def set_fmt_missing_sd( self ):
 | 
				
			||||||
 | 
							self.has_fmt_missing_sd = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def set_context_error( self ):
 | 
				
			||||||
 | 
							self.has_context_error = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def set_named_fmt_mismatch( self ):
 | 
				
			||||||
 | 
							self.has_named_fmt_mismatch = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						def set_xml_error( self ):
 | 
				
			||||||
 | 
							self.has_xml_error = 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def read_msgs( fname ):
 | 
				
			||||||
 | 
						empty_pat   = re.compile( r'^ \s* $',      re.VERBOSE )
 | 
				
			||||||
 | 
						comment_pat = re.compile( r'\#',           re.VERBOSE )
 | 
				
			||||||
 | 
						msgid_pat   = re.compile( r'msgid \s+ "',  re.VERBOSE )
 | 
				
			||||||
 | 
						msgstr_pat  = re.compile( r'msgstr \s+ "', re.VERBOSE )
 | 
				
			||||||
 | 
						str_pat     = re.compile( r'"',            re.VERBOSE )
 | 
				
			||||||
 | 
						old_pat     = re.compile( r'\#~ \s+ ',     re.VERBOSE )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						f = open( fname )
 | 
				
			||||||
 | 
						lines = f.readlines()
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						# parse it like a statemachine
 | 
				
			||||||
 | 
						NONE   = 0			# Nothing detected, yet
 | 
				
			||||||
 | 
						CMNT   = 1			# Inside comment part
 | 
				
			||||||
 | 
						MSGID  = 2			# Inside msgid part
 | 
				
			||||||
 | 
						MSGSTR = 3			# Inside msgstr part
 | 
				
			||||||
 | 
						STR    = 4			# A continuation string
 | 
				
			||||||
 | 
						OLD    = 5			# An old pattern with #~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						state = NONE
 | 
				
			||||||
 | 
						msg = None
 | 
				
			||||||
 | 
						msgs = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for ix in range( len(lines) ):	# Use line numbers for messages
 | 
				
			||||||
 | 
							line = lines[ix]
 | 
				
			||||||
 | 
							lineno = ix + 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							m = empty_pat.match( line )
 | 
				
			||||||
 | 
							if m:
 | 
				
			||||||
 | 
								continue	# Empty lines are not interesting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							# What's the next state?
 | 
				
			||||||
 | 
							if  old_pat.match( line ):
 | 
				
			||||||
 | 
								next_state = OLD
 | 
				
			||||||
 | 
							elif comment_pat.match( line ):
 | 
				
			||||||
 | 
								next_state = CMNT
 | 
				
			||||||
 | 
							elif msgid_pat.match( line ):
 | 
				
			||||||
 | 
								next_state = MSGID
 | 
				
			||||||
 | 
							elif msgstr_pat.match( line ):
 | 
				
			||||||
 | 
								next_state = MSGSTR
 | 
				
			||||||
 | 
							elif str_pat.match( line ):
 | 
				
			||||||
 | 
								next_state = STR
 | 
				
			||||||
 | 
							else:
 | 
				
			||||||
 | 
								next_state = NONE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							#print "%(state)d->%(next_state)d\t%(line)s" % vars()
 | 
				
			||||||
 | 
							if state == NONE:
 | 
				
			||||||
 | 
								# expect msgid or comment or old stuff
 | 
				
			||||||
 | 
								if next_state == CMNT:
 | 
				
			||||||
 | 
									state = CMNT
 | 
				
			||||||
 | 
									msg = Msgid( lineno ) # Start with an empty new item
 | 
				
			||||||
 | 
									msgs.append( msg )
 | 
				
			||||||
 | 
									msg.add_cmnt( line )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								elif next_state == MSGID:
 | 
				
			||||||
 | 
									state = MSGID
 | 
				
			||||||
 | 
									msg = Msgid( lineno ) # Start with an empty new item
 | 
				
			||||||
 | 
									msgs.append( msg )
 | 
				
			||||||
 | 
									msg.add_msgid( line )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								elif next_state == MSGSTR:
 | 
				
			||||||
 | 
									print 'WARNING: Wild msgstr at %(fname)s:%(lineno)d' % vars()
 | 
				
			||||||
 | 
									state = MSGSTR
 | 
				
			||||||
 | 
									msg = Msgid( lineno ) # Start with an empty new item
 | 
				
			||||||
 | 
									msgs.append( msg )
 | 
				
			||||||
 | 
									msg.add_msgstr( line )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								elif next_state == STR:
 | 
				
			||||||
 | 
									print 'WARNING: Wild string at %(fname)s:%(lineno)d' % vars()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								elif next_state == OLD:
 | 
				
			||||||
 | 
									pass	# Just skip
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							elif state == CMNT:
 | 
				
			||||||
 | 
								if next_state == CMNT:
 | 
				
			||||||
 | 
									if msg:
 | 
				
			||||||
 | 
										msg.add_cmnt( line )
 | 
				
			||||||
 | 
									else:
 | 
				
			||||||
 | 
										# Note. We may need to do something about these comments
 | 
				
			||||||
 | 
										# Skip for now
 | 
				
			||||||
 | 
										pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								elif next_state == MSGID:
 | 
				
			||||||
 | 
									state = MSGID
 | 
				
			||||||
 | 
									if not msg:
 | 
				
			||||||
 | 
										msg = Msgid( lineno ) # Start with an empty new item
 | 
				
			||||||
 | 
										msgs.append( msg )
 | 
				
			||||||
 | 
									msg.add_msgid( line )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								elif next_state == MSGSTR:
 | 
				
			||||||
 | 
									print 'WARNING: Wild msgstr at %(fname)s:%(lineno)d' % vars()
 | 
				
			||||||
 | 
									state = MSGSTR
 | 
				
			||||||
 | 
									msg = Msgid( lineno ) # Start with an empty new item
 | 
				
			||||||
 | 
									msgs.append( msg )
 | 
				
			||||||
 | 
									msg.add_msgstr( line )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								elif next_state == STR:
 | 
				
			||||||
 | 
									print 'WARNING: Wild string at %(fname)s:%(lineno)d' % vars()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								elif next_state == OLD:
 | 
				
			||||||
 | 
									msg = None
 | 
				
			||||||
 | 
									pass	# Just skip
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							elif state == MSGID:
 | 
				
			||||||
 | 
								if next_state == CMNT:
 | 
				
			||||||
 | 
									# Hmmm. A comment here?
 | 
				
			||||||
 | 
									print 'WARNING: Unexpted comment at %(fname)s:%(lineno)d' % vars()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								elif next_state == MSGID:
 | 
				
			||||||
 | 
									raise Exception( 'Unexpected msgid at %(fname)s:%(lineno)d' % vars() )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								elif next_state == MSGSTR:
 | 
				
			||||||
 | 
									state = MSGSTR
 | 
				
			||||||
 | 
									msg.add_msgstr( line )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								elif next_state == STR:
 | 
				
			||||||
 | 
									msg.add_msgid( line )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								elif next_state == OLD:
 | 
				
			||||||
 | 
									msg = None
 | 
				
			||||||
 | 
									pass	# Just skip
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							elif state == MSGSTR:
 | 
				
			||||||
 | 
								if next_state == CMNT:
 | 
				
			||||||
 | 
									# A comment probably starts a new item
 | 
				
			||||||
 | 
									state = CMNT
 | 
				
			||||||
 | 
									msg = Msgid( lineno )
 | 
				
			||||||
 | 
									msgs.append( msg )
 | 
				
			||||||
 | 
									msg.add_cmnt( line )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								elif next_state == MSGID:
 | 
				
			||||||
 | 
									state = MSGID
 | 
				
			||||||
 | 
									msg = Msgid( lineno )
 | 
				
			||||||
 | 
									msgs.append( msg )
 | 
				
			||||||
 | 
									msg.add_msgid( line )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								elif next_state == MSGSTR:
 | 
				
			||||||
 | 
									raise Exception( 'Unexpected msgstr at %(fname)s:%(lineno)d' % vars() )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								elif next_state == STR:
 | 
				
			||||||
 | 
									msg.add_msgstr( line )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								elif next_state == OLD:
 | 
				
			||||||
 | 
									msg = None
 | 
				
			||||||
 | 
									pass	# Just skip
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							else:
 | 
				
			||||||
 | 
								raise Exception( 'Unexpected state in po parsing (state = %d)' % state )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# Strip items with just comments. (Can this happen?)
 | 
				
			||||||
 | 
						msgs1 = []
 | 
				
			||||||
 | 
						for m in msgs:
 | 
				
			||||||
 | 
							if not m.msgid() and not m.msgstr():
 | 
				
			||||||
 | 
								#print "INFO: No msgid or msgstr at %s:%s" % ( fname, m.lineno )
 | 
				
			||||||
 | 
								pass
 | 
				
			||||||
 | 
							else:
 | 
				
			||||||
 | 
								msgs1.append( m )
 | 
				
			||||||
 | 
						msgs = msgs1
 | 
				
			||||||
 | 
						return msgs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def analyze_msgs( fname, msgs, nr_templates = None, nth = 0 ):
 | 
				
			||||||
 | 
						nr_fuzzy = 0
 | 
				
			||||||
 | 
						nr_untranslated = 0
 | 
				
			||||||
 | 
						nr_sfmt_mismatches = 0
 | 
				
			||||||
 | 
						nr_named_fmt_mismatches = 0
 | 
				
			||||||
 | 
						nr_fmt_missing_sd = 0
 | 
				
			||||||
 | 
						nr_context_errors = 0
 | 
				
			||||||
 | 
						nr_xml_errors = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# A pattern to find %() without s or d
 | 
				
			||||||
 | 
						# Here is a command to use for testing
 | 
				
			||||||
 | 
						# print re.compile('% \( \w+ \) \d* (\D|$)', re.VERBOSE).findall( '%(event_name)s: %(place)s%(endnotes)s. ' )
 | 
				
			||||||
 | 
						find_named_fmt_pat2 = re.compile('% \( \w+ \) \d* (\D|$)', re.VERBOSE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# A pattern to find all %()
 | 
				
			||||||
 | 
						find_named_fmt_pat = re.compile('% \( \w+ \) \d* \D', re.VERBOSE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						# Special XML characters
 | 
				
			||||||
 | 
						# It is not allowed to have a quote, an ampersand or an angle bracket
 | 
				
			||||||
 | 
						xml_chars_pat = re.compile( r'(?<=\W) > | " | & (?!(quot|nbsp|gt|amp);)', re.VERBOSE )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for msg in msgs:
 | 
				
			||||||
 | 
							msgid = msg.msgid()
 | 
				
			||||||
 | 
							msgstr = msg.msgstr()
 | 
				
			||||||
 | 
							#print
 | 
				
			||||||
 | 
							#print "msgid: %(msgid)s" % vars()
 | 
				
			||||||
 | 
							#print "msgstr: %(msgstr)s" % vars()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if not msgstr:
 | 
				
			||||||
 | 
								nr_untranslated += 1
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if msg.is_fuzzy:
 | 
				
			||||||
 | 
								nr_fuzzy += 1
 | 
				
			||||||
 | 
								# Skip fuzzies or not?
 | 
				
			||||||
 | 
								# continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							cnt1 = msgid.count('%s')
 | 
				
			||||||
 | 
							cnt2 = msgstr.count('%s')
 | 
				
			||||||
 | 
							if cnt1 != cnt2:
 | 
				
			||||||
 | 
								nr_sfmt_mismatches += 1
 | 
				
			||||||
 | 
								msg.set_sfmt_mismatch()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							# Same number of named formats?
 | 
				
			||||||
 | 
							fmts1 = find_named_fmt_pat.findall( msgid )
 | 
				
			||||||
 | 
							fmts2 = find_named_fmt_pat.findall( msgstr )
 | 
				
			||||||
 | 
							if len( fmts1 ) != len( fmts2 ):
 | 
				
			||||||
 | 
								if not msg.has_sfmt_mismatch:
 | 
				
			||||||
 | 
									nr_sfmt_mismatches += 1
 | 
				
			||||||
 | 
									msg.set_sfmt_mismatch()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							# Do we have the same named formats?
 | 
				
			||||||
 | 
							fmts1.sort()
 | 
				
			||||||
 | 
							fmts2.sort()
 | 
				
			||||||
 | 
							if fmts1 != fmts2:
 | 
				
			||||||
 | 
								nr_named_fmt_mismatches += 1
 | 
				
			||||||
 | 
								msg.set_named_fmt_mismatch()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							# Any formats missing format letter?
 | 
				
			||||||
 | 
							fmts = find_named_fmt_pat2.findall( msgstr )
 | 
				
			||||||
 | 
							for f in fmts:
 | 
				
			||||||
 | 
								if not f in ('s', 'd'):
 | 
				
			||||||
 | 
									nr_fmt_missing_sd += 1
 | 
				
			||||||
 | 
									msg.set_fmt_missing_sd()
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							# Runaway context. In the translated part we only to see
 | 
				
			||||||
 | 
							# the translation of the word after the |
 | 
				
			||||||
 | 
							if msgid.count('|') > 0 and msgstr.count('|') > 0 and msgid != msgstr:
 | 
				
			||||||
 | 
								nr_context_errors += 1
 | 
				
			||||||
 | 
								msg.set_context_error()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							# XML errors
 | 
				
			||||||
 | 
							# Only look at messages in the tips.xml
 | 
				
			||||||
 | 
							if msg.is_tips_xml():
 | 
				
			||||||
 | 
								if xml_chars_pat.search( msgstr ):
 | 
				
			||||||
 | 
									nr_xml_errors += 1
 | 
				
			||||||
 | 
									msg.set_xml_error()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						nr_msgs = len(msgs)
 | 
				
			||||||
 | 
						if nth > 0:
 | 
				
			||||||
 | 
							print
 | 
				
			||||||
 | 
							print "====================================="
 | 
				
			||||||
 | 
						print "%-20s%s"     % ( "File:",              fname )
 | 
				
			||||||
 | 
						print "%-20s%d"     % ( "Template total:",    nr_templates )
 | 
				
			||||||
 | 
						print "%-20s%d"     % ( "PO total:",          nr_msgs )
 | 
				
			||||||
 | 
						print "%-20s%d"     % ( "Fuzzy:",             nr_fuzzy )
 | 
				
			||||||
 | 
						print "%-20s%d"     % ( "Untranslated:",      nr_untranslated )
 | 
				
			||||||
 | 
						print "%-20s%d"     % ( "%s mismatches:",     nr_sfmt_mismatches )
 | 
				
			||||||
 | 
						print "%-20s%d"     % ( "%() name mismatches:",   nr_named_fmt_mismatches )
 | 
				
			||||||
 | 
						print "%-20s%d"     % ( "%() missing s/d:",   nr_fmt_missing_sd )
 | 
				
			||||||
 | 
						print "%-20s%d"     % ( "Runaway context:",   nr_context_errors )
 | 
				
			||||||
 | 
						print "%-20s%d"     % ( "XML special chars:", nr_xml_errors )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						po_coverage = (1.0 - (float(nr_untranslated) / float(nr_msgs))) * 100
 | 
				
			||||||
 | 
						print "%-20s%5.2f%%" % ( "PO Coverage:",       po_coverage )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						template_coverage = po_coverage * float(nr_msgs) / float(nr_templates)
 | 
				
			||||||
 | 
						print "%-20s%5.2f%%" % ( "Template Coverage:", template_coverage )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if nr_sfmt_mismatches:
 | 
				
			||||||
 | 
							print
 | 
				
			||||||
 | 
							print "-------- %s mismatches --------------"
 | 
				
			||||||
 | 
							for m in msgs:
 | 
				
			||||||
 | 
								if m.has_sfmt_mismatch:
 | 
				
			||||||
 | 
									m.diag()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if nr_named_fmt_mismatches:
 | 
				
			||||||
 | 
							print
 | 
				
			||||||
 | 
							print "-------- %() name mismatches --------------"
 | 
				
			||||||
 | 
							for m in msgs:
 | 
				
			||||||
 | 
								if m.has_named_fmt_mismatch:
 | 
				
			||||||
 | 
									m.diag()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if nr_fmt_missing_sd:
 | 
				
			||||||
 | 
							print
 | 
				
			||||||
 | 
							print "-------- %() without 's' or 'd' mismatches --------------"
 | 
				
			||||||
 | 
							for m in msgs:
 | 
				
			||||||
 | 
								if m.has_fmt_missing_sd:
 | 
				
			||||||
 | 
									m.diag()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if nr_context_errors:
 | 
				
			||||||
 | 
							print
 | 
				
			||||||
 | 
							print "-------- Runaway context in translation ---------"
 | 
				
			||||||
 | 
							for m in msgs:
 | 
				
			||||||
 | 
								if m.has_context_error:
 | 
				
			||||||
 | 
									m.diag()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if nr_xml_errors:
 | 
				
			||||||
 | 
							print
 | 
				
			||||||
 | 
							print "-------- unescaped XML special characters ---------"
 | 
				
			||||||
 | 
							for m in msgs:
 | 
				
			||||||
 | 
								if m.has_xml_error:
 | 
				
			||||||
 | 
									m.diag()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def main():
 | 
				
			||||||
 | 
						try:
 | 
				
			||||||
 | 
							pot_msgs = read_msgs( 'gramps.pot' )
 | 
				
			||||||
 | 
							nr_templates = len( pot_msgs )
 | 
				
			||||||
 | 
							#analyze_msgs( 'gramps.pot', pot_msgs )
 | 
				
			||||||
 | 
							nth = 0
 | 
				
			||||||
 | 
							for fname in sys.argv[1:]:
 | 
				
			||||||
 | 
								msgs = read_msgs( fname )
 | 
				
			||||||
 | 
								analyze_msgs( fname, msgs, nr_templates, nth )
 | 
				
			||||||
 | 
								nth += 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						except Exception, e:
 | 
				
			||||||
 | 
							print e
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if __name__ == "__main__":
 | 
				
			||||||
 | 
						main()
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user