# Creating cross-version compatible documentation with Workbench

Wolfram Workbench allows you to create documentation for your own packages that is similar to the build-in documentation center. Integrating this type of documentation with the documentation center is possible starting from Mathematica 6, and hence I would like to create packages whose documentation nicely integrates with Mathematica 6, 7, 8, and 9.

However, Workbench doesn't support this out of the box. Its documentation tools seem to be a spin-off from WRI own tools to create version-specific documentation, so it is no wonder creating cross-version compatible docs in Workbench is nearly impossible.

So far, I've identified the following obstacles to creating cross-version compatible docs:

1. Documentation indices built with Mathematica <= 8 are not compatible with Mathematica 9, so docs built with <= 8 are not searchable in 9. See this workaround.
2. Documentation pages built with a given version of Mathematica always give the "This notebook was created in a more recent version of Mathematica" dialog warning in older versions. See this workaround.
3. The PacletInfo.m syntax has changed with Mathematica 9; it now requires a "Kernel" extension. This chokes the Mathematica 6 PacletManager, ruling out docs simultaneously compatible with 6 and 9. See this workaround.
4. Workbench 2 can only properly build docs with Mathematica 7 or 8. Mathematica 6 returns malformed notebooks (see this workaround by Simon Rochester), and 9 freezes halfway the build process for large packages.
5. Docs build with lower version look pretty bad on Mathematica 9. See this partial workaround.

Points 2 and 5 are by no means deal-breakers, but 1 in combination with 4 certainly is. I've managed to find work-arounds for most of these issues, which I've posted below.

If someone has identified similar issues or has better workarounds, I'm all ears!

-
Re: point 4...what are the problems that you are having generating documentation in Mma 6 with Workbench 2? When I started doing cross-version documentation, I had some issues running a doc build in version 6, but they were all resolved by resaving the templates and stylesheets in version 6, and making a couple of tweaks to the documentation tools palettes. If you like, I can describe what I did in an answer. –  Simon Rochester Jul 12 '13 at 9:08
@SimonRochester I was seeing some red boxes in the converted notebooks, which indeed could be due to missing templates / stylesheets. I'd appreciate it if you could write up the steps you took in an answer! –  Teake Nutma Jul 12 '13 at 10:50
Re: points 1 and 3, documentation searching and navigation seem to be working fine for me in M- 9 using the index built in M- 6, and omitting the Kernel extension in PacletInfo.m. Can you describe the problems I should be looking for? –  Simon Rochester Jul 19 '13 at 20:23
@SimonRochester The kernel extension is needed so that the ?symbol command resolves the ">>" link to the documentation on M9. But indeed searching M6 generated docs (including the index) seems to work fine on M9. However, I have it straight from the horse's mouth that the Paclet Manager in M9 was completely rewritten (from Java to C++) so docs (and specifically indices) built with earlier version cannot be expected to work reliably on M9. The other way around is guaranteed to work, as the paclet manager is forward compatible. –  Teake Nutma Jul 20 '13 at 9:16
Thanks, I see the issue with the ">>" link now. My next question is about the "Kernel" paclet extension in M- 6. My version of M- 6 (6.0.3, for 64bit Windows) seems to handle it fine, without any complaints. Is it a problem in earlier versions of M- 6, maybe, or does it depend on the OS? –  Simon Rochester Jul 21 '13 at 3:10

Even though documentation indices built with Mathematica 8 or older are not compatible with Mathematica 9, indices built with 9 are compatible with 8 or older. And even though Mathematica 9 can choke up converting documentation notebooks, it has no problems generating documentation indices.

So the workaround to points 1 and 4 in the question above is to first build the complete documentation (with or without indices) with Mathematica 7 or 8, and then build the indices with 9.

The indices can be build with the following Ant build file (adjust ProjectName where necessary):

<?xml version="1.0"?>
<project name="ProjectName" default="buildIndex" basedir=".">

<property name="language" value="English" />
<property name="dist" value="${basedir}/build" /> <property name="pacletContainer" value="ProjectName"/> <property name="docOutputDir" value="Documentation"/> <property name="outputDir" value="${dist}/${pacletContainer}/${docOutputDir}" />
value="${mathematicaInstallDir}/SystemFiles/Links/JLink"/> <property name="appPath" value="${appPath.default}"/>

<!-- Echo variables. -->
<echo message="MathKernel: ${mathExe}"/> <echo message="Mathematica Install Dir:${mathematicaInstallDir}"/>

<fileset dir="${jlinkpath}"> <include name="**/JLink.jar"/> <include name="**/SystemFiles/*"/> </fileset> </path> <taskdef name="mathematica" classname="com.wolfram.jlink.util.MathematicaTask" > <classpath refid="jlink.lib" /> </taskdef> <property name="JLinkLoaded" value="true"/> <!-- Build target. --> <target name="buildIndex"> <echo message="Mathematica" /> <mathematica exe="${mathExe}">
<![CDATA[

(* Mathematica code goes here *)

]]>
</mathematica>
</target>

</project>


The placeholder (* Mathematica code goes here *) has to be replaced with the following:

(* full messages *)
$MessagePrePrint = .; SetDirectory[AntProperty["basedir"]]; AntLog["_"]; (* Configure Front End *) If[ AntProperty["frontEndLaunchFlags"] =!= Null, SetOptions[ DeveloperInstallFrontEnd, DeveloperLaunchFlags -> AntProperty["frontEndLaunchFlags"] ] ]; (* Add apps to$Path*)
If[ !MemberQ[$Path, AntProperty["appPath"]], PrependTo[$Path, AntProperty["appPath"]]
];

outputDir   = AntProperty["outputDir"];
language    = AntProperty["language"];

Needs["DocumentationBuild"];

(* Create Index directory *)
indexDir = If[ Position[FileNameSplit[outputDir], language] === {},
ToFileName[{outputDir, language, "Index"}]
,
ToFileName[{
FileNameTake[
outputDir,
{1, Position[FileNameSplit[outputDir], language][[1, 1]]}
],
"Index"
}]
];
If[ FileType@indexDir === Directory,
AntLog["Deleting previous Index directory."];
DeleteDirectory[indexDir, DeleteContents -> True];
];
AntLog["Creating Index directory."];
CreateDirectory[indexDir];

If[ FileType@indexDir === Directory,
(* Open path to Indexer *)
Needs["DocumentationSearch"];
indexer = DocumentationSearchNewDocumentationNotebookIndexer[indexDir];
AntLog["Creation of NotebookIndexer Failed!"];
];
(* Create Spelling Index directory *)
indexSpellDir = ToFileName[{DirectoryName[indexDir]}, "SpellIndex"];
If[ FileType@indexSpellDir === Directory,
AntLog["Deleting previous Spelling Index directory."];
DeleteDirectory[indexSpellDir, DeleteContents -> True];
];
AntLog["Creating Spelling Index directory."];
CreateDirectory[indexSpellDir];
,
AntLog["Creation of Index directory Failed! No index will be created."];
Quit[];
];

(* Find all notebooks in the output dir. *)
notebooks = FileNames["*.nb", {outputDir}, Infinity];
AntLog["Found " <> ToString@Length@notebooks <> " notebooks."];

(* Index every notebook found. *)
IndexNotebook[notebook_] := Module[
{
plainText   = Import[notebook, {"NB", "Plaintext"}],
metaData    = DocumentationBuildUtilsGetSearchMetaDataList[notebook]
},
(* add text to index *)
DocumentationSearchAddDocumentationNotebook[
indexer,
plainText,
];
AntLog["Indexed notebook " <> FileNameTake[notebook] <> "."];
,
AntLog["Skipped notebook " <> FileNameTake[notebook] <> "."];
]
];
IndexNotebook /@ notebooks;

(* Close the indices. *)
AntLog["Closing Index"];
res = DocumentationSearchCloseDocumentationNotebookIndexer[indexer];
AntLog["Index closed. " <> ToString @ If[res =!= Null, res, " "] ];

