Support hyperlink
In this topic, we will support hyperlinking in rtf files.
Create a hyperlink in the rtf file:
- Open
foo.rtf
byWord
. - Add a hyperlink in content
- Set the link target to an existing
bar.rtf
- Save the document.
About link
An author can write any valid hyperlink in the document, and then needs to run DocFX build
to update file links.
What is file link:
- The hyperlink must be a relative path and not rooted.
- valid:
foo\bar.rtf
,../foobar.rtf
- invalid:
/foo.rtf
,c:\foo\bar.rtf
,http://foo.bar/
,mailto:foo@bar.foobar
- valid:
- The file must exist.
Why update file link:
The story is:
- In
foo.rtf
, it has a file link tobar.rtf
. - In document build,
bar.rtf
generates a file with the namebar.html
. - But in
foo.rtf
, the link target is stillbar.rtf
, thus in the output folder we cannot find this file and we will get a broken link. - To resolve the broken link, we need to update the link target from
bar.rtf
tobar.html
.
File link is a relative path, but we cannot track the relative path easily. So we track the normalized file path instead.
What is a normalized file path:
- It always starts from the working folder (the folder that contains
docfx.json
), and we write it as~/
. - No
../
or./
or//
- Replace
\
with/
. - No url encoding. The path must be same as it in the file system.
- No anchor.
Finally, a valid normalized file path looks like: ~/foo/bar.rtf
.
Pros
Same form in different documents when the target is the same file.
When file structure is:
z:\a\b\foo.rtf z:\a\b\c\bar.rtf z:\a\b\c\foobar.rtf
Link target
c/foobar.rtf
infoo.rtf
and link targetfoobar.rtf
inbar.rtf
is the same file. When the working folder isz:\a\
, the link target is always~/b/c/foobar.rtf
.Avoids differences in style when referring to the same file.
For example, the following hyperlinks target the same file:
a/foo.rtf
,./a/foo.rtf
,a/b/../foo.rtf
,a//foo.rtf
,a\foo.rtf
Cons
- A folder with the name
~
is not supported.
- A folder with the name
Prepare
Open the rtf plug-in library project in
Visual Studio
.Add nuget packages:
for plug-in:Docfx.Utility
Add framework assembly reference:
System.Core
,System.Web
,System.Xml.Linq
Update rtf document processor
Following the rules for hyperlink, add a
FixLink
help method:private static void FixLink(XAttribute link, RelativePath filePath, HashSet<string> linkToFiles) { string linkFile; string anchor = null; if (PathUtility.IsRelativePath(link.Value)) { var index = link.Value.IndexOf('#'); if (index == -1) { linkFile = link.Value; } else if (index == 0) { return; } else { linkFile = link.Value.Remove(index); anchor = link.Value.Substring(index); } var path = filePath + (RelativePath)linkFile; var file = (string)path.GetPathFromWorkingFolder(); link.Value = file + anchor; linkToFiles.Add(HttpUtility.UrlDecode(file)); } }
RelativePath
helps us generate the links correctly.Then add
CollectLinksAndFixDocument
method:private static HashSet<string> CollectLinksAndFixDocument(FileModel model) { string content = (string)((Dictionary<string, object>)model.Content)["conceptual"]; var doc = XDocument.Parse(content); var links = from attr in doc.Descendants().Attributes() where "href".Equals(attr.Name.LocalName, StringComparison.OrdinalIgnoreCase) || "src".Equals(attr.Name.LocalName, StringComparison.OrdinalIgnoreCase) select attr; var path = (RelativePath)model.File; var linkToFiles = new HashSet<string>(); foreach (var link in links) { FixLink(link, path, linkToFiles); } using (var sw = new StringWriter()) { doc.Save(sw); ((Dictionary<string, object>)model.Content)["conceptual"] = sw.ToString(); } return linkToFiles; }
Modify
Save
method with report links:public SaveResult Save(FileModel model) { HashSet<string> linkToFiles = CollectLinksAndFixDocument(model); return new SaveResult { DocumentType = "Conceptual", ModelFile = model.File, LinkToFiles = linkToFiles.ToImmutableArray(), }; }
View final RtfDocumentProcessor.cs
Test and verify
- Build project.
- Copy dll to
Plugins
folder. - Modify rtf file, create hyperlink, link to another rtf file, and save.
- Build with command
DocFX build
. - Verify output html file.