All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.bernardomg.velocity.tool.SiteTool Maven / Gradle / Ivy

/**
 * The MIT License (MIT)
 * 

* Copyright (c) 2015-2023 the original author or authors. *

* Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: *

* The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. *

* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.bernardomg.velocity.tool; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; import org.apache.velocity.tools.config.DefaultKey; import org.jsoup.Jsoup; import org.jsoup.nodes.Element; import org.jsoup.parser.Tag; /** * Utilities class for fixing several issues in Doxia generated sites, updating and homogenising their layouts. *

* This was created for Maven Sites. These are built through Doxia which supports XHTML, and not HTML5, and so this * library generates outdated pages. Also some of the reports which can be generated through Maven plugins suffer from * similar issues and add another problem, that their layouts may not match at all. *

* The Docs Maven Skin and its requirements have dictated * the development of this class. For more generic methods use the {@link com.bernardomg.velocity.tool.HtmlTool * HtmlTool}. * * @author Bernardo Martínez Garrido */ @DefaultKey("siteTool") public class SiteTool { /** * Regular expresion indicating invalid values for ids and internal links which will be replaced by hyphens. */ private static final String ID_HYPHEN_REGEX = "[ _]"; /** * Regular expresion indicating invalid values for ids and internal links will will be removed. */ private static final String ID_REJECTED_REGEX = "[^\\w#-]"; /** * Constructs an instance of the utilities class. */ public SiteTool() { super(); } /** * Fixes links to anchors in the same page. *

* Any link such as {@code A link} will be transformed into * {@code A link}. *

* The href value will receive the following modifications, only if it is an internal link: *

    *
  • Text will be set to lower case
  • *
  • Underscores will be removed
  • *
  • Points will be removed
  • *
  • Empty spaces will be removed
  • *
* * @param root * root element with anchors to fix * @return transformed element */ public final Element fixAnchorLinks(final Element root) { String ref; // Value of the href attribute String id; // Formatted id Objects.requireNonNull(root, "Received a null pointer as root element"); // Anchors for (final Element anchor : root.getElementsByTag("a")) { // If the attribute doesn't exist then the ref will be an empty // string ref = anchor.attr("href"); if ((!ref.isEmpty()) && ("#".equals(ref.substring(0, 1)))) { id = formatId(ref); anchor.attr("href", id); } } return root; } /** * Adds or fixes heading ids. *

* If a heading has an id it will be corrected if needed, otherwise it will be created from the heading text. *

* The following operations are applied during this process: *

    *
  • Text will be set to lower case
  • *
  • Underscores will be removed
  • *
  • Points will be removed
  • *
  • Empty spaces will be removed
  • *
*

* With this headings will end looking like {@code

A heading

}. * * @param root * root element with headings where an id should be added * @return transformed element */ public final Element fixHeadingIds(final Element root) { final Collection headings; // Headings to fix String idText; // Text to generate the id String id; // Formatted id Objects.requireNonNull(root, "Received a null pointer as root element"); // Table rows with tags in a headings = root.select("h1,h2,h3,h4,h5,h6"); for (final Element heading : headings) { if (heading.hasAttr("id")) { // Contains an id // The id text is taken from the attribute idText = heading.attr("id"); } else { // Doesn't contain an id // The id text is taken from the heading text idText = heading.text(); } id = formatId(idText); heading.attr("id", id); } return root; } /** * Fixes a Maven Site report. *

* This is prepared for the following reports: *

    *
  • Changes report
  • *
  • Checkstyle
  • *
  • CPD
  • *
  • Dependencies
  • *
  • Failsafe report
  • *
  • Findbugs
  • *
  • JDepend
  • *
  • License
  • *
  • Plugins
  • *
  • Plugin management
  • *
  • Project summary
  • *
  • PMD
  • *
  • Surefire report
  • *
  • Tags list
  • *
  • Team list
  • *
* Most of the times, the fix consists on correcting the heading levels, and adding an initial heading if needed. * * @param root * root element with the report * @param report * the report name * @return transformed element */ public final Element fixReport(final Element root, final String report) { Objects.requireNonNull(root, "Received a null pointer as root element"); Objects.requireNonNull(report, "Received a null pointer as report"); switch (report) { case "changes-report": fixReportChanges(root); break; case "checkstyle": case "checkstyle-aggregate": fixReportCheckstyle(root); break; case "cpd": fixReportCpd(root); break; case "dependencies": fixReportDependencies(root); break; case "dependency-analysis": fixReportDependencyAnalysis(root); break; case "failsafe-report": fixReportFailsafe(root); break; case "findbugs": case "spotbugs": fixReportFindbugs(root); break; case "jdepend-report": fixReportJdepend(root); break; case "license": case "licenses": fixReportLicense(root); break; case "plugins": fixReportPlugins(root); break; case "plugin-management": fixReportPluginManagement(root); break; case "pmd": fixReportPmd(root); break; case "project-summary": case "summary": fixReportProjectSummary(root); break; case "surefire-report": fixReportSurefire(root); break; case "taglist": fixReportTaglist(root); break; case "team-list": case "team": fixReportTeamList(root); break; default: break; } return root; } /** * Transforms the default icons used by the Maven Site to Font Awesome icons. * * @param root * root element with the page * @return transformed element */ public final Element transformIcons(final Element root) { final Map replacements; // Texts to replace and // replacements Objects.requireNonNull(root, "Received a null pointer as root element"); replacements = new HashMap<>(); replacements.put("img[src$=images/add.gif]", "Addition"); replacements.put("img[src$=images/remove.gif]", "Remove"); replacements.put("img[src$=images/fix.gif]", "Fix"); replacements.put("img[src$=images/update.gif]", "Refresh"); replacements.put("img[src$=images/icon_help_sml.gif]", "Question"); replacements.put("img[src$=images/icon_success_sml.gif]", "Passed"); replacements.put("img[src$=images/icon_warning_sml.gif]", "Warning"); replacements.put("img[src$=images/icon_error_sml.gif]", "Failed"); replacements.put("img[src$=images/icon_info_sml.gif]", "Info"); replaceAll(root, replacements); return root; } /** * Transforms simple {@code } elements to {@code
} elements. *

* This will wrap {@code } elements with a {@code

} element, and add a {@code
} with the * contents of the image's {@code alt} attribute, if said attribute exists. * * @param root * root element with images to transform * @return transformed element */ public final Element transformImagesToFigures(final Element root) { final Collection images; // Image elements from the final Collection figures; // figure elements from the Element figure; //
element Element caption; //
element Objects.requireNonNull(root, "Received a null pointer as root element"); images = root.select("img"); for (final Element img : images) { figure = new Element(Tag.valueOf("figure"), ""); img.replaceWith(figure); figure.appendChild(img); if (img.hasAttr("alt")) { caption = new Element(Tag.valueOf("figcaption"), ""); caption.text(img.attr("alt")); figure.appendChild(caption); } } figures = root.select("figure"); for (final Element fig : figures) { if ("p".equals(fig.parent() .tag() .getName())) { fig.parent() .unwrap(); } } return root; } /** * Fixes the changes report page. * * @param root * root element for the report page to fix */ private final void fixReportChanges(final Element root) { final Collection headings; // Headings in the body final Collection sections; // Sections in the body final Element section; // First section Element timeElement; // Element with the date Element smallElement; // Element with the small date String text; // Heading text String[] texts; // Split heading text // Sets all the h2 to h1 for (final Element head : root.getElementsByTag("h2")) { head.tagName("h1"); } headings = root.getElementsByTag("h3"); if (!headings.isEmpty()) { // Sets first h3 to h2 headings.iterator() .next() .tagName("h2"); } // Takes again all the h3 elements, to avoid the new h2 for (final Element heading : root.getElementsByTag("h3")) { // Moves the heading id to the parent heading.parent() .attr("id", heading.id()); heading.removeAttr("id"); // Transforms the date on the heading text = heading.text(); texts = text.split("–", 2); if (texts.length == 2) { timeElement = new Element(Tag.valueOf("time"), ""); timeElement.text(texts[1].trim()); smallElement = new Element(Tag.valueOf("small"), ""); smallElement.append("("); smallElement.appendChild(timeElement); smallElement.append(")"); heading.text(texts[0]); heading.appendChild(smallElement); } } // Moves all the elements out of the sections sections = root.getElementsByTag("section"); if (!sections.isEmpty()) { section = sections.iterator() .next(); for (final Element child : section.children()) { child.remove(); root.appendChild(child); } section.remove(); } } /** * Fixes the Checkstyle report page. * * @param root * root element for the report page to fix */ private final void fixReportCheckstyle(final Element root) { final Collection elements; // Found elements elements = root.getElementsByTag("h2"); if (!elements.isEmpty()) { elements.iterator() .next() .tagName("h1"); } root.select("img[src=\"images/rss.png\"]") .remove(); } /** * Fixes the CPD report page. * * @param root * element for the body of the report page */ private final void fixReportCpd(final Element root) { final Collection elements; // Found elements elements = root.getElementsByTag("h2"); if (!elements.isEmpty()) { elements.iterator() .next() .tagName("h1"); } } /** * Fixes the dependencies report page. * * @param root * root element for the report page to fix */ private final void fixReportDependencies(final Element root) { root.prepend("

Dependencies Report

"); } /** * Fixes the dependency analysis report page. * * @param root * root element for the report page to fix */ private final void fixReportDependencyAnalysis(final Element root) { for (final Element head : root.getElementsByTag("h2")) { head.tagName("h1"); } for (final Element head : root.getElementsByTag("h3")) { head.tagName("h2"); } } /** * Fixes the Failsafe report page. * * @param root * root element for the report page to fix */ private final void fixReportFailsafe(final Element root) { final Collection elements; // Found elements final Element heading; // First h2 heading elements = root.getElementsByTag("h2"); if (!elements.isEmpty()) { heading = elements.iterator() .next(); heading.tagName("h1"); heading.text("Failsafe Report"); } } /** * Fixes the Findbugs report page. * * @param root * root element for the report page to fix */ private final void fixReportFindbugs(final Element root) { final Collection elements; // Found elements elements = root.getElementsByTag("h2"); if (!elements.isEmpty()) { elements.iterator() .next() .tagName("h1"); } } /** * Fixes the JDepend report page. * * @param root * root element for the report page to fix */ private final void fixReportJdepend(final Element root) { final Collection elements; // Found elements elements = root.getElementsByTag("h2"); if (!elements.isEmpty()) { elements.iterator() .next() .tagName("h1"); } } /** * Fixes the License report page. * * @param root * root element for the report page to fix */ private final void fixReportLicense(final Element root) { root.prepend("

License

"); } /** * Fixes the plugin management report page. * * @param root * root element for the report page to fix */ private final void fixReportPluginManagement(final Element root) { final Collection sections; // Sections in the body final Element section; // Section element for (final Element head : root.getElementsByTag("h2")) { head.tagName("h1"); } sections = root.getElementsByTag("section"); if (!sections.isEmpty()) { section = sections.iterator() .next(); for (final Element child : section.children()) { child.remove(); root.appendChild(child); } section.remove(); } } /** * Fixes the plugins report page. * * @param root * root element for the report page to fix */ private final void fixReportPlugins(final Element root) { root.prepend("

Plugins Report

"); } /** * Fixes the PMD report page. * * @param root * root element for the report page to fix */ private final void fixReportPmd(final Element root) { final Collection elements; // Found elements elements = root.getElementsByTag("h2"); if (!elements.isEmpty()) { elements.iterator() .next() .tagName("h1"); } } /** * Fixes the project summary report page. * * @param root * root element for the report page to fix */ private final void fixReportProjectSummary(final Element root) { for (final Element head : root.getElementsByTag("h2")) { head.tagName("h1"); } for (final Element head : root.getElementsByTag("h3")) { head.tagName("h2"); } } /** * Fixes the Surefire report page. * * @param root * root element for the report page to fix */ private final void fixReportSurefire(final Element root) { final Collection elements; // Found elements elements = root.getElementsByTag("h2"); if (!elements.isEmpty()) { elements.iterator() .next() .tagName("h1"); } } /** * Fixes the tag list report page. * * @param root * root element for the report page to fix */ private final void fixReportTaglist(final Element root) { final Collection elements; // Found elements elements = root.getElementsByTag("h2"); if (!elements.isEmpty()) { elements.iterator() .next() .tagName("h1"); } } /** * Fixes the team list report page. * * @param root * root element for the report page to fix */ private final void fixReportTeamList(final Element root) { for (final Element head : root.getElementsByTag("h2")) { head.tagName("h1"); } for (final Element head : root.getElementsByTag("h3")) { head.tagName("h2"); } } /** * Formats the received id, transforming it into a valid internal anchor id. * * @param id * id to transform * @return a valid anchor id */ private final String formatId(final String id) { return id.trim() .replaceAll(ID_HYPHEN_REGEX, "-") .replaceAll(ID_REJECTED_REGEX, ""); } /** * Returns the result from replacing a collection of HTML elements on the received HTML code. *

* These elements are received as a {@code Map}, made up of pairs where the key is a CSS selector, and the value is * the replacement for the selected element. * * @param root * root element for the content to modify * @param replacements * {@code Map} where the key is a CSS selector and the value the element's replacement */ private final void replaceAll(final Element root, final Map replacements) { String selector; // Iterated selector String replacement; // Iterated HTML replacement Element replacementElem; // Iterated replacement Collection elements; // Selected elements Element replacementBody; // Body of the replacement for (final Entry replacementEntry : replacements.entrySet()) { selector = replacementEntry.getKey(); replacement = replacementEntry.getValue(); elements = root.select(selector); if (!elements.isEmpty()) { // There are elements to replace // Processes the replacement replacementBody = Jsoup.parse(replacement) .body(); if (!replacementBody.children() .isEmpty()) { replacementElem = replacementBody.child(0); // Replaces the elements for (final Element element : elements) { element.replaceWith(replacementElem.clone()); } } } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy