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