You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2025/05/04 14:38:29 UTC

(commons-jelly) 01/02: Sort members

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jelly.git

commit fb088290c3ebd6a82aa65e98cfe0748b28ea504e
Author: Gary D. Gregory <ga...@gmail.com>
AuthorDate: Sun May 4 10:33:20 2025 -0400

    Sort members
---
 .../apache/commons/jelly/DynaBeanTagSupport.java   |   38 +-
 .../java/org/apache/commons/jelly/DynaTag.java     |    8 +-
 .../main/java/org/apache/commons/jelly/Jelly.java  |  230 +--
 .../org/apache/commons/jelly/JellyContext.java     | 1078 ++++++-------
 .../org/apache/commons/jelly/JellyException.java   |  130 +-
 .../apache/commons/jelly/JellyTagException.java    |   16 +-
 .../org/apache/commons/jelly/LocationAware.java    |   32 +-
 .../org/apache/commons/jelly/MapTagSupport.java    |   20 +-
 .../main/java/org/apache/commons/jelly/Tag.java    |   60 +-
 .../java/org/apache/commons/jelly/TagLibrary.java  |   84 +-
 .../java/org/apache/commons/jelly/TagSupport.java  |  272 ++--
 .../java/org/apache/commons/jelly/XMLOutput.java   | 1284 +++++++--------
 .../jelly/expression/CompositeExpression.java      |   98 +-
 .../jelly/expression/ConstantExpression.java       |   20 +-
 .../commons/jelly/expression/Expression.java       |   22 +-
 .../jelly/expression/ExpressionSupport.java        |   52 +-
 .../jelly/expression/jexl/JexlExpression.java      |  140 +-
 .../expression/jexl/JexlExpressionFactory.java     |   82 +-
 .../jelly/expression/xpath/XPathExpression.java    |   50 +-
 .../org/apache/commons/jelly/impl/Attribute.java   |   48 +-
 .../jelly/impl/DefaultTagLibraryResolver.java      |  172 +-
 .../apache/commons/jelly/impl/DynamicBeanTag.java  |   68 +-
 .../commons/jelly/impl/DynamicDynaBeanTag.java     |   54 +-
 .../org/apache/commons/jelly/impl/DynamicTag.java  |   16 +-
 .../commons/jelly/impl/DynamicTagLibrary.java      |   88 +-
 .../org/apache/commons/jelly/impl/Embedded.java    |  214 +--
 .../commons/jelly/impl/ExpressionScript.java       |   28 +-
 .../org/apache/commons/jelly/impl/ScriptBlock.java |   42 +-
 .../org/apache/commons/jelly/impl/StaticTag.java   |   60 +-
 .../apache/commons/jelly/impl/StaticTagScript.java |   36 +-
 .../org/apache/commons/jelly/impl/TagScript.java   |  844 +++++-----
 .../org/apache/commons/jelly/impl/TextScript.java  |   88 +-
 .../jelly/parser/DefaultNamespaceFilter.java       |   36 +-
 .../org/apache/commons/jelly/parser/XMLParser.java | 1698 ++++++++++----------
 .../apache/commons/jelly/servlet/JellyServlet.java |  100 +-
 .../commons/jelly/servlet/JellyServletContext.java |   16 +-
 .../org/apache/commons/jelly/tags/Resources.java   |   14 +-
 .../org/apache/commons/jelly/tags/core/ArgTag.java |  286 ++--
 .../jelly/tags/core/BaseClassLoaderTag.java        |   14 +-
 .../apache/commons/jelly/tags/core/CaseTag.java    |   24 +-
 .../apache/commons/jelly/tags/core/CatchTag.java   |  112 +-
 .../apache/commons/jelly/tags/core/DefaultTag.java |   14 +-
 .../apache/commons/jelly/tags/core/FileTag.java    |  128 +-
 .../apache/commons/jelly/tags/core/ForEachTag.java |  262 +--
 .../commons/jelly/tags/core/GetStaticTag.java      |   66 +-
 .../apache/commons/jelly/tags/core/ImportTag.java  |   16 +-
 .../apache/commons/jelly/tags/core/IncludeTag.java |   76 +-
 .../commons/jelly/tags/core/InvokeStaticTag.java   |   90 +-
 .../apache/commons/jelly/tags/core/InvokeTag.java  |   40 +-
 .../apache/commons/jelly/tags/core/MuteTag.java    |   30 +-
 .../org/apache/commons/jelly/tags/core/NewTag.java |   20 +-
 .../apache/commons/jelly/tags/core/ParseTag.java   |  116 +-
 .../org/apache/commons/jelly/tags/core/SetTag.java |   90 +-
 .../apache/commons/jelly/tags/core/SwitchTag.java  |   70 +-
 .../apache/commons/jelly/tags/core/ThreadTag.java  |   22 +-
 .../apache/commons/jelly/tags/core/UseBeanTag.java |  168 +-
 .../apache/commons/jelly/tags/core/UseListTag.java |   20 +-
 .../apache/commons/jelly/test/BaseJellyTest.java   |   50 +-
 .../commons/jelly/util/ClassLoaderUtils.java       |   26 +-
 .../commons/jelly/util/CommandLineParser.java      |    4 +-
 .../commons/jelly/util/NestedRuntimeException.java |   18 +-
 .../commons/jelly/util/SafeContentHandler.java     |   34 +-
 .../org/apache/commons/jelly/util/TagUtils.java    |    8 +-
 .../commons/jelly/xpath/XPathComparator.java       |   48 +-
 .../test/java/org/apache/commons/jelly/TJEcho.java |   24 +-
 .../org/apache/commons/jelly/TestCoreTags.java     |   38 +-
 .../commons/jelly/TestDoctypeDefinitionXXE.java    |   14 +-
 .../java/org/apache/commons/jelly/TestTrim.java    |   24 +-
 .../org/apache/commons/jelly/TestXMLOutput.java    |   20 +-
 .../commons/jelly/core/BaseMemoryLeakTest.java     |    8 +-
 .../org/apache/commons/jelly/core/Customer.java    |   54 +-
 .../apache/commons/jelly/core/ExceptionBean.java   |    8 +-
 .../commons/jelly/core/ModularExceptionBean.java   |   26 +-
 .../java/org/apache/commons/jelly/core/Order.java  |   26 +-
 .../org/apache/commons/jelly/core/Product.java     |   10 +-
 .../org/apache/commons/jelly/core/TestArgTag.java  |  222 +--
 .../apache/commons/jelly/core/TestBreakTag.java    |   20 +-
 .../apache/commons/jelly/core/TestChooseTag.java   |    8 +-
 .../org/apache/commons/jelly/core/TestFileTag.java |   34 +-
 .../apache/commons/jelly/core/TestForEachTag.java  |    8 +-
 .../commons/jelly/core/TestGetStaticTag.java       |    8 +-
 .../apache/commons/jelly/core/TestIncludeTag.java  |   92 +-
 .../commons/jelly/core/TestInvokeStaticTag.java    |  104 +-
 .../apache/commons/jelly/core/TestInvokeTag.java   |   84 +-
 .../commons/jelly/core/TestNamespacePrefixes.java  |    8 +-
 .../org/apache/commons/jelly/core/TestNewTag.java  |   84 +-
 .../apache/commons/jelly/core/TestSwitchTag.java   |  172 +-
 .../jelly/core/TestUnexpectedTagCaching.java       |   74 +-
 .../apache/commons/jelly/core/TestUnknownTags.java |    8 +-
 .../apache/commons/jelly/core/TestUseBeanTag.java  |   44 +-
 .../expression/TestCustomExpressionFactory.java    |   20 +-
 .../commons/jelly/expression/TestDynaBeans.java    |   40 +-
 .../commons/jelly/expression/TestExpressions.java  |   60 +-
 .../commons/jelly/tags/junit/AssertThrowsTag.java  |   58 +-
 .../apache/commons/jelly/tags/junit/CaseTag.java   |   20 +-
 .../tags/junit/JellyAssertionFailedError.java      |   94 +-
 .../apache/commons/jelly/tags/junit/RunTag.java    |  164 +-
 .../apache/commons/jelly/tags/junit/SuiteTag.java  |   38 +-
 .../commons/jelly/test/impl/TestEmbedded.java      |   58 +-
 .../jelly/test/xml/TestDefaultNamespaceFilter.java |    8 +-
 .../commons/jelly/test/xml/TestDummyXMLOutput.java |    8 +-
 .../jelly/test/xml/TestNonexistentTags.java        |   10 +-
 .../commons/jelly/test/xml/TestXMLParserCache.java |    8 +-
 .../commons/jelly/test/xml/TestXMLValidation.java  |    8 +-
 .../org/apache/commons/jelly/tags/ant/AntTag.java  |  418 ++---
 .../commons/jelly/tags/ant/AntTagLibrary.java      |   76 +-
 .../jelly/tags/ant/DefaultPropsHandler.java        |  132 +-
 .../apache/commons/jelly/tags/ant/FileScanner.java |   30 +-
 .../commons/jelly/tags/ant/FileScannerTag.java     |   20 +-
 .../commons/jelly/tags/ant/GrantProject.java       |  162 +-
 .../commons/jelly/tags/ant/JellyPropsHandler.java  |   34 +-
 .../commons/jelly/tags/ant/PropsHandler.java       |   92 +-
 .../commons/jelly/tags/ant/SetPropertyTag.java     |   30 +-
 .../apache/commons/jelly/task/AntJellyContext.java |   66 +-
 .../org/apache/commons/jelly/task/JellyTask.java   |  148 +-
 .../org/apache/commons/jelly/ant/tag/DummyTag.java |   18 +-
 .../apache/commons/jelly/ant/task/DummyTask.java   |   38 +-
 .../apache/commons/jelly/tags/antlr/AntlrTag.java  |   20 +-
 .../apache/commons/jelly/avalon/JellyService.java  |   22 +-
 .../commons/jelly/avalon/JellyServiceImpl.java     |  170 +-
 .../commons/jelly/tags/bean/BeanPropertyTag.java   |   62 +-
 .../apache/commons/jelly/tags/bean/BeanTag.java    |   48 +-
 .../commons/jelly/tags/bean/BeanTagLibrary.java    |   86 +-
 .../apache/commons/jelly/tags/bean/BeandefTag.java |   50 +-
 .../apache/commons/jelly/tags/bean/Customer.java   |   52 +-
 .../apache/commons/jelly/tags/bean/Manager.java    |   34 +-
 .../commons/jelly/tags/bean/MyContainerTag.java    |   14 +-
 .../commons/jelly/tags/bean/MyTagLibrary.java      |   34 +-
 .../org/apache/commons/jelly/tags/bean/Order.java  |   26 +-
 .../apache/commons/jelly/tags/bean/Product.java    |   10 +-
 .../jelly/tags/beanshell/BeanShellExpression.java  |   14 +-
 .../jelly/tags/beanshell/JellyInterpreter.java     |   48 +-
 .../jelly/tags/beanshell/TestBeanShellEL.java      |   26 +-
 .../jelly/tags/betwixt/IntrospectorTag.java        |  144 +-
 .../commons/jelly/tags/betwixt/ParseTag.java       |   58 +-
 .../org/apache/commons/digester/rss/Channel.java   |  310 ++--
 .../org/apache/commons/digester/rss/Image.java     |   88 +-
 .../java/org/apache/commons/digester/rss/Item.java |   40 +-
 .../apache/commons/digester/rss/RSSDigester.java   |  256 +--
 .../org/apache/commons/digester/rss/TextInput.java |   56 +-
 .../commons/jelly/tags/bsf/BSFExpression.java      |   14 +-
 .../jelly/tags/bsf/BSFExpressionFactory.java       |   56 +-
 .../commons/jelly/tags/bsf/BSFTagLibrary.java      |   16 +-
 .../jelly/tags/bsf/JellyContextRegistry.java       |    8 +-
 .../apache/commons/jelly/tags/bsf/ScriptTag.java   |   10 +-
 .../commons/jelly/tags/define/AttributeTag.java    |   14 +-
 .../apache/commons/jelly/tags/define/BeanTag.java  |   36 +-
 .../commons/jelly/tags/define/ClassLoaderTag.java  |   62 +-
 .../commons/jelly/tags/define/DynaBeanTag.java     |   30 +-
 .../commons/jelly/tags/define/ExtendTag.java       |   12 +-
 .../commons/jelly/tags/define/InvokeTag.java       |   16 +-
 .../commons/jelly/tags/define/JellyBeanTag.java    |   32 +-
 .../commons/jelly/tags/define/TaglibTag.java       |   22 +-
 .../commons/jelly/tags/define/MyRunnable.java      |   20 +-
 .../commons/jelly/tags/define/TestDynamicTags.java |   28 +-
 .../commons/jelly/tags/dynabean/DynabeanTag.java   |    8 +-
 .../commons/jelly/tags/dynabean/DynaclassTag.java  |   20 +-
 .../commons/jelly/tags/dynabean/PropertyTag.java   |   24 +-
 .../apache/commons/jelly/tags/dynabean/SetTag.java |   54 +-
 .../apache/commons/jelly/tags/email/EmailTag.java  |  124 +-
 .../apache/commons/jelly/tags/fmt/BundleTag.java   |  200 +--
 .../commons/jelly/tags/fmt/FormatDateTag.java      |  164 +-
 .../jelly/tags/fmt/LocalizationContext.java        |   38 +-
 .../apache/commons/jelly/tags/fmt/MessageTag.java  |   24 +-
 .../commons/jelly/tags/fmt/SetBundleTag.java       |    8 +-
 .../commons/jelly/tags/fmt/SetLocaleTag.java       |  330 ++--
 .../commons/jelly/tags/fmt/SetTimeZoneTag.java     |    8 +-
 .../apache/commons/jelly/tags/fmt/TimeZoneTag.java |   90 +-
 .../apache/commons/jelly/tags/html/ParseTag.java   |   46 +-
 .../apache/commons/jelly/tags/http/HeaderTag.java  |   16 +-
 .../commons/jelly/tags/http/HttpTagSupport.java    |  188 +--
 .../commons/jelly/tags/http/MultipartPostTag.java  |   18 +-
 .../commons/jelly/tags/http/ParameterTag.java      |   16 +-
 .../apache/commons/jelly/tags/http/PartTag.java    |   64 +-
 .../org/apache/commons/jelly/tags/http/Proxy.java  |   20 +-
 .../org/apache/commons/jelly/tags/http/PutTag.java |   22 +-
 .../apache/commons/jelly/tags/http/SessionTag.java |  126 +-
 .../commons/jelly/tags/interaction/AskTag.java     |  126 +-
 .../commons/jelly/tags/jaxme/GeneratorTag.java     |   66 +-
 .../commons/jelly/tags/jaxme/MarshallTag.java      |   44 +-
 .../commons/jelly/tags/jaxme/UnmarshallTag.java    |   46 +-
 .../jaxme/examples/misc/address/AddressType.java   |   14 +-
 .../jaxme/examples/misc/address/ObjectFactory.java |   20 +-
 .../misc/address/impl/AddressTypeHandler.java      |  242 +--
 .../misc/address/impl/AddressTypeImpl.java         |   30 +-
 .../misc/address/impl/AddressTypeSerializer.java   |    4 +-
 .../jelly/tags/jetty/AbstractMethodHandlerTag.java |    6 +-
 .../commons/jelly/tags/jetty/HttpContextTag.java   |   84 +-
 .../jelly/tags/jetty/JellyResourceHttpHandler.java |   52 +-
 .../jelly/tags/jetty/JettyHttpServerTag.java       |   82 +-
 .../apache/commons/jelly/tags/jetty/RealmTag.java  |   32 +-
 .../jelly/tags/jetty/ResponseHeaderTag.java        |   16 +-
 .../jelly/tags/jetty/SecurityHandlerTag.java       |  136 +-
 .../jelly/tags/jetty/SocketListenerTag.java        |   34 +-
 .../jelly/jetty/TestJettyHttpServerTags.java       |   92 +-
 .../apache/commons/jelly/tags/jface/ActionTag.java |   24 +-
 .../jelly/tags/jface/DoubleClickListenerTag.java   |   34 +-
 .../commons/jelly/tags/jface/JFaceImageTag.java    |   42 +-
 .../jelly/tags/jface/JFaceLayoutDataTag.java       |   24 +-
 .../commons/jelly/tags/jface/JFaceLayoutTag.java   |   24 +-
 .../commons/jelly/tags/jface/JFaceTagLibrary.java  |  120 +-
 .../commons/jelly/tags/jface/MenuManagerTag.java   |   30 +-
 .../tags/jface/SelectionChangedListenerTag.java    |   22 +-
 .../apache/commons/jelly/tags/jface/ViewerTag.java |   22 +-
 .../tags/jface/window/ApplicationWindowTag.java    |   22 +-
 .../commons/jelly/tags/jms/ConnectionTag.java      |   14 +-
 .../commons/jelly/tags/jms/MapMessageTag.java      |   22 +-
 .../jelly/tags/jms/MessageOperationTag.java        |   56 +-
 .../apache/commons/jelly/tags/jms/MessageTag.java  |   66 +-
 .../commons/jelly/tags/jms/ObjectMessageTag.java   |   18 +-
 .../apache/commons/jelly/tags/jms/ReceiveTag.java  |   34 +-
 .../commons/jelly/tags/jms/StopwatchTag.java       |   24 +-
 .../commons/jelly/tags/jms/SubscribeTag.java       |   14 +-
 .../commons/jelly/tags/jms/TextMessageTag.java     |   16 +-
 .../commons/jelly/tags/jmx/OperationTag.java       |   52 +-
 .../apache/commons/jelly/tags/jmx/RegisterTag.java |   32 +-
 .../apache/commons/jelly/tags/jmx/ServerTag.java   |   22 +-
 .../org/apache/commons/jelly/tags/jmx/Dummy.java   |   16 +-
 .../apache/commons/jelly/tags/jmx/DummyMBean.java  |    4 +-
 .../commons/jelly/tags/jsl/ApplyTemplatesTag.java  |    8 +-
 .../apache/commons/jelly/tags/jsl/StyleTag.java    |   28 +-
 .../commons/jelly/tags/jsl/StylesheetTag.java      |  100 +-
 .../apache/commons/jelly/tags/jsl/TemplateTag.java |  114 +-
 .../jelly/tags/jsl/XPathPatternExpression.java     |   14 +-
 .../java/org/apache/commons/jelly/jsl/TestJSL.java |   20 +-
 .../commons/jelly/tags/junit/AssertTagSupport.java |  305 ++--
 .../commons/jelly/tags/junit/AssertThrowsTag.java  |   58 +-
 .../apache/commons/jelly/tags/junit/CaseTag.java   |   20 +-
 .../tags/junit/JellyAssertionFailedError.java      |   94 +-
 .../apache/commons/jelly/tags/junit/RunTag.java    |  164 +-
 .../apache/commons/jelly/tags/junit/SuiteTag.java  |   42 +-
 .../commons/jelly/tags/log/LogTagSupport.java      |   24 +-
 .../org/apache/commons/jelly/tags/log/MockLog.java |  162 +-
 .../commons/jelly/tags/memory/FreeMemoryTag.java   |   22 +-
 .../apache/commons/jelly/tags/ojb/BrokerTag.java   |   16 +-
 .../apache/commons/jelly/tags/ojb/StoreTag.java    |   10 +-
 .../commons/jelly/tags/quartz/CronTriggerTag.java  |  174 +-
 .../apache/commons/jelly/tags/quartz/JobTag.java   |   80 +-
 .../commons/jelly/tags/regexp/RegexpTag.java       |   70 +-
 .../commons/jelly/tags/regexp/ContainsTagTest.java |   10 +-
 .../commons/jelly/tags/regexp/MatchTagTest.java    |   10 +-
 .../commons/jelly/tags/soap/InvokeRawTag.java      |   12 +-
 .../apache/commons/jelly/tags/soap/InvokeTag.java  |   90 +-
 .../commons/jelly/tags/soap/StringInputStream.java |   44 +-
 .../java/javax/servlet/jsp/jstl/sql/Result.java    |   28 +-
 .../javax/servlet/jsp/jstl/sql/ResultImpl.java     |   52 +-
 .../commons/jelly/tags/sql/DataSourceWrapper.java  |   70 +-
 .../commons/jelly/tags/sql/DateParamTag.java       |   58 +-
 .../apache/commons/jelly/tags/sql/DriverTag.java   |   70 +-
 .../apache/commons/jelly/tags/sql/ParamTag.java    |   14 +-
 .../apache/commons/jelly/tags/sql/QueryTag.java    |   40 +-
 .../apache/commons/jelly/tags/sql/ResultImpl.java  |  130 +-
 .../commons/jelly/tags/sql/SetDataSourceTag.java   |   74 +-
 .../commons/jelly/tags/sql/SqlTagSupport.java      |   96 +-
 .../commons/jelly/tags/sql/TransactionTag.java     |   74 +-
 .../apache/commons/jelly/tags/swing/ActionTag.java |   42 +-
 .../commons/jelly/tags/swing/BorderAlignTag.java   |   22 +-
 .../commons/jelly/tags/swing/BorderLayoutTag.java  |   22 +-
 .../commons/jelly/tags/swing/BorderTagSupport.java |   18 +-
 .../commons/jelly/tags/swing/ButtonGroupTag.java   |    8 +-
 .../commons/jelly/tags/swing/CardLayoutTag.java    |   14 +-
 .../commons/jelly/tags/swing/ComponentTag.java     |  332 ++--
 .../commons/jelly/tags/swing/ConstraintTag.java    |   82 +-
 .../apache/commons/jelly/tags/swing/DialogTag.java |   30 +-
 .../commons/jelly/tags/swing/EmptyBorderTag.java   |   32 +-
 .../commons/jelly/tags/swing/EtchedBorderTag.java  |   34 +-
 .../commons/jelly/tags/swing/FocusListenerTag.java |   48 +-
 .../apache/commons/jelly/tags/swing/FontTag.java   |   30 +-
 .../apache/commons/jelly/tags/swing/GbcTag.java    |   30 +-
 .../commons/jelly/tags/swing/KeyListenerTag.java   |   52 +-
 .../commons/jelly/tags/swing/LayoutTagSupport.java |   28 +-
 .../commons/jelly/tags/swing/SwingTagLibrary.java  |   36 +-
 .../commons/jelly/tags/swing/TableLayoutTag.java   |   14 +-
 .../jelly/tags/swing/TableModelColumnTag.java      |   18 +-
 .../commons/jelly/tags/swing/TableModelTag.java    |   10 +-
 .../org/apache/commons/jelly/tags/swing/TdTag.java |  142 +-
 .../commons/jelly/tags/swing/TitledBorderTag.java  |  156 +-
 .../jelly/tags/swing/WindowListenerTag.java        |   50 +-
 .../tags/swing/impl/GridBagConstraintBean.java     |  278 ++--
 .../tags/swing/model/ExpressionTableColumn.java    |   16 +-
 .../tags/swing/model/ExpressionTableModel.java     |  104 +-
 .../apache/commons/jelly/core/BaseJellyTest.java   |   34 +-
 .../commons/jelly/demos/HomepageBuilder.java       |   46 +-
 .../apache/commons/jelly/swing/MyTableModel.java   |   34 +-
 .../apache/commons/jelly/swing/TestConverters.java |   58 +-
 .../apache/commons/jelly/swing/TestSwingTags.java  |  156 +-
 .../apache/commons/jelly/tags/swt/ColorTag.java    |  100 +-
 .../apache/commons/jelly/tags/swt/DialogTag.java   |   10 +-
 .../org/apache/commons/jelly/tags/swt/FontTag.java |  168 +-
 .../org/apache/commons/jelly/tags/swt/GCTag.java   |   78 +-
 .../apache/commons/jelly/tags/swt/ImageTag.java    |  122 +-
 .../commons/jelly/tags/swt/LayoutDataTag.java      |   38 +-
 .../apache/commons/jelly/tags/swt/LayoutTag.java   |   38 +-
 .../commons/jelly/tags/swt/LayoutTagSupport.java   |   38 +-
 .../apache/commons/jelly/tags/swt/OnEventTag.java  |   52 +-
 .../apache/commons/jelly/tags/swt/SwtHelper.java   |   38 +-
 .../commons/jelly/tags/swt/SwtTagLibrary.java      |   82 +-
 .../apache/commons/jelly/tags/swt/WidgetTag.java   |  248 +--
 .../jelly/tags/swt/converters/ColorConverter.java  |   92 +-
 .../jelly/tags/swt/converters/PointConverter.java  |   30 +-
 .../commons/jelly/tags/threads/GroupTag.java       |   10 +-
 .../commons/jelly/tags/threads/JellyThread.java    |   20 +-
 .../apache/commons/jelly/tags/threads/JoinTag.java |   24 +-
 .../apache/commons/jelly/tags/threads/Mutex.java   |   10 +-
 .../commons/jelly/tags/threads/NotifyTag.java      |   14 +-
 .../commons/jelly/tags/threads/RunnableStatus.java |  130 +-
 .../commons/jelly/tags/threads/ThreadTag.java      |   66 +-
 .../commons/jelly/tags/threads/UseMutexTag.java    |    6 +-
 .../commons/jelly/tags/threads/UseThreadTag.java   |   42 +-
 .../commons/jelly/tags/threads/WaitForTag.java     |   62 +-
 .../apache/commons/jelly/tags/threads/WaitTag.java |   18 +-
 .../commons/jelly/tags/util/LoadTextTag.java       |   78 +-
 .../apache/commons/jelly/tags/util/ReplaceTag.java |   58 +-
 .../apache/commons/jelly/tags/util/SortTag.java    |   10 +-
 .../commons/jelly/tags/util/TokenizeTag.java       |   12 +-
 .../apache/commons/jelly/tags/util/Customer.java   |   22 +-
 .../commons/jelly/tags/validate/ValidateTag.java   |   84 +-
 .../commons/jelly/tags/validate/VerifierTag.java   |   58 +-
 .../jelly/tags/velocity/JellyContextAdapter.java   |   52 +-
 .../commons/jelly/tags/velocity/MergeTag.java      |  118 +-
 .../tags/velocity/JellyContextAdapterTest.java     |  162 +-
 .../commons/jelly/tags/xml/AttributeTag.java       |   12 +-
 .../apache/commons/jelly/tags/xml/CopyOfTag.java   |    8 +-
 .../org/apache/commons/jelly/tags/xml/CopyTag.java |    6 +-
 .../apache/commons/jelly/tags/xml/ElementTag.java  |  142 +-
 .../apache/commons/jelly/tags/xml/ForEachTag.java  |   38 +-
 .../org/apache/commons/jelly/tags/xml/IfTag.java   |   10 +-
 .../apache/commons/jelly/tags/xml/ParamTag.java    |   12 +-
 .../apache/commons/jelly/tags/xml/ParseTag.java    |   50 +-
 .../commons/jelly/tags/xml/ParseTagSupport.java    |  138 +-
 .../jelly/tags/xml/ReplaceNamespaceTag.java        |   56 +-
 .../org/apache/commons/jelly/tags/xml/SetTag.java  |  180 +--
 .../org/apache/commons/jelly/tags/xml/SortTag.java |   16 +-
 .../commons/jelly/tags/xml/TransformTag.java       |  936 +++++------
 .../apache/commons/jelly/tags/xml/TestParser.java  |   30 +-
 .../apache/commons/jelly/tags/xml/TestXMLTags.java |  380 ++---
 .../commons/jelly/tags/xmlunit/ActualTag.java      |   10 +-
 .../tags/xmlunit/AssertDocumentsEqualTag.java      |   10 +-
 .../commons/jelly/tags/xmlunit/ExpectedTag.java    |   10 +-
 .../jelly/tags/xmlunit/XMLUnitTagSupport.java      |   54 +-
 340 files changed, 13230 insertions(+), 13233 deletions(-)

diff --git a/core/src/main/java/org/apache/commons/jelly/DynaBeanTagSupport.java b/core/src/main/java/org/apache/commons/jelly/DynaBeanTagSupport.java
index 575ccaf2..db889582 100644
--- a/core/src/main/java/org/apache/commons/jelly/DynaBeanTagSupport.java
+++ b/core/src/main/java/org/apache/commons/jelly/DynaBeanTagSupport.java
@@ -39,20 +39,12 @@ public abstract class DynaBeanTagSupport extends DynaTagSupport {
         this.dynaBean = dynaBean;
     }
      
-    /** Sets the context in which the tag will be run. */
-    @Override
-    public void setContext(JellyContext context) throws JellyTagException {
-        this.context = context;
-        beforeSetAttributes();
-    }
-    
-    /** Sets an attribute value of this tag before the tag is invoked
+    /**
+     * Callback to allow processing to occur before the attributes are about to be set
      */
-    @Override
-    public void setAttribute(String name, Object value) throws JellyTagException {
-        getDynaBean().set(name, value);
+    public void beforeSetAttributes() throws JellyTagException {
     }
-
+    
     /**
      * @return the type of the given attribute. By default just return
      * Object.class if this is not known.
@@ -65,7 +57,7 @@ public abstract class DynaBeanTagSupport extends DynaTagSupport {
         }
         return Object.class;
     }
-    
+
     /** 
      * @return the DynaBean which is used to store the
      *  attributes of this tag
@@ -74,6 +66,20 @@ public abstract class DynaBeanTagSupport extends DynaTagSupport {
         return dynaBean;
     }
     
+    /** Sets an attribute value of this tag before the tag is invoked
+     */
+    @Override
+    public void setAttribute(String name, Object value) throws JellyTagException {
+        getDynaBean().set(name, value);
+    }
+    
+    /** Sets the context in which the tag will be run. */
+    @Override
+    public void setContext(JellyContext context) throws JellyTagException {
+        this.context = context;
+        beforeSetAttributes();
+    }
+
     /**
      * Sets the DynaBean which is used to store the
      *  attributes of this tag
@@ -81,11 +87,5 @@ public abstract class DynaBeanTagSupport extends DynaTagSupport {
     public void setDynaBean(DynaBean dynaBean) {
         this.dynaBean = dynaBean;
     }
-
-    /**
-     * Callback to allow processing to occur before the attributes are about to be set
-     */
-    public void beforeSetAttributes() throws JellyTagException {
-    }
     
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/DynaTag.java b/core/src/main/java/org/apache/commons/jelly/DynaTag.java
index 39aa8eac..537e87ea 100644
--- a/core/src/main/java/org/apache/commons/jelly/DynaTag.java
+++ b/core/src/main/java/org/apache/commons/jelly/DynaTag.java
@@ -31,10 +31,6 @@ package org.apache.commons.jelly;
 
 public interface DynaTag extends Tag {
 
-    /** Sets an attribute value of this tag before the tag is invoked
-     */
-    public void setAttribute(String name, Object value) throws JellyTagException;
-
     /**
      * @return the type of the given attribute. By default just return
      * Object.class if this is not known.
@@ -42,4 +38,8 @@ public interface DynaTag extends Tag {
      * be evaluated and just passed in as the attribute value.
      */
     public Class getAttributeType(String name) throws JellyTagException;
+
+    /** Sets an attribute value of this tag before the tag is invoked
+     */
+    public void setAttribute(String name, Object value) throws JellyTagException;
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/Jelly.java b/core/src/main/java/org/apache/commons/jelly/Jelly.java
index ed3abc39..6b0846a3 100644
--- a/core/src/main/java/org/apache/commons/jelly/Jelly.java
+++ b/core/src/main/java/org/apache/commons/jelly/Jelly.java
@@ -47,31 +47,14 @@ public class Jelly {
     /** The Log to which logging calls will be made. */
     private static final Log log = LogFactory.getLog(Jelly.class);
     
-    /** The JellyContext to use */
-    private JellyContext context;
-    
-    /** The URL of the script to execute */
-    private URL url;
-    
-    /** The URL of the root context for other scripts */
-    private URL rootContext;
+    public static String getJellyBuildDate() {
+        return readBuildTimestampResource("jelly-build-date.txt");
+    }
     
-    /** Whether we have loaded the properties yet */
-    private boolean loadedProperties = false;
-
-    /**
-     * whether to override the default namespace
-     */
-    private String defaultNamespaceURI = null;
-
-    /**
-     * whether or not to validate the Jelly script
-     */
-    private boolean validateXML = false;
-        
-    public Jelly() {
+    public static String getJellyVersion() {
+        return readBuildTimestampResource("jelly-version.txt");
     }
-
+    
     /**
      * Usage: jelly [scriptFile] [-script scriptFile -o outputFile -Dsysprop=syspropval]
      */
@@ -99,15 +82,6 @@ public class Jelly {
         }
     }
     
-    
-    public static String getJellyVersion() {
-        return readBuildTimestampResource("jelly-version.txt");
-    }
-    
-    public static String getJellyBuildDate() {
-        return readBuildTimestampResource("jelly-build-date.txt");
-    }
-    
     private static String readBuildTimestampResource(String name) {
         java.io.Reader in = null;
         try {
@@ -127,6 +101,32 @@ public class Jelly {
             throw new IllegalStateException("Resource \"" + name + "\" not found.");
         }
     }
+
+    /** The JellyContext to use */
+    private JellyContext context;
+
+    /** The URL of the script to execute */
+    private URL url;
+        
+    /** The URL of the root context for other scripts */
+    private URL rootContext;
+
+    /** Whether we have loaded the properties yet */
+    private boolean loadedProperties = false;
+    
+    
+    /**
+     * whether to override the default namespace
+     */
+    private String defaultNamespaceURI = null;
+    
+    /**
+     * whether or not to validate the Jelly script
+     */
+    private boolean validateXML = false;
+    
+    public Jelly() {
+    }
     
     
 
@@ -168,41 +168,6 @@ public class Jelly {
     // Properties
     //-------------------------------------------------------------------------                
     
-    /** 
-     * Sets the script URL to use as an absolute URL or a relative file name
-     */
-    public void setScript(String script) throws MalformedURLException {
-        setUrl(resolveURL(script));
-    }
-    
-    public URL getUrl() {
-        return url;
-    }
-    
-    /** 
-     * Sets the script URL to use 
-     */
-    public void setUrl(URL url) {
-        this.url = url;
-    }
-    
-    /** 
-     * Gets the root context
-     */
-    public URL getRootContext() throws MalformedURLException {
-        if (rootContext == null) {
-            rootContext = new File(System.getProperty("user.dir")).toURL();
-        }
-        return rootContext;
-    }
-    
-    /** 
-     * Sets the root context
-     */
-    public void setRootContext(URL rootContext) {
-        this.rootContext = rootContext;
-    }
-    
     /**
      * The context to use
      */
@@ -217,56 +182,20 @@ public class Jelly {
         return context;
     }
     
-    /**
-     * Allows the Jelly context to be explicitly set; note that it is the caller's
-     * responsibility to make sure that the URLs etc are properly configured
-     * @param context
-     */
-    public void setJellyContext(JellyContext context) {
-    	this.context = context;
-    }
-
-    /**
-     * Sets the jelly namespace to use for unprefixed elements.
-     * Will be overridden by an explicit namespace in the
-     * XML document.
-     * 
-     * @param namespace jelly namespace to use (e.g. 'jelly:core')
-     */
-    public void setDefaultNamespaceURI(String namespace) {
-        this.defaultNamespaceURI = namespace;
-    }
-
-    /**
-     * When set to true, the XML parser will attempt to validate
-     * the Jelly XML before converting it into a Script.
-     * 
-     * @param validate whether or not to validate
+    /** 
+     * Gets the root context
      */
-    public void setValidateXML(boolean validate) {
-        this.validateXML = validate;
+    public URL getRootContext() throws MalformedURLException {
+        if (rootContext == null) {
+            rootContext = new File(System.getProperty("user.dir")).toURL();
+        }
+        return rootContext;
     }
     
-    // Implementation methods
-    //-------------------------------------------------------------------------                
-    /**
-     * @return the URL for the relative file name or absolute URL 
-     */
-    protected URL resolveURL(String name) throws MalformedURLException {
-        
-        URL resourceUrl = ClassLoaderUtils.getClassLoader(getClass()).getResource(name);
-        if (resourceUrl == null)
-        {
-            File file = new File(name);
-            if (file.exists()) {
-                return file.toURL();
-            }
-            return new URL(name);
-        } else {
-            return resourceUrl;
-        }
+    public URL getUrl() {
+        return url;
     }
-
+    
     /**
      * Attempts to load jelly.properties from the current directory,
      * the users home directory or from the classpath
@@ -292,7 +221,7 @@ public class Jelly {
             }
         }
     }
-
+    
     /**
      * Load properties from a file into the context
      * @param f
@@ -316,7 +245,7 @@ public class Jelly {
             }
         }
     }
-
+    
     /**
      * Loads the properties from the given input stream 
      */    
@@ -333,4 +262,75 @@ public class Jelly {
             theContext.setVariable(key, value);
         }
     }
+    
+    // Implementation methods
+    //-------------------------------------------------------------------------                
+    /**
+     * @return the URL for the relative file name or absolute URL 
+     */
+    protected URL resolveURL(String name) throws MalformedURLException {
+        
+        URL resourceUrl = ClassLoaderUtils.getClassLoader(getClass()).getResource(name);
+        if (resourceUrl == null)
+        {
+            File file = new File(name);
+            if (file.exists()) {
+                return file.toURL();
+            }
+            return new URL(name);
+        } else {
+            return resourceUrl;
+        }
+    }
+
+    /**
+     * Sets the jelly namespace to use for unprefixed elements.
+     * Will be overridden by an explicit namespace in the
+     * XML document.
+     * 
+     * @param namespace jelly namespace to use (e.g. 'jelly:core')
+     */
+    public void setDefaultNamespaceURI(String namespace) {
+        this.defaultNamespaceURI = namespace;
+    }
+
+    /**
+     * Allows the Jelly context to be explicitly set; note that it is the caller's
+     * responsibility to make sure that the URLs etc are properly configured
+     * @param context
+     */
+    public void setJellyContext(JellyContext context) {
+    	this.context = context;
+    }
+    
+    /** 
+     * Sets the root context
+     */
+    public void setRootContext(URL rootContext) {
+        this.rootContext = rootContext;
+    }
+
+    /** 
+     * Sets the script URL to use as an absolute URL or a relative file name
+     */
+    public void setScript(String script) throws MalformedURLException {
+        setUrl(resolveURL(script));
+    }
+
+    /** 
+     * Sets the script URL to use 
+     */
+    public void setUrl(URL url) {
+        this.url = url;
+    }
+
+    /**
+     * When set to true, the XML parser will attempt to validate
+     * the Jelly XML before converting it into a Script.
+     * 
+     * @param validate whether or not to validate
+     */
+    public void setValidateXML(boolean validate) {
+        this.validateXML = validate;
+    }
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/JellyContext.java b/core/src/main/java/org/apache/commons/jelly/JellyContext.java
index 704ab54d..5a308e3e 100644
--- a/core/src/main/java/org/apache/commons/jelly/JellyContext.java
+++ b/core/src/main/java/org/apache/commons/jelly/JellyContext.java
@@ -112,25 +112,6 @@ public class JellyContext {
         init();
     }
 
-    /**
-     * Create a new context with the given rootURL
-     * @param rootURL the root URL used in resolving absolute resources i.e. those starting with '/'
-     */
-    public JellyContext(URL rootURL) {
-        this( rootURL, rootURL );
-    }
-
-    /**
-     * Create a new context with the given rootURL and currentURL
-     * @param rootURL the root URL used in resolving absolute resources i.e. those starting with '/'
-     * @param currentURL the root URL used in resolving relative resources
-     */
-    public JellyContext(URL rootURL, URL currentURL) {
-        this.rootURL = rootURL;
-        this.currentURL = currentURL;
-        init();
-    }
-
     /**
      * Create a new context with the given parent context.
      * The parent's rootURL and currentURL are set on the child, and the parent's variables are
@@ -175,36 +156,138 @@ public class JellyContext {
     }
 
     /**
-     * Initialize the context.
-     * This includes adding the context to itself under the name <code>context</code> and
-     * making the System Properties available as <code>systemScope</code>
+     * Create a new context with the given rootURL
+     * @param rootURL the root URL used in resolving absolute resources i.e. those starting with '/'
      */
-    private void init() {
-        variables.put("context", this);
+    public JellyContext(URL rootURL) {
+        this( rootURL, rootURL );
+    }
+
+    /**
+     * Create a new context with the given rootURL and currentURL
+     * @param rootURL the root URL used in resolving absolute resources i.e. those starting with '/'
+     * @param currentURL the root URL used in resolving relative resources
+     */
+    public JellyContext(URL rootURL, URL currentURL) {
+        this.rootURL = rootURL;
+        this.currentURL = currentURL;
+        init();
+    }
+
+    /** Clears variables set by Tags.
+     * @see #clearVariables()
+      */
+    public void clear() {
+        clearVariables();
+    }
+
+    /** Clears variables set by Tags (variables set while running a Jelly script)
+     * @see #clear()
+     */
+    protected void clearVariables() {
+        variables.clear();
+    }
+
+    /**
+     * Attempts to parse the script from the given InputSource using the
+     * {@link #getResource} method then returns the compiled script.
+     */
+    public Script compileScript(InputSource source) throws JellyException {
+        XMLParser parser = getXMLParser();
+        parser.setContext(this);
+
+        Script script = null;
         try {
-            variables.put("systemScope", System.getProperties());
-        } catch (SecurityException e) {
-            log.debug("security exception accessing system properties", e);
+            script = parser.parse(source);
+        } catch (IOException e) {
+            throw new JellyException(JellyContext.BAD_PARSE, e);
+        } catch (SAXException e) {
+            throw new JellyException(JellyContext.BAD_PARSE, e);
         }
+
+        return script.compile();
     }
 
     /**
-     * @return the parent context for this context
+     * Attempts to parse the script from the given uri using the
+     * {@link #getResource} method then returns the compiled script.
      */
-    public JellyContext getParent() {
-        return parent;
+    public Script compileScript(String uri) throws JellyException {
+        XMLParser parser = getXMLParser();
+        parser.setContext(this);
+        InputStream in = getResourceAsStream(uri);
+        if (in == null) {
+            throw new JellyException("Could not find Jelly script: " + uri);
+        }
+        Script script = null;
+        try {
+            script = parser.parse(in);
+        } catch (IOException e) {
+            throw new JellyException(JellyContext.BAD_PARSE, e);
+        } catch (SAXException e) {
+            throw new JellyException(JellyContext.BAD_PARSE, e);
+        }
+
+        return script.compile();
     }
 
     /**
-     * @return the scope of the given name, such as the 'parent' scope.
-     * If Jelly is used in a Servlet situation then 'request', 'session' and 'application' are other names
-     * for scopes
+     * Attempts to parse the script from the given URL using the
+     * {@link #getResource} method then returns the compiled script.
      */
-    public JellyContext getScope(String name) {
-        if ( "parent".equals( name ) ) {
-            return getParent();
+    public Script compileScript(URL url) throws JellyException {
+        XMLParser parser = getXMLParser();
+        parser.setContext(this);
+
+        Script script = null;
+        try {
+            script = parser.parse(url.toString());
+        } catch (IOException e) {
+            throw new JellyException(JellyContext.BAD_PARSE, e);
+        } catch (SAXException e) {
+            throw new JellyException(JellyContext.BAD_PARSE, e);
         }
-        return null;
+
+        return script.compile();
+    }
+
+    /**
+     * Factory method to create a new child of this context
+     */
+    protected JellyContext createChildContext() {
+        return new JellyContext(this);
+    }
+
+    // Implementation methods
+    //-------------------------------------------------------------------------
+    /**
+     * @return a new relative URL from the given root and with the addition of the
+     * extra relative URI
+     *
+     * @param rootURL is the root context from which the relative URI will be applied
+     * @param relativeURI is the relative URI (without a leading "/")
+     * @throws MalformedURLException if the URL is invalid.
+     */
+    protected URL createRelativeURL(URL rootURL, String relativeURI)
+        throws MalformedURLException {
+        URL url = rootURL;
+        if (url == null) {
+            File file = new File(System.getProperty("user.dir"));
+            url = file.toURL();
+        }
+        String urlText = url.toString() + relativeURI;
+        if ( log.isDebugEnabled() ) {
+            log.debug("Attempting to open url: " + urlText);
+        }
+        return new URL(urlText);
+    }
+
+    /**
+     * Factory method to allow JellyContext implementations to overload how an XMLParser
+     * is created - such as to overload what the default ExpressionFactory should be.
+     */
+    protected XMLParser createXMLParser() {
+        return new XMLParser(allowDtdToCallExternalEntities);
     }
 
     /**
@@ -235,214 +318,149 @@ public class JellyContext {
         return answer;
     }
 
-    /** @return the value of the given variable name */
-    public Object getVariable(String name) {
-        Object value = variables.get(name);
-        boolean definedHere = value != null || variables.containsKey(name);
-
-        if (definedHere) return value;
-
-        if ( value == null && isInherit() ) {
-            JellyContext parentContext = getParent();
-            if (parentContext != null) {
-                value = parentContext.getVariable( name );
-            }
-        }
-
-        // ### this is a hack - remove this when we have support for pluggable Scopes
-        if ( value == null ) {
-            value = getSystemProperty(name);
-        }
-
-        return value;
-    }
-
     /**
-     * Gets a system property and handle security exceptions
-     * @param name the name of the property to retrieve
-     * @return the value of the property, or null if a SecurityException occurs
+     * Gets the class loader to be used for instantiating application objects
+     * when required.  This is determined based upon the following rules:
+     * <ul>
+     * <li>The class loader set by <code>setClassLoader()</code>, if any</li>
+     * <li>The thread context class loader, if it exists and the
+     *     <code>useContextClassLoader</code> property is set to true</li>
+     * <li>The class loader used to load the XMLParser class itself.
+     * </ul>
      */
-    private Object getSystemProperty(String name) {
-        try {
-            return System.getProperty(name);
-        }
-        catch (SecurityException e) {
-            log.debug("security exception accessing system properties", e);
-        }
-        return null;
+    public ClassLoader getClassLoader() {
+        return ClassLoaderUtils.getClassLoader(classLoader, useContextClassLoader, getClass());
     }
 
     /**
-     * @return the value of the given variable name in the given variable scope
-     * @param name is the name of the variable
-     * @param scopeName is the optional scope name such as 'parent'. For servlet environments
-     * this could be 'application', 'session' or 'request'.
+     * @return the current URL context of the current script that is executing.
+     *  This URL context is used to deduce relative scripts when relative URIs are
+     *  used in calls to {@link #getResource} to process relative scripts.
      */
-    public Object getVariable(String name, String scopeName) {
-        JellyContext scope = getScope(scopeName);
-        if ( scope != null ) {
-            return scope.getVariable(name);
-        }
-        return null;
+    public URL getCurrentURL() {
+        return currentURL;
     }
 
-    /** Sets the value of the named variable */
-    public void setVariable(String name, Object value) {
-        if ( isExport() ) {
-            getParent().setVariable( name, value );
-            return;
-        }
-        if (value == null) {
-            variables.remove(name);
-        }
-        else {
-            variables.put(name, value);
+    /**
+     * Strips off the name of a script to create a new context URL
+     */
+    protected URL getJellyContextURL(InputSource source) throws MalformedURLException {
+        String text = source.getSystemId();
+        if (text != null) {
+            int idx = text.lastIndexOf('/');
+            text = text.substring(0, idx + 1);
+            return new URL(text);
+        } else {
+            return null;
         }
+        
     }
 
     /**
-     * Sets the value of the given variable name in the given variable scope
-     * @param name is the name of the variable
-     * @param scopeName is the optional scope name such as 'parent'. For servlet environments
-     *  this could be 'application', 'session' or 'request'.
-     * @param value is the value of the attribute
+     * Strips off the name of a script to create a new context URL
      */
-    public void setVariable(String name, String scopeName, Object value) {
-        JellyContext scope = getScope(scopeName);
-        if ( scope != null ) {
-            scope.setVariable(name, value);
-        }
-    }
-
-    /** Removes the given variable */
-    public void removeVariable(String name) {
-        variables.remove(name);
-    }
-
-    /**
-     * Removes the given variable in the specified scope.
-     *
-     * @param name is the name of the variable
-     * @param scopeName is the optional scope name such as 'parent'. For servlet environments
-     *  this could be 'application', 'session' or 'request'.
-     */
-    public void removeVariable(String name, String scopeName) {
-        JellyContext scope = getScope(scopeName);
-        if ( scope != null ) {
-            scope.removeVariable(name);
-        }
-    }
-
-    /**
-     * @return an Iterator over the current variable names in this
-     * context
-     */
-    public Iterator getVariableNames() {
-        return variables.keySet().iterator();
+    protected URL getJellyContextURL(URL url) throws MalformedURLException {
+        String text = url.toString();
+        int idx = text.lastIndexOf('/');
+        text = text.substring(0, idx + 1);
+        return new URL(text);
     }
 
     /**
-     * @return the Map of variables in this scope
+     * @return the parent context for this context
      */
-    public Map getVariables() {
-        return variables;
+    public JellyContext getParent() {
+        return parent;
     }
 
     /**
-     * Sets the Map of variables to use
+     * Returns a URL for the given resource from the specified path.
+     * If the uri starts with "/" then the path is taken as relative to
+     * the current context root.
+     * If the uri is a well formed URL then it is used.
+     * If the uri is a file that exists and can be read then it is used.
+     * Otherwise the uri is interpreted as relative to the current context (the
+     * location of the current script).
      */
-    public void setVariables(Map variables) {
-        // I have seen this fail when the passed Map contains a key, value
-        // pair where the value is null
-        for (Iterator iter = variables.entrySet().iterator(); iter.hasNext();) {
-            Map.Entry element = (Map.Entry) iter.next();
-            if (element.getValue() != null) {
-                this.variables.put(element.getKey(), element.getValue());
+    public URL getResource(String uri) throws MalformedURLException {
+        if (uri.startsWith("/")) {
+            // append this uri to the context root
+            return createRelativeURL(rootURL, uri.substring(1));
+        }
+        else {
+            try {
+                return new URL(uri);
+            }
+            catch (MalformedURLException e) {
+                // lets try find a relative resource
+                try {
+                    return createRelativeURL(currentURL, uri);
+                } catch (MalformedURLException e2) {
+                    throw e;
+                }
             }
         }
-        //this.variables.putAll( variables );
-    }
-
-    /**
-     * A factory method to create a new child context of the
-     * current context.
-     */
-    public JellyContext newJellyContext(Map newVariables) {
-        // XXXX: should allow this new context to
-        // XXXX: inherit parent contexts?
-        // XXXX: Or at least publish the parent scope
-        // XXXX: as a Map in this new variable scope?
-        newVariables.put("parentScope", variables);
-        JellyContext answer = createChildContext();
-        answer.setVariables(newVariables);
-        return answer;
     }
 
     /**
-     * A factory method to create a new child context of the
-     * current context.
+     * Attempts to open an InputStream to the given resource at the specified path.
+     * If the uri starts with "/" then the path is taken as relative to
+     * the current context root. If the uri is a well formed URL then it
+     * is used. Otherwise the uri is interpreted as relative to the current
+     * context (the location of the current script).
+     *
+     * @return null if this resource could not be loaded, otherwise the resources
+     *  input stream is returned.
      */
-    public JellyContext newJellyContext() {
-        return createChildContext();
-    }
-    
-    /** Clears variables set by Tags.
-     * @see #clearVariables()
-      */
-    public void clear() {
-        clearVariables();
+    public InputStream getResourceAsStream(String uri) {
+        try {
+            URL url = getResource(uri);
+            return url.openStream();
+        }
+        catch (Exception e) {
+            if (log.isTraceEnabled()) {
+                log.trace(
+                    "Caught exception attempting to open: " + uri + ". Exception: " + e,
+                    e);
+            }
+            return null;
+        }
     }
     
-    /** Clears variables set by Tags (variables set while running a Jelly script)
-     * @see #clear()
+    /**
+     * @return the current root context URL from which all absolute resource URIs
+     *  will be relative to. For example in a web application the root URL will
+     *  map to the web directory which contains the WEB-INF directory.
      */
-    protected void clearVariables() {
-        variables.clear();
+    public URL getRootURL() {
+        return rootURL;
     }
     
-    /** Registers the given tag library against the given namespace URI.
-     * This should be called before the parser is used.
+    /**
+     * @return the scope of the given name, such as the 'parent' scope.
+     * If Jelly is used in a Servlet situation then 'request', 'session' and 'application' are other names
+     * for scopes
      */
-    public void registerTagLibrary(String namespaceURI, TagLibrary taglib) {
-        if (log.isDebugEnabled()) {
-            log.debug("Registering tag library to: " + namespaceURI + " taglib: " + taglib);
-        }
-        taglibs.put(namespaceURI, taglib);
-
-        if (isExportLibraries() && parent != null) {
-            parent.registerTagLibrary( namespaceURI, taglib );
+    public JellyContext getScope(String name) {
+        if ( "parent".equals( name ) ) {
+            return getParent();
         }
+        return null;
     }
-
-    /** Registers the given tag library class name against the given namespace URI.
-     * The class will be loaded via the given ClassLoader
-     * This should be called before the parser is used.
+    
+    /**
+     * Gets a system property and handle security exceptions
+     * @param name the name of the property to retrieve
+     * @return the value of the property, or null if a SecurityException occurs
      */
-    public void registerTagLibrary(
-        String namespaceURI,
-        String className) {
-
-        if (log.isDebugEnabled()) {
-            log.debug("Registering tag library to: " + namespaceURI + " taglib: " + className);
-        }
-        taglibs.put(namespaceURI, className);
-
-        if (isExportLibraries() && parent != null) {
-            parent.registerTagLibrary( namespaceURI, className );
-        }
-    }
-
-    public boolean isTagLibraryRegistered(String namespaceURI) {
-        boolean answer = taglibs.containsKey( namespaceURI );
-        if (answer) {
-            return true;
-        }
-        else if ( parent != null ) {
-            return parent.isTagLibraryRegistered(namespaceURI);
+    private Object getSystemProperty(String name) {
+        try {
+            return System.getProperty(name);
         }
-        else {
-            return false;
+        catch (SecurityException e) {
+            log.debug("security exception accessing system properties", e);
         }
+        return null;
     }
 
     /**
@@ -498,67 +516,62 @@ public class JellyContext {
     }
 
     /**
-     * Attempts to parse the script from the given uri using the
-     * {@link #getResource} method then returns the compiled script.
+     * Gets the boolean as to whether the context classloader should be used.
      */
-    public Script compileScript(String uri) throws JellyException {
-        XMLParser parser = getXMLParser();
-        parser.setContext(this);
-        InputStream in = getResourceAsStream(uri);
-        if (in == null) {
-            throw new JellyException("Could not find Jelly script: " + uri);
+    public boolean getUseContextClassLoader() {
+        return useContextClassLoader;
+    }
+
+    /** @return the value of the given variable name */
+    public Object getVariable(String name) {
+        Object value = variables.get(name);
+        boolean definedHere = value != null || variables.containsKey(name);
+
+        if (definedHere) return value;
+
+        if ( value == null && isInherit() ) {
+            JellyContext parentContext = getParent();
+            if (parentContext != null) {
+                value = parentContext.getVariable( name );
+            }
         }
-        Script script = null;
-        try {
-            script = parser.parse(in);
-        } catch (IOException e) {
-            throw new JellyException(JellyContext.BAD_PARSE, e);
-        } catch (SAXException e) {
-            throw new JellyException(JellyContext.BAD_PARSE, e);
+
+        // ### this is a hack - remove this when we have support for pluggable Scopes
+        if ( value == null ) {
+            value = getSystemProperty(name);
         }
 
-        return script.compile();
+        return value;
     }
 
     /**
-     * Attempts to parse the script from the given URL using the
-     * {@link #getResource} method then returns the compiled script.
+     * @return the value of the given variable name in the given variable scope
+     * @param name is the name of the variable
+     * @param scopeName is the optional scope name such as 'parent'. For servlet environments
+     * this could be 'application', 'session' or 'request'.
      */
-    public Script compileScript(URL url) throws JellyException {
-        XMLParser parser = getXMLParser();
-        parser.setContext(this);
-
-        Script script = null;
-        try {
-            script = parser.parse(url.toString());
-        } catch (IOException e) {
-            throw new JellyException(JellyContext.BAD_PARSE, e);
-        } catch (SAXException e) {
-            throw new JellyException(JellyContext.BAD_PARSE, e);
+    public Object getVariable(String name, String scopeName) {
+        JellyContext scope = getScope(scopeName);
+        if ( scope != null ) {
+            return scope.getVariable(name);
         }
+        return null;
+    }
 
-        return script.compile();
+    /**
+     * @return an Iterator over the current variable names in this
+     * context
+     */
+    public Iterator getVariableNames() {
+        return variables.keySet().iterator();
     }
 
     /**
-     * Attempts to parse the script from the given InputSource using the
-     * {@link #getResource} method then returns the compiled script.
+     * @return the Map of variables in this scope
      */
-    public Script compileScript(InputSource source) throws JellyException {
-        XMLParser parser = getXMLParser();
-        parser.setContext(this);
-
-        Script script = null;
-        try {
-            script = parser.parse(source);
-        } catch (IOException e) {
-            throw new JellyException(JellyContext.BAD_PARSE, e);
-        } catch (SAXException e) {
-            throw new JellyException(JellyContext.BAD_PARSE, e);
-        }
-
-        return script.compile();
-    }
+    public Map getVariables() {
+        return variables;
+    }
 
     /**
      * @return a thread pooled XMLParser to avoid the startup overhead
@@ -570,88 +583,170 @@ public class JellyContext {
     }
 
     /**
-     * Factory method to allow JellyContext implementations to overload how an XMLParser
-     * is created - such as to overload what the default ExpressionFactory should be.
+     * Initialize the context.
+     * This includes adding the context to itself under the name <code>context</code> and
+     * making the System Properties available as <code>systemScope</code>
      */
-    protected XMLParser createXMLParser() {
-        return new XMLParser(allowDtdToCallExternalEntities);
+    private void init() {
+        variables.put("context", this);
+        try {
+            variables.put("systemScope", System.getProperties());
+        } catch (SecurityException e) {
+            log.debug("security exception accessing system properties", e);
+        }
     }
 
     /**
-     * Parses the script from the given File then compiles it and runs it.
-     *
-     * @return the new child context that was used to run the script
+     * @return whether we should allow our doctype definitions to call out to external entities.
      */
-    public JellyContext runScript(File file, XMLOutput output) throws JellyException {
-        try {
-            return runScript(file.toURL(), output, JellyContext.DEFAULT_EXPORT,
-                JellyContext.DEFAULT_INHERIT);
-        } catch (MalformedURLException e) {
-            throw new JellyException(e.toString());
-        }
+    public boolean isAllowDtdToCallExternalEntities() {
+        return this.allowDtdToCallExternalEntities;
     }
 
     /**
-     * Parses the script from the given URL then compiles it and runs it.
+     * Returns whether caching of Tag instances, per thread, is enabled.
+     * Caching Tags can boost performance, on some JVMs, by reducing the cost of
+     * object construction when running Jelly inside a multi-threaded application server
+     * such as a Servlet engine.
      *
-     * @return the new child context that was used to run the script
+     * @return whether caching of Tag instances is enabled.
      */
-    public JellyContext runScript(URL url, XMLOutput output) throws JellyException {
-        return runScript(url, output, JellyContext.DEFAULT_EXPORT,
-            JellyContext.DEFAULT_INHERIT);
+    public boolean isCacheTags() {
+        return cacheTags;
     }
 
     /**
-     * Parses the script from the given InputSource then compiles it and runs it.
-     *
-     * @return the new child context that was used to run the script
+     * @return whether we should export variable definitions to our parent context
      */
-    public JellyContext runScript(InputSource source, XMLOutput output) throws JellyException {
-        return runScript(source, output, JellyContext.DEFAULT_EXPORT,
-            JellyContext.DEFAULT_INHERIT);
+    public boolean isExport() {
+        return this.export;
     }
 
     /**
-     * Parses the script from the given uri using the
-     * JellyContext.getResource() API then compiles it and runs it.
-     *
-     * @return the new child context that was used to run the script
+     * Returns whether we export tag libraries to our parents context
+     * @return boolean
      */
-    public JellyContext runScript(String uri, XMLOutput output) throws JellyException {
-        URL url = null;
-        try {
-            url = getResource(uri);
-        } catch (MalformedURLException e) {
-            throw new JellyException(e.toString());
+    public boolean isExportLibraries() {
+        return exportLibraries;
+    }
+
+    /**
+     * @return whether we should inherit variables from our parent context
+     */
+    public boolean isInherit() {
+        return this.inherit;
+    }
+
+    /**
+	 * @return the suppressExpressionExceptions
+	 * @deprecated after v1.1, exceptions will never be suppressed
+	 */
+	public boolean isSuppressExpressionExceptions() {
+		return suppressExpressionExceptions;
+	}
+
+    public boolean isTagLibraryRegistered(String namespaceURI) {
+        boolean answer = taglibs.containsKey( namespaceURI );
+        if (answer) {
+            return true;
+        }
+        else if ( parent != null ) {
+            return parent.isTagLibraryRegistered(namespaceURI);
+        }
+        else {
+            return false;
         }
+    }
 
-        if (url == null) {
-            throw new JellyException("Could not find Jelly script: " + url);
+    /**
+     * A factory method to create a new child context of the
+     * current context.
+     */
+    public JellyContext newJellyContext() {
+        return createChildContext();
+    }
+
+    /**
+     * A factory method to create a new child context of the
+     * current context.
+     */
+    public JellyContext newJellyContext(Map newVariables) {
+        // XXXX: should allow this new context to
+        // XXXX: inherit parent contexts?
+        // XXXX: Or at least publish the parent scope
+        // XXXX: as a Map in this new variable scope?
+        newVariables.put("parentScope", variables);
+        JellyContext answer = createChildContext();
+        answer.setVariables(newVariables);
+        return answer;
+    }
+
+    /** Registers the given tag library class name against the given namespace URI.
+     * The class will be loaded via the given ClassLoader
+     * This should be called before the parser is used.
+     */
+    public void registerTagLibrary(
+        String namespaceURI,
+        String className) {
+
+        if (log.isDebugEnabled()) {
+            log.debug("Registering tag library to: " + namespaceURI + " taglib: " + className);
         }
-        return runScript(url, output, JellyContext.DEFAULT_EXPORT,
-            JellyContext.DEFAULT_INHERIT);
+        taglibs.put(namespaceURI, className);
+
+        if (isExportLibraries() && parent != null) {
+            parent.registerTagLibrary( namespaceURI, className );
+        }
+    }
+
+    // Properties
+    //-------------------------------------------------------------------------
+
+    /** Registers the given tag library against the given namespace URI.
+     * This should be called before the parser is used.
+     */
+    public void registerTagLibrary(String namespaceURI, TagLibrary taglib) {
+        if (log.isDebugEnabled()) {
+            log.debug("Registering tag library to: " + namespaceURI + " taglib: " + taglib);
+        }
+        taglibs.put(namespaceURI, taglib);
+
+        if (isExportLibraries() && parent != null) {
+            parent.registerTagLibrary( namespaceURI, taglib );
+        }
+    }
+
+    /** Removes the given variable */
+    public void removeVariable(String name) {
+        variables.remove(name);
     }
 
     /**
-     * Parses the script from the given uri using the
-     * JellyContext.getResource() API then compiles it and runs it.
+     * Removes the given variable in the specified scope.
+     *
+     * @param name is the name of the variable
+     * @param scopeName is the optional scope name such as 'parent'. For servlet environments
+     *  this could be 'application', 'session' or 'request'.
+     */
+    public void removeVariable(String name, String scopeName) {
+        JellyContext scope = getScope(scopeName);
+        if ( scope != null ) {
+            scope.removeVariable(name);
+        }
+    }
+
+    /**
+     * Parses the script from the given File then compiles it and runs it.
      *
      * @return the new child context that was used to run the script
      */
-    public JellyContext runScript(String uri, XMLOutput output,
-                          boolean export, boolean inherit) throws JellyException {
-        URL url = null;
+    public JellyContext runScript(File file, XMLOutput output) throws JellyException {
         try {
-            url = getResource(uri);
+            return runScript(file.toURL(), output, JellyContext.DEFAULT_EXPORT,
+                JellyContext.DEFAULT_INHERIT);
         } catch (MalformedURLException e) {
             throw new JellyException(e.toString());
         }
-
-        if (url == null) {
-            throw new JellyException("Could not find Jelly script: " + url);
-        }
-
-        return runScript(url, output, export, inherit);
     }
 
     /**
@@ -669,13 +764,13 @@ public class JellyContext {
     }
 
     /**
-     * Parses the script from the given URL then compiles it and runs it.
+     * Parses the script from the given InputSource then compiles it and runs it.
      *
      * @return the new child context that was used to run the script
      */
-    public JellyContext runScript(URL url, XMLOutput output,
-                          boolean export, boolean inherit) throws JellyException {
-        return runScript(new InputSource(url.toString()), output, export, inherit);
+    public JellyContext runScript(InputSource source, XMLOutput output) throws JellyException {
+        return runScript(source, output, JellyContext.DEFAULT_EXPORT,
+            JellyContext.DEFAULT_INHERIT);
     }
 
     /**
@@ -710,171 +805,73 @@ public class JellyContext {
             log.debug( "root context URL: " + newJellyContext.rootURL );
             log.debug( "current context URL: " + newJellyContext.currentURL );
         }
-
-        script.run(newJellyContext, output);
-
-        return newJellyContext;
-    }
-
-    /**
-     * Returns a URL for the given resource from the specified path.
-     * If the uri starts with "/" then the path is taken as relative to
-     * the current context root.
-     * If the uri is a well formed URL then it is used.
-     * If the uri is a file that exists and can be read then it is used.
-     * Otherwise the uri is interpreted as relative to the current context (the
-     * location of the current script).
-     */
-    public URL getResource(String uri) throws MalformedURLException {
-        if (uri.startsWith("/")) {
-            // append this uri to the context root
-            return createRelativeURL(rootURL, uri.substring(1));
-        }
-        else {
-            try {
-                return new URL(uri);
-            }
-            catch (MalformedURLException e) {
-                // lets try find a relative resource
-                try {
-                    return createRelativeURL(currentURL, uri);
-                } catch (MalformedURLException e2) {
-                    throw e;
-                }
-            }
-        }
-    }
-
-    /**
-     * Attempts to open an InputStream to the given resource at the specified path.
-     * If the uri starts with "/" then the path is taken as relative to
-     * the current context root. If the uri is a well formed URL then it
-     * is used. Otherwise the uri is interpreted as relative to the current
-     * context (the location of the current script).
-     *
-     * @return null if this resource could not be loaded, otherwise the resources
-     *  input stream is returned.
-     */
-    public InputStream getResourceAsStream(String uri) {
-        try {
-            URL url = getResource(uri);
-            return url.openStream();
-        }
-        catch (Exception e) {
-            if (log.isTraceEnabled()) {
-                log.trace(
-                    "Caught exception attempting to open: " + uri + ". Exception: " + e,
-                    e);
-            }
-            return null;
-        }
-    }
-
-    // Properties
-    //-------------------------------------------------------------------------
-
-    /**
-     * @return the current root context URL from which all absolute resource URIs
-     *  will be relative to. For example in a web application the root URL will
-     *  map to the web directory which contains the WEB-INF directory.
-     */
-    public URL getRootURL() {
-        return rootURL;
-    }
-
-    /**
-     * Sets the current root context URL from which all absolute resource URIs
-     *  will be relative to. For example in a web application the root URL will
-     *  map to the web directory which contains the WEB-INF directory.
-     */
-    public void setRootURL(URL rootURL) {
-        this.rootURL = rootURL;
-    }
-
-    /**
-     * @return the current URL context of the current script that is executing.
-     *  This URL context is used to deduce relative scripts when relative URIs are
-     *  used in calls to {@link #getResource} to process relative scripts.
-     */
-    public URL getCurrentURL() {
-        return currentURL;
-    }
-
-    /**
-     * Sets the current URL context of the current script that is executing.
-     *  This URL context is used to deduce relative scripts when relative URIs are
-     *  used in calls to {@link #getResource} to process relative scripts.
-     */
-    public void setCurrentURL(URL currentURL) {
-        this.currentURL = currentURL;
-    }
-
-    /**
-     * Returns whether caching of Tag instances, per thread, is enabled.
-     * Caching Tags can boost performance, on some JVMs, by reducing the cost of
-     * object construction when running Jelly inside a multi-threaded application server
-     * such as a Servlet engine.
-     *
-     * @return whether caching of Tag instances is enabled.
-     */
-    public boolean isCacheTags() {
-        return cacheTags;
+
+        script.run(newJellyContext, output);
+
+        return newJellyContext;
     }
 
     /**
-     * Sets whether caching of Tag instances, per thread, is enabled.
-     * Caching Tags can boost performance, on some JVMs, by reducing the cost of
-     * object construction when running Jelly inside a multi-threaded application server
-     * such as a Servlet engine.
+     * Parses the script from the given uri using the
+     * JellyContext.getResource() API then compiles it and runs it.
      *
-     * @param cacheTags Whether caching should be enabled or disabled.
+     * @return the new child context that was used to run the script
      */
-    public void setCacheTags(boolean cacheTags) {
-        this.cacheTags = cacheTags;
-    }
+    public JellyContext runScript(String uri, XMLOutput output) throws JellyException {
+        URL url = null;
+        try {
+            url = getResource(uri);
+        } catch (MalformedURLException e) {
+            throw new JellyException(e.toString());
+        }
 
-    /**
-     * Returns whether we export tag libraries to our parents context
-     * @return boolean
-     */
-    public boolean isExportLibraries() {
-        return exportLibraries;
+        if (url == null) {
+            throw new JellyException("Could not find Jelly script: " + url);
+        }
+        return runScript(url, output, JellyContext.DEFAULT_EXPORT,
+            JellyContext.DEFAULT_INHERIT);
     }
 
     /**
-     * Sets whether we export tag libraries to our parents context
-     * @param exportLibraries The exportLibraries to set
+     * Parses the script from the given uri using the
+     * JellyContext.getResource() API then compiles it and runs it.
+     *
+     * @return the new child context that was used to run the script
      */
-    public void setExportLibraries(boolean exportLibraries) {
-        this.exportLibraries = exportLibraries;
-    }
+    public JellyContext runScript(String uri, XMLOutput output,
+                          boolean export, boolean inherit) throws JellyException {
+        URL url = null;
+        try {
+            url = getResource(uri);
+        } catch (MalformedURLException e) {
+            throw new JellyException(e.toString());
+        }
 
-    /**
-     * Sets whether we should export variable definitions to our parent context
-     */
-    public void setExport(boolean export) {
-        this.export = export;
-    }
+        if (url == null) {
+            throw new JellyException("Could not find Jelly script: " + url);
+        }
 
-    /**
-     * @return whether we should export variable definitions to our parent context
-     */
-    public boolean isExport() {
-        return this.export;
+        return runScript(url, output, export, inherit);
     }
 
     /**
-     * Sets whether we should inherit variables from our parent context
+     * Parses the script from the given URL then compiles it and runs it.
+     *
+     * @return the new child context that was used to run the script
      */
-    public void setInherit(boolean inherit) {
-        this.inherit = inherit;
+    public JellyContext runScript(URL url, XMLOutput output) throws JellyException {
+        return runScript(url, output, JellyContext.DEFAULT_EXPORT,
+            JellyContext.DEFAULT_INHERIT);
     }
 
     /**
-     * @return whether we should inherit variables from our parent context
+     * Parses the script from the given URL then compiles it and runs it.
+     *
+     * @return the new child context that was used to run the script
      */
-    public boolean isInherit() {
-        return this.inherit;
+    public JellyContext runScript(URL url, XMLOutput output,
+                          boolean export, boolean inherit) throws JellyException {
+        return runScript(new InputSource(url.toString()), output, export, inherit);
     }
 
     /**
@@ -885,24 +882,15 @@ public class JellyContext {
     }
 
     /**
-     * @return whether we should allow our doctype definitions to call out to external entities.
-     */
-    public boolean isAllowDtdToCallExternalEntities() {
-        return this.allowDtdToCallExternalEntities;
-    }
-
-    /**
-     * Gets the class loader to be used for instantiating application objects
-     * when required.  This is determined based upon the following rules:
-     * <ul>
-     * <li>The class loader set by <code>setClassLoader()</code>, if any</li>
-     * <li>The thread context class loader, if it exists and the
-     *     <code>useContextClassLoader</code> property is set to true</li>
-     * <li>The class loader used to load the XMLParser class itself.
-     * </ul>
+     * Sets whether caching of Tag instances, per thread, is enabled.
+     * Caching Tags can boost performance, on some JVMs, by reducing the cost of
+     * object construction when running Jelly inside a multi-threaded application server
+     * such as a Servlet engine.
+     *
+     * @param cacheTags Whether caching should be enabled or disabled.
      */
-    public ClassLoader getClassLoader() {
-        return ClassLoaderUtils.getClassLoader(classLoader, useContextClassLoader, getClass());
+    public void setCacheTags(boolean cacheTags) {
+        this.cacheTags = cacheTags;
     }
 
     /**
@@ -917,79 +905,34 @@ public class JellyContext {
     }
 
     /**
-     * Gets the boolean as to whether the context classloader should be used.
-     */
-    public boolean getUseContextClassLoader() {
-        return useContextClassLoader;
-    }
-
-    /**
-     * Determine whether to use the Context ClassLoader (the one found by
-     * calling <code>Thread.currentThread().getContextClassLoader()</code>)
-     * to resolve/load classes.  If not
-     * using Context ClassLoader, then the class-loading defaults to
-     * using the calling-class' ClassLoader.
-     *
-     * @param use determines whether to use JellyContext ClassLoader.
-     */
-    public void setUseContextClassLoader(boolean use) {
-        useContextClassLoader = use;
-    }
-
-    // Implementation methods
-    //-------------------------------------------------------------------------
-    /**
-     * @return a new relative URL from the given root and with the addition of the
-     * extra relative URI
-     *
-     * @param rootURL is the root context from which the relative URI will be applied
-     * @param relativeURI is the relative URI (without a leading "/")
-     * @throws MalformedURLException if the URL is invalid.
+     * Sets the current URL context of the current script that is executing.
+     *  This URL context is used to deduce relative scripts when relative URIs are
+     *  used in calls to {@link #getResource} to process relative scripts.
      */
-    protected URL createRelativeURL(URL rootURL, String relativeURI)
-        throws MalformedURLException {
-        URL url = rootURL;
-        if (url == null) {
-            File file = new File(System.getProperty("user.dir"));
-            url = file.toURL();
-        }
-        String urlText = url.toString() + relativeURI;
-        if ( log.isDebugEnabled() ) {
-            log.debug("Attempting to open url: " + urlText);
-        }
-        return new URL(urlText);
+    public void setCurrentURL(URL currentURL) {
+        this.currentURL = currentURL;
     }
 
     /**
-     * Strips off the name of a script to create a new context URL
+     * Sets whether we should export variable definitions to our parent context
      */
-    protected URL getJellyContextURL(URL url) throws MalformedURLException {
-        String text = url.toString();
-        int idx = text.lastIndexOf('/');
-        text = text.substring(0, idx + 1);
-        return new URL(text);
+    public void setExport(boolean export) {
+        this.export = export;
     }
 
     /**
-     * Strips off the name of a script to create a new context URL
+     * Sets whether we export tag libraries to our parents context
+     * @param exportLibraries The exportLibraries to set
      */
-    protected URL getJellyContextURL(InputSource source) throws MalformedURLException {
-        String text = source.getSystemId();
-        if (text != null) {
-            int idx = text.lastIndexOf('/');
-            text = text.substring(0, idx + 1);
-            return new URL(text);
-        } else {
-            return null;
-        }
-        
+    public void setExportLibraries(boolean exportLibraries) {
+        this.exportLibraries = exportLibraries;
     }
 
     /**
-     * Factory method to create a new child of this context
+     * Sets whether we should inherit variables from our parent context
      */
-    protected JellyContext createChildContext() {
-        return new JellyContext(this);
+    public void setInherit(boolean inherit) {
+        this.inherit = inherit;
     }
 
     /**
@@ -1019,15 +962,16 @@ public class JellyContext {
 
     }
 
-	/**
-	 * @return the suppressExpressionExceptions
-	 * @deprecated after v1.1, exceptions will never be suppressed
-	 */
-	public boolean isSuppressExpressionExceptions() {
-		return suppressExpressionExceptions;
-	}
+    /**
+     * Sets the current root context URL from which all absolute resource URIs
+     *  will be relative to. For example in a web application the root URL will
+     *  map to the web directory which contains the WEB-INF directory.
+     */
+    public void setRootURL(URL rootURL) {
+        this.rootURL = rootURL;
+    }
 
-	/**
+    /**
 	 * @param suppressExpressionExceptions the suppressExpressionExceptions to set
 	 * @deprecated after v1.1, exceptions will never be suppressed
 	 */
@@ -1035,4 +979,60 @@ public class JellyContext {
 		this.suppressExpressionExceptions = suppressExpressionExceptions;
 	}
 
+    /**
+     * Determine whether to use the Context ClassLoader (the one found by
+     * calling <code>Thread.currentThread().getContextClassLoader()</code>)
+     * to resolve/load classes.  If not
+     * using Context ClassLoader, then the class-loading defaults to
+     * using the calling-class' ClassLoader.
+     *
+     * @param use determines whether to use JellyContext ClassLoader.
+     */
+    public void setUseContextClassLoader(boolean use) {
+        useContextClassLoader = use;
+    }
+
+    /** Sets the value of the named variable */
+    public void setVariable(String name, Object value) {
+        if ( isExport() ) {
+            getParent().setVariable( name, value );
+            return;
+        }
+        if (value == null) {
+            variables.remove(name);
+        }
+        else {
+            variables.put(name, value);
+        }
+    }
+
+	/**
+     * Sets the value of the given variable name in the given variable scope
+     * @param name is the name of the variable
+     * @param scopeName is the optional scope name such as 'parent'. For servlet environments
+     *  this could be 'application', 'session' or 'request'.
+     * @param value is the value of the attribute
+     */
+    public void setVariable(String name, String scopeName, Object value) {
+        JellyContext scope = getScope(scopeName);
+        if ( scope != null ) {
+            scope.setVariable(name, value);
+        }
+    }
+
+	/**
+     * Sets the Map of variables to use
+     */
+    public void setVariables(Map variables) {
+        // I have seen this fail when the passed Map contains a key, value
+        // pair where the value is null
+        for (Iterator iter = variables.entrySet().iterator(); iter.hasNext();) {
+            Map.Entry element = (Map.Entry) iter.next();
+            if (element.getValue() != null) {
+                this.variables.put(element.getKey(), element.getValue());
+            }
+        }
+        //this.variables.putAll( variables );
+    }
+
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/JellyException.java b/core/src/main/java/org/apache/commons/jelly/JellyException.java
index c5df5179..76272d76 100644
--- a/core/src/main/java/org/apache/commons/jelly/JellyException.java
+++ b/core/src/main/java/org/apache/commons/jelly/JellyException.java
@@ -48,20 +48,19 @@ public class JellyException extends Exception implements LocationAware {
         super(message);
     }
 
-    public JellyException(String message, Throwable cause) {
-        super(message);
-        this.cause = cause;
+    public JellyException(String reason, String fileName, String elementName, int columnNumber, int lineNumber) {
+        super(reason);
+        this.fileName = fileName;
+        this.elementName = elementName;
+        this.columnNumber = columnNumber;
+        this.lineNumber = lineNumber;
     }
     
-    public JellyException(Throwable cause) {
-        super(cause.getLocalizedMessage());
+    public JellyException(String message, Throwable cause) {
+        super(message);
         this.cause = cause;
     }
     
-    public JellyException(Throwable cause, String fileName, String elementName, int columnNumber, int lineNumber) {
-        this(cause.getLocalizedMessage(), cause, fileName, elementName, columnNumber, lineNumber);
-    }
-    
     public JellyException(String reason, Throwable cause, String fileName, String elementName, int columnNumber, int lineNumber) {
         super( (reason == null ? cause.getClass().getName() : reason) );
         this.cause = cause;
@@ -71,12 +70,13 @@ public class JellyException extends Exception implements LocationAware {
         this.lineNumber = lineNumber;
     }
     
-    public JellyException(String reason, String fileName, String elementName, int columnNumber, int lineNumber) {
-        super(reason);
-        this.fileName = fileName;
-        this.elementName = elementName;
-        this.columnNumber = columnNumber;
-        this.lineNumber = lineNumber;
+    public JellyException(Throwable cause) {
+        super(cause.getLocalizedMessage());
+        this.cause = cause;
+    }
+    
+    public JellyException(Throwable cause, String fileName, String elementName, int columnNumber, int lineNumber) {
+        this(cause.getLocalizedMessage(), cause, fileName, elementName, columnNumber, lineNumber);
     }
     
     @Override
@@ -85,22 +85,6 @@ public class JellyException extends Exception implements LocationAware {
     }
 
     
-    /** 
-     * @return the line number of the tag 
-     */
-    @Override
-    public int getLineNumber() {
-        return lineNumber;
-    }
-    
-    /** 
-     * Sets the line number of the tag 
-     */
-    @Override
-    public void setLineNumber(int lineNumber) {
-        this.lineNumber = lineNumber;
-    }
-
     /** 
      * @return the column number of the tag 
      */
@@ -110,11 +94,11 @@ public class JellyException extends Exception implements LocationAware {
     }
     
     /** 
-     * Sets the column number of the tag 
+     * @return the element name which caused the problem
      */
     @Override
-    public void setColumnNumber(int columnNumber) {
-        this.columnNumber = columnNumber;
+    public String getElementName() {
+        return elementName;
     }
 
     /** 
@@ -124,33 +108,15 @@ public class JellyException extends Exception implements LocationAware {
     public String getFileName() {
         return fileName;
     }
-
-    /** 
-     * Sets the Jelly file which caused the problem 
-     */
-    @Override
-    public void setFileName(String fileName) {
-        this.fileName = fileName;
-    }
     
-
     /** 
-     * @return the element name which caused the problem
+     * @return the line number of the tag 
      */
     @Override
-    public String getElementName() {
-        return elementName;
+    public int getLineNumber() {
+        return lineNumber;
     }
 
-    /** 
-     * Sets the element name which caused the problem
-     */
-    @Override
-    public void setElementName(String elementName) {
-        this.elementName = elementName;
-    }
-    
-    
     @Override
     public String getMessage() {
         if (fileName == null && lineNumber == -1 && columnNumber == -1 && elementName == null) {
@@ -163,10 +129,19 @@ public class JellyException extends Exception implements LocationAware {
     public String getReason() {
         return super.getMessage();
     }
+    
 
-    // #### overload the printStackTrace methods...
     @Override
-    public void printStackTrace(PrintWriter s) { 
+    public void printStackTrace() {
+        super.printStackTrace();
+        if (cause != null) {
+            System.out.println("Root cause");
+            cause.printStackTrace();
+        }
+    }
+
+    @Override
+    public void printStackTrace(PrintStream s) {
         synchronized (s) {
             super.printStackTrace(s);
             if  (cause != null) {
@@ -175,9 +150,11 @@ public class JellyException extends Exception implements LocationAware {
             }
         }
     }
-        
+    
+    
+    // #### overload the printStackTrace methods...
     @Override
-    public void printStackTrace(PrintStream s) {
+    public void printStackTrace(PrintWriter s) { 
         synchronized (s) {
             super.printStackTrace(s);
             if  (cause != null) {
@@ -187,13 +164,36 @@ public class JellyException extends Exception implements LocationAware {
         }
     }
 
+    /** 
+     * Sets the column number of the tag 
+     */
     @Override
-    public void printStackTrace() {
-        super.printStackTrace();
-        if (cause != null) {
-            System.out.println("Root cause");
-            cause.printStackTrace();
-        }
+    public void setColumnNumber(int columnNumber) {
+        this.columnNumber = columnNumber;
+    }
+
+    /** 
+     * Sets the element name which caused the problem
+     */
+    @Override
+    public void setElementName(String elementName) {
+        this.elementName = elementName;
+    }
+        
+    /** 
+     * Sets the Jelly file which caused the problem 
+     */
+    @Override
+    public void setFileName(String fileName) {
+        this.fileName = fileName;
+    }
+
+    /** 
+     * Sets the line number of the tag 
+     */
+    @Override
+    public void setLineNumber(int lineNumber) {
+        this.lineNumber = lineNumber;
     }
 
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/JellyTagException.java b/core/src/main/java/org/apache/commons/jelly/JellyTagException.java
index 5b4ac1f9..0ada4c0d 100644
--- a/core/src/main/java/org/apache/commons/jelly/JellyTagException.java
+++ b/core/src/main/java/org/apache/commons/jelly/JellyTagException.java
@@ -32,10 +32,18 @@ public class JellyTagException extends JellyException {
         super(message);
     }
 
+    public JellyTagException(String reason, String fileName, String elementName, int columnNumber, int lineNumber) {
+        super(reason, fileName, elementName, columnNumber, lineNumber);
+    }
+
     public JellyTagException(String message, Throwable cause) {
         super(message, cause);
     }
 
+    public JellyTagException(String reason, Throwable cause, String fileName, String elementName, int columnNumber, int lineNumber) {
+        super(reason, cause, fileName, elementName, columnNumber, lineNumber);
+    }
+
     public JellyTagException(Throwable cause) {
         super(cause);
     }
@@ -44,12 +52,4 @@ public class JellyTagException extends JellyException {
         super(cause, fileName, elementName, columnNumber, lineNumber);
     }
 
-    public JellyTagException(String reason, Throwable cause, String fileName, String elementName, int columnNumber, int lineNumber) {
-        super(reason, cause, fileName, elementName, columnNumber, lineNumber);
-    }
-
-    public JellyTagException(String reason, String fileName, String elementName, int columnNumber, int lineNumber) {
-        super(reason, fileName, elementName, columnNumber, lineNumber);
-    }
-
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/LocationAware.java b/core/src/main/java/org/apache/commons/jelly/LocationAware.java
index 5fff2065..079213d6 100644
--- a/core/src/main/java/org/apache/commons/jelly/LocationAware.java
+++ b/core/src/main/java/org/apache/commons/jelly/LocationAware.java
@@ -26,42 +26,42 @@ package org.apache.commons.jelly;
 public interface LocationAware {
     
     /** 
-     * @return the line number of the tag 
+     * @return the column number of the tag 
      */
-    int getLineNumber();
+    int getColumnNumber();
     
     /** 
-     * Sets the line number of the tag 
+     * @return the element name which caused the problem
      */
-    void setLineNumber(int lineNumber);
+    String getElementName();
 
     /** 
-     * @return the column number of the tag 
+     * @return the Jelly file which caused the problem 
      */
-    int getColumnNumber();
+    String getFileName();
     
     /** 
-     * Sets the column number of the tag 
+     * @return the line number of the tag 
      */
-    void setColumnNumber(int columnNumber);
+    int getLineNumber();
 
     /** 
-     * @return the Jelly file which caused the problem 
+     * Sets the column number of the tag 
      */
-    String getFileName();
+    void setColumnNumber(int columnNumber);
     
     /** 
-     * Sets the Jelly file which caused the problem 
+     * Sets the element name which caused the problem
      */
-    void setFileName(String fileName);
+    void setElementName(String elementName);
     
     /** 
-     * @return the element name which caused the problem
+     * Sets the Jelly file which caused the problem 
      */
-    String getElementName();
+    void setFileName(String fileName);
 
     /** 
-     * Sets the element name which caused the problem
+     * Sets the line number of the tag 
      */
-    void setElementName(String elementName);
+    void setLineNumber(int lineNumber);
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/MapTagSupport.java b/core/src/main/java/org/apache/commons/jelly/MapTagSupport.java
index 6bf4940e..bcea3f8c 100644
--- a/core/src/main/java/org/apache/commons/jelly/MapTagSupport.java
+++ b/core/src/main/java/org/apache/commons/jelly/MapTagSupport.java
@@ -32,11 +32,13 @@ public abstract class MapTagSupport extends DynaTagSupport {
 
     private Map map;
 
-    /** Sets an attribute value of this tag before the tag is invoked
+    /**
+     * A Factory Method which allows derived tags to overload the Map
+     * implementation used by this tag
      */
-    @Override
-    public void setAttribute(String name, Object value) {
-        getAttributes().put(name, value);
+
+    protected Map createAttributes() {
+        return new HashMap();
     }
 
     /** 
@@ -49,13 +51,11 @@ public abstract class MapTagSupport extends DynaTagSupport {
         }
         return map;
     }
-    /**
-     * A Factory Method which allows derived tags to overload the Map
-     * implementation used by this tag
+    /** Sets an attribute value of this tag before the tag is invoked
      */
-
-    protected Map createAttributes() {
-        return new HashMap();
+    @Override
+    public void setAttribute(String name, Object value) {
+        getAttributes().put(name, value);
     }
 
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/Tag.java b/core/src/main/java/org/apache/commons/jelly/Tag.java
index 84dc3d2d..0fadda7d 100644
--- a/core/src/main/java/org/apache/commons/jelly/Tag.java
+++ b/core/src/main/java/org/apache/commons/jelly/Tag.java
@@ -25,51 +25,54 @@ package org.apache.commons.jelly;
 public interface Tag {
 
     /**
-     * Gets the parent of this tag.
+     * Evaluates this tag after all the tags properties have been initialized.
      *
-     * @return the parent of this tag.
+     * @param output output stream.
+     * @throws MissingAttributeException Thrown on error.
+     * @throws JellyTagException Thrown on error.
      */
-    public Tag getParent();
+    public void doTag(XMLOutput output) throws MissingAttributeException, JellyTagException;
 
     /**
-     * Sets the parent of this tag.
-     *
-     * @param parent the parent of this tag.
+     * @return the body of the tag.
      */
-    public void setParent(Tag parent);
+    public Script getBody();
 
     /**
-     * Gets the TagLibrary, null if this is an unrecognized tag (ie a StaticTag)
+     * Gets the context in which the tag will be run.
      *
-     * @return the TagLibrary.
+     * @return the context in which the tag will be run
      */
-    public TagLibrary getTagLib();
+    public JellyContext getContext();
 
     /**
-     * Sets the tag library.
+     * Gets the parent of this tag.
      *
-     * @param tagLibrary the tag library.
+     * @return the parent of this tag.
      */
-    public void setTagLib(TagLibrary tagLibrary);
+    public Tag getParent();
 
     /**
-     * @return the body of the tag.
+     * Gets the TagLibrary, null if this is an unrecognized tag (ie a StaticTag)
+     *
+     * @return the TagLibrary.
      */
-    public Script getBody();
+    public TagLibrary getTagLib();
 
     /**
-     * Sets the body of the tag.
+     * A helper method to invoke this tags body
      *
-     * @param body the body of the tag.
+     * @param output XML output stream.
+     * @throws JellyTagException Thrown on error.
      */
-    public void setBody(Script body);
+    public void invokeBody(XMLOutput output) throws JellyTagException;
 
     /**
-     * Gets the context in which the tag will be run.
+     * Sets the body of the tag.
      *
-     * @return the context in which the tag will be run
+     * @param body the body of the tag.
      */
-    public JellyContext getContext();
+    public void setBody(Script body);
 
     /**
      * Sets the context in which the tag will be run.
@@ -80,19 +83,16 @@ public interface Tag {
     public void setContext(JellyContext context) throws JellyTagException;
 
     /**
-     * Evaluates this tag after all the tags properties have been initialized.
+     * Sets the parent of this tag.
      *
-     * @param output output stream.
-     * @throws MissingAttributeException Thrown on error.
-     * @throws JellyTagException Thrown on error.
+     * @param parent the parent of this tag.
      */
-    public void doTag(XMLOutput output) throws MissingAttributeException, JellyTagException;
+    public void setParent(Tag parent);
 
     /**
-     * A helper method to invoke this tags body
+     * Sets the tag library.
      *
-     * @param output XML output stream.
-     * @throws JellyTagException Thrown on error.
+     * @param tagLibrary the tag library.
      */
-    public void invokeBody(XMLOutput output) throws JellyTagException;
+    public void setTagLib(TagLibrary tagLibrary);
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/TagLibrary.java b/core/src/main/java/org/apache/commons/jelly/TagLibrary.java
index 3ee4dbfd..7be2171c 100644
--- a/core/src/main/java/org/apache/commons/jelly/TagLibrary.java
+++ b/core/src/main/java/org/apache/commons/jelly/TagLibrary.java
@@ -36,10 +36,7 @@ import org.xml.sax.Attributes;
 
 public abstract class TagLibrary {
 	
-	private boolean allowUnknownTags = true;
-    private Map tags = new HashMap();
-
-    static {
+	static {
 
         // register standard converters
 
@@ -60,6 +57,9 @@ public abstract class TagLibrary {
             File.class
         );
     }
+    private boolean allowUnknownTags = true;
+
+    private Map tags = new HashMap();
 
     /**
 	 * Default Constructor
@@ -76,19 +76,24 @@ public abstract class TagLibrary {
 		this.allowUnknownTags = allowUnknownTags;
 	}
 
-	/** Creates a new script to execute the given tag name and attributes */
-    public TagScript createTagScript(String name, Attributes attributes)
+	/** Allows taglibs to use their own expression evaluation mechanism */
+    public Expression createExpression(
+        ExpressionFactory factory,
+        TagScript tagScript,
+        String attributeName,
+        String attributeValue)
         throws JellyException {
 
-        Object value = tags.get(name);
-        if (value instanceof Class) {
-            Class type = (Class) value;
-            return TagScript.newInstance(type);
+        ExpressionFactory myFactory = getExpressionFactory();
+        if (myFactory == null) {
+            myFactory = factory;
         }
-        else if (value instanceof TagFactory) {
-            return new TagScript( (TagFactory) value );
+        if (myFactory != null) {
+            return CompositeExpression.parse(attributeValue, myFactory);
         }
-        return null;
+
+        // will use a constant expression instead
+        return new ConstantExpression(attributeValue);
     }
 
     /** Creates a new Tag for the given tag name and attributes */
@@ -111,43 +116,24 @@ public abstract class TagLibrary {
     	return null;
     }
 
-    /** Allows taglibs to use their own expression evaluation mechanism */
-    public Expression createExpression(
-        ExpressionFactory factory,
-        TagScript tagScript,
-        String attributeName,
-        String attributeValue)
+    /** Creates a new script to execute the given tag name and attributes */
+    public TagScript createTagScript(String name, Attributes attributes)
         throws JellyException {
 
-        ExpressionFactory myFactory = getExpressionFactory();
-        if (myFactory == null) {
-            myFactory = factory;
+        Object value = tags.get(name);
+        if (value instanceof Class) {
+            Class type = (Class) value;
+            return TagScript.newInstance(type);
         }
-        if (myFactory != null) {
-            return CompositeExpression.parse(attributeValue, myFactory);
+        else if (value instanceof TagFactory) {
+            return new TagScript( (TagFactory) value );
         }
-
-        // will use a constant expression instead
-        return new ConstantExpression(attributeValue);
+        return null;
     }
 
     // Implementation methods
     //-------------------------------------------------------------------------
 
-    /**
-     * Registers a tag implementation Class for a given tag name
-     */
-    protected void registerTag(String name, Class type) {
-        tags.put(name, type);
-    }
-
-    /**
-     * Registers a tag factory for a given tag name
-     */
-    protected void registerTagFactory(String name, TagFactory tagFactory) {
-        tags.put(name, tagFactory);
-    }
-
     /** Allows derived tag libraries to use their own factory */
     public ExpressionFactory getExpressionFactory() {
         return null;
@@ -157,13 +143,27 @@ public abstract class TagLibrary {
         return tags;
     }
 
-	/**
+    /**
 	 * @return the allowUnknownTags
 	 */
 	public boolean isAllowUnknownTags() {
 		return allowUnknownTags;
 	}
 
+    /**
+     * Registers a tag implementation Class for a given tag name
+     */
+    protected void registerTag(String name, Class type) {
+        tags.put(name, type);
+    }
+
+	/**
+     * Registers a tag factory for a given tag name
+     */
+    protected void registerTagFactory(String name, TagFactory tagFactory) {
+        tags.put(name, tagFactory);
+    }
+
 	/**
 	 * @param allowUnknownTags the allowUnknownTags to set
 	 */
diff --git a/core/src/main/java/org/apache/commons/jelly/TagSupport.java b/core/src/main/java/org/apache/commons/jelly/TagSupport.java
index c98847d6..22036b7c 100644
--- a/core/src/main/java/org/apache/commons/jelly/TagSupport.java
+++ b/core/src/main/java/org/apache/commons/jelly/TagSupport.java
@@ -29,24 +29,6 @@ import org.apache.commons.jelly.util.TagUtils;
 
 public abstract class TagSupport implements Tag {
 
-    /** The parent of this tag */
-    protected Tag parent;
-    
-    /** The TagLibrary which defines this tag */
-    protected TagLibrary tagLibrary;
-
-    /** The body of the tag */
-    protected Script body;
-    /** The current context */
-
-    protected Boolean shouldTrim;
-    protected boolean hasTrimmed;
-
-    protected JellyContext context;
-
-    /** Whether XML text should be escaped */
-    private boolean escapeText = true;
-
     /**
      * Searches up the parent hierarchy from the given tag
      * for a Tag of the given type
@@ -67,6 +49,19 @@ public abstract class TagSupport implements Tag {
         }
         return null;
     }
+    
+    /**
+     * Searches up the parent hierarchy from the given tag
+     * for a Tag matching one or more of given types.
+     *
+     * @param from the tag to start searching from
+     * @param tagClasses an array of types that might match
+     * @return the tag of the given type or null if it could not be found
+     * @see #findAncestorWithClass(Tag,Collection)
+     */
+    public static Tag findAncestorWithClass(Tag from, Class[] tagClasses) {
+        return findAncestorWithClass(from,Arrays.asList(tagClasses));
+    }
 
     /**
      * Searches up the parent hierarchy from the given tag
@@ -88,128 +83,30 @@ public abstract class TagSupport implements Tag {
         }
         return null;
     }
+    /** The parent of this tag */
+    protected Tag parent;
+    /** The TagLibrary which defines this tag */
+    protected TagLibrary tagLibrary;
 
-    /**
-     * Searches up the parent hierarchy from the given tag
-     * for a Tag matching one or more of given types.
-     *
-     * @param from the tag to start searching from
-     * @param tagClasses an array of types that might match
-     * @return the tag of the given type or null if it could not be found
-     * @see #findAncestorWithClass(Tag,Collection)
-     */
-    public static Tag findAncestorWithClass(Tag from, Class[] tagClasses) {
-        return findAncestorWithClass(from,Arrays.asList(tagClasses));
-    }
-
-    public TagSupport() {
-    }
-
-    public TagSupport(boolean shouldTrim) {
-        setTrim( shouldTrim );
-    }
-
-    /**
-     * Sets whether whitespace inside this tag should be trimmed or not.
-     * Defaults to true so whitespace is trimmed
-     */
-    public void setTrim(boolean shouldTrim) {
-        if ( shouldTrim ) {
-            this.shouldTrim = Boolean.TRUE;
-        }
-        else {
-            this.shouldTrim = Boolean.FALSE;
-        }
-    }
-
-    public boolean isTrim() {
-        if ( this.shouldTrim == null ) {
-            Tag parent = getParent();
-            if ( parent == null ) {
-                return true;
-            }
-            else {
-                if ( parent instanceof TagSupport ) {
-                    TagSupport parentSupport = (TagSupport) parent;
-
-                    this.shouldTrim = ( parentSupport.isTrim() ? Boolean.TRUE : Boolean.FALSE );
-                }
-                else {
-                    this.shouldTrim = Boolean.TRUE;
-                }
-            }
-        }
-
-        return this.shouldTrim.booleanValue();
-    }
-
-    /** @return the parent of this tag */
-    @Override
-    public Tag getParent() {
-        return parent;
-    }
-
-    /** Sets the parent of this tag */
-    @Override
-    public void setParent(Tag parent) {
-        this.parent = parent;
-    }
+    /** The body of the tag */
+    protected Script body;
 
-    /* (non-Javadoc)
-	 * @see org.apache.commons.jelly.Tag#getTagLib()
-	 */
-	@Override
-    public TagLibrary getTagLib() {
-		return tagLibrary;
-	}
+    /** The current context */
 
-	/* (non-Javadoc)
-	 * @see org.apache.commons.jelly.Tag#setTagLib(org.apache.commons.jelly.TagLibrary)
-	 */
-	@Override
-    public void setTagLib(TagLibrary tagLibrary) {
-		if (this.tagLibrary != null && tagLibrary != this.tagLibrary)
-			throw new IllegalArgumentException("Cannot setTagLib once set");
-		this.tagLibrary = tagLibrary;
-	}
+    protected Boolean shouldTrim;
 
-	/** @return the body of the tag */
-    @Override
-    public Script getBody() {
-        if (! hasTrimmed) {
-            hasTrimmed = true;
-            if (isTrim()) {
-                trimBody();
-            }
-        }
-        return body;
-    }
+    protected boolean hasTrimmed;
 
-    /** Sets the body of the tag */
-    @Override
-    public void setBody(Script body) {
-        this.body = body;
-        this.hasTrimmed = false;
-    }
+    protected JellyContext context;
 
-    /** @return the context in which the tag will be run */
-    @Override
-    public JellyContext getContext() {
-        return context;
-    }
+    /** Whether XML text should be escaped */
+    private boolean escapeText = true;
 
-    /** Sets the context in which the tag will be run */
-    @Override
-    public void setContext(JellyContext context) throws JellyTagException {
-        this.context = context;
+    public TagSupport() {
     }
 
-    /**
-     * Invokes the body of this tag using the given output
-     */
-    @Override
-    public void invokeBody(XMLOutput output) throws JellyTagException {
-        getBody().run(context, output);
+    public TagSupport(boolean shouldTrim) {
+        setTrim( shouldTrim );
     }
 
     // Implementation methods
@@ -239,6 +136,18 @@ public abstract class TagSupport implements Tag {
         return findAncestorWithClass(getParent(), parentClasses);
     }
 
+    /** @return the body of the tag */
+    @Override
+    public Script getBody() {
+        if (! hasTrimmed) {
+            hasTrimmed = true;
+            if (isTrim()) {
+                trimBody();
+            }
+        }
+        return body;
+    }
+
     /**
      * Executes the body of the tag and returns the result as a String.
      *
@@ -248,7 +157,7 @@ public abstract class TagSupport implements Tag {
         return getBodyText(escapeText);
     }
 
-    /**
+	/**
      * Executes the body of the tag and returns the result as a String.
      *
      * @param shouldEscape Signal if the text should be escaped.
@@ -260,12 +169,32 @@ public abstract class TagSupport implements Tag {
         return writer.toString();
     }
 
+	/** @return the context in which the tag will be run */
+    @Override
+    public JellyContext getContext() {
+        return context;
+    }
+
+    /** @return the parent of this tag */
+    @Override
+    public Tag getParent() {
+        return parent;
+    }
+
+    /* (non-Javadoc)
+	 * @see org.apache.commons.jelly.Tag#getTagLib()
+	 */
+	@Override
+    public TagLibrary getTagLib() {
+		return tagLibrary;
+	}
+
     /**
-     * Find all text nodes inside the top level of this body and
-     * if they are just whitespace then remove them
+     * Invokes the body of this tag using the given output
      */
-    protected void trimBody() {
-        TagUtils.trimScript(body);
+    @Override
+    public void invokeBody(XMLOutput output) throws JellyTagException {
+        getBody().run(context, output);
     }
 
     /**
@@ -275,6 +204,40 @@ public abstract class TagSupport implements Tag {
         return escapeText;
     }
 
+    public boolean isTrim() {
+        if ( this.shouldTrim == null ) {
+            Tag parent = getParent();
+            if ( parent == null ) {
+                return true;
+            }
+            else {
+                if ( parent instanceof TagSupport ) {
+                    TagSupport parentSupport = (TagSupport) parent;
+
+                    this.shouldTrim = ( parentSupport.isTrim() ? Boolean.TRUE : Boolean.FALSE );
+                }
+                else {
+                    this.shouldTrim = Boolean.TRUE;
+                }
+            }
+        }
+
+        return this.shouldTrim.booleanValue();
+    }
+
+    /** Sets the body of the tag */
+    @Override
+    public void setBody(Script body) {
+        this.body = body;
+        this.hasTrimmed = false;
+    }
+
+    /** Sets the context in which the tag will be run */
+    @Override
+    public void setContext(JellyContext context) throws JellyTagException {
+        this.context = context;
+    }
+
     /**
      * Sets whether the body of the tag should be escaped as text (so that &lt; and &gt; are
      * escaped as &amp;lt; and &amp;gt;), which is the default or leave the text as XML.
@@ -282,4 +245,41 @@ public abstract class TagSupport implements Tag {
     public void setEscapeText(boolean escapeText) {
         this.escapeText = escapeText;
     }
+
+    /** Sets the parent of this tag */
+    @Override
+    public void setParent(Tag parent) {
+        this.parent = parent;
+    }
+
+    /* (non-Javadoc)
+	 * @see org.apache.commons.jelly.Tag#setTagLib(org.apache.commons.jelly.TagLibrary)
+	 */
+	@Override
+    public void setTagLib(TagLibrary tagLibrary) {
+		if (this.tagLibrary != null && tagLibrary != this.tagLibrary)
+			throw new IllegalArgumentException("Cannot setTagLib once set");
+		this.tagLibrary = tagLibrary;
+	}
+
+    /**
+     * Sets whether whitespace inside this tag should be trimmed or not.
+     * Defaults to true so whitespace is trimmed
+     */
+    public void setTrim(boolean shouldTrim) {
+        if ( shouldTrim ) {
+            this.shouldTrim = Boolean.TRUE;
+        }
+        else {
+            this.shouldTrim = Boolean.FALSE;
+        }
+    }
+
+    /**
+     * Find all text nodes inside the top level of this body and
+     * if they are just whitespace then remove them
+     */
+    protected void trimBody() {
+        TagUtils.trimScript(body);
+    }
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/XMLOutput.java b/core/src/main/java/org/apache/commons/jelly/XMLOutput.java
index 97512697..9a05fe41 100644
--- a/core/src/main/java/org/apache/commons/jelly/XMLOutput.java
+++ b/core/src/main/java/org/apache/commons/jelly/XMLOutput.java
@@ -47,136 +47,152 @@ import org.xml.sax.helpers.DefaultHandler;
 
 public class XMLOutput implements ContentHandler, LexicalHandler {
 
-    protected static final String[] LEXICAL_HANDLER_NAMES =
-     {
-        "http://xml.org/sax/properties/lexical-handler",
-        "http://xml.org/sax/handlers/LexicalHandler" };
+    private final class NamespaceStack {
+        /** A list of maps: Each map contains prefix to uri mapping */
+        private List nsStack;
 
-    /** Empty attributes. */
-    private static final Attributes EMPTY_ATTRIBUTES = new AttributesImpl();
+        private NamespaceStack() {
+            this.nsStack = new ArrayList();
+            this.nsStack.add(new HashMap());
+        }
 
-    /** The Log to which logging calls will be made. */
-    private static final Log log = LogFactory.getLog(XMLOutput.class);
+        public void decreaseLevel() {
+            nsStack.remove(0);
+        }
 
-    /** The default for escaping of text. */
-    private static final boolean DEFAULT_ESCAPE_TEXT = false;
+        public void increaseLevel() {
+            nsStack.add(0, new HashMap());
+        }
 
-    /** The SAX ContentHandler that output goes to. */
-    private ContentHandler contentHandler;
+        private boolean isRootNodeDefaultNs(String prefix, String uri) {
+            return ("".equals(prefix) && "".equals(uri) && nsStack.size() == 1);
+        }
 
-    /** The SAX LexicalHandler that output goes to. */
-    private LexicalHandler lexicalHandler;
+        public void popNamespace(String prefix) throws SAXException {
+            Map prefixUriMap = (Map)nsStack.get(0);
 
-    /** Stack of known namespaces. */
-    private NamespaceStack namespaceStack = new NamespaceStack();
+            if (prefix == null) {
+                prefix = "";
+            }
 
-    public XMLOutput() {
-    }
+            if ("xml".equals(prefix)) {
+                // We should ignore setting 'xml' prefix
+                // As declared in java of ContentHandler#startPrefixMapping
+                return;
+            }
 
-    /** The XML-output will relay the SAX events to the indicated
-     * contentHandler.
-     * @param contentHandler
-     */
-    public XMLOutput(ContentHandler contentHandler) {
-        this.contentHandler = contentHandler;
-        // often classes will implement LexicalHandler as well
-        if (contentHandler instanceof LexicalHandler) {
-            this.lexicalHandler = (LexicalHandler) contentHandler;
+            if (prefixUriMap.containsKey(prefix)) {
+                String uri = (String) prefixUriMap.get(prefix);
+                prefixUriMap.remove(prefix);
+                // If we havent called startPrefixMapping for root node if we wanted to avoid xmlns=""
+                // We aren't going to call endPrefixMapping neither
+                if (!isRootNodeDefaultNs(prefix, uri)) {
+//                    System.out.println(">>>"+XMLOutput.this.hashCode()+">NamespaceStack.popNamespace() prefix="+prefix);
+                    contentHandler.endPrefixMapping(prefix);
+                }
+            }/* else {
+                improper nesting ? or already removed in popNamespaces
+            }
+            */
         }
-    }
-
-    /** The XML-output will relay the SAX events to the indicated
-     * content-handler lexical-handler.
-     * @param contentHandler
-     * @param lexicalHandler
-     */
-    public XMLOutput(
-        ContentHandler contentHandler,
-        LexicalHandler lexicalHandler) {
-        this.contentHandler = contentHandler;
-        this.lexicalHandler = lexicalHandler;
-    }
-
-    @Override
-    public String toString() {
-        return super.toString()
-            + "[contentHandler="
-            + contentHandler
-            + ";lexicalHandler="
-            + lexicalHandler
-            + "]";
-    }
 
-    /**
-     * Provides a useful hook that implementations
-     * can use to close the
-     * underlying OutputStream or Writer.
-     *
-     * @throws IOException
-     */
-    public void close() throws IOException {
-    }
+        public void popNamespaces() throws SAXException {
+            Map prefixUriMap = (Map)nsStack.get(0);
+            for (Iterator iter = prefixUriMap.keySet().iterator();iter.hasNext();) {
+                String prefix = (String)iter.next();
+                String uri = (String) prefixUriMap.get(prefix);
+                iter.remove();
 
-    /** Flushes the underlying stream if {@link XMLWriter}
-     * or {@link XMLOutput}.
-     *
-     * @throws IOException
-     */
-    public void flush() throws IOException {
-        if (contentHandler instanceof XMLWriter) {
-            ((XMLWriter)contentHandler).flush();
-        } else if (contentHandler instanceof XMLOutput) {
-            ((XMLOutput)contentHandler).flush();
+                // If we havent called startPrefixMapping for root node if we wanted to avoid xmlns=""
+                // We aren't going to call endPrefixMapping neither
+                if (!isRootNodeDefaultNs(prefix, uri)) {
+//                    System.out.println(">>>"+XMLOutput.this.hashCode()+">NamespaceStack.popNamespaces() prefix="+prefix);
+                    contentHandler.endPrefixMapping(prefix);
+                }
+            }
         }
-    }
 
-    // Static helper methods
-    //-------------------------------------------------------------------------
+        public void pushNamespace(String prefix, String uri) throws SAXException {
+            Map prefixUriMap;
 
-    /**
-     * Creates an XMLOutput from an existing SAX XMLReader.
-     */
-    public static XMLOutput createXMLOutput(XMLReader xmlReader) {
-        XMLOutput output = new XMLOutput(xmlReader.getContentHandler());
+            if (prefix == null) {
+                prefix = "";
+            }
+            if (uri == null) {
+                uri = "";
+            }
 
-        // isn't it lovely what we've got to do to find the LexicalHandler... ;-)
-        for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
-            try {
-                Object value = xmlReader.getProperty(LEXICAL_HANDLER_NAMES[i]);
-                if (value instanceof LexicalHandler) {
-                    output.setLexicalHandler((LexicalHandler) value);
+            if ("xml".equals(prefix)) {
+                // We should ignore setting 'xml' prefix
+                // As declared in java of ContentHandler#startPrefixMapping
+                return;
+            }
+
+            // Lets find out if we already declared this same prefix,
+            // if not declare in current depth map (the first of list)
+            // and call contentHandler.startPrefixMapping(prefix, uri);
+            boolean isNew = true;
+            for (Iterator iter = nsStack.iterator(); iter.hasNext();) {
+                prefixUriMap = (Map) iter.next();
+                if (prefixUriMap.containsKey(prefix)) {
+                    if (uri.equals(prefixUriMap.get(prefix))) {
+                        // Its an active namespace already
+                        // System.out.println(">>>"+XMLOutput.this.hashCode()+">NamespaceStack.pushNamespace() IS NOT NEW prefix="+prefix+",uri="+uri);
+                        isNew = false;
+                    }
+                    // We found it in stack
+                    // If it was exactly the same, we won't bother
                     break;
                 }
-            } catch (Exception e) {
-                // ignore any unsupported-operation exceptions
-                if (log.isDebugEnabled()) {
-                    log.debug("error setting lexical handler properties", e);
+            }
+
+            if (isNew) {
+                // not declared sometime before
+                prefixUriMap = (Map) nsStack.get(0); // Current depth map
+                // Sanity check: Don't let two prefixes for different uris in
+                // same depth
+                if (prefixUriMap.containsKey(prefix)) {
+                    if (!uri.equals(prefixUriMap.get(prefix))) {
+                        throw new SAXException("Cannot set same prefix to different URI in same node: trying to add prefix \""
+                                + prefix + "\" for uri \""+uri+"\" whereas the declared ones are " + prefixUriMap);
+                    }
+                } else {
+                    prefixUriMap.put(prefix, uri);
+
+                    // To avoid setting xmlns="" for top node (not very nice :D)
+                    // We need to specifically check this condition
+                    if (!isRootNodeDefaultNs(prefix, uri)) {
+//                        System.out.println(">>>"+XMLOutput.this.hashCode()+">NamespaceStack.pushNamespace() prefix="+prefix+",uri="+uri);
+                        contentHandler.startPrefixMapping(prefix, uri);
+                    }
                 }
             }
         }
-        return output;
     }
 
-    /**
-     * Creates a text based XMLOutput which converts all XML events into
-     * text and writes to the underlying Writer.
-     */
-    public static XMLOutput createXMLOutput(Writer writer) {
-        return createXMLOutput(writer, DEFAULT_ESCAPE_TEXT);
-    }
+    protected static final String[] LEXICAL_HANDLER_NAMES =
+     {
+        "http://xml.org/sax/properties/lexical-handler",
+        "http://xml.org/sax/handlers/LexicalHandler" };
+
+    /** Empty attributes. */
+    private static final Attributes EMPTY_ATTRIBUTES = new AttributesImpl();
+
+    /** The Log to which logging calls will be made. */
+    private static final Log log = LogFactory.getLog(XMLOutput.class);
+
+    /** The default for escaping of text. */
+    private static final boolean DEFAULT_ESCAPE_TEXT = false;
 
     /**
-     * Creates a text based XMLOutput which converts all XML events into
-     * text and writes to the underlying Writer.
+     * returns an XMLOutput object that will discard all
+     * tag-generated XML events.  Useful when tag output is not expected
+     * or not significant.
      *
-     * @param writer is the writer to output to
-     * @param escapeText is whether or not text output will be escaped. This must be true
-     *   if the underlying output is XML or could be false if the underlying output is textual.
+     * @return a no-op XMLOutput
      */
-    public static XMLOutput createXMLOutput(Writer writer, boolean escapeText) {
-        XMLWriter xmlWriter = new XMLWriter(writer);
-        xmlWriter.setEscapeText(escapeText);
-        return createXMLOutput(xmlWriter);
+    public static XMLOutput createDummyXMLOutput() {
+        return new XMLOutput(new DefaultHandler());
     }
 
     /**
@@ -205,119 +221,200 @@ public class XMLOutput implements ContentHandler, LexicalHandler {
     }
 
     /**
-     * returns an XMLOutput object that will discard all
-     * tag-generated XML events.  Useful when tag output is not expected
-     * or not significant.
-     *
-     * @return a no-op XMLOutput
+     * Creates a text based XMLOutput which converts all XML events into
+     * text and writes to the underlying Writer.
      */
-    public static XMLOutput createDummyXMLOutput() {
-        return new XMLOutput(new DefaultHandler());
+    public static XMLOutput createXMLOutput(Writer writer) {
+        return createXMLOutput(writer, DEFAULT_ESCAPE_TEXT);
     }
 
-    // Extra helper methods provided for tag authors
-    //-------------------------------------------------------------------------
-
     /**
-     * Outputs the given String as a piece of valid text in the
-     * XML event stream.
-     * Any special XML characters should come out properly escaped.
+     * Creates a text based XMLOutput which converts all XML events into
+     * text and writes to the underlying Writer.
+     *
+     * @param writer is the writer to output to
+     * @param escapeText is whether or not text output will be escaped. This must be true
+     *   if the underlying output is XML or could be false if the underlying output is textual.
      */
-    public void write(String text) throws SAXException {
-        char[] ch = text.toCharArray();
-        characters(ch, 0, ch.length);
+    public static XMLOutput createXMLOutput(Writer writer, boolean escapeText) {
+        XMLWriter xmlWriter = new XMLWriter(writer);
+        xmlWriter.setEscapeText(escapeText);
+        return createXMLOutput(xmlWriter);
     }
 
     /**
-     * Outputs the given String as a piece of CDATA in the
-     * XML event stream.
+     * Creates an XMLOutput from an existing SAX XMLReader.
      */
-    public void writeCDATA(String text) throws SAXException {
-        startCDATA();
-        char[] ch = text.toCharArray();
-        characters(ch, 0, ch.length);
-        endCDATA();
+    public static XMLOutput createXMLOutput(XMLReader xmlReader) {
+        XMLOutput output = new XMLOutput(xmlReader.getContentHandler());
+
+        // isn't it lovely what we've got to do to find the LexicalHandler... ;-)
+        for (int i = 0; i < LEXICAL_HANDLER_NAMES.length; i++) {
+            try {
+                Object value = xmlReader.getProperty(LEXICAL_HANDLER_NAMES[i]);
+                if (value instanceof LexicalHandler) {
+                    output.setLexicalHandler((LexicalHandler) value);
+                    break;
+                }
+            } catch (Exception e) {
+                // ignore any unsupported-operation exceptions
+                if (log.isDebugEnabled()) {
+                    log.debug("error setting lexical handler properties", e);
+                }
+            }
+        }
+        return output;
     }
 
+    // Implementation methods
+    //-------------------------------------------------------------------------
     /**
-     * Outputs a comment to the XML stream.
+     * Factory method to create a new XMLOutput from an XMLWriter
      */
-    public void writeComment(String text) throws SAXException {
-        char[] ch = text.toCharArray();
-        comment(ch, 0, ch.length);
+    protected static XMLOutput createXMLOutput(final XMLWriter xmlWriter) {
+        XMLOutput answer = new XMLOutput() {
+            @Override
+            public void close() throws IOException {
+                xmlWriter.close();
+            }
+        };
+        answer.setContentHandler(xmlWriter);
+        answer.setLexicalHandler(xmlWriter);
+        return answer;
     }
 
-    /**
-     * Helper method for outputting a start element event
-     * for an element in no namespace.
+    /** The SAX ContentHandler that output goes to. */
+    private ContentHandler contentHandler;
+
+    // Static helper methods
+    //-------------------------------------------------------------------------
+
+    /** The SAX LexicalHandler that output goes to. */
+    private LexicalHandler lexicalHandler;
+
+    /** Stack of known namespaces. */
+    private NamespaceStack namespaceStack = new NamespaceStack();
+
+    public XMLOutput() {
+    }
+
+    /** The XML-output will relay the SAX events to the indicated
+     * contentHandler.
+     * @param contentHandler
      */
-    public void startElement(String localName) throws SAXException {
-        startElement("", localName, localName, EMPTY_ATTRIBUTES);
+    public XMLOutput(ContentHandler contentHandler) {
+        this.contentHandler = contentHandler;
+        // often classes will implement LexicalHandler as well
+        if (contentHandler instanceof LexicalHandler) {
+            this.lexicalHandler = (LexicalHandler) contentHandler;
+        }
     }
 
-    /**
-     * Helper method for outputting a start element event
-     * for an element in no namespace.
+    /** The XML-output will relay the SAX events to the indicated
+     * content-handler lexical-handler.
+     * @param contentHandler
+     * @param lexicalHandler
      */
-    public void startElement(String localName, Attributes attributes) throws SAXException {
-        startElement("", localName, localName, attributes);
+    public XMLOutput(
+        ContentHandler contentHandler,
+        LexicalHandler lexicalHandler) {
+        this.contentHandler = contentHandler;
+        this.lexicalHandler = lexicalHandler;
     }
 
     /**
-     * Helper method for outputting an end element event
-     * for an element in no namespace.
+     * Receive notification of character data.
+     *
+     * <p>The Parser will call this method to report each chunk of
+     * character data.  SAX parsers may return all contiguous character
+     * data in a single chunk, or they may split it into several
+     * chunks; however, all of the characters in any single event
+     * must come from the same external entity so that the Locator
+     * provides useful information.</p>
+     *
+     * <p>The application must not attempt to read from the array
+     * outside of the specified range.</p>
+     *
+     * <p>Individual characters may consist of more than one Java
+     * <code>char</code> value.  There are two important cases where this
+     * happens, because characters can't be represented in just sixteen bits.
+     * In one case, characters are represented in a <em>Surrogate Pair</em>,
+     * using two special Unicode values. Such characters are in the so-called
+     * "Astral Planes", with a code point above U+FFFF.  A second case involves
+     * composite characters, such as a base character combining with one or
+     * more accent characters.</p>
+     *
+     * <p> Your code should not assume that algorithms using
+     * <code>char</code>-at-a-time idioms will be working in character
+     * units; in some cases they will split characters.  This is relevant
+     * wherever XML permits arbitrary characters, such as attribute values,
+     * processing instruction data, and comments as well as in data reported
+     * from this method.  It's also generally relevant whenever Java code
+     * manipulates internationalized text; the issue isn't unique to XML.</p>
+     *
+     * <p>Note that some parsers will report whitespace in element
+     * content using the {@link #ignorableWhitespace ignorableWhitespace}
+     * method rather than this one (validating parsers <em>must</em>
+     * do so).</p>
+     *
+     * @param ch The characters from the XML document.
+     * @param start The start position in the array.
+     * @param length The number of characters to read from the array.
+     * @throws org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see #ignorableWhitespace
+     * @see org.xml.sax.Locator
      */
-    public void endElement(String localName) throws SAXException {
-        endElement("", localName, localName);
+    @Override
+    public void characters(char[] ch, int start, int length) throws SAXException {
+        contentHandler.characters(ch, start, length);
     }
 
-    // ContentHandler interface
+    // Extra helper methods provided for tag authors
     //-------------------------------------------------------------------------
 
     /**
-     * Receive an object for locating the origin of SAX document events.
-     *
-     * <p>SAX parsers are strongly encouraged (though not absolutely
-     * required) to supply a locator: if it does so, it must supply
-     * the locator to the application by invoking this method before
-     * invoking any of the other methods in the ContentHandler
-     * interface.</p>
+     * Provides a useful hook that implementations
+     * can use to close the
+     * underlying OutputStream or Writer.
      *
-     * <p>The locator allows the application to determine the end
-     * position of any document-related event, even if the parser is
-     * not reporting an error.  Typically, the application will
-     * use this information for reporting its own errors (such as
-     * character content that does not match an application's
-     * business rules).  The information returned by the locator
-     * is probably not sufficient for use with a search engine.</p>
+     * @throws IOException
+     */
+    public void close() throws IOException {
+    }
+
+    /**
+     * Report an XML comment anywhere in the document.
      *
-     * <p>Note that the locator will return correct information only
-     * during the invocation of the events in this interface.  The
-     * application should not attempt to use it at any other time.</p>
+     * <p>This callback will be used for comments inside or outside the
+     * document element, including comments in the external DTD
+     * subset (if read).  Comments in the DTD must be properly
+     * nested inside start/endDTD and start/endEntity events (if
+     * used).</p>
      *
-     * @param locator An object that can return the location of
-     *                any SAX document event.
-     * @see org.xml.sax.Locator
+     * @param ch An array holding the characters in the comment.
+     * @param start The starting position in the array.
+     * @param length The number of characters to use from the array.
+     * @throws SAXException The application may raise an exception.
      */
     @Override
-    public void setDocumentLocator(Locator locator) {
-        contentHandler.setDocumentLocator(locator);
+    public void comment(char ch[], int start, int length) throws SAXException {
+        if (lexicalHandler != null) {
+            lexicalHandler.comment(ch, start, length);
+        }
     }
 
     /**
-     * Receive notification of the beginning of a document.
-     *
-     * <p>The SAX parser will invoke this method only once, before any
-     * other event callbacks (except for {@link #setDocumentLocator
-     * setDocumentLocator}).</p>
+     * Report the end of a CDATA section.
      *
-     * @throws org.xml.sax.SAXException Any SAX exception, possibly
-     *            wrapping another exception.
-     * @see #endDocument
+     * @throws SAXException The application may raise an exception.
+     * @see #startCDATA
      */
     @Override
-    public void startDocument() throws SAXException {
-        contentHandler.startDocument();
+    public void endCDATA() throws SAXException {
+        if (lexicalHandler != null) {
+            lexicalHandler.endCDATA();
+        }
     }
 
     /**
@@ -339,164 +436,32 @@ public class XMLOutput implements ContentHandler, LexicalHandler {
     }
 
     /**
-     * Begin the scope of a prefix-URI Namespace mapping.
-     *
-     * <p>The information from this event is not necessary for
-     * normal Namespace processing: the SAX XML reader will
-     * automatically replace prefixes for element and attribute
-     * names when the <code>http://xml.org/sax/features/namespaces</code>
-     * feature is <var>true</var> (the default).</p>
-     *
-     * <p>There are cases, however, when applications need to
-     * use prefixes in character data or in attribute values,
-     * where they cannot safely be expanded automatically; the
-     * start/endPrefixMapping event supplies the information
-     * to the application to expand prefixes in those contexts
-     * itself, if necessary.</p>
-     *
-     * <p>Note that start/endPrefixMapping events are not
-     * guaranteed to be properly nested relative to each other:
-     * all startPrefixMapping events will occur immediately before the
-     * corresponding {@link #startElement startElement} event,
-     * and all {@link #endPrefixMapping endPrefixMapping}
-     * events will occur immediately after the corresponding
-     * {@link #endElement endElement} event,
-     * but their order is not otherwise
-     * guaranteed.</p>
+     * Report the end of DTD declarations.
      *
-     * <p>There should never be start/endPrefixMapping events for the
-     * "xml" prefix, since it is predeclared and immutable.</p>
+     * <p>This method is intended to report the end of the
+     * DOCTYPE declaration; if the document has no DOCTYPE declaration,
+     * this method will not be invoked.</p>
      *
-     * @param prefix The Namespace prefix being declared.
-     *  An empty string is used for the default element namespace,
-     *  which has no prefix.
-     * @param uri The Namespace URI the prefix is mapped to.
-     * @throws org.xml.sax.SAXException The client may throw
-     *            an exception during processing.
-     * @see #endPrefixMapping
-     * @see #startElement
+     * @throws SAXException The application may raise an exception.
+     * @see #startDTD
      */
     @Override
-    public void startPrefixMapping(String prefix, String uri) throws SAXException {
-        namespaceStack.pushNamespace(prefix, uri);
-        // contentHandler.startPrefixMapping(prefix, uri) will be called if needed
-        // in pushNamespace
+    public void endDTD() throws SAXException {
+        if (lexicalHandler != null) {
+            lexicalHandler.endDTD();
+        }
     }
 
     /**
-     * End the scope of a prefix-URI mapping.
-     *
-     * <p>See {@link #startPrefixMapping startPrefixMapping} for
-     * details.  These events will always occur immediately after the
-     * corresponding {@link #endElement endElement} event, but the order of
-     * {@link #endPrefixMapping endPrefixMapping} events is not otherwise
-     * guaranteed.</p>
-     *
-     * @param prefix The prefix that was being mapped.
-     *  This is the empty string when a default mapping scope ends.
-     * @throws org.xml.sax.SAXException The client may throw
-     *            an exception during processing.
-     * @see #startPrefixMapping
-     * @see #endElement
+     * Helper method for outputting an end element event
+     * for an element in no namespace.
      */
-    @Override
-    public void endPrefixMapping(String prefix) throws SAXException {
-        // End prefix mapping was already called after endElement
-        // contentHandler.endPrefixMapping(prefix);
+    public void endElement(String localName) throws SAXException {
+        endElement("", localName, localName);
     }
 
-    /**
-     * Receive notification of the beginning of an element.
-     *
-     * <p>The Parser will invoke this method at the beginning of every
-     * element in the XML document; there will be a corresponding
-     * {@link #endElement endElement} event for every startElement event
-     * (even when the element is empty). All of the element's content will be
-     * reported, in order, before the corresponding endElement
-     * event.</p>
-     *
-     * <p>This event allows up to three name components for each
-     * element:</p>
-     *
-     * <ol>
-     * <li>the Namespace URI;</li>
-     * <li>the local name; and</li>
-     * <li>the qualified (prefixed) name.</li>
-     * </ol>
-     *
-     * <p>Any or all of these may be provided, depending on the
-     * values of the <var>http://xml.org/sax/features/namespaces</var>
-     * and the <var>http://xml.org/sax/features/namespace-prefixes</var>
-     * properties:</p>
-     *
-     * <ul>
-     * <li>the Namespace URI and local name are required when
-     * the namespaces property is <var>true</var> (the default), and are
-     * optional when the namespaces property is <var>false</var> (if one is
-     * specified, both must be);</li>
-     * <li>the qualified name is required when the namespace-prefixes property
-     * is <var>true</var>, and is optional when the namespace-prefixes property
-     * is <var>false</var> (the default).</li>
-     * </ul>
-     *
-     * <p>Note that the attribute list provided will contain only
-     * attributes with explicit values (specified or defaulted):
-     * #IMPLIED attributes will be omitted.  The attribute list
-     * will contain attributes used for Namespace declarations
-     * (xmlns* attributes) only if the
-     * <code>http://xml.org/sax/features/namespace-prefixes</code>
-     * property is true (it is false by default, and support for a
-     * true value is optional).</p>
-     *
-     * <p>Like {@link #characters characters()}, attribute values may have
-     * characters that need more than one <code>char</code> value.  </p>
-     *
-     * @param uri The Namespace URI, or the empty string if the
-     *        element has no Namespace URI or if Namespace
-     *        processing is not being performed.
-     * @param localName The local name (without prefix), or the
-     *        empty string if Namespace processing is not being
-     *        performed.
-     * @param qName The qualified name (with prefix), or the
-     *        empty string if qualified names are not available.
-     * @param atts The attributes attached to the element.  If
-     *        there are no attributes, it shall be an empty
-     *        Attributes object.
-     * @throws org.xml.sax.SAXException Any SAX exception, possibly
-     *            wrapping another exception.
-     * @see #endElement
-     * @see org.xml.sax.Attributes
-     */
-    @Override
-    public void startElement(
-        String uri,
-        String localName,
-        String qName,
-        Attributes atts)
-        throws SAXException {
-
-        int idx = qName.indexOf(':');
-        String attNsPrefix = "";
-        if (idx >= 0) {
-            attNsPrefix = qName.substring(0, idx);
-        }
-        namespaceStack.pushNamespace(attNsPrefix, uri);
-        for (int i = 0; i < atts.getLength(); i++) {
-            String attQName = atts.getQName(i);
-            // An attribute only has an namespace if has a prefix
-            // If not, stays in namespace of containing node
-            idx = attQName.indexOf(':');
-            if (idx >= 0) {
-                attNsPrefix = attQName.substring(0, idx);
-                String attUri = atts.getURI(i);
-                namespaceStack.pushNamespace(attNsPrefix, attUri);
-            }
-        }
-
-        contentHandler.startElement(uri, localName, qName, atts);
-        // Inform namespaceStack of a new depth
-        namespaceStack.increaseLevel();
-    }
+    // ContentHandler interface
+    //-------------------------------------------------------------------------
 
     /**
      * Receive notification of the end of an element.
@@ -529,51 +494,68 @@ public class XMLOutput implements ContentHandler, LexicalHandler {
     }
 
     /**
-     * Receive notification of character data.
-     *
-     * <p>The Parser will call this method to report each chunk of
-     * character data.  SAX parsers may return all contiguous character
-     * data in a single chunk, or they may split it into several
-     * chunks; however, all of the characters in any single event
-     * must come from the same external entity so that the Locator
-     * provides useful information.</p>
-     *
-     * <p>The application must not attempt to read from the array
-     * outside of the specified range.</p>
-     *
-     * <p>Individual characters may consist of more than one Java
-     * <code>char</code> value.  There are two important cases where this
-     * happens, because characters can't be represented in just sixteen bits.
-     * In one case, characters are represented in a <em>Surrogate Pair</em>,
-     * using two special Unicode values. Such characters are in the so-called
-     * "Astral Planes", with a code point above U+FFFF.  A second case involves
-     * composite characters, such as a base character combining with one or
-     * more accent characters.</p>
+     * Report the end of an entity.
      *
-     * <p> Your code should not assume that algorithms using
-     * <code>char</code>-at-a-time idioms will be working in character
-     * units; in some cases they will split characters.  This is relevant
-     * wherever XML permits arbitrary characters, such as attribute values,
-     * processing instruction data, and comments as well as in data reported
-     * from this method.  It's also generally relevant whenever Java code
-     * manipulates internationalized text; the issue isn't unique to XML.</p>
+     * @param name The name of the entity that is ending.
+     * @throws SAXException The application may raise an exception.
+     * @see #startEntity
+     */
+    @Override
+    public void endEntity(String name) throws SAXException {
+        if (lexicalHandler != null) {
+            lexicalHandler.endEntity(name);
+        }
+    }
+
+    /**
+     * End the scope of a prefix-URI mapping.
      *
-     * <p>Note that some parsers will report whitespace in element
-     * content using the {@link #ignorableWhitespace ignorableWhitespace}
-     * method rather than this one (validating parsers <em>must</em>
-     * do so).</p>
+     * <p>See {@link #startPrefixMapping startPrefixMapping} for
+     * details.  These events will always occur immediately after the
+     * corresponding {@link #endElement endElement} event, but the order of
+     * {@link #endPrefixMapping endPrefixMapping} events is not otherwise
+     * guaranteed.</p>
      *
-     * @param ch The characters from the XML document.
-     * @param start The start position in the array.
-     * @param length The number of characters to read from the array.
-     * @throws org.xml.sax.SAXException Any SAX exception, possibly
-     *            wrapping another exception.
-     * @see #ignorableWhitespace
-     * @see org.xml.sax.Locator
+     * @param prefix The prefix that was being mapped.
+     *  This is the empty string when a default mapping scope ends.
+     * @throws org.xml.sax.SAXException The client may throw
+     *            an exception during processing.
+     * @see #startPrefixMapping
+     * @see #endElement
      */
     @Override
-    public void characters(char[] ch, int start, int length) throws SAXException {
-        contentHandler.characters(ch, start, length);
+    public void endPrefixMapping(String prefix) throws SAXException {
+        // End prefix mapping was already called after endElement
+        // contentHandler.endPrefixMapping(prefix);
+    }
+
+    /** Flushes the underlying stream if {@link XMLWriter}
+     * or {@link XMLOutput}.
+     *
+     * @throws IOException
+     */
+    public void flush() throws IOException {
+        if (contentHandler instanceof XMLWriter) {
+            ((XMLWriter)contentHandler).flush();
+        } else if (contentHandler instanceof XMLOutput) {
+            ((XMLOutput)contentHandler).flush();
+        }
+    }
+
+    // Properties
+    //-------------------------------------------------------------------------
+    /**
+     * @return the SAX ContentHandler to use to pipe SAX events into
+     */
+    public ContentHandler getContentHandler() {
+        return contentHandler;
+    }
+
+    /**
+     * @return the SAX LexicalHandler to use to pipe SAX events into
+     */
+    public LexicalHandler getLexicalHandler() {
+        return lexicalHandler;
     }
 
     /**
@@ -606,6 +588,28 @@ public class XMLOutput implements ContentHandler, LexicalHandler {
         contentHandler.ignorableWhitespace(ch, start, length);
     }
 
+    /** Pass data through the pipeline.
+      * By default, this call is ignored.
+      * Subclasses are invited to use this as a way for children tags to
+      * pass data to their parent.
+      * 
+      * @param object the data to pass
+      * @throws SAXException The application may raise an exception.
+      */
+    public void objectData(Object object) throws SAXException {
+        if (contentHandler instanceof XMLOutput)
+            ((XMLOutput) contentHandler).objectData(object);
+        else {
+            if (object!=null) {
+                String output=object.toString();
+                write(output);
+            } else {
+                // we could have a "configurable null-toString"...
+                write("null");
+            }
+        }
+    }
+
     /**
      * Receive notification of a processing instruction.
      *
@@ -635,37 +639,121 @@ public class XMLOutput implements ContentHandler, LexicalHandler {
     }
 
     /**
-     * Receive notification of a skipped entity.
-     * This is not called for entity references within markup constructs
-     * such as element start tags or markup declarations.  (The XML
-     * recommendation requires reporting skipped external entities.
-     * SAX also reports internal entity expansion/non-expansion, except
-     * within markup constructs.)
-     *
-     * <p>The Parser will invoke this method each time the entity is
-     * skipped.  Non-validating processors may skip entities if they
-     * have not seen the declarations (because, for example, the
-     * entity was declared in an external DTD subset).  All processors
-     * may skip external entities, depending on the values of the
-     * <code>http://xml.org/sax/features/external-general-entities</code>
-     * and the
-     * <code>http://xml.org/sax/features/external-parameter-entities</code>
-     * properties.</p>
+     * Sets the SAX ContentHandler to pipe SAX events into
      *
-     * @param name The name of the skipped entity.  If it is a
-     *        parameter entity, the name will begin with '%', and if
-     *        it is the external DTD subset, it will be the string
-     *        "[dtd]".
-     * @throws org.xml.sax.SAXException Any SAX exception, possibly
-     *            wrapping another exception.
+     * @param contentHandler is the new ContentHandler to use.
+     *      This value cannot be null.
      */
-    @Override
-    public void skippedEntity(String name) throws SAXException {
-        contentHandler.skippedEntity(name);
+    public void setContentHandler(ContentHandler contentHandler) {
+        this.contentHandler = Objects.requireNonNull(contentHandler, "contentHandler");
     }
 
-    // Lexical Handler interface
-    //-------------------------------------------------------------------------
+    /**
+     * Receive an object for locating the origin of SAX document events.
+     *
+     * <p>SAX parsers are strongly encouraged (though not absolutely
+     * required) to supply a locator: if it does so, it must supply
+     * the locator to the application by invoking this method before
+     * invoking any of the other methods in the ContentHandler
+     * interface.</p>
+     *
+     * <p>The locator allows the application to determine the end
+     * position of any document-related event, even if the parser is
+     * not reporting an error.  Typically, the application will
+     * use this information for reporting its own errors (such as
+     * character content that does not match an application's
+     * business rules).  The information returned by the locator
+     * is probably not sufficient for use with a search engine.</p>
+     *
+     * <p>Note that the locator will return correct information only
+     * during the invocation of the events in this interface.  The
+     * application should not attempt to use it at any other time.</p>
+     *
+     * @param locator An object that can return the location of
+     *                any SAX document event.
+     * @see org.xml.sax.Locator
+     */
+    @Override
+    public void setDocumentLocator(Locator locator) {
+        contentHandler.setDocumentLocator(locator);
+    }
+
+    // Lexical Handler interface
+    //-------------------------------------------------------------------------
+
+    /**
+     * Sets the SAX LexicalHandler to pipe SAX events into
+     *
+     * @param lexicalHandler is the new LexicalHandler to use.
+     *      This value can be null.
+     */
+    public void setLexicalHandler(LexicalHandler lexicalHandler) {
+        this.lexicalHandler = lexicalHandler;
+    }
+
+    /**
+     * Receive notification of a skipped entity.
+     * This is not called for entity references within markup constructs
+     * such as element start tags or markup declarations.  (The XML
+     * recommendation requires reporting skipped external entities.
+     * SAX also reports internal entity expansion/non-expansion, except
+     * within markup constructs.)
+     *
+     * <p>The Parser will invoke this method each time the entity is
+     * skipped.  Non-validating processors may skip entities if they
+     * have not seen the declarations (because, for example, the
+     * entity was declared in an external DTD subset).  All processors
+     * may skip external entities, depending on the values of the
+     * <code>http://xml.org/sax/features/external-general-entities</code>
+     * and the
+     * <code>http://xml.org/sax/features/external-parameter-entities</code>
+     * properties.</p>
+     *
+     * @param name The name of the skipped entity.  If it is a
+     *        parameter entity, the name will begin with '%', and if
+     *        it is the external DTD subset, it will be the string
+     *        "[dtd]".
+     * @throws org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     */
+    @Override
+    public void skippedEntity(String name) throws SAXException {
+        contentHandler.skippedEntity(name);
+    }
+
+    /**
+     * Report the start of a CDATA section.
+     *
+     * <p>The contents of the CDATA section will be reported through
+     * the regular {@link org.xml.sax.ContentHandler#characters
+     * characters} event; this event is intended only to report
+     * the boundary.</p>
+     *
+     * @throws SAXException The application may raise an exception.
+     * @see #endCDATA
+     */
+    @Override
+    public void startCDATA() throws SAXException {
+        if (lexicalHandler != null) {
+            lexicalHandler.startCDATA();
+        }
+    }
+
+    /**
+     * Receive notification of the beginning of a document.
+     *
+     * <p>The SAX parser will invoke this method only once, before any
+     * other event callbacks (except for {@link #setDocumentLocator
+     * setDocumentLocator}).</p>
+     *
+     * @throws org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see #endDocument
+     */
+    @Override
+    public void startDocument() throws SAXException {
+        contentHandler.startDocument();
+    }
 
     /**
      * Report the start of DTD declarations, if any.
@@ -714,20 +802,112 @@ public class XMLOutput implements ContentHandler, LexicalHandler {
     }
 
     /**
-     * Report the end of DTD declarations.
+     * Helper method for outputting a start element event
+     * for an element in no namespace.
+     */
+    public void startElement(String localName) throws SAXException {
+        startElement("", localName, localName, EMPTY_ATTRIBUTES);
+    }
+
+    /**
+     * Helper method for outputting a start element event
+     * for an element in no namespace.
+     */
+    public void startElement(String localName, Attributes attributes) throws SAXException {
+        startElement("", localName, localName, attributes);
+    }
+    
+    /**
+     * Receive notification of the beginning of an element.
      *
-     * <p>This method is intended to report the end of the
-     * DOCTYPE declaration; if the document has no DOCTYPE declaration,
-     * this method will not be invoked.</p>
+     * <p>The Parser will invoke this method at the beginning of every
+     * element in the XML document; there will be a corresponding
+     * {@link #endElement endElement} event for every startElement event
+     * (even when the element is empty). All of the element's content will be
+     * reported, in order, before the corresponding endElement
+     * event.</p>
      *
-     * @throws SAXException The application may raise an exception.
-     * @see #startDTD
+     * <p>This event allows up to three name components for each
+     * element:</p>
+     *
+     * <ol>
+     * <li>the Namespace URI;</li>
+     * <li>the local name; and</li>
+     * <li>the qualified (prefixed) name.</li>
+     * </ol>
+     *
+     * <p>Any or all of these may be provided, depending on the
+     * values of the <var>http://xml.org/sax/features/namespaces</var>
+     * and the <var>http://xml.org/sax/features/namespace-prefixes</var>
+     * properties:</p>
+     *
+     * <ul>
+     * <li>the Namespace URI and local name are required when
+     * the namespaces property is <var>true</var> (the default), and are
+     * optional when the namespaces property is <var>false</var> (if one is
+     * specified, both must be);</li>
+     * <li>the qualified name is required when the namespace-prefixes property
+     * is <var>true</var>, and is optional when the namespace-prefixes property
+     * is <var>false</var> (the default).</li>
+     * </ul>
+     *
+     * <p>Note that the attribute list provided will contain only
+     * attributes with explicit values (specified or defaulted):
+     * #IMPLIED attributes will be omitted.  The attribute list
+     * will contain attributes used for Namespace declarations
+     * (xmlns* attributes) only if the
+     * <code>http://xml.org/sax/features/namespace-prefixes</code>
+     * property is true (it is false by default, and support for a
+     * true value is optional).</p>
+     *
+     * <p>Like {@link #characters characters()}, attribute values may have
+     * characters that need more than one <code>char</code> value.  </p>
+     *
+     * @param uri The Namespace URI, or the empty string if the
+     *        element has no Namespace URI or if Namespace
+     *        processing is not being performed.
+     * @param localName The local name (without prefix), or the
+     *        empty string if Namespace processing is not being
+     *        performed.
+     * @param qName The qualified name (with prefix), or the
+     *        empty string if qualified names are not available.
+     * @param atts The attributes attached to the element.  If
+     *        there are no attributes, it shall be an empty
+     *        Attributes object.
+     * @throws org.xml.sax.SAXException Any SAX exception, possibly
+     *            wrapping another exception.
+     * @see #endElement
+     * @see org.xml.sax.Attributes
      */
     @Override
-    public void endDTD() throws SAXException {
-        if (lexicalHandler != null) {
-            lexicalHandler.endDTD();
+    public void startElement(
+        String uri,
+        String localName,
+        String qName,
+        Attributes atts)
+        throws SAXException {
+
+        int idx = qName.indexOf(':');
+        String attNsPrefix = "";
+        if (idx >= 0) {
+            attNsPrefix = qName.substring(0, idx);
+        }
+        namespaceStack.pushNamespace(attNsPrefix, uri);
+        for (int i = 0; i < atts.getLength(); i++) {
+            String attQName = atts.getQName(i);
+            // An attribute only has an namespace if has a prefix
+            // If not, stays in namespace of containing node
+            idx = attQName.indexOf(':');
+            if (idx >= 0) {
+                attNsPrefix = attQName.substring(0, idx);
+                String attUri = atts.getURI(i);
+                namespaceStack.pushNamespace(attNsPrefix, attUri);
+            }
         }
+
+        contentHandler.startElement(uri, localName, qName, atts);
+        // Inform namespaceStack of a new depth
+        namespaceStack.increaseLevel();
     }
 
     /**
@@ -787,266 +967,86 @@ public class XMLOutput implements ContentHandler, LexicalHandler {
     }
 
     /**
-     * Report the end of an entity.
+     * Begin the scope of a prefix-URI Namespace mapping.
      *
-     * @param name The name of the entity that is ending.
-     * @throws SAXException The application may raise an exception.
-     * @see #startEntity
-     */
-    @Override
-    public void endEntity(String name) throws SAXException {
-        if (lexicalHandler != null) {
-            lexicalHandler.endEntity(name);
-        }
-    }
-
-    /**
-     * Report the start of a CDATA section.
+     * <p>The information from this event is not necessary for
+     * normal Namespace processing: the SAX XML reader will
+     * automatically replace prefixes for element and attribute
+     * names when the <code>http://xml.org/sax/features/namespaces</code>
+     * feature is <var>true</var> (the default).</p>
      *
-     * <p>The contents of the CDATA section will be reported through
-     * the regular {@link org.xml.sax.ContentHandler#characters
-     * characters} event; this event is intended only to report
-     * the boundary.</p>
+     * <p>There are cases, however, when applications need to
+     * use prefixes in character data or in attribute values,
+     * where they cannot safely be expanded automatically; the
+     * start/endPrefixMapping event supplies the information
+     * to the application to expand prefixes in those contexts
+     * itself, if necessary.</p>
      *
-     * @throws SAXException The application may raise an exception.
-     * @see #endCDATA
-     */
-    @Override
-    public void startCDATA() throws SAXException {
-        if (lexicalHandler != null) {
-            lexicalHandler.startCDATA();
-        }
-    }
-
-    /**
-     * Report the end of a CDATA section.
-     *
-     * @throws SAXException The application may raise an exception.
-     * @see #startCDATA
-     */
-    @Override
-    public void endCDATA() throws SAXException {
-        if (lexicalHandler != null) {
-            lexicalHandler.endCDATA();
-        }
-    }
-
-    /**
-     * Report an XML comment anywhere in the document.
+     * <p>Note that start/endPrefixMapping events are not
+     * guaranteed to be properly nested relative to each other:
+     * all startPrefixMapping events will occur immediately before the
+     * corresponding {@link #startElement startElement} event,
+     * and all {@link #endPrefixMapping endPrefixMapping}
+     * events will occur immediately after the corresponding
+     * {@link #endElement endElement} event,
+     * but their order is not otherwise
+     * guaranteed.</p>
      *
-     * <p>This callback will be used for comments inside or outside the
-     * document element, including comments in the external DTD
-     * subset (if read).  Comments in the DTD must be properly
-     * nested inside start/endDTD and start/endEntity events (if
-     * used).</p>
+     * <p>There should never be start/endPrefixMapping events for the
+     * "xml" prefix, since it is predeclared and immutable.</p>
      *
-     * @param ch An array holding the characters in the comment.
-     * @param start The starting position in the array.
-     * @param length The number of characters to use from the array.
-     * @throws SAXException The application may raise an exception.
+     * @param prefix The Namespace prefix being declared.
+     *  An empty string is used for the default element namespace,
+     *  which has no prefix.
+     * @param uri The Namespace URI the prefix is mapped to.
+     * @throws org.xml.sax.SAXException The client may throw
+     *            an exception during processing.
+     * @see #endPrefixMapping
+     * @see #startElement
      */
     @Override
-    public void comment(char ch[], int start, int length) throws SAXException {
-        if (lexicalHandler != null) {
-            lexicalHandler.comment(ch, start, length);
-        }
-    }
-    
-    /** Pass data through the pipeline.
-      * By default, this call is ignored.
-      * Subclasses are invited to use this as a way for children tags to
-      * pass data to their parent.
-      * 
-      * @param object the data to pass
-      * @throws SAXException The application may raise an exception.
-      */
-    public void objectData(Object object) throws SAXException {
-        if (contentHandler instanceof XMLOutput)
-            ((XMLOutput) contentHandler).objectData(object);
-        else {
-            if (object!=null) {
-                String output=object.toString();
-                write(output);
-            } else {
-                // we could have a "configurable null-toString"...
-                write("null");
-            }
-        }
-    }
-
-    // Properties
-    //-------------------------------------------------------------------------
-    /**
-     * @return the SAX ContentHandler to use to pipe SAX events into
-     */
-    public ContentHandler getContentHandler() {
-        return contentHandler;
+    public void startPrefixMapping(String prefix, String uri) throws SAXException {
+        namespaceStack.pushNamespace(prefix, uri);
+        // contentHandler.startPrefixMapping(prefix, uri) will be called if needed
+        // in pushNamespace
     }
 
-    /**
-     * Sets the SAX ContentHandler to pipe SAX events into
-     *
-     * @param contentHandler is the new ContentHandler to use.
-     *      This value cannot be null.
-     */
-    public void setContentHandler(ContentHandler contentHandler) {
-        this.contentHandler = Objects.requireNonNull(contentHandler, "contentHandler");
+    @Override
+    public String toString() {
+        return super.toString()
+            + "[contentHandler="
+            + contentHandler
+            + ";lexicalHandler="
+            + lexicalHandler
+            + "]";
     }
 
     /**
-     * @return the SAX LexicalHandler to use to pipe SAX events into
+     * Outputs the given String as a piece of valid text in the
+     * XML event stream.
+     * Any special XML characters should come out properly escaped.
      */
-    public LexicalHandler getLexicalHandler() {
-        return lexicalHandler;
+    public void write(String text) throws SAXException {
+        char[] ch = text.toCharArray();
+        characters(ch, 0, ch.length);
     }
 
     /**
-     * Sets the SAX LexicalHandler to pipe SAX events into
-     *
-     * @param lexicalHandler is the new LexicalHandler to use.
-     *      This value can be null.
+     * Outputs the given String as a piece of CDATA in the
+     * XML event stream.
      */
-    public void setLexicalHandler(LexicalHandler lexicalHandler) {
-        this.lexicalHandler = lexicalHandler;
+    public void writeCDATA(String text) throws SAXException {
+        startCDATA();
+        char[] ch = text.toCharArray();
+        characters(ch, 0, ch.length);
+        endCDATA();
     }
 
-    // Implementation methods
-    //-------------------------------------------------------------------------
     /**
-     * Factory method to create a new XMLOutput from an XMLWriter
+     * Outputs a comment to the XML stream.
      */
-    protected static XMLOutput createXMLOutput(final XMLWriter xmlWriter) {
-        XMLOutput answer = new XMLOutput() {
-            @Override
-            public void close() throws IOException {
-                xmlWriter.close();
-            }
-        };
-        answer.setContentHandler(xmlWriter);
-        answer.setLexicalHandler(xmlWriter);
-        return answer;
-    }
-
-    private final class NamespaceStack {
-        /** A list of maps: Each map contains prefix to uri mapping */
-        private List nsStack;
-
-        private NamespaceStack() {
-            this.nsStack = new ArrayList();
-            this.nsStack.add(new HashMap());
-        }
-
-        private boolean isRootNodeDefaultNs(String prefix, String uri) {
-            return ("".equals(prefix) && "".equals(uri) && nsStack.size() == 1);
-        }
-
-        public void pushNamespace(String prefix, String uri) throws SAXException {
-            Map prefixUriMap;
-
-            if (prefix == null) {
-                prefix = "";
-            }
-            if (uri == null) {
-                uri = "";
-            }
-
-            if ("xml".equals(prefix)) {
-                // We should ignore setting 'xml' prefix
-                // As declared in java of ContentHandler#startPrefixMapping
-                return;
-            }
-
-            // Lets find out if we already declared this same prefix,
-            // if not declare in current depth map (the first of list)
-            // and call contentHandler.startPrefixMapping(prefix, uri);
-            boolean isNew = true;
-            for (Iterator iter = nsStack.iterator(); iter.hasNext();) {
-                prefixUriMap = (Map) iter.next();
-                if (prefixUriMap.containsKey(prefix)) {
-                    if (uri.equals(prefixUriMap.get(prefix))) {
-                        // Its an active namespace already
-                        // System.out.println(">>>"+XMLOutput.this.hashCode()+">NamespaceStack.pushNamespace() IS NOT NEW prefix="+prefix+",uri="+uri);
-                        isNew = false;
-                    }
-                    // We found it in stack
-                    // If it was exactly the same, we won't bother
-                    break;
-                }
-            }
-
-            if (isNew) {
-                // not declared sometime before
-                prefixUriMap = (Map) nsStack.get(0); // Current depth map
-                // Sanity check: Don't let two prefixes for different uris in
-                // same depth
-                if (prefixUriMap.containsKey(prefix)) {
-                    if (!uri.equals(prefixUriMap.get(prefix))) {
-                        throw new SAXException("Cannot set same prefix to different URI in same node: trying to add prefix \""
-                                + prefix + "\" for uri \""+uri+"\" whereas the declared ones are " + prefixUriMap);
-                    }
-                } else {
-                    prefixUriMap.put(prefix, uri);
-
-                    // To avoid setting xmlns="" for top node (not very nice :D)
-                    // We need to specifically check this condition
-                    if (!isRootNodeDefaultNs(prefix, uri)) {
-//                        System.out.println(">>>"+XMLOutput.this.hashCode()+">NamespaceStack.pushNamespace() prefix="+prefix+",uri="+uri);
-                        contentHandler.startPrefixMapping(prefix, uri);
-                    }
-                }
-            }
-        }
-
-        public void popNamespaces() throws SAXException {
-            Map prefixUriMap = (Map)nsStack.get(0);
-            for (Iterator iter = prefixUriMap.keySet().iterator();iter.hasNext();) {
-                String prefix = (String)iter.next();
-                String uri = (String) prefixUriMap.get(prefix);
-                iter.remove();
-
-                // If we havent called startPrefixMapping for root node if we wanted to avoid xmlns=""
-                // We aren't going to call endPrefixMapping neither
-                if (!isRootNodeDefaultNs(prefix, uri)) {
-//                    System.out.println(">>>"+XMLOutput.this.hashCode()+">NamespaceStack.popNamespaces() prefix="+prefix);
-                    contentHandler.endPrefixMapping(prefix);
-                }
-            }
-        }
-
-        public void popNamespace(String prefix) throws SAXException {
-            Map prefixUriMap = (Map)nsStack.get(0);
-
-            if (prefix == null) {
-                prefix = "";
-            }
-
-            if ("xml".equals(prefix)) {
-                // We should ignore setting 'xml' prefix
-                // As declared in java of ContentHandler#startPrefixMapping
-                return;
-            }
-
-            if (prefixUriMap.containsKey(prefix)) {
-                String uri = (String) prefixUriMap.get(prefix);
-                prefixUriMap.remove(prefix);
-                // If we havent called startPrefixMapping for root node if we wanted to avoid xmlns=""
-                // We aren't going to call endPrefixMapping neither
-                if (!isRootNodeDefaultNs(prefix, uri)) {
-//                    System.out.println(">>>"+XMLOutput.this.hashCode()+">NamespaceStack.popNamespace() prefix="+prefix);
-                    contentHandler.endPrefixMapping(prefix);
-                }
-            }/* else {
-                improper nesting ? or already removed in popNamespaces
-            }
-            */
-        }
-
-        public void decreaseLevel() {
-            nsStack.remove(0);
-        }
-
-        public void increaseLevel() {
-            nsStack.add(0, new HashMap());
-        }
+    public void writeComment(String text) throws SAXException {
+        char[] ch = text.toCharArray();
+        comment(ch, 0, ch.length);
     }
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/expression/CompositeExpression.java b/core/src/main/java/org/apache/commons/jelly/expression/CompositeExpression.java
index c2ce6f82..903dcd45 100644
--- a/core/src/main/java/org/apache/commons/jelly/expression/CompositeExpression.java
+++ b/core/src/main/java/org/apache/commons/jelly/expression/CompositeExpression.java
@@ -31,22 +31,6 @@ import org.apache.commons.jelly.JellyException;
  */
 public class CompositeExpression extends ExpressionSupport {
 
-    /** The expressions */
-    private List expressions;
-
-    public CompositeExpression() {
-        this.expressions = new ArrayList();
-    }
-
-    public CompositeExpression(List expressions) {
-        this.expressions = expressions;
-    }
-
-    @Override
-    public String toString() {
-        return super.toString() + "[expressions=" + expressions +"]";
-    }
-
     /**
      * Parses the given String to be either a ConstantExpression, an Expression denoted as
      * "${foo}" or some String with embedded expressions such as "abc${something}def${else}xyz"
@@ -203,22 +187,14 @@ public class CompositeExpression extends ExpressionSupport {
         return answer;
     }
 
-    // Properties
-    //-------------------------------------------------------------------------
+    /** The expressions */
+    private List expressions;
 
-    /**
-     * @return the Expression objects that make up this
-     * composite expression
-     */
-    public List getExpressions() {
-        return expressions;
+    public CompositeExpression() {
+        this.expressions = new ArrayList();
     }
 
-    /**
-     * Sets the Expression objects that make up this
-     * composite expression
-     */
-    public void setExpressions(List expressions) {
+    public CompositeExpression(List expressions) {
         this.expressions = expressions;
     }
 
@@ -229,6 +205,9 @@ public class CompositeExpression extends ExpressionSupport {
         expressions.add(expression);
     }
 
+    // Properties
+    //-------------------------------------------------------------------------
+
     /**
      * A helper method to add a new constant text expression
      */
@@ -236,23 +215,22 @@ public class CompositeExpression extends ExpressionSupport {
         addExpression(new ConstantExpression(text));
     }
 
-    // Expression interface
-    //-------------------------------------------------------------------------
-
+    // inherit javadoc from interface
     @Override
-    public String getExpressionText() {
-        StringBuilder buffer = new StringBuilder();
-        for (Iterator iter = expressions.iterator(); iter.hasNext(); ) {
-            Expression expression = (Expression) iter.next();
-            buffer.append( expression.getExpressionText() );
-        }
-        return buffer.toString();
+    public Object evaluate(JellyContext context) {
+        return evaluateAsString(context);
     }
 
     // inherit javadoc from interface
     @Override
-    public Object evaluate(JellyContext context) {
-        return evaluateAsString(context);
+    public Iterator evaluateAsIterator(JellyContext context) {
+        String value = evaluateAsString(context);
+        if ( value == null ) {
+            return Collections.EMPTY_LIST.iterator();
+        }
+        else {
+            return new SingletonIterator( value );
+        }
     }
 
     // inherit javadoc from interface
@@ -270,15 +248,37 @@ public class CompositeExpression extends ExpressionSupport {
 
     }
 
-    // inherit javadoc from interface
+    // Expression interface
+    //-------------------------------------------------------------------------
+
+    /**
+     * @return the Expression objects that make up this
+     * composite expression
+     */
+    public List getExpressions() {
+        return expressions;
+    }
+
     @Override
-    public Iterator evaluateAsIterator(JellyContext context) {
-        String value = evaluateAsString(context);
-        if ( value == null ) {
-            return Collections.EMPTY_LIST.iterator();
-        }
-        else {
-            return new SingletonIterator( value );
+    public String getExpressionText() {
+        StringBuilder buffer = new StringBuilder();
+        for (Iterator iter = expressions.iterator(); iter.hasNext(); ) {
+            Expression expression = (Expression) iter.next();
+            buffer.append( expression.getExpressionText() );
         }
+        return buffer.toString();
+    }
+
+    /**
+     * Sets the Expression objects that make up this
+     * composite expression
+     */
+    public void setExpressions(List expressions) {
+        this.expressions = expressions;
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + "[expressions=" + expressions +"]";
     }
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/expression/ConstantExpression.java b/core/src/main/java/org/apache/commons/jelly/expression/ConstantExpression.java
index 016beb29..d74cc689 100644
--- a/core/src/main/java/org/apache/commons/jelly/expression/ConstantExpression.java
+++ b/core/src/main/java/org/apache/commons/jelly/expression/ConstantExpression.java
@@ -40,16 +40,6 @@ public class ConstantExpression extends ExpressionSupport {
         this.value = value;
     }
 
-    @Override
-    public String toString() {
-        return super.toString() + "[value=" + value +"]";
-    }
-
-    @Override
-    public String getExpressionText() {
-        return String.valueOf(value);
-    }
-
     /**
       * Evaluate expression against given context.
       *
@@ -61,6 +51,11 @@ public class ConstantExpression extends ExpressionSupport {
         return value;
     }
 
+    @Override
+    public String getExpressionText() {
+        return String.valueOf(value);
+    }
+
     /** Gets the constant value of this expression */
     public Object getValue() {
         return value;
@@ -70,4 +65,9 @@ public class ConstantExpression extends ExpressionSupport {
     public void setValue(Object value) {
         this.value = value;
     }
+
+    @Override
+    public String toString() {
+        return super.toString() + "[value=" + value +"]";
+    }
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/expression/Expression.java b/core/src/main/java/org/apache/commons/jelly/expression/Expression.java
index c3d08088..8146a2bd 100644
--- a/core/src/main/java/org/apache/commons/jelly/expression/Expression.java
+++ b/core/src/main/java/org/apache/commons/jelly/expression/Expression.java
@@ -25,23 +25,12 @@ import org.apache.commons.jelly.JellyContext;
   */
 public interface Expression {
 
-    /**
-     * @return the textual representation of this expression
-     */
-    public String getExpressionText();
-
     /**
      * Evaluates the expression with the given context
      * and returns the result
      */
     public Object evaluate(JellyContext context);
 
-    /**
-     * Evaluates the expression with the given context
-     * coercing the result to be a String.
-     */
-    public String evaluateAsString(JellyContext context);
-
     /**
      * Evaluates the expression with the given context
      * coercing the result to be a boolean.
@@ -54,6 +43,12 @@ public interface Expression {
      */
     public Iterator evaluateAsIterator(JellyContext context);
 
+    /**
+     * Evaluates the expression with the given context
+     * coercing the result to be a String.
+     */
+    public String evaluateAsString(JellyContext context);
+
     /**
      * This method evaluates the expression until a value (a non-Expression) object
      * is returned.
@@ -71,4 +66,9 @@ public interface Expression {
      * actual underlying value object.
      */
     public Object evaluateRecurse(JellyContext context);
+
+    /**
+     * @return the textual representation of this expression
+     */
+    public String getExpressionText();
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/expression/ExpressionSupport.java b/core/src/main/java/org/apache/commons/jelly/expression/ExpressionSupport.java
index 6145569d..39031099 100644
--- a/core/src/main/java/org/apache/commons/jelly/expression/ExpressionSupport.java
+++ b/core/src/main/java/org/apache/commons/jelly/expression/ExpressionSupport.java
@@ -38,32 +38,6 @@ public abstract class ExpressionSupport implements Expression {
 
     protected static final Iterator EMPTY_ITERATOR = Collections.EMPTY_LIST.iterator();
 
-    // inherit javadoc from interface
-    @Override
-    public String evaluateAsString(JellyContext context) {
-        Object value = evaluateRecurse(context);
-        // sometimes when Jelly is used inside Maven the value
-        // of an expression can actually be an expression.
-        // e.g. ${foo.bar} can lookup "foo.bar" in a Maven context
-        // which could actually be an expression
-
-        if ( value != null ) {
-            return value.toString();
-        }
-        return null;
-    }
-
-    // inherit javadoc from interface
-    @Override
-    public Object evaluateRecurse(JellyContext context) {
-        Object value = evaluate(context);
-        if (value instanceof Expression) {
-            Expression expression = (Expression) value;
-            return expression.evaluateRecurse(context);
-        }
-        return value;
-    }
-
     // inherit javadoc from interface
     @Override
     public boolean evaluateAsBoolean(JellyContext context) {
@@ -118,4 +92,30 @@ public abstract class ExpressionSupport implements Expression {
             return new SingletonIterator( value );
         }
     }
+
+    // inherit javadoc from interface
+    @Override
+    public String evaluateAsString(JellyContext context) {
+        Object value = evaluateRecurse(context);
+        // sometimes when Jelly is used inside Maven the value
+        // of an expression can actually be an expression.
+        // e.g. ${foo.bar} can lookup "foo.bar" in a Maven context
+        // which could actually be an expression
+
+        if ( value != null ) {
+            return value.toString();
+        }
+        return null;
+    }
+
+    // inherit javadoc from interface
+    @Override
+    public Object evaluateRecurse(JellyContext context) {
+        Object value = evaluate(context);
+        if (value instanceof Expression) {
+            Expression expression = (Expression) value;
+            return expression.evaluateRecurse(context);
+        }
+        return value;
+    }
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/expression/jexl/JexlExpression.java b/core/src/main/java/org/apache/commons/jelly/expression/jexl/JexlExpression.java
index 10e0bca0..31c48ea9 100644
--- a/core/src/main/java/org/apache/commons/jelly/expression/jexl/JexlExpression.java
+++ b/core/src/main/java/org/apache/commons/jelly/expression/jexl/JexlExpression.java
@@ -28,66 +28,6 @@ import org.apache.commons.jexl.JexlContext;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 
-/**
- * Represents a <a href="https://commons.apache.org/jexl/">Jexl</a>
- * expression which fully supports the Expression Language in JSTL and JSP
- * along with some extra features like object method invocation.
- */
-
-public class JexlExpression extends ExpressionSupport {
-
-    /** The Log to which logging calls will be made. */
-    private static final Log log = LogFactory.getLog(JexlExpression.class);
-
-    /** The Jexl expression object */
-    private Expression expression;
-
-    public JexlExpression(Expression expression) {
-        this.expression = expression;
-    }
-
-    @Override
-    public String toString() {
-        return super.toString() + "[" + expression.getExpression() + "]";
-    }
-
-    // Expression interface
-    //-------------------------------------------------------------------------
-    @Override
-    public String getExpressionText() {
-        return "${" + expression.getExpression() + "}";
-    }
-
-    @Override
-    public Object evaluate(JellyContext context) {
-        try {
-            JexlContext jexlContext = new JellyJexlContext( context );
-            if (log.isDebugEnabled()) {
-                log.debug("Evaluating EL: " + expression.getExpression());
-            }
-            Object value = expression.evaluate(jexlContext);
-
-            if (log.isDebugEnabled()) {
-                log.debug("value of expression: " + value);
-            }
-
-            return value;
-        }
-        catch (Exception e) {
-        	if (context.isSuppressExpressionExceptions()) {
-	            log.warn("Caught exception evaluating: " + expression + ". Reason: " + e, e);
-	            return null;
-        	} else {
-        		if (e instanceof RuntimeException)
-        			throw (RuntimeException)e;
-        		if (e instanceof IllegalStateException)
-        			throw (IllegalStateException )e;
-            	throw (IllegalStateException)new IllegalStateException (e.getMessage(), e);
-        	}
-        }
-    }
-}
-
 final class JellyJexlContext implements JexlContext {
 
     private Map vars;
@@ -97,14 +37,14 @@ final class JellyJexlContext implements JexlContext {
     }
 
     @Override
-    public void setVars(Map vars) {
-        this.vars.clear();
-        this.vars.putAll( vars );
+    public Map getVars() {
+        return this.vars;
     }
 
     @Override
-    public Map getVars() {
-        return this.vars;
+    public void setVars(Map vars) {
+        this.vars.clear();
+        this.vars.putAll( vars );
     }
 }
 
@@ -116,11 +56,6 @@ final class JellyMap implements Map {
         this.context = context;
     }
 
-    @Override
-    public Object get(Object key) {
-        return context.getVariable( (String) key );
-    }
-
     @Override
     public void clear() {
         // not implemented
@@ -141,6 +76,11 @@ final class JellyMap implements Map {
         return null;
     }
 
+    @Override
+    public Object get(Object key) {
+        return context.getVariable( (String) key );
+    }
+
     @Override
     public boolean isEmpty() {
         return false;
@@ -176,3 +116,63 @@ final class JellyMap implements Map {
         return null;
     }
 }
+
+/**
+ * Represents a <a href="https://commons.apache.org/jexl/">Jexl</a>
+ * expression which fully supports the Expression Language in JSTL and JSP
+ * along with some extra features like object method invocation.
+ */
+
+public class JexlExpression extends ExpressionSupport {
+
+    /** The Log to which logging calls will be made. */
+    private static final Log log = LogFactory.getLog(JexlExpression.class);
+
+    /** The Jexl expression object */
+    private Expression expression;
+
+    public JexlExpression(Expression expression) {
+        this.expression = expression;
+    }
+
+    @Override
+    public Object evaluate(JellyContext context) {
+        try {
+            JexlContext jexlContext = new JellyJexlContext( context );
+            if (log.isDebugEnabled()) {
+                log.debug("Evaluating EL: " + expression.getExpression());
+            }
+            Object value = expression.evaluate(jexlContext);
+
+            if (log.isDebugEnabled()) {
+                log.debug("value of expression: " + value);
+            }
+
+            return value;
+        }
+        catch (Exception e) {
+        	if (context.isSuppressExpressionExceptions()) {
+	            log.warn("Caught exception evaluating: " + expression + ". Reason: " + e, e);
+	            return null;
+        	} else {
+        		if (e instanceof RuntimeException)
+        			throw (RuntimeException)e;
+        		if (e instanceof IllegalStateException)
+        			throw (IllegalStateException )e;
+            	throw (IllegalStateException)new IllegalStateException (e.getMessage(), e);
+        	}
+        }
+    }
+
+    // Expression interface
+    //-------------------------------------------------------------------------
+    @Override
+    public String getExpressionText() {
+        return "${" + expression.getExpression() + "}";
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + "[" + expression.getExpression() + "]";
+    }
+}
diff --git a/core/src/main/java/org/apache/commons/jelly/expression/jexl/JexlExpressionFactory.java b/core/src/main/java/org/apache/commons/jelly/expression/jexl/JexlExpressionFactory.java
index 3a6c7042..5dd2c17a 100644
--- a/core/src/main/java/org/apache/commons/jelly/expression/jexl/JexlExpressionFactory.java
+++ b/core/src/main/java/org/apache/commons/jelly/expression/jexl/JexlExpressionFactory.java
@@ -34,9 +34,44 @@ import org.apache.commons.jelly.expression.ExpressionSupport;
 
 public class JexlExpressionFactory implements ExpressionFactory {
 
+    private final class ExpressionSupportLocal extends ExpressionSupport {
+
+        protected Expression jexlExpression = null;
+        protected String text = null;
+
+        public ExpressionSupportLocal(Expression jexlExpression, String text) {
+            this.jexlExpression = jexlExpression;
+            this.text = text;
+        }
+
+        @Override
+        public Object evaluate(JellyContext context) {
+            Object answer = jexlExpression.evaluate(context);
+
+            if ( answer == null ) {
+                answer = context.getVariable(text);
+            }
+
+            return answer;
+        }
+
+        @Override
+        public String getExpressionText() {
+            return "${" + text + "}";
+        }
+
+        @Override
+        public String toString() {
+            return super.toString() + "[expression:" + text + "]";
+        }
+    }
+
     /** Whether we should allow Ant-style expressions, using dots as part of variable name */
     private boolean supportAntVariables = true;
 
+    // Properties
+    //-------------------------------------------------------------------------
+
     // ExpressionFactory interface
     //-------------------------------------------------------------------------
     @Override
@@ -69,9 +104,6 @@ public class JexlExpressionFactory implements ExpressionFactory {
         return jexlExpression;
     }
 
-    // Properties
-    //-------------------------------------------------------------------------
-
     /**
      * @return whether we should allow Ant-style expressions, using dots as
      * part of variable name
@@ -80,14 +112,6 @@ public class JexlExpressionFactory implements ExpressionFactory {
         return supportAntVariables;
     }
 
-    /**
-     * Sets whether we should allow Ant-style expressions, using dots as
-     * part of variable name
-     */
-    public void setSupportAntVariables(boolean supportAntVariables) {
-        this.supportAntVariables = supportAntVariables;
-    }
-
     // Implementation methods
     //-------------------------------------------------------------------------
 
@@ -107,36 +131,12 @@ public class JexlExpressionFactory implements ExpressionFactory {
         return true;
     }
 
-    private final class ExpressionSupportLocal extends ExpressionSupport {
-
-        protected Expression jexlExpression = null;
-        protected String text = null;
-
-        public ExpressionSupportLocal(Expression jexlExpression, String text) {
-            this.jexlExpression = jexlExpression;
-            this.text = text;
-        }
-
-        @Override
-        public Object evaluate(JellyContext context) {
-            Object answer = jexlExpression.evaluate(context);
-
-            if ( answer == null ) {
-                answer = context.getVariable(text);
-            }
-
-            return answer;
-        }
-
-        @Override
-        public String getExpressionText() {
-            return "${" + text + "}";
-        }
-
-        @Override
-        public String toString() {
-            return super.toString() + "[expression:" + text + "]";
-        }
+    /**
+     * Sets whether we should allow Ant-style expressions, using dots as
+     * part of variable name
+     */
+    public void setSupportAntVariables(boolean supportAntVariables) {
+        this.supportAntVariables = supportAntVariables;
     }
 
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/expression/xpath/XPathExpression.java b/core/src/main/java/org/apache/commons/jelly/expression/xpath/XPathExpression.java
index ac44e112..4026d46c 100644
--- a/core/src/main/java/org/apache/commons/jelly/expression/xpath/XPathExpression.java
+++ b/core/src/main/java/org/apache/commons/jelly/expression/xpath/XPathExpression.java
@@ -58,16 +58,21 @@ public class XPathExpression extends ExpressionSupport implements VariableContex
         this.uris = createUriMap(namespaceContext);
     }
 
-    @Override
-    public String toString() {
-        return getExpressionText();
-    }
-
-    // Expression interface
-    //-------------------------------------------------------------------------
-    @Override
-    public String getExpressionText() {
-        return this.text;
+    /**
+     * Factory method to create a synchronized Map of non-null and non-blank
+     * namespace prefixes to namespace URIs
+     */
+    protected Map createUriMap(Map namespaceContext) {
+        // now lets clone the Map but ignoring default or null prefixes
+        Map uris = new Hashtable(namespaceContext.size());
+        for (Iterator iter = namespaceContext.entrySet().iterator(); iter.hasNext(); ) {
+            Map.Entry entry = (Map.Entry) iter.next();
+            String prefix = (String) entry.getKey();
+            if (prefix != null && prefix.length() != 0) {
+                uris.put(prefix, entry.getValue());
+            }
+        }
+        return uris;
     }
 
     @Override
@@ -99,6 +104,13 @@ public class XPathExpression extends ExpressionSupport implements VariableContex
         return null;
     }
 
+    // Expression interface
+    //-------------------------------------------------------------------------
+    @Override
+    public String getExpressionText() {
+        return this.text;
+    }
+
     // VariableContext interface
     //-------------------------------------------------------------------------
     @Override
@@ -117,20 +129,8 @@ public class XPathExpression extends ExpressionSupport implements VariableContex
     // Implementation methods
     //-------------------------------------------------------------------------
 
-    /**
-     * Factory method to create a synchronized Map of non-null and non-blank
-     * namespace prefixes to namespace URIs
-     */
-    protected Map createUriMap(Map namespaceContext) {
-        // now lets clone the Map but ignoring default or null prefixes
-        Map uris = new Hashtable(namespaceContext.size());
-        for (Iterator iter = namespaceContext.entrySet().iterator(); iter.hasNext(); ) {
-            Map.Entry entry = (Map.Entry) iter.next();
-            String prefix = (String) entry.getKey();
-            if (prefix != null && prefix.length() != 0) {
-                uris.put(prefix, entry.getValue());
-            }
-        }
-        return uris;
+    @Override
+    public String toString() {
+        return getExpressionText();
     }
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/impl/Attribute.java b/core/src/main/java/org/apache/commons/jelly/impl/Attribute.java
index f3cadc1b..952e1cdf 100644
--- a/core/src/main/java/org/apache/commons/jelly/impl/Attribute.java
+++ b/core/src/main/java/org/apache/commons/jelly/impl/Attribute.java
@@ -40,19 +40,11 @@ public class Attribute {
     //-------------------------------------------------------------------------
 
     /**
-     * Returns whether this attribute is required.
-     * @return boolean
-     */
-    public boolean isRequired() {
-        return required;
-    }
-
-    /**
-     * Sets whether this attribute is required.
-     * @param required is true if this attribute is a mandatory attribute
+     * Returns the defaultValue.
+     * @return Expression
      */
-    public void setRequired(boolean required) {
-        this.required = required;
+    public Expression getDefaultValue() {
+        return defaultValue;
     }
 
     /**
@@ -64,19 +56,11 @@ public class Attribute {
     }
 
     /**
-     * Sets the name.
-     * @param name The name to set
-     */
-    public void setName(String name) {
-        this.name = name;
-    }
-
-    /**
-     * Returns the defaultValue.
-     * @return Expression
+     * Returns whether this attribute is required.
+     * @return boolean
      */
-    public Expression getDefaultValue() {
-        return defaultValue;
+    public boolean isRequired() {
+        return required;
     }
 
     /**
@@ -87,4 +71,20 @@ public class Attribute {
         this.defaultValue = defaultValue;
     }
 
+    /**
+     * Sets the name.
+     * @param name The name to set
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Sets whether this attribute is required.
+     * @param required is true if this attribute is a mandatory attribute
+     */
+    public void setRequired(boolean required) {
+        this.required = required;
+    }
+
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/impl/DefaultTagLibraryResolver.java b/core/src/main/java/org/apache/commons/jelly/impl/DefaultTagLibraryResolver.java
index f919fc8a..d2e559b1 100644
--- a/core/src/main/java/org/apache/commons/jelly/impl/DefaultTagLibraryResolver.java
+++ b/core/src/main/java/org/apache/commons/jelly/impl/DefaultTagLibraryResolver.java
@@ -59,6 +59,85 @@ public class DefaultTagLibraryResolver implements TagLibraryResolver {
     // TagLibraryResolver interface
     //-------------------------------------------------------------------------
 
+    /**
+     * Gets the class loader to be used for instantiating application objects
+     * when required.  This is determined based upon the following rules:
+     * <ul>
+     * <li>The class loader set by <code>setClassLoader()</code>, if any</li>
+     * <li>The thread context class loader, if it exists and the
+     *     <code>useContextClassLoader</code> property is set to true</li>
+     * <li>The class loader used to load the XMLParser class itself.
+     * </ul>
+     */
+    public ClassLoader getClassLoader() {
+        return ClassLoaderUtils.getClassLoader(classLoader, useContextClassLoader, getClass());
+    }
+
+    // Properties
+    //-------------------------------------------------------------------------
+
+    /**
+     * @return the DiscoverClasses instance to use to locate services.
+     *  This object is lazily created if it has not been configured.
+     */
+    public DiscoverClasses getDiscoverClasses() {
+        if ( discovery == null ) {
+            ClassLoaders loaders = ClassLoaders.getAppLoaders(TagLibrary.class, getClass(), false);
+            discovery = new DiscoverClasses(loaders);
+        }
+        return discovery;
+    }
+
+    /**
+     * Gets the boolean as to whether the context classloader should be used.
+     */
+    public boolean getUseContextClassLoader() {
+        return useContextClassLoader;
+    }
+
+    /**
+     * Instantiates the given class name. Otherwise an exception is logged
+     * and null is returned
+     */
+    protected TagLibrary loadClass(String uri, String className) {
+        try {
+            Class theClass = getClassLoader().loadClass(className);
+            if ( theClass != null ) {
+                return newInstance(uri, theClass);
+            }
+        }
+        catch (ClassNotFoundException e) {
+            log.error("Could not find the class: " + className + " when trying to resolve URI: " + uri, e);
+        }
+        return null;
+    }
+
+    /**
+     * Creates a new instance of the given TagLibrary class or
+     * return null if it could not be instantiated.
+     */
+    protected TagLibrary newInstance(String uri, Class theClass) {
+        try {
+            Object object = theClass.newInstance();
+            if (object instanceof TagLibrary) {
+                return (TagLibrary) object;
+            }
+            else {
+                log.error(
+                    "The tag library object mapped to: "
+                        + uri
+                        + " is not a TagLibrary. Object = "
+                        + object);
+            }
+        }
+        catch (Exception e) {
+            log.error(
+                "Could not instantiate instance of class: " + theClass.getName() + ". Reason: " + e,
+                e);
+        }
+        return null;
+    }
+
     /**
      * Attempts to resolve the given URI to be associated with a TagLibrary
      * otherwise null is returned to indicate no tag library could be found
@@ -106,23 +185,6 @@ public class DefaultTagLibraryResolver implements TagLibraryResolver {
         return null;
     }
 
-    // Properties
-    //-------------------------------------------------------------------------
-
-    /**
-     * Gets the class loader to be used for instantiating application objects
-     * when required.  This is determined based upon the following rules:
-     * <ul>
-     * <li>The class loader set by <code>setClassLoader()</code>, if any</li>
-     * <li>The thread context class loader, if it exists and the
-     *     <code>useContextClassLoader</code> property is set to true</li>
-     * <li>The class loader used to load the XMLParser class itself.
-     * </ul>
-     */
-    public ClassLoader getClassLoader() {
-        return ClassLoaderUtils.getClassLoader(classLoader, useContextClassLoader, getClass());
-    }
-
     /**
      * Sets the class loader to be used for instantiating application objects
      * when required.
@@ -134,11 +196,15 @@ public class DefaultTagLibraryResolver implements TagLibraryResolver {
         this.classLoader = classLoader;
     }
 
+    // Implementation methods
+    //-------------------------------------------------------------------------
+
     /**
-     * Gets the boolean as to whether the context classloader should be used.
+     * Sets the fully configured DiscoverClasses instance to be used to
+     * lookup services
      */
-    public boolean getUseContextClassLoader() {
-        return useContextClassLoader;
+    public void setDiscoverClasses(DiscoverClasses discovery) {
+        this.discovery = discovery;
     }
 
     /**
@@ -154,70 +220,4 @@ public class DefaultTagLibraryResolver implements TagLibraryResolver {
         useContextClassLoader = use;
     }
 
-    /**
-     * @return the DiscoverClasses instance to use to locate services.
-     *  This object is lazily created if it has not been configured.
-     */
-    public DiscoverClasses getDiscoverClasses() {
-        if ( discovery == null ) {
-            ClassLoaders loaders = ClassLoaders.getAppLoaders(TagLibrary.class, getClass(), false);
-            discovery = new DiscoverClasses(loaders);
-        }
-        return discovery;
-    }
-
-    /**
-     * Sets the fully configured DiscoverClasses instance to be used to
-     * lookup services
-     */
-    public void setDiscoverClasses(DiscoverClasses discovery) {
-        this.discovery = discovery;
-    }
-
-    // Implementation methods
-    //-------------------------------------------------------------------------
-
-    /**
-     * Instantiates the given class name. Otherwise an exception is logged
-     * and null is returned
-     */
-    protected TagLibrary loadClass(String uri, String className) {
-        try {
-            Class theClass = getClassLoader().loadClass(className);
-            if ( theClass != null ) {
-                return newInstance(uri, theClass);
-            }
-        }
-        catch (ClassNotFoundException e) {
-            log.error("Could not find the class: " + className + " when trying to resolve URI: " + uri, e);
-        }
-        return null;
-    }
-
-    /**
-     * Creates a new instance of the given TagLibrary class or
-     * return null if it could not be instantiated.
-     */
-    protected TagLibrary newInstance(String uri, Class theClass) {
-        try {
-            Object object = theClass.newInstance();
-            if (object instanceof TagLibrary) {
-                return (TagLibrary) object;
-            }
-            else {
-                log.error(
-                    "The tag library object mapped to: "
-                        + uri
-                        + " is not a TagLibrary. Object = "
-                        + object);
-            }
-        }
-        catch (Exception e) {
-            log.error(
-                "Could not instantiate instance of class: " + theClass.getName() + ". Reason: " + e,
-                e);
-        }
-        return null;
-    }
-
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/impl/DynamicBeanTag.java b/core/src/main/java/org/apache/commons/jelly/impl/DynamicBeanTag.java
index 5fb1557e..70a217dd 100644
--- a/core/src/main/java/org/apache/commons/jelly/impl/DynamicBeanTag.java
+++ b/core/src/main/java/org/apache/commons/jelly/impl/DynamicBeanTag.java
@@ -101,33 +101,6 @@ public class DynamicBeanTag extends DynaBeanTagSupport implements BeanSource {
         setAttributesSet.clear();
     }
 
-    @Override
-    public void setAttribute(String name, Object value) throws JellyTagException {
-        boolean isVariableName = false;
-        if (variableNameAttribute != null ) {
-            if ( variableNameAttribute.equals( name ) ) {
-                if (value == null) {
-                    var = null;
-                }
-                else {
-                    var = value.toString();
-                }
-                isVariableName = true;
-            }
-        }
-        if (! isVariableName) {
-
-            // #### strictly speaking we could
-            // know what attributes are specified at compile time
-            // so this dynamic set is unnecessary
-            setAttributesSet.add(name);
-
-            // we could maybe implement attribute specific validation here
-
-            super.setAttribute(name, value);
-        }
-    }
-
     // Tag interface
     //-------------------------------------------------------------------------
     @Override
@@ -196,6 +169,16 @@ public class DynamicBeanTag extends DynaBeanTagSupport implements BeanSource {
         }
     }
 
+    // Properties
+    //-------------------------------------------------------------------------
+    /**
+     * @return the bean that has just been created
+     */
+    @Override
+    public Object getBean() {
+        return bean;
+    }
+
     /**
      * Report the state of the bean when method invocation fails
      * so that the user can determine any problems that might
@@ -220,13 +203,30 @@ public class DynamicBeanTag extends DynaBeanTagSupport implements BeanSource {
         throw new JellyTagException(e);
     }
 
-    // Properties
-    //-------------------------------------------------------------------------
-    /**
-     * @return the bean that has just been created
-     */
     @Override
-    public Object getBean() {
-        return bean;
+    public void setAttribute(String name, Object value) throws JellyTagException {
+        boolean isVariableName = false;
+        if (variableNameAttribute != null ) {
+            if ( variableNameAttribute.equals( name ) ) {
+                if (value == null) {
+                    var = null;
+                }
+                else {
+                    var = value.toString();
+                }
+                isVariableName = true;
+            }
+        }
+        if (! isVariableName) {
+
+            // #### strictly speaking we could
+            // know what attributes are specified at compile time
+            // so this dynamic set is unnecessary
+            setAttributesSet.add(name);
+
+            // we could maybe implement attribute specific validation here
+
+            super.setAttribute(name, value);
+        }
     }
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/impl/DynamicDynaBeanTag.java b/core/src/main/java/org/apache/commons/jelly/impl/DynamicDynaBeanTag.java
index b4d71191..b8f748dd 100644
--- a/core/src/main/java/org/apache/commons/jelly/impl/DynamicDynaBeanTag.java
+++ b/core/src/main/java/org/apache/commons/jelly/impl/DynamicDynaBeanTag.java
@@ -74,33 +74,6 @@ public class DynamicDynaBeanTag extends DynaBeanTagSupport implements BeanSource
         setAttributesSet.clear();
     }
 
-    @Override
-    public void setAttribute(String name, Object value) throws JellyTagException {
-        boolean isVariableName = false;
-        if (variableNameAttribute != null ) {
-            if ( variableNameAttribute.equals( name ) ) {
-                if (value == null) {
-                    var = null;
-                }
-                else {
-                    var = value.toString();
-                }
-                isVariableName = true;
-            }
-        }
-        if (! isVariableName) {
-
-            // #### strictly speaking we could
-            // know what attributes are specified at compile time
-            // so this dynamic set is unnecessary
-            setAttributesSet.add(name);
-
-            // we could maybe implement attribute specific validation here
-
-            super.setAttribute(name, value);
-        }
-    }
-
     // Tag interface
     //-------------------------------------------------------------------------
     @Override
@@ -145,4 +118,31 @@ public class DynamicDynaBeanTag extends DynaBeanTagSupport implements BeanSource
     public Object getBean() {
         return getDynaBean();
     }
+
+    @Override
+    public void setAttribute(String name, Object value) throws JellyTagException {
+        boolean isVariableName = false;
+        if (variableNameAttribute != null ) {
+            if ( variableNameAttribute.equals( name ) ) {
+                if (value == null) {
+                    var = null;
+                }
+                else {
+                    var = value.toString();
+                }
+                isVariableName = true;
+            }
+        }
+        if (! isVariableName) {
+
+            // #### strictly speaking we could
+            // know what attributes are specified at compile time
+            // so this dynamic set is unnecessary
+            setAttributesSet.add(name);
+
+            // we could maybe implement attribute specific validation here
+
+            super.setAttribute(name, value);
+        }
+    }
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/impl/DynamicTag.java b/core/src/main/java/org/apache/commons/jelly/impl/DynamicTag.java
index 595e381e..b5c7987b 100644
--- a/core/src/main/java/org/apache/commons/jelly/impl/DynamicTag.java
+++ b/core/src/main/java/org/apache/commons/jelly/impl/DynamicTag.java
@@ -77,14 +77,6 @@ public class DynamicTag extends DynaTagSupport {
         getTemplate().run(newJellyContext, output);
     }
 
-    // DynaTag interface
-    //-------------------------------------------------------------------------
-    @Override
-    public void setAttribute(String name, Object value) {
-        attributes.put(name, value);
-        attributes.put(name + "Attr", value);
-    }
-
     // Properties
     //-------------------------------------------------------------------------
     /** The template to be executed by this tag which may well
@@ -94,6 +86,14 @@ public class DynamicTag extends DynaTagSupport {
         return template;
     }
 
+    // DynaTag interface
+    //-------------------------------------------------------------------------
+    @Override
+    public void setAttribute(String name, Object value) {
+        attributes.put(name, value);
+        attributes.put(name + "Attr", value);
+    }
+
     public void setTemplate(Script template) {
         this.template = template;
     }
diff --git a/core/src/main/java/org/apache/commons/jelly/impl/DynamicTagLibrary.java b/core/src/main/java/org/apache/commons/jelly/impl/DynamicTagLibrary.java
index 7770104d..f8dacae8 100644
--- a/core/src/main/java/org/apache/commons/jelly/impl/DynamicTagLibrary.java
+++ b/core/src/main/java/org/apache/commons/jelly/impl/DynamicTagLibrary.java
@@ -42,21 +42,6 @@ public class DynamicTagLibrary extends TagLibrary {
         this.uri = uri;
     }
 
-    /** Creates a new script to execute the given tag name and attributes */
-    @Override
-    public TagScript createTagScript(final String name, final Attributes attributes)
-        throws JellyException {
-
-        return new TagScript(
-            new TagFactory() {
-                @Override
-                public Tag createTag(String name, Attributes attributes) throws JellyException {
-                    return DynamicTagLibrary.this.createTag(name, attributes);
-                }
-            }
-        );
-    }
-
     /** Creates a new Tag for the given tag name if it exists */
     @Override
     public Tag createTag(String name, Attributes attributes)
@@ -79,30 +64,19 @@ public class DynamicTagLibrary extends TagLibrary {
         return null;
     }
 
-    /**
-     * Creates a new tag with the given name and template
-     */
-    public void registerDynamicTag(String name, Script template) {
-        templates.put(name, template);
-    }
-
-    /**
-     * Creates a new Jelly Bean Tag with the given name
-     */
-    public void registerBeanTag(String name, TagFactory factory) {
-        templates.put(name, factory);
-    }
+    /** Creates a new script to execute the given tag name and attributes */
+    @Override
+    public TagScript createTagScript(final String name, final Attributes attributes)
+        throws JellyException {
 
-    /**
-     * Returns the script associated with the given tag name
-     *
-     * @param name The tag name
-     * @return The script associated with <code>name</code>, or
-     *         {@code null} if the tag doesn't exist or isn't a script
-     */
-    public Script getDynamicTag(String name) {
-        Object result = templates.get(name);
-        return (result instanceof Script) ? (Script) result : null;
+        return new TagScript(
+            new TagFactory() {
+                @Override
+                public Tag createTag(String name, Attributes attributes) throws JellyException {
+                    return DynamicTagLibrary.this.createTag(name, attributes);
+                }
+            }
+        );
     }
 
     /**
@@ -126,22 +100,44 @@ public class DynamicTagLibrary extends TagLibrary {
         return result;
     }
 
+    /**
+     * Returns the script associated with the given tag name
+     *
+     * @param name The tag name
+     * @return The script associated with <code>name</code>, or
+     *         {@code null} if the tag doesn't exist or isn't a script
+     */
+    public Script getDynamicTag(String name) {
+        Object result = templates.get(name);
+        return (result instanceof Script) ? (Script) result : null;
+    }
+
+    /**
+     * Returns the parent library which will be used to resolve unknown tags.
+     * @return TagLibrary
+     */
+    public TagLibrary getParent() {
+        return parent;
+    }
+
     // Properties
     //-------------------------------------------------------------------------
     public String getUri() {
         return uri;
     }
 
-    public void setUri(String uri) {
-        this.uri = uri;
+    /**
+     * Creates a new Jelly Bean Tag with the given name
+     */
+    public void registerBeanTag(String name, TagFactory factory) {
+        templates.put(name, factory);
     }
 
     /**
-     * Returns the parent library which will be used to resolve unknown tags.
-     * @return TagLibrary
+     * Creates a new tag with the given name and template
      */
-    public TagLibrary getParent() {
-        return parent;
+    public void registerDynamicTag(String name, Script template) {
+        templates.put(name, template);
     }
 
     /**
@@ -152,4 +148,8 @@ public class DynamicTagLibrary extends TagLibrary {
         this.parent = parent;
     }
 
+    public void setUri(String uri) {
+        this.uri = uri;
+    }
+
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/impl/Embedded.java b/core/src/main/java/org/apache/commons/jelly/impl/Embedded.java
index c4188b58..87d85ea6 100644
--- a/core/src/main/java/org/apache/commons/jelly/impl/Embedded.java
+++ b/core/src/main/java/org/apache/commons/jelly/impl/Embedded.java
@@ -56,6 +56,8 @@ import org.xml.sax.SAXException;
  *  </pre>
  */
 public class Embedded {
+    /** The Log to which logging calls will be made. */
+    private static final Log log = LogFactory.getLog(Embedded.class);
     /** Jelly Engine */
     Jelly jellyEngine = new Jelly();
     /** JellyContext*/
@@ -75,8 +77,6 @@ public class Embedded {
     boolean scriptCompiled = false;
     /** ErrorMsg*/
     private String errorMsg;
-    /** The Log to which logging calls will be made. */
-    private static final Log log = LogFactory.getLog(Embedded.class);
 
     /**
      * Default Constructor
@@ -86,74 +86,6 @@ public class Embedded {
         //m_context.setClassLoader(new TagLibraryClassLoader(m_context));
     }
 
-    /**
-     * Method setContext.
-     * @param context
-     */
-    public void setContext(JellyContext context) {
-        this.context = context;
-    }
-
-    /**
-     * Method getContext.
-     * @return JellyContext
-     */
-    public JellyContext getContext() {
-        return context;
-    }
-
-    /**
-     * Sets a new variable within the context for the script to use.
-     * @param name
-     * @param value
-     */
-    public void setVariable(String name, Object value) {
-        context.setVariable(name, value);
-    }
-
-    /**
-     * Sets the input script
-     * @param scriptAsString
-     */
-    public void setScript(String scriptAsString) {
-
-        try {
-            URL url = resolveURL(scriptAsString);
-            inputStream = url.openStream();
-        }
-        catch (MalformedURLException e) {
-            //Encapsulate the string within
-            inputStream = new ByteArrayInputStream(scriptAsString.getBytes());
-        }
-        catch (IOException e) {
-            //Error reading from the URL
-            inputStream = null;
-        }
-
-        compileScriptAndKeep();
-
-    }
-
-    /**
-     * @return the URL for the relative file name or absolute URL
-     */
-    private URL resolveURL(String name) throws MalformedURLException {
-        File file = new File(name);
-        if (file.exists()) {
-            return file.toURL();
-        }
-        return new URL(name);
-    }
-
-    /**
-     * Sets the input stream
-     * @param scriptAsInputStream
-     */
-    public void setScript(InputStream scriptAsInputStream) {
-        inputStream = scriptAsInputStream;
-        compileScriptAndKeep();
-    }
-
     /**
      * Compile the script
      */
@@ -178,12 +110,54 @@ public class Embedded {
     }
 
     /**
-     * Method setOutputStream.
-     * @param outputStream
+     * Execute the jelly script and capture the errors (ifany) within.
      */
-    public void setOutputStream(OutputStream outputStream) {
-        this.outputStream = outputStream;
-        this.output = XMLOutput.createXMLOutput(new OutputStreamWriter(this.outputStream));
+    public boolean execute() {
+        if (log.isDebugEnabled())
+            log.debug("Starting Execution");
+        //If script has not been compiled then return the errorMsg that occurredd during compilation
+        if (!scriptCompiled) {
+            if (log.isErrorEnabled())
+                log.error(scriptCompilationException.getMessage());
+            setErrorMsg(scriptCompilationException.getMessage());
+            return false;
+        }
+        if (inputStream == null) {
+            if (log.isErrorEnabled())
+                log.error("[Error] Input script-resource NOT accessible");
+            setErrorMsg("[Error] Input script-resource NOT accessible");
+            return false;
+        }
+        try {
+            script.run(context, output);
+            outputStream.close();
+            output.flush();
+        }
+        catch (Exception e) {
+            if (log.isErrorEnabled())
+                log.error(e.getMessage());
+            setErrorMsg(e.getMessage());
+            return false;
+        }
+        if (log.isDebugEnabled())
+            log.debug("Done Executing");
+        return true;
+    }
+
+    /**
+     * Method getContext.
+     * @return JellyContext
+     */
+    public JellyContext getContext() {
+        return context;
+    }
+
+    /**
+     * Returns the errorMsg.
+     * @return String
+     */
+    public String getErrorMsg() {
+        return errorMsg;
     }
 
     /**
@@ -206,11 +180,22 @@ public class Embedded {
     }
 
     /**
-     * Returns the errorMsg.
-     * @return String
+     * @return the URL for the relative file name or absolute URL
      */
-    public String getErrorMsg() {
-        return errorMsg;
+    private URL resolveURL(String name) throws MalformedURLException {
+        File file = new File(name);
+        if (file.exists()) {
+            return file.toURL();
+        }
+        return new URL(name);
+    }
+
+    /**
+     * Method setContext.
+     * @param context
+     */
+    public void setContext(JellyContext context) {
+        this.context = context;
     }
 
     /**
@@ -222,38 +207,53 @@ public class Embedded {
     }
 
     /**
-     * Execute the jelly script and capture the errors (ifany) within.
+     * Method setOutputStream.
+     * @param outputStream
      */
-    public boolean execute() {
-        if (log.isDebugEnabled())
-            log.debug("Starting Execution");
-        //If script has not been compiled then return the errorMsg that occurredd during compilation
-        if (!scriptCompiled) {
-            if (log.isErrorEnabled())
-                log.error(scriptCompilationException.getMessage());
-            setErrorMsg(scriptCompilationException.getMessage());
-            return false;
-        }
-        if (inputStream == null) {
-            if (log.isErrorEnabled())
-                log.error("[Error] Input script-resource NOT accessible");
-            setErrorMsg("[Error] Input script-resource NOT accessible");
-            return false;
-        }
+    public void setOutputStream(OutputStream outputStream) {
+        this.outputStream = outputStream;
+        this.output = XMLOutput.createXMLOutput(new OutputStreamWriter(this.outputStream));
+    }
+
+    /**
+     * Sets the input stream
+     * @param scriptAsInputStream
+     */
+    public void setScript(InputStream scriptAsInputStream) {
+        inputStream = scriptAsInputStream;
+        compileScriptAndKeep();
+    }
+
+    /**
+     * Sets the input script
+     * @param scriptAsString
+     */
+    public void setScript(String scriptAsString) {
+
         try {
-            script.run(context, output);
-            outputStream.close();
-            output.flush();
+            URL url = resolveURL(scriptAsString);
+            inputStream = url.openStream();
         }
-        catch (Exception e) {
-            if (log.isErrorEnabled())
-                log.error(e.getMessage());
-            setErrorMsg(e.getMessage());
-            return false;
+        catch (MalformedURLException e) {
+            //Encapsulate the string within
+            inputStream = new ByteArrayInputStream(scriptAsString.getBytes());
         }
-        if (log.isDebugEnabled())
-            log.debug("Done Executing");
-        return true;
+        catch (IOException e) {
+            //Error reading from the URL
+            inputStream = null;
+        }
+
+        compileScriptAndKeep();
+
+    }
+
+    /**
+     * Sets a new variable within the context for the script to use.
+     * @param name
+     * @param value
+     */
+    public void setVariable(String name, Object value) {
+        context.setVariable(name, value);
     }
 
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/impl/ExpressionScript.java b/core/src/main/java/org/apache/commons/jelly/impl/ExpressionScript.java
index a2e61687..3562a945 100644
--- a/core/src/main/java/org/apache/commons/jelly/impl/ExpressionScript.java
+++ b/core/src/main/java/org/apache/commons/jelly/impl/ExpressionScript.java
@@ -38,9 +38,11 @@ public class ExpressionScript implements Script {
         this.expression = expression;
     }
 
+    // Script interface
+    //-------------------------------------------------------------------------
     @Override
-    public String toString() {
-        return super.toString() + "[expression=" + expression + "]";
+    public Script compile() {
+        return this;
     }
 
     /** @return the expression evaluated as a String and output by this script */
@@ -48,18 +50,6 @@ public class ExpressionScript implements Script {
         return expression;
     }
 
-    /** Sets the expression evaluated as a String and output by this script */
-    public void setExpression(Expression expression) {
-        this.expression = expression;
-    }
-
-    // Script interface
-    //-------------------------------------------------------------------------
-    @Override
-    public Script compile() {
-        return this;
-    }
-
     /** Evaluates the body of a tag */
     @Override
     public void run(JellyContext context, XMLOutput output) throws JellyTagException {
@@ -74,4 +64,14 @@ public class ExpressionScript implements Script {
 
         }
     }
+
+    /** Sets the expression evaluated as a String and output by this script */
+    public void setExpression(Expression expression) {
+        this.expression = expression;
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + "[expression=" + expression + "]";
+    }
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/impl/ScriptBlock.java b/core/src/main/java/org/apache/commons/jelly/impl/ScriptBlock.java
index 6441ecea..713f92cf 100644
--- a/core/src/main/java/org/apache/commons/jelly/impl/ScriptBlock.java
+++ b/core/src/main/java/org/apache/commons/jelly/impl/ScriptBlock.java
@@ -39,32 +39,11 @@ public class ScriptBlock implements Script {
     public ScriptBlock() {
     }
 
-    /**
-     * @see Object#toString()
-     */
-    @Override
-    public String toString() {
-        return super.toString() + "[scripts=" + list + "]";
-    }
-
     /** Add a new script to the end of this block */
     public void addScript(Script script) {
         list.add(script);
     }
 
-    /** Removes a script from this block */
-    public void removeScript(Script script) {
-        list.remove(script);
-    }
-
-    /**
-     * Gets the child scripts that make up this block. This list is live
-     * so that it can be modified if required
-     */
-    public List getScriptList() {
-        return list;
-    }
-
     // Script interface
     //-------------------------------------------------------------------------
     @Override
@@ -82,6 +61,19 @@ public class ScriptBlock implements Script {
         return this;
     }
 
+    /**
+     * Gets the child scripts that make up this block. This list is live
+     * so that it can be modified if required
+     */
+    public List getScriptList() {
+        return list;
+    }
+
+    /** Removes a script from this block */
+    public void removeScript(Script script) {
+        list.remove(script);
+    }
+
     /** Evaluates the body of a tag */
     @Override
     public void run(JellyContext context, XMLOutput output) throws JellyTagException {
@@ -96,6 +88,14 @@ public class ScriptBlock implements Script {
             script.run(context, output);
         }
     }
+
+    /**
+     * @see Object#toString()
+     */
+    @Override
+    public String toString() {
+        return super.toString() + "[scripts=" + list + "]";
+    }
     
     /**
      * Trim the body of the script.
diff --git a/core/src/main/java/org/apache/commons/jelly/impl/StaticTag.java b/core/src/main/java/org/apache/commons/jelly/impl/StaticTag.java
index b30f19be..0cb0bf4e 100644
--- a/core/src/main/java/org/apache/commons/jelly/impl/StaticTag.java
+++ b/core/src/main/java/org/apache/commons/jelly/impl/StaticTag.java
@@ -50,11 +50,6 @@ public class StaticTag extends DynaTagSupport {
         this.qualifiedName = qualifiedName;
     }
 
-    @Override
-    public String toString() {
-        return super.toString() + "[qname=" + qualifiedName + ";attributes=" + attributes + "]";
-    }
-
     // Tag interface
     //-------------------------------------------------------------------------
     @Override
@@ -70,13 +65,18 @@ public class StaticTag extends DynaTagSupport {
         }
     }
 
-    public void setAttribute(String name, String prefix, String nsURI, Object value) {
-        if (value == null)
-            return;
-        if (prefix != null && prefix.length() > 0)
-            attributes.addAttribute(nsURI, name, prefix + ":" + name, "CDATA", value.toString());
-        else
-            attributes.addAttribute("", name, name, "CDATA", value.toString());
+    public String getLocalName() {
+        return localName;
+    }
+
+    public String getQName() {
+        return qualifiedName;
+    }
+
+    // Properties
+    //-------------------------------------------------------------------------
+    public String getUri() {
+        return uri;
     }
 
     // DynaTag interface
@@ -96,18 +96,21 @@ public class StaticTag extends DynaTagSupport {
         }
     }
 
-    // Properties
-    //-------------------------------------------------------------------------
-    public String getUri() {
-        return uri;
-    }
-
-    public void setUri(String uri) {
-        this.uri = uri;
+    public void setAttribute(String name, String prefix, String nsURI, Object value) {
+        if (value == null)
+            return;
+        if (prefix != null && prefix.length() > 0)
+            attributes.addAttribute(nsURI, name, prefix + ":" + name, "CDATA", value.toString());
+        else
+            attributes.addAttribute("", name, name, "CDATA", value.toString());
     }
 
-    public String getQName() {
-        return qualifiedName;
+    public void setLocalName(String localName) {
+        this.localName = localName;
+        // FIXME This just doesn't seem right or work...
+        if (qualifiedName == null || !qualifiedName.endsWith(localName)) {
+            localName = qualifiedName;
+        }
     }
 
     public void setQName(String qualifiedName) {
@@ -121,15 +124,12 @@ public class StaticTag extends DynaTagSupport {
         }
     }
 
-    public String getLocalName() {
-        return localName;
+    public void setUri(String uri) {
+        this.uri = uri;
     }
 
-    public void setLocalName(String localName) {
-        this.localName = localName;
-        // FIXME This just doesn't seem right or work...
-        if (qualifiedName == null || !qualifiedName.endsWith(localName)) {
-            localName = qualifiedName;
-        }
+    @Override
+    public String toString() {
+        return super.toString() + "[qname=" + qualifiedName + ";attributes=" + attributes + "]";
     }
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/impl/StaticTagScript.java b/core/src/main/java/org/apache/commons/jelly/impl/StaticTagScript.java
index 6d838a69..49aca4b3 100644
--- a/core/src/main/java/org/apache/commons/jelly/impl/StaticTagScript.java
+++ b/core/src/main/java/org/apache/commons/jelly/impl/StaticTagScript.java
@@ -45,6 +45,24 @@ public class StaticTagScript extends TagScript {
         super(tagFactory);
     }
 
+    /**
+     * Attempts to find a dynamically created tag that has been created since this
+     * script was compiled
+     */
+    protected Tag findDynamicTag(JellyContext context, StaticTag tag) throws JellyException {
+        // lets see if there's a tag library for this URI...
+        TagLibrary taglib = context.getTagLibrary( tag.getUri() );
+        if ( taglib != null ) {
+            Tag newTag = taglib.createTag( tag.getLocalName(), getSaxAttributes() );
+            if ( newTag != null ) {
+                newTag.setParent( tag.getParent() );
+                newTag.setBody( tag.getBody() );
+                return newTag;
+            }
+        }
+        return tag;
+    }
+
     // Script interface
     //-------------------------------------------------------------------------
     @Override
@@ -120,22 +138,4 @@ public class StaticTagScript extends TagScript {
             throw new JellyTagException("could not end namespace prefixes", e);
         }
     }
-
-    /**
-     * Attempts to find a dynamically created tag that has been created since this
-     * script was compiled
-     */
-    protected Tag findDynamicTag(JellyContext context, StaticTag tag) throws JellyException {
-        // lets see if there's a tag library for this URI...
-        TagLibrary taglib = context.getTagLibrary( tag.getUri() );
-        if ( taglib != null ) {
-            Tag newTag = taglib.createTag( tag.getLocalName(), getSaxAttributes() );
-            if ( newTag != null ) {
-                newTag.setParent( tag.getParent() );
-                newTag.setBody( tag.getBody() );
-                return newTag;
-            }
-        }
-        return tag;
-    }
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/impl/TagScript.java b/core/src/main/java/org/apache/commons/jelly/impl/TagScript.java
index ce54fd4e..34f32574 100644
--- a/core/src/main/java/org/apache/commons/jelly/impl/TagScript.java
+++ b/core/src/main/java/org/apache/commons/jelly/impl/TagScript.java
@@ -48,6 +48,23 @@ import org.xml.sax.Attributes;
 import org.xml.sax.Locator;
 import org.xml.sax.SAXException;
 
+final class ExpressionAttribute {
+    String name;
+    String prefix;
+
+    String nsURI;
+    Expression exp;
+    public ExpressionAttribute(String name, Expression exp) {
+        this(name, "", "", exp);
+    }
+    public ExpressionAttribute(String name, String prefix, String nsURI, Expression exp) {
+        this.name = name;
+        this.prefix = prefix;
+        this.nsURI = nsURI;
+        this.exp = exp;
+    }
+}
+
 /**
  * <p><code>TagScript</code> is a Script that evaluates a custom tag.</p>
  *
@@ -59,6 +76,15 @@ public class TagScript implements Script {
     /** The Log to which logging calls will be made. */
     private static final Log log = LogFactory.getLog(TagScript.class);
 
+    /**
+     * @return a new TagScript based on whether
+     * the given Tag class is a bean tag or DynaTag
+     */
+    public static TagScript newInstance(Class tagClass) {
+        TagFactory factory = new DefaultTagFactory(tagClass);
+        return new TagScript(factory);
+    }
+
     /** The attribute expressions that are created */
     protected Map attributes = new Hashtable();
 
@@ -83,10 +109,10 @@ public class TagScript implements Script {
 
     /** The line number of the tag */
     private int lineNumber = -1;
-
+    
     /** The column number of the tag */
     private int columnNumber = -1;
-    
+
     /** The TagLibrary that we belong to */
     private TagLibrary tagLibrary;
 
@@ -98,26 +124,17 @@ public class TagScript implements Script {
 
     /** The parent TagScript */
     private TagScript parent;
-
+    
     /** The SAX attributes */
     private Attributes saxAttributes;
     
     /** The url of the script when parsed */
     private URL scriptURL = null;
-    
+
     /** A synchronized WeakHashMap from the current Thread (key) to a Tag object (value).
      */
     private Map threadLocalTagCache = Collections.synchronizedMap(new WeakHashMap());
 
-    /**
-     * @return a new TagScript based on whether
-     * the given Tag class is a bean tag or DynaTag
-     */
-    public static TagScript newInstance(Class tagClass) {
-        TagFactory factory = new DefaultTagFactory(tagClass);
-        return new TagScript(factory);
-    }
-
     public TagScript() {
     }
 
@@ -125,43 +142,6 @@ public class TagScript implements Script {
         this.tagFactory = tagFactory;
     }
 
-    @Override
-    public String toString() {
-        return super.toString() + "[tag=" + elementName + ";at=" + lineNumber + ":" + columnNumber + "]";
-    }
-
-    /**
-     * Compiles the tags body
-     */
-    @Override
-    public Script compile() throws JellyException {
-        if (tagBody != null) {
-            tagBody = tagBody.compile();
-        }
-        return this;
-    }
-
-    /**
-     * Sets the optional namespaces prefix to URI map of
-     * the namespaces attached to this Tag
-     */
-    public void setTagNamespacesMap(Map tagNamespacesMap) {
-        // lets check that this is a thread-safe map
-        if ( ! (tagNamespacesMap instanceof Hashtable) ) {
-            tagNamespacesMap = new Hashtable( tagNamespacesMap );
-        }
-        this.tagNamespacesMap = tagNamespacesMap;
-    }
-
-    /**
-     * Configures this TagScript from the SAX Locator, setting the column
-     * and line numbers
-     */
-    public void setLocator(Locator locator) {
-        setLineNumber( locator.getLineNumber() );
-        setColumnNumber( locator.getColumnNumber() );
-    }
-
     /** Add an initialization attribute for the tag.
      * This method must be called after the setTag() method
      */
@@ -184,236 +164,171 @@ public class TagScript implements Script {
         attributes.put(name, new ExpressionAttribute(name, prefix, nsURI, expression));
     }
 
+    protected void applyLocation(LocationAware locationAware) {
+        if (locationAware.getLineNumber() == -1) {
+            locationAware.setColumnNumber(columnNumber);
+            locationAware.setLineNumber(lineNumber);
+        }
+        if ( locationAware.getFileName() == null ) {
+            locationAware.setFileName( fileName );
+        }
+        if ( locationAware.getElementName() == null ) {
+            locationAware.setElementName( elementName );
+        }
+    }
+
     /**
-     * Strips off the name of a script to create a new context URL
-     * FIXME: Copied from JellyContext
+     * Flushes the current cached tag so that it will be created, lazily, next invocation
      */
-    private URL getJellyContextURL(URL url) throws MalformedURLException {
-        String text = url.toString();
-        int idx = text.lastIndexOf('/');
-        text = text.substring(0, idx + 1);
-        return new URL(text);
+    protected void clearTag() {
+        Thread t = Thread.currentThread();
+        threadLocalTagCache.put(t, null);
     }
 
-    // Script interface
-    //-------------------------------------------------------------------------
-
-    /** Evaluates the body of a tag */
+    /**
+     * Compiles the tags body
+     */
     @Override
-    public void run(JellyContext context, XMLOutput output) throws JellyTagException {
-        URL rootURL = context.getRootURL();
-        URL currentURL = context.getCurrentURL();
-        if ( ! context.isCacheTags() ) {
-            clearTag();
+    public Script compile() throws JellyException {
+        if (tagBody != null) {
+            tagBody = tagBody.compile();
         }
-        try {
-            Tag tag = getTag(context);
-            if ( tag == null ) {
-                return;
-            }
-            tag.setContext(context);
-            setContextURLs(context);
-
-            if ( tag instanceof DynaTag ) {
-                DynaTag dynaTag = (DynaTag) tag;
-
-                // ### probably compiling this to 2 arrays might be quicker and smaller
-                for (Iterator iter = attributes.entrySet().iterator(); iter.hasNext();) {
-                    Map.Entry entry = (Map.Entry) iter.next();
-                    String name = (String) entry.getKey();
-                    Expression expression = ((ExpressionAttribute) entry.getValue()).exp;
-
-                    Class type = dynaTag.getAttributeType(name);
-                    Object value = null;
-                    if (type != null && type.isAssignableFrom(Expression.class) && !type.isAssignableFrom(Object.class)) {
-                        value = expression;
-                    }
-                    else {
-                        value = expression.evaluateRecurse(context);
-                    }
-                    dynaTag.setAttribute(name, value);
-                }
-            }
-            else {
-                // treat the tag as a bean
-                DynaBean dynaBean = new ConvertingWrapDynaBean( tag );
-                for (Iterator iter = attributes.entrySet().iterator(); iter.hasNext();) {
-                    Map.Entry entry = (Map.Entry) iter.next();
-                    String name = (String) entry.getKey();
-                    Expression expression = ((ExpressionAttribute) entry.getValue()).exp;
-
-                    DynaProperty property = dynaBean.getDynaClass().getDynaProperty(name);
-                    if (property == null) {
-                        throw new JellyException("This tag does not understand the '" + name + "' attribute" );
-                    }
-                    Class type = property.getType();
-
-                    Object value = null;
-                    if (type.isAssignableFrom(Expression.class) && !type.isAssignableFrom(Object.class)) {
-                        value = expression;
-                    }
-                    else {
-                        value = expression.evaluateRecurse(context);
-                    }
-                    dynaBean.set(name, value);
-                }
-            }
+        return this;
+    }
 
-            tag.doTag(output);
-            if (output != null) {
-                output.flush();
-            }
-        }
-        catch (JellyTagException e) {
-            handleException(e);
-        } catch (JellyException e) {
-            handleException(e);
-        } catch (IOException e) {
-            handleException(e);
-        } catch (RuntimeException e) {
-            handleException(e);
+    /**
+     * Compiles a newly created tag if required, sets its parent and body.
+     */
+    protected void configureTag(Tag tag, JellyContext context) throws JellyException {
+    	tag.setTagLib(tagLibrary);
+        if (tag instanceof CompilableTag) {
+            ((CompilableTag) tag).compile();
         }
-        catch (Error e) {
-           /*
-            * Not sure if we should be converting errors to exceptions,
-            * but not trivial to remove because JUnit tags throw
-            * Errors in the normal course of operation.  Hmm...
-            */
-            handleException(e);
-        } finally {
-            context.setRootURL(rootURL);
-            context.setCurrentURL(currentURL);
+        Tag parentTag = null;
+        if ( parent != null ) {
+            parentTag = parent.getTag(context);
         }
+        tag.setParent( parentTag );
+        tag.setBody( tagBody );
 
+        if (tag instanceof NamespaceAwareTag) {
+            NamespaceAwareTag naTag = (NamespaceAwareTag) tag;
+            naTag.setNamespaceContext(getNamespaceContext());
+        }
+        if (tag instanceof LocationAware) {
+            applyLocation((LocationAware) tag);
+        }
     }
 
     /**
-     * Sets the context's root and current URL if not present
-     * @param context
-     * @throws JellyTagException
+     * Converts the given value to the required type.
+     *
+     * @param value is the value to be converted. This will not be null
+     * @param requiredType the type that the value should be converted to
      */
-    protected void setContextURLs(JellyContext context) throws JellyTagException {
-        if ((context.getCurrentURL() == null || context.getRootURL() == null) && scriptURL != null)
-        {
-            if (context.getRootURL() == null) context.setRootURL(scriptURL);
-            if (context.getCurrentURL() == null) context.setCurrentURL(scriptURL);
+    protected Object convertType(Object value, Class requiredType)
+        throws JellyException {
+        if (requiredType.isInstance(value)) {
+            return value;
+        }
+        if (value instanceof String) {
+            return ConvertUtils.convert((String) value, requiredType);
         }
+        return value;
     }
 
-    // Properties
+    // Script interface
     //-------------------------------------------------------------------------
 
     /**
-     * @return the tag to be evaluated, creating it lazily if required.
+     * Creates a new Jelly exception, adorning it with location information
      */
-    public Tag getTag(JellyContext context) throws JellyException {
-        Thread t = Thread.currentThread();
-        Tag tag = (Tag) threadLocalTagCache.get(t);
-        if (tag == null) {
-            tag = createTag();
-            if (tag != null) {
-                threadLocalTagCache.put(t, tag);
-                configureTag(tag, context);
-            }
-        }
-        return tag;
+    protected JellyException createJellyException(String reason) {
+        return new JellyException(
+            reason, fileName, elementName, columnNumber, lineNumber
+        );
     }
 
     /**
-     * Returns the Factory of Tag instances.
-     * @return the factory
+     * Creates a new Jelly exception, adorning it with location information
      */
-    public TagFactory getTagFactory() {
-        return tagFactory;
-    }
+    protected JellyException createJellyException(String reason, Exception cause) {
+        if (cause instanceof JellyException) {
+            return (JellyException) cause;
+        }
 
-    /**
-     * Sets the Factory of Tag instances.
-     * @param tagFactory The factory to set
-     */
-    public void setTagFactory(TagFactory tagFactory) {
-        this.tagFactory = tagFactory;
+        if (cause instanceof InvocationTargetException) {
+            return new JellyException(
+                reason,
+                ((InvocationTargetException) cause).getTargetException(),
+                fileName,
+                elementName,
+                columnNumber,
+                lineNumber);
+        }
+        return new JellyException(
+            reason, cause, fileName, elementName, columnNumber, lineNumber
+        );
     }
 
-    /**
-	 * @return the tagLibrary
-	 */
-	public TagLibrary getTagLibrary() {
-		return tagLibrary;
-	}
-
-	/**
-	 * @param tagLibrary the tagLibrary to set
-	 */
-	public void setTagLibrary(TagLibrary tagLibrary) {
-		this.tagLibrary = tagLibrary;
-	}
+    // Properties
+    //-------------------------------------------------------------------------
 
-	/**
-     * Returns the parent.
-     * @return TagScript
+    /**
+     * Factory method to create a new Tag instance.
+     * The default implementation is to delegate to the TagFactory
      */
-    public TagScript getParent() {
-        return parent;
+    protected Tag createTag() throws JellyException {
+        if ( tagFactory != null) {
+            return tagFactory.createTag(localName, getSaxAttributes());
+        }
+        return null;
     }
 
     /**
-     * Returns the tagBody.
-     * @return Script
+     * End the new namespace prefixes mapped for the current element
      */
-    public Script getTagBody() {
-        return tagBody;
+    protected void endNamespacePrefixes(XMLOutput output) throws SAXException {
+        if ( tagNamespacesMap != null ) {
+            for ( Iterator iter = tagNamespacesMap.keySet().iterator(); iter.hasNext(); ) {
+                String prefix = (String) iter.next();
+                output.endPrefixMapping(prefix);
+            }
+        }
     }
 
     /**
-     * Sets the parent.
-     * @param parent The parent to set
+     * @return the column number of the tag
      */
-    public void setParent(TagScript parent) {
-        this.parent = parent;
+    public int getColumnNumber() {
+        return columnNumber;
     }
 
     /**
-     * Sets the tagBody.
-     * @param tagBody The tagBody to set
+     * @return the element name which caused the problem
      */
-    public void setTagBody(Script tagBody) {
-        this.tagBody = tagBody;
+    public String getElementName() {
+        return elementName;
     }
 
-    /**
+	/**
      * @return the Jelly file which caused the problem
      */
     public String getFileName() {
         return fileName;
     }
 
-    /**
-     * Sets the Jelly file which caused the problem
-     */
-    public void setFileName(String fileName) {
-        this.fileName = fileName;
-        try
-        {
-            this.scriptURL = getJellyContextURL(new URL(fileName));
-        } catch (MalformedURLException e) {
-            log.debug("error setting script url", e);
-        }
-    }
-
-    /**
-     * @return the element name which caused the problem
+	/**
+     * Strips off the name of a script to create a new context URL
+     * FIXME: Copied from JellyContext
      */
-    public String getElementName() {
-        return elementName;
+    private URL getJellyContextURL(URL url) throws MalformedURLException {
+        String text = url.toString();
+        int idx = text.lastIndexOf('/');
+        text = text.substring(0, idx + 1);
+        return new URL(text);
     }
 
-    /**
-     * Sets the element name which caused the problem
-     */
-    public void setElementName(String elementName) {
-        this.elementName = elementName;
-    }
     /**
      * @return the line number of the tag
      */
@@ -421,43 +336,6 @@ public class TagScript implements Script {
         return lineNumber;
     }
 
-    /**
-     * Sets the line number of the tag
-     */
-    public void setLineNumber(int lineNumber) {
-        this.lineNumber = lineNumber;
-    }
-
-    /**
-     * @return the column number of the tag
-     */
-    public int getColumnNumber() {
-        return columnNumber;
-    }
-
-    /**
-     * Sets the column number of the tag
-     */
-    public void setColumnNumber(int columnNumber) {
-        this.columnNumber = columnNumber;
-    }
-
-    /**
-     * Returns the SAX attributes of this tag
-     * @return Attributes
-     */
-    public Attributes getSaxAttributes() {
-        return saxAttributes;
-    }
-
-    /**
-     * Sets the SAX attributes of this tag
-     * @param saxAttributes The saxAttributes to set
-     */
-    public void setSaxAttributes(Attributes saxAttributes) {
-        this.saxAttributes = saxAttributes;
-    }
-
     /**
      * Returns the local, non namespaced XML name of this tag
      * @return String
@@ -466,14 +344,6 @@ public class TagScript implements Script {
         return localName;
     }
 
-    /**
-     * Sets the local, non namespaced name of this tag.
-     * @param localName The localName to set
-     */
-    public void setLocalName(String localName) {
-        this.localName = localName;
-    }
-
     /**
      * Returns the namespace context of this tag. This is all the prefixes
      * in scope in the document where this tag is used which are mapped to
@@ -504,134 +374,106 @@ public class TagScript implements Script {
         return namespaceContext;
     }
 
-    // Implementation methods
-    //-------------------------------------------------------------------------
-
     /**
-     * Factory method to create a new Tag instance.
-     * The default implementation is to delegate to the TagFactory
+     * Returns the parent.
+     * @return TagScript
      */
-    protected Tag createTag() throws JellyException {
-        if ( tagFactory != null) {
-            return tagFactory.createTag(localName, getSaxAttributes());
-        }
-        return null;
+    public TagScript getParent() {
+        return parent;
     }
 
-	
     /**
-     * Compiles a newly created tag if required, sets its parent and body.
+     * Returns the SAX attributes of this tag
+     * @return Attributes
      */
-    protected void configureTag(Tag tag, JellyContext context) throws JellyException {
-    	tag.setTagLib(tagLibrary);
-        if (tag instanceof CompilableTag) {
-            ((CompilableTag) tag).compile();
-        }
-        Tag parentTag = null;
-        if ( parent != null ) {
-            parentTag = parent.getTag(context);
-        }
-        tag.setParent( parentTag );
-        tag.setBody( tagBody );
-
-        if (tag instanceof NamespaceAwareTag) {
-            NamespaceAwareTag naTag = (NamespaceAwareTag) tag;
-            naTag.setNamespaceContext(getNamespaceContext());
-        }
-        if (tag instanceof LocationAware) {
-            applyLocation((LocationAware) tag);
-        }
+    public Attributes getSaxAttributes() {
+        return saxAttributes;
     }
 
     /**
-     * Flushes the current cached tag so that it will be created, lazily, next invocation
+     * @return the tag to be evaluated, creating it lazily if required.
      */
-    protected void clearTag() {
+    public Tag getTag(JellyContext context) throws JellyException {
         Thread t = Thread.currentThread();
-        threadLocalTagCache.put(t, null);
+        Tag tag = (Tag) threadLocalTagCache.get(t);
+        if (tag == null) {
+            tag = createTag();
+            if (tag != null) {
+                threadLocalTagCache.put(t, tag);
+                configureTag(tag, context);
+            }
+        }
+        return tag;
     }
 
     /**
-     * Allows the script to set the tag instance to be used, such as in a StaticTagScript
-     * when a StaticTag is switched with a DynamicTag
+     * Returns the tagBody.
+     * @return Script
      */
-    protected void setTag(Tag tag, JellyContext context) {
-        Thread t = Thread.currentThread();
-        threadLocalTagCache.put(t, tag);
+    public Script getTagBody() {
+        return tagBody;
     }
-
     /**
-     * Output the new namespace prefixes used for this element
+     * Returns the Factory of Tag instances.
+     * @return the factory
      */
-    protected void startNamespacePrefixes(XMLOutput output) throws SAXException {
-        if ( tagNamespacesMap != null ) {
-            for ( Iterator iter = tagNamespacesMap.entrySet().iterator(); iter.hasNext(); ) {
-                Map.Entry entry = (Map.Entry) iter.next();
-                String prefix = (String) entry.getKey();
-                String uri = (String) entry.getValue();
-                output.startPrefixMapping(prefix, uri);
-            }
-        }
+    public TagFactory getTagFactory() {
+        return tagFactory;
     }
 
     /**
-     * End the new namespace prefixes mapped for the current element
-     */
-    protected void endNamespacePrefixes(XMLOutput output) throws SAXException {
-        if ( tagNamespacesMap != null ) {
-            for ( Iterator iter = tagNamespacesMap.keySet().iterator(); iter.hasNext(); ) {
-                String prefix = (String) iter.next();
-                output.endPrefixMapping(prefix);
-            }
-        }
-    }
+	 * @return the tagLibrary
+	 */
+	public TagLibrary getTagLibrary() {
+		return tagLibrary;
+	}
 
     /**
-     * Converts the given value to the required type.
+     * A helper method to handle this non-Jelly exception.
+     * This method will rethrow the exception, wrapped in a JellyException
+     * while adding line number information etc.
      *
-     * @param value is the value to be converted. This will not be null
-     * @param requiredType the type that the value should be converted to
+     * Is this method wise?
      */
-    protected Object convertType(Object value, Class requiredType)
-        throws JellyException {
-        if (requiredType.isInstance(value)) {
-            return value;
+    protected void handleException(Error e) throws Error, JellyTagException {
+        if (log.isTraceEnabled()) {
+            log.trace( "Caught exception: " + e, e );
         }
-        if (value instanceof String) {
-            return ConvertUtils.convert((String) value, requiredType);
+
+        if (e instanceof LocationAware) {
+            applyLocation((LocationAware) e);
         }
-        return value;
-    }
 
-    /**
-     * Creates a new Jelly exception, adorning it with location information
-     */
-    protected JellyException createJellyException(String reason) {
-        return new JellyException(
-            reason, fileName, elementName, columnNumber, lineNumber
-        );
+        throw new JellyTagException(e, fileName, elementName, columnNumber, lineNumber);
     }
 
     /**
-     * Creates a new Jelly exception, adorning it with location information
+     * A helper method to handle this non-Jelly exception.
+     * This method will rethrow the exception, wrapped in a JellyException
+     * while adding line number information etc.
      */
-    protected JellyException createJellyException(String reason, Exception cause) {
-        if (cause instanceof JellyException) {
-            return (JellyException) cause;
+    protected void handleException(Exception e) throws JellyTagException {
+        if (log.isTraceEnabled()) {
+            log.trace( "Caught exception: " + e, e );
         }
 
-        if (cause instanceof InvocationTargetException) {
-            return new JellyException(
-                reason,
-                ((InvocationTargetException) cause).getTargetException(),
-                fileName,
-                elementName,
-                columnNumber,
-                lineNumber);
+        if (e instanceof LocationAware) {
+            applyLocation((LocationAware) e);
         }
-        return new JellyException(
-            reason, cause, fileName, elementName, columnNumber, lineNumber
-        );
+
+        if ( e instanceof JellyException ) {
+            e.fillInStackTrace();
+        }
+
+        if ( e instanceof InvocationTargetException) {
+            throw new JellyTagException( ((InvocationTargetException)e).getTargetException(),
+                                      fileName,
+                                      elementName,
+                                      columnNumber,
+                                      lineNumber );
+        }
+
+        throw new JellyTagException(e, fileName, elementName, columnNumber, lineNumber);
     }
 
     /**
@@ -639,14 +481,14 @@ public class TagScript implements Script {
      * This method adorns the JellyException with location information
      * such as adding line number information etc.
      */
-    protected void handleException(JellyTagException e) throws JellyTagException {
+    protected void handleException(JellyException e) throws JellyTagException {
         if (log.isTraceEnabled()) {
             log.trace( "Caught exception: " + e, e );
         }
 
         applyLocation(e);
 
-        throw e;
+        throw new JellyTagException(e);
     }
 
     /**
@@ -654,91 +496,249 @@ public class TagScript implements Script {
      * This method adorns the JellyException with location information
      * such as adding line number information etc.
      */
-    protected void handleException(JellyException e) throws JellyTagException {
+    protected void handleException(JellyTagException e) throws JellyTagException {
         if (log.isTraceEnabled()) {
             log.trace( "Caught exception: " + e, e );
         }
 
         applyLocation(e);
 
-        throw new JellyTagException(e);
+        throw e;
     }
 
-    protected void applyLocation(LocationAware locationAware) {
-        if (locationAware.getLineNumber() == -1) {
-            locationAware.setColumnNumber(columnNumber);
-            locationAware.setLineNumber(lineNumber);
+    /** Evaluates the body of a tag */
+    @Override
+    public void run(JellyContext context, XMLOutput output) throws JellyTagException {
+        URL rootURL = context.getRootURL();
+        URL currentURL = context.getCurrentURL();
+        if ( ! context.isCacheTags() ) {
+            clearTag();
         }
-        if ( locationAware.getFileName() == null ) {
-            locationAware.setFileName( fileName );
+        try {
+            Tag tag = getTag(context);
+            if ( tag == null ) {
+                return;
+            }
+            tag.setContext(context);
+            setContextURLs(context);
+
+            if ( tag instanceof DynaTag ) {
+                DynaTag dynaTag = (DynaTag) tag;
+
+                // ### probably compiling this to 2 arrays might be quicker and smaller
+                for (Iterator iter = attributes.entrySet().iterator(); iter.hasNext();) {
+                    Map.Entry entry = (Map.Entry) iter.next();
+                    String name = (String) entry.getKey();
+                    Expression expression = ((ExpressionAttribute) entry.getValue()).exp;
+
+                    Class type = dynaTag.getAttributeType(name);
+                    Object value = null;
+                    if (type != null && type.isAssignableFrom(Expression.class) && !type.isAssignableFrom(Object.class)) {
+                        value = expression;
+                    }
+                    else {
+                        value = expression.evaluateRecurse(context);
+                    }
+                    dynaTag.setAttribute(name, value);
+                }
+            }
+            else {
+                // treat the tag as a bean
+                DynaBean dynaBean = new ConvertingWrapDynaBean( tag );
+                for (Iterator iter = attributes.entrySet().iterator(); iter.hasNext();) {
+                    Map.Entry entry = (Map.Entry) iter.next();
+                    String name = (String) entry.getKey();
+                    Expression expression = ((ExpressionAttribute) entry.getValue()).exp;
+
+                    DynaProperty property = dynaBean.getDynaClass().getDynaProperty(name);
+                    if (property == null) {
+                        throw new JellyException("This tag does not understand the '" + name + "' attribute" );
+                    }
+                    Class type = property.getType();
+
+                    Object value = null;
+                    if (type.isAssignableFrom(Expression.class) && !type.isAssignableFrom(Object.class)) {
+                        value = expression;
+                    }
+                    else {
+                        value = expression.evaluateRecurse(context);
+                    }
+                    dynaBean.set(name, value);
+                }
+            }
+
+            tag.doTag(output);
+            if (output != null) {
+                output.flush();
+            }
         }
-        if ( locationAware.getElementName() == null ) {
-            locationAware.setElementName( elementName );
+        catch (JellyTagException e) {
+            handleException(e);
+        } catch (JellyException e) {
+            handleException(e);
+        } catch (IOException e) {
+            handleException(e);
+        } catch (RuntimeException e) {
+            handleException(e);
+        }
+        catch (Error e) {
+           /*
+            * Not sure if we should be converting errors to exceptions,
+            * but not trivial to remove because JUnit tags throw
+            * Errors in the normal course of operation.  Hmm...
+            */
+            handleException(e);
+        } finally {
+            context.setRootURL(rootURL);
+            context.setCurrentURL(currentURL);
         }
+
     }
 
     /**
-     * A helper method to handle this non-Jelly exception.
-     * This method will rethrow the exception, wrapped in a JellyException
-     * while adding line number information etc.
+     * Sets the column number of the tag
      */
-    protected void handleException(Exception e) throws JellyTagException {
-        if (log.isTraceEnabled()) {
-            log.trace( "Caught exception: " + e, e );
-        }
+    public void setColumnNumber(int columnNumber) {
+        this.columnNumber = columnNumber;
+    }
 
-        if (e instanceof LocationAware) {
-            applyLocation((LocationAware) e);
+    /**
+     * Sets the context's root and current URL if not present
+     * @param context
+     * @throws JellyTagException
+     */
+    protected void setContextURLs(JellyContext context) throws JellyTagException {
+        if ((context.getCurrentURL() == null || context.getRootURL() == null) && scriptURL != null)
+        {
+            if (context.getRootURL() == null) context.setRootURL(scriptURL);
+            if (context.getCurrentURL() == null) context.setCurrentURL(scriptURL);
         }
+    }
 
-        if ( e instanceof JellyException ) {
-            e.fillInStackTrace();
-        }
+    // Implementation methods
+    //-------------------------------------------------------------------------
 
-        if ( e instanceof InvocationTargetException) {
-            throw new JellyTagException( ((InvocationTargetException)e).getTargetException(),
-                                      fileName,
-                                      elementName,
-                                      columnNumber,
-                                      lineNumber );
+    /**
+     * Sets the element name which caused the problem
+     */
+    public void setElementName(String elementName) {
+        this.elementName = elementName;
+    }
+
+	
+    /**
+     * Sets the Jelly file which caused the problem
+     */
+    public void setFileName(String fileName) {
+        this.fileName = fileName;
+        try
+        {
+            this.scriptURL = getJellyContextURL(new URL(fileName));
+        } catch (MalformedURLException e) {
+            log.debug("error setting script url", e);
         }
+    }
 
-        throw new JellyTagException(e, fileName, elementName, columnNumber, lineNumber);
+    /**
+     * Sets the line number of the tag
+     */
+    public void setLineNumber(int lineNumber) {
+        this.lineNumber = lineNumber;
     }
 
     /**
-     * A helper method to handle this non-Jelly exception.
-     * This method will rethrow the exception, wrapped in a JellyException
-     * while adding line number information etc.
-     *
-     * Is this method wise?
+     * Sets the local, non namespaced name of this tag.
+     * @param localName The localName to set
      */
-    protected void handleException(Error e) throws Error, JellyTagException {
-        if (log.isTraceEnabled()) {
-            log.trace( "Caught exception: " + e, e );
-        }
+    public void setLocalName(String localName) {
+        this.localName = localName;
+    }
 
-        if (e instanceof LocationAware) {
-            applyLocation((LocationAware) e);
-        }
+    /**
+     * Configures this TagScript from the SAX Locator, setting the column
+     * and line numbers
+     */
+    public void setLocator(Locator locator) {
+        setLineNumber( locator.getLineNumber() );
+        setColumnNumber( locator.getColumnNumber() );
+    }
 
-        throw new JellyTagException(e, fileName, elementName, columnNumber, lineNumber);
+    /**
+     * Sets the parent.
+     * @param parent The parent to set
+     */
+    public void setParent(TagScript parent) {
+        this.parent = parent;
     }
-}
 
-final class ExpressionAttribute {
-    public ExpressionAttribute(String name, Expression exp) {
-        this(name, "", "", exp);
+    /**
+     * Sets the SAX attributes of this tag
+     * @param saxAttributes The saxAttributes to set
+     */
+    public void setSaxAttributes(Attributes saxAttributes) {
+        this.saxAttributes = saxAttributes;
     }
-    public ExpressionAttribute(String name, String prefix, String nsURI, Expression exp) {
-        this.name = name;
-        this.prefix = prefix;
-        this.nsURI = nsURI;
-        this.exp = exp;
+
+    /**
+     * Allows the script to set the tag instance to be used, such as in a StaticTagScript
+     * when a StaticTag is switched with a DynamicTag
+     */
+    protected void setTag(Tag tag, JellyContext context) {
+        Thread t = Thread.currentThread();
+        threadLocalTagCache.put(t, tag);
     }
 
-    String name;
-    String prefix;
-    String nsURI;
-    Expression exp;
+    /**
+     * Sets the tagBody.
+     * @param tagBody The tagBody to set
+     */
+    public void setTagBody(Script tagBody) {
+        this.tagBody = tagBody;
+    }
+
+    /**
+     * Sets the Factory of Tag instances.
+     * @param tagFactory The factory to set
+     */
+    public void setTagFactory(TagFactory tagFactory) {
+        this.tagFactory = tagFactory;
+    }
+
+    /**
+	 * @param tagLibrary the tagLibrary to set
+	 */
+	public void setTagLibrary(TagLibrary tagLibrary) {
+		this.tagLibrary = tagLibrary;
+	}
+
+    /**
+     * Sets the optional namespaces prefix to URI map of
+     * the namespaces attached to this Tag
+     */
+    public void setTagNamespacesMap(Map tagNamespacesMap) {
+        // lets check that this is a thread-safe map
+        if ( ! (tagNamespacesMap instanceof Hashtable) ) {
+            tagNamespacesMap = new Hashtable( tagNamespacesMap );
+        }
+        this.tagNamespacesMap = tagNamespacesMap;
+    }
+
+    /**
+     * Output the new namespace prefixes used for this element
+     */
+    protected void startNamespacePrefixes(XMLOutput output) throws SAXException {
+        if ( tagNamespacesMap != null ) {
+            for ( Iterator iter = tagNamespacesMap.entrySet().iterator(); iter.hasNext(); ) {
+                Map.Entry entry = (Map.Entry) iter.next();
+                String prefix = (String) entry.getKey();
+                String uri = (String) entry.getValue();
+                output.startPrefixMapping(prefix, uri);
+            }
+        }
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + "[tag=" + elementName + ";at=" + lineNumber + ":" + columnNumber + "]";
+    }
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/impl/TextScript.java b/core/src/main/java/org/apache/commons/jelly/impl/TextScript.java
index c04ab518..e4d3516f 100644
--- a/core/src/main/java/org/apache/commons/jelly/impl/TextScript.java
+++ b/core/src/main/java/org/apache/commons/jelly/impl/TextScript.java
@@ -36,32 +36,38 @@ public class TextScript implements Script {
         this.text = text;
     }
 
+    // Script interface
+    //-------------------------------------------------------------------------
     @Override
-    public String toString() {
-        return super.toString() + "[text=" + text + "]";
+    public Script compile() {
+        return this;
     }
 
-    /**
-     * Trims whitespace from the start and end of the text in this script
-     */
-    public void trimWhitespace() {
-        this.text = text.trim();
+    /** @return the text output by this script */
+    public String getText() {
+        return text;
     }
 
-    /**
-     * Trims whitespace from the start of the text
-     */
-    public void trimStartWhitespace() {
-        int index = 0;
-        for ( int length = text.length(); index < length; index++ ) {
-            char ch = text.charAt(index);
-            if (!Character.isWhitespace(ch)) {
-                break;
+    /** Evaluates the body of a tag */
+    @Override
+    public void run(JellyContext context, XMLOutput output) throws JellyTagException {
+        if (text != null) {
+            try {
+                output.write(text);
+            } catch (SAXException e) {
+                throw new JellyTagException("could not write to XMLOutput", e);
             }
         }
-        if ( index > 0 ) {
-            this.text = text.substring(index);
-        }
+    }
+
+    /** Sets the text output by this script */
+    public void setText(String text) {
+        this.text = text;
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + "[text=" + text + "]";
     }
 
     /**
@@ -81,32 +87,26 @@ public class TextScript implements Script {
         }
     }
 
-    /** @return the text output by this script */
-    public String getText() {
-        return text;
-    }
-
-    /** Sets the text output by this script */
-    public void setText(String text) {
-        this.text = text;
-    }
-
-    // Script interface
-    //-------------------------------------------------------------------------
-    @Override
-    public Script compile() {
-        return this;
-    }
-
-    /** Evaluates the body of a tag */
-    @Override
-    public void run(JellyContext context, XMLOutput output) throws JellyTagException {
-        if (text != null) {
-            try {
-                output.write(text);
-            } catch (SAXException e) {
-                throw new JellyTagException("could not write to XMLOutput", e);
+    /**
+     * Trims whitespace from the start of the text
+     */
+    public void trimStartWhitespace() {
+        int index = 0;
+        for ( int length = text.length(); index < length; index++ ) {
+            char ch = text.charAt(index);
+            if (!Character.isWhitespace(ch)) {
+                break;
             }
         }
+        if ( index > 0 ) {
+            this.text = text.substring(index);
+        }
+    }
+
+    /**
+     * Trims whitespace from the start and end of the text in this script
+     */
+    public void trimWhitespace() {
+        this.text = text.trim();
     }
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/parser/DefaultNamespaceFilter.java b/core/src/main/java/org/apache/commons/jelly/parser/DefaultNamespaceFilter.java
index d85b21f0..e0b0894d 100644
--- a/core/src/main/java/org/apache/commons/jelly/parser/DefaultNamespaceFilter.java
+++ b/core/src/main/java/org/apache/commons/jelly/parser/DefaultNamespaceFilter.java
@@ -45,19 +45,19 @@ public class DefaultNamespaceFilter extends XMLFilterImpl {
     /**
      * All incoming empty URIs will be remapped to the default.
      *
-     * @param prefix incoming prefix
-     * @param uri    URI to check and potentially replace
+     * @param namespaceURI
+     *                  URI to check and potentially replace
+     * @param localName
+     * @param qName
      * @throws SAXException
      */
     @Override
-    public void startPrefixMapping(java.lang.String prefix,
-                                   java.lang.String uri)
-    throws SAXException {
-
-        if (uri.isEmpty()) {
-            super.startPrefixMapping(prefix, this.uriDefault);
+    public void endElement(String namespaceURI, String localName, String qName)
+            throws SAXException {
+        if (namespaceURI.isEmpty()) {
+            super.endElement(this.uriDefault, localName, qName);
         } else {
-            super.startPrefixMapping(prefix, uri);
+            super.endElement(namespaceURI, localName, qName);
         }
     }
 
@@ -88,19 +88,19 @@ public class DefaultNamespaceFilter extends XMLFilterImpl {
     /**
      * All incoming empty URIs will be remapped to the default.
      *
-     * @param namespaceURI
-     *                  URI to check and potentially replace
-     * @param localName
-     * @param qName
+     * @param prefix incoming prefix
+     * @param uri    URI to check and potentially replace
      * @throws SAXException
      */
     @Override
-    public void endElement(String namespaceURI, String localName, String qName)
-            throws SAXException {
-        if (namespaceURI.isEmpty()) {
-            super.endElement(this.uriDefault, localName, qName);
+    public void startPrefixMapping(java.lang.String prefix,
+                                   java.lang.String uri)
+    throws SAXException {
+
+        if (uri.isEmpty()) {
+            super.startPrefixMapping(prefix, this.uriDefault);
         } else {
-            super.endElement(namespaceURI, localName, qName);
+            super.startPrefixMapping(prefix, uri);
         }
     }
 }
\ No newline at end of file
diff --git a/core/src/main/java/org/apache/commons/jelly/parser/XMLParser.java b/core/src/main/java/org/apache/commons/jelly/parser/XMLParser.java
index 2a40b2b6..068a8990 100644
--- a/core/src/main/java/org/apache/commons/jelly/parser/XMLParser.java
+++ b/core/src/main/java/org/apache/commons/jelly/parser/XMLParser.java
@@ -73,6 +73,11 @@ public class XMLParser extends DefaultHandler {
      */
     private static Properties jellyProperties;
 
+    /**
+     * The SAXParserFactory that is created the first time we need it.
+     */
+    protected static SAXParserFactory factory = null;
+
     /** JellyContext which is used to locate tag libraries*/
     private JellyContext context = new JellyContext();
 
@@ -117,11 +122,6 @@ public class XMLParser extends DefaultHandler {
      */
     protected ErrorHandler errorHandler = null;
 
-    /**
-     * The SAXParserFactory that is created the first time we need it.
-     */
-    protected static SAXParserFactory factory = null;
-
     /**
      * The SAXParser we will use to parse the input stream.
      */
@@ -220,535 +220,332 @@ public class XMLParser extends DefaultHandler {
     }
 
     /**
-     * Parse the content of the specified file using this XMLParser.  Returns
-     * the root element from the object stack (if any).
-     *
-     * @param file File containing the XML data to be parsed
-     * @return The script.
-     * @throws IOException if an input/output error occurs
-     * @throws SAXException if a parsing exception occurs
-     */
-    public Script parse(File file) throws IOException, SAXException {
-        return parse(file.toURL());
-    }
-
-    /**
-     * Parse the content of the specified file using this XMLParser.  Returns
-     * the root element from the object stack (if any).
-     *
-     * @param url URL containing the XML data to be parsed
-     * @return The script.
-     * @throws IOException if an input/output error occurs
-     * @throws SAXException if a parsing exception occurs
+     * Adds the given Expression object to the current Script.
      */
-    public Script parse(URL url) throws IOException, SAXException {
-        ensureConfigured();
-        this.fileName = url.toString();
-
-        InputSource source = new InputSource(url.toString());
+    protected void addExpressionScript(ScriptBlock script, Expression expression) {
+        if ( expression instanceof ConstantExpression ) {
+            ConstantExpression constantExpression
+                = (ConstantExpression) expression;
+            Object value = constantExpression.getValue();
+            if ( value != null ) {
+                script.addScript(new TextScript( value.toString() ));
+            }
+        }
+        else
+        if ( expression instanceof CompositeExpression ) {
+            CompositeTextScriptBlock newBlock = new CompositeTextScriptBlock();
+            script.addScript(newBlock);
 
-        getXMLReader().parse(source);
-        return script;
+            CompositeExpression compositeExpression
+                = (CompositeExpression) expression;
+            Iterator iter = compositeExpression.getExpressions().iterator();
+            while (iter.hasNext()) {
+                addExpressionScript( newBlock, (Expression) iter.next() );
+            }
+        }
+        else {
+            script.addScript(new ExpressionScript(expression));
+        }
     }
 
     /**
-     * Parse the content of the specified input source using this XMLParser.
-     * Returns the root element from the object stack (if any).
-     *
-     * @param input Input source containing the XML data to be parsed
-     * @return The script.
-     * @throws IOException if an input/output error occurs
-     * @throws SAXException if a parsing exception occurs
+     * Adds the text to the current script block parsing any embedded
+     * expressions into ExpressionScript objects.
      */
-    public Script parse(InputSource input) throws IOException, SAXException {
-        ensureConfigured();
-        this.fileName = input.getSystemId();
-        getXMLReader().parse(input);
-        return script;
+    protected void addTextScript(String text) throws JellyException {
+    	Expression expression = createExpression(tagScript, "", text);
+        addExpressionScript(script, expression);
     }
 
     /**
-     * Parse the content of the specified input stream using this XMLParser.
-     * Returns the root element from the object stack (if any).
-     * (Note: if reading a File or URL, use one of the URL-based
-     * parse methods instead.  This method will not be able
-     * to resolve any relative paths inside a DTD.)
+     * Process notification of character data received from the body of
+     * an XML element.
      *
-     * @param input  Input stream containing the XML data to be parsed
-     * @return The script.
-     * @throws IOException
-     *                   if an input/output error occurs
-     * @throws SAXException
-     *                   if a parsing exception occurs
+     * @param buffer The characters from the XML document
+     * @param start Starting offset into the buffer
+     * @param length Number of characters from the buffer
+     * @throws SAXException if a parsing error is to be reported
      */
-    public Script parse(InputStream input) throws IOException, SAXException {
-        ensureConfigured();
-        this.fileName = getCurrentURI();
-        getXMLReader().parse(new InputSource(input));
-        return script;
+    @Override
+    public void characters(char buffer[], int start, int length)
+        throws SAXException {
+        textBuffer.append(buffer, start, length);
     }
 
     /**
-     * Parse the content of the specified reader using this XMLParser.
-     * Returns the root element from the object stack (if any).
-     * (Note: if reading a File or URL, use one of the URL-based
-     * parse methods instead.  This method will not be able
-     * to resolve any relative paths inside a DTD.)
-     *
-     * @param reader Reader containing the XML data to be parsed
-     * @return The script.
-     * @throws IOException
-     *                   if an input/output error occurs
-     * @throws SAXException
-     *                   if a parsing exception occurs
+     * This method is called only once before parsing occurs
+     * which allows tag libraries to be registered and so forth
      */
-    public Script parse(Reader reader) throws IOException, SAXException {
-        ensureConfigured();
-        this.fileName = getCurrentURI();
-        getXMLReader().parse(new InputSource(reader));
-        return script;
+    protected void configure() {
+        // load the properties file of libraries available
+        Properties properties = getJellyProperties();
+        for (Iterator iter = properties.entrySet().iterator(); iter.hasNext();) {
+            Map.Entry entry = (Map.Entry) iter.next();
+            String uri = (String) entry.getKey();
+            String className = (String) entry.getValue();
+            String libraryURI = "jelly:" + uri;
+
+            // don't overload any Mock Tags already
+            if ( ! context.isTagLibraryRegistered(libraryURI) ) {
+                context.registerTagLibrary(libraryURI, className);
+            }
+        }
     }
 
     /**
-     * Parse the content of the specified URI using this XMLParser.
-     * Returns the root element from the object stack (if any).
-     *
-     * @param uri URI containing the XML data to be parsed
-     * @return The script.
-     * @throws IOException if an input/output error occurs
-     * @throws SAXException if a parsing exception occurs
+     * Configure a newly created TagScript instance before any Expressions are created;
+     * see configureTagScript(TagScript) for StaticTags
+     * @param tagLibrary the TagLibrary that created the TagScript
+     * @param aTagScript the TagScript that's just been created
      */
-    public Script parse(String uri) throws IOException, SAXException {
-        ensureConfigured();
-        this.fileName = uri;
-        getXMLReader().parse(uri);
-        return script;
+    protected void configureTagScript(TagLibrary tagLibrary,  TagScript aTagScript) {
+        // Set the TagLibrary that created the script
+        aTagScript.setTagLibrary(tagLibrary);
+
+        configureTagScript(aTagScript);
     }
 
     /**
-     * Return the currently mapped namespace URI for the specified prefix,
-     * if any; otherwise return {@code null}.  These mappings come and
-     * go dynamically as the document is parsed.
-     *
-     * @param prefix Prefix to look up
-     * @return The matching prefix.
+     * Configure a newly created TagScript instance before any Expressions are created
+     * @param aTagScript
      */
-    public String findNamespaceURI(String prefix) {
-        ArrayDeque stack = (ArrayDeque) namespaces.get(prefix);
-        if (stack == null) {
-            return (null);
-        }
-        try {
-            return ((String) stack.peek());
-        }
-        catch (EmptyStackException e) {
-            return (null);
-        }
-    }
+    protected void configureTagScript(TagScript aTagScript) {
+        // set parent relationship...
+        aTagScript.setParent(this.tagScript);
 
-    public JellyContext getContext() {
-        return context;
+        // set the namespace Map
+        if ( elementNamespaces != null ) {
+            aTagScript.setTagNamespacesMap( elementNamespaces );
+            elementNamespaces = null;
+        }
     }
 
-    public void setContext(JellyContext context) {
-        this.context = context;
+    protected Expression createConstantExpression(
+        String tagName,
+        String attributeName,
+        String attributeValue)  {
+        return new ConstantExpression(attributeValue);
     }
 
     /**
-     * Sets the jelly namespace to use for unprefixed elements.
-     * Will be overridden by an explicit namespace in the
-     * XML document.
-     *
-     * @param namespace jelly namespace to use (e.g. 'jelly:core')
+     * Creates an expression, using the ExpressionFactory returned by getExpressionFactory();
+     * the default implementation defers to the TagLibrary to create the Expression
+     * @param attributeName
+     * @param value
+     * @return The parsed exception.
+     * @throws JellyException
      */
-    public void setDefaultNamespaceURI(String namespace) {
-        this.defaultNamespaceURI = namespace;
+    public Expression createExpression(TagScript script, String attributeName, String value) throws JellyException {
+    	ExpressionFactory factory = getExpressionFactory(script);
+    	TagLibrary tagLibrary = script.getTagLibrary();
+    	if (tagLibrary != null)
+    		return tagLibrary.createExpression(factory, script, attributeName, value);
+       	return CompositeExpression.parse(value, factory);
     }
 
     /**
-     * Return the class loader to be used for instantiating application objects
-     * when required.  This is determined based upon the following rules:
-     * <ul>
-     * <li>The class loader set by <code>setClassLoader()</code>, if any</li>
-     * <li>The thread context class loader, if it exists and the
-     *     <code>useContextClassLoader</code> property is set to true</li>
-     * <li>The class loader used to load the XMLParser class itself.
-     * </ul>
+     * Called to create an instance of the ExpressionFactory (@see getExpressionFactory)
+     *
+     * @return A new JexlExpressionFactory.
      */
-    public ClassLoader getClassLoader() {
-        return ClassLoaderUtils.getClassLoader(classLoader, useContextClassLoader, getClass());
+    protected ExpressionFactory createExpressionFactory() {
+        return new JexlExpressionFactory();
     }
 
     /**
-     * Sets the class loader to be used for instantiating application objects
-     * when required.
+     * Create a SAX exception which also understands about the location in
+     * the digester file where the exception occurs
      *
-     * @param classLoader The new class loader to use, or {@code null}
-     *  to revert to the standard rules
+     * @return the new exception
      */
-    public void setClassLoader(ClassLoader classLoader) {
-        this.classLoader = classLoader;
+    protected SAXException createSAXException(Exception e) {
+        return createSAXException(e.getMessage(), e);
     }
 
     /**
-     * Return the boolean as to whether the context classloader should be used.
+     * Create a SAX exception which also understands about the location in
+     * the digester file where the exception occurs
+     *
+     * @return the new exception
      */
-    public boolean getUseContextClassLoader() {
-        return useContextClassLoader;
+    protected SAXException createSAXException(String message) {
+        return createSAXException(message, null);
     }
 
     /**
-     * Determine whether to use the Context ClassLoader (the one found by
-     * calling <code>Thread.currentThread().getContextClassLoader()</code>)
-     * to resolve/load classes.  If not
-     * using Context ClassLoader, then the class-loading defaults to
-     * using the calling-class' ClassLoader.
+     * Create a SAX exception which also understands about the location in
+     * the file where the exception occurs
      *
-     * @param use determines whether to use JellyContext ClassLoader.
-     */
-    public void setUseContextClassLoader(boolean use) {
-        useContextClassLoader = use;
-    }
-
-    /**
-     * Return the error handler for this XMLParser.
-     */
-    public ErrorHandler getErrorHandler() {
-        return (this.errorHandler);
-    }
-
-    /**
-     * Sets the error handler for this XMLParser.
-     *
-     * @param errorHandler The new error handler
-     */
-    public void setErrorHandler(ErrorHandler errorHandler) {
-        this.errorHandler = errorHandler;
-    }
-
-    /**
-     * Return the current Logger associated with this instance of the XMLParser
-     */
-    public Log getLogger() {
-        return log;
-    }
-
-    /**
-     * Sets the current logger for this XMLParser.
-     */
-    public void setLogger(Log log) {
-        this.log = log;
-    }
-
-    /**
-     * Locates an expression factory by looking at the TagLibrary for the specified
-     * tag and falling back by creating one.  If the current tag is a StaticTag (IE
-     * has no TagLibrary) then continues up the stack looking for a valid one.
-     * @param tagScript
-     * @return the expression factory used to evaluate tag attributes
-     */
-    public ExpressionFactory getExpressionFactory(TagScript tagScript) {
-    	// Check the tag library
-    	TagLibrary tagLibrary = null;
-    	if (tagScript != null)
-    		tagLibrary = tagScript.getTagLibrary();
-
-    	// If the tagScript is a StaticTag, then go up the stack looking for a
-    	//	tagScript that belongs to a real TagLibrary
-    	if (tagLibrary == null && tagScript instanceof StaticTagScript)
-    		for (int i = tagScriptStack.size() - 1; i > -1; i--) {
-    			TagScript script = (TagScript)tagScriptStack.get(i);
-    			tagLibrary = script.getTagLibrary();
-    			if (tagLibrary != null)
-    				break;
-    			if (!(script instanceof StaticTagScript))
-    				break;
-    		}
-    	if (tagLibrary != null) {
-    		ExpressionFactory factory = tagLibrary.getExpressionFactory();
-    		if (factory != null)
-    			return factory;
-    	}
-        if (expressionFactory == null) {
-            expressionFactory = createExpressionFactory();
-        }
-        return expressionFactory;
-    }
-
-    /** Sets the expression factory used to evaluate tag attributes */
-    public void setExpressionFactory(ExpressionFactory expressionFactory) {
-        this.expressionFactory = expressionFactory;
-    }
-
-    /**
-     * Called to create an instance of the ExpressionFactory (@see getExpressionFactory)
-     *
-     * @return A new JexlExpressionFactory.
-     */
-    protected ExpressionFactory createExpressionFactory() {
-        return new JexlExpressionFactory();
-    }
-
-    /**
-     * Creates an expression, using the ExpressionFactory returned by getExpressionFactory();
-     * the default implementation defers to the TagLibrary to create the Expression
-     * @param attributeName
-     * @param value
-     * @return The parsed exception.
-     * @throws JellyException
-     */
-    public Expression createExpression(TagScript script, String attributeName, String value) throws JellyException {
-    	ExpressionFactory factory = getExpressionFactory(script);
-    	TagLibrary tagLibrary = script.getTagLibrary();
-    	if (tagLibrary != null)
-    		return tagLibrary.createExpression(factory, script, attributeName, value);
-       	return CompositeExpression.parse(value, factory);
-    }
-
-    /**
-     * Return the SAXParser we will use to parse the input stream.  If there
-     * is a problem creating the parser, return {@code null}.
+     * @return the new exception
      */
-    public SAXParser getParser() {
-        // Return the parser we already created (if any)
-        if (parser != null) {
-            return (parser);
+    protected SAXException createSAXException(String message, Exception e) {
+        log.warn("Underlying exception: " + e);
+        if (e != null) {
+            e.printStackTrace();
         }
-        // Create and return a new parser
-        synchronized (this) {
-            try {
-                if (factory == null) {
-                    factory = SAXParserFactory.newInstance();
-                }
-                factory.setNamespaceAware(true);
-                factory.setValidating(validating);
-                parser = factory.newSAXParser();
-                return (parser);
+        if (locator != null) {
+            String error =
+                "Error at ("
+                    + locator.getLineNumber()
+                    + ", "
+                    + locator.getColumnNumber()
+                    + "): "
+                    + message;
+            if (e != null) {
+                return new SAXParseException(error, locator, e);
             }
-            catch (Exception e) {
-                log.error("XMLParser.getParser: ", e);
-                return (null);
+            else {
+                return new SAXParseException(error, locator);
             }
         }
-    }
-
-    /**
-     * By setting the reader in the constructor, you can bypass JAXP and
-     * be able to use digester in Weblogic 6.0.
-     *
-     * @deprecated Use getXMLReader() instead, which can throw a
-     *  SAXException if the reader cannot be instantiated
-     */
-    public XMLReader getReader() {
-        try {
-            return (getXMLReader());
+        log.error("No Locator!");
+        if (e != null) {
+            return new SAXException(message, e);
         }
-        catch (SAXException e) {
-            log.error("Cannot get XMLReader", e);
-            return (null);
+        else {
+            return new SAXException(message);
         }
     }
 
     /**
-     * Return the XMLReader to be used for parsing the input document.
-     *
-     * @throws SAXException if no XMLReader can be instantiated
+     * Factory method to create a static Tag that represents some static content.
      */
-    public synchronized XMLReader getXMLReader() throws SAXException {
-        if (reader == null) {
-            reader = getParser().getXMLReader();
-            if (!allowDtdToCallExternalEntities) {
-                reader.setFeature("http://xml.org/sax/features/external-general-entities", false);
-                reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
-                reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
-            }
-            if (this.defaultNamespaceURI != null) {
-                reader = new DefaultNamespaceFilter(this.defaultNamespaceURI, reader);
+    protected TagScript createStaticTag(
+        final String namespaceURI,
+        final String localName,
+        final String qName,
+        Attributes list)
+        throws SAXException {
+        try {
+            //StaticTag tag = new StaticTag( namespaceURI, localName, qName );
+            StaticTagScript script = new StaticTagScript(
+                new TagFactory() {
+                    @Override
+                    public Tag createTag(String name, Attributes attributes) {
+                        return new StaticTag( namespaceURI, localName, qName );
+                    }
+                }
+            );
+            configureTagScript(script);
+
+            // now iterate through through the expressions
+            int size = list.getLength();
+            for (int i = 0; i < size; i++) {
+                String attributeValue = list.getValue(i);
+                Expression expression = createExpression(script, null, attributeValue);
+//                Expression expression = CompositeExpression.parse(
+//                        attributeValue, getExpressionFactory()
+//                    );
+                String attrQName = list.getQName(i);
+                int p = attrQName.indexOf(':');
+                String prefix = p>=0 ?
+                        attrQName.substring(0, p):
+                        "";
+                script.addAttribute(list.getLocalName(i),
+                        prefix, list.getURI(i), expression);
             }
+            return script;
+        }
+        catch (Exception e) {
+            log.warn(
+                "Could not create static tag for URI: "
+                    + namespaceURI
+                    + " tag name: "
+                    + localName,
+                e);
+            throw createSAXException(e);
         }
-        //set up the parse
-        reader.setContentHandler(this);
-        reader.setDTDHandler(this);
-        //reader.setEntityResolver(this);
-        reader.setErrorHandler(this);
-
-        return reader;
-    }
-
-    /**
-     * Return the validating parser flag.
-     */
-    public boolean getValidating() {
-        return (this.validating);
-    }
-
-    /**
-     * Sets the validating parser flag.  This must be called before
-     * <code>parse()</code> is called the first time.
-     *
-     * @param validating The new validating parser flag.
-     */
-    public void setValidating(boolean validating) {
-        this.validating = validating;
-    }
-
-    /**
-     * Returns the script that has just been created if this class is used
-     * as a SAX ContentHandler and passed into some XML processor or parser.
-     *
-     * @return the ScriptBlock created if SAX events are piped into this class,
-     * which must include a startDocument() and endDocument()
-     */
-    public ScriptBlock getScript() {
-        return script;
-    }
-
-    // ContentHandler interface
-    //-------------------------------------------------------------------------
-    /**
-     * Process notification of the beginning of the document being reached.
-     *
-     * @throws SAXException if a parsing error is to be reported
-     */
-    @Override
-    public void startDocument() throws SAXException {
-        script = new ScriptBlock();
-        textBuffer = new StringBuffer();
-        tagScript = null;
-        scriptStack.clear();
-        tagScriptStack.clear();
-    }
-
-    /**
-     * Process notification of the end of the document being reached.
-     *
-     * @throws SAXException if a parsing error is to be reported
-     */
-    @Override
-    public void endDocument() throws SAXException {
-        textBuffer = null;
-    }
-
-    /**
-     * Gets the internal state for allowance for DTDs to call external resources.
-     *
-     * @return true if we are allowed to make external calls to entities during XML
-     * parsing by custom declared resources in the DTD.
-     */
-    public boolean isAllowDtdToCallExternalEntities() {
-        return allowDtdToCallExternalEntities;
-    }
-
-    /**
-     * Sets the boolean
-     * allowDtdToCallExternalEntities. If this is set to false,
-     * the XMLParser will be created with:
-     * XMLReader spf = XMLReaderFactory.createXMLReader();
-     * spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
-     * spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
-     * spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd",false);
-     * as given by
-     * https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#XMLReader
-     */
-    public void setAllowDtdToCallExternalEntities(boolean allowDtdToCallExternalEntities) {
-        this.allowDtdToCallExternalEntities = allowDtdToCallExternalEntities;
     }
 
     /**
-     * Process notification of the start of an XML element being reached.
-     *
-     * @param namespaceURI The Namespace URI, or the empty string if the
-     *   element has no Namespace URI or if Namespace processing is not
-     *   being performed.
-     * @param localName The local name (without prefix), or the empty
-     *   string if Namespace processing is not being performed.
-     * @param qName The qualified name (with prefix), or the empty
-     *   string if qualified names are not available.\
-     * @param list The attributes attached to the element. If there are
-     *   no attributes, it shall be an empty Attributes object.
-     * @throws SAXException if a parsing error is to be reported
+     * Factory method to create new Tag script for the given namespaceURI and name or
+     * return null if this is not a custom Tag.
      */
-    @Override
-    public void startElement(
+    protected TagScript createTag(
         String namespaceURI,
         String localName,
-        String qName,
         Attributes list)
         throws SAXException {
-
         try {
-            // add check to ensure namespace URI is "" for no namespace
-            if ( namespaceURI == null ) {
-                namespaceURI = "";
+            // use the URI to load a taglib
+            TagLibrary taglib = context.getTagLibrary(namespaceURI);
+            if (taglib == null) {
+                if (namespaceURI != null && namespaceURI.startsWith("jelly:")) {
+                    String uri = namespaceURI.substring(6);
+                    // try to find the class on the classpath
+                    try {
+                        Class taglibClass = getClassLoader().loadClass(uri);
+                        taglib = (TagLibrary) taglibClass.getConstructor().newInstance();
+                        context.registerTagLibrary(namespaceURI, taglib);
+                    }
+                    catch (ClassNotFoundException e) {
+                        throw createSAXException("Could not load class: " + uri + " so taglib instantiation failed", e);
+                    }
+                    catch (IllegalAccessException e) {
+                        throw createSAXException("Constructor for class is not accessible: " + uri + " so taglib instantiation failed", e);
+                    }
+                    catch (InstantiationException e) {
+                        throw createSAXException("Class could not be instantiated: " + uri + " so taglib instantiation failed", e);
+                    }
+                    catch (ClassCastException e) {
+                        throw createSAXException("Class is not a TagLibrary: " + uri + " so taglib instantiation failed", e);
+                    }
+                }
             }
+            if (taglib != null) {
+                TagScript script = taglib.createTagScript(localName, list);
+                if ( script != null ) {
+                    configureTagScript(taglib, script);
 
-            // if this is a tag then create a script to run it
-            // otherwise pass the text to the current body
-            TagScript newTagScript = createTag(namespaceURI, localName, list);
-            if (newTagScript == null) {
-                newTagScript = createStaticTag(namespaceURI, localName, qName, list);
-            }
-            tagScript = newTagScript;
-            tagScriptStack.add(tagScript);
-            if (tagScript != null) {
-                // set the line number details
-                if ( locator != null ) {
-                    tagScript.setLocator(locator);
-                }
-                // sets the file name element names
-                if (fileName != null)
-                	tagScript.setFileName(fileName);
-                tagScript.setElementName(qName);
-                tagScript.setLocalName(localName);
+                    // clone the attributes to keep them around after this parse
+                    script.setSaxAttributes(new AttributesImpl(list));
 
-                if (textBuffer.length() > 0) {
-                    addTextScript(textBuffer.toString());
-                    textBuffer.setLength(0);
-                }
-                script.addScript(tagScript);
-                // start a new body
-                scriptStack.push(script);
-                script = new ScriptBlock();
-                tagScript.setTagBody(script);
-            }
-            else {
-                // XXXX: might wanna handle empty elements later...
-                textBuffer.append("<");
-                textBuffer.append(qName);
-                int size = list.getLength();
-                for (int i = 0; i < size; i++) {
-                    textBuffer.append(" ");
-                    textBuffer.append(list.getQName(i));
-                    textBuffer.append("=");
-                    textBuffer.append("\"");
-                    textBuffer.append(list.getValue(i));
-                    textBuffer.append("\"");
-                }
-                textBuffer.append(">");
+                    // now iterate through through the expressions
+                    int size = list.getLength();
+                    for (int i = 0; i < size; i++) {
+                        String attributeName = list.getLocalName(i);
+                        // Fix for JELLY-184 where the xmlns attributes have a blank name and cause
+                        //	an exception later on
+                        if (attributeName.length() == 0)
+                        	continue;
+                        String attributeValue = list.getValue(i);
+                        Expression expression =
+                            createExpression(script,
+                                attributeName,
+                                attributeValue);
+//                        Expression expression =
+//                            taglib.createExpression(
+//                                getExpressionFactory(),
+//                                script,
+//                                attributeName,
+//                                attributeValue);
+                        if (expression == null) {
+                            expression = createConstantExpression(localName, attributeName, attributeValue);
+                        }
+                        script.addAttribute(attributeName, expression);
+                    }
+                } else if (!taglib.isAllowUnknownTags())
+                    throw new JellyException("Unrecognized tag called " + localName + " in TagLibrary " + namespaceURI);
+                return script;
             }
-        }
-        catch (SAXException e) {
-            throw e;
+            return null;
         }
         catch (Exception e) {
-            log.error( "Caught exception: " + e, e );
-            throw new SAXException( "Runtime Exception: " + e, e );
+            log.warn(
+                "Could not create taglib or URI: " + namespaceURI + " tag name: " + localName,
+                e);
+            throw createSAXException(e);
         }
     }
 
     /**
-     * Process notification of character data received from the body of
-     * an XML element.
+     * Process notification of the end of the document being reached.
      *
-     * @param buffer The characters from the XML document
-     * @param start Starting offset into the buffer
-     * @param length Number of characters from the buffer
      * @throws SAXException if a parsing error is to be reported
      */
     @Override
-    public void characters(char buffer[], int start, int length)
-        throws SAXException {
-        textBuffer.append(buffer, start, length);
+    public void endDocument() throws SAXException {
+        textBuffer = null;
     }
 
     /**
@@ -785,61 +582,322 @@ public class XMLParser extends DefaultHandler {
             if ( tagScriptStack.isEmpty() ) {
                 tagScript = null;
             }
-            else {
-                tagScript = (TagScript) tagScriptStack.get(tagScriptStack.size() - 1);
+            else {
+                tagScript = (TagScript) tagScriptStack.get(tagScriptStack.size() - 1);
+            }
+        } catch (Exception e) {
+            log.error( "Caught exception: " + e, e );
+            throw new SAXException( "Runtime Exception: " + e, e );
+        }
+    }
+
+    /**
+     * Process notification that a namespace prefix is going out of scope.
+     *
+     * @param prefix Prefix that is going out of scope
+     * @throws SAXException if a parsing error is to be reported
+     */
+    @Override
+    public void endPrefixMapping(String prefix) throws SAXException {
+        // Deregister this prefix mapping
+        ArrayDeque stack = (ArrayDeque) namespaces.get(prefix);
+        if (stack == null) {
+            return;
+        }
+        try {
+            stack.pop();
+            if (stack.isEmpty()) {
+                namespaces.remove(prefix);
+            }
+        }
+        catch (EmptyStackException e) {
+            throw createSAXException("endPrefixMapping popped too many times");
+        }
+    }
+
+    // Implementation methods
+    //-------------------------------------------------------------------------
+    /**
+     * If this object has not been configured then register the default
+     * namespaces
+     */
+    private void ensureConfigured() {
+        if (!configured) {
+            configure();
+            configured = true;
+        }
+    }
+
+    /**
+     * Forward notification of a parsing error to the application supplied
+     * error handler, if any, otherwise throw a SAXException with the error.
+     *
+     * @param exception The error information
+     * @throws SAXException if a parsing exception occurs
+     */
+    @Override
+    public void error(SAXParseException exception) throws SAXException {
+        log.error(
+            "Parse Error at line "
+                + exception.getLineNumber()
+                + " column "
+                + exception.getColumnNumber()
+                + ": "
+                + exception.getMessage(),
+            exception);
+        if (errorHandler != null) {
+            errorHandler.error(exception);
+        } else {
+            throw exception;
+        }
+    }
+
+    /**
+     * Forward notification of a fatal parsing error to the application
+     * supplied error handler, if any, otherwise throw a SAXException with the error.
+     *
+     * @param exception The fatal error information
+     * @throws SAXException if a parsing exception occurs
+     */
+    @Override
+    public void fatalError(SAXParseException exception) throws SAXException {
+        log.error(
+            "Parse Fatal Error at line "
+                + exception.getLineNumber()
+                + " column "
+                + exception.getColumnNumber()
+                + ": "
+                + exception.getMessage(),
+            exception);
+        if (errorHandler != null) {
+            errorHandler.fatalError(exception);
+        } else {
+            throw exception;
+        }
+    }
+
+    /**
+     * Return the currently mapped namespace URI for the specified prefix,
+     * if any; otherwise return {@code null}.  These mappings come and
+     * go dynamically as the document is parsed.
+     *
+     * @param prefix Prefix to look up
+     * @return The matching prefix.
+     */
+    public String findNamespaceURI(String prefix) {
+        ArrayDeque stack = (ArrayDeque) namespaces.get(prefix);
+        if (stack == null) {
+            return (null);
+        }
+        try {
+            return ((String) stack.peek());
+        }
+        catch (EmptyStackException e) {
+            return (null);
+        }
+    }
+
+    /**
+     * Return the class loader to be used for instantiating application objects
+     * when required.  This is determined based upon the following rules:
+     * <ul>
+     * <li>The class loader set by <code>setClassLoader()</code>, if any</li>
+     * <li>The thread context class loader, if it exists and the
+     *     <code>useContextClassLoader</code> property is set to true</li>
+     * <li>The class loader used to load the XMLParser class itself.
+     * </ul>
+     */
+    public ClassLoader getClassLoader() {
+        return ClassLoaderUtils.getClassLoader(classLoader, useContextClassLoader, getClass());
+    }
+
+    public JellyContext getContext() {
+        return context;
+    }
+
+    /**
+     * @return the current context URI as a String or null if there is no
+     * current context defined on the JellyContext
+     */
+    protected String getCurrentURI() {
+        URL url = this.getContext().getCurrentURL();
+        return (url != null) ? url.toString() : null;
+    }
+
+    /**
+     * Return the error handler for this XMLParser.
+     */
+    public ErrorHandler getErrorHandler() {
+        return (this.errorHandler);
+    }
+
+    /**
+     * Locates an expression factory by looking at the TagLibrary for the specified
+     * tag and falling back by creating one.  If the current tag is a StaticTag (IE
+     * has no TagLibrary) then continues up the stack looking for a valid one.
+     * @param tagScript
+     * @return the expression factory used to evaluate tag attributes
+     */
+    public ExpressionFactory getExpressionFactory(TagScript tagScript) {
+    	// Check the tag library
+    	TagLibrary tagLibrary = null;
+    	if (tagScript != null)
+    		tagLibrary = tagScript.getTagLibrary();
+
+    	// If the tagScript is a StaticTag, then go up the stack looking for a
+    	//	tagScript that belongs to a real TagLibrary
+    	if (tagLibrary == null && tagScript instanceof StaticTagScript)
+    		for (int i = tagScriptStack.size() - 1; i > -1; i--) {
+    			TagScript script = (TagScript)tagScriptStack.get(i);
+    			tagLibrary = script.getTagLibrary();
+    			if (tagLibrary != null)
+    				break;
+    			if (!(script instanceof StaticTagScript))
+    				break;
+    		}
+    	if (tagLibrary != null) {
+    		ExpressionFactory factory = tagLibrary.getExpressionFactory();
+    		if (factory != null)
+    			return factory;
+    	}
+        if (expressionFactory == null) {
+            expressionFactory = createExpressionFactory();
+        }
+        return expressionFactory;
+    }
+
+    /**
+     * A helper method which loads the static Jelly properties once on startup
+     */
+    protected synchronized Properties getJellyProperties() {
+        if (jellyProperties == null) {
+            jellyProperties = new Properties();
+
+            InputStream in = null;
+            URL url =
+                getClassLoader().getResource("org/apache/commons/jelly/jelly.properties");
+            if (url != null) {
+                log.debug("Loading Jelly default tag libraries from: " + url);
+                try {
+                    in = url.openStream();
+                    jellyProperties .load(in);
+                }
+                catch (IOException e) {
+                    log.error("Could not load jelly properties from: " + url + ". Reason: " + e, e);
+                }
+                finally {
+                    try {
+                        in.close();
+                    }
+                    catch (Exception e) {
+                        if (log.isDebugEnabled()) log.debug("error closing jelly.properties", e);
+                    }
+                }
+            }
+        }
+        return jellyProperties;
+    }
+
+    /**
+     * Return the current Logger associated with this instance of the XMLParser
+     */
+    public Log getLogger() {
+        return log;
+    }
+
+    /**
+     * Return the SAXParser we will use to parse the input stream.  If there
+     * is a problem creating the parser, return {@code null}.
+     */
+    public SAXParser getParser() {
+        // Return the parser we already created (if any)
+        if (parser != null) {
+            return (parser);
+        }
+        // Create and return a new parser
+        synchronized (this) {
+            try {
+                if (factory == null) {
+                    factory = SAXParserFactory.newInstance();
+                }
+                factory.setNamespaceAware(true);
+                factory.setValidating(validating);
+                parser = factory.newSAXParser();
+                return (parser);
+            }
+            catch (Exception e) {
+                log.error("XMLParser.getParser: ", e);
+                return (null);
             }
-        } catch (Exception e) {
-            log.error( "Caught exception: " + e, e );
-            throw new SAXException( "Runtime Exception: " + e, e );
         }
     }
 
     /**
-     * Process notification that a namespace prefix is coming in to scope.
+     * By setting the reader in the constructor, you can bypass JAXP and
+     * be able to use digester in Weblogic 6.0.
      *
-     * @param prefix Prefix that is being declared
-     * @param namespaceURI Corresponding namespace URI being mapped to
-     * @throws SAXException if a parsing error is to be reported
+     * @deprecated Use getXMLReader() instead, which can throw a
+     *  SAXException if the reader cannot be instantiated
      */
-    @Override
-    public void startPrefixMapping(String prefix, String namespaceURI)
-        throws SAXException {
-        // Register this prefix mapping
-        ArrayDeque stack = (ArrayDeque) namespaces.get(prefix);
-        if (stack == null) {
-            stack = new ArrayDeque();
-            namespaces.put(prefix, stack);
+    public XMLReader getReader() {
+        try {
+            return (getXMLReader());
         }
-        stack.push(namespaceURI);
-
-        if ( elementNamespaces == null ) {
-            elementNamespaces = new HashMap();
+        catch (SAXException e) {
+            log.error("Cannot get XMLReader", e);
+            return (null);
         }
-        elementNamespaces.put(prefix, namespaceURI);
     }
 
     /**
-     * Process notification that a namespace prefix is going out of scope.
+     * Returns the script that has just been created if this class is used
+     * as a SAX ContentHandler and passed into some XML processor or parser.
      *
-     * @param prefix Prefix that is going out of scope
-     * @throws SAXException if a parsing error is to be reported
+     * @return the ScriptBlock created if SAX events are piped into this class,
+     * which must include a startDocument() and endDocument()
      */
-    @Override
-    public void endPrefixMapping(String prefix) throws SAXException {
-        // Deregister this prefix mapping
-        ArrayDeque stack = (ArrayDeque) namespaces.get(prefix);
-        if (stack == null) {
-            return;
-        }
-        try {
-            stack.pop();
-            if (stack.isEmpty()) {
-                namespaces.remove(prefix);
+    public ScriptBlock getScript() {
+        return script;
+    }
+
+    /**
+     * Return the boolean as to whether the context classloader should be used.
+     */
+    public boolean getUseContextClassLoader() {
+        return useContextClassLoader;
+    }
+
+    /**
+     * Return the validating parser flag.
+     */
+    public boolean getValidating() {
+        return (this.validating);
+    }
+
+    /**
+     * Return the XMLReader to be used for parsing the input document.
+     *
+     * @throws SAXException if no XMLReader can be instantiated
+     */
+    public synchronized XMLReader getXMLReader() throws SAXException {
+        if (reader == null) {
+            reader = getParser().getXMLReader();
+            if (!allowDtdToCallExternalEntities) {
+                reader.setFeature("http://xml.org/sax/features/external-general-entities", false);
+                reader.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+                reader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
+            }
+            if (this.defaultNamespaceURI != null) {
+                reader = new DefaultNamespaceFilter(this.defaultNamespaceURI, reader);
             }
         }
-        catch (EmptyStackException e) {
-            throw createSAXException("endPrefixMapping popped too many times");
-        }
+        //set up the parse
+        reader.setContentHandler(this);
+        reader.setDTDHandler(this);
+        //reader.setEntityResolver(this);
+        reader.setErrorHandler(this);
+
+        return reader;
     }
 
     /**
@@ -858,467 +916,409 @@ public class XMLParser extends DefaultHandler {
     }
 
     /**
-     * Process notification of a processing instruction that was encountered.
+     * Gets the internal state for allowance for DTDs to call external resources.
      *
-     * @param target The processing instruction target
-     * @param data The processing instruction data (if any)
-     * @throws SAXException if a parsing error is to be reported
+     * @return true if we are allowed to make external calls to entities during XML
+     * parsing by custom declared resources in the DTD.
      */
-    @Override
-    public void processingInstruction(String target, String data)
-        throws SAXException {
-        // No processing is required
+    public boolean isAllowDtdToCallExternalEntities() {
+        return allowDtdToCallExternalEntities;
     }
 
     /**
-     * Sets the document locator associated with our parser.
+     * Receive notification of a notation declaration event.
      *
-     * @param locator The new locator
+     * @param name The notation name
+     * @param publicId The public identifier (if any)
+     * @param systemId The system identifier (if any)
      */
     @Override
-    public void setDocumentLocator(Locator locator) {
-        this.locator = locator;
+    public void notationDecl(String name, String publicId, String systemId) {
     }
 
     /**
-     * Process notification of a skipped entity.
+     * Parse the content of the specified file using this XMLParser.  Returns
+     * the root element from the object stack (if any).
      *
-     * @param name Name of the skipped entity
-     * @throws SAXException if a parsing error is to be reported
+     * @param file File containing the XML data to be parsed
+     * @return The script.
+     * @throws IOException if an input/output error occurs
+     * @throws SAXException if a parsing exception occurs
      */
-    @Override
-    public void skippedEntity(String name) throws SAXException {
-        // No processing required
+    public Script parse(File file) throws IOException, SAXException {
+        return parse(file.toURL());
     }
 
     /**
-     * Receive notification of a notation declaration event.
+     * Parse the content of the specified input source using this XMLParser.
+     * Returns the root element from the object stack (if any).
      *
-     * @param name The notation name
-     * @param publicId The public identifier (if any)
-     * @param systemId The system identifier (if any)
+     * @param input Input source containing the XML data to be parsed
+     * @return The script.
+     * @throws IOException if an input/output error occurs
+     * @throws SAXException if a parsing exception occurs
      */
-    @Override
-    public void notationDecl(String name, String publicId, String systemId) {
+    public Script parse(InputSource input) throws IOException, SAXException {
+        ensureConfigured();
+        this.fileName = input.getSystemId();
+        getXMLReader().parse(input);
+        return script;
     }
 
     /**
-     * Receive notification of an unparsed entity declaration event.
+     * Parse the content of the specified input stream using this XMLParser.
+     * Returns the root element from the object stack (if any).
+     * (Note: if reading a File or URL, use one of the URL-based
+     * parse methods instead.  This method will not be able
+     * to resolve any relative paths inside a DTD.)
      *
-     * @param name The unparsed entity name
-     * @param publicId The public identifier (if any)
-     * @param systemId The system identifier (if any)
-     * @param notation The name of the associated notation
+     * @param input  Input stream containing the XML data to be parsed
+     * @return The script.
+     * @throws IOException
+     *                   if an input/output error occurs
+     * @throws SAXException
+     *                   if a parsing exception occurs
      */
-    @Override
-    public void unparsedEntityDecl(
-        String name,
-        String publicId,
-        String systemId,
-        String notation) {
+    public Script parse(InputStream input) throws IOException, SAXException {
+        ensureConfigured();
+        this.fileName = getCurrentURI();
+        getXMLReader().parse(new InputSource(input));
+        return script;
     }
 
     /**
-     * Forward notification of a parsing error to the application supplied
-     * error handler, if any, otherwise throw a SAXException with the error.
+     * Parse the content of the specified reader using this XMLParser.
+     * Returns the root element from the object stack (if any).
+     * (Note: if reading a File or URL, use one of the URL-based
+     * parse methods instead.  This method will not be able
+     * to resolve any relative paths inside a DTD.)
      *
-     * @param exception The error information
-     * @throws SAXException if a parsing exception occurs
+     * @param reader Reader containing the XML data to be parsed
+     * @return The script.
+     * @throws IOException
+     *                   if an input/output error occurs
+     * @throws SAXException
+     *                   if a parsing exception occurs
      */
-    @Override
-    public void error(SAXParseException exception) throws SAXException {
-        log.error(
-            "Parse Error at line "
-                + exception.getLineNumber()
-                + " column "
-                + exception.getColumnNumber()
-                + ": "
-                + exception.getMessage(),
-            exception);
-        if (errorHandler != null) {
-            errorHandler.error(exception);
-        } else {
-            throw exception;
-        }
+    public Script parse(Reader reader) throws IOException, SAXException {
+        ensureConfigured();
+        this.fileName = getCurrentURI();
+        getXMLReader().parse(new InputSource(reader));
+        return script;
     }
 
     /**
-     * Forward notification of a fatal parsing error to the application
-     * supplied error handler, if any, otherwise throw a SAXException with the error.
+     * Parse the content of the specified URI using this XMLParser.
+     * Returns the root element from the object stack (if any).
      *
-     * @param exception The fatal error information
+     * @param uri URI containing the XML data to be parsed
+     * @return The script.
+     * @throws IOException if an input/output error occurs
      * @throws SAXException if a parsing exception occurs
      */
-    @Override
-    public void fatalError(SAXParseException exception) throws SAXException {
-        log.error(
-            "Parse Fatal Error at line "
-                + exception.getLineNumber()
-                + " column "
-                + exception.getColumnNumber()
-                + ": "
-                + exception.getMessage(),
-            exception);
-        if (errorHandler != null) {
-            errorHandler.fatalError(exception);
-        } else {
-            throw exception;
-        }
+    public Script parse(String uri) throws IOException, SAXException {
+        ensureConfigured();
+        this.fileName = uri;
+        getXMLReader().parse(uri);
+        return script;
     }
 
-    /**
-     * Forward notification of a parse warning to the application supplied
-     * error handler (if any).  Unlike XMLParser.error(SAXParseException) and
-     * XMLParser.fatalError(SAXParseException), this implementation will
-     * NOT throw a SAXException by default if no error handler is supplied.
+    /**
+     * Parse the content of the specified file using this XMLParser.  Returns
+     * the root element from the object stack (if any).
      *
-     * @param exception The warning information
+     * @param url URL containing the XML data to be parsed
+     * @return The script.
+     * @throws IOException if an input/output error occurs
      * @throws SAXException if a parsing exception occurs
      */
-    @Override
-    public void warning(SAXParseException exception) throws SAXException {
-        log.error(
-            "Parse Warning at line "
-                + exception.getLineNumber()
-                + " column "
-                + exception.getColumnNumber()
-                + ": "
-                + exception.getMessage(),
-            exception);
-        if (errorHandler != null) {
-            errorHandler.warning(exception);
-        }
+    public Script parse(URL url) throws IOException, SAXException {
+        ensureConfigured();
+        this.fileName = url.toString();
+
+        InputSource source = new InputSource(url.toString());
+
+        getXMLReader().parse(source);
+        return script;
     }
 
-    // Implementation methods
-    //-------------------------------------------------------------------------
     /**
-     * If this object has not been configured then register the default
-     * namespaces
+     * Process notification of a processing instruction that was encountered.
+     *
+     * @param target The processing instruction target
+     * @param data The processing instruction data (if any)
+     * @throws SAXException if a parsing error is to be reported
      */
-    private void ensureConfigured() {
-        if (!configured) {
-            configure();
-            configured = true;
-        }
+    @Override
+    public void processingInstruction(String target, String data)
+        throws SAXException {
+        // No processing is required
     }
 
     /**
-     * This method is called only once before parsing occurs
-     * which allows tag libraries to be registered and so forth
+     * Sets the boolean
+     * allowDtdToCallExternalEntities. If this is set to false,
+     * the XMLParser will be created with:
+     * XMLReader spf = XMLReaderFactory.createXMLReader();
+     * spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
+     * spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
+     * spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd",false);
+     * as given by
+     * https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#XMLReader
      */
-    protected void configure() {
-        // load the properties file of libraries available
-        Properties properties = getJellyProperties();
-        for (Iterator iter = properties.entrySet().iterator(); iter.hasNext();) {
-            Map.Entry entry = (Map.Entry) iter.next();
-            String uri = (String) entry.getKey();
-            String className = (String) entry.getValue();
-            String libraryURI = "jelly:" + uri;
-
-            // don't overload any Mock Tags already
-            if ( ! context.isTagLibraryRegistered(libraryURI) ) {
-                context.registerTagLibrary(libraryURI, className);
-            }
-        }
+    public void setAllowDtdToCallExternalEntities(boolean allowDtdToCallExternalEntities) {
+        this.allowDtdToCallExternalEntities = allowDtdToCallExternalEntities;
     }
 
     /**
-     * A helper method which loads the static Jelly properties once on startup
+     * Sets the class loader to be used for instantiating application objects
+     * when required.
+     *
+     * @param classLoader The new class loader to use, or {@code null}
+     *  to revert to the standard rules
      */
-    protected synchronized Properties getJellyProperties() {
-        if (jellyProperties == null) {
-            jellyProperties = new Properties();
+    public void setClassLoader(ClassLoader classLoader) {
+        this.classLoader = classLoader;
+    }
 
-            InputStream in = null;
-            URL url =
-                getClassLoader().getResource("org/apache/commons/jelly/jelly.properties");
-            if (url != null) {
-                log.debug("Loading Jelly default tag libraries from: " + url);
-                try {
-                    in = url.openStream();
-                    jellyProperties .load(in);
-                }
-                catch (IOException e) {
-                    log.error("Could not load jelly properties from: " + url + ". Reason: " + e, e);
-                }
-                finally {
-                    try {
-                        in.close();
-                    }
-                    catch (Exception e) {
-                        if (log.isDebugEnabled()) log.debug("error closing jelly.properties", e);
-                    }
-                }
-            }
-        }
-        return jellyProperties;
+    public void setContext(JellyContext context) {
+        this.context = context;
     }
 
     /**
-     * Factory method to create new Tag script for the given namespaceURI and name or
-     * return null if this is not a custom Tag.
+     * Sets the jelly namespace to use for unprefixed elements.
+     * Will be overridden by an explicit namespace in the
+     * XML document.
+     *
+     * @param namespace jelly namespace to use (e.g. 'jelly:core')
      */
-    protected TagScript createTag(
-        String namespaceURI,
-        String localName,
-        Attributes list)
-        throws SAXException {
-        try {
-            // use the URI to load a taglib
-            TagLibrary taglib = context.getTagLibrary(namespaceURI);
-            if (taglib == null) {
-                if (namespaceURI != null && namespaceURI.startsWith("jelly:")) {
-                    String uri = namespaceURI.substring(6);
-                    // try to find the class on the classpath
-                    try {
-                        Class taglibClass = getClassLoader().loadClass(uri);
-                        taglib = (TagLibrary) taglibClass.getConstructor().newInstance();
-                        context.registerTagLibrary(namespaceURI, taglib);
-                    }
-                    catch (ClassNotFoundException e) {
-                        throw createSAXException("Could not load class: " + uri + " so taglib instantiation failed", e);
-                    }
-                    catch (IllegalAccessException e) {
-                        throw createSAXException("Constructor for class is not accessible: " + uri + " so taglib instantiation failed", e);
-                    }
-                    catch (InstantiationException e) {
-                        throw createSAXException("Class could not be instantiated: " + uri + " so taglib instantiation failed", e);
-                    }
-                    catch (ClassCastException e) {
-                        throw createSAXException("Class is not a TagLibrary: " + uri + " so taglib instantiation failed", e);
-                    }
-                }
-            }
-            if (taglib != null) {
-                TagScript script = taglib.createTagScript(localName, list);
-                if ( script != null ) {
-                    configureTagScript(taglib, script);
-
-                    // clone the attributes to keep them around after this parse
-                    script.setSaxAttributes(new AttributesImpl(list));
-
-                    // now iterate through through the expressions
-                    int size = list.getLength();
-                    for (int i = 0; i < size; i++) {
-                        String attributeName = list.getLocalName(i);
-                        // Fix for JELLY-184 where the xmlns attributes have a blank name and cause
-                        //	an exception later on
-                        if (attributeName.length() == 0)
-                        	continue;
-                        String attributeValue = list.getValue(i);
-                        Expression expression =
-                            createExpression(script,
-                                attributeName,
-                                attributeValue);
-//                        Expression expression =
-//                            taglib.createExpression(
-//                                getExpressionFactory(),
-//                                script,
-//                                attributeName,
-//                                attributeValue);
-                        if (expression == null) {
-                            expression = createConstantExpression(localName, attributeName, attributeValue);
-                        }
-                        script.addAttribute(attributeName, expression);
-                    }
-                } else if (!taglib.isAllowUnknownTags())
-                    throw new JellyException("Unrecognized tag called " + localName + " in TagLibrary " + namespaceURI);
-                return script;
-            }
-            return null;
-        }
-        catch (Exception e) {
-            log.warn(
-                "Could not create taglib or URI: " + namespaceURI + " tag name: " + localName,
-                e);
-            throw createSAXException(e);
-        }
+    public void setDefaultNamespaceURI(String namespace) {
+        this.defaultNamespaceURI = namespace;
     }
 
     /**
-     * Factory method to create a static Tag that represents some static content.
+     * Sets the document locator associated with our parser.
+     *
+     * @param locator The new locator
      */
-    protected TagScript createStaticTag(
-        final String namespaceURI,
-        final String localName,
-        final String qName,
-        Attributes list)
-        throws SAXException {
-        try {
-            //StaticTag tag = new StaticTag( namespaceURI, localName, qName );
-            StaticTagScript script = new StaticTagScript(
-                new TagFactory() {
-                    @Override
-                    public Tag createTag(String name, Attributes attributes) {
-                        return new StaticTag( namespaceURI, localName, qName );
-                    }
-                }
-            );
-            configureTagScript(script);
-
-            // now iterate through through the expressions
-            int size = list.getLength();
-            for (int i = 0; i < size; i++) {
-                String attributeValue = list.getValue(i);
-                Expression expression = createExpression(script, null, attributeValue);
-//                Expression expression = CompositeExpression.parse(
-//                        attributeValue, getExpressionFactory()
-//                    );
-                String attrQName = list.getQName(i);
-                int p = attrQName.indexOf(':');
-                String prefix = p>=0 ?
-                        attrQName.substring(0, p):
-                        "";
-                script.addAttribute(list.getLocalName(i),
-                        prefix, list.getURI(i), expression);
-            }
-            return script;
-        }
-        catch (Exception e) {
-            log.warn(
-                "Could not create static tag for URI: "
-                    + namespaceURI
-                    + " tag name: "
-                    + localName,
-                e);
-            throw createSAXException(e);
-        }
+    @Override
+    public void setDocumentLocator(Locator locator) {
+        this.locator = locator;
     }
 
     /**
-     * Configure a newly created TagScript instance before any Expressions are created;
-     * see configureTagScript(TagScript) for StaticTags
-     * @param tagLibrary the TagLibrary that created the TagScript
-     * @param aTagScript the TagScript that's just been created
+     * Sets the error handler for this XMLParser.
+     *
+     * @param errorHandler The new error handler
      */
-    protected void configureTagScript(TagLibrary tagLibrary,  TagScript aTagScript) {
-        // Set the TagLibrary that created the script
-        aTagScript.setTagLibrary(tagLibrary);
+    public void setErrorHandler(ErrorHandler errorHandler) {
+        this.errorHandler = errorHandler;
+    }
 
-        configureTagScript(aTagScript);
+    /** Sets the expression factory used to evaluate tag attributes */
+    public void setExpressionFactory(ExpressionFactory expressionFactory) {
+        this.expressionFactory = expressionFactory;
     }
 
     /**
-     * Configure a newly created TagScript instance before any Expressions are created
-     * @param aTagScript
+     * Sets the current logger for this XMLParser.
      */
-    protected void configureTagScript(TagScript aTagScript) {
-        // set parent relationship...
-        aTagScript.setParent(this.tagScript);
-
-        // set the namespace Map
-        if ( elementNamespaces != null ) {
-            aTagScript.setTagNamespacesMap( elementNamespaces );
-            elementNamespaces = null;
-        }
+    public void setLogger(Log log) {
+        this.log = log;
     }
 
     /**
-     * Adds the text to the current script block parsing any embedded
-     * expressions into ExpressionScript objects.
+     * Determine whether to use the Context ClassLoader (the one found by
+     * calling <code>Thread.currentThread().getContextClassLoader()</code>)
+     * to resolve/load classes.  If not
+     * using Context ClassLoader, then the class-loading defaults to
+     * using the calling-class' ClassLoader.
+     *
+     * @param use determines whether to use JellyContext ClassLoader.
      */
-    protected void addTextScript(String text) throws JellyException {
-    	Expression expression = createExpression(tagScript, "", text);
-        addExpressionScript(script, expression);
+    public void setUseContextClassLoader(boolean use) {
+        useContextClassLoader = use;
     }
 
     /**
-     * Adds the given Expression object to the current Script.
+     * Sets the validating parser flag.  This must be called before
+     * <code>parse()</code> is called the first time.
+     *
+     * @param validating The new validating parser flag.
      */
-    protected void addExpressionScript(ScriptBlock script, Expression expression) {
-        if ( expression instanceof ConstantExpression ) {
-            ConstantExpression constantExpression
-                = (ConstantExpression) expression;
-            Object value = constantExpression.getValue();
-            if ( value != null ) {
-                script.addScript(new TextScript( value.toString() ));
-            }
-        }
-        else
-        if ( expression instanceof CompositeExpression ) {
-            CompositeTextScriptBlock newBlock = new CompositeTextScriptBlock();
-            script.addScript(newBlock);
-
-            CompositeExpression compositeExpression
-                = (CompositeExpression) expression;
-            Iterator iter = compositeExpression.getExpressions().iterator();
-            while (iter.hasNext()) {
-                addExpressionScript( newBlock, (Expression) iter.next() );
-            }
-        }
-        else {
-            script.addScript(new ExpressionScript(expression));
-        }
+    public void setValidating(boolean validating) {
+        this.validating = validating;
     }
 
-    protected Expression createConstantExpression(
-        String tagName,
-        String attributeName,
-        String attributeValue)  {
-        return new ConstantExpression(attributeValue);
+    /**
+     * Process notification of a skipped entity.
+     *
+     * @param name Name of the skipped entity
+     * @throws SAXException if a parsing error is to be reported
+     */
+    @Override
+    public void skippedEntity(String name) throws SAXException {
+        // No processing required
     }
 
+    // ContentHandler interface
+    //-------------------------------------------------------------------------
     /**
-     * @return the current context URI as a String or null if there is no
-     * current context defined on the JellyContext
+     * Process notification of the beginning of the document being reached.
+     *
+     * @throws SAXException if a parsing error is to be reported
      */
-    protected String getCurrentURI() {
-        URL url = this.getContext().getCurrentURL();
-        return (url != null) ? url.toString() : null;
+    @Override
+    public void startDocument() throws SAXException {
+        script = new ScriptBlock();
+        textBuffer = new StringBuffer();
+        tagScript = null;
+        scriptStack.clear();
+        tagScriptStack.clear();
     }
 
     /**
-     * Create a SAX exception which also understands about the location in
-     * the file where the exception occurs
+     * Process notification of the start of an XML element being reached.
      *
-     * @return the new exception
+     * @param namespaceURI The Namespace URI, or the empty string if the
+     *   element has no Namespace URI or if Namespace processing is not
+     *   being performed.
+     * @param localName The local name (without prefix), or the empty
+     *   string if Namespace processing is not being performed.
+     * @param qName The qualified name (with prefix), or the empty
+     *   string if qualified names are not available.\
+     * @param list The attributes attached to the element. If there are
+     *   no attributes, it shall be an empty Attributes object.
+     * @throws SAXException if a parsing error is to be reported
      */
-    protected SAXException createSAXException(String message, Exception e) {
-        log.warn("Underlying exception: " + e);
-        if (e != null) {
-            e.printStackTrace();
-        }
-        if (locator != null) {
-            String error =
-                "Error at ("
-                    + locator.getLineNumber()
-                    + ", "
-                    + locator.getColumnNumber()
-                    + "): "
-                    + message;
-            if (e != null) {
-                return new SAXParseException(error, locator, e);
+    @Override
+    public void startElement(
+        String namespaceURI,
+        String localName,
+        String qName,
+        Attributes list)
+        throws SAXException {
+
+        try {
+            // add check to ensure namespace URI is "" for no namespace
+            if ( namespaceURI == null ) {
+                namespaceURI = "";
+            }
+
+            // if this is a tag then create a script to run it
+            // otherwise pass the text to the current body
+            TagScript newTagScript = createTag(namespaceURI, localName, list);
+            if (newTagScript == null) {
+                newTagScript = createStaticTag(namespaceURI, localName, qName, list);
+            }
+            tagScript = newTagScript;
+            tagScriptStack.add(tagScript);
+            if (tagScript != null) {
+                // set the line number details
+                if ( locator != null ) {
+                    tagScript.setLocator(locator);
+                }
+                // sets the file name element names
+                if (fileName != null)
+                	tagScript.setFileName(fileName);
+                tagScript.setElementName(qName);
+                tagScript.setLocalName(localName);
+
+                if (textBuffer.length() > 0) {
+                    addTextScript(textBuffer.toString());
+                    textBuffer.setLength(0);
+                }
+                script.addScript(tagScript);
+                // start a new body
+                scriptStack.push(script);
+                script = new ScriptBlock();
+                tagScript.setTagBody(script);
             }
             else {
-                return new SAXParseException(error, locator);
+                // XXXX: might wanna handle empty elements later...
+                textBuffer.append("<");
+                textBuffer.append(qName);
+                int size = list.getLength();
+                for (int i = 0; i < size; i++) {
+                    textBuffer.append(" ");
+                    textBuffer.append(list.getQName(i));
+                    textBuffer.append("=");
+                    textBuffer.append("\"");
+                    textBuffer.append(list.getValue(i));
+                    textBuffer.append("\"");
+                }
+                textBuffer.append(">");
             }
         }
-        log.error("No Locator!");
-        if (e != null) {
-            return new SAXException(message, e);
+        catch (SAXException e) {
+            throw e;
         }
-        else {
-            return new SAXException(message);
+        catch (Exception e) {
+            log.error( "Caught exception: " + e, e );
+            throw new SAXException( "Runtime Exception: " + e, e );
         }
     }
 
     /**
-     * Create a SAX exception which also understands about the location in
-     * the digester file where the exception occurs
+     * Process notification that a namespace prefix is coming in to scope.
      *
-     * @return the new exception
+     * @param prefix Prefix that is being declared
+     * @param namespaceURI Corresponding namespace URI being mapped to
+     * @throws SAXException if a parsing error is to be reported
      */
-    protected SAXException createSAXException(Exception e) {
-        return createSAXException(e.getMessage(), e);
+    @Override
+    public void startPrefixMapping(String prefix, String namespaceURI)
+        throws SAXException {
+        // Register this prefix mapping
+        ArrayDeque stack = (ArrayDeque) namespaces.get(prefix);
+        if (stack == null) {
+            stack = new ArrayDeque();
+            namespaces.put(prefix, stack);
+        }
+        stack.push(namespaceURI);
+
+        if ( elementNamespaces == null ) {
+            elementNamespaces = new HashMap();
+        }
+        elementNamespaces.put(prefix, namespaceURI);
     }
+
     /**
-     * Create a SAX exception which also understands about the location in
-     * the digester file where the exception occurs
+     * Receive notification of an unparsed entity declaration event.
      *
-     * @return the new exception
+     * @param name The unparsed entity name
+     * @param publicId The public identifier (if any)
+     * @param systemId The system identifier (if any)
+     * @param notation The name of the associated notation
      */
-    protected SAXException createSAXException(String message) {
-        return createSAXException(message, null);
+    @Override
+    public void unparsedEntityDecl(
+        String name,
+        String publicId,
+        String systemId,
+        String notation) {
+    }
+    /**
+     * Forward notification of a parse warning to the application supplied
+     * error handler (if any).  Unlike XMLParser.error(SAXParseException) and
+     * XMLParser.fatalError(SAXParseException), this implementation will
+     * NOT throw a SAXException by default if no error handler is supplied.
+     *
+     * @param exception The warning information
+     * @throws SAXException if a parsing exception occurs
+     */
+    @Override
+    public void warning(SAXParseException exception) throws SAXException {
+        log.error(
+            "Parse Warning at line "
+                + exception.getLineNumber()
+                + " column "
+                + exception.getColumnNumber()
+                + ": "
+                + exception.getMessage(),
+            exception);
+        if (errorHandler != null) {
+            errorHandler.warning(exception);
+        }
     }
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/servlet/JellyServlet.java b/core/src/main/java/org/apache/commons/jelly/servlet/JellyServlet.java
index 443cdbd0..8b551e01 100644
--- a/core/src/main/java/org/apache/commons/jelly/servlet/JellyServlet.java
+++ b/core/src/main/java/org/apache/commons/jelly/servlet/JellyServlet.java
@@ -48,6 +48,22 @@ public class JellyServlet extends HttpServlet {
      */
     public static final String RESPONSE = "response";
 
+    /**
+     * See org.apache.velocity.servlet.VelocityServlet#createContext
+     * @param req
+     * @param res
+     * @return a new context.
+     */
+    protected JellyContext createContext(
+        HttpServletRequest req,
+        HttpServletResponse res) {
+
+        JellyContext ctx = new JellyServletContext(getServletContext());
+        ctx.setVariable(REQUEST, req);
+        ctx.setVariable(RESPONSE, res);
+        return ctx;
+    }
+
     @Override
     protected void doGet(
         HttpServletRequest request,
@@ -87,19 +103,42 @@ public class JellyServlet extends HttpServlet {
     }
 
     /**
-     * See org.apache.velocity.servlet.VelocityServlet#createContext
-     * @param req
-     * @param res
-     * @return a new context.
+     * Invoked when there is an error thrown in any part of doRequest() processing.
+     * <br><br>
+     * Default will send a simple HTML response indicating there was a problem.
+     *<br><br>
+     * Ripped from VelocityServlet.
+     *
+     * @param request original HttpServletRequest from servlet container.
+     * @param response HttpServletResponse object from servlet container.
+     * @param cause  Exception that was thrown by some other part of process.
      */
-    protected JellyContext createContext(
-        HttpServletRequest req,
-        HttpServletResponse res) {
+    protected void error(
+        HttpServletRequest request,
+        HttpServletResponse response,
+        Exception cause)
+        throws ServletException, IOException {
 
-        JellyContext ctx = new JellyServletContext(getServletContext());
-        ctx.setVariable(REQUEST, req);
-        ctx.setVariable(RESPONSE, res);
-        return ctx;
+        StringBuilder html = new StringBuilder();
+        html.append("<html>");
+        html.append("<title>Error</title>");
+        html.append("<body bgcolor=\"#ffffff\">");
+        html.append("<h2>JellyServlet : Error processing the script</h2>");
+        html.append("<pre>");
+        String why = cause.getMessage();
+        if (why != null && why.trim().length() > 0) {
+            html.append(why);
+            html.append("<br>");
+        }
+
+        StringWriter sw = new StringWriter();
+        cause.printStackTrace(new PrintWriter(sw));
+
+        html.append(sw.toString());
+        html.append("</pre>");
+        html.append("</body>");
+        html.append("</html>");
+        response.getOutputStream().print(html.toString());
     }
 
     /**
@@ -154,43 +193,4 @@ public class JellyServlet extends HttpServlet {
         xmlOutput.close();
         output.flush();
     }
-
-    /**
-     * Invoked when there is an error thrown in any part of doRequest() processing.
-     * <br><br>
-     * Default will send a simple HTML response indicating there was a problem.
-     *<br><br>
-     * Ripped from VelocityServlet.
-     *
-     * @param request original HttpServletRequest from servlet container.
-     * @param response HttpServletResponse object from servlet container.
-     * @param cause  Exception that was thrown by some other part of process.
-     */
-    protected void error(
-        HttpServletRequest request,
-        HttpServletResponse response,
-        Exception cause)
-        throws ServletException, IOException {
-
-        StringBuilder html = new StringBuilder();
-        html.append("<html>");
-        html.append("<title>Error</title>");
-        html.append("<body bgcolor=\"#ffffff\">");
-        html.append("<h2>JellyServlet : Error processing the script</h2>");
-        html.append("<pre>");
-        String why = cause.getMessage();
-        if (why != null && why.trim().length() > 0) {
-            html.append(why);
-            html.append("<br>");
-        }
-
-        StringWriter sw = new StringWriter();
-        cause.printStackTrace(new PrintWriter(sw));
-
-        html.append(sw.toString());
-        html.append("</pre>");
-        html.append("</body>");
-        html.append("</html>");
-        response.getOutputStream().print(html.toString());
-    }
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/servlet/JellyServletContext.java b/core/src/main/java/org/apache/commons/jelly/servlet/JellyServletContext.java
index 3b987982..ea5e635f 100644
--- a/core/src/main/java/org/apache/commons/jelly/servlet/JellyServletContext.java
+++ b/core/src/main/java/org/apache/commons/jelly/servlet/JellyServletContext.java
@@ -35,15 +35,20 @@ public class JellyServletContext extends JellyContext {
     public JellyServletContext() {
     }
 
-    public JellyServletContext(final ServletContext ctx) {
+    public JellyServletContext(final JellyContext parent, final ServletContext ctx) {
+        super(parent);
         this.ctx = ctx;
     }
 
-    public JellyServletContext(final JellyContext parent, final ServletContext ctx) {
-        super(parent);
+    public JellyServletContext(final ServletContext ctx) {
         this.ctx = ctx;
     }
 
+    @Override
+    protected JellyContext createChildContext() {
+        return new JellyServletContext(this, ctx);
+    }
+
     /**
      * Allow access of relative URIs when performing &lt;j:include&gt;.
      *
@@ -66,9 +71,4 @@ public class JellyServletContext extends JellyContext {
     public InputStream getResourceAsStream(final String s) {
         return ctx.getResourceAsStream(s);
     }
-
-    @Override
-    protected JellyContext createChildContext() {
-        return new JellyServletContext(this, ctx);
-    }
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/tags/Resources.java b/core/src/main/java/org/apache/commons/jelly/tags/Resources.java
index f0015bb1..cd877510 100644
--- a/core/src/main/java/org/apache/commons/jelly/tags/Resources.java
+++ b/core/src/main/java/org/apache/commons/jelly/tags/Resources.java
@@ -60,13 +60,6 @@ public class Resources {
     return rb.getString(name);
     }
 
-    /** Gets a message with arbitrarily many arguments. */
-    public static String getMessage(String name, Object[] a)
-        throws MissingResourceException {
-    String res = rb.getString(name);
-    return MessageFormat.format(res, a);
-    }
-
     /** Gets a message with one argument. */
     public static String getMessage(String name, Object a1)
         throws MissingResourceException {
@@ -121,4 +114,11 @@ public class Resources {
     return getMessage(name, new Object[] { a1, a2, a3, a4, a5, a6 });
     }
 
+    /** Gets a message with arbitrarily many arguments. */
+    public static String getMessage(String name, Object[] a)
+        throws MissingResourceException {
+    String res = rb.getString(name);
+    return MessageFormat.format(res, a);
+    }
+
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/tags/core/ArgTag.java b/core/src/main/java/org/apache/commons/jelly/tags/core/ArgTag.java
index 470cecde..9104e212 100644
--- a/core/src/main/java/org/apache/commons/jelly/tags/core/ArgTag.java
+++ b/core/src/main/java/org/apache/commons/jelly/tags/core/ArgTag.java
@@ -42,141 +42,12 @@ public class ArgTag extends BaseClassLoaderTag {
     // constructors
     //-------------------------------------------------------------------------
 
-    public ArgTag() {
-    }
+    /** My bag of converters, by target Class */
+    private static Map converterMap = new HashMap();
 
     // attribute setters
     //-------------------------------------------------------------------------
 
-    /**
-     * The name of the argument class or type, if any.
-     * This may be a fully specified class name or
-     * a primitive type name
-     * (<code>boolean</code>, <code>int</code>, <code>double</code>, etc.).
-     */
-    public void setType(String type) {
-        this.typeString = type;
-    }
-
-    /** The (possibly null) value of this argument. */
-    public void setValue(Object value) {
-        this.value= value;
-    }
-
-    // tag methods
-    //-------------------------------------------------------------------------
-
-    @Override
-    public void doTag(XMLOutput output) throws JellyTagException {
-        invokeBody(output);
-
-        Class klass = null;
-        if ("boolean".equals(typeString)) {
-            klass = Boolean.TYPE;
-            assertNotNull(value);
-        } else if ("byte".equals(typeString)) {
-            klass = Byte.TYPE;
-            assertNotNull(value);
-        } else if ("short".equals(typeString)) {
-            klass = Short.TYPE;
-            assertNotNull(value);
-        } else if ("int".equals(typeString)) {
-            klass = Integer.TYPE;
-            assertNotNull(value);
-        } else if ("char".equals(typeString)) {
-            klass = Character.TYPE;
-            assertNotNull(value);
-        } else if ("float".equals(typeString)) {
-            klass = Float.TYPE;
-            assertNotNull(value);
-        } else if ("long".equals(typeString)) {
-            klass = Long.TYPE;
-            assertNotNull(value);
-        } else if ("double".equals(typeString)) {
-            klass = Double.TYPE;
-            assertNotNull(value);
-        } else if (null != typeString) {
-            try {
-              // klass = getClassLoader().loadClass(typeString);
-              // JELLY-274: rather use the three args static class-load-method
-              klass = Class.forName(typeString, false, getClassLoader());
-            } catch (ClassNotFoundException e) {
-                throw new JellyTagException(e);
-            }
-        } else if (null == value) { // and (by construction) null == typeString
-            klass = Object.class;
-        } else {
-            klass = value.getClass();
-        }
-
-        if (!isInstanceOf(klass, value)) {
-            if (klass.equals(Class.class)) {
-                try {
-                    // value = getClassLoader().loadClass((String) value);
-                    // JELLY-274: rather use three-args class.forName
-                    value = Class.forName((String) value, false, getClassLoader());
-                } catch (ClassNotFoundException e) {
-                    throw new JellyTagException(e);
-                }
-            } else {
-                value = convert(klass, value);
-            }
-        }
-
-        ArgTagParent parent = (ArgTagParent)findAncestorWithClass(ArgTagParent.class);
-        if (null == parent) {
-            throw new JellyTagException("This tag must be enclosed inside an ArgTagParent implementation (for example, <new> or <invoke>)");
-        } else {
-            parent.addArgument(klass, value);
-        }
-    }
-
-    // private methods
-    //-------------------------------------------------------------------------
-
-    private void assertNotNull(Object value) throws JellyTagException {
-        if (null == value) {
-            throw new JellyTagException("A " + typeString + " instance cannot be null.");
-        }
-    }
-
-    private boolean isInstanceOf(Class klass, Object value) {
-        return (null == value || (klass.isInstance(value)));
-    }
-
-    // attributes
-    //-------------------------------------------------------------------------
-
-    /** The name of the parameter type, if any. */
-    private String typeString;
-
-    /** The value of the parameter, if any */
-    private Object value;
-
-    // static stuff
-    //-------------------------------------------------------------------------
-
-    private static Object convert(Class klass, Object value) throws JellyTagException {
-        if (null == value) {
-            return null;
-        } else if (!klass.isInstance(value)) {
-            Converter converter = (Converter) (converterMap.get(klass));
-            if (null == converter) {
-                throw new JellyTagException("Can't convert " + value + " to " + klass);
-            }
-            try {
-                return converter.convert(klass, value);
-            } catch (ConversionException e) {
-                throw new JellyTagException("Can't convert " + value + " to " + klass + " (" + e.toString() + ")", e);
-            }
-        } else {
-            return value;
-        }
-
-    }
-
-    /** My bag of converters, by target Class */
-    private static Map converterMap = new HashMap();
     // these inner classes should probably move to beanutils
     static {
         {
@@ -191,6 +62,8 @@ public class ArgTag extends BaseClassLoaderTag {
         }
         {
             Converter c = new Converter() {
+                private Converter inner = new ByteConverter();
+
                 @Override
                 public Object convert(Class klass, Object value) {
                     if (value instanceof Number) {
@@ -199,14 +72,14 @@ public class ArgTag extends BaseClassLoaderTag {
                         return inner.convert(klass, value);
                     }
                 }
-
-                private Converter inner = new ByteConverter();
             };
             converterMap.put(Byte.TYPE, c);
             converterMap.put(Byte.class, c);
         }
         {
             Converter c = new Converter() {
+                private Converter inner = new ShortConverter();
+
                 @Override
                 public Object convert(Class klass, Object value) {
                     if (value instanceof Number) {
@@ -215,14 +88,14 @@ public class ArgTag extends BaseClassLoaderTag {
                         return inner.convert(klass, value);
                     }
                 }
-
-                private Converter inner = new ShortConverter();
             };
             converterMap.put(Short.TYPE, c);
             converterMap.put(Short.class, c);
         }
         {
             Converter c = new Converter() {
+                private Converter inner = new IntegerConverter();
+
                 @Override
                 public Object convert(Class klass, Object value) {
                     if (value instanceof Number) {
@@ -231,14 +104,14 @@ public class ArgTag extends BaseClassLoaderTag {
                         return inner.convert(klass, value);
                     }
                 }
-
-                private Converter inner = new IntegerConverter();
             };
             converterMap.put(Integer.TYPE, c);
             converterMap.put(Integer.class, c);
         }
         {
             Converter c = new Converter() {
+                private Converter inner = new LongConverter();
+
                 @Override
                 public Object convert(Class klass, Object value) {
                     if (value instanceof Number) {
@@ -247,14 +120,14 @@ public class ArgTag extends BaseClassLoaderTag {
                         return inner.convert(klass, value);
                     }
                 }
-
-                private Converter inner = new LongConverter();
             };
             converterMap.put(Long.TYPE, c);
             converterMap.put(Long.class, c);
         }
         {
             Converter c = new Converter() {
+                private Converter inner = new FloatConverter();
+
                 @Override
                 public Object convert(Class klass, Object value) {
                     if (value instanceof Number) {
@@ -263,14 +136,14 @@ public class ArgTag extends BaseClassLoaderTag {
                         return inner.convert(klass, value);
                     }
                 }
-
-                private Converter inner = new FloatConverter();
             };
             converterMap.put(Float.TYPE, c);
             converterMap.put(Float.class, c);
         }
         {
             Converter c = new Converter() {
+                private Converter inner = new DoubleConverter();
+
                 @Override
                 public Object convert(Class klass, Object value) {
                     if (value instanceof Number) {
@@ -279,11 +152,138 @@ public class ArgTag extends BaseClassLoaderTag {
                         return inner.convert(klass, value);
                     }
                 }
-
-                private Converter inner = new DoubleConverter();
             };
             converterMap.put(Double.TYPE, c);
             converterMap.put(Double.class, c);
         }
     }
+
+    private static Object convert(Class klass, Object value) throws JellyTagException {
+        if (null == value) {
+            return null;
+        } else if (!klass.isInstance(value)) {
+            Converter converter = (Converter) (converterMap.get(klass));
+            if (null == converter) {
+                throw new JellyTagException("Can't convert " + value + " to " + klass);
+            }
+            try {
+                return converter.convert(klass, value);
+            } catch (ConversionException e) {
+                throw new JellyTagException("Can't convert " + value + " to " + klass + " (" + e.toString() + ")", e);
+            }
+        } else {
+            return value;
+        }
+
+    }
+
+    // tag methods
+    //-------------------------------------------------------------------------
+
+    /** The name of the parameter type, if any. */
+    private String typeString;
+
+    // private methods
+    //-------------------------------------------------------------------------
+
+    /** The value of the parameter, if any */
+    private Object value;
+
+    public ArgTag() {
+    }
+
+    // attributes
+    //-------------------------------------------------------------------------
+
+    private void assertNotNull(Object value) throws JellyTagException {
+        if (null == value) {
+            throw new JellyTagException("A " + typeString + " instance cannot be null.");
+        }
+    }
+
+    @Override
+    public void doTag(XMLOutput output) throws JellyTagException {
+        invokeBody(output);
+
+        Class klass = null;
+        if ("boolean".equals(typeString)) {
+            klass = Boolean.TYPE;
+            assertNotNull(value);
+        } else if ("byte".equals(typeString)) {
+            klass = Byte.TYPE;
+            assertNotNull(value);
+        } else if ("short".equals(typeString)) {
+            klass = Short.TYPE;
+            assertNotNull(value);
+        } else if ("int".equals(typeString)) {
+            klass = Integer.TYPE;
+            assertNotNull(value);
+        } else if ("char".equals(typeString)) {
+            klass = Character.TYPE;
+            assertNotNull(value);
+        } else if ("float".equals(typeString)) {
+            klass = Float.TYPE;
+            assertNotNull(value);
+        } else if ("long".equals(typeString)) {
+            klass = Long.TYPE;
+            assertNotNull(value);
+        } else if ("double".equals(typeString)) {
+            klass = Double.TYPE;
+            assertNotNull(value);
+        } else if (null != typeString) {
+            try {
+              // klass = getClassLoader().loadClass(typeString);
+              // JELLY-274: rather use the three args static class-load-method
+              klass = Class.forName(typeString, false, getClassLoader());
+            } catch (ClassNotFoundException e) {
+                throw new JellyTagException(e);
+            }
+        } else if (null == value) { // and (by construction) null == typeString
+            klass = Object.class;
+        } else {
+            klass = value.getClass();
+        }
+
+        if (!isInstanceOf(klass, value)) {
+            if (klass.equals(Class.class)) {
+                try {
+                    // value = getClassLoader().loadClass((String) value);
+                    // JELLY-274: rather use three-args class.forName
+                    value = Class.forName((String) value, false, getClassLoader());
+                } catch (ClassNotFoundException e) {
+                    throw new JellyTagException(e);
+                }
+            } else {
+                value = convert(klass, value);
+            }
+        }
+
+        ArgTagParent parent = (ArgTagParent)findAncestorWithClass(ArgTagParent.class);
+        if (null == parent) {
+            throw new JellyTagException("This tag must be enclosed inside an ArgTagParent implementation (for example, <new> or <invoke>)");
+        } else {
+            parent.addArgument(klass, value);
+        }
+    }
+
+    // static stuff
+    //-------------------------------------------------------------------------
+
+    private boolean isInstanceOf(Class klass, Object value) {
+        return (null == value || (klass.isInstance(value)));
+    }
+
+    /**
+     * The name of the argument class or type, if any.
+     * This may be a fully specified class name or
+     * a primitive type name
+     * (<code>boolean</code>, <code>int</code>, <code>double</code>, etc.).
+     */
+    public void setType(String type) {
+        this.typeString = type;
+    }
+    /** The (possibly null) value of this argument. */
+    public void setValue(Object value) {
+        this.value= value;
+    }
 }
diff --git a/core/src/main/java/org/apache/commons/jelly/tags/core/BaseClassLoaderTag.java b/core/src/main/java/org/apache/commons/jelly/tags/core/BaseClassLoaderTag.java
index 5a1fc358..befd7ed7 100644
--- a/core/src/main/java/org/apache/commons/jelly/tags/core/BaseClassLoaderTag.java
+++ b/core/src/main/java/org/apache/commons/jelly/tags/core/BaseClassLoaderTag.java
@@ -50,6 +50,13 @@ public abstract class BaseClassLoaderTag extends TagSupport {
         return ClassLoaderUtils.getClassLoader(classLoader, useContextClassLoader, getClass());
     }
 
+    /**
+     * Gets the boolean as to whether the context classloader should be used.
+     */
+    public boolean getUseContextClassLoader() {
+        return useContextClassLoader;
+    }
+
     /**
... 27588 lines suppressed ...