The challenge: I needed to build internal and external versions of Help from the same source. Putting internal-only information into the Help project itself lets me single-source content and lets staff search across all relevant information, without jumping between sites. Putting pre-release content in the internal Help also makes it possible for reviewers and affected groups to read about and respond to enhancements that are still in development, without doing branch-specific builds and publishing.
However, conditional inclusion of files and content is not supported out of the box by Sandcastle, my authoring tool.
Why Sandcastle? For my new job, I inherited a C# API documentation project that is authored natively in Sandcastle. Although Eric Woodruff has done a magnificent job of maintaining and enhancing the project, Sandcastle remains a lower-level tool than I have ever before used as my primary authoring interface. All conceptual content in Sandcastle is written in raw MAML (.aml) and any reused content must be externalized as named items in XML (.token) files. No external HTML source files can be merged in (only included and linked), so content options are limited to these native topic and token formats.
The process I'll describe here works with the native capabilities of Sandcastle to achieve a rudimentary form of conditional builds — enough to support external builds that are a subset of the content of internal builds, all from shared files.
My discovery: This "conditional publishing" leverages three behaviors that I discovered through testing:
- When the compiler cannot find a token being referenced, it does not fail the build; it just logs a warning.
- When the compiler cannot find a token being referenced, it does not put garbage messaging in the help output (such as "Token not found.").
- The Sandcastle Help File Builder tolerates having multiple projects (*.shfb, *.content) cohabiting the same space (folder), which many documentation tools do not.
So, I can selectively hide content in internal-only files that I don't let my public project see, without causing errors or ugliness in that public Help. In a nutshell, I only need to clone the critical files and have the internal project use those new files.
Which files to create
These are the critical files:
- *.shfbproj (XML project file, with all settings and lists of files that can be used in builds)
- *.content (XML map for which topic files to include in Help, with what naming and order)
- *.tokens (XML collection of named items -- tokens -- for reusable content)
If A is your public Help and B is your internal Help, these are your new files:
- B.shfbproj ― the internal help project file, which references the new "B.*" files
- B.content ― organizes topics that appear in internal Help only
Note: To stack multiple content files, set the <SortOrder> properties to get B at the top. - B.tokens ― holds blurbs that appear in internal Help only
- B.aml ― introduces the internal topics and lists upcoming/internal enhancements
Tip: Set it to be the default topic for B.content.
How to add a new topic for internal Help
- While working in the B project, create the new topic (.aml) file.
- Position it where you want in B.content.
- Build both internal and external Help, verifying that the topic appears only in the former.
How to add a new snippet for internal Help
- In B.tokens (which I like to edit in Notepad++ with XML Tools), add your new token.
- In all of the topic (*.aml) files that need it, insert the new token (<token>Foo</token>).
- Build both internal and external Help, verifying that the snippet appears only in the former.
How to move internal content into public Help
As soon as a new enhancement is ready for release, a few quick edits can move that content into production:
- For tokens, just cut the item from B.tokens and paste it into A.tokens.
- For topics, just delete the topic from B.content and add it into A.content.
- For New Features, just cut the release section from B.aml and paste it into New_Features.aml.
A final bit of bulletproofing
To avoid excessive editing of the project files (the most fragile!!), I added a final bit of automation:
- B.bat ― the batch file that regenerates B.shfbproj and rebuilds & posts to the intranet
- B.py ― the Python script it calls to swap the key values in the regenerated project
The trick to keeping this dead simple was to add two empty files to A.shfbproj as placeholder token and content files, so that the batch file and script can just clone A to B and substitute "B" for "empty":
- text = re.sub(r'empty.tokens', 'B.tokens', text)
- text = re.sub(r'empty.content', 'B.content', text)
- text = re.sub(r'developersupport', 'mary.connor', text) [to direct Feedback emails to me]
By regenerating B.shfbproj each time, it can never get out of sync with new content in A. Now I can sleep better. :-)