KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > forrest > util > IdGeneratorTransformer


1 /*
2  * Copyright 1999-2004 The Apache Software Foundation or its licensors,
3  * as applicable.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17 package org.apache.forrest.util;
18
19 import java.io.IOException JavaDoc;
20 import java.io.Serializable JavaDoc;
21 import java.net.URLEncoder JavaDoc;
22 import java.util.Map JavaDoc;
23
24 import org.apache.avalon.framework.activity.Disposable;
25 import org.apache.avalon.framework.configuration.Configurable;
26 import org.apache.avalon.framework.configuration.Configuration;
27 import org.apache.avalon.framework.configuration.ConfigurationException;
28 import org.apache.avalon.framework.parameters.Parameters;
29 import org.apache.avalon.framework.service.ServiceManager;
30 import org.apache.avalon.framework.service.ServiceException;
31 import org.apache.cocoon.transformation.AbstractDOMTransformer;
32 import org.apache.cocoon.ProcessingException;
33 import org.apache.cocoon.caching.CacheableProcessingComponent;
34 import org.apache.cocoon.environment.SourceResolver;
35 import org.apache.cocoon.util.HashUtil;
36 import org.apache.excalibur.source.SourceValidity;
37 import org.apache.excalibur.source.impl.validity.NOPValidity;
38 import org.apache.excalibur.xml.xpath.XPathProcessor;
39 import org.w3c.dom.Document JavaDoc;
40 import org.w3c.dom.Element JavaDoc;
41 import org.w3c.dom.NodeList JavaDoc;
42 import org.xml.sax.SAXException JavaDoc;
43
44 /**
45  * A Transformer for adding a URL-encoded 'id' attribute to a node, whose value
46  * is determined by the string value of another node.
47  *
48  * <p>
49  * For example, if we were parsing XML like:
50  * <pre>
51  * &lt;section>
52  * &lt;title>Blah blah</title>
53  * ....
54  * &lt;/section>
55  * </pre>
56  * We could add an 'id' attribute to the 'section' element with a transformer
57  * configured as follows:
58  * <pre>
59  * &lt;map:transformer name="idgen"
60  * SRC="org.apache.cocoon.transformation.IdGeneratorTransformer">
61  * &lt;element>/document/body//*[local-name() = 'section']&lt;/element>
62  * &lt;id>title/text()&lt;/id>
63  * &lt;/map:transformer>
64  * </pre>
65  * The 'element' parameter is an XPath expression identifying candidates for
66  * having an id added.
67  * The 'id' parameter is an XPath relative to each found 'element', and
68  * specifies a string to use as the id attribute value. The value will be URL
69  * encoded in the id attribute. If an id with the specified value already
70  * exists, the new id will be made unique with XPath's
71  * <code>generate-id()</code> function.
72  * <p>
73  * By default, the added attribute is called <code>id</code>. This can be
74  * altered by specifying an <code>id-attr</code> parameter:
75  * <pre>
76  * &lt;id-attr>ID&lt;/id-attr>
77  * </pre>
78  * If the specified attribute is already present on the node, it will not be
79  * rewritten.
80  */

81 public class IdGeneratorTransformer
82     extends AbstractDOMTransformer
83     implements CacheableProcessingComponent, Configurable, Disposable
84 {
85
86     /** XPath Processor */
87     private XPathProcessor processor = null;
88
89     protected String JavaDoc elementXPath = null;
90     protected String JavaDoc idXPath = null;
91     protected String JavaDoc idAttr = null;
92
93     public void configure(Configuration configuration) throws ConfigurationException {
94         getLogger().info("## || Configuring IdGeneratorTransformer with "+configuration);
95         this.elementXPath = configuration.getChild("element").getValue(null);
96         this.idXPath = configuration.getChild("id").getValue(null);
97         this.idAttr = configuration.getChild("id-attr").getValue("id");
98         if (elementXPath == null) {
99             throw new ConfigurationException(
100                     "## The IdGenerator 'element' parameter must be specified. For example, "+
101                     "<element>/document/body//*[local-name() = 'section']</element>");
102         }
103         if (idXPath == null) {
104             throw new ConfigurationException(
105                     "## The IdGenerator 'id' parameter must be specified. For example,"+
106                     "<id>title/text()</id>");
107         }
108     }
109
110     public void setup(SourceResolver resolver, Map JavaDoc objectModel,
111             String JavaDoc source, Parameters parameters)
112         throws ProcessingException, SAXException JavaDoc, IOException JavaDoc
113     {
114         super.setup(resolver, objectModel, source, parameters);
115         /*
116          If you prefer dynamic configuration, use this instead of
117          configure(), and remember to clear the fields in recycle()
118
119         this.elementXPath = (String)parameters.getParameter("element", null);
120         if (this.elementXPath == null) {
121             throw new ProcessingException(
122                     "The IdGenerator 'element' parameter must be specified. For example, "+
123                     "<map:parameter name=\"element\" value=\"/document/body//*[local-name() = 'section']\"/>");
124         }
125         this.idXPath = (String)parameters.getParameter("id", null);
126         if (idXPath == null) {
127             throw new ProcessingException(
128                     "The IdGenerator 'id' parameter must be specified. For example,"+
129                     "<map:parameter name=\"id\" value=\"title/text()\"/>");
130         }
131         this.idAttr = (String)parameters.getParameter("id-attr", "id");
132         */

133     }
134
135     public void service(ServiceManager manager) throws ServiceException {
136         super.service(manager);
137         try {
138             this.processor = (XPathProcessor)this.manager.lookup(XPathProcessor.ROLE);
139         } catch (Exception JavaDoc e) {
140             getLogger().error("cannot obtain XPathProcessor", e);
141         }
142     }
143
144     /** Implementation of a template method declared in AbstractDOMTransformer.
145      * @param doc DOM of XML received by the transformer
146      * @return A pared-down DOM.
147      */

148     protected Document JavaDoc transform(Document JavaDoc doc) {
149         getLogger().debug("## Transforming with element='"+elementXPath+"', id='"+idXPath+"'");
150         Document JavaDoc newDoc = null;
151         try {
152             newDoc = addIds(doc, elementXPath, idXPath);
153         } catch (SAXException JavaDoc se) {
154             // Really ought to be able to propagate these to caller
155
getLogger().error("Error when transforming XML: "+se.getMessage(), se.getException());
156             throw new RuntimeException JavaDoc("Error transforming XML. See error log for details: "+se.getMessage()+". Nested exception: "+se.getException().getMessage());
157         }
158         return newDoc;
159     }
160
161     private Document JavaDoc addIds(Document JavaDoc doc, String JavaDoc elementXPath, String JavaDoc idXPath) throws SAXException JavaDoc {
162         getLogger().debug("## Using element XPath "+elementXPath);
163         NodeList JavaDoc sects = processor.selectNodeList(doc, elementXPath);
164         getLogger().debug("## .. got "+sects.getLength()+" sections");
165         for (int i=0; i<sects.getLength(); i++) {
166             Element JavaDoc sect = (Element JavaDoc)sects.item(i);
167             if (!sect.hasAttribute(this.idAttr)) {
168                 sect.normalize();
169                 getLogger().debug("## Using id XPath "+idXPath);
170                 String JavaDoc id = null;
171                 try {
172                   id = processor.evaluateAsString(sect, idXPath).trim();
173                 } catch (Exception JavaDoc e) {
174                     throw new SAXException JavaDoc("'id' XPath expression '"+idXPath+"' does not return a text node: "+e, e);
175                 }
176                 getLogger().info("## Got id "+id);
177                 // Use of the new version of encode method to avoid to generate URI such as :
178
// - <a HREF="#Quelques+r%E8gles...">Quelques règles...</a>
179
// Which is not, curiously, well decoded...
180
// The new methode - which allow to specify "UTF-8" gives :
181
// - <a HREF="#Quelques+r%C3%A8gles...">Quelques règles...</a>
182
// And it works OK,
183
String JavaDoc newId;
184                 try {
185                   newId = URLEncoder.encode(id, "UTF-8");
186                 }
187                 catch( java.io.UnsupportedEncodingException JavaDoc e )
188                 {
189                   getLogger().error("cannot encode Id, using generate-id instead...", e);
190                   newId = processor.evaluateAsString(sect, "generate-id()");
191         }
192                 newId = avoidConflicts(doc, sect, this.idAttr, newId);
193                 // Upgrade to DOM 2 support
194
//sect.setAttribute(this.idAttr, newId);
195
sect.setAttributeNS(sect.getNamespaceURI(), this.idAttr, newId);
196             }
197         }
198         return doc;
199     }
200
201     /**
202      * Ensure that IDs aren't repeated in the document. If an element with the
203      * specified id is already present, <code>generate-id</code> is used to
204      * distinguish the new one.
205      */

206     private String JavaDoc avoidConflicts(Document JavaDoc doc, Element JavaDoc sect, String JavaDoc idAttr, String JavaDoc newId) {
207         // We rely on the URLencoding of newId to avoid ' conflicts here:
208
NodeList JavaDoc conflicts = processor.selectNodeList(doc, "//*[@"+idAttr+"='"+newId+"']");
209         int numConflicts = conflicts.getLength();
210         getLogger().info("## "+numConflicts+" conflicts with "+newId);
211         if (numConflicts != 0) {
212             newId += "-"+processor.evaluateAsString(sect, "generate-id()");
213         }
214         return newId;
215     }
216
217     // Cache methods
218

219     /**
220      * Generate the unique key.
221      * This key must be unique inside the space of this component.
222      *
223      * @return A hash of the element and id parameters, thus uniquely
224      * identifying this IdGenerator amongst it's peers.
225      */

226     public Serializable JavaDoc getKey() {
227         return ""+HashUtil.hash(this.elementXPath+this.idXPath);
228     }
229
230     // for backwards-compat
231
public Serializable JavaDoc generateKey() {
232       return getKey();
233     }
234
235
236     /**
237      * Generate the validity object.
238      *
239      * @return An "always valid" SourceValidity object. This transformer has no
240      * inputs other than the incoming SAX events.
241      */

242     public SourceValidity getValidity() {
243         return new NOPValidity();
244     }
245
246     public SourceValidity generateValidity() {
247       return getValidity();
248     }
249
250
251     /**
252      * Recycle the component.
253      */

254     public void recycle() {
255         super.recycle();
256         // Uncomment these if we're dynamically (in the map:transform) configuring
257
//this.elementXPath = null;
258
//this.idXPath = null;
259
// note that we don't turf our processor,
260
}
261
262     /**
263      * dispose
264      */

265     public void dispose() {
266         super.dispose();
267         this.processor = null;
268         this.elementXPath = null;
269         this.idXPath = null;
270         this.idAttr = null;
271     }
272 }
273
Popular Tags