Torsten, although I've said that doing this with xslt is feasible I think I was wrong. So here is a transformer that does a) handle the complete xform binding spec and adds instance data as <value/> child element to form controls and b) adds a @name attribute to them containing the form name + "/" + path to instance data (e.g. name="order_form/address/street") It does not remove anything from the document, e.g. form declarations or @refs. The package name is a temporal convenience and will change. I did waste quite some time toying with an DTM representation, since this transformer is quite slow and DTM looked like it would speed it up a lot. E.g. creating the cached form declaration took half the time. Unfortunately I had to find out the hard way that a number of important API calls are not implemented yet.... :-( Since this transformer caches form declarations which also contain the model necessary for validation, I think it would be nice to "register" this model (+submitInfo) somewhere so that it is available when validation takes place. This doesn't need to be session bound (doesn't register instance data). This information would be useful also when determining which form has been submitted. Caching is done only during the lifetime of a transformer instance as I have no idea how to decide easily if a cached form declaration & instance data is still valid. Ideas? BTW if I understand the xforms WD correctly, form controls need to set the default namespace or child elements like <caption/>, <hint/> &c. need to explicitly carry their namespace. So your sample xform.xml needs to be changed: <xform:textbox ref="order/city" xform="orderForm"> <caption>City</caption> </xform:textbox> becomes <xform:textbox ref="order/city" xform="orderForm" xmlns="http://www.w3.org/2001/06/xforms"> <caption>City</caption> </xform:textbox> I will next look into such a "registry" that keeps track of xform declarations and provides them to a validator. Obviously, I will go and see if I can use the code you posted earlier :-) Chris. -- C h r i s t i a n H a u l [EMAIL PROTECTED] fingerprint: 99B0 1D9D 7919 644A 4837 7D73 FEF9 6856 335A 9E08
package haul; import org.apache.cocoon.transformation.AbstractTransformer; import java.util.Map; import java.util.HashMap; import java.util.Stack; import java.io.IOException; import org.apache.cocoon.transformation.AbstractTransformer; import org.apache.cocoon.ProcessingException; import org.apache.cocoon.environment.SourceResolver; import org.apache.avalon.framework.parameters.Parameters; import org.apache.avalon.excalibur.pool.Poolable; import org.apache.log.Logger; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.AttributesImpl; import org.w3c.dom.Node; import org.w3c.dom.Document; import org.apache.xpath.XPath; import org.apache.xpath.XPathContext; import org.apache.xml.utils.PrefixResolverDefault; import org.apache.cocoon.xml.dom.DOMBuilder; import javax.xml.transform.TransformerException; import org.apache.cocoon.components.parser.Parser; import org.apache.avalon.framework.activity.Disposable; import org.apache.avalon.framework.component.Component; import org.apache.avalon.framework.component.Composable; import org.apache.avalon.framework.component.ComponentManager; import org.apache.avalon.excalibur.pool.Poolable; /** * XFormsTransformer will prepend to all form controls' names their * xpath plus it will create an additional child element to them that * contains the current value of the control. * * The value will be taken from the bound to instance data. * * @author <a href="mailto:[EMAIL PROTECTED]">Christian Haul</a> * @version CVS $Revision: 1.4.1.2 $ $Date: 2001/08/06 13:36:27 $ $Author: haul $ */ public class XFormsTransformer extends AbstractTransformer implements Poolable, Composable, Disposable { // begin support classes /** * This (inner) helper class just allows to store pairs of String on a Stack * */ protected class XFormsTransformerHelper { /** * Name of the xform */ public String xform=null; /** * XPath expression (relative to xform:instance when dealing with controls) */ public String ref=null; XFormsTransformerHelper ( String xform, String ref ) { this.xform=xform; this.ref=ref; } XFormsTransformerHelper () { this.xform=null; this.ref=null; } public String toString() { return "XTH('"+this.xform+"','"+this.ref+"')"; } } /** * Cache structures needed for XPath evaluation. * * Since every lookup of instance data requires an XPath * expression to be evaluated, this class caches the structures * that are required for this. I.e. Xalan creates a DTM * representation first. Unfortunately, DTM has not matured enough * yet to work with it instead of DOM here, although it seems to * be significantly faster. */ protected class XFormsTransformerXPathHelper { boolean initialized = false; XPathContext xpathSupport = null; PrefixResolverDefault prefixResolver = null; Node xform = null; int ctxtNode = 0; XFormsTransformerXPathHelper( Node xform ) { initialized = false; this.xform = xform; } private void initialize() { xpathSupport = new XPathContext(); prefixResolver = new PrefixResolverDefault( (xform.getNodeType() == Node.DOCUMENT_NODE) ? ((Document) xform).getDocumentElement() : xform); ctxtNode = xpathSupport.getDTMHandleFromNode(xform); } /** * Evalutate a XPath expression. Expression needs to select a * single node, otherwise only the first on will be returned. */ public Node selectNode( String xpath ) throws TransformerException { if (!initialized) { initialize (); initialized = true; } XPath _xpath = new XPath(xpath, null, prefixResolver, XPath.SELECT, null); return _xpath.execute(xpathSupport, ctxtNode, prefixResolver).nodeset().nextNode(); } /** * Return document's root node. */ public Node getForm() { return xform; } } // end support classes // begin constants static final String XFORMS_URI = "http://www.w3.org/2001/06/xforms"; static final String MYNAME = "XFORMS TRANS"; // debug purposes static final String INSTANCE_PATH = "//*[local-name(.)='instance' and namespace-uri(.)='"+XFORMS_URI+"']/"; // end constants // begin instance data private DOMBuilder builder; private ComponentManager manager; private Parser parser; protected Stack instancePath = null; // current element's path / binding protected HashMap instanceData = null; // maps form ids (names) to cached form declaration protected String defaultForm = null; protected HashMap idMap = null; // maps ids to xpath expressions protected boolean isForm = false; protected String formName = null; protected String xformsPrefix = "xform"; // end instance data /** BEGIN SitemapComponent methods **/ public void setup(SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws ProcessingException, SAXException, IOException { instancePath = new Stack(); instanceData = new HashMap(); idMap = new HashMap(); isForm = false; formName = null; defaultForm = null; } /** END SitemapComponent methods **/ public void compose(ComponentManager manager) { this.manager = manager; try { getLogger().debug("Looking up " + Parser.ROLE); parser = (Parser)manager.lookup(Parser.ROLE); } catch (Exception e) { getLogger().error("Could not find component", e); } } public void dispose() { if (parser!=null) { manager.release((Component)parser); } } /** BEGIN SAX ContentHandler handlers **/ public void startPrefixMapping(String prefix, String uri) throws SAXException { super.startPrefixMapping(prefix,uri); if (isForm) { builder.startPrefixMapping(prefix,uri); } } public void startElement(String uri, String name, String raw, Attributes attributes) throws SAXException { if (isForm) { // element belongs to a XForms declaration trackXPath( uri, name, raw, attributes ); builder.startElement(uri,name,raw,attributes); super.contentHandler.startElement(uri,name,raw, attributes); } else { if (uri.equalsIgnoreCase(XFORMS_URI)) { if (name.equalsIgnoreCase("xform")) { isForm = true; startXFormDecl(uri, name, raw, attributes); builder.startElement(uri,name,raw,attributes); super.contentHandler.startElement(uri,name,raw, attributes); } else { handleFormControl(uri, name, raw, attributes); } } else { super.contentHandler.startElement(uri,name,raw, attributes); } } } public void endElement(String uri,String name,String raw) throws SAXException { if (isForm) { builder.endElement(uri,name,raw); if (uri.equalsIgnoreCase(XFORMS_URI) && name.equalsIgnoreCase("xform")) { isForm = false; builder.endDocument(); instanceData.put(formName, new XFormsTransformerXPathHelper (builder.getDocument().getFirstChild())); } else { instancePath.pop(); } } else if ( uri.equalsIgnoreCase(XFORMS_URI)) { // Form Control instancePath.pop(); } super.contentHandler.endElement(uri,name,raw); } public void characters(char c[], int start, int len) throws SAXException { if (isForm) { builder.characters(c,start,len); } super.contentHandler.characters(c,start,len); } public void startCDATA() throws SAXException { if (isForm) { builder.startCDATA(); } super.lexicalHandler.startCDATA(); } public void endCDATA() throws SAXException { if (isForm) { builder.endCDATA(); } super.lexicalHandler.endCDATA(); } /** END SAX ContentHandler handlers **/ // private helper methods /** * Keep track of xpath relative to this form's declaration so that * ids can be resolved accordingly. Resolve ids to xpath expressions * and handle indirect binding. */ private void trackXPath( String uri, String name, String raw, Attributes attributes ) { String path = "/"+raw; if ( !instancePath.empty() ){ XFormsTransformerHelper xth = (XFormsTransformerHelper) instancePath.peek(); path = xth.ref+path; } instancePath.push(new XFormsTransformerHelper(formName, path)); String nodeID = attributes.getValue("id"); if ( nodeID != null) { if ("bind".equalsIgnoreCase(name)) { // indirect binding idMap.put(nodeID, new XFormsTransformerHelper(formName, attributes.getValue("ref"))); } else { // direct binding // relocate xpath expression to <xform:instance/> idMap.put( nodeID, new XFormsTransformerHelper( formName, path.substring(path.indexOf(xformsPrefix+":"+"instance")+xformsPrefix.length()+9) ) ); } } } /** * Setup document builder to cache form declaration. */ private void startXFormDecl ( String uri, String name, String raw, Attributes attributes) throws SAXException { // start of form declaration formName = attributes.getValue("id"); if (formName == null) { formName = "__default"; } if (defaultForm == null) { defaultForm = formName; } // create a document from xform data builder = new DOMBuilder(parser); builder.startDocument(); // extract xmlns prefix for xforms if ( raw.indexOf(":") != -1 ) { xformsPrefix=raw.substring(0,raw.indexOf(":")); builder.startPrefixMapping(xformsPrefix,XFORMS_URI); } } /** * Do XPath lookup and add a <value/> child element to the current element * that contains the referenced instance data. */ private void addValueChild ( String xform, String ref ) throws SAXException { super.contentHandler.startElement("","value","value", new AttributesImpl()); try { String thePath = INSTANCE_PATH + ref; Node theData = ((XFormsTransformerXPathHelper) instanceData.get(xform)).selectNode(thePath.toString()); if ( theData != null ) { String theValue = theData.getNodeValue(); if (theValue != null ) { super.contentHandler.characters(theValue.toCharArray(),0,theValue.length()); } else { if (theData.getNodeType()==Node.ELEMENT_NODE && theData.hasChildNodes()) { theValue=theData.getFirstChild().getNodeValue(); super.contentHandler.characters(theValue.toCharArray(),0,theValue.length()); } } } else { getLogger().debug(MYNAME+": path "+thePath+"not found"); } } catch (TransformerException e) { getLogger().debug(MYNAME+" Exception in instance data lookup. Path='"+ref+"'\n"+e); } super.contentHandler.endElement("","value","value"); } /** * Do all necessary steps to resolve scoped, direct & indirect binding, add @name and <value/>. * * <p> For all XForm controls resolve binding and add a child * element holding the referenced value. Add a @name attribute * that reflects the xpath expression to the referenced instance * data, with ids resolved.</p> */ private void handleFormControl(String uri, String name, String raw, Attributes attributes) throws SAXException { // Form Control String ref = attributes.getValue("ref"); String xform = attributes.getValue("xform"); String newName = null; boolean addValue = false; AttributesImpl newAttributes = null; if ( ref == null ) { // no control if (!instancePath.empty()) { XFormsTransformerHelper parent = (XFormsTransformerHelper) instancePath.peek(); ref = parent.ref; xform = parent.xform; } } else { if ( ref.startsWith("/") ){ // absolute path, all is well if ( xform == null ) { // FIXME: should we check for enclosing element's form? xform = defaultForm; } } else if ( ref.startsWith("id(") ) { // resolve id and find associated form String id = ref.substring(3,ref.lastIndexOf(")")); XFormsTransformerHelper xth = (XFormsTransformerHelper) idMap.get(id); ref = xth.ref; xform = xth.xform; } else { // scoped resolution needed ? if (!instancePath.empty()){ XFormsTransformerHelper parent = (XFormsTransformerHelper) instancePath.peek(); if (parent.xform != null && (parent.xform == xform || xform == null)) { // same form ref= parent.ref + "/" + ref; xform=parent.xform; } else { // different forms or parent has no reference data if (xform == null) { xform = defaultForm; ref = "/" + ref; } } } else { // not nested if ( xform == null ) { xform = defaultForm; } ref = "/" + ref; } } newName = xform + ref; } // change name attribute to absolute Path containing xform name if (newName!=null) { newAttributes = new AttributesImpl(attributes); int nameIndex = newAttributes.getIndex("name"); if (nameIndex == -1) { newAttributes.addAttribute("","name","name","CDATA", newName); } else { getLogger().debug(MYNAME+": element "+name+" already has @name! Overwriting it."); newAttributes.setAttribute(nameIndex,"","name","name","CDATA", newName); } addValue=true; } instancePath.push(new XFormsTransformerHelper(xform, ref)); super.contentHandler.startElement(uri,name,raw,(newAttributes==null? attributes : newAttributes)); // lookup instance data // insert new child ("value") if (addValue) { addValueChild(xform, ref); } } }
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, email: [EMAIL PROTECTED]