In this post I will show you how to easily work with Sparx Enterprise Architect Baseline information in C#.

Baseline processing in Enterprise Architect is exposed via the ProjectClass (EA.Repository.GetProjectInterface()). From there we have to use a two-step approach to get a baseline and its stored information:

  1. Invoke project.GetBaselines() with a PackageGuid to get the stored baselines for a sopecific package.
  2. Invoke project.DoBaselineCompare() with the previous PackageGuid and a BaselineGuid returned in the previous step.

Project.GetBaselines

Suppose we retrieve the baselines of a package (with Guid {50168942-9238-44a6-89F3-B624125BFBAE}) with the baselines as outlined in the figure below. After our call to project.GetBaselines("{50168942-9238-44a6-89F3-B624125BFBAE}", "") we get an XML similar to this:

<?xml version="1.0" encoding="UTF-8"?>
<EA.BaseLines package="{50168942-9238-44a6-89F3-B624125BFBAE}">
   <Baseline name="Requirements" version="1.0" notes="Baseline time: 09/08/2019 19:44:12" guid="{93F584F1-8B22-4eca-AB96-5FA1011975D4}" />
   <Baseline name="Requirements" version="2.0" notes="Baseline time: 09/08/2019 19:44:18" guid="{BD589BFA-5C09-4f2b-A029-F19D7F791341}" />
</EA.BaseLines>
PackageBaselines
PackageBaselines

As we can see every baselines has its own unique guid and a version attribute.

Project.DoBaselineCompare

If we now compare the model against the first baseline (with Guid {93F584F1-8B22-4eca-AB96-5FA1011975D4}) and call project.DoBaselineCompare("{50168942-9238-44a6-89F3-B624125BFBAE}", "{93F584F1-8B22-4eca-AB96-5FA1011975D4}", "") EA will return the follwing XML:

<?xml version="1.0" encoding="UTF-8"?>
<EA.CompareLog>
  <ComparePackage name="Requirements" guid="EAID_50168942_9238_44a6_89F3_B624125BFBAE" packageID="211" comparedBy="edgar" comparedOn="2019-08-09 19:50:13">
  <CompareResults hasChanges="true">
    <CompareItem name="Requirements" type="Package" guid="{50168942-9238-44a6-89F3-B624125BFBAE}" status="Identical">
    <Properties>
      <Property name="Abstract" model="false" baseline="false" status="Identical" />
      <Property name="Alias" status="Identical" />
      <Property name="Author" model="edgar" baseline="edgar" status="Identical" />
      <Property name="Complexity" model="1" baseline="1" status="Identical" />
      <Property name="IsLeaf" model="false" baseline="false" status="Identical" />
      <Property name="IsSpec" model="false" baseline="false" status="Identical" />
      <Property name="IsRoot" model="false" baseline="false" status="Identical" />
      <Property name="Keywords" status="Identical" />
      <Property name="Multiplicity" status="Identical" />
      <Property name="Name" model="Requirements" baseline="Requirements" status="Identical" />
      <Property name="Notes" status="Identical" />
      <Property name="Persistence" status="Identical" />
      <Property name="Phase" model="1.0" baseline="1.0" status="Identical" />
      <Property name="Scope" model="Public" baseline="Public" status="Identical" />
      <Property name="Status" model="Proposed" baseline="Proposed" status="Identical" />
      <Property name="Stereotype" status="Identical" />
      <Property name="Type" model="Package" baseline="Package" status="Identical" />
      <Property name="Version" model="1.0" baseline="1.0" status="Identical" />
      <Property name="Classifier" status="Identical" />
      <Property name="Visibility" status="Identical" />
      <Property name="Concurrency" status="Identical" />
      <Property name="Cardinality" status="Identical" />
      <Property name="Style" status="Identical" />
      <Property name="Advanced Properties" status="Identical" />
    </Properties>
    <CompareItem name="Requirement2" type="Requirement" guid="{EC192A01-B94F-4540-87D9-353C72AEC19F}" status="Changed">
      <Properties>
      <Property name="Abstract" model="false" baseline="false" status="Identical" />
      <Property name="Alias" status="Identical" />
      <Property name="Author" model="edgar" baseline="edgar" status="Identical" />
      <Property name="DateCreated" model="27/07/2019 22:03:41" baseline="27/07/2019 22:03:41" status="Identical" />
      <Property name="DateModified" model="09/08/2019 19:50:03" baseline="27/07/2019 22:03:41" status="Changed" />
      <Property name="Complexity" model="1" baseline="1" status="Identical" />
      <Property name="GenFile" status="Identical" />
      <Property name="GenType" model="<none>" baseline="<none>" status="Identical" />
      <Property name="IsLeaf" model="false" baseline="false" status="Identical" />
      <Property name="IsSpec" model="false" baseline="false" status="Identical" />
      <Property name="IsRoot" model="false" baseline="false" status="Identical" />
      <Property name="Keywords" status="Identical" />
      <Property name="Multiplicity" status="Identical" />
      <Property name="Name" model="Requirement2" baseline="Requirement1" status="Changed" />
      <Property name="Notes" status="Identical" />
      <Property name="ParentPackage" model="Requirements" baseline="Requirements" status="Identical" />
      <Property name="Persistence" status="Identical" />
      <Property name="Phase" model="1.0" baseline="1.0" status="Identical" />
      <Property name="Scope" model="Public" baseline="Public" status="Identical" />
      <Property name="Status" model="Proposed" baseline="Proposed" status="Identical" />
      <Property name="Stereotype" status="Identical" />
      <Property name="Type" model="Requirement" baseline="Requirement" status="Identical" />
      <Property name="Version" model="1.0" baseline="1.0" status="Identical" />
      <Property name="Classifier" status="Identical" />
      <Property name="Visibility" status="Identical" />
      <Property name="Concurrency" status="Identical" />
      <Property name="Cardinality" status="Identical" />
      <Property name="Style" status="Identical" />
      <Property name="Advanced Properties" status="Identical" />
      </Properties>
    </CompareItem>
    </CompareItem>
  </CompareResults>
  </ComparePackage>
</EA.CompareLog>

Side note: The guid attribute in CompareItem is not always a real GUID, but can also contain trailing textual information (for examples with connectors). So we have to check the type first before trying to convert it to a real guid.

The simplified element structure looks like this

EA.CompareLog
  ComparePackage 
    CompareResults
      CompareItem [0..*]
        Properties [0..1]
          Property [0..*]
        CompareItem [0..*]

Each CompareItem and Property has a Status with one of the following contents:

  • Identical, if the model does not differ from the baseline
  • Changed, if there is at least one change in the model from the baseline
  • Baseline only, if the element was deleted from the model
  • Model only, if the element has been created after the baseline has been created

If the baseline and the model are completely the same, then the hasChanges attribute on the CompareResults elements is False – otherwise True.

BaselineComparison Visitor

Looping through the comparison data can be tedious, so I use a simple visitor that calls back my code for the elements I am interested in:

public interface IBaselineComparisonVisitor
{
  void Visit(BaselineComparison baselineComparison);
  void Visit(BaselineComparison baselineComparison, string status);
  void VisitPackage(BaselineComparison.ComparePackageElement element);
  void VisitResults(BaselineComparison.CompareResultsElement element);
  void VisitElements(List<BaselineComparison.CompareItemElement> elements);
  void VisitElements(List<BaselineComparison.CompareItemElement> elements, BaselineComparison.CompareItemElement parent);
  void VisitElement(BaselineComparison.CompareItemElement element, BaselineComparison.CompareItemElement parent);
  void VisitElement(BaselineComparison.CompareItemElement element);
  void VisitProperties(List<BaselineComparison.PropertyElement> elements, BaselineComparison.CompareItemElement parent);
  void VisitProperties(List<BaselineComparison.PropertyElement> elements);
  void VisitProperty(BaselineComparison.PropertyElement element, BaselineComparison.CompareItemElement parent);
  void VisitProperty(BaselineComparison.PropertyElement element);
}

Suppose I only want to retrieve changed element and property count, I create a derived visitor class and only override the following methods:

public class MyBaselineVisitor : BaselineComparisonVisitor
{
  public int ElementCount;
  public int PropertyCount;

  public override void VisitProperty(BaselineComparison.PropertyElement element)
  {
    PropertyCount++;
  }

  public override void VisitElement(BaselineComparison.CompareItemElement element)
  {
    ElementCount++;
  }
}

The code to invoke the comparison in a unit test would then look like this:

[TestMethod]
public void Test()
{
  var baseline = XmlSerialiserBase.Deserialise<BaselineComparison>(BaselineComparisonTest.XML);

  var sut = new MyBaselineVisitor();
  sut.Visit(baseline, BaselineComparisonStatus.Changed);

  Assert.AreEqual(1, sut.ElementCount);
  Assert.AreEqual(2, sut.PropertyCount);
}

The Assert statements verify the correct behaviour (based on the example XML above).

This is actually it. With a small utility classes we no longer need to use MSXML2 and iterating over xml elements but persue a clean approach working with native C# objects.

Complete Example

In the Gist below you find the following files:

  1. DTO for the baseline summary information that you can use with the XmlSerialiser to convert the returned XML into a C# object.
  2. DTO for the baseline comparison result that you can use with the XmlSerialiser to convert the baseline comparison XML into a C# object.
  3. A simple visitor class that helps you iterate over the BaselineComparison object.

Deserialising the XML into the C# classes can be performed like this:

public static T Deserialise<T>(string xml)
  where T : XmlSerialiserBase
{
  using (var stream = new StringReader(xml))
  {
    return (T) new XmlSerializer(typeof(T)).Deserialize(stream);
  }
}


/**
* Copyright 2018 d-fens GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Xml.Serialization;
namespace biz.dfch.CS.EA.ProductModeler.Public.Ea.Conversion.Baseline
{
[XmlSerializerFormat]
[XmlRoot("EA.CompareLog")]
public class BaselineComparison : XmlSerialiserBase
{
[XmlElement(nameof(ComparePackage))]
public ComparePackageElement ComparePackage { get; set; } = new ComparePackageElement();
[XmlSerializerFormat]
public class ComparePackageElement
{
[XmlAttribute("name")]
public string Name { get; set; }
[XmlAttribute("guid")]
public string EaId { get; set; }
[XmlAttribute("packageID")]
public int PackageId { get; set; }
[XmlAttribute("comparedBy")]
public string ComparedBy { get; set; }
[XmlAttribute("comparedOn")]
public string ComparedOnString { get; set; }
public DateTime ComparedOn => DateTime.Parse(ComparedOnString);
[XmlElement(nameof(CompareResults))]
public CompareResultsElement CompareResults = new CompareResultsElement();
}
public class CompareResultsElement
{
[XmlAttribute("hasChanges")]
public bool HasChanges { get; set; }
[XmlElement("CompareItem")]
public List<CompareItemElement> CompareItems { get; set; } = new List<CompareItemElement>();
}
public class CompareItemElement
{
[XmlAttribute("name")]
public string Name { get; set; }
[XmlAttribute("type")]
public string Type { get; set; }
[XmlAttribute("guid")]
public string Id { get; set; }
[XmlAttribute("status")]
public string Status { get; set; }
[XmlElement(nameof(Properties))]
public PropertiesElement Properties { get; set; } = new PropertiesElement();
[XmlElement("CompareItem")]
public List<CompareItemElement> CompareItems { get; set; } = new List<CompareItemElement>();
}
public class PropertiesElement
{
[XmlElement("Property")]
public List<PropertyElement> Properties { get; set; } = new List<PropertyElement>();
}
public class PropertyElement
{
[XmlAttribute("name")]
public string Name { get; set; }
[XmlAttribute("model")]
public string Model { get; set; }
[XmlAttribute("baseline")]
public string Baseline { get; set; }
[XmlAttribute("status")]
public string Status { get; set; }
}
}
}


/**
* Copyright 2018 d-fens GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System.Collections.Generic;
using System.Diagnostics.Contracts;
namespace biz.dfch.CS.EA.ProductModeler.Public.Ea.Conversion.Baseline
{
public abstract class BaselineComparisonVisitor : IBaselineComparisonVisitor
{
public void Visit(BaselineComparison baselineComparison)
{
Visit(baselineComparison, BaselineComparisonStatus.Any);
}
public void Visit(BaselineComparison baselineComparison, string status)
{
Contract.Requires(null != baselineComparison);
VisitPackage(baselineComparison.ComparePackage);
VisitResults(baselineComparison.ComparePackage.CompareResults);
RecursiveVisitElements(baselineComparison.ComparePackage.CompareResults.CompareItems, null, status);
}
private void RecursiveVisitElements(List<BaselineComparison.CompareItemElement> elements, BaselineComparison.CompareItemElement parent, string status)
{
VisitElements(elements);
VisitElements(elements, parent);
foreach (var element in elements)
{
if (!string.IsNullOrWhiteSpace(status) && element.Status != status)
{
RecursiveVisitElements(element.CompareItems, element, status);
continue;
}
VisitElement(element);
VisitElement(element, parent);
VisitProperties(element.Properties.Properties);
VisitProperties(element.Properties.Properties, element);
foreach (var property in element.Properties.Properties)
{
if (!string.IsNullOrWhiteSpace(status) && property.Status != status) continue;
VisitProperty(property);
VisitProperty(property, element);
}
RecursiveVisitElements(element.CompareItems, element, status);
}
}
public virtual void VisitPackage(BaselineComparison.ComparePackageElement element) { }
public virtual void VisitResults(BaselineComparison.CompareResultsElement element) { }
public virtual void VisitElements(List<BaselineComparison.CompareItemElement> elements) { }
public virtual void VisitElements(List<BaselineComparison.CompareItemElement> elements, BaselineComparison.CompareItemElement parent) { }
public virtual void VisitElement(BaselineComparison.CompareItemElement element, BaselineComparison.CompareItemElement parent) { }
public virtual void VisitElement(BaselineComparison.CompareItemElement element) { }
public virtual void VisitProperties(List<BaselineComparison.PropertyElement> elements, BaselineComparison.CompareItemElement parent) { }
public virtual void VisitProperties(List<BaselineComparison.PropertyElement> elements) { }
public virtual void VisitProperty(BaselineComparison.PropertyElement element, BaselineComparison.CompareItemElement parent) { }
public virtual void VisitProperty(BaselineComparison.PropertyElement element) { }
}
}


/**
* Copyright 2018 d-fens GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Xml.Serialization;
namespace biz.dfch.CS.EA.ProductModeler.Public.Ea.Conversion.Baseline
{
[XmlSerializerFormat]
[XmlRoot("EA.BaseLines")]
public class BaselineSummary : XmlSerialiserBase
{
[XmlAttribute("package")]
public Guid PackageId { get; set; }
[XmlElement("Baseline")]
public List<BaselineElement> Baselines { get; set; } = new List<BaselineElement>();
public class BaselineElement
{
[XmlAttribute("name")]
public string Name { get; set; }
[XmlAttribute("version")]
public string Version { get; set; }
[XmlAttribute("notes")]
public string Description { get; set; }
[XmlAttribute("guid")]
public Guid Id { get; set; }
}
}
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.