KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > outerj > daisy > books > publisher > impl > publicationprocess > NumberingTask


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

16 package org.outerj.daisy.books.publisher.impl.publicationprocess;
17
18 import org.outerj.daisy.books.publisher.impl.util.AbstractContentHandler;
19 import org.outerj.daisy.books.publisher.impl.BookInstanceLayout;
20 import org.outerj.daisy.books.store.BookInstance;
21 import org.outerj.daisy.xmlutil.XmlSerializer;
22 import org.outerj.daisy.xmlutil.LocalSAXParserFactory;
23 import org.xml.sax.ContentHandler JavaDoc;
24 import org.xml.sax.Attributes JavaDoc;
25 import org.xml.sax.SAXException JavaDoc;
26 import org.xml.sax.InputSource JavaDoc;
27 import org.apache.cocoon.xml.AttributesImpl;
28 import org.apache.cocoon.i18n.Bundle;
29
30 import javax.xml.parsers.SAXParser JavaDoc;
31 import java.util.*;
32 import java.util.regex.Pattern JavaDoc;
33 import java.util.regex.Matcher JavaDoc;
34 import java.io.InputStream JavaDoc;
35 import java.io.OutputStream JavaDoc;
36
37 public class NumberingTask implements PublicationProcessTask {
38     private final String JavaDoc input;
39     private final String JavaDoc output;
40
41     public NumberingTask(String JavaDoc input, String JavaDoc output) {
42         this.input = input;
43         this.output = output;
44     }
45
46     public void run(PublicationContext context) throws Exception JavaDoc {
47         context.getPublicationLog().info("Running numbering task.");
48         BookInstance bookInstance = context.getBookInstance();
49         String JavaDoc publicationOutputPath = BookInstanceLayout.getPublicationOutputPath(context.getPublicationOutputName());
50         String JavaDoc startXmlLocation = publicationOutputPath + input;
51         InputStream JavaDoc is = null;
52         OutputStream JavaDoc os = null;
53         try {
54             is = bookInstance.getResource(startXmlLocation);
55             os = bookInstance.getResourceOutputStream(publicationOutputPath + output);
56             SAXParser JavaDoc parser = LocalSAXParserFactory.getSAXParserFactory().newSAXParser();
57             XmlSerializer serializer = new XmlSerializer(os);
58             NumberingHandler numberingHandler = new NumberingHandler(serializer, context);
59             parser.getXMLReader().setContentHandler(numberingHandler);
60             parser.getXMLReader().parse(new InputSource JavaDoc(is));
61         } finally {
62             if (is != null)
63                 try { is.close(); } catch (Exception JavaDoc e) {}
64             if (os != null)
65                 try { os.close(); } catch (Exception JavaDoc e) {}
66         }
67     }
68
69     static class NumberingHandler extends AbstractContentHandler {
70         private final Stack headers;
71         private PublicationContext context;
72         private TypedCounter figureCounter = new TypedCounter();
73         private TypedCounter tableCounter = new TypedCounter();
74
75         private static final Pattern JavaDoc headerPattern = Pattern.compile("h([0-9]+)");
76
77         public NumberingHandler(ContentHandler consumer, PublicationContext context) {
78             super(consumer);
79             headers = new Stack();
80             this.context = context;
81         }
82
83         public void startElement(String JavaDoc namespaceURI, String JavaDoc localName, String JavaDoc qName, Attributes JavaDoc atts) throws SAXException JavaDoc {
84             Matcher JavaDoc matcher = headerPattern.matcher(localName);
85             if (namespaceURI.equals("")) {
86                 if (matcher.matches()) {
87                     int level = Integer.parseInt(matcher.group(1));
88                     int nullLevel = level - 1;
89
90                     if (level == 1) {
91                         figureCounter.reset();
92                         tableCounter.reset();
93                     }
94
95                     String JavaDoc sectionType = atts.getValue("daisySectionType");
96                     if (sectionType == null) {
97                         if (headers.isEmpty()) {
98                             sectionType = "default";
99                         } else {
100                             // inherit section type from parent
101
sectionType = ((SectionInfo)headers.peek()).sectionType;
102                         }
103                     }
104
105                     SectionInfo newSectionInfo = null;
106                     if (nullLevel == headers.size()) {
107                         int number = getStartNumber(headers.size() + 1, sectionType);
108                         newSectionInfo = new SectionInfo(number, sectionType, !shouldIncreaseNumber(sectionType));
109                     } else if (nullLevel < headers.size()) {
110                         SectionInfo prevSection = null;
111                         while (level <= headers.size())
112                             prevSection = (SectionInfo)headers.pop();
113                         if (shouldIncreaseNumber(sectionType)) {
114                             if (!prevSection.sectionType.equals(sectionType) && shouldResetNumber(sectionType)) {
115                                 newSectionInfo = new SectionInfo(1, sectionType, false);
116                             } else {
117                                 newSectionInfo = new SectionInfo(prevSection.number + 1, sectionType, false);
118                             }
119                         } else {
120                             newSectionInfo = new SectionInfo(prevSection.number, sectionType, true);
121                         }
122                     } else {
123                         // jumped over a heading level -- stop numbering sections
124
}
125                     if (newSectionInfo != null) {
126                         headers.push(newSectionInfo);
127                         if (!newSectionInfo.anonymous) {
128                             NumberingPattern numberingPattern = getSectionNumberPattern(headers.size(), sectionType);
129                             if (numberingPattern != null) {
130                                 numberingPattern.apply((SectionInfo[])headers.toArray(new SectionInfo[headers.size()]), context.getI18nBundle());
131                                 AttributesImpl newAttrs = new AttributesImpl(atts);
132                                 addNumberAttributes(newAttrs, newSectionInfo);
133                                 atts = newAttrs;
134                             }
135                         }
136                     }
137                 } else if (localName.equals("img")) {
138                     atts = processArtifact(atts, "daisy-image-type", figureCounter, "figure");
139                 } else if (localName.equals("table")) {
140                     atts = processArtifact(atts, "daisy-table-type", tableCounter, "table");
141                 }
142             }
143             super.startElement(namespaceURI, localName, qName, atts);
144         }
145
146         private void addNumberAttributes(AttributesImpl attrs, SectionInfo sectionInfo) {
147             attrs.addCDATAAttribute("daisyNumber", sectionInfo.completeFormattedNumber);
148             attrs.addCDATAAttribute("daisyPartialNumber", sectionInfo.formattedNumber);
149             attrs.addCDATAAttribute("daisyRawNumber", String.valueOf(sectionInfo.number));
150         }
151
152         private NumberingPattern getSectionNumberPattern(int level, String JavaDoc sectionType) throws SAXException JavaDoc {
153             String JavaDoc pattern = (String JavaDoc)context.getProperties().get("numbering." + sectionType + ".h" + level);
154             if (pattern == null || pattern.trim().equals(""))
155                 return null;
156             try {
157                 return parseNumberingPattern(pattern);
158             } catch (Exception JavaDoc e) {
159                 throw new SAXException JavaDoc(e);
160             }
161         }
162
163         private int getStartNumber(int level, String JavaDoc sectionType) throws SAXException JavaDoc {
164             String JavaDoc propName = "numbering." + sectionType + ".h" + level + ".start-number";
165             String JavaDoc startNumber = (String JavaDoc)context.getProperties().get(propName);
166             if (startNumber == null || startNumber.trim().equals("")) {
167                 return 1;
168             } else {
169                 try {
170                     return Integer.parseInt(startNumber);
171                 } catch (NumberFormatException JavaDoc e) {
172                     throw new SAXException JavaDoc("Start number specified for property \"" + propName + "\" is not an integer number: " + startNumber);
173                 }
174             }
175         }
176
177         private boolean shouldIncreaseNumber(String JavaDoc sectionType) {
178             String JavaDoc propName = "numbering." + sectionType + ".increase-number";
179             String JavaDoc increaseNumber = (String JavaDoc)context.getProperties().get(propName);
180             if (increaseNumber == null || increaseNumber.trim().equals(""))
181                 return true;
182             else
183                 return increaseNumber.equalsIgnoreCase("true");
184         }
185
186         private boolean shouldResetNumber(String JavaDoc sectionType) {
187             String JavaDoc propName = "numbering." + sectionType + ".reset-number";
188             String JavaDoc resetNumber = (String JavaDoc)context.getProperties().get(propName);
189             if (resetNumber == null || resetNumber.trim().equals(""))
190                 return false;
191             else
192                 return resetNumber.equalsIgnoreCase("true");
193         }
194
195         private Attributes JavaDoc processArtifact(Attributes JavaDoc attrs, String JavaDoc typeAttrName, TypedCounter counter, String JavaDoc artifactName) throws SAXException JavaDoc {
196             String JavaDoc caption = attrs.getValue("daisy-caption");
197             if (caption != null) {
198                 AttributesImpl newAttrs = new AttributesImpl(attrs);
199
200                 String JavaDoc type = attrs.getValue(typeAttrName);
201                 if (type == null || type.trim().equals("")) {
202                     type = "default";
203                     // update attributes with this type -- avoids that e.g. stylesheets also need this same logic
204
if (newAttrs.getIndex(typeAttrName) != -1)
205                         newAttrs.setValue(newAttrs.getIndex(typeAttrName), type);
206                     else
207                         newAttrs.addCDATAAttribute(typeAttrName, type);
208                 }
209
210                 int number = counter.getNextNumber(type);
211                 NumberingPattern pattern = getArtifactNumberPattern(artifactName, type);
212                 if (pattern != null) {
213                     // use a dummy section to represent the table or figure
214
SectionInfo dummySection = new SectionInfo(number, type, false);
215                     SectionInfo[] sectionInfos = new SectionInfo[headers.size() + 1];
216                     for (int i = 0; i < headers.size(); i++)
217                         sectionInfos[i] = (SectionInfo)headers.get(i);
218                     sectionInfos[sectionInfos.length - 1] = dummySection;
219                     pattern.apply(sectionInfos, context.getI18nBundle());
220                     addNumberAttributes(newAttrs, dummySection);
221                 }
222                 return newAttrs;
223             }
224             return attrs;
225         }
226
227         private NumberingPattern getArtifactNumberPattern(String JavaDoc artifactName, String JavaDoc type) throws SAXException JavaDoc {
228             String JavaDoc pattern = (String JavaDoc)context.getProperties().get(artifactName + "." + type + ".numberpattern");
229             if (pattern == null || pattern.trim().equals(""))
230                 return null;
231             try {
232                 return parseNumberingPattern(pattern);
233             } catch (Exception JavaDoc e) {
234                 throw new SAXException JavaDoc(e);
235             }
236         }
237     }
238
239     static class TypedCounter {
240         private Map counters = new HashMap();
241
242         public void reset() {
243             counters.clear();
244         }
245
246         public int getNextNumber(String JavaDoc type) {
247             Integer JavaDoc value = (Integer JavaDoc)counters.get(type);
248             value = value == null ? new Integer JavaDoc(1) : new Integer JavaDoc(value.intValue() + 1);
249             counters.put(type, value);
250             return value.intValue();
251         }
252     }
253
254     /**
255      * Parses a numbering pattern.
256      *
257      * A numbering pattern is simply a string in which certain characters have a special
258      * meaning. Characters that do not have a special meaning are output as is.
259      *
260      * A numbering pattern must contain exactly one of the following: 1, I, i, A, a.
261      * This character will be replaced by the actual section number, in the style as
262      * indicated by the character.
263      *
264      * A numbering pattern may in addition include 'hx' strings to reference the section
265      * number of the parent heading of level x.
266      *
267      * For example 'h1.h2.1' would be a nice format for a level 3 heading.
268      *
269      * Additionally, numbering patterns can contain i18n keys to be looked up
270      * in a resource bundle. The syntax for this is to put the i18n key between
271      * dollar signs, i.e. $chapter$.
272      */

273     static NumberingPattern parseNumberingPattern(String JavaDoc pattern) throws Exception JavaDoc {
274         SectionNumber sectionNumber = null;
275         List parts = new ArrayList();
276
277         for (int i = 0; i < pattern.length(); i++) {
278             char c = pattern.charAt(i);
279             switch (c) {
280                 case '1':
281                 case 'I':
282                 case 'i':
283                 case 'A':
284                 case 'a':
285                     if (sectionNumber != null) {
286                         throw new Exception JavaDoc("Numbering pattern contains double section number reference: \"" + pattern + "\".");
287                     }
288                     sectionNumber = new SectionNumber(c);
289                     parts.add(sectionNumber);
290                     break;
291                 case 'h':
292                     i = i + 1;
293                     if (i >= pattern.length() || !( (pattern.charAt(i) >= '0' && pattern.charAt(i) <= '9') || pattern.charAt(i) == 'r')) {
294                         throw new Exception JavaDoc("Error in numbering pattern: character h should be followed by an integer number or 'r': \"" + pattern + "\".");
295                     }
296                     int level;
297                     if (pattern.charAt(i) == 'r')
298                         level = -1;
299                     else
300                         level = pattern.charAt(i) - '0';
301                     ParentSectionNumber parentSectionNumber = new ParentSectionNumber(level);
302                     parts.add(parentSectionNumber);
303                     break;
304                 case '$':
305                     // i18n key - search for next $
306
int endPos = pattern.indexOf('$', i + 1);
307                     if (endPos == -1)
308                         throw new Exception JavaDoc("Error in numbering pattern: unclosed i18n key reference: \"" + pattern + "\".");
309                     String JavaDoc i18nKey = pattern.substring(i + 1, endPos);
310                     parts.add(new I18nPart(i18nKey));
311                     i = endPos;
312                     break;
313                 default:
314                     FreeChar freeChar = new FreeChar(c);
315                     parts.add(freeChar);
316                     break;
317             }
318         }
319
320         if (sectionNumber == null) {
321             throw new Exception JavaDoc("Numbering pattern is missing section number reference.");
322         }
323
324         return new NumberingPattern((NumberPatternPart[])parts.toArray(new NumberPatternPart[parts.size()]));
325     }
326
327
328     static class NumberingPattern {
329         private final NumberPatternPart[] parts;
330
331         public NumberingPattern(NumberPatternPart[] parts) {
332             this.parts = parts;
333         }
334
335         /**
336          * Applies the numbering pattern to the last SectionInfo in the supplied array,
337          * assuming that a NumberingPattern has already been applied to the earlier sections
338          * in the array.
339          */

340         public void apply(SectionInfo[] sectionInfos, Bundle bundle) {
341             StringBuffer JavaDoc result = new StringBuffer JavaDoc();
342             for (int i = 0; i < parts.length; i++) {
343                 parts[i].output(result, sectionInfos, bundle);
344             }
345             sectionInfos[sectionInfos.length - 1].completeFormattedNumber = result.toString();
346         }
347     }
348
349     static interface NumberPatternPart {
350         void output(StringBuffer JavaDoc result, SectionInfo[] sectionInfos, Bundle bundle);
351     }
352
353     static class FreeChar implements NumberPatternPart {
354         private final char c;
355
356         public FreeChar(char c) {
357             this.c = c;
358         }
359
360         public void output(StringBuffer JavaDoc result, SectionInfo[] sectionInfos, Bundle bundle) {
361             result.append(c);
362         }
363     }
364
365     static class I18nPart implements NumberPatternPart {
366         private final String JavaDoc i18nKey;
367
368         public I18nPart(String JavaDoc i18nKey) {
369             this.i18nKey = i18nKey;
370         }
371
372         public void output(StringBuffer JavaDoc result, SectionInfo[] sectionInfos, Bundle bundle) {
373             result.append(bundle.getString(i18nKey));
374         }
375     }
376
377     static class ParentSectionNumber implements NumberPatternPart {
378         private final int level;
379
380         public ParentSectionNumber(int level) {
381             this.level = level;
382         }
383
384         public void output(StringBuffer JavaDoc result, SectionInfo[] sectionInfos, Bundle bundle) {
385             if (level != -1) {
386                 int index = level - 1;
387                 if (index >= 0 && index < sectionInfos.length) {
388                     result.append(sectionInfos[index].formattedNumber);
389                 } else {
390                     result.append('h').append(level);
391                 }
392             } else {
393                 String JavaDoc number = null;
394                 for (int i = 0; i < sectionInfos.length; i++) {
395                     if (!sectionInfos[i].anonymous) {
396                         number = sectionInfos[i].formattedNumber;
397                         break;
398                     }
399                 }
400                 if (number != null)
401                     result.append(number);
402                 else
403                     result.append("hr");
404             }
405         }
406     }
407
408     static class SectionNumber implements NumberPatternPart {
409         private final char type;
410
411         public SectionNumber(char type) {
412             this.type = type;
413         }
414
415         public void output(StringBuffer JavaDoc result, SectionInfo[] sectionInfos, Bundle bundle) {
416             int number = sectionInfos[sectionInfos.length - 1].number;
417             String JavaDoc value;
418             switch (type) {
419                 case '1':
420                     value = String.valueOf(number);
421                     break;
422                 case 'I':
423                     value = NumeratorFormatter.long2roman(number, true);
424                     break;
425                 case 'i':
426                     value = NumeratorFormatter.long2roman(number, true).toLowerCase();
427                     break;
428                 case 'A':
429                     value = NumeratorFormatter.int2alphaCount(number);
430                     break;
431                 case 'a':
432                     value = NumeratorFormatter.int2alphaCount(number).toLowerCase();
433                     break;
434                 default:
435                     throw new RuntimeException JavaDoc("Unsupported numbering type: " + type);
436             }
437             sectionInfos[sectionInfos.length - 1].formattedNumber = value;
438             result.append(value);
439         }
440     }
441
442     static class SectionInfo {
443         private final int number;
444         private final String JavaDoc sectionType;
445         private String JavaDoc formattedNumber = "0";
446         private String JavaDoc completeFormattedNumber;
447         private boolean anonymous;
448
449         public SectionInfo(int number, String JavaDoc sectionType, boolean anonymous) {
450             this.number = number;
451             this.sectionType = sectionType;
452             this.anonymous = anonymous;
453         }
454     }
455 }
456
Popular Tags