I am creating a Basic macro for LibreOffice Writer to check for broken internal links. In a nutshell:
generate a list of all anchors
loop through the document, finding internal hyperlinks
if the internal hyperlink is not on the anchor list, open it for editing (and stop)
My code has a few unsolved issues:
(within fnBuildAnchorList) How do we get the numbering for each heading? For example, if the first level 1 heading text is “Introduction”, the correct anchor is #1.Introduction|outline and we are recording Introduction|outline
(within subInspectLink) How do we properly test a hyperlink to a heading? I notice that when I manually follow a link to a heading, it will succeed when the numbering is the same, but also when the text is the same. e.g. if there’s an internal link #1.My first heading|outline, it can be reached with the hyperlink #1.Previous header name|outline but also with the hyperlink #2.3.5.My first heading|outline
(within subInspectLink) How do we open a specific hyperlink for editing? Do we pass parameters to .uno:EditHyperlink? Do we move the cursor? (All moves I found were relative, e.g. .uno:GoRight) Do we use the text portion’s .Start and .End properties?
REM ***** BASIC *****
Option Explicit
' PrintArray displays a MsgBox with the whole array
' for DEBUG purposes only
Sub subPrintArray(sTitle as String, theArray() as String)
Dim sArray
sArray = sTitle & ":" & Chr(13) & Join(theArray,Chr(13))
MsgBox(sArray, 64, "***DEBUG")
End sub
' auxiliary sub for BuildAnchorList
Sub subAddItemToAnchorList (oAnchors() as String, sTheAnchor as String, sType as String)
Dim sAnchor
Select Case sType
Case "Heading":
sAnchor = sTheAnchor + "|outline"
Case "Table":
sAnchor = sTheAnchor + "|table"
Case "Text Frame":
sAnchor = sTheAnchor + "|frame"
Case "Image":
sAnchor = sTheAnchor + "|graphic"
Case "Object":
sAnchor = sTheAnchor + "|ole"
Case "Section":
sAnchor = sTheAnchor + "|region"
Case "Bookmark":
sAnchor = sTheAnchor
End Select
ReDim Preserve oAnchors(UBound(oAnchors)+1) as String
oAnchors(UBound(oAnchors)) = sAnchor
End Sub
' auxiliary sub for BuildAnchorList
Sub subAddArrayToAnchorList (oAnchors() as String, oNewAnchors() as String, sType as String)
Dim i, iStart, iStop
iStart = LBound(oNewAnchors)
iStop = UBound(oNewAnchors)
If iStop < iStart then Exit Sub ' empty array, nothing to do
For i = iStart to iStop
subAddItemToAnchorList (oAnchors, oNewAnchors(i), sType)
Next
End Sub
Function fnBuildAnchorList()
Dim oDoc as Object, oAnchors() as String
oDoc = ThisComponent
' get the whole document outline
Dim oParagraphs, thisPara, oTextPortions, thisPortion
oParagraphs = oDoc.Text.createEnumeration ' all the paragraphs
Do While oParagraphs.hasMoreElements
thisPara = oParagraphs.nextElement
If thisPara.ImplementationName = "SwXParagraph" then ' is a paragraph
If thisPara.OutlineLevel>0 Then ' is a heading
' ***
' *** TO DO: How do we get the numbering for each heading?
' For example, if the first level 1 heading text is “Introduction”,
' the correct anchor is `#1.Introduction|outline`
' and we are recording `Introduction|outline`
' ***
subAddItemToAnchorList (oAnchors, thisPara.String, "Heading")
End if
End if
Loop
' text tables, text frames, images, objects, bookmarks and text sections
subAddArrayToAnchorList(oAnchors, oDoc.getTextTables().ElementNames, "Table")
subAddArrayToAnchorList(oAnchors, oDoc.getTextFrames().ElementNames, "Text Frame")
subAddArrayToAnchorList(oAnchors, oDoc.getGraphicObjects().ElementNames, "Image")
subAddArrayToAnchorList(oAnchors, oDoc.getEmbeddedObjects().ElementNames, "Object")
subAddArrayToAnchorList(oAnchors, oDoc.Bookmarks.ElementNames, "Bookmark")
subAddArrayToAnchorList(oAnchors, oDoc.getTextSections().ElementNames, "Section")
fnBuildAnchorList = oAnchors
End Function
Function fnIsInArray( theString as String, theArray() as String )
Dim i as Integer, iStart as Integer, iStop as Integer
iStart = LBound(theArray)
iStop = UBound(theArray)
If iStart<=iStop then
For i = iStart to iStop
If theString = theArray(i) then
fnIsInArray = True
Exit function
End if
Next
End if
fnIsInArray = False
End function
Function fnIsOutlineInArray ( theString as String, theArray() as String )
Dim i as Integer
For i = LBound(theArray) to UBound(theArray)
If theArray(i) = Right(theString,Len(theArray(i))) then
fnIsOutlineInArray = True
Exit function
End if
Next
fnIsOutlineInArray = False
End function
' auxiliary function to FindBrokenInternalLinks
' inspects any links inside the current document fragment
' used to have an enumeration inside an enumeration, per OOo examples,
' but tables don't have .createEnumeration
Sub subInspectLinks( oAnchors as Object, oFragment as Object, iFragments as Integer, iLinks as Integer )
Dim sMsg, sImplementation, thisPortion
sImplementation = oFragment.implementationName
Select Case sImplementation
Case "SwXParagraph":
' paragraphs can be enumerated
Dim oParaPortions, sLink, notFound
oParaPortions = oFragment.createEnumeration
' go through all the text portions in current paragraph
While oParaPortions.hasMoreElements
thisPortion = oParaPortions.nextElement
iFragments = iFragments + 1
If Left(thisPortion.HyperLinkURL, 1) = "#" then
' internal link found: get it all except initial # character
iLinks = iLinks + 1
sLink = right(thisPortion.HyperLinkURL, Len(thisPortion.HyperLinkURL)-1)
If Left(sLink,14) = "__RefHeading__" then
' link inside a table of contents, no need to check
notFound = False
Elseif Right(sLink,8) = "|outline" then
' special case for outline: since we don't know how to get the
' outline numbering, we have to match the right most part of the
' link only
notFound = not fnIsOutlineInArray(sLink, oAnchors)
Else
notFound = not fnIsInArray(sLink, oAnchors)
End if
If notFound then
' anchor not found
' *** DEBUG: code below up to MsgBox
sMsg = "Fragment #" & iFragments & ", internal link #" & iLinks & Chr(13) _
& "Bad link: [" & thisPortion.String & "] -> [" _
& thisPortion.HyperLinkURL & "] " & Chr(13) _
& "Paragraph:" & Chr(13) & oFragment.String & Chr(13) _
& "OK to continue, Cancel to stop"
Dim iChoice as Integer
iChoice = MsgBox (sMsg, 48+1, "Find broken internal link")
If iChoice = 2 Then End
' ***
' *** TO DO: How do we open a _specific_ hyperlink for editing?
' Do we pass parameters to `.uno:EditHyperlink`?
' Do we move the cursor? (Except all moves I found were relative,
' e.g. `.uno:GoRight`)
' Do we use the text portion’s `.Start` and `.End` properties?
' ***
End If
End if
Wend
' *** END paragraph
Case "SwXTextTable":
' text tables have cells
Dim i, eCells, thisCell, oCellPortions
eCells = oFragment.getCellNames()
For i = LBound(eCells) to UBound(eCells)
thisCell = oFragment.getCellByName(eCells(i))
oCellPortions = thisCell.createEnumeration
While oCellPortions.hasMoreElements
thisPortion = oCellPortions.nextElement
iFragments = iFragments + 1
' a table cell may contain a paragraph or another table,
' so call recursively
subInspectLinks (oAnchors, thisPortion, iFragments, iLinks)
Wend
' xray thisPortion
'SwXCell has .String
Next
' *** END text table
Case Else
sMsg = "Implementation method '" & sImplementation & "' not covered by regular code." _
& "OK to continue, Cancel to stop"
If 2 = MsgBox(sMsg, 48+1) then End
' uses xray for element inspection; if not available, comment the two following lines
BasicLibraries.loadLibrary("XrayTool")
xray oFragment
' *** END unknown case
End Select
End sub
Sub FindBrokenInternalLinks
' Find the next broken internal link
'
' Pseudocode:
'
' * generate link of anchors - *** TO DO: prefix the outline numbering for headings
' * loop, searching for internal links
' - is the internal link in the anchor list?
' * Yes: continue to next link
' * No: (broken link found)
' - select that link text - *** TO DO: cannot select it
' - open link editor so user can fix this
' - stop
' * end loop
' * display message "No bad internal links found"
Dim oDoc as Object, oFragments as Object, thisFragment as Object
Dim iFragments as Integer, iLinks as Integer, sMsg as String
Dim oAnchors() as String ' list of all anchors in the document
' Dim sMsg ' for MsgBox
oDoc = ThisComponent
' get all document anchors
oAnchors = fnBuildAnchorList()
' subPrintArray("Anchor list", oAnchors) ' *** DEBUG ***
' MsgBox( UBound(oAnchors)-LBound(oAnchors)+1 & " anchors found – stand by for checking")
' find links
iFragments = 0 ' fragment counter
iLinks = 0 ' internal link counter
oFragments = oDoc.Text.createEnumeration ' has all the paragraphs
While oFragments.hasMoreElements
thisFragment = oFragments.nextElement
iFragments = iFragments + 1
subInspectLinks (oAnchors, thisFragment, iFragments, iLinks)
Wend
If iLinks then
sMsg = iLinks & " internal links found, all good"
Else
sMsg = "This document has no internal links"
End if
MsgBox (sMsg, 64, "Find broken internal link")
End Sub
' *** END FindBrokenInternalLinks ***
You can check the first issue using any document with a heading – a MsgBox will pop up with all the anchors, and you’ll see the missing outline numbering.
The second issue needs a document with a bad internal link.
Check out cOOol. You could either use this
instead of creating a macro,
or else borrow some concepts from the code.
Testing the links (possibly with .uno:JumpToMark) does not seem like it would be helpful,
because internal links always go somewhere even if the target does not exist.
Instead, construct a list of valid targets as you suggested.
To hold the list of valid targets, the cOOol code uses a Python set.
If you want to use Basic, then data structures are more limited.
However it can be done either by declaring a new a
Collection object
or by using Basic arrays, perhaps with ReDim.
Also have a look at how the cOOol code defines the valid target strings. For example:
internal_targets.add('0.' * heading_level + data + '|outline')
To open the hyperlink dialog, select the hyperlinked text and then call:
dispatcher.executeDispatch(document, ".uno:EditHyperlink", "", 0, Array())
EDIT:
Ok, I worked on this for several hours and came up with the following code:
REM ***** BASIC *****
Option Explicit
' PrintArray displays a MsgBox with the whole array
' for DEBUG purposes only
Sub subPrintArray(sTitle as String, theArray() as String)
Dim sArray
sArray = sTitle & ":" & Chr(13) & Join(theArray,Chr(13))
MsgBox(sArray, 64, "***DEBUG")
End sub
' auxiliary sub for BuildAnchorList
Sub subAddItemToAnchorList (oAnchors() as String, sTheAnchor as String, sType as String)
Dim sAnchor
Select Case sType
Case "Heading":
sAnchor = sTheAnchor + "|outline"
Case "Table":
sAnchor = sTheAnchor + "|table"
Case "Text Frame":
sAnchor = sTheAnchor + "|frame"
Case "Image":
sAnchor = sTheAnchor + "|graphic"
Case "Object":
sAnchor = sTheAnchor + "|ole"
Case "Section":
sAnchor = sTheAnchor + "|region"
Case "Bookmark":
sAnchor = sTheAnchor
End Select
ReDim Preserve oAnchors(UBound(oAnchors)+1) as String
oAnchors(UBound(oAnchors)) = sAnchor
End Sub
' auxiliary sub for BuildAnchorList
Sub subAddArrayToAnchorList (oAnchors() as String, oNewAnchors() as String, sType as String)
Dim i, iStart, iStop
iStart = LBound(oNewAnchors)
iStop = UBound(oNewAnchors)
If iStop < iStart then Exit Sub ' empty array, nothing to do
For i = iStart to iStop
subAddItemToAnchorList (oAnchors, oNewAnchors(i), sType)
Next
End Sub
' Updates outlineLevels for the current level.
' Returns a string like "1.2.3"
Function fnGetOutlinePrefix(outlineLevel as Integer, outlineLevels() as Integer)
Dim level as Integer, prefix as String
outlineLevels(outlineLevel) = outlineLevels(outlineLevel) + 1
For level = outlineLevel + 1 to 9
' Reset all lower levels.
outlineLevels(level) = 0
Next
prefix = ""
For level = 0 To outlineLevel
prefix = prefix & outlineLevels(level) & "."
Next
fnGetOutlinePrefix = prefix
End Function
Function fnBuildAnchorList()
Dim oDoc as Object, oAnchors() as String, anchorName as String
Dim level as Integer, levelCounter as Integer
Dim outlineLevels(10) as Integer
For level = 0 to 9
outlineLevels(level) = 0
Next
oDoc = ThisComponent
' get the whole document outline
Dim oParagraphs, thisPara, oTextPortions, thisPortion
oParagraphs = oDoc.Text.createEnumeration ' all the paragraphs
Do While oParagraphs.hasMoreElements
thisPara = oParagraphs.nextElement
If thisPara.ImplementationName = "SwXParagraph" then ' is a paragraph
If thisPara.OutlineLevel>0 Then ' is a heading
level = thisPara.OutlineLevel - 1
anchorName = fnGetOutlinePrefix(level, outlineLevels) & thisPara.String
subAddItemToAnchorList (oAnchors, anchorName, "Heading")
End if
End if
Loop
' text tables, text frames, images, objects, bookmarks and text sections
subAddArrayToAnchorList(oAnchors, oDoc.getTextTables().ElementNames, "Table")
subAddArrayToAnchorList(oAnchors, oDoc.getTextFrames().ElementNames, "Text Frame")
subAddArrayToAnchorList(oAnchors, oDoc.getGraphicObjects().ElementNames, "Image")
subAddArrayToAnchorList(oAnchors, oDoc.getEmbeddedObjects().ElementNames, "Object")
subAddArrayToAnchorList(oAnchors, oDoc.Bookmarks.ElementNames, "Bookmark")
subAddArrayToAnchorList(oAnchors, oDoc.getTextSections().ElementNames, "Section")
fnBuildAnchorList = oAnchors
End Function
Function fnIsInArray( theString as String, theArray() as String )
Dim i as Integer
For i = LBound(theArray()) To UBound(theArray())
If theString = theArray(i) Then
fnIsInArray = True
Exit function
End if
Next
fnIsInArray = False
End function
' Open a _specific_ hyperlink for editing.
Sub subEditHyperlink(textRange as Object)
Dim document As Object
Dim dispatcher As Object
Dim oVC As Object
oVC = ThisComponent.getCurrentController().getViewCursor()
oVC.gotoRange(textRange.getStart(), False)
document = ThisComponent.CurrentController.Frame
dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")
dispatcher.executeDispatch(document, ".uno:EditHyperlink", "", 0, Array())
End Sub
' auxiliary function to FindBrokenInternalLinks
' inspects any links inside the current document fragment
' used to have an enumeration inside an enumeration, per OOo examples,
' but tables don't have .createEnumeration
Sub subInspectLinks(oAnchors() as String, oFragment as Object, iFragments as Integer, iLinks as Integer, iBadLinks as Integer)
Dim sMsg, sImplementation, thisPortion
sImplementation = oFragment.implementationName
Select Case sImplementation
Case "SwXParagraph":
' paragraphs can be enumerated
Dim oParaPortions, sLink, notFound
oParaPortions = oFragment.createEnumeration
' go through all the text portions in current paragraph
While oParaPortions.hasMoreElements
thisPortion = oParaPortions.nextElement
iFragments = iFragments + 1
If Left(thisPortion.HyperLinkURL, 1) = "#" then
' internal link found: get it all except initial # character
iLinks = iLinks + 1
sLink = right(thisPortion.HyperLinkURL, Len(thisPortion.HyperLinkURL)-1)
If Left(sLink,14) = "__RefHeading__" then
' link inside a table of contents, no need to check
notFound = False
Else
notFound = not fnIsInArray(sLink, oAnchors)
End if
If notFound then
' anchor not found
' *** DEBUG: code below up to MsgBox
iBadLinks = iBadLinks + 1
sMsg = "Fragment #" & iFragments & ", internal link #" & iLinks & Chr(13) _
& "Bad link: [" & thisPortion.String & "] -> [" _
& thisPortion.HyperLinkURL & "] " & Chr(13) _
& "Paragraph:" & Chr(13) & oFragment.String & Chr(13) _
& "Yes to edit link, No to continue, Cancel to stop"
Dim iChoice as Integer
iChoice = MsgBox (sMsg, MB_YESNOCANCEL + MB_ICONEXCLAMATION, _
"Find broken internal link")
If iChoice = IDCANCEL Then
End
ElseIf iChoice = IDYES Then
subEditHyperlink(thisPortion)
End If
End If
End if
Wend
' *** END paragraph
Case "SwXTextTable":
' text tables have cells
Dim i, eCells, thisCell, oCellPortions
eCells = oFragment.getCellNames()
For i = LBound(eCells) to UBound(eCells)
thisCell = oFragment.getCellByName(eCells(i))
oCellPortions = thisCell.createEnumeration
While oCellPortions.hasMoreElements
thisPortion = oCellPortions.nextElement
iFragments = iFragments + 1
' a table cell may contain a paragraph or another table,
' so call recursively
subInspectLinks (oAnchors, thisPortion, iFragments, iLinks)
Wend
' xray thisPortion
'SwXCell has .String
Next
' *** END text table
Case Else
sMsg = "Implementation method '" & sImplementation & "' not covered by regular code." _
& "OK to continue, Cancel to stop"
If 2 = MsgBox(sMsg, 48+1) then End
' uses xray for element inspection; if not available, comment the two following lines
BasicLibraries.loadLibrary("XrayTool")
xray oFragment
' *** END unknown case
End Select
End sub
Sub FindBrokenInternalLinks
' Find the next broken internal link
'
' Pseudocode:
'
' * generate link of anchors - *** TO DO: prefix the outline numbering
' * for headings loop, searching for internal links
' - is the internal link in the anchor list?
' * Yes: continue to next link
' * No: (broken link found)
' - select that link text - *** TO DO: cannot select it
' - open link editor so user can fix this
' - stop
' * end loop
' * display message "No bad internal links found"
Dim oDoc as Object, oFragments as Object, thisFragment as Object
Dim iFragments as Integer, iLinks as Integer, iBadLinks as Integer, sMsg as String
Dim oAnchors() as String ' list of all anchors in the document
oDoc = ThisComponent
' get all document anchors
oAnchors = fnBuildAnchorList()
' subPrintArray("Anchor list", oAnchors) ' *** DEBUG ***
' MsgBox( UBound(oAnchors)-LBound(oAnchors)+1 & " anchors found – stand by for checking")
' find links
iFragments = 0 ' fragment counter
iLinks = 0 ' internal link counter
iBadLinks = 0
oFragments = oDoc.Text.createEnumeration ' has all the paragraphs
While oFragments.hasMoreElements
thisFragment = oFragments.nextElement
iFragments = iFragments + 1
subInspectLinks (oAnchors, thisFragment, iFragments, iLinks, iBadLinks)
Wend
If iBadLinks > 0 Then
sMsg = iBadLinks & " bad link(s), " & iLinks - iBadLinks & " good link(s)"
ElseIf iLinks Then
sMsg = iLinks & " internal link(s) found, all good"
Else
sMsg = "This document has no internal links"
End if
MsgBox (sMsg, 64, "Find broken internal link")
End Sub
' *** END FindBrokenInternalLinks ***
It now checks for outline numbering. Maybe it's too strict -- perhaps it would be good to have an option to turn off outline number checking.
As far as issue 3, this code now opens the proper links for editing (as long as "Yes" is clicked in the message box).
AS I using the VB6.0 for create a Dialog Box with the ListBox, but only I can get the String text with Trim(DlgText$("xxxxx")), for the other side still I could not found how to get it.
Most of the answer from network said that could be using [LisBox_ID].Selected to get the item that what they want, but I can not get the same result.
For my Code:
[Dialog]
Function aOpenDialog As Boolean
aOpenDialog = False
iArrayLoop = 0
Begin Dialog UserDialog ,,250,120,ScriptTitle,.ActivateDlgControls
Text 5,5,130,10,"Sub Booking End Date", .tf_InsertionSetEndDate
ListBox 5,15,100,100,aArrayList, .aArrayList
Text 110,5,130,10,"After Date [DD-MMM-YYYY]", .tf_AfterDate
TextBox 110,15,55,10, .txt_AfterDate
Text 110,25,55,10,"Change Reason", .tf_ChangeReason
TextBox 110,35,130,10, .txt_ChangeReason
OKButton 110,45,70,10, .btn_Save
CancelButton 110,55,70,10, .btn_Cancel
End Dialog
Dim dlg As UserDialog
aArrayList(1) = "Day1"
aArrayList(2) = "Day2"
Dialog dlg
End Function
[ActiveDlgControls]
Function ActivateDlgControls(ControlName$, Action%, SuppValue%)
If (Action% = 2 And ControlName$ = "btn_Save") Then
sMissingMessage = ""
If (Not IsDate(CStr(Trim(DlgText$("txt_AfterDate"))))) Then
sMissingMessage = sMissingMessage & "- Please input the correct day format"
Else
MsgBox Format(Trim(DlgText$("txt_AfterDate")), "dd mmm yyyy")
' This Area will be using for get the selected array item id
' I can found the selected items with String
MsgBox Trim(DlgText$("aArrayList"))
' Unknow way to found the selected items id
' MsgBox dlg.aArrayList.SelectedItem(x)
End If
If (sMissingMessage <> "") Then
ActivateDlgControls = 1
iCheckResult = 1
sMissingMessage = "Information Missing:" & sMissingMessage
MsgBox sMissingMessage
End If
ElseIf (Action% = 2 And ControlName$ = "btn_Cancel") Then
iCheckResult = 2
End If
End Function
Any idea how should I get the selected ListBox item?
I want to get the array number that I selected inside the LisBox.
Although I got an other stupid idea for get the index like as below code:
For iArrayLoopCheck = 0 To UBound(aArrayList)
If (aArrayList(iArrayLoopCheck) = Trim(DlgText$("aArrayList")))Then
MsgBox "You Select item: " & iArrayLoopCheck
Exit For
End If
Next
Still I was looking for any smart code/ items/ easy way to get the result quickly just like get the String value in array like: Trim(DlgText$("xxxxx"))
Best Regards,
KT
To get the selected index of the list box:
list.ListIndex
returns 0 if the first item is selected, 1 if the second item is selected, etc., and -1 if no item is selected.
In a sample.csv file, which has fixed number of columns, I have to extract a particular field value and store it in a variable using VBScript.
sample.csv
100,SN,100.SN,"100|SN| 435623| serkasg| 15.32|
100|SN| 435624| serkasg| 15.353|
100|SN| 437825| serkasg| 15.353|"," 0 2345"
101,SN,100.SN,"100|SN| 435623| serkasg| 15.32|
100|SN| 435624| serkasg| 15.353|
100|SN| 437825| serkasg| 15.353|"," 0 2346"
I want to parse the last two fields which are within double quotes and store them in two different array variables for each row.
You could try using an ADO connection
Option Explicit
dim ado: set ado = CreateObject("ADODB.Connection")
ado.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\txtFilesFolder\;Extended Properties=""text;HDR=No;FMT=Delimited"";"
ado.open
dim recordSet: set recordSet = ado.Execute("SELECT * FROM [samples.csv]")
dim field3, field4
do until recordSet.EOF
field3 = recordSet.Fields(3).Value
field4 = recordSet.Fields(4).Value
' use your fields here
recordSet.MoveNext
loop
recordSet.close
ado.close
You may have an issue if those fields are greater than 255 characters in length - if they are, they may return truncated. You also may have better luck with ODBC or ACE connection strings instead of the Jet one I've used here.
Since CSV's are comma-separated, you can use the Split() function to separate the fields into an array:
' Read a line from the CSV...
strLine = myCSV.ReadLine()
' Split by comma into an array...
a = Split(strLine, ",")
Since you have a static number of columns (5), the last field will always be a(4) and the second-to-last field will be a(3).
Your CSV data seems to contain 2 embedded hard returns (CR, LF) per line. Then the first line ReadLine returns is:
100,SN,100.SN,"100|SN| 435623| serkasg| 15.32|
The solution below unwraps these lines before extracting the required fields.
Option Explicit
Const ForReading = 1
Const ForAppending = 8
Const TristateUseDefault = 2 ' Opens the file using the system default.
Const TristateTrue = 1 ' Opens the file as Unicode.
Const TristateFalse = 0 ' Opens the file as ASCII.
Dim FSO, TextStream, Line, LineNo, Fields, Field4, Field5
ExtractFields "sample.csv"
Sub ExtractFields(FileName)
Set FSO = CreateObject("Scripting.FileSystemObject")
If FSO.FileExists(FileName) Then
Line = ""
LineNo = 0
Set TextStream = FSO.OpenTextFile(FileName, ForReading, False, TristateFalse)
Do While Not TextStream.AtEndOfStream
Line = Line & TextStream.ReadLine()
LineNo = LineNo + 1
If LineNo mod 3 = 0 Then
Fields = Split(Line, ",")
Field4 = Fields(3)
Field5 = Fields(4)
MsgBox "Line " & LineNo / 3 & ": " & vbNewLine & vbNewLine _
& "Field4: " & Field4 & vbNewLine & vbNewLine _
& "Field5: " & Field5
Line = ""
End If
Loop
TextStream.Close()
Else
MsgBox "File " & FileName & " ... Not found"
End If
End Sub
Here is an alternative solution that allows for single or multiline CSV records. It uses a regular expression which simplifies the logic for handling multiline records. This solution does not remove CRLF characters embedded in a record; I've left that as an exercise for you :)
Option Explicit
Const ForReading = 1
Const ForAppending = 8
Const TristateUseDefault = 2 ' Opens the file using the system default.
Const TristateTrue = 1 ' Opens the file as Unicode.
Const TristateFalse = 0 ' Opens the file as ASCII.
Dim FSO, TextStream, Text, MyRegExp, MyMatches, MyMatch, Field4, Field5
ExtractFields "sample.csv"
Sub ExtractFields(FileName)
Set FSO = CreateObject("Scripting.FileSystemObject")
If FSO.FileExists(FileName) Then
Set MyRegExp = New RegExp
MyRegExp.Multiline = True
MyRegExp.Global = True
MyRegExp.Pattern = """([^""]+)"",""([^""]+)"""
Set TextStream = FSO.OpenTextFile(FileName, ForReading, False, TristateFalse)
Text = TextStream.ReadAll
Set MyMatches = MyRegExp.Execute(Text)
For Each MyMatch in MyMatches
Field4 = SubMatches(0)
Field5 = SubMatches(1)
MsgBox "Field4: " & vbNewLine & Field4 & vbNewLine & vbNewLine _
& "Field5: " & vbNewLine & Field5, 0, "Found Match"
Next
Set MyMatches = Nothing
TextStream.Close()
Else
MsgBox "File " & FileName & " ... Not found"
End If
End Sub