View Javadoc

1   /*
2    * Copyright (c) 2010 Kathryn Huxtable.
3    *
4    * This file is part of the Image Generator Maven plugin.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   *
18   * $Id$
19   */
20  package org.kathrynhuxtable.maven.plugins.htmlfiltersite;
21  
22  import java.io.File;
23  import java.io.FileInputStream;
24  import java.io.FileWriter;
25  import java.io.IOException;
26  import java.io.InputStreamReader;
27  import java.io.Reader;
28  import java.io.StringReader;
29  import java.io.StringWriter;
30  import java.io.UnsupportedEncodingException;
31  import java.text.DateFormat;
32  import java.text.SimpleDateFormat;
33  import java.util.ArrayList;
34  import java.util.Date;
35  import java.util.HashMap;
36  import java.util.List;
37  import java.util.Locale;
38  
39  import org.apache.maven.artifact.repository.ArtifactRepository;
40  import org.apache.maven.doxia.site.decoration.DecorationModel;
41  import org.apache.maven.doxia.tools.SiteTool;
42  import org.apache.maven.doxia.tools.SiteToolException;
43  import org.apache.maven.plugin.AbstractMojo;
44  import org.apache.maven.plugin.MojoExecutionException;
45  import org.apache.maven.project.MavenProject;
46  import org.apache.velocity.Template;
47  import org.apache.velocity.VelocityContext;
48  import org.apache.velocity.app.VelocityEngine;
49  import org.apache.velocity.exception.MethodInvocationException;
50  import org.apache.velocity.exception.ParseErrorException;
51  import org.apache.velocity.exception.ResourceNotFoundException;
52  import org.codehaus.plexus.i18n.DefaultI18N;
53  import org.codehaus.plexus.i18n.I18N;
54  import org.codehaus.plexus.personality.plexus.lifecycle.phase.InitializationException;
55  import org.codehaus.plexus.util.FileUtils;
56  import org.codehaus.plexus.util.PathTool;
57  import org.codehaus.plexus.util.ReaderFactory;
58  import org.codehaus.plexus.util.StringUtils;
59  import org.jdom.Document;
60  import org.jdom.Element;
61  import org.jdom.JDOMException;
62  import org.jdom.input.SAXBuilder;
63  import org.jdom.output.Format;
64  import org.jdom.xpath.XPath;
65  
66  /**
67   * Goal runs Velocity on the files in the specified directory.
68   *
69   * @description                  Runs Velocity on the files in the specified
70   *                               directory.
71   * @goal                         merge
72   * @phase                        pre-site
73   * @requiresDependencyResolution runtime
74   */
75  public class MergeMojo extends AbstractMojo {
76  
77      /**
78       * Specifies the input encoding.
79       *
80       * @parameter expression="${encoding}"
81       *            default-value="${project.build.sourceEncoding}"
82       */
83      private String inputEncoding;
84  
85      /**
86       * Specifies the output encoding.
87       *
88       * @parameter expression="${outputEncoding}"
89       *            default-value="${project.reporting.outputEncoding}"
90       */
91      private String outputEncoding;
92  
93      /**
94       * Remote repositories used for the project.
95       *
96       * @todo      this is used for site descriptor resolution - it should relate
97       *            to the actual project but for some reason they are not always
98       *            filled in
99       * @parameter expression="${project.remoteArtifactRepositories}"
100      */
101     protected List<?> repositories;
102 
103     /**
104      * The local repository.
105      *
106      * @parameter expression="${localRepository}"
107      */
108     protected ArtifactRepository localRepository;
109 
110     /**
111      * Match pattern for the files to be processed.
112      *
113      * @parameter expression="${htmlfiltersite.filePattern}"
114      *            default-value="**\/*.html,**\/*.html.vm"
115      */
116     private String filePattern;
117 
118     /**
119      * Match pattern for the files to be filtered.
120      *
121      * @parameter expression="${htmlfiltersite.filterExtension}"
122      *            default-value=".html.vm"
123      */
124     private String filterExtension;
125 
126     /**
127      * Directory containing the site.xml file and the source for apt, fml and
128      * xdoc docs, e.g. ${basedir}/src/site.
129      *
130      * @parameter expression="${htmlfiltersite.siteDirectory}"
131      *            default-value="${basedir}/src/site"
132      */
133     private File siteDirectory;
134 
135     /**
136      * Location of the source directory.
137      *
138      * @parameter expression="${htmlfiltersite.sourceDirectory}"
139      *            default-value="${basedir}/src/site/html"
140      */
141     private File sourceDirectory;
142 
143     /**
144      * Location of the output directory.
145      *
146      * @parameter expression="${htmlfiltersite.targetDirectory}"
147      *            default-value="${project.build.directory}/generated-site/resources"
148      */
149     private File targetDirectory;
150 
151     /**
152      * Velocity template for filtering.
153      *
154      * @parameter expression="${htmlfiltersite.templateFile}"
155      *            default-value="${basedir}/src/site/site.vm"
156      */
157     private File templateFile;
158 
159     /**
160      * The maven project.
161      *
162      * @parameter expression="${project}"
163      * @required
164      * @readonly
165      */
166     protected MavenProject project;
167 
168     /** Plexis internationalization element. */
169     private I18N i18n = new DefaultI18N();
170 
171     /**
172      * The reactor projects.
173      *
174      * @parameter expression="${reactorProjects}"
175      * @required
176      * @readonly
177      */
178     protected List<?> reactorProjects;
179 
180     /**
181      * The Doxia SiteTool object.
182      *
183      * @component
184      */
185     private SiteTool siteTool;
186 
187     /**
188      * Maven project.
189      *
190      * @return the project
191      */
192     public MavenProject getProject() {
193         return project;
194     }
195 
196     /**
197      * Set the project.
198      *
199      * @param project the project to set
200      */
201     public void setProject(MavenProject project) {
202         this.project = project;
203     }
204 
205     /**
206      * Gets the input files encoding.
207      *
208      * @return The input files encoding, never <code>null</code>.
209      */
210     protected String getInputEncoding() {
211         return (inputEncoding == null) ? ReaderFactory.ISO_8859_1 : inputEncoding;
212     }
213 
214     /**
215      * Set the input encoding.
216      *
217      * @param inputEncoding the inputEncoding to set
218      */
219     public void setInputEncoding(String inputEncoding) {
220         this.inputEncoding = inputEncoding;
221     }
222 
223     /**
224      * Set the output encoding.
225      *
226      * @param outputEncoding the outputEncoding to set
227      */
228     public void setOutputEncoding(String outputEncoding) {
229         this.outputEncoding = outputEncoding;
230     }
231 
232     /**
233      * Gets the effective reporting output files encoding.
234      *
235      * @return The effective reporting output file encoding, never <code>
236      *         null</code>.
237      */
238     protected String getOutputEncoding() {
239         return (outputEncoding == null) ? ReaderFactory.UTF_8 : outputEncoding;
240     }
241 
242     /**
243      * Set the repositories.
244      *
245      * @param repositories the repositories to set
246      */
247     public void setRepositories(List<?> repositories) {
248         this.repositories = repositories;
249     }
250 
251     /**
252      * Set the local repository.
253      *
254      * @param localRepository the localRepository to set
255      */
256     public void setLocalRepository(ArtifactRepository localRepository) {
257         this.localRepository = localRepository;
258     }
259 
260     /**
261      * Set the file pattern.
262      *
263      * @param filePattern the filePattern to set
264      */
265     public void setFilePattern(String filePattern) {
266         this.filePattern = filePattern;
267     }
268 
269     /**
270      * Set the site directory.
271      *
272      * @param siteDirectory the siteDirectory to set
273      */
274     public void setSiteDirectory(File siteDirectory) {
275         this.siteDirectory = siteDirectory;
276     }
277 
278     /**
279      * Set the source directory.
280      *
281      * @param sourceDirectory the sourceDirectory to set
282      */
283     public void setSourceDirectory(File sourceDirectory) {
284         this.sourceDirectory = sourceDirectory;
285     }
286 
287     /**
288      * Set the target directory.
289      *
290      * @param targetDirectory the targetDirectory to set
291      */
292     public void setTargetDirectory(File targetDirectory) {
293         this.targetDirectory = targetDirectory;
294     }
295 
296     /**
297      * Set the template file.
298      *
299      * @param templateFile the templateFile to set
300      */
301     public void setTemplateFile(File templateFile) {
302         this.templateFile = templateFile;
303     }
304 
305     /**
306      * Set the Plexus internationalization object.
307      *
308      * @param i18n the i18n to set
309      */
310     public void setI18n(I18N i18n) {
311         this.i18n = i18n;
312     }
313 
314     /**
315      * Set the reactor projects.
316      *
317      * @param reactorProjects the reactorProjects to set
318      */
319     public void setReactorProjects(List<?> reactorProjects) {
320         this.reactorProjects = reactorProjects;
321     }
322 
323     /**
324      * Set the site tool object.
325      *
326      * @param siteTool the siteTool to set
327      */
328     public void setSiteTool(SiteTool siteTool) {
329         this.siteTool = siteTool;
330     }
331 
332     /**
333      * @see org.apache.maven.plugin.AbstractMojo#execute()
334      */
335     public void execute() throws MojoExecutionException {
336         VelocityEngine ve         = initializeVelocityEngine();
337         Template       template   = getVelocityTemplate(ve);
338         AttributeMap   attributes = new AttributeMap();
339 
340         try {
341             ((DefaultI18N) i18n).initialize();
342         } catch (InitializationException e) {
343             e.printStackTrace();
344             throw new MojoExecutionException("Unable to initialize I18N object", e);
345         }
346 
347         if (attributes.get("project") == null) {
348             attributes.put("project", project);
349         }
350 
351         // Put any of the properties in directly into the Velocity attributes
352         attributes.putAll(project.getProperties());
353 
354         if (attributes.get("inputEncoding") == null) {
355             attributes.put("inputEncoding", getInputEncoding());
356         }
357 
358         if (attributes.get("outputEncoding") == null) {
359             attributes.put("outputEncoding", getOutputEncoding());
360         }
361 
362         DecorationModel decorationModel;
363 
364         try {
365             decorationModel = siteTool.getDecorationModel(project, reactorProjects, localRepository, repositories,
366                                                           getRelativeFilePath(project.getBasedir(), siteDirectory),
367                                                           Locale.getDefault(), getInputEncoding(), getOutputEncoding());
368         } catch (SiteToolException e) {
369             throw new MojoExecutionException("SiteToolException: " + e.getMessage(), e);
370         }
371 
372         attributes.put("currentDate", new Date());
373 
374         attributes.put("lastPublished", new SimpleDateFormat("dd MMM yyyy").format(new Date()));
375 
376         List<String> fileList = getFileList();
377 
378         for (String file : fileList) {
379             mergeFile(file, ve, template, decorationModel, attributes);
380         }
381     }
382 
383     /**
384      * Initialize the velocity engine.
385      *
386      * @return the velocity engine.
387      *
388      * @throws MojoExecutionException
389      */
390     private VelocityEngine initializeVelocityEngine() throws MojoExecutionException {
391         VelocityEngine ve = new VelocityEngine();
392 
393         try {
394             ve.addProperty("resource.loader", "file, class");
395             ve.addProperty("class.resource.loader.description", "Velocity Classpath Resource Loader");
396             ve.addProperty("class.resource.loader.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
397             ve.init();
398         } catch (Exception e) {
399             e.printStackTrace();
400             throw new MojoExecutionException("Unable to initialize Velocity engine", e);
401         }
402 
403         return ve;
404     }
405 
406     /**
407      * Get the velocity template.
408      *
409      * @param  ve
410      *
411      * @return the velocity template.
412      *
413      * @throws MojoExecutionException
414      */
415     private Template getVelocityTemplate(VelocityEngine ve) throws MojoExecutionException {
416         Template template = null;
417 
418         try {
419             template = ve.getTemplate(getRelativeFilePath(project.getBasedir(), templateFile));
420         } catch (ResourceNotFoundException e) {
421             e.printStackTrace();
422             throw new MojoExecutionException("Unable to locate template " + templateFile, e);
423         } catch (ParseErrorException e) {
424             e.printStackTrace();
425             throw new MojoExecutionException("Problem parsing the template", e);
426         } catch (MethodInvocationException e) {
427             e.printStackTrace();
428             throw new MojoExecutionException("Something invoked in the template threw an exception", e);
429         } catch (Exception e) {
430             e.printStackTrace();
431             throw new MojoExecutionException("Some random template parsing error occurred", e);
432         }
433 
434         return template;
435     }
436 
437     /**
438      * Merge a file with the velocity template, filtering it if necessary.
439      *
440      * @param  file            the file to merge
441      * @param  ve              the velocity engine.
442      * @param  template        the velocity template.
443      * @param  decorationModel the Doxia decoration model.
444      * @param  attributes      attributes set from POM and such.
445      *
446      * @throws MojoExecutionException
447      */
448     private void mergeFile(String file, VelocityEngine ve, Template template, DecorationModel decorationModel, AttributeMap attributes)
449         throws MojoExecutionException {
450         File    sourceFile  = new File(sourceDirectory, file);
451         boolean doFiltering = (filterExtension != null && filterExtension.equals(file.substring(file.length() - filterExtension.length())));
452 
453         File       targetFile = null;
454         FileWriter fileWriter = null;
455         Reader     fileReader = null;
456 
457         if (doFiltering) {
458             targetFile = new File(targetDirectory, file.substring(0, file.length() - filterExtension.length()) + ".html");
459         } else {
460             targetFile = new File(targetDirectory, file);
461         }
462 
463         if (!targetFile.getParentFile().exists()) {
464             targetFile.getParentFile().mkdirs();
465         }
466 
467         VelocityContext context = createContext(sourceFile, decorationModel, attributes);
468 
469         try {
470             StringWriter sw = new StringWriter();
471 
472             fileReader = new InputStreamReader(new FileInputStream(sourceFile), "UTF-8");
473             // If file ends in filter extension, filter it through Velocity before merging with template.
474             if (doFiltering) {
475                 if (!ve.evaluate(context, sw, "htmlfilter-site", fileReader)) {
476                     throw new MojoExecutionException("Unable to evaluate html file " + sourceFile);
477                 }
478 
479                 closeReader(fileReader);
480                 fileReader = new StringReader(sw.toString());
481             }
482 
483             Document doc = parseXHTMLDocument(fileReader);
484 
485             addInfoFromDocument(context, decorationModel, doc, null);
486 
487             fileWriter = new FileWriter(targetFile);
488 
489             template.merge(context, fileWriter);
490         } catch (Exception e) {
491             e.printStackTrace();
492             throw new MojoExecutionException("Unable to merge Velocity", e);
493         } finally {
494             closeReader(fileReader);
495             closeWriter(fileWriter);
496         }
497     }
498 
499     /**
500      * Add the information from the document to the Velocity context.
501      *
502      * @param context         the velocity context.
503      * @param decorationModel the Doxia decoration model.
504      * @param doc             the document.
505      * @param createDate      the create date.
506      */
507     private void addInfoFromDocument(VelocityContext context, DecorationModel decorationModel, Document doc, Date createDate) {
508         context.put("authors", getAuthors(doc));
509 
510         String title = "";
511 
512         if (decorationModel.getName() != null) {
513             title = decorationModel.getName();
514         } else if (project.getName() != null) {
515             title = project.getName();
516         }
517 
518         if (title.length() > 0) {
519             title += " - ";
520         }
521 
522         title += getTitle(doc);
523 
524         context.put("title", title);
525 
526         context.put("headContent", getHeadContent(doc));
527 
528         context.put("bodyContent", getBodyContent(doc));
529 
530         SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
531 
532         if (createDate != null) {
533             context.put("dateCreation", sdf.format(createDate));
534         }
535     }
536 
537     /**
538      * Close a Reader ignoring any exception.
539      *
540      * @param reader the reader to close.
541      */
542     private void closeReader(Reader reader) {
543         if (reader != null) {
544             try {
545                 reader.close();
546             } catch (IOException e) {
547             }
548         }
549     }
550 
551     /**
552      * Close a Writer ignoring any exceptions.
553      *
554      * @param writer the Writer to close.
555      */
556     private void closeWriter(FileWriter writer) {
557         if (writer != null) {
558             try {
559                 writer.close();
560             } catch (IOException e) {
561             }
562         }
563     }
564 
565     /**
566      * Get the list of filenames to merge.
567      *
568      * @return a list of filenames to merge.
569      */
570     @SuppressWarnings("unchecked")
571     private List<String> getFileList() {
572         List<String> fileList = null;
573 
574         try {
575             fileList = FileUtils.getFileNames(sourceDirectory, filePattern, "", false, true);
576         } catch (IOException e) {
577             // TODO Auto-generated catch block
578             e.printStackTrace();
579         }
580 
581         return fileList;
582     }
583 
584     /**
585      * Get the relative file path for a file.
586      *
587      * @param  oldPath the base path.
588      * @param  newPath the new path.
589      *
590      * @return the relative path to the newPath based on the oldPath.
591      */
592     private String getRelativeFilePath(File oldPath, File newPath) {
593         List<String> names = new ArrayList<String>();
594 
595         oldPath = oldPath.getAbsoluteFile();
596         newPath = newPath.getAbsoluteFile();
597 
598         while (newPath != null && !newPath.equals(oldPath)) {
599             names.add(newPath.getName());
600             newPath = newPath.getParentFile();
601         }
602 
603         if (newPath == null) {
604             return "";
605         }
606 
607         StringBuilder result = new StringBuilder();
608 
609         for (int i = names.size() - 1; i >= 0; i--) {
610             if (result.length() > 0) {
611                 result.append('/');
612             }
613 
614             result.append(names.get(i));
615         }
616 
617         return result.toString();
618     }
619 
620     /**
621      * Create the velocity context.
622      *
623      * @param  sourceFile      the source file.
624      * @param  decorationModel the Doxia decoration model.
625      * @param  attributes      the attributes from the POM and such.
626      *
627      * @return the velocity context.
628      */
629     private VelocityContext createContext(File sourceFile, DecorationModel decorationModel, AttributeMap attributes) {
630         VelocityContext context      = new VelocityContext();
631 
632         // ----------------------------------------------------------------------
633         // Data objects
634         // ----------------------------------------------------------------------
635 
636         String          relativePath = PathTool.getRelativePath(sourceDirectory.getPath(), sourceFile.getPath());
637 
638         context.put("relativePath", relativePath);
639 
640         SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");
641 
642         context.put("dateRevision", sdf.format(new Date()));
643 
644         context.put("decoration", decorationModel);
645 
646         context.put("currentDate", new Date());
647 
648         Locale locale = Locale.getDefault();
649 
650         context.put("dateFormat", DateFormat.getDateInstance(DateFormat.DEFAULT, locale));
651 
652         String  currentFileName = sourceFile.getName();
653         String  alignedFileName = PathTool.calculateLink(getRelativeFilePath(sourceDirectory, sourceFile), relativePath);
654         boolean doFiltering     = (filterExtension != null
655                     && filterExtension.equals(currentFileName.substring(currentFileName.length() - filterExtension.length())));
656 
657         if (doFiltering) {
658             currentFileName = currentFileName.substring(0, currentFileName.length() - filterExtension.length()) + ".html";
659             alignedFileName = alignedFileName.substring(0, alignedFileName.length() - filterExtension.length()) + ".html";
660         }
661 
662         context.put("currentFileName", currentFileName);
663 
664         context.put("alignedFileName", alignedFileName);
665 
666         context.put("locale", locale);
667 
668         // Add global properties.
669         if (attributes != null) {
670             for (Object o : attributes.keySet()) {
671                 context.put((String) o, attributes.get(o));
672             }
673         }
674 
675         // ----------------------------------------------------------------------
676         // Tools
677         // ----------------------------------------------------------------------
678 
679         context.put("PathTool", new PathTool());
680 
681         context.put("FileUtils", new FileUtils());
682 
683         context.put("StringUtils", new StringUtils());
684 
685         context.put("i18n", i18n);
686 
687         return context;
688     }
689 
690     /**
691      * Parse a document from text in the reader.
692      *
693      * @param  reader the Reader from which to parse the document.
694      *
695      * @return the JDom document parsed from the XHTML file.
696      *
697      * @throws IOException   if the reader cannot be read.
698      * @throws JDOMException if the reader cannot be parsed.
699      */
700     private Document parseXHTMLDocument(Reader reader) throws JDOMException, IOException {
701         Document document = null;
702 
703         SAXBuilder builder = new SAXBuilder();
704 
705         builder.setEntityResolver(new DTDHandler());
706         builder.setIgnoringElementContentWhitespace(false);
707         builder.setIgnoringBoundaryWhitespace(false);
708         document = builder.build(reader);
709 
710         return document;
711     }
712 
713     /**
714      * Get the document title.
715      *
716      * @param  document the document.
717      *
718      * @return the title.
719      */
720     private String getTitle(Document document) {
721         Element element = null;
722 
723         element = selectSingleNode(document.getRootElement(), "/xhtml:html/xhtml:head/xhtml:title");
724         if (element == null) {
725             return null;
726         }
727 
728         return element.getText();
729     }
730 
731     /**
732      * Get the document authors.
733      *
734      * @param  document the document.
735      *
736      * @return a list of authors.
737      */
738     private List<String> getAuthors(Document document) {
739         List<Element> nl   = getXPathList(document, "/xhtml:html/xhtml:head/xhtml:meta[@class='author']");
740         List<String>  list = new ArrayList<String>();
741 
742         for (Element elem : nl) {
743             String author = selectSingleNode(elem, "xhtml:td[@class='author']").getText();
744 
745             list.add(author);
746         }
747 
748         return list;
749     }
750 
751     /**
752      * Extract a list matching an XPath path. This surreptitiously adds the
753      * XHTML namespace.
754      *
755      * @param  document the document.
756      * @param  path     the path to select.
757      *
758      * @return the list of elements matching the path.
759      */
760     @SuppressWarnings("unchecked")
761     protected List<Element> getXPathList(Document document, String path) {
762         try {
763             XPath xpath = XPath.newInstance(path);
764 
765             xpath.addNamespace("xhtml", "http://www.w3.org/1999/xhtml");
766             List<Element> nl = (List<Element>) xpath.selectNodes(document);
767 
768             return nl;
769         } catch (JDOMException e) {
770             return new ArrayList<Element>();
771         }
772     }
773 
774     /**
775      * Get the body content as a string.
776      *
777      * @param  document the document.
778      *
779      * @return the body content.
780      */
781     private String getBodyContent(Document document) {
782         Element element = null;
783 
784         element = selectSingleNode(document.getRootElement(), "/xhtml:html/xhtml:body");
785         if (element == null) {
786             return null;
787         }
788 
789         return getElementContentsAsText(element);
790     }
791 
792     /**
793      * Get the head content as a string.
794      *
795      * @param  document the document.
796      *
797      * @return the head content.
798      */
799     private String getHeadContent(Document document) {
800         Element element = null;
801 
802         element = selectSingleNode(document.getRootElement(), "/xhtml:html/xhtml:head");
803         if (element == null) {
804             return null;
805         }
806 
807         return getElementContentsAsText(element);
808     }
809 
810     /**
811      * Get the element contents as a String.
812      *
813      * @param  element the element.
814      *
815      * @return the element contents.
816      */
817     private String getElementContentsAsText(Element element) {
818         StringBuilder text     = new StringBuilder();
819         HTMLOutputter writer   = new HTMLOutputter(Format.getPrettyFormat().setTextMode(Format.TextMode.TRIM_FULL_WHITE)
820                                                        .setExpandEmptyElements(true));
821         List<Element> children = getChildren(element);
822 
823         if (children.size() == 0) {
824             return element.getText();
825         }
826 
827         for (Element child : children) {
828             StringWriter sw = new StringWriter();
829 
830             try {
831                 writer.output(child, sw);
832             } catch (UnsupportedEncodingException e) {
833                 e.printStackTrace();
834             } catch (IOException e) {
835                 // TODO Auto-generated catch block
836                 e.printStackTrace();
837             }
838 
839             text.append(sw);
840         }
841 
842         return text.toString();
843     }
844 
845     /**
846      * Wrapper around Element.getChildren() to suppress the type warning.
847      *
848      * @param  element the element whose children to get.
849      *
850      * @return a List of Elements representing the children of the specified
851      *         element.
852      */
853     @SuppressWarnings("unchecked")
854     private List<Element> getChildren(Element element) {
855         return (List<Element>) element.getChildren();
856     }
857 
858     /**
859      * Select a single node using XPath.
860      *
861      * @param  element the element.
862      * @param  path    the path to select.
863      *
864      * @return the element selected.
865      */
866     private Element selectSingleNode(Element element, String path) {
867         try {
868             XPath xpath = XPath.newInstance(path);
869 
870             xpath.addNamespace("xhtml", "http://www.w3.org/1999/xhtml");
871             return (Element) xpath.selectSingleNode(element);
872         } catch (JDOMException e) {
873             // TODO Auto-generated catch block
874             e.printStackTrace();
875             return null;
876         }
877     }
878 
879     /**
880      * Simplify references to the attribute hash map.
881      */
882     private static class AttributeMap extends HashMap<Object, Object> {
883         private static final long serialVersionUID = 1787343499009497124L;
884     }
885 }