[px] Better error reporting when encountering a parsing error in a PX.

This commit is contained in:
Gaetan Delannay 2013-06-26 17:06:06 +02:00
parent e4b84be05e
commit e6cacd10dd
4 changed files with 248 additions and 102 deletions

View file

@ -269,9 +269,9 @@ class ToolMixin(BaseMixin):
'''When must the portlet be shown?''' '''When must the portlet be shown?'''
# Not on 'edit' pages. # Not on 'edit' pages.
if layoutType == 'edit': return if layoutType == 'edit': return
if context.id == 'ui': context = context.getParentNode() if context and (context.id == 'ui'): context = context.getParentNode()
res = True res = True
if hasattr(context.aq_base, 'appy'): if context and hasattr(context.aq_base, 'appy'):
appyObj = context.appy() appyObj = context.appy()
try: try:
res = appyObj.showPortlet() res = appyObj.showPortlet()

View file

@ -41,6 +41,11 @@ function showLoginForm() {
loginFields.style.display = "inline"; loginFields.style.display = "inline";
} }
function switchLanguage(selectWidget) {
var language = selectWidget.options[selectWidget.selectedIndex].value;
window.location = "/config/changeLanguage?language=" + language;
}
var isIe = (navigator.appName == "Microsoft Internet Explorer"); var isIe = (navigator.appName == "Microsoft Internet Explorer");
function getElementsHavingName(tag, name) { function getElementsHavingName(tag, name) {

View file

@ -27,6 +27,131 @@ class AbstractWrapper(object):
'''Any real Appy-managed Zope object has a companion object that is an '''Any real Appy-managed Zope object has a companion object that is an
instance of this class.''' instance of this class.'''
pxPhases = Px('''<p>Phases</p>
''')
pxPortlet = Px('''
<x var="toolUrl=tool.url;
queryUrl='%s/ui/query' % toolUrl;
currentSearch=req.get('search', None);
currentClass=req.get('className', None);
currentPage=req['PATH_INFO'].rsplit('/',1)[-1];
rootClasses=ztool.getRootClasses();
phases=contextObj and contextObj.getAppyPhases() or None">
<x if="contextObj and \
phases and contextObj.mayNavigate()">:self.pxPhases</x>
<!-- One section for every searchable root class -->
<x for="rootClass in [rc for rc in rootClasses \
if tool.userMaySearch(rc)]">
<!-- A separator if required -->
<div class="portletSep" var="nb=loop.rootClass.nb"
if="(nb != 0) or ((nb == 0) and phases)"></div>
<!-- Section title (link triggers the default search) -->
<div class="portletContent"
var="searchInfo=ztool.getGroupedSearches(rootClass)">
<div class="portletTitle">
<a var="queryParam=searchInfo['default'] and \
searchInfo['default']['name'] or ''"
href=":'%s?className=%s&amp;search=%s' % \
(queryUrl,rootClass,queryParam)"
class=":(not currentSearch and (currentClass==rootClass) and \
(currentPage=='query')) and \
'portletCurrent' or ''">::_(rootClass + '_plural')</a>
</div>
<!-- Actions -->
<x var="addPermission='%s: Add %s' % (appName, rootClass);
userMayAdd=user.has_permission(addPermission, appFolder);
createMeans=ztool.getCreateMeans(rootClass)">
<!-- Create a new object from a web form -->
<input type="button" class="button"
if="userMayAdd and ('form' in createMeans)"
style=":'background-image: url(%s/ui/buttonAdd.png)' % appUrl"
value=":_('query_create')"
onclick=":'window.location=&quot;%s/do?action=Create&amp;' \
'className=%s&quot;' % (toolUrl, rootClass)"/>
<!-- Create object(s) by importing data -->
<input type="button" class="button"
if="userMayAdd and ('import' in createMeans)"
style=":'background-image: url(%s/ui/buttonImport.png)'% appUrl"
value=":_('query_import')"
onclick=":'window.location=&quot;%s/ui/import?' \
'className=%s&quot;' % (toolUrl, rootClass)"/>
</x>
<!-- Searches -->
<x if="ztool.advancedSearchEnabledFor(rootClass)">
<!-- Live search -->
<form action=":'%s/config/do' % appUrl">
<input type="hidden" name="action" value="SearchObjects"/>
<input type="hidden" name="className" value=":rootClass"/>
<table cellpadding="0" cellspacing="0">
<tr valign="bottom">
<td><input type="text" size="14" name="w_SearchableText"
class="inputSearch"/></td>
<td><input type="image" style="cursor:pointer"
src=":'%s/ui/search.gif' % appUrl"
title=":_('search_button')"/></td>
</tr>
</table>
</form>
<!-- Advanced search -->
<div var="highlighted=(currentClass == rootClass) and \
(currentPage == 'search')"
class="highlighted and 'portletSearch portletCurrent' or \
'portletSearch'"
align=":dright">
<a var="text=_('search_title')" style="font-size: 88%"
href=":'%s/ui/search?className=%s' % (toolUrl, rootClass)"
title=":text"><x>:text</x>...</a>
</div>
</x>
<!-- Predefined searches -->
<x for="widget in searchInfo['searches']">
<x if="widget['type'] == 'group'">
<!--metal:s use-macro="app/ui/portlet/macros/group"/-->
</x>
<x if="widget['type'] != 'group'">
<x var="search=widget">
<!--metal:s use-macro="app/ui/portlet/macros/search"/-->
</x>
</x>
</x>
</div>
</x>
</x>
''')
pxMessage = Px('''
<x var="messages=ztool.consumeMessages()" if="messages">
<div class="message">
<!-- The icon for closing the message -->
<img src=":'%s/ui/close.png' % appUrl" align=":dright"
style="cursor:pointer"
onclick="this.parentNode.style.display='none'"/>
<!-- The message content -->
<x>::messages</x>
</div>
</x>
''')
pxFooter = Px('''
<table cellpadding="0" cellspacing="0" width="100%" class="footer">
<tr>
<td align=":dright">Made with
<a href="http://appyframework.org" target="_blank">Appy</a></td></tr>
</table>
''')
pxTemplate = Px(''' pxTemplate = Px('''
<html var="tool=self.tool; ztool=tool.o; user=tool.user; <html var="tool=self.tool; ztool=tool.o; user=tool.user;
isAnon=ztool.userIsAnon(); app=ztool.getApp(); isAnon=ztool.userIsAnon(); app=ztool.getApp();
@ -96,7 +221,7 @@ class AbstractWrapper(object):
<input type="button" onclick="doAskPasswordReinit()" <input type="button" onclick="doAskPasswordReinit()"
value=":_('ask_password_reinit')"/> value=":_('ask_password_reinit')"/>
<input type="button" onclick="closePopup('askPasswordReinitPopup')" <input type="button" onclick="closePopup('askPasswordReinitPopup')"
value="_('object_cancel')"/> value=":_('object_cancel')"/>
</div> </div>
</form> </form>
</div> </div>
@ -127,10 +252,9 @@ class AbstractWrapper(object):
<!-- Language selector --> <!-- Language selector -->
<x if="ztool.showLanguageSelector()"> <x if="ztool.showLanguageSelector()">
<select class="pageLink" <select var="languages=ztool.getLanguages();
var="languages=ztool.getLanguages();
defaultLanguage=languages[0]" defaultLanguage=languages[0]"
onchange=":'window.location=&quot;%s/config/changeLanguage?language=&quot; + this.options[this.selectedIndex].value' % appUrl"> class="pageLink" onchange="switchLanguage(this)">
<option for="lg in languages" value=":lg" <option for="lg in languages" value=":lg"
selected=":lang == lg">:ztool.getLanguageName(lg)</option> selected=":lang == lg">:ztool.getLanguageName(lg)</option>
</select> </select>
@ -138,101 +262,98 @@ class AbstractWrapper(object):
</div> </div>
</td> </td>
</tr> </tr>
<!--tal:comment replace="nothing">The message strip</tal:comment>
<tr valign="top"> <!-- The message strip -->
<td> <tr valign="top">
<div style="position: relative"> <td><div style="position: relative">:self.pxMessage</div></td>
<metal:msg use-macro="app/ui/page/macros/message"/> </tr>
</div>
</td> <!-- The user strip -->
</tr> <tr>
<tal:comment replace="nothing">The user strip</tal:comment> <td>
<tr> <table class="userStrip" width="100%">
<td> <tr>
<table class="userStrip" width="100%"> <!-- The user login form for anonymous users -->
<tr> <td align="center"
<tal:comment replace="nothing">The user login form for anonymous users</tal:comment> if="isAnon and ('/temp_folder/' not in req['ACTUAL_URL'])">
<td align="center" <form id="loginForm" name="loginForm" method="post" class="login"
tal:condition="python: isAnon and ('/temp_folder/' not in req['ACTUAL_URL'])"> action=":tool.url + '/performLogin'">
<form id="loginForm" name="loginForm" method="post" class="login" <input type="hidden" name="js_enabled" id="js_enabled" value="0"/>
tal:attributes="action python: tool.absolute_url() + '/performLogin'"> <input type="hidden" name="cookies_enabled" id="cookies_enabled"
<input type="hidden" name="js_enabled" id="js_enabled" value="0"/> value=""/>
<input type="hidden" name="cookies_enabled" id="cookies_enabled" value=""/> <input type="hidden" name="login_name" id="login_name" value=""/>
<input type="hidden" name="login_name" id="login_name" value=""/> <input type="hidden" name="pwd_empty" id="pwd_empty" value="0"/>
<input type="hidden" name="pwd_empty" id="pwd_empty" value="0"/> <!-- Login fields, directly shown or not (depends on
<tal:comment replace="nothing">The login fields, directly shown or not (depends on discreetLogin)</tal:comment> discreetLogin) -->
<span id="loginFields" name="loginFields" <span id="loginFields" name="loginFields"
tal:attributes="style python: discreetLogin and 'display:none' or 'display:block'"> style=":discreetLogin and 'display:none' or 'display:block'">
<span class="userStripText" tal:content="python: _('app_login')"/> <span class="userStripText">:_('app_login')</span>
<input type="text" name="__ac_name" id="__ac_name" value="" style="width: 142px"/>&nbsp; <input type="text" name="__ac_name" id="__ac_name" value=""
<span class="userStripText" tal:content="python: _('app_password')"/> style="width: 142px"/>&nbsp;
<input type="password" name="__ac_password" id="__ac_password" style="width: 142px"/> <span class="userStripText">:_('app_password')</span>
<input type="submit" name="submit" onclick="setLoginVars()" <input type="password" name="__ac_password" id="__ac_password"
tal:define="label python: _('app_connect')" tal:attributes="value label; alt label"/> style="width: 142px"/>
<tal:comment replace="nothing">Forgot password?</tal:comment> <input type="submit" name="submit" onclick="setLoginVars()"
<a class="lostPassword" href="javascript: openPopup('askPasswordReinitPopup')" var="label=_('app_connect')" value=":label" alt=":label"/>
tal:condition="tool/showForgotPassword" <!-- Forgot password? -->
tal:content="python: _('forgot_password')"> <a if="ztool.showForgotPassword()"
</a> href="javascript: openPopup('askPasswordReinitPopup')"
</span> class="lostPassword">:_('forgot_password')</a>
</form> </span>
</td> </form>
<tal:comment replace="nothing">User info and controls for authenticated users</tal:comment> </td>
<td tal:condition="not: isAnon">
<table class="buttons" width="99%"> <!-- User info and controls for authenticated users -->
<tr> <td if="not isAnon">
<td> <table class="buttons" width="99%">
<tal:comment replace="nothing">Config</tal:comment> <tr>
<a tal:condition="python: user.has_role('Manager')" <td>
tal:attributes="href python: tool.getUrl(nav=''); <!-- Config -->
title python: _('%sTool' % appName)"> <a if="user.has_role('Manager')" href=":tool.url"
<img tal:attributes="src string:$appUrl/ui/appyConfig.gif"/> title="_('%sTool' % appName)">
</a> <img src="'%s/ui/appyConfig.gif' % appUrl"/></a>
<tal:comment replace="nothing">Additional icons from icons.pt</tal:comment> <!-- Additional icons from icons.pt -->
<metal:call use-macro="app/ui/icons/macros/icons"/> <!--metal:call use-macro="app/ui/icons/macros/icons"/-->
<tal:comment replace="nothing">Log out</tal:comment> <!-- Log out -->
<a tal:attributes="href python: tool.absolute_url() + '/performLogout'; <a href=":tool.url + '/performLogout'" title=":_('app_logout')">
title python: _('app_logout')"> <img src=":'%s/ui/logout.gif' % appUrl"/></a>
<img tal:attributes="src string: $appUrl/ui/logout.gif"/> </td>
</a> <td class="userStripText" var="userInfo=ztool.getUserLine()"
</td> align=":dright">
<td class="userStripText" tal:define="userInfo tool/getUserLine" tal:attributes="align dright"> <span>:userInfo[0]</span>
<span tal:content="python: userInfo[0]"></span> <a if="userInfo[1]" href=":userInfo[1]">
<a tal:condition="python: userInfo[1]" <img src=":'%s/ui/edit.png' % appUrl"/></a>
tal:attributes="href python: userInfo[1]"> </td>
<img tal:attributes="src string: $appUrl/ui/edit.png"/> </tr>
</a> </table>
</td> </td>
</tr> </tr>
</table> </table>
</td> </td>
</tr> </tr>
</table>
</td> <!-- The navigation strip -->
</tr> <tr if="contextObj and (layoutType == 'view')">
<tal:comment replace="nothing">The navigation strip</tal:comment> <td>
<tr tal:condition="python: contextObj and (layoutType == 'view')"> <!--metal:navigate use-macro="app/ui/navigate/macros/navigationStrip"/-->
<td><metal:navigate use-macro="app/ui/navigate/macros/navigationStrip"/></td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>
<table width="100%" cellpadding="0" cellspacing="0"> <table width="100%" cellpadding="0" cellspacing="0">
<tr valign="top"> <tr valign="top">
<tal:comment replace="nothing">Portlet</tal:comment> <!-- The portlet -->
<td tal:condition="python: tool.showPortlet(context, layoutType)" class="portlet"> <td if="ztool.showPortlet(contextObj, layoutType)"
<metal:portlet use-macro="app/ui/portlet/macros/portlet"/> class="portlet">:self.pxPortlet</td>
</td> <!-- Page content -->
<tal:comment replace="nothing">Page content</tal:comment> <td class="content">:content</td>
<td class="content"><span metal:define-slot="content"></span></td> </tr>
</tr> </table>
</table> </td>
</td> </tr>
</tr> <!-- Footer -->
<tr><tal:comment replace="nothing">Footer</tal:comment> <tr><td>:self.pxFooter</td></tr>
<td><metal:call use-macro="app/ui/footer/macros/footer"/></td> </table>
</tr-->
</table>
<x>:content</x>
</body> </body>
</html> </html>
''', prologue=Px.xhtmlPrologue) ''', prologue=Px.xhtmlPrologue)

View file

@ -3,6 +3,7 @@
Python and XML.''' Python and XML.'''
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import xml.sax
from px_parser import PxParser, PxEnvironment from px_parser import PxParser, PxEnvironment
from appy.pod.buffers import MemoryBuffer from appy.pod.buffers import MemoryBuffer
@ -45,13 +46,32 @@ class Px:
self.parser = PxParser(PxEnvironment(), self) self.parser = PxParser(PxEnvironment(), self)
# Parses self.content (a PX code in a string) with self.parser, to # Parses self.content (a PX code in a string) with self.parser, to
# produce a tree of memory buffers. # produce a tree of memory buffers.
self.parser.parse(self.content) try:
self.parser.parse(self.content)
except xml.sax.SAXParseException, spe:
self.completeErrorMessage(spe)
raise spe
# Is this PX based on a template PX? # Is this PX based on a template PX?
self.template = template self.template = template
self.hook = hook self.hook = hook
# Is there some (XML, XHTML...) prologue to dump? # Is there some (XML, XHTML...) prologue to dump?
self.prologue = prologue self.prologue = prologue
def completeErrorMessage(self, parsingError):
'''A p_parsingError occurred. Complete the error message with the
erroneous line from self.content.'''
# Split lines from self.content
splitted = self.content.split('\n')
i = parsingError.getLineNumber() - 1
# Get the erroneous line, and add a subsequent line for indicating
# the erroneous column.
column = ' ' * (parsingError.getColumnNumber()-1) + '^'
lines = [splitted[i], column]
# Get the previous and next lines when present.
if i > 0: lines.insert(0, splitted[i-1])
if i < len(splitted)-1: lines.append(splitted[i+1])
parsingError._msg += '\n%s' % '\n'.join(lines)
def __call__(self, context, applyTemplate=True): def __call__(self, context, applyTemplate=True):
'''Renders the PX. '''Renders the PX.