Browse Source

Changes after code review

pull/58/head
Geert van Horrik 8 years ago
parent
commit
f516b9378e
1 changed files with 308 additions and 321 deletions
  1. +308
    -321
      src/DocNet/NavigationLevel.cs

+ 308
- 321
src/DocNet/NavigationLevel.cs View File

@@ -31,325 +31,312 @@ using Newtonsoft.Json.Linq;

namespace Docnet
{
public class NavigationLevel : NavigationElement<List<INavigationElement>>
{
private readonly string _rootDirectory;

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)
{
INavigationElement toAdd;
if (child.Value.Type == JTokenType.String)
{
var nameToUse = child.Key;

var isIndexElement = child.Key == "__index";
if (isIndexElement)
{
nameToUse = this.Name;
}

var childValue = child.Value.ToObject<string>();

var endsWithWildcards = childValue.EndsWith("**");
if (endsWithWildcards)
{
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(_rootDirectory)
{
Name = child.Key,
IsRoot = false
};
subLevel.Load((JObject)child.Value);
toAdd = subLevel;
}
toAdd.ParentContainer = this;
this.Value.Add(toAdd);
}
}


/// <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 override void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath)
{
activePath.Push(this);
foreach (var element in this.Value)
{
element.CollectSearchIndexEntries(collectedEntries, activePath);
}
activePath.Pop();
}


/// <summary>
/// Generates the output for this navigation element
/// </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)
{
activePath.Push(this);
int i = 0;
while (i < this.Value.Count)
{
var element = this.Value[i];
element.GenerateOutput(activeConfig, activePath);
i++;
}
activePath.Pop();
}


/// <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>
/// <returns></returns>
public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot)
{
var fragments = new List<string>();
if (!this.IsRoot)
{
fragments.Add("<li class=\"tocentry\">");
}
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)
{
fragments.Add("<ul class=\"current\">");
}
else
{
fragments.Add("<ul>");
}

// 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)
{
fragments.Add(string.Format("{0}{1}</span></li>", elementStartTag, this.Name));
}
else
{
if (this.IsRoot)
{
fragments.Add(indexElement.PerformGenerateToCFragment(navigatedPath, relativePathToRoot));
}
else
{
fragments.Add(string.Format("{0}<a href=\"{1}{2}\">{3}</a></span></li>", elementStartTag, relativePathToRoot, HttpUtility.UrlPathEncode(indexElement.TargetURL),
this.Name));
}
}
// then the elements in the container. Index elements are skipped here.
foreach (var element in this.Value)
{
fragments.Add(element.GenerateToCFragment(navigatedPath, relativePathToRoot));
}
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));
}
if (!this.IsRoot)
{
fragments.Add("</li>");
}
return string.Join(Environment.NewLine, fragments.ToArray());
}

private NavigationLevel CreateGeneratedLevel(string path)
{
var root = new NavigationLevel(_rootDirectory)
{
ParentContainer = this
};

foreach (var mdFile in Directory.GetFiles(path, "*.md", SearchOption.TopDirectoryOnly))
{
var name = FindTitleInMdFile(mdFile);
if (string.IsNullOrWhiteSpace(name))
{
continue;
}

var relativeFilePath = GetRelativePath(_rootDirectory, mdFile);

var item = new SimpleNavigationElement
{
Name = name,
Value = relativeFilePath,
ParentContainer = root
};

root.Value.Add(item);
}

foreach (var directory in Directory.GetDirectories(path, "*", SearchOption.TopDirectoryOnly))
{
var directoryInfo = new DirectoryInfo(directory);

var subDirectoryNavigationElement = CreateGeneratedLevel(directory);

subDirectoryNavigationElement.Name = directoryInfo.Name;
subDirectoryNavigationElement.ParentContainer = root;

root.Value.Add(subDirectoryNavigationElement);
}

return root;
}

private string GetRelativePath(string origin, string fullPath)
{
var pathUri = new Uri(fullPath);

if (!origin.EndsWith(Path.DirectorySeparatorChar.ToString()))
{
origin += Path.DirectorySeparatorChar;
}

var folderUri = new Uri(origin);
return Uri.UnescapeDataString(folderUri.MakeRelativeUri(pathUri).ToString().Replace('/', Path.DirectorySeparatorChar));
}

private string FindTitleInMdFile(string path)
{
var title = string.Empty;

using (var fileStream = File.OpenRead(path))
{
using (var streamReader = new StreamReader(fileStream))
{
var line = string.Empty;

while (string.IsNullOrWhiteSpace(line))
{
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;
}
}
}
}
}

return title;
}


#region Properties
public override string TargetURL
{
get
{
var defaultElement = this.IndexElement;
if (defaultElement == null)
{
return string.Empty;
}
return defaultElement.TargetURL ?? string.Empty;
}
}


public SimpleNavigationElement IndexElement
{
get
{
var toReturn = this.Value.FirstOrDefault(e => e.IsIndexElement) as SimpleNavigationElement;
if (toReturn == null)
{
// no index element, add an artificial one.
var path = string.Empty;
if (this.ParentContainer != null)
{
path = Path.GetDirectoryName(this.ParentContainer.TargetURL);
}
var nameToUse = this.Name.Replace(".", "").Replace('/', '_').Replace("\\", "_").Replace(":", "").Replace(" ", "");
if (string.IsNullOrWhiteSpace(nameToUse))
{
return null;
}
toReturn = new SimpleNavigationElement() { ParentContainer = this, Value = string.Format("{0}{1}.md", path, nameToUse), Name = this.Name, IsIndexElement = true };
this.Value.Add(toReturn);
}

return toReturn;
}
}


/// <summary>
/// Gets / sets a value indicating whether this element is the __index element
/// </summary>
public override bool IsIndexElement
{
// never an index
get { return false; }
set
{
// nop;
}
}


public bool IsRoot { get; set; }
#endregion
}
public class NavigationLevel : NavigationElement<List<INavigationElement>>
{
#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)
{
INavigationElement toAdd;
if (child.Value.Type == JTokenType.String)
{
var nameToUse = child.Key;

var isIndexElement = child.Key == "__index";
if (isIndexElement)
{
nameToUse = this.Name;
}

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(_rootDirectory)
{
Name = child.Key,
IsRoot = false
};
subLevel.Load((JObject)child.Value);
toAdd = subLevel;
}
toAdd.ParentContainer = this;
this.Value.Add(toAdd);
}
}


/// <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 override void CollectSearchIndexEntries(List<SearchIndexEntry> collectedEntries, NavigatedPath activePath)
{
activePath.Push(this);
foreach (var element in this.Value)
{
element.CollectSearchIndexEntries(collectedEntries, activePath);
}
activePath.Pop();
}


/// <summary>
/// Generates the output for this navigation element
/// </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)
{
activePath.Push(this);
int i = 0;
while (i < this.Value.Count)
{
var element = this.Value[i];
element.GenerateOutput(activeConfig, activePath);
i++;
}
activePath.Pop();
}


/// <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>
/// <returns></returns>
public override string GenerateToCFragment(NavigatedPath navigatedPath, string relativePathToRoot)
{
var fragments = new List<string>();
if (!this.IsRoot)
{
fragments.Add("<li class=\"tocentry\">");
}
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)
{
fragments.Add("<ul class=\"current\">");
}
else
{
fragments.Add("<ul>");
}

// 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)
{
fragments.Add(string.Format("{0}{1}</span></li>", elementStartTag, this.Name));
}
else
{
if (this.IsRoot)
{
fragments.Add(indexElement.PerformGenerateToCFragment(navigatedPath, relativePathToRoot));
}
else
{
fragments.Add(string.Format("{0}<a href=\"{1}{2}\">{3}</a></span></li>", elementStartTag, relativePathToRoot, HttpUtility.UrlPathEncode(indexElement.TargetURL),
this.Name));
}
}
// then the elements in the container. Index elements are skipped here.
foreach (var element in this.Value)
{
fragments.Add(element.GenerateToCFragment(navigatedPath, relativePathToRoot));
}
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));
}
if (!this.IsRoot)
{
fragments.Add("</li>");
}
return string.Join(Environment.NewLine, fragments.ToArray());
}

private NavigationLevel CreateGeneratedLevel(string path)
{
var root = new NavigationLevel(_rootDirectory)
{
ParentContainer = this
};

foreach (var mdFile in Directory.GetFiles(path, "*.md", SearchOption.TopDirectoryOnly))
{
var name = FindTitleInMdFile(mdFile);
if (string.IsNullOrWhiteSpace(name))
{
continue;
}

var relativeFilePath = Utils.MakeRelativePath(mdFile, _rootDirectory);

var item = new SimpleNavigationElement
{
Name = name,
Value = relativeFilePath,
ParentContainer = root
};

root.Value.Add(item);
}

foreach (var directory in Directory.GetDirectories(path, "*", SearchOption.TopDirectoryOnly))
{
var directoryInfo = new DirectoryInfo(directory);

var subDirectoryNavigationElement = CreateGeneratedLevel(directory);

subDirectoryNavigationElement.Name = directoryInfo.Name;
subDirectoryNavigationElement.ParentContainer = root;

root.Value.Add(subDirectoryNavigationElement);
}

return root;
}

private string FindTitleInMdFile(string path)
{
var title = string.Empty;

using (var fileStream = File.OpenRead(path))
{
using (var streamReader = new StreamReader(fileStream))
{
var line = string.Empty;

while (string.IsNullOrWhiteSpace(line))
{
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;
}
}
}
}
}

return title;
}


#region Properties
public override string TargetURL
{
get
{
var defaultElement = this.IndexElement;
if (defaultElement == null)
{
return string.Empty;
}
return defaultElement.TargetURL ?? string.Empty;
}
}


public SimpleNavigationElement IndexElement
{
get
{
var toReturn = this.Value.FirstOrDefault(e => e.IsIndexElement) as SimpleNavigationElement;
if (toReturn == null)
{
// no index element, add an artificial one.
var path = string.Empty;
if (this.ParentContainer != null)
{
path = Path.GetDirectoryName(this.ParentContainer.TargetURL);
}
var nameToUse = this.Name.Replace(".", "").Replace('/', '_').Replace("\\", "_").Replace(":", "").Replace(" ", "");
if (string.IsNullOrWhiteSpace(nameToUse))
{
return null;
}
toReturn = new SimpleNavigationElement() { ParentContainer = this, Value = string.Format("{0}{1}.md", path, nameToUse), Name = this.Name, IsIndexElement = true };
this.Value.Add(toReturn);
}

return toReturn;
}
}


/// <summary>
/// Gets / sets a value indicating whether this element is the __index element
/// </summary>
public override bool IsIndexElement
{
// never an index
get { return false; }
set
{
// nop;
}
}


public bool IsRoot { get; set; }
#endregion
}
}

Loading…
Cancel
Save