I will try to explain the issue as clear as possible.
I have a column in an Excel file and each cell in this column contains a description of some issue. The description has four levels such as Name, Issue, Solution and Result, all these four in the same cell.
I need VBA code that will find each level in each cell and create line break in the cell.
So instead of this:
Name: 123 Issue: My issue: Solution: Try to resolve Result: Resolved.
After the code runs will be like this:
Name: 123 (line break)
Issue: My issue (line break)
Solution: Try to resolve (line break)
Result: Resolved (line break)
Please let me know if there is any solution?
Select the cell containing the data and run:
Sub FixData()
Dim r As Range
Set r = ActiveCell
t = r.Text
t = Replace(t, "Issue:", Chr(10) & "Issue:")
t = Replace(t, "Solution:", Chr(10) & "Solution:")
t = Replace(t, "Result:", Chr(10) & "Result:")
r.Value = t
r.WrapText = True
End Sub
If necessary, you can put this in a loop.
loop through the cells and add linefeeds.
sub makelfs()
dim i as long, j as long, arr as variant, str as string
arr = array("Issue:","Solution:","Result:")
with worksheets("excel file")
for i=2 to .cells(.rows.count, "a column in excel file").end(xlup).row
str = .cells(i, "a column in excel file").value2
for j = lbound(arr) to ubound(arr)
str = replace(str, arr(j), vblf & arr(j))
next j
.cells(i, "a column in excel file") = str
.cells(i, "a column in excel file").wraptext = true
next i
end with
end sub
s = "Name: 123 Issue: My issue: Solution: Try to resolve Result: Resolved."
arr = Split(s, Chr(32))
For Each Item In arr
If cnt > 0 Then
If Right(Item, 1) = ":" Then Item = vbCrLf & Item
End If
output = output & Item & " "
cnt = cnt + 1
Next Item
Debug.Print output
Using a slightly different approach which doesn't rely on Issue, Solution and Result being present.
As said in my comment - look for the first space before the colon and replace it with a line feed (put vbcr in my comment - should be vblf).
Public Function AddLineBreak(Target As Range) As String
Dim lColon As Long
Dim lSpace As Long
Dim sFinal As String
sFinal = Target.Value
lSpace = Len(sFinal)
Do While lSpace <> 0
sFinal = Left(sFinal, lSpace - 1) & Replace(sFinal, " ", vbLf, lSpace, 1)
lColon = InStrRev(sFinal, ":", lSpace - 1)
lSpace = InStrRev(sFinal, " ", lColon)
Loop
AddLineBreak = Trim(sFinal)
End Function
You can call the function in a procedure:
Sub Test()
Dim rCell As Range
For Each rCell In Sheet1.Range("A1:A13")
rCell = AddLineBreak(rCell)
Next rCell
End Sub
or as a worksheet function:
=AddLineBreak(A1)
This assumes an error in the original string you posted:
Name: 123 Issue: My issue: Solution: Try to resolve Result: Resolved. should be
Name: 123 Issue: My issue Solution: Try to resolve Result: Resolved.
(extra colon before Solution which is not shown in your After code example).
Edit - it also means you cannot have spaces in your headings. So you can have " Issue:" or " My_Issue:" but not " My Issue:"
Related
I am required to extract passages of text from the contents of Excel cells in which the originator has essentially done a manual Track Changes using Strikethrough font. The passages are identifiable with certain character patterns, but I have to ignore Strikethrough characters to see them. The Strikethrough characters do not appear in regular locations within each cell, so are essentially randomly dispersed with normal font text.
I have achieved my goal using VBA for Excel, but the solution is extremely (and impracticably) slow. Having searched this site and the wider web for answers, it seems the use of the Characters object is to blame.
So my question is: has anyone found a way of parsing such text that does not involve the Characters object?
The sub I wrote to do the parsing is too long to post here, but following is some test code which uses the Characters object in a similar way. This takes 60 s to parse a cell with 3000 characters in it. At that speed, it would take 50 hours to process the entire spreadsheet I've been given.
Private Sub FindLineBreakChars(TargetCell As Excel.Range)
Dim n As Integer
Dim ch As String
Dim st As Boolean
If TargetCell.Cells.Count <> 1 Then
Call MsgBox("Error: more or less than one cell in range specified.")
Else
If IsEmpty(TargetCell.Value) Then
Call MsgBox("Error: target cell is empty.")
Else
If Len(TargetCell.Value) = 0 Then
Call MsgBox("Error: target cell contains an empty string.")
Else
'Parse the characters in the cell one by one.
For n = 1 To TargetCell.Characters.Count
ch = TargetCell.Characters(n, 1).Text
st = TargetCell.Characters(n, 1).Font.Strikethrough
If ch = vbCr Then
Debug.Print "#" & n & ": Carriage Return (vbCr)" & ", strikethrough = " & st & vbCrLf
ElseIf ch = vbLf Then
Debug.Print "#" & n & ": Line Feed (vbLf)" & ", strikethrough = " & st & vbCrLf
End If
Next n
End If
End If
End If
End Sub
You're right, the access to Characters is very slow, so your goal should be to reduce it's usage as much as possible.
I don't understand your requirement details, but the following code should get you an idea how you could speed up the code. It reads the content of a cell only once, split the text into separate lines, calculates the position of the single linefeed characters and look at that position for the formatting. As far as I know there is no way to access the formatting all at once, but now the access to the characters-object is reduced to one per line:
With TargetCell
Dim lines() As String, lineNo As Integer, textLen As Long
lines = Split(.Value2, vbLf)
textLen = Len(lines(0)) + 1
For lineNo = 1 To UBound(lines)
Dim st
st = .Characters(textLen, 1).Font.Strikethrough
Debug.Print "#" & textLen & ": LineFeed (vbLf) strikethrough = " & st
textLen = textLen + Len(lines(lineNo)) + 1
Next lineNo
End With
To my knowledge, Excel stores Linebreaks in a cell using just the LineFeed character, so the code is checking only that.
This might meet your performance needs: it calls a function which parses the XML representation of the cell content, removes the struck-out sections, and returns the remaining text.
It will be much faster than looping over Characters
Sub Tester()
Debug.Print NoStrikeThrough(Range("A1"))
End Sub
'Needs a reference to Microsoft XML, v6.0
' in your VBA Project references
Function NoStrikeThrough(c As Range) '
Dim doc As New MSXML2.DOMDocument60, rv As String
Dim x As MSXML2.IXMLDOMNode, s As MSXML2.IXMLDOMNode
'need to add some namespaces
doc.SetProperty "SelectionNamespaces", _
"xmlns:ss='urn:schemas-microsoft-com:office:spreadsheet' " & _
"xmlns:ht='http://www.w3.org/TR/REC-html40'"
doc.LoadXML c.Value(11) 'cell data as XML
Set x = doc.SelectSingleNode("//ss:Data")'<< cell content
Set s = x.SelectSingleNode("//ht:S") '<< strikethrough
Do While Not s Is Nothing
Debug.Print "Struck:", s.Text
x.RemoveChild s '<< remove struck section
Set s = x.SelectSingleNode("//ht:S")
Loop
NoStrikeThrough = doc.Text
End Function
EDIT: here's another way to go at it, by breaking up the text into "blocks" and checking each block to see if it has any strikethrough. How much faster this is than going character-by-character may depend on block size and the distribution of struck-out text in each cell.
Function NoStrikeThrough2(c As Range)
Const BLOCK As Long = 50
Dim L As Long, i As Long, n As Long, pos As Long, x As Long
Dim rv As String, s As String, v
L = Len(c.Value)
n = Application.Ceiling(L / BLOCK, 1) 'how many blocks to check
pos = 1 'block start position
For i = 1 To n
v = c.Characters(pos, BLOCK).Font.Strikethrough
If IsNull(v) Then
'if strikethough is "mixed" in this block - parse out
' character-by-character
s = ""
For x = pos To pos + BLOCK
If Not c.Characters(x, 1).Font.Strikethrough Then
s = s & c.Characters(x, 1).Text
End If
Next x
rv = rv & s
ElseIf v = False Then
'no strikethrough - take the whole block
rv = rv & c.Characters(pos, BLOCK).Text
End If
pos = pos + BLOCK 'next block position.
Next i
NoStrikeThrough2 = rv
End Function
EDIT2: if you need to make sure all newline characters are not struck out before processing the cell -
Sub ClearParaStrikes(c As Range)
Dim pos As Long
pos = InStr(pos + 1, c.Value, vbLf)
Do While pos > 0
Debug.Print "vbLf at " & pos
c.Characters(pos, 1).Font.Strikethrough = False
pos = InStr(pos + 1, c.Value, vbLf)
Loop
End Sub
I am new to VBA in excel. So, i create a string from multiple parts and output it in a cell on some sheet like this:
Sheets("Output").Cells(iRow, 1).Value = LArray(0) + "?" + adding + "/#" + LArrayNew(1)
I am expecting: text?text/#text
But result on excel sheet is: text?text#text
Where is the "/"?
Something like:
Sub dural()
Dim LArray(0 To 1) As String
Dim adding As String, LArrayNew(0 To 1) As String
adding = "X"
LArrayNew(1) = "New"
LArray(0) = "0"
iRow = 1
Sheets("Output").Cells(iRow, 1).Value = LArray(0) & "?" & adding & "/#" & LArrayNew(1)
End Sub
will produce:
0?X/#New
Note the slash is there!
I am trying to use the code below, however I don't understand why it is printing out a blank message box? Additionally, there is only one for each day, and it is saying there is 2 files?
How do I print these back effectively, second, how do I then use that to open the sheet?
The files are written as samadmin15112018_??????.csv Where the question marks are a time stamp which I don't know.
Sub runFA()
Const yourfilepath = "R:\samsdrive\sam\test\"
Dim s As String
Dim x As Integer
Dim v() As String
s = Dir(yourfilepath & "samadmin" & format(Sheets("Name").Range("C3"), "yyyymmdd") & "_*.csv")
v = Split(vbNullString)
Do Until s = ""
x = x + 1
ReDim Preserve v(x + 1)
s = Dir()
Loop
If UBound(v) > 0 Then
MsgBox "There are " & UBound(v) & " workbooks", vbOKOnly
MsgBox v(x + 1)
Else
If v(0) <> "" Then Workbooks.Open (yourfilepath & v(0))
MsgBox ("There are 0 ")
End If
End Sub
Fixing the previous answer...
You were getting an empty element because the original code resized the array for the first element, which meant that v(0) was always going to be vbNullString. With string arrays, you can take advantage of the Split function's behavior of returning an array with a UBound of -1 and an LBound of 0 if you're going to add elements to it dynamically:
Sub runFA()
Const targetPath = "R:\samsdrive\sam\test\"
Dim located() As String
located = Split(vbNullString)
Dim result As String
result = Dir$(targetPath & "samadmin" & Format$(Sheets("Name").Range("C3"), "yyyymmdd") & "_*.csv")
Do Until result = vbNullString
ReDim Preserve located(UBound(located) + 1)
located(UBound(located)) = result
result = Dir$()
Loop
If UBound(located) <> 0 Then
MsgBox "There are " & (UBound(located) + 1) & " workbooks", vbOKOnly
Else
Workbooks.Open targetPath & result
End If
End Sub
A couple other things to note
I changed the variable names from single letter identifiers to something a little easier to read and understand.
The indentation is now consistant.
It uses the string typed functions for Dir and Format.
You don't need to track the count of results with x at all.
If you only have one element in the results array, you can simply use result - there isn't any reason to index back into the array.
So currently in column B, there are cells that have no spaces before them but there are also cells that do.
For example: cell B1 would just be "Market:" but then cell B4, B5,B6,B10, B14, etc would have "_____Total:" ( __ meaning blank space)
What would I have to write to delete the 5 spaces before "Total:"?
I currently have:
mos = Range("B:B")
For Each x In mos
xv = x.Value
If Left(xv, 1) = "" Then xv = Right(xv, Len(xv) - 1)
Next xv
which gives me an error.
Please let me know what I should do.
Thanks!
Use Trim$ function. Trim function removes excess trailing and leading white space. Trim$ is a typed function and is faster.
Using Intersect, as below, means only processing the necessary number of rows and not the entire column B.
Be explicit. Put Option Explicit at the top of your code to force variable declaration and declare your variables. And fully qualify which sheet you are working with.
As noted in another answer, there are variants such as LTrim$ just to remove from left (Start of string) and RTrim$, just to remove from right (end of string).
Code:
Option Explicit
Public Sub test()
Dim mos As Range
Dim x As Range
With ActiveSheet 'change to appropriate worksheetname
Set mos = .Range("B:B")
For Each x In Intersect(mos, .UsedRange)
x = Trim$(x)
Next x
End With
End Sub
Bonus:
There are a lot of existing functions written to "clean" strings removing all "extra" white space characters i.e. leaving only single internal white spaces.
Here is one by Henry Habermacher
Public Function removeObsoleteWhiteSpace _
(FromString As Variant) As Variant
If IsNull(FromString) Then 'handle Null values
removeObsoleteWhiteSpace = Null
Exit Function
End If
Dim strTemp As String
strTemp = Replace(FromString, vbCr, " ")
strTemp = Replace(strTemp, vbLf, " ")
strTemp = Replace(strTemp, vbTab, " ")
strTemp = Replace(strTemp, vbVerticalTab, " ")
strTemp = Replace(strTemp, vbBack, " ")
strTemp = Replace(strTemp, vbNullChar, " ")
While InStr(strTemp, " ") > 0
strTemp = Replace(strTemp, " ", " ")
Wend
strTemp = Trim(strTemp)
removeObsoleteWhiteSpace = strTemp
End Function
Example call:
Public Sub testing()
Debug.Print removeObsoleteWhiteSpace(" my string with a few spaces ")
End Sub
To avoid looping you can use:
Dim myAddress As String
myAddress = "B1:B" & Cells(Rows.Count, 2).End(xlUp).Row
Range(myAddress) = Evaluate("IF(" & myAddress & "="""","""",TRIM(" & myAddress & "))")
which can be also written as:
With Range("B1", Cells(Rows.Count, 2).End(xlUp))
.Value = Evaluate("IF(" & .Address & "="""","""",TRIM(" & .Address & "))")
End With
or Replace() method:
Range("B1", Cells(Rows.Count, 2).End(xlUp)).Replace what:=" ", replacement:="", lookat:=xlPart
of course all above solutions assume that you only have extra spaces at the beginning of each cell content, or they will trim (first two solutions) or eliminate (3rd solution) other spaces also
if you actually have the need to eliminate the leading spaces (the ones preceding the first not blank character) leaving all other ones in place, then you have to loop and use LTrim() VBA function:
Dim cell As Range
For Each cell In Range("B1", Cells(Rows.Count, 2).End(xlUp))
cell.Value = LTrim(cell.Value)
Next
Sorry, didn't realize your loop was only to remove spaces.
This is how I trim my spaces:
Sub trim()
Dim mos As Range
Set mos = Range("B:B")
mos.Value = Application.trim(mos)
End Sub
I need to maintain a count of replacements made before implementing the Worksheets(...).Columns(...).Replace function using Excel VBA.
Can anyone guide me regarding code that I probably need to insert in *** below for counting the replacements that are about to occur in the next line of code? Thanks.
Function Value_Replace(TabName As String, ColumnTitle As String, val_Old As String, val_New As String)
Dim MyColumn, CountReplacements As Long
Dim MyColumnLetter As String
MyColumn = WorksheetFunction.Match(ColumnTitle, ActiveWorkbook.Sheets(TabName).Range("1:1"), 0)
'CountReplacements = ***?
Worksheets(TabName).Columns(MyColumnLetter).Replace _
what:=val_Old, Replacement:=val_New, _
SearchOrder:=xlByColumns, MatchCase:=False
Value_Replace = "Values " & CountReplacements & " in column " & MyColumnLetter & " updated!"
End Function
I propose to store in the cell (eg [A1]) and the number of repetitions for each call to change to increment it. But it must be the end of all calculations to clear the cell that would be the next time you call this function, the function would not start incrementing the previous value.
Some will look like this:
[A1].value = [A1].value + 1
CountReplacements = [A1].value
How about using COUNTIF with * Old_Str *? You don't need to count the actual replacements before they happen, just find out how many occurrences of Old_Str there are in your column within the contents of each cell before you start the replacement. Doing it on New_Str after you'd replaced it would be unwise unless you could guarantee there were no occurrences of New-Str before you executed the replacement .
CountReplacements = WorksheetFunction.CountIf(ActiveSheet.Columns(MyColumnLetter), "*" & val_Old & "*")
Give it a go and see
Thanks. But this worked as well:
While Not ConsecutiveEmpty = 1
If IsEmpty(Worksheets("Sheet1").Cells(LastRow, 2).Value) Then
ConsecutiveEmpty = ConsecutiveEmpty + 1
End If
LastRow = LastRow + 1
Wend
MyCount = 0
For i = 2 To LastRow
If Worksheets("Sheet1").Cells(i, MyColumn).Value = val_Old Then
MyCount = MyCount + 1
End If
Next