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