BorisKozo / XmlViewAddin

1 stars 2 forks source link

XmlViewAddin is now part of the VuGenPowerPack and is available from here: https://github.com/BorisKozo/VuGenPowerPack

This repository will not be updated anymore.

XmlViewAddin

Disclaimer

The following document and all the code in this repository are not part of the LoadRunner product! You are using the code and data and the products generated by compiling this code at your own risk.

General

We are going to create a new VuGen addin which displays XML content from an action parameter in a separate window directly from the script. The goal here is not to provide some highly useful functionality but to introduce the readers to the VuGen extensibility mechanism.

Compilation Note

To compile the project you must add %LR_DIR%\bin to the reference paths of the project. There is a compiled version in the bin directory in the root of the repository. If you don't want to compile yourself you can copy the content of this directory to %LR_DIR%\addins\extra\XmlViewAddin.

Addins

Directory Structure

VuGen 11.5x is based on SharpDevelop 4.1 and therefore it uses the SharpDevelop (SD) extensibility layer. There is an additional layer of services but it is not used in this example. VuGen itself contains only the core extensibility layer and the application starter, all other functionality including code editing, recording, replaying, script management, etc... are extensions to VuGen written in the same way as presented here.

All the extensions are defined in the %LR_DIR%\addins directory through the use of .addin files. Under the aforementioned directory you can find the following core addins:

The rest of the addins are distributed throughout the subdirectories by topic. As evident from the directory structure, VuGen is separated into three layers - SharpDevelop, Utt, and VuGen. The reason for this separation is not important to this discussion.

Some useful terms

Previously we discussed the term addin but where do those addins go when VuGen starts up? The addins are combined during the application startup into one big tree-like structure known as the addin-tree. The addin tree is built of nodes where each node contains codons. A codon is basically a piece of code that "knows" how to do something. For example, if we want to add a new menu item, then we create a codon which implements an interface of a menu item (an example of this later) and add a definition of that codon to the appropriate place in the addin file. To "tell" the addin-tree to which tree node we want our codon to be added, we use the path property which is a concatenated list of all the nodes in the route to the target node. The path uses the id property of any codon to generate a new node for this codon so essentially each codon generates a node in the tree. For example, if we want to add my menu item into the main menu of the application, we should put it into the /SharpDevelop/Workbench/MainMenu path. This path basically reads, root -> SharpDevelop -> Workbench -> MainMenu node.

Addin file structure

The addin file is a simple XML file with a predefined structure. This section briefly describes the most important (and mandatory) parts of this file but it is a good idea to learn more directly from the SD website. The following is an example of a simple addin file:

<AddIn name = "XmlViewAddin"
       author = "Boris Kozorovitzky"
       description = "Adds the functionality to easily view XML data directly from the editor">

  <Runtime>
    <Import assembly = "XmlViewAddin.dll"/>
  </Runtime>

  <Manifest>
    <Identity name="XmlViewAddin" version = "0.0.1.0" />
  </Manifest>

  <Path name = "/SharpDevelop/Workbench/MainMenu">
    <MenuItem id = "MenuItemTest" label = "TEST!" type="Menu"></MenuItem>
  </Path>

</AddIn>

The main element <AddIn> contains basic information on the the addin file and the author. Inside we find the <Runtime> element. This element is very important because it links the .addin file with all the dlls implementing its functionality. In this example we import only the dll which we are going to produce for this example called XmlViewAddin.dll. We can specify as many dlls as we need in this section, each in its own <Import> element.

The <Manifest> element contains the metadata about the addin. In the example we specify only the identity of the addin and the version number. This is a mandatory element because it is needed to register the addin with the extensibility layer.

The <Path> element tells the extensibility layer where to put the codons within the addin-Tree. In the example we can see that we add a menu item with the label "TEST!" to the main menu of our application.

Writing your own addin

In this section we write the initial files for our addin. The code in this repository is implemented in Visual Studio 2010 but any editor can be used for this task. Specifically, VuGen is shipped with SharpDevelop 4.1 (including sources) so it is possible to install it directly from the LR DVD here \Additional Components\Third Parties\SharpDevelop_4_1_src\SharpDevelop-4.1.0.8000.zip.

We start by creating a C# class library and adding a .addin file to it. You can use any .Net language. Our addin file contains the XML from the example above. The next thing we do is to configure the class library to be built into the following folder: %LR_DIR%\addins\extra\XmlViewAddin (we create this folder manually first) and the build action of my .addin file to Copy always. This will copy the update version of my addin directly to VuGen every time we build it. For easier debugging we set the Start Action of my debugger to start VuGen by pointing it to %LR_DIR%\bin\vugen.exe. We can "run" the addin by pressing F5. An instance of VuGen should start and the main menu should contain our item:

VuGen with the new menu item

In the next sections I try to use as little "inside" knowledge as possible although this is not always possible.

Step 1 - Adding a context menu command

In this step we add a menu item to the context menu of the editor. We start by adding a menu item to the context menu of the editor pane. To do this we must first find the path of this context menu. We open an empty script and right click the editor to see where we want to place the menu item. It seems that the best place is right after the "Go to Step in Replay log" item. We search for the item name throughout all the .addin files. We found one instance of this string in the VuGenDebugger.addin so let's copy it to our addin file with the relevant changed.

  <Path name = "/SharpDevelop/ViewContent/TextEditor/ContextMenu">
    <MenuItem id="OpenXmlView" insertafter="GoToLine" insertbefore="InsertSeparator"
            label = "Open in XML viewer"
            class = "XmlViewAddin.OpenXmlViewCommand"/>
  </Path>

A few things to notice here:

Our code looks something like this:

using System;
using HP.Utt.UttCore;

namespace XmlViewAddin
{
  public class OpenXmlViewCommand : UttBaseWpfCommand
  {
    public override void Run()
    {
      throw new NotImplementedException();
    }
  }
}

We can verify that the menu item "works" by running our addin and in VuGen right-clicking anywhere in the editor. In the context menu we select the "Open in XML viewer" menu item and see an error dialog (since we throw an exception in the Run method).

Step 2 - Get the selected text from the editor

In this step we get the selected text from the editor so that we can parse it into XML. To this end we will use the UttCodeEditor wrapper around the code editor. To use it we need some additional references for our project. Add the following dlls to the references: ICSharpCode.SharpDevelop.dll and HP.Utt.CodeEditor.dll (don't forget to set "Copy local" to false). Now we can simply use this code

      ITextEditor editor = UttCodeEditor.GetActiveTextEditor();
      if (editor == null)
        return;

to retrieve the current editor. The editor object has a SelectedText property which we use to get, as the name implies, the selected text.

Step 3 - Infer the XML value

We want to convert the selected text to a valid XML string. Specifically, we are interested in Soap request strings which come escaped. The following code is a simple algorithm to trim out the unneeded parts from that string.

      string[] lines = editor.SelectedText.Split(new string[]{Environment.NewLine},StringSplitOptions.RemoveEmptyEntries);
      StringBuilder result = new StringBuilder();
      foreach (string line in lines) 
      {
        string proccessedLine = line.Trim();
        //Remove the leading and trailing " - this is VuGen specific code
        if (proccessedLine.StartsWith("\""))
        {
          proccessedLine = proccessedLine.Remove(0, 1);
        }
        if (proccessedLine.EndsWith("\""))
        {
          proccessedLine = proccessedLine.Remove(proccessedLine.Length-1, 1);
        }
        proccessedLine = proccessedLine.Replace("\\","");

        result.Append(proccessedLine);
      }

At the end of the process we should have the selected value correctly in the result StringBuilder. To verify that the result is a valid XML document we add a small static verification method IsValidXml before we pass the read XML to the display component. The code looks something like this:

    public static bool IsValidXml(string xml)
    {
      XmlDocument doc = new XmlDocument();
      try
      {
        doc.LoadXml(xml);
      }
      catch
      {
        return false;
      }

      return true;
    }

(perhaps not the best code but good enough for our discussion)

Step 4 - Show the XML in a nice way

Now that we have the XML string we want to show it nicely in a modal dialog. We could open a regular dialog and use a textbox to display the XML but we can simply reuse the VuGen dialogs framework and the VuGen XML viewer to display our information. First we add the dialogs framework. We reference the following dlls: HP.Utt.Dialog.dll, HP.Utt.Common.dll, and HP.LR.VuGen.Common.dll from %LR_DIR%\bin* and PresentationFramework, System.Xaml, and WindowsBase from the GAC (the later three are needed to use the WPF framework and are not directly related to VuGen). Next, we reference the VuGen XML viewer component conveniently named HP.LR.VuGen.XmlViewer.dll from %LR_DIR%\bin*.

At this point things get a little tricky, not because of bad API but because of naming. The CustomDialog class is a container for all your dialog needs. We can set its content to any WPF control we want and it will display it. Moreover, we have various services the CustomDialog provides for us such as buttons, persistence, and much more. In the dialog we place the VuGen XML viewer named XmlViewSingleContent (yes, I know this name is very confusing). Since we are using the MVVM development paradigm the XmlViewSingleContent expects a view-model of type SingleDirectionData (again, very confusing). The view-model takes an XmlDocument class which we can easily create from our XML string. The code is therefore looks like this:

    private void ShowXmlDialog(string xml)
    {
      CustomDialog dialog = new CustomDialog();
      XmlViewSingleContent content = new XmlViewSingleContent();
      SingleDirectionData data = new SingleDirectionData();
      content.DataContext = data;
      XmlDocument doc = new XmlDocument();
      doc.LoadXml(xml);
      data.Document = doc;
      dialog.Content = content;
      dialog.Show();
    }

I call the above method with the string we calculated in the Run method and the result is like this:

VuGen displays my XML

Step 5 - Improving usability

We have the basic dialog but we want to include two improvements. The first would be to add some buttons to close the dialog. The custom dialog provides built in methods to add commonly used buttons so we add just the Ok button that would close the dialog:

   dialog.AddOkButton();

The second thing we want to add is to disable the menu item we added if the selected text is not a valid XML. To this end we add a Condition that would check the selected text. Firs we add a new class to our project which implements the IConditionEvaluator interface, we call this new class IsValidXmlSelectedCondition :

  public class IsValidXmlSelectedCondition:IConditionEvaluator
  {
    public bool IsValid(object owner, Condition condition)
    {
      return false;
    }
  }

Currently, our condition fails all the time but it will soon change. Remember that IsValid method we added earlier? We can reuse it here for exactly the same propose. We extract the part of the Run method which finds the XML string into a static method called GetSelectedString so the condition code becomes:

  public class IsValidXmlSelectedCondition:IConditionEvaluator
  {
    public bool IsValid(object owner, Condition condition)
    {
      return OpenXmlViewCommand.IsValidXml(OpenXmlViewCommand.GetSelectedString());
    }
  }

To use the condition we just implemented we need to do two things:

   <Runtime>
    <Import assembly = "XmlViewAddin.dll">
      <ConditionEvaluator name="IsValidXmlSelected" class="XmlViewAddin.IsValidXmlSelectedCondition"/>
    </Import>
  </Runtime>

Now all that remains is to wrap our menu item with the condition and watch the magic.

  <Path name = "/SharpDevelop/ViewContent/TextEditor/ContextMenu">
    <Condition name="IsValidXmlSelected" action="Disable">
      <MenuItem id="OpenXmlView" insertafter="GoToLine" insertbefore="InsertSeparator"
              label = "Open in XML viewer"
              class = "XmlViewAddin.OpenXmlViewCommand"/>
    </Condition>
  </Path>

The name of the condition we use here must be the same as the name in the definition. the action can be either Disable (if we want the menu item to appear disabled) or Exclude (if we don't want the menu item to appear at all).

The final code looks like this:

  public class OpenXmlViewCommand : UttBaseWpfCommand
  {
    public static bool IsValidXml(string xml)
    {
      if (string.IsNullOrWhiteSpace(xml))
      {
        return false;
      }

      XmlDocument doc = new XmlDocument();
      try
      {
        doc.LoadXml(xml);
      }
      catch
      {
        return false;
      }

      return true;
    }

    public static string GetSelectedString()
    {
      ITextEditor editor = UttCodeEditor.GetActiveTextEditor();
      if (editor == null)
        return null;
      string[] lines = editor.SelectedText.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
      StringBuilder result = new StringBuilder();
      foreach (string line in lines)
      {
        string proccessedLine = line.Trim();
        //Remove the leading and trailing " - this is VuGen specific code
        if (proccessedLine.StartsWith("\""))
        {
          proccessedLine = proccessedLine.Remove(0, 1);
        }
        if (proccessedLine.EndsWith("\""))
        {
          proccessedLine = proccessedLine.Remove(proccessedLine.Length - 1, 1);
        }
        proccessedLine = proccessedLine.Replace("\\", "");

        result.Append(proccessedLine);
      }

      return result.ToString();
    }

    public override void Run()
    {
      string xml = GetSelectedString();
      if (IsValidXml(xml))
      {
        ShowXmlDialog(xml);
      }
    }

    private void ShowXmlDialog(string xml)
    {
      CustomDialog dialog = new CustomDialog();
      XmlViewSingleContent content = new XmlViewSingleContent();
      SingleDirectionData data = new SingleDirectionData();
      content.DataContext = data;
      XmlDocument doc = new XmlDocument();
      doc.LoadXml(xml);
      data.Document = doc;
      dialog.Content = content;
      dialog.MaxWidth = 800;
      dialog.AddOkButton();
      dialog.AddButton("reformat", Reformat, System.Windows.Input.Key.R, System.Windows.Input.ModifierKeys.Alt, "Reformat");
      dialog.Show();
    }
}

Step 6 - Reformat on exit

A nice addition to our addin would be a button that reformats the original selected text into a nice XML in the script. In this step we are going to do just that. First lets add a button that would call our reformat method and close the dialog. Doing this is as simple as calling a CustomDialog AddButton method.

dialog.AddButton("reformat", Reformat, System.Windows.Input.Key.R, System.Windows.Input.ModifierKeys.Alt, "Reformat");

Now all that remains is to implement the Reformat method:

    private void Reformat(CustomDialog dialog)
    {
      ITextEditor editor = UttCodeEditor.GetActiveTextEditor();
      string selectedText = editor.SelectedText;
      string[] xmlLines = FormatXml(GetSelectedString()).Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
      StringBuilder formattedText = new StringBuilder();
      foreach (string line in xmlLines)
      {
        formattedText.AppendLine("\""+line.Replace("\"", "\\\"")+"\"");
      }

      formattedText.Remove(formattedText.Length - Environment.NewLine.Length, Environment.NewLine.Length);

      if (!selectedText.StartsWith("\""))
      {
        formattedText.Insert(0, "\"" + Environment.NewLine);
      }

      if (!selectedText.EndsWith("\""))
      {
        formattedText.Append(Environment.NewLine+"\"");
      }

      editor.Document.Replace(editor.SelectionStart, editor.SelectionLength, formattedText.ToString());
      dialog.Close();
    }

    private string FormatXml(String xml)
    {
      try
      {
        XDocument doc = XDocument.Parse(xml);
        return doc.ToString();
      }
      catch (Exception)
      {
        return xml;
      }
    }

Conclusion

We wrote a simple addin for VuGen to display and possibly reformat XML text in the editor. We saw how to integrate with the VuGen extensibility framework and how to use basic constructs to interact with the editor. Next, we implemented conditions and some logic for our CustomDialog. We can see that with some knowledge of the classes behind VuGen we can easily create functionality that can make script development much easier.

Good Luck!