SitemapService.java

/*
 * Copyright © 2024-2025 The CTAN Team and individual authors
 *
 * This file is distributed under the 3-clause BSD license.
 * See file LICENSE for details.
 */
package org.ctan.site.services;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.ctan.site.resources.catalogue.api.XmlWriter;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.core.JsonParser.Feature;
import com.fasterxml.jackson.databind.ObjectMapper;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

/**
 * The class <code>Sitemap3Resource</code> contains the controller for the
 * version resource.
 *
 * @author <a href="mailto:gene@ctan.org">Gerd Neugebauer</a>
 */
@Slf4j
public class SitemapService {

    /**
     * The class <code>SitemapBanner</code> contains the transport object to
     * encode a sitemap banner.
     */
    @Getter
    @JsonInclude(Include.NON_NULL)
    @JsonIgnoreProperties(ignoreUnknown = true)
    static class SitemapBanner {

        private String by;

        private String height;

        private String position;

        private String source;

        private String src;
    }

    /**
     * The class <code>SitemapList</code> contains the transport object to
     * encode a list of sitemap pages.
     */
    @Getter
    @JsonInclude(Include.NON_NULL)
    public static class SitemapList extends ArrayList<SitemapPage> {

        /**
         * The field <code>serialVersionUID</code> contains the id for
         * serialisation.
         */
        private static final long serialVersionUID = 1L;
    }

    /**
     * The class <code>SitemapPage</code> contains the transport object to
     * encode a sitemap page.
     */
    @Getter
    @JsonInclude(Include.NON_NULL)
    @JsonIgnoreProperties(ignoreUnknown = true)
    @SuppressFBWarnings(value = "EI_EXPOSE_REP")
    public static class SitemapPage {

        private String arg;

        private SitemapBanner banner;

        private List<SitemapPage> children;

        private String clip;

        private String cond;

        private Boolean hide;

        private String icon;

        private String id;

        private String title;

        private String to;
    }

    /**
     * The field <code>MAPPER</code> contains the object mapper.
     */
    static final ObjectMapper MAPPER =
        new ObjectMapper().enable(Feature.ALLOW_COMMENTS);

    /**
     * The method <code>asJson</code> provides means to create a sitemap as
     * object tree.
     *
     * @return the sitemap
     */
    public SitemapList asJson() {

        try (var is = SitemapService.class.getClassLoader()
            .getResourceAsStream("data/sitemap.json")) {
            return MAPPER.readValue(is, SitemapList.class);
        } catch (IOException e) {
            log.error("parsing data/sitemap.json failed", e);
            return null;
        }
    }

    /**
     * The method <code>asXml</code> provides means to create a sitemap as
     * XML-formatted String.
     *
     * @return the XML
     */
    public String asXml() {

        var sitemap = new XmlWriter();
        sitemap.out("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
        sitemap.out(
            "<urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
                + "    xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd\"\n"
                + "    xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">");
        asXml(sitemap, " ", asJson());
        sitemap.out("</urlset>");
        return sitemap.toString();
    }

    /**
     * The method <code>asXml</code> provides means to create a sitemap as
     * XML-formatted String.
     *
     * @param sitemap the site map
     * @param prefix the whitespace for indentation
     * @param list the list of pages
     */
    private void asXml(XmlWriter sitemap, String prefix,
        List<SitemapPage> list) {

        if (list == null) {
            return;
        }
        var prefix2 = prefix + " ";
        for (var it : list) {
            sitemap.out(prefix, "<url>\n",
                prefix, " <loc>https://ctan.org", it.getTo(), "</loc>\n",
                prefix, "</url>\n");
            asXml(sitemap, prefix2, it.getChildren());
        }
    }
}