VBA take screenshot of filtered Excel and send to each row in iteration - vba

I want to run a Macro in Excel to loop through a number of rows, apply a filter to a spreadsheet with the name of the person, take a screenshot, and send an Email to that person with the screenshot.
My current code does not iterate through a range (only 1 record), and does not take a screenshot and insert into email.
Would greatly appreciate assistance with this.
My current code:
Sub SendEmailtoEachResource_Click()
' Macro - Intended functionality
' For each person (resource) apply a filter to the 'Allocation'
' tab, and take a screenshot. Send an email with the screenshot
' to each person.
Dim Resoucename As String
Dim ResouceEmail As String
'Current State: Apply filter, and send 1 email to the below details
ResourceName = Range("D4")
resourceEmail = Range("E4")
'ACTION - Future State:
'For each person in column D
'Send email to email address on same row in Coumn E
' ##Start Loop
'Go to Allocation Tab, Apply Filter of resouce name
Sheets("Allocation").Select
Range("A1:BH28").Select
ActiveSheet.Range("$A$8:$BI$826").AutoFilter Field:=5, Criteria1:= _
ResourceName
ActiveWindow.SmallScroll Down:=-21
ActiveWindow.ScrollRow = 9
Range("A1:BV836").Select
' ACTION: Take Screenshot of filtered results
'setup email
Dim aOutlook As Object
Dim aEmail As Object
Dim outlookResName As String
Dim SendAddress As String
Set aOutlook = CreateObject("Outlook.Application")
Set aEmail = aOutlook.CreateItem(0)
outlookResName = ActiveCell.Value
SendAddress = "me#email.com"
aEmail.To = resourceEmail
aEmail.Subject = "Resource assignment Report for " & ResourceName
aEmail.HTMLBody = "Your report is below {Insert Screenshot}"
'ACTION: Paste screenshot HERE
aEmail.display
' Will change to .send when VBA is working fully. This could send ~100 emails
' ## End LOOP
End Sub

It seems to me that you have two questions rolled up in one here: (1) how to loop through the rows of your spreadsheet and (2) how to take a screenshot and insert it into the e-mail. Maybe you should consider posting two separate questions.
With that in mind, I'll address the loop issue. There are many ways to achieve what you want.
A) You could use the row numbers
For i = 7 To 9
ResourceName = Cells(i, 4)
ResourceEmail = Cells(i, 5)
' The rest of your code here
Next i
B) You could start at the first row and keep moving down until you find an empty cell.
i = 7
Do Until Cells(i, 4) = ""
ResourceName = Cells(i, 4)
ResourceEmail = Cells(i, 5)
' The rest of your code here
i = i + 1
Loop
C) You could give the cells containing the list of resources a name (say, "resources") and loop through its rows.
Set MyList = ActiveWorkbook.Names("resources").RefersToRange
For Each person In MyList.Rows
ResourceName = person.Cells(1, 4)
ResourceEmail = person.Cells(1, 5)
' The rest of your code here
Next person
Why don't you choose one method and then we see where we go from there?

Related

Extracting cell contents based on the string within the cell using dictionaries

I am quite new to VBA and was looking through stackoverflow. I found a neat VBA macro that utilizes Dictionaries and seemed like it could be applied to my problem also. However, after editing the macro I can't seem to make it work like I mean to.
My data is as follows: In column A I have Review numbers, Review topics and Analysis numbers. These follow a structure where Review number is 1st, then 2 rows later there is Review topic and under Review topic can be multiple Analysis numbers but the 1st one is 2 rows below the Review topic. In column B there are details about the reviewed item. I am interested in 3 different ones(height, weight and price). There is sometimes extra details here which is why I use string-matching (InStr). Sometimes there are less details. In general the data does not have a specific enough form to rely on the amount of rows between specific groups of data.
Data looks generally like this: https://imgur.com/a/QcdrMcR
The goal is to move extract the contents of the cells containing Review number, Review topic, Analysis number, Height, Weight and Price. These should be in separate cells on the same row. In case of multiple analysis, the following analysis should be below the row containing the 1st analysis and height, weight and price following this like before. Review number and topic dont need to be duplicated.
In the code I use dictionaries and a whole lotta ElseIfs. As I said, this is largely taken from another post. It works fine if I try to find the 1st analysis detail but when I try to find all 3 it stops working altogether giving me error 424 object required on the 2 last ElseIfs in the 1st loop. Moreover, the part that mostly works (finding the detailA which is height) only works if the searched string is found on the cell one row below the current row. In other words, it only works if the height/detailA is on row i+1
Sub FindData()
Dim datasheet As Worksheet
Dim reportsheet As Worksheet
Dim SearchString As String
Dim SearchString2 As String
Dim i As Integer
Set datasheet = Sheet1
Set reportsheet = Sheet2
Dim chNum As String
Dim chSub As String
Dim analysisNum As String
Dim detailA As String
Dim detailB As String
Dim detailC As String
Dim ReviewCollection As New Dictionary
Dim dictKey1 As Variant
Dim dictKey2 As Variant
Dim dictKey3 As Variant
Dim dictKey4 As Variant
Dim dictKey5 As Variant
Dim dictKey6 As Variant
reportsheet.Range("A1:H200").ClearContents
finalrow = datasheet.Cells(datasheet.Rows.Count, 1).End(xlUp).Row
For i = 1 To finalrow
SearchString = datasheet.Range("A" & i)
SearchString2 = datasheet.Range("B" & i)
If InStr(1, SearchString, "Review number") Then
chNum = datasheet.Cells(i, 1)
ReviewCollection.Add chNum, New Dictionary 'For review numbers
ElseIf InStr(1, SearchString, "Review topic") Then
chSub = datasheet.Cells(i, 1)
ReviewCollection.Item(chNum).Add chSub, New Dictionary 'For review topics
ElseIf InStr(1, SearchString, "Analysis number") Then
analysisNum = datasheet.Cells(i, 1)
ReviewCollection.Item(chNum).Item(chSub).Add analysisNum, New Dictionary 'For Analysis numbers
ElseIf InStr(1, SearchString2, "Height") Then
detailA = datasheet.Cells(i, 2)
ReviewCollection.Item(chNum).Item(chSub).Item(analysisNum).Add detailA, New Dictionary 'For Analysis detail #1
'ElseIf InStr(1, SearchString2, "Weight") Then
' detailB = datasheet.Cells(i, 2)
' ReviewCollection.Item(chNum).Item(chSub).Item(analysisNum).Item(detailA).Add detailB, New Dictionary 'For Analysis detail #2
'ElseIf InStr(1, SearchString2, "Price") Then
' detailA = datasheet.Cells(i, 2)
' ReviewCollection.Item(chNum).Item(chSub).Item(analysisNum).Item(detailA).Item(detailB).Add detailC, New Dictionary 'For Analysis detail #3
End If
Next i
'Loop to print out the dictionary
i = 1
For Each dictKey1 In ReviewCollection.Keys
reportsheet.Cells(i, 1) = dictKey1 'Review number
If ReviewCollection.Item(dictKey1).Count > 0 Then
For Each dictKey2 In ReviewCollection.Item(dictKey1).Keys
reportsheet.Cells(i, 2) = dictKey2 'Review topic
If ReviewCollection.Item(dictKey1).Item(dictKey2).Count > 0 Then
For Each dictKey3 In ReviewCollection.Item(dictKey1).Item(dictKey2).Keys 'Report Number
reportsheet.Cells(i, 3) = dictKey3
If ReviewCollection.Item(dictKey1).Item(dictKey2).Item(dictKey3).Count > 0 Then
For Each dictKey4 In ReviewCollection.Item(dictKey1).Item(dictKey2).Item(dictKey3).Keys 'Analysis detail #1
reportsheet.Cells(i, 4) = dictKey4
'START of the printing for the problematic area
If ReviewCollection.Item(dictKey1).Item(dictKey2).Item(dictKey3).Item(dictKey4).Count > 0 Then
For Each dictKey5 In ReviewCollection.Item(dictKey1).Item(dictKey2).Item(dictKey3).Item(dictKey4).Keys 'Analysis detail #2
reportsheet.Cells(i, 5) = dictKey5
If ReviewCollection.Item(dictKey1).Item(dictKey2).Item(dictKey3).Item(dictKey4).Item(dictKey5).Count > 0 Then
For Each dictKey6 In ReviewCollection.Item(dictKey1).Item(dictKey2).Item(dictKey3).Item(dictKey4).Item(dictKey5).Keys 'Analysis detail #3
reportsheet.Cells(i, 6) = dictKey6
Next dictKey6
Else
i = i + 1 'no reports, so moves down to prevent overwriting change number
End If
Next dictKey5
Else
i = i + 1 'no reports, so moves down to prevent overwriting change number
End If
'END of the problematic area
Next dictKey4
Else
i = i + 1 'no reports, so moves down to prevent overwriting change number
End If
Next dictKey3
Else
i = i + 1 'no reports, so moves down to prevent overwriting change number
End If
Next dictKey2
Else
i = i + 1 'no change subject, so moves down to prevent overwriting change number
End If
Next dictKey1
End Sub
I am also open to any other improvements. My logic seems very heavy but I couldn't get it working even to this degree with any of the other ways (tried using more loops and less if-structures).
I plan on trimming the contents of the cells to only include the numbers but this is a worry for the future. I already have made working excel formulas for this.

How to get multiple results with one vlookup in VBA, Where vlookup is the part of the whole string (vlookup value)

I have 3 sheets, in sheet one I have a column "Register Codes" and I have extracted the unique codes in next column. Please check the below image.
Based on these unique codes, sub-codes are allocated in sheet 2. please check the below image.
Now what I am trying here is that in sheet 3 I need every "Register code" with the relevant "sub-code" which is allocated in sheet2 based on the "unique ID" given in Sheet1. please check the below image for expected output.
I have been using various combinations of formulas but could not get a proper solution. What is the best way to do it in VBA as I just started learning in this field.
Subject to a few conditions the following code will do what you want. Install it in a standard code module (by default "Module1", but you can name it as you like) in the workbook where you have your data.
Option Explicit
Enum Nws ' Worksheet navigation
NwsFirstDataRow = 2 ' presumed the same for all worksheets
NwsCode = 1 ' 1 = column A (change as required)
NwsSubCode ' No value means previous + 1
NwsNumer
End Enum
Sub NumerList()
' 05 Apr 2017
Dim Wb As Workbook ' all sheets are in the same workbook
Dim WsCodes As Worksheet ' Register codes
Dim WsNum As Worksheet ' Sub-code values
Dim WsOut As Worksheet ' Output worksheet
Dim RegName As String, RegCode As String
Dim Sp() As String
Dim Rs As Long ' Source row in WsNum
Dim Rt As Long ' Target row in WsOut
Dim R As Long, Rl As Long ' rows / Last row in WsCodes
Set Wb = ActiveWorkbook ' Make sure it is active!
Set WsCodes = Wb.Worksheets("Reg Codes") ' Change name to your liking
Set WsNum = Wb.Worksheets("Code Values") ' Change name to your liking
On Error Resume Next
Set WsOut = Wb.Worksheets("Output") ' Change name to your liking
If Err Then
Set WsOut = Wb.Worksheets.Add(After:=WsNum)
WsOut.Name = "Output" ' create the worksheet if it doesn't exist
End If
On Error GoTo 0
Rt = NwsFirstDataRow
With WsCodes
Rl = .Cells(.Rows.Count, NwsCode).End(xlUp).Row
For R = NwsFirstDataRow To Rl
RegName = .Cells(R, NwsCode).Value
Sp = Split(RegName, "-")
If UBound(Sp) > 1 Then ' must find at least 2 dashes
RegCode = Trim(Sp(1))
Else
RegCode = ""
End If
If Len(RegCode) Then
On Error Resume Next
Rs = WorksheetFunction.Match(RegCode, WsNum.Columns(NwsCode), 0)
If Err Then Rs = 0
On Error GoTo 0
If Rs Then
Do
WsOut.Cells(Rt, NwsCode).Value = RegName
WsOut.Cells(Rt, NwsSubCode).Value = WsNum.Cells(Rs, NwsSubCode).Value
WsOut.Cells(Rt, NwsNumer).Value = WsNum.Cells(Rs, NwsNumer).Value
Rt = Rt + 1
Rs = Rs + 1
Loop While WsNum.Cells(Rs, NwsCode).Value = RegCode
Else
RegCode = ""
End If
End If
If Len(RegCode) = 0 Then
WsOut.Cells(Rt, NwsCode).Value = RegName
WsOut.Cells(Rt, NwsSubCode).Value = "No sub-code found"
Rt = Rt + 1
End If
Next R
End With
End Sub
And here are the conditions.
All 3 sheets must be in the same workbook. If you have them in different workbooks the code must be adapted to handle more than one workbook.
The two worksheets with data must exist. They must be named as the code prescribes or the code must be modified to match the names they have. The same goes for the Output worksheet, but that sheet will be created by the code if it doesn't exist. You can change its name in the code.
The enumeration at the top of the code presumes that all 3 sheets are identically formatted with no data in row 1 (captions) and data in columns A, B and C. Changes aren't difficult but must be made if you want a different input or output. You can change the columns in the existing code by assigning other values to the columns in the enum, but the code requires the same arrangement in all sheets.
The extracted codes in the Codes sheet aren't used. The code does its own extraction. It will mark an error in the output list if a code can't be extracted or if it isn't found in the Sub-code list.
The sub-codes in the Numer sheet must be sorted like the picture you posted. The code will look for the first occurrence of "image" and find the subcodes in the following rows while the code is "image" in column A. It will not find further occurrences of "image" that might follow after an intermission.
The code doesn't do any colouring. Adding it wouldn't be difficult, but you would have to specify some rules, such as "20 different colours for the first 20 codes and then repeat the same sequence".
Other cell formatting could be added without much effort because each cell is already individually named. More properties can be added easily.

Website data table scraper

Before I ask my question, I'm an amateur coder with basically no meaningful experience beyond VBA in ms office apps (I know - noob!)
I'm trying to create a web scraper using VBA to import data into excel and as per my comments in the below extract of code, the best I've been able to find on this is was in the winning answer to this question.
Below, I'm using investing.com as an example but in reality my project will be across multiple sites and will feed into a matrices which will be updating daily and self cannibalizing as events expire - For this reason I'd rather front-up the workload on the code side to make the inputs on an ongoing basis as minimal as possible (for me).
With that in mind, can I ask if there's a way to do any of the following (brace yourself, this will be cringe-worthy basic knowledge for some):
Is there a way in which I can and navigate to a url and run a for each loop on every table on that page (without have a known id for any)? this is to speed up my code as much as it's to minimise my inputs as there'll be quite a bit of data to be updated and I was planning on putting a 2 minute looping trigger on the refresh.
Instead of doing what I've been doing below, is it possible to reference a table, rather than a row, and do something along the lines of Cells(2,5).value to return the value within row 1, column 4? (assuming that both the array indexing starts at 0 in both dimensions?) Further to that, my first column (my primary key in some ways) may not be in the same order on all sources so is there a way in which I could do the equivalent to Columns("A:A").Find(What:=[Primary key], After:=Cells(1, 1), LookIn:=xlValues, LookAt:=xlWhole, SearchOrder:=xlByRows, SearchDirection:=xlNext, MatchCase:=True, SearchFormat:=False).Row to find what row within the table relates to the even I'm looking for?
Code :
Sub Scraper()
Dim appIE, allRowOfData As Object
' As per https://stackoverflow.com/questions/27066963/scraping-data-from-website-using-vba
Set appIE = CreateObject("internetexplorer.application")
With appIE
.Navigate "http://uk.investing.com/rates-bonds/financial-futures" 'Sample page
.Visible = False
End With
Do While appIE.Busy
Application.Wait (Now + TimeValue("0:00:01")) 'If page not open, wait a second befor trying again
Loop
Set allRowOfData = appIE.document.getElementById("pair_8907")
'tr id="[ID of row within table]"
Dim myValue As String: myValue = allRowOfData.Cells(8).innerHTML
'The 8 is the column number of the table
'(note: column numbers start at 0 so the 9th column should have "8" entered here
Set appIE = Nothing
Range("A1").Value = myValue
End Sub
If you want to use Excel functions to navigate the tables why not dump the tables first onto a worksheet this code works for me
Option Explicit
Sub Scraper()
Dim appIE As Object
' As per http://stackoverflow.com/questions/27066963/scraping-data-from-website-using-vba
Set appIE = CreateObject("internetexplorer.application")
With appIE
.Navigate "http://uk.investing.com/rates-bonds/financial-futures" 'Sample page
.Visible = True
End With
Do While appIE.Busy
DoEvents
Application.Wait (Now + TimeValue("0:00:01")) 'If page not open, wait a second befor trying again
Loop
'Debug.Print TypeName(appIE.document)
Dim doc As Object 'MSHTML.HTMLDocument
Set doc = appIE.document
'* appIE busy is good but you need to wait for the whole document to completely load and initialise so use this
While doc.readyState <> "complete"
DoEvents
Wend
'* we can select all the tables because they share the same CSS class name
Dim tablesSelectedByClass As Object 'MSHTML.HTMLElementCollection
Set tablesSelectedByClass = doc.getElementsByClassName("genTbl")
'* you can change this, it was just convenient for me to add sheets to my workbook
Dim shNewResults As Excel.Worksheet
Set shNewResults = ThisWorkbook.Worksheets.Add
Dim lRowCursor As Long '* this controls pasting down the sheet
lRowCursor = 1
Dim lTableIndexLoop As Long
For lTableIndexLoop = 0 To tablesSelectedByClass.Length - 1
Dim tableLoop As Object 'MSHTML.HTMLTable
Set tableLoop = tablesSelectedByClass.Item(lTableIndexLoop)
If LenB(tableLoop.ID) > 0 Then '* there are some extra nonsense tables, this subselects
Dim sParentColumn As String, objParentColumn As Object ' MSHTML.HTMLSemanticElement
Set objParentColumn = FindMyColumn(tableLoop, sParentColumn) '* need to understand is table on left hand or right hand side
Dim vHeader As Variant: vHeader = Empty
If sParentColumn = "leftColumn" Then
'* tables on the left have a preceding H3 element with the table's description
Dim objH3Headers As Object
Set objH3Headers = objParentColumn.getElementsByTagName("H3")
vHeader = objH3Headers.Item(lTableIndexLoop).innerText
Else
'* tables on the right have a hidden attribute we can use
vHeader = tableLoop.Attributes.Item("data-gae").Value
If Len(vHeader) > 3 Then
vHeader = Mid$(vHeader, 4)
Mid$(vHeader, 1, 1) = Chr(Asc(Mid$(vHeader, 1, 1)) - 32)
End If
End If
'* tables on the right do not have column headers
Dim bHasColumnHeaders As Boolean
bHasColumnHeaders = (tableLoop.ChildNodes.Length = 2)
Dim vTableCells() As Variant '* this will be our table data container which we will paste in one go
Dim lRowCount As Long: lRowCount = 0
Dim lColumnCount As Long: lColumnCount = 0
Dim lDataHeadersSectionIdx As Long: lDataHeadersSectionIdx = 0
Dim objColumnHeaders As Object: Set objColumnHeaders = Nothing
If bHasColumnHeaders Then
Set objColumnHeaders = tableLoop.ChildNodes.Item(0).ChildNodes.Item(0)
lRowCount = lRowCount + 1
lDataHeadersSectionIdx = 1
Else
lDataHeadersSectionIdx = 0
End If
Dim objDataRows As Object 'MSHTML.HTMLElementCollection
Set objDataRows = tableLoop.ChildNodes.Item(lDataHeadersSectionIdx).ChildNodes
lColumnCount = objDataRows.Item(0).ChildNodes.Length
lRowCount = lRowCount + objDataRows.Length
ReDim vTableCells(1 To lRowCount, 1 To lColumnCount) As Variant
'* we have them get the column headers
Dim lColLoop As Long
If bHasColumnHeaders Then
For lColLoop = 1 To lColumnCount
vTableCells(1, lColLoop) = objColumnHeaders.ChildNodes.Item(lColLoop - 1).innerText
Next
End If
'* get the data cells
Dim lRowLoop As Long
For lRowLoop = 1 To lRowCount - VBA.IIf(bHasColumnHeaders, 1, 0)
For lColLoop = 1 To lColumnCount
vTableCells(lRowLoop + VBA.IIf(bHasColumnHeaders, 1, 0), lColLoop) = objDataRows.Item(lRowLoop - 1).ChildNodes.Item(lColLoop - 1).innerText
Next
Next
'* paste our table description
shNewResults.Cells(lRowCursor, 1).Value2 = vHeader
lRowCursor = lRowCursor + 1
'* paste our table data
shNewResults.Cells(lRowCursor, 1).Resize(lRowCount, lColumnCount).Value2 = vTableCells
lRowCursor = lRowCursor + lRowCount + 1
End If
Next
End Sub
Function FindMyColumn(ByVal node As Object, ByRef psColumn As String) As Object
'* this code ascends the DOM looking for "column" in the id of each node
While InStr(1, node.ID, "column", vbTextCompare) = 0 And Not node.ParentNode Is Nothing
DoEvents
Set node = node.ParentNode
Wend
If InStr(1, node.ID, "column", vbTextCompare) > 0 Then
Set FindMyColumn = node
psColumn = CStr(node.ID)
End If
End Function
By the way, if you trade a lot the brokers get rich and you get poor, brokerage charges really impact in long run.

