PDA

View Full Version : Stuck on proper use of InsertBefore



EricFletcher
03-02-2007, 02:54 PM
I am trying to use VBA to add html tags to a styled Word document. The code below loops through each paragraph and recognizes the style names, but I get an compile error related to my use of InsertBefore and InsertAfter (Method or data member not found).
Sub TagStyledDocument()

Dim rng As Range
Set rng = ActiveDocument.Range

Dim stylename As String
Dim thisparanum, numparas As Integer
numparas = rng.Paragraphs.Count
thisparanum = 0

Do While thisparanum <= numparas
thisparanum = thisparanum + 1
stylename = rng.Paragraphs(thisparanum).Style
Select Case stylename
Case "Heading 1"
rng.Paragraphs(thisparanum).InsertBefore "<h1>"
rng.Paragraphs(thisparanum).InsertAfter "</h1>"
Case "Heading 2"
'similar
Case "Heading 3"
'similar
Case "Body Text"
'similar
End Select
Loop

End Sub

As you can probably see, I am trying to add html tags to the beginning and end of each paragraph. Also, I'm using counters so I can collect the number of each tag added -- but I suspect there would be a better way than to have a counter variable for each style. Arrays maybe?

Any pointers and suggestions would be appreciated!

Dave
03-03-2007, 01:23 AM
I'm guessing that you need a selection before you can "insert before" ie. insert before what? On edit I don't seem to have much further to offer. Good luck with this. Dave

fumei
03-03-2007, 03:46 PM
No, no.
rng.Paragraphs(thisparanum)does NOT, repeat, NOT use the range of that paragraph.

To use the range of the paragraph itself, you must use...Range, as in: Case "Heading 1"
rng.Paragraphs(thisparanum).Range.InsertBefore "<h1>"
rng.Paragraphs(thisparanum).Range.InsertAfter "</h1>" Note however, that this will insert the </h1> AFTER the range...which includes the paragraph mark.

So it will put it at the START of the next paragraph. You can fix that of course.

May I suggest an alternative? Since you are, in fact, dealing with paragraphs, why not use paragraphs?Dim oPara As Word.Paragraph
Dim r As Word.Range
For Each oPara In ActiveDocument.Paragraphs
Set r = oPara.Range
Select Case oPara.Style
Case "Heading 1"
With r
.InsertBefore "<h1>"
.MoveEnd Unit:=wdCharacter, Count:=-1
.InsertAfter "</h1>"
End With
Case "Heading 2"
With r
.InsertBefore "<h2>"
.MoveEnd Unit:=wdCharacter, Count:=-1
.InsertAfter "</h2>"
End With
End Select
Next

Note the use of a Range object for each paragraph, moving the .End so the range terminates before the paragraph mark, THEN inserting after.

No need for the stylename string, or the integer counters (or counting).

EricFletcher
03-04-2007, 07:45 AM
Thanks Gerry. I got a similar tip from someone else and now have it working with the For Each construction:
Sub TagStyledDocument()
Dim para As Paragraph
Dim stylename As String
Dim strTagData As String

For Each para In ActiveDocument.Paragraphs
stylename = para.Style
Select Case stylename
Case "Heading 1"
strTagData = "h1"
Case "Heading 2"
strTagData = "h2"
Case "Heading 3"
strTagData = "h3"
Case "Body Text"
strTagData = "p"
'-- add case statements for each style to be tagged
End Select
With para.Range
.InsertBefore "<" & strTagData & ">"
.MoveEnd wdCharacter, -1
.InsertAfter "</" & strTagData & ">"
End With
Next para
End Sub
I'm still getting my head around the object model: on the surface, this seemed much simpler to use the basic selection methods, but I could see roadblocks looming.

My next challenge was working out how to manage list tags: if I encounter a List Bullet or List Number style (or one of its variants), I need to include the <ul></ul> or <ol></ol> tags. It occurred to me that if I could set up a 2D array like {"List Number","ol";"List Bullet","ul";"List Number 1","ol";"List Bullet 1","ul"}, I could test the style name and get the appropriate container tag. (I don't know the proper syntax, but I would want to use the 2nd element associated with a match on the 1st.)

This way, if a list element was encountered, I'd add the appropriate starter, and set a flag so I could avoid adding again until a non-list element triggered the need for a close tag.

But if that is possible, a 3D array might do it all and avoid the need for all the Case statements. So, {"Heading 1","h1","-"; "Body Text","p","-"; "List Number","li","ol"; "List Bullet","li","ul"} would have the style name, the associated tag (my strTagData above), and the container name ("-" if not a member of a container).

Is this possible with VBA or am I veering off on a dead end here?

fumei
03-04-2007, 08:10 PM
Just put of curiosity, what is the specific reason for declaring and using the variable "stylename"? You use:Dim stylename As String

stylename = para.Style
Select Case stylename There is, of course, nothing wrong in doing so, but it is not needed. There is no need to use the variable.

Just wondering.
Select Case para.Style does the job.


I am looking at your array idea.

EricFletcher
03-05-2007, 02:01 PM
Just put of curiosity, what is the specific reason for declaring and using the variable "stylename"? ...

I am looking at your array idea.
You are quite right Gerry; that variable isn't needed. It was a leftover from an earlier stage where I'd been putting up a message box to report the current style when it deviated from the most common one in my target document.

I've been playing around with the array idea, but I'm not clear about how best to populate an array. Consider the following:
Sub fillArray()
Dim a1, a2 As Integer
Dim u1 As Integer
Dim v6str As String

v6str = "Heading 1;h1;h|Heading 2;h2;h|Heading 3;h3;h|Body Text;p;b|List Number;li;ol|List Bullet;li;ul"
u1 = UBound(Split(v6str, "|"))

ReDim v6arr(u1, 3) As String

For a1 = 0 To u1
For a2 = 0 To 2
v6arr(a1, a2) = Split((Split(v6str, "|")(a1)), ";")(a2)
Next
Selection.TypeText v6arr(a1, 0) & " uses " & v6arr(a1, 1) _
& " with type=" & v6arr(a1, 2) & vbCrLf
Next

End Sub
This approach gives me the 5x3 array I want but it seems like an awkward way to fill it. (Since I want to have it open-ended to be able to add more elements, I had to figure out how I could use UBound to get the count of "sets" for the "x" value in the array.)

This does give me an alternative to the Case Select construction, but I can't see how to avoid having to loop through the array testing for the style name each time to find the index to use for the associated other values. For example, if the current para.Style is "List Number", I could increment a counter from 0 until para.Style=v6arr(i,0), so strTagData would be v6arr(i,1) and the container type would be v6arr(i,2).

Is there a way to determine the index without looping?

fumei
03-05-2007, 04:23 PM
Can you edit you post above and use an underscore? The code window goes...WAAAAAAAAAYYYYYY off for me. Thanks.

Ummmmm, multi-dimensional arrays are not my strong suit. I am trying to work this out myself.

Are there typos in your code? I am confused by the different names. Dim v6str As String

v6str = "Heading 1;h1;h|Heading 2;h2;h| _
Heading 3;h3;h|Body Text;p;b| _
List Number;li;ol|List Bullet;li;ul"
u1 = UBound(Split(v6str, "|"))

ReDim v6arr(u1, 3) As String

For a1 = 0 To u1
For a2 = 0 To 2
v6arr(a1, a2) = Split((Split(v6str, "|")(a1)), ";")(a2)
Next As I said, I am not fully up on multi-dimensional arrays. What is the difference between v6arr and v6str???

v6arr does not sem to be declared.

fumei
03-05-2007, 04:25 PM
Oh, wait a sec. Is v6arr declared someplace else? Globally? If so, then I get it...I think.

fumei
03-05-2007, 04:27 PM
How is v6arr declared?

EricFletcher
03-05-2007, 05:41 PM
I don't think there are typos... I copied the code directly from the VB window where it works just fine here. The v6arr is defined with the ReDim statement and is not defined elsewhere. I found that if I used "Dim" it gave me an error. I recalled seeing a similar approach used for defining an array where the size was dynamic.

Sorry about the long lines: I did break the printing line with _ but couldn't break the quoted string. My display has lots of room so this is something I often overlook: the code below is narrower but I could only get around the quoted string by the concatenation kludge!
Sub fillArray()
Dim a1, a2 As Integer
Dim u1 As Integer
Dim v6str As String

v6str = "Heading 1;h1;h|Heading 2;h2;h"
v6str = v6str & "|Heading 3;h3;h|Body Text;p;b"
v6str = v6str & "|List Number;li;ol|List Bullet;li;ul"
u1 = UBound(Split(v6str, "|"))

ReDim v6arr(u1, 3) As String

For a1 = 0 To u1
For a2 = 0 To 2
v6arr(a1, a2) = Split((Split(v6str, "|")(a1)), ";")(a2)
Next
Selection.TypeText v6arr(a1, 0) & " uses " & v6arr(a1, 1) _
& " with type=" & v6arr(a1, 2) & vbCrLf
Next
End Sub

The longest line now is 67 characters: out of interest, how many characters is generally considered to be a suitable width?

Essentially, I am holding my information in an Nx3 string named v6str. Each set of 3 items is delimited by the | character, and each item is delimited by the semicolon. I use the Split function with the | to get the number of sets (u1). The array can then be populated via the loops by using a nested Split function to parse out the elements.

I included the "Selection.TypeText" line to make a list to ensure it all worked as planned.

I'll try using this in my modified tagger code, but I would like to avoid having to loop through the array each time I process a paragraph.

fumei
03-05-2007, 06:43 PM
You can break the quoted string with the underscore just like you did with your concatentation.v6str = "Heading 1;h1;h|Heading 2;h2;h|" & _
"Heading 3;h3;h|Body Text;p;b|" & _
"List Number;li;ol|List Bullet;li;ul"

EricFletcher
03-05-2007, 06:49 PM
Okay, I've incorporated the array idea into the tagger.
Sub TagStyledDocumentBetter()
'-- evolving version
Dim para As Paragraph
Dim a1, a2 As Integer
Dim u1 As Integer
Dim arrayStr As String

arrayStr = "Heading 1;h1;h|Heading 2;h2;h|Heading 3;h3;h"
arrayStr = arrayStr & "|Body Text;p;b|List Number;li;ol"
arrayStr = arrayStr & "|List Bullet;li;ul|List Continue;lc;p"
u1 = UBound(Split(arrayStr, "|"))

ReDim styleArray(u1, 3) As String

For a1 = 0 To u1
For a2 = 0 To 2
styleArray(a1, a2) = Split((Split(arrayStr, "|")(a1)), ";")(a2)
Next
Next

'-- style name=styleArray(a1, 0)
'-- html code=styleArray(a1, 1)
'-- type=styleArray(a1, 2)

For Each para In ActiveDocument.Paragraphs
For a1 = 0 To u1
If para.Style = styleArray(a1, 0) Then Exit For
Next
With para.Range
.InsertBefore "<" & styleArray(a1, 1) & ">"
.MoveEnd wdCharacter, -1
.InsertAfter "</" & styleArray(a1, 1) & ">"
End With
Next para
End Sub

This works, and eliminates the Case Select construction, so it is more flexible. I renamed variables here to make them more meaningful. The arrayStr string needs to be entered carefully, but I suppose I could use loops to read the values in from a text file to avoid having to change code to add another style set.

As I mentioned earlier, I'm trying to improve my skills at code that goes beyond minor tweaks to recorded macros. If you have any suggestions about how I've done any of this, I'm open.