Browse Source

Merge branch 'master' into pr/max-level-in-toc

# Conflicts:
#	src/DocNet/INavigationElement.cs
#	src/DocNet/NavigatedPath.cs
#	src/DocNet/NavigationElement.cs
#	src/DocNet/NavigationLevel.cs
#	src/DocNet/SimpleNavigationElement.cs
tags/v0.16
Geert van Horrik 8 years ago
parent
commit
fad6eb5ab0
12 changed files with 440 additions and 170 deletions
  1. +22
    -3
      src/DocNet/Config.cs
  2. +2
    -0
      src/DocNet/Docnet.csproj
  3. +5
    -2
      src/DocNet/Engine.cs
  4. +13
    -6
      src/DocNet/INavigationElement.cs
  5. +35
    -0
      src/DocNet/INavigationElementExtensions.cs
  6. +7
    -6
      src/DocNet/NavigatedPath.cs
  7. +14
    -6
      src/DocNet/NavigationElement.cs
  8. +219
    -51
      src/DocNet/NavigationLevel.cs
  9. +11
    -0
      src/DocNet/PathSpecification.cs
  10. +2
    -2
      src/DocNet/Properties/AssemblyInfo.cs
  11. +66
    -76
      src/DocNet/SimpleNavigationElement.cs
  12. +44
    -18
      src/MarkdownDeep/MardownDeep.cs

+ 22
- 3
src/DocNet/Config.cs View File

@@ -131,7 +131,7 @@ namespace Docnet
private void GenerateSearchDataIndex()
{
var collectedSearchEntries = new List<SearchIndexEntry>();
this.Pages.CollectSearchIndexEntries(collectedSearchEntries, new NavigatedPath());
this.Pages.CollectSearchIndexEntries(collectedSearchEntries, new NavigatedPath(), this.PathSpecification);
JObject searchIndex = new JObject(new JProperty("docs",
new JArray(
collectedSearchEntries.Select(e=>new JObject(
@@ -164,7 +164,7 @@ namespace Docnet
searchSimpleElement.ExtraScriptProducerFunc = e=> @"
<script>var base_url = '.';</script>
<script data-main=""js/search.js"" src=""js/require.js""></script>";
searchSimpleElement.GenerateOutput(this, activePath);
searchSimpleElement.GenerateOutput(this, activePath, this.PathSpecification);
activePath.Pop();
}

@@ -264,6 +264,25 @@ namespace Docnet
get { return _templateContents ?? string.Empty; }
}

public PathSpecification PathSpecification
{
get
{
var pathSpecification = PathSpecification.Full;

var pathSpecificationAsString = (string)_configData.PathSpecification;
if (!string.IsNullOrWhiteSpace(pathSpecificationAsString))
{
if (!Enum.TryParse(pathSpecificationAsString, true, out pathSpecification))
{
pathSpecification = PathSpecification.Full;
}
}

return pathSpecification;
}
}

public NavigationLevel Pages
{
get
@@ -271,7 +290,7 @@ namespace Docnet
if(_pages == null)
{
JObject rawPages = _configData.Pages;
_pages = new NavigationLevel() {Name = "Home", IsRoot = true};
_pages = new NavigationLevel(Source) {Name = "Home", IsRoot = true};
_pages.Load(rawPages);
}
return _pages;


+ 2
- 0
src/DocNet/Docnet.csproj View File

@@ -60,10 +60,12 @@
<Compile Include="NavigatedPath.cs" />
<Compile Include="NavigationElement.cs" />
<Compile Include="NavigationLevel.cs" />
<Compile Include="PathSpecification.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SearchIndexEntry.cs" />
<Compile Include="SimpleNavigationElement.cs" />
<Compile Include="INavigationElementExtensions.cs" />
<Compile Include="Utils.cs" />
</ItemGroup>
<ItemGroup>


+ 5
- 2
src/DocNet/Engine.cs View File

@@ -70,11 +70,14 @@ namespace Docnet
Console.WriteLine("Errors occurred, can't continue!");
return null;
}
if(config.Pages.IndexElement == null)

var indexElement = config.Pages.GetIndexElement(config.PathSpecification);
if(indexElement == null)
{
Console.WriteLine("[ERROR] Root __index not found. The root navigationlevel is required to have an __index element");
return null;
}

return config;
}

@@ -95,7 +98,7 @@ namespace Docnet
Console.WriteLine("Copying source folders to copy.");
_loadedConfig.CopySourceFoldersToCopy();
Console.WriteLine("Generating pages in '{0}'", _loadedConfig.Destination);
_loadedConfig.Pages.GenerateOutput(_loadedConfig, new NavigatedPath());
_loadedConfig.Pages.GenerateOutput(_loadedConfig, new NavigatedPath(), _loadedConfig.PathSpecification);
Console.WriteLine("Generating search index");
_loadedConfig.GenerateSearchData();
Console.WriteLine("Done!");


+ 13
- 6
src/DocNet/INavigationElement.cs View File

@@ -35,32 +35,39 @@ namespace Docnet
/// </summary>
/// <param name="activeConfig">The active configuration to use for the output.</param>
/// <param name="activePath">The active path navigated through the ToC to reach this element.</param>
void GenerateOutput(Config activeConfig, NavigatedPath activePath);
/// <param name="pathSpecification">The path specification.</param>
void GenerateOutput(Config activeConfig, NavigatedPath activePath, PathSpecification pathSpecification);

/// <summary>
/// Generates the ToC fragment for this element, which can either be a simple line or a full expanded menu.
/// </summary>
/// <param name="navigatedPath">The navigated path to the current element, which doesn't necessarily have to be this element.</param>
/// <param name="relativePathToRoot">The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path.</param>
/// <param name="maxLevel">The maximum level.</param>
/// <param name="pathSpecification">The path specification.</param>
/// <returns></returns>
string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, int maxLevel);
string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification);
/// <summary>
/// Collects the search index entries. These are created from simple navigation elements found in this container, which aren't index element.
/// </summary>
/// <param name="collectedEntries">The collected entries.</param>
/// <param name="activePath">The active path currently navigated.</param>
void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath);
/// <param name="pathSpecification">The path specification.</param>
void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath, PathSpecification pathSpecification);

/// <summary>
/// Gets the target URL with respect to the <see cref="PathSpecification"/>.
/// </summary>
/// <param name="pathSpecification">The path specification.</param>
/// <returns></returns>
string GetTargetURL(PathSpecification pathSpecification);

/// <summary>
/// Gets a value indicating whether this element is the __index element
/// </summary>
bool IsIndexElement { get; set; }
bool IsAutoGenerated { get; set; }
string Name { get; set; }
object Value { get; set; }
string TargetURL { get; }
NavigationLevel ParentContainer { get; set; }
}
}

+ 35
- 0
src/DocNet/INavigationElementExtensions.cs View File

@@ -0,0 +1,35 @@
using System;
using System.Web;

namespace Docnet
{
public static class INavigationElementExtensions
{
private const string IndexHtmFileName = "index.htm";

/// <summary>
/// Gets the final URL by encoding the path and by removing the filename if it equals <c>index.htm</c>.
/// </summary>
/// <param name="navigationElement">The navigation element.</param>
/// <param name="pathSpecification">The path specification.</param>
/// <returns></returns>
public static string GetFinalTargetUrl(this INavigationElement navigationElement, PathSpecification pathSpecification)
{
var targetUrl = navigationElement.GetTargetURL(pathSpecification);
var link = HttpUtility.UrlPathEncode(targetUrl);

// Disabled for now as discussed in #65 (https://github.com/FransBouma/DocNet/pull/65), but
// is required for #44
//if (pathSpecification == PathSpecification.RelativeAsFolder)
//{
// if (link.Length > IndexHtmFileName.Length &&
// link.EndsWith(IndexHtmFileName, StringComparison.InvariantCultureIgnoreCase))
// {
// link = link.Substring(0, link.Length - IndexHtmFileName.Length);
// }
//}

return link;
}
}
}

+ 7
- 6
src/DocNet/NavigatedPath.cs View File

@@ -35,17 +35,18 @@ namespace Docnet
public class NavigatedPath : Stack<INavigationElement>
{
/// <summary>
/// Creates the bread crumbs HTML of the elements in this path, delimited by '/' characters.
/// Creates the bread crumbs HTML of the elements in this path, delimited by '/' characters.
/// </summary>
/// <param name="relativePathToRoot">The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path.</param>
/// <param name="pathSpecification">The path specification.</param>
/// <returns></returns>
public string CreateBreadCrumbsHTML(string relativePathToRoot)
public string CreateBreadCrumbsHTML(string relativePathToRoot, PathSpecification pathSpecification)
{
var fragments = new List<string>();
// we enumerate a stack, which enumerates from top to bottom, so we have to reverse things first.
foreach(var element in this.Reverse())
{
var targetURL = element.TargetURL;
var targetURL = element.GetTargetURL(pathSpecification);
if(string.IsNullOrWhiteSpace(targetURL))
{
fragments.Add(string.Format("<li>{0}</li>", element.Name));
@@ -76,9 +77,9 @@ namespace Docnet
/// aren't, are not expanded.
/// </summary>
/// <param name="relativePathToRoot">The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path.</param>
/// <param name="maxLevel">The maximum level.</param>
/// <param name="pathSpecification">The path specification.</param>
/// <returns></returns>
public string CreateToCHTML(string relativePathToRoot, int maxLevel)
public string CreateToCHTML(string relativePathToRoot, PathSpecification pathSpecification)
{
// the root container is the bottom element of this path. We use that container to build the root and navigate any node open along the navigated path.
var rootContainer = this.Reverse().FirstOrDefault() as NavigationLevel;
@@ -87,7 +88,7 @@ namespace Docnet
// no root container, no TOC
return string.Empty;
}
return rootContainer.GenerateToCFragment(this, relativePathToRoot, maxLevel);
return rootContainer.GenerateToCFragment(this, relativePathToRoot, pathSpecification);
}
}
}

+ 14
- 6
src/DocNet/NavigationElement.cs View File

@@ -36,31 +36,39 @@ namespace Docnet
/// </summary>
/// <param name="activeConfig">The active configuration to use for the output.</param>
/// <param name="activePath">The active path navigated through the ToC to reach this element.</param>
/// <returns>true if everything went well, false otherwise</returns>
public abstract void GenerateOutput(Config activeConfig, NavigatedPath activePath);
/// <param name="pathSpecification">The path specification.</param>
public abstract void GenerateOutput(Config activeConfig, NavigatedPath activePath, PathSpecification pathSpecification);
/// <summary>
/// Generates the ToC fragment for this element, which can either be a simple line or a full expanded menu.
/// </summary>
/// <param name="navigatedPath">The navigated path to the current element, which doesn't necessarily have to be this element.</param>
/// <param name="relativePathToRoot">The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path.</param>
/// <param name="maxLevel">The maximum level.</param>
/// <param name="pathSpecification">The path specification.</param>
/// <returns></returns>
public abstract string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, int maxLevel);
public abstract string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification);
/// <summary>
/// Collects the search index entries. These are created from simple navigation elements found in this container, which aren't index element.
/// </summary>
/// <param name="collectedEntries">The collected entries.</param>
/// <param name="activePath">The active path currently navigated.</param>
public abstract void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath);
/// <param name="pathSpecification">The path specification.</param>
public abstract void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath, PathSpecification pathSpecification);

/// <summary>
/// Gets the target URL with respect to the <see cref="PathSpecification"/>.
/// </summary>
/// <param name="pathSpecification">The path specification.</param>
/// <returns></returns>
public abstract string GetTargetURL(PathSpecification pathSpecification);

#region Properties
public abstract string TargetURL { get; }
/// <summary>
/// Gets / sets a value indicating whether this element is the __index element
/// </summary>
public abstract bool IsIndexElement { get; set; }

public bool IsAutoGenerated { get; set; }

public string Name { get; set; }
/// <summary>
/// Gets or sets the value of this element, which can either be a string or a NavigationLevel


+ 219
- 51
src/DocNet/NavigationLevel.cs View File

@@ -33,30 +33,65 @@ namespace Docnet
{
public class NavigationLevel : NavigationElement<List<INavigationElement>>
{
public NavigationLevel() : base()
#region Members
private readonly string _rootDirectory;
#endregion

public NavigationLevel(string rootDirectory)
: base()
{
this._rootDirectory = rootDirectory;
this.Value = new List<INavigationElement>();
}


public void Load(JObject dataFromFile)
{
foreach(KeyValuePair<string, JToken> child in dataFromFile)
foreach (KeyValuePair<string, JToken> child in dataFromFile)
{
INavigationElement toAdd;
if(child.Value.Type == JTokenType.String)
if (child.Value.Type == JTokenType.String)
{
var nameToUse = child.Key;

var isIndexElement = child.Key == "__index";
if(isIndexElement)
if (isIndexElement)
{
nameToUse = this.Name;
}
toAdd = new SimpleNavigationElement() { Name = nameToUse, Value = child.Value.ToObject<string>(), IsIndexElement = isIndexElement};

var childValue = child.Value.ToObject<string>();
if (childValue.EndsWith("**"))
{
var path = childValue.Replace("**", string.Empty)
.Replace('\\', Path.DirectorySeparatorChar)
.Replace('/', Path.DirectorySeparatorChar);

if (!Path.IsPathRooted(path))
{
path = Path.Combine(_rootDirectory, path);
}

toAdd = CreateGeneratedLevel(path);
toAdd.Name = nameToUse;
}
else
{
toAdd = new SimpleNavigationElement
{
Name = nameToUse,
Value = childValue,
IsIndexElement = isIndexElement
};
}
}
else
{
var subLevel = new NavigationLevel() { Name = child.Key, IsRoot = false};
var subLevel = new NavigationLevel(_rootDirectory)
{
Name = child.Key,
IsRoot = false
};
subLevel.Load((JObject)child.Value);
toAdd = subLevel;
}
@@ -71,12 +106,13 @@ namespace Docnet
/// </summary>
/// <param name="collectedEntries">The collected entries.</param>
/// <param name="activePath">The active path currently navigated.</param>
public override void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath)
/// <param name="pathSpecification">The path specification.</param>
public override void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath, PathSpecification pathSpecification)
{
activePath.Push(this);
foreach(var element in this.Value)
foreach (var element in this.Value)
{
element.CollectSearchIndexEntries(collectedEntries, activePath);
element.CollectSearchIndexEntries(collectedEntries, activePath, pathSpecification);
}
activePath.Pop();
}
@@ -87,14 +123,15 @@ namespace Docnet
/// </summary>
/// <param name="activeConfig">The active configuration to use for the output.</param>
/// <param name="activePath">The active path navigated through the ToC to reach this element.</param>
public override void GenerateOutput(Config activeConfig, NavigatedPath activePath)
/// <param name="pathSpecification">The path specification.</param>
public override void GenerateOutput(Config activeConfig, NavigatedPath activePath, PathSpecification pathSpecification)
{
activePath.Push(this);
int i = 0;
while(i<this.Value.Count)
while (i < this.Value.Count)
{
var element = this.Value[i];
element.GenerateOutput(activeConfig, activePath);
element.GenerateOutput(activeConfig, activePath, pathSpecification);
i++;
}
activePath.Pop();
@@ -106,20 +143,20 @@ namespace Docnet
/// </summary>
/// <param name="navigatedPath">The navigated path to the current element, which doesn't necessarily have to be this element.</param>
/// <param name="relativePathToRoot">The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path.</param>
/// <param name="maxLevel">The maximum level.</param>
/// <param name="pathSpecification">The path specification.</param>
/// <returns></returns>
public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, int maxLevel)
public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification)
{
var fragments = new List<string>();
if(!this.IsRoot)
if (!this.IsRoot)
{
fragments.Add("<li class=\"tocentry\">");
}
if(navigatedPath.Contains(this))
if (navigatedPath.Contains(this))
{
// we're expanded. If we're not root and on the top of the navigated path stack, our index page is the page we're currently generating the ToC for, so
// we have to mark the entry as 'current'
if(navigatedPath.Peek() == this && !this.IsRoot)
if (navigatedPath.Peek() == this && !this.IsRoot)
{
fragments.Add("<ul class=\"current\">");
}
@@ -130,37 +167,37 @@ namespace Docnet

// first render the level header, which is the index element, if present or a label. The root always has an __index element otherwise we'd have stopped at load.
var elementStartTag = "<li><span class=\"navigationgroup\"><i class=\"fa fa-caret-down\"></i> ";
var indexElement = this.IndexElement;
if(indexElement == null)
var indexElement = this.GetIndexElement(pathSpecification);
if (indexElement == null)
{
fragments.Add(string.Format("{0}{1}</span></li>", elementStartTag, this.Name));
}
else
{
if(this.IsRoot)
if (this.IsRoot)
{
fragments.Add(indexElement.PerformGenerateToCFragment(navigatedPath, relativePathToRoot, maxLevel));
fragments.Add(indexElement.PerformGenerateToCFragment(navigatedPath, relativePathToRoot, pathSpecification));
}
else
{
fragments.Add(string.Format("{0}<a href=\"{1}{2}\">{3}</a></span></li>", elementStartTag, relativePathToRoot, HttpUtility.UrlPathEncode(indexElement.TargetURL),
this.Name));
fragments.Add(string.Format("{0}<a href=\"{1}{2}\">{3}</a></span></li>",
elementStartTag, relativePathToRoot, indexElement.GetFinalTargetUrl(pathSpecification), this.Name));
}
}
// then the elements in the container. Index elements are skipped here.
foreach(var element in this.Value)
foreach (var element in this.Value)
{
fragments.Add(element.GenerateToCFragment(navigatedPath, relativePathToRoot, maxLevel));
fragments.Add(element.GenerateToCFragment(navigatedPath, relativePathToRoot, pathSpecification));
}
fragments.Add("</ul>");
}
else
{
// just a link
fragments.Add(string.Format("<span class=\"navigationgroup\"><i class=\"fa fa-caret-right\"></i> <a href=\"{0}{1}\">{2}</a></span>",
relativePathToRoot, HttpUtility.UrlPathEncode(this.TargetURL), this.Name));
fragments.Add(string.Format("<span class=\"navigationgroup\"><i class=\"fa fa-caret-right\"></i> <a href=\"{0}{1}\">{2}</a></span>",
relativePathToRoot, this.GetFinalTargetUrl(pathSpecification), this.Name));
}
if(!this.IsRoot)
if (!this.IsRoot)
{
fragments.Add("</li>");
}
@@ -168,48 +205,178 @@ namespace Docnet
}


#region Properties
public override string TargetURL
private NavigationLevel CreateGeneratedLevel(string path)
{
get
var root = new NavigationLevel(_rootDirectory)
{
ParentContainer = this,
IsAutoGenerated = true
};

foreach (var mdFile in Directory.GetFiles(path, "*.md", SearchOption.TopDirectoryOnly))
{
var defaultElement = this.IndexElement;
if(defaultElement == null)
var name = FindTitleInMdFile(mdFile);
if (string.IsNullOrWhiteSpace(name))
{
return string.Empty;
continue;
}
return defaultElement.TargetURL ?? string.Empty;

var item = new SimpleNavigationElement
{
Name = name,
Value = Path.Combine(Utils.MakeRelativePath(_rootDirectory, path), Path.GetFileName(mdFile)),
ParentContainer = root,
IsAutoGenerated = true
};

root.Value.Add(item);
}

foreach (var directory in Directory.GetDirectories(path, "*", SearchOption.TopDirectoryOnly))
{
var subDirectoryNavigationElement = CreateGeneratedLevel(directory);
subDirectoryNavigationElement.Name = new DirectoryInfo(directory).Name;
subDirectoryNavigationElement.ParentContainer = root;

root.Value.Add(subDirectoryNavigationElement);
}

return root;
}


public SimpleNavigationElement IndexElement
private string FindTitleInMdFile(string path)
{
get
var title = string.Empty;

using (var fileStream = File.OpenRead(path))
{
var toReturn = this.Value.FirstOrDefault(e => e.IsIndexElement) as SimpleNavigationElement;
if(toReturn == null)
using (var streamReader = new StreamReader(fileStream))
{
// no index element, add an artificial one.
var path = string.Empty;
if(this.ParentContainer != null)
var line = string.Empty;
while (string.IsNullOrWhiteSpace(line))
{
path = Path.GetDirectoryName(this.ParentContainer.TargetURL);
line = streamReader.ReadLine();
if (!string.IsNullOrWhiteSpace(line))
{
line = line.Trim();
while (line.StartsWith("#"))
{
line = line.Substring(1).Trim();
}
if (!string.IsNullOrWhiteSpace(line))
{
title = line;
break;
}
}
}
var nameToUse = this.Name.Replace(".", "").Replace('/', '_').Replace("\\", "_").Replace(":", "").Replace(" ", "");
if(string.IsNullOrWhiteSpace(nameToUse))
}
}

return title;
}

public override string GetTargetURL(PathSpecification pathSpecification)
{
var defaultElement = this.GetIndexElement(pathSpecification);
if (defaultElement == null)
{
return string.Empty;
}

return defaultElement.GetTargetURL(pathSpecification) ?? string.Empty;
}

public SimpleNavigationElement GetIndexElement(PathSpecification pathSpecification)
{
var toReturn = this.Value.FirstOrDefault(e => e.IsIndexElement) as SimpleNavigationElement;
if (toReturn == null)
{
// no index element, add an artificial one.
var path = string.Empty;

// Don't check parents when using relative paths since we need to walk the tree manually
if (pathSpecification == PathSpecification.Full)
{
if (this.ParentContainer != null)
{
return null;
path = Path.GetDirectoryName(this.ParentContainer.GetTargetURL(pathSpecification));
}
toReturn = new SimpleNavigationElement() {ParentContainer = this, Value = string.Format("{0}{1}.md", path, nameToUse), Name = this.Name, IsIndexElement = true};
this.Value.Add(toReturn);
}

return toReturn;
var nameToUse = this.Name.Replace(".", "").Replace('/', '_').Replace("\\", "_").Replace(":", "").Replace(" ", "");
if (string.IsNullOrWhiteSpace(nameToUse))
{
return null;
}

var value = string.Format("{0}{1}.md", path, nameToUse);

switch (pathSpecification)
{
case PathSpecification.Full:
// Default is correct
break;

case PathSpecification.Relative:
case PathSpecification.RelativeAsFolder:
if (!IsRoot)
{
string preferredPath = null;

// We're making a big assumption here, but we can get the first page and assume it's
// in the right folder.

// Find first (simple) child and use 1 level up. A SimpleNavigationElement mostly represents a folder
// thus is excellent to be used as a folder name
var firstSimpleChildPage = (SimpleNavigationElement) this.Value.FirstOrDefault(x => x is SimpleNavigationElement && !ReferenceEquals(this, x));
if (firstSimpleChildPage != null)
{
preferredPath = Path.GetDirectoryName(firstSimpleChildPage.Value);
}
else
{
// This is representing an empty folder. Search for first child navigation that has real childs,
// then retrieve the path by going levels up.
var firstChildNavigationLevel = (NavigationLevel)this.Value.FirstOrDefault(x => x is NavigationLevel && ((NavigationLevel)x).Value.Any() && !ReferenceEquals(this, x));
if (firstChildNavigationLevel != null)
{
var targetUrl = firstChildNavigationLevel.Value.First().GetTargetURL(pathSpecification);

// 3 times since we need 2 parents up
preferredPath = Path.GetDirectoryName(targetUrl);
preferredPath = Path.GetDirectoryName(preferredPath);
preferredPath = Path.GetDirectoryName(preferredPath);
}
}

if (!string.IsNullOrWhiteSpace(preferredPath))
{
value = Path.Combine(preferredPath, "index.md");
}
}
break;

default:
throw new ArgumentOutOfRangeException(nameof(pathSpecification), pathSpecification, null);
}

toReturn = new SimpleNavigationElement
{
ParentContainer = this,
Value = value,
Name = this.Name,
IsIndexElement = true
};

this.Value.Add(toReturn);
}
}

return toReturn;
}

#region Properties
/// <summary>
/// Gets / sets a value indicating whether this element is the __index element
/// </summary>
@@ -217,7 +384,8 @@ namespace Docnet
{
// never an index
get { return false; }
set {
set
{
// nop;
}
}


+ 11
- 0
src/DocNet/PathSpecification.cs View File

@@ -0,0 +1,11 @@
namespace Docnet
{
public enum PathSpecification
{
Full,

Relative,

RelativeAsFolder
}
}

+ 2
- 2
src/DocNet/Properties/AssemblyInfo.cs View File

@@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.15.0.0")]
[assembly: AssemblyFileVersion("0.15.0")]
[assembly: AssemblyVersion("0.16.0.0")]
[assembly: AssemblyFileVersion("0.16.0")]

+ 66
- 76
src/DocNet/SimpleNavigationElement.cs View File

@@ -27,7 +27,6 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using MarkdownDeep;

namespace Docnet
{
@@ -35,14 +34,13 @@ namespace Docnet
{
#region Members
private string _targetURLForHTML;

private readonly List<Heading> _relativeLinksOnPage; // first element in Tuple is anchor name, second is name for ToC.
private List<Tuple<string, string>> _relativeH2LinksOnPage; // first element in Tuple is anchor name, second is name for ToC.
#endregion


public SimpleNavigationElement()
{
_relativeLinksOnPage = new List<Heading>();
_relativeH2LinksOnPage = new List<Tuple<string, string>>();
}


@@ -51,53 +49,56 @@ namespace Docnet
/// </summary>
/// <param name="activeConfig">The active configuration to use for the output.</param>
/// <param name="activePath">The active path navigated through the ToC to reach this element.</param>
public override void GenerateOutput(Config activeConfig, NavigatedPath activePath)
/// <param name="pathSpecification">The path specification.</param>
/// <exception cref="System.IO.FileNotFoundException"></exception>
public override void GenerateOutput(Config activeConfig, NavigatedPath activePath, PathSpecification pathSpecification)
{
// if we're the __index element, we're not pushing ourselves on the path, as we're representing the container we're in, which is already on the path.
if (!this.IsIndexElement)
if(!this.IsIndexElement)
{
activePath.Push(this);
}
_relativeLinksOnPage.Clear();
_relativeH2LinksOnPage.Clear();
var sourceFile = Utils.MakeAbsolutePath(activeConfig.Source, this.Value);
var destinationFile = Utils.MakeAbsolutePath(activeConfig.Destination, this.TargetURL);
var destinationFile = Utils.MakeAbsolutePath(activeConfig.Destination, this.GetTargetURL(pathSpecification));
var sb = new StringBuilder(activeConfig.PageTemplateContents.Length + 2048);
var content = string.Empty;
this.MarkdownFromFile = string.Empty;
var relativePathToRoot = Utils.MakeRelativePathForUri(Path.GetDirectoryName(destinationFile), activeConfig.Destination);
if (File.Exists(sourceFile))
if(File.Exists(sourceFile))
{
this.MarkdownFromFile = File.ReadAllText(sourceFile, Encoding.UTF8);
// Check if the content contains @@include tag
content = Utils.IncludeProcessor(this.MarkdownFromFile, Utils.MakeAbsolutePath(activeConfig.Source, activeConfig.IncludeFolder));
content = Utils.ConvertMarkdownToHtml(content, Path.GetDirectoryName(destinationFile), activeConfig.Destination, sourceFile, _relativeLinksOnPage, activeConfig.ConvertLocalLinks);
content = Utils.ConvertMarkdownToHtml(content, Path.GetDirectoryName(destinationFile), activeConfig.Destination, sourceFile, _relativeH2LinksOnPage, activeConfig.ConvertLocalLinks);
}
else
{
// if we're not the index element, the file is missing and potentially it's an error in the config page.
// Otherwise we can simply assume we are a missing index page and we'll generate default markdown so the user has something to look at.
if (this.IsIndexElement)
if(this.IsIndexElement)
{
// replace with default markdown snippet. This is the name of our container and links to the elements in that container as we are the index page that's not
// specified / existend.
var defaultMarkdown = new StringBuilder();
defaultMarkdown.AppendFormat("# {0}{1}{1}", this.ParentContainer.Name, Environment.NewLine);
defaultMarkdown.AppendFormat("Please select one of the topics in this section:{0}{0}", Environment.NewLine);
foreach (var sibling in this.ParentContainer.Value)
foreach(var sibling in this.ParentContainer.Value)
{
if (sibling == this)
if(sibling == this)
{
continue;
}
defaultMarkdown.AppendFormat("* [{0}]({1}{2}){3}", sibling.Name, relativePathToRoot, HttpUtility.UrlPathEncode(sibling.TargetURL), Environment.NewLine);
defaultMarkdown.AppendFormat("* [{0}]({1}{2}){3}", sibling.Name, relativePathToRoot,
sibling.GetFinalTargetUrl(pathSpecification), Environment.NewLine);
}
defaultMarkdown.Append(Environment.NewLine);
content = Utils.ConvertMarkdownToHtml(defaultMarkdown.ToString(), Path.GetDirectoryName(destinationFile), activeConfig.Destination, string.Empty, _relativeLinksOnPage, activeConfig.ConvertLocalLinks);
content = Utils.ConvertMarkdownToHtml(defaultMarkdown.ToString(), Path.GetDirectoryName(destinationFile), activeConfig.Destination, string.Empty, _relativeH2LinksOnPage, activeConfig.ConvertLocalLinks);
}
else
{
// target not found. See if there's a content producer func to produce html for us. If not, we can only conclude an error in the config file.
if (this.ContentProducerFunc == null)
if(this.ContentProducerFunc == null)
{
throw new FileNotFoundException(string.Format("The specified markdown file '{0}' couldn't be found. Aborting", sourceFile));
}
@@ -109,17 +110,17 @@ namespace Docnet
sb.Replace("{{Footer}}", activeConfig.Footer);
sb.Replace("{{TopicTitle}}", this.Name);
sb.Replace("{{Path}}", relativePathToRoot);
sb.Replace("{{RelativeSourceFileName}}", Utils.MakeRelativePathForUri(activeConfig.Destination, sourceFile).TrimEnd('/'));
sb.Replace("{{RelativeTargetFileName}}", Utils.MakeRelativePathForUri(activeConfig.Destination, destinationFile).TrimEnd('/'));
sb.Replace("{{Breadcrumbs}}", activePath.CreateBreadCrumbsHTML(relativePathToRoot));
sb.Replace("{{ToC}}", activePath.CreateToCHTML(relativePathToRoot, activeConfig.MaxLevelInToC));
sb.Replace("{{RelativeSourceFileName}}", Utils.MakeRelativePathForUri(activeConfig.Destination, sourceFile).TrimEnd('/'));
sb.Replace("{{RelativeTargetFileName}}", Utils.MakeRelativePathForUri(activeConfig.Destination, destinationFile).TrimEnd('/'));
sb.Replace("{{Breadcrumbs}}", activePath.CreateBreadCrumbsHTML(relativePathToRoot, pathSpecification));
sb.Replace("{{ToC}}", activePath.CreateToCHTML(relativePathToRoot, pathSpecification));
sb.Replace("{{ExtraScript}}", (this.ExtraScriptProducerFunc == null) ? string.Empty : this.ExtraScriptProducerFunc(this));

// the last action has to be replacing the content marker, so markers in the content which we have in the template as well aren't replaced
sb.Replace("{{Content}}", content);
Utils.CreateFoldersIfRequired(destinationFile);
File.WriteAllText(destinationFile, sb.ToString());
if (!this.IsIndexElement)
if(!this.IsIndexElement)
{
activePath.Pop();
}
@@ -131,14 +132,15 @@ namespace Docnet
/// </summary>
/// <param name="collectedEntries">The collected entries.</param>
/// <param name="activePath">The active path currently navigated.</param>
public override void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath)
/// <param name="pathSpecification">The path specification.</param>
public override void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath, PathSpecification pathSpecification)
{
activePath.Push(this);
// simply convert ourselves into an entry if we're not an index
if (!this.IsIndexElement)
if(!this.IsIndexElement)
{
var toAdd = new SearchIndexEntry();
toAdd.Fill(this.MarkdownFromFile, this.TargetURL, this.Name, activePath);
toAdd.Fill(this.MarkdownFromFile, this.GetTargetURL(pathSpecification), this.Name, activePath);
collectedEntries.Add(toAdd);
}
activePath.Pop();
@@ -150,17 +152,17 @@ namespace Docnet
/// </summary>
/// <param name="navigatedPath">The navigated path to the current element, which doesn't necessarily have to be this element.</param>
/// <param name="relativePathToRoot">The relative path back to the URL root, e.g. ../.., so it can be used for links to elements in this path.</param>
/// <param name="maxLevel">The maximum level.</param>
/// <param name="pathSpecification">The path specification.</param>
/// <returns></returns>
public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, int maxLevel)
public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification)
{
// index elements are rendered in the parent container.
if (this.IsIndexElement)
if(this.IsIndexElement)
{
return string.Empty;
}

return PerformGenerateToCFragment(navigatedPath, relativePathToRoot, maxLevel, null);
return PerformGenerateToCFragment(navigatedPath, relativePathToRoot, pathSpecification);
}


@@ -170,17 +172,16 @@ namespace Docnet
/// </summary>
/// <param name="navigatedPath">The navigated path.</param>
/// <param name="relativePathToRoot">The relative path to root.</param>
/// <param name="maxLevel">The maximum level.</param>
/// <param name="parentHeading">The parent heading.</param>
/// <param name="pathSpecification">The path specification.</param>
/// <returns></returns>
public string PerformGenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, int maxLevel)
public string PerformGenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, PathSpecification pathSpecification)
{
// we can't navigate deeper from here. If we are the element being navigated to, we are the current and will have to emit any additional relative URLs too.
bool isCurrent = navigatedPath.Contains(this);
var fragments = new List<string>();
var liClass = "tocentry";
var aClass = string.Empty;
if (isCurrent)
if(isCurrent)
{
liClass = "tocentry current";
aClass = "current";
@@ -189,71 +190,60 @@ namespace Docnet
string.IsNullOrWhiteSpace(liClass) ? string.Empty : string.Format(" class=\"{0}\"", liClass),
string.IsNullOrWhiteSpace(aClass) ? string.Empty : string.Format(" class=\"{0}\"", aClass),
relativePathToRoot,
HttpUtility.UrlPathEncode(this.TargetURL),
this.GetFinalTargetUrl(pathSpecification),
this.Name));

if (isCurrent)
if(isCurrent && _relativeH2LinksOnPage.Any())
{
var content = PerformGenerateToCFragment(navigatedPath, relativePathToRoot, maxLevel, null);
if (!string.IsNullOrWhiteSpace(content))
// generate relative links
fragments.Add(string.Format("<ul class=\"{0}\">", this.ParentContainer.IsRoot ? "currentrelativeroot" : "currentrelative"));
foreach(var p in _relativeH2LinksOnPage)
{
fragments.Add(content);
fragments.Add(string.Format("<li class=\"tocentry\"><a href=\"#{0}\">{1}</a></li>", p.Item1, p.Item2));
}
fragments.Add("</ul>");
}
else
{
fragments.Add("</li>");
}

fragments.Add("</li>");

return string.Join(Environment.NewLine, fragments.ToArray());
}

private string PerformGenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot, int maxLevel, Heading parentHeading)
/// <summary>
/// Gets the target URL with respect to the <see cref="T:Docnet.PathSpecification" />.
/// </summary>
/// <param name="pathSpecification">The path specification.</param>
/// <returns></returns>
/// <exception cref="System.NotImplementedException"></exception>
public override string GetTargetURL(PathSpecification pathSpecification)
{
var fragments = new List<string>();

var headings = (parentHeading != null) ? parentHeading.Children : _relativeLinksOnPage;
var includedHeadings = headings.Where(x => x.Level > 1 && x.Level <= maxLevel).ToList();
if (includedHeadings.Count > 0)
if (_targetURLForHTML == null)
{
fragments.Add(string.Format("<ul class=\"{0}\">", this.ParentContainer.IsRoot ? "currentrelativeroot" : "currentrelative"));
_targetURLForHTML = (this.Value ?? string.Empty);

// generate relative links
foreach (var heading in includedHeadings)
{
fragments.Add(string.Format("<li class=\"tocentry\"><a href=\"#{0}\">{1}</a></li>", heading.Id, heading.Name));
var toReplace = ".md";
var replacement = ".htm";

var headingContent = PerformGenerateToCFragment(navigatedPath, relativePathToRoot, maxLevel, heading);
if (!string.IsNullOrWhiteSpace(headingContent))
if (pathSpecification == PathSpecification.RelativeAsFolder)
{
if (!IsIndexElement && !_targetURLForHTML.EndsWith("index.md", StringComparison.InvariantCultureIgnoreCase))
{
fragments.Add(headingContent);
replacement = "/index.htm";
}
}

fragments.Add("</ul>");
}

return string.Join(Environment.NewLine, fragments.ToArray());
}


#region Properties
public override string TargetURL
{
get
{
if (_targetURLForHTML == null)
if (_targetURLForHTML.EndsWith(toReplace, StringComparison.InvariantCultureIgnoreCase))
{
_targetURLForHTML = (this.Value ?? string.Empty);
if (_targetURLForHTML.ToLowerInvariant().EndsWith(".md"))
{
_targetURLForHTML = _targetURLForHTML.Substring(0, _targetURLForHTML.Length - 3) + ".htm";
}
_targetURLForHTML = _targetURLForHTML.Replace("\\", "/");
_targetURLForHTML = _targetURLForHTML.Substring(0, _targetURLForHTML.Length - toReplace.Length) + replacement;
}
return _targetURLForHTML;

_targetURLForHTML = _targetURLForHTML.Replace("\\", "/");
}
}

return _targetURLForHTML;
}

#region Properties
/// <summary>
/// Gets / sets a value indicating whether this element is the __index element
/// </summary>


+ 44
- 18
src/MarkdownDeep/MardownDeep.cs View File

@@ -14,6 +14,7 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

@@ -281,8 +282,10 @@ namespace MarkdownDeep
}

// Override to supply the size of an image
public virtual bool OnGetImageSize(string url, bool TitledImage, out int width, out int height)
public virtual bool OnGetImageSize(string url, bool TitledImage, out int width, out int height, out string finalUrl)
{
finalUrl = url;

if (GetImageSizeFunc != null)
{
var info = new ImageInfo() { url = url, titled_image=TitledImage };
@@ -316,30 +319,51 @@ namespace MarkdownDeep
url=url.Substring(1);
}

str=str + "\\" + url.Replace("/", "\\");
var success = false;

// Because PathSpecification.RelativeAsFolder creates an additional layer of directories,
// this trial & error code was implemented to ensure that images could be found
var count = 0;
while (count < 2)
{
//Create an image object from the uploaded file
try
{
var fileName = str + "\\";
var currentUrl = url;

//
for (int i = 0; i < count; i++)
{
currentUrl = "../" + currentUrl;
}

//Create an image object from the uploaded file
try
{
var img = System.Drawing.Image.FromFile(str);
width=img.Width;
height=img.Height;
fileName += currentUrl.Replace("/", "\\");

if (File.Exists(fileName))
{
var img = System.Drawing.Image.FromFile(fileName);
width = img.Width;
height = img.Height;
finalUrl = currentUrl;

if (MaxImageWidth != 0 && width > MaxImageWidth)
{
height = (int)((double)height * (double)MaxImageWidth / (double)width);
width = MaxImageWidth;
}

if (MaxImageWidth != 0 && width>MaxImageWidth)
success = true;
break;
}
}
catch (Exception)
{
height=(int)((double)height * (double)MaxImageWidth / (double)width);
width=MaxImageWidth;
}

return true;
}
catch (Exception)
{
return false;
count++;
}

return success;
}

@@ -388,9 +412,11 @@ namespace MarkdownDeep
}

// Try to determine width and height
var url = tag.attributes["src"];
int width, height;
if (OnGetImageSize(tag.attributes["src"], TitledImage, out width, out height))
if (OnGetImageSize(url, TitledImage, out width, out height, out url))
{
tag.attributes["src"] = url;
tag.attributes["width"] = width.ToString();
tag.attributes["height"] = height.ToString();
}


Loading…
Cancel
Save