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