Boolean value does not properly assign based on conditions in for loop

I am quite new to programming and VBA but I have been using a lot of help questions and answers here on stack overflow to learn! I am attempting to create a loop that will determine if a value from a "task" sheet is missing from a "preview" sheet and if so to add the number and line to the "preview" sheet from the "task" sheet. For an example of the data on each sheet:
Task Sheet
TASK VALUE description
11 task 1
12 task 2
13 task 3
Preview Sheet
PREVIEW VALUE Description
1111 preview 1
2222 preview 2
11 task 1
3333 preview 3
13 task 3
The aim is to compare each number in the preview sheet to each value in the task sheet. If a value in the task sheet is not found in the preview sheet, then it should add that value and the entire line into the preview sheet from the task sheet.
My main approach was to create a for loop that would compare each number on the "preview" sheet to each value on the "task" sheet. If it found that two values matched, it would set the variable newTask = False and then exit the nested for loop to move on to the next comparison. If it found that the value from the data sheet was not present in the main sheet, it would set newTask = True and run through until there was no more values to compare. Then, if newTask = True, it would copy and paste the value and line from the data sheet into the main sheet. This is my attempt at coding this:
Dim newTask As Boolean
iP = (Worksheets("parents").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) - 1 'count of parent workorders
iC = (Worksheets("child").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) - 1 ' count of child workorders
iT = (Worksheets("task").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) - 1 ' count of task workorders
iPr = (Worksheets("preview").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) + 1 ' this will set iPr one row below the last row on the preview page
nT = 0
Set prRng = Sheets("Preview").Range(Sheets("Preview").Cells(iPr, 1), Sheets("Preview").Cells(iPr, 11))
For n = 1 To iPr
taskWO = Worksheets("task").Cells(n + 1, 1).Value
For nT = 1 To iT
previewWO = Worksheets("preview").Cells(nT + 1, 1).Value
If previewWO = taskWO Then
newTask = False
Exit For
ElseIf previewWO <> taskWO Then
newTask = True
End If
Next nT
If newTask = True Then
Set tRng = Sheets("task").Range(Sheets("task").Cells(n + 1, 1), Sheets("task").Cells(n + 1, 11))
Sheets("Preview").Range(Sheets("Preview").Cells(iPr, 1), Sheets("Preview").Cells(iPr, 11)) = tRng.Value
Sheets("Preview").Cells(iPr, 12) = Sheets("task").Cells(n + 1, 13).Value
iPr = (Worksheets("preview").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) + 1
End If
Next n
However, when I run this code, it keeps newTask = True, even if the values do match across the preview and task sheets. Debugging confirmed this and if it comes across similar values, it just continues over it as if they do not equal each other. This causes all values from the task sheet to copy over to the preview sheet, adding many duplicates. I also attempted a Do Until loop but get the same results: Here is my attempt at that:
Dim newTask As Boolean
iP = (Worksheets("parents").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) - 1 'count of parent workorders
iC = (Worksheets("child").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) - 1 ' count of child workorders
iT = (Worksheets("task").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) - 1 ' count of task workorders
iPr = (Worksheets("preview").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) + 1 ' this will set iPr one row below the last row on the preview page
nT = 0
taskWO = Worksheets("task").Cells(n + 1, 1).Value
Set prRng = Sheets("Preview").Range(Sheets("Preview").Cells(iPr, 1), Sheets("Preview").Cells(iPr, 11))
For n = 1 To iPr
taskWO = Worksheets("task").Cells(n + 1, 1).Value
Do Until taskWO = previewWO Or nT = iT
previewWO = Worksheets("preview").Cells(nT + 1, 1).Value
nT = nT + 1
If nT = iT Then
Set tRng = Sheets("task").Range(Sheets("task").Cells(n + 1, 1), Sheets("task").Cells(n + 1, 11))
Sheets("Preview").Range(Sheets("Preview").Cells(iPr, 1), Sheets("Preview").Cells(iPr, 11)) = tRng.Value
Sheets("Preview").Cells(iPr, 12) = Sheets("task").Cells(n + 1, 13).Value
iPr = (Worksheets("preview").Columns("a").Cells.SpecialCells(xlCellTypeConstants).Count) + 1
End If
Loop
Next n
I have done a lot of searching but I cannot find any way to get this to stop duplicating values... but I apologize if I missed a thread that has this information or help. I feel like it is something simple but I just cannot figure it out. Could I please get some help on how to correct this? If this is not the correct method, could you also please mention why the loop does not work or what went wrong so I can know in the future? Thank you for any help you provide! If you need some more information, please let me know.
Kyle, without being a complete re-work of your code, here's some code that I think might help you out. If I understand your problem correctly, the main issue you're having is that you're unable to properly determine if a value in one range exists in another. When I have this issue, I usually will use a dictionary object because it's fast, and provides an easy way to check if a value is in a list. To use a dictionary, however, you'll likely have to add a reference to it first. To do this, go to the tools menu and select "References". Scroll down until you've found "Microsoft Scripting Runtime" and select that item. After that, the following code should run just fine.
Sub Testq()
Set Dict = New Dictionary
For Each Cel In Worksheets("preview").Columns(1).SpecialCells(xlCellTypeConstants)
'Add the "Preview Values" of all the cells into your dictionary as Keys.
'Set the value of each key to the "Description" which is in the row next to it.
Dict(Trim(Cel.Value)) = Trim(Cells(Cel.Row(), 2))
Next
'Lets add in the header row of the task worksheet to prevent it from getting coppied over.
Dict(Worksheets("task").Cells(1, 1).Value) = Worksheets("task").Cells(1, 2).Value
'Now loop through all of the values in your "Task" table, checking them against the Dictionary
'to see if there are any new ones.
For Each Cel In Worksheets("Task").Columns(1).SpecialCells(xlCellTypeConstants)
If Not Dict.Exists(Trim(Cel.Value)) Then 'We have a new value.
TaskValue = Trim(Cel.Value)
Description = Trim(Worksheets("Task").Cells(Cel.Row(), 2))
Debug.Print "Yup, I found one that's missing: " & Trim(TaskValue)
'Now add the missing value to the end of your "preview" sheet.
LastRow = Worksheets("preview").Cells(Range("A:A").Rows.Count, "A").End(xlUp).Row
Worksheets("preview").Cells(LastRow + 1, 1) = Trim(TaskValue)
Worksheets("preview").Cells(LastRow + 1, 2) = Trim(Description)
End If
Next
End Sub
Since you mentioned that you're new to VBA, I'll point out that to view the output of the Debug.Print statement, you'll need to display the "Immediate" window. Do this by selecting it from the View menu. As I get a clearer picture of your project, I'll supplement this answer as needed, but for now I hope this will help you solve much of the problem you're having.

VBA makro to format XML in Excel to CSV

I need to reformat a XML file to .CSV.
I already opened the XML in Excel and did a little formating but now I really need to write a macro to get the data into shape. I already started bu I really have issues with the loop logic.
the List has a couple thousand Articles with a variable amount of subarticles.
each subarticle as a the same amount of properties but not every article has the same properties.
https://picload.org/image/ipialic/now.jpg
https://picload.org/image/ipialip/then.jpg
My Code up till now looks like this:
Option Explicit
Dim rowCount As Long, articleCount As Long, propertyCount As Integer, name As String
Sub Sortfunction()
rowCount = 1
articleCount = 0
propertyCount = 0
Do While Sheets("Test").Cells(rowCount, 1).Value <> "end"
If Cells(rowCount, 1).Value = "Reference" Then
rowCount = rowCount + 1
Do While Cells(rowCount, 3).Value = ""
If Cells(rowCount, 3).Value = "4" Then
End If
articleCount = articleCount + 1
Loop
articleCount = articleCount + 1
End If
rowCount = rowCount + 1
Loop
Sheets("result").Cells(1, 1).Value = rowCount
Sheets("result").Cells(2, 1).Value = articleCount
End Sub
At the end of the document i wrote the "end" to have a hook to stop the loop.
Can anyone provide some help? I'm really not the best programmer :-/
I'd really appreciate any help I can get :-)
here he's a translation into algorithm and some tips on functions
update: it was more tricky than I thought... I had to rewrite the code.
The main problem is "how to decide when change column".
I choose this solution "Each product in reference must have the same amount of properties".
If it's not the case, please indicate "how you decide when you have to create a new Column" (you can explain it in plain words)
Here the code rewrited. I tried it on your exemple, it work
Public Sub test()
' Set the range to navigate in your first sheet
Dim cell As Range: Set cell = Sheets("Feuil1").Range("A1")
' set the range to navigate in your result sheet
Dim res As Range: Set res = Nothing
' pos will be used to know the position of a product
Dim lastProperties As Range, posProperties As Range
' While the cell value is not "end"
Do While cell <> "end"
' if the cell is a reference
If cell = "Reference" Then
' Set the range of res
If res Is Nothing Then
Set res = Sheets("Feuil2").Range("A1")
Else
Set res = Sheets("Feuil2").Range("A" & lastProperties.offset(2).Row)
End If
' I set Offset(2) so you will have an empty line between 2 references
' Set the text of the new reference in the result
res = cell.offset(, 1) ' The reference is the cell 1 offset the right of the cell "Reference"
' WARNING : here no writing of titles anymore. It'll be done in the "Else".
' Here you just write "new reference" and reinit var
Else
' Here we have a property
' If the property alreay exist, consider it a new product in the reference
' When we are on a new property, the column of the product if the next to the right
If GetProperties(cell.offset(, 3), res, posProperties) Then
Set lastProperties = posProperties
End If
posProperties = cell.offset(, 4)
End If
' BIG FORGET: you have to get the next cell
Set cell = cell.offset(1)
Loop
End Sub
And the function to search / create your properties
Private Function GetProperties(ByVal propValues As String, ByVal start As Range, ByRef position As Range) As Boolean
Set position = start.offset(1)
' Is the cell below the properties ? Return the row below
' Search for the first "empty row" on the line
If position = propValues Then
Do
Set position = position.offset(, 1)
Loop While Trim(position) <> ""
' Indicate it's an existing value
GetProperties = True
Exit Function
End If
' Is the range empty ?
If Trim(position) = "" Then
' Create the new properties
position = propValues
Set position = position.offset(, 1)
GetProperties = False
Exit Function
End If
' Search the properties in the row below
GetProperties = GetProperties(propValues, position, position)
End Function
It should do the work. If you have any question on understanding some part, don't hesitate
if you don't know about Offset, some reading : https://msdn.microsoft.com/en-us/library/office/ff840060.aspx