[px] Better error reporting when encountering a parsing error in a PX.
This commit is contained in:
parent
e4b84be05e
commit
e6cacd10dd
|
@ -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()
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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&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="%s/do?action=Create&' \
|
||||||
|
'className=%s"' % (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="%s/ui/import?' \
|
||||||
|
'className=%s"' % (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="%s/config/changeLanguage?language=" + 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,70 +262,67 @@ class AbstractWrapper(object):
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<!--tal:comment replace="nothing">The message strip</tal:comment>
|
|
||||||
|
<!-- The message strip -->
|
||||||
<tr valign="top">
|
<tr valign="top">
|
||||||
<td>
|
<td><div style="position: relative">:self.pxMessage</div></td>
|
||||||
<div style="position: relative">
|
|
||||||
<metal:msg use-macro="app/ui/page/macros/message"/>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tal:comment replace="nothing">The user strip</tal:comment>
|
|
||||||
|
<!-- The user strip -->
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<table class="userStrip" width="100%">
|
<table class="userStrip" width="100%">
|
||||||
<tr>
|
<tr>
|
||||||
<tal:comment replace="nothing">The user login form for anonymous users</tal:comment>
|
<!-- The user login form for anonymous users -->
|
||||||
<td align="center"
|
<td align="center"
|
||||||
tal:condition="python: isAnon and ('/temp_folder/' not in req['ACTUAL_URL'])">
|
if="isAnon and ('/temp_folder/' not in req['ACTUAL_URL'])">
|
||||||
<form id="loginForm" name="loginForm" method="post" class="login"
|
<form id="loginForm" name="loginForm" method="post" class="login"
|
||||||
tal:attributes="action python: tool.absolute_url() + '/performLogin'">
|
action=":tool.url + '/performLogin'">
|
||||||
<input type="hidden" name="js_enabled" id="js_enabled" value="0"/>
|
<input type="hidden" name="js_enabled" id="js_enabled" value="0"/>
|
||||||
<input type="hidden" name="cookies_enabled" id="cookies_enabled" 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"/>
|
||||||
<tal:comment replace="nothing">The login fields, directly shown or not (depends on discreetLogin)</tal:comment>
|
<!-- Login fields, directly shown or not (depends on
|
||||||
|
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"/>
|
<input type="text" name="__ac_name" id="__ac_name" value=""
|
||||||
<span class="userStripText" tal:content="python: _('app_password')"/>
|
style="width: 142px"/>
|
||||||
<input type="password" name="__ac_password" id="__ac_password" style="width: 142px"/>
|
<span class="userStripText">:_('app_password')</span>
|
||||||
|
<input type="password" name="__ac_password" id="__ac_password"
|
||||||
|
style="width: 142px"/>
|
||||||
<input type="submit" name="submit" onclick="setLoginVars()"
|
<input type="submit" name="submit" onclick="setLoginVars()"
|
||||||
tal:define="label python: _('app_connect')" tal:attributes="value label; alt label"/>
|
var="label=_('app_connect')" value=":label" alt=":label"/>
|
||||||
<tal:comment replace="nothing">Forgot password?</tal:comment>
|
<!-- Forgot password? -->
|
||||||
<a class="lostPassword" href="javascript: openPopup('askPasswordReinitPopup')"
|
<a if="ztool.showForgotPassword()"
|
||||||
tal:condition="tool/showForgotPassword"
|
href="javascript: openPopup('askPasswordReinitPopup')"
|
||||||
tal:content="python: _('forgot_password')">
|
class="lostPassword">:_('forgot_password')</a>
|
||||||
</a>
|
|
||||||
</span>
|
</span>
|
||||||
</form>
|
</form>
|
||||||
</td>
|
</td>
|
||||||
<tal:comment replace="nothing">User info and controls for authenticated users</tal:comment>
|
|
||||||
<td tal:condition="not: isAnon">
|
<!-- User info and controls for authenticated users -->
|
||||||
|
<td if="not isAnon">
|
||||||
<table class="buttons" width="99%">
|
<table class="buttons" width="99%">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<tal:comment replace="nothing">Config</tal:comment>
|
<!-- Config -->
|
||||||
<a tal:condition="python: user.has_role('Manager')"
|
<a if="user.has_role('Manager')" href=":tool.url"
|
||||||
tal:attributes="href python: tool.getUrl(nav='');
|
title="_('%sTool' % appName)">
|
||||||
title python: _('%sTool' % appName)">
|
<img src="'%s/ui/appyConfig.gif' % appUrl"/></a>
|
||||||
<img tal:attributes="src string:$appUrl/ui/appyConfig.gif"/>
|
<!-- Additional icons from icons.pt -->
|
||||||
</a>
|
<!--metal:call use-macro="app/ui/icons/macros/icons"/-->
|
||||||
<tal:comment replace="nothing">Additional icons from icons.pt</tal:comment>
|
<!-- Log out -->
|
||||||
<metal:call use-macro="app/ui/icons/macros/icons"/>
|
<a href=":tool.url + '/performLogout'" title=":_('app_logout')">
|
||||||
<tal:comment replace="nothing">Log out</tal:comment>
|
<img src=":'%s/ui/logout.gif' % appUrl"/></a>
|
||||||
<a tal:attributes="href python: tool.absolute_url() + '/performLogout';
|
|
||||||
title python: _('app_logout')">
|
|
||||||
<img tal:attributes="src string: $appUrl/ui/logout.gif"/>
|
|
||||||
</a>
|
|
||||||
</td>
|
</td>
|
||||||
<td class="userStripText" tal:define="userInfo tool/getUserLine" tal:attributes="align dright">
|
<td class="userStripText" var="userInfo=ztool.getUserLine()"
|
||||||
<span tal:content="python: userInfo[0]"></span>
|
align=":dright">
|
||||||
<a tal:condition="python: userInfo[1]"
|
<span>:userInfo[0]</span>
|
||||||
tal:attributes="href python: userInfo[1]">
|
<a if="userInfo[1]" href=":userInfo[1]">
|
||||||
<img tal:attributes="src string: $appUrl/ui/edit.png"/>
|
<img src=":'%s/ui/edit.png' % appUrl"/></a>
|
||||||
</a>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
@ -210,29 +331,29 @@ class AbstractWrapper(object):
|
||||||
</table>
|
</table>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tal:comment replace="nothing">The navigation strip</tal:comment>
|
|
||||||
<tr tal:condition="python: contextObj and (layoutType == 'view')">
|
<!-- The navigation strip -->
|
||||||
<td><metal:navigate use-macro="app/ui/navigate/macros/navigationStrip"/></td>
|
<tr if="contextObj and (layoutType == 'view')">
|
||||||
|
<td>
|
||||||
|
<!--metal:navigate use-macro="app/ui/navigate/macros/navigationStrip"/-->
|
||||||
|
</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>
|
||||||
<tr><tal:comment replace="nothing">Footer</tal:comment>
|
<!-- Footer -->
|
||||||
<td><metal:call use-macro="app/ui/footer/macros/footer"/></td>
|
<tr><td>:self.pxFooter</td></tr>
|
||||||
</tr-->
|
|
||||||
</table>
|
</table>
|
||||||
<x>:content</x>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
''', prologue=Px.xhtmlPrologue)
|
''', prologue=Px.xhtmlPrologue)
|
||||||
|
|
|
@ -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.
|
||||||
|
try:
|
||||||
self.parser.parse(self.content)
|
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.
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue