Added offline mode and support visitor tracking (like Google Analytics)
diff --git a/src/main/org/freemarker/docgen/FileUtil.java b/src/main/org/freemarker/docgen/FileUtil.java
index 04e19d4..92a11b1 100644
--- a/src/main/org/freemarker/docgen/FileUtil.java
+++ b/src/main/org/freemarker/docgen/FileUtil.java
@@ -5,10 +5,15 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.InputStreamReader;
 import java.io.OutputStream;
+import java.io.Reader;
+import java.nio.charset.Charset;
 
 final class FileUtil {
     
+    private static final int READ_BUFFER_SIZE = 4096;
+
     // Can't be instantiated
     private FileUtil() {
         // nop
@@ -207,4 +212,29 @@
         return false;
     }
 
+    public static String loadString(File f, Charset charset) throws IOException {
+        FileInputStream in = new FileInputStream(f);
+        try {
+            return loadString(in, charset);
+        } finally {
+            in.close();
+        }
+    }
+    
+    public static String loadString(InputStream in, Charset charset)
+            throws IOException {
+        Reader r = new InputStreamReader(in, charset);
+        StringBuilder sb = new StringBuilder(256);
+        try {
+            char[] buf = new char[READ_BUFFER_SIZE];
+            int ln;
+            while ((ln = r.read(buf)) != -1) {
+                sb.append(buf, 0, ln);
+            }
+        } finally {
+            r.close();
+        }
+        return sb.toString();
+    }
+
 }
diff --git a/src/main/org/freemarker/docgen/Transform.java b/src/main/org/freemarker/docgen/Transform.java
index 3cb6591..da3fd69 100644
--- a/src/main/org/freemarker/docgen/Transform.java
+++ b/src/main/org/freemarker/docgen/Transform.java
@@ -32,6 +32,7 @@
 import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.io.Writer;
+import java.nio.charset.Charset;
 import java.text.Collator;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -78,6 +79,7 @@
  * <ul>
  *   <li>{@link #setSourceDirectory(File)}
  *   <li>{@link #setDestinationDirectory(File)}
+ *   <li>{@link #setOffline(Boolean)}, unless the configuration file specifies this
  * </ul>
  * 
  * <p>All files and directories in the source directory will be copied into the
@@ -333,6 +335,8 @@
     static final String DIR_TEMPLATES = "docgen-templates";
     
     static final String SETTING_VALIDATION = "validation";
+    static final String SETTING_OFFLINE = "offline";
+    static final String SETTING_ONLINE_TRACKER_HTML = "onlineTrackerHTML";
     static final String SETTING_INTERNAL_BOOKMARKS = "internalBookmarks";
     static final String SETTING_EXTERNAL_BOOKMARKS = "externalBookmarks";
     static final String SETTING_OLINKS = "olinks";
@@ -363,6 +367,10 @@
             = "maximumProgramlistingWidth";
     static final String SETTING_ECLIPSE_LINK_TO = "link_to";
 
+    private static final String VAR_OFFLINE
+            = SETTING_OFFLINE;
+    private static final String VAR_ONLINE_TRACKER_HTML
+            = SETTING_ONLINE_TRACKER_HTML;
     private static final String VAR_SHOW_EDITORAL_NOTES
             = "showEditoralNotes";
     private static final String VAR_TRANSFORM_START_TIME
@@ -399,6 +407,8 @@
     private static final String VAR_SHOW_NAVIGATION_BAR = "showNavigationBar";
     private static final String VAR_SHOW_BREADCRUMB = "showBreadCrumb";
 
+    private static final Charset UTF_8 = Charset.forName("UTF-8");    
+    
     private static final String PAGE_TYPE_DETAILED_TOC = "docgen:detailed_toc";
     
     // Docgen-specific XML attributes (added during DOM-tree postediting):
@@ -498,8 +508,12 @@
     private File destDir;
     
     private File srcDir;
-
+    
     private File contentDir;
+
+    private Boolean offline;
+    
+    private String onlineTrackerHTML;    
     
     /** Element types for which a new output file is created  */
     private DocumentStructureRank lowestFileElemenRank
@@ -705,6 +719,19 @@
                                     "Unknown validation option: " + name);
                         }
                     }
+                } else if (settingName.equals(SETTING_OFFLINE)) {
+                    if (offline == null) {  // Ignore if the caller has already set this
+                        offline = itIsABooleanSetting(cfgFile, settingName, settingValue);
+                    }
+                } else if (settingName.equals(SETTING_ONLINE_TRACKER_HTML)) {
+                    String onlineTrackerHtmlPath = itIsAStringSetting(cfgFile, settingName, settingValue);
+                    File f = new File(getSourceDirectory(), onlineTrackerHtmlPath);
+                    if (!f.exists()) {
+                        throw newCfgFileException(
+                                cfgFile, SETTING_ONLINE_TRACKER_HTML,
+                                "File not found: " + f.toPath());
+                    }
+                    onlineTrackerHTML = FileUtil.loadString(f, UTF_8);
                 } else if (settingName.equals(SETTING_ECLIPSE)) {
                     Map<String, Object> m = itIsAMapSetting(
                             cfgFile, SETTING_ECLIPSE, settingValue);
@@ -810,6 +837,11 @@
                             + Transform.class.getName() + ". Also, note that "
                             + "setting names are case-sensitive.)");
                 }
+            } // for each cfg settings
+            
+            if (offline == null) {
+                throw new DocgenException(
+                        "The \"offline\" setting wasn't specified; it must be set to true or false"); 
             }
         }
         
@@ -901,6 +933,10 @@
         try {
             // Settings:
             fmConfig.setSharedVariable(
+                    VAR_OFFLINE, offline);
+            fmConfig.setSharedVariable(
+                    VAR_ONLINE_TRACKER_HTML, onlineTrackerHTML);
+            fmConfig.setSharedVariable(
                     VAR_SHOW_EDITORAL_NOTES, showEditoralNotes);
             fmConfig.setSharedVariable(
                     VAR_SHOW_XXE_LOGO, showXXELogo);
@@ -2104,6 +2140,14 @@
     public void setSourceDirectory(File srcDir) {
         this.srcDir = srcDir;
     }
+    
+    public Boolean getOffline() {
+        return offline;
+    }
+
+    public void setOffline(Boolean offline) {
+        this.offline = offline;
+    }
 
     public boolean getShowEditoralNotes() {
         return showEditoralNotes;
diff --git a/src/main/org/freemarker/docgen/TransformAntTask.java b/src/main/org/freemarker/docgen/TransformAntTask.java
index cd3d17f..da55dc1 100644
--- a/src/main/org/freemarker/docgen/TransformAntTask.java
+++ b/src/main/org/freemarker/docgen/TransformAntTask.java
@@ -27,7 +27,7 @@
     &lt;/delete>
     
     <b>&lt;docgen:transform <!--
-           -->srcdir="<i>SRC_DIR</i>" destdir="<i>DEST_DIR</i>" /></b>
+           -->srcdir="<i>SRC_DIR</i>" destdir="<i>DEST_DIR</i>" offline="<i>OFFLINE</i>" /&gt;</b>
   &lt;/target>
 
 &lt;/project>
@@ -64,6 +64,14 @@
         transform.setGenerateEclipseToC(value);
     }
     
+    public Boolean getOffline() {
+        return transform.getOffline();
+    }
+
+    public void setOffline(Boolean offline) {
+        transform.setOffline(offline);
+    }
+    
     @Override
     public void execute() {
         try {
diff --git a/src/main/org/freemarker/docgen/templates/page.ftl b/src/main/org/freemarker/docgen/templates/page.ftl
index 1024a01..aa3c357 100644
--- a/src/main/org/freemarker/docgen/templates/page.ftl
+++ b/src/main/org/freemarker/docgen/templates/page.ftl
@@ -114,6 +114,9 @@
     <img src="docgen-resources/img/linktargetmarker.gif" alt="Here!" />
   </div>
 [/#if]
+[#if !offline && onlineTrackerHTML??]
+  ${onlineTrackerHTML}
+[/#if]
 </body>
 </html>