29 private const string RELATIONSHIPS_NAMESPACE =
"http://schemas.openxmlformats.org/package/2006/relationships";
30 private ZipArchive archive;
52 public void Init(ZipArchive archive,
Workbook workbook, IOptions readerOptions)
54 this.archive = archive;
67 throw new IOException(
"The discovery reader was not initialized with a ZIP archive.");
71 throw new IOException(
"The discovery reader was not initialized with a workbook.");
73 RelationshipCatalog catalog = DiscoverRelationships();
74 Workbook.AuxiliaryData.SetData(PlugInUUID.DiscoveryReader, PlugInUUID.DiscoveryCatalogEntity, catalog);
81 private RelationshipCatalog DiscoverRelationships()
83 RelationshipCatalog catalog =
new RelationshipCatalog();
84 List<ZipArchiveEntry> relationshipEntries = archive.Entries
85 .Where(entry => IsPotentialRelationshipPart(entry.FullName))
86 .OrderBy(entry => entry.FullName, StringComparer.Ordinal)
89 foreach (ZipArchiveEntry entry
in relationshipEntries)
91 if (!TryGetSourcePartPath(entry.FullName, out
string sourcePartPath, out
string pathError))
93 HandlePartIssue(catalog, entry.FullName, pathError,
null);
96 DiscoverRelationshipPart(entry, sourcePartPath, catalog);
108 private void DiscoverRelationshipPart(ZipArchiveEntry entry,
string sourcePartPath, RelationshipCatalog catalog)
110 List<RelationshipInfo> partRelationships =
new List<RelationshipInfo>();
111 List<RelationshipDiscoveryIssue> partIssues =
new List<RelationshipDiscoveryIssue>();
112 HashSet<string> relationshipIds =
new HashSet<string>(StringComparer.Ordinal);
115 using (System.IO.Stream stream = entry.Open())
116 using (XmlReader reader = XmlReader.Create(stream, XmlStreamUtils.CreateSettings()))
118 bool rootFound =
false;
119 while (reader.Read())
121 if (reader.NodeType != XmlNodeType.Element)
127 if (reader.Depth != 0 || reader.LocalName !=
"Relationships" || reader.NamespaceURI != RELATIONSHIPS_NAMESPACE)
129 throw new XmlException(
"The relationship part has an invalid root element.");
134 if (reader.Depth != 1 || reader.LocalName !=
"Relationship" || reader.NamespaceURI != RELATIONSHIPS_NAMESPACE)
136 throw new XmlException(
"The relationship part contains an invalid element.");
139 RelationshipInfo relationship = ParseRelationship(reader, entry.FullName, sourcePartPath, partIssues);
140 if (relationship ==
null)
144 if (!relationshipIds.Add(relationship.Id))
146 string reason =
"The relationship identifier is duplicated within its source part.";
147 if (IsStrictValidation)
149 throw CreateDiscoveryException(entry.FullName, relationship.Id, reason,
null);
151 partIssues.Add(
new RelationshipDiscoveryIssue(entry.FullName, relationship.Id, reason));
154 partRelationships.Add(relationship);
164 HandlePartIssue(catalog, entry.FullName,
"The relationship part could not be parsed.", ex);
168 foreach (RelationshipInfo relationship
in partRelationships)
170 if (!catalog.TryAdd(relationship))
172 string reason =
"The relationship identifier is duplicated within its source part.";
173 if (IsStrictValidation)
175 throw CreateDiscoveryException(entry.FullName, relationship.Id, reason,
null);
177 catalog.AddIssue(
new RelationshipDiscoveryIssue(entry.FullName, relationship.Id, reason));
180 foreach (RelationshipDiscoveryIssue issue
in partIssues)
182 catalog.AddIssue(issue);
195 private RelationshipInfo ParseRelationship(XmlReader reader,
string relationshipPartPath,
string sourcePartPath, List<RelationshipDiscoveryIssue> issues)
197 string id = reader.GetAttribute(
"Id");
198 string type = reader.GetAttribute(
"Type");
199 string target = reader.GetAttribute(
"Target");
200 string targetModeValue = reader.GetAttribute(
"TargetMode");
201 string reason = ValidateRequiredAttributes(
id, type, target);
204 return HandleRelationshipIssue(relationshipPartPath,
id, reason, issues,
null);
209 XmlConvert.VerifyNCName(
id);
213 return HandleRelationshipIssue(relationshipPartPath,
id,
"The relationship identifier is not a valid XML NCName.", issues, ex);
215 if (!Uri.TryCreate(type, UriKind.Absolute, out Uri unusedTypeUri))
217 return HandleRelationshipIssue(relationshipPartPath,
id,
"The relationship type is not an absolute URI.", issues,
null);
219 if (!Uri.TryCreate(target, UriKind.RelativeOrAbsolute, out Uri targetUri))
221 return HandleRelationshipIssue(relationshipPartPath,
id,
"The relationship target is not a valid URI reference.", issues,
null);
224 TargetMode targetMode;
225 if (
string.IsNullOrEmpty(targetModeValue) || targetModeValue ==
"Internal")
227 targetMode = TargetMode.Internal;
229 else if (targetModeValue ==
"External")
231 targetMode = TargetMode.External;
235 return HandleRelationshipIssue(relationshipPartPath,
id,
"The relationship TargetMode is invalid.", issues,
null);
238 string resolvedTargetPath =
null;
239 if (targetMode == TargetMode.Internal)
241 bool rootRelativeTarget = target.StartsWith(
"/", StringComparison.Ordinal);
242 if (target.StartsWith(
"//", StringComparison.Ordinal))
244 return HandleRelationshipIssue(relationshipPartPath,
id,
"An internal relationship target cannot be a network-path reference.", issues,
null);
246 if (targetUri.IsAbsoluteUri && !rootRelativeTarget)
248 return HandleRelationshipIssue(relationshipPartPath,
id,
"An internal relationship target cannot be an absolute URI.", issues,
null);
250 if (rootRelativeTarget)
252 targetUri =
new Uri(target, UriKind.Relative);
254 Uri sourceUri =
new Uri(
"/" + sourcePartPath, UriKind.Relative);
255 Uri resolvedTargetUri = PackUriHelper.ResolvePartUri(sourceUri, targetUri);
256 resolvedTargetPath = resolvedTargetUri.OriginalString.TrimStart(
'/');
259 return new RelationshipInfo(
id, type, target, targetMode, relationshipPartPath, sourcePartPath, resolvedTargetPath);
271 private RelationshipInfo HandleRelationshipIssue(
string relationshipPartPath,
string relationshipId,
string reason, List<RelationshipDiscoveryIssue> issues, Exception innerException)
273 if (IsStrictValidation)
275 throw CreateDiscoveryException(relationshipPartPath, relationshipId, reason, innerException);
277 issues.Add(
new RelationshipDiscoveryIssue(relationshipPartPath, relationshipId, reason));
288 private void HandlePartIssue(RelationshipCatalog catalog,
string relationshipPartPath,
string reason, Exception innerException)
290 if (IsStrictValidation)
292 throw CreateDiscoveryException(relationshipPartPath,
null, reason, innerException);
294 catalog.AddIssue(
new RelationshipDiscoveryIssue(relationshipPartPath,
null, reason));
304 private static string ValidateRequiredAttributes(
string id,
string type,
string target)
306 if (
string.IsNullOrWhiteSpace(
id))
308 return "The relationship Id attribute is missing or empty.";
310 if (
string.IsNullOrWhiteSpace(type))
312 return "The relationship Type attribute is missing or empty.";
314 if (
string.IsNullOrWhiteSpace(target))
316 return "The relationship Target attribute is missing or empty.";
326 private static bool IsPotentialRelationshipPart(
string path)
328 return !
string.IsNullOrEmpty(path)
329 && path.EndsWith(
".rels", StringComparison.OrdinalIgnoreCase)
330 && (path.StartsWith(
"_rels/", StringComparison.Ordinal) || path.IndexOf(
"/_rels/", StringComparison.Ordinal) >= 0);
340 private static bool TryGetSourcePartPath(
string relationshipPartPath, out
string sourcePartPath, out
string error)
342 sourcePartPath =
null;
346 Uri relationshipPartUri = PackUriHelper.CreatePartUri(
new Uri(
"/" + relationshipPartPath, UriKind.Relative));
347 if (!PackUriHelper.IsRelationshipPartUri(relationshipPartUri)
348 || !
string.Equals(relationshipPartUri.OriginalString.TrimStart(
'/'), relationshipPartPath, StringComparison.Ordinal))
350 error =
"The ZIP entry path is not a valid OPC relationship-part path.";
353 Uri sourcePartUri = PackUriHelper.GetSourcePartUriFromRelationshipPartUri(relationshipPartUri);
354 sourcePartPath = sourcePartUri.OriginalString.TrimStart(
'/');
359 error =
"The ZIP entry path is not a valid OPC relationship-part path: " + ex.Message;
372 private static IOException CreateDiscoveryException(
string relationshipPartPath,
string relationshipId,
string reason, Exception innerException)
374 string relationshipContext = relationshipId ==
null ? string.Empty :
" (Id '" + relationshipId +
"')";
375 string message =
"The relationship part '" + relationshipPartPath +
"'" + relationshipContext +
" is invalid. " + reason;
383 private bool IsStrictValidation
387 ReaderOptions readerOptions =
Options as ReaderOptions;
388 return readerOptions !=
null && readerOptions.EnforceStrictValidation;