Skip to content

Loading different file formats

Abigail Alexander edited this page Dec 19, 2022 · 5 revisions

Loading OPI screens

All existing display managers (EDM, MEDM, CS-Studio, Phoebus, etc) define absolute layouts by pixel. In principle it should be possible to load each of these formats with a generic parser, assuming that each widget in an existing format must map to a widget in cs-web-proto. Meanwhile, we have our own json-based file format that we also need to parse.

Widgets are defined in terms of key-value pairs, where the key is a string and the value may be one of a a number of different types, such as string, number, action or rule. Within a file format, values must be somehow encoded as strings and the parser needs to know how to turn those strings into the appropriate type.

Some widgets have child widgets, making the parsed file into a tree structure.

When parsing foreign file formats, there are possible incompatibilities:

  • the widget may not have an equivalent in cs-web-proto
  • a property in the source file may map on to multiple properties in cs-web-proto
  • multiple properties in the source file may map on to one property in cs-web-proto

Generic parsing algorithm

The following mechanism is generic:

  • parse the file format into a tree of JavaScript objects
  • for each widget:
    • select the corresponding widget
    • a 'simple' property maps on to one property on the target widget. Parse these
    • a 'complex' property consumes one or more properties and creates one or more properties on the target widget. Parse these
    • 'patch functions' ensure that the resulting widget is consistent. Run each patch function on the widget
    • recursively apply this logic to all child widgets

Writing a parser

All the file parsers are currently in src/ui/widgets/EmbeddedDisplay. opiParser.ts contains the .opi file parser.

Generic steps

  • define a function to parse the file e.g. parseOpi
  • parse the file into a tree of javascript objects such that each widget is an object and parent widgets have a property that contains an array of child widgets
  • provide a function that returns the target widget given the widget ID
  • provide an object containing 'simple' parsers
  • provide an object containing 'complex' parsers
  • provide an array of 'patch functions'
  • call parseWidget() passing the parsed files and function objects
  • 'register' this file parser in embeddedDisplay.ts

Detailed steps

To parse a property from a file, you can make use of existing parsing functions or create your own. If the properties are simple values such as boolean, color, number, string, position or font, then simply adding the properties to the OPI_SIMPLE_PARSERS dictionary and allocating the correct type of parser function will ensure this property is parsed and passed to your component

export const OPI_SIMPLE_PARSERS: ParserDict = {
  text: ["text", opiParseString],
  name: ["name", opiParseString],
  textAlign: ["horizontal_alignment", opiParseHorizontalAlignment],
  backgroundColor: ["background_color", opiParseColor],
  ...
}

The property names must be changed from snake case to camel case to follow convention. Some properties require more complex parsing; for these properties, you should:

  • Define a function to parse the property e.g. opiParseAxes. This should parse the value(s) you need from the props, and it should return an instance of a specified complex prop class.
function opiParseAxes(props: any): Axes {
  const count = opiParseNumber(props.axis_count);
  const axes: Axis[] = [];
  // Parse all of the 'axis' properties
  for (let i = 0; i < count; i++) {
    const _axis = parseMultipleNamedProps("axis", props, i);
    axes.push(_axis);
  }
  return new Axes(count, axes);
}
  • Create the class for your prop by adding a file to src/types. Then, append the new class to the GenericProp type list in src/types/props.ts. This allows the OPI_COMPLEX_PARSERS or BOB_COMPLEX_PARSERS dictionaries to return instances of your new class
  • Add your new complex parser into the BOB/OPI_COMPLEX_PARSERS dictionary.

The final step is to writing a parser is to map the widget to the type. This ensures that whenever a widget of a given type e.g. label is encountered in a .opi or .bob file, the correct widget is created in cs-web-proto and corresponding props passed to it.

/**
 * maps a widget typeId specified in an opi file to a corresponding registered
 * widget name
 */
const OPI_WIDGET_MAPPING: { [key: string]: any } = {
  "org.csstudio.opibuilder.Display": "display",
  "org.csstudio.opibuilder.widgets.TextUpdate": "readback",
  "org.csstudio.opibuilder.widgets.TextInput": "input",
  "org.csstudio.opibuilder.widgets.Label": "label",
  ...
}

.opi files

The CS-Studio display manager BOY uses an xml format (.opi). We can load opi files with a few difficulties.

Since rules act on named properties, and we do not necessarily use the same property names as the original widgets, we need to edit the content of each rule to ensure the property names are correct. Additionally, we sometimes need to convert the value from a rule into the appropriate object. We use a 'patch function' for this.

.bob files

The Phoebus Display Builder (see Github) has a clean xml-based file format (.bob), improved from the CS-Studio format. Parsing .bob files is similar but slightly different to parsing .opi files.

Example

A screen containing only one Label widget looks like this:

<?xml version="1.0" encoding="UTF-8"?>                                          
<display version="2.0.0">                                                       
  <name>Display</name>                                                          
  <width>100</width>                                                            
  <height>100</height>                                                          
  <widget type="label" version="2.0.0">                                         
    <name>Label</name>                                                          
    <text>Hello</text>                                                          
    <x>30</x>                                                                   
    <y>20</y>                                                                   
    <width>120</width>                                                          
    <height>40</height>                                                         
  </widget>                                                                     
</display> 

Each widget has default values that are not included in the xml if they are not changed.