Tridion Core service - working with a Hierarchical Taxonomy - tridion

I'm using the Tridion Core Service (Tridion 2011 SP1) to retrieve a list of keywords for given Category ID.
CoreService2010Client client = new CoreService2010Client();
XElement xmlCategoryKeywords = client.GetListXml(category.Id,
new KeywordsFilterData());
This returns what seems to be a flat XML structure representing our taxonomy which is 4 levels deep.
The documentation details an approach for working with this:
var categoryKeywords = xmlCategoryKeywords.Elements().Select(element =>
element.Attribute("ID").Value).Select(id => (KeywordData)client.Read(id, null)
);
foreach (KeywordData keyword in categoryKeywords)
{
Console.WriteLine("\t Keyword ID={0}, Title={1}", keyword.Id, keyword.Title);
}
However this will only list each Keyword. The KeywordData object contains property ParentKeywords so it would be possible to build the hierarchy in memory.
Is it possible to retrieve XML from the Core Service with a hierarchical structure? Or an easier way to work with this data?

One way is to use TaxonomiesOwlFilterData:
string publicationId = "tcm:0-3-1";
var filter = new TaxonomiesOwlFilterData();
filter.RootCategories = new[] {new LinkToCategoryData{ IdRef = "tcm:3-158-512"},};
var list = ClientAdmin.GetListXml(publicationId, filter);
As you see it is called on publication, but you can narrow it down to one or more categories. It will return you scary XML list that you can further process like this:
XNamespace tcmc = publicationId + "/Categories#";
XNamespace rdf = "http://www.w3.org/1999/02/22-rdf-syntax-ns#";
XNamespace tcmt = "http://www.tridion.com/ContentManager/5.2/Taxonomies#";
var taxonomyTree = new Dictionary<string, List<string>>();
var keywordNodes = list.Descendants(tcmc + "cat");
foreach (var keywordNode in keywordNodes)
{
var parents = new List<string>();
var parentNodes = keywordNode.Descendants(tcmt + "parentKeyword");
if (parentNodes.Count() > 0)
{
foreach (var parentNode in parentNodes)
{
parents.Add(parentNode.Attribute(rdf + "resource").Value);
}
}
taxonomyTree.Add(keywordNode.Attribute(rdf + "about").Value, parents);
}
As a result you will get unordered list of your keywords and corresponding parents that you can further process as you like. Item that has no parent is obviously a parent keyword. It might not be the most beatiful solution, but at least you will need only one call to server and not read each keyword.

You could process each branch, level by level. Here's some code I've been playing around with that does that:
CoreService2010Client client = new CoreService2010Client("basicHttp_2010");
KeywordsFilterData keywordsDataFilter = new KeywordsFilterData()
{
BaseColumns = ListBaseColumns.IdAndTitle,
IsRoot = true
};
UsingItemsFilterData usingItemsFilter = new UsingItemsFilterData()
{
BaseColumns = ListBaseColumns.IdAndTitle,
ItemTypes = new[] { ItemType.Keyword },
InRepository = new LinkToRepositoryData() { IdRef = "tcm:0-1-1" }
};
XElement parents = client.GetListXml("tcm:1-272-512", keywordsDataFilter);
foreach (XElement parent in parents.Descendants())
{
// Do something with the parent (top level) KW
XElement children = client.GetListXml(parent.Attribute("ID").Value, usingItemsFilter);
foreach (XElement child in children.Descendants())
{
// Do something with the child KW
}
}
I've found in the past that processing a flat list in to a hierarchy (in my case a list of all SGs in a Publication) created a massive overhead compared to processing a branch at a time. Of course I should caveat that by saying that I tried that with an old (early 5.x) version of Tridion so things may have improved since then.

Tridion 2011 SP1 comes with a new CoreService EndPoint. CoreService 2011. Its recommended to use the latest endpoint. Latest endpoint has new functionalists also bug fixes. SP1 also has a default coreservice client proxy that u can use directly in your code.

Related

Properties in PublishTransactionFilterData to narrow down the search using Tridion Core Services

I am using Tridion Core Service to get all the Components published from a publication on a specific target. The code is as below.Since there can be lot of components and the result obtained using this code contains all items, Is there any way to narrow down the results (like providing ItemType and recursive as false).
var filter = new PublishTransactionsFilterData
{
PublicationTarget = new LinkToPublicationTargetData { IdRef = targetId },
ForRepository = new LinkToRepositoryData { IdRef = GetPublication(publicationId)},
BaseColumns = ListBaseColumns.IdAndTitle,
};
XElement t= Instance.GetSystemWideListXml(filter);
var v = t.Elements().Where(k => k.Attribute("ItemType").Value == "16");
First of all your code will always return null, as because of ListBaseColumns.IdAndTitle there will be no ItemType attribute. The only way to narrow down the results are properties you can find on PublishTransactionsFilterData, which are:
EndDate, StartDate to search only inside this timestamp
Priority, if you know it
PublishedBy, if you know who published it
All the rest is post filtering, which is also ok. You can filter on everything you have in XML. Sample XML looks like this:
<tcm:Item ID="tcm:0-241-66560" Title="page" Allow="24576" Deny="67108864" Icon="T64L0P0" ItemType="64" ItemID="tcm:2-72-64" State="4" StateChangeDate="2013-03-11T14:53:55" Publication="Test" PublicationTarget="Local" ItemPath="\Test\New Structure Group" Action="0" ScheduleDate="" UserId="tcm:0-11-65552" User="domain\user" Priority="4" Managed="0" />
You can postfilter on any of the attributes

