How do I add a schema to an IXMLDOMDocument?
For example, I want to generate the XML:
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="Frob" Target="Grob"/>
</Relationships>
I can construct the DOMDocument60 object (pseudo-code):
DOMDocument60 doc = new DOMDocument60();
IXMLDOMElement relationships = doc.appendChild(doc.createElement("Relationships"));
IXMLDOMElement relationship = relationships.appendChild(doc.createElement("Relationship"));
relationship.setAttribute("Id", "rId1");
relationship.setAttribute("Type", "Frob");
relationship.setAttribute("Target", "Grob");
Now comes the question of how to add the namespace.
How to add the namespace?
If I do the obvious solution, setting an attribute on the Relationships node called xmlns:
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
through something like:
relationships.setAttribute("xmlns",
"http://schemas.openxmlformats.org/package/2006/relationships");
When the document is saved, it causes the resulting xml to be wrong:
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="Frob" Target="Grob" xmlns=""/>
</Relationships>
It places empty xmlns attributes on every other element. In this little test document it only misapplies the xmlns to one element. In the real world there are dozens, or a few million other elements with an empty xmlns attribute.
namespaceURI property
I tried setting the namespaceURI property of the Relationships element:
relationshps.namespaceURI := "http://schemas.openxmlformats.org/package/2006/relationships";
but the property is read-only.
schemas Property
The document does have a schemas property, which gets or sets an XMLSchemaCache object. But it requires an actual schema document. E.g. trying to just set a schema doesn't work:
schemas = new XMLSchemaCache60();
schemas.add('', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
doc.schemas := schemas;
But that tries to actually load the schema url, rather than not loading the schema because it isn't a URI.
Perhaps I have to randomly try other things:
schemas = new XMLSchemaCache60();
schemas.add('http://schemas.openxmlformats.org/spreadsheetml/2006/main', null);
doc.schemas := schemas;
But that causes no xmlns to be emitted.
Rather than trying to build an XML document the correct way, I could always use a StringBuilder to build the XML manually, and then have parse it into an XML Document object.
But I'd rather do it the right way.
The trick is to realize the W3C DOM Level 2 and 3 have a method createElementNS đź•—:
Creates an element with the specified namespace URI and qualified name.
Syntax
element = document.createElementNS(namespaceURI, qualifiedName);
However MSXML 6 only supports DOM Level 1.
Fortunately, W3C DOM Level 1 did have a method to create an element with a namespace: createNodeđź•—:
Creates a node using the supplied type, name, and namespace.
HRESULT createNode(VARIANT Type, BSTR name, BSTR namespaceURI, out IXMLDOMNode node);
Thus my solution is that i have to change:
relationships: IXMLDOMElement = doc.createElement("Relationships");
into:
const NODE_ELEMENT: Integer = 1;
const ns: string = "http://schemas.openxmlformats.org/package/2006/relationships";
relationships: IXMLDOMElement = doc.createNode(NODE_ELEMENT, "Relationships", namespace);
A sucky part is that every element must be created in that namespace:
function AddElementNS(IXMLDOMNode parentNode, String tagName, String namespaceURI): IXMLDOMElement;
{
doc: IXMLDOMDocument = parentNode as IXMLDOMDocument;
if (doc == null)
doc = parentNode.ownerDocument;
if (namespaceURI <> "")
Result = doc.createNode(NODE_ELEMENT, tagName, namespaceURI)
else
Result = doc.createElement(tagName);
parentNode.appendChild(Result);
}
relationships: IXMLDOMElement = AddElementNS(doc, "Relationships", ns);
relationship: IXMLDOMElement = AddElementNS(relationships, "Relationship", ns);
relationship.setAttribute("Id", "rId1");
relationship.setAttribute("Type", "Frob");
relationship.setAttribute("Target", "Grob");
Bonus Reading
Creating XML with namespaces with Javascript and MSXML đź•—
Related
String mySQLString = "select * from document where documentTitle like '%test%' ";
SearchSQL sql = new SearchSQL(mySQLString);
IndependentObjectSet s = search.fetchObjects(sql, 10, null, true);
Document doc;
PageIterator iterator = s.pageIterator();
iterator.nextPage();
for (Object object : iterator.getCurrentPage()) {
doc = (Document) object;
Properties properties = doc.getProperties();
//I am trying to get an absolute or relative path here for every document.
// for eg: /objectstorename/foldername/filename like this.
}
I have tried searching propeties and class descriptions in document . but can't able to find the path. ?
To do it all in one single query (as you are trying to do in your code) you can create a join with the ReferentialContainmentRelationship table. The property Head of this table points to the document, the property Tail points to the folder the document is filled in and the property ContainmentName is the name the document has in the folder. Use the following code to construct the document path:
SearchSQL searchSQL = new SearchSQL("SELECT R.ContainmentName, R.Tail, D.This FROM Document AS D WITH INCLUDESUBCLASSES INNER JOIN ReferentialContainmentRelationship AS R WITH INCLUDESUBCLASSES ON D.This = R.Head WHERE DocumentTitle like '%test%'");
SearchScope searchScope = new SearchScope(objectStore);
RepositoryRowSet objects = searchScope.fetchRows(searchSQL, null, null, null);
Iterator<RepositoryRow> iterator = objects.iterator();
while (iterator.hasNext()) {
RepositoryRow repositoryRow = iterator.next();
Properties properties = repositoryRow.getProperties();
Folder folder = (Folder) properties.get("Tail").getEngineObjectValue();
String containmentName = properties.get("ContainmentName").getStringValue();
System.out.println(folder.get_PathName() + "/" + containmentName);
}
Paths constructed this way can also be used to fetch the object from the object store. The query code can be optimized by using a property filter as the third argument of the fetchRows() method. Don't know how this behaves if the document is filed in multiple folders.
I suggest you explore the "Creating DynamicReferentialContainmentRelationship Objects" section of FileNet documentation:
https://www.ibm.com/support/knowledgecenter/SSNW2F_5.5.0/com.ibm.p8.ce.dev.ce.doc/containment_procedures.htm#containment_procedures__fldr_creating_a_drcr
A FileNet Ddocument can be assigned to multiple Folders, so you can have several logical "Paths" for a given document.
At end, you should get something like "Folder.get_PathName() + DynamicReferentialContainmentRelationship.get_Name()" to display the full pathname.
As described by samples in FileNet documentation, a relationship object (e.g. DynamicReferentialContainmentRelationship) controls the relation of document/folder:
myRelationshipObject.set_Head(myDocument);
myRelationshipObject.set_Tail(myFolder);
Also, keep in mind that a FileNet Document can be also a "unfiled" document, so there is no actual "pathname" or folder "relationship" to be retrieved.
tl;dr from FileNet Content Engine - Database Table for Physical path
Documents are stored among the directories at the leaf level using a hashing algorithm to evenly distribute files among these leaf directories.
I am working in a Spring 3.1 application and I need to find a String template document located in Alfresco's repository. I can already create a file in alfresco with OpenCMIS, but I couldn't figure out how to find a template, so if anyone knows how to do it or point me an example, please let me know, thanks in advance!
There are a number of options you can use. First of all, you need to have a criteria that uniquely identifies your document. Here below I'll show some, hopefully your case falls in one of them or they will inspire you towards a proper solution. The following uses pseudo code, please have a look to the OpenCMIS dev guide for working with the Java client API.
BY ID
Once you create a Document via CMIS, you get the unique ID of it that you can store in your application for later retrieval.
Map<String, Object> templateProperties = createDocumentProperties();
Folder folder = getTemplatesFolder();
ObjectId templateId = createTemplateIn(folder);
storeTemplateId(templateId.getId(), templateProperties); // persist the ID
...
// later on
...
String id = getTemplateId(); // retrieve the ID
Session session = openCMISSession();
Document template = (Document)session.getObject(id);
BY PATH
Similar to the previous example, you will have to take note of where you stored the document instead of its ID, or having a way to construct the path by hand.
String path = getTemplatePath(); // either recover it from DB or construct a path
Document template = (Document)session.getObjectByPath(path);
BY PROPERTY VALUE
Let's say you can use a specific metadata field on a template Document that allows you to easily retrieve it afterwards (e.g. you created some specific Alfresco metadata model for your use case).
String meta = TemplateProperties.TEMPLATE_ID; // e.g. my:templateId
String type = TemplateProperties.TEMPLATE_TYPE; // e.g. my:template
String templateMeta = "TEMPLATE1";
Map<String, Object> templateProperties = createDocumentProperties();
templateProperties.put(meta, templateMeta);
templateProperties.put(PropertyIds.OBJECT_TYPE_ID, type);
createTemplate(templateProperties);
...
// later on
...
String type = TemplateProperties.TEMPLATE_TYPE; // e.g. my:template
String meta = TemplateProperties.TEMPLATE_ID;
String tplId = "TEMPLATE1";
String query = String.format("SELECT * FROM % WHERE % = '%'", type, meta, tplId);
ItemIterable<QueryResult> i = session.query(query, false);
QueryResult qr = i.iterator().next(); // let's assume we have one single match
String templateId = qr.getPropertyByQueryName("cmis:objectId").getFirstValue()
Document template = (Document)session.getObject(templateId);
BY QUERY
The previous approach is not really tied to search by property name, and can be easily extended to use any kind of query that identifies your templates. Have a look at the Alfresco page on its CMIS query language implementation details to learn more ways of querying the repository.
I'm working with an xml document in C# that has multiple (100+) points of stock market data. I'd like to create objects and add them to a List<> by passing initialization values retrieved from an xml document via linq. At the moment I'm just able to run a linq query and return one of the xml fields, in the code below, the attribute "symbol." I'd also like to return the document's "LastTradeDate, DaysLow, DaysHigh, LastTradePriceOnly, Open, and Volume." From there, my custom constructor is: StockDataPoint(Symbol, TradeDate, Open, High, Low, Close, Volume). A nudge in the right direction would be great. Here's the current linq:
var makeInfo =
from s in doc.Descendants("quote")
where s.Element("LastTradeDate") != null
&& s.Attribute("symbol") != null
let dateStr = s.Element("LastTradeDate").Value
where !string.IsNullOrEmpty(dateStr)
&& DateTime.Parse(dateStr, enUS) == targetDate
select s.Attribute("symbol").Value;
Well it depends on your XML format, but you might just want something like:
...
select new StockDataPoint((string) s.Attribute("symbol"),
(DateTime) s.Attribute("TradeDate"),
(decimal) s.Attribute("Open"),
(decimal) s.Attribute("High"),
(decimal) s.Attribute("Low"),
(decimal) s.Attribute("Close"),
(long) s.Attribute("Volume"));
Note that by use the explicit operators on XAttribute, you can avoid performing the parse yourself. Indeed, you can use this earlier in your query too:
var makeInfo = from s in doc.Descendants("quote")
where s.Attribute("symbol") &&
(DateTime?) s.Attribute("LastTradeDate") == targetDate
select ...
If the target of the cast is a nullable type (either a nullable value type or a reference type) then if the attribute is missing, the result will be the null value for that type, which is very handy.
You need to create a class:
select new YourClass {
Symbol = s.Attribute("symbol").Value,
...
}
Ok, I'll explain this as much as I can...
I've got a Site Lookup Column called EEE Content Type which refers to the Site Content Item Type Types List.
Now in my custom list (which inherits from Item), I am referencing that column, and it comes up in sharepoint fine and displays the lookup values.
The issue is when I'm using SPMetal.exe to generate the types it whinges about "Key isn't present in the dictionary" and fails. So I remove the definition of the column in the parameters.xml file for SPMetal, and re-generate the classes.
Now I've manually added the property and association.
private EntityRef<SiteContentItemTypeItem> _eeeContentType;
[Association(Name = "EEE_x0020_Content_x0020_Type", Storage = "_eeeContentType", MultivalueType = AssociationType.Single, List = "Site Content Item Types")]
public SiteContentItemTypeItem EEEContentType
{
get
{
return this._eeeContentType.GetEntity();
}
set
{
this._eeeContentType.SetEntity(value);
}
}
SiteContentItemTypeItem inherits from Item so its class is empty.
But when I load the custom list I have created, i get the first entry and the EEEContentType field is null...
using (IntranetDataContext context = new IntranetDataContext("http://siteurl")) {
context.ObjectTrackingEnabled = false;
EntityList<SiteContentItem> alerts = context.GetList<SiteContentItem>("User Alerts");
SiteContentItem alert = (from tmpalert in alerts where tmpalert.Id == 1 select tmpalert).First();
SiteContentItemTypeItem contentType = alert.EEEContentType;
}
I'm all out of ideas...
Should the List value in the Association attribute be that of a collection in the class or is it refering to the actual lookup list name?
Figured it out...
Stupid of me to "assume" when creating site lookup columns via code that SharePoint would use the proper naming conventions for FieldNames with spaces.
So the fieldName was correct, its InternalName wasn't the one I was expecting. And as sharepoint linq requires the internal names, it was throwing internal exceptions in the Linq.SharePoint DLL.
This is kinda theoretical question,
I was looking at someone else' code (below) and my simple solution was to instantiate the collection outside linq, but I can guess there will be cases where I'd want to instantiate the objects inside the query, and perhaps only on a selection of elements.
Here's a simplified example of how this was being done (badly).
var pods = (from n in ids
where new Node(Convert.ToInt32(n)).HasValue("propertyName")
select new
{
Id = Convert.ToInt32(n),
Url = new Node(Convert.ToInt32(n)).Url,
Name = new Node(Convert.ToInt32(n)).Title()
}).ToList();
Irrelevant Note: in this case the Node constructor is getting data from a memory cache.
How can I improve this example to only instantiate each object once using linq?
Cheers.
Murray.
Use a let clause like this:
var pods = (
from n in ids
let id = Convert.ToInt32(n)
let node = new Node(id)
where node.HasValue("propertyName")
select new
{
Id = id,
Url = node.Url,
Name = node.Title()
}
).ToList();
For more information please see let clause (C# Reference):
In a query expression, it is sometimes
useful to store the result of a
sub-expression in order to use it in
subsequent clauses. You can do this
with the let keyword, which creates a
new range variable and initializes it
with the result of the expression you
supply. Once initialized with a value,
the range variable cannot be used to
store another value. However, if the
range variable holds a queryable type,
it can be queried.