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