Keep getting ~N/A - excel

I have a simple 2 column array and all I want to do is use Vlookup to find the matching value in Column 1 and return the value out of column 2. I keep getting #N/A which implies that my "answer" isn't there. I thought I had done everything correctly, but I can't seem to find the answer despite reading MANY #N/A questions. This is my code:-
Dim y as Variant
Dim Misc_Pay(1 to 16,1 to 2 ) as Variant.
' Populate the Misc_Pay array
Misc_Pay(1,2) = "Cancer,3"
Misc_Pay(2,2) = "Clerical,Last of Month"
Misc_Pay(3,2) = "Halifax,14"
Misc_Pay(4,2) = "Reward,1"
'and so on down to
Misc_Pay(16,2) = "Last Line,End of File"
'My Vlook up code is:-
y = (Application.Vlookup("Reward",Misc_Pay,2,False)
I've then been printing "y" out so I can see what value it is picking up, except it doesn't appear to be picking anything up, but e.g. "Reward" is patently there.
If I can get this line to work then the full line code will be:-
If(iserror(Application.Vlookup("Reward,Misc_Bay,2,False) then 'do something' Else 'do something else'

To start, the code you posted won't compile, due to the period at the end on line 2, and the extra ( on the last line.
If you're trying to do a lookup in a 2D array, you need to set values for both the key and lookup columns (in this case, columns 1 and 2 respectively). Currently, you're just assigning the entire string with the comma to the second column, so there's nothing to look up in the first column.
I think what you want is this:
Dim y As Variant
Dim Misc_Pay(1 To 16, 1 To 2) As Variant
Misc_Pay(1, 1) = "Cancer"
Misc_Pay(1, 2) = "3"
Misc_Pay(2, 1) = "Clerical"
Misc_Pay(2, 2) = "Last of Month"
Misc_Pay(3, 1) = "Halifax"
Misc_Pay(3, 2) = "14"
Misc_Pay(4, 1) = "Reward"
Misc_Pay(4, 2) = "1"
Misc_Pay(16, 1) = "Last Line"
Misc_Pay(16, 2) = "End of File"
y = Application.VLookup("Reward", Misc_Pay, 2, False)

Related

Find and string in a string and change it

I have a 2-dimensional array of values looking like that:
In a different table, I have long strings with VALUE_1, VALUE_2 that can be found anywhere. It looks like in the table below:
Now, I want to write a program that translates the existing VALUE_1, VALUE_2 etc. in the long strings by adding the respective element in the 2nd dimension of the array (/BB, /CCC etc.) and if necessary duplicating and separating the values with a comma and a blank space. So VALUE_1 for example is turned into VALUE_1/BB, VALUE_1/A for each finding in the string. The result is supposed to look exactly like in the table below.
That's challenging. I my first approach I tried to locate the VALUE_1, VALUE_2 in the strings by using InStr() but I don't think that this will help me since only the first hit is taken into consideration. I need every occurrence.
For i = 1 To Worksheets("table2").Range("H1").End(xlDown).Row
For j = LBound(arr2) To UBound(arr2)
If InStr(Worksheets("table2").Range("H" & i), arr2(j, 0)) > 0 Then
Worksheets("table2").Range("H" & i).Font.Bold = True
End If
Next j
Next i
Use your 2D table to build a scripting dictionary so that value1 is associated with the concatenation of all column values in column 2 that have value 1 in the first column.
In the (untested) code below the array (ipArray)is that derived from the 2D range.
Public Function GetReplacements(ByVal ipArray As Variant) As Scripting.dictionary
Dim myD As Scripting.dictionary
Set myD = New Scripting.dictionary
Dim myIndex As Long
For myIndex = LBound(ipArray) To UBound(ipArray)
Dim myKey As String
myKey = ipArray(myIndex, 1)
Dim myItem As String
myItem = ipArray(myIndex, 2)
If myD.exists(myKey) Then
myD.Item(myKey) = myD.Item(myKey) & ", " & myKey & myItem
Else
myD.Add myKey, myKey & myItem
End If
Next
Set GetReplacements = myD
End Function
Now when you find an item such as "Value 1" you can replace with the value retrieved from the dictionary.
Building on #freeflow's excellent answer, I would also use a Scripting.Dictionary to hold the mappings from VALUE1 etc. to the target text.
I would then use Replace for each key in the Dictionary. You can loop like:
Dim key as Variant
For Each key in dict
Replace(<your string>, CStr(key), dict(key))
Next key
This will work so long as all your 'find' strings are totally unique i.e. none of them appears within another - so if you had "Value" and "Value 1" it would not work. Also, the simplest form of this method only works if there is a one-to-one mapping of text strings.
Thus, if your sample data is representative, you would want to look into using the Count argument of Replace so that you can replace the second occurrence of VALUE_4 with the different text, and so on.
I would do this by storing the dict values as an array e.g.
Dim my_arr(1 to 3) as String
my_arr(1) = "VALUE_4/CCC"
my_arr(2) = "VALUE_4/DDDD"
my_arr(3) = "VALUE_4/A"
dict.Add "VALUE_4", my_arr
Then when you are looping through, you can keep track of a counter (call it 'i' for example) and then you can just use Replace with a count of 1, increment 'i' by 1, and then use 'i' in each iteration to call on the relevant element of the array stored against VALUE_4 in the dict, like:
For Each key in dict
For i = LBound(dict(key)) to UBound(dict(key))
Replace (<your string>, CStr(key), dict(key)(i), 1, 1)
Next i
Next key
Hopefully you can build from there to what you need? Having reread your original post, I actually think my simplest solution would work (but I'll leave the more complex solution there in case it's of use to you or others), so long as dict is used to store the one-to-one mapping of, for example, "VALUE_1" to "VALUE_1/BB, VALUE_1/A" - you can loop through your original table and build those strings by concatenation - maybe even directly in the dict:
For Each cell in TableCol1 ' assuming it is cells, otherwise use an appropriate form of loop
tmp_str = cell.Value2
If dict.Exists(tmp_str) Then
dict(tmp_str) = dict(tmp_str) + ", " + tmp_str+cell.Offset(0,1).Value2
Else
dict.Add tmp_str, tmp_str + cell.Offset(0,1).Value2
End If
Next cell

update data via macro from another workbook

I need some help with vba code. I'm self-lerning so please be understanding for simply cases ;)
I'm trying to create macro which will be looking for some records/cells in one workbook (FileA) (determined by 3 conditions) and then paste values to another workbook (FileB) and after that find in another tab in FileB some values where condition will be pasted value to match them with looking value (I belivie it could be done somehow with Vlookup but I get stuck).
Below problematic for me part of code (I'm working on some files found in work, no one use it now).
First issue is with Set Update, I don't know why it takes it as "Nothing"... conditions are true - I mean "pp1" is existing in column A in FileA.
Second issue shows when I change i start range to some "later" numbers, eg From i = 2280, macro is ignoring last line where should assign some values (again shows update2 as "nothing") but value if pp2 is existing in W column in tab data...
Dim name As String
name = "[path to file on sharepoint]"
Application.ScreenUpdating = False
Workbooks.Open Filename:=name
a = 1
For i = 2263 To 14000
If Workbooks("FileA").Sheets("Main").Cells(i, 11) = "CANCEL" And Workbooks("FileA").Sheets("Main").Cells(i, 6) = "DENIS" And Workbooks("FileA").Sheets("Main").Cells(i, 5) > 1301358454 Then
pp1 = Workbooks("FileA").Sheets("Main").Cells(i, 1)
If pp1 > 0 Then
Set Update = Workbooks("FileA").Worksheets("Main").Range("A:A").Find(pp1, lookat:=xlPart)
If Update > 0 Then
Update = Update.Row
Workbooks("FileB").Worksheets("lost").Cells(a, 1).Value = Workbooks("FileA").Worksheets("Main").Cells(Update, 5)
pp2 = Workbooks("FileB").Worksheets("lost").Cells(a, 1)
update2 = Workbooks("FileB").Worksheets("data").Range("W:W").Find(pp2, lookat:=xlPart).Row
Workbooks("FileB").Worksheets("lost").Cells(a, 5) = Workbooks("FileB").Worksheets("data").Cells(update2, 43)

How to loop through XML-nodes and validate if values exists?

I have through an API fetched my data as an XML, and I wish to cycle through nodes (there are several of the same type) and add them to certain fields/a table.
Example from the XML-file:
<HistRating
xmlns="">
<EndrAr>2020</EndrAr>
<EndrMnd>7</EndrMnd>
<Rating>A</Rating>
</HistRating>
<HistRating
xmlns="">
<EndrAr>2019</EndrAr>
<EndrMnd>6</EndrMnd>
<Rating>A</Rating>
</HistRating>
I have tried the following format (at this point the XML I need is in a string in xmlDoc xmlDoc = CreateObject("MSXML2.DOMDocument.6.0"). Fully aware that this is not a really "sexy" way to write it, but I'm new at this game:
Set nodeXML = xmlDoc.getElementsByTagName("EndrAr")
Range("G1").Value = nodeXML(1).Text
Range("H1").Value = nodeXML(2).Text
Range("I1").Value = nodeXML(3).Text
Set nodeXML = xmlDoc.getElementsByTagName("EndrMnd")
Range("G2").Value = nodeXML(1).Text
Range("H2").Value = nodeXML(2).Text
Range("I2").Value = nodeXML(3).Text
Set nodeXML = xmlDoc.getElementsByTagName("Rating")
Range("G3").Value = nodeXML(1).Text
Range("H3").Value = nodeXML(2).Text
Range("I3").Value = nodeXML(3).Text
This works great as long as all three items are there. Unfortunately that is not given. If it is a new company i.e. (3) wont exist (there is one line per year above), and I would like to either set the cell to Blank or No value or something.
The result from when I run the above code:
But if I try to add a line 4 to test what happens if value does not exists I get the following (for obvious reasons)
What I would love some help with is:
Can I by some "magic" add a ifmissing (tried it, but could not get it to work)?
Other ways to add a if variable is not found, input following into cell
Or are there a complete different way I should have solved this?
This is to add accounting data from last X available years (where X is ie 4, or less if not 4 is available) from 30 nodes.
You could use an Error trapping Function. Note in the code below we choose not to use the returned boolean.
Dim myTest as String
.
.
TryReadingXmlNode nodeXML,1, myText
Range("G1").Value = myText
.
.
Public Function TryReadingXmlNode(ByVal ipNode as object, ByVal ipIndex as Long, ByRef opText as string) as boolean
On Error Resume Next
opText=ipNode.Item(ipIndex).Text
TryReadingXmlNode=Len(opText)>0
If err.number>0 then opText="NoValue"
on Error Goto 0
End Function
Start by querying all of the HistRating elements, then loop over that collection:
Const MAX_YEARS As Long = 4
Dim ratings, rating, c As Range, i as Long
Set c= Range("A1")
Set ratings = xmlDoc.getElementsByTagName("HistRating")
For Each rating in ratings
c.offset(0, i) = rating.getElementsByTagName("EndrAr")(0).Text
c.offset(1, i) = rating.getElementsByTagName("EndrMnd")(0).Text
c.offset(2, i) = rating.getElementsByTagName("Rating")(0).Text
i = i + 1
If i >= MAX_YEARS Then Exit For 'exit if processed enough nodes
Next rating

Importing CustomDocumentProperties from Word to Excel using VBA

I am trying to pull CustomDocumentProperties from a Word Document (that I select using Application.GetOpenFilename) to an Excel Sheet.
I can get the to run code using the number of the item:
Set ExcelRange = Range("DataFields")
For r = 1 To ExcelRange.Rows.Count Step 1
ExcelWorkbook.Sheets("Sheet1").Cells(r, 3) = WordDoc.CustomDocumentProperties(r).Value
Next r
If I hard code the name of the custom property, it also works:
Set ExcelRange = Range("DataFields")
For r = 1 To ExcelRange.Rows.Count Step 1
ExcelWorkbook.Sheets("Sheet1").Cells(r, 3) = WordDoc.CustomDocumentProperties("Subject Name").Value
Next r
Obviously in this case, it returns the "Subject Name" 7 times).
I don't want to return all the values - only specific ones based on the values of a named range (in this case DataFields).
DataFields references A1:A7. I would like to take those cells(that contain the names of the custom properties) and paste the value of the corresponding custom properties in C1:C7
However, I can seem to get the code to return values in C1:C7 based on the values in A1:A7.
Based on my (limited) knowledge, I thought that the following would return the desired results, but it's not working for me:
Set ExcelRange = Range("DataFields")
For r = 1 To ExcelRange.Rows.Count Step 1
ExcelWorkbook.Sheets("Sheet1").Cells(r, 3) = WordDoc.CustomDocumentProperties(ExcelRange(r, 1))
Next r
Any help would be appreciated.
Thanks!

Extract tables from pdf (to excel), pref. w/ vba

I am trying to extract tables from pdf files with vba and export them to excel. If everything works out the way it should, it should go all automatic. The problem is that the table are not standardized.
This is what I have so far.
VBA (Excel) runs XPDF, and converts all .pdf files found in current folder to a text file.
VBA (Excel) reads through each text file line by line.
And the code:
With New Scripting.FileSystemObject
With .OpenTextFile(strFileName, 1, False, 0)
If Not .AtEndOfStream Then .SkipLine
Do Until .AtEndOfStream
//do something
Loop
End With
End With
This all works great. But now I am getting to the issue of extracting the tables from the text files.
What I am trying to do is VBA to find a string e.g. "Year's Income", and then output the data, after it, into columns. (Until the table ends.)
The first part is not very difficult (find a certain string), but how would I go about the second part. The text file will look like this Pastebin. The problem is that the text is not standardized. Thus for example some tables have 3-year columns (2010 2011 2012) and some only two (or 1), some tables have more spaces between the columnn, and some do not include certain rows (such as Capital Asset, net).
I was thinking about doing something like this but not sure how to go about it in VBA.
Find user defined string. eg. "Table 1: Years' Return."
a. Next line find years; if there are two we will need three columns in output (titles +, 2x year), if there are three we will need four (titles +, 3x year).. etc
b. Create title column + column for each year.
When reaching end of line, go to next line
a. Read text -> output to column 1.
b. Recognize spaces (Are spaces > 3?) as start of column 2. Read numbers -> output to column 2.
c. (if column = 3) Recognize spaces as start of column 3. Read numbers -> output to column 3.
d. (if column = 4) Recognize spaces as start of column 4. Read numbers -> output to column 4.
Each line, loop 4.
Next line does not include any numbers - End table. (probably the easiet just a user defined number, after 15 characters no number? end table)
I based my first version on Pdf to excel, but reading online people do not recommend OpenFile but rather FileSystemObject (even though it seems to be a lot slower).
Any pointers to get me started, mainly on step 2?
You have a number of ways to dissect a text file and depending on how complex it is might cause you to lean one way or another. I started this and it got a bit out of hand... enjoy.
Based on the sample you've provided and the additional comments, I noted the following. Some of these may work well for simple files but can get unwieldy with bigger more complex files. Furthermore, there may be slightly more efficient methods or tricks to what I have used here but this will definitely get you going an achieve the desired outcome. Hopefully this makes sense in conjunction with the code provided:
You can use booleans to help you determine what 'section' of the text file you are in. Ie use InStr on the current line to
determine you are in a Table by looking for the text 'Table' and then
once you know you are in the 'Table' section of the file start
looking for the 'Assets' section etc
You can use a few methods to determine the number of years (or columns) you have. The Split function along with a loop will do
the job.
If your files always have constant formatting, even only in certain parts, you can take advantage of this. For example, if you know your
file line will always have a dollar sign in front of the them, then
you know this will define the column widths and you can use this on
subsequent lines of text.
The following code will extract the Assets details from the text file, you can mod it to extract other sections. It should handle multiple rows. Hopefully I've commented it sufficient. Have a look and I'll edit if needs to help out further.
Sub ReadInTextFile()
Dim fs As Scripting.FileSystemObject, fsFile As Scripting.TextStream
Dim sFileName As String, sLine As String, vYears As Variant
Dim iNoColumns As Integer, ii As Integer, iCount As Integer
Dim bIsTable As Boolean, bIsAssets As Boolean, bIsLiabilities As Boolean, bIsNetAssets As Boolean
Set fs = CreateObject("Scripting.FileSystemObject")
sFileName = "G:\Sample.txt"
Set fsFile = fs.OpenTextFile(sFileName, 1, False)
'Loop through the file as you've already done
Do While fsFile.AtEndOfStream <> True
'Determine flag positions in text file
sLine = fsFile.Readline
Debug.Print VBA.Len(sLine)
'Always skip empty lines (including single spaceS)
If VBA.Len(sLine) > 1 Then
'We've found a new table so we can reset the booleans
If VBA.InStr(1, sLine, "Table") > 0 Then
bIsTable = True
bIsAssets = False
bIsNetAssets = False
bIsLiabilities = False
iNoColumns = 0
End If
'Perhaps you want to also have some sort of way to designate that a table has finished. Like so
If VBA.Instr(1, sLine, "Some text that designates the end of the table") Then
bIsTable = False
End If
'If we're in the table section then we want to read in the data
If bIsTable Then
'Check for your different sections. You could make this constant if your text file allowed it.
If VBA.InStr(1, sLine, "Assets") > 0 And VBA.InStr(1, sLine, "Net") = 0 Then bIsAssets = True: bIsLiabilities = False: bIsNetAssets = False
If VBA.InStr(1, sLine, "Liabilities") > 0 Then bIsAssets = False: bIsLiabilities = True: bIsNetAssets = False
If VBA.InStr(1, sLine, "Net Assests") > 0 Then bIsAssets = True: bIsLiabilities = False: bIsNetAssets = True
'If we haven't triggered any of these booleans then we're at the column headings
If Not bIsAssets And Not bIsLiabilities And Not bIsNetAssets And VBA.InStr(1, sLine, "Table") = 0 Then
'Trim the current line to remove leading and trailing spaces then use the split function to determine the number of years
vYears = VBA.Split(VBA.Trim$(sLine), " ")
For ii = LBound(vYears) To UBound(vYears)
If VBA.Len(vYears(ii)) > 0 Then iNoColumns = iNoColumns + 1
Next ii
'Now we can redefine some variables to hold the information (you'll want to redim after you've collected the info)
ReDim sAssets(1 To iNoColumns + 1, 1 To 100) As String
ReDim iColumns(1 To iNoColumns) As Integer
Else
If bIsAssets Then
'Skip the heading line
If Not VBA.Trim$(sLine) = "Assets" Then
'Increment the counter
iCount = iCount + 1
'If iCount reaches it's limit you'll have to redim preseve you sAssets array (I'll leave this to you)
If iCount > 99 Then
'You'll find other posts on stackoverflow to do this
End If
'This will happen on the first row, it'll happen everytime you
'hit a $ sign but you could code to only do so the first time
If VBA.InStr(1, sLine, "$") > 0 Then
iColumns(1) = VBA.InStr(1, sLine, "$")
For ii = 2 To iNoColumns
'We need to start at the next character across
iColumns(ii) = VBA.InStr(iColumns(ii - 1) + 1, sLine, "$")
Next ii
End If
'The first part (the name) is simply up to the $ sign (trimmed of spaces)
sAssets(1, iCount) = VBA.Trim$(VBA.Mid$(sLine, 1, iColumns(1) - 1))
For ii = 2 To iNoColumns
'Then we can loop around for the rest
sAssets(ii, iCount) = VBA.Trim$(VBA.Mid$(sLine, iColumns(ii) + 1, iColumns(ii) - iColumns(ii - 1)))
Next ii
'Now do the last column
If VBA.Len(sLine) > iColumns(iNoColumns) Then
sAssets(iNoColumns + 1, iCount) = VBA.Trim$(VBA.Right$(sLine, VBA.Len(sLine) - iColumns(iNoColumns)))
End If
Else
'Reset the counter
iCount = 0
End If
End If
End If
End If
End If
Loop
'Clean up
fsFile.Close
Set fsFile = Nothing
Set fs = Nothing
End Sub
I cannot examine the sample data as the PasteBin has been removed. Based on what I can glean from the problem description, it seems to me that using Regular Expressions would make parsing the data much easier.
Add a reference to the Scripting Runtime scrrun.dll for the FileSystemObject.
Add a reference to the Microsoft VBScript Regular Expressions 5.5. library for the RegExp object.
Instantiate a RegEx object with
Dim objRE As New RegExp
Set the Pattern property to "(\bd{4}\b){1,3}"
The above pattern should match on lines containing strings like:
2010
2010 2011
2010 2011 2012
The number of spaces between the year strings is irrelevant, as long as there is at least one (since we're not expecting to encounter strings like 201020112012 for example)
Set the Global property to True
The captured groups will be found in the individual Match objects from the MatchCollection returned by the Execute method of the RegEx object objRE. So declare the appropriate objects:
Dim objMatches as MatchCollection
Dim objMatch as Match
Dim intMatchCount 'tells you how many year strings were found, if any
Assuming you've set up a FileSystemObject object and are scanning the text file, reading each line into a variable strLine
First test to see if the current line contains the pattern sought:
If objRE.Test(strLine) Then
'do something
Else
'skip over this line
End If
Set objMatches = objRe.Execute(strLine)
intMatchCount = objMatches.Count
For i = 0 To intMatchCount - 1
'processing code such as writing the years as column headings in Excel
Set objMatch = objMatches(i)
e.g. ActiveCell.Value = objMatch.Value
'subsequent lines beneath the line containing the year strings should
'have the amounts, which may be captured in a similar fashion using an
'additional RegExp object and a Pattern such as "(\b\d+\b){1,3}" for
'whole numbers or "(\b\d+\.\d+\b){1,3}" for floats. For currency, you
'can use "(\b\$\d+\.\d{2}\b){1,3}"
Next i
This is just a rough outline of how I would approach this challenge. I hope there is something in this code outline that will be of help to you.
Another way to do this I have some success with is to use VBA to convert to a .doc or .docx file and then search for and pull tables from the Word file. They can be easily extracted into Excel sheets. The conversion seems to handle tables nicely. Note however that it works on a page by page basis so tables extending over a page end up as separate tables in the word doc.

Resources