Linking to 3rd party online docs

Feb 14, 2008 at 3:59 PM
I am trying to link from within my XML doc comments to 3rd party online docs without having to hard code the URL in my source code.
This means, I am not including these docs as "additional content" (see SHFB GUI), which would mean including the physical files.

So far I have not found a way to configure this with the SHFB GUI.

My first approach was to simply do it by including an entity reference (to be expanded later) in my doc comments, and then manually inserting
XML entity declarations in the XML file generated by the C# compiler. Unfortunately, the C# compiler refuses to process such comments as it expects the entities to have been declared already, which - of course - cannot be done in the doc comments.

So my question is, has anyone dealt with - and solved - a similar problem already, preferably within SHFB?
I am afraid I might have to write a SHFB plugin or console program to post-process the XML files after the compiler creates them.

Karl
Feb 14, 2008 at 5:18 PM
Hi Karl,

I'm not sure about SHFB in particular, but I don't think Sandcastle provides support for converting entity references to third-party hyperlinks. To MSDN hyperlinks, yes, via ResolveReferenceLinksComponent2.

It sounds like what you need is a ResolveExternalLinksComponent, which could read the mappings for custom IDs to hyperlinks from an XML file. But you'd have to create the mappings manually, so I'm not sure how useful that would be. (Note that the IDs would not be entity references, since the compiler can't resolve them - as you've discovered). If you're interested then I'll consider writing the component.

- Dave
Feb 14, 2008 at 7:43 PM
Hi Dave,

Thanks for your interest in my problem.


It sounds like what you need is a ResolveExternalLinksComponent, which could read the mappings for custom IDs to hyperlinks from an XML file. But you'd have to create the mappings manually, so I'm not sure how useful that would be. (Note that the IDs would not be entity references, since the compiler can't resolve them - as you've discovered). If you're interested then I'll consider writing the component.


Hi Dave,

what I had in mind originally was something like writing a <see> tag:
<see href="&external_root;/docs/item.html">External Item</see>
where the entity reference &external_root; is replaced later by some base URL.

This would assume that the overall doc structure on the external link is fixed, but then you get away with defining just one general entity.

Another option I though about was (as entity references don't work), to let the programmer define his own syntax, e.g.:
<see href="@external_root;/docs/item.html">External Item</see>
where "@external_root;" is converted to "&external_root;" through some regex processing later.
Obviously that is not quite as simple, since the syntax should be such that false positives are avoided and repeated processing should not have an effect - the post-processing should be "idempotent", and it all has to be well-formed XML.

So, how would you do this using ID attributes?

Karl
Feb 14, 2008 at 7:43 PM
Edited Feb 14, 2008 at 8:08 PM
Duplicate post - removed.
Feb 14, 2008 at 8:43 PM
Hi Karl,


<see href="&external_root;/docs/item.html">External Item</see>
This would assume that the overall doc structure on the external link is fixed, but then you get away with defining just one general entity.

Interesting idea. I had in mind something like the following:

<see xref="example.docs.item">External Item</see>

with the component's inner XML configuration as something like this:

<component type="ResolveExternalLinksComponent" assembly="..">
  <mappings>
     <url id="example.docs.item" href="http://example.com/docs/item.html" />
  </mappings>
</component>
but I wonder if the two ideas could be mixed:

<component type="ResolveExternalLinksComponent" assembly="..">
  <mappings>
    <url id="example.docs.item" href="http://example.com/docs/item.html" />
    <baseurl id="example" href="http://example.com" />
  </mappings>
</component>
so that you could use the following two <see> elements to point to the same URL:

<summary>
<see xref="example.docs.item">External Item</see>
<see vref="{example}/docs/item.html">External Item</see>
</summary>
The x in xref indicates an eXternal URL mapping.
The v in vref indicates a Virtual URL, using {example} as the ID to look up its base.

Personally, I like the xref approach better since URLs don't have to be known while authoring content. The component's editor could provide a way to manage the list of URLs and the IDs to which they map, making configuration really simple. Also, you could quickly make a change to the URL for any particular ID if it needs to be updated globally.

- Dave
Feb 15, 2008 at 2:51 AM
Edited Feb 15, 2008 at 2:53 AM
Sorry for this - but it seems when using FireFox, messages get posted twice. See below.
Feb 15, 2008 at 2:51 AM
Hi Dave,

davedev wrote:
but I wonder if the two ideas could be mixed:
<component type="ResolveExternalLinksComponent" assembly="..">
  <mappings>
    <url id="example.docs.item" href="http://example.com/docs/item.html" />
    <baseurl id="example" href="http://example.com" />
  </mappings>
</component>
so that you could use the following two <see> elements to point to the same URL:

<summary>
<see xref="example.docs.item">External Item</see>
<see vref="{example}/docs/item.html">External Item</see>
</summary>
The x in xref indicates an eXternal URL mapping.
The v in vref indicates a Virtual URL, using {example} as the ID to look up its base.

Personally, I like the xref approach better since URLs don't have to be known while authoring content. The component's editor could provide a way to manage the list of URLs and the IDs to which they map, making configuration really simple. Also, you could quickly make a change to the URL for any particular ID if it needs to be updated globally.

Yes, I like the xref approach as well, it bothers me though, that it requires more work. Maybe some work could be saved through defaulting. We could for instance use the xref value (the id) itself as the href value that is appended to the base URI. We could also define a global base URL, and maybe individual group base URLs. Example:
Let's say we have
<summary>
<see xref="/docs/regularitem.html">Regular Item</see>
<see xref="/docs/specialitem.html">Special Item</see>
<see xref="/docs/uniqueitem.html">Unique Item</see>
</summary>
and we want as default a base URI of http://example.com - it should apply to the "Regular Item". We want as a special case use the base URI http://special.com for "Special Item" and as an extra special case we want to map "Unique Item" to an html file that does not correspond to its xref/id value:
<component type="ResolveExternalLinksComponent" baseURI="http://example.com" "assembly="..">
  <mappings baseURI="http://special.com">
    <url id="/docs/specialitem.html" />
    . . .
  </mappings>
  <mappings baseURI="http://unique.com">
    <url id="/docs/uniqueitem.html" href="/docs/uniqueitem_V2.0.html" />
    . . .
  </mappings>
</component>
This would give us the URI values
    • http://example.com/docs/regularitem.html - all defaulted
    • http://special.com/docs/specialitem.html - base URI specified, the rest defaulted
    • http://unique.com/docs/uniqueitem_V2.0.html - complete href value specified

Through this approach, the xref values are still not restricted in generality, but the defaulting rules give them a special interpretation through which work can be saved.

Karl
Feb 15, 2008 at 6:44 AM
Hi Karl,


Yes, I like the xref approach as well, it bothers me though, that it requires more work. Maybe some work could be saved through defaulting. We could for instance use the xref value (the id) itself as the href value that is appended to the base URI.

I don't like the idea of having the ID as the virtual URL itself. I understand why you think that managing an ID for each URL would be more work, but I think overall it would be less work in actuality, especially with a GUI to help configure mappings.

For example, if you decided to use a pattern for IDs, such as setting them to the namespace and type name of third-party controls, then typing <see xref="ThirdPartyLib.CustomControl"/> while authoring documentation would be quite easy and intuitive; more so than having to hard-code a long URL each time it's needed throughout all of your documentation (imagine a URL with a cryptic path and query string).

And again, if the URL ever changes (which you have no control over since it's a third-party website) then you only need to change the URL mapping instead of every reference in every topic.


and we want as default a base URI of http://example.com - it should apply to the "Regular Item". We want as a special case use the base URI http://special.com for "Special Item" and as an extra special case we want to map "Unique Item" to an html file that does not correspond to its xref/id value

This would save the trouble of having to type http://domain.com each time, which IMO isn't all that difficult anyway if you're already hard-coding the rest of a potentially long URL.

But I think it's uncommon that the same virtual path would apply to different base URLs. So if the ID is the URL itself then segregating mappings by base paths would never be required. The component's only purpose would be to slap on a base path to virtual URLs hard-coded in the documentation. I think we can get more value out of a component that resolves external links.

Without mixing ID and URL semantics, grouping by base paths just to reduce the amount of redundancy in the mappings themselves might be useful though:

<component type="ResolveExternalLinksComponent" "assembly="..">
  <mappings base="http://special.com">
    <url id="ThirdPartyControl.SomeType:1" href="/docs/sometype_V1.0.html" />
    <url id="ThirdPartyControl.AnotherType:1" href="/docs/anothertype_V1.0.html" />
  </mappings>
  <mappings base="http://unique.com">
    <url id="ThirdPartyControl.SomeType:2" href="/docs/sometype_V2.0.html" />
    <url id="ThirdPartyControl.AnotherType:2" href="/docs/anothertype_V2.0.html" />
  </mappings>
</component>
The base attribute would be optional, of course.

I'll probably have the time to build this component over the weekend - it shouldn't take long. I'll make sure it works without DocProject installed as well.

- Dave
Feb 15, 2008 at 2:28 PM
Hi Dave,


davedev wrote:
I don't like the idea of having the ID as the virtual URL itself. I understand why you think that managing an ID for each URL would be more work, but I think overall it would be less work in actuality, especially with a GUI to help configure mappings.


Actually, I didn't force the ID as the virtual URL, it could have been anything. Only, what I proposed was that if there was no explicit mapping from the xref/ID to a URL provided, it would be interpreted as representing a URL (or rather URI), which could be absolute (no base URL provided) or relative.


For example, if you decided to use a pattern for IDs, such as setting them to the namespace and type name of third-party controls, then typing <see xref="ThirdPartyLib.CustomControl"/> while authoring documentation would be quite easy and intuitive; more so than having to hard-code a long URL each time it's needed throughout all of your documentation (imagine a URL with a cryptic path and query string).

And again, if the URL ever changes (which you have no control over since it's a third-party website) then you only need to change the URL mapping instead of every reference in every topic.


Well, that was the purpose of my base URLs. Maybe you understand better if I show you my concrete example.
The library I wrote (a while ago) is a .NET wrapper for Berkeley DB (http://www.oracle.com/database/berkeley-db/index.html).
Obviously I don't want to duplicate all the BDB docs, I only want to document my wrapper classes and for the actual functionality want to point back to the original documentation (which targets a C-API). The internal structure of these docs will not change anymore, so I can hard code the relative URLs to the BDB documentation in my code. However, the actual location of the docs is variable. It could be on Oracle's web site (where it could move around), or it could be on your local hard disk where BDB is installed.

So, in my case, the base URL would point to the location of the docs, and the relative URLs would be the IDs for the external documentation pages, which will never change for a specific version of BDB.



and we want as default a base URI of http://example.com - it should apply to the "Regular Item". We want as a special case use the base URI http://special.com for "Special Item" and as an extra special case we want to map "Unique Item" to an html file that does not correspond to its xref/id value

This would save the trouble of having to type http://domain.com each time, which IMO isn't all that difficult anyway if you're already hard-coding the rest of a potentially long URL.

But I think it's uncommon that the same virtual path would apply to different base URLs. So if the ID is the URL itself then segregating mappings by base paths would never be required. The component's only purpose would be to slap on a base path to virtual URLs hard-coded in the documentation. I think we can get more value out of a component that resolves external links.


Actually, as you can see from my example, the same relative URL (which really can be seen as the documentation ID for a specific function or data structure in a C-API) can be applied to different base URL (the doc location), but of course no in the same instance of the document. Basically, what I want to do is re-run my doc build process with a different base URL in case Oracle changes their doc location.


Without mixing ID and URL semantics, grouping by base paths just to reduce the amount of redundancy in the mappings themselves might be useful though:

<component type="ResolveExternalLinksComponent" "assembly="..">
  <mappings base="http://special.com">
    <url id="ThirdPartyControl.SomeType:1" href="/docs/sometype_V1.0.html" />
    <url id="ThirdPartyControl.AnotherType:1" href="/docs/anothertype_V1.0.html" />
  </mappings>
  <mappings base="http://unique.com">
    <url id="ThirdPartyControl.SomeType:2" href="/docs/sometype_V2.0.html" />
    <url id="ThirdPartyControl.AnotherType:2" href="/docs/anothertype_V2.0.html" />
  </mappings>
</component>
The base attribute would be optional, of course.


From what I can tell you propose basically the same as I suggested, only you would not do any defaulting for those IDs where no mapping was provided. So the only difference really is that I proposed that for unmapped xref values, they will be interpreted as relative or absolute URLs. There could be two levels of being "unmapped":
  1. no mapping at all: to be interpreted as URL, optionally using a global base URL (specified on component level)
  2. mapping without href specified: use base URL from mappings group, interpreting xref/ID as relative URL


I'll probably have the time to build this component over the weekend - it shouldn't take long. I'll make sure it works without DocProject installed as well.


That would be great. I can always augment it with whatever special needs I have (e.g. add defaulting).

Regards,

Karl
Feb 15, 2008 at 5:31 PM
Hi Karl,

I think we're basically on the same page here.

My last example is like the third case in your first example, where a URL-based ID could be overridden with a complete URL, although my example doesn't override a URL-based ID, it simply maps an ID to a URL (I think you get the idea though :).

Here are the problems that I have with your solution:
  1. I don't like the mixing of semantics. An ID attribute should only be an ID, or it should be a URL, but not both. You're trying to save typing but, as I mentioned previously, you may end up doing much more (see below).
  2. Assuming that relative URLs will never change is a problem, and a corner-case if it just-so-happens to be true.

You can't say for sure what Oracle will do with their website and you can't guarantee that the relative paths for documentation in subsequent releases will not change, even if it's installed locally.

For example, the MSDN library has gone through changes of its entire URL-mapping structure in the past, so having assumed that only the base URL could change would have created a lot of work for you now since you'd have to update URLs in every single topic, for both local and online references. And it doesn't matter whether they're being interpreted by the component as IDs or actual URLs, since they'll be invalid URLs now either way. Updating the XML mappings to override invalid relative URLs with correct, full URLs, would mean that you'd be leaving broken relative URLs laced throughout your documentation. Who wants to type broken URLs as an ID when creating new documentation? Nobody, so you'll end up having documentation containing old URLs, being mapped to new ones, and new IDs that represent the latest URLs. If they change, the cycle repeats and your documentation becomes a management nightmare full of URLs, when really it should only contain IDs (references) to begin with.

My recommendation is to keep the URLs out of your documentation and place them in the mappings, even if you feel that only the base path may change. It should be less work in the long run since you'll never have to adjust the IDs. The mappings file will be the only place you need to go to update existing documentation if any of the paths change, base or relative.

And again, typing an intuitive ID might actually be less work then having to look up a valid URL and then pull out the appropriate relative portion of it.


That would be great. I can always augment it with whatever special needs I have (e.g. add defaulting).

Sounds like a plan. But if you still feel that I'm being too hasty in rejecting your ID can also be a URL idea, then please let me know what I'm missing. I don't want you to have to do extra work if it isn't necessary :)

Thanks,
Dave
Feb 15, 2008 at 6:27 PM


davedev wrote:
Here are the problems that I have with your solution:
  1. I don't like the mixing of semantics. An ID attribute should only be an ID, or it should be a URL, but not both. You're trying to save typing but, as I mentioned previously, you may end up doing much more (see below).
  2. Assuming that relative URLs will never change is a problem, and a corner-case if it just-so-happens to be true.


I agree in principle, but don't forget, the ultimate goal of an xref attribute is to map to an URI. From that perspective interpreting an unmapped ID as a URI (which, btw, is a form of ID: Uniform Resource Identifier) is quite natural. From a web oriented point of view you could even say that the ID should be a URI.

To the second point: I am quite confident that in my case relative URLs for a given Berkeley DB release remain unchanged, as released BDB packages are not being changed anymore. So, whatever Oracle puts online is a copy of the released docs. Newer docs (for newer versions of BDB) may change of course.


You can't say for sure what Oracle will do with their website and you can't guarantee that the relative paths for documentation in subsequent releases will not change, even if it's installed locally.


But as the relative URIs are already valid identifiers, all I have to do is add a mapping if the old URI is not valid anymore. Now, you could say that in such a case I would now have a possibly lengthy relative URI as xref/ID value, and you would be right. However, should that bother me, I can then simply replace the ID with a short value as I have to add a mapping anyway.


Sounds like a plan. But if you still feel that I'm being too hasty in rejecting your ID can also be a URL idea, then please let me know what I'm missing. I don't want you to have to do extra work if it isn't necessary :)


Thanks for the friendly attitude. I also don't want you to do work you consider unnecessary. ;-)

Regards,

Karl
Feb 15, 2008 at 8:19 PM
Edited Feb 15, 2008 at 9:32 PM
Hi Karl,


I agree in principle, but don't forget, the ultimate goal of an xref attribute is to map to an URI. From that perspective interpreting an unmapped ID as a URI (which, btw, is a form of ID: Uniform Resource Identifier) is quite natural. From a web oriented point of view you could even say that the ID should be a URI.

Just because a URL is unique doesn't mean that it's a good candidate for an ID. I think IDs should be short and they should not contain any information about what they're referencing. The purpose being what I've already stated: pulling the URLs out of the documentation will make managing them easier and authoring documentation might be quicker using intuitive IDs. That would be the difference between xref and href. If you want to use an actual URI instead, then you could use href like normal and set the <base> tag in the HTML header.

Ok, let's try a different angle then since we can't seem to agree on this :)

My guess is that you're not happy (edit) with a purely ID to URL mapping solution because it seems like it would be more work for you while authoring documentation. Otherwise, there's no reason that I can see not to use it instead since it's simpler and intuitive.

For example, here would be a few common scenarios with my solution.

Scenario 1
Task: Add a hyperlink in documentation that references an external topic.
  1. Decide that you want to add an external link while authoring a topic.
  2. Add the reference and specify a unique ID that's either completely arbitrary, or following some pre-determined pattern such as company.product.topic (recommended): <see xref="id"/>.
  3. Find the exact URL that you want.
  4. Edit the mappings XML (via the component's GUI or notepad, for example) and add the new ID with its related URL.
Scenario 2
Task: Add a hyperlink in documentation for an external topic that differs from the topic in Scenario 1, but shares the same base path.
  1. Decide that you want to add an external link while authoring a topic.
  2. Add the reference and specify a unique ID: <see xref="id2"/>.
  3. Find the exact URL that you want.
  4. Edit the mappings XML (via the component's GUI or notepad, for example) and add the new ID with its related URL.
Scenario 3
Task: The relative portion of the URL associated with the ID added in Scenario 1 has changed and it must be updated everywhere it's been used.
  1. Update the mappings XML to use the new URL for that particular ID; there should be only one occurrence since the ID is unique, and the change will automatically propagate to all topics that reference the ID.
Scenario 4
Task: The base portion of the URL associated with the ID added in Scenario 2 has changed and it must be updated everywhere it's been used.
  1. Update the mappings XML to use the new URL for that particular ID; there should be only one occurrence since the ID is unique, and the change will automatically propagate to all topics that reference the ID.
Now here are the same scenarios using your solution:

Scenario 1
Task: Add a hyperlink in documentation that references an external topic.
Pre: The base portion of the external topic's URL is the same that's being used for most or all of the external links in the entire documentation set.
One-time setup: Set the default base path in the mappings XML for unmapped IDs, which are actually relative URIs themselves.
  1. Decide that you want to add an external link while authoring a topic.
  2. Find the exact URL that you want.
  3. Add the reference and specify the relative portion of the URL as the ID: <see xref="url1"/>.
  4. Alt: If the base portion is not the default used throughout the documentation set, then update the mappings to add the ID under the appropriate <mappings base="pathA"> element.
Scenario 2
Task: Add a hyperlink in documentation for an external topic that differs from the topic in Scenario 1, but shares the same base path.
  1. Decide that you want to add an external link while authoring a topic.
  2. Find the exact URL that you want.
  3. Add the reference and specify the relative portion of the URL as the ID: <see xref="url2"/>.
  4. Alt: If the base portion is not the default used throughout the documentation set, then update the mappings to add the ID under the appropriate <mappings base="pathB"> element.
Scenario 3
Task: The relative portion of the URL associated with the ID added in Scenario 1 has changed and it must be updated everywhere it's been used.
  1. Update the mappings XML to use the new relative URL in place of the old URL by falling back to Scenario 3 of the first solution (add an explicit href to the mapping). The old, invalid URL, remains as the ID in all referencing topics.
Scenario 4
Task: The base portion of the URL associated with the ID added in Scenario 2 has changed and it must be updated everywhere it's been used.
  1. Update the mappings XML by removing the <url /> element for the ID from its current <mappings base="path"> element, if it exists, and add it to a new <mappings base="new_path"> element. If it doesn't exist, create it.
The benefits of the first solution are:
  1. Keeps topics clean.
  2. Easy bookkeeping since all URLs are found in one place - the mappings XML.
  3. Adapts easily to changes in URLs.
  4. Allows an author to concentrate on the documentation instead of URLs while authoring a topic. (Using a pattern for IDs would help tremendously.)
It seems to boil down to this: The second solution solves the following issue that you might have with the first solution:
  1. For external links that share the same base portion of URLs throughout the documentation, if the path is used more commonly than other paths, a single visit to the mappings XML is alleviated each time a new external hyperlink is introduced into the documentation.
Although, if you gather all of the IDs in the first solution after a topic is finished, then you only need to make a single trip per topic, so the second solution might only alleviate a single trip per topic.

Personally, I think the negatives far outweigh the positives with the second solution. And just from reading the scenarios I think it's clear how simple the first solution is for handling all possibilities, and how the second solution can be more complex than it has to be, just to alleviate a single trip to the mappings XML while authoring each topic.

So have I missed anything important?

- Dave
Feb 15, 2008 at 10:58 PM
Hi Dave,

I had already written a longer reply, but then I realized
  1. I will probably not convince you.
  2. You do the work, you make the call.
  3. In the actual implementation the difference between our approaches will likely be minimal. Basically, you will treat unmapped IDs as error, I will pass them through (after applying an optional base URI). So it should not be much work to modify your code to my needs.

Regards,

Karl
Feb 15, 2008 at 11:32 PM
Hi Karl,

Does this mean that I'm still missing important points or that it's all just a matter of opinion? ;)

I'm probably going to build the component tomorrow morning. I'll consider including vref support so that it works like your idea: IDs are assumed to be URLs if they are not mapped. Then at least you won't have to make the changes yourself and with two distinct terms, xref and vref, it would be explicit whether an ID was being used as an ID or possibly a URL instead (in the latter case you'd have to refer to the mappings to find out, whereas in the former it's obvious from within the topic).

Otherwise, I'll keep your 3rd point in mind - that you may want to add vref behavior yourself.

What do you think about supporting an optional boolean attribute named, verify on the component and individual <mappings> that when true URLs would be validated using the System.Uri class? A simple way to catch typos, although I'm not sure how useful that would really be. And should the default be true or false? I'm leaning toward true.

Thanks for the discussion.

- Dave
Feb 16, 2008 at 12:05 AM

davedev wrote:
Hi Karl,

Does this mean that I'm still missing important points or that it's all just a matter of opinion? ;)

I think to a point it was turning into a philosophical discussion and leading us away from the simple matter at hand: Finding a way to solve a practical problem.


I'm probably going to build the component tomorrow morning. I'll consider including vref support so that it works like your idea: IDs are assumed to be URLs if they are not mapped. Then at least you won't have to make the changes yourself and with two distinct terms, xref and vref, it would be explicit whether an ID was being used as an ID or possibly a URL instead (in the latter case you'd have to refer to the mappings to find out, whereas in the former it's obvious from within the topic).

Otherwise, I'll keep your 3rd point in mind - that you may want to add vref behavior yourself.

Both is fine with me - I appreciate your work.


What do you think about supporting an optional boolean attribute named, verify on the component and individual <mappings> that when true URLs would be validated using the System.Uri class? A simple way to catch typos, although I'm not sure how useful that would really be. And should the default be true or false? I'm leaning toward true.


Isn't the end result something that should be a valid URI? What else should an ID be resolved to?
I am not sure it should even be optional.


Regards and Thanks,

Karl
Feb 16, 2008 at 12:16 AM
Edited Feb 16, 2008 at 12:17 AM
Hi Karl,

I tried introducing scenarios to keep it practical. But I guess it doesn't matter anymore :)


Isn't the end result something that should be a valid URI?

I was thinking about script, so no not necessarily; e.g., <see xref="javascript:callme();">Show Example</see>.

EDIT: Bit of a mistake in my post: xref would be an ID that pointed to a mapping that contained the JavaScript in my example.

The component doesn't have to verify the URLs anyway. But since script is probably going to be used less often, I figured adding optional verification with a default of true would be acceptable.

- Dave
Feb 16, 2008 at 12:39 AM
Hi Dave,

davedev wrote:

Isn't the end result something that should be a valid URI?

I was thinking about script, so no not necessarily; e.g., <see xref="javascript:callme();">Show Example</see>.

I believe even a Javascript link has to be a valid URI (which your example is, I think)!
However, the javascript: URI scheme is not officially registered (but that has nothing to do with URI validity).

Regards,

Karl
Feb 16, 2008 at 1:52 AM
Hi Karl,


I believe even a Javascript link has to be a valid URI.

Apparently the standard is that parsers must automatically escape reserved characters when they appear in the JavaScript: scheme. So technically it's not a valid URI, per se, but it passes validation:

Uri uri;
if (Uri.TryCreate("javascript:alert('mailto:'+'dave@nowhere?body=#?hello@:#world@?');", UriKind.RelativeOrAbsolute, out uri))
  Console.WriteLine("Absolute={0}: {1}", uri.IsAbsoluteUri, uri);
else
  Console.WriteLine("Nope.");
Prints:

Absolute=True: javascript:alert('mailto:'+'dave@nowhere?body=#?hello@:#world@?');
surprisingly.

I guess I can omit the verify attribute :)

- Dave
Feb 16, 2008 at 2:39 AM


davedev wrote:
Apparently the standard is that parsers must automatically escape reserved characters when they appear in the JavaScript: scheme. So technically it's not a valid URI, per se, but it passes validation:

I guess it is a valid generic URI, just not a valid JavaScript URI (reserved characters only need to be escaped when they conflict with the URI's purpose), and the System.Uri class cannot know all possible URI schemes' rules.

Regards,

Karl
Feb 18, 2008 at 6:29 PM
Edited Feb 18, 2008 at 6:29 PM
Hi Karl,

I just uploaded the beta:

ResolveExternalLinksComponent Beta 1
https://www.codeplex.com/Release/ProjectReleases.aspx?ProjectName=DocProject&ReleaseId=10831

Please let me know what you think :)

- Dave
Feb 18, 2008 at 6:31 PM
And here's the preliminary documentation:

ResolveExternalLinksComponent
http://www.codeplex.com/DocProject/Wiki/View.aspx?title=ResolveExternalLinksComponent
Feb 19, 2008 at 1:30 AM
Hi Dave,


davedev wrote:
I just uploaded the beta:

ResolveExternalLinksComponent Beta 1
https://www.codeplex.com/Release/ProjectReleases.aspx?ProjectName=DocProject&ReleaseId=10831

Please let me know what you think :)


Very nice work. Thank you!
It looks as if the vref attribute functionality does everything I need.

I got it installed in SHFB with this component declaration in SandcastleBuilder.Components.config:
    <!-- External links custom component by Dave Sexton -->
    <component id="Resolve External Links Component"
        type="DaveSexton.Sandcastle.ResolveExternalLinksComponent"
        assembly="{@ComponentsFolder}\DaveSexton.Sandcastle.dll">  
      <insert placement="before" type="Microsoft.Ddue.Tools.TransformComponent" />
      <configureMethod name="ConfigureComponent" />
    </component>
A few remarks:
GUI Editor
It seems that the GUI configuration editor can't edit the top level attributes (base, target for the component element). So I had to edit the SHFB project configuration file by hand.
Mappings
When only the component is defined, but no mappings, then BuildAssembler crashes with this exception:
Unhandled Exception: System.Collections.Generic.KeyNotFoundException: The given key was not present in the dictionary.
   at System.ThrowHelper.ThrowKeyNotFoundException()
   at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
   at DaveSexton.Sandcastle.ResolveExternalLinksComponentConfiguration.GetMappingExact(String topicId, String id)
   at DaveSexton.Sandcastle.ResolveExternalLinksComponentConfiguration.GetMapping(String topicId, String id)
   at DaveSexton.Sandcastle.ResolveExternalLinksComponent.Apply(XmlDocument document, String topicId, ResolveExternalLinksComponentConfiguration configuration)
   at DaveSexton.Sandcastle.ResolveExternalLinksComponent.Apply(XmlDocument document, String key)
   at Microsoft.Ddue.Tools.BuildAssembler.Apply(IEnumerable`1 topics)
   at Microsoft.Ddue.Tools.BuildAssembler.Apply(String manifestFile)
   at Microsoft.Ddue.Tools.BuildAssemblerConsole.Main(String[] args)
In my case, I didn't need to define any mappings!
See Also
It would be nice if the same attributes could also be used for the <seealso> tag.

Best regards,

Karl


Editor
Feb 19, 2008 at 3:06 AM
The component should have its own component configuration file so that you don't have to merge it with the one supplied with SHFB. Just drop it along with the assembly in the BuildComponents folder and SHFB will find it.

Eric
Feb 19, 2008 at 3:24 AM
Hi Karl,

Thanks for the feedback.


It seems that the GUI configuration editor can't edit the top level attributes.

Sorry, that was an oversight on my part because DocProject allows you to edit those attributes in the DocProject Properties window. I'll see if I can make some room in the editor for them.


When only the component is defined, but no mappings, then BuildAssembler crashes.

I was able to reproduce this. However, it's not because there are no mappings, per se, but because there are no mappings in the default topic scope. If you were to only add mappings that had a topic attribute you'd get the same error.

I solved the problem by adding the following code to line 268 in ResolveExternalLinksComponentConfiguration.cs:

&& mappingScopes.ContainsKey("")
Note: I just replaced the currently deployed version with the latest build since there's only one download anyway. (Presumably, from you :)


It would be nice if the same attributes could also be used for the <seealso> tag.

Good idea :)

- Dave
Feb 19, 2008 at 2:29 PM
Hi Eric,

EWoodruff wrote:
The component should have its own component configuration file so that you don't have to merge it with the one supplied with SHFB. Just drop it along with the assembly in the BuildComponents folder and SHFB will find it.


Thanks for the tip.

Karl
Feb 19, 2008 at 2:47 PM
Hi Dave,

davedev wrote:
Sorry, that was an oversight on my part because DocProject allows you to edit those attributes in the DocProject Properties window. I'll see if I can make some room in the editor for them.

That would be appreciated.


When only the component is defined, but no mappings, then BuildAssembler crashes.

I solved the problem by adding the following code to line 268 in ResolveExternalLinksComponentConfiguration.cs:
&& mappingScopes.ContainsKey("")

It works now.

Thanks,

Karl