AntLog["Creating Spelling Index"];
res = DocumentationSearchCreateSpellIndex[indexDir, indexSpellDir];
AntLog["Spelling Index closed. " <> ToString @ If[res =!= Null, res, " "] ];


Most of the above Mathematica code comes directly from the source of the documentation tools; they're just the parts that create the index.

-
do you have a screenshot of your result? –  Stefan Jul 9 '13 at 18:14
@Stefan Of what would you like a screenshot? A symbol reference page, a search result in the documentation center, or the output of an Information[symbol] query? –  Teake Nutma Jul 9 '13 at 18:19
hi. a symbol reference page...especially this 'more information' thing made me wild...i'd be glad if you solved that problem... –  Stefan Jul 9 '13 at 18:24
@Stefan Sorry, that problem I haven't solved. I had hoped it would be possible with conditional statements inside the Cell expression of "More Information", something of the form If[$Version < 9., "More Information", "Details and options"]. But AFAIK conditional statements inside text cells don't work. – Teake Nutma Jul 9 '13 at 18:34 anyways (+1) for your efforts...appreciate it really...it looks just so terrible and i don't have a clue (about everything) and especially that ant thing (any xml stuff) is complete greek to me – Stefan Jul 9 '13 at 18:43 This is a workaround for point 2 and 5 in the question, suppressing the "This notebook was created in a more recent version of Mathematica" dialog warnings and improving the rendering of the MORE INFORMATION button a bit. The idea here is to do an Ant search-and-replace after building the doc notebooks, replacing and removing the bits in the notebooks we don't want. Open the docbuild.xml file, and find these lines: <target name="main" depends="clean"> <antcall target="docbuild" /> </target>  Replace it with the following <target name="main" depends="clean"> <antcall target="docbuild" /> <antcall target="replaceStuff" /> </target> <target name="replaceStuff"> <!-- Remove CacheIDs --> <replace dir="${dist}" value=''>
<include name="**/*.nb"/>
<replacetoken>(*CacheID: 234*)</replacetoken>
</replace>
<!-- Change FrontEndVersion to 6.0 -->
<replaceregexp match='FrontEndVersion -> "(.*)"' replace='FrontEndVersion -> "6.0"' byline="true">
<fileset dir="${dist}" includes="**/*.nb"/> </replaceregexp> <replaceregexp match='FrontEndVersion->"(.*)"' replace='FrontEndVersion->"6.0"' byline="true"> <fileset dir="${dist}" includes="**/*.nb"/>
</replaceregexp>
<replace dir="${dist}" value='StyleBox[RowBox[{"MORE", " ", "INFORMATION"}], "NotesFrameText"]'> <include name="**/*.nb"/> <replacetoken><![CDATA[FrameBox[ StyleBox[ RowBox[{"MORE", " ", "INFORMATION"}], "NotesFrameText"], StripOnInput->False]]]></replacetoken> </replace> </target>  Be careful with the replacetoken which has the <!CDATA ... ]]> code: it is sensitive to the spaces after newlines. There should be 3 spaces before StyleBox[, 4 spaces before RowBox[, and 3 spaces before StripOnInput. It would be better to use regular expressions here, but unfortunately those are not my strong point. Also note that I've tested this with docs that are build with Mathematica 7; for other versions you might need to adjust the replace tokens a bit. Here's how the MORE INFORMATION looks without the FrameBox on Mathematica 9: And here's how it looks on Mathematica 8: - This is a workaround for point 3 in the question, making PacletInfo.m files for Mathematica 9 also work on 6. Due to a change in the PacletManager in Mathematica 9, PacletInfo files need an additional line in order for PacletManager to parse them completely. Here's an example: Paclet[ Name -> "xTras", Version -> "1.1.3", MathematicaVersion -> "6+", Description -> "Field theory inspired additions to xAct", Creator -> "Teake Nutma", Extensions -> { (* The "Kernel" extension is new for Mathematica 9 *) { "Kernel", Context -> {"xTras"} }, { "Documentation", Resources -> {"Guides/xTras"}, Language -> "English", MainPage -> "Guides/xTras", LinkBase -> "xTras" } } ]  Unfortunately, the "Kernel" extension chokes the PacletManager in Mathematica 6 ( 7 and 8 are unaffected): There's a simple workaround for this, but it's not pretty. The idea is to temporarily replace the PacletInfo.m file of our package, rebuild the paclet data, and afterwards restore the PacletInfo.m file. Here's how: (* Adjust this when necessary. *) packageName = "PackageName"; If[System$VersionNumber < 7. && System$VersionNumber > 5.9, (* First, get the location of the pacletinfo.m file. *) pacletFile = First@FileNames[ ToFileName[{packageName},"PacletInfo.m"],$Path
];
(* Read in the pacletinfo file. *)
paclet      = Get[pacletFile];
(* Remove the kernel extension. *)
pacletNew   = paclet /. {"Kernel", ___} -> Sequence[];
(* Write out the new package info file. *)
Put[pacletNew, pacletFile];

(* Rebuild the paclet data. *)
PacletManagerRebuildPacletData[];

(* Restore the old pacletinfo.m file. *)
Put[paclet, pacletFile];
]


This code could for example go in the Kernel/init.m file of your package.

-
That is quit complicated. What does the Kernel extension even do? –  Liam William Oct 15 at 5:34

Here's what I did in Workbench 2 to allow documentation editing and building using Mma 6. It's basically a fix for the version 6 half of Point 4 in the question.

We need to work with files in the DocumentationTools folder -- in my installation the folder is located at C:\Users\Simon\.eclipse\1678130227\configuration\org.eclipse.osgi\bundles\10\1\.cp\MathematicaSource\DocumentationTools\

Needless to say, before editing or resaving any of these files, make a copy of the original under a different name, so that it can be restored later if necessary.

First we resave stylesheets and templates in version 6. Open each of the following files in Mma 6, click ok in the version warning dialog, and then save:

• All of the files in DocumentationTools\FrontEnd\StyleSheets\Wolfram\
• All of the files of the form *BaseTemplateExt.nb in DocumentationTools\FrontEnd\TextResources\
• All of the files in DocumentationTools\Examples\

The package file DocumentationTools\DocumentationTools.m uses the function StringTrim, which was new in version 7. Also, it uses the option TooltipDelay, which in version 6 was known as ActionDelay. To add these definitions, open DocumentationTools.m, and after the line reading Begin["Private"] (around line 738), add the following:

If[$VersionNumber<7., StringTrim[x_String]:=StringReplace[x,{StartOfString~~Whitespace->"",Whitespace~~EndOfString->""}]; TooltipDelay=ActionDelay; ];  Finally, we need to regenerate the DocumentationTools palettes for version 6: In DocumentationTools\FrontEnd\Palettes\, save the original palette file DocumentationTools.nb under a different name, and do the same for SpecialForms.nb in DocumentationTools\FrontEnd\TextResources\. Then open DocumentationTools\DocumentationToolsPalSource.nb in Mma 6, and do a find and replace, replacing TooltipDelay with ActionDelay. Then execute this notebook, which will regenerate the DocumentationTools.nb and SpecialForms.nb palettes. Technically, ActionDelay isn't supported past version 6, but it actually seems to work fine in versions 7,8, and 9, so we can use these same regenerated palettes in all versions. That's it. That lets me create all the documentation with version 6...it all then works perfectly in versions 7 and 8. That's why it was so shocking for me to open things up in version 9 and see how badly it had broken... The only thing I've found that doesn't work right in version 6 is the "Annotate" button on the DocumentationTools palette...I can work on that if anybody cares. I'm not sure about some of the more complicated utilities, like "Generate Function Pages" and "Fix Links to Symbols", since I never use them. Please comment if you notice any other issues using this technique. - Update 4: The code in this answer has been incorporated into @jkuczm's WWBCommon package. I encourage everyone creating cross-version documentation in Workbench to use it. This is a workaround for Point 5 in the question, allowing cross-version documentation to be built that fixes the layout and text problems in version 9 (and 10), while still displaying correctly in versions 6--8. Update 3: I uncovered a rendering bug on Windows when viewing the doc pages at different magnification settings, which was also reported on OS X. The new version of the code implements a workaround for the bug (see the Notes section, below). There still hasn't been any testing on Linux. Feedback is appreciated. Using this technique when generating documentation in Mathematica 6 (with the method described in my other answer), along with Teake's techniques of generating the index in Mathematica 9 and writing a cross-version PacletInfo.m, seems to completely solve the cross-version documentation problem. ## Background: I was inspired by Teake's comment that he'd like to do something like If[$Version < 9., "More Information", "Details and options"] if it were possible. It turns out that it's not possible to include functions in cell contents, but functions can be included in cell options, as mentioned in the documentation here (bottom of the page).

To control the display of content in a cell using options, we can use the option CellFrameLabels to effectively embed another cell into first cell. For example, to emulate a text cell with cell expression

Cell["hello world", "Text"]


we can create a cell that has cell expression

Cell["", "Text", CellFrameLabels->{{Cell["hello world","Text"],None},{None,None}}]


(To edit the cell expression for a cell, put the cursor in the cell and choose "Show Expression" from the Cell menu. Then edit, and choose "Show Expression" again to toggle back to the display form.)

Functions used in the cell expression are evaluated in the Front End. That means, unless we use Dynamic (not recommended for evaluation overhead reasons), we are limited to Front End functions defined in the context FEPrivate. To get a (partial) list of these functions, we can evaluate Names["FEPrivate*"]. Some of the functions apparently don't show up in the list until after you have already evaluated them, however, adding some mystery to the process of finding these functions.

To see the result of a Front End function call returned directly as output, you can make a call to the Front End from a regular Input cell using wrapper functions, like so:

MathLinkCallFrontEndHeld[FrontEndValue[FEPrivate$ProductVersion]]  "9.0"  As an example of a text cell with conditional text, we can use the Front End functions FEPrivateSwitch and FEPrivate$ProductVersion to create a text cell that displays the version of Mathematica in which it is viewed:

Cell["", "Text",CellFrameLabels->{{
FEPrivateSwitch[
FEPrivate$ProductVersion, "6.0",Cell["6.0","Text"], "7.0",Cell["7.0","Text"], "8.0",Cell["8.0","Text"], "9.0",Cell["9.0","Text"], True,Cell["Unknown","Text"] ], None},{None,None}}]  Now we can apply this to fixing the documentation layout problems in version 9. The problems I want to fix are • Change boxed "MORE INFORMATION" to "Details and Options" or "Details", as appropriate • Change other section headings from ALL CAPS to Title Case in Symbol, Tutorial, and Guide pages • Fix spacing before and after section headings • Style the Tutorial section heading in Guide pages to match other section headings • Style the tutorial links on Guide pages to match links in other sections • As a bonus, put the count of the total number of examples in each Symbol page after the Examples section heading, as has been added in version 9 • Update 2: Get proper opener behavior for the section header cells, where clicking anywhere in the cell opens/closes the section This is accomplished using Mathematica code in the docbuild.xml file to read in each doc nb file (after all doc files have been built), make the necessary replacements, and save it again. ## The Code (update 3): Add somewhere to the docbuild.xml file (for example, near the end, just before the line reading </project>): <target name="main" depends="clean"> <antcall target="docbuild" /> </target> <target name="crossVersionReplacements"> <mathematica exe="${mathExe}">
<![CDATA[

(* Start Front End *)
DeveloperInstallFrontEnd[];

(* Get all .nb files from build directory *)
outputDir = AntProperty["dist"];
files = FileNames["*.nb", outputDir, Infinity];

AntLog["Running cross-version replacements on "<>ToString@Length@files<>" files in "<>outputDir];

(* Function to discriminate between versions 6--8 and 9 *)
IfPreVer9[pre_, post_] := FEPrivateIf[
FEPrivateOr[FEPrivateSameQ[FEPrivate$ProductVersion, "6.0"], FEPrivateSameQ[FEPrivate$ProductVersion, "7.0"],
FEPrivateSameQ[FEPrivate\$ProductVersion, "8.0"]],
pre, post];

(* Section spacer that displays as very thin cell in versions 6--8 *)
mySectionSpacer[sty_] = Cell["", sty,
CellSize -> IfPreVer9[{Inherited, 1}, Inherited],
CellElementSpacings -> {CellMinHeight ->
IfPreVer9[1, Inherited]},
CellMargins -> IfPreVer9[0, Inherited],
Editable -> False, Selectable -> False, Deletable -> False,
ShowCellBracket -> False, ShowSelection -> False];

(* List of styles for section headings that we will do replacements on *)
"MoreInformationSection",
"PrimaryExamplesSection", "RelatedDemonstrationsSection",
"TutorialsSection",
};

(* Utility functions*)
myspacer = Cell[BoxData[ToBoxes[Spacer[24]]]];

toTitleCase[str_String] :=
StringReplace[ToLowerCase[str],
WordBoundary ~~ x_ :> ToUpperCase[x]];

framelabelopts = {CellSize -> {5000, Inherited}};

cellopts = {"WholeCellGroupOpener" -> True,
CellFrameLabelMargins -> 0,
CellElementSpacings -> {"CellMinHeight" -> 3},
CellSize -> {Inherited, IfPreVer9[11, 14]}};

(* Loop over files *)
Do[
(* Importing nb files containing Manipulate objects can throw a bunch of benign newline interpretation warnings *)
Quiet[expr = Get[file];, Syntax::newl];

(* Convert section headings to Title Case in version 9 *)
expr = expr /. {
Cell[con_, sty : "NotesSection", o___] :>
Cell["", sty, Sequence @@ cellopts, o, CellFrameLabels -> {{
IfPreVer9[
Cell[con, sty, Sequence @@ framelabelopts],
con /. TextData[Cell[BoxData[box : ButtonBox[__]]]] :> TextData[box] /.
box_FrameBox ->
Cell[TextData[{myspacer, "Details and Options"}],
"NotesSection", Sequence @@ framelabelopts]
], None}, {None, None}}],
(*Guide Tutorials section-- need to mimic standard version 9 section style*)
Cell[con_, sty : "GuideTutorialsSection", o___] :>
Cell["", sty, Sequence @@ cellopts, o,
CellMargins ->
IfPreVer9[Inherited, {{Inherited, Inherited}, {Inherited, 20}}],
CellFrameLabels -> {{
IfPreVer9[
Cell[con, sty, Sequence @@ framelabelopts],
con /. str_String ->
Cell[TextData[{myspacer, toTitleCase[str]}], sty,
Sequence @@ framelabelopts]
], None}, {None, None}}],
(*Examples section-- add placeholder for total example count*)
Cell[con_, sty : "PrimaryExamplesSection", o___] :>
Cell["", sty, o, Sequence @@ cellopts, CellFrameLabels -> {{
IfPreVer9[
Cell[con, sty, Sequence @@ framelabelopts],
con /. (ButtonBox[str_String, bbo___] :>
ButtonBox[
Cell[TextData[{myspacer, toTitleCase[str], "  ",
"InsertExampleCount"}], sty,
Sequence @@ framelabelopts], bbo])
], None}, {None, None}}],
(*All other section headings with text only in the title*)
Cell[con_String, sty : Alternatives @@ headingStyles, o___] :>
Cell["", sty, Sequence @@ cellopts, o, CellFrameLabels -> {{
IfPreVer9[
Cell[con, sty, Sequence @@ framelabelopts],
Cell[TextData[{myspacer, toTitleCase[con]}], sty,
Sequence @@ framelabelopts]
], None}, {None, None}}],
(*All other section headings with buttons in the title*)
Cell[con_, sty : Alternatives @@ headingStyles, o___] :>
Cell["", sty, Sequence @@ cellopts, o, CellFrameLabels -> {{
IfPreVer9[
Cell[con, sty, Sequence @@ framelabelopts],
con /. (ButtonBox[str_String, bbo___] :>
ButtonBox[
Cell[TextData[{myspacer, toTitleCase[str]}], sty,
Sequence @@ framelabelopts], bbo])
], None}, {None, None}}]
};

(* Replace "Details and Options" with "Details" if there is no mention of options in the notes section *)
expr = expr /.
notescell : CellGroupData[{Cell["", "NotesSection", ___], ___}, ___] :>
(notescell /. If[
Count[notescell, str_String /; ! StringFreeQ[str, {"option", "Option"}], Infinity] > 1,
{},
"Details and Options" -> "Details"
]);

(* Add total example count to Examples section heading in version 9 *)
expr = expr /. (examplegroup : Cell[CellGroupData[{Cell[_, "PrimaryExamplesSection", ___], ___}, ___], ___]) :>
(examplegroup /. "InsertExampleCount" ->
Cell["(" <>
ToString[Total@Cases[examplegroup, Cell[countstring_, "ExampleCount"] :> ToExpression[countstring], Infinity]] <>
")", "ExampleCount"]
);

(* Add spacers before and after section content in version 9 *)
expr = expr /. {Cell[
CellGroupData[{c1 : Cell[_, "SeeAlsoSection", ___], c2__}, o2___], o3___] :>
Cell[CellGroupData[{c1, c2}, o2], o3],
Cell[CellGroupData[{c1 : Cell[_, "PrimaryExamplesSection", ___], c2__}, o2___], o3___] :>
Cell[CellGroupData[{c1, c2, mySectionSpacer["SectionFooterSpacer"]}, o2], o3],
Cell[CellGroupData[{c1 : Cell[_, Alternatives @@ headingStyles, ___], c2__}, o2___], o3___] :>
Cell[CellGroupData[{c1, mySectionSpacer["SectionHeaderSpacer"], c2, mySectionSpacer["SectionFooterSpacer"]}, o2], o3]};

(*Fix button behavior in Example subsections*)
expr = expr /.
Cell[con__, sty : "ExampleSection" | "ExampleSubsection", o___] :>
Cell[con, sty, "WholeCellGroupOpener" -> True, o];

(*Fix rendering bug pre-version 9 *)
expr = expr /.
Cell[c_, "GuideAbstract", o___] :>
Cell[c, "GuideAbstract", CellFrame -> IfPreVer9[{{0, 0}, {1, 0}}, Inherited], o];

(* Improve font appearance for tutorial links in Guide pages, and mimic section heading style for Guide Tutorial heading *)

(* Save notebook using Front End *)
DeveloperUseFrontEnd[NotebookSave[NotebookPut[expr], file]],

{file,files}];
]]>
</mathematica>
</target>


Then, at the end of (but inside) the "docbuild" target, just after the line reading

<antcall target="setupPaclet" />


<antcall target="crossVersionReplacements" />


This will call the replacement routine each time a doc build is done, and produce the cross-version files.

## Some Notes:

• I couldn't call the "crossVersionReplacements" target from the "main" target, because the "docbuild" target has already loaded the J/Link library, and Ant doesn't want to load it a second time. Probably there's some other way around this, but I just called "crossVersionReplacements" from inside "docbuild", to piggyback on the already-loaded library.

• The "Details and Options" section heading should just read "Details" if there are no options for the function listed. To accomplish that, I search for the substring "option" or "Option" in the Details section, and replace the heading if no instances are found (other than in the heading itself).

• WRI added spacer cells at the beginning and ends of sections in the version 9 documentation, to obtain spacing that disappears when the sections are closed. To get a cross-version version of this, I added spacer cells in the appropriate places that display as very thin cells in pre-version 9 Front Ends.

• The Guide pages have been totally redesigned in version 9, so there's no way to match them exactly. In particular, the Tutorials heading and links have been completely restyled. I tried to give things a consistent look by undoing these changes, to match the Tutorials style with the style of the other sections. Update: I realized I could do this a little more simply by swapping out the cell style in the Tutorials heading for another section heading style.

• I encountered a rendering bug on the pre-version 9 Guide pages, affecting the layout on the whole page, which I found was triggered by the use of a non-integer value for a frame specification in the Abstract text. I adjusted the value to avoid the bug.

• To get the total number of examples on a symbol page, I added together the existing example counts for all of the example subsections.

• I learned some interesting things along the way, such as that the box representation of a Spacer is given by

ToBoxes@Spacer[24]

   InterpretationBox[StyleBox[GraphicsBox[{}, ImageSize -> {24, 0},
BaselinePosition -> Baseline], "CacheGraphics" -> False], Spacer[24]]

• Update 3: In testing on Windows, I found that when a magnification level of less than 100% is chosen for the documentation notebook, the cell frame label is reduced by a different amount than the enclosing cell, causing layout errors. These are the same glitches as Teake reported seeing in OS X. There isn't much we can do about the different magnification factors. (Mathematica's magnification functionality has always been a bit of a crapshoot.) However, a workaround is to increase the width of the frame label cell, so as to run the rendering glitch right off of the page. This is implemented in the updated code. Please let me know if you encounter any other issues.

-
Very nice, big +1 for finding the FEPrivate constructs. It works perfectly on OS X, except for one minor visual glitch on MM 6-7-8 and a bit of extra whitespace on MM 9. –  Teake Nutma Jul 16 '13 at 9:48
Thanks. Actually, that glitch looks a lot like the rendering bug that I found a workaround for on Windows, and I also encountered a similar extra whitespace problem that I managed to avoid. So maybe when I get to play with it on a mac I'll be able to fix those up somehow. –  Simon Rochester Jul 16 '13 at 10:43
It seems the FEPrivate functions are only evaluated in cell options, which is probably why you resorted to putting them in CellFrameLabels. For me this is one hack too far, so I'm hoping there a way to do this directly in the cell contents. –  Teake Nutma Jul 16 '13 at 18:57
I guess everyone has their limits :). I'm coming from doing web design in the '90s, when no hack was too far for cross-browser compatibility. I'm getting near-pixel-perfect rendering with it on Windows, so if I can replicate that on OS X and Linux I'll be pretty happy with it. The alternative is probably multiple package versions or an install script that automatically selects the correct version of the documentation. If/when Workbench 3 comes out, that will probably be the most attractive option anyway, since I'm sure they'll jettison support for pre-Mma 9 documentation. –  Simon Rochester Jul 16 '13 at 21:11
I just tried update 2, but unfortunately the same visual problems persist (both M678 and M9 glitches). FWIW, my M9 version is 9.0.1, and I build the docs with 8.0.4. –  Teake Nutma Jul 25 '13 at 10:09

In addition to the 5 points in the question, in Mathematica 9 the "MORE ABOUT" section is renamed to "Related Guides".

For the appearance below the "See Also" section I have added a simple code to the solution of @SimonRochester.

(*More About section-- renamed to Related Guides*)
Cell[con_, sty : "MoreAboutSection", o___] :>
Cell["", sty, Sequence @@ cellopts, o, CellFrameLabels -> {{
IfPreVer9[
Cell[con, sty, Sequence @@ framelabelopts],
con /. str_String ->
Cell[TextData[{myspacer, "Related Guides"}], sty,
Sequence @@ framelabelopts]
], None}, {None, None}}],


These lines have to be added before (*Guide Tutorials section-- need to mimic standard version 9 section style*) in his solution.

There is another appearance at the top (in the navigation link) where it says "More About >>" next to "Url >>", but I don't have a solution for this.

-