Autofiltering Excel with multiple filter conditions - perl

I am trying to autofilter in Excel using the below VBScript code. This script called multiple times from a Perl program.
Dim objExcel : Set objExcel = GetObject(,"Excel.Application")
objExcel.Visible = True
objExcel.Selection.AutoFilter
objExcel.ActiveSheet.Range("G1").AutoFilter WScript.Arguments.Item(0), _
WScript.Arguments.Item (1)
Now I would like to know: is there a way by which I can pass an array for WScript.Arguments.Item (1) so that all the conditions are selected in one go? The task is to delete the filtered value. I call this script through Perl multiple times and the above script filter one value at a time and delete. The program works fine, but is slow.
Following is the part of Perl which calls the VBScript.
while(<FILE>){
chomp;
system("CSCRIPT "."\"$currentWorkingDirectory\"".'aboveVBS.vbs 9 '."\"$_\"");
sleep(2);
}

If you put quotes around the values, VBScript will treat it as a single argument.
> cscript script.vbs arg1 "multiple values for arg 2"
In the script:
WScript.Echo WScript.Arguments.Count ' ==> 2
a = Split(WScript.Arguments(1))
WScript.Echo a(0) ' ==> multiple
WScript.Echo a(1) ' ==> values
WScript.Echo a(2) ' ==> for
WScript.Echo a(3) ' ==> arg
WScript.Echo a(4) ' ==> 2
Excel expects:
Range.AutoFilter <Field>, <Criteria>, <Operator>
If you want a list of criteria to filter on, you'll use xlFilterValues for the <Operator> argument. <Criteria> will be an array of string values, which we created above.
Const xlFilterVaues = 7
objExcel.ActiveSheet.Range("G1").AutoFilter WScript.Arguments.Item(0), a, xlFilterValues
So, just try adding Split() around WScript.Arguments(1) in your existing code, and pass xlFilterValues for the third param.

If only your second argument changes, you could pass the entire content of your data file to the VBScript:
local $/;
my $args = <FILE>;
$args =~ s/^\s+|\s+$//g;
$args =~ s/\r?\n/" "/g;
system("cscript \"$currentWorkingDirectory\\your.vbs\" 9 \"$args\"");
and change the processing in your VBScript to this:
Set xl = CreateObject("Excel.Application")
xl.Visible = True
Set wb = xl.Workbook.Open("C:\path\to\your.xlsx")
Set ws = wb.Sheets(1)
...
xl.Selection.AutoFilter
For i = 1 To WScript.Arguments.Count - 1
ws.Range("G1").AutoFilter WScript.Arguments(0), WScript.Arguments(i)
...
Next
Or you could simply call the VBScript with the field and the path to the data file:
system("cscript \"$currentWorkingDirectory\\your.vbs\" 9 \"$filepath\"");
and do all the processing in VBScript:
Set xl = CreateObject("Excel.Application")
xl.Visible = True
Set wb = xl.Workbook.Open("C:\path\to\your.xlsx")
Set ws = wb.Sheets(1)
...
xl.Selection.AutoFilter
Set fso = CreateObject("Scripting.FileSystemObject")
Set f = fso.OpenTextFile(WScript.Arguments(1))
Do Until f.AtEndOfStream
ws.Range("G1").AutoFilter WScript.Arguments(0), f.ReadLine
...
Next
f.Close
Applying more than 2 AutoFilter conditions to a column at the same time is not possible. Check the signature of the AutoFilter method in the documentation:
expression .AutoFilter(Field, Criteria1, Operator, Criteria2, VisibleDropDown)
expression An expression that returns a Range object.
You have Critera1 and Criteria2 and an Operator for combining the two. Calling the AutoFilter method with another set of criteria replaces the existing criteria.

Related

How to stop autofilter macro failing intermittently

I have a macro which is designed to autofilter for certain criteria and then hide certain columns and copy what is left to the appropriate file. Sometimes the file filters correctly, but sometimes it stops on the Selection.AutoFilter line with a RE 1004 error, "Method of range class failed". This usually happens if I run the macro immediately after opening the file. If I reset the entire sheet with a macro I have to unhide everything, it filters correctly.
If it does filter correctly, it omits certain columns when pasting to the destination file. Those columns are the first one right after a handful of blank ones. I need it to copy either all visible columns except the header, or can even be changed to columns A - X, as that is the extent of the information required.
Here is the macro
Sub OO_Away_Lay_1()
'
' OO Away Lay v1 Macro
' This macro will filter for 1x2
'
Dim ws As Worksheet, lc As Long, lr As Long
Set ws = ActiveSheet
'range from A1 to last column header and last row
lc = ws.Cells(1, ws.Columns.Count).End(xlToLeft).Column
lr = ws.Cells.Find("*", after:=ws.Range("A1"), LookAt:=xlPart, _
SearchOrder:=xlByRows, SearchDirection:=xlPrevious).Row
With ws.Range("A1", ws.Cells(lr, lc))
.HorizontalAlignment = xlCenter
Selection.AutoFilter
.AutoFilter Field:=24, Criteria1:="Draw", Operator:=xlFilterValues
If .Rows.Count - 1 > 0 Then
On Error Resume Next
.Columns("L:S").EntireColumn.Hidden = True
.Columns("U:W").EntireColumn.Hidden = True
.Columns("Y:CK").EntireColumn.Hidden = True
.Offset(1, 0).Resize(.Rows.Count - 1).SpecialCells(xlCellTypeVisible).Copy
On Error GoTo 0
Else
Exit Sub
End If
End With
Workbooks("Predictology_Trading Template v3.1.xlsm").Sheets("OO Away Lay v1") _
.Range("A" & Rows.Count).End(xlUp).Offset(1).PasteSpecial xlPasteValues
Application.CutCopyMode = False
End Sub
Any thoughts on fixing it so it autofilters all the time and also copies all of the required data?
cheers

How can I extract first and last line from multiple text blocks separated with new line?

I have a file containing multiple tests with detailed action written one beneath another. All test blocks are separated one from another by new line. I want to extract only first and last line from the all blocks and put it on one line for each block into a new file. Here is an example:
input.txt:
[test1]
duration
summary
code=
Results= PASS
[test2]
duration
summary=x
code=
Results=FAIL
.....
[testX]
duration
summary=x
code=
Results= PASS
output.txt should be sometime like this:
test1 PASS
test2 FAIL
...
testX PASS
eg2:
[Linux_MP3Enc_xffv.2_Con_37_003]
type = testcase
summary = MP3 encoder test
ActionGroup[Linux_Enc] = PASS
ActionGroup[Linux_Playb] = PASS
ActionGroup[Linux_Pause_Resume] = PASS
ActionGroup[Linux_Fast_Seek] = PASS
Duration = 230.607398987 s
Total_Result = PASS
[Composer__vtx_007]
type = testcase
summary = composer
Background[0xff000000] = PASS
Background[0xffFFFFFF] = PASS
Background[0xffFF0000] = PASS
Background[0xff00FF00] = PASS
Background[0xff0000FF] = PASS
Background[0xff00FFFF] = PASS
Background[0xffFFFF00] = PASS
Background[0xffFF00FF] = PASS
Duration = 28.3567230701 s
Total_Result = PASS
[Videox_Rotate_008]
type = testcase
summary = rotation
Rotation[0] = PASS
Rotation[1] = PASS
Rotation[2] = PASS
Rotation[3] = PASS
Duration = 14.0116529465 s
Total_Result = PASS
Thank you!
Short and simple gnu awk:
awk -F= -v RS='' '{print $1 $NF}' file
[Linux_MP3Enc_xffv.2_Con_37_003] PASS
[Composer__vtx_007] PASS
[Videox_Rotate_008] PASS
If you do not like the brackets:
awk -F'[]=[]' -v RS='' '{print $2 $NF}' file
Linux_MP3Enc_xffv.2_Con_37_003 PASS
Composer__vtx_007 PASS
Videox_Rotate_008 PASS
Using sed as tagged (although other tools would probably be more natural to use) :
sed -nE '/^\[.*\]$/h;s/^Results= ?//;t r;b;:r;H;x;s/\n/ /;p'
Explanation :
/^\[.*\]$/h # matches the [...] lines, put them in the hold buffer
s/^Results= ?// # matches the Results= lines, discards the useless part
t r;b # on lines which matched, jump to label r;
# otherwise jump to the end (and start processing the next line)
:r;H;x;s/\n/ /;p # label r; append the pattern space (which contains the end of the Results= line)
# to the hold buffer. Switch Hold buffer and pattern space,
# replace the linefeed in the pattern space by a space and print it
You can try it here.
One way to solve this is using a regular expression such as:
(?<testId>test\d+)(?:.*\n){4}.*(?<outcome>PASS|FAIL)
The regex matches your sample output and stores the test id (e.g. "test1") in the capture group named "testId" and the outcome (e.g. "PASS") in the capture group "outcome".
(Test it in regexr)
The regex can be used in any language with regex support. The below code shows how to do it in Python.
(Test it in repl.it)
import re
# Read from input.txt
with open('input.txt', 'r') as f:
indata = f.read()
# Modify the regex slightly to fit Python regex syntax
pattern = '(?:.*)(?P<testId>test\d+)(?:.*\n){4}.*(?P<outcome>PASS|FAIL)'
# Get a generator which yeilds all matches
matches = re.finditer(pattern, indata)
# Combine the matches to a list of strings
outputs = ['{} {}'.format(m.group('testId'), m.group('outcome')) for m in matches]
# Join all rows to one string
output = '\n'.join(outputs)
# Write to output.txt
with open('output.txt', 'w') as f:
f.write(output)
Running the above script on input.txt containing:
[test1]
duration
summary
code=
Results= PASS
[test2]
duration
summary=x
code=
Results=FAIL
[test444]
duration
summary=x
code=
Results= PASS
yields a file output.txt containing:
test1 PASS
test2 FAIL
test444 PASS
In order to print the first and last line from the block, how about:
awk -v RS="" '{
n = split($0, a, /\n/)
print a[1]
print a[n]
}' input.txt
Result for the 1st example:
[Linux_MP3Enc_xffv.2_Con_37_003]
Total_Result = PASS
[Composer__vtx_007]
Total_Result = PASS
[Videox_Rotate_008]
Total_Result = PASS
The man page of awk tells:
If RS is set to the null string, then records are separated by blank lines.
You can easily split the block with blank lines with this feature.
Hope this helps.

How to edit all word documents in a directory?

I've been given the scut job of correcting some hundred or so code testing reports that have been filled out incorrectly by a senior coder who has more import work to do.
Unluckily for me all the files are ms-word documents. But luckily for the formatting is all the same and the errors are all made in the same cells in the same table.
In the past I wrote a bash to edit to change single quotes to double quotes on multiple xml files. But that was with a linux machine. This time around I have only a window machine.
Any hints where to begin?
The answer was to use VBA. I built two subroutines.
The first subRoutine loops through the directory and
opens each *.doc file it finds. Then on the open document file it calls
the second subRoutine. After the second subRoutine is finished the document
is saved and then closed.
Sub DoVBRoutineNow()
Dim file
Dim path As String
path = "C:\Documents and Settings\userName\My Documents\myWorkFolder\"
file = Dir(path & "*.doc")
Do While file <> ""
Documents.Open FileName:=path & file
Call editCellsTableRow2
ActiveDocument.Save
ActiveDocument.Close
file = Dir()
Loop
End Sub
~~~~~~
The second subRoutine only works if all documents have the same formating.
For example: The second row of the only table in the document has cells numbered 6, 7, 8. These contain "dd/MM/yyyy" , "Last Name", "First Name"
These cells need to be changed to "yyyy/MM/dd", "Surname", "Given Name"
Sub editCellsTableRow2()
Application.ScreenUpdating = False
Dim Tbl As Table, cel As Cell, i As Long, n As Long
With ActiveDocument
For Each Tbl In .Tables
Tbl.Rows(2).Alignment = xlCenter
For Each cel In Tbl.Rows(2).Cells
If cel.ColumnIndex = 6 Then
cel.Range.Text = vbCrLf + "yyyy/MM/dd"
End If
If cel.ColumnIndex = 7 Then
cel.Range.Text = vbCrLf + "Surname"
End If
If cel.ColumnIndex = 8 Then
cel.Range.Text = vbCrLf + "Given Name"
End If
Next cel
Next Tbl
End With
Set cel = Nothing: Set Tbl = Nothing
Application.ScreenUpdating = True
End Sub

How to pass attribute text as a variable to select a single node in xml using VBScript?

I'm trying to select a single node in an xml file using VBscript using the following code
Set node = xmlDoc.selectingSingleNode(".//node()[#name = 'anything']")
This works perfectly if I write what I need to pass as a text.
But I need to pass this 'anything' as a variable X
I tried the following but neither is working
xmlDoc.selectingSingleNode(".//node()[#name = X]")
xmlDoc.selectingSingleNode(".//node()[#name = '&X&']")
Any suggestions are appreciated
Just concatenate properly:
>> X = "abc"
>> WScript.Echo ".//node()[#name = '" & X & "']"
>>
.//node()[#name = 'abc']

Can I create horizontal autofilter in OpenOffice Calc

The autofilter is sorting data vertically, but I want to filter rows horizontally.
Lets say that I have the following table:
1 2 2 1 2
B A E F F
B D E F F
C D E F F
What I can do is to set an autofilter and filter only the rows containing "B" in the first column. What I would like to do is to filter only the rows that contain "2" (in this case the rows are second, third and the last in this case).
I have found some information regarding this matter. All of the answers I found are containing some macros to get the job done, but they were written for MS Excel, and are not compatible with OpenOffice
For example, this macros should get the rows filtered, but is not working in OpenOffice Calc:
Option Explicit
Sub horizontal_filter()
'Erik Van Geit
'060910
Dim LC As Integer 'Last Column
Dim R As Long
Dim i As Integer
Dim FilterValue As String
Const FilterColumn = 1 '1 is most logical value but you may change this
R = ActiveCell.Row
LC = Cells(R, Columns.Count).End(xlToLeft).Column
FilterValue = Cells(R, FilterColumn)
Application.ScreenUpdating = False
'to filter starting after FilterColumn
For i = FilterColumn + 1 To LC
'to filter all columns even before the filtercolumn
'For i = 1 To LC
If i <> FilterColumn Then
Columns(i).Hidden = Cells(R, i) <> FilterValue
End If
Next i
Application.ScreenUpdating = True
End Sub
Any help is greatly appreciated!
You can't, under the assumption of reasonable expense. It's much easier just to transform your data so that rows get columns and vice versa. So, i would strongly recommend transforming the data using Paste Special together with the Transpose option. You could even do this dynamically by using the TRANSPOSE() function.
EDIT:
Now i got it - you want to hide columns based on a certain value. This is possible using a macro in fact, so my first answer was incorrect - sorry for that! There are some macros around that will do this for you. You can combine such a solution with an auto filter. Here's a solution by king_026 from the OpenOffice.org forums (slightly adapted to table structure - see below):
REM ***** BASIC *****
sub hide
rem ----------------------------------------------------------------------
rem define variables
dim document as object
dim dispatcher as object
rem ----------------------------------------------------------------------
rem get access to the document
document = ThisComponent.CurrentController.Frame
dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")
rem get the current column
nCol = ThisComponent.CurrentSelection.CellAddress.Column
rem set the properties for moving right
dim args2(1) as new com.sun.star.beans.PropertyValue
args2(0).Name = "By"
args2(0).Value = 1
args2(1).Name = "Sel"
args2(1).Value = false
rem make thecurrent column counter
dim cCol as integer
CCol = 0
rem goto the first column
dim args1(0) as new com.sun.star.beans.PropertyValue
args1(0).Name = "ToPoint"
args1(0).Value = "$A$2"
dispatcher.executeDispatch(document, ".uno:GoToCell", "", 0, args1())
rem loop until you get back to the selected cell
Do Until cCol > nCol
rem hide if the cell value is 1
if ThisComponent.CurrentSelection.string <> "" and ThisComponent.CurrentSelection.value = 1 then
rem ----------------------------------------------------------------------
dispatcher.executeDispatch(document, ".uno:HideColumn", "", 0, Array())
End if
rem goto the right nad increment the column counter
dispatcher.executeDispatch(document, ".uno:GoRight", "", 0, args2())
cCol = cCol + 1
Loop
End sub
So, the following table:
will look like this after Autofilter on Col1 and after the macro did his work: