Task 425: .MSC File Format
Task 425: .MSC File Format
1. List of all the properties of this file format intrinsic to its file system
The .MSC file format is a text-based format used by Mscgen for describing Message Sequence Charts. It is not binary and has no magic number or header intrinsic to a file system (e.g., no specific byte signature, as it is plain ASCII/UTF-8 text). The format is defined by its syntax, which includes global options, entity declarations with attributes, arc declarations with types and attributes, and comments. All properties are keyword-based and case-insensitive for keywords, but case-sensitive for values like labels. The intrinsic properties are the syntactic elements that define the structure and content, as follows:
Global Options (key-value pairs at the start of the msc { block, comma-separated):
hscale: Horizontal scale factor (numeric or "auto").width: Absolute image width in pixels (numeric or "auto").arcgradient: Pixel slope for arcs (numeric).wordwraparcs: Boolean to enable word-wrapping for arc labels (true/false, on/off, 1/0).
Entity Attributes (in square brackets after entity names, comma-separated):
label: Text label for the entity (string, supports \n for multi-line).linecolourorlinecolor: Color for the entity's lifeline.textcolourortextcolor: Color for the entity's label text.textbgcolourortextbgcolor: Background color for the entity's label.arclinecolourorarclinecolor: Default line color for arcs from this entity.arctextcolourorarctextcolor: Default text color for arcs from this entity.arctextbgcolourorarctextbgcolor: Default text background color for arcs from this entity.
Arc Types (symbols used between entities to define interactions):
->or<-: Standard message (solid line arrow).=>or<=: Method call.>>or<<: Method return.=>>or<<=: Callback.:>or<:: Emphasized message (double line).-xorx-: Lost message.->*or*<-: Broadcast to all entities....: Discontinuity (dashed horizontal line).---: Divider (horizontal line for comments or sections).|||: Vertical spacer.box: Rectangular box (for states or conditions).rbox: Rounded rectangular box.abox: Angular (diamond) box.note: Note box.
Arc Attributes (in square brackets after arc declarations, comma-separated):
label: Text displayed on the arc (string, supports \n for multi-line).URL: Hyperlink for the label (string, e.g., URL or \ref for references).ID: Superscript identifier for the arc (string).IDURL: Hyperlink for the ID (string).arcskip: Vertical row offset at destination (numeric).linecolourorlinecolor: Arc line color.textcolourortextcolor: Arc label text color.textbgcolourortextbgcolor: Arc label background color (fills boxes for box types).
Color Values (used in color attributes, RGB #hex or predefined names):
- Predefined: white, silver, gray, black, maroon, red, orange, yellow, olive, green, lime, aqua, teal, blue, navy, indigo, purple, violet, fuchsia.
- Custom: #RRGGBB (e.g., #ff0000 for red).
Other Structural Properties:
msc { ... }: Required enclosing block for the entire description.- Comments: #, //, or /* ... */.
- Entity declarations: Comma-separated list before arcs (e.g., a,b,c;).
- Whitespace and newlines: Ignored by parser.
The format is human-readable text, with no file system-specific metadata beyond standard text file properties (e.g., encoding typically UTF-8, line endings CR/LF or LF).
2. Two direct download links for files of format .MSC
- https://raw.githubusercontent.com/maidsafe/brb_membership/master/round_robin_split_vote_2.msc
- https://raw.githubusercontent.com/maidsafe/brb_membership/master/round_robin_split_vote_3.msc
3. Write a ghost blog embedded html javascript that allows a user to drag n drop a file of format .MSC and it will dump to screen all these properties
Drag and Drop .MSC File
4. Write a python class that can open any file of format .MSC and decode read and write and print to console all the properties from the above list
import re
import json
class MSCParser:
def __init__(self):
self.properties = {
'globalOptions': {},
'entities': [],
'arcs': [],
'colors': set(),
'comments': []
}
def read(self, filepath):
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
self._parse(content)
def _parse(self, content):
# Global options
options_match = re.search(r'msc\s*{\s*([^}]*);', content, re.IGNORECASE)
if options_match:
options = options_match.group(1).split(',')
for opt in options:
key_value = opt.split('=')
if len(key_value) == 2:
key = key_value[0].strip().replace('"', '').replace("'", '')
value = key_value[1].strip().replace('"', '').replace("'", '')
self.properties['globalOptions'][key] = value
# Entities
entity_matches = re.findall(r'([a-z0-9_"]+[^;]*);', content, re.IGNORECASE)
for ent in entity_matches:
entity_name = ent.split('[')[0].strip()
attr_match = re.search(r'\[(.*)\]', ent)
attrs = self._parse_attributes(attr_match.group(1)) if attr_match else {}
self.properties['entities'].append({'name': entity_name, 'attributes': attrs})
self._extract_colors(attrs)
# Arcs
arc_lines = [line.strip() for line in content.split(';') if re.search(r'[-:=x>|<.*]', line)]
for arc in arc_lines:
parts = re.split(r'([-><:=x|.-]+)', arc)
if len(parts) >= 3:
from_ent = parts[0].strip()
arc_type = parts[1].strip()
to_ent = parts[2].split('[')[0].strip()
attr_match = re.search(r'\[(.*)\]', arc)
attrs = self._parse_attributes(attr_match.group(1)) if attr_match else {}
self.properties['arcs'].append({'from': from_ent, 'type': arc_type, 'to': to_ent, 'attributes': attrs})
self._extract_colors(attrs)
# Comments
for line in content.split('\n'):
stripped = line.strip()
if stripped.startswith('#') or stripped.startswith('//'):
self.properties['comments'].append(stripped)
self.properties['colors'] = list(self.properties['colors'])
def _parse_attributes(self, attr_str):
attrs = {}
attr_parts = attr_str.split(',')
for attr in attr_parts:
key_value = attr.split('=')
if len(key_value) == 2:
key = key_value[0].strip().replace('"', '').replace("'", '')
value = key_value[1].strip().replace('"', '').replace("'", '')
attrs[key] = value
return attrs
def _extract_colors(self, attrs):
color_keys = ['linecolour', 'linecolor', 'textcolour', 'textcolor', 'textbgcolour', 'textbgcolor', 'arclinecolour', 'arclinecolor', 'arctextcolour', 'arctextcolor', 'arctextbgcolour', 'arctextbgcolor']
for key, value in attrs.items():
if key in color_keys:
self.properties['colors'].add(value)
def print_properties(self):
print(json.dumps(self.properties, indent=4))
def write(self, filepath):
with open(filepath, 'w', encoding='utf-8') as f:
# Reconstruct basic structure (simplified, not full round-trip)
f.write('msc {\n')
# Global options
opts = ', '.join([f'{k}="{v}"' for k, v in self.properties['globalOptions'].items()])
if opts:
f.write(f' {opts};\n')
# Entities
ents = ', '.join([f'{e["name"]} [{", ".join([f'{k}="{v}"' for k, v in e["attributes"].items()])}]' if e["attributes"] else e["name"] for e in self.properties['entities']])
f.write(f' {ents};\n')
# Arcs
for a in self.properties['arcs']:
attrs = f' [{", ".join([f'{k}="{v}"' for k, v in a["attributes"].items()])}]' if a["attributes"] else ''
f.write(f' {a["from"]} {a["type"]} {a["to"]}{attrs};\n')
# Comments
for c in self.properties['comments']:
f.write(f'{c}\n')
f.write('}\n')
# Example usage
# parser = MSCParser()
# parser.read('example.msc')
# parser.print_properties()
# parser.write('output.msc')
5. Write a java class that can open any file of format .MSC and decode read and write and print to console all the properties from the above list
import java.io.*;
import java.util.*;
import java.util.regex.*;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class MSCParser {
private Map<String, Object> properties = new HashMap<>();
public MSCParser() {
properties.put("globalOptions", new HashMap<String, String>());
properties.put("entities", new ArrayList<Map<String, Object>>());
properties.put("arcs", new ArrayList<Map<String, Object>>());
properties.put("colors", new HashSet<String>());
properties.put("comments", new ArrayList<String>());
}
public void read(String filepath) throws IOException {
StringBuilder content = new StringBuilder();
try (BufferedReader br = new BufferedReader(new FileReader(filepath))) {
String line;
while (line = br.readLine()) != null) {
content.append(line).append("\n");
}
}
parse(content.toString());
}
private void parse(String content) {
// Global options
Pattern optionsPattern = Pattern.compile("msc\\s*\\{\\s*([^}]*)\\;", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
Matcher optionsMatcher = optionsPattern.matcher(content);
if (optionsMatcher.find()) {
String[] options = optionsMatcher.group(1).split(",");
Map<String, String> globalOptions = (Map<String, String>) properties.get("globalOptions");
for (String opt : options) {
String[] kv = opt.split("=");
if (kv.length == 2) {
String key = kv[0].trim().replaceAll("[\"']", "");
String value = kv[1].trim().replaceAll("[\"']", "");
globalOptions.put(key, value);
}
}
}
// Entities
Pattern entityPattern = Pattern.compile("([a-z0-9_\"]+[^;]*);", Pattern.CASE_INSENSITIVE);
Matcher entityMatcher = entityPattern.matcher(content);
List<Map<String, Object>> entities = (List<Map<String, Object>>) properties.get("entities");
while (entityMatcher.find()) {
String ent = entityMatcher.group(1);
String name = ent.split("\\[")[0].trim();
Map<String, String> attrs = new HashMap<>();
Pattern attrPattern = Pattern.compile("\\[(.*)\\]");
Matcher attrMatcher = attrPattern.matcher(ent);
if (attrMatcher.find()) {
attrs = parseAttributes(attrMatcher.group(1));
}
Map<String, Object> entityMap = new HashMap<>();
entityMap.put("name", name);
entityMap.put("attributes", attrs);
entities.add(entityMap);
extractColors(attrs);
}
// Arcs
String[] lines = content.split("\n");
List<Map<String, Object>> arcs = (List<Map<String, Object>>) properties.get("arcs");
for (String line : lines) {
line = line.trim();
if (line.matches(".*[-:=x>|<.*].*")) {
Pattern arcPattern = Pattern.compile("(.*?)\\s*([-><:=x|.-]+)\\s*(.*)");
Matcher arcMatcher = arcPattern.matcher(line);
if (arcMatcher.find()) {
String from = arcMatcher.group(1).trim();
String type = arcMatcher.group(2).trim();
String toAndAttrs = arcMatcher.group(3).trim();
String to = toAndAttrs.split("\\[")[0].trim();
Map<String, String> attrs = new HashMap<>();
Pattern attrPattern = Pattern.compile("\\[(.*)\\]");
Matcher attrMatcher = attrPattern.matcher(toAndAttrs);
if (attrMatcher.find()) {
attrs = parseAttributes(attrMatcher.group(1));
}
Map<String, Object> arcMap = new HashMap<>();
arcMap.put("from", from);
arcMap.put("type", type);
arcMap.put("to", to);
arcMap.put("attributes", attrs);
arcs.add(arcMap);
extractColors(attrs);
}
}
}
// Comments
List<String> comments = (List<String>) properties.get("comments");
for (String line : lines) {
String stripped = line.trim();
if (stripped.startsWith("#") || stripped.startsWith("//")) {
comments.add(stripped);
}
}
((Set<String>) properties.get("colors")).toArray(); // Convert to list if needed
}
private Map<String, String> parseAttributes(String attrStr) {
Map<String, String> attrs = new HashMap<>();
String[] attrParts = attrStr.split(",");
for (String attr : attrParts) {
String[] kv = attr.split("=");
if (kv.length == 2) {
String key = kv[0].trim().replaceAll("[\"']", "");
String value = kv[1].trim().replaceAll("[\"']", "");
attrs.put(key, value);
}
}
return attrs;
}
private void extractColors(Map<String, String> attrs) {
Set<String> colors = (Set<String>) properties.get("colors");
String[] colorKeys = {"linecolour", "linecolor", "textcolour", "textcolor", "textbgcolour", "textbgcolor", "arclinecolour", "arclinecolor", "arctextcolour", "arctextcolor", "arctextbgcolour", "arctextbgcolor"};
for (String key : colorKeys) {
if (attrs.containsKey(key)) {
colors.add(attrs.get(key));
}
}
}
public void printProperties() {
Gson gson = new GsonBuilder().setPrettyPrinting().create();
System.out.println(gson.toJson(properties));
}
public void write(String filepath) throws IOException {
try (BufferedWriter bw = new BufferedWriter(new FileWriter(filepath))) {
bw.write("msc {\n");
Map<String, String> globalOptions = (Map<String, String>) properties.get("globalOptions");
String opts = String.join(", ", globalOptions.entrySet().stream().map(e -> e.getKey() + "=\"" + e.getValue() + "\"").toArray(String[]::new));
if (!opts.isEmpty()) {
bw.write(" " + opts + ";\n");
}
List<Map<String, Object>> entities = (List<Map<String, Object>>) properties.get("entities");
String ents = String.join(", ", entities.stream().map(e -> {
Map<String, String> attrs = (Map<String, String>) e.get("attributes");
String attrStr = attrs.isEmpty() ? "" : " [" + String.join(", ", attrs.entrySet().stream().map(a -> a.getKey() + "=\"" + a.getValue() + "\"").toArray(String[]::new)) + "]";
return (String) e.get("name") + attrStr;
}).toArray(String[]::new));
bw.write(" " + ents + ";\n");
List<Map<String, Object>> arcs = (List<Map<String, Object>>) properties.get("arcs");
for (Map<String, Object> a : arcs) {
Map<String, String> attrs = (Map<String, String>) a.get("attributes");
String attrStr = attrs.isEmpty() ? "" : " [" + String.join(", ", attrs.entrySet().stream().map(at -> at.getKey() + "=\"" + at.getValue() + "\"").toArray(String[]::new)) + "]";
bw.write(" " + a.get("from") + " " + a.get("type") + " " + a.get("to") + attrStr + ";\n");
}
List<String> comments = (List<String>) properties.get("comments");
for (String c : comments) {
bw.write(c + "\n");
}
bw.write("}\n");
}
}
// Example usage
// public static void main(String[] args) throws IOException {
// MSCParser parser = new MSCParser();
// parser.read("example.msc");
// parser.printProperties();
// parser.write("output.msc");
// }
}
6. Write a javascript class that can open any file of format .MSC and decode read and write and print to console all the properties from the above list
class MSCParser {
constructor() {
this.properties = {
globalOptions: {},
entities: [],
arcs: [],
colors: new Set(),
comments: []
};
}
async read(filepath) {
// For node.js, use fs
const fs = require('fs');
const content = fs.readFileSync(filepath, 'utf8');
this._parse(content);
}
_parse(content) {
// Global options
const optionsMatch = content.match(/msc\s*{\s*([^}]*);/i);
if (optionsMatch) {
const options = optionsMatch[1].split(',');
options.forEach(opt => {
const [key, value] = opt.split('=').map(s => s.trim().replace(/["']/g, ''));
if (key) this.properties.globalOptions[key] = value;
});
}
// Entities
const entityMatch = content.match(/([a-z0-9_"]+[^;]*);/gi);
if (entityMatch) {
entityMatch.forEach(ent => {
const entity = ent.replace(';', '').trim();
const attrMatch = entity.match(/\[(.*)\]/);
const attrs = attrMatch ? this._parseAttributes(attrMatch[1]) : {};
this.properties.entities.push({ name: entity.split('[')[0].trim(), attributes: attrs });
this._extractColors(attrs);
});
}
// Arcs
const arcLines = content.split(';').filter(line => line.trim().match(/[-:=x>|<.*]/));
arcLines.forEach(arc => {
const parts = arc.split(/([-><:=x|.-]+)/).map(s => s.trim());
const type = parts[1] || '';
const attrMatch = arc.match(/\[(.*)\]/);
const attrs = attrMatch ? this._parseAttributes(attrMatch[1]) : {};
this.properties.arcs.push({ from: parts[0], type, to: parts[2], attributes: attrs });
this._extractColors(attrs);
});
// Comments
content.split('\n').forEach(line => {
const stripped = line.trim();
if (stripped.startsWith('#') || stripped.startsWith('//')) {
this.properties.comments.push(stripped);
}
});
this.properties.colors = Array.from(this.properties.colors);
}
_parseAttributes(str) {
const attrs = {};
str.split(',').forEach(attr => {
const [key, value] = attr.split('=').map(s => s.trim().replace(/["']/g, ''));
if (key) attrs[key] = value;
});
return attrs;
}
_extractColors(attrs) {
Object.entries(attrs).forEach(([key, value]) => {
if (key.includes('colour') || key.includes('color')) {
this.properties.colors.add(value);
}
});
}
printProperties() {
console.log(JSON.stringify(this.properties, null, 2));
}
write(filepath) {
const fs = require('fs');
let output = 'msc {\n';
// Global options
const opts = Object.entries(this.properties.globalOptions).map(([k, v]) => `${k}="${v}"`).join(', ');
if (opts) output += ` ${opts};\n`;
// Entities
const ents = this.properties.entities.map(e => {
const attrs = Object.entries(e.attributes).map(([k, v]) => `${k}="${v}"`).join(', ');
return attrs ? `${e.name} [${attrs}]` : e.name;
}).join(', ');
output += ` ${ents};\n`;
// Arcs
this.properties.arcs.forEach(a => {
const attrs = Object.entries(a.attributes).map(([k, v]) => `${k}="${v}"`).join(', ');
const attrStr = attrs ? ` [${attrs}]` : '';
output += ` ${a.from} ${a.type} ${a.to}${attrStr};\n`;
});
// Comments
this.properties.comments.forEach(c => {
output += `${c}\n`;
});
output += '}\n';
fs.writeFileSync(filepath, output, 'utf8');
}
}
// Example usage (in Node.js)
// const parser = new MSCParser();
// await parser.read('example.msc');
// parser.printProperties();
// parser.write('output.msc');
7. Write a c class that can open any file of format .MSC and decode read and write and print to console all the properties from the above list
Note: This is implemented in C++ for class support, as "c class" likely implies C++.
#include <iostream>
#include <fstream>
#include <sstream>
#include <regex>
#include <map>
#include <vector>
#include <set>
#include <nlohmann/json.hpp> // Assume json library for printing, or replace with custom
using json = nlohmann::json;
class MSCParser {
private:
std::map<std::string, std::string> globalOptions;
std::vector<std::map<std::string, std::string>> entities; // Simplified, name in map as "name"
std::vector<std::map<std::string, std::string>> arcs; // from, type, to as strings in map
std::set<std::string> colors;
std::vector<std::string> comments;
public:
void read(const std::string& filepath) {
std::ifstream file(filepath);
std::stringstream content;
content << file.rdbuf();
parse(content.str());
}
void parse(const std::string& content) {
// Global options
std::regex optionsRegex("msc\\s*\\{\\s*([^}]*)\\;", std::regex::icase);
std::smatch optionsMatch;
if (std::regex_search(content, optionsMatch, optionsRegex)) {
std::string optionsStr = optionsMatch[1].str();
std::regex optRegex("([^,]+)");
std::sregex_iterator optIter(optionsStr.begin(), optionsStr.end(), optRegex);
for (; optIter != std::sregex_iterator(); ++optIter) {
std::string opt = optIter->str();
size_t eqPos = opt.find('=');
if (eqPos != std::string::npos) {
std::string key = opt.substr(0, eqPos);
std::string value = opt.substr(eqPos + 1);
key.erase(remove(key.begin(), key.end(), '"'), key.end());
key.erase(remove(key.begin(), key.end(), '\''), key.end());
value.erase(remove(value.begin(), value.end(), '"'), value.end());
value.erase(remove(value.begin(), value.end(), '\''), value.end());
key.erase(0, key.find_first_not_of(" \t"));
key.erase(key.find_last_not_of(" \t") + 1);
value.erase(0, value.find_first_not_of(" \t"));
value.erase(value.find_last_not_of(" \t") + 1);
globalOptions[key] = value;
}
}
}
// Entities
std::regex entityRegex("([a-z0-9_\"]+[^;]*);", std::regex::icase);
std::sregex_iterator entityIter(content.begin(), content.end(), entityRegex);
for (; entityIter != std::sregex_iterator(); ++entityIter) {
std::string ent = entityIter->str(1);
std::map<std::string, std::string> entityMap;
size_t bracketPos = ent.find('[');
std::string name = ent.substr(0, bracketPos);
name.erase(0, name.find_first_not_of(" \t"));
name.erase(name.find_last_not_of(" \t") + 1);
entityMap["name"] = name;
if (bracketPos != std::string::npos) {
std::string attrStr = ent.substr(bracketPos + 1, ent.find(']') - bracketPos - 1);
entityMap.update(parseAttributes(attrStr));
extractColors(entityMap);
}
entities.push_back(entityMap);
}
// Arcs
std::stringstream ss(content);
std::string line;
while (std::getline(ss, line, ';')) {
line.erase(0, line.find_first_not_of(" \t\n\r\f\v"));
line.erase(line.find_last_not_of(" \t\n\r\f\v") + 1);
if (std::regex_match(line, std::regex(".*[-:=x>|<.*].*"))) {
std::regex arcRegex("(.*?)\\s*([-><:=x|.-]+)\\s*(.*)");
std::smatch arcMatch;
if (std::regex_match(line, arcMatch, arcRegex)) {
std::map<std::string, std::string> arcMap;
arcMap["from"] = arcMatch[1].str();
arcMap["type"] = arcMatch[2].str();
std::string toAndAttrs = arcMatch[3].str();
size_t bracketPos = toAndAttrs.find('[');
std::string to = toAndAttrs.substr(0, bracketPos);
to.erase(0, to.find_first_not_of(" \t"));
to.erase(to.find_last_not_of(" \t") + 1);
arcMap["to"] = to;
if (bracketPos != std::string::npos) {
std::string attrStr = toAndAttrs.substr(bracketPos + 1, toAndAttrs.find(']') - bracketPos - 1);
auto attrs = parseAttributes(attrStr);
for (auto& pair : attrs) {
arcMap[pair.first] = pair.second;
}
extractColors(attrs);
}
arcs.push_back(arcMap);
}
}
}
// Comments
std::stringstream ss2(content);
while (std::getline(ss2, line)) {
line.erase(0, line.find_first_not_of(" \t"));
line.erase(line.find_last_not_of(" \t") + 1);
if (line.rfind('#', 0) == 0 || line.rfind("//", 0) == 0) {
comments.push_back(line);
}
}
}
std::map<std::string, std::string> parseAttributes(const std::string& attrStr) {
std::map<std::string, std::string> attrs;
std::regex attrRegex("([^,]+)");
std::sregex_iterator attrIter(attrStr.begin(), attrStr.end(), attrRegex);
for (; attrIter != std::sregex_iterator(); ++attrIter) {
std::string attr = attrIter->str();
size_t eqPos = attr.find('=');
if (eqPos != std::string::npos) {
std::string key = attr.substr(0, eqPos);
std::string value = attr.substr(eqPos + 1);
key.erase(remove(key.begin(), key.end(), '"'), key.end());
key.erase(remove(key.begin(), key.end(), '\''), key.end());
value.erase(remove(value.begin(), value.end(), '"'), value.end());
value.erase(remove(value.begin(), value.end(), '\''), value.end());
key.erase(0, key.find_first_not_of(" \t"));
key.erase(key.find_last_not_of(" \t") + 1);
value.erase(0, value.find_first_not_of(" \t"));
value.erase(value.find_last_not_of(" \t") + 1);
attrs[key] = value;
}
}
return attrs;
}
void extractColors(const std::map<std::string, std::string>& attrs) {
std::vector<std::string> colorKeys = {"linecolour", "linecolor", "textcolour", "textcolor", "textbgcolour", "textbgcolor", "arclinecolour", "arclinecolor", "arctextcolour", "arctextcolor", "arctextbgcolour", "arctextbgcolor"};
for (const auto& key : colorKeys) {
auto it = attrs.find(key);
if (it != attrs.end()) {
colors.insert(it->second);
}
}
}
void printProperties() {
json j;
j["globalOptions"] = globalOptions;
j["entities"] = entities;
j["arcs"] = arcs;
std::vector<std::string> colorVec(colors.begin(), colors.end());
j["colors"] = colorVec;
j["comments"] = comments;
std::cout << j.dump(4) << std::endl;
}
void write(const std::string& filepath) {
std::ofstream file(filepath);
file << "msc {" << std::endl;
std::string opts;
for (const auto& pair : globalOptions) {
if (!opts.empty()) opts += ", ";
opts += pair.first + "=\"" + pair.second + "\"";
}
if (!opts.empty()) file << " " << opts << ";" << std::endl;
std::string ents;
for (const auto& e : entities) {
if (!ents.empty()) ents += ", ";
ents += e.at("name");
std::string attrStr;
for (const auto& pair : e) {
if (pair.first != "name") {
if (!attrStr.empty()) attrStr += ", ";
attrStr += pair.first + "=\"" + pair.second + "\"";
}
}
if (!attrStr.empty()) ents += " [" + attrStr + "]";
}
file << " " << ents << ";" << std::endl;
for (const auto& a : arcs) {
std::string from = a.at("from");
std::string type = a.at("type");
std::string to = a.at("to");
std::string attrStr;
for (const auto& pair : a) {
if (pair.first != "from" && pair.first != "type" && pair.first != "to") {
if (!attrStr.empty()) attrStr += ", ";
attrStr += pair.first + "=\"" + pair.second + "\"";
}
}
file << " " << from << " " << type << " " << to;
if (!attrStr.empty()) file << " [" << attrStr << "]";
file << ";" << std::endl;
}
for (const auto& c : comments) {
file << c << std::endl;
}
file << "}" << std::endl;
}
};
// Example usage
// int main() {
// MSCParser parser;
// parser.read("example.msc");
// parser.printProperties();
// parser.write("output.msc");
// return 0;
// }