private void GenerateCollectionOutput(RdfXmlWriterContext context, INode key) { OutputRdfCollection c = context.Collections[key]; c.HasBeenWritten = true; if (c.IsExplicit) { if (c.Triples.Count == 0) { //If No Triples then an isolated blank node so add rdf:nodeID and return context.Writer.WriteAttributeString("rdf", "nodeID", NamespaceMapper.RDF, context.BlankNodeMapper.GetOutputID(((IBlankNode)key).InternalID)); return; } //First see if there is a typed triple available (only applicable if we have more than one triple) INode rdfType = context.Graph.CreateUriNode(UriFactory.Create(RdfSpecsHelper.RdfType)); Triple typeTriple = c.Triples.FirstOrDefault(t => t.Predicate.Equals(rdfType) && t.Object.NodeType == NodeType.Uri); if (typeTriple != null) { //Should be safe to invoke GenerateSubjectOutput but we can't allow rdf:Description this.GenerateSubjectOutput(context, c.Triples, false); } else { //Otherwise we invoke GeneratePredicateOutput (and use rdf:parseType="Resource" if there was more than 1 triple) context.Writer.WriteAttributeString("rdf", "parseType", NamespaceMapper.RDF, "Resource"); foreach (Triple t in c.Triples) { this.GeneratePredicateOutput(context, t); context.TriplesDone.Add(t); } } } else { //If No Triples then use rdf:about rdf:nil and return if (c.Triples.Count == 0) { context.Writer.WriteAttributeString("rdf", "about", NamespaceMapper.RDF, RdfSpecsHelper.RdfListNil); return; } //Going to need rdf:parseType="Resource" on current predicate context.Writer.WriteAttributeString("rdf", "parseType", NamespaceMapper.RDF, "Resource"); this.GenerateCollectionItemOutput(context, c); } }
/// <summary> /// Helper method which finds Collections expressed in the Graph which can be compressed into concise collection syntax constructs in some RDF syntaxes /// </summary> /// <param name="context">Writer Context</param> /// <param name="mode">Collection Search Mode</param> public static void FindCollections(ICollectionCompressingWriterContext context, CollectionSearchMode mode) { //Prepare the RDF Nodes we need INode first, rest, nil; first = context.Graph.CreateUriNode(UriFactory.Create(RdfSpecsHelper.RdfListFirst)); rest = context.Graph.CreateUriNode(UriFactory.Create(RdfSpecsHelper.RdfListRest)); nil = context.Graph.CreateUriNode(UriFactory.Create(RdfSpecsHelper.RdfListNil)); //First we're going to look for implicit collections we can represent using the //brackets syntax of (a b c) if (mode == CollectionSearchMode.All || mode == CollectionSearchMode.ImplicitOnly) { //Find all rdf:rest rdf:nil Triples foreach (Triple t in context.Graph.GetTriplesWithPredicateObject(rest, nil)) { //If has named list node cannot compress if (t.Subject.NodeType != NodeType.Blank) { break; } //Build the collection recursively OutputRdfCollection c = new OutputRdfCollection(false); //Get the thing that is the rdf:first related to this rdf:rest IEnumerable <Triple> firsts = context.Graph.GetTriplesWithSubjectPredicate(t.Subject, first);//context.Graph.GetTriples(relfirstsel).Distinct(); Triple temp; if (firsts.Count() > 1) { //Strange error throw new RdfOutputException(WriterErrorMessages.MalformedCollectionWithMultipleFirsts); } else { //Stick this item onto the Stack temp = firsts.First(); c.Triples.Add(temp); } //See if this thing is the rdf:rest of anything else do { IEnumerable <Triple> ts = context.Graph.GetTriplesWithPredicateObject(rest, firsts.First().Subject); //Stop when there isn't a rdf:rest if (ts.Count() == 0) { break; } foreach (Triple t2 in ts) { firsts = context.Graph.GetTriplesWithSubjectPredicate(t2.Subject, first).Distinct(); if (firsts.Count() > 1) { //Strange error throw new RdfOutputException(WriterErrorMessages.MalformedCollectionWithMultipleFirsts); } else { //Stick this item onto the Stack temp = firsts.First(); //If Item is a named list node cannot compress if (temp.Subject.NodeType != NodeType.Blank) { break; } c.Triples.Add(temp); } } } while (true); //Can only compress if every List Node has a Blank Node Subject if (c.Triples.All(x => x.Subject.NodeType == NodeType.Blank)) { context.Collections.Add(firsts.First().Subject, c); } } } //Now we want to look for explicit collections which are representable //using Blank Node syntax [p1 o1; p2 o2; p3 o3] if (mode == CollectionSearchMode.All || mode == CollectionSearchMode.ExplicitOnly) { foreach (IBlankNode b in context.Graph.Nodes.BlankNodes) { if (context.Collections.ContainsKey(b)) { continue; } List <Triple> ts = context.Graph.GetTriples(b).ToList(); ts.RemoveAll(t => t.Predicate.Equals(first)); ts.RemoveAll(t => t.Predicate.Equals(rest)); if (ts.Count <= 1) { //This Blank Node is only used once //Add an empty explicit collection - we'll interpret this as [] later context.Collections.Add(b, new OutputRdfCollection(true)); } else { if (context.Graph.GetTriplesWithObject(b).Count() == 1) { ts.RemoveAll(t => t.Object.Equals(b)); } OutputRdfCollection c = new OutputRdfCollection(true); c.Triples.AddRange(ts); context.Collections.Add(b, c); } } } //If no collections found then no further processing if (context.Collections.Count == 0) { return; } //Once we've found all the Collections we need to check which are actually eligible for compression List <KeyValuePair <INode, OutputRdfCollection> > cs = context.Collections.ToList(); //1 - If all the Triples pertaining to a particular Node are in the Collection then a collection is not eligible foreach (KeyValuePair <INode, OutputRdfCollection> kvp in cs) { OutputRdfCollection c = kvp.Value; if (c.IsExplicit) { //For explicit collections if all Triples mentioning the Target Blank Node are in the Collection then can't compress //If there are no Triples in the Collection then this is a single use Blank Node so can always compress if (c.Triples.Count > 0 && c.Triples.Count == context.Graph.GetTriples(kvp.Key).Count()) { context.Collections.Remove(kvp.Key); } } else { //For implicit collections if the number of Triples in the Collection is exactly ((t*3) - 1) those in the Graph then //can't compress i.e. the collection is not linked to anything else //Or if the number of mentions compared to the expected mentions differs by more than 1 then //can't compress i.e. the collection is linked to more than one thing int mentions = context.Graph.GetTriples(kvp.Key).Count(); int expectedMentions = ((c.Triples.Count * 3) - 1); if (expectedMentions == mentions || mentions - expectedMentions != 1) { context.Collections.Remove(kvp.Key); } } } if (context.Collections.Count == 0) { return; } //2 - Look for cyclic collection dependencies cs = context.Collections.OrderByDescending(kvp => kvp.Value.Triples.Count).ToList(); //First build up a dependencies table Dictionary <INode, List <INode> > dependencies = new Dictionary <INode, List <INode> >(); foreach (KeyValuePair <INode, OutputRdfCollection> kvp in cs) { OutputRdfCollection c = kvp.Value; //Empty Blank Node Collections cannot be cyclic i.e. [] if (c.Triples.Count == 0) { continue; } //Otherwise check each Object of the Triples for other Blank Nodes List <INode> ds = new List <INode>(); foreach (Triple t in c.Triples) { //Only care about Blank Nodes which aren't the collection root but are the root for another collection if (t.Object.NodeType == NodeType.Blank && !t.Object.Equals(kvp.Key) && context.Collections.ContainsKey(t.Object)) { ds.Add(t.Object); } } if (ds.Count > 0) { dependencies.Add(kvp.Key, ds); } } //Now go back through that table looking for cycles foreach (INode n in dependencies.Keys) { List <INode> ds = dependencies[n]; if (ds.Count == 0) { continue; } foreach (INode d in ds.ToList()) { if (dependencies.ContainsKey(d)) { ds.AddRange(dependencies[d]); } } //We can tell if there is a cycle since ds will now contain n if (ds.Contains(n)) { context.Collections.Remove(n); } } if (context.Collections.Count == 0) { return; } //Finally fill out the TriplesDone for each Collection foreach (KeyValuePair <INode, OutputRdfCollection> kvp in context.Collections) { OutputRdfCollection c = kvp.Value; if (c.IsExplicit) { foreach (Triple t in c.Triples) { context.TriplesDone.Add(t); } } else { INode temp = kvp.Key; for (int i = 0; i < c.Triples.Count; i++) { context.TriplesDone.Add(c.Triples[i]); if (i < c.Triples.Count - 1) { context.TriplesDone.Add(new Triple(c.Triples[i].Subject, rest, c.Triples[i + 1].Subject)); } else { context.TriplesDone.Add(new Triple(c.Triples[i].Subject, rest, nil)); } } } } //As a final sanity check look for any Explicit Collection Key which is used more than once cs = context.Collections.ToList(); foreach (KeyValuePair <INode, OutputRdfCollection> kvp in cs) { OutputRdfCollection c = kvp.Value; if (c.IsExplicit) { int mentions = context.Graph.GetTriples(kvp.Key).Where(t => !context.TriplesDone.Contains(t)).Count(); if (mentions - 1 > c.Triples.Count) { context.Collections.Remove(kvp.Key); } } } }
private void GenerateSubjectOutput(RdfXmlWriterContext context, List <Triple> ts, bool allowRdfDescription) { // If nothing to do return if (ts.Count == 0) { return; } context.NamespaceMap.IncrementNesting(); // First off determine what the XML Element should be // If there is a rdf:type triple then create a typed node // Otherwise create a rdf:Description node INode rdfType = context.Graph.CreateUriNode(UriFactory.Create(RdfSpecsHelper.RdfType)); Triple typeTriple = ts.FirstOrDefault(t => t.Predicate.Equals(rdfType) && t.Object.NodeType == NodeType.Uri); INode subj; if (typeTriple != null) { // Create Typed Node subj = typeTriple.Subject; // Generate the Type Reference creating a temporary namespace if necessary UriRefType outType; IUriNode typeNode = (IUriNode)typeTriple.Object; String uriref = GenerateUriRef(context, typeNode.Uri, UriRefType.QName, out outType); if (outType != UriRefType.QName) { // Need to generate a temporary namespace and try generating a QName again String tempPrefix, tempUri; GenerateTemporaryNamespace(context, typeNode, out tempPrefix, out tempUri); uriref = GenerateUriRef(context, typeNode.Uri, UriRefType.QName, out outType); if (outType != UriRefType.QName) { if (allowRdfDescription) { // Still couldn't generate a QName so fall back to rdf:Description // Note that in this case we don't remove the typeTriple from those to be written as we still need to // write it later context.Writer.WriteStartElement("rdf", "Description", NamespaceMapper.RDF); } else { throw new RdfOutputException(WriterErrorMessages.UnreducablePropertyURIUnserializable); } } else { if (uriref.Contains(':')) { // Type Node in relevant namespace context.Writer.WriteStartElement(uriref.Substring(0, uriref.IndexOf(':')), uriref.Substring(uriref.IndexOf(':') + 1), tempUri); } else { // Type Node in default namespace context.Writer.WriteStartElement(uriref); } ts.Remove(typeTriple); context.TriplesDone.Add(typeTriple); } // Remember to define the temporary namespace on the current element context.Writer.WriteAttributeString("xmlns", tempPrefix, null, Uri.EscapeUriString(tempUri)); } else { // Generated a valid QName if (uriref.Contains(':')) { // Create an element with appropriate namespace String ns = context.NamespaceMap.GetNamespaceUri(uriref.Substring(0, uriref.IndexOf(':'))).AbsoluteUri; context.Writer.WriteStartElement(uriref.Substring(0, uriref.IndexOf(':')), uriref.Substring(uriref.IndexOf(':') + 1), ns); } else { // Create an element in default namespace context.Writer.WriteStartElement(uriref); } context.TriplesDone.Add(typeTriple); ts.Remove(typeTriple); } } else { subj = ts.First().Subject; if (allowRdfDescription) { // Create rdf:Description Node context.Writer.WriteStartElement("rdf", "Description", NamespaceMapper.RDF); } else { throw new RdfOutputException(WriterErrorMessages.UnreducablePropertyURIUnserializable); } } // Always remember to add rdf:about or rdf:nodeID as appropriate if (subj.NodeType == NodeType.Uri) { context.Writer.WriteAttributeString("rdf", "about", NamespaceMapper.RDF, subj.ToString());//Uri.EscapeUriString(subj.ToString())); } else { // Can omit the rdf:nodeID if nesting level is > 2 i.e. not a top level subject node if (context.NamespaceMap.NestingLevel <= 2) { context.Writer.WriteAttributeString("rdf", "nodeID", NamespaceMapper.RDF, context.BlankNodeMapper.GetOutputID(((IBlankNode)subj).InternalID)); } } // If use of attributes is enabled we'll encode triples with simple literal objects // as attributes on the subject node directly if (context.UseAttributes) { // Next find any simple literals we can attach directly to the Subject Node List <Triple> simpleLiterals = new List <Triple>(); HashSet <INode> simpleLiteralPredicates = new HashSet <INode>(); foreach (Triple t in ts) { if (t.Object.NodeType == NodeType.Literal) { ILiteralNode lit = (ILiteralNode)t.Object; if (lit.DataType == null && lit.Language.Equals(String.Empty)) { if (!simpleLiteralPredicates.Contains(t.Predicate)) { simpleLiteralPredicates.Add(t.Predicate); simpleLiterals.Add(t); } } } } // Now go ahead and attach these to the Subject Node as attributes GenerateSimpleLiteralAttributes(context, simpleLiterals); simpleLiterals.ForEach(t => context.TriplesDone.Add(t)); simpleLiterals.ForEach(t => ts.Remove(t)); } // Then generate Predicate Output for each remaining Triple foreach (Triple t in ts) { GeneratePredicateOutput(context, t); context.TriplesDone.Add(t); } // Also check for the rare case where the subject is the key to a collection if (context.Collections.ContainsKey(subj)) { OutputRdfCollection collection = context.Collections[subj]; if (!collection.IsExplicit) { GenerateCollectionItemOutput(context, collection); collection.HasBeenWritten = true; } } context.Writer.WriteEndElement(); context.NamespaceMap.DecrementNesting(); }
private void GenerateCollectionOutput(RdfXmlWriterContext context, INode key) { OutputRdfCollection c = context.Collections[key]; if (!c.IsExplicit) { if (context.NamespaceMap.NestingLevel > 2) { // Need to set the Predicate to have a rdf:parseType of Resource context.Writer.WriteAttributeString("rdf", "parseType", NamespaceMapper.RDF, "Resource"); } int length = c.Triples.Count; while (c.Triples.Count > 0) { // Get the Next Item and generate the rdf:first element INode next = c.Triples.First().Object; c.Triples.RemoveAt(0); context.NamespaceMap.IncrementNesting(); context.Writer.WriteStartElement("rdf", "first", NamespaceMapper.RDF); // Set the value of the rdf:first Item switch (next.NodeType) { case NodeType.Blank: context.Writer.WriteAttributeString("rdf", "nodeID", NamespaceMapper.RDF, context.BlankNodeMapper.GetOutputID(((IBlankNode)next).InternalID)); break; case NodeType.GraphLiteral: throw new RdfOutputException(WriterErrorMessages.GraphLiteralsUnserializable("RDF/XML")); case NodeType.Literal: this.GenerateLiteralOutput(context, (ILiteralNode)next); break; case NodeType.Uri: this.GenerateUriOutput(context, (IUriNode)next, "rdf:resource"); break; default: throw new RdfOutputException(WriterErrorMessages.UnknownNodeTypeUnserializable("RDF/XML")); } // Now generate the rdf:rest element context.NamespaceMap.DecrementNesting(); context.Writer.WriteEndElement(); context.NamespaceMap.IncrementNesting(); context.Writer.WriteStartElement("rdf", "rest", NamespaceMapper.RDF); if (c.Triples.Count >= 1) { // Set Parse Type to resource context.Writer.WriteAttributeString("rdf", "parseType", NamespaceMapper.RDF, "Resource"); } else { // Terminate list with an rdf:nil context.Writer.WriteStartAttribute("rdf", "resource", NamespaceMapper.RDF); if (context.UseDtd) { context.Writer.WriteRaw("&rdf;nil"); } else { context.Writer.WriteRaw(NamespaceMapper.RDF + "nil"); } context.Writer.WriteEndAttribute(); } } for (int i = 0; i < length; i++) { context.NamespaceMap.DecrementNesting(); context.Writer.WriteEndElement(); } } else { if (c.Triples.Count == 0) { // Terminate the Blank Node triple by adding a rdf:nodeID attribute context.Writer.WriteAttributeString("rdf", "nodeID", NamespaceMapper.RDF, context.BlankNodeMapper.GetOutputID(((IBlankNode)key).InternalID)); } else { // Need to set the Predicate to have a rdf:parseType of Resource if (context.NamespaceMap.NestingLevel > 2) { // Need to set the Predicate to have a rdf:parseType of Resource context.Writer.WriteAttributeString("rdf", "parseType", NamespaceMapper.RDF, "Resource"); } // Output the Predicate Object list while (c.Triples.Count > 0) { Triple t = c.Triples[0]; c.Triples.RemoveAt(0); INode nextPred = t.Predicate; INode nextObj = t.Object; // Generate the predicate this.GeneratePredicateNode(context, nextPred); // Output the Object switch (nextObj.NodeType) { case NodeType.Blank: if (context.Collections.ContainsKey(nextObj)) { // Output a Collection this.GenerateCollectionOutput(context, nextObj); } else { context.Writer.WriteAttributeString("rdf", "nodeID", NamespaceMapper.RDF, context.BlankNodeMapper.GetOutputID(((IBlankNode)key).InternalID)); } break; case NodeType.GraphLiteral: throw new RdfOutputException(WriterErrorMessages.GraphLiteralsUnserializable("RDF/XML")); case NodeType.Literal: this.GenerateLiteralOutput(context, (ILiteralNode)nextObj); break; case NodeType.Uri: this.GenerateUriOutput(context, (IUriNode)nextObj, "rdf:resource"); break; default: throw new RdfOutputException(WriterErrorMessages.UnknownNodeTypeUnserializable("RDF/XML")); } context.Writer.WriteEndElement(); } } } }
/// <summary> /// Internal Helper method which converts a Collection into Turtle Syntax /// </summary> /// <param name="context">Writer Context</param> /// <param name="c">Collection to convert</param> /// <param name="indent">Indentation</param> /// <returns></returns> private String GenerateCollectionOutput(CompressingTurtleWriterContext context, OutputRdfCollection c, int indent) { StringBuilder output = new StringBuilder(); bool first = true; if (!c.IsExplicit) { output.Append('('); while (c.Triples.Count > 0) { if (context.PrettyPrint && !first) { output.Append(new String(' ', indent)); } first = false; output.Append(this.GenerateNodeOutput(context, c.Triples.First().Object, TripleSegment.Object, indent)); c.Triples.RemoveAt(0); if (c.Triples.Count > 0) { output.Append(' '); } } output.Append(')'); } else { if (c.Triples.Count == 0) { //Empty Collection //Can represent as a single Blank Node [] output.Append("[]"); } else { output.Append('['); while (c.Triples.Count > 0) { if (context.PrettyPrint && !first) { output.Append(new String(' ', indent)); } first = false; String temp = this.GenerateNodeOutput(context, c.Triples.First().Predicate, TripleSegment.Predicate, indent); output.Append(temp); output.Append(' '); int addIndent; if (temp.Contains('\n')) { addIndent = temp.Split('\n').Last().Length; } else { addIndent = temp.Length; } output.Append(this.GenerateNodeOutput(context, c.Triples.First().Object, TripleSegment.Object, indent + 2 + addIndent)); c.Triples.RemoveAt(0); if (c.Triples.Count > 0) { output.AppendLine(" ; "); output.Append(' '); } } output.Append(']'); } } return(output.ToString()); }
/// <summary> /// Internal Helper method which converts a Collection into Turtle Syntax /// </summary> /// <param name="context">Writer Context</param> /// <param name="c">Collection to convert</param> /// <param name="indent">Indentation</param> /// <returns></returns> private String GenerateCollectionOutput(CompressingTurtleWriterContext context, OutputRdfCollection c, int indent) { StringBuilder output = new StringBuilder(); bool first = true; if (!c.IsExplicit) { output.Append('('); while (c.Triples.Count > 0) { if (context.PrettyPrint && !first) output.Append(new String(' ', indent)); first = false; output.Append(this.GenerateNodeOutput(context, c.Triples.First().Object, TripleSegment.Object, indent)); c.Triples.RemoveAt(0); if (c.Triples.Count > 0) { output.Append(' '); } } output.Append(')'); } else { if (c.Triples.Count == 0) { //Empty Collection //Can represent as a single Blank Node [] output.Append("[]"); } else { output.Append('['); while (c.Triples.Count > 0) { if (context.PrettyPrint && !first) output.Append(new String(' ', indent)); first = false; String temp = this.GenerateNodeOutput(context, c.Triples.First().Predicate, TripleSegment.Predicate, indent); output.Append(temp); output.Append(' '); int addIndent; if (temp.Contains('\n')) { addIndent = temp.Split('\n').Last().Length; } else { addIndent = temp.Length; } output.Append(this.GenerateNodeOutput(context, c.Triples.First().Object, TripleSegment.Object, indent + 2 + addIndent)); c.Triples.RemoveAt(0); if (c.Triples.Count > 0) { output.AppendLine(" ; "); output.Append(' '); } } output.Append(']'); } } return output.ToString(); }
private void GenerateCollectionOutput(RdfXmlWriterContext context, INode key, XmlElement pred, ref int nextNamespaceID, List <String> tempNamespaces, XmlDocument doc) { OutputRdfCollection c = context.Collections[key]; c.HasBeenWritten = true; if (!c.IsExplicit) { XmlAttribute parseType; if (pred.ParentNode != doc.DocumentElement) { //Need to set the Predicate to have a rdf:parseType of Resource parseType = doc.CreateAttribute("rdf:parseType", NamespaceMapper.RDF); parseType.Value = "Resource"; pred.Attributes.Append(parseType); } XmlElement first, rest; while (c.Triples.Count > 0) { //Get the Next Item and generate rdf:first and rdf:rest Nodes INode next = c.Triples.First().Object; c.Triples.RemoveAt(0); first = doc.CreateElement("rdf:first", NamespaceMapper.RDF); rest = doc.CreateElement("rdf:rest", NamespaceMapper.RDF); pred.AppendChild(first); pred.AppendChild(rest); //Set the value of the rdf:first Item switch (next.NodeType) { case NodeType.Blank: XmlAttribute nodeID = doc.CreateAttribute("rdf:nodeID", NamespaceMapper.RDF); nodeID.Value = ((IBlankNode)next).InternalID; first.Attributes.Append(nodeID); break; case NodeType.GraphLiteral: throw new RdfOutputException(WriterErrorMessages.GraphLiteralsUnserializable("RDF/XML")); case NodeType.Literal: this.GenerateLiteralOutput(context, (ILiteralNode)next, first, doc); break; case NodeType.Uri: this.GenerateUriOutput(context, (IUriNode)next, "rdf:resource", tempNamespaces, first, doc); break; default: throw new RdfOutputException(WriterErrorMessages.UnknownNodeTypeUnserializable("RDF/XML")); } if (c.Triples.Count >= 1) { //Set Parse Type to resource parseType = doc.CreateAttribute("rdf:parseType", NamespaceMapper.RDF); parseType.Value = "Resource"; rest.Attributes.Append(parseType); pred = rest; } else { //Terminate list with an rdf:nil XmlAttribute res = doc.CreateAttribute("rdf:resource"); res.InnerXml = "&rdf;nil"; rest.Attributes.Append(res); } } } else { if (c.Triples.Count == 0) { //Terminate the Blank Node triple by adding a rdf:nodeID attribute XmlAttribute nodeID = doc.CreateAttribute("rdf:nodeID", NamespaceMapper.RDF); nodeID.Value = ((IBlankNode)key).InternalID; pred.Attributes.Append(nodeID); } else { //Need to set the Predicate to have a rdf:parseType of Resource if (pred.Name != "rdf:Description" && pred.ParentNode != doc.DocumentElement) { XmlAttribute parseType = doc.CreateAttribute("rdf:parseType", NamespaceMapper.RDF); parseType.Value = "Resource"; pred.Attributes.Append(parseType); } //Output the Predicate Object list while (c.Triples.Count > 0) { INode nextPred = c.Triples.First().Predicate; INode nextObj = c.Triples.First().Object; c.Triples.RemoveAt(0); XmlElement p; //Generate the predicate p = this.GeneratePredicateNode(context, nextPred, ref nextNamespaceID, tempNamespaces, doc, pred); //Output the Object switch (nextObj.NodeType) { case NodeType.Blank: if (context.Collections.ContainsKey(nextObj)) { //Output a Collection this.GenerateCollectionOutput(context, nextObj, p, ref nextNamespaceID, tempNamespaces, doc); } else { XmlAttribute nodeID = doc.CreateAttribute("rdf:nodeID", NamespaceMapper.RDF); nodeID.Value = ((IBlankNode)nextObj).InternalID; p.Attributes.Append(nodeID); } break; case NodeType.GraphLiteral: throw new RdfOutputException(WriterErrorMessages.GraphLiteralsUnserializable("RDF/XML")); case NodeType.Literal: this.GenerateLiteralOutput(context, (ILiteralNode)nextObj, p, doc); break; case NodeType.Uri: this.GenerateUriOutput(context, (IUriNode)nextObj, "rdf:resource", tempNamespaces, p, doc); break; default: throw new RdfOutputException(WriterErrorMessages.UnknownNodeTypeUnserializable("RDF/XML")); } } } } }
/// <summary> /// Helper method which finds Collections expressed in the Graph which can be compressed into concise collection syntax constructs in some RDF syntaxes /// </summary> /// <param name="context">Writer Context</param> /// <param name="mode">Collection Search Mode</param> public static void FindCollections(ICollectionCompressingWriterContext context, CollectionSearchMode mode) { //Prepare the RDF Nodes we need INode first, rest, nil; first = context.Graph.CreateUriNode(new Uri(RdfSpecsHelper.RdfListFirst)); rest = context.Graph.CreateUriNode(new Uri(RdfSpecsHelper.RdfListRest)); nil = context.Graph.CreateUriNode(new Uri(RdfSpecsHelper.RdfListNil)); //First we're going to look for implicit collections we can represent using the //brackets syntax of (a b c) if (mode == CollectionSearchMode.All || mode == CollectionSearchMode.ImplicitOnly) { //Find all rdf:rest rdf:nil Triples foreach (Triple t in context.Graph.GetTriplesWithPredicateObject(rest, nil)) { //If has named list node cannot compress if (t.Subject.NodeType != NodeType.Blank) break; //Build the collection recursively OutputRdfCollection c = new OutputRdfCollection(false); //Get the thing that is the rdf:first related to this rdf:rest IEnumerable<Triple> firsts = context.Graph.GetTriplesWithSubjectPredicate(t.Subject, first);//context.Graph.GetTriples(relfirstsel).Distinct(); Triple temp; if (firsts.Count() > 1) { //Strange error throw new RdfOutputException(WriterErrorMessages.MalformedCollectionWithMultipleFirsts); } else { //Stick this item onto the Stack temp = firsts.First(); c.Triples.Add(temp); } //See if this thing is the rdf:rest of anything else do { IEnumerable<Triple> ts = context.Graph.GetTriplesWithPredicateObject(rest, firsts.First().Subject); //Stop when there isn't a rdf:rest if (ts.Count() == 0) { break; } foreach (Triple t2 in ts) { firsts = context.Graph.GetTriplesWithSubjectPredicate(t2.Subject, first).Distinct(); if (firsts.Count() > 1) { //Strange error throw new RdfOutputException(WriterErrorMessages.MalformedCollectionWithMultipleFirsts); } else { //Stick this item onto the Stack temp = firsts.First(); //If Item is a named list node cannot compress if (temp.Subject.NodeType != NodeType.Blank) break; c.Triples.Add(temp); } } } while (true); //Can only compress if every List Node has a Blank Node Subject if (c.Triples.All(x => x.Subject.NodeType == NodeType.Blank)) { context.Collections.Add(firsts.First().Subject, c); } } } //Now we want to look for explicit collections which are representable //using Blank Node syntax [p1 o1; p2 o2; p3 o3] if (mode == CollectionSearchMode.All || mode == CollectionSearchMode.ExplicitOnly) { foreach (IBlankNode b in context.Graph.Nodes.BlankNodes) { if (context.Collections.ContainsKey(b)) continue; List<Triple> ts = context.Graph.GetTriples(b).ToList(); ts.RemoveAll(t => t.Predicate.Equals(first)); ts.RemoveAll(t => t.Predicate.Equals(rest)); if (ts.Count <= 1) { //This Blank Node is only used once //Add an empty explicit collection - we'll interpret this as [] later context.Collections.Add(b, new OutputRdfCollection(true)); } else { if (context.Graph.GetTriplesWithObject(b).Count() == 1) { ts.RemoveAll(t => t.Object.Equals(b)); } OutputRdfCollection c = new OutputRdfCollection(true); c.Triples.AddRange(ts); context.Collections.Add(b, c); } } } //If no collections found then no further processing if (context.Collections.Count == 0) return; //Once we've found all the Collections we need to check which are actually eligible for compression List<KeyValuePair<INode, OutputRdfCollection>> cs = context.Collections.ToList(); //1 - If all the Triples pertaining to a particular Node are in the Collection then a collection is not eligible foreach (KeyValuePair<INode, OutputRdfCollection> kvp in cs) { OutputRdfCollection c = kvp.Value; if (c.IsExplicit) { //For explicit collections if all Triples mentioning the Target Blank Node are in the Collection then can't compress //If there are no Triples in the Collection then this is a single use Blank Node so can always compress if (c.Triples.Count > 0 && c.Triples.Count == context.Graph.GetTriples(kvp.Key).Count()) { context.Collections.Remove(kvp.Key); } } else { //For implicit collections if the number of Triples in the Collection is exactly ((t*3) - 1) those in the Graph then //can't compress i.e. the collection is not linked to anything else //Or if the number of mentions compared to the expected mentions differs by more than 1 then //can't compress i.e. the collection is linked to more than one thing int mentions = context.Graph.GetTriples(kvp.Key).Count(); int expectedMentions = ((c.Triples.Count * 3) - 1); if (expectedMentions == mentions || mentions-expectedMentions != 1) { context.Collections.Remove(kvp.Key); } } } if (context.Collections.Count == 0) return; //2 - Look for cyclic collection dependencies cs = context.Collections.OrderByDescending(kvp => kvp.Value.Triples.Count).ToList(); //First build up a dependencies table Dictionary<INode,List<INode>> dependencies = new Dictionary<INode, List<INode>>(); foreach (KeyValuePair<INode, OutputRdfCollection> kvp in cs) { OutputRdfCollection c = kvp.Value; //Empty Blank Node Collections cannot be cyclic i.e. [] if (c.Triples.Count == 0) continue; //Otherwise check each Object of the Triples for other Blank Nodes List<INode> ds = new List<INode>(); foreach (Triple t in c.Triples) { //Only care about Blank Nodes which aren't the collection root but are the root for another collection if (t.Object.NodeType == NodeType.Blank && !t.Object.Equals(kvp.Key) && context.Collections.ContainsKey(t.Object)) { ds.Add(t.Object); } } if (ds.Count > 0) { dependencies.Add(kvp.Key, ds); } } //Now go back through that table looking for cycles foreach (INode n in dependencies.Keys) { List<INode> ds = dependencies[n]; if (ds.Count == 0) continue; foreach (INode d in ds.ToList()) { if (dependencies.ContainsKey(d)) { ds.AddRange(dependencies[d]); } } //We can tell if there is a cycle since ds will now contain n if (ds.Contains(n)) { context.Collections.Remove(n); } } if (context.Collections.Count == 0) return; //Finally fill out the TriplesDone for each Collection foreach (KeyValuePair<INode, OutputRdfCollection> kvp in context.Collections) { OutputRdfCollection c = kvp.Value; if (c.IsExplicit) { foreach (Triple t in c.Triples) { context.TriplesDone.Add(t); } } else { INode temp = kvp.Key; for (int i = 0; i < c.Triples.Count; i++) { context.TriplesDone.Add(c.Triples[i]); if (i < c.Triples.Count - 1) { context.TriplesDone.Add(new Triple(c.Triples[i].Subject, rest, c.Triples[i + 1].Subject)); } else { context.TriplesDone.Add(new Triple(c.Triples[i].Subject, rest, nil)); } } } } //As a final sanity check look for any Explicit Collection Key which is used more than once cs = context.Collections.ToList(); foreach (KeyValuePair<INode, OutputRdfCollection> kvp in cs) { OutputRdfCollection c = kvp.Value; if (c.IsExplicit) { int mentions = context.Graph.GetTriples(kvp.Key).Where(t => !context.TriplesDone.Contains(t)).Count(); if (mentions-1 > c.Triples.Count) { context.Collections.Remove(kvp.Key); } } } }
private void GenerateCollectionOutput(RdfXmlWriterContext context, INode key) { OutputRdfCollection c = context.Collections[key]; c.HasBeenWritten = true; if (c.IsExplicit) { if (c.Triples.Count == 0) { //If No Triples then an isolated blank node so add rdf:nodeID and return context.Writer.WriteAttributeString("rdf", "nodeID", NamespaceMapper.RDF, context.BlankNodeMapper.GetOutputID(((IBlankNode)key).InternalID)); return; } //First see if there is a typed triple available INode rdfType = context.Graph.CreateUriNode(UriFactory.Create(RdfSpecsHelper.RdfType)); Triple typeTriple = c.Triples.FirstOrDefault(t => t.Predicate.Equals(rdfType) && t.Object.NodeType == NodeType.Uri); if (typeTriple != null) { //Should be safe to invoke GenerateSubjectOutput but we can't allow rdf:Description this.GenerateSubjectOutput(context, c.Triples, false); } else { //Otherwise we use rdf:parseType="Resource" and invoke GeneratePredicateOutput context.Writer.WriteAttributeString("rdf", "parseType", NamespaceMapper.RDF, "Resource"); foreach (Triple t in c.Triples) { this.GeneratePredicateOutput(context, t); context.TriplesDone.Add(t); } } } else { //If No Triples then use rdf:about rdf:nil and return if (c.Triples.Count == 0) { context.Writer.WriteAttributeString("rdf", "about", NamespaceMapper.RDF, RdfSpecsHelper.RdfListNil); return; } //Going to need rdf:parseType="Resource" on current predicate context.Writer.WriteAttributeString("rdf", "parseType", NamespaceMapper.RDF, "Resource"); //Then output the elements of the Collection int toClose = c.Triples.Count; while (c.Triples.Count > 0) { Triple t = c.Triples[0]; c.Triples.RemoveAt(0); //rdf:first Node context.Writer.WriteStartElement("rdf", "first", NamespaceMapper.RDF); this.GenerateObjectOutput(context, t); context.TriplesDone.Add(t); context.Writer.WriteEndElement(); //rdf:rest Node context.Writer.WriteStartElement("rdf", "rest", NamespaceMapper.RDF); //context.Writer.WriteAttributeString("rdf", "parseType", NamespaceMapper.RDF, "Resource"); } //Terminate the list and close all the open rdf:rest elements context.Writer.WriteAttributeString("rdf", "resource", NamespaceMapper.RDF, RdfSpecsHelper.RdfListNil); for (int i = 0; i < toClose; i++) { context.Writer.WriteEndElement(); } } }