1 package org.apache.maven.doxia.module.xdoc;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 import java.io.IOException;
23 import java.io.Reader;
24 import java.io.StringReader;
25 import java.io.StringWriter;
26 import java.util.HashMap;
27 import java.util.Map;
28
29 import javax.swing.text.html.HTML.Attribute;
30
31 import org.apache.maven.doxia.macro.MacroExecutionException;
32 import org.apache.maven.doxia.macro.manager.MacroNotFoundException;
33 import org.apache.maven.doxia.macro.MacroRequest;
34 import org.apache.maven.doxia.parser.ParseException;
35 import org.apache.maven.doxia.parser.Parser;
36 import org.apache.maven.doxia.parser.XhtmlBaseParser;
37 import org.apache.maven.doxia.sink.Sink;
38 import org.apache.maven.doxia.sink.SinkEventAttributeSet;
39 import org.apache.maven.doxia.util.HtmlTools;
40
41 import org.codehaus.plexus.component.annotations.Component;
42 import org.codehaus.plexus.util.IOUtil;
43 import org.codehaus.plexus.util.StringUtils;
44 import org.codehaus.plexus.util.xml.pull.XmlPullParser;
45 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
46
47
48
49
50
51
52
53
54 @Component( role = Parser.class, hint = "xdoc" )
55 public class XdocParser
56 extends XhtmlBaseParser
57 implements XdocMarkup
58 {
59
60
61
62 private String sourceContent;
63
64
65
66
67 private boolean isEmptyElement;
68
69
70
71
72 private String macroName;
73
74
75
76
77 private Map<String, Object> macroParameters = new HashMap<String, Object>();
78
79
80
81
82 private boolean inHead;
83
84
85
86
87 private boolean hasTitle;
88
89
90
91
92 public void parse( Reader source, Sink sink )
93 throws ParseException
94 {
95 this.sourceContent = null;
96 init();
97
98 try
99 {
100 StringWriter contentWriter = new StringWriter();
101 IOUtil.copy( source, contentWriter );
102 sourceContent = contentWriter.toString();
103 }
104 catch ( IOException ex )
105 {
106 throw new ParseException( "Error reading the input source: " + ex.getMessage(), ex );
107 }
108 finally
109 {
110 IOUtil.close( source );
111 }
112
113 Reader tmp = new StringReader( sourceContent );
114
115
116
117
118 try
119 {
120 super.parse( tmp, sink );
121 }
122 finally
123 {
124 this.sourceContent = null;
125
126 setSecondParsing( false );
127 init();
128 }
129 }
130
131
132
133
134 protected void handleStartTag( XmlPullParser parser, Sink sink )
135 throws XmlPullParserException, MacroExecutionException
136 {
137 isEmptyElement = parser.isEmptyElementTag();
138
139 SinkEventAttributeSet attribs = getAttributesFromParser( parser );
140
141 if ( parser.getName().equals( DOCUMENT_TAG.toString() ) )
142 {
143
144 return;
145 }
146 else if ( parser.getName().equals( HEAD.toString() ) )
147 {
148 if ( !inHead )
149 {
150 this.inHead = true;
151
152 sink.head( attribs );
153 }
154 }
155 else if ( parser.getName().equals( TITLE.toString() ) )
156 {
157 if ( hasTitle )
158 {
159 getLog().warn( "<title> was already defined in <properties>, ignored <title> in <head>." );
160
161 try
162 {
163 parser.nextText();
164 }
165 catch ( IOException ex )
166 {
167 throw new XmlPullParserException( "Failed to parse text", parser, ex );
168 }
169 }
170 else
171 {
172 sink.title( attribs );
173 }
174 }
175 else if ( parser.getName().equals( AUTHOR_TAG.toString() ) )
176 {
177 sink.author( attribs );
178 }
179 else if ( parser.getName().equals( DATE_TAG.toString() ) )
180 {
181 sink.date( attribs );
182 }
183 else if ( parser.getName().equals( META.toString() ) )
184 {
185 handleMetaStart( parser, sink, attribs );
186 }
187 else if ( parser.getName().equals( BODY.toString() ) )
188 {
189 if ( inHead )
190 {
191 sink.head_();
192 this.inHead = false;
193 }
194
195 sink.body( attribs );
196 }
197 else if ( parser.getName().equals( SECTION_TAG.toString() ) )
198 {
199 handleSectionStart( Sink.SECTION_LEVEL_1, sink, attribs, parser );
200 }
201 else if ( parser.getName().equals( SUBSECTION_TAG.toString() ) )
202 {
203 handleSectionStart( Sink.SECTION_LEVEL_2, sink, attribs, parser );
204 }
205 else if ( parser.getName().equals( SOURCE_TAG.toString() ) )
206 {
207 verbatim();
208
209 attribs.addAttributes( SinkEventAttributeSet.BOXED );
210
211 sink.verbatim( attribs );
212 }
213 else if ( parser.getName().equals( PROPERTIES_TAG.toString() ) )
214 {
215 if ( !inHead )
216 {
217 this.inHead = true;
218
219 sink.head( attribs );
220 }
221 }
222
223
224
225
226
227 else if ( parser.getName().equals( MACRO_TAG.toString() ) )
228 {
229 handleMacroStart( parser );
230 }
231 else if ( parser.getName().equals( PARAM.toString() ) )
232 {
233 handleParamStart( parser, sink );
234 }
235 else if ( !baseStartTag( parser, sink ) )
236 {
237 if ( isEmptyElement )
238 {
239 handleUnknown( parser, sink, TAG_TYPE_SIMPLE );
240 }
241 else
242 {
243 handleUnknown( parser, sink, TAG_TYPE_START );
244 }
245
246 if ( getLog().isDebugEnabled() )
247 {
248 String position = "[" + parser.getLineNumber() + ":" + parser.getColumnNumber() + "]";
249 String tag = "<" + parser.getName() + ">";
250
251 getLog().debug( "Unrecognized xdoc tag: " + tag + " at " + position );
252 }
253 }
254 }
255
256
257
258
259 protected void handleEndTag( XmlPullParser parser, Sink sink )
260 throws XmlPullParserException, MacroExecutionException
261 {
262 if ( parser.getName().equals( DOCUMENT_TAG.toString() ) )
263 {
264
265 return;
266 }
267 else if ( parser.getName().equals( HEAD.toString() ) )
268 {
269
270 }
271 else if ( parser.getName().equals( BODY.toString() ) )
272 {
273 consecutiveSections( 0, sink );
274
275 sink.body_();
276 }
277 else if ( parser.getName().equals( TITLE.toString() ) )
278 {
279 if ( !hasTitle )
280 {
281 sink.title_();
282 this.hasTitle = true;
283 }
284 }
285 else if ( parser.getName().equals( AUTHOR_TAG.toString() ) )
286 {
287 sink.author_();
288 }
289 else if ( parser.getName().equals( DATE_TAG.toString() ) )
290 {
291 sink.date_();
292 }
293 else if ( parser.getName().equals( SOURCE_TAG.toString() ) )
294 {
295 verbatim_();
296
297 sink.verbatim_();
298 }
299 else if ( parser.getName().equals( PROPERTIES_TAG.toString() ) )
300 {
301
302 }
303 else if ( parser.getName().equals( MACRO_TAG.toString() ) )
304 {
305 handleMacroEnd( sink );
306 }
307 else if ( parser.getName().equals( PARAM.toString() ) )
308 {
309 if ( !StringUtils.isNotEmpty( macroName ) )
310 {
311 handleUnknown( parser, sink, TAG_TYPE_END );
312 }
313 }
314 else if ( parser.getName().equals( SECTION_TAG.toString() ) )
315 {
316 consecutiveSections( 0, sink );
317
318 sink.section1_();
319 }
320 else if ( parser.getName().equals( SUBSECTION_TAG.toString() ) )
321 {
322 consecutiveSections( Sink.SECTION_LEVEL_1, sink );
323 }
324 else if ( !baseEndTag( parser, sink ) )
325 {
326 if ( !isEmptyElement )
327 {
328 handleUnknown( parser, sink, TAG_TYPE_END );
329 }
330 }
331
332 isEmptyElement = false;
333 }
334
335
336
337
338 protected void consecutiveSections( int newLevel, Sink sink )
339 {
340 closeOpenSections( newLevel, sink );
341 openMissingSections( newLevel, sink );
342
343 setSectionLevel( newLevel );
344 }
345
346
347
348
349 protected void init()
350 {
351 super.init();
352
353 this.isEmptyElement = false;
354 this.macroName = null;
355 this.macroParameters = null;
356 this.inHead = false;
357 this.hasTitle = false;
358 }
359
360
361
362
363 private void closeOpenSections( int newLevel, Sink sink )
364 {
365 while ( getSectionLevel() >= newLevel )
366 {
367 if ( getSectionLevel() == Sink.SECTION_LEVEL_5 )
368 {
369 sink.section5_();
370 }
371 else if ( getSectionLevel() == Sink.SECTION_LEVEL_4 )
372 {
373 sink.section4_();
374 }
375 else if ( getSectionLevel() == Sink.SECTION_LEVEL_3 )
376 {
377 sink.section3_();
378 }
379 else if ( getSectionLevel() == Sink.SECTION_LEVEL_2 )
380 {
381 sink.section2_();
382 }
383
384 setSectionLevel( getSectionLevel() - 1 );
385 }
386 }
387
388 private void handleMacroEnd( Sink sink )
389 throws MacroExecutionException
390 {
391 if ( !isSecondParsing() )
392 {
393 if ( StringUtils.isNotEmpty( macroName ) )
394 {
395
396 macroParameters.put( "sourceContent", sourceContent );
397 XdocParser xdocParser = new XdocParser();
398 xdocParser.setSecondParsing( true );
399 macroParameters.put( "parser", xdocParser );
400
401 MacroRequest request = new MacroRequest( macroParameters, getBasedir() );
402
403 try
404 {
405 executeMacro( macroName, request, sink );
406 }
407 catch ( MacroNotFoundException me )
408 {
409 throw new MacroExecutionException( "Macro not found: " + macroName, me );
410 }
411 }
412 }
413
414
415 macroName = null;
416 macroParameters = null;
417 }
418
419 private void handleMacroStart( XmlPullParser parser )
420 throws MacroExecutionException
421 {
422 if ( !isSecondParsing() )
423 {
424 macroName = parser.getAttributeValue( null, Attribute.NAME.toString() );
425
426 if ( macroParameters == null )
427 {
428 macroParameters = new HashMap<String, Object>();
429 }
430
431 if ( StringUtils.isEmpty( macroName ) )
432 {
433 throw new MacroExecutionException(
434 "The '" + Attribute.NAME.toString() + "' attribute for the '" + MACRO_TAG.toString()
435 + "' tag is required." );
436 }
437 }
438 }
439
440 private void handleMetaStart( XmlPullParser parser, Sink sink, SinkEventAttributeSet attribs )
441 {
442 String name = parser.getAttributeValue( null, Attribute.NAME.toString() );
443 String content = parser.getAttributeValue( null, Attribute.CONTENT.toString() );
444
445 if ( "author".equals( name ) )
446 {
447 sink.author( null );
448 sink.text( content );
449 sink.author_();
450 }
451 else if ( "date".equals( name ) )
452 {
453 sink.date( null );
454 sink.text( content );
455 sink.date_();
456 }
457 else
458 {
459 sink.unknown( "meta", new Object[]{ Integer.valueOf( TAG_TYPE_SIMPLE ) }, attribs );
460 }
461 }
462
463 private void handleParamStart( XmlPullParser parser, Sink sink )
464 throws MacroExecutionException
465 {
466 if ( !isSecondParsing() )
467 {
468 if ( StringUtils.isNotEmpty( macroName ) )
469 {
470 String paramName = parser.getAttributeValue( null, Attribute.NAME.toString() );
471 String paramValue = parser.getAttributeValue( null, Attribute.VALUE.toString() );
472
473 if ( StringUtils.isEmpty( paramName ) || StringUtils.isEmpty( paramValue ) )
474 {
475 throw new MacroExecutionException(
476 "'" + Attribute.NAME.toString() + "' and '" + Attribute.VALUE.toString()
477 + "' attributes for the '" + PARAM.toString() + "' tag are required inside the '"
478 + MACRO_TAG.toString() + "' tag." );
479 }
480
481 macroParameters.put( paramName, paramValue );
482 }
483 else
484 {
485
486 handleUnknown( parser, sink, TAG_TYPE_START );
487 }
488 }
489 }
490
491 private void handleSectionStart( int level, Sink sink, SinkEventAttributeSet attribs, XmlPullParser parser )
492 {
493 consecutiveSections( level, sink );
494
495 Object id = attribs.getAttribute( Attribute.ID.toString() );
496
497 if ( id != null )
498 {
499 sink.anchor( id.toString() );
500 sink.anchor_();
501 }
502
503 sink.section( level, attribs );
504 sink.sectionTitle( level, null );
505 sink.text( HtmlTools.unescapeHTML( parser.getAttributeValue( null, Attribute.NAME.toString() ) ) );
506 sink.sectionTitle_( level );
507 }
508
509
510
511
512 private void openMissingSections( int newLevel, Sink sink )
513 {
514 while ( getSectionLevel() < newLevel - 1 )
515 {
516 setSectionLevel( getSectionLevel() + 1 );
517
518 if ( getSectionLevel() == Sink.SECTION_LEVEL_5 )
519 {
520 sink.section5();
521 }
522 else if ( getSectionLevel() == Sink.SECTION_LEVEL_4 )
523 {
524 sink.section4();
525 }
526 else if ( getSectionLevel() == Sink.SECTION_LEVEL_3 )
527 {
528 sink.section3();
529 }
530 else if ( getSectionLevel() == Sink.SECTION_LEVEL_2 )
531 {
532 sink.section2();
533 }
534 }
535 }
536 }