Using Tags to Store Custom Information in Publisher 2003 and Publisher 2002
Microsoft Office Publisher 2003
Microsoft Publisher 2002
Summary: You can use the Tags collection to persist a wide range of custom information within your publication, at the publication, page, or shape level. View an in-depth example of using tags to store data source information in a catalog merge publication. (16 printed pages)
What Are Publisher Tags?
Adding, Editing, and Deleting Custom Tags
Catalog Merge Scenarios Using Publication, Page, and Shape Tags
Publisher Tags Object Model Hierarchy
Using the various members of the Publisher object model, you can set and persist many different kinds of information in a publication. But sometimes there's custom information you want to save with the publication that isn't represented by any method or property in the Publisher object model. For those cases, the Tags collection enables you to persist custom information in a publication at the publication, page, or shape level.
In this article we'll explain what tags are and how to use them. We'll show you a few simple examples of tags in use, and then walk you through a more complex example that uses tags to store merge information at the publication, page, and shape level.
Tags are, in essence, generic storage bins where you can place whatever custom information, or metadata, you want to persist with the publication. The Tags collection is a child of the Document, Page, ShapeRange, and Shape objects, so you can save data at the publication, page, or individual shape level.
Each Tag object contains two main properties: a name, and a value. The Name property is simply a string that identifies the tag. The Value property of a Tag object is what makes tags so flexible and useful. The Value property takes a Variant data type, which means it can store any data type except fixed-length strings. This means you can store a wide range of information types in tags, including user-defined types. For more information about the Variant data type, see Variant Data Type.
Another interesting aspect of tags is that the Tags collection is one of the rare pieces of the Publisher object model that doesn't have a counterpart in the application's user interface. Tags are a totally programmatic feature: you can only access Tag objects through code. Unless you explicitly create an interface to expose them, such as by displaying them in a dialog box, or the user writes code to find them, end users aren't going to be aware of them at all. Which makes them an ideal place to store information you need to persist to perform programmatic tasks, but that you don't want the average, that is, non-programming, user to have easy access to.
Tags are particularly useful for storing the following general types of information:
- Publication, page, or shape attribute information that isn't already persisted in any of the available object model properties
- Variable data that is needed for running some procedure or programmatic functionality
To add a Tag object, use the Tags.Add method, which takes the Tag object name and value as parameters. The following line of code adds a Tag object named CreationDate, with a value of the current date, to the active document:
ActiveDocument.Tags.Add "CreationDate", CStr(Now)
Each Tag object has a Name and Value property. The Name property is read-only and must be unique within a given Tags collection. For example, you can't have two Tag objects named "Creation Date" in a single Shape object, but you can have multiple Shape objects that each have a single Tag object named that.
As mentioned earlier, the Value property takes a Variant data type, which means it can store any data type except fixed-length strings.
ActiveDocument.Tags("Creation Date").Value = "Today"
To remove a Tag object, simply call its Delete method:
As you can tell, adding tags and setting their values is fairly simple. So the question isn't really how to use tags, but what to use them for. What information should you store in tags? For the most part, your business processes determine the answer to that. What information do you need to persist in a publication to complete your business tasks?
Let's take a look at a few simple examples to get you thinking about how tags can work for you.
Suppose you create a catalog of your products, including prices that are subject to change over time. You print the catalog on a desktop printer. However, you want to be sure your employees don't print the catalog for a customer once it's more than three months old. So when you finish creating the catalog, you add a document tag, called Expiration Date, and set its value to a date 90 days in the future:
ActiveDocument.Tags.Add "ExpirationDate", CStr(Now + 90)
Then, you write an event handler for opening the document. The event handler compares the value of the Expiration Date tag with the current date. If the publication is opened after the expiration date, a dialog box displays advising the user not to print the catalog because the prices may not be accurate anymore.
Note For this code to work, place this event handler in the ThisDocument object module for the publication.
Private Sub Document_Open() If CDate(ActiveDocument.Tags("ExpirationDate").Value) < Now Then MsgBox Prompt:="Prices in this catalog may be out of date." & _ vbLf & "Do not release this catalog to a customer" & vbLf & _ "without checking that prices are accurate.", _ Buttons:=vbExclamation, _ Title:="Expiration Date Passed" End If End Sub
Or perhaps you want to classify the publication you create. Suppose you're creating publications for a number of projects. You might want to add a Tag object to each publication to identify which project it's for. So you add a Tag object, named for the project, that has an initial value of "InProcess":
ActiveDocument.Tags.Add "Farble Project", "InProcess"
When the project is done, you change the value to "Complete". Then, you write a procedure that goes through your various work folders once a week and moves any publications where the tag value equals Complete to a "Completed Projects" folder.
Using the FindShapesByTag Method
The Document.FindShapesByTag method enables you to identify all shapes in a publication that contain a specific Tag object. The method returns a ShapeRange collection comprised of the shapes from all publication pages that contain a specific Tag object. This includes shapes contained in grouped shapes, as well as shapes on master pages.
The Publisher object model doesn't currently contain any methods to return Document or Page objects that contain specific Tag objects.
For example, suppose you create a book, and each chapter is a separate publication. Within each chapter you use text box shapes for a number of different functions: the main chapter text, but also side-bar stories, pull quotes, figure captions, and so on. Programmatically, there's no way to tell the function of each text box shape. But you can use tags to do exactly that. When you create each publication, add a Tag object to each text box that denotes what you're using it for. For example:
Activedocument.Selection.ShapeRange(1).Tags.Add "StoryType", "PullQuote"
Note The previous code example assigns the tag to the first shape in the ShapeRange you currently have selected in the active document. Obviously, if you only have one shape selected, then that's shape one.
Later, you decide that you want to change the font you used for all the pull quotes, and increase the font's point size while you're at it. You can quickly create a procedure that identifies all the text boxes containing pull quotes, based on the value of their StoryType tag, and makes the appropriate changes.
Note that the Documents collections contains each publication that is currently open on your computer. The following example assumes that you open each publication you want to affect.
Sub FormatPullQoutes() Dim d As Document Dim sr As ShapeRange Dim s As Shape For Each d In Documents Set sr = d.FindShapesByTag("StoryType") For Each s In sr If s.Tags("StoryType").Value = "PullQuote" Then s.TextFrame.TextRange.Font.Name = "Arial" s.TextFrame.TextRange.Font.Size = 20 End If Next s Next d End Sub
Now, let's examine a few more complex examples that use the Publisher catalog merge functionality.
By design, when you perform a catalog merge, the publication pages you create contain no information about the catalog merge publication or data source used to create them. This is true whether you create a new publication, or append the catalog merge results to an existing publication. The publication has no "memory" of being created by the catalog merge process at all. And there are several good reasons, including security issues, why Publisher does not store information about the publication or data source used to perform the merge.
But what if there are instances where you want to persist this information in the catalog publication? The next two code examples use tags to store this information; the first scenario involves merging repeatedly to an existing publication, while the second involves creating a catalog publication.
Note The following examples assume you are familiar with the catalog merge functionality in Publisher 2003. For more information on programmatically executing catalog merges, see Creating Catalogs with Microsoft Office Publisher 2003.
Scenario: Using Publisher Tags to Track Multiple Catalog Merges into a Single Document
Suppose you want to build a single publication as a catalog, and you use several different catalog merge areas and/or data sources. Perhaps each section of your catalog uses a different data source, or each section has a different layout and so uses a different catalog merge area. Because of the complexity of the merge, you want to know which pages in the publication where created with which data sources and publications, and when they were created.
The following code example shows how you could use page-level tags to store this information.
First, the example opens the specified publication and records how many pages are in the publication prior to executing the merge. Then the code executes the merge, appending the resulting pages to the specified publication. Next, the code adds tags to each new page created by the merge. These tags contain the following information:
- The data source used in the merge
- The publication that contains the catalog merge area used in the merge
- Date and time of the merge
The code then creates or increments a document-level tag called "Number Of Merges," which contains the number of merge results that are appended to the specified publication. Finally, the code creates another document-level tag to contain the number of records processed in the current catalog merge.
This example assumes you have created a catalog merge, and written a procedure that calls this function.
Note This example stores information, including the file path and name of the publication and data source used to perform the merge, in the specified publication. Depending on your business processes, storing this information in another publication may present a security concern.
Function ExecuteMergeAndTagPages(filePath As String) Dim d As Document Dim p As Page Dim intPages As Integer Dim i As Integer Dim t As Tag Dim TagExists As Boolean TagExists = False 'Open the publication 'and record current number of pages Set d = Application.Open(filePath) intPages = d.Pages.Count 'Close publication without saving d.Application.Quit 'Append the merge to the specified publication Set d = ActiveDocument.MailMerge.Execute(Pause:=False, _ Destination:=pbMergeToExistingPublication, _ Filename:=filePath) 'Add page-specific tags 'containing merge data information 'to each new page created by the merge For i = (intPages + 1) To d.Pages.Count Set p = d.Pages(i) With p.Tags .Add "Data Source", ActiveDocument.MailMerge.DataSource.Name .Add "Merge Pub", ActiveDocument.FullName .Add "Page Creation Date", CStr(Now) End With Next i 'Find the Number of Merges tag 'if it already exists 'and increment it for this merge For Each t In d.Tags If t.Name = "Number of Merges" Then t.Value = t.Value + 1 TagExists = True Exit For End If Next t 'Create the Number of Merges tag 'if it does not alreay exist If TagExists = False Then d.Tags.Add Name:="Number of Merges", Value:=1 End If 'Create a tag that contains the number of records 'in this specific merge d.Tags.Add Name:="Merge " & d.Tags("Number of Merges").Value & _ " Record No", _ Value:=ActiveDocument.MailMerge.DataSource.RecordCount End Function
Once you store this information, of course, you can write code that programmatically queries the tags and takes action based on the results. You could write a procedure that deletes all the pages created using a certain data source, or created on a specified date.
Scenario: Using Publisher Tags to Store Data Record Information in Shapes
In this next scenario, we merge to a new publication, but we want to persist even more detailed information about the data source. For each shape, we want to know the record in the data source for which it contains information. We also want to know the data source fields for which it contains information, and the value of those data fields. That way, we can manipulate the shapes later based on the data source record for which they were generated, without having to refer back to or alter the data source itself, or regenerate the catalog publication.
Note Again, these examples store detailed information about the data source in the generated catalog publication. Depending on your business processes, this could present a security concern.
First, we need to add tags to the shapes in the catalog merge area that contain merge fields. When Publisher executes a catalog merge, for each record in the data source it copies each shape in the catalog merge area onto the new publication page, replacing the data fields in each shape with the actual record field data.
Figure 1. Example of a catalog merge
1. Data source with record information, such as item names and descriptions
2. Catalog merge template with catalog merge area containing merge fields that are placeholders for record information
3. Resulting merged pages displaying multiple records per page
Publisher copies the shapes in the catalog merge area, including any tags contained in those shapes. So, our first step is to add a Tag object to each shape that contains a data field. The Tag object is named the same as the data field that the shape contains. Its initial value is not important; we overwrite it later.
The following code does just that. It examines each text box shape in the catalog merge area. If the text box contains a mail merge field, the code adds a Tag object, named the same as the mail merge field, to that shape. Publisher names mail merge fields after the data source fields they represent; therefore, the code actually names the Tag object the same as the data source field. If a text box contains multiple data fields, it gets assigned multiple tags, one for each data field.
This code only applies to text data fields. There is no way in the Publisher 2003 object model to identify whether a picture shape contains a picture data field.
The code then adds another tag, named Record, to each shape in the catalog merge area. The Record tag is assigned an initial value of "0."
Sub AddTagsToMergeShapes() Dim s As Shape Dim cs As Shape Dim f As Field Dim i As Integer 'Add tags for each mail merge field in the text frame Set s = ActiveDocument.Selection.ShapeRange(1) If s.Type = pbCatalogMergeArea Then For Each cs In s.CatalogMergeItems If (cs.Type = pbTextFrame) Then If cs.TextFrame.TextRange.Fields.Count > 0 Then With cs.TextFrame.TextRange.Fields 'Fields don't support For Each...Next looping 'so create a For i = 1 to ... loop instead For i = 1 To .Count 'Identifies mail merge fields If .Item(i).Type = pbFieldMailMerge Then 'Adds tag named after mail merge field name 'minus the bracketing carrots cs.Tags.Add Mid(.Item(i).TextRange, 2, _ Len(.Item(i).TextRange) - 2), _ 0 End If Next i End With End If End If 'Add a record number tag to each shape in the merge area 'whether or not it has a mail merge field cs.Tags.Add "Record", 0 Next cs 'Error logic if shape selected is not merge area Else MsgBox prompt:="This shape is not a catalog merge area", _ Title:="Incorrect Shape Type" End If End Sub
Now, we're ready to perform the catalog merge. The next code procedure executes the merge to a new publication, then assigns several tags to each page in the publication. These tags contain the following information:
- The file path and name of the data source used
- The table in the data source used
- The number of records included in the merge
- The publication that contains the catalog merge area used to generate the merge
- The data and time Publisher performed the merge
The code then calls two other procedures, which we'll examine in turn.
Sub TagMergePagesAndShapes() Dim d As Document Dim p As Page Set d = ActiveDocument.MailMerge.Execute(msoFalse, pbMergeToNewPublication) 'Add tags containing merge information 'to each page For Each p In d.Pages With p.Tags .Add "Data Source", ActiveDocument.MailMerge.DataSource.Name .Add "Data Source Table", ActiveDocument.MailMerge.DataSource.TableName .Add "Number of Records", ActiveDocument.MailMerge.DataSource.RecordCount .Add "Merge Pub", ActiveDocument.FullName .Add "Page Creation Date", CStr(Now) End With Next p 'Call sub to number tag shapes by record NumberRecordTags d, ActiveDocument 'Call sub to add values to the numbered tags AddValuesToTags d, ActiveDocument End Sub
The first of these functions numbers shapes containing tags sequentially throughout the publication. The code takes advantage of two facts:
- Publisher indexes Shape objects in the Shapes collection according to the order in which they were added to the page.
- When performing a merge, Publisher adds all the shapes for the first merge record, then all the shapes for the second, and so on throughout sequentially numbered pages.
Because of this, we can assume that the first shape in a publication with a tag named Item contains the Item data field value for the first record, and so on.
The code uses the FindShapesByTag method to identify all the shapes that contain a tag named for a specific data field (for example, Item). The code iterates through the resulting ShapeRange collection, sequentially numbering the value of each shape's Record tag. The first shape with an Item tag gets its Record tag value set to 1, the next shape to 2, and so on. The code does the same for tags named after each data field.
In essence, this code numbers shapes containing tags named after data fields sequentially. This enables us to determine relationships programmatically between the text boxes containing the tags. For example, the text box containing the Price tag where
Tags("Record").Value = 10 relates to the text box containing the Item tag where the Record tag value is 10 as well. We can tell which shapes contain data from which data record.
Function NumberRecordTags(d As Document, md As Document) Dim sr As ShapeRange Dim sh As Shape Dim t As Tag Dim i As Integer Dim ri As Integer With d 'For each data field in the merge data source For i = 1 To md.MailMerge.DataSource.DataFields.Count 'Find all shapes with a tag 'named the same as the specified data field Set sr = .FindShapesByTag(md.MailMerge.DataSource.DataFields.Item(i).Name) If Not sr Is Nothing Then ri = 1 For Each sh In sr For Each t In sh.Tags 'Find the corresponding Record tag 'in the shape 'and set its value If t.Name = "Record" Then t.Value = ri ri = ri + 1 Exit For End If Next t Next sh End If Next i End With End Function
We can go a step further. Now that we have the shapes containing the tags numbered sequentially, we can directly relate them to the records in the data source, which are also numbered sequentially. The following function sets the Value property of each tag named for a data field to the actual value in the corresponding data source record.
Again, the code uses the FindShapesByTag method to identify all the shapes that contain a tag named for a specific data field (for example, Item). Next, the code iterates through each record in the data source, setting each as the active record in turn.
Figure 2. Data source with selected active record
The code then finds the Shape object with the same index as the active record, and sets the tag value equal to the data field value of the active record. For example, for the first record, the code finds the first shape in the ShapeRange collection, and sets the value of its Item tag to the value of the Item field for that record. The code performs this for each record in the data source, with the end result that each Shape tag named for a data source now contains the actual value for the record.
Function AddValuesToTags(d As Document, md As Document) Dim sr As ShapeRange Dim sh As Shape Dim t As Tag Dim i As Integer Dim ri As Integer With d 'For each data field in the merge data source For i = 1 To md.MailMerge.DataSource.DataFields.Count 'Find all shapes with a tag 'named the same as the specified data field Set sr = .FindShapesByTag(md.MailMerge.DataSource.DataFields.Item(i).Name) If Not sr Is Nothing Then For ri = 1 To sr.Count 'Increment through each record in the data source md.MailMerge.DataSource.ActiveRecord = ri 'Set tag value equal to the data field value sr(ri).Tags(md.MailMerge.DataSource.DataFields.Item(i).Name).Value = _ md.MailMerge.DataSource.DataFields.Item(i).Value Next ri End If Next i End With End Function
The result is a publication that "remembers" the data source and publication used to create it. Page-level tags contain:
- The file and table name of the data source used.
- The publication containing the catalog merge area used.
- The data and time Publisher performed the merge.
While Shape-level tags contain:
- The record number of the data source record.
Note This number represents the record number among the records selected for the merge only. If you excluded records in the data source from the catalog merge, this number does not match the numbering in the original data source.
- The name of the data source field, and the field value for that particular record.
Now that this information persists within the catalog publication, we can do any number of things, without having to refer back to the data source, or regenerate the catalog publication if we want to make a change. For example, suppose we later decide to offer a ten percent discount on any item priced over $10. We could add an additional column for the discount price to the data source, and then regenerate the catalog. However, we may not have permission to alter the data source, and we may have further customized the catalog with changes that are lost if we regenerate it. Because of the information we store in the tags, we can accomplish the same thing without either altering the source files or losing our custom changes.
The following code finds all the text boxes that contain a Price tag. If the Price Tag object Value property is greater than 10, the code calculates a discounted price and inserts text listing that price into the shape.
Function DisplayDiscount(d As Document) Dim sr As ShapeRange Dim sh As Shape Dim dis As Single Dim txt As TextRange With d 'Find all shapes with a Price tag Set sr = .FindShapesByTag("Price") If Not sr Is Nothing Then For Each sh In sr 'If the Price is greater than ten dollars If sh.Tags("Price").Value > 10 Then 'Create a discount price dis = (sh.Tags("Price").Value) - ((sh.Tags("Price").Value) * 0.1) 'Add text to the shape 'that lists the discount price Set txt = sh.TextFrame.TextRange.InsertAfter _ (NewText:=vbLf & "Now only " & CStr(Round(dis, 2)) & "! Save 10%") 'Format discount price text With txt .Font.Color.RGB = 255 .Font.Bold = msoTrue End With End If Next sh End If End With End Function
Below is a diagram of the Publisher object model section pertaining to the Tags collection.
Figure 3. The Tags object model section of Publisher
Hopefully, you can see just how flexible and useful tags can be to track programmatic metadata. Whether it's attribute information that isn't already tracked in any available object model properties, or variable data that is needed for running some procedure or programmatic functionality, tags are an ideal storage mechanism for persisting the data you need for your business processes within your publications themselves.
This section lists resources you can use to learn more about the products and technologies mentioned or used in this article.