From gotcha at swing.be Fri Aug 8 04:55:10 2003 From: gotcha at swing.be (Godefroid Chapelle) Date: Sun Aug 10 17:05:18 2008 Subject: [ZPT-CVS] CVS: Zope/lib/python/TAL - TALInterpreter.py:1.78.8.1 Message-ID: <200308080855.h788tAb11327@cvs.baymountain.com> Update of /cvs-repository/Zope/lib/python/TAL In directory cvs.zope.org:/tmp/cvs-serv11159 Modified Files: Tag: gotcha-talz3_backport-branch TALInterpreter.py Log Message: backport of few fixes from Zope3 === Zope/lib/python/TAL/TALInterpreter.py 1.78 => 1.78.8.1 === --- Zope/lib/python/TAL/TALInterpreter.py:1.78 Mon Apr 7 13:38:27 2003 +++ Zope/lib/python/TAL/TALInterpreter.py Fri Aug 8 04:55:01 2003 @@ -180,18 +180,7 @@ self.macroStack.append([macroName, slots, entering, self.i18nContext]) def popMacro(self): - stuff = self.macroStack.pop() - self.i18nContext = stuff[3] - return stuff - - def macroContext(self, what): - macroStack = self.macroStack - i = len(macroStack) - while i > 0: - i = i-1 - if macroStack[i][0] == what: - return i - return -1 + return self.macroStack.pop() def __call__(self): assert self.level == 0 @@ -292,14 +281,21 @@ if _len(item) == 2: name, s = item else: - ok, name, s = attrAction(self, item) + # item[2] is the 'action' field: + if item[2] in ('metal', 'tal', 'xmlns', 'i18n'): + if not self.showtal: + continue + ok, name, s = self.attrAction(item) + else: + ok, name, s = attrAction(self, item) if not ok: continue slen = _len(s) if (wrap and col >= align and col + 1 + slen > wrap): - append("\n" + " "*align) + append("\n") + append(" "*align) col = align + slen else: append(" ") @@ -314,8 +310,7 @@ def attrAction(self, item): name, value, action = item[:3] - if action == 'insert' or (action in ('metal', 'tal', 'xmlns', 'i18n') - and not self.showtal): + if action == 'insert': return 0, name, value macs = self.macroStack if action == 'metal' and self.metal and macs: @@ -345,8 +340,6 @@ return 1, name, value def attrAction_tal(self, item): - if item[2] in ('metal', 'tal', 'xmlns', 'i18n'): - return self.attrAction(item) name, value, action = item[:3] ok = 1 expr, xlat, msgid = item[3:] @@ -408,14 +401,6 @@ else: self.do_optTag(stuff) bytecode_handlers["optTag"] = do_optTag - - def dumpMacroStack(self, prefix, suffix, value): - sys.stderr.write("+---- %s%s = %s\n" % (prefix, suffix, value)) - for i in range(len(self.macroStack)): - what, macroName, slots = self.macroStack[i][:3] - sys.stderr.write("| %2d. %-12s %-12s %s\n" % - (i, what, macroName, slots and slots.keys())) - sys.stderr.write("+--------------------------------------\n") def do_rawtextBeginScope(self, (s, col, position, closeprev, dict)): self._stream_write(s) From gotcha at swing.be Fri Aug 8 05:16:17 2003 From: gotcha at swing.be (Godefroid Chapelle) Date: Sun Aug 10 17:05:18 2008 Subject: [ZPT-CVS] CVS: Zope/lib/python/TAL - TALInterpreter.py:1.79 Message-ID: <200308080916.h789GHU14738@cvs.baymountain.com> Update of /cvs-repository/Zope/lib/python/TAL In directory cvs.zope.org:/tmp/cvs-serv14663 Modified Files: TALInterpreter.py Log Message: merge from gotcha-talz3_backport-branch === Zope/lib/python/TAL/TALInterpreter.py 1.78 => 1.79 === --- Zope/lib/python/TAL/TALInterpreter.py:1.78 Mon Apr 7 13:38:27 2003 +++ Zope/lib/python/TAL/TALInterpreter.py Fri Aug 8 05:16:10 2003 @@ -180,18 +180,7 @@ self.macroStack.append([macroName, slots, entering, self.i18nContext]) def popMacro(self): - stuff = self.macroStack.pop() - self.i18nContext = stuff[3] - return stuff - - def macroContext(self, what): - macroStack = self.macroStack - i = len(macroStack) - while i > 0: - i = i-1 - if macroStack[i][0] == what: - return i - return -1 + return self.macroStack.pop() def __call__(self): assert self.level == 0 @@ -292,14 +281,21 @@ if _len(item) == 2: name, s = item else: - ok, name, s = attrAction(self, item) + # item[2] is the 'action' field: + if item[2] in ('metal', 'tal', 'xmlns', 'i18n'): + if not self.showtal: + continue + ok, name, s = self.attrAction(item) + else: + ok, name, s = attrAction(self, item) if not ok: continue slen = _len(s) if (wrap and col >= align and col + 1 + slen > wrap): - append("\n" + " "*align) + append("\n") + append(" "*align) col = align + slen else: append(" ") @@ -314,8 +310,7 @@ def attrAction(self, item): name, value, action = item[:3] - if action == 'insert' or (action in ('metal', 'tal', 'xmlns', 'i18n') - and not self.showtal): + if action == 'insert': return 0, name, value macs = self.macroStack if action == 'metal' and self.metal and macs: @@ -345,8 +340,6 @@ return 1, name, value def attrAction_tal(self, item): - if item[2] in ('metal', 'tal', 'xmlns', 'i18n'): - return self.attrAction(item) name, value, action = item[:3] ok = 1 expr, xlat, msgid = item[3:] @@ -408,14 +401,6 @@ else: self.do_optTag(stuff) bytecode_handlers["optTag"] = do_optTag - - def dumpMacroStack(self, prefix, suffix, value): - sys.stderr.write("+---- %s%s = %s\n" % (prefix, suffix, value)) - for i in range(len(self.macroStack)): - what, macroName, slots = self.macroStack[i][:3] - sys.stderr.write("| %2d. %-12s %-12s %s\n" % - (i, what, macroName, slots and slots.keys())) - sys.stderr.write("+--------------------------------------\n") def do_rawtextBeginScope(self, (s, col, position, closeprev, dict)): self._stream_write(s) From gotcha at swing.be Fri Aug 8 06:37:43 2003 From: gotcha at swing.be (Godefroid Chapelle) Date: Sun Aug 10 17:05:18 2008 Subject: [ZPT-CVS] CVS: Zope/lib/python/TAL - TALInterpreter.py:1.78.4.1 Message-ID: <200308081037.h78AbhD27545@cvs.baymountain.com> Update of /cvs-repository/Zope/lib/python/TAL In directory cvs.zope.org:/tmp/cvs-serv27448 Modified Files: Tag: Zope-2_7-branch TALInterpreter.py Log Message: merging from gotcha-talz3_backport-branch === Zope/lib/python/TAL/TALInterpreter.py 1.78 => 1.78.4.1 === --- Zope/lib/python/TAL/TALInterpreter.py:1.78 Mon Apr 7 13:38:27 2003 +++ Zope/lib/python/TAL/TALInterpreter.py Fri Aug 8 06:37:36 2003 @@ -180,18 +180,7 @@ self.macroStack.append([macroName, slots, entering, self.i18nContext]) def popMacro(self): - stuff = self.macroStack.pop() - self.i18nContext = stuff[3] - return stuff - - def macroContext(self, what): - macroStack = self.macroStack - i = len(macroStack) - while i > 0: - i = i-1 - if macroStack[i][0] == what: - return i - return -1 + return self.macroStack.pop() def __call__(self): assert self.level == 0 @@ -292,14 +281,21 @@ if _len(item) == 2: name, s = item else: - ok, name, s = attrAction(self, item) + # item[2] is the 'action' field: + if item[2] in ('metal', 'tal', 'xmlns', 'i18n'): + if not self.showtal: + continue + ok, name, s = self.attrAction(item) + else: + ok, name, s = attrAction(self, item) if not ok: continue slen = _len(s) if (wrap and col >= align and col + 1 + slen > wrap): - append("\n" + " "*align) + append("\n") + append(" "*align) col = align + slen else: append(" ") @@ -314,8 +310,7 @@ def attrAction(self, item): name, value, action = item[:3] - if action == 'insert' or (action in ('metal', 'tal', 'xmlns', 'i18n') - and not self.showtal): + if action == 'insert': return 0, name, value macs = self.macroStack if action == 'metal' and self.metal and macs: @@ -345,8 +340,6 @@ return 1, name, value def attrAction_tal(self, item): - if item[2] in ('metal', 'tal', 'xmlns', 'i18n'): - return self.attrAction(item) name, value, action = item[:3] ok = 1 expr, xlat, msgid = item[3:] @@ -408,14 +401,6 @@ else: self.do_optTag(stuff) bytecode_handlers["optTag"] = do_optTag - - def dumpMacroStack(self, prefix, suffix, value): - sys.stderr.write("+---- %s%s = %s\n" % (prefix, suffix, value)) - for i in range(len(self.macroStack)): - what, macroName, slots = self.macroStack[i][:3] - sys.stderr.write("| %2d. %-12s %-12s %s\n" % - (i, what, macroName, slots and slots.keys())) - sys.stderr.write("+--------------------------------------\n") def do_rawtextBeginScope(self, (s, col, position, closeprev, dict)): self._stream_write(s) From gotcha at swing.be Fri Aug 15 09:50:31 2003 From: gotcha at swing.be (Godefroid Chapelle) Date: Sun Aug 10 17:05:18 2008 Subject: [ZPT-CVS] CVS: Zope/lib/python/TAL/tests/output - test29.html:1.1.6.1 test_metal4.html:1.1.2.1 test_metal5.html:1.1.2.1 test_metal6.html:1.1.2.1 test_metal7.html:1.1.2.1 test_metal1.html:1.5.38.1 Message-ID: <200308151350.h7FDoVA09947@cvs.baymountain.com> Update of /cvs-repository/Zope/lib/python/TAL/tests/output In directory cvs.zope.org:/tmp/cvs-serv8434/tests/output Modified Files: Tag: gotcha-talz3_backport-branch test_metal1.html Added Files: Tag: gotcha-talz3_backport-branch test29.html test_metal4.html test_metal5.html test_metal6.html test_metal7.html Log Message: backport of TAL fixes from z3 - i18n and metal interactions - fix handling of nested translations with tal:content/replace and i18n:name some reformatting to ease comparisons between 2.x and 3 === Added File Zope/lib/python/TAL/tests/output/test29.html ===
AT THE TONE THE TIME WILL BE 59 MINUTES AFTER 6 PM... BEEP!
=== Added File Zope/lib/python/TAL/tests/output/test_metal4.html === Z3 UI === Added File Zope/lib/python/TAL/tests/output/test_metal5.html === Z3 UI === Added File Zope/lib/python/TAL/tests/output/test_metal6.html === Z3 UI === Added File Zope/lib/python/TAL/tests/output/test_metal7.html === === Zope/lib/python/TAL/tests/output/test_metal1.html 1.5 => 1.5.38.1 === --- Zope/lib/python/TAL/tests/output/test_metal1.html:1.5 Mon Oct 28 15:43:11 2002 +++ Zope/lib/python/TAL/tests/output/test_metal1.html Fri Aug 15 09:50:19 2003 @@ -22,7 +22,7 @@ AAA - + INNER BBB @@ -48,7 +48,7 @@ AAA - + INNER INNERSLOT @@ -63,7 +63,7 @@ INNER - INNERSLOT + INNERSLOT INNER @@ -72,8 +72,8 @@ INNER - INSLOT + INSLOT -INSLOT +INSLOT From gotcha at swing.be Fri Aug 15 09:50:46 2003 From: gotcha at swing.be (Godefroid Chapelle) Date: Sun Aug 10 17:05:19 2008 Subject: [ZPT-CVS] CVS: Zope/lib/python/TAL - TALGenerator.py:1.65.4.1 TALInterpreter.py:1.78.8.2 Message-ID: <200308151350.h7FDokY09999@cvs.baymountain.com> Update of /cvs-repository/Zope/lib/python/TAL In directory cvs.zope.org:/tmp/cvs-serv8434 Modified Files: Tag: gotcha-talz3_backport-branch TALGenerator.py TALInterpreter.py Log Message: backport of TAL fixes from z3 - i18n and metal interactions - fix handling of nested translations with tal:content/replace and i18n:name some reformatting to ease comparisons between 2.x and 3 === Zope/lib/python/TAL/TALGenerator.py 1.65 => 1.65.4.1 === --- Zope/lib/python/TAL/TALGenerator.py:1.65 Fri Jul 25 14:54:36 2003 +++ Zope/lib/python/TAL/TALGenerator.py Fri Aug 15 09:50:08 2003 @@ -64,6 +64,7 @@ self.source_file = source_file self.emit("setSourceFile", source_file) self.i18nContext = TranslationContext() + self.i18nLevel = 0 def getCode(self): assert not self.stack @@ -73,7 +74,7 @@ def optimize(self, program): output = [] collect = [] - rawseen = cursor = 0 + cursor = 0 if self.xml: endsep = "/>" else: @@ -118,7 +119,6 @@ output.append(("rawtextOffset", (text, len(text)))) if opcode != None: output.append(self.optimizeArgsList(item)) - rawseen = cursor+1 collect = [] return self.optimizeCommonTriple(output) @@ -180,9 +180,9 @@ output = program[:2] prev2, prev1 = output for item in program[2:]: - if ( item[0] == "beginScope" - and prev1[0] == "setPosition" - and prev2[0] == "rawtextColumn"): + if ( item[0] == "beginScope" + and prev1[0] == "setPosition" + and prev2[0] == "rawtextColumn"): position = output.pop()[1] text, column = output.pop()[1] prev1 = None, None @@ -319,7 +319,7 @@ assert key == "structure" self.emit("insertStructure", cexpr, attrDict, program) - def emitI18nVariable(self, varname, action, expression): + def emitI18nVariable(self, stuff): # Used for i18n:name attributes. arg is extra information describing # how the contents of the variable should get filled in, and it will # either be a 1-tuple or a 2-tuple. If arg[0] is None, then the @@ -332,6 +332,7 @@ # calculate the contents of the variable, e.g. # "I live in " + varname, action, expression = stuff m = _name_rx.match(varname) if m is None or m.group() != varname: raise TALError("illegal i18n:name: %r" % varname, self.position) @@ -525,6 +526,11 @@ varname = i18ndict.get('name') i18ndata = i18ndict.get('data') + if varname and not self.i18nLevel: + raise I18NError( + "i18n:name can only occur inside a translation unit", + position) + if i18ndata and not msgid: raise I18NError("i18n:data must be accompanied by i18n:translate", position) @@ -584,7 +590,7 @@ todo["defineSlot"] = defineSlot if defineSlot or i18ndict: - + domain = i18ndict.get("domain") or self.i18nContext.domain source = i18ndict.get("source") or self.i18nContext.source target = i18ndict.get("target") or self.i18nContext.target @@ -627,22 +633,28 @@ if repeatWhitespace: self.emitText(repeatWhitespace) if content: - todo["content"] = content - if replace: + if varname: + todo['i18nvar'] = (varname, I18N_CONTENT, None) + todo["content"] = content + self.pushProgram() + else: + todo["content"] = content + elif replace: # tal:replace w/ i18n:name has slightly different semantics. What # we're actually replacing then is the contents of the ${name} # placeholder. if varname: - todo['i18nvar'] = (varname, replace) + todo['i18nvar'] = (varname, I18N_EXPRESSION, replace) else: todo["replace"] = replace self.pushProgram() # i18n:name w/o tal:replace uses the content as the interpolation # dictionary values elif varname: - todo['i18nvar'] = (varname, None) + todo['i18nvar'] = (varname, I18N_REPLACE, None) self.pushProgram() if msgid is not None: + self.i18nLevel += 1 todo['msgid'] = msgid if i18ndata: todo['i18ndata'] = i18ndata @@ -682,10 +694,12 @@ self.emitStartTag(name, self.replaceAttrs(attrlist, repldict), isend) if optTag: self.pushProgram() - if content: + if content and not varname: self.pushProgram() if msgid is not None: self.pushProgram() + if content and varname: + self.pushProgram() if todo and position != (None, None): todo["position"] = position self.todoPush(todo) @@ -731,10 +745,7 @@ # If there's no tal:content or tal:replace in the tag with the # i18n:name, tal:replace is the default. - i18nNameAction = I18N_REPLACE if content: - if varname: - i18nNameAction = I18N_CONTENT self.emitSubstitution(content, {}) # If we're looking at an implicit msgid, emit the insertTranslation # opcode now, so that the end tag doesn't become part of the implicit @@ -742,8 +753,14 @@ # the opcode after the i18nVariable opcode so we can better handle # tags with both of them in them (and in the latter case, the contents # would be thrown away for msgid purposes). - if msgid is not None and not varname: - self.emitTranslation(msgid, i18ndata) + # + # Still, we should emit insertTranslation opcode before i18nVariable + # in case tal:content, i18n:translate and i18n:name in the same tag + if msgid is not None: + if (not varname) or ( + varname and (varname[1] == I18N_CONTENT)): + self.emitTranslation(msgid, i18ndata) + self.i18nLevel -= 1 if optTag: self.emitOptTag(name, optTag, isend) elif not isend: @@ -760,20 +777,24 @@ if replace: self.emitSubstitution(replace, repldict) elif varname: - if varname[1] is not None: - i18nNameAction = I18N_EXPRESSION # o varname[0] is the variable name - # o i18nNameAction is either + # o varname[1] is either # - I18N_REPLACE for implicit tal:replace # - I18N_CONTENT for tal:content # - I18N_EXPRESSION for explicit tal:replace - # o varname[1] will be None for the first two actions and the + # o varname[2] will be None for the first two actions and the # replacement tal expression for the third action. - self.emitI18nVariable(varname[0], i18nNameAction, varname[1]) + assert (varname[1] + in [I18N_REPLACE, I18N_CONTENT, I18N_EXPRESSION]) + self.emitI18nVariable(varname) # Do not test for "msgid is not None", i.e. we only want to test for # explicit msgids here. See comment above. - if msgid is not None and varname: - self.emitTranslation(msgid, i18ndata) + if msgid is not None: + # in case tal:content, i18n:translate and i18n:name in the + # same tag insertTranslation opcode has already been + # emitted + if varname and (varname[1] <> I18N_CONTENT): + self.emitTranslation(msgid, i18ndata) if repeat: self.emitRepeat(repeat) if condition: === Zope/lib/python/TAL/TALInterpreter.py 1.78.8.1 => 1.78.8.2 === --- Zope/lib/python/TAL/TALInterpreter.py:1.78.8.1 Fri Aug 8 04:55:01 2003 +++ Zope/lib/python/TAL/TALInterpreter.py Fri Aug 15 09:50:08 2003 @@ -327,7 +327,7 @@ name = prefix + "use-macro" value = macs[-1][0] # Macro name elif suffix == "define-slot": - name = prefix + "slot" + name = prefix + "fill-slot" elif suffix == "fill-slot": pass else: @@ -418,9 +418,9 @@ def do_rawtextBeginScope_tal(self, (s, col, position, closeprev, dict)): self._stream_write(s) self.col = col - self.position = position - self.engine.setPosition(position) engine = self.engine + self.position = position + engine.setPosition(position) if closeprev: engine.endScope() engine.beginScope() From gotcha at swing.be Fri Aug 15 09:50:49 2003 From: gotcha at swing.be (Godefroid Chapelle) Date: Sun Aug 10 17:05:19 2008 Subject: [ZPT-CVS] CVS: Zope/lib/python/TAL/tests - test_htmltalparser.py:1.33.4.1 test_talinterpreter.py:1.7.2.1 Message-ID: <200308151350.h7FDon910019@cvs.baymountain.com> Update of /cvs-repository/Zope/lib/python/TAL/tests In directory cvs.zope.org:/tmp/cvs-serv8434/tests Modified Files: Tag: gotcha-talz3_backport-branch test_htmltalparser.py test_talinterpreter.py Log Message: backport of TAL fixes from z3 - i18n and metal interactions - fix handling of nested translations with tal:content/replace and i18n:name some reformatting to ease comparisons between 2.x and 3 === Zope/lib/python/TAL/tests/test_htmltalparser.py 1.33 => 1.33.4.1 === --- Zope/lib/python/TAL/tests/test_htmltalparser.py:1.33 Tue Jul 22 09:28:46 2003 +++ Zope/lib/python/TAL/tests/test_htmltalparser.py Fri Aug 15 09:50:11 2003 @@ -614,6 +614,38 @@ ('rawtextColumn', ('\n', 0)) ]) + def test_i18n_name_with_content(self): + self._run_check('
This is text for ' + '.' + '
', [ +('setPosition', (1, 0)), +('beginScope', {'i18n:translate': ''}), +('startTag', ('div', [('i18n:translate', '', 'i18n')])), +('insertTranslation', + ('', + [('rawtextOffset', ('This is text for ', 17)), + ('setPosition', (1, 40)), + ('beginScope', + {'tal:content': 'bar', 'i18n:name': 'bar_name', 'i18n:translate': ''}), + ('i18nVariable', + ('bar_name', + [('startTag', + ('span', + [('i18n:translate', '', 'i18n'), + ('tal:content', 'bar', 'tal'), + ('i18n:name', 'bar_name', 'i18n')])), + ('insertTranslation', + ('', + [('insertText', ('$bar$', []))])), + ('rawtextOffset', ('
', 7))], + None)), + ('endScope', ()), + ('rawtextOffset', ('.', 1))])), +('endScope', ()), +('rawtextOffset', ('', 6)) + ]) + + def check_i18n_name_implicit_value(self): # input/test22.html self._run_check('''\ @@ -725,31 +757,38 @@ def check_i18n_data_with_name(self): # input/test29.html self._run_check('''\ -At the tone the time will be +
At the tone the time will be 2:32 pm... beep! -''', [ - ('rawtextBeginScope', - ('At the tone the time will be\n', - 0, - (2, 0), - 0, - {'i18n:data': 'here/currentTime', - 'i18n:name': 'time', - 'i18n:translate': 'timefmt'})), - ('insertTranslation', - ('timefmt', - [('startTag', - ('span', - [('i18n:data', 'here/currentTime', 'i18n'), - ('i18n:translate', 'timefmt', 'i18n'), - ('i18n:name', 'time', 'i18n')])), - ('i18nVariable', ('time', [], None))], - '$here/currentTime$')), - ('endScope', ()), - ('rawtextColumn', ('... beep!\n', 0)) - ]) + i18n:name="time">2:32 pm... beep!
+''', +[('setPosition', (1, 0)), + ('beginScope', {'i18n:translate': ''}), + ('startTag', ('div', [('i18n:translate', '', 'i18n')])), + ('insertTranslation', + ('', + [('rawtextBeginScope', + ('At the tone the time will be\n', + 0, + (2, 0), + 0, + {'i18n:data': 'here/currentTime', + 'i18n:name': 'time', + 'i18n:translate': 'timefmt'})), + ('insertTranslation', + ('timefmt', + [('startTag', + ('span', + [('i18n:data', 'here/currentTime', 'i18n'), + ('i18n:translate', 'timefmt', 'i18n'), + ('i18n:name', 'time', 'i18n')])), + ('i18nVariable', ('time', [], None))], + '$here/currentTime$')), + ('endScope', ()), + ('rawtextOffset', ('... beep!', 9))])), + ('endScope', ()), + ('rawtextColumn', ('\n', 0))] +) def check_i18n_explicit_msgid_with_name(self): # input/test26.html === Zope/lib/python/TAL/tests/test_talinterpreter.py 1.7 => 1.7.2.1 === --- Zope/lib/python/TAL/tests/test_talinterpreter.py:1.7 Wed Jul 30 18:16:49 2003 +++ Zope/lib/python/TAL/tests/test_talinterpreter.py Fri Aug 15 09:50:11 2003 @@ -20,11 +20,11 @@ from StringIO import StringIO -from TAL.TALDefs import METALError +from TAL.TALDefs import METALError, I18NError from TAL.HTMLTALParser import HTMLTALParser from TAL.TALInterpreter import TALInterpreter +from TAL.DummyEngine import DummyEngine, DummyTranslationService from TAL.TALInterpreter import interpolate -from TAL.DummyEngine import DummyEngine class TestCaseBase(unittest.TestCase): @@ -60,6 +60,130 @@ self.macro[0] = ("version", "duh") +class I18NCornerTestCase(TestCaseBase): + + def setUp(self): + self.engine = DummyEngine() + self.engine.setLocal('bar', 'BaRvAlUe') + + def _check(self, program, expected): + result = StringIO() + self.interpreter = TALInterpreter(program, {}, self.engine, + stream=result) + self.interpreter() + self.assertEqual(expected, result.getvalue()) + + def test_content_with_messageid_and_i18nname_and_i18ntranslate(self): + # Let's tell the user this is incredibly silly! + self.assertRaises( + I18NError, self._compile, + '') + + def test_content_with_plaintext_and_i18nname_and_i18ntranslate(self): + # Let's tell the user this is incredibly silly! + self.assertRaises( + I18NError, self._compile, + 'green') + + def test_translate_static_text_as_dynamic(self): + program, macros = self._compile( + '
This is text for ' + '.' + '
') + self._check(program, + '
THIS IS TEXT FOR BARVALUE.
\n') + + def test_translate_static_text_as_dynamic_from_bytecode(self): + program = [('version', '1.4'), + ('mode', 'html'), +('setPosition', (1, 0)), +('beginScope', {'i18n:translate': ''}), +('startTag', ('div', [('i18n:translate', '', 'i18n')])), +('insertTranslation', + ('', + [('rawtextOffset', ('This is text for ', 17)), + ('setPosition', (1, 40)), + ('beginScope', + {'tal:content': 'bar', 'i18n:name': 'bar_name', 'i18n:translate': ''}), + ('i18nVariable', + ('bar_name', + [('startTag', + ('span', + [('i18n:translate', '', 'i18n'), + ('tal:content', 'bar', 'tal'), + ('i18n:name', 'bar_name', 'i18n')])), + ('insertTranslation', + ('', + [('insertText', ('$bar$', []))])), + ('rawtextOffset', ('
', 7))], + None)), + ('endScope', ()), + ('rawtextOffset', ('.', 1))])), +('endScope', ()), +('rawtextOffset', ('', 6)) +] + self._check(program, + '
THIS IS TEXT FOR BARVALUE.
\n') + + def test_for_correct_msgids(self): + + class CollectingTranslationService(DummyTranslationService): + data = [] + + def translate(self, domain, msgid, mapping=None, + context=None, target_language=None, default=None): + self.data.append(msgid) + return DummyTranslationService.translate( + self, + domain, msgid, mapping, context, target_language, default) + + xlatsvc = CollectingTranslationService() + self.engine.translationService = xlatsvc + result = StringIO() + program, macros = self._compile( + '
This is text for ' + '.
') + self.interpreter = TALInterpreter(program, {}, self.engine, + stream=result) + self.interpreter() + self.assert_('BaRvAlUe' in xlatsvc.data) + self.assert_('This is text for ${bar_name}.' in + xlatsvc.data) + self.assertEqual( + '
THIS IS TEXT FOR BARVALUE.
\n', + result.getvalue()) + + +class I18NErrorsTestCase(TestCaseBase): + + def _check(self, src, msg): + try: + self._compile(src) + except I18NError: + pass + else: + self.fail(msg) + + def test_id_with_replace(self): + self._check('

', + "expected i18n:id with tal:replace to be denied") + + def test_missing_values(self): + self._check('

', + "missing i18n:attributes value not caught") + self._check('

', + "missing i18n:data value not caught") + self._check('

', + "missing i18n:id value not caught") + + def test_id_with_attributes(self): + self._check('''''', + "expected attribute being both part of tal:attributes" + + " and having a msgid in i18n:attributes to be denied") + class OutputPresentationTestCase(TestCaseBase): def check_attribute_wrapping(self): @@ -159,6 +283,7 @@ suite.addTest(unittest.makeSuite(MacroErrorsTestCase, "check_")) suite.addTest(unittest.makeSuite(OutputPresentationTestCase, "check_")) suite.addTest(unittest.makeSuite(InterpolateTestCase, "check_")) + suite.addTest(unittest.makeSuite(I18NCornerTestCase)) return suite From gotcha at swing.be Fri Aug 15 09:50:55 2003 From: gotcha at swing.be (Godefroid Chapelle) Date: Sun Aug 10 17:05:19 2008 Subject: [ZPT-CVS] CVS: Zope/lib/python/TAL/tests/input - test29.html:1.1.6.1 test_metal4.html:1.1.2.1 test_metal5.html:1.1.2.1 test_metal6.html:1.1.2.1 test_metal7.html:1.1.2.1 test_metal1.html:1.4.38.1 Message-ID: <200308151350.h7FDoti10031@cvs.baymountain.com> Update of /cvs-repository/Zope/lib/python/TAL/tests/input In directory cvs.zope.org:/tmp/cvs-serv8434/tests/input Modified Files: Tag: gotcha-talz3_backport-branch test_metal1.html Added Files: Tag: gotcha-talz3_backport-branch test29.html test_metal4.html test_metal5.html test_metal6.html test_metal7.html Log Message: backport of TAL fixes from z3 - i18n and metal interactions - fix handling of nested translations with tal:content/replace and i18n:name some reformatting to ease comparisons between 2.x and 3 === Added File Zope/lib/python/TAL/tests/input/test29.html ===
At the tone the time will be 2:32 pm... beep!
=== Added File Zope/lib/python/TAL/tests/input/test_metal4.html === Z3 UI === Added File Zope/lib/python/TAL/tests/input/test_metal5.html === Z3 UI === Added File Zope/lib/python/TAL/tests/input/test_metal6.html === Z3 UI === Added File Zope/lib/python/TAL/tests/input/test_metal7.html === === Zope/lib/python/TAL/tests/input/test_metal1.html 1.4 => 1.4.38.1 === --- Zope/lib/python/TAL/tests/input/test_metal1.html:1.4 Mon Oct 28 15:43:10 2002 +++ Zope/lib/python/TAL/tests/input/test_metal1.html Fri Aug 15 09:50:15 2003 @@ -54,7 +54,7 @@ - INSLOT + INSLOT From gotcha at swing.be Fri Aug 15 09:59:04 2003 From: gotcha at swing.be (Godefroid Chapelle) Date: Sun Aug 10 17:05:19 2008 Subject: [ZPT-CVS] CVS: Zope/lib/python/TAL/tests/output - test29.html:1.2 test_metal4.html:1.2 test_metal5.html:1.2 test_metal6.html:1.2 test_metal7.html:1.2 test_metal1.html:1.6 Message-ID: <200308151359.h7FDx4t11506@cvs.baymountain.com> Update of /cvs-repository/Zope/lib/python/TAL/tests/output In directory cvs.zope.org:/tmp/cvs-serv11232/tests/output Modified Files: test_metal1.html Added Files: test29.html test_metal4.html test_metal5.html test_metal6.html test_metal7.html Log Message: merge from gotcha-talz3_backport-branch backport of TAL fixes from z3 - i18n and metal interactions - fix handling of nested translations with tal:content/replace and i18n:name some reformatting to ease comparisons between 2.x and 3 === Zope/lib/python/TAL/tests/output/test29.html 1.1 => 1.2 === --- /dev/null Fri Aug 15 09:59:01 2003 +++ Zope/lib/python/TAL/tests/output/test29.html Fri Aug 15 09:58:52 2003 @@ -0,0 +1 @@ +
AT THE TONE THE TIME WILL BE 59 MINUTES AFTER 6 PM... BEEP!
=== Zope/lib/python/TAL/tests/output/test_metal4.html 1.1 => 1.2 === --- /dev/null Fri Aug 15 09:59:01 2003 +++ Zope/lib/python/TAL/tests/output/test_metal4.html Fri Aug 15 09:58:52 2003 @@ -0,0 +1,4 @@ + + + Z3 UI + === Zope/lib/python/TAL/tests/output/test_metal5.html 1.1 => 1.2 === --- /dev/null Fri Aug 15 09:59:01 2003 +++ Zope/lib/python/TAL/tests/output/test_metal5.html Fri Aug 15 09:58:52 2003 @@ -0,0 +1,4 @@ + + + Z3 UI + === Zope/lib/python/TAL/tests/output/test_metal6.html 1.1 => 1.2 === --- /dev/null Fri Aug 15 09:59:02 2003 +++ Zope/lib/python/TAL/tests/output/test_metal6.html Fri Aug 15 09:58:52 2003 @@ -0,0 +1,5 @@ + + + Z3 UI + + === Zope/lib/python/TAL/tests/output/test_metal7.html 1.1 => 1.2 === --- /dev/null Fri Aug 15 09:59:03 2003 +++ Zope/lib/python/TAL/tests/output/test_metal7.html Fri Aug 15 09:58:52 2003 @@ -0,0 +1,6 @@ + + + + + + === Zope/lib/python/TAL/tests/output/test_metal1.html 1.5 => 1.6 === --- Zope/lib/python/TAL/tests/output/test_metal1.html:1.5 Mon Oct 28 15:43:11 2002 +++ Zope/lib/python/TAL/tests/output/test_metal1.html Fri Aug 15 09:58:52 2003 @@ -22,7 +22,7 @@ AAA - + INNER BBB @@ -48,7 +48,7 @@ AAA - + INNER INNERSLOT @@ -63,7 +63,7 @@ INNER - INNERSLOT + INNERSLOT INNER @@ -72,8 +72,8 @@ INNER - INSLOT + INSLOT -INSLOT +INSLOT From gotcha at swing.be Fri Aug 15 09:59:17 2003 From: gotcha at swing.be (Godefroid Chapelle) Date: Sun Aug 10 17:05:19 2008 Subject: [ZPT-CVS] CVS: Zope/lib/python/TAL - TALGenerator.py:1.66 TALInterpreter.py:1.80 Message-ID: <200308151359.h7FDxHn11556@cvs.baymountain.com> Update of /cvs-repository/Zope/lib/python/TAL In directory cvs.zope.org:/tmp/cvs-serv11232 Modified Files: TALGenerator.py TALInterpreter.py Log Message: merge from gotcha-talz3_backport-branch backport of TAL fixes from z3 - i18n and metal interactions - fix handling of nested translations with tal:content/replace and i18n:name some reformatting to ease comparisons between 2.x and 3 === Zope/lib/python/TAL/TALGenerator.py 1.65 => 1.66 === --- Zope/lib/python/TAL/TALGenerator.py:1.65 Fri Jul 25 14:54:36 2003 +++ Zope/lib/python/TAL/TALGenerator.py Fri Aug 15 09:58:37 2003 @@ -64,6 +64,7 @@ self.source_file = source_file self.emit("setSourceFile", source_file) self.i18nContext = TranslationContext() + self.i18nLevel = 0 def getCode(self): assert not self.stack @@ -73,7 +74,7 @@ def optimize(self, program): output = [] collect = [] - rawseen = cursor = 0 + cursor = 0 if self.xml: endsep = "/>" else: @@ -118,7 +119,6 @@ output.append(("rawtextOffset", (text, len(text)))) if opcode != None: output.append(self.optimizeArgsList(item)) - rawseen = cursor+1 collect = [] return self.optimizeCommonTriple(output) @@ -180,9 +180,9 @@ output = program[:2] prev2, prev1 = output for item in program[2:]: - if ( item[0] == "beginScope" - and prev1[0] == "setPosition" - and prev2[0] == "rawtextColumn"): + if ( item[0] == "beginScope" + and prev1[0] == "setPosition" + and prev2[0] == "rawtextColumn"): position = output.pop()[1] text, column = output.pop()[1] prev1 = None, None @@ -319,7 +319,7 @@ assert key == "structure" self.emit("insertStructure", cexpr, attrDict, program) - def emitI18nVariable(self, varname, action, expression): + def emitI18nVariable(self, stuff): # Used for i18n:name attributes. arg is extra information describing # how the contents of the variable should get filled in, and it will # either be a 1-tuple or a 2-tuple. If arg[0] is None, then the @@ -332,6 +332,7 @@ # calculate the contents of the variable, e.g. # "I live in " + varname, action, expression = stuff m = _name_rx.match(varname) if m is None or m.group() != varname: raise TALError("illegal i18n:name: %r" % varname, self.position) @@ -525,6 +526,11 @@ varname = i18ndict.get('name') i18ndata = i18ndict.get('data') + if varname and not self.i18nLevel: + raise I18NError( + "i18n:name can only occur inside a translation unit", + position) + if i18ndata and not msgid: raise I18NError("i18n:data must be accompanied by i18n:translate", position) @@ -584,7 +590,7 @@ todo["defineSlot"] = defineSlot if defineSlot or i18ndict: - + domain = i18ndict.get("domain") or self.i18nContext.domain source = i18ndict.get("source") or self.i18nContext.source target = i18ndict.get("target") or self.i18nContext.target @@ -627,22 +633,28 @@ if repeatWhitespace: self.emitText(repeatWhitespace) if content: - todo["content"] = content - if replace: + if varname: + todo['i18nvar'] = (varname, I18N_CONTENT, None) + todo["content"] = content + self.pushProgram() + else: + todo["content"] = content + elif replace: # tal:replace w/ i18n:name has slightly different semantics. What # we're actually replacing then is the contents of the ${name} # placeholder. if varname: - todo['i18nvar'] = (varname, replace) + todo['i18nvar'] = (varname, I18N_EXPRESSION, replace) else: todo["replace"] = replace self.pushProgram() # i18n:name w/o tal:replace uses the content as the interpolation # dictionary values elif varname: - todo['i18nvar'] = (varname, None) + todo['i18nvar'] = (varname, I18N_REPLACE, None) self.pushProgram() if msgid is not None: + self.i18nLevel += 1 todo['msgid'] = msgid if i18ndata: todo['i18ndata'] = i18ndata @@ -682,10 +694,12 @@ self.emitStartTag(name, self.replaceAttrs(attrlist, repldict), isend) if optTag: self.pushProgram() - if content: + if content and not varname: self.pushProgram() if msgid is not None: self.pushProgram() + if content and varname: + self.pushProgram() if todo and position != (None, None): todo["position"] = position self.todoPush(todo) @@ -731,10 +745,7 @@ # If there's no tal:content or tal:replace in the tag with the # i18n:name, tal:replace is the default. - i18nNameAction = I18N_REPLACE if content: - if varname: - i18nNameAction = I18N_CONTENT self.emitSubstitution(content, {}) # If we're looking at an implicit msgid, emit the insertTranslation # opcode now, so that the end tag doesn't become part of the implicit @@ -742,8 +753,14 @@ # the opcode after the i18nVariable opcode so we can better handle # tags with both of them in them (and in the latter case, the contents # would be thrown away for msgid purposes). - if msgid is not None and not varname: - self.emitTranslation(msgid, i18ndata) + # + # Still, we should emit insertTranslation opcode before i18nVariable + # in case tal:content, i18n:translate and i18n:name in the same tag + if msgid is not None: + if (not varname) or ( + varname and (varname[1] == I18N_CONTENT)): + self.emitTranslation(msgid, i18ndata) + self.i18nLevel -= 1 if optTag: self.emitOptTag(name, optTag, isend) elif not isend: @@ -760,20 +777,24 @@ if replace: self.emitSubstitution(replace, repldict) elif varname: - if varname[1] is not None: - i18nNameAction = I18N_EXPRESSION # o varname[0] is the variable name - # o i18nNameAction is either + # o varname[1] is either # - I18N_REPLACE for implicit tal:replace # - I18N_CONTENT for tal:content # - I18N_EXPRESSION for explicit tal:replace - # o varname[1] will be None for the first two actions and the + # o varname[2] will be None for the first two actions and the # replacement tal expression for the third action. - self.emitI18nVariable(varname[0], i18nNameAction, varname[1]) + assert (varname[1] + in [I18N_REPLACE, I18N_CONTENT, I18N_EXPRESSION]) + self.emitI18nVariable(varname) # Do not test for "msgid is not None", i.e. we only want to test for # explicit msgids here. See comment above. - if msgid is not None and varname: - self.emitTranslation(msgid, i18ndata) + if msgid is not None: + # in case tal:content, i18n:translate and i18n:name in the + # same tag insertTranslation opcode has already been + # emitted + if varname and (varname[1] <> I18N_CONTENT): + self.emitTranslation(msgid, i18ndata) if repeat: self.emitRepeat(repeat) if condition: === Zope/lib/python/TAL/TALInterpreter.py 1.79 => 1.80 === --- Zope/lib/python/TAL/TALInterpreter.py:1.79 Fri Aug 8 05:16:10 2003 +++ Zope/lib/python/TAL/TALInterpreter.py Fri Aug 15 09:58:38 2003 @@ -327,7 +327,7 @@ name = prefix + "use-macro" value = macs[-1][0] # Macro name elif suffix == "define-slot": - name = prefix + "slot" + name = prefix + "fill-slot" elif suffix == "fill-slot": pass else: @@ -418,9 +418,9 @@ def do_rawtextBeginScope_tal(self, (s, col, position, closeprev, dict)): self._stream_write(s) self.col = col - self.position = position - self.engine.setPosition(position) engine = self.engine + self.position = position + engine.setPosition(position) if closeprev: engine.endScope() engine.beginScope() From gotcha at swing.be Fri Aug 15 09:59:21 2003 From: gotcha at swing.be (Godefroid Chapelle) Date: Sun Aug 10 17:05:19 2008 Subject: [ZPT-CVS] CVS: Zope/lib/python/TAL/tests - test_htmltalparser.py:1.34 test_talinterpreter.py:1.8 Message-ID: <200308151359.h7FDxLK11579@cvs.baymountain.com> Update of /cvs-repository/Zope/lib/python/TAL/tests In directory cvs.zope.org:/tmp/cvs-serv11232/tests Modified Files: test_htmltalparser.py test_talinterpreter.py Log Message: merge from gotcha-talz3_backport-branch backport of TAL fixes from z3 - i18n and metal interactions - fix handling of nested translations with tal:content/replace and i18n:name some reformatting to ease comparisons between 2.x and 3 === Zope/lib/python/TAL/tests/test_htmltalparser.py 1.33 => 1.34 === --- Zope/lib/python/TAL/tests/test_htmltalparser.py:1.33 Tue Jul 22 09:28:46 2003 +++ Zope/lib/python/TAL/tests/test_htmltalparser.py Fri Aug 15 09:58:43 2003 @@ -614,6 +614,38 @@ ('rawtextColumn', ('\n', 0)) ]) + def test_i18n_name_with_content(self): + self._run_check('
This is text for ' + '.' + '
', [ +('setPosition', (1, 0)), +('beginScope', {'i18n:translate': ''}), +('startTag', ('div', [('i18n:translate', '', 'i18n')])), +('insertTranslation', + ('', + [('rawtextOffset', ('This is text for ', 17)), + ('setPosition', (1, 40)), + ('beginScope', + {'tal:content': 'bar', 'i18n:name': 'bar_name', 'i18n:translate': ''}), + ('i18nVariable', + ('bar_name', + [('startTag', + ('span', + [('i18n:translate', '', 'i18n'), + ('tal:content', 'bar', 'tal'), + ('i18n:name', 'bar_name', 'i18n')])), + ('insertTranslation', + ('', + [('insertText', ('$bar$', []))])), + ('rawtextOffset', ('
', 7))], + None)), + ('endScope', ()), + ('rawtextOffset', ('.', 1))])), +('endScope', ()), +('rawtextOffset', ('', 6)) + ]) + + def check_i18n_name_implicit_value(self): # input/test22.html self._run_check('''\ @@ -725,31 +757,38 @@ def check_i18n_data_with_name(self): # input/test29.html self._run_check('''\ -At the tone the time will be +
At the tone the time will be 2:32 pm... beep! -''', [ - ('rawtextBeginScope', - ('At the tone the time will be\n', - 0, - (2, 0), - 0, - {'i18n:data': 'here/currentTime', - 'i18n:name': 'time', - 'i18n:translate': 'timefmt'})), - ('insertTranslation', - ('timefmt', - [('startTag', - ('span', - [('i18n:data', 'here/currentTime', 'i18n'), - ('i18n:translate', 'timefmt', 'i18n'), - ('i18n:name', 'time', 'i18n')])), - ('i18nVariable', ('time', [], None))], - '$here/currentTime$')), - ('endScope', ()), - ('rawtextColumn', ('... beep!\n', 0)) - ]) + i18n:name="time">2:32 pm... beep!
+''', +[('setPosition', (1, 0)), + ('beginScope', {'i18n:translate': ''}), + ('startTag', ('div', [('i18n:translate', '', 'i18n')])), + ('insertTranslation', + ('', + [('rawtextBeginScope', + ('At the tone the time will be\n', + 0, + (2, 0), + 0, + {'i18n:data': 'here/currentTime', + 'i18n:name': 'time', + 'i18n:translate': 'timefmt'})), + ('insertTranslation', + ('timefmt', + [('startTag', + ('span', + [('i18n:data', 'here/currentTime', 'i18n'), + ('i18n:translate', 'timefmt', 'i18n'), + ('i18n:name', 'time', 'i18n')])), + ('i18nVariable', ('time', [], None))], + '$here/currentTime$')), + ('endScope', ()), + ('rawtextOffset', ('... beep!', 9))])), + ('endScope', ()), + ('rawtextColumn', ('\n', 0))] +) def check_i18n_explicit_msgid_with_name(self): # input/test26.html === Zope/lib/python/TAL/tests/test_talinterpreter.py 1.7 => 1.8 === --- Zope/lib/python/TAL/tests/test_talinterpreter.py:1.7 Wed Jul 30 18:16:49 2003 +++ Zope/lib/python/TAL/tests/test_talinterpreter.py Fri Aug 15 09:58:43 2003 @@ -20,11 +20,11 @@ from StringIO import StringIO -from TAL.TALDefs import METALError +from TAL.TALDefs import METALError, I18NError from TAL.HTMLTALParser import HTMLTALParser from TAL.TALInterpreter import TALInterpreter +from TAL.DummyEngine import DummyEngine, DummyTranslationService from TAL.TALInterpreter import interpolate -from TAL.DummyEngine import DummyEngine class TestCaseBase(unittest.TestCase): @@ -60,6 +60,130 @@ self.macro[0] = ("version", "duh") +class I18NCornerTestCase(TestCaseBase): + + def setUp(self): + self.engine = DummyEngine() + self.engine.setLocal('bar', 'BaRvAlUe') + + def _check(self, program, expected): + result = StringIO() + self.interpreter = TALInterpreter(program, {}, self.engine, + stream=result) + self.interpreter() + self.assertEqual(expected, result.getvalue()) + + def test_content_with_messageid_and_i18nname_and_i18ntranslate(self): + # Let's tell the user this is incredibly silly! + self.assertRaises( + I18NError, self._compile, + '') + + def test_content_with_plaintext_and_i18nname_and_i18ntranslate(self): + # Let's tell the user this is incredibly silly! + self.assertRaises( + I18NError, self._compile, + 'green') + + def test_translate_static_text_as_dynamic(self): + program, macros = self._compile( + '
This is text for ' + '.' + '
') + self._check(program, + '
THIS IS TEXT FOR BARVALUE.
\n') + + def test_translate_static_text_as_dynamic_from_bytecode(self): + program = [('version', '1.4'), + ('mode', 'html'), +('setPosition', (1, 0)), +('beginScope', {'i18n:translate': ''}), +('startTag', ('div', [('i18n:translate', '', 'i18n')])), +('insertTranslation', + ('', + [('rawtextOffset', ('This is text for ', 17)), + ('setPosition', (1, 40)), + ('beginScope', + {'tal:content': 'bar', 'i18n:name': 'bar_name', 'i18n:translate': ''}), + ('i18nVariable', + ('bar_name', + [('startTag', + ('span', + [('i18n:translate', '', 'i18n'), + ('tal:content', 'bar', 'tal'), + ('i18n:name', 'bar_name', 'i18n')])), + ('insertTranslation', + ('', + [('insertText', ('$bar$', []))])), + ('rawtextOffset', ('
', 7))], + None)), + ('endScope', ()), + ('rawtextOffset', ('.', 1))])), +('endScope', ()), +('rawtextOffset', ('', 6)) +] + self._check(program, + '
THIS IS TEXT FOR BARVALUE.
\n') + + def test_for_correct_msgids(self): + + class CollectingTranslationService(DummyTranslationService): + data = [] + + def translate(self, domain, msgid, mapping=None, + context=None, target_language=None, default=None): + self.data.append(msgid) + return DummyTranslationService.translate( + self, + domain, msgid, mapping, context, target_language, default) + + xlatsvc = CollectingTranslationService() + self.engine.translationService = xlatsvc + result = StringIO() + program, macros = self._compile( + '
This is text for ' + '.
') + self.interpreter = TALInterpreter(program, {}, self.engine, + stream=result) + self.interpreter() + self.assert_('BaRvAlUe' in xlatsvc.data) + self.assert_('This is text for ${bar_name}.' in + xlatsvc.data) + self.assertEqual( + '
THIS IS TEXT FOR BARVALUE.
\n', + result.getvalue()) + + +class I18NErrorsTestCase(TestCaseBase): + + def _check(self, src, msg): + try: + self._compile(src) + except I18NError: + pass + else: + self.fail(msg) + + def test_id_with_replace(self): + self._check('

', + "expected i18n:id with tal:replace to be denied") + + def test_missing_values(self): + self._check('

', + "missing i18n:attributes value not caught") + self._check('

', + "missing i18n:data value not caught") + self._check('

', + "missing i18n:id value not caught") + + def test_id_with_attributes(self): + self._check('''''', + "expected attribute being both part of tal:attributes" + + " and having a msgid in i18n:attributes to be denied") + class OutputPresentationTestCase(TestCaseBase): def check_attribute_wrapping(self): @@ -159,6 +283,7 @@ suite.addTest(unittest.makeSuite(MacroErrorsTestCase, "check_")) suite.addTest(unittest.makeSuite(OutputPresentationTestCase, "check_")) suite.addTest(unittest.makeSuite(InterpolateTestCase, "check_")) + suite.addTest(unittest.makeSuite(I18NCornerTestCase)) return suite From gotcha at swing.be Fri Aug 15 09:59:27 2003 From: gotcha at swing.be (Godefroid Chapelle) Date: Sun Aug 10 17:05:19 2008 Subject: [ZPT-CVS] CVS: Zope/lib/python/TAL/tests/input - test29.html:1.2 test_metal4.html:1.2 test_metal5.html:1.2 test_metal6.html:1.2 test_metal7.html:1.2 test_metal1.html:1.5 Message-ID: <200308151359.h7FDxRF11611@cvs.baymountain.com> Update of /cvs-repository/Zope/lib/python/TAL/tests/input In directory cvs.zope.org:/tmp/cvs-serv11232/tests/input Modified Files: test_metal1.html Added Files: test29.html test_metal4.html test_metal5.html test_metal6.html test_metal7.html Log Message: merge from gotcha-talz3_backport-branch backport of TAL fixes from z3 - i18n and metal interactions - fix handling of nested translations with tal:content/replace and i18n:name some reformatting to ease comparisons between 2.x and 3 === Zope/lib/python/TAL/tests/input/test29.html 1.1 => 1.2 === --- /dev/null Fri Aug 15 09:59:26 2003 +++ Zope/lib/python/TAL/tests/input/test29.html Fri Aug 15 09:58:47 2003 @@ -0,0 +1,4 @@ +
At the tone the time will be +2:32 pm... beep!
=== Zope/lib/python/TAL/tests/input/test_metal4.html 1.1 => 1.2 === --- /dev/null Fri Aug 15 09:59:26 2003 +++ Zope/lib/python/TAL/tests/input/test_metal4.html Fri Aug 15 09:58:47 2003 @@ -0,0 +1,4 @@ + + + Z3 UI + === Zope/lib/python/TAL/tests/input/test_metal5.html 1.1 => 1.2 === --- /dev/null Fri Aug 15 09:59:26 2003 +++ Zope/lib/python/TAL/tests/input/test_metal5.html Fri Aug 15 09:58:47 2003 @@ -0,0 +1,4 @@ + + + Z3 UI + === Zope/lib/python/TAL/tests/input/test_metal6.html 1.1 => 1.2 === --- /dev/null Fri Aug 15 09:59:26 2003 +++ Zope/lib/python/TAL/tests/input/test_metal6.html Fri Aug 15 09:58:47 2003 @@ -0,0 +1,5 @@ + + + Z3 UI + + === Zope/lib/python/TAL/tests/input/test_metal7.html 1.1 => 1.2 === --- /dev/null Fri Aug 15 09:59:26 2003 +++ Zope/lib/python/TAL/tests/input/test_metal7.html Fri Aug 15 09:58:47 2003 @@ -0,0 +1,4 @@ + + + + \ No newline at end of file === Zope/lib/python/TAL/tests/input/test_metal1.html 1.4 => 1.5 === --- Zope/lib/python/TAL/tests/input/test_metal1.html:1.4 Mon Oct 28 15:43:10 2002 +++ Zope/lib/python/TAL/tests/input/test_metal1.html Fri Aug 15 09:58:47 2003 @@ -54,7 +54,7 @@ - INSLOT + INSLOT From gotcha at swing.be Fri Aug 15 10:14:58 2003 From: gotcha at swing.be (Godefroid Chapelle) Date: Sun Aug 10 17:05:19 2008 Subject: [ZPT-CVS] CVS: Zope/lib/python/TAL/tests/output - test29.html:1.2.2.1 test_metal4.html:1.2.2.1 test_metal5.html:1.2.2.1 test_metal6.html:1.2.2.1 test_metal7.html:1.2.2.1 test_metal1.html:1.5.34.1 Message-ID: <200308151414.h7FEEwp14318@cvs.baymountain.com> Update of /cvs-repository/Zope/lib/python/TAL/tests/output In directory cvs.zope.org:/tmp/cvs-serv14077/tests/output Modified Files: Tag: Zope-2_7-branch test_metal1.html Added Files: Tag: Zope-2_7-branch test29.html test_metal4.html test_metal5.html test_metal6.html test_metal7.html Log Message: merge from gotcha-talz3_backport-branch backport of TAL fixes from z3 - i18n and metal interactions - fix handling of nested translations with tal:content/replace and i18n:name some reformatting to ease comparisons between 2.x and 3 === Added File Zope/lib/python/TAL/tests/output/test29.html ===
AT THE TONE THE TIME WILL BE 59 MINUTES AFTER 6 PM... BEEP!
=== Added File Zope/lib/python/TAL/tests/output/test_metal4.html === Z3 UI === Added File Zope/lib/python/TAL/tests/output/test_metal5.html === Z3 UI === Added File Zope/lib/python/TAL/tests/output/test_metal6.html === Z3 UI === Added File Zope/lib/python/TAL/tests/output/test_metal7.html === === Zope/lib/python/TAL/tests/output/test_metal1.html 1.5 => 1.5.34.1 === --- Zope/lib/python/TAL/tests/output/test_metal1.html:1.5 Mon Oct 28 15:43:11 2002 +++ Zope/lib/python/TAL/tests/output/test_metal1.html Fri Aug 15 10:14:48 2003 @@ -22,7 +22,7 @@ AAA - + INNER BBB @@ -48,7 +48,7 @@ AAA - + INNER INNERSLOT @@ -63,7 +63,7 @@ INNER - INNERSLOT + INNERSLOT INNER @@ -72,8 +72,8 @@ INNER - INSLOT + INSLOT -INSLOT +INSLOT From gotcha at swing.be Fri Aug 15 10:15:14 2003 From: gotcha at swing.be (Godefroid Chapelle) Date: Sun Aug 10 17:05:19 2008 Subject: [ZPT-CVS] CVS: Zope/lib/python/TAL - TALGenerator.py:1.63.4.3 TALInterpreter.py:1.78.4.2 Message-ID: <200308151415.h7FEFEI14376@cvs.baymountain.com> Update of /cvs-repository/Zope/lib/python/TAL In directory cvs.zope.org:/tmp/cvs-serv14077 Modified Files: Tag: Zope-2_7-branch TALGenerator.py TALInterpreter.py Log Message: merge from gotcha-talz3_backport-branch backport of TAL fixes from z3 - i18n and metal interactions - fix handling of nested translations with tal:content/replace and i18n:name some reformatting to ease comparisons between 2.x and 3 === Zope/lib/python/TAL/TALGenerator.py 1.63.4.2 => 1.63.4.3 === --- Zope/lib/python/TAL/TALGenerator.py:1.63.4.2 Fri Jul 25 12:14:53 2003 +++ Zope/lib/python/TAL/TALGenerator.py Fri Aug 15 10:14:37 2003 @@ -64,6 +64,7 @@ self.source_file = source_file self.emit("setSourceFile", source_file) self.i18nContext = TranslationContext() + self.i18nLevel = 0 def getCode(self): assert not self.stack @@ -73,7 +74,7 @@ def optimize(self, program): output = [] collect = [] - rawseen = cursor = 0 + cursor = 0 if self.xml: endsep = "/>" else: @@ -118,7 +119,6 @@ output.append(("rawtextOffset", (text, len(text)))) if opcode != None: output.append(self.optimizeArgsList(item)) - rawseen = cursor+1 collect = [] return self.optimizeCommonTriple(output) @@ -180,9 +180,9 @@ output = program[:2] prev2, prev1 = output for item in program[2:]: - if ( item[0] == "beginScope" - and prev1[0] == "setPosition" - and prev2[0] == "rawtextColumn"): + if ( item[0] == "beginScope" + and prev1[0] == "setPosition" + and prev2[0] == "rawtextColumn"): position = output.pop()[1] text, column = output.pop()[1] prev1 = None, None @@ -319,7 +319,7 @@ assert key == "structure" self.emit("insertStructure", cexpr, attrDict, program) - def emitI18nVariable(self, varname, action, expression): + def emitI18nVariable(self, stuff): # Used for i18n:name attributes. arg is extra information describing # how the contents of the variable should get filled in, and it will # either be a 1-tuple or a 2-tuple. If arg[0] is None, then the @@ -332,6 +332,7 @@ # calculate the contents of the variable, e.g. # "I live in " + varname, action, expression = stuff m = _name_rx.match(varname) if m is None or m.group() != varname: raise TALError("illegal i18n:name: %r" % varname, self.position) @@ -525,6 +526,11 @@ varname = i18ndict.get('name') i18ndata = i18ndict.get('data') + if varname and not self.i18nLevel: + raise I18NError( + "i18n:name can only occur inside a translation unit", + position) + if i18ndata and not msgid: raise I18NError("i18n:data must be accompanied by i18n:translate", position) @@ -584,7 +590,7 @@ todo["defineSlot"] = defineSlot if defineSlot or i18ndict: - + domain = i18ndict.get("domain") or self.i18nContext.domain source = i18ndict.get("source") or self.i18nContext.source target = i18ndict.get("target") or self.i18nContext.target @@ -627,22 +633,28 @@ if repeatWhitespace: self.emitText(repeatWhitespace) if content: - todo["content"] = content - if replace: + if varname: + todo['i18nvar'] = (varname, I18N_CONTENT, None) + todo["content"] = content + self.pushProgram() + else: + todo["content"] = content + elif replace: # tal:replace w/ i18n:name has slightly different semantics. What # we're actually replacing then is the contents of the ${name} # placeholder. if varname: - todo['i18nvar'] = (varname, replace) + todo['i18nvar'] = (varname, I18N_EXPRESSION, replace) else: todo["replace"] = replace self.pushProgram() # i18n:name w/o tal:replace uses the content as the interpolation # dictionary values elif varname: - todo['i18nvar'] = (varname, None) + todo['i18nvar'] = (varname, I18N_REPLACE, None) self.pushProgram() if msgid is not None: + self.i18nLevel += 1 todo['msgid'] = msgid if i18ndata: todo['i18ndata'] = i18ndata @@ -682,10 +694,12 @@ self.emitStartTag(name, self.replaceAttrs(attrlist, repldict), isend) if optTag: self.pushProgram() - if content: + if content and not varname: self.pushProgram() if msgid is not None: self.pushProgram() + if content and varname: + self.pushProgram() if todo and position != (None, None): todo["position"] = position self.todoPush(todo) @@ -731,10 +745,7 @@ # If there's no tal:content or tal:replace in the tag with the # i18n:name, tal:replace is the default. - i18nNameAction = I18N_REPLACE if content: - if varname: - i18nNameAction = I18N_CONTENT self.emitSubstitution(content, {}) # If we're looking at an implicit msgid, emit the insertTranslation # opcode now, so that the end tag doesn't become part of the implicit @@ -742,8 +753,14 @@ # the opcode after the i18nVariable opcode so we can better handle # tags with both of them in them (and in the latter case, the contents # would be thrown away for msgid purposes). - if msgid is not None and not varname: - self.emitTranslation(msgid, i18ndata) + # + # Still, we should emit insertTranslation opcode before i18nVariable + # in case tal:content, i18n:translate and i18n:name in the same tag + if msgid is not None: + if (not varname) or ( + varname and (varname[1] == I18N_CONTENT)): + self.emitTranslation(msgid, i18ndata) + self.i18nLevel -= 1 if optTag: self.emitOptTag(name, optTag, isend) elif not isend: @@ -760,20 +777,24 @@ if replace: self.emitSubstitution(replace, repldict) elif varname: - if varname[1] is not None: - i18nNameAction = I18N_EXPRESSION # o varname[0] is the variable name - # o i18nNameAction is either + # o varname[1] is either # - I18N_REPLACE for implicit tal:replace # - I18N_CONTENT for tal:content # - I18N_EXPRESSION for explicit tal:replace - # o varname[1] will be None for the first two actions and the + # o varname[2] will be None for the first two actions and the # replacement tal expression for the third action. - self.emitI18nVariable(varname[0], i18nNameAction, varname[1]) + assert (varname[1] + in [I18N_REPLACE, I18N_CONTENT, I18N_EXPRESSION]) + self.emitI18nVariable(varname) # Do not test for "msgid is not None", i.e. we only want to test for # explicit msgids here. See comment above. - if msgid is not None and varname: - self.emitTranslation(msgid, i18ndata) + if msgid is not None: + # in case tal:content, i18n:translate and i18n:name in the + # same tag insertTranslation opcode has already been + # emitted + if varname and (varname[1] <> I18N_CONTENT): + self.emitTranslation(msgid, i18ndata) if repeat: self.emitRepeat(repeat) if condition: === Zope/lib/python/TAL/TALInterpreter.py 1.78.4.1 => 1.78.4.2 === --- Zope/lib/python/TAL/TALInterpreter.py:1.78.4.1 Fri Aug 8 06:37:36 2003 +++ Zope/lib/python/TAL/TALInterpreter.py Fri Aug 15 10:14:37 2003 @@ -327,7 +327,7 @@ name = prefix + "use-macro" value = macs[-1][0] # Macro name elif suffix == "define-slot": - name = prefix + "slot" + name = prefix + "fill-slot" elif suffix == "fill-slot": pass else: @@ -418,9 +418,9 @@ def do_rawtextBeginScope_tal(self, (s, col, position, closeprev, dict)): self._stream_write(s) self.col = col - self.position = position - self.engine.setPosition(position) engine = self.engine + self.position = position + engine.setPosition(position) if closeprev: engine.endScope() engine.beginScope() From gotcha at swing.be Fri Aug 15 10:15:18 2003 From: gotcha at swing.be (Godefroid Chapelle) Date: Sun Aug 10 17:05:19 2008 Subject: [ZPT-CVS] CVS: Zope/lib/python/TAL/tests - test_htmltalparser.py:1.32.2.2 test_talinterpreter.py:1.6.12.2 Message-ID: <200308151415.h7FEFII14398@cvs.baymountain.com> Update of /cvs-repository/Zope/lib/python/TAL/tests In directory cvs.zope.org:/tmp/cvs-serv14077/tests Modified Files: Tag: Zope-2_7-branch test_htmltalparser.py test_talinterpreter.py Log Message: merge from gotcha-talz3_backport-branch backport of TAL fixes from z3 - i18n and metal interactions - fix handling of nested translations with tal:content/replace and i18n:name some reformatting to ease comparisons between 2.x and 3 === Zope/lib/python/TAL/tests/test_htmltalparser.py 1.32.2.1 => 1.32.2.2 === --- Zope/lib/python/TAL/tests/test_htmltalparser.py:1.32.2.1 Tue Jul 22 10:52:21 2003 +++ Zope/lib/python/TAL/tests/test_htmltalparser.py Fri Aug 15 10:14:40 2003 @@ -614,6 +614,38 @@ ('rawtextColumn', ('\n', 0)) ]) + def test_i18n_name_with_content(self): + self._run_check('
This is text for ' + '.' + '
', [ +('setPosition', (1, 0)), +('beginScope', {'i18n:translate': ''}), +('startTag', ('div', [('i18n:translate', '', 'i18n')])), +('insertTranslation', + ('', + [('rawtextOffset', ('This is text for ', 17)), + ('setPosition', (1, 40)), + ('beginScope', + {'tal:content': 'bar', 'i18n:name': 'bar_name', 'i18n:translate': ''}), + ('i18nVariable', + ('bar_name', + [('startTag', + ('span', + [('i18n:translate', '', 'i18n'), + ('tal:content', 'bar', 'tal'), + ('i18n:name', 'bar_name', 'i18n')])), + ('insertTranslation', + ('', + [('insertText', ('$bar$', []))])), + ('rawtextOffset', ('
', 7))], + None)), + ('endScope', ()), + ('rawtextOffset', ('.', 1))])), +('endScope', ()), +('rawtextOffset', ('', 6)) + ]) + + def check_i18n_name_implicit_value(self): # input/test22.html self._run_check('''\ @@ -725,31 +757,38 @@ def check_i18n_data_with_name(self): # input/test29.html self._run_check('''\ -At the tone the time will be +
At the tone the time will be 2:32 pm... beep! -''', [ - ('rawtextBeginScope', - ('At the tone the time will be\n', - 0, - (2, 0), - 0, - {'i18n:data': 'here/currentTime', - 'i18n:name': 'time', - 'i18n:translate': 'timefmt'})), - ('insertTranslation', - ('timefmt', - [('startTag', - ('span', - [('i18n:data', 'here/currentTime', 'i18n'), - ('i18n:translate', 'timefmt', 'i18n'), - ('i18n:name', 'time', 'i18n')])), - ('i18nVariable', ('time', [], None))], - '$here/currentTime$')), - ('endScope', ()), - ('rawtextColumn', ('... beep!\n', 0)) - ]) + i18n:name="time">2:32 pm... beep!
+''', +[('setPosition', (1, 0)), + ('beginScope', {'i18n:translate': ''}), + ('startTag', ('div', [('i18n:translate', '', 'i18n')])), + ('insertTranslation', + ('', + [('rawtextBeginScope', + ('At the tone the time will be\n', + 0, + (2, 0), + 0, + {'i18n:data': 'here/currentTime', + 'i18n:name': 'time', + 'i18n:translate': 'timefmt'})), + ('insertTranslation', + ('timefmt', + [('startTag', + ('span', + [('i18n:data', 'here/currentTime', 'i18n'), + ('i18n:translate', 'timefmt', 'i18n'), + ('i18n:name', 'time', 'i18n')])), + ('i18nVariable', ('time', [], None))], + '$here/currentTime$')), + ('endScope', ()), + ('rawtextOffset', ('... beep!', 9))])), + ('endScope', ()), + ('rawtextColumn', ('\n', 0))] +) def check_i18n_explicit_msgid_with_name(self): # input/test26.html === Zope/lib/python/TAL/tests/test_talinterpreter.py 1.6.12.1 => 1.6.12.2 === --- Zope/lib/python/TAL/tests/test_talinterpreter.py:1.6.12.1 Wed Jul 30 18:19:29 2003 +++ Zope/lib/python/TAL/tests/test_talinterpreter.py Fri Aug 15 10:14:40 2003 @@ -20,11 +20,11 @@ from StringIO import StringIO -from TAL.TALDefs import METALError +from TAL.TALDefs import METALError, I18NError from TAL.HTMLTALParser import HTMLTALParser from TAL.TALInterpreter import TALInterpreter +from TAL.DummyEngine import DummyEngine, DummyTranslationService from TAL.TALInterpreter import interpolate -from TAL.DummyEngine import DummyEngine class TestCaseBase(unittest.TestCase): @@ -60,6 +60,130 @@ self.macro[0] = ("version", "duh") +class I18NCornerTestCase(TestCaseBase): + + def setUp(self): + self.engine = DummyEngine() + self.engine.setLocal('bar', 'BaRvAlUe') + + def _check(self, program, expected): + result = StringIO() + self.interpreter = TALInterpreter(program, {}, self.engine, + stream=result) + self.interpreter() + self.assertEqual(expected, result.getvalue()) + + def test_content_with_messageid_and_i18nname_and_i18ntranslate(self): + # Let's tell the user this is incredibly silly! + self.assertRaises( + I18NError, self._compile, + '') + + def test_content_with_plaintext_and_i18nname_and_i18ntranslate(self): + # Let's tell the user this is incredibly silly! + self.assertRaises( + I18NError, self._compile, + 'green') + + def test_translate_static_text_as_dynamic(self): + program, macros = self._compile( + '
This is text for ' + '.' + '
') + self._check(program, + '
THIS IS TEXT FOR BARVALUE.
\n') + + def test_translate_static_text_as_dynamic_from_bytecode(self): + program = [('version', '1.4'), + ('mode', 'html'), +('setPosition', (1, 0)), +('beginScope', {'i18n:translate': ''}), +('startTag', ('div', [('i18n:translate', '', 'i18n')])), +('insertTranslation', + ('', + [('rawtextOffset', ('This is text for ', 17)), + ('setPosition', (1, 40)), + ('beginScope', + {'tal:content': 'bar', 'i18n:name': 'bar_name', 'i18n:translate': ''}), + ('i18nVariable', + ('bar_name', + [('startTag', + ('span', + [('i18n:translate', '', 'i18n'), + ('tal:content', 'bar', 'tal'), + ('i18n:name', 'bar_name', 'i18n')])), + ('insertTranslation', + ('', + [('insertText', ('$bar$', []))])), + ('rawtextOffset', ('
', 7))], + None)), + ('endScope', ()), + ('rawtextOffset', ('.', 1))])), +('endScope', ()), +('rawtextOffset', ('', 6)) +] + self._check(program, + '
THIS IS TEXT FOR BARVALUE.
\n') + + def test_for_correct_msgids(self): + + class CollectingTranslationService(DummyTranslationService): + data = [] + + def translate(self, domain, msgid, mapping=None, + context=None, target_language=None, default=None): + self.data.append(msgid) + return DummyTranslationService.translate( + self, + domain, msgid, mapping, context, target_language, default) + + xlatsvc = CollectingTranslationService() + self.engine.translationService = xlatsvc + result = StringIO() + program, macros = self._compile( + '
This is text for ' + '.
') + self.interpreter = TALInterpreter(program, {}, self.engine, + stream=result) + self.interpreter() + self.assert_('BaRvAlUe' in xlatsvc.data) + self.assert_('This is text for ${bar_name}.' in + xlatsvc.data) + self.assertEqual( + '
THIS IS TEXT FOR BARVALUE.
\n', + result.getvalue()) + + +class I18NErrorsTestCase(TestCaseBase): + + def _check(self, src, msg): + try: + self._compile(src) + except I18NError: + pass + else: + self.fail(msg) + + def test_id_with_replace(self): + self._check('

', + "expected i18n:id with tal:replace to be denied") + + def test_missing_values(self): + self._check('

', + "missing i18n:attributes value not caught") + self._check('

', + "missing i18n:data value not caught") + self._check('

', + "missing i18n:id value not caught") + + def test_id_with_attributes(self): + self._check('''''', + "expected attribute being both part of tal:attributes" + + " and having a msgid in i18n:attributes to be denied") + class OutputPresentationTestCase(TestCaseBase): def check_attribute_wrapping(self): @@ -159,6 +283,7 @@ suite.addTest(unittest.makeSuite(MacroErrorsTestCase, "check_")) suite.addTest(unittest.makeSuite(OutputPresentationTestCase, "check_")) suite.addTest(unittest.makeSuite(InterpolateTestCase, "check_")) + suite.addTest(unittest.makeSuite(I18NCornerTestCase)) return suite From gotcha at swing.be Fri Aug 15 10:15:22 2003 From: gotcha at swing.be (Godefroid Chapelle) Date: Sun Aug 10 17:05:19 2008 Subject: [ZPT-CVS] CVS: Zope/lib/python/TAL/tests/input - test29.html:1.2.2.1 test_metal4.html:1.2.2.1 test_metal5.html:1.2.2.1 test_metal6.html:1.2.2.1 test_metal7.html:1.2.2.1 test_metal1.html:1.4.34.1 Message-ID: <200308151415.h7FEFMH14426@cvs.baymountain.com> Update of /cvs-repository/Zope/lib/python/TAL/tests/input In directory cvs.zope.org:/tmp/cvs-serv14077/tests/input Modified Files: Tag: Zope-2_7-branch test_metal1.html Added Files: Tag: Zope-2_7-branch test29.html test_metal4.html test_metal5.html test_metal6.html test_metal7.html Log Message: merge from gotcha-talz3_backport-branch backport of TAL fixes from z3 - i18n and metal interactions - fix handling of nested translations with tal:content/replace and i18n:name some reformatting to ease comparisons between 2.x and 3 === Added File Zope/lib/python/TAL/tests/input/test29.html ===
At the tone the time will be 2:32 pm... beep!
=== Added File Zope/lib/python/TAL/tests/input/test_metal4.html === Z3 UI === Added File Zope/lib/python/TAL/tests/input/test_metal5.html === Z3 UI === Added File Zope/lib/python/TAL/tests/input/test_metal6.html === Z3 UI === Added File Zope/lib/python/TAL/tests/input/test_metal7.html === === Zope/lib/python/TAL/tests/input/test_metal1.html 1.4 => 1.4.34.1 === --- Zope/lib/python/TAL/tests/input/test_metal1.html:1.4 Mon Oct 28 15:43:10 2002 +++ Zope/lib/python/TAL/tests/input/test_metal1.html Fri Aug 15 10:14:44 2003 @@ -54,7 +54,7 @@ - INSLOT + INSLOT From fred at zope.com Mon Aug 18 12:53:15 2003 From: fred at zope.com (Fred L. Drake, Jr.) Date: Sun Aug 10 17:05:19 2008 Subject: [ZPT-CVS] CVS: Zope/lib/python/TAL - TALGenerator.py:1.67 Message-ID: <200308181653.h7IGrFj07693@cvs.baymountain.com> Update of /cvs-repository/Zope/lib/python/TAL In directory cvs.zope.org:/tmp/cvs-serv7651 Modified Files: TALGenerator.py Log Message: - wrap some long lines - move shared text for warnings so it isn't repeated === Zope/lib/python/TAL/TALGenerator.py 1.66 => 1.67 === --- Zope/lib/python/TAL/TALGenerator.py:1.66 Fri Aug 15 09:58:37 2003 +++ Zope/lib/python/TAL/TALGenerator.py Mon Aug 18 12:53:08 2003 @@ -669,8 +669,8 @@ else: repldict = {} if i18nattrs: - i18nattrs = _parseI18nAttributes(i18nattrs, attrlist, repldict, self.position, - self.xml) + i18nattrs = _parseI18nAttributes(i18nattrs, attrlist, repldict, + self.position, self.xml) else: i18nattrs = {} # Convert repldict's name-->expr mapping to a @@ -678,9 +678,9 @@ for key, value in repldict.items(): if i18nattrs.get(key, None): raise I18NError( - ("attribute [%s] cannot both be part of tal:attributes" + - " and have a msgid in i18n:attributes") % key, - position) + ("attribute [%s] cannot both be part of tal:attributes" + " and have a msgid in i18n:attributes") % key, + position) ce = self.compileExpression(value) repldict[key] = ce, key in i18nattrs, i18nattrs.get(key) for key in i18nattrs: @@ -789,7 +789,7 @@ self.emitI18nVariable(varname) # Do not test for "msgid is not None", i.e. we only want to test for # explicit msgids here. See comment above. - if msgid is not None: + if msgid is not None: # in case tal:content, i18n:translate and i18n:name in the # same tag insertTranslation opcode has already been # emitted @@ -821,11 +821,13 @@ d = {} if ';' in i18nattrs: i18nattrlist = i18nattrs.split(';') - i18nattrlist = [attr.strip().split() for attr in i18nattrlist if attr.strip()] + i18nattrlist = [attr.strip().split() + for attr in i18nattrlist if attr.strip()] for parts in i18nattrlist: if len(parts) > 2: - raise TALError("illegal i18n:attributes specification: %r" % spec, - position) + raise TALError( + "illegal i18n:attributes specification: %r" % spec, + position) if len(parts) == 2: attr, msgid = parts else: @@ -836,34 +838,34 @@ attr = attr.lower() if attr in d: raise TALError( - "attribute may only be specified once in i18n:attributes: %r" - % attr, + "attribute may only be specified once in i18n:attributes: " + + `attr`, position) d[attr] = msgid else: i18nattrlist = i18nattrs.split() if len(i18nattrlist) == 2: staticattrs = [attr[0] for attr in attrlist if len(attr) == 2] - if (not i18nattrlist[1] in staticattrs) and (not i18nattrlist[1] in repldict): + if ( (not i18nattrlist[1] in staticattrs) + and (not i18nattrlist[1] in repldict)): attr, msgid = i18nattrlist - d[attr] = msgid + d[attr] = msgid else: import warnings - warnings.warn('Space separated attributes in i18n:attributes' - + ' are deprecated (i18n:attributes="value title"). Please use' - + ' semicolon to separate attributes' - + ' (i18n:attributes="value; title").', DeprecationWarning) + warnings.warn(I18N_ATTRIBUTES_WARNING, DeprecationWarning) for attr in i18nattrlist: d[attr] = None - else: + else: import warnings - warnings.warn('Space separated attributes in i18n:attributes' - + ' are deprecated (i18n:attributes="value title"). Please use' - + ' semicolon to separate attributes' - + ' (i18n:attributes="value; title").', DeprecationWarning) + warnings.warn(I18N_ATTRIBUTES_WARNING, DeprecationWarning) for attr in i18nattrlist: d[attr] = None return d + +I18N_ATTRIBUTES_WARNING = ( + 'Space separated attributes in i18n:attributes are deprecated' + ' (i18n:attributes="value title"). Please use a semicolon to' + ' separate attributes (i18n:attributes="value; title").') def test(): t = TALGenerator() From gotcha at swing.be Wed Aug 20 17:32:38 2003 From: gotcha at swing.be (Godefroid Chapelle) Date: Sun Aug 10 17:05:19 2008 Subject: [ZPT-CVS] CVS: Zope/lib/python/TAL - TALGenerator.py:1.63.4.4 Message-ID: <200308202132.h7KLWc400348@cvs.baymountain.com> Update of /cvs-repository/Zope/lib/python/TAL In directory cvs.zope.org:/tmp/cvs-serv330 Modified Files: Tag: Zope-2_7-branch TALGenerator.py Log Message: management of i18n:attributes was not similar in both space and semicolon separated lists === Zope/lib/python/TAL/TALGenerator.py 1.63.4.3 => 1.63.4.4 === --- Zope/lib/python/TAL/TALGenerator.py:1.63.4.3 Fri Aug 15 10:14:37 2003 +++ Zope/lib/python/TAL/TALGenerator.py Wed Aug 20 17:32:37 2003 @@ -670,7 +670,7 @@ repldict = {} if i18nattrs: i18nattrs = _parseI18nAttributes(i18nattrs, attrlist, repldict, self.position, - self.xml) + self.xml, self.source_file) else: i18nattrs = {} # Convert repldict's name-->expr mapping to a @@ -817,14 +817,26 @@ self.emitDefineMacro(defineMacro) -def _parseI18nAttributes(i18nattrs, attrlist, repldict, position, xml): +def _parseI18nAttributes(i18nattrs, attrlist, repldict, position, + xml, source_file): + + def addAttribute(dic, attr, msgid, position, xml): + if not xml: + attr = attr.lower() + if attr in dic: + raise TALError( + "attribute may only be specified once in i18n:attributes: %r" + % attr, + position) + dic[attr] = msgid + d = {} if ';' in i18nattrs: i18nattrlist = i18nattrs.split(';') i18nattrlist = [attr.strip().split() for attr in i18nattrlist if attr.strip()] for parts in i18nattrlist: if len(parts) > 2: - raise TALError("illegal i18n:attributes specification: %r" % spec, + raise TALError("illegal i18n:attributes specification: %r" % parts, position) if len(parts) == 2: attr, msgid = parts @@ -832,37 +844,38 @@ # len(parts) == 1 attr = parts[0] msgid = None - if not xml: - attr = attr.lower() - if attr in d: - raise TALError( - "attribute may only be specified once in i18n:attributes: %r" - % attr, - position) - d[attr] = msgid + addAttribute(d, attr, msgid, position, xml) else: i18nattrlist = i18nattrs.split() if len(i18nattrlist) == 2: staticattrs = [attr[0] for attr in attrlist if len(attr) == 2] if (not i18nattrlist[1] in staticattrs) and (not i18nattrlist[1] in repldict): - attr, msgid = i18nattrlist - d[attr] = msgid + attr, msgid = i18nattrlist + addAttribute(d, attr, msgid, position, xml) else: import warnings warnings.warn('Space separated attributes in i18n:attributes' + ' are deprecated (i18n:attributes="value title"). Please use' + ' semicolon to separate attributes' - + ' (i18n:attributes="value; title").', DeprecationWarning) + + ' (i18n:attributes="value; title").' + + '\nFile %s at row, column %s\nAttributes %s' + % (source_file, str(position), i18nattrs) + , DeprecationWarning) + msgid = None for attr in i18nattrlist: - d[attr] = None + addAttribute(d, attr, msgid, position, xml) else: import warnings warnings.warn('Space separated attributes in i18n:attributes' + ' are deprecated (i18n:attributes="value title"). Please use' + ' semicolon to separate attributes' - + ' (i18n:attributes="value; title").', DeprecationWarning) + + ' (i18n:attributes="value; title").' + + '\nFile %s at row, column %s\nAttributes %s' + % (source_file, str(position), i18nattrs) + , DeprecationWarning) + msgid = None for attr in i18nattrlist: - d[attr] = None + addAttribute(d, attr, msgid, position, xml) return d def test(): From gotcha at swing.be Wed Aug 20 17:44:02 2003 From: gotcha at swing.be (Godefroid Chapelle) Date: Sun Aug 10 17:05:19 2008 Subject: [ZPT-CVS] CVS: Zope/lib/python/TAL - TALGenerator.py:1.63.4.5 Message-ID: <200308202144.h7KLi2M02180@cvs.baymountain.com> Update of /cvs-repository/Zope/lib/python/TAL In directory cvs.zope.org:/tmp/cvs-serv2124 Modified Files: Tag: Zope-2_7-branch TALGenerator.py Log Message: reformatted for Fred === Zope/lib/python/TAL/TALGenerator.py 1.63.4.4 => 1.63.4.5 === --- Zope/lib/python/TAL/TALGenerator.py:1.63.4.4 Wed Aug 20 17:32:37 2003 +++ Zope/lib/python/TAL/TALGenerator.py Wed Aug 20 17:44:01 2003 @@ -669,8 +669,9 @@ else: repldict = {} if i18nattrs: - i18nattrs = _parseI18nAttributes(i18nattrs, attrlist, repldict, self.position, - self.xml, self.source_file) + i18nattrs = _parseI18nAttributes(i18nattrs, attrlist, repldict, + self.position, self.xml, + self.source_file) else: i18nattrs = {} # Convert repldict's name-->expr mapping to a @@ -825,19 +826,20 @@ attr = attr.lower() if attr in dic: raise TALError( - "attribute may only be specified once in i18n:attributes: %r" - % attr, + "attribute may only be specified once in i18n:attributes: " + + attr, position) dic[attr] = msgid d = {} if ';' in i18nattrs: i18nattrlist = i18nattrs.split(';') - i18nattrlist = [attr.strip().split() for attr in i18nattrlist if attr.strip()] + i18nattrlist = [attr.strip().split() + for attr in i18nattrlist if attr.strip()] for parts in i18nattrlist: if len(parts) > 2: - raise TALError("illegal i18n:attributes specification: %r" % parts, - position) + raise TALError("illegal i18n:attributes specification: %r" + % parts, position) if len(parts) == 2: attr, msgid = parts else: @@ -849,16 +851,13 @@ i18nattrlist = i18nattrs.split() if len(i18nattrlist) == 2: staticattrs = [attr[0] for attr in attrlist if len(attr) == 2] - if (not i18nattrlist[1] in staticattrs) and (not i18nattrlist[1] in repldict): + if (not i18nattrlist[1] in staticattrs) and ( + not i18nattrlist[1] in repldict): attr, msgid = i18nattrlist addAttribute(d, attr, msgid, position, xml) else: import warnings - warnings.warn('Space separated attributes in i18n:attributes' - + ' are deprecated (i18n:attributes="value title"). Please use' - + ' semicolon to separate attributes' - + ' (i18n:attributes="value; title").' - + '\nFile %s at row, column %s\nAttributes %s' + warnings.warn(I18N_ATTRIBUTES_WARNING % (source_file, str(position), i18nattrs) , DeprecationWarning) msgid = None @@ -866,17 +865,20 @@ addAttribute(d, attr, msgid, position, xml) else: import warnings - warnings.warn('Space separated attributes in i18n:attributes' - + ' are deprecated (i18n:attributes="value title"). Please use' - + ' semicolon to separate attributes' - + ' (i18n:attributes="value; title").' - + '\nFile %s at row, column %s\nAttributes %s' + warnings.warn(I18N_ATTRIBUTES_WARNING % (source_file, str(position), i18nattrs) , DeprecationWarning) msgid = None for attr in i18nattrlist: addAttribute(d, attr, msgid, position, xml) return d + +I18N_ATTRIBUTES_WARNING = ( + 'Space separated attributes in i18n:attributes' + ' are deprecated (i18n:attributes="value title"). Please use' + ' semicolon to separate attributes' + ' (i18n:attributes="value; title").' + '\nFile %s at row, column %s\nAttributes %s') def test(): t = TALGenerator() From gotcha at swing.be Wed Aug 20 17:43:41 2003 From: gotcha at swing.be (Godefroid Chapelle) Date: Sun Aug 10 17:05:19 2008 Subject: [ZPT-CVS] CVS: Zope/lib/python/TAL - TALGenerator.py:1.69 Message-ID: <200308202143.h7KLhfw02117@cvs.baymountain.com> Update of /cvs-repository/Zope/lib/python/TAL In directory cvs.zope.org:/tmp/cvs-serv1947 Modified Files: TALGenerator.py Log Message: reformatted for Fred === Zope/lib/python/TAL/TALGenerator.py 1.68 => 1.69 === --- Zope/lib/python/TAL/TALGenerator.py:1.68 Wed Aug 20 17:32:14 2003 +++ Zope/lib/python/TAL/TALGenerator.py Wed Aug 20 17:43:40 2003 @@ -669,8 +669,9 @@ else: repldict = {} if i18nattrs: - i18nattrs = _parseI18nAttributes(i18nattrs, attrlist, repldict, self.position, - self.xml, self.source_file) + i18nattrs = _parseI18nAttributes(i18nattrs, attrlist, repldict, + self.position, self.xml, + self.source_file) else: i18nattrs = {} # Convert repldict's name-->expr mapping to a @@ -825,19 +826,20 @@ attr = attr.lower() if attr in dic: raise TALError( - "attribute may only be specified once in i18n:attributes: %r" - % attr, + "attribute may only be specified once in i18n:attributes: " + + attr, position) dic[attr] = msgid d = {} if ';' in i18nattrs: i18nattrlist = i18nattrs.split(';') - i18nattrlist = [attr.strip().split() for attr in i18nattrlist if attr.strip()] + i18nattrlist = [attr.strip().split() + for attr in i18nattrlist if attr.strip()] for parts in i18nattrlist: if len(parts) > 2: - raise TALError("illegal i18n:attributes specification: %r" % parts, - position) + raise TALError("illegal i18n:attributes specification: %r" + % parts, position) if len(parts) == 2: attr, msgid = parts else: @@ -849,16 +851,13 @@ i18nattrlist = i18nattrs.split() if len(i18nattrlist) == 2: staticattrs = [attr[0] for attr in attrlist if len(attr) == 2] - if (not i18nattrlist[1] in staticattrs) and (not i18nattrlist[1] in repldict): + if (not i18nattrlist[1] in staticattrs) and ( + not i18nattrlist[1] in repldict): attr, msgid = i18nattrlist addAttribute(d, attr, msgid, position, xml) else: import warnings - warnings.warn('Space separated attributes in i18n:attributes' - + ' are deprecated (i18n:attributes="value title"). Please use' - + ' semicolon to separate attributes' - + ' (i18n:attributes="value; title").' - + '\nFile %s at row, column %s\nAttributes %s' + warnings.warn(I18N_ATTRIBUTES_WARNING % (source_file, str(position), i18nattrs) , DeprecationWarning) msgid = None @@ -866,17 +865,20 @@ addAttribute(d, attr, msgid, position, xml) else: import warnings - warnings.warn('Space separated attributes in i18n:attributes' - + ' are deprecated (i18n:attributes="value title"). Please use' - + ' semicolon to separate attributes' - + ' (i18n:attributes="value; title").' - + '\nFile %s at row, column %s\nAttributes %s' + warnings.warn(I18N_ATTRIBUTES_WARNING % (source_file, str(position), i18nattrs) , DeprecationWarning) msgid = None for attr in i18nattrlist: addAttribute(d, attr, msgid, position, xml) return d + +I18N_ATTRIBUTES_WARNING = ( + 'Space separated attributes in i18n:attributes' + ' are deprecated (i18n:attributes="value title"). Please use' + ' semicolon to separate attributes' + ' (i18n:attributes="value; title").' + '\nFile %s at row, column %s\nAttributes %s') def test(): t = TALGenerator() From gotcha at swing.be Wed Aug 20 17:04:02 2003 From: gotcha at swing.be (Godefroid Chapelle) Date: Sun Aug 10 17:05:19 2008 Subject: [ZPT-CVS] CVS: Zope/lib/python/TAL - talgettext.py:1.2 Message-ID: <200308202104.h7KL42N28316@cvs.baymountain.com> Update of /cvs-repository/Zope/lib/python/TAL In directory cvs.zope.org:/tmp/cvs-serv28087 Added Files: talgettext.py Log Message: - backported from Z3 - hoping it will become a standard tool as available with each Zope === Zope/lib/python/TAL/talgettext.py 1.1 => 1.2 === --- /dev/null Wed Aug 20 17:04:02 2003 +++ Zope/lib/python/TAL/talgettext.py Wed Aug 20 17:04:01 2003 @@ -0,0 +1,314 @@ + + + +#!/usr/bin/env python +############################################################################## +# +# Copyright (c) 2002 Zope Corporation and Contributors. +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# +############################################################################## + +"""Program to extract internationalization markup from Page Templates. + +Once you have marked up a Page Template file with i18n: namespace tags, use +this program to extract GNU gettext .po file entries. + +Usage: talgettext.py [options] files +Options: + -h / --help + Print this message and exit. + -o / --output + Output the translation .po file to . + -u / --update + Update the existing translation with any new translation strings + found. +""" + +import sys +import time +import getopt +import traceback + +from TAL.HTMLTALParser import HTMLTALParser +from TAL.TALInterpreter import TALInterpreter +from TAL.DummyEngine import DummyEngine +from ITALES import ITALESEngine +from TAL.TALDefs import TALESError + +__version__ = '$Revision$' + +pot_header = '''\ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR ORGANIZATION +# FIRST AUTHOR , YEAR. +# +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\\n" +"POT-Creation-Date: %(time)s\\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n" +"Last-Translator: FULL NAME \\n" +"Language-Team: LANGUAGE \\n" +"MIME-Version: 1.0\\n" +"Content-Type: text/plain; charset=CHARSET\\n" +"Content-Transfer-Encoding: ENCODING\\n" +"Generated-By: talgettext.py %(version)s\\n" +''' + +NLSTR = '"\n"' + +try: + True +except NameError: + True=1 + False=0 + +def usage(code, msg=''): + # Python 2.1 required + print >> sys.stderr, __doc__ + if msg: + print >> sys.stderr, msg + sys.exit(code) + + +class POTALInterpreter(TALInterpreter): + def translate(self, msgid, default, i18ndict=None, obj=None): + # XXX is this right? + if i18ndict is None: + i18ndict = {} + if obj: + i18ndict.update(obj) + # XXX Mmmh, it seems that sometimes the msgid is None; is that really + # possible? + if msgid is None: + return None + # XXX We need to pass in one of context or target_language + return self.engine.translate(msgid, self.i18nContext.domain, i18ndict, + position=self.position, default=default) + + +class POEngine(DummyEngine): + __implements__ = ITALESEngine + + def __init__(self, macros=None): + self.catalog = {} + DummyEngine.__init__(self, macros) + + def evaluate(*args): + return '' # who cares + + def evaluatePathOrVar(*args): + return '' # who cares + + def evaluateSequence(self, expr): + return (0,) # dummy + + def evaluateBoolean(self, expr): + return True # dummy + + def translate(self, msgid, domain=None, mapping=None, default=None, + # XXX position is not part of the ITALESEngine + # interface + position=None): + + if domain not in self.catalog: + self.catalog[domain] = {} + domain = self.catalog[domain] + + if msgid not in domain: + domain[msgid] = [] + domain[msgid].append((self.file, position)) + return 'x' + + +class UpdatePOEngine(POEngine): + """A slightly-less braindead POEngine which supports loading an existing + .po file first.""" + + def __init__ (self, macros=None, filename=None): + POEngine.__init__(self, macros) + + self._filename = filename + self._loadFile() + self.base = self.catalog + self.catalog = {} + + def __add(self, id, s, fuzzy): + "Add a non-fuzzy translation to the dictionary." + if not fuzzy and str: + # check for multi-line values and munge them appropriately + if '\n' in s: + lines = s.rstrip().split('\n') + s = NLSTR.join(lines) + self.catalog[id] = s + + def _loadFile(self): + # shamelessly cribbed from Python's Tools/i18n/msgfmt.py + # 25-Mar-2003 Nathan R. Yergler (nathan@zope.org) + # 14-Apr-2003 Hacked by Barry Warsaw (barry@zope.com) + + ID = 1 + STR = 2 + + try: + lines = open(self._filename).readlines() + except IOError, msg: + print >> sys.stderr, msg + sys.exit(1) + + section = None + fuzzy = False + + # Parse the catalog + lno = 0 + for l in lines: + lno += True + # If we get a comment line after a msgstr, this is a new entry + if l[0] == '#' and section == STR: + self.__add(msgid, msgstr, fuzzy) + section = None + fuzzy = False + # Record a fuzzy mark + if l[:2] == '#,' and l.find('fuzzy'): + fuzzy = True + # Skip comments + if l[0] == '#': + continue + # Now we are in a msgid section, output previous section + if l.startswith('msgid'): + if section == STR: + self.__add(msgid, msgstr, fuzzy) + section = ID + l = l[5:] + msgid = msgstr = '' + # Now we are in a msgstr section + elif l.startswith('msgstr'): + section = STR + l = l[6:] + # Skip empty lines + if not l.strip(): + continue + # XXX: Does this always follow Python escape semantics? + l = eval(l) + if section == ID: + msgid += l + elif section == STR: + msgstr += '%s\n' % l + else: + print >> sys.stderr, 'Syntax error on %s:%d' % (infile, lno), \ + 'before:' + print >> sys.stderr, l + sys.exit(1) + # Add last entry + if section == STR: + self.__add(msgid, msgstr, fuzzy) + + def evaluate(self, expression): + try: + return POEngine.evaluate(self, expression) + except TALESError: + pass + + def evaluatePathOrVar(self, expr): + return 'who cares' + + def translate(self, msgid, domain=None, mapping=None, default=None, + position=None): + if msgid not in self.base: + POEngine.translate(self, msgid, domain, mapping, default, position) + return 'x' + + +def main(): + try: + opts, args = getopt.getopt( + sys.argv[1:], + 'ho:u:', + ['help', 'output=', 'update=']) + except getopt.error, msg: + usage(1, msg) + + outfile = None + engine = None + update_mode = False + for opt, arg in opts: + if opt in ('-h', '--help'): + usage(0) + elif opt in ('-o', '--output'): + outfile = arg + elif opt in ('-u', '--update'): + update_mode = True + if outfile is None: + outfile = arg + engine = UpdatePOEngine(filename=arg) + + if not args: + print 'nothing to do' + return + + # We don't care about the rendered output of the .pt file + class Devnull: + def write(self, s): + pass + + # check if we've already instantiated an engine; + # if not, use the stupidest one available + if not engine: + engine = POEngine() + + # process each file specified + for filename in args: + try: + engine.file = filename + p = HTMLTALParser() + p.parseFile(filename) + program, macros = p.getCode() + POTALInterpreter(program, macros, engine, stream=Devnull(), + metal=False)() + except: # Hee hee, I love bare excepts! + print 'There was an error processing', filename + traceback.print_exc() + + # Now output the keys in the engine. Write them to a file if --output or + # --update was specified; otherwise use standard out. + if (outfile is None): + outfile = sys.stdout + else: + outfile = file(outfile, update_mode and "a" or "w") + + catalog = {} + for domain in engine.catalog.keys(): + catalog.update(engine.catalog[domain]) + + messages = catalog.copy() + try: + messages.update(engine.base) + except AttributeError: + pass + if '' not in messages: + print >> outfile, pot_header % {'time': time.ctime(), + 'version': __version__} + + msgids = catalog.keys() + # XXX: You should not sort by msgid, but by filename and position. (SR) + msgids.sort() + for msgid in msgids: + positions = catalog[msgid] + for filename, position in positions: + outfile.write('#: %s:%s\n' % (filename, position[0])) + + outfile.write('msgid "%s"\n' % msgid) + outfile.write('msgstr ""\n') + outfile.write('\n') + + +if __name__ == '__main__': + main() From gotcha at swing.be Wed Aug 20 17:01:06 2003 From: gotcha at swing.be (Godefroid Chapelle) Date: Sun Aug 10 17:05:19 2008 Subject: [ZPT-CVS] CVS: Zope/lib/python/TAL - talgettext.py:1.1.2.1 Message-ID: <200308202101.h7KL16U27927@cvs.baymountain.com> Update of /cvs-repository/Zope/lib/python/TAL In directory cvs.zope.org:/tmp/cvs-serv26878 Added Files: Tag: Zope-2_7-branch talgettext.py Log Message: - backported from Z3 - hoping it will become standard tool as it will be available with each Zope === Added File Zope/lib/python/TAL/talgettext.py === #!/usr/bin/env python ############################################################################## # # Copyright (c) 2002 Zope Corporation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Program to extract internationalization markup from Page Templates. Once you have marked up a Page Template file with i18n: namespace tags, use this program to extract GNU gettext .po file entries. Usage: talgettext.py [options] files Options: -h / --help Print this message and exit. -o / --output Output the translation .po file to . -u / --update Update the existing translation with any new translation strings found. """ import sys import time import getopt import traceback from TAL.HTMLTALParser import HTMLTALParser from TAL.TALInterpreter import TALInterpreter from TAL.DummyEngine import DummyEngine from ITALES import ITALESEngine from TAL.TALDefs import TALESError __version__ = '$Revision: 1.1.2.1 $' pot_header = '''\ # SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR ORGANIZATION # FIRST AUTHOR , YEAR. # msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\\n" "POT-Creation-Date: %(time)s\\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n" "Last-Translator: FULL NAME \\n" "Language-Team: LANGUAGE \\n" "MIME-Version: 1.0\\n" "Content-Type: text/plain; charset=CHARSET\\n" "Content-Transfer-Encoding: ENCODING\\n" "Generated-By: talgettext.py %(version)s\\n" ''' NLSTR = '"\n"' try: True except NameError: True=1 False=0 def usage(code, msg=''): # Python 2.1 required print >> sys.stderr, __doc__ if msg: print >> sys.stderr, msg sys.exit(code) class POTALInterpreter(TALInterpreter): def translate(self, msgid, default, i18ndict=None, obj=None): # XXX is this right? if i18ndict is None: i18ndict = {} if obj: i18ndict.update(obj) # XXX Mmmh, it seems that sometimes the msgid is None; is that really # possible? if msgid is None: return None # XXX We need to pass in one of context or target_language return self.engine.translate(msgid, self.i18nContext.domain, i18ndict, position=self.position, default=default) class POEngine(DummyEngine): __implements__ = ITALESEngine def __init__(self, macros=None): self.catalog = {} DummyEngine.__init__(self, macros) def evaluate(*args): return '' # who cares def evaluatePathOrVar(*args): return '' # who cares def evaluateSequence(self, expr): return (0,) # dummy def evaluateBoolean(self, expr): return True # dummy def translate(self, msgid, domain=None, mapping=None, default=None, # XXX position is not part of the ITALESEngine # interface position=None): if domain not in self.catalog: self.catalog[domain] = {} domain = self.catalog[domain] if msgid not in domain: domain[msgid] = [] domain[msgid].append((self.file, position)) return 'x' class UpdatePOEngine(POEngine): """A slightly-less braindead POEngine which supports loading an existing .po file first.""" def __init__ (self, macros=None, filename=None): POEngine.__init__(self, macros) self._filename = filename self._loadFile() self.base = self.catalog self.catalog = {} def __add(self, id, s, fuzzy): "Add a non-fuzzy translation to the dictionary." if not fuzzy and str: # check for multi-line values and munge them appropriately if '\n' in s: lines = s.rstrip().split('\n') s = NLSTR.join(lines) self.catalog[id] = s def _loadFile(self): # shamelessly cribbed from Python's Tools/i18n/msgfmt.py # 25-Mar-2003 Nathan R. Yergler (nathan@zope.org) # 14-Apr-2003 Hacked by Barry Warsaw (barry@zope.com) ID = 1 STR = 2 try: lines = open(self._filename).readlines() except IOError, msg: print >> sys.stderr, msg sys.exit(1) section = None fuzzy = False # Parse the catalog lno = 0 for l in lines: lno += True # If we get a comment line after a msgstr, this is a new entry if l[0] == '#' and section == STR: self.__add(msgid, msgstr, fuzzy) section = None fuzzy = False # Record a fuzzy mark if l[:2] == '#,' and l.find('fuzzy'): fuzzy = True # Skip comments if l[0] == '#': continue # Now we are in a msgid section, output previous section if l.startswith('msgid'): if section == STR: self.__add(msgid, msgstr, fuzzy) section = ID l = l[5:] msgid = msgstr = '' # Now we are in a msgstr section elif l.startswith('msgstr'): section = STR l = l[6:] # Skip empty lines if not l.strip(): continue # XXX: Does this always follow Python escape semantics? l = eval(l) if section == ID: msgid += l elif section == STR: msgstr += '%s\n' % l else: print >> sys.stderr, 'Syntax error on %s:%d' % (infile, lno), \ 'before:' print >> sys.stderr, l sys.exit(1) # Add last entry if section == STR: self.__add(msgid, msgstr, fuzzy) def evaluate(self, expression): try: return POEngine.evaluate(self, expression) except TALESError: pass def evaluatePathOrVar(self, expr): return 'who cares' def translate(self, msgid, domain=None, mapping=None, default=None, position=None): if msgid not in self.base: POEngine.translate(self, msgid, domain, mapping, default, position) return 'x' def main(): try: opts, args = getopt.getopt( sys.argv[1:], 'ho:u:', ['help', 'output=', 'update=']) except getopt.error, msg: usage(1, msg) outfile = None engine = None update_mode = False for opt, arg in opts: if opt in ('-h', '--help'): usage(0) elif opt in ('-o', '--output'): outfile = arg elif opt in ('-u', '--update'): update_mode = True if outfile is None: outfile = arg engine = UpdatePOEngine(filename=arg) if not args: print 'nothing to do' return # We don't care about the rendered output of the .pt file class Devnull: def write(self, s): pass # check if we've already instantiated an engine; # if not, use the stupidest one available if not engine: engine = POEngine() # process each file specified for filename in args: try: engine.file = filename p = HTMLTALParser() p.parseFile(filename) program, macros = p.getCode() POTALInterpreter(program, macros, engine, stream=Devnull(), metal=False)() except: # Hee hee, I love bare excepts! print 'There was an error processing', filename traceback.print_exc() # Now output the keys in the engine. Write them to a file if --output or # --update was specified; otherwise use standard out. if (outfile is None): outfile = sys.stdout else: outfile = file(outfile, update_mode and "a" or "w") catalog = {} for domain in engine.catalog.keys(): catalog.update(engine.catalog[domain]) messages = catalog.copy() try: messages.update(engine.base) except AttributeError: pass if '' not in messages: print >> outfile, pot_header % {'time': time.ctime(), 'version': __version__} msgids = catalog.keys() # XXX: You should not sort by msgid, but by filename and position. (SR) msgids.sort() for msgid in msgids: positions = catalog[msgid] for filename, position in positions: outfile.write('#: %s:%s\n' % (filename, position[0])) outfile.write('msgid "%s"\n' % msgid) outfile.write('msgstr ""\n') outfile.write('\n') if __name__ == '__main__': main() From gotcha at swing.be Wed Aug 20 17:32:15 2003 From: gotcha at swing.be (Godefroid Chapelle) Date: Sun Aug 10 17:05:19 2008 Subject: [ZPT-CVS] CVS: Zope/lib/python/TAL - TALGenerator.py:1.68 Message-ID: <200308202132.h7KLWFT00325@cvs.baymountain.com> Update of /cvs-repository/Zope/lib/python/TAL In directory cvs.zope.org:/tmp/cvs-serv32620 Modified Files: TALGenerator.py Log Message: management of i18n:attributes was not similar in both space and semicolon separated lists === Zope/lib/python/TAL/TALGenerator.py 1.67 => 1.68 === --- Zope/lib/python/TAL/TALGenerator.py:1.67 Mon Aug 18 12:53:08 2003 +++ Zope/lib/python/TAL/TALGenerator.py Wed Aug 20 17:32:14 2003 @@ -669,8 +669,8 @@ else: repldict = {} if i18nattrs: - i18nattrs = _parseI18nAttributes(i18nattrs, attrlist, repldict, - self.position, self.xml) + i18nattrs = _parseI18nAttributes(i18nattrs, attrlist, repldict, self.position, + self.xml, self.source_file) else: i18nattrs = {} # Convert repldict's name-->expr mapping to a @@ -678,9 +678,9 @@ for key, value in repldict.items(): if i18nattrs.get(key, None): raise I18NError( - ("attribute [%s] cannot both be part of tal:attributes" - " and have a msgid in i18n:attributes") % key, - position) + ("attribute [%s] cannot both be part of tal:attributes" + + " and have a msgid in i18n:attributes") % key, + position) ce = self.compileExpression(value) repldict[key] = ce, key in i18nattrs, i18nattrs.get(key) for key in i18nattrs: @@ -789,7 +789,7 @@ self.emitI18nVariable(varname) # Do not test for "msgid is not None", i.e. we only want to test for # explicit msgids here. See comment above. - if msgid is not None: + if msgid is not None: # in case tal:content, i18n:translate and i18n:name in the # same tag insertTranslation opcode has already been # emitted @@ -817,55 +817,66 @@ self.emitDefineMacro(defineMacro) -def _parseI18nAttributes(i18nattrs, attrlist, repldict, position, xml): +def _parseI18nAttributes(i18nattrs, attrlist, repldict, position, + xml, source_file): + + def addAttribute(dic, attr, msgid, position, xml): + if not xml: + attr = attr.lower() + if attr in dic: + raise TALError( + "attribute may only be specified once in i18n:attributes: %r" + % attr, + position) + dic[attr] = msgid + d = {} if ';' in i18nattrs: i18nattrlist = i18nattrs.split(';') - i18nattrlist = [attr.strip().split() - for attr in i18nattrlist if attr.strip()] + i18nattrlist = [attr.strip().split() for attr in i18nattrlist if attr.strip()] for parts in i18nattrlist: if len(parts) > 2: - raise TALError( - "illegal i18n:attributes specification: %r" % spec, - position) + raise TALError("illegal i18n:attributes specification: %r" % parts, + position) if len(parts) == 2: attr, msgid = parts else: # len(parts) == 1 attr = parts[0] msgid = None - if not xml: - attr = attr.lower() - if attr in d: - raise TALError( - "attribute may only be specified once in i18n:attributes: " - + `attr`, - position) - d[attr] = msgid + addAttribute(d, attr, msgid, position, xml) else: i18nattrlist = i18nattrs.split() if len(i18nattrlist) == 2: staticattrs = [attr[0] for attr in attrlist if len(attr) == 2] - if ( (not i18nattrlist[1] in staticattrs) - and (not i18nattrlist[1] in repldict)): - attr, msgid = i18nattrlist - d[attr] = msgid + if (not i18nattrlist[1] in staticattrs) and (not i18nattrlist[1] in repldict): + attr, msgid = i18nattrlist + addAttribute(d, attr, msgid, position, xml) else: import warnings - warnings.warn(I18N_ATTRIBUTES_WARNING, DeprecationWarning) + warnings.warn('Space separated attributes in i18n:attributes' + + ' are deprecated (i18n:attributes="value title"). Please use' + + ' semicolon to separate attributes' + + ' (i18n:attributes="value; title").' + + '\nFile %s at row, column %s\nAttributes %s' + % (source_file, str(position), i18nattrs) + , DeprecationWarning) + msgid = None for attr in i18nattrlist: - d[attr] = None - else: + addAttribute(d, attr, msgid, position, xml) + else: import warnings - warnings.warn(I18N_ATTRIBUTES_WARNING, DeprecationWarning) + warnings.warn('Space separated attributes in i18n:attributes' + + ' are deprecated (i18n:attributes="value title"). Please use' + + ' semicolon to separate attributes' + + ' (i18n:attributes="value; title").' + + '\nFile %s at row, column %s\nAttributes %s' + % (source_file, str(position), i18nattrs) + , DeprecationWarning) + msgid = None for attr in i18nattrlist: - d[attr] = None + addAttribute(d, attr, msgid, position, xml) return d - -I18N_ATTRIBUTES_WARNING = ( - 'Space separated attributes in i18n:attributes are deprecated' - ' (i18n:attributes="value title"). Please use a semicolon to' - ' separate attributes (i18n:attributes="value; title").') def test(): t = TALGenerator() From evan at 4-am.com Fri Aug 29 14:15:39 2003 From: evan at 4-am.com (Evan Simpson) Date: Sun Aug 10 17:05:19 2008 Subject: [ZPT-CVS] CVS: Releases/Zope/lib/python/TAL/tests - test_htmltalparser.py:1.33.2.1 Message-ID: <200308291815.h7TIFdS26551@cvs.baymountain.com> Update of /cvs-repository/Releases/Zope/lib/python/TAL/tests In directory cvs.zope.org:/tmp/cvs-serv25364/lib/python/TAL/tests Modified Files: Tag: evan-pathprefix-branch test_htmltalparser.py Log Message: Added the ability to define prefixes using tal:define="prefix name expr". Expression types for the prefix namespace are *not* the same as normal TALES expression types. There are only two: 'builtin' and 'ext'. 'builtin' allows the built-in prefix types to be renamed, and 'ext' (for 'external' and/or 'extension') allows access to additional prefix types defined by trusted code. An example of this is "ext:PageTemplates.NumericConversions", defined in "ExtSample.py". The convention I'm trying to establish is that 'ext' prefix types use names of the form ".". In order to accomodate this new facility, I had to defer the compilation step for prefixed path segments until they are evaluated. The compiled segment is cached, and reused if the mapping from local prefix name to prefix type doesn't change (which it usually won't). === Releases/Zope/lib/python/TAL/tests/test_htmltalparser.py 1.33 => 1.33.2.1 === --- Releases/Zope/lib/python/TAL/tests/test_htmltalparser.py:1.33 Tue Jul 22 09:28:46 2003 +++ Releases/Zope/lib/python/TAL/tests/test_htmltalparser.py Fri Aug 29 14:15:38 2003 @@ -296,6 +296,18 @@ rawtext('

'), ]) + def check_define_7(self): + self._run_check( + "

", [ + ('setPosition', (1, 0)), + ('beginScope', {'tal:define': 'prefix v builtin:var'}), + ('setPrefix', ('v', 'builtin:var')), + ('startTag', ('p', + [('tal:define', 'prefix v builtin:var', 'tal')])), + ('endScope', ()), + rawtext('

'), + ]) + def check_condition(self): self._run_check( "

foo

", [ From evan at 4-am.com Fri Aug 29 14:16:08 2003 From: evan at 4-am.com (Evan Simpson) Date: Sun Aug 10 17:05:19 2008 Subject: [ZPT-CVS] CVS: Releases/Zope/lib/python/Products/PageTemplates - ExtSample.py:1.1.2.1 Expressions.py:1.43.46.2 PathIterator.py:1.4.70.1 PathPrefixes.py:1.1.2.2 TALES.py:1.38.4.1 ZopePageTemplate.py:1.45.2.1 Message-ID: <200308291816.h7TIG8326715@cvs.baymountain.com> Update of /cvs-repository/Releases/Zope/lib/python/Products/PageTemplates In directory cvs.zope.org:/tmp/cvs-serv25364/lib/python/Products/PageTemplates Modified Files: Tag: evan-pathprefix-branch Expressions.py PathIterator.py PathPrefixes.py TALES.py ZopePageTemplate.py Added Files: Tag: evan-pathprefix-branch ExtSample.py Log Message: Added the ability to define prefixes using tal:define="prefix name expr". Expression types for the prefix namespace are *not* the same as normal TALES expression types. There are only two: 'builtin' and 'ext'. 'builtin' allows the built-in prefix types to be renamed, and 'ext' (for 'external' and/or 'extension') allows access to additional prefix types defined by trusted code. An example of this is "ext:PageTemplates.NumericConversions", defined in "ExtSample.py". The convention I'm trying to establish is that 'ext' prefix types use names of the form ".". In order to accomodate this new facility, I had to defer the compilation step for prefixed path segments until they are evaluated. The compiled segment is cached, and reused if the mapping from local prefix name to prefix type doesn't change (which it usually won't). === Added File Releases/Zope/lib/python/Products/PageTemplates/ExtSample.py === from Products.PageTemplates.PathPrefixes import exts, PathPrefix def n_compiler(prefix, arg): try: return globals()['f_' + arg] except IndexError: raise CompilerError, '%s:%s not found.' % (prefix, arg) def n_handler(prefix, arg, object, path, econtext): return arg(object) exts['PageTemplates.NumericConversions'] = PathPrefix(n_compiler, n_handler) f_oct = oct f_hex = hex f_abs = abs f_int = int def f_odd(i): return not bool(i % 2) def f_even(i): return bool(i % 2) def f_parity(i): if i % 2: return 'odd' return 'even' def f_letter(i, base=ord('a'), radix=26): s = '' while 1: i, off = divmod(i, radix) s = chr(base + off) + s if not i: return s def f_Letter(i): return f_letter(i, base=ord('A')) def f_Roman(i, rnvalues=( (1000,'M'),(900,'CM'),(500,'D'),(400,'CD'), (100,'C'),(90,'XC'),(50,'L'),(40,'XL'), (10,'X'),(9,'IX'),(5,'V'),(4,'IV'),(1,'I')) ): n = abs(int(i)) s = '' for v, r in rnvalues: rct, n = divmod(n, v) s = s + r * rct return s def f_roman(i): return f_Roman(i).lower() === Releases/Zope/lib/python/Products/PageTemplates/Expressions.py 1.43.46.1 => 1.43.46.2 === --- Releases/Zope/lib/python/Products/PageTemplates/Expressions.py:1.43.46.1 Tue Jul 29 15:55:43 2003 +++ Releases/Zope/lib/python/Products/PageTemplates/Expressions.py Fri Aug 29 14:15:36 2003 @@ -19,7 +19,7 @@ __version__='$Revision$'[11:-2] -import re, sys +import re, sys, weakref from TALES import Engine, CompilerError, _valid_name, NAME_RE, \ Undefined, Default, _parse_expr from Acquisition import aq_base, aq_inner, aq_parent @@ -112,16 +112,24 @@ raise return ob -class SubPathHandler: - def __init__(self, prefix, arg, handler, do_validate): +_pph_cache = weakref.WeakValueDictionary() + +class PathPrefixHandler(object): + '''Instances of this class are called when they are encountered + during path traversal. The current traversal object, path, + context, and validator function are passed to it.''' + def __call__(self, object, path, econtext, validate): + pass + +class RegPathPrefixHandler(PathPrefixHandler): + def __init__(self, global_name, prefix, arg): + self.global_name = global_name self.prefix = prefix self.arg = arg - self.handler = handler - self.do_validate = do_validate def __call__(self, object, path, econtext, validate): arg = self.arg - o = self.handler(self.prefix, arg, object, path, econtext) - if self.do_validate and not validate(object, object, arg, o): + o = self.prefix.handler(self.global_name, arg, object, path, econtext) + if self.prefix.do_validate and not validate(object, object, arg, o): raise Unauthorized, arg return o @@ -131,31 +139,55 @@ self._base = base = path.pop(0) if not _valid_name(base): raise CompilerError, 'Invalid variable name "%s"' % base - # Parse path self._dp = dp = [] - prefixes = PathPrefixes._subpath_prefixes + self._pps = pps = [] + # Find dynamic and prefixed path segments for i in range(len(path)): e = path[i] if e[:1] == '?' and _valid_name(e[1:]): dp.append((i, e[1:])) elif ':' in e: - prefix, arg = e.split(':', 1) - if not prefixes.has_key(prefix): - raise CompilerError, ( - 'Unknown prefix "%s"' % prefix) - compiler, handler, do_v = prefixes.get(prefix) - if compiler is not None: - arg = compiler(prefix, arg) - if handler is None: - path[i] = arg - else: - path[i] = SubPathHandler(prefix, arg, handler, do_v) + pps.append((i, e.split(':', 1))) dp.reverse() + pps.reverse() + self.pmap_cache = {} + def check_pps(self, prefixes): + pmap_cache = self.pmap_cache + builtins = PathPrefixes.builtins + for i, (local_pname, arg) in self._pps: + if prefixes.has_key(local_pname): + global_pname, prefix = prefixes[local_pname] + elif builtins.has_key(local_pname): + global_pname = local_pname + prefix = builtins[global_pname] + else: + raise CompilerError, ( + 'Unknown prefix "%s"' % local_pname) + if prefix == pmap_cache.get(local_pname): + # We've already handled this path segment correctly. + continue + + # Remember the new local->global prefix mapping, and + # replace the path segment with a new handler. + pmap_cache[local_pname] = prefix + pph = _pph_cache.get((prefix, arg)) + if pph is None: + if prefix.compiler is not None: + arg = prefix.compiler(global_pname, arg) + if prefix.handler is None: + pph = arg + else: + pph = RegPathPrefixHandler(global_pname, prefix, arg) + _pph_cache[(prefix, arg)] = pph + self._path[i] = pph + def _eval(self, econtext, list=list, isinstance=isinstance, StringType=type('')): vars = econtext.vars path = self._path + if self._pps: + self.check_pps(econtext.prefixes) if self._dp: path = list(path) # Copy! for i, varname in self._dp: @@ -335,7 +367,7 @@ object = object(*name) continue - if isinstance(name, SubPathHandler): + if isinstance(name, PathPrefixHandler): object = name(object, path, econtext, validate) continue === Releases/Zope/lib/python/Products/PageTemplates/PathIterator.py 1.4 => 1.4.70.1 === --- Releases/Zope/lib/python/Products/PageTemplates/PathIterator.py:1.4 Wed Aug 14 18:17:24 2002 +++ Releases/Zope/lib/python/Products/PageTemplates/PathIterator.py Fri Aug 29 14:15:36 2003 @@ -39,8 +39,8 @@ name = filter(None, name) securityManager = getSecurityManager() try: - ob1 = restrictedTraverse(ob1, name, securityManager) - ob2 = restrictedTraverse(ob2, name, securityManager) + ob1 = restrictedTraverse(ob1, name, securityManager, None) + ob2 = restrictedTraverse(ob2, name, securityManager, None) except Undefs: return 0 return ob1 == ob2 === Releases/Zope/lib/python/Products/PageTemplates/PathPrefixes.py 1.1.2.1 => 1.1.2.2 === --- Releases/Zope/lib/python/Products/PageTemplates/PathPrefixes.py:1.1.2.1 Tue Jul 29 15:55:43 2003 +++ Releases/Zope/lib/python/Products/PageTemplates/PathPrefixes.py Fri Aug 29 14:15:36 2003 @@ -1,38 +1,69 @@ from TALES import _valid_name, CompilerError -_subpath_prefixes = {} +class PrefixRegistry(dict): + def __setitem__(self, k, v): + if not isinstance(v, PathPrefix): + raise ValueError, '%s is not a PathPrefix' % `v` + dict.__setitem__(self, str(k), v) + setdefault = update = copy = None + +builtins = PrefixRegistry() +exts = PrefixRegistry() def initialize(): global guarded_getattr - from Expressions import guarded_getattr + from Expressions import guarded_getattr, _engine + reg = _engine.registerPrefixType + reg('builtin', PrefixExpr) + reg('ext', PrefixExpr) + +class PrefixExpr: + def __init__(self, name, expr, compiler): + self._name = name + self._s = expr.strip() + if name == 'builtin': + self.registry = builtins + elif name == 'ext': + self.registry = exts + else: + raise CompilerError, ('PrefixExpr called by unknown name %s' + % name) + def __call__(self, econtext): + try: + return self.registry[self._s] + except IndexError: + raise NameError, ('%s is not a %s: path prefix' % + (`self._s`, self._name)) + def __repr__(self): + return '%s:%s' % (self._name, `self._s`) -def registerSubPathPrefix(prefix, compiler=None, handler=None, - do_validate=0): - '''Register a prefix for subpath expressions. +class PathPrefix(object): + '''Implementation of a prefix for path expression segments. - A prefixed subpath is a subpath of the form "{px}:{arg}", - where {px} is the name of a prefix, and {arg} is an arbitrary + A prefixed path segment has the form "{px}:{arg}", + where {px} is the local name of a prefix, and {arg} is an arbitrary (and possibly empty) argument for the prefix. - When a subpath is prefixed, during compilation the compiler + The first time a prefixed path segment is traversed, the compiler (if any) for the prefix is called with {px} and {arg} as arguments, and the returned value replaces {arg} in further processing. If no handler is provided, {arg} replaces the - subpath. + path segment. - If a handler is provided, it is called during traversal + If a handler is provided, it is called each time the segment + is traversed (after the compiler, on the first traversal), with {prefix}, {arg}, the current traversal object, the list of remaining path elements, and the expression context. The value returned by the handler replaces the current traversal object. If do_validate is true, the security validator is checked. ''' - if not _valid_name(prefix): - raise ValueError, ( - 'Invalid subpath prefix "%s"' % prefix) - if compiler is None and handler is None: - raise ValueError, ("registerSubPathPrefix requires either " - "a compiler or a handler, or both.") - _subpath_prefixes[str(prefix)] = (compiler, handler, do_validate) + def __init__(self, compiler=None, handler=None, do_validate=0): + if compiler is None and handler is None: + raise ValueError, ("A PathPrefix requires either " + "a compiler or a handler, or both.") + self.compiler = compiler + self.handler = handler + self.do_validate = do_validate # 'var:x' is replaced with the value of variable 'x' def var_compiler(prefix, arg): @@ -44,7 +75,7 @@ def var_handler(prefix, arg, object, path, econtext): path.append(econtext.vars[arg]) return object -registerSubPathPrefix('var', var_compiler, var_handler) +builtins['var'] = PathPrefix(var_compiler, var_handler) # 'call:' calls the current object. # 'call:x, y, x' passes variables 'x', 'y', and 'z' as arguments. @@ -54,22 +85,22 @@ if not _valid_name(name): raise CompilerError, ('"%s" is not a valid variable name' % name) - return args + return tuple(args) def call_handler(prefix, arg, object, path, econtext): args = [econtext.vars[name] for name in arg] return object(*args) -registerSubPathPrefix('call', call_compiler, call_handler) +builtins['call'] = PathPrefix(call_compiler, call_handler) # 'key:foo' tries to fetch key 'foo' of the current object. def key_handler(prefix, arg, object, path, econtext): return object[arg] -registerSubPathPrefix('key', handler=key_handler, do_validate=1) +builtins['key'] = PathPrefix(handler=key_handler, do_validate=1) # 'item:6' tries to fetch integer key '6' of the current object. def item_compiler(prefix, arg): return int(arg) -registerSubPathPrefix('item', compiler=item_compiler, - handler=key_handler, do_validate=1) +builtins['item'] = PathPrefix(compiler=item_compiler, + handler=key_handler, do_validate=1) # 'attr:foo' tries to fetch attribute 'foo' of the current object. def attr_compiler(prefix, arg): arg = arg.strip() @@ -79,25 +110,30 @@ return arg def attr_handler(prefix, arg, object, path, econtext): return guarded_getattr(object, arg) -registerSubPathPrefix('attr', attr_compiler, attr_handler) +builtins['attr'] = PathPrefix(attr_compiler, attr_handler) -# 'fmt:dollars_and_cents' calls standard PythonScript library +# 'standard:dollars_and_cents' calls standard PythonScript library # function 'dollars_and_cents' on the current object. - -# 'fmt:%.2f' uses the Python formatting operator to format the -# current object as a floating point number with two decimal places. try: from Products.PythonScripts import standard - _fmt_names = ('whole_dollars', 'dollars_and_cents', + _std_names = ('whole_dollars', 'dollars_and_cents', 'structured_text', 'restructured_text', 'sql_quote', 'html_quote', 'url_quote', 'url_quote_plus', 'newline_to_br', 'thousands_commas', 'url_unquote', 'url_unquote_plus', 'urlencode') except: - _fmt_names = () + _std_names = None + +if _std_names: + def std_handler(prefix, arg, object, path, econtext): + if arg in _std_names: + return getattr(standard, arg)(object) + raise NameError, '%s is not a standard Script function' % `arg` + builtins['standard'] = PathPrefix(handler=std_handler) + +# 'fmt:%.2f' uses the Python formatting operator to format the +# current object as a floating point number with two decimal places. def fmt_handler(prefix, arg, object, path, econtext): - if arg in _fmt_names: - return getattr(standard, arg)(object) return arg % object -registerSubPathPrefix('fmt', handler=fmt_handler) +builtins['fmt'] = PathPrefix(handler=fmt_handler) === Releases/Zope/lib/python/Products/PageTemplates/TALES.py 1.38 => 1.38.4.1 === --- Releases/Zope/lib/python/Products/PageTemplates/TALES.py:1.38 Wed May 14 17:55:14 2003 +++ Releases/Zope/lib/python/Products/PageTemplates/TALES.py Fri Aug 29 14:15:36 2003 @@ -102,6 +102,7 @@ def __init__(self, Iterator=None): self.types = {} + self.ptypes = {} if Iterator is not None: self.Iterator = Iterator @@ -118,20 +119,47 @@ def getTypes(self): return self.types + def registerPrefixType(self, name, handler): + if not _valid_name(name): + raise RegistrationError, 'Invalid Prefix type "%s".' % name + ptypes = self.ptypes + if ptypes.has_key(name): + raise RegistrationError, ( + 'Multiple registrations for Prefix type "%s".' % + name) + ptypes[name] = handler + + def getPrefixTypes(self): + return self.ptypes + def compile(self, expression): m = _parse_expr(expression) if m: - type = m.group(1) + etype = m.group(1) expr = expression[m.end():] else: - type = "standard" + etype = "standard" expr = expression try: - handler = self.types[type] + handler = self.types[etype] + except KeyError: + raise CompilerError, ( + 'Unrecognized expression type "%s".' % etype) + return handler(etype, expr, self) + + def compilePrefix(self, expression): + m = _parse_expr(expression) + if m: + ptype = m.group(1) + expr = expression[m.end():] + else: + ptype = None + try: + handler = self.ptypes[ptype] except KeyError: raise CompilerError, ( - 'Unrecognized expression type "%s".' % type) - return handler(type, expr, self) + 'Unrecognized prefix type "%s".' % ptype) + return expr, handler(ptype, expr, self) def getContext(self, contexts=None, **kwcontexts): if contexts is not None: @@ -169,6 +197,7 @@ self.global_vars = gv = contexts.copy() self.local_vars = lv = {} self.vars = self._context_class(gv, lv) + self.prefixes = {} # Keep track of what needs to be popped as each scope ends. self._scope_stack = [] @@ -200,6 +229,11 @@ def setGlobal(self, name, value): self.global_vars[name] = value + + def setPrefix(self, name, expr): + __traceback_supplement__ = (TALESTracebackSupplement, self, expr) + pname, expr = self._compiler.compilePrefix(expr) + self.prefixes[name] = pname, expr(self) def setRepeat(self, name, expr): expr = self.evaluate(expr) === Releases/Zope/lib/python/Products/PageTemplates/ZopePageTemplate.py 1.45 => 1.45.2.1 === --- Releases/Zope/lib/python/Products/PageTemplates/ZopePageTemplate.py:1.45 Sun Jul 6 06:43:56 2003 +++ Releases/Zope/lib/python/Products/PageTemplates/ZopePageTemplate.py Fri Aug 29 14:15:36 2003 @@ -361,4 +361,4 @@ ) context.registerHelp() context.registerHelpTitle('Zope Help') - + import ExtSample From evan at 4-am.com Fri Aug 29 14:16:08 2003 From: evan at 4-am.com (Evan Simpson) Date: Sun Aug 10 17:05:19 2008 Subject: [ZPT-CVS] CVS: Releases/Zope/lib/python/Products/PageTemplates/tests - testExpressions.py:1.11.46.1 testTALES.py:1.8.14.1 Message-ID: <200308291816.h7TIG8F26725@cvs.baymountain.com> Update of /cvs-repository/Releases/Zope/lib/python/Products/PageTemplates/tests In directory cvs.zope.org:/tmp/cvs-serv25364/lib/python/Products/PageTemplates/tests Modified Files: Tag: evan-pathprefix-branch testExpressions.py testTALES.py Log Message: Added the ability to define prefixes using tal:define="prefix name expr". Expression types for the prefix namespace are *not* the same as normal TALES expression types. There are only two: 'builtin' and 'ext'. 'builtin' allows the built-in prefix types to be renamed, and 'ext' (for 'external' and/or 'extension') allows access to additional prefix types defined by trusted code. An example of this is "ext:PageTemplates.NumericConversions", defined in "ExtSample.py". The convention I'm trying to establish is that 'ext' prefix types use names of the form ".". In order to accomodate this new facility, I had to defer the compilation step for prefixed path segments until they are evaluated. The compiled segment is cached, and reused if the mapping from local prefix name to prefix type doesn't change (which it usually won't). === Releases/Zope/lib/python/Products/PageTemplates/tests/testExpressions.py 1.11 => 1.11.46.1 === --- Releases/Zope/lib/python/Products/PageTemplates/tests/testExpressions.py:1.11 Thu Sep 26 17:33:17 2002 +++ Releases/Zope/lib/python/Products/PageTemplates/tests/testExpressions.py Fri Aug 29 14:15:37 2003 @@ -8,8 +8,12 @@ self.e = e = Expressions.getEngine() self.ec = e.getContext( one = 1, - d = {'one': 1, 'b': 'b', '': 'blank', '_': 'under'}, + d = {'one': 1, 1: 'one', + 'b': 'b', + '': 'blank', '_': 'under', + 'keys': ('skeleton', 'master')}, blank = '', + s = 's', ) def tearDown(self): @@ -51,6 +55,21 @@ assert ec.evaluate('x | string:x') == 'x' assert ec.evaluate('x | string:$one') == '1' assert ec.evaluate('x | not:exists:x') + + def testPrefixes(self): + '''Test prefixed path segments''' + ec = self.ec + assert ec.evaluate('d/key:one') == 1 + assert ec.evaluate('d/item:1') == 'one' + assert ec.evaluate('d/key:keys/item:0') == 'skeleton' + assert ec.evaluate('d/var:blank') == 'blank' + assert ec.evaluate('d/attr:get/call:blank') == 'blank' + assert ec.evaluate('d/attr:get/call:s') == None + assert ec.evaluate('one/fmt:%0.3f') == '1.000' + assert ec.evaluate('d/fmt:%(one)s + %(one)s = 2') == '1 + 1 = 2' + + ec.setPrefix('i', 'builtin:item') + assert ec.evaluate('d/key:keys/i:0') == 'skeleton' def test_suite(): return unittest.makeSuite(ExpressionTests) === Releases/Zope/lib/python/Products/PageTemplates/tests/testTALES.py 1.8 => 1.8.14.1 === --- Releases/Zope/lib/python/Products/PageTemplates/tests/testTALES.py:1.8 Thu Feb 27 11:18:41 2003 +++ Releases/Zope/lib/python/Products/PageTemplates/tests/testTALES.py Fri Aug 29 14:15:37 2003 @@ -1,6 +1,6 @@ import os, sys, unittest -from Products.PageTemplates import TALES +from Products.PageTemplates import TALES, PathPrefixes from Products.PageTemplates.tests import harness1 import string @@ -70,6 +70,34 @@ else: assert 0, 'Invalid type name "%s" accepted.' % name + def testRegisterPrefixType(self): + '''Test prefix type registration''' + e = TALES.Engine() + e.registerPrefixType('builtin', PathPrefixes.PrefixExpr) + assert e.getPrefixTypes()['builtin'] == PathPrefixes.PrefixExpr + + def testRegisterPrefixTypeUnique(self): + '''Test prefix type registration uniqueness''' + e = TALES.Engine() + e.registerPrefixType('builtin', PathPrefixes.PrefixExpr) + try: + e.registerPrefixType('builtin', PathPrefixes.PrefixExpr) + except TALES.RegistrationError: + pass + else: + assert 0, "Duplicate registration accepted." + + def testRegisterPrefixTypeNameConstraints(self): + '''Test constraints on prefix type names''' + e = TALES.Engine() + for name in '1A', 'A!', 'AB ': + try: + e.registerPrefixType(name, PathPrefixes.PrefixExpr) + except TALES.RegistrationError: + pass + else: + assert 0, 'Invalid type name "%s" accepted.' % name + def testCompile(self): '''Test expression compilation''' e = TALES.Engine() @@ -78,6 +106,15 @@ assert ce(None) == ('simple', 'x'), ( 'Improperly compiled expression %s.' % `ce`) + def testCompilePrefix(self): + '''Test prefix compilation''' + e = TALES.Engine() + e.registerPrefixType('builtin', PathPrefixes.PrefixExpr) + pname, ce = e.compilePrefix('builtin:var') + assert pname == 'var', 'prefix name %s != var' % ptype + assert ce(None) == PathPrefixes.builtins['var'], ( + 'Improperly compiled prefix %s gave %s.' % (`ce`, `ce(None)`)) + def testGetContext(self): '''Test Context creation''' TALES.Engine().getContext() @@ -88,6 +125,7 @@ e = TALES.Engine() e.registerType('simple', TALES.SimpleExpr) e.registerType('unicode', DummyUnicodeExpr) + e.registerPrefixType('builtin', PathPrefixes.PrefixExpr) return e.getContext(**kws) def testContext0(self): @@ -125,6 +163,15 @@ assert c['g'] == 1, "Global from inner scope" ctxt.endScope() + + def testPrefixes(self): + '''Test Prefixes''' + ctxt = self.getContext() + ctxt.beginScope() + ctxt.setPrefix('i', 'builtin:item') + + assert ctxt.prefixes['i'] == ('item', PathPrefixes.builtins['item']), ( + 'Prefix registration failed') def test_suite(): return unittest.makeSuite(TALESTests) From evan at 4-am.com Fri Aug 29 14:16:09 2003 From: evan at 4-am.com (Evan Simpson) Date: Sun Aug 10 17:05:19 2008 Subject: [ZPT-CVS] CVS: Releases/Zope/lib/python/TAL - DummyEngine.py:1.37.6.1 ITALES.py:1.4.6.1 TALGenerator.py:1.65.2.1 TALInterpreter.py:1.78.6.1 Message-ID: <200308291816.h7TIG9k26738@cvs.baymountain.com> Update of /cvs-repository/Releases/Zope/lib/python/TAL In directory cvs.zope.org:/tmp/cvs-serv25364/lib/python/TAL Modified Files: Tag: evan-pathprefix-branch DummyEngine.py ITALES.py TALGenerator.py TALInterpreter.py Log Message: Added the ability to define prefixes using tal:define="prefix name expr". Expression types for the prefix namespace are *not* the same as normal TALES expression types. There are only two: 'builtin' and 'ext'. 'builtin' allows the built-in prefix types to be renamed, and 'ext' (for 'external' and/or 'extension') allows access to additional prefix types defined by trusted code. An example of this is "ext:PageTemplates.NumericConversions", defined in "ExtSample.py". The convention I'm trying to establish is that 'ext' prefix types use names of the form ".". In order to accomodate this new facility, I had to defer the compilation step for prefixed path segments until they are evaluated. The compiled segment is cached, and reused if the mapping from local prefix name to prefix type doesn't change (which it usually won't). === Releases/Zope/lib/python/TAL/DummyEngine.py 1.37 => 1.37.6.1 === --- Releases/Zope/lib/python/TAL/DummyEngine.py:1.37 Mon Apr 7 13:38:27 2003 +++ Releases/Zope/lib/python/TAL/DummyEngine.py Fri Aug 29 14:15:38 2003 @@ -95,6 +95,9 @@ def setGlobal(self, name, value): self.globals[name] = value + def setPrefix(self, name, expr): + pass + def evaluate(self, expression): assert (expression.startswith("$") and expression.endswith("$"), expression) === Releases/Zope/lib/python/TAL/ITALES.py 1.4 => 1.4.6.1 === --- Releases/Zope/lib/python/TAL/ITALES.py:1.4 Mon Apr 7 14:45:45 2003 +++ Releases/Zope/lib/python/TAL/ITALES.py Fri Aug 29 14:15:38 2003 @@ -131,6 +131,12 @@ The variable will be named 'name' and have the value 'value'. """ + def setPrefix(name, expr): + """Set a prefix mapping. + + The prefix will be named 'name' and have the value of 'expr'. + """ + def setRepeat(name, compiled_expression): """ """ === Releases/Zope/lib/python/TAL/TALGenerator.py 1.65 => 1.65.2.1 === --- Releases/Zope/lib/python/TAL/TALGenerator.py:1.65 Fri Jul 25 14:54:36 2003 +++ Releases/Zope/lib/python/TAL/TALGenerator.py Fri Aug 29 14:15:38 2003 @@ -266,11 +266,16 @@ def emitDefines(self, defines): for part in TALDefs.splitParts(defines): m = re.match( - r"(?s)\s*(?:(global|local)\s+)?(%s)\s+(.*)\Z" % NAME_RE, part) + r"(?s)\s*(?:(global|local|prefix)\s+)?(%s)\s+(.*)\Z" % + NAME_RE, part) if not m: raise TALError("invalid define syntax: " + `part`, self.position) scope, name, expr = m.group(1, 2, 3) + if scope == "prefix": + self.emit("setPrefix", name, expr) + continue + scope = scope or "local" cexpr = self.compileExpression(expr) if scope == "local": === Releases/Zope/lib/python/TAL/TALInterpreter.py 1.78 => 1.78.6.1 === --- Releases/Zope/lib/python/TAL/TALInterpreter.py:1.78 Mon Apr 7 13:38:27 2003 +++ Releases/Zope/lib/python/TAL/TALInterpreter.py Fri Aug 29 14:15:38 2003 @@ -472,6 +472,10 @@ self.engine.setGlobal(name, self.engine.evaluateValue(expr)) bytecode_handlers["setGlobal"] = do_setLocal + def do_setPrefix_tal(self, (name, expr)): + self.engine.setPrefix(name, expr) + bytecode_handlers["setPrefix"] = do_setLocal + def do_beginI18nContext(self, settings): get = settings.get self.i18nContext = TranslationContext(self.i18nContext, @@ -733,6 +737,7 @@ bytecode_handlers_tal["beginScope"] = do_beginScope_tal bytecode_handlers_tal["setLocal"] = do_setLocal_tal bytecode_handlers_tal["setGlobal"] = do_setGlobal_tal + bytecode_handlers_tal["setPrefix"] = do_setPrefix_tal bytecode_handlers_tal["insertStructure"] = do_insertStructure_tal bytecode_handlers_tal["insertText"] = do_insertText_tal bytecode_handlers_tal["loop"] = do_loop_tal