adding sitemap
diff --git a/src/main/org/freemarker/docgen/Transform.java b/src/main/org/freemarker/docgen/Transform.java
index 5b03cdd..3e4156f 100644
--- a/src/main/org/freemarker/docgen/Transform.java
+++ b/src/main/org/freemarker/docgen/Transform.java
@@ -112,7 +112,7 @@
  *   <li><p><tt>docgen.cjson</tt> file (optional):
  *       contains Docgen settings. It uses an extended JSON syntax; see more
  *       <a href="#cjsonLanguage">later</a>.
- *       
+ *
  *       <p>Some notes about the settings in general:
  *       <ul>
  *         <li>Values that are of type "map" keep the order of entries as they were specified,
@@ -123,7 +123,7 @@
  *         (see that later).</li>
  *         <li>All settings are optional, except where noted otherwise</li>
  *       </ul>
- *        
+ *
  *       <p>The supported settings are:
  *       <ul>
  *         <li>
@@ -183,7 +183,7 @@
  *            from the DocBook document if {@code offline} is {@code false}. This is just like
  *            if have deleted these elements (with their nested content) from the XML file.
  *            (This meant to be used to remove sections that are redundant when the book is the
- *            part of a bigger site.)  
+ *            part of a bigger site.)
  *         <li><p><tt>seoMeta</tt> (map):
  *            Let you customize the metadata used by search engines for the selected pages. The page is selected with
  *            the map's key, which is either an {@code xml:id} or {@code "file:"} + output file name. The value of
@@ -422,7 +422,7 @@
     // -------------------------------------------------------------------------
     // Constants:
 
-	static final String FILE_BOOK = "book.xml";
+		static final String FILE_BOOK = "book.xml";
     static final String FILE_ARTICLE = "article.xml";
     static final String FILE_SETTINGS = "docgen.cjson";
     /** Used for the Table of Contents file when a different node was marked to be the index.html. */
@@ -435,7 +435,10 @@
     static final String FILE_ECLIPSE_TOC_TEMPLATE = "eclipse-toc.ftl";
     static final String FILE_ECLIPSE_TOC_OUTPUT = "eclipse-toc.xml";
     static final String DIR_TEMPLATES = "docgen-templates";
-    
+
+		static final String FILE_SITEMAP_XML_TEMPLATE = "sitemap.ftl";
+		static final String FILE_SITEMAP_XML_OUTPUT = "sitemap.xml";
+
     static final String SETTING_VALIDATION = "validation";
     static final String SETTING_OFFLINE = "offline";
     static final String SETTING_SIMPLE_NAVIGATION_MODE = "simpleNavigationMode";
@@ -483,7 +486,7 @@
     static final String SETTING_VALIDATION_MAXIMUM_PROGRAMLISTING_WIDTH
             = "maximumProgramlistingWidth";
     static final String SETTING_ECLIPSE_LINK_TO = "link_to";
-    
+
     static final String SETTING_SEO_META_KEY_TITLE = "title";
     static final String SETTING_SEO_META_KEY_FULL_TITLE = "fullTitle";
     static final String SETTING_SEO_META_KEY_DESCRIPTION = "description";
@@ -503,7 +506,7 @@
         COMMON_LINK_KEYS.add(COMMON_LINK_KEY_CLASS);
         COMMON_LINK_KEYS.add(COMMON_LINK_KEY_HREF);
     }
-    
+
     private static final String VAR_OFFLINE
             = SETTING_OFFLINE;
     private static final String VAR_SIMPLE_NAVIGATION_MODE
@@ -567,7 +570,7 @@
 
     private static final String OLINK_SCHEMA_START = "olink:";
     private static final String ID_SCHEMA_START = "id:";
-    
+
     private static final Charset UTF_8 = Charset.forName("UTF-8");
 
     // Docgen-specific XML attributes (added during DOM-tree postediting):
@@ -627,7 +630,7 @@
 
     /** An element for which it's not possible to create a link. */
     private static final String A_DOCGEN_NOT_ADDRESSABLE = "docgen_not_addressable";
-    
+
 	private static final String AV_INDEX_ROLE = "index.html";
 
     /**
@@ -670,7 +673,7 @@
     private static final String E_SEARCHRESULTS = "searchresults";
 	private static final String SEARCH_RESULTS_PAGE_TITLE = "Search results";
 	private static final String SEARCH_RESULTS_ELEMENT_ID = "searchresults";
-    
+
     // -------------------------------------------------------------------------
     // Settings:
 
@@ -685,7 +688,7 @@
     private String deployUrl;
 
     private String onlineTrackerHTML;
-    
+
     private Set<String> removeNodesWhenOnline;
 
     /** Element types for which a new output file is created  */
@@ -704,7 +707,7 @@
     private boolean generateEclipseTOC;
 
     private boolean simpleNavigationMode;
-    
+
     private boolean showEditoralNotes;
 
     private boolean showXXELogo;
@@ -731,10 +734,10 @@
     private Map<String, Map<String, String>> socialLinks;
 
     private HashMap<String, String> logo;
-    
+
     private String copyrightHolder;
     private Integer copyrightStartYear;
-    
+
     private Map<String, Map<String, String>> seoMeta;
 
     private DocgenValidationOptions validationOps
@@ -1202,7 +1205,7 @@
 
         // - Post-edit and examine the DOM:
         preprocessDOM(doc);
-        
+
         // Resolve Docgen URL schemes in setting values:
         if (tabs != null) {
             for (Entry<String, String> tabEnt : tabs.entrySet()) {
@@ -1349,6 +1352,27 @@
             }
         }
 
+				// - Generate Sitemap XML:
+				{
+						logger.info("Generating Sitemap XML...");
+						Template template = fmConfig.getTemplate(FILE_SITEMAP_XML_TEMPLATE);
+						try (Writer wr = new BufferedWriter(
+										new OutputStreamWriter(
+														new FileOutputStream(
+																		new File(destDir, FILE_SITEMAP_XML_OUTPUT)),
+														UTF_8))) {
+								try {
+										SimpleHash dataModel = new SimpleHash(fmConfig.getObjectWrapper());
+												dataModel.put(VAR_JSON_TOC_ROOT, tocNodes.get(0));
+										template.process(dataModel, wr, null, NodeModel.wrap(doc));
+								} catch (TemplateException e) {
+										throw new BugException("Failed to generate Sitemap XML"
+														+ "(see cause exception).", e);
+								}
+						}
+				}
+
+
         // - Generate the HTML-s:
         logger.info("Generating HTML files...");
         int htmlFileCounter = 0;
@@ -1369,7 +1393,7 @@
                 }
             }
         }
-        
+
 		if (!offline && searchKey != null) {
 			try {
 				generateSearchResultsHTMLFile(doc);
@@ -1413,7 +1437,7 @@
         	if (simpleNavigationMode) {
         		throw new DocgenException("Eclipse ToC generation is untested/unsupported with simpleNavigationMode=true.");
         	}
-        	
+
             logger.info("Generating Eclipse ToC...");
             Template template = fmConfig.getTemplate(FILE_ECLIPSE_TOC_TEMPLATE);
             try (Writer wr = new BufferedWriter(
@@ -1533,7 +1557,7 @@
         }
         return (List<String>) settingValue;
     }
-    
+
     private String castSettingToString(File cfgFile,
             String settingName, Object settingValue) throws DocgenException {
         if (!(settingValue instanceof String)) {
@@ -1621,10 +1645,10 @@
                     + "Map-s, but some of them is a "
                     + CJSONInterpreter.cjsonTypeOf(mapEntryValue) + ".");
         }
-        
+
         if (requiredKeys == null) requiredKeys = Collections.emptySet();
         if (optionalKeys == null) optionalKeys = Collections.emptySet();
-        
+
         Map<?, ?> mapEntryValueAsMap = (Map<?, ?>) mapEntryValue;
         for (Entry<?, ?> valueEnt : mapEntryValueAsMap.entrySet()) {
             Object key = valueEnt.getKey();
@@ -1673,7 +1697,7 @@
         }
         return (Map<String, String>) mapEntryValue;
     }
-    
+
     private void copyCommonStatic(String path) throws IOException {
         FileUtil.copyResourceIntoFile(
                 Transform.class, "statics", path,
@@ -1809,7 +1833,7 @@
                     }
                 }
                 if (loRef.endsWith(".svg")) {
-                    String pngRef = ref.substring(0, ref.length() - 4) + ".png"; 
+                    String pngRef = ref.substring(0, ref.length() - 4) + ".png";
                     if (!new File(contentDir, pngRef.replace('/', File.separatorChar)).isFile()) {
                         throw new DocgenException(
                                 XMLUtil.theSomethingElement(elem)
@@ -1894,7 +1918,7 @@
 
     private void preprocessDOM_applyRemoveNodesWhenOnlineSetting(Document doc) throws DocgenException {
         if (offline || removeNodesWhenOnline == null || removeNodesWhenOnline.isEmpty()) return;
-        
+
         HashSet<String> idsToRemoveLeft = new HashSet<String>(removeNodesWhenOnline);
         preprocessDOM_applyRemoveNodesWhenOnlineSetting_inner(
                 doc.getDocumentElement(), idsToRemoveLeft);
@@ -1925,7 +1949,7 @@
             }
         }
     }
-    
+
     /**
      * Annotates the document structure nodes with so called ranks.
      * About ranks see: {@link #setting_lowestFileElementRank}.
@@ -2045,7 +2069,7 @@
         preprocessDOM_buildTOC_inner(doc, 0, null);
         if (tocNodes.size() > 0) {
             preprocessDOM_buildTOC_checkEnsureHasIndexHhml(tocNodes);
-            
+
             preprocessDOM_buildTOC_checkTOCTopology(tocNodes.get(0));
 
             if (!tocNodes.get(0).isFileElement()) {
@@ -2053,9 +2077,9 @@
                         "The root ToC node must be a file-element.");
             }
             preprocessDOM_buildTOC_checkFileTopology(tocNodes.get(0));
-            
+
             if (simpleNavigationMode) {
-                // Must do it at the end: We need the docgen_... XML attributes here, and we must be past the 
+                // Must do it at the end: We need the docgen_... XML attributes here, and we must be past the
                 // TOC topology checks.
                 for (TOCNode tocNode : tocNodes) {
                     if (tocNode.isFileElement()
@@ -2065,7 +2089,7 @@
                     }
                 }
             }
-            
+
             if (!validationOps.getOutputFilesCanUseAutoID()) {
                 for (TOCNode tocNode : tocNodes) {
                     String outputFileName = tocNode.getOutputFileName();
@@ -2314,13 +2338,13 @@
             // The document element is never an external link ToC node.
             return null;
         }
-        
+
 		Element title = getTitle(elem);
 		if (title == null) {
 		    // An element without title can't be an external link ToC node
 		    return null;
 		}
-		
+
 		Iterator<Element> it = XMLUtil.childrenElementsOf(title).iterator();
 		if (it.hasNext()) {
 		    Element firstChild = it.next();
@@ -2354,7 +2378,7 @@
 	}
 
 	/**
-     * Ensures that 
+     * Ensures that
      * @param tocNodes
      * @throws DocgenException
      */
@@ -2546,7 +2570,7 @@
                         seoMetaMap.get(SETTING_SEO_META_KEY_DESCRIPTION));
             }
         }
-        
+
         boolean generateDetailedTOC = false;
         if (isTheDocumentElement) {
             // Find out if a detailed ToC will be useful:
@@ -2583,7 +2607,7 @@
 
     private void generateSearchResultsHTMLFile(Document doc) throws TemplateException, IOException, DocgenException {
         SimpleHash dataModel = new SimpleHash(fmConfig.getObjectWrapper());
-        
+
         dataModel.put(VAR_PAGE_TYPE, PAGE_TYPE_SEARCH_RESULTS);
         dataModel.put(VAR_TOC_DISPLAY_DEPTH, maxMainTOFDisplayDepth);
 
@@ -2597,13 +2621,13 @@
             searchresultsElem.setAttribute("id", SEARCH_RESULTS_ELEMENT_ID);
 
             searchresultsElem.setAttribute(A_DOCGEN_RANK, E_SECTION);
-            
+
         	// Docgen templates may expect page-elements to have a title:
 	        Element titleElem = doc.createElementNS(XMLNS_DOCBOOK5, E_TITLE);
 	        titleElem.setTextContent(SEARCH_RESULTS_PAGE_TITLE);
 	        searchresultsElem.appendChild(titleElem);
         }
-        
+
         // We must add it to the document so that .node?root and such will work.
         doc.getDocumentElement().appendChild(searchresultsElem);
         try {
@@ -2611,7 +2635,7 @@
 	        searchresultsTOCNode.setFileElement(true);
 	        searchresultsTOCNode.setOutputFileName(FILE_SEARCH_RESULTS_HTML);
 	        currentFileTOCNode = searchresultsTOCNode;
-	
+
 	        generateHTMLFile_inner(dataModel, currentFileTOCNode.getOutputFileName());
         } finally {
         	doc.getDocumentElement().removeChild(searchresultsElem);
@@ -2683,12 +2707,12 @@
         if (elem.hasAttribute(A_DOCGEN_NOT_ADDRESSABLE)) {
             return null;
         }
-        
+
         String extLink = getExternalLinkTOCNodeURLOrNull(elem);
         if (extLink != null) {
             return extLink;
         }
-        
+
         // Find the closest id:
         String id = null;
         Node node = elem;
@@ -2781,7 +2805,7 @@
 
         return createElementLinkURL(elem);
     }
-    
+
     private TemplateMethodModelEx createLinkFromNode
             = new TemplateMethodModelEx() {
 
@@ -3004,7 +3028,7 @@
         public void setFileElement(boolean fileElement) {
             this.fileElement = fileElement;
         }
-        
+
         public boolean isFileElement() {
             return fileElement;
         }
@@ -3022,7 +3046,7 @@
             return traversalIndex > 0
                     ? tocNodes.get(traversalIndex - 1) : null;
         }
-        
+
     }
 
     enum DocumentStructureRank {
diff --git a/src/main/org/freemarker/docgen/templates/sitemap.ftl b/src/main/org/freemarker/docgen/templates/sitemap.ftl
new file mode 100644
index 0000000..01a6e39
--- /dev/null
+++ b/src/main/org/freemarker/docgen/templates/sitemap.ftl
@@ -0,0 +1,20 @@
+<#import "util.ftl" as u>
+<#macro sitemapUrls node>
+  <#local url = CreateLinkFromNode(node.element)?xml>
+  <#if url?? && node.fileElement?c == "true">
+    <url>
+      <loc>${deployUrl}${url}</loc>
+      <lastmod>${.now?string.iso_m_u}</lastmod>
+    </url>
+    <#local child = node.firstChild!>
+    <#list 1.. as _>
+        <#if !child?hasContent><#break></#if>
+        <@sitemapUrls child />
+        <#local child = child.next!>
+    </#list>    
+  </#if>
+</#macro>
+<?xml version="1.0" encoding="UTF-8"?>
+<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
+  <@sitemapUrls tocRoot />
+</urlset>