Best way to programmatically get linked objects per stereotype - enterprise-architect

I need to programmatically (javascript) get a linked object of a given element per stereotype, even if it is more than one level up.
So, for example in the next figure, I expect to get Obj1 from both el1 and el2, but never Obj2.
I have this solution already but is seems not too elegant and its time consuming:
function count(main_str, sub_str)
{
main_str += '';
sub_str += '';
if (sub_str.length <= 0)
{
return main_str.length + 1;
}
subStr = sub_str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return (main_str.match(new RegExp(subStr, 'gi')) || []).length;
}
function getLinkedObjects(objectID, connectionType, end_or_start) {
//This function gets the objects linked to the given objectID. It can be filtered by 'connectionType' and by weather the given object is in start or end position.
if (connectionType == '' || connectionType == 'any') {
var connector = ""
} else {
var connector = " and Connector_type = '"+connectionType+"'"
}
if (end_or_start == 'start') {
var SQLquery = "select obj.Object_ID, obj.Name from (t_object obj inner join (select * from t_connector where start_object_ID = "+objectID+connector+") q on q.End_object_ID = obj.Object_ID) where obj.Object_ID <> "+objectID+connector
} else if (end_or_start == 'start') {
var SQLquery = "select obj.Object_ID, obj.Name from (t_object obj inner join (select * from t_connector where end_object_ID = "+objectID+connector+") q on q.start_object_ID = obj.Object_ID) where obj.Object_ID <> "+objectID+connector
} else if (end_or_start == '' || end_or_start == 'both') {
var SQLquery = "select distinct obj.Object_ID, obj.Name from (t_object obj inner join (select * from t_connector where (start_object_ID = "+objectID+connector+" or end_object_ID = "+objectID+connector+")) q on (q.Start_object_ID = obj.Object_ID or q.End_object_ID = obj.Object_ID)) where obj.Object_ID <> "+objectID+connector
} else {
var SQLquery = ""
}
var conn_elements = Repository.GetElementSet(SQLquery, 2);
return conn_elements;
}
function getObjectStream(elemid, stereotype) {
//This function gets all the stream of objects and/or blocks linked to the given objectID.
var i = 0
var streams = []
var linked_objs = getLinkedObjects(elemid, "", "both");
for (var l = 0; l < linked_objs.Count; l++) {
var level1 = getLinkedObjects(linked_objs.GetAt(l).ElementID, "", "both");
if (linked_objs.GetAt(l).Stereotype == stereotype) {
var stream = "l0-- " + linked_objs.GetAt(l).Name
streams.push(stream)
break
} else {
for (var l1 = 0; l1 < level1.Count; l1++) {
var level2 = getLinkedObjects(level1.GetAt(l1).ElementID, "", "both");
if (level1.GetAt(l1).Stereotype == stereotype) {
var stream = "l0-- " + linked_objs.GetAt(l).Name + " l1-- " + level1.GetAt(l1).Name
streams.push(stream)
break
} else {
for (var l2 = 0; l2 < level2.Count; l2++) {
if (level2.GetAt(l2).Stereotype == stereotype) {
var stream = "l0-- " + linked_objs.GetAt(l).Name + " l1-- " + level1.GetAt(l1).Name + " l2-- " + level2.GetAt(l2).Name
streams.push(stream)
break
}
}
}
}
}
}
var w = ''
var level = 5
for (var f = 0; f < streams.length; f++) {
levels = count(streams[f], "--")
if (levels < level) {
level = levels - 1
w = streams[f]
}
}
var le = "l"+level+"-- "
var n = w.search(le) + 5;
var winner = w.substring(n, w.lenght);
return winner
}
function main() {
var linked_per_stereotype = getObjectStream(1234, "A");
Session.Output(linked_per_stereotype);
}
main();
Any suggestions for a better approach for this?
Thank you!

I'm not really familiar with JS and it depends on your model. So just thinking loud: if there were a limited number of the desired stereotypes (and having language construct like in Python which might or might not be present in JS) I would just query all elements having the stereotype along with their connectors in a JOIN and make a hash of the result by object ID. So I could traverse simply by indexing the hash from the connector source/end ID. I would assume the number of stereotyped elements is rather low so that would be an approach.
If your model were huge and having tons of these stereotypes there might be no way around single queries. Maybe Geert has something doing that in one go.

Related

Is there a way to replace multiple keywords in a string and wrapping them with their own keyword?

Say I have a string:
typed = "need replace this ap"
str = "hello I need to replace this asap"
so the end result I want would be this:
newStr = "hello I <bold>need</bold> to <bold>replace</bold> <bold>this</bold> as<bold>ap</bold>"
please don't mind the weird syntax.
I wonder if the order would matter, for example:
typed = "applicable app"
str = "the app is very applicable in many applications"
The end result I wish should be:
newStr = "the <bold>app</bold> is very <bold>applicable</bold> in many <bold>app</bold>lications"
right? is this possible?
Hey,If You can ignore the weird HTML syntax here,
Then I have wrote a solution for you,
Paste this code in dart pad here
removeDuplicates(var typed, var str) {
Map<String, String> m = new Map<String, String>();
var n = typed.length;
String ans = "";
//for storing the "typed" string (word by word) into a map "m" variable for later searching purpose
String temp = "";
int i = 0;
for (i = 0; i < n; i++) {
if (typed[i] == " ") {
m[temp] = temp;
temp = "";
} else {
temp = temp + typed[i];
}
}
//for storing the last word of the string "typed", coz loop will never find a space in last of the string
m[temp] = temp;
// map variable loop for search from map "m" in the "str" string, and matching if the word is present or not
var n2 = str.length;
String temp2 = "";
for (int j = 0; j < n2; j++) {
if (str[j] == " ") {
if (m.containsKey(temp2)) {
} else {
ans = ans + " " + temp2; //storing the "temp2" string into "ans" string, everytime it finds a space and if the string is not already present in the map "m"
}
temp2 = "";
} else {
temp2 = temp2 + str[j];
}
}
//for searching for the last word of the string "str" in map "m", coz loop will never find a space in last of the string,
if (m.containsKey(temp2)) {
} else {
ans = ans + " " + temp2;
}
return ans;
}
void main() {
String typed = "need replace this ap";
var str = "hello I need to replace this asap";
String answer = removeDuplicates(typed, str);
print(answer);
}
Here, I have made a method removeDuplicates() to simplify your work, You just have to pass those string in your method, and then it will return you the desired answer string by removing the duplicates, with a new string.
UPDATED CODE (TO SHOW HTML CODE):
removeDuplicates(var typed, var str) {
Map<String, String> m = new Map<String, String>();
var n = typed.length;
String ans = "";
//for storing the "typed" string (word by word) into a map "m" variable for later searching purpose
String temp = "";
int i = 0;
for (i = 0; i < n; i++) {
if (typed[i] == " ") {
m[temp] = temp;
temp = "";
} else {
temp = temp + typed[i];
}
}
//for storing the last word of the string "typed", coz loop will never find a space in last of the string
m[temp] = temp;
print(m);
// map variable loop for search from map "m" in the "str" string, and matching if the word is present or not
var n2 = str.length;
String temp2 = "";
for (int j = 0; j < n2; j++) {
if (str[j] == " ") {
if (m.containsKey(temp2)) {
temp2 = "<bold>" + temp2 + "</bold> ";
ans = ans + " " + temp2;
} else {
ans = ans +
" " +
temp2; //storing the "temp2" string into "ans" string, everytime it finds a space and if the string is not already present in the map "m"
}
temp2 = "";
} else {
temp2 = temp2 + str[j];
}
}
//for searching for the last word of the string "str" in map "m", coz loop will never find a space in last of the string,
if (m.containsKey(temp2)) {
temp2 = "<bold>" + temp2 + "</bold> ";
temp2 = "";
} else {
ans = ans + " " + temp2;
temp2 = "";
}
return ans;
}
void main() {
var typed = "applicable app";
var str = "the app is very applicable in many applications";
String answer = removeDuplicates(typed, str);
print(answer);
}
UPDATE 2 (ALL THANKS TO PSKINK FOR THE str.replaceAllMapped APPROACH)
replaceWithBoldIfExists(String typed, String str) {
var n = typed.length;
List<String> searchList = new List<String>();
String temp = "";
int i = 0;
for (i = 0; i < n; i++) {
if (typed[i] == " ") {
searchList.add(temp);
temp = "";
} else {
temp = temp + typed[i];
}
}
searchList.add(temp);
String pat = searchList.join('|');
final pattern = RegExp(pat);
final replaced =
str.replaceAllMapped(pattern, (m) => '<bold>${m.group(0)}</bold>');
return replaced;
}
void main() {
var typed = "need replace this ap";
var str = "hello I need to replace this asap";
print(replaceWithBoldIfExists(typed, str));
}

Enterprise Architect - How to set column key to Autonum?

I have a bunch of tables with Id int primary keys. However, I forgot to set AutoNum to True in the UI. Since changing all hundreds of tables is tedious, how can I set this property for all Id columns?
I have built a script that runs through each table and detects the Id column:
var package as EA.Package;
package = Repository.GetTreeSelectedPackage();
var tablesEnumerator = new Enumerator(package.Elements);
while (!tablesEnumerator.atEnd()) {
var table as EA.Element;
table = tablesEnumerator.item();
var methodsEnumerator = new Enumerator(table.Methods);
while (!methodsEnumerator.atEnd()) {
var method as EA.Method;
method = methodsEnumerator.item();
if (method.Name !== "Id") { continue; }
Session.Output(method.Name);
// Now what?!
}
}
I have searched for AutoNum in EnterpriseArchitect docs and APIs, but was unable to find suitable references.
According to Autonum in Column Properties inaccessible you can actually change the AutoNum behaviour via API with the means of TaggedValues. So there is no need of direct SQL updates to the database.
Setting the tagged values property and AutoNum on the Id attribute (not the method of the table seems to do the magic. It tried it via the builtin script engine and it works:
Before running the script
After running the script
The update script
!INC Local Scripts.EAConstants-JScript
function main()
{
var package = Repository.GetTreeSelectedPackage();
var elements as EA.Collection;
elements = package.Elements;
Session.Output("Elements Count " + elements.Count);
for ( var ec = 0 ; ec < elements.Count ; ec++ )
{
var element as EA.Element;
element = elements.GetAt(ec);
if("Table" != element.MetaType) continue;
Session.Output("Element: Name '" + element.Name + "' [" + element.ElementGUID + "] '" + element.MetaType + "'.");
var attributes as EA.Collection;
attributes = element.Attributes;
for ( var ac = 0; ac < attributes.Count ; ac++)
{
var attribute as EA.Attribute;
attribute = attributes.GetAt(ac);
if("Id" != attribute.Name) continue;
Session.Output("Attribute: Name '" + attribute.Name + "' [" + attribute.AttributeGUID + "] in element '"+ element.Name + "' [" + element.MetaType + "].");
var hasTvProperty = false;
var hasTvAutonum = false;
var taggedValues as EA.Collection;
taggedValues = attribute.TaggedValues;
Session.Output("TaggedValues: Count " + taggedValues.Count);
for ( var tc = 0; tc < taggedValues.Count; tc++)
{
var taggedValue as EA.TaggedValue;
taggedValue = taggedValues.GetAt(tc);
if("property" != taggedValue.Name && "AutoNum" != taggedValue.Name) continue;
Session.Output("TaggedValue: Name '" + taggedValue.Name + "'. Value '" + taggedValue.Value + "'");
if("property" != taggedValue.Name)
{
taggedValue.Value = "AutoNum=1;StartNum=1;Increment=1;";
taggedValue.Update();
element.Update();
hasTvProperty = true;
}
if("AutoNum" != taggedValue.Name)
{
taggedValue.Value = "True";
taggedValue.Update();
element.Update();
hasTvAutonum = true;
}
}
if(!hasTvProperty)
{
var tv = taggedValues.AddNew("property", "AutoNum=1;StartNum=1;Increment=1;");
tv.Update();
element.Update();
}
if(!hasTvAutonum)
{
var tv = taggedValues.AddNew("AutoNum", "True");
tv.Update();
element.Update();
}
break;
}
}
}
main();
Content of the t_attributetags table

Sparx EA Jscript Information Flows Realized

How do I retrieve a collection of all the Information Flows Realized by a connector of type Dependency using Jscript please?
First option would be to use the API.
Loop the connectors and check EA.Connector.ConveyedItems
But that will be terribly slow for anything but a trivial model.
So the only sane way is to use EA.Repository.SQLQuery(string SQL) to get a list of connectorID's and then use EA.Repository.GetConnectorByID(int ID) to get the connector objects.
The SQL query you need is something in the nature of
select *
from ((((t_connector c
inner join t_xref x on (x.Client = c.ea_guid
and x.Name = 'MOFProps'
and x.Type = 'connector property'
and x.Behavior = 'abstraction'))
inner join t_connector cf on x.Description like '%' + cf.ea_guid + '%')
inner join t_xref xf on (xf.Client = cf.ea_guid
and xf.Name = 'MOFProps'
and xf.Type = 'connector property'
and xf.Behavior = 'conveyed'))
inner join t_object o on o.ea_guid like xf.Description)
where c.Connector_Type = 'Dependency'
Replace % with * in case you are working on a .eap (MS-Access) file.
I also have an implementation in C#. On class ConnectorWrapper there is this operation to get the information flows from the dependency.
/// <summary>
/// convenience method to return the information flows that realize this Relationship
/// </summary>
/// <returns>the information flows that realize this Relationship</returns>
public virtual HashSet<UML.InfomationFlows.InformationFlow> getInformationFlows()
{
HashSet<UML.InfomationFlows.InformationFlow> informationFlows = new HashSet<UML.InfomationFlows.InformationFlow>();
string sqlGetInformationFlowIDs = #"select x.description
from (t_connector c
inner join t_xref x on (x.client = c.ea_guid and x.Name = 'MOFProps'))
where c.ea_guid = '" + this.guid + "'";
var queryResult = this.model.SQLQuery(sqlGetInformationFlowIDs);
var descriptionNode = queryResult.SelectSingleNode(this.model.formatXPath("//description"));
if (descriptionNode != null)
{
foreach (string ifGUID in descriptionNode.InnerText.Split(','))
{
var informationFlow = this.model.getRelationByGUID(ifGUID) as UML.InfomationFlows.InformationFlow;
if (informationFlow != null )
{
informationFlows.Add(informationFlow);
}
}
}
return informationFlows;
}
Once you have the InformationFlow this code gets the conveyed items
public HashSet<UML.Classes.Kernel.Classifier> conveyed
{
get
{
if (_conveyed == null)
{
string getXrefDescription = #"select x.Description from t_xref x
where x.Name = 'MOFProps'
and x.Behavior = 'conveyed'
and x.client = '" + this.guid + "'";
//xrefdescription contains the GUID's of the conveyed elements comma separated
var xrefDescription = this.model.SQLQuery(getXrefDescription).SelectSingleNode(this.model.formatXPath("//Description"));
if (xrefDescription != null)
{
foreach (string conveyedGUID in xrefDescription.InnerText.Split(','))
{
var conveyedElement = this.model.getElementWrapperByGUID(conveyedGUID) as UML.Classes.Kernel.Classifier;
if (conveyedElement != null)
{
//initialize if needed
if (_conveyed == null)
{
_conveyed = new HashSet<UML.Classes.Kernel.Classifier>();
}
//add the element
_conveyed.Add(conveyedElement);
}
}
}
}
//nothing found, return empty list.
if (_conveyed == null)
{
_conveyed = new HashSet<UML.Classes.Kernel.Classifier>();
}
return _conveyed;
}
}

Google Spreadsheet - How to avoid sending email duplicates?

I am having an issue with a script. I used the following script from Google Developers Website in order to do a simple merge mail. See https://developers.google.com/apps-script/articles/mail_merge
I modified a bit the script so to prevent email duplicates. However, even if the script seems to work as it marks 'EMAIL_SENT' in each row every time an email is sent. It does not pay attention if the mail as already been marked and still send the mail.
I believe there is an error at line 16 "var emailSent = rowData[6];"
I would really appreciate if someone could help me. Whoever you are thanks in advance.
Here is the modified script :
var EMAIL_SENT = "EMAIL_SENT";
function sendEmails() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dataSheet = ss.getSheets()[0];
var dataRange = dataSheet.getRange(2, 1, dataSheet.getMaxRows() - 1, 7);
var templateSheet = ss.getSheets()[1];
var emailTemplate = templateSheet.getRange("A2").getValue();
var objects = getRowsData(dataSheet, dataRange);
for (var i = 0; i < objects.length; ++i) {
var Resume = DriveApp.getFilesByName('Resume.pdf') var Portfolio = DriveApp.getFilesByName('Portfolio.pdf') var rowData = objects[i];
var emailText = fillInTemplateFromObject(emailTemplate, rowData);
var emailSubject = "Architectural Internship";
var emailSent = rowData[6];
if (emailSent != EMAIL_SENT) {
MailApp.sendEmail(rowData.emailAddress, emailSubject, emailText, {
attachments: [Resume.next(), Portfolio.next()]
});
dataSheet.getRange(2 + i, 7).setValue(EMAIL_SENT);
SpreadsheetApp.flush();
}
}
}
function fillInTemplateFromObject(template, data) {
var email = template;
var templateVars = template.match(/\${\"[^\"]+\"}/g);
for (var i = 0; i < templateVars.length; ++i) {
var variableData = data[normalizeHeader(templateVars[i])];
email = email.replace(templateVars[i], variableData || "");
}
return email;
}
function getRowsData(sheet, range, columnHeadersRowIndex) {
columnHeadersRowIndex = columnHeadersRowIndex || range.getRowIndex() - 1;
var numColumns = range.getEndColumn() - range.getColumn() + 1;
var headersRange = sheet.getRange(columnHeadersRowIndex, range.getColumn(), 1, numColumns);
var headers = headersRange.getValues()[0];
return getObjects(range.getValues(), normalizeHeaders(headers));
}
function getObjects(data, keys) {
var objects = [];
for (var i = 0; i < data.length; ++i) {
var object = {};
var hasData = false;
for (var j = 0; j < data[i].length; ++j) {
var cellData = data[i][j];
if (isCellEmpty(cellData)) {
continue;
}
object[keys[j]] = cellData;
hasData = true;
}
if (hasData) {
objects.push(object);
}
}
return objects;
}
function normalizeHeaders(headers) {
var keys = [];
for (var i = 0; i < headers.length; ++i) {
var key = normalizeHeader(headers[i]);
if (key.length > 0) {
keys.push(key);
}
}
return keys;
}
function normalizeHeader(header) {
var key = "";
var upperCase = false;
for (var i = 0; i < header.length; ++i) {
var letter = header[i];
if (letter == " " && key.length > 0) {
upperCase = true;
continue;
}
if (!isAlnum(letter)) {
continue;
}
if (key.length == 0 && isDigit(letter)) {
continue;
}
if (upperCase) {
upperCase = false;
key += letter.toUpperCase();
} else {
key += letter.toLowerCase();
}
}
return key;
}
// Returns true if the cell where cellData was read from is empty. // Arguments: // - cellData: string function isCellEmpty(cellData) {
return typeof(cellData) == "string" && cellData == "";
}
// Returns true if the character char is alphabetical, false otherwise. function isAlnum(char) { return char >= 'A' && char <= 'Z' || char >= 'a' && char <= 'z' || isDigit(char); }
// Returns true if the character char is a digit, false otherwise. function isDigit(char) { return char >= '0' && char <= '9'; }
Your code is really hard to read and the functions that return 2 or more objects make it even harder...you are using variable names that are also a bit confusing.... but that is probably a personal pov :-)
Anyway, I think I've found the issue: when you write var rowData = objects[i];
This "object" is actually the result of the getRowData function but if you look at this function, you'll see that it returns 2 objects, the first one being itself the result of another function (getObjects) ...
You are checking the value is the 6th element of the array which is actually an object and compare it to a string. The equality will never be true.
I didn't go further in the analyse since I found it really confusing ( as I already said) but at least you have a first element to check .
I would suggest you rewrite this code in a more simple way and use more appropriate variable names to help you while debugging.
I would recommend logging both values before executing to make sure they are the same. I would also guess that the email_sent and EMAIL_SENT are different data types. Can also try forcing the value to string for comparison.
To clarify:
logger.Log(emailSent);
logger.Log(EMAIL_SENT);
if (emailSent.toString() != EMAIL_SENT.toString())
{...
Error is in this line of code -
var dataRange = sheet.getRange(startRow, 1, numRows, 2)
It's considering only 2 columns in the range. Changed 2 to 3 and it worked fine.

Calling a function from onEdit() trigger doesn't work

I want to run a function that updates some values when I edit one cell of a column. This line of the trigger works well: dataCell0.setValue(today_date(new Date())[2]);. But this other line updatePercent(); doesn't. But if I call this updatePercent() function from a time based trigger (in Resources), it works well. What is going wrong with this updatePercent() call?
function onEdit(){
var s = SpreadsheetApp.getActiveSheet();
if( ( s.getName() == "mySheet1" ) || (s.getName() == "mySheet2") ) { //checks that we're on the correct sheet
var r = s.getActiveCell();
if( s.getRange(1, r.getColumn()).getValue() == "PORCENT_TIME") { // If you type a porcent, it adds its date.
var dataCell0 = r.offset(0, 1);
dataCell0.setValue(today_date(new Date())[2]);
updatePercent();
}
}
}
Here the updatePercent function code:
/**
* A function to update percent values accoding to input date.
**/
function updatePercent() {
var sheet = SpreadsheetApp.getActiveSheet();
var column = getColumnNrByName(sheet, "PORCENT_TIME");
var input = sheet.getRange(2, column+1, sheet.getLastRow(), 4).getValues();
var output = [];
for (var i = 0; i < input.length; i++) {
var fulfilledPercent = input[i][0];
Logger.log("fulfilledPercent = " + fulfilledPercent);
var finalDate = input[i][3];
Logger.log("finalDate = " + input[i][3]);
if ( (typeof fulfilledPercent == "number") && (finalDate instanceof Date) ) {
var inputDate = input[i][1]; // Date when input was added.
var restPorcentPen = 100 - fulfilledPercent;
var restantDays = dataDiff(inputDate, finalDate);
var percentDay = restPorcentPen/restantDays;
Logger.log("percentDay = " + percentDay);
var passedTime = dataDiff(inputDate, new Date());
Logger.log("passedTime = " + passedTime);
var passedPorcent = passedTime * percentDay; // How much percent this passed time is?
Logger.log("passedPorcent = " + passedPorcent);
var newPorcent = (fulfilledPercent + passedPorcent);
newPorcent = Math.round(newPorcent * 100) / 100;
Logger.log("newPorcent = " + newPorcent);
var newInputDate = hoje_data(new Date())[2]; // Now update the new input date
// newPorcent = newPorcent.toFixed(2);
output.push([newPorcent, newInputDate]);
sheet.getRange(2, column+1, output.length, 2).setValues(output);
Logger.log(" ");
var column25Dec = getColumnNrByName(sheet, "PORCENT_25DEZ");
var passedTimeSince25Dec = dataDiff(new Date(2013,11,25), new Date()); // Months: January is 0;
var decPercent = (newPorcent - (passedTimeSince25Dec * percentDay)); // .toFixed(2).replace(".", ",");
decPercent = Math.round(decPercent * 100) / 100;
// if (sheet.getRange(output.length+1, column25Dec+1).getValues() == ''){
sheet.getRange(output.length+1, column25Dec+1).setValue(decPercent );
// }
var remainingYears = dataDiffYears(new Date(), finalDate);
sheet.getRange(output.length+1, column).setValue(remainingYears);
}
else {
newPorcent = "Put a final date"
output.push([newPorcent, inputDate]);
sheet.getRange(2, column+1, output.length, 2).setValues(output);
}
if (finalDate instanceof Date){
var remainingYears = dataDiffYears(new Date(), finalDate);
// Logger.log("remainingYears = " + remainingYears);
}
else {
remainingYears = "insert a valid date";
}
sheet.getRange(output.length+1, column).setValue(remainingYears);
}
}
I will guess you're using the new gSheets. Check if it will work in the old-style sheets. The new sheets' onEdit trigger has problems, particularly with getActive.
My problem was in the updatePercent() funciton. Thank you, guys!