<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Ashita.org &#187; Macro</title>
	<atom:link href="http://www.ashita.org/tag/macro/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.ashita.org</link>
	<description></description>
	<lastBuildDate>Wed, 27 Apr 2011 22:37:20 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Word macro to insert sequence of numbers</title>
		<link>http://www.ashita.org/word-macro-to-insert-sequence-of-numbers/</link>
		<comments>http://www.ashita.org/word-macro-to-insert-sequence-of-numbers/#comments</comments>
		<pubDate>Sun, 17 Aug 2008 09:31:48 +0000</pubDate>
		<dc:creator>Jon</dc:creator>
				<category><![CDATA[VBA]]></category>
		<category><![CDATA[Macro]]></category>
		<category><![CDATA[Microsoft Word]]></category>
		<category><![CDATA[Regex]]></category>

		<guid isPermaLink="false">http://ashita.org/?p=93</guid>
		<description><![CDATA[I know I promised an article on CSS and Javascript using opacity, but first I&#8217;ll share a recent macro i created for a friend of mine. I used to make macros from time to time, sometimes quite complex ones, for Word and excel, but haven&#8217;t for a few years. The following, however, works quite well, [...]]]></description>
			<content:encoded><![CDATA[<p>I know I promised an article on CSS and Javascript using opacity, but first I&#8217;ll share a recent macro i created for a friend of mine.</p>
<p>I used to make macros from time to time, sometimes quite complex ones, for Word and excel, but haven&#8217;t for a few years. The following, however, works quite well, and since there was generally a lack of information on how to do just this, I thought I&#8217;d share it with you.</p>
<p>First off, here are the three files involved, <a href="http://www.ashita.org/files/NumberInsert.frm">NumberInsert.frm</a>, <a href="http://www.ashita.org/files/NumberInsert.frx">NumberInsert.frx</a> and <a href="http://www.ashita.org/files/PatentTranslationMacros.bas">PatentTranslationMacros.bas</a>.</p>
<p>The code for Sequence(), the main macro, is as follows:</p>
<pre>Sub Sequence()
	Dim regEx, Match, Matches

	' Get the active word document
	Set objWdDoc = Word.Application.ActiveDocument          

	' Set our range to be the entire document contents
	Set objWdRange = objWdDoc.Content                       

	' To be used for the result string
	Dim Result As String                                    

	' Create a regular expression object.
	Set regEx = CreateObject("VBScript.RegExp")             

	'Show our form
	NumberInsert.Show

	If NumberInsert.Tag Then
		' Get the value of the string we want to find from the form
		regEx.Pattern = NumberInsert.FindStr.Value
		' Set case sensitivity.
		regEx.IgnoreCase = False
		' Set global applicability to false. This was the odd part since
		' no examples on the internet, that I could find, used
		' Globabl = False.
		regEx.Global = False                        

		' A little silly but we have to use something to catch the return value from MoveStart
		Dim temp As Long        

		Dim length As Long, repStart As String, repEnd As String
		' How many digits to show, retrieved from the form and converted to Long
		length = CLng(NumberInsert.NumberLength.Value)
		' the replacement string for the portion BEFORE the inserted number
		repStart = NumberInsert.ReplaceStart
		' the replacement string for the portion AFTER the inserted number
		repEnd = NumberInsert.ReplaceEnd                

		Dim realIndex As Long

		Dim i As Integer        ' For our step counter
	        i = 0
		Do
			' Get the first match (Global = False, remember)
			Set Matches = regEx.Execute(objWdRange) 

			' If there isn't a match, Exit
			If Matches.Count = 0 Then
		                Exit Do
		        End If

			' Get the first match from the MatchCollection.
	        	Set Match = Matches(0)                  

			' Increment our counter. Since we're starting from 1 we increment before we
			' change the value
			i = i + 1                               

			' Convert the characters to full width Japanese characters
			' String(Length, "0") gives us the format pattern for the number
			tStr = StrConv(Format(i, String(length, "0")), vbWide, 1041)    

			' regex replace using the found value and our pattern from the form
			Result = regEx.Replace(Match.Value, repStart &amp; tStr &amp; repEnd)   

			' add the index for the range start and the match index which only considers
			' its position within the range
			realIndex = objWdRange.Start + Match.FirstIndex         

			' Insert our result into the range
			objWdDoc.Range(realIndex, realIndex + Len(Match.Value)).Text = Result   

			' move the start point on our range by the length
			' of our replacement plus the position within the
			' range, plus one
			temp = objWdRange.MoveStart(wdCharacter, Match.FirstIndex + Len(Match.Value) + 1)
        Loop
    End If

End Sub</pre>
<p>However, let&#8217;s look at some of the individual components in more detail</p>
<pre>Set regEx = CreateObject("VBScript.RegExp")</pre>
<p>The most portable way to include regular expressions in your macro code is to use the VBScript object &#8211; It&#8217;s just been around longer than VBA support for regex.</p>
<pre>regEx.Global = False</pre>
<p>Every example for using regex in VBA or VBScript will show you <code>Global = True</code>, even the MSDN library. <code>Global = False</code> finds the first match only. Otherwise it performs the same. <code>Execute()</code> still returns an array of matches even though there&#8217;s only one element.</p>
<pre>length = CLng(NumberInsert.NumberLength.Value)
' How many digits to show, retrieved from the form and converted to Long

tStr = StrConv(Format(i, String(length, "0")), vbWide, 1041)
' Convert the characters to full width Japanese characters</pre>
<p><code>length</code> is just the number of digits to show and in the <code>tStr</code> assignment I use <code>String(length, "0")</code> to generate the format string needed, for example if the user wanted to show a number 4 digits long, then the format string produced would be <code>"0000"</code>.</p>
<p><code>tStr</code> also uses the <code>StrConv()</code> function, which takes up to three parameters. The first parameter is the string to convert, the second is the format to convert to, in this case I used <code>vbWide</code>, but other formats include <code>vbLower</code>, <code>vbUpper</code>, <code>vbHiragana</code>, and others.  The best resource for this info is at <a href="http://msdn.microsoft.com/en-us/library/aa263373(VS.60).aspx">MSDN</a>. Amazingly, many pages omit over half of the formats available and never included better resources. The third parameter is the Locale ID. In this case, I used 1041, the Japanese locale, because I was converting to full-width and needed a far-east locale to do so.  This table at <a href="http://msdn.microsoft.com/en-us/library/0h88fahh.aspx">MSDN</a> has the best info on locale IDs.</p>
<pre>' add the index for the range start and the match index which only considers
' its position within the range
realIndex = objWdRange.Start + Match.FirstIndex</pre>
<p>As the start point of our range can, and does, change, we need to get the index in the document, not just the index within the range. Simply summing the two values does the job.</p>
<pre>temp = objWdRange.MoveStart(wdCharacter, Match.FirstIndex + Len(Match.Value) + 1)
	' move the start point on our range by the length
	' of our replacement plus the position within the
	' range, plus one</pre>
<p>Finally, we move the start of the range. <code>MoveStart</code> takes two parameters. The first is the unit to change by. In this case characters (<code>wdCharacter</code>), but you could use <code>wdWord</code>, <code>wdLine</code>, etc. The second parameter is the amount to move by, NOT the position to move to.</p>
<p>Why do a non-global regex match? Why move the start of our range? Well, whenever we insert replacement text that is longer or shorter than the original, the indexes stored in the match array are no longer valid as those character have moved since we executed the regex. Therefore, we need to get one at a time. As to the second question, we want to begin our next step of the search only on the text after our previous match, otherwise the replaced value could also match and we&#8217;d end up with an infinite loop continually replacing the first match. And that&#8217;s a bad thing, trust me.</p>
<p>The other components of the above are pretty standard and,  if you have a passing familiarity with regex and macros, are hopefully clear from the comments.</p>
<p>The second part of this system is the form. The best way is to import the form file into your word app and have a look, but here&#8217;s a screen shot of the form with values filled in.</p>
<div id="attachment_96" class="wp-caption aligncenter" style="width: 347px"><a href="http://ashita.org/wp-content/uploads/2008/08/sequencess.png"><img class="size-full wp-image-96" title="sequencess" src="http://ashita.org/wp-content/uploads/2008/08/sequencess.png" alt="NumberInsert.frm Example" width="337" height="336" /></a><p class="wp-caption-text">NumberInsert.frm Example</p></div>
<p>The code behind it is VERY simple and doesn&#8217;t do any validation on the fields. For something more professional you&#8217;d definitely want to add some checks on the inputs.</p>
<pre>Private Sub CancelBtn_Click()
    Me.Tag = False
    Me.Hide
End Sub

Private Sub NumberLength_Change()
    NumberLengthSpin.Value = NumberLength.Value
    MakeSample
End Sub

Private Sub OKBtn_Click()
    Me.Tag = True
    Me.Hide
End Sub

Private Sub ReplaceEnd_Change()
    MakeSample
End Sub

Private Sub ReplaceStart_Change()
    MakeSample
End Sub

Private Sub MakeSample()
    SampleText.Caption = ReplaceStart.Value &amp; Format("15", String(CLng(NumberLength.Value), "0")) &amp; ReplaceEnd.Value

End Sub

Private Sub NumberLengthSpin_SpinUp()
    NumberLength.Value = NumberLengthSpin.Value
    MakeSample
End Sub

Private Sub NumberLengthSpin_SpinDown()
    NumberLength.Value = NumberLengthSpin.Value
    MakeSample
End Sub</pre>
<p>There&#8217;s not too much to say about this other than making sure your spin buttons and numerical field update eachother when they&#8217;re changed. Me.Tag is used to pass True or False so Sequence() knows if the user canceled (False) or not (True). Finally MakeSample is called from any procedure which can change the value of the sample shown&#8230; so almost all of them. Again, adding some checks here   could help. For example, you could check to make sure the user has the right number of ( and ) and that they aren&#8217;t nested in any way, and check that the number of stored values called for in the replacement is also equal to the number of (, ) pairs.</p>
<p>Hopefully this helps the next poor soul who goes looking through web pages in the hopes of doing something like this.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.ashita.org/word-macro-to-insert-sequence-of-numbers/feed/</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
	</channel>
</rss>

