I'm checking if two XML elements given by string are equal with this method:
private static bool XmlEquals(string s1, string s2)
{
return XNode.DeepEquals(XElement.Parse(s1), XElement.Parse(s2));
}
This work unless one of the elements has opening and closing tags and the other has a closed tag like this:
<MyElement SomeAttribute="some value" />
<MyElement SomeAttribute="some value"></MyElement>
Can I somehow compare two XML elements in a way, that the above case is considered equal?
Straightforward way to solve this particular problem is to introduce closing brackets explicitly:
private static bool XmlEquals(string s1, string s2)
{
var firstElement = XElement.Parse(s1);
var secondElement = XElement.Parse(s2);
IntroduceClosingBracket(firstElement);
IntroduceClosingBracket(secondElement);
return XNode.DeepEquals(firstElement, secondElement);
}
private static void IntroduceClosingBracket(XElement element)
{
foreach (var descendant in element.DescendantsAndSelf())
{
if (descendant.IsEmpty)
{
descendant.SetValue(String.Empty);
}
}
}
Loop through all descendants may cause a performance hit, though.
If the XMLs does not have to be perfectly identical like in #defaultlocale's solution which really compares everything (even comments) you could use LINQ to compare only things that you are interested in. To show the idea I've made the comparison of attribute values optional.
Test data:
var xml1 = #"
<elm1 attr1='val1'>
<elm2 attr2='val1'>
<elm3 attr3='val1' />
</elm2>
</elm1>";
var xml2 = #"
<elm1 attr1='val1'>
<elm2 attr2='val1'>
<elm3 attr3='val1' attr='val2' />
</elm2>
</elm1>";
Recursive comparison:
// Just a helper.
private static Tuple<XElement, XElement> Compare(
string xml1,
string xml2,
bool compareAttributeValues)
{
return Compare(
XElement.Parse(xml1),
XElement.Parse(xml2),
compareAttributeValues);
}
// Compares XElements recursively
// and returns the two nodes that are different if any.
private static Tuple<XElement, XElement> Compare(
XElement xElm1,
XElement xElm2,
bool compareAttributeValues)
{
// Elements are different if they have a different number of children.
if (xElm1.Elements().Count() != xElm2.Elements().Count())
{
return new Tuple<XElement, XElement>(xElm1, xElm2);
}
// Enumerate both elements at the same time.
var elements = Enumerable.Zip(
xElm1.Elements(),
xElm2.Elements(),
(x, y) => new Tuple<XElement, XElement>(x, y));
foreach (var item elements )
{
// Elements are equal if they have the same names...
bool haveSameNames = xElm1.Name.LocalName == xElm2.Name.LocalName;
// and the same attributes.
bool haveSameAttributes =
item.Item1
// Concatenate and group attributes by their name.
.Attributes()
.Concat(item.Item2.Attributes())
.GroupBy(x => x.Name.LocalName, (key, items) => new
{
Name = key,
Items = items,
// Attiribute value comparison can be skipped.
// If enabled compare attribute values.
// They are equal if the result is only one group.
HaveSameValues =
compareAttributeValues == false
|| items.GroupBy(y => y.Value).Count() == 1,
Count = items.Count()
})
// Each attribute group must contain two items
// if they are identical (one per element).
.Where(x => x.Count != 2 || x.HaveSameValues == false)
.Any() == false;
if (!haveSameNames || !haveSameAttributes)
{
return item;
}
else
{
// Return the different nodes.
return Compare(item.Item1, item.Item2, compareAttributeValues);
}
}
// No differences found.
return null;
}
an issue using the XNode.DeepEquals when there is an empty tags,
Best way to compare all XML elements from XML documents (this should works even if the XML closing tags are different)
public bool CompareXml()
{
var doc = #"
<ContactPersons>
<ContactPersonRole>General</ContactPersonRole>
<Person>
<Name>Aravind Kumar Eriventy</Name>
<Email/>
<Mobile>9052534488</Mobile>
</Person>
</ContactPersons>";
var doc1 = #"
<ContactPersons>
<ContactPersonRole>General</ContactPersonRole>
<Person>
<Name>Aravind Kumar Eriventy</Name>
<Email></Email>
<Mobile>9052534488</Mobile>
</Person>
</ContactPersons>";
return XmlDocCompare(XDocument.Parse(doc), XDocument.Parse(doc1));
}
private static bool XmlDocCompare(XDocument doc,XDocument doc1)
{
IntroduceClosingBracket(doc.Root);
IntroduceClosingBracket(doc1.Root);
return XNode.DeepEquals(doc1, doc);
}
private static void IntroduceClosingBracket(XElement element)
{
foreach (var descendant in element.DescendantsAndSelf())
{
if (descendant.IsEmpty)
{
descendant.SetValue(String.Empty);
}
}
}
You could always split the strings at the quotation marks and then compare them.
s1Array = s1.Split('\"');
s2Array = s2.Split('\"');
if(s1[0] == s2[0] && s1[1] == s2[1])
return true;
Ignoring empty values is as simple as checking
if(s1[1] == String.Empty || s1[1] == null)...
Related
I'm new to Roslyn. I'm wondering if there's a way to tell if a variable is in scope at some position in a semantic model. To give a bit of background on what I'm doing, I'm trying to transform foreach blocks that iterate through the results of a Select, e.g. of the form
foreach (string str in new int[0].Select(i => i.ToString()))
{
}
to
foreach (int item in new int[0])
{
string str = item.ToString();
}
Here is the relevant portion of my code fix provider. Currently, I am hard-coding the iteration variable to be item:
var ident = SyntaxFactory.Identifier("item");
Then, I am retrieving the Body of the selector's SimpleLambdaExpressionSyntax, and (in the above case) substituting the parameter i with item to get item.ToString():
var paramTokens = from token in selectorBody.DescendantTokens()
where token.Text == selectorParam.Identifier.Text
select token;
selectorBody = selectorBody.ReplaceTokens(paramTokens, (_, __) => ident);
I want to know if there is a way to tell whether a variable named item is already in scope at the location of the foreach block, so my code fix provider does not generate a conflicting variable declaration. Would this be possible to somehow achieve using the SemanticModel/Symbol/etc. APIs?
Thanks.
I could think of two ways to do it.
Using this test code so I could test the different declarations (field, property, variable, class names)
const string code = #"
public class AClass{
private int MyFld = 5;
protected double MyProp{get;set;}
public void AMethod(){
string myVar = null;
for (int myIterator=0; myIterator<10;myIterator++)
foreach (string str in new int[0].Select(i => i.ToString())){ }
}
public void AnotherMethod()
{
string anotherVar = null;
}
}";
-
void Main()
{
var tree = CSharpSyntaxTree.ParseText(code);
var root = tree.GetRoot();
var startNode = root
.DescendantNodes()
.OfType<SimpleLambdaExpressionSyntax>() // start at the Select() lambda
.FirstOrDefault();
FindSymbolDeclarationsInAncestors(startNode, "myVar").Dump(); // True
FindSymbolDeclarationsInAncestors(startNode, "anotherVar").Dump(); // False
CompilationLookUpSymbols(tree, startNode, "myVar").Dump(); // True
CompilationLookUpSymbols(tree, startNode, "anotherVar").Dump(); // False
}
// You could manually traverse the ancestor nodes, and find the different DeclarationSyntax-es.
// I may have missed some, like CatchDeclarationSyntax..
// Error-prone but more fun.
public bool FindSymbolDeclarationsInAncestors(CSharpSyntaxNode currentNode, string symbolToFind)
{
return currentNode
.Ancestors().SelectMany(a => a.ChildNodes()) // get direct siblings
.SelectMany(node => // find different declarations
(node as VariableDeclarationSyntax)?.Variables.Select(v => v.Identifier.ValueText)
?? (node as FieldDeclarationSyntax)?.Declaration?.Variables.Select(v => v.Identifier.ValueText)
?? (node as LocalDeclarationStatementSyntax)?.Declaration?.Variables.Select(v => v.Identifier.ValueText)
?? new[] {
(node as PropertyDeclarationSyntax)?.Identifier.ValueText,
(node as MethodDeclarationSyntax)?.Identifier.ValueText,
(node as ClassDeclarationSyntax)?.Identifier.ValueText,
})
.Any(member => string.Equals(member, symbolToFind));
}
// Or use the SemanticModel from the CSharpCompilation.
// Possibly slower? Also, not as much fun as manually traversing trees.
public bool CompilationLookUpSymbols(SyntaxTree tree, CSharpSyntaxNode currentNode, string symbolToFind)
{
var compilation = CSharpCompilation.Create("dummy", new[] { tree });
var model = compilation.GetSemanticModel(tree);
return model.LookupSymbols(currentNode.SpanStart, name: symbolToFind).Any();
}
I'm modifying .docx documents with DocumentFormat.OpenXml library. I know element ordering is important, otherwise the document will not pass schema validation and might result a document that can't be opened in Word.
Now I need to add a DocumentProtection element to DocumentSettingsPart. And I need to insert this child element in the right place inside of a parent.
The schema looks like this:
There are quite a lot of possible ordering of child elements. At the moment I'm adding this element like this:
var documentProtection = new DocumentProtection()
{
// do the configuration
};
DocumentSettingsPart settings = doc.MainDocumentPart.DocumentSettingsPart;
var rootElement = settings.RootElement;
var prevElement =
rootElement.GetFirstChild<DoNotTrackFormatting>() ??
rootElement.GetFirstChild<DoNotTrackMoves>() ??
rootElement.GetFirstChild<TrackRevisions>() ??
rootElement.GetFirstChild<RevisionView>() ??
rootElement.GetFirstChild<DocumentType>() ??
rootElement.GetFirstChild<StylePaneSortMethods>() ??
// SNIP
rootElement.GetFirstChild<Zoom>() ??
rootElement.GetFirstChild<View>() ??
(OpenXmlLeafElement)rootElement.GetFirstChild<WriteProtection>();
rootElement.InsertAfter(documentProtection, prevElement);
I.e. I'm trying to find if any possible element that should go before mine already exists in the document. And then insert DocumentProtection after that element. And given amount of elements this list gets pretty boring.
Is there a better way to add DocumentProtection so it is schema compliant and does not involve enumeration of all possible elements?
There isn't a nice way to achieve what you want. You'll have to tinker with the collection and you're responsible for keeping the order correct.
Using ILSpy on the Settings class you'll find that the implementors used a helper method SetElement<T> on the base class that takes a position and an instance to insert.
Unfortunately that helper method is marked internal so we can't leverage it if you try to subclass Settings. Instead I re-implemented the needed functionality so you'll have a subclass of Settings that does offer a property for DocumentProtection but uses the re-implemented solution to find the correct location to insert the node:
SettingsExt
public class SettingsExt: Settings
{
// contruct based on XML
public SettingsExt(string outerXml)
:base(outerXml)
{
// empty
}
public DocumentProtection DocumentProtection
{
// get is easy
get
{
return this.GetFirstChild<DocumentProtection>();
}
// reimplemented SetElement based on
// reversed engineered Settings class
set
{
// eleTagNames is a static string[] declared later
// it holds all the names of the elements in the right order
int sequenceNumber = eleTagNames
.Select((s, i) => new { s= s, idx = i })
.Where(s => s.s == "documentProtection")
.Select((s) => s.idx)
.First();
OpenXmlElement openXmlElement = this.FirstChild;
OpenXmlElement refChild = null;
while (openXmlElement != null)
{
// a bit naive
int currentSequence = eleTagNames
.Select((s, i) => new { s = s, idx = i })
.Where(s => s.s == openXmlElement.LocalName)
.Select((s) => s.idx)
.First(); ;
if (currentSequence == sequenceNumber)
{
if (openXmlElement is DocumentProtection)
{
refChild = openXmlElement.PreviousSibling();
this.RemoveChild<OpenXmlElement>(openXmlElement);
break;
}
refChild = openXmlElement;
}
else
{
if (currentSequence > sequenceNumber)
{
break;
}
refChild = openXmlElement;
}
openXmlElement = openXmlElement.NextSibling();
}
if (value != null)
{
this.InsertAfter(value, refChild);
}
}
}
// order of elements in the sequence!
static readonly string[] eleTagNames = new string[]
{
"writeProtection",
"view",
"zoom",
"removePersonalInformation",
"removeDateAndTime",
"doNotDisplayPageBoundaries",
"displayBackgroundShape",
"printPostScriptOverText",
"printFractionalCharacterWidth",
"printFormsData",
"embedTrueTypeFonts",
"embedSystemFonts",
"saveSubsetFonts",
"saveFormsData",
"mirrorMargins",
"alignBordersAndEdges",
"bordersDoNotSurroundHeader",
"bordersDoNotSurroundFooter",
"gutterAtTop",
"hideSpellingErrors",
"hideGrammaticalErrors",
"activeWritingStyle",
"proofState",
"formsDesign",
"attachedTemplate",
"linkStyles",
"stylePaneFormatFilter",
"stylePaneSortMethod",
"documentType",
"mailMerge",
"revisionView",
"trackRevisions",
"doNotTrackMoves",
"doNotTrackFormatting",
"documentProtection",
"autoFormatOverride",
"styleLockTheme",
"styleLockQFSet",
"defaultTabStop",
"autoHyphenation",
"consecutiveHyphenLimit",
"hyphenationZone",
"doNotHyphenateCaps",
"showEnvelope",
"summaryLength",
"clickAndTypeStyle",
"defaultTableStyle",
"evenAndOddHeaders",
"bookFoldRevPrinting",
"bookFoldPrinting",
"bookFoldPrintingSheets",
"drawingGridHorizontalSpacing",
"drawingGridVerticalSpacing",
"displayHorizontalDrawingGridEvery",
"displayVerticalDrawingGridEvery",
"doNotUseMarginsForDrawingGridOrigin",
"drawingGridHorizontalOrigin",
"drawingGridVerticalOrigin",
"doNotShadeFormData",
"noPunctuationKerning",
"characterSpacingControl",
"printTwoOnOne",
"strictFirstAndLastChars",
"noLineBreaksAfter",
"noLineBreaksBefore",
"savePreviewPicture",
"doNotValidateAgainstSchema",
"saveInvalidXml",
"ignoreMixedContent",
"alwaysShowPlaceholderText",
"doNotDemarcateInvalidXml",
"saveXmlDataOnly",
"useXSLTWhenSaving",
"saveThroughXslt",
"showXMLTags",
"alwaysMergeEmptyNamespace",
"updateFields",
"hdrShapeDefaults",
"footnotePr",
"endnotePr",
"compat",
"docVars",
"rsids",
"mathPr",
"uiCompat97To2003",
"attachedSchema",
"themeFontLang",
"clrSchemeMapping",
"doNotIncludeSubdocsInStats",
"doNotAutoCompressPictures",
"forceUpgrade",
"captions",
"readModeInkLockDown",
"smartTagType",
"schemaLibrary",
"shapeDefaults",
"doNotEmbedSmartTags",
"decimalSymbol",
"listSeparator",
"docId",
"discardImageEditingData",
"defaultImageDpi",
"conflictMode"};
}
A typical usage scenario with this class is as follows:
using (var doc = WordprocessingDocument.Open(#"c:\tmp\test.docx", true))
{
var documentProtection = new DocumentProtection()
{
Formatting = DocumentFormat.OpenXml.OnOffValue.FromBoolean(true)
};
DocumentSettingsPart settings = doc.MainDocumentPart.DocumentSettingsPart;
// instantiate our ExtendedSettings class based on the
// original Settings
var extset = new SettingsExt(settings.Settings.OuterXml);
// new or existing?
if (extset.DocumentProtection == null)
{
extset.DocumentProtection = documentProtection;
}
else
{
// replace existing values
}
// this is key to make sure our own DOMTree is saved!
// don't forget this
settings.Settings = extset;
}
I am trying to use Linq to XML in my c# program to extract element names from an auto-generated XSD file. This means I cannot do queries based on element names/ contents, and instead need to find elements based on their position in the xsd.
I'm trying to do this with a function that returns an element's 'depth'. Somehow, if I use it in my query, I get not only the elements I need but also all underlying elements/attributes. I am guessing I need an "exclude" function in some way, but I don't know how to do this. This is the code I have at the moment:
static public void XsdReader()
{
var xsd = XDocument.Load(#"c:\XsdToLoad.xml");
var elementsAtDepthEight = from e in xsd.Descendants()
where GetElementDepth(e) == 8
select e;
foreach (var p in elementsAtDepthEight)
{
Console.WriteLine(p.ToString());
}
}
static int GetElementDepth(XElement element)
{
int result = 1;
//always return 1 as the root node has depth of 1.
//in addition, return the maximum depth returned by this method called on all the children.
if (element.Parent != null)
{
result += GetElementDepth(element.Parent);
}
return result;
}
The simplest way to measure "depth" is to count ancestors:
var elementsAtDepth8 = xsd.Descendants()
.Where(x => x.Ancestors().Count() == 8);
No need to write your own recursive code at all.
Another slightly odd alternative would be to write:
var elementsAtDepth = xsd.Elements()
.Elements()
.Elements()
.Elements()
.Elements()
.Elements()
.Elements()
.Elements();
You could obviously write a pair of recursive methods to do this:
public static IEnumerable<XElement> ElementsAtDepth(this XNode node, int depth)
{
return node.Elements().ElementsAtDepth(depth - 1);
}
public static IEnumerable<XElement> ElementsAtDepth(
this IEnumerable<XElement> elements, int depth)
{
// TODO: Validate that depth >= 0
return depth == 0 ? elements : elements.Elements().ElementsAtDepth(depth - 1);
}
Im trying to read a certain attributes from following xml file (as console program)
http://api.openweathermap.org/data/2.5/forecast?q=lahti,fin&mode=xml
As you see inside 'forecast' element there are multiple 'time' elements. What I want is to pick certain 'time' element and then pick given thing inside of it (lets say 'symbol') and print all/any attributes it has.
I want to be able to control which 'time' element I pick and which attributes I want to print.
This far all I have managed to do is to print every 'time' element and their attributes and also I managed to print every attribute inside of given 'time' element. But I just can't figure how to control it.
With the following code, I can print everything inside the first 'time' element. Item(0) is the index of element and the for loop makes sure that I don't get empty lines. As you can see from xml file, some 'time' elements has different amount of attributes inside of them so I guess I need to call them By name insted of index.
static void xmlReader()
{
int i;
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(parseLink());
foreach (XmlNode xmlNode in xmlDoc.DocumentElement.GetElementsByTagName("time").Item(0))
for (i = 0; i < xmlNode.Attributes.Count; i++)
{
Console.WriteLine(xmlNode.Attributes[i].Value);
}
Console.ReadKey();
}
Use Linq2Xml, it's much easier and convenient.
public static void Main()
{
var forecast = XDocument.Load(#"http://api.openweathermap.org/data/2.5/forecast?q=lahti,fin&mode=xml")
.Root
.Element("forecast");
foreach (var time in forecast.Elements("time")
.Where(e => e.Element("clouds")
.Attribute("value")
.Value == "overcast clouds"))
{
Console.WriteLine(time.Element("symbol").Attribute("name").Value);
}
}
Using XDocument is a bit easier here...
private static void XmlOutput()
{
var filePathAndName = #"http://api.openweathermap.org/data/2.5/forecast?q=lahti,fin&mode=xml";
var xmlDoc = XDocument.Load(filePathAndName);
// Get list of Nodes matching the "time" name or any other filters you wish.
var nodes = xmlDoc.Descendants().Where(nd => nd.Name.LocalName == "time");
// Filter the node list to only those where the date is as specified (and any other filters you wish).
// This could be done in the initial linq query - just making it clearer here.
nodes = nodes.Where(nd => nd.Attributes().Any(cnd => cnd.Name.LocalName == "from" && cnd.Value.Contains("2015-03-07")));
foreach (XElement element in nodes)
{
// For each descendant node where named "symbol"...
foreach(var node in element.Descendants().Where(nd => nd.Name.LocalName == "symbol"))
{
// Write out these particular attributes ("number" and "name")
string output = "";
output += node.Attributes().FirstOrDefault(nd => nd.Name.LocalName == "number").Value;
output += ", " + node.Attributes().FirstOrDefault(nd => nd.Name.LocalName == "name").Value;
Console.WriteLine(output);
}
}
Console.ReadKey();
}
Similar #aush's response, but with some formatting
var doc = XDocument.Load(#"http://api.openweathermap.org/data/2.5/forecast?q=lahti,fin&mode=xml");
var forecastEl = doc.Root.Element(XName.Get("forecast"));
var timeNodes = from e in forecastEl.Elements("time")
where e.Element("symbol")
.Attribute(XName.Get("name"))
.Value == "light snow"
select e;
var colFormat = "{0,-20} {1,-20} {2,-30}";
Console.WriteLine(colFormat, "TimeFrom", "TimeTo", "SymbolName");
foreach(var time in timeNodes)
Console.WriteLine(colFormat
, time.Attribute("from").Value
, time.Attribute("to").Value
, time.Element("symbol").Attribute("name").Value);
Results:
TimeFrom TimeTo SymbolName
2015-03-07T12:00:00 2015-03-07T15:00:00 light snow
2015-03-07T15:00:00 2015-03-07T18:00:00 light snow
You can use XmlReader (tutorial) as flowing,
Here I get from attribute from time element and name attribute sybol element.. And they are for same Time element. Also added extracting example for value of cloud
using (XmlReader reader = XmlReader.Create(#"http://api.openweathermap.org/data/2.5/forecast?q=lahti,fin&mode=xml"))
{
// Walk through all Elements
while (reader.Read())
{
// If we meet time element ; go inside and walk
if (reader.Name == "time")
{
Console.WriteLine("A new TIME ");
// Extract from attribute
String from = reader["from"];
if (from != null)
{
Console.WriteLine("\t From : " + from);
}
// Now walk through all elements inside same Time element
// Here I use do-while ; what we check is End element of time : </time> .. when we walk till we meet </time> we are inside children of same Time
// That mean we start from <time> and walk till we meet </time>
do
{
reader.Read();
if (reader.Name == "symbol")
{
// You can use this approach for any Attribute in symbol Element
String name = reader["name"];
if (name != null)
{
Console.WriteLine("\t Symbol name :" + name);
}
}
if (reader.Name == "clouds")
{
String clouds = reader["value"];
if (clouds != null)
{
Console.WriteLine("\t\t Clouds value : " + clouds);
}
}
} while (reader.NodeType != XmlNodeType.EndElement && reader.Name != "time");
}
}
}
Simply try this on your Console program..
Using my xml library here, you can search for an actual DateTime value like this:
XElement root = XElement.Load(#"http://api.openweathermap.org/data/2.5/forecast?q=lahti,fin&mode=xml");
var search = DateTime.Parse("2015-03-07T21:00:00");
XElement time = root.XPathElement("//time[#from >= {0} and #to > {1}]", search, search);
var value = time.ToString();
I have a list of site URLs,
/node1
/node1/sub-node1
/node2
/node2/sub-node1
The list is given to me in a random order, I need to order it so the the top level is first, followed by sub-levels and so on (because I cannot create /node2/sub-node1 without /node2 existing). Is there a clean way to do this?
Right now I'm just making a recursive call, saying if I can't create sub-node1 because node2 exists, create node2. I'd like to have the order of the list determine the creation and get rid of my recursive call.
My first thought was ordering by length of the string... but then I thought of a list like this, that might include something like aliases for short names:
/longsitename/
/a
/a/b/c/
/a
/a/b/
/otherlongsitename/
... and I thought a better option was to order by the number of level-separator characters first:
IEnumerable<string> SortURLs(IEnumerable<string> urls)
{
return urls.OrderBy(s => s.Count(c => c == '/')).ThenBy(s => s);
}
Then I thought about it some more and I saw this line in your question:
I cannot create /node2/sub-node1 without /node2 existing
Aha! The order of sections or within a section does not really matter, as long as children are always listed after parents. With that in mind, my original thought was okay and ordering by length of the string alone should be just fine:
IEnumerable<string> SortURLs(IEnumerable<string> urls)
{
return urls.OrderBy(s => s.Length);
}
Which lead me at last to wondering why I cared about the length at all? If I just sort the strings, regardless of length, strings with the same beginning will always sort the shorter string first. Thus, at last:
IEnumerable<string> SortURLs(IEnumerable<string> urls)
{
return urls.OrderBy(s => s);
}
I'll leave the first sample up because it may be useful if, at some point in the future, you need a more lexical or logical sort order.
Is there a clean way to do this?
Just sorting the list of URI's using a standard string sort should get you what you need. In general, "a" will order before "aa" in a string sort, so "/node1" should end up before "/node1/sub-node".
For example:
List<string> test = new List<string> { "/node1/sub-node1", "/node2/sub-node1", "/node1", "/node2" };
foreach(var uri in test.OrderBy(s => s))
Console.WriteLine(uri);
This will print:
/node1
/node1/sub-node1
/node2
/node2/sub-node1
Perhaps this works for you:
var nodes = new[] { "/node1", "/node1/sub-node1", "/node2", "/node2/sub-node1" };
var orderedNodes = nodes
.Select(n => new { Levels = Path.GetFullPath(n).Split('\\').Length, Node = n })
.OrderBy(p => p.Levels).ThenBy(p => p.Node);
Result:
foreach(var nodeInfo in orderedNodes)
{
Console.WriteLine("Path:{0} Depth:{1}", nodeInfo.Node, nodeInfo.Levels);
}
Path:/node1 Depth:2
Path:/node2 Depth:2
Path:/node1/sub-node1 Depth:3
Path:/node2/sub-node1 Depth:3
var values = new string[]{"/node1", "/node1/sub-node1" ,"/node2", "/node2/sub-node1"};
foreach(var val in values.OrderBy(e => e))
{
Console.WriteLine(val);
}
The best is to use natural sorting since your strings are mixed between strings and numbers. Because if you use other sorting methods or techniques and you have like this example:
List<string> test = new List<string> { "/node1/sub-node1" ,"/node13","/node10","/node2/sub-node1", "/node1", "/node2" };
the output will be:
/node1
/node1/sub-node1
/node10
/node13
/node2
/node2/sub-node1
which is not sorted.
You can look at this Implementation
If you mean you need all the first level nodes before all the second level nodes, sort by the number of slashes /:
string[] array = {"/node1","/node1/sub-node1", "/node2", "/node2/sub-node1"};
array = array.OrderBy(s => s.Count(c => c == '/')).ToArray();
foreach(string s in array)
System.Console.WriteLine(s);
Result:
/node1
/node2
/node1/sub-node1
/node2/sub-node1
If you just need parent nodes before child nodes, it doesn't get much simpler than
Array.Sort(array);
Result:
/node1
/node1/sub-node1
/node2
/node2/sub-node1
Recursion is actually exactly what you should use, since this is most easily represented by a tree structure.
public class PathNode {
public readonly string Name;
private readonly IDictionary<string, PathNode> _children;
public PathNode(string name) {
Name = name;
_children = new Dictionary<string, PathNode>(StringComparer.InvariantCultureIgnoreCase);
}
public PathNode AddChild(string name) {
PathNode child;
if (_children.TryGetValue(name, out child)) {
return child;
}
child = new PathNode(name);
_children.Add(name, child);
return child;
}
public void Traverse(Action<PathNode> action) {
action(this);
foreach (var pathNode in _children.OrderBy(kvp => kvp.Key)) {
pathNode.Value.Traverse(action);
}
}
}
Which you can then use like this:
var root = new PathNode(String.Empty);
var links = new[] { "/node1/sub-node1", "/node1", "/node2/sub-node-2", "/node2", "/node2/sub-node-1" };
foreach (var link in links) {
if (String.IsNullOrWhiteSpace(link)) {
continue;
}
var node = root;
var lastIndex = link.IndexOf("/", StringComparison.InvariantCultureIgnoreCase);
if (lastIndex < 0) {
node.AddChild(link);
continue;
}
while (lastIndex >= 0) {
lastIndex = link.IndexOf("/", lastIndex + 1, StringComparison.InvariantCultureIgnoreCase);
node = node.AddChild(lastIndex > 0
? link.Substring(0, lastIndex) // Still inside the link
: link // No more slashies
);
}
}
var orderedLinks = new List<string>();
root.Traverse(pn => orderedLinks.Add(pn.Name));
foreach (var orderedLink in orderedLinks.Where(l => !String.IsNullOrWhiteSpace(l))) {
Console.Out.WriteLine(orderedLink);
}
Which should print:
/node1
/node1/sub-node1
/node2
/node2/sub-node-1
/node2/sub-node-2