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.components.language.markup;
018:
019: import org.apache.avalon.framework.logger.Logger;
020:
021: import org.apache.cocoon.ProcessingException;
022: import org.apache.cocoon.components.language.programming.ProgrammingLanguage;
023: import org.apache.cocoon.xml.AbstractXMLPipe;
024: import org.apache.cocoon.xml.XMLConsumer;
025: import org.apache.cocoon.xml.XMLUtils;
026:
027: import org.xml.sax.Attributes;
028: import org.xml.sax.ContentHandler;
029: import org.xml.sax.SAXException;
030: import org.xml.sax.ext.LexicalHandler;
031: import org.xml.sax.helpers.AttributesImpl;
032:
033: import java.io.File;
034: import java.io.IOException;
035: import java.util.ArrayList;
036: import java.util.HashSet;
037: import java.util.Iterator;
038: import java.util.List;
039: import java.util.Set;
040:
041: /**
042: * Base implementation of <code>MarkupLanguage</code>. This class uses
043: * logicsheets as the only means of code generation. Code generation
044: * should be decoupled from this context!!!
045: *
046: * @author <a href="mailto:ricardo@apache.org">Ricardo Rocha</a>
047: * @author <a href="mailto:ssahuc@apache.org">Sebastien Sahuc</a>
048: * @author <a href="mailto:dims@yahoo.com">Davanum Srinivas</a>
049: * @author <a href="mailto:ovidiu@cup.hp.com">Ovidiu Predescu</a>
050: * @author <a href="mailto:vgritsenko@apache.org">Vadim Gritsenko</a>
051: * @version $Id: CocoonMarkupLanguage.java 433543 2006-08-22 06:22:54Z crossley $
052: */
053: public abstract class CocoonMarkupLanguage extends
054: AbstractMarkupLanguage {
055: /**
056: * Store the dependencies of the currently loaded program.
057: */
058: private final Set dependencies = new HashSet();
059:
060: /** The default constructor. */
061: public CocoonMarkupLanguage() {
062: }
063:
064: /**
065: * Recycle this component: clear logic sheet list and dependencies.
066: */
067: public void recycle() {
068: super .recycle();
069: this .dependencies.clear();
070: }
071:
072: /**
073: * Prepare the input source for logicsheet processing and code generation
074: * with a preprocess filter.
075: * The return <code>XMLFilter</code> object is the first filter on the
076: * transformer chain.
077: *
078: * The markup language preprocess filter adds information on the root element
079: * such as creation-date, file-name and file-path, plus it use the the passed
080: * programming language to quote <code>Strings</code> on PCDATA node.
081: *
082: * @param filename The source filename
083: * @param language The target programming language
084: * @return The preprocess filter
085: *
086: * @see PreProcessFilter
087: */
088: protected AbstractXMLPipe getPreprocessFilter(String filename,
089: AbstractXMLPipe filter, ProgrammingLanguage language) {
090: PreProcessFilter prefilter = new PreProcessFilter(filter,
091: filename, language);
092: prefilter.enableLogging(getLogger());
093: return prefilter;
094: }
095:
096: /**
097: * Returns a filter that chain on the fly the requested transformers for source
098: * code generation. This method scans the input SAX events for
099: * <?xml-logicsheet?> processing instructions and top-level
100: * <prefix:logicsheet> elements. Logicsheet declarations are removed from
101: * the input document.
102: *
103: * @param logicsheetMarkupGenerator the logicsheet markup generator
104: * @return XMLFilter the filter that build on the fly the transformer chain
105: */
106: protected TransformerChainBuilderFilter getTransformerChainBuilder(
107: LogicsheetCodeGenerator logicsheetMarkupGenerator) {
108: CocoonTransformerChainBuilderFilter filter = new CocoonTransformerChainBuilderFilter(
109: logicsheetMarkupGenerator);
110: filter.enableLogging(getLogger());
111: return filter;
112: }
113:
114: // This is required here to avoid IllegalAccessError when
115: // CocoonTransformerChainBuilderFilter invokes the method.
116: protected void addLogicsheetToList(LanguageDescriptor language,
117: String logicsheetLocation) throws IOException,
118: SAXException, ProcessingException {
119: super .addLogicsheetToList(language, logicsheetLocation);
120: }
121:
122: /**
123: * Add a dependency on an external file to the document for inclusion in
124: * generated code. This is used to populate a list of <code>File</code>'s
125: * tested for change on each invocation; this information is used to assert
126: * whether regeneration is necessary. XSP uses <xsp:dependency>
127: * elements for this purpose.
128: *
129: * @param location The file path of the dependent file
130: * @see AbstractMarkupLanguage
131: * @see org.apache.cocoon.generation.ServerPagesGenerator
132: * @see org.apache.cocoon.generation.AbstractServerPage
133: */
134: protected void addDependency(String location) {
135: dependencies.add(location);
136: }
137:
138: /**
139: * Returns the root element for this language.
140: */
141: public abstract String getRootElement();
142:
143: //
144: // Inner classes
145: //
146:
147: /**
148: * Preprocess filter for Cocoon Markup languages.
149: * It looks for PI event other that <?xml-logisheet href="...">
150: * for quoting them;
151: * It adds creation-date, file-name and file-path attributes to the root
152: * Element;
153: * And it quotes the PCDATA based by calling the quote method of the
154: * programming language.
155: *
156: * @see org.xml.sax.ContentHandler
157: */
158: public class PreProcessFilter extends AbstractXMLPipe {
159: protected Logger log;
160:
161: protected AbstractXMLPipe filter;
162:
163: protected String filename;
164:
165: protected boolean isRootElem;
166:
167: protected ProgrammingLanguage language;
168:
169: protected String localPrefix;
170:
171: /**
172: * @param filename the filename
173: * @param language the programming language
174: */
175: public PreProcessFilter(AbstractXMLPipe filter,
176: String filename, ProgrammingLanguage language) {
177: super ();
178: this .filename = filename;
179: this .language = language;
180: this .filter = filter;
181: // Put meself in front of filter
182: super .setLexicalHandler(this .filter);
183: super .setContentHandler(this .filter);
184: }
185:
186: public void setConsumer(XMLConsumer consumer) {
187: // Add consumer after filter
188: this .filter.setConsumer(consumer);
189: }
190:
191: public void setContentHandler(ContentHandler handler) {
192: this .filter.setContentHandler(handler);
193: }
194:
195: public void setLexicalHandler(LexicalHandler handler) {
196: this .filter.setLexicalHandler(handler);
197: }
198:
199: public void enableLogging(Logger logger) {
200: if (this .log == null) {
201: this .log = logger;
202: }
203: }
204:
205: public void startDocument() throws SAXException {
206: super .startDocument();
207: isRootElem = true;
208: }
209:
210: public void processingInstruction(String target, String data)
211: throws SAXException {
212: if (!"xml-logicsheet".equals(target)) {
213: data = this .language.quoteString(data);
214: }
215: super .processingInstruction(target, data);
216: }
217:
218: public void startPrefixMapping(String prefix, String uri)
219: throws SAXException {
220: if (CocoonMarkupLanguage.this .getURI().equals(uri)) {
221: this .localPrefix = prefix;
222: }
223: super .startPrefixMapping(prefix, uri);
224: }
225:
226: public void startElement(String namespaceURI, String localName,
227: String qName, Attributes atts) throws SAXException {
228: if (isRootElem) {
229: if (!CocoonMarkupLanguage.this .getURI().equals(
230: namespaceURI)
231: || !CocoonMarkupLanguage.this .getRootElement()
232: .equals(localName)) {
233: throw new SAXException(
234: "This page is not valid page of this markup langugage."
235: + " Root element is: "
236: + namespaceURI
237: + ":"
238: + localName
239: + ", must be: "
240: + CocoonMarkupLanguage.this
241: .getURI()
242: + ":"
243: + CocoonMarkupLanguage.this
244: .getRootElement());
245: }
246:
247: isRootElem = false;
248: // Store path and file name
249: int pos = this .filename.lastIndexOf(File.separatorChar);
250: String name = this .filename.substring(pos + 1);
251: String path = this .filename.substring(0, pos).replace(
252: File.separatorChar, '/');
253: // update the attributes
254: AttributesImpl newAtts;
255: if (atts == null || atts.getLength() == 0) {
256: newAtts = new AttributesImpl();
257: } else {
258: newAtts = new AttributesImpl(atts);
259: }
260: newAtts.addAttribute("", "file-name", "file-name",
261: "CDATA", name);
262: newAtts.addAttribute("", "file-path", "file-path",
263: "CDATA", path);
264: newAtts.addAttribute("", "creation-date",
265: "creation-date", "CDATA", String.valueOf(System
266: .currentTimeMillis()));
267: // forward element with the modified attribute
268: super .startElement(namespaceURI, localName, qName,
269: newAtts);
270: } else {
271: super
272: .startElement(namespaceURI, localName, qName,
273: atts);
274: }
275: }
276: }
277:
278: /**
279: * This filter builds on the fly a chain of transformers. It extends the
280: * <code>AbstractMarkupLanguage.TransformerChainBuilderFilter</code> so
281: * it can add common markup language features such as:
282: * <ul>
283: * <li>Looking for <?xml-logisheet href="..."?;> PI and
284: * <xsp:xml-logisheet location="..."> elements to register
285: * user defined logicsheets;</li>
286: * <li>Adding all the dependencies related to the pages as
287: * <xsp:dependency;>...</xsp:dependency;></li>
288: * </ul>
289: *
290: * @see org.xml.sax.ContentHandler
291: */
292: public class CocoonTransformerChainBuilderFilter extends
293: TransformerChainBuilderFilter {
294:
295: protected Logger log;
296:
297: private List startPrefix;
298:
299: private Object[] rootElement;
300:
301: private StringBuffer rootChars;
302:
303: private boolean isRootElem;
304:
305: private boolean insideRootElement;
306:
307: private boolean finished;
308:
309: private String localPrefix;
310:
311: /**
312: * @param logicsheetMarkupGenerator the code generator
313: */
314: public CocoonTransformerChainBuilderFilter(
315: LogicsheetCodeGenerator logicsheetMarkupGenerator) {
316: super (logicsheetMarkupGenerator);
317: }
318:
319: /**
320: * Provide component with a logger.
321: *
322: * @param logger the logger
323: */
324: public void enableLogging(Logger logger) {
325: if (this .log == null) {
326: this .log = logger;
327: }
328: }
329:
330: public void processingInstruction(String target, String data)
331: throws SAXException {
332: // Retrieve logicsheets declared by processing-instruction
333: if ("xml-logicsheet".equals(target)) {
334: int start = data.indexOf("href");
335: if (start >= 0) {
336: // add 6, for lenght of 'href', plus '=' char, plus '"' char
337: start += 6;
338: // get the quote char. Can be " or '
339: char quote = data.charAt(start - 1);
340: int end = data.indexOf(quote, start);
341: String href = data.substring(start, end);
342:
343: try {
344: CocoonMarkupLanguage.this .addLogicsheetToList(
345: language, href);
346: } catch (ProcessingException pe) {
347: log
348: .warn(
349: "ProcessingException in SitemapMarkupLanguage",
350: pe);
351: throw new SAXException(pe);
352: } catch (IOException ioe) {
353: log
354: .warn(
355: "CocoonMarkupLanguage.processingInstruction",
356: ioe);
357: throw new SAXException(ioe);
358: }
359: }
360: // Do not forward the PI event.
361: return;
362: }
363:
364: // Call super when this is not a logicsheet related PI
365: super .processingInstruction(target, data);
366: }
367:
368: public void startDocument() throws SAXException {
369: isRootElem = true;
370: insideRootElement = false;
371: finished = false;
372: startPrefix = new ArrayList();
373: rootChars = new StringBuffer();
374: }
375:
376: public void startElement(String namespaceURI, String localName,
377: String qName, Attributes atts) throws SAXException {
378: if (finished) {
379: // Call super method
380: super
381: .startElement(namespaceURI, localName, qName,
382: atts);
383: } else {
384: // Need more work
385: if (isRootElem) {
386: localPrefix = "";
387: if (qName.indexOf(':') != -1)
388: localPrefix = qName.substring(0, qName
389: .indexOf(':'));
390:
391: isRootElem = false;
392: // Cache the root element and resend the SAX event when
393: // we've finished dealing with <xsp:logicsheet > elements
394: rootElement = new Object[4];
395: rootElement[0] = namespaceURI;
396: rootElement[1] = localName;
397: rootElement[2] = qName;
398: rootElement[3] = atts;
399: } else {
400: insideRootElement = true;
401: // Retrieve logicsheets declared by top-level elements <xsp:logicsheet ...>
402: // And do not forward the startElement event
403: if (CocoonMarkupLanguage.this .getURI().equals(
404: namespaceURI)
405: && "logicsheet".equals(localName)) {
406: String href = atts.getValue("location");
407: try {
408: CocoonMarkupLanguage.this
409: .addLogicsheetToList(language, href);
410: } catch (ProcessingException pe) {
411: log
412: .warn(
413: "CocoonMarkupLanguage.startElement",
414: pe);
415: throw new SAXException(pe);
416: } catch (IOException ioe) {
417: log
418: .warn(
419: "CocoonMarkupLanguage.startElement",
420: ioe);
421: throw new SAXException(ioe);
422: }
423: } else {
424: // This element is not a <xsp:logicsheet> element, so finish
425: // by:
426: // * setting the 'fisnished' flag to true ;
427: // * refiring all the cached events ;
428: // * firing all the necessary event dealing with file dependencies
429: finished = true;
430:
431: // Send SAX events 'startDocument'
432: super .startDocument();
433:
434: // Send all prefix namespace
435: String[] prefixArray;
436: for (int i = 0; i < startPrefix.size(); i++) {
437: prefixArray = (String[]) startPrefix.get(i);
438: super .startPrefixMapping(prefixArray[0],
439: prefixArray[1]);
440: }
441:
442: // Send cached RootElement event
443: super .startElement((String) rootElement[0],
444: (String) rootElement[1],
445: (String) rootElement[2],
446: (Attributes) rootElement[3]);
447:
448: // Send cached characters
449: char[] ch = rootChars.toString().toCharArray();
450: if (ch.length > 0) {
451: super .characters(ch, 0, ch.length);
452: }
453:
454: // Send the events dealing with dependencies.
455: // If some dependencies exist, then creates
456: // <xsp:dependency> elements
457: char[] locationChars;
458: Iterator iter = CocoonMarkupLanguage.this .dependencies
459: .iterator();
460: while (iter.hasNext()) {
461: super .startElement((String) rootElement[0],
462: "dependency", localPrefix
463: + ":dependency",
464: XMLUtils.EMPTY_ATTRIBUTES);
465: locationChars = ((String) iter.next())
466: .toCharArray();
467: super .characters(locationChars, 0,
468: locationChars.length);
469: super .endElement((String) rootElement[0],
470: "dependency", localPrefix
471: + ":dependency");
472: }
473:
474: // And finally forward current Element.
475: super .startElement(namespaceURI, localName,
476: qName, atts);
477: }
478: }
479: }
480: }
481:
482: public void endElement(String namespaceURI, String localName,
483: String qName) throws SAXException {
484: if (finished) {
485: // Forward the events
486: super .endElement(namespaceURI, localName, qName);
487: }
488: }
489:
490: public void characters(char[] ch, int start, int length)
491: throws SAXException {
492: if (finished) {
493: super .characters(ch, start, length);
494: } else if (!insideRootElement) {
495: // Caching the PCDATA for the root element
496: rootChars.append(ch, start, length);
497: }
498: }
499:
500: public void startPrefixMapping(String prefix, String uri)
501: throws SAXException {
502: if (finished) {
503: super .startPrefixMapping(prefix, uri);
504: } else {
505: String[] prefixArray = new String[2];
506: prefixArray[0] = prefix;
507: prefixArray[1] = uri;
508: startPrefix.add(prefixArray);
509: }
510: }
511: }
512: }
|