Related
This references my last question which appears to have been abandoned. I am experiencing an odd "bug" if you will with C# and MS VS 2015. To reproduce the error, follow the steps:
Open console app project and copy paste code below.
Set a break point here:
First run code past break point, it works! :D
Then run code again but this time STOP at the break point and DRAG the executing statement cursor INTO the if statement from here:
to here:
Hit Continue and an NRE exception is thrown. Why does this happen? Is it just me? What is the technical explination for this?
CODE:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace testapp
{
class Program
{
static void Main(string[] args)
{
FILECollection randomCollection = new FILECollection();
// Fill with junk test data:
for(int i = 0; i<10; i++)
{
FILE junkfile = new FILE() { fileName = i.ToString(), folderName = i.ToString(), fileHashDigest = new byte[1] };
randomCollection.Add(junkfile);
}
if (true)
{
Console.WriteLine("testing this weird exception issue...");
FILE test;
test = new FILE();
test.fileName = "3";
test.folderName = "3";
test.fileHashDigest = new byte[1];
FILE exists = randomCollection.Where(f => f.fileName == test.fileName &&
f.fileHashDigest.SequenceEqual(test.fileHashDigest)).First();
}
}
}
public class FILE
{
public FILE() { _fileName = "";}
private string _fileName;
public string fileName
{
get
{
if (false)
return this._fileName.ToUpper();
else
return this._fileName;
}
set
{
if (false)
this._fileName = value.ToUpper();
else
this._fileName = value;
}
}
public string folderName { get; set; }
public byte[] fileHashDigest { get; set; }
}
public class FILECollection : IEnumerable<FILE>, ICollection<FILE>
{
private HashSet<FILE> svgHash;
private static List<FILE> PreallocationList;
public string FileName = "N/A";
/// <summary>
/// Default Constructor, will not
/// preallocate memory.
/// </summary>
/// <param name="PreallocationSize"></param>
public FILECollection()
{
this.svgHash = new HashSet<FILE>();
this.svgHash.Clear();
}
/// <summary>
/// Overload Constructor Preallocates
/// memory to be used for the new
/// FILE Collection.
/// </summary>
public FILECollection(int PreallocationSize, string fileName = "N/A", int fileHashDigestSize = 32)
{
FileName = fileName;
PreallocationList = new List<FILE>(PreallocationSize);
for (int i = 0; i <= PreallocationSize; i++)
{
byte[] buffer = new byte[fileHashDigestSize];
FILE preallocationSVG = new FILE()
{
fileName = "",
folderName = "",
fileHashDigest = buffer
};
PreallocationList.Add(preallocationSVG);
}
this.svgHash = new HashSet<FILE>(PreallocationList);
this.svgHash.Clear(); // Capacity remains unchanged until a call to TrimExcess is made.
}
/// <summary>
/// Add an FILE file to
/// the FILE Collection.
/// </summary>
/// <param name="svg"></param>
public void Add(FILE svg)
{
this.svgHash.Add(svg);
}
/// <summary>
/// Removes all elements
/// from the FILE Collection
/// </summary>
public void Clear()
{
svgHash.Clear();
}
/// <summary>
/// Determine if the FILE collection
/// contains the EXACT FILE file, folder,
/// and byte[] sequence. This guarantees
/// that the collection contains the EXACT
/// file you are looking for.
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool Contains(FILE item)
{
return svgHash.Any(f => f.fileHashDigest.SequenceEqual(item.fileHashDigest) &&
f.fileName == item.fileName &&
f.folderName == item.folderName);
}
/// <summary>
/// Determine if the FILE collection
/// contains the same file and folder name,
/// byte[] sequence is not compared. The file and folder
/// name may be the same but this does not guarantee the
/// file contents are exactly the same. Use Contains() instead.
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool ContainsPartially(FILE item)
{
return svgHash.Any(f => f.fileName == item.fileName &&
f.folderName == item.folderName);
}
/// <summary>
/// Returns the total number
/// of FILE files in the Collection.
/// </summary>
public int Count
{ get { return svgHash.Count(); } }
public bool IsReadOnly
{ get { return true; } }
public void CopyTo(FILE[] array, int arrayIndex)
{
svgHash.CopyTo(array, arrayIndex);
}
public bool Remove(FILE item)
{
return svgHash.Remove(item);
}
public IEnumerator<FILE> GetEnumerator()
{
return svgHash.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return svgHash.GetEnumerator();
}
}
}
I think either I am debugging in a terribly wrong way, or Microsoft should take a look at this. It's like future code is breaking current code...which is impossible!
OK here's my best guess..
First, as I mentioned in the comments, the exception doesn't occur if you comment out the line FILE exists = randomCollection.Where(f => f.fileName == test.fileName && f.fileHashDigest.SequenceEqual(test.fileHashDigest)).First();
Second, I noticed the same behavior can be reproduced with the following code:
if (true)
{
object o;
o = new object();
Func<bool> m = () => o == null;
}
i.e. the cause seems to be related to the variable being used in a lambda expression. So, looking at the same code snippet above in ILSpy I get the following:
Program.<>c__DisplayClass0_0 <>c__DisplayClass0_ = new Program.<>c__DisplayClass0_0();
<>c__DisplayClass0_.o = new object();
Func<bool> func = new Func<bool>(<>c__DisplayClass0_.<Main>b__0);
so my best guess is that the NullReferenceException refers to <>c__DisplayClass0_ intance being null - and I'm therefore inclined to believe that the stepping through the if(true) actually skipped the first line where <>c__DisplayClass0_ is instantiated
I'm working on Developing a Web-API project, and i'm very impressed with the auto generated documentation by Microsoft's HelpPages.
i enabled custom documentation using the official site creating Help Pages
the documentation is generated successfully BUT none of the references to Classes from the <See cref=""> Tag seems to be added to the description, the HelpPages Simply ignores them (that's for a reason).
i really wanted to have this feature in my project, i searched a lot (got close sometimes) but none gave a convincing answer.
That's why i decided to post my solution to this tweak and hopefully benefit other programmers and spare them some time and effort.
(my answer is in the replies below)
my solution is the following:
you've got your custom documentation (from the generated xml file) working
enable HTML and XML tags within the documentation, they normally get filtered out, thanks this Post you can preserve them.
simply go to: ProjectName > Areas > HelpPage > XmlDocumentationProvider.cs
on line 123 in method: GetTagValue(XPathNavigator parentNode, string tagName)
change the code return node.Value.Trim(); to return node.InnerXml;
create the following partial view:
ProjectName\Areas\HelpPage\Views\Help**_XML_SeeTagsRenderer.cshtml**
this is my code:
#using System.Web.Http;
#using MyProject.Areas.HelpPage.Controllers;
#using MyProject.Areas.HelpPage;
#using MyProject.Areas.HelpPage.ModelDescriptions
#using System.Text.RegularExpressions
#model string
#{
int #index = 0;
string #xml = Model;
if (#xml == null)
#xml = "";
Regex #seeRegex = new Regex("<( *)see( +)cref=\"([^\"]):([^\"]+)\"( *)/>");//Regex("<see cref=\"T:([^\"]+)\" />");
Match #xmlSee = #seeRegex.Match(#xml);
string #typeAsText = "";
Type #tp;
ModelDescriptionGenerator modelDescriptionGenerator = (new HelpController()).Configuration.GetModelDescriptionGenerator();
}
#if (xml !="" && xmlSee != null && xmlSee.Length > 0)
{
while (xmlSee != null && xmlSee.Length > 0)
{
#MvcHtmlString.Create(#xml.Substring(#index, #xmlSee.Index - #index))
int startingIndex = xmlSee.Value.IndexOf(':')+1;
int endIndex = xmlSee.Value.IndexOf('"', startingIndex);
typeAsText = xmlSee.Value.Substring(startingIndex, endIndex - startingIndex); //.Replace("<see cref=\"T:", "").Replace("\" />", "");
System.Reflection.Assembly ThisAssembly = typeof(ThisProject.Controllers.HomeController).Assembly;
tp = ThisAssembly.GetType(#typeAsText);
if (tp == null)//try another referenced project
{
System.Reflection.Assembly externalAssembly = typeof(MyExternalReferncedProject.AnyClassInIt).Assembly;
tp = externalAssembly.GetType(#typeAsText);
}
if (tp == null)//also another referenced project- as needed
{
System.Reflection.Assembly anotherExtAssembly = typeof(MyExternalReferncedProject2.AnyClassInIt).Assembly;
tp = anotherExtAssembly .GetType(#typeAsText);
}
if(tp == null)//case of nested class
{
System.Reflection.Assembly thisAssembly = typeof(ThisProject.Controllers.HomeController).Assembly;
//the below code is done to support detecting nested classes.
var processedTypeString = typeAsText;
var lastIndexofPoint = typeAsText.LastIndexOf('.');
while (lastIndexofPoint > 0 && tp == null)
{
processedTypeString = processedTypeString.Insert(lastIndexofPoint, "+").Remove(lastIndexofPoint + 1, 1);
tp = SPLocatorBLLAssembly.GetType(processedTypeString);//nested class are recognized as: namespace.outerClass+nestedClass
lastIndexofPoint = processedTypeString.LastIndexOf('.');
}
}
if (#tp != null)
{
ModelDescription md = modelDescriptionGenerator.GetOrCreateModelDescription(tp);
#Html.DisplayFor(m => md.ModelType, "ModelDescriptionLink", new { modelDescription = md })
}
else
{
#MvcHtmlString.Create(#typeAsText)
}
index = xmlSee.Index + xmlSee.Length;
xmlSee = xmlSee.NextMatch();
}
#MvcHtmlString.Create(#xml.Substring(#index, #xml.Length - #index))
}
else
{
#MvcHtmlString.Create(#xml);
}
Finally Go to: ProjectName\Areas\HelpPage\Views\Help\DisplayTemplates**Parameters.cshtml**
at line '20' we have the code corresponding to the Description in the documentation.
REPLACE this:
<td class="parameter-documentation">
<p>
#parameter.Documentation
</p>
</td>
With THIS:
<td class="parameter-documentation">
<p>
#Html.Partial("_XML_SeeTagsRenderer", (#parameter.Documentation == null? "" : #parameter.Documentation.ToString()))
</p>
</td>
& Voila you must have it working now.
Notes:
i tried putting HTML list inside the docs and it rendered it fine
i tried multiple class references (multiple <see cref="MyClass"> and i worked fine
you can't refer to a class that is declared within a class
when you refer to a class that is outside the current project please add the assembly .getType of a class within that project (check my code above)
any un-found class found inside a <see cref> will have it's full name printed in the description (for example if you reference a property or namespace, the code won't identify it as a type/class but it will be printed)
I have implemented class which process xml documentation block and changes documentation tags to html tags.
/// <summary>
/// Reprensets xml help block converter interface.
/// </summary>
public class HelpBlockRenderer : IHelpBlockRenderer
{
/// <summary>
/// Stores regex to parse <c>See</c> tag.
/// </summary>
private static readonly Regex regexSeeTag = new Regex("<( *)see( +)cref=\"(?<prefix>[^\"]):(?<member>[^\"]+)\"( *)/>",
RegexOptions.IgnoreCase);
/// <summary>
/// Stores the pair tag coversion dictionary.
/// </summary>
private static readonly Dictionary<string, string> pairedTagConvertsion = new Dictionary<string, string>()
{
{ "para", "p" },
{ "c", "b" }
};
/// <summary>
/// Stores configuration.
/// </summary>
private HttpConfiguration config;
/// <summary>
/// Initializes a new instance of the <see cref="HelpBlockRenderer"/> class.
/// </summary>
/// <param name="config">The configuration.</param>
public HelpBlockRenderer(HttpConfiguration config)
{
this.config = config;
}
/// <summary>
/// Initializes a new instance of the <see cref="HelpBlockRenderer"/> class.
/// </summary>
public HelpBlockRenderer()
: this(GlobalConfiguration.Configuration)
{
}
/// <summary>
/// Renders specified xml help block to valid html content.
/// </summary>
/// <param name="helpBlock">The help block.</param>
/// <param name="urlHelper">The url helper for link building.</param>
/// <returns>The html content.</returns>
public HtmlString RenderHelpBlock(string helpBlock, UrlHelper urlHelper)
{
if (string.IsNullOrEmpty(helpBlock))
{
return new HtmlString(string.Empty);
}
string result = helpBlock;
result = this.RenderSeeTag(result, urlHelper);
result = this.RenderPairedTags(result);
return new HtmlString(result);
}
/// <summary>
/// Process <c>See</c> tag.
/// </summary>
/// <param name="helpBlock">Hte original help block string.</param>
/// <param name="urlHelper">The url helper for link building.</param>
/// <returns>The html content.</returns>
private string RenderSeeTag(string helpBlock, UrlHelper urlHelper)
{
string result = helpBlock;
Match match = null;
while ((match = HelpBlockRenderer.regexSeeTag.Match(result)).Success)
{
var originalValues = match.Value;
var prefix = match.Groups["prefix"].Value;
var anchorText = string.Empty;
var link = string.Empty;
switch (prefix)
{
case "T":
{
// if See tag has reference to type, then get value from member regex group.
var modelType = match.Groups["member"].Value;
anchorText = modelType.Substring(modelType.LastIndexOf(".") + 1);
link = urlHelper.Action("ResourceModel", "Help",
new
{
modelName = anchorText,
area = "ApiHelpPage"
});
break;
}
case "M":
{
// Check that specified type member is API member.
var apiDescriptor = this.GetApiDescriptor(match.Groups["member"].Value);
if (apiDescriptor != null)
{
anchorText = apiDescriptor.ActionDescriptor.ActionName;
link = urlHelper.Action("Api", "Help",
new
{
apiId = ApiDescriptionExtensions.GetFriendlyId(apiDescriptor),
area = "ApiHelpPage"
});
}
else
{
// Web API Help can generate help only for whole API model,
// So, in case if See tag contains link to model member, replace link with link to model class.
var modelType = match.Groups["member"].Value.Substring(0, match.Groups["member"].Value.LastIndexOf("."));
anchorText = modelType.Substring(modelType.LastIndexOf(".") + 1);
link = urlHelper.Action("ResourceModel", "Help",
new
{
modelName = anchorText,
area = "ApiHelpPage"
});
}
break;
}
default:
{
anchorText = match.Groups["member"].Value;
// By default link will be rendered with empty anrchor.
link = "#";
break;
}
}
// Build the anchor.
var anchor = string.Format("{1}", link, anchorText);
result = result.Replace(originalValues, anchor);
}
return result;
}
/// <summary>
/// Converts original help paired tags to html tags.
/// </summary>
/// <param name="helpBlock">The help block.</param>
/// <returns>The html content.</returns>
private string RenderPairedTags(string helpBlock)
{
var result = helpBlock;
foreach (var key in HelpBlockRenderer.pairedTagConvertsion.Keys)
{
Regex beginTagRegex = new Regex(string.Format("<{0}>", key), RegexOptions.IgnoreCase);
Regex endTagRegex = new Regex(string.Format("</{0}>", key), RegexOptions.IgnoreCase);
result = beginTagRegex.Replace(result, string.Format("<{0}>", HelpBlockRenderer.pairedTagConvertsion[key]));
result = endTagRegex.Replace(result, string.Format("</{0}>", HelpBlockRenderer.pairedTagConvertsion[key]));
}
return result;
}
/// <summary>
/// Gets the api descriptor by specified member name.
/// </summary>
/// <param name="member">The member fullname.</param>
/// <returns>The api descriptor.</returns>
private ApiDescription GetApiDescriptor(string member)
{
Regex controllerActionRegex = new Regex("[a-zA-Z0-9\\.]+\\.(?<controller>[a-zA-Z0-9]+)Controller\\.(?<action>[a-zA-Z0-9]+)\\(.*\\)");
var match = controllerActionRegex.Match(member);
if (match.Success)
{
var controller = match.Groups["controller"].Value;
var action = match.Groups["action"].Value;
var descriptions = this.config.Services.GetApiExplorer().ApiDescriptions;
return descriptions.FirstOrDefault(x => x.ActionDescriptor.ActionName.Equals(action) &&
x.ActionDescriptor.ControllerDescriptor.ControllerName == controller);
}
return null;
}
}
To use it, you will need to change XmlDocumentationProvider class:
private static string GetTagValue(XPathNavigator parentNode, string tagName)
{
if (parentNode != null)
{
XPathNavigator node = parentNode.SelectSingleNode(tagName);
if (node != null)
{
return node.InnerXml;
}
}
return null;
}
And then I wrote extension class to use this class directly from view:
/// <summary>
/// Represents html help content extension class.
/// Contains methods to convert Xml help blocks to html string.
/// </summary>
public static class HtmlHelpContentExtensions
{
/// <summary>
/// Converts help block in xml format to html string with proper tags, links and etc.
/// </summary>
/// <param name="helpBlock">The help block content.</param>
/// <param name="urlHelper">The url helper for link building.</param>
/// <returns>The resulting html string.</returns>
public static HtmlString ToHelpContent(this string helpBlock, UrlHelper urlHelper)
{
// Initialize your rendrer here or take it from IoC
return renderer.RenderHelpBlock(helpBlock, urlHelper);
}
}
And finally, for example in Parameters.cshtml:
<td class="parameter-documentation">
<p>#parameter.Documentation.ToHelpContent(Url)</p>
#if(!string.IsNullOrEmpty(parameter.Remarks))
{
<p>#parameter.Remarks.ToHelpContent(Url)</p>
}
</td>
I can't deal with a regular expression to separate the argument from function.
The function takes arguments in following way:
FunctionName(arg1;arg2;...;argn)
Now to make the rest of my code work I need to do the following-put every argument in ():
FunctionName((arg1);(arg2);(arg3))
The problem is that the arg can be anything- a number, an operator, other function
The test code for the solution is:
The function before regexp:
Function1((a1^5-4)/2;1/sin(a2);a3;a4)+Function2(a1;a2;1/a3)
After i needd to get sth like this:
Function1(((a1^5-4)/2);(1/sin(a2));(a3);(a4))+Function2((a1);(a2);(1/a3))
Unless I'm missing something, isn't it as simple as replacing ; with );( and surrounding the whole thing in ( ) ?
Using Regex:
(?:([^;()]+);?)+
and LINQ:
string result = "FunctionName(" +
String.Join(";",
from Capture capture in
Regex.Matches(inputString, #"FunctionName\((?:([^;()]+);?)+\)")[0].Groups[1].
Captures
select "(" + capture.Value + ")") + ")";
This is a far cry from a Regex but the potential for nested functions combined with the fact that this is a structured language being modified that a lexer/parser scheme is more appropriate.
Here is an example of a system that processes things of this nature
First, we define something that can be located in the input (the expression to modify)
public interface ISourcePart
{
/// <summary>
/// Gets the string representation of the kind of thing we're working with
/// </summary>
string Kind { get; }
/// <summary>
/// Gets the position this information is found at in the original source
/// </summary>
int Position { get; }
/// <summary>
/// Gets a representation of this data as Token objects
/// </summary>
/// <returns>An array of Token objects representing the data</returns>
Token[] AsTokens();
}
Next, we'll define a construct for housing tokens (identifiable portions of the source text)
public class Token : ISourcePart
{
public int Position { get; set; }
public Token[] AsTokens()
{
return new[] {this};
}
public string Kind { get; set; }
/// <summary>
/// Gets or sets the value of the token
/// </summary>
public string Value { get; set; }
/// <summary>
/// Creates a new Token
/// </summary>
/// <param name="kind">The kind (name) of the token</param>
/// <param name="match">The Match the token is to be generated from</param>
/// <param name="index">The offset from the beginning of the file the index of the match is relative to</param>
/// <returns>The newly created token</returns>
public static Token Create(string kind, Match match, int index)
{
return new Token
{
Position = match.Index + index,
Kind = kind,
Value = match.Value
};
}
/// <summary>
/// Creates a new Token
/// </summary>
/// <param name="kind">The kind (name) of the token</param>
/// <param name="value">The value to assign to the token</param>
/// <param name="position">The absolute position in the source file the value is located at</param>
/// <returns>The newly created token</returns>
public static Token Create(string kind, string value, int position)
{
return new Token
{
Kind = kind,
Value = value,
Position = position
};
}
}
We'll use Regexes to find our tokens in this example (below - Excerpt from Program.cs in my demo project).
/// <summary>
/// Breaks an input string into recognizable tokens
/// </summary>
/// <param name="source">The input string to break up</param>
/// <returns>The set of tokens located within the string</returns>
static IEnumerable<Token> Tokenize(string source)
{
var tokens = new List<Token>();
var sourceParts = new[] { new KeyValuePair<string, int>(source, 0) };
tokens.AddRange(Tokenize(OpenParen, "\\(", ref sourceParts));
tokens.AddRange(Tokenize(CloseParen, "\\)", ref sourceParts));
tokens.AddRange(Tokenize(Semi, ";", ref sourceParts));
tokens.AddRange(Tokenize(Operator, "[\\^\\\\*\\+\\-/]", ref sourceParts));
tokens.AddRange(Tokenize(Literal, "\\w+", ref sourceParts));
return tokens.OrderBy(x => x.Position);
}
As you can see, I've defined patterns for open and close parenthesis, semicolons, basic math operators and letters and numbers.
The Tokenize method is defined as follows (again from Program.cs in my demo project)
/// <summary>
/// Performs tokenization of a collection of non-tokenized data parts with a specific pattern
/// </summary>
/// <param name="tokenKind">The name to give the located tokens</param>
/// <param name="pattern">The pattern to use to match the tokens</param>
/// <param name="untokenizedParts">The portions of the input that have yet to be tokenized (organized as text vs. position in source)</param>
/// <returns>The set of tokens matching the given pattern located in the untokenized portions of the input, <paramref name="untokenizedParts"/> is updated as a result of this call</returns>
static IEnumerable<Token> Tokenize(string tokenKind, string pattern, ref KeyValuePair<string, int>[] untokenizedParts)
{
//Do a bit of setup
var resultParts = new List<KeyValuePair<string, int>>();
var resultTokens = new List<Token>();
var regex = new Regex(pattern);
//Look through all of our currently untokenized data
foreach (var part in untokenizedParts)
{
//Find all of our available matches
var matches = regex.Matches(part.Key).OfType<Match>().ToList();
//If we don't have any, keep the data as untokenized and move to the next chunk
if (matches.Count == 0)
{
resultParts.Add(part);
continue;
}
//Store the untokenized data in a working copy and save the absolute index it reported itself at in the source file
var workingPart = part.Key;
var index = part.Value;
//Look through each of the matches that were found within this untokenized segment
foreach (var match in matches)
{
//Calculate the effective start of the match within the working copy of the data
var effectiveStart = match.Index - (part.Key.Length - workingPart.Length);
resultTokens.Add(Token.Create(tokenKind, match, part.Value));
//If we didn't match at the beginning, save off the first portion to the set of untokenized data we'll give back
if (effectiveStart > 0)
{
var value = workingPart.Substring(0, effectiveStart);
resultParts.Add(new KeyValuePair<string, int>(value, index));
}
//Get rid of the portion of the working copy we've already used
if (match.Index + match.Length < part.Key.Length)
{
workingPart = workingPart.Substring(effectiveStart + match.Length);
}
else
{
workingPart = string.Empty;
}
//Update the current absolute index in the source file we're reporting to be at
index += effectiveStart + match.Length;
}
//If we've got remaining data in the working copy, add it back to the untokenized data
if (!string.IsNullOrEmpty(workingPart))
{
resultParts.Add(new KeyValuePair<string, int>(workingPart, index));
}
}
//Update the untokenized data to contain what we couldn't process with this pattern
untokenizedParts = resultParts.ToArray();
//Return the tokens we were able to extract
return resultTokens;
}
Now that we've got the methods and types in place to handle our tokenized data, we need to be able to recognize pieces of larger meaning, like calls to simple functions (like sin(x)), complex functions (like Function1(a1;a2;a3)), basic mathematical operations (like +, -, *, etc.), and so on. We'll make a simple parser for dealing with that; firstly we'll define a match condition for a parse node.
public class ParseNodeDefinition
{
/// <summary>
/// The set of parse node definitions that could be transitioned to from this one
/// </summary>
private readonly IList<ParseNodeDefinition> _nextNodeOptions;
/// <summary>
/// Creates a new ParseNodeDefinition
/// </summary>
private ParseNodeDefinition()
{
_nextNodeOptions = new List<ParseNodeDefinition>();
}
/// <summary>
/// Gets whether or not this definition is an acceptable ending point for the parse tree
/// </summary>
public bool IsValidEnd { get; private set; }
/// <summary>
/// Gets the name an item must have for it to be matched by this definition
/// </summary>
public string MatchItemsNamed { get; private set; }
/// <summary>
/// Gets the set of parse node definitions that could be transitioned to from this one
/// </summary>
public IEnumerable<ParseNodeDefinition> NextNodeOptions
{
get { return _nextNodeOptions; }
}
/// <summary>
/// Gets or sets the tag that will be associated with the data if matched
/// </summary>
public string Tag { get; set; }
/// <summary>
/// Creates a new ParseNodeDefinition matching items with the specified name/kind.
/// </summary>
/// <param name="matchItemsNamed">The name of the item to be matched</param>
/// <param name="tag">The tag to associate with matched items</param>
/// <param name="isValidEnd">Whether or not the element is a valid end to the parse tree</param>
/// <returns>A ParseNodeDefinition capable of matching items of the given name</returns>
public static ParseNodeDefinition Create(string matchItemsNamed, string tag, bool isValidEnd)
{
return new ParseNodeDefinition { MatchItemsNamed = matchItemsNamed, Tag = tag, IsValidEnd = isValidEnd };
}
public ParseNodeDefinition AddOption(string matchItemsNamed)
{
return AddOption(matchItemsNamed, string.Empty, false);
}
public ParseNodeDefinition AddOption(string matchItemsNamed, string tag)
{
return AddOption(matchItemsNamed, tag, false);
}
/// <summary>
/// Adds an option for a named node to follow this one in the parse tree the node is a part of
/// </summary>
/// <param name="matchItemsNamed">The name of the item to be matched</param>
/// <param name="tag">The tag to associate with matched items</param>
/// <param name="isValidEnd">Whether or not the element is a valid end to the parse tree</param>
/// <returns>The ParseNodeDefinition that has been added</returns>
public ParseNodeDefinition AddOption(string matchItemsNamed, string tag, bool isValidEnd)
{
var node = Create(matchItemsNamed, tag, isValidEnd);
_nextNodeOptions.Add(node);
return node;
}
public ParseNodeDefinition AddOption(string matchItemsNamed, bool isValidEnd)
{
return AddOption(matchItemsNamed, string.Empty, isValidEnd);
}
/// <summary>
/// Links the given node as an option for a state to follow this one in the parse tree this node is a part of
/// </summary>
/// <param name="next">The node to add as an option</param>
public void LinkTo(ParseNodeDefinition next)
{
_nextNodeOptions.Add(next);
}
}
This will let us match a single element by name (whether it's a ParseTree defined later) or a Token as they both implement the ISourcePart interface. Next we'll make a ParseTreeDefinition that allows us to specify sequences of ParseNodeDefinitions for matching.
public class ParseTreeDefinition
{
/// <summary>
/// The set of parse node definitions that constitute an initial match to the parse tree
/// </summary>
private readonly IList<ParseNodeDefinition> _initialNodeOptions;
/// <summary>
/// Creates a new ParseTreeDefinition
/// </summary>
/// <param name="name">The name to give to parse trees generated from full matches</param>
public ParseTreeDefinition(string name)
{
_initialNodeOptions = new List<ParseNodeDefinition>();
Name = name;
}
/// <summary>
/// Gets the set of parse node definitions that constitute an initial match to the parse tree
/// </summary>
public IEnumerable<ParseNodeDefinition> InitialNodeOptions { get { return _initialNodeOptions; } }
/// <summary>
/// Gets the name of the ParseTreeDefinition
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Adds an option for a named node to follow this one in the parse tree the node is a part of
/// </summary>
/// <param name="matchItemsNamed">The name of the item to be matched</param>
/// <returns>The ParseNodeDefinition that has been added</returns>
public ParseNodeDefinition AddOption(string matchItemsNamed)
{
return AddOption(matchItemsNamed, string.Empty, false);
}
/// <summary>
/// Adds an option for a named node to follow this one in the parse tree the node is a part of
/// </summary>
/// <param name="matchItemsNamed">The name of the item to be matched</param>
/// <param name="tag">The tag to associate with matched items</param>
/// <returns>The ParseNodeDefinition that has been added</returns>
public ParseNodeDefinition AddOption(string matchItemsNamed, string tag)
{
return AddOption(matchItemsNamed, tag, false);
}
/// <summary>
/// Adds an option for a named node to follow this one in the parse tree the node is a part of
/// </summary>
/// <param name="matchItemsNamed">The name of the item to be matched</param>
/// <param name="tag">The tag to associate with matched items</param>
/// <param name="isValidEnd">Whether or not the element is a valid end to the parse tree</param>
/// <returns>The ParseNodeDefinition that has been added</returns>
public ParseNodeDefinition AddOption(string matchItemsNamed, string tag, bool isValidEnd)
{
var node = ParseNodeDefinition.Create(matchItemsNamed, tag, isValidEnd);
_initialNodeOptions.Add(node);
return node;
}
/// <summary>
/// Adds an option for a named node to follow this one in the parse tree the node is a part of
/// </summary>
/// <param name="matchItemsNamed">The name of the item to be matched</param>
/// <param name="isValidEnd">Whether or not the element is a valid end to the parse tree</param>
/// <returns>The ParseNodeDefinition that has been added</returns>
public ParseNodeDefinition AddOption(string matchItemsNamed, bool isValidEnd)
{
return AddOption(matchItemsNamed, string.Empty, isValidEnd);
}
/// <summary>
/// Attempts to follow a particular branch in the parse tree from a given starting point in a set of source parts
/// </summary>
/// <param name="parts">The set of source parts to attempt to match in</param>
/// <param name="startIndex">The position to start the matching attempt at</param>
/// <param name="required">The definition that must be matched for the branch to be followed</param>
/// <param name="nodes">The set of nodes that have been matched so far</param>
/// <returns>true if the branch was followed to completion, false otherwise</returns>
private static bool FollowBranch(IList<ISourcePart> parts, int startIndex, ParseNodeDefinition required, ICollection<ParseNode> nodes)
{
if (parts[startIndex].Kind != required.MatchItemsNamed)
{
return false;
}
nodes.Add(new ParseNode(parts[startIndex], required.Tag));
return parts.Count > (startIndex + 1) && required.NextNodeOptions.Any(x => FollowBranch(parts, startIndex + 1, x, nodes)) || required.IsValidEnd;
}
/// <summary>
/// Attempt to match the parse tree definition against a set of source parts
/// </summary>
/// <param name="parts">The source parts to match against</param>
/// <returns>true if the parse tree was matched, false otherwise. parts is updated by this method to consolidate matched nodes into a ParseTree</returns>
public bool Parse(ref IList<ISourcePart> parts)
{
var partsCopy = parts.ToList();
for (var i = 0; i < parts.Count; ++i)
{
var tree = new List<ParseNode>();
if (InitialNodeOptions.Any(x => FollowBranch(partsCopy, i, x, tree)))
{
partsCopy.RemoveRange(i, tree.Count);
partsCopy.Insert(i, new ParseTree(Name, tree.ToArray(), tree[0].Position));
parts = partsCopy;
return true;
}
}
return false;
}
}
Of course these don't do us much good without having some place to store the results of the matchers we've defined so far, so let's define ParseTree and ParseNode where a ParseTree is simply a collection of ParseNode objects where ParseNode is a wrapper around a ParseTree or Token (or more generically any ISourcePart).
public class ParseTree : ISourcePart
{
/// <summary>
/// Creates a new ParseTree
/// </summary>
/// <param name="kind">The kind (name) of tree this is</param>
/// <param name="nodes">The nodes the tree matches</param>
/// <param name="position">The position in the source file this tree is located at</param>
public ParseTree(string kind, IEnumerable<ISourcePart> nodes, int position)
{
Kind = kind;
ParseNodes = nodes.ToList();
Position = position;
}
public string Kind { get; private set; }
public int Position { get; private set; }
/// <summary>
/// Gets the nodes that make up this parse tree
/// </summary>
public IList<ISourcePart> ParseNodes { get; internal set; }
public Token[] AsTokens()
{
return ParseNodes.SelectMany(x => x.AsTokens()).ToArray();
}
}
public class ParseNode : ISourcePart
{
/// <summary>
/// Creates a new ParseNode
/// </summary>
/// <param name="sourcePart">The data that was matched to create this node</param>
/// <param name="tag">The tag data (if any) associated with the node</param>
public ParseNode(ISourcePart sourcePart, string tag)
{
SourcePart = sourcePart;
Tag = tag;
}
public string Kind { get { return SourcePart.Kind; } }
/// <summary>
/// Gets the tag associated with the matched data
/// </summary>
public string Tag { get; private set; }
/// <summary>
/// Gets the data that was matched to create this node
/// </summary>
public ISourcePart SourcePart { get; private set; }
public int Position { get { return SourcePart.Position; } }
public Token[] AsTokens()
{
return SourcePart.AsTokens();
}
}
That's it for the constructs we need, so we'll move into configuring our parse tree definitions. The code from here on is from Program.cs in my demo.
As you might have noticed in the block above about declaring the patterns for each token, there were some values referenced but not defined, here they are.
private const string CloseParen = "CloseParen";
private const string ComplexFunctionCall = "ComplexFunctionCall";
private const string FunctionCallStart = "FunctionCallStart";
private const string Literal = "Literal";
private const string OpenParen = "OpenParen";
private const string Operator = "Operator";
private const string ParenthesisRequiredElement = "ParenthesisRequiredElement";
private const string ParenthesizedItem = "ParenthesizedItem";
private const string Semi = "Semi";
private const string SimpleFunctionCall = "SimpleFunctionCall";
Let's begin by defining a pattern that matches literals (\w+ pattern) that are followed by open parenthesis; we'll use this to match things like sin( or Function1(.
static ParseTreeDefinition CreateFunctionCallStartTree()
{
var tree = new ParseTreeDefinition(FunctionCallStart);
var name = tree.AddOption(Literal);
name.AddOption(OpenParen, true);
return tree;
}
Really not a whole lot to it, setup a tree, add an option for the first thing to match as a Literal, add an option of the next thing to match as an open parenthesis and say that it can end the parse tree.
Now for one that's a little more complex, binary mathematical operations (couldn't think of any unary operations that would need to be included)
static ParseTreeDefinition CreateBinaryOperationResultTree()
{
var tree = new ParseTreeDefinition(Literal);
var parenthesizedItem = tree.AddOption(ParenthesizedItem);
var literal = tree.AddOption(Literal);
var simpleCall = tree.AddOption(SimpleFunctionCall);
var complexCall = tree.AddOption(ComplexFunctionCall);
var #operator = parenthesizedItem.AddOption(Operator);
literal.LinkTo(#operator);
simpleCall.LinkTo(#operator);
complexCall.LinkTo(#operator);
#operator.AddOption(ParenthesizedItem, true);
#operator.AddOption(Literal, true);
#operator.AddOption(SimpleFunctionCall, true);
#operator.AddOption(ComplexFunctionCall, true);
return tree;
}
Here we say that the parse tree can start with a parenthesized item (like (1/2)), a literal (like a5 or 3), a simple call (like sin(4)) or a complex one (like Function1(a1;a2;a3)). In essence we've just defined the options for the left hand operand. Next, we say that the parenthesized item must be followed by an Operator (one of the mathematical operators from the pattern declared way up at the beginning) and, for convenience, we'll say that all of the other options for the left hand operand can progress to that same state (having the operator). Next, the operator must have a right hand side as well, so we give it a duplicate set of options to progress to. Note that they are not the same definitions as the left hand operands, these have the flag set to be able to terminate the parse tree. Notice that the parse tree is named Literal to avoid having to specify yet another kind of element to match all over the place.
Next up, parenthesized items:
static ParseTreeDefinition CreateParenthesizedItemTree()
{
var tree = new ParseTreeDefinition(ParenthesizedItem);
var openParen = tree.AddOption(OpenParen);
var nestedSimpleCall = openParen.AddOption(SimpleFunctionCall);
var nestedComplexCall = openParen.AddOption(ComplexFunctionCall);
var arg = openParen.AddOption(Literal);
var parenthesizedItem = openParen.AddOption(ParenthesizedItem);
var closeParen = nestedSimpleCall.AddOption(CloseParen, true);
arg.LinkTo(closeParen);
parenthesizedItem.LinkTo(closeParen);
nestedComplexCall.LinkTo(closeParen);
return tree;
}
Nice and easy with this one, start with a parenthesis, follow it up with pretty much anything, follow that with another parenthesis to close it.
Simple calls (like sin(x))
static ParseTreeDefinition CreateSimpleFunctionCallTree()
{
var tree = new ParseTreeDefinition(SimpleFunctionCall);
var openParen = tree.AddOption(FunctionCallStart);
var nestedItem = openParen.AddOption(ParenthesizedItem);
var nestedSimpleCall = openParen.AddOption(SimpleFunctionCall);
var nestedComplexCall = openParen.AddOption(ComplexFunctionCall);
var arg = openParen.AddOption(Literal);
var parenthesizedItem = openParen.AddOption(ParenthesizedItem);
var closeParen = nestedSimpleCall.AddOption(CloseParen, true);
arg.LinkTo(closeParen);
nestedItem.LinkTo(closeParen);
parenthesizedItem.LinkTo(closeParen);
nestedComplexCall.LinkTo(closeParen);
return tree;
}
Complex calls (like Function1(a1;a2;a3))
static ParseTreeDefinition CreateComplexFunctionCallTree()
{
var tree = new ParseTreeDefinition(ComplexFunctionCall);
var openParen = tree.AddOption(FunctionCallStart);
var arg = openParen.AddOption(Literal, ParenthesisRequiredElement);
var simpleCall = openParen.AddOption(SimpleFunctionCall, ParenthesisRequiredElement);
var complexCall = openParen.AddOption(ComplexFunctionCall, ParenthesisRequiredElement);
var nested = openParen.AddOption(ParenthesizedItem);
var semi = arg.AddOption(Semi);
simpleCall.LinkTo(semi);
complexCall.LinkTo(semi);
nested.LinkTo(semi);
var arg2 = semi.AddOption(Literal, ParenthesisRequiredElement);
var simpleCall2 = semi.AddOption(SimpleFunctionCall, ParenthesisRequiredElement);
var complexCall2 = semi.AddOption(ComplexFunctionCall, ParenthesisRequiredElement);
var nested2 = semi.AddOption(ParenthesizedItem);
arg2.LinkTo(semi);
simpleCall2.LinkTo(semi);
complexCall2.LinkTo(semi);
nested2.LinkTo(semi);
var closeParen = arg2.AddOption(CloseParen, true);
arg2.LinkTo(closeParen);
simpleCall2.LinkTo(closeParen);
complexCall2.LinkTo(closeParen);
return tree;
}
That's all the trees we'll need, so let's take a look at the code that runs this all
static void Main()
{
//The input string
const string input = #"Function1((a1^5-4)/2;1/sin(a2);a3;a4)+Function2(a1;a2;1/a3)";
//Locate the recognizable tokens within the source
IList<ISourcePart> tokens = Tokenize(input).Cast<ISourcePart>().ToList();
//Create the parse trees we'll need to be able to recognize the different parts of the input
var functionCallStartTree = CreateFunctionCallStartTree();
var parenthethesizedItemTree = CreateParenthesizedItemTree();
var simpleFunctionCallTree = CreateSimpleFunctionCallTree();
var complexFunctionCallTree = CreateComplexFunctionCallTree();
var binaryOpTree = CreateBinaryOperationResultTree();
//Parse until we can't parse anymore
while (functionCallStartTree.Parse(ref tokens) || binaryOpTree.Parse(ref tokens) || parenthethesizedItemTree.Parse(ref tokens) || simpleFunctionCallTree.Parse(ref tokens) || complexFunctionCallTree.Parse(ref tokens))
{ }
//Run our post processing to fix the parenthesis in the input
FixParenthesis(ref tokens);
//Collapse our parse tree(s) back to a string
var values = tokens.OrderBy(x => x.Position).SelectMany(x => x.AsTokens()).Select(x => x.Value);
//Print out our results and wait
Console.WriteLine(string.Join(string.Empty, values));
Console.ReadLine();
}
The only thing we've got left to define is how to actually do the wrapping of the elements in the argument list of a "complex" call. That's handled by the FixParenthesis method.
private static void FixParenthesis(ref IList<ISourcePart> items)
{
//Iterate through the set we're examining
for (var i = 0; i < items.Count; ++i)
{
var parseNode = items[i] as ParseNode;
//If we've got a parse node...
if (parseNode != null)
{
var nodeTree = parseNode.SourcePart as ParseTree;
//If the parse node represents a parse tree...
if (nodeTree != null)
{
//Fix parenthesis within the tree
var nodes = nodeTree.ParseNodes;
FixParenthesis(ref nodes);
nodeTree.ParseNodes = nodes;
}
//If this parse node required parenthesis, replace the subtree and add them
if (parseNode.Tag == ParenthesisRequiredElement)
{
var nodeContents = parseNode.AsTokens();
var combined = string.Join(string.Empty, nodeContents.OrderBy(x => x.Position).Select(x => x.Value));
items[i] = Token.Create(parseNode.Kind, string.Format("({0})", combined), parseNode.Position);
}
continue;
}
var parseTree = items[i] as ParseTree;
//If we've got a parse tree...
if (parseTree != null)
{
//Fix parenthesis within the tree
var nodes = parseTree.ParseNodes;
FixParenthesis(ref nodes);
parseTree.ParseNodes = nodes;
}
}
}
At any rate, I hope this has helped or at least provided a fun diversion.
I probably managed to deal with it(now testing). It turned out to be 5-stage operation. Assuming that '{' and ';' cannot occur in function I've done sth like this:
sBuffer = Regex.Replace(sBuffer, #"(?<sep>[;])", "};{");
sBuffer = Regex.Replace(sBuffer, #"([(])(?<arg>.+?)[}]", "({${arg}}");
sBuffer = Regex.Replace(sBuffer, #"([;])(?<arg>.+?)([)]){1}", ";${arg}})");
sBuffer = Regex.Replace(sBuffer, #"{", "(");
sBuffer = Regex.Replace(sBuffer, #"}", ")");
0.
function1((a1^5-4)/2;1/sin(a2);a3;a4)+function2(a1;a2;1/a3)'
1.First line replaces ; with };{
function1((a1^5-4)/2};{1/sin(a2)};{a3};{a4)+function2(a1};{a2};{1/a3)
2.For first argument - after ( or (not intended) arguments which contain ')' replace (arg};with ({arg}:
function1({(a1^5-4)/2};{1/sin({a2)};{a3};{a4)+function2({a1};{a2};{1/a3)
3. The same at the and of function: {arg) with {arg}:
function1({(a1^5-4)/2};{1/sin({a2})};{a3};{a4})+function2({a1};{a2};{1/a3})
4.5. Replace '{' and '}' with '(' ')':
function1(((a1^5-4)/2);(1/sin((a2)));(a3);(a4))+function2((a1);(a2);(1/a3))
We have some extra () specially when argument itself is surrounded by '(' ')' (nested function) but it doesn't metter as the code is then proceed by Reversed Polish Notation
This is my first code for regexp(I found out about rgexp just few days ago- I'm a beginer) . I hope it's satisfies all the cases (at least those that can occur in excel formulas)
For one of my projects we utilize a Content Delivery Network (EdgeCast) and I was wondering if anyone knew of an existing (open source) library for C# that we could leverage? In a nutshell, I am investigating the ability to create directories, upload files, and allow some general maintenance (rename files, delete files) via our admin panel. Any insight would be appreciated.
NOTE: My code to do this. I don't know if they have an updated API at this point so please check. This code is circa 2009 (as-is, no warranties, use at your own risk, etc).
EdgeCast.cs
using System;
using edgeCastWS = [DOMAIN].com.edgecast.api.EdgeCastWebServices;
namespace [DOMAIN].IO.ContentDeliveryNetwork
{
/// <summary>
/// <b>Authentication</b>
/// <para>Each web service call will include a string parameter called strCredential which will ensure that customers may only access data related to their own accounts. This credential is a string made up of a few separate pieces of information.</para>
/// <para>A typical credential a customer would use may look like: “c:bas:user#email.com:password”</para>
/// <para> * The field format of this string is delimited with a colon “:” character.</para>
/// <para> * The first field is the type of client which is calling the web service. “c” represents an individual customer.</para>
/// <para> * The second field is the security type. “bas” represents Basic Authentication.</para>
/// <para> * With basic authentication the third field is the username which is usually in email address notation.</para>
/// <para> * With basic authentication the fourth field is the password associated with the user.</para>
/// <para>If the username and password are not valid, then the credential will prevent the specified function from proceeding. A SOAP exception will be generated and sent back to the client that called the function. The message contained in the exception will contain the string “Access Denied.”</para>
/// </summary>
public class EdgeCast : IDisposable
{
#region Declarations
[ThreadStatic]
private static EdgeCast _current;
private static EdgeCastCredential _credential = null;
private static string _customerID = string.Empty;
private static edgeCastWS _edgeCastWS = null;
#endregion
#region Constructor
public EdgeCast()
{
if (_current != null)
{
throw new InvalidOperationException("Only one ContentDeliveryNetwork may be created on a given thread.");
}
_current = this;
_edgeCastWS = new edgeCastWS();
ConstructCredentials();
_customerID = AppSettings.EdgeCast_CustomerID;
}
#endregion
#region API Methods
/// <summary>
/// Purges a file from all of the EdgeCast caching servers. This process may take a few minutes to fully complete.
/// </summary>
/// <param name="Path">This is path of the file or folder to purge, in URL format. To specify a folder, please put a trailing slash at the end of the URL. All files under that folder will be purged</param>
/// <param name="MediaType">HTTP Large Object = 3, HTTP Small Object = 8, Flash = 2, Windows = 1</param>
/// <returns>An integer that indicates whether or not the transaction was successful. 0 represents yes, -1 represents no</returns>
internal static short PurgeFileFromEdge(string Path, int MediaType)
{
return _edgeCastWS.PurgeFileFromEdge(_credential.Credential, _customerID, Path, MediaType);
}
/// <summary>
/// Loads a file to disk on all of the EdgeCast caching servers. This process may take a few minutes to fully complete.
/// </summary>
/// <param name="Path">This is path of the file to load, in URL format. Folders may not be loaded and will be ignored</param>
/// <param name="MediaType">HTTP Large Object = 3, HTTP Small Object = 8</param>
/// <returns>An integer that indicates whether or not the transaction was successful. 0 represents yes, -1 represents no</returns>
internal static short LoadFileToEdge(string Path, int MediaType)
{
return _edgeCastWS.LoadFileToEdge(_credential.Credential, _customerID, Path, MediaType);
}
/// <summary>
/// This method call is used to add a token authentication directory. All additions and changes should be processed every 30 minutes
/// </summary>
/// <param name="Dir">Directory Path should be starting from the root. Example: /directory1/directory2</param>
/// <param name="MediaType">Use 1 for Windows, 2 for Flash, 3 for HTTP Caching / Progressive download.</param>
/// <returns></returns>
internal static short TokenDirAdd(string Dir, int MediaType)
{
return _edgeCastWS.TokenDirAdd(_credential.Credential, _customerID, Dir, MediaType);
}
internal static string TokenEncrypt(string Key, string Args)
{
return _edgeCastWS.TokenEncrypt(_credential.Credential, Key, Args);
}
internal static short TokenKeyUpdate(string Key, int MediaType)
{
return _edgeCastWS.TokenKeyUpdate(_credential.Credential, _customerID, Key, MediaType);
}
#endregion
#region FTP Methods
/// <summary>
/// Does a FTP Upload of a file on the local system
/// </summary>
/// <param name="targetPath">The full path to save to on the FTP server (.\Campaigns\Prod\[Company GUID])</param>
/// <param name="sourceFileName">The full path and filename of the file to be uploaded (C:\[DOMAIN]\files\[filename])</param>
internal static void FTP_UploadFile(string targetPath, string sourceFileName)
{
string[] dirs = targetPath.Split(new char[] { '\\', '/' });
using (FTPFactory myFTP = new FTPFactory())
{
myFTP.login();
myFTP.setBinaryMode(true);
// Make sure the directories are there and change down to the bottom most directory...
foreach (string dirName in dirs)
{
bool dirExists = true;
try { myFTP.chdir(dirName); }
catch { dirExists = false; }
if (!dirExists)
{
myFTP.mkdir(dirName);
myFTP.chdir(dirName);
}
}
// upload the file now...
myFTP.upload(sourceFileName);
}
}
/// <summary>
/// Returns a string array of files in the target directory
/// </summary>
/// <param name="targetPath">The target directory</param>
/// <returns>string array or null if no files found</returns>
internal static string[] FTP_GetFileList(string targetPath)
{
string[] dirs = targetPath.Split(new char[] { '\\', '/' });
string[] files = null;
using (FTPFactory myFTP = new FTPFactory())
{
myFTP.login();
myFTP.setBinaryMode(true);
// Make sure the directories are there and change down to the bottom most directory...
foreach (string dirName in dirs)
{
bool dirExists = true;
try { myFTP.chdir(dirName); }
catch { dirExists = false; }
if (!dirExists)
{
myFTP.mkdir(dirName);
myFTP.chdir(dirName);
}
}
// get the list of files now...
files = myFTP.getFileList("*.*");
}
return files;
}
/// <summary>
/// Checks to see if a file exists
/// </summary>
/// <param name="targetPath">The CDN directory path to look in</param>
/// <param name="targetFile">The file to look for</param>
/// <returns>true = found, false = not found</returns>
internal static bool FTP_DoesFileExist(string targetPath, string targetFile)
{
bool retFlag = true;
string[] dirs = targetPath.Split(new char[] { '\\', '/' });
using (FTPFactory myFTP = new FTPFactory())
{
myFTP.login();
myFTP.setBinaryMode(true);
// change to the target directory - if it does not exists, neither does the file! simple...
foreach (string dirName in dirs)
{
try { myFTP.chdir(dirName); }
catch { retFlag = false; }
}
if (retFlag)
{
try { retFlag = myFTP.getFileSize(targetFile) > 0; }
catch { retFlag = false; }
}
}
return retFlag;
}
internal static bool FTP_DoesFileExist(string targetFile)
{
string targetPath = targetFile.Replace(#"http://", "");
targetPath = targetPath.Replace(#"https://", "");
targetPath = targetPath.Replace(#"cdn1.[DOMAIN].com/", "");
targetPath = targetPath.Replace(#"/", #"\");
targetPath = targetPath.Substring(0, targetPath.LastIndexOf(#"\"));
targetFile = targetFile.Substring(targetFile.LastIndexOf(#"/") + 1);
return FTP_DoesFileExist(targetPath, targetFile);
}
#endregion
#region Helper Methods
static private string ConstructCredentials()
{
_credential = new EdgeCastCredential();
return _credential.Credential;
}
static private string ConstructCredentials(string credentials)
{
_credential = new EdgeCastCredential(credentials);
return _credential.Credential;
}
static private string ConstructCredentials(string typeOfClient, string securityType, string userName, string password)
{
_credential = new EdgeCastCredential(typeOfClient, securityType, userName, password);
return _credential.Credential;
}
#endregion
#region IDisposable Members
public void Dispose()
{
_current = null;
}
#endregion
}
}
EdgeCastCredential.cs
using System;
using rivSecurity = [DOMAIN].Security.Cryptography.Internal;
namespace [DOMAIN].IO.ContentDeliveryNetwork
{
internal class EdgeCastCredential
{
#region Properties
public string TypeOfClient;
public string SecurityType;
public string UserName;
public string Password;
public string Credential { get { return String.Format("{0}:{1}:{2}:{3}", TypeOfClient, SecurityType, UserName, Password); } }
#endregion
#region Constructor
public EdgeCastCredential()
{
TypeOfClient = AppSettings.EdgeCast_TypeOfClient;
SecurityType = AppSettings.EdgeCast_SecurityType;
UserName = rivSecurity.Decrypt(AppSettings.EdgeCast_UserName);
Password = rivSecurity.Decrypt(AppSettings.EdgeCast_Password);
}
public EdgeCastCredential(string credentials)
{
string[] parts = credentials.Split(new char[] { ':' });
}
public EdgeCastCredential(string typeOfClient, string securityType, string userName, string password)
{
TypeOfClient = typeOfClient;
SecurityType = securityType;
UserName = userName;
Password = password;
}
#endregion
}
}
Never mind. I created a FTP library and used what little they had for an API. I can not now read/upload/delete files and purge the CDN.
How can I change an attribute of an element in an XML file, using C#?
Mike;
Everytime I need to modify an XML document I work it this way:
//Here is the variable with which you assign a new value to the attribute
string newValue = string.Empty;
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(xmlFile);
XmlNode node = xmlDoc.SelectSingleNode("Root/Node/Element");
node.Attributes[0].Value = newValue;
xmlDoc.Save(xmlFile);
//xmlFile is the path of your file to be modified
I hope you find it useful
Using LINQ to xml if you are using framework 3.5:
using System.Xml.Linq;
XDocument xmlFile = XDocument.Load("books.xml");
var query = from c in xmlFile.Elements("catalog").Elements("book")
select c;
foreach (XElement book in query)
{
book.Attribute("attr1").Value = "MyNewValue";
}
xmlFile.Save("books.xml");
If the attribute you want to change doesn't exist or has been accidentally removed, then an exception occurs. I suggest you first create a new attribute and send it to a function like the following:
private void SetAttrSafe(XmlNode node,params XmlAttribute[] attrList)
{
foreach (var attr in attrList)
{
if (node.Attributes[attr.Name] != null)
{
node.Attributes[attr.Name].Value = attr.Value;
}
else
{
node.Attributes.Append(attr);
}
}
}
Usage:
XmlAttribute attr = dom.CreateAttribute("name");
attr.Value = value;
SetAttrSafe(node, attr);
Here's the beginnings of a parser class to get you started. This ended up being my solution to a similar problem:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace XML
{
public class Parser
{
private string _FilePath = string.Empty;
private XDocument _XML_Doc = null;
public Parser(string filePath)
{
_FilePath = filePath;
_XML_Doc = XDocument.Load(_FilePath);
}
/// <summary>
/// Replaces values of all attributes of a given name (attributeName) with the specified new value (newValue) in all elements.
/// </summary>
/// <param name="attributeName"></param>
/// <param name="newValue"></param>
public void ReplaceAtrribute(string attributeName, string newValue)
{
ReplaceAtrribute(string.Empty, attributeName, new List<string> { }, newValue);
}
/// <summary>
/// Replaces values of all attributes of a given name (attributeName) with the specified new value (newValue) in elements with a given name (elementName).
/// </summary>
/// <param name="elementName"></param>
/// <param name="attributeName"></param>
/// <param name="newValue"></param>
public void ReplaceAtrribute(string elementName, string attributeName, string newValue)
{
ReplaceAtrribute(elementName, attributeName, new List<string> { }, newValue);
}
/// <summary>
/// Replaces values of all attributes of a given name (attributeName) and value (oldValue)
/// with the specified new value (newValue) in elements with a given name (elementName).
/// </summary>
/// <param name="elementName"></param>
/// <param name="attributeName"></param>
/// <param name="oldValue"></param>
/// <param name="newValue"></param>
public void ReplaceAtrribute(string elementName, string attributeName, string oldValue, string newValue)
{
ReplaceAtrribute(elementName, attributeName, new List<string> { oldValue }, newValue);
}
/// <summary>
/// Replaces values of all attributes of a given name (attributeName), which has one of a list of values (oldValues),
/// with the specified new value (newValue) in elements with a given name (elementName).
/// If oldValues is empty then oldValues will be ignored.
/// </summary>
/// <param name="elementName"></param>
/// <param name="attributeName"></param>
/// <param name="oldValues"></param>
/// <param name="newValue"></param>
public void ReplaceAtrribute(string elementName, string attributeName, List<string> oldValues, string newValue)
{
List<XElement> elements = _XML_Doc.Elements().Descendants().ToList();
foreach (XElement element in elements)
{
if (elementName == string.Empty | element.Name.LocalName.ToString() == elementName)
{
if (element.Attribute(attributeName) != null)
{
if (oldValues.Count == 0 || oldValues.Contains(element.Attribute(attributeName).Value))
{ element.Attribute(attributeName).Value = newValue; }
}
}
}
}
public void SaveChangesToFile()
{
_XML_Doc.Save(_FilePath);
}
}
}