Infinite loop on the currency symbol alt key code 0164 - vba

In my sript I use
If Selection.Text Like "* [aAwWzZiIoOuUVQ] *"
but if script check a table falls into infinite loop on the currency symbol alt key code 0164.
I use MsgBox Selection.Text and loops on such a line (and similar with marks 0164):
MsgBox show:
How to skip checking such a line in the table, go further in the code or exit the For Next loop.
Something like:
If Selection.Text = ChrW(164) Then
[EDIT]
Ok my fault.
This is 7 lines not 1.
And selected text is not ChrW(164) but NOTHING.
I try If Selection.Text = "" Then
or
If Selection.Text = ChrW(0) Then
but loop not stop.

[EDIT2]
Dim NumLines As Long
Selection.EndKey Unit:=wdStory, Extend:=wdExtend
NumLines = Selection.Range.ComputeStatistics(wdStatisticLines)
MsgBox "Lines to check " & (NumLines) & " If you want STOP: Press CTRL+Pause Break"
Selection.Collaps
For i = 1 To NumLines...
Now I understand where the mistake is.
The macro did not loop indefinitely, even though it seemed to. It didn't hang on any particular character, it simply counted each table cell as a separate line and took that value as the final digit of the loop counter.

Related

Check if a Range of text fits onto a single line

I'm programmatically filling in a regulated form template where lines are predefined (as table cells):
(Using plain text Content Controls as placeholders but this isn't relevant to the current question.)
So, I have to break long text into lines manually (auto-adding rows or something is not an option because page breaks are also predefined).
Now, since characters have different width, I cannot just set some hardcoded character limit to break at (or rather, I can, and that's what I'm doing now, but this has proven to be inefficient and unreliable, as expected). So:
How do I check if a Range of text fits on a single line -- and if it doesn't, how much of it fits?
I've checked out Range Members (Word) but can't see anything relevant.
The only way is to .Select that text, them manipulate the selection. Selection in the only object for which you can use wdLine as a boundary. Nothing else in the Word object model works with automatic line breaks.
Sub GetFirstLineOfRange(RangeToCheck As Range, FirstLineRange As Range)
'Otherwise, Word doesn't always insert automatic line breaks
'and all the text will programmatically look like it's on a single line
If Not Application.Visible Or Not Application.ScreenUpdating Then
Application.ScreenRefresh
End If
Dim SelectionRange As Range
Set SelectionRange = Selection.Range
Set FirstLineRange = RangeToCheck
FirstLineRange.Select
Selection.Collapse Direction:=wdCollapseStart
Selection.EndOf Unit:=wdLine, Extend:=wdExtend
Set FirstLineRange = Selection.Range
If FirstLineRange.End > RangeToCheck.End Then
FirstLineRange.End = RangeToCheck.End
End If
SelectionRange.Select
End Sub
Function IsRangeOnOneLine(RangeToCheck As Range) As Boolean
Dim FirstLineRange As Range
GetFirstLineOfRange RangeToCheck, FirstLineRange
IsRangeOnOneLine = FirstLineRange.End >= RangeToCheck.End
End Function
The subroutine GetFirstLineOfRange takes a RangeToCheck and sets FirstLineRange to the first text line in the given range.
The function IsRangeOnOneLine takes a RangeToCheck and returns True if the range fits on one line of text, and False otherwise. The function works by getting the first text line in the given range and checking whether it contains the range or not.
The manipulation of the Selection in GetFirstLineOfRange is necessary because the subroutine wants to move the end of the range to the end of the line, and the movement unit wdLine is available only with Selection. The subroutine saves and restores the current Selection; if this is not necessary then the temporary variable SelectionRange and the associated statements can be deleted.
Note:
There is no need to scroll anything - which in any event is not reliable. Try something based on:
With Selection
If .Characters.First.Information(wdVerticalPositionRelativeToPage) = _
.Characters.Last.Information(wdVerticalPositionRelativeToPage) Then
MsgBox .Text & vbCr & vbCr & "Spans one line or less."
Else
MsgBox .Text & vbCr & vbCr & "Spans more than one line."
End If
End With

Testing for "hard" page break

How can I test whether the insertion point is at the start of a new page created by a manual page break? It seems like it should be as simple as checking if the preceding character is CHR(12), but that doesn't seem to work.
If Selection.Type = CHR(12) Then
Selection.TypeText Text:="HARD PAGE"
Else
Selection.TypeText Text:="NO HARD PAGE"
End If
Is it just a syntax error or do I have the wrong approach here?
You have to move the selection (or the range) backwards. Selection.Text (or Range.Text) always returns the character following the IP. Of course, you may not want to actually move the selection. That means you can work with a Range object to do the testing.
Since you have to move backwards, anyway, to test whether there's a hard pagebreak, I've put it in a loop so that the selection can be anywhere on the page, to begin with.
Also, I've added a check whether the macro has started on the first page, since you'd otherwise go into an infinite loop, moving backwards from the Selection to the next page.
Sub CheckWhetherHardPageBreak()
Dim rngToCheck As word.Range
Dim pgNr As Long
Dim pgNrChange As Long
Set rngToCheck = Selection.Range
pgNr = rngToCheck.Information(wdActiveEndPageNumber)
If pgNr = 1 Then
MsgBox "Can't start on Page 1"
Exit Sub
End If
pgNrChange = pgNr
Do While pgNrChange = pgNr
rngToCheck.MoveEnd wdCharacter, -1
pgNrChange = rngToCheck.Information(wdActiveEndPageNumber)
Loop
'Extend the selection to include the following character
'So that ASC() works
rngToCheck.MoveEnd wdCharacter, 1
If Asc(rngToCheck.Text) <> 12 Then
'Move it back before the previous character
'as the character immediately following a hard page break is Chr(13)
rngToCheck.MoveEnd wdCharacter, -2
End If
rngToCheck.MoveEnd wdCharacter, 1
If Asc(rngToCheck) = 12 Then
Selection.TypeText Text:="HARD PAGE"
Else
Selection.TypeText Text:="NO HARD PAGE"
End If
End Sub
I think you might be intending to use Chr(13) or Chr(10).
More information here:
Stack Overflow: What are carriage return, linefeed, and form feed?

Indent multiple lines the same way as plain text editor

Sometimes I have code blocks in my Word documents, and I want to work with them without copying to plain text editor.
Namely, I want to have an ability to indent/unindent multiple lines of code using "Tab" character. This task is very simple in any plain text editor or IDE, but for the sake of clarity, I will show it here. Tabs are shown as black arrows:
Initial state
Using the Shift key or mouse, I selected a part of JavaScript function
Then I pressed Tab key on my keyboard
Selected lines were indented by inserting tab character on each line.
How it could be done with VBA?
Since I don't post any code (as evidence of my own efforts), I don't expect to get something completely working. But at least, I hope to get an understanding "how" it could be done.
As David suggested, I recorded a macro. Here how it looks:
Sub Indentator()
Selection.TypeText Text:=vbTab
End Sub
The problem is, that I don't understand how to get it work for multiple lines. If I select them, this macro (and it was not surprise for me) just inserts "Tab" instead of selection.
Insert a tab character at the start of each paragraph in the selection:
Sub Indentator()
Dim para As Paragraph
For Each para In Selection.Paragraphs
para.Range.InsertBefore vbTab
Next
End Sub
(This assumes that each of your code "lines" is a new "paragraph" in Word, which it usually would be if you are intending to copy/paste this to/from actual code.)
If the macros are named IncreaseIndent and DecreaseIndent, they can be run using the Increase and Decrease Indent buttons on the Home tab.
Sub IncreaseIndent()
If Selection.Start = Selection.End Then
Selection.InsertBefore vbTab
Selection.Start = Selection.End
Else
Dim p As Paragraph
For Each p In Selection.Paragraphs
p.Range.InsertBefore vbTab
Next
End If
End Sub
Sub DecreaseIndent()
If Selection.Start = Selection.Paragraphs(1).Range.Start Then
Selection.Start = Selection.Start + 1
End If
Dim p As Paragraph, c As Range
For Each p In Selection.Paragraphs
Set c = p.Range.Characters(1)
If c.Text = vbTab Then c.Delete
Next
End Sub
Reference https://wordmvp.com/FAQs/MacrosVBA/InterceptSavePrint.htm

Replace string and rest of line with other string

So here's what I want to do:
I have a string origStr, which is at the beginning of a line in a Word document. After origStr is an unknown value (e.g. 23 or 2,6)
I want to find that string and replace the value after with another known value, but keep the string.
Example in my Word document:
Diam. diastole, mm: 53
[Running VBA makro where user input is 54,3]
Expected Result:
Diam. diastole, mm: 54,3
Actual result:
Diam. diastole, mm: 54,3 53
So here's what i got:
origStr = LArray(i, j - 1).Caption & ": *"
replStr = LArray(i, j - 1).Caption & ": " & TBArray(i, j).Text
With ActiveDocument.Content.Find
.MatchWildcards = True
.ClearFormatting
.Text = origStr
.Replacement.ClearFormatting
.Replacement.Text = replStr
.Execute Replace:=wdReplaceAll, Forward:=True, Wrap:=wdFindStop
End With
Why isn't my old value being deleted?
Thanks in advance!
Word uses "lazy" pattern matching which is good in this case because otherwise it would just replace the rest of your entire document. Vincent G gave a solution for one word (no more than one space in the rest of the line), here is something more general:
To get Word to replace everything until the end of the line, you need to include the end of the line into the search string. ^l or ^11 is the control code for new line (shift+enter), ^12 is page or section break, ^13 is carriage return.
This is the most "recent" list I found.
So "yourtext*^11" would match everything from "yourtext" until the next new line and so on.
Depending on your formatting, it could be either one of the above so you have to be careful. If you replace "*^12" but your lines end with new line, you will replace the whole paragraph.
To catch all of them, you can group them: yourtext*[^11^12^13] will match everything until the next new line or carriage return. However if we replace that, the end of the line will be replaced as well so we have to include it in the replacement text. How do we know what it was?
We can use () to define a submatch and \1, \2 etc to use these submatches in the replacement text:
origStr = LArray(i, j - 1).Caption & ": *([^11^12^13])"
replStr = LArray(i, j - 1).Caption & ": " & TBArray(i, j).Text & "\1"
Example:
before:
after replacing with origStr = "yourtext*([^11^12^13])" and replStr = "yourtext newtext\1":
Note: These expressions also work with the Find and Replace dialog, just make sure "Use Wildcards" is enabled
Its not really a VBA problem, the same happening in "Find and Replace" dialog.
Replace your original string with:
origStr = LArray(i, j - 1).Caption & ": <*>"

VBA: Debug.Print without newline?

For debugging in VBA, I have several Debug.Print statements throughout my code. For a line-parsing block, I'd like to print the line, and output any flags inline with the line, without having multiple Debug.Print sFlag & sLine statements throughout the many if/elseif/else blocks.
Is there a way, within VBA, to suppress the newline at the end of a Debug.Print statement?
It turns out you can easily do this by simply adding a semicolon to the end of your Debug.Print statement. Like so:
While oExec.StdOut.AtEndOfStream = False
sLine = oExec.StdOut.ReadLine
If bInFileSystem Then
If AnalyzeFileSystemLine(sLine) = False Then
Debug.Print "[FSERR]: ";
End If
ElseIf bInWASCheck Then
If AnalyzeWASLine(sLine) = False Then
Debug.Print "[WASERR]: ";
End If
End If
Debug.Print sLine
Wend
So, some sample output will be:
test text things look good!
[FSERR]: uh oh this file system is at 90% capacity!
some more good looking text :)
[WASERR]: oh no an app server is down!