Using Roslyn, how to enumerate members (Namespaces, classes etc) details in Visual Basic Document? - roslyn

Using Roslyn, the only mechanism for determining members of Visual Basic document appears to be:
var members = SyntaxTree.GetRoot().DescendantNodes().Where(node =>
node is ClassStatementSyntax ||
node is FunctionAggregationSyntax ||
node is IncompleteMemberSyntax ||
node is MethodBaseSyntax ||
node is ModuleStatementSyntax ||
node is NamespaceStatementSyntax ||
node is PropertyStatementSyntax ||
node is SubNewStatementSyntax
);
How do get the member name, StarLineNumber and EndLineNumber of each member?

Exists not only the one way to get it:
1) As you try: I willn't show this way for all of kind member (they count are huge and the logic is the similar), but only a one of them, for example ClassStatementSyntax:
to achive it name just get ClassStatementSyntax.Identifier.ValueText
to get start line you can use Location as one of ways:
var location = Location.Create(SyntaxTree, ClassStatementSyntax.Identifier.Span);
var startLine = location.GetLineSpan().StartLinePosition.Line;
logic for retrieving the end line looks like a logic to receive the start line but it dependents on the corresponding closing statement (some kind of end statement or self)
2) More useful way – use SemanticModel to get a data that you want:
In this way you will need to receive semantic info only for ClassStatementSyntax, ModuleStatementSyntxt and NamespaceStatementSyntax, and all of their members will be received just calling GetMembers():
...
SemanticModel semanticModel = // usually it is received from the corresponding compilation
var typeSyntax = // ClassStatementSyntax, ModuleStatementSyntxt or NamespaceStatementSyntax
string name = null;
int startLine;
int endLine;
var info = semanticModel.GetSymbolInfo(typeSyntax);
if (info.Symbol is INamespaceOrTypeSymbol typeSymbol)
{
name = typeSymbol.Name; // retrieve Name
startLine = semanticModel.SyntaxTree.GetLineSpan(typeSymbol.DeclaringSyntaxReferences[0].Span).StartLinePosition.Line; //retrieve start line
endLine = semanticModel.SyntaxTree.GetLineSpan(typeSymbol.DeclaringSyntaxReferences[0].Span).EndLinePosition.Line; //retrieve end line
foreach (var item in typeSymbol.GetMembers())
{
// do the same logic for retrieving name and lines for all others members without calling GetMembers()
}
}
else if (semanticModel.GetDeclaredSymbol(typeSyntax) is INamespaceOrTypeSymbol typeSymbol2)
{
name = typeSymbol2.Name; // retrieve Name
startLine = semanticModel.SyntaxTree.GetLineSpan(typeSymbol2.DeclaringSyntaxReferences[0].Span).StartLinePosition.Line; //retrieve start line
endLine = semanticModel.SyntaxTree.GetLineSpan(typeSymbol2.DeclaringSyntaxReferences[0].Span).EndLinePosition.Line; //retrieve end line
foreach (var item in typeSymbol2.GetMembers())
{
// do the same logic for retrieving name and lines for all others members without calling GetMembers()
}
}
But attention, when you have a partial declaration your DeclaringSyntaxReferences will have a couple items, so you need to filter SyntaxReference by your current SyntaxTree

Related

Duplicate value in list getting saved multiple times (Single record)

I am trying to update SObject record depending on the crtieria, id it matches create a SObject variable and adding values and then finally adding this into a List.When a match is found the Sobject variable here'PRrecordupdate' gets created and values get assigend but as I add it in a list,the original value already present in the list is removed and this latest PRrecordupdate get saved multiple times.Please look into the code below and let me know what I am doing wrong.Thanks
//CODE
List<Domain_review__C> newPR = new List<Domain_review__C>();
List<Domain_review__C> PRCollectionupdate = new List<Domain_review__C>();
Domain_review__C PRrecordupdate = new Domain_review__C();
map<id,domain_review__C> PRmap = new map<id,domain_review__C>();
List<Domain_Review__c> PR = [Select id,name,Property_Name_text__c from Domain_Review__c Where Exchange_Quality_ID__c =: a0W56000002PgXfEAK];
For(domain_review__c a: PR)
{
For(Domain_review__c b: newPR)
{
if(a.Property_Name_text__c == b.property_text__c)
{
PRrecordupdate.Id = a.Id;
PRrecordupdate.Approval_Status__c = b.Approval_Status__c;
PRrecordupdate.Rejection_Reason__c = b.Rejection_Reason__c;
PRrecordupdate.seller_name__c = b.seller_name__c;
PRrecordupdate.Inventory_Relationship__c = b.Inventory_Relationship__c;
PRCollectionupdate.add(PRrecordupdate);
//?? here, it's removing the earlier record and updating with new record multiple times, means at the end in PRCollectionupdate list only one record is saved multiple times//
}
}
}
PRmap.putall(PRCollectionupdate);
if(PRmap.size()>0){
update PRmap.values();
}

Univocity - parse each TSV file row to different Type of class object

I have a tsv file which has fixed rows but each row is mapped to different Java Class.
For example.
recordType recordValue1
recordType recordValue1 recordValue2
for First row I have follofing class:
public class FirstRow implements ItsvRecord {
#Parsed(index = 0)
private String recordType;
#Parsed(index = 1)
private String recordValue1;
public FirstRow() {
}
}
and for second row I have:
public class SecondRow implements ItsvRecord {
#Parsed(index = 0)
private String recordType;
#Parsed(index = 1)
private String recordValue1;
public SecondRow() {
}
}
I want to parse the TSV file directly to the respective objects but I am falling short of ideas.
Use an InputValueSwitch. This will match a value in a particular column of each row to determine what RowProcessor to use. Example:
Create two (or more) processors for each type of record you need to process:
final BeanListProcessor<FirstRow> firstProcessor = new BeanListProcessor<FirstRow>(FirstRow.class);
final BeanListProcessor<SecondRow> secondProcessor = new BeanListProcessor<SecondRow>(SecondRow.class);
Create an InputValueSwitch:
//0 means that the first column of each row has a value that
//identifies what is the type of record you are dealing with
InputValueSwitch valueSwitch = new InputValueSwitch(0);
//assigns the first processor to rows whose first column contain the 'firstRowType' value
valueSwitch.addSwitchForValue("firstRowType", firstProcessor);
//assigns the second processor to rows whose first column contain the 'secondRowType' value
valueSwitch.addSwitchForValue("secondRowType", secondProcessor);
Parse as usual:
TsvParserSettings settings = new TsvParserSettings(); //configure...
// your row processor is the switch
settings.setProcessor(valueSwitch);
TsvParser parser = new TsvParser(settings);
Reader input = new StringReader(""+
"firstRowType\trecordValue1\n" +
"secondRowType\trecordValue1\trecordValue2");
parser.parse(input);
Get the parsed objects from your processors:
List<FirstRow> firstTypeObjects = firstProcessor.getBeans();
List<SecondRow> secondTypeObjects = secondProcessor.getBeans();
The output will be*:
[FirstRow{recordType='firstRowType', recordValue1='recordValue1'}]
[SecondRow{recordType='secondRowType', recordValue1='recordValue1', recordValue2='recordValue2'}]
Assuming you have a sane toString() implemented in your classes
If you want to manage associations among the objects that are parsed:
If your FirstRow should contain the elements parsed for records of type SecondRow, simply override the rowProcessorSwitched method:
InputValueSwitch valueSwitch = new InputValueSwitch(0) {
#Override
public void rowProcessorSwitched(RowProcessor from, RowProcessor to) {
if (from == secondProcessor) {
List<FirstRow> firstRows = firstProcessor.getBeans();
FirstRow mostRecentRow = firstRows.get(firstRows.size() - 1);
mostRecentRow.addRowsOfOtherType(secondProcessor.getBeans());
secondProcessor.getBeans().clear();
}
}
};
The above assumes your FirstRow class has a addRowsOfOtherType method that takes a list of SecondRow as parameter.
And that's it!
You can even mix and match other types of RowProcessor. There's another example here that demonstrates this.
Hope this helps.

Numbered lists continues when Apps Script appends template-content into document

I have an Apps Script that copies the content of an template-file to the end of a document. It works with one minor annoyance: the numbered list continues from one copy to the next.
I have many different templates that users can append to the end of the document. Each template is stored in its own Document.
function addSub(template_id){
var mainBody = DocumentApp.getActiveDocument().getBody();
var tempBody = DocumentApp.openById(template_id).getBody();
for(var i = 0;i<tempBody .getNumChildren();i++){
var element = tempBody .getChild(i);
if(element.getType() == DocumentApp.ElementType.TABLE)
mainBody.appendTable(element.copy());
else if(element.getType() == DocumentApp.ElementType.PARAGRAPH)
mainBody.appendParagraph(element.copy());
else if(element.getType() == DocumentApp.ElementType.LIST_ITEM)
mainBody.appendListItem(element.copy());
else if(element.getType() == DocumentApp.ElementType.PAGE_BREAK)
mainBody.appendPageBreak(element.copy());
}
}
It could look like this: ( I want the list to reset for each new copy of the template)
table with name of this template
some raw text
List item1
List item2
table with name of this template
some raw text
List item1
List item2
Do you now that ListItems have an ID, which is a STRING and you can access it via myListItem.getListId().
It seems that all your ListItems have the same ID.
If this is the case, the numbering has to be as you described.
Why do they have the same ListID?
I don't know.
Seems that the body.appendListem method always chooses the same listId.
I didn't test it yet, but you could try to set the listID of the newly append ListItem to that of the original document, if they are different.
Yes, i know, the .copy() method should enclose this information, but the body.appendListItem method may not care.
So, you could try to first save the detached copy of the listItem.
Then append it to the new body.
And then set the id of the newly appended listItem to that of the detached copy.
It's stupid, i know, but it may help.
Didn't try it yet.
And i have little experience with listItems, bit what i saw up to now is that there seems to be only one ListId in the body of a document, if you append or insert listItems.
This could be the cause of the problem.
Hope this helps.
After Richard Gantz solved it, It was corrected by this code:
var listItemDictionary = {};//top
...
else if(element.getType() == DocumentApp.ElementType.LIST_ITEM){
var listCopy = element.copy().asListItem()
var lcID = listCopy.getListId();
if (listItemDictionary[lcID] == null){
var tempLI = mainBody.appendListItem("temp")
listItemDictionary[lcID] = tempLI;
}
Logger.log(lcID)
mainBody.insertListItem(childIndex+j, listCopy.setListId(listItemDictionary[lcID]));
}
...
if(listItemDictionary){//bottom
mainBody.appendParagraph("");
for(var key in listItemDictionary){
listItemDictionary[key].clear().removeFromParent()
}
}
Based on the answer from Niklas Ternvall/Richard Gantz, I found a simpler solution for the case of each template having no more than one list.
function addSub(template_id) {
var mainBody = DocumentApp.getActiveDocument().getBody();
var tempBody = DocumentApp.openById(template_id).getBody();
var listID = null;
for(var i = 0;i<tempBody.getNumChildren();i++){
var element = tempBody.getChild(i).copy();
var type = element.getType();
if(type == DocumentApp.ElementType.TABLE)
mainBody.appendTable(element);
else if(type == DocumentApp.ElementType.PARAGRAPH)
mainBody.appendParagraph(element);
else if(type == DocumentApp.ElementType.LIST_ITEM){
if(listID==null) // First list item
listID = mainBody.appendListItem('temp'); // Define new listID
mainBody.appendListItem(element).setListId(listID); // Apply to copy
}
else if(type == DocumentApp.ElementType.PAGE_BREAK)
mainBody.appendPageBreak(element);
}
mainBody.removeChild(listID); // Delete temporary list item
}
Each time you call the function, listID=null is the indicator for whether or not there have been any list items in the template. When you get to the first list item, appending the text 'temp' forces a new list and hence a new listID that you can apply to the list items from the template. After you finish going through the template, mainBody.removeChild(listID) removes 'temp' from the top of your list.
This solution worked for me when using one template 100 times in one document, essentially as a mail merge. I'm fairly new to Apps Script, so I would appreciate any feedback if there is a reason this wouldn't work for a single-list template.

Qjson handling an returned array of ojbects

I'm using Qjson to parse a json object that is returned from a web service. I'm stuck on handling an array of complex ojects.
At the first level the web service returns a map consisting of "error", "id", and "return". If there are no errors I can get the first level value by using
nestedMap = m_jsonObject["result"].toMap();
group = new Group();
group->Caption = nestedMap["Caption"].toString();
group->CollectionCount = nestedMap["CollectionCount"].toInt();
I can even get a date item value that is at the second level using
group->ModifiedOn = nestedMap["ModifiedOn"].toMap()["Value"].toDateTime();
I have an object called "Elements" that consists of 29 key-value pairs. The web service is returning an array of these "Elements" and I am unable to find the right way to parse it. In the header file the container for the elements is defined as
QList<GroupElement> Elements;
The line
group->Elements = nestedMap["Elements"].toList();
causes the compiler to throw an error 'error: no match for 'operator=' in '((MyClass*)this)->MyClass::group->Group::Elements = QVariant::toMap() const()'
I would like to learn the correct syntax to put this element into the class.
Update: I wrote another function to convert the QVariantMap object to a
first:
The group-> Elements object was changed to a
class ParentClass{
QList<SharedDataPointer<Address> > Elements;
other class memmbers...
};
Second:
A method to convert the QMap object to an Address object was created
QSharedDataPointer<Address>
API_1_6::mapToAddress(QVariantMap o)
{
QSharedDataPointer<Address> address (new Address());
address-> FirstName = o["FirstName"].toString();
address->LastName = o["LastName"].toString();
address->CompanyName = o["CompanyName"].toString();
address->Street = o["Street"].toString();
address->Street2 = o["Street2"].toString();
address->City = o["City"].toString();
address->Zip = o["Zip"].toString();
address-> State = o["State"].toString();
address->Country = o["Country"].toString();
address->Phone = o["Phone"].toString();
address->Phone2 = o["Phone2"].toString();
address-> Fax = o["Fax"].toString();
address-> Url = o["Url"].toString();
address->Email = o["Email"].toString();
address->Other = o["Other"].toString();
return address;
}
third: In the code, foreach is used to walk through the list and create and store the new objects
// get the list of the elements
elementsList = nestedMap["Elements"].toList();
// Add the element, converted to the new type, to the Elements object of the'parent' class
foreach(QVariant qElement, elementsList){
group-> Elements.append(mapToAddress(qElement))
}

Command "timing out" during Sitecore automated item creation

I have a command that I've added to a "national" node in the Sitecore Editor. The command generates a list of the 50 state nodes beneath it. It then goes through the state nodes (1 at a time) and generates a list of local nodes under each state node. Within the lists of local nodes, I iterate through them and check if the new item exists - if it does not, I add (create) it as a child under the local node. Ultimately, there are nearly 300 local items that are being added during the course of this command.
Is there a more efficient way to do this (fast query to get the 300 local nodes into one list, then check if the item exists, and create it)? If so, I'm not sure how to do that...
I'm not sure what is the most costly part of the operation. Ultimately, I'm still doing up to 300 separate queries to check if it's there, followed by insert statements, so that may still take a while to run. If so, which setting in web config will increase the applicable "timeout" setting in Sitecore? The sample structure is as follows:
//Derive the template from the name of the item (page) that was passed in - this assumes that the template name and the item name are the same
Sitecore.Data.Database database = Sitecore.Data.Database.GetDatabase("master");
TemplateItem contentPageTemplate = database.SelectSingleItem("fast:/sitecore/Templates/User Defined/Home/Pages/Local Site/" + newPage);
Sitecore.Data.Items.Item[] stateNodes = null;
Sitecore.Data.Items.Item[] localNodes = null;
Item localHomePage = null;
Item newLocalPage = null;
int webBusinessID = 0;
string ID = "";
WebBusiness business;
//Get all of the immediate child nodes (state pages) under the "parent" node ("National Locations") - and put them into a list or array
stateNodes = database.SelectItems("fast:/sitecore/content/Home/National Locations/*");
for (int i = 0; i < stateNodes.Length; i++)
{
if (stateNodes[i].Children.Count > 0)
{
localNodes = database.SelectItems("fast:/sitecore/content/Home/National Locations/" + stateNodes[i].Fields["State Abbreviation"].ToString() + "/*");
}
else
{
//Do nothing
}
for (int j = 0; j < localNodes.Length; j++)
{
localHomePage = localNodes[j];
if (localHomePage.Publishing.IsPublishable(DateTime.Now, false) == true)
{
//If the new page does not exist, create it
if (localHomePage.Children[newPage] == null)
{
newLocalPage = localHomePage.Add(newPage, contentPageTemplate);
counter = counter + 1;
}
else
{
//Additional business logic
}
}
}
}
Unless I'm missing logic/code you don't have there, I think you can simply trim this down to one query to get to the local nodes by changing the XPath:
localNodes = database.SelectItems("fast:/sitecore/content/Home/National Locations/*/*");
The change is to get all immediate sub-items of the immediate sub-items of "National Locations"