001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.cocoon.transformation;
018:
019: import org.apache.avalon.framework.parameters.Parameters;
020: import org.apache.cocoon.ProcessingException;
021: import org.apache.cocoon.caching.CacheableProcessingComponent;
022: import org.apache.cocoon.environment.SourceResolver;
023: import org.apache.excalibur.source.SourceValidity;
024: import org.apache.excalibur.source.impl.validity.NOPValidity;
025: import org.xml.sax.Attributes;
026: import org.xml.sax.SAXException;
027: import org.xml.sax.helpers.AttributesImpl;
028:
029: import java.io.IOException;
030: import java.util.Map;
031:
032: /**
033: * @cocoon.sitemap.component.documentation
034: * The filter transformer can be used to let only an amount of elements through in
035: * a given block.
036: *
037: * @cocoon.sitemap.component.name filter
038: * @cocoon.sitemap.component.documentation.caching TBD
039: * @cocoon.sitemap.component.logger sitemap.transformer.filter
040: *
041: *
042: * The filter transformer can be used to let only an amount of elements through in
043: * a given block.
044: *
045: * <p>Usage in the sitemap:
046: * <map:transform type="filter">
047: * <map:parameter name="element-name" value="row"/>
048: * <map:parameter name="count" value="5"/>
049: * <map:parameter name="blocknr" value="3"/>
050: * </map:transform>
051: *
052: * <p>Only the 3rd block will be shown, containing only 5 row elements.
053: *
054: * <p><b>Known limitation: behaviour of transformer when trigger elements are nested
055: * is not predictable.</b>
056: *
057: * @author <a href="mailto:sven.beauprez@the-ecorp.com">Sven Beauprez</a>
058: * @author <a href="mailto:cziegeler@apache.org">Carsten Ziegeler</a>
059: * @version CVS $Id: FilterTransformer.java 433543 2006-08-22 06:22:54Z crossley $
060: */
061: public class FilterTransformer extends AbstractTransformer implements
062: CacheableProcessingComponent {
063:
064: private static final String ELEMENT = "element-name";
065: private static final String COUNT = "count";
066: private static final String BLOCKNR = "blocknr";
067: private static final String BLOCK = "block";
068: private static final String BLOCKID = "id";
069: private static final int DEFAULT_COUNT = 10;
070: private static final int DEFAULT_BLOCK = 1;
071:
072: protected int counter;
073: protected int count;
074: protected int blocknr;
075: protected int currentBlocknr;
076: protected String elementName;
077: protected String parentName;
078: protected boolean skip;
079: protected boolean foundIt;
080:
081: /** BEGIN SitemapComponent methods **/
082: public void setup(SourceResolver resolver, Map objectModel,
083: String source, Parameters parameters)
084: throws ProcessingException, SAXException, IOException {
085: this .counter = 0;
086: this .currentBlocknr = 0;
087: this .skip = false;
088: this .foundIt = false;
089: this .parentName = null;
090: this .elementName = parameters.getParameter(ELEMENT, "");
091: this .count = parameters.getParameterAsInteger(COUNT,
092: DEFAULT_COUNT);
093: this .blocknr = parameters.getParameterAsInteger(BLOCKNR,
094: DEFAULT_BLOCK);
095: if (this .elementName == null || this .elementName.length() == 0
096: || this .count == 0) {
097: throw new ProcessingException("FilterTransformer: both "
098: + ELEMENT + " and " + COUNT
099: + " parameters need to be specified");
100: }
101: }
102:
103: /**
104: * Generate the unique key.
105: * This key must be unique inside the space of this component.
106: * This method must be invoked before the generateValidity() method.
107: *
108: * @return The generated key or <code>0</code> if the component
109: * is currently not cacheable.
110: */
111: public java.io.Serializable getKey() {
112: return this .elementName + '<' + this .count + '>' + this .blocknr;
113: }
114:
115: /**
116: * Generate the validity object.
117: * Before this method can be invoked the generateKey() method
118: * must be invoked.
119: *
120: * @return The generated validity object or <code>null</code> if the
121: * component is currently not cacheable.
122: */
123: public SourceValidity getValidity() {
124: return NOPValidity.SHARED_INSTANCE;
125: }
126:
127: /** BEGIN SAX ContentHandler handlers **/
128: public void startElement(String uri, String name, String raw,
129: Attributes attributes) throws SAXException {
130: if (name.equalsIgnoreCase(elementName)) {
131: this .foundIt = true;
132: this .counter++;
133: if (this .counter <= (this .count * (this .blocknr))
134: && this .counter > (this .count * (this .blocknr - 1))) {
135: this .skip = false;
136: } else {
137: this .skip = true;
138: }
139: if (this .currentBlocknr != (int) Math
140: .ceil((float) this .counter / this .count)) {
141: this .currentBlocknr = (int) Math
142: .ceil((float) this .counter / this .count);
143: AttributesImpl attr = new AttributesImpl();
144: attr.addAttribute("", BLOCKID, BLOCKID, "CDATA", String
145: .valueOf(this .currentBlocknr));
146: if (this .counter < this .count) {
147: super .contentHandler.startElement("", BLOCK, BLOCK,
148: attr);
149: } else {
150: // fix Bugzilla Bug 13904, check if counter == 1
151: // in this case there is no startElement("", BLOCK, BLOCK)
152: // written, yet
153: if (this .counter > 1) {
154: super .contentHandler.endElement("", BLOCK,
155: BLOCK);
156: }
157: super .contentHandler.startElement("", BLOCK, BLOCK,
158: attr);
159: }
160: }
161: } else if (!this .foundIt) {
162: this .parentName = name;
163: }
164: if (!this .skip) {
165: super .contentHandler.startElement(uri, name, raw,
166: attributes);
167: }
168: }
169:
170: public void endElement(String uri, String name, String raw)
171: throws SAXException {
172: if (this .foundIt && name.equals(this .parentName)) {
173: // FIXME: VG: This will fail on XML like:
174: // <parent>
175: // <element>
176: // <parent>
177: super .contentHandler.endElement("", BLOCK, BLOCK);
178: super .contentHandler.endElement(uri, name, raw);
179: this .foundIt = false;
180: this .skip = false;
181: } else if (!this .skip) {
182: super .contentHandler.endElement(uri, name, raw);
183: }
184: }
185:
186: public void characters(char c[], int start, int len)
187: throws SAXException {
188: if (!this .skip) {
189: super .contentHandler.characters(c, start, len);
190: }
191: }
192:
193: public void processingInstruction(String target, String data)
194: throws SAXException {
195: if (!this .skip) {
196: super .contentHandler.processingInstruction(target, data);
197: }
198: }
199:
200: public void startEntity(String name) throws SAXException {
201: if (!this .skip) {
202: super .lexicalHandler.startEntity(name);
203: }
204: }
205:
206: public void endEntity(String name) throws SAXException {
207: if (!this .skip) {
208: super .lexicalHandler.endEntity(name);
209: }
210: }
211:
212: public void startCDATA() throws SAXException {
213: if (!this .skip) {
214: super .lexicalHandler.startCDATA();
215: }
216: }
217:
218: public void endCDATA() throws SAXException {
219: if (!this .skip) {
220: super .lexicalHandler.endCDATA();
221: }
222: }
223:
224: public void comment(char ch[], int start, int len)
225: throws SAXException {
226: if (!this.skip) {
227: super.lexicalHandler.comment(ch, start, len);
228: }
229: }
230: }
|