Tutorial: Generate complex documents from Enterprise Architect with a two-step semi-automated approach
- Step 1: Create the virtual document with a script
- Step 2: Generate the document from the virtual document
Document generation is important in most organisations that work with Enterprise Architect. Storing information in the model is good, but most stakeholders still require documents.
Documents are using as a tool for validation, as a work order, or just to give you an overview of a specific set of thing in the model. The main advantages of a document versus a model is that it has a limited scope and a linear order. You can read a document from start to finish, whereas you cannot do that with a model. Documents are, and will always be an important tool in developing applications.
Enterprise Architect comes with a sophisticated document generator that has seen great improvements in the last versions (v10, v11, v12). You also get a standard set of document templates that will quickly generate a document for a part of your model.
The problems arise however the moment you try to do something a bit more sophisticated with the document templates. Get information from different packages, use different templates for the same (set of) elements, etc… You will quickly realize that the approach of having one large template that generates the whole document is not useful for real-world document requirements.
Luckily EA has an alternative to the single template approach: Virtual Documents
Free Download
I’ve put together an example model and an MDG to demonstrate this approach. The MDG contains all scripts, templates and SQL Searches to generate a decent Use Case Document.
After you open the Hotellio.eap file, right click on the Reservations use case diagram and choose Create Use Case Document
Then select the package to store the virtual document in and fill in the version.
Finally select the «master document» package and press F8 so start the document generation. Make sure to select the correct cover page and style sheet
Virtual Documents
Virtual Documents are used to define the contents of a document as a set of individual sections, each with its own source and template.
There are two types of elements that are used for virtual documents
- Master Document
Represents the document to be printed. You can give it some parameters that can be used in the document itself such as title, Version, Status, etc… - Model Document
Represents a section in your document. The model document gets an RTFTemplate, and you have to define a source for this section. The source tell us which element(s) from the model should be used in this section. There are two ways to define the source. You can either drag one or more packages onto it, or you can fill in the Model Search and Search Term.
Master Documents get stored in the model as stereotyped packages and they contain stereotypes classes to represent the Model Documents
In the project browser this looks like this.
The Master Documents get the stereotype «master document» They contain tagged values to store things like ReportAlias, ReportTitle. Event though you can assign an RTF template to the master document this seems to be pointless since it gets ignored when printing a document.
Model Documents get the stereotype «model document». The links to the packages that serve as the source for a model document are created by dragging the package from the project browser onto the model document. This creates an attribute that maintains the link to the package.
Tagged values are used to indicate the template to use, the model search to use, and the search value to use on that search.
This mechanism of model documents allows you to very precisely control what will be printed in the document.
Templates
RTF templates are stored in the Resources section of your model. In order to see and manage them enable the resources view using View|Resources
Templates for document that have a package as source can start from the package tag and access all element, diagram etc.. in that package.
If your source is an SQL search that returns elements your template content can be placed in the element section
SQL Search
The SQL search I use to select a single element based on its GUID this following:
select c.ea_guid AS CLASSGUID,c.object_type AS CLASSTYPE,c.name AS Name, c.stereotype AS Stereotype, package.name AS PackageName ,package_p1.name AS PackageLevel1,package_p2.name AS PackageLevel2,package_p3.name AS PackageLevel3 from ((((t_object c inner join t_package package on c.package_id = package.package_id) left join t_package package_p1 on package_p1.package_id = package.parent_id) left join t_package package_p2 on package_p2.package_id = package_p1.parent_id) left join t_package package_p3 on package_p3.package_id = package_p2.parent_id) where c.ea_guid like '<Search Term>'
Printing
When you select the Master Document you can open up the document generation dialog by pressing F8
Here you can set the cover page, table of contents, and the style sheet to use.
Side note: Invest in the style sheet. It will save you time and frustration afterwards. It is difficult, and awkward to set styles using the RTF editor, but if you do it once in the stylesheet you don’t have to to it over and over again in each template. Also make sure to create your own “normal” style and call it “my normal” or something like that. Use this instead of the regular “normal”. There seems to be some weird behavior associated by the “normal” style, so I learned it’s best not to use it at all.
Then press generate, and the document generator will create one big document containing
- The cover page
- The table of contents page
- All model documents in the order they appear in the project browser.
If you are not happy with the content of the document, or the order of the model documents then you can change them in EA and re-print the document.
One of the advantages is that the content of the document is stored in EA. So after a revision of the model you can print exactly the same content again and compare it with the previous version.
Automatically generate virtual documents
Using virtual documents like that is all nice and good, but it still requires quite a lot of work to assemble the virtual document itself. For a somewhat realistic document you easily end up with tens of model documents, all pointing to different element or packages, and different template.
And you have to repeat that process for all the different documents you want to generate.
Doing the same thing over and over again is exactly what computers are build for. Instead of having to assemble the virtual document manually we will write a little script to do it for us.
The first thing well need to do is to make the Master Document package. This VBScript function does that for us.
function addMasterDocument(packageGUID, documentName,documentVersion,documentAlias) dim ownerPackage as EA.Package set ownerPackage = Repository.GetPackageByGuid(packageGUID) dim masterDocumentPackage as EA.Package set masterDocumentPackage = ownerPackage.Packages.AddNew(documentName, "package") masterDocumentPackage.Update masterDocumentPackage.Element.Stereotype = "master document" masterDocumentPackage.Alias = documentAlias masterDocumentPackage.Version = documentVersion masterDocumentPackage.Update 'link to the master template dim templateTag as EA.TaggedValue for each templateTag in masterDocumentPackage.Element.TaggedValues if templateTag.Name = "RTFTemplate" then templateTag.Value = "(model document: master template)" templateTag.Notes = "Default: (model document: master template)" templateTag.Update exit for end if next 'return set addMasterDocument= masterDocumentPackage end function
Then we need to make the Model Documents.To make one with a Package as source we use this function
function addModelDocumentForPackage(masterDocument,package,name, treepos, template) dim modelDocElement as EA.Element set modelDocElement = masterDocument.Elements.AddNew(name, "Class") 'set the position modelDocElement.TreePos = treepos modelDocElement.StereotypeEx = "model document" modelDocElement.Update 'add tagged values dim templateTag as EA.TaggedValue for each templateTag in modelDocElement.TaggedValues if templateTag.Name = "RTFTemplate" then templateTag.Value = template templateTag.Notes = "Default: Model Report" templateTag.Update exit for end if next 'add attribute dim attribute as EA.Attribute set attribute = modelDocElement.Attributes.AddNew(package.Name, "Package") attribute.ClassifierID = package.Element.ElementID attribute.Update end function
To make one with an SQL Search as source we use this function
function addModelDocumentWithSearch(masterDocument, template,elementName, elementGUID, treepos, searchName) dim modelDocElement as EA.Element; set modelDocElement = masterDocument.Elements.AddNew(elementName, "Class") 'set the position modelDocElement.TreePos = treepos modelDocElement.StereotypeEx = "model document" modelDocElement.Update dim templateTag as EA.TaggedValue if len(elementGUID) > 0 then for each templateTag in modelDocElement.TaggedValues if templateTag.Name = "RTFTemplate" then templateTag.Value = template templateTag.Notes = "Default: Model Report" templateTag.Update elseif templateTag.Name = "SearchName" then templateTag.Value = searchName templateTag.Update elseif templateTag.Name = "SearchValue" then templateTag.Value = elementGUID templateTag.Update end if next else 'add tagged values for each templateTag in modelDocElement.TaggedValues if templateTag.Name = "RTFTemplate" then templateTag.Value = template templateTag.Notes = "Default: Model Report" templateTag.Update exit for end if next 'no GUID provided. Set masterdocument package ID as dummy attribute to make the template work dim attribute as EA.Attribute set attribute = modelDocElement.Attributes.AddNew(masterDocument.Name, "Package") attribute.ClassifierID = masterDocument.Element.ElementID attribute.Update end if end function
The the last script we is one to actually use these functions in order to create the virtual document. In the example provided I’ve split the script in two parts. One part that remains in the library, and one script that is put in the Diagram Group so we can call it from a diagram directly.
The script in the Diagram Group takes care of getting the user input
option explicit !INC Bellekens DocGen.UseCaseDocument ' Script Name: Create Use Case Document ' Author: Geert Bellekens ' Purpose: Create the virtual document for a Use Case Document based on the open diagram ' Copy this script in a Diagram Group to call it from the diagram directly. ' Date: 11/11/2015 ' sub OnDiagramScript() dim documentsPackage as EA.Package 'select the package to generate the virtual document in Msgbox "Please select the package to generate the virtual document in",vbOKOnly+vbQuestion,"Document Package" set documentsPackage = selectPackage() if not documentsPackage is nothing then ' Get a reference to the current diagram dim currentDiagram as EA.Diagram set currentDiagram = Repository.GetCurrentDiagram() if not currentDiagram is nothing then createUseCaseDocument currentDiagram, documentsPackage.PackageGUID Msgbox "Select the Master Document and press F8 to generate document",vbOKOnly+vbInformation,"Finished!" else Session.Prompt "This script requires a diagram to be visible", promptOK end if end if end sub OnDiagramScript
The script in the library will use the functions mentioned above to start building the virtual document
!INC Local Scripts.EAConstants-VBScript !INC Bellekens DocGen.DocGenHelpers !INC Bellekens DocGen.Util ' ' Script Name: UseCaseDocuemnt ' Author: Geert Bellekens ' Purpose: Create the virtual document for a Use Case Document based on the given diagram ' Date: 11/11/2015 ' dim useCaseDocumentsPackageGUID function createUseCaseDocument( diagram, documentsPackageGUID) useCaseDocumentsPackageGUID = documentsPackageGUID 'first create a master document dim masterDocument as EA.Package set masterDocument = makeUseCaseMasterDocument(diagram) if not masterDocument is nothing then dim i i = 0 'use case diagram part 1 addModelDocumentForDiagram masterDocument,diagram, i, "UCD_Use Case Diagram" i = i + 1 'add Actors dim diagramPackage as EA.Package set diagramPackage = Repository.GetPackageByID(diagram.PackageID) addModelDocumentForPackage masterDocument, diagramPackage, diagram.Name & " Actors", i, "UCD_Actors" i = i + 1 ' We only want to report the use cases that are shown within the scope boundary on this diagram 'get the boundary diagram object in the diagram dim boundaries set boundaries = getDiagramObjects(diagram,"Boundary") Session.Output boundaries.Count 'get the use cases dim usecases if boundaries.Count > 0 then set usecases = getElementsFromDiagramInBoundary(diagram, "UseCase",boundaries(0)) Session.Output "boundary found" else set usecases = getElementsFromDiagram(diagram, "UseCase") end if 'sort use cases alphabetically set usecases = sortElementsByName(usecases) 'add the use cases i = addUseCases(masterDocument, usecases, i) Repository.RefreshModelView(masterDocument.PackageID) 'select the created master document in the project browser Repository.ShowInProjectView(masterDocument) end if end function function makeUseCaseMasterDocument(currentDiagram) 'we should ask the user for a version dim documentTitle dim documentVersion dim documentName dim diagramName set makeUseCaseMasterDocument = nothing diagramName = currentDiagram.Name 'to make sure document version is filled in documentVersion = "" documentVersion = InputBox("Please enter the version of this document", "Document version", "x.y.z" ) if documentVersion <> "" then 'OK, we have a version, continue documentName = "UCD - " & diagramName & " v. " & documentVersion dim masterDocument as EA.Package set masterDocument = addMasterDocumentWithDetails(useCaseDocumentsPackageGUID, documentName,documentVersion,diagramName) set makeUseCaseMasterDocument = masterDocument end if end function 'add the use cases to the document function addUseCases(masterDocument, usecases, i) dim usecase as EA.Element for each usecase in usecases 'use case part 1 addModelDocument masterDocument, "UCD_Use Case details part1", "UC " & usecase.Name & " Part 1", usecase.ElementGUID, i i = i + 1 'get the nested scenario diagram dim activity as EA.Element set activity = getActivityForUsecase(usecase) 'add scenario diagram if not activity is nothing then addModelDocument masterDocument, "UCD_Use Case Scenarios Diagram", "UC " & usecase.Name & " Scenarios diagram", activity.ElementGUID, i i = i + 1 end if 'use case part 2 addModelDocument masterDocument, "UCD_Use Case details part2","UC " & usecase.Name & " Part 2", usecase.ElementGUID, i i = i + 1 next 'return the new i addUseCases = i end function function getActivityForUsecase(usecase) set getActivityForUsecase = getNestedDiagramOnwerForElement(usecase, "Activity") end function function getInteractionForUseCase(usecase) set getInteractionForUseCase = getNestedDiagramOnwerForElement(usecase, "Interaction") end function function getNestedDiagramOnwerForElement(element, elementType) dim diagramOnwer as EA.Element set diagramOnwer = nothing dim nestedElement as EA.Element for each nestedElement in element.Elements if nestedElement.Type = elementType and nestedElement.Diagrams.Count > 0 then set diagramOnwer = nestedElement exit for end if next set getNestedDiagramOnwerForElement = diagramOnwer end function 'sort the elements in the given ArrayList of EA.Elements by their name function sortElementsByName (elements) dim i dim goAgain goAgain = false dim thisElement as EA.Element dim nextElement as EA.Element for i = 0 to elements.Count -2 step 1 set thisElement = elements(i) set nextElement = elements(i +1) if elementIsAfter(thisElement, nextElement) then elements.RemoveAt(i +1) elements.Insert i, nextElement goAgain = true end if next 'if we had to swap an element then we go over the list again if goAgain then set elements = sortElementsByName (elements) end if 'return the sorted list set sortElementsByName = elements end function 'check if the name of the next element is bigger then the name of the first element function elementIsAfter (thisElement, nextElement) dim compareResult compareResult = StrComp(thisElement.Name, nextElement.Name,1) if compareResult > 0 then elementIsAfter = True else elementIsAfter = False end if end function
Great work! As per fragment SQL, I wish the syntax were the same as for SQL queries. I usually try to test my searches using the SQL search facility, and I often forget to change “” or #PACKAGE#
to #OBJECTID#, and my query fails during doc generation.
I do like Geert’s stuff, they are not only handy, especially when you in a hurry (and when ain’t you in a hurry?), but give valuable insights into EA workings, so that you can develop your own tools. This one attempt to improve the most catastrophic feature of EA, complex document generation will become another important part of the package he shares with us, and will server as a basis for many of my own development, I’m pretty sure.
Thanks, I’m happy you like it. I developed this approach for a customer that had very specific demands for their documents, which I couldn’t meet any other way. The example shown here is a strongly simplified version of the actual thing.
I just can’t wait the day when Sparx implements a document generator based on a new approach. I mean an XML-like things, even if we need to write declarative code, where expressions and branches can be used in templates, and where formatting works as expected. I had to develop a huge Word macro which I run on each generated document because of the so many formatting problems and inconsistencies generated docs have.
The thing that annoys me the most is the fact that a diagram is always re-scaled to fit exactly on a page; which means that my caption ALWAYS ends up on the next page, so we have to manually go over the whole document and make the diagrams a little bit smaller so the caption stays on the same page. I’ve sent in a feature request asking for some way to get that fixed. Other then that I’m reasonably happy with the current approach. Sure, there are still a lot of things that can be improved, but compared to the docuemnt generator from v 6.5 we have come a long way.
Yep, smart image resizing is one of the features my mega-macro performs for the very same reason. 🙂 I also hate that it’s virtually random whether content pieces will be formatted with the style applied to them in templates. A very common thing is that bulletpoints disappear from list items, usually not ahead of all, but of some. Making line breaks, such in template fragments, is just another random thing. I have introduced a couple of #formattingTags# I use in my document templates, which then I process in my macro and create the required formatting. For example #firstItem#some ea-stuff#lastItem# — and if #firstitem# is immediately followed by #lastitem# in the generated doc, then I know the list was empty, and then I may delete the whole line, or include some boilerplate text like “N/A”. Or I delete “,#lastitem#” to not have a comma at the end of inline lists. However it is an error-prone and time consuming process, and I hope one day Sparx will offer such features like handling empty paragraphs and lists and so on.
Thanks a lot for the amazing tutorial.
I would like to try it, but the “free download” text is not a link..
Thanks in advance!
Try this link: https://bellekens.com/free-downloads/#!/Bellekens-DocGen-MDG-and-example-model/p/56803587/category=12516650
Thanks,
Still see “Free Downloads” not as a link.
Something wrong on my machine?
It’s not supposed to be a link. But underneath there should the details of the download from the webshop component. That section is generated by a bunch of javascript code, so I guess it’s possible something on your side is blocking it from showing up.
Thank you Mr. Bellekens,
EA can be a bit ambitious for someone New to business process modeling with BPMN 2.0. this documentation tool will help save me some time.
I’ve walked through your code several times, as given in your example model and MDG, and I’m confused about how a template grabs only specific elements from the diagram. I want to understand this process so I can implement this level of specificty in my own reports.
Specifically, I’m looking at the UCD_Actors template. The {Element.Name} only grabs the name of the actors present in the diagram. In the script, I think I can see how you’re using the current diagram as a source for the template, and how the template is linked to the model document “«model document» Reservations Actors”, but I don’t understand how you’re specifically getting only the actors. Does it have to do with how you define the attribute for the model document? Perhaps I’m not understanding something about the use of sections in the document editor?
Hi Dana,
The reason the UCD_Actors only reports actors is because of the Exclude Filter in the template properties.
I exclude everything except for Actors. Without the filter it would report every element shown on the diagram.
Geert
Hi Geert,
Thanks for the post. I am looking to create a document that contains all the elements that contain tests from within my model. It should also display parent elements that may or may not contain tests. I created a model search and saved it:
SELECT
t_object.ea_guid AS CLASSGUID,
t_object.Object_Type AS CLASSTYPE
FROM
t_object LEFT OUTER JOIN
t_object t_object_o1 ON t_object.[Object_ID] = t_object_o1.ParentID LEFT OUTER JOIN
t_object t_object_o2 ON t_object_o1.[Object_ID] = t_object_o2.ParentID INNER JOIN
t_objecttests ON t_object.[Object_ID] = t_objecttests.[Object_ID] OR t_object_o1.[Object_ID] = t_objecttests.[Object_ID] OR t_object_o2.[Object_ID] = t_objecttests.[Object_ID]
WHERE
t_object.package_id = #Package#
GROUP BY
t_object.[Object_ID],
t_object.ea_guid,
t_object.Object_Type
Inside my RTF template I then tried to import the search in document options -> element filters -> import search -> from EA but there was nothing listed in the dialog box. Instead i tried to export the search and then do document options -> element filters -> import search -> from file but EA crashed.
Are you able to advise?
Thanks
I’ve never used that import feature before, so I couldn’t comment on whether or not it is supposed to work.
Usually I either use SQL searches in the custom query part of SQL fragments, or in the tagged value of the model document.
Hi Geert,
Is it possible to generate a virtual document based on diagram element [SQL order diagram element by position], and get sordet element embedded document?
Thanks 4 advice
Yes you can, it would take a bit of scripting, but I’ve done things like that in the past.
Just be careful that the order you define based on the diagram positions is actually the only, or most logical order.
Very often you cannot reliably determine a 1 dimensional order based on 2-dimensional coordinates.
If there is any doubt at all I would go back to alphabetical ordering to avoid confused readers.
Geert
Thank you. I’ll try something 🙂
Hi Geert,
Great article – Many thanks. One quick question. What’s your advise on the way to structure your “Documentation” components for efficient and easy report generation. I’ve read about various ways, but each seems to come with its drawbacks. Essentially, I have a package structure similar to the following:
0-Project Name(L0)
0-1-DataArchitecture(L1)
0-1-1- Entities(L2)
0-1-2- Diagrams(L2)
0-2-IntegrationArchitecture(L1)
0-2-1- Components
0-2-2- Diagrams
Just a note, the structure above is a simplified version of my structure – I just want to understand the concept though. Let’s say I need to create a Solutions Architecture Report consisting of Data and Integration Architecture. These are the options I have.
1. Create a MasterDocument(Report Package) with a single Model Document which will be at Root Level (L0). The challenge here is dealing with all the nesting of packages to actually select the information I need – using multiple fragments.
2. Create a MasterDocument(Report Package) with two Model Documents which occur at Level 1 (L1). Each can be mapped to its own template. The challenge here is getting the numbering in the report correct as you are spanning across two templates.
Your thoughts and advise will be much appreciated.
Anuj
Hi Anuj
That is really hard to say without know the document requirements.
There is no general rule.
If you are looking for a document that simply reflects your model structure and reports on the contents you might not even need to use virtual documents.
Guidelines you can use are:
– keep the templates as simple as possible
– keep the number of model documents as low as possible.
Geert
HI Geert,
I try your approach to the generation of documents in EA (version 13.5.1351) and get an error after selecting the menu item “Create Use Case Document”.
—————————
Error
—————————
Bellekens DocGen.Util
, Line: 280
—————————
OK
—————————
What can be done? Any ideas.
Hi Dmitry,
That’s weird. When I look at line 280 in that script in my repository I only see a comment line.
That can hardly be the source of an error I guess.
What does that line read in your model?
Geert
hi, Greet
thanks in advance,
i’d like to know how to separate the diagrams if the one package have ore than one diagrams, as i know, the template will add it to document one by one, not separate.
Thx
Hi Ivana,
The only option you have then is to add a filter in the template properties.
You can filter it on for example diagram type.
If there are multiple diagrams in a single package, and you have nothing to set them apart then it will be difficult.
Geert
Hi. I know this is probably “ancient history” now, but I am still using EA 12.1, and I tried to follow this procedure.
I downloaded and opened your example model. When I right-click the use case diagram and select Scripts and then Create Use Case Document, nothing happens.
Is there some setting I need to make this work?
No, there should be no special settings. I’m not sure why this happens.
You can try to execute the script directly from the scripting library, to see if EA comes up with an error message of some kind.