Linq to xml combine values - vb.net

Consider the following MediaObject class
Public Class MediaObject
Public Path as String
Public File as String
Public Sub New(_path as String, _file as String)
Path = _path
File = _file
End Sub
End Class
I have the following XML (myxml):
<records>
<media>
<path>\\first\path</path>
<file>firstfile</file>
</media>
<media>
<path>\\second\path</path>
<file>secondfile</file>
</media>
<records>
To get a list of MediaObjects I use this :
Dim mobjects As New List(Of MediaObject)
Dim x As XDocument = XDocument.Parse(myxml)
mobjects = (From m In x.<records>.<media> Select media = New MediaObject(m.<path>.Value, m.<file>.Value)).ToList()
All is fine.
But now consider this new XML (where second file is an alternate of the first one) :
<records>
<media>
<path>\\first\path</path>
<file>firstfile</file>
<path>\\second\path</path>
<file>secondfile</file>
</media>
</records>
I can easily get either of properties but not both, i.e.
Dim mobjects As New List(Of MediaObject)
Dim x As XDocument = XDocument.Parse(myxml)
'here get only the paths
Dim r = (From m In x.<records>.<media> Select media = (From t In m.<path> Select New MediaObject(t.Value, Nothing)).ToList()).ToList()
mobjects = r(0)
How would I go to create a list of MediaObjects in this context ?
(let's consider path and file values in the xml are in sequence and go 2 by 2)
Thanks!
UPDATE:
Sorry I wasn't precise enough.
Here is the real world scenario.
There can many paths and files and it's guaranteed
paths come before files
there is the same number of paths and files
all paths come first, then all files
Sample:
<records>
<media>
<some_tags />
<path>\\first\path</path>
<path>\\second\path</path>
<might_be_something_here />
<file>firstfile</file>
<file>secondfile</file>
<more_tags />
</media>
</records>
PS: I cannot change the XML, which comes from another system...

Assuming you're simply pairing up every nth path and file, you can do this:
Dim paths = doc...<path>
Dim files = doc...<file>
Dim query = paths.Zip(files, Function(p, f)
New MediaObject(CType(p, String), CType(f, String))
)
You don't even have to worry about possible elements that are in between, they'll simply be ignored.

Related

How to order list of files by file name with number?

I have a bunch of files in a directory that I am trying to get based off their type. Once I have them I would like to order them by file name (there is a number in them and I would like to order them that way)
My files returned are:
file-1.txt
file-2.txt
...
file-10.txt
file-11.txt
...
file-20.txt
But the order I get them in looks something more closely to this:
file-1.txt
file-10.txt
file-11.txt
...
file-2.txt
file-20.txt
Right now I am using Directory.GetFiles() and attempting to using the linq OrderBy property. However, I am failing pretty badly with what I would need to do to order my list of files like the first list above.
Directory.GetFiles() seems to be returning a list of strings so I am unable to get the list of file properties such as filename or name.
Here is my code currently:
documentPages = Directory.GetFiles(documentPath, "*.txt").OrderBy(Function(p) p).ToList()
Would anyone have any ideas?
It sounds like you might be looking for a "NaturalSort" - the kind of display File Explorer uses to order filenames containing numerals. For this you need a custom comparer:
Imports System.Runtime.InteropServices
Partial Class NativeMethods
<DllImport("shlwapi.dll", CharSet:=CharSet.Unicode)>
Private Shared Function StrCmpLogicalW(s1 As String, s2 As String) As Int32
End Function
Friend Shared Function NaturalStringCompare(str1 As String, str2 As String) As Int32
Return StrCmpLogicalW(str1, str2)
End Function
End Class
Public Class NaturalStringComparer
Implements IComparer(Of String)
Public Function Compare(x As String, y As String) As Integer Implements IComparer(Of String).Compare
Return NativeMethods.NaturalStringCompare(x, y)
End Function
End Class
Use it to sort the results you get:
Dim myComparer As New NaturalStringComparer
' OP post only shows the filename without path, so strip off path:
' (wont affect the result, just the display)
Dim files = Directory.EnumerateFiles(path_name_here).
Select(Function(s) Path.GetFileName(s)).ToList
Console.WriteLine("Before: {0}", String.Join(", ", files))
' sort the list using the Natural Comparer:
files.Sort(myComparer)
Console.WriteLine("After: {0}", String.Join(", ", files))
Results (one-lined to save space):
Before: file-1.txt, file-10.txt, file-11.txt, file-19.txt, file-2.txt, file-20.txt, file-3.txt, file-9.txt, file-99.txt
After: file-1.txt, file-2.txt, file-3.txt, file-9.txt, file-10.txt, file-11.txt, file-19.txt, file-20.txt, file-99.txt
One of the advantages of this is that it doesnt rely on a specific pattern or coding. It is more all-purpose and will handle more than one set of numbers in the text:
Game of Thrones\4 - A Feast For Crows\1 - Prologue.mp3
Game of Thrones\4 - A Feast For Crows\2 - The Prophet.mp3
...
Game of Thrones\4 - A Feast For Crows\10 - Brienne II.mp3
Game of Thrones\4 - A Feast For Crows\11 - Sansa.mp3
A Natural String Sort is so handy, is is something I personally dont mind polluting Intellisense with by creating an extension:
' List<string> version
<Extension>
Function ToNaturalSort(l As List(Of String)) As List(Of String)
l.Sort(New NaturalStringComparer())
Return l
End Function
' array version
<Extension>
Function ToNaturalSort(a As String()) As String()
Array.Sort(a, New NaturalStringComparer())
Return a
End Function
Usage now is even easier:
Dim files = Directory.EnumerateFiles(your_path).
Select(Function(s) Path.GetFileName(s)).
ToList.
ToNaturalSort()
' or without the path stripping:
Dim files = Directory.EnumerateFiles(your_path).ToList.ToNaturalSort()
I'm assuming the file and .txt parts are mutable, and just here as placeholders for file names and types that can vary.
I don't use regular expressions very often, so this may need some work yet, but it's definitely the direction you need to go:
Dim exp As String = "-([0-9]+)[.][^.]*$"
documentPages = Directory.GetFiles(documentPath, "*.txt").OrderBy(Function(p) Integer.Parse(Regex.Matches(p, exp)(0).Groups(1).Value)).ToList()
Looking again, I see I missed that you are filtering by *.txt files, which can help us narrow the expression:
Dim exp As String = "-([0-9]+)[.]txt$"
Another possible improvement brought by the other answer that includes test data is to allow for whitespace between the - and numerals:
Dim exp As String = "-[ ]*([0-9]+)[.]txt$"
It's further worth noting that the above will fail if there are text files that don't follow the pattern. We can account for that if needed:
Dim exp As String = "-[ ]*([0-9]+)[.][^.]*$"
Dim docs = Directory.GetFiles(documentPath, "*.txt")
documentPages = docs.OrderBy(
Function(p)
Dim matches As MatchCollection = Regex.Matches(p, exp)
If matches.Count = 0 OrElse matches(0).Groups.Count < 2 Then Return 0
Return Integer.Parse(matches(0).Groups(1).Value)
End Function).ToList()
You could also use Integer.MaxValue as your default option, depending on whether you want those to appear at the beginning or end of the list.

Readonly in keyvaluepair

Well I have created a program that takes some files (Mp3) and change their tags
recently I wanted to add some new Subs (like: Take the songs name and make every letter in it upercase). The problem is that i use a list with its items to be keyvaluepairs
Public MP3List As New List(Of KeyValuePair(Of String, String))
When i tried to edit the key or value of any Item in that list i get an error (That this is READONLY)
Example:
For Each Song In MP3List
Song.Key = "Something"
Next
I add items like this :
Private Function OpenAFile()
Dim MP3List1 = MP3List
Dim oFileDialog As New OpenFileDialog
oFileDialog.Title = "Επέλεξε ένα MP3 Άρχειο"
oFileDialog.Filter = "MP3 Files|*.mp3|All Files|*.*"
oFileDialog.Multiselect = True
Dim Path As String = ""
Dim Name As String = ""
Dim NewPair As New KeyValuePair(Of String, String)
If oFileDialog.ShowDialog = Windows.Forms.DialogResult.OK Then
For Each sPath In oFileDialog.FileNames
Path = New String(sPath)
Name = New String(Strings.Split(Path, "\").ToList(Strings.Split(Path, "\").ToList.Count - 1))
NewPair = New KeyValuePair(Of String, String)(Name, Path)
If Not MP3List1.Contains(NewPair) Then MP3List1.Add(NewPair)
Next
End If
Return MP3List1
End Function
So the idea is this: Each time i press A button to add a song it will run the function OpenAFile() and it was working fine then . Now that i want to change a key or value i get this error
Thanks for the Help and sorry for bad english
The Keys in a KeyValuePair are readonly because they are often used as the key in a hash table. Changing the key would cause issues where you would lose your item in the hash.
If you want to do something like this, you could always create your own data type that stores a key and value. An overly simplified example would be as follows.
Public Structure PathNamePair
Public Property Path As String
Public Property Name As String
Public Sub New(path As String, name As String)
Me.Path = path
Me.Name = name
End Sub
End Structure
I will note that in order to get better performance with your Contains method, you should also implement IEquatable(Of T), but that's probably beyond the scope of this question. I will also note that it is not best practice to have a ValueType (Structure) that is mutable.

Directory.GetFiles(strFolderPath) EXCLUDING Certain File Names VB.NET

How would a do a Directory.GetFiles and exclude files called "abc" and "xyz"?
Basically I have a DIR where all the files exist and a particular group of files has to be sent to one department and the the "abc" and "xyz" file need to be sent to another?
At the moment I do this:
'Standard Documents
Dim strAttachments As List(Of String) = New List(Of String)
strAttachments.AddRange(Directory.GetFiles(GlobalVariables.strFolderPath))
I need the same functionality but excluding the files and then do a similar command as above to include the files to another address.
Cheers,,
James
UPDATE
Dim exclude = {"ATS_Declaration"}
Dim myFiles = From fn In Directory.EnumerateFiles(GlobalVariables.strFolderPath)
Where Not exclude.Contains(Path.GetFileNameWithoutExtension(fn), StringComparer.InvariantCultureIgnoreCase)
Dim strAttachments As List(Of String) = New List(Of String)(myFiles)
You can use LINQ and System.IO.Path.GetFileNameWithoutExtension:
Dim exclude = { "abc", "xyz" }
Dim myFiles = From fn in Directory.EnumerateFiles(GlobalVariables.strFolderPath)
Where Not exclude.Contains(Path.GetFileNameWithoutExtension(fn), StringComparer.InvariantCultureIgnorecase)
Dim strAttachments As List(Of String) = New List(Of String)(myFiles)
Note that i have used EnumerateFiles since that does not need to load all into memory first.
I couldn't get the answers here to exclude the file (not sure why Contains fails to match unless the file name is exactly the same as the exclusion string)
The answer below works wonderfully
Check this out: Exclude files from Directory.EnumerateFiles based on multiple criteria

Sorting the result of Directory.GetFiles in vb.net

I have one directory contain all the tif formate file ,near about 30 file with the name like that B_1 ,B_2...upto B_15 and F_1 ,F_2...upto F_1.
when i am getting file from getfile method.
Dim di As New IO.DirectoryInfo("c:\ABC\")
Dim diar1 As IO.FileInfo() = di.GetFiles()
But while retriving using for each loop i am getting the result like
B_1,B_10,B_11,B_12,B_13,B_14,B_15,B_2,B_3...upto B_9
same like
F_1,F_10,F_11,F_12,F_13,F_14,F_15,F_2,F_3...upto F_9
But problem is that
I want in pattern like
B_1,B_2,B_3,B_4.....B_9,B_10,B_11......B_15
and then
F_1,F_2,F_3,F_4.....F_9,F_10,F_11......F_15
Actually My task is getting all the file from directory and join all the tiff file file like
F_1,B_1,F_2,B_2...F_9,B_9,F_10,B_10,F_11,B_11,....F_15,B_15
I have achieved everthing all means join tiff and but file start with B and F i am storing in respective arrayList but due to comming file in B_,B_10..so on thats why i am getting Problem...
Plz help me...
The simplest solution is to create a method that returns a sort key as a string, for instance, in your situation, something like this should suffice:
Public Function GetFileInfoSortKey(fi As FileInfo) As String
Dim parts() As String = fi.Name.Split("_"c)
Dim sortKey As String = Nothing
If parts.Length = 2 Then
sortKey = parts(1).PadLeft(10) & parts(0)
Else
sortKey = fi.Name
End If
Return sortKey
End Function
Then, you can use that method to easily sort the array of FileInfo objects, like this:
Array.Sort(diar1, Function(x, y) GetFileInfoSortKey(x).CompareTo(GetFileInfoSortKey(y)))
If you don't care about keeping it as an array, you may want to use the OrderBy extension method provided by LINQ:
Dim diar1 As IEnumerable(Of FileInfo) = di.GetFiles().OrderBy(Of String)(AddressOf GetFileInfoSortKey)
Alternatively, if you are using an older version of Visual Studio which does not support lambda expressions, you can do it by creating a separate comparer method, like this:
Public Function FileInfoComparer(x As FileInfo, y As FileInfo) As Integer
Return GetFileInfoSortKey(x).CompareTo(GetFileInfoSortKey(y))
End Function
Then you could call Array.Sort using that comparer method, like this:
Array.Sort(diar1, AddressOf FileInfoComparer)

Retrieving data from xml using xmlDocument, xmlNode and xmlNodelist

This is a sample code in vb.net in which i retrieve the details of elements without attributes.
For Each standardItemInfoNode In ItemInfoNodes
baseDataNodes = ItemInfoNodes.ChildNodes
bFirstInRow = True
For Each baseDataNode As XmlNode In baseDataNodes
If (bFirstInRow) Then
bFirstInRow = False
Else
Response.Write("<br>")
End If
Response.Write(baseDataNode.Name & ": " & baseDataNode.InnerText)
Next
Next
How can i retrieve the details of the xml like having node with attributes and its child also having attributes. I need to retrieve all the attributes of node and its child node which are present in the middle of other xml tags.
I'm not sure exactly what you're asking, and I can't give you a specific example without knowing the format of the XML you are trying to process, but I think what you are looking for is the Attributes property of the XmlNode objects. Each XmlNode has an Attributes property that allows you to access all the attributes for that node. Here's the MSDN page that explains it (and provides a simple example):
http://msdn.microsoft.com/en-us/library/7f285y48.aspx
EDIT:
Using the example XML you posted in the comment, you could read the all the values and attributes like this:
Dim doc As XmlDocument = New XmlDocument()
doc.LoadXml("<EventTracker><StandardItem><Header1>Header1 Text</Header1> <Header2>Header2 Text</Header2></StandardItem><Item> <Events> <EventSub EventId='73' EventName='Orchestra' Description='0'> <Person PersonId='189323156' PersonName='Chandra' Address='Arunachal'/><Person PersonId='189323172' PersonName='Sekhar' Address='Himachal'/></EventSub> </Events> </Item> </EventTracker>")
Dim header1 As String = doc.SelectSingleNode("EventTracker/StandardItem/Header1").InnerText
Dim header2 As String = doc.SelectSingleNode("EventTracker/StandardItem/Header2").InnerText
For Each eventSubNode As XmlNode In doc.SelectNodes("EventTracker/Item/Events/EventSub")
Dim eventId As String = eventSubNode.Attributes("EventId").InnerText
Dim eventName As String = eventSubNode.Attributes("EventName").InnerText
Dim eventDescription As String = eventSubNode.Attributes("Description").InnerText
For Each personNode As XmlNode In eventSubNode.SelectNodes("Person")
Dim personId As String = personNode.Attributes("PersonId").InnerText
Dim personName As String = personNode.Attributes("PersonName").InnerText
Dim personAddress As String = personNode.Attributes("Address").InnerText
Next
Next
However, if you are loading all the data in the XML like this, I would recommend deserializing the XML into an EventTracker object. Or, as I said in my comment on your post, if the only purpose for reading the XML document is to transform it into another XML or HTML document, I would recommend using XSLT, instead.
If you want to test if an attribute exists, you can do something like this:
Dim attribute As XmlNode = personNode.Attributes.GetNamedItem("PersonId")
If attribute IsNot Nothing Then
Dim personId As String = attribute.InnerText
End If
However, this would be much easier with serialization since the deserialized object would simply have null properties for any elements that didn't exist.
You can use SelectSingleNode("XPath or NodeName") and loop through the Attributes.Item(index) on that node. You can also, if you know the childnode name in advance, loop through SelectSingleNode("XPath Or NodeName").SelectSingleNode("XPath or ChildName").Attributes.Item(index).