1 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 ; 24 import org.xml.sax.Attributes ; 25 import org.xml.sax.SAXException ; 26 import org.xml.sax.InputSource ; 27 import org.apache.cocoon.xml.AttributesImpl; 28 import org.apache.cocoon.i18n.Bundle; 29 30 import javax.xml.parsers.SAXParser ; 31 import java.util.*; 32 import java.util.regex.Pattern ; 33 import java.util.regex.Matcher ; 34 import java.io.InputStream ; 35 import java.io.OutputStream ; 36 37 public class NumberingTask implements PublicationProcessTask { 38 private final String input; 39 private final String output; 40 41 public NumberingTask(String input, String output) { 42 this.input = input; 43 this.output = output; 44 } 45 46 public void run(PublicationContext context) throws Exception { 47 context.getPublicationLog().info("Running numbering task."); 48 BookInstance bookInstance = context.getBookInstance(); 49 String publicationOutputPath = BookInstanceLayout.getPublicationOutputPath(context.getPublicationOutputName()); 50 String startXmlLocation = publicationOutputPath + input; 51 InputStream is = null; 52 OutputStream os = null; 53 try { 54 is = bookInstance.getResource(startXmlLocation); 55 os = bookInstance.getResourceOutputStream(publicationOutputPath + output); 56 SAXParser 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 (is)); 61 } finally { 62 if (is != null) 63 try { is.close(); } catch (Exception e) {} 64 if (os != null) 65 try { os.close(); } catch (Exception 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 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 namespaceURI, String localName, String qName, Attributes atts) throws SAXException { 84 Matcher 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 sectionType = atts.getValue("daisySectionType"); 96 if (sectionType == null) { 97 if (headers.isEmpty()) { 98 sectionType = "default"; 99 } else { 100 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 } 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 sectionType) throws SAXException { 153 String pattern = (String )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 e) { 159 throw new SAXException (e); 160 } 161 } 162 163 private int getStartNumber(int level, String sectionType) throws SAXException { 164 String propName = "numbering." + sectionType + ".h" + level + ".start-number"; 165 String startNumber = (String )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 e) { 172 throw new SAXException ("Start number specified for property \"" + propName + "\" is not an integer number: " + startNumber); 173 } 174 } 175 } 176 177 private boolean shouldIncreaseNumber(String sectionType) { 178 String propName = "numbering." + sectionType + ".increase-number"; 179 String increaseNumber = (String )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 sectionType) { 187 String propName = "numbering." + sectionType + ".reset-number"; 188 String resetNumber = (String )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 processArtifact(Attributes attrs, String typeAttrName, TypedCounter counter, String artifactName) throws SAXException { 196 String caption = attrs.getValue("daisy-caption"); 197 if (caption != null) { 198 AttributesImpl newAttrs = new AttributesImpl(attrs); 199 200 String type = attrs.getValue(typeAttrName); 201 if (type == null || type.trim().equals("")) { 202 type = "default"; 203 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 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 artifactName, String type) throws SAXException { 228 String pattern = (String )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 e) { 234 throw new SAXException (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 type) { 247 Integer value = (Integer )counters.get(type); 248 value = value == null ? new Integer (1) : new Integer (value.intValue() + 1); 249 counters.put(type, value); 250 return value.intValue(); 251 } 252 } 253 254 273 static NumberingPattern parseNumberingPattern(String pattern) throws Exception { 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 ("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 ("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 int endPos = pattern.indexOf('$', i + 1); 307 if (endPos == -1) 308 throw new Exception ("Error in numbering pattern: unclosed i18n key reference: \"" + pattern + "\"."); 309 String 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 ("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 340 public void apply(SectionInfo[] sectionInfos, Bundle bundle) { 341 StringBuffer result = new StringBuffer (); 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 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 result, SectionInfo[] sectionInfos, Bundle bundle) { 361 result.append(c); 362 } 363 } 364 365 static class I18nPart implements NumberPatternPart { 366 private final String i18nKey; 367 368 public I18nPart(String i18nKey) { 369 this.i18nKey = i18nKey; 370 } 371 372 public void output(StringBuffer 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 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 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 result, SectionInfo[] sectionInfos, Bundle bundle) { 416 int number = sectionInfos[sectionInfos.length - 1].number; 417 String 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 ("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 sectionType; 445 private String formattedNumber = "0"; 446 private String completeFormattedNumber; 447 private boolean anonymous; 448 449 public SectionInfo(int number, String sectionType, boolean anonymous) { 450 this.number = number; 451 this.sectionType = sectionType; 452 this.anonymous = anonymous; 453 } 454 } 455 } 456 | Popular Tags |