Reading all components from folder and subfolder

I am working on Tridon 2009 using .NET Templating C# 2.0
I need to read all the components from folders and its subfolder.
If in my code I write:
OrganizationalItem imageFolder =
(OrganizationalItem)m_Engine.GetObject(comp.OrganizationalItem.Id);
I am able to read all the components in subfolder from the place where indicator component is present, but I am not able to read other components present in the folder where indicator is present.
But If I write
OrganizationalItem imageFolder = (OrganizationalItem)m_Engine.GetObject(
comp.OrganizationalItem.OrganizationalItem.Id);
then I am able to read only folder where indicator component is present.
Below is my code.
XmlDocument doc = xBase.createNewXmlDocRoot("ImageLibrary");
XmlElement root = doc.DocumentElement;
Filter filter = new Filter();
Component comp = this.GetComponent();
filter.Conditions["ItemType"] = ItemType.Folder;
filter.Conditions["Recursive"] = "true";
OrganizationalItem imageFolder =
(OrganizationalItem)m_Engine.GetObject(comp.OrganizationalItem.Id);
XmlElement itemList = imageFolder.GetListItems(filter);
foreach (XmlElement itemImg in itemList)
{
filter.Conditions["ItemType"] = ItemType.Component;
filter.Conditions["BasedOnSchema"] = comp.Schema.Id;
OrganizationalItem imgFolder =
(OrganizationalItem)m_Engine.GetObject(itemImg.GetAttribute("ID")
.ToString());
XmlElement imageLibs = imgFolder.GetListItems(filter);
doc = this.createImageNodes(imageLibs, doc, filter, comp);
foreach (XmlElement imglib in imageLibsList)
{
XmlElement imageroot = doc.CreateElement("Image");
XmlElement uploadeddateNode = doc.CreateElement("DateUploaded");
Component imgComp =
(Component)m_Engine.GetObject(imglib.GetAttribute("ID"));
}
}
Please suggest.
I see a lot of superfluous code on your snippet regarding the question "Reading all components from folder and subfolder"
But answering the question itself, when you are doing:
OrganizationalItem imageFolder = (OrganizationalItem)m_Engine.GetObject(comp.OrganizationalItem.Id);
Your are not being able to read components present on that folder, because you have previously set the filter to folders only on the following line:
filter.Conditions["ItemType"] = ItemType.Folder;
Solution:
If you want to retrieve all components on the "indicator component" folder and below, you need to set the filter on your first search as following:
filter.Conditions["Recursive"] = "true";
filter.Conditions["ItemType"] = ItemType.Component;
filter.Conditions["BasedOnSchema"] = comp.Schema.Id;
And perform the search:
OrganizationalItem imageFolder = (OrganizationalItem)m_Engine.GetObject(comp.OrganizationalItem.Id);
XmlElement itemList = imageFolder.GetListItems(filter);
Pretty basic stuff. Try to avoid using Filter class, since it was deprecated in 2009, and use GetListItems as much as possible as fetching lists is ALWAYS faster.
public class GetComponentsInSameFolder : ITemplate
{
public void Transform(Engine engine, Package package)
{
TemplatingLogger log = TemplatingLogger.GetLogger(GetType());
if (package.GetByName(Package.ComponentName) == null)
{
log.Info("This template should only be used with Component Templates. Could not find component in package, exiting");
return;
}
var c = (Component)engine.GetObject(package.GetByName(Package.ComponentName));
var container = (Folder)c.OrganizationalItem;
var filter = new OrganizationalItemItemsFilter(engine.GetSession()) { ItemTypes = new[] { ItemType.Component } };
// Always faster to use GetListItems if we only need limited elements
foreach (XmlNode node in container.GetListItems(filter))
{
string componentId = node.Attributes["ID"].Value;
string componentTitle = node.Attributes["Title"].Value;
}
// If we need more info, use GetItems instead
foreach (Component component in container.GetItems(filter))
{
// If your filter is messed up, GetItems will return objects that may
// not be a Component, in which case the code will blow up with an
// InvalidCastException. Be careful with filter.ItemTypes[]
Schema componentSchema = component.Schema;
SchemaPurpose purpose = componentSchema.Purpose;
XmlElement content = component.Content;
}
}
}
I'd think you'd want to collect sub folders and recursively call your function for each of them, which seems like what you're trying to achieve.
Is this function called createImageNodes() and where do you set imageLibsList?
It looks like you're treating each item as a folder in your first loop, what about the components?

BasedOnSchemas option in Tridion 2011 CoreService

I'm trying to understand the purpose of the BasedOnSchemas option in the OrganizationalItemItemsFilterData filter.
The documentation clearly states:
"Gets or sets the BasedOnSchemas condition to return only items that are using the given schemas"
So it should be possible to only retrieve components of a specific schema, right?
here's my code:
LinkToSchemaData[] schemaLinks = new[] {
new LinkToSchemaData { IdRef = "tcm:113-362325-8" }
};
OrganizationalItemItemsFilterData filter =
new OrganizationalItemItemsFilterData();
filter.BaseColumns = ListBaseColumns.Extended;
filter.ItemTypes = new ItemType[] { ItemType.Component };
filter.Recursive = true;
filter.BasedOnSchemas = schemaLinks;
XElement items = client.GetListXml("tcm:113-14192-2", filter);
The XElement items will however, contain multiple types of components, not only those of schema tcm:113-362325-8
How can I retrieve only those components that are based on my schema?
Using both BasedOnSchemas and Recursive = true is not supported. Remove the the recursiveness and you'll find that the schema filter works.
If you want to get a "recursive" list of all Components for a certain Schema, consider doing a WhereUsed on the Schema.
GetListXml("tcm:5-59-8", new UsingItemsFilterData())

Get type of metadata fields of a Metadata schema

I want to get the all fields along with type/datatype of the metadata fields of a Metadata schema.
I have written below sample code to achieve the functionality and I am able to get Name, Description etc but could not find any property with type/dataType. If anyone of you have any idea, please suggest...
var client = new SessionAwareCoreService2010Client();
client.ClientCredentials.Windows.ClientCredential.UserName = "myUserName";
client.ClientCredentials.Windows.ClientCredential.Password = "myPassword";
client.Open();
if (client.State == System.ServiceModel.CommunicationState.Opened)
{
var schemaUri = "tcm:1-47-8";
var fields= client.ReadSchemaFields(schemaUri, true, new ReadOptions());
var fieldName = fields.MetadataFields[0].Name;
}
To know the type of a field, you only need to examine the .NET type of the field.
I typically use an "is" check, but you can also call GetType if you want.
For example:
var client = new SessionAwareCoreService2010Client();
client.ClientCredentials.Windows.ClientCredential.UserName = "myUserName";
client.ClientCredentials.Windows.ClientCredential.Password = "myPassword";
client.Open();
if (client.State == System.ServiceModel.CommunicationState.Opened)
{
var schemaUri = "tcm:1-47-8";
var fields= client.ReadSchemaFields(schemaUri, true, new ReadOptions());
foreach (var field in fields.MetadataFields)
{
if (field is SingleLineTextFieldDefinitionData)
{
// Do something specifically for single-line text fields
}
}
}
The ReadSchemaFields method exposes only the definition of the fields. So it is essentially a wrapper around the properties you enter while you define the field in a schema.
The Content and Metadata are exposed by ComponentData.Content and ComponentData.Metadata respectively. But those are exposed as XML strings, so you will have to do your own parsing of them.
If you (understandably) don't feel like that, have a look at this helper class: http://code.google.com/p/tridion-practice/wiki/ChangeContentOrMetadata
You might also want to read my answer to this question: Updating Components using the Core Service in SDL Tridion 2011

Get List of Localized Items

I need to get the list of localized items of a publication programatically using coreservice in tridion. Could any one suggest me.
I would use the GetListXml method and specify a BluePrintChainFilterData filter object.
var subjectId = "[TCM Uri of your item]";
var filter = new BluePrintChainFilterData
{
Direction = BluePrintChainDirection.Down
};
var subjectBluePrintChainList = coreServiceClient.GetListXml(subjectId, filter);
You then still need to verify the localized items from the received list.
This wasn't in my original answer, and probably isn't complete because I don't take into account namespaces, but the following would work to select the localized (not shared) items.
var localizedItems = subjectBluePrintChainList.Elements("Item")
.Where(element => "false".Equals(element.Attribute("IsShared").Value, StringComparison.OrdinalIgnoreCase));
The only way I know is to use search functionality:
var searchQuery = new SearchQueryData();
searchQuery.BlueprintStatus = SearchBlueprintStatus.Localized;
searchQuery.FromRepository = new LinkToRepositoryData{IdRef = "tcm:0-5-1"};
var resultXml = ClientAdmin.GetSearchResultsXml(searchQuery);
var result = ClientAdmin.GetSearchResults(searchQuery);

Resources