Build the single page version of the spec with the new script.
authorCameron McCormack <cam@mcc.id.au>
Fri, 14 Sep 2012 20:03:34 +1000
changeset 62 c7d24fd2e63b
parent 61 6f7ca5478368
child 63 fcfc39607c52
Build the single page version of the spec with the new script.
build.py
publish/publish.js
single-page.xsl
--- a/build.py	Tue Aug 21 17:29:06 2012 +1000
+++ b/build.py	Fri Sep 14 20:03:34 2012 +1000
@@ -178,11 +178,11 @@
       break
 
 if buildSinglePage:
-  chaptersNoIndex = " ".join(all[1:])
-  run("xsltproc --novalid --stringparam publish '" + publish_dir +
-      "' --stringparam chapters '" + chaptersNoIndex + "' " +
-      join(tools_dir, "single-page.xsl") + " " +
-      join(publish_dir, "index.html") + " > " + single_page)
+  os.chdir(master_dir)
+  run("node \"" +
+      native_path(join(tools_dir, "publish/publish.js")) +
+      "\" --build-single-page")
+  os.chdir(repo_dir) # chdir back
 
 # Copy over anything else that needs to be copied to 'publish':
 
--- a/publish/publish.js	Tue Aug 21 17:29:06 2012 +1000
+++ b/publish/publish.js	Fri Sep 14 20:03:34 2012 +1000
@@ -2,7 +2,8 @@
     namespaces = require('./namespaces.js'),
     utils = require('./utils.js'),
     processing = require('./processing.js'),
-    fs = require('fs');
+    fs = require('fs'),
+    DOMParser = require('xmldom').DOMParser;
 
 var conf;
 
@@ -13,6 +14,7 @@
   console.error('  --list-pages            Print the names of all pages of the specification.');
   console.error('  --build [PAGE ...]      Builds the specified pages, or all pages if');
   console.error('                            none specified.');
+  console.error('  --build-single-page     Builds the single page version of the specification.');
   console.error('  --local-style           Link to local W3C style sheets rather than w3.org.');
   console.error('  --help                  Show this message.');
 }
@@ -27,6 +29,9 @@
       case '--build':
         opts.build = true;
         break;
+      case '--build-single-page':
+        opts.buildSinglePage = true;
+        break;
       case '--local-style':
         opts.localStyle = true;
         break;
@@ -91,8 +96,102 @@
   fs.writeFileSync(outputFilename, doc.toString());
 }
 
+function buildSinglePage() {
+  var outputFilename = conf.outputDirectory + 'single-page.html';
+  var parser = new DOMParser();
+  parser.recordPositions = true;
+
+  // New document.
+  var doc = parser.parseFromString('<html><head></head><body></body></html>');
+  var head = doc.documentElement.firstChild;
+  var body = doc.documentElement.lastChild;
+
+  // Add HTML doctype.
+  doc.insertBefore(doc.implementation.createDocumentType("html", "", ""), doc.firstChild);
+
+  var foundMathjax = false;
+
+  // Add the contents of each published chapter.
+  for (var i = 0; i < conf.pageOrder.length; i++) {
+    var page = conf.pageOrder[i];
+    var pageDocument = utils.parseXHTML(conf.outputDirectory + page + '.html');
+    var pageHead = utils.child(pageDocument.documentElement, 'head');
+    var pageBody = utils.child(pageDocument.documentElement, 'body');
+    if (i == 0) {
+      // The <head> contents of the index are used for the single page document.
+      head.appendChild(utils.cloneChildren(pageHead));
+    } else {
+      // Separate each page.
+      body.appendChild(utils.parse('<hr class="chapter-divider"/>'));
+    }
+
+    utils.forEachNode(pageHead, function(n) {
+      // Copy a <style> in any chapter's <head> into the single page <head>.
+      if (n.nodeName == 'style') {
+        head.appendChild(n.cloneNode(true));
+      }
+      // Note if Mathjax was used anywhere.
+      if (n.nodeName == 'script' &&
+          n.getAttribute('src') == 'style/load-mathjax.js') {
+        foundMathjax = true;
+      }
+    });
+
+    // Clone the body of the chapter.
+    var clonedPageBody = utils.cloneChildren(pageBody);
+
+    // Fix up IDs so that they don't clash between chapters.
+    utils.forEachNode(clonedPageBody, function(n) {
+      if (n.nodeType == n.ELEMENT_NODE) {
+        if (n.hasAttribute('id')) {
+          n.setAttribute('id', page + '-' + n.getAttribute('id'));
+        }
+        if (n.nodeName == 'a') {
+          if (n.hasAttribute('name')) {
+            n.setAttribute('name', page + '-' + n.getAttribute('name'));
+          }
+          if (n.hasAttribute('href')) {
+            var href = n.getAttribute('href');
+            if (href[0] == '#') {
+              n.setAttribute('href', '#' + page + '-' + href.substring(1));
+            } else if (href.match(/^([^\/]+)\.html#(.*)$/)) {
+              n.setAttribute('href', '#' + RegExp.$1 + '-' + RegExp.$2);
+            } else if (href.match(/^([^\/]+)\.html$/)) {
+              n.setAttribute('href', '#chapter-' + RegExp.$1);
+            }
+          }
+        }
+      }
+    });
+
+    // Copy the chapter in.
+    body.appendChild(utils.parse('<div id="chapter-{{page}}" class="{{classes}}">{{pagebody}}</div>',
+                                 { page: page,
+                                   classes: pageBody.getAttribute('class'),
+                                   pagebody: clonedPageBody }));
+  }
+
+  // Remove all references to expanders.js.
+  utils.forEachNode(doc, function(n) {
+    if (n.nodeName == 'script' &&
+        n.getAttribute('src') == 'style/expanders.js') {
+      n.parentNode.removeChild(n);
+    }
+  });
+
+  // Add one reference to expanders.js at the end of the document.
+  body.appendChild(utils.parse('<script src="style/expanders.js"></script>'));
+
+  // Add reference to load-mathjax.js if we found one in any chapter.
+  if (foundMathjax) {
+    head.appendChild(utils.parse('<script src="style/load-mathjax.js"></script>'));
+  }
+
+  fs.writeFileSync(outputFilename, doc.toString());
+}
+
 var opts = parseOptions();
-if (opts.help || (!!opts.listPages + !!opts.build) != 1) {
+if (opts.help || (!!opts.listPages + !!opts.build + !!opts.buildSinglePage) != 1) {
   syntax();
   process.exit(opts.help ? 0 : 1);
 } else {
@@ -102,5 +201,7 @@
     listPages();
   } else if (opts.build) {
     buildPages(opts.rest);
+  } else if (opts.buildSinglePage) {
+    buildSinglePage();
   }
 }
--- a/single-page.xsl	Tue Aug 21 17:29:06 2012 +1000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,155 +0,0 @@
-<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
-                xmlns:h='http://www.w3.org/1999/xhtml'
-                xmlns='http://www.w3.org/1999/xhtml'
-                version='1.0'>
-
-  <xsl:output method='xml' encoding='UTF-8'
-              doctype-public='-//W3C//DTD XHTML 1.0 Transitional//EN'
-              doctype-system='http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'/>
-
-  <xsl:param name='chapters'/>
-  <xsl:param name='publish'/>
-
-  <xsl:template match='/h:html/h:body'>
-    <xsl:copy>
-      <xsl:apply-templates select="@*"/>
-      <xsl:apply-templates/>
-      <xsl:call-template name='do-chapters'>
-        <xsl:with-param name='cs' select='concat($chapters, " ")'/>
-      </xsl:call-template>
-      <script src='style/expanders.js'><xsl:text> </xsl:text></script>
-    </xsl:copy>
-  </xsl:template>
-
-  <xsl:template match='h:div[@class="header" or @class="footer" or @class="header top" or @class="header bottom"]'/>
-
-  <xsl:template match="h:head">
-    <xsl:copy>
-      <xsl:apply-templates select="@*"/>
-      <xsl:apply-templates select="*[not(self::script)]"/>
-      <xsl:call-template name='do-chapters-style'>
-        <xsl:with-param name='cs' select='concat($chapters, " ")'/>
-      </xsl:call-template>
-      <script>
-        var n, local = location.protocol == "file:";
-        if (local) {
-          for (n = document.head.firstChild; n; n = n.nextSibling) {
-            if (n.nodeName.toLowerCase() == "link") {
-              if (n.getAttribute("href").indexOf("//") == 0) {
-                n.href = "https:" + n.getAttribute("href");
-              }
-            }
-          }
-        }
-      </script>
-      <xsl:call-template name='do-chapters-script'>
-        <xsl:with-param name='cs' select='concat($chapters, " ")'/>
-      </xsl:call-template>
-    </xsl:copy>
-  </xsl:template>
-
-  <xsl:template match='@id'>
-    <xsl:param name='c'/>
-    <xsl:attribute name='id'>
-      <xsl:value-of select='concat($c, .)'/>
-    </xsl:attribute>
-  </xsl:template>
-
-  <xsl:template match='h:a/@name'>
-    <xsl:param name='c'/>
-    <xsl:attribute name='name'>
-      <xsl:value-of select='concat($c, .)'/>
-    </xsl:attribute>
-  </xsl:template>
-
-  <xsl:template match='h:a/@href'>
-    <xsl:param name='c'/>
-    <xsl:attribute name='href'>
-      <xsl:choose>
-        <xsl:when test='starts-with(., "#")'>
-          <xsl:value-of select='concat("#", $c, substring(., 2))'/>
-        </xsl:when>
-        <xsl:when test='substring-after(substring-before(., "#"), ".") = "html" and not(contains(., "/"))'>
-          <xsl:value-of select='concat("#", substring-before(., ".html"), "-", substring-after(., "#"))'/>
-        </xsl:when>
-        <xsl:when test='contains(concat(" ", $chapters, " "), concat(" ", substring-before(., ".html"), " "))'>
-          <xsl:value-of select='concat("#chapter-", substring-before(., ".html"))'/>
-        </xsl:when>
-        <xsl:when test='contains(concat(" ", $chapters, " "), concat(" ", ., " "))'>
-          <xsl:value-of select='concat("#chapter-", .)'/>
-        </xsl:when>
-        <xsl:otherwise>
-          <xsl:value-of select='.'/>
-        </xsl:otherwise>
-      </xsl:choose>
-    </xsl:attribute>
-  </xsl:template>
-
-  <xsl:template match='@aria-describedby'>
-    <xsl:param name='c'/>
-    <xsl:attribute name='aria-describedby'>
-      <xsl:value-of select='concat($c, .)'/>
-    </xsl:attribute>
-  </xsl:template>
-
-  <xsl:template match='h:script[@src="style/expanders.js"]'/>
-  <xsl:template match='h:script[@data-script-mathjax]'/>
-
-  <xsl:template match='node()|@*'>
-    <xsl:param name='c'/>
-    <xsl:copy>
-      <xsl:apply-templates select="@*">
-        <xsl:with-param name='c' select='$c'/>
-      </xsl:apply-templates>
-      <xsl:apply-templates>
-        <xsl:with-param name='c' select='$c'/>
-      </xsl:apply-templates>
-    </xsl:copy>
-  </xsl:template>
-
-  <xsl:template name='do-chapters'>
-    <xsl:param name='cs'/>
-    <xsl:variable name='c' select='concat(substring-before($cs, " "), "-")'/>
-    <xsl:if test='$c != "" and $c != "-"'>
-      <hr class='chapter-divider'/>
-      <xsl:text>&#xa;</xsl:text>
-      <div id='chapter-{substring($c, 1, string-length($c) - 1)}'>
-        <xsl:apply-templates select='document(concat($publish, "/", substring($c, 1, string-length($c) - 1), ".html"))/h:html/h:body/node()'>
-          <xsl:with-param name='c' select='$c'/>
-        </xsl:apply-templates>
-      </div>
-      <xsl:call-template name='do-chapters'>
-        <xsl:with-param name='cs' select='substring-after($cs, " ")'/>
-      </xsl:call-template>
-    </xsl:if>
-  </xsl:template>
-
-  <xsl:template name='do-chapters-style'>
-    <xsl:param name='cs'/>
-    <xsl:variable name='c' select='concat(substring-before($cs, " "), "-")'/>
-    <xsl:if test='$c != "" and $c != "-"'>
-      <xsl:apply-templates select='document(concat($publish, "/", substring($c, 1, string-length($c) - 1), ".html"))/h:html/h:head/h:style'/>
-      <xsl:call-template name='do-chapters-style'>
-        <xsl:with-param name='cs' select='substring-after($cs, " ")'/>
-      </xsl:call-template>
-    </xsl:if>
-  </xsl:template>
-
-  <xsl:template name='do-chapters-script'>
-    <xsl:param name='cs'/>
-    <xsl:variable name='c' select='concat(substring-before($cs, " "), "-")'/>
-    <xsl:if test='$c != "" and $c != "-"'>
-      <xsl:variable name="mathjax" select='document(concat($publish, "/", substring($c, 1, string-length($c) - 1), ".html"))/h:html/h:head/h:script[@data-script-mathjax]'/>
-      <xsl:choose>
-        <xsl:when test="count($mathjax) != 0">
-          <xsl:copy-of select="$mathjax[1]"/>
-        </xsl:when>
-        <xsl:otherwise>
-          <xsl:call-template name='do-chapters-script'>
-            <xsl:with-param name='cs' select='substring-after($cs, " ")'/>
-          </xsl:call-template>
-        </xsl:otherwise>
-      </xsl:choose>
-    </xsl:if>
-  </xsl:template>
-</xsl:stylesheet>