1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.doxia.module.xdoc;
20
21 import javax.inject.Named;
22 import javax.inject.Singleton;
23 import javax.swing.text.html.HTML.Attribute;
24
25 import java.io.IOException;
26 import java.io.Reader;
27 import java.io.StringReader;
28 import java.io.StringWriter;
29 import java.util.HashMap;
30 import java.util.Map;
31
32 import org.apache.commons.io.IOUtils;
33 import org.apache.maven.doxia.macro.MacroExecutionException;
34 import org.apache.maven.doxia.macro.MacroRequest;
35 import org.apache.maven.doxia.macro.manager.MacroNotFoundException;
36 import org.apache.maven.doxia.parser.ParseException;
37 import org.apache.maven.doxia.parser.Xhtml5BaseParser;
38 import org.apache.maven.doxia.sink.Sink;
39 import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
40 import org.apache.maven.doxia.util.HtmlTools;
41 import org.codehaus.plexus.util.xml.pull.XmlPullParser;
42 import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46
47
48
49
50
51
52 @Singleton
53 @Named("xdoc")
54 public class XdocParser extends Xhtml5BaseParser implements XdocMarkup {
55 private static final Logger LOGGER = LoggerFactory.getLogger(XdocParser.class);
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) throws ParseException {
89 this.sourceContent = null;
90
91 try (Reader reader = source) {
92 StringWriter contentWriter = new StringWriter();
93 IOUtils.copy(reader, contentWriter);
94 sourceContent = contentWriter.toString();
95 } catch (IOException ex) {
96 throw new ParseException("Error reading the input source", ex);
97 }
98
99
100
101
102 try {
103 super.parse(new StringReader(sourceContent), sink, reference);
104 } finally {
105 this.sourceContent = null;
106 }
107 }
108
109
110 protected void handleStartTag(XmlPullParser parser, Sink sink)
111 throws XmlPullParserException, MacroExecutionException {
112 isEmptyElement = parser.isEmptyElementTag();
113
114 SinkEventAttributeSet attribs = getAttributesFromParser(parser);
115
116 if (parser.getName().equals(DOCUMENT_TAG.toString())) {
117
118 return;
119 } else if (parser.getName().equals(HEAD.toString())) {
120 if (!inHead)
121 {
122 this.inHead = true;
123
124 sink.head(attribs);
125 }
126 } else if (parser.getName().equals(TITLE.toString())) {
127 if (hasTitle) {
128 LOGGER.warn("<title> was already defined in <properties>, ignored <title> in <head>.");
129
130 try {
131 parser.nextText();
132 } catch (IOException ex) {
133 throw new XmlPullParserException("Failed to parse text", parser, ex);
134 }
135 } else {
136 sink.title(attribs);
137 }
138 } else if (parser.getName().equals(AUTHOR_TAG.toString())) {
139 sink.author(attribs);
140 } else if (parser.getName().equals(DATE_TAG.toString())) {
141 sink.date(attribs);
142 } else if (parser.getName().equals(META.toString())) {
143 handleMetaStart(parser, sink, attribs);
144 } else if (parser.getName().equals(BODY.toString())) {
145 if (inHead) {
146 sink.head_();
147 this.inHead = false;
148 }
149
150 sink.body(attribs);
151 } else if (parser.getName().equals(SECTION_TAG.toString())) {
152 handleSectionStart(Sink.SECTION_LEVEL_1, sink, attribs, parser);
153 } else if (parser.getName().equals(SUBSECTION_TAG.toString())) {
154 handleSectionStart(Sink.SECTION_LEVEL_2, sink, attribs, parser);
155 } else if (parser.getName().equals(SOURCE_TAG.toString())) {
156 verbatim();
157
158 attribs.addAttributes(SinkEventAttributeSet.SOURCE);
159
160 sink.verbatim(attribs);
161 } else if (parser.getName().equals(PROPERTIES_TAG.toString())) {
162 if (!inHead)
163 {
164 this.inHead = true;
165
166 sink.head(attribs);
167 }
168 }
169
170
171
172
173
174 else if (parser.getName().equals(MACRO_TAG.toString())) {
175 handleMacroStart(parser);
176 } else if (parser.getName().equals(PARAM.toString())) {
177 handleParamStart(parser, sink);
178 } else if (!baseStartTag(parser, sink)) {
179 if (isEmptyElement) {
180 handleUnknown(parser, sink, TAG_TYPE_SIMPLE);
181 } else {
182 handleUnknown(parser, sink, TAG_TYPE_START);
183 }
184
185 LOGGER.warn(
186 "Unrecognized xdoc tag <{}> at [{}:{}]",
187 parser.getName(),
188 parser.getLineNumber(),
189 parser.getColumnNumber());
190 }
191 }
192
193
194 protected void handleEndTag(XmlPullParser parser, Sink sink)
195 throws XmlPullParserException, MacroExecutionException {
196 if (parser.getName().equals(DOCUMENT_TAG.toString())) {
197
198 return;
199 } else if (parser.getName().equals(HEAD.toString())) {
200
201 } else if (parser.getName().equals(BODY.toString())) {
202 consecutiveSections(0, sink);
203
204 sink.body_();
205 } else if (parser.getName().equals(TITLE.toString())) {
206 if (!hasTitle) {
207 sink.title_();
208 this.hasTitle = true;
209 }
210 } else if (parser.getName().equals(AUTHOR_TAG.toString())) {
211 sink.author_();
212 } else if (parser.getName().equals(DATE_TAG.toString())) {
213 sink.date_();
214 } else if (parser.getName().equals(SOURCE_TAG.toString())) {
215 verbatim_();
216
217 sink.verbatim_();
218 } else if (parser.getName().equals(PROPERTIES_TAG.toString())) {
219
220 } else if (parser.getName().equals(MACRO_TAG.toString())) {
221 handleMacroEnd(sink);
222 } else if (parser.getName().equals(PARAM.toString())) {
223 if (!(macroName != null && !macroName.isEmpty())) {
224 handleUnknown(parser, sink, TAG_TYPE_END);
225 }
226 } else if (parser.getName().equals(SECTION_TAG.toString())) {
227 consecutiveSections(0, sink);
228
229 sink.section1_();
230 } else if (parser.getName().equals(SUBSECTION_TAG.toString())) {
231 consecutiveSections(Sink.SECTION_LEVEL_1, sink);
232
233
234 } else if (!baseEndTag(parser, sink)) {
235 if (!isEmptyElement) {
236 handleUnknown(parser, sink, TAG_TYPE_END);
237 }
238 }
239
240 isEmptyElement = false;
241 }
242
243
244 protected void consecutiveSections(int newLevel, Sink sink) {
245 closeOpenSections(newLevel, sink);
246 openMissingSections(newLevel, sink);
247
248 setSectionLevel(newLevel);
249 }
250
251
252
253
254 protected void init() {
255 super.init();
256
257 this.isEmptyElement = false;
258 this.macroName = null;
259 this.macroParameters = null;
260 this.inHead = false;
261 this.hasTitle = false;
262 }
263
264
265
266
267 private void closeOpenSections(int newLevel, Sink sink) {
268 while (getSectionLevel() >= newLevel) {
269 if (getSectionLevel() == Sink.SECTION_LEVEL_5) {
270 sink.section5_();
271 } else if (getSectionLevel() == Sink.SECTION_LEVEL_4) {
272 sink.section4_();
273 } else if (getSectionLevel() == Sink.SECTION_LEVEL_3) {
274 sink.section3_();
275 } else if (getSectionLevel() == Sink.SECTION_LEVEL_2) {
276 sink.section2_();
277 }
278
279 setSectionLevel(getSectionLevel() - 1);
280 }
281 }
282
283 private void handleMacroEnd(Sink sink) throws MacroExecutionException {
284 if (!isSecondParsing() && (macroName != null && !macroName.isEmpty())) {
285 MacroRequest request = new MacroRequest(sourceContent, new XdocParser(), macroParameters, getBasedir());
286
287 try {
288 executeMacro(macroName, request, sink);
289 } catch (MacroNotFoundException me) {
290 throw new MacroExecutionException("Macro not found: " + macroName, me);
291 }
292 }
293
294
295 macroName = null;
296 macroParameters = null;
297 }
298
299 private void handleMacroStart(XmlPullParser parser) throws MacroExecutionException {
300 if (!isSecondParsing()) {
301 macroName = parser.getAttributeValue(null, Attribute.NAME.toString());
302
303 if (macroParameters == null) {
304 macroParameters = new HashMap<>();
305 }
306
307 if (macroName == null || macroName.isEmpty()) {
308 throw new MacroExecutionException("The '" + Attribute.NAME.toString() + "' attribute for the '"
309 + MACRO_TAG.toString() + "' tag is required.");
310 }
311 }
312 }
313
314 private void handleMetaStart(XmlPullParser parser, Sink sink, SinkEventAttributeSet attribs) {
315 String name = parser.getAttributeValue(null, Attribute.NAME.toString());
316 String content = parser.getAttributeValue(null, Attribute.CONTENT.toString());
317
318 if ("author".equals(name)) {
319 sink.author(null);
320 sink.text(content);
321 sink.author_();
322 } else if ("date".equals(name)) {
323 sink.date(null);
324 sink.text(content);
325 sink.date_();
326 } else {
327 sink.unknown("meta", new Object[] {TAG_TYPE_SIMPLE}, attribs);
328 }
329 }
330
331 private void handleParamStart(XmlPullParser parser, Sink sink) throws MacroExecutionException {
332 if (!isSecondParsing()) {
333 if (macroName != null && !macroName.isEmpty()) {
334 String paramName = parser.getAttributeValue(null, Attribute.NAME.toString());
335 String paramValue = parser.getAttributeValue(null, Attribute.VALUE.toString());
336
337 if ((paramName == null || paramName.isEmpty()) || (paramValue == null || paramValue.isEmpty())) {
338 throw new MacroExecutionException(
339 "'" + Attribute.NAME.toString() + "' and '" + Attribute.VALUE.toString()
340 + "' attributes for the '" + PARAM.toString() + "' tag are required inside the '"
341 + MACRO_TAG.toString() + "' tag.");
342 }
343
344 macroParameters.put(paramName, paramValue);
345 } else {
346
347 handleUnknown(parser, sink, TAG_TYPE_START);
348 }
349 }
350 }
351
352 private void handleSectionStart(int level, Sink sink, SinkEventAttributeSet attribs, XmlPullParser parser) {
353 consecutiveSections(level, sink);
354
355 Object id = attribs.getAttribute(Attribute.ID.toString());
356
357 if (id != null) {
358 sink.anchor(id.toString());
359 sink.anchor_();
360 }
361
362 sink.section(level, attribs);
363 sink.sectionTitle(level, null);
364 sink.text(HtmlTools.unescapeHTML(parser.getAttributeValue(null, Attribute.NAME.toString())));
365 sink.sectionTitle_(level);
366 }
367
368
369
370
371 private void openMissingSections(int newLevel, Sink sink) {
372 while (getSectionLevel() < newLevel - 1) {
373 setSectionLevel(getSectionLevel() + 1);
374
375 if (getSectionLevel() == Sink.SECTION_LEVEL_5) {
376 sink.section5();
377 } else if (getSectionLevel() == Sink.SECTION_LEVEL_4) {
378 sink.section4();
379 } else if (getSectionLevel() == Sink.SECTION_LEVEL_3) {
380 sink.section3();
381 } else if (getSectionLevel() == Sink.SECTION_LEVEL_2) {
382 sink.section2();
383 }
384 }
385 }
386 }