diff --git a/Source/Document Structure/SvgLink.cs b/Source/Document Structure/SvgLink.cs new file mode 100644 index 000000000..a2eaaf3b4 --- /dev/null +++ b/Source/Document Structure/SvgLink.cs @@ -0,0 +1,111 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net; +using System.Text; + +namespace Svg +{ + [NonSvgElementAttribute("link")] + public class SvgLink : NonSvgElement + { + public SvgLink() + : base("link") + { + } + public enum RelativeValue + { + Unknown, + Alternate, + Author, + Help, + Icon, + License, + Next, + Pingback, + Preconnect, + Prefetch, + Preload, + Prerender, + Prev, + Search, + Stylesheet + } + + public override SvgElement DeepCopy() + { + return DeepCopy(); + } + + [SvgAttribute("href")] + public string Href + { + get { return GetAttribute("href", false, string.Empty); } + set { Attributes["href"] = value; } + } + + [SvgAttribute("rel")] + public RelativeValue Rel + { + get { return GetAttribute("rel", false, RelativeValue.Unknown); } + set { Attributes["rel"] = value; } + } + + public string GetLinkContentAsText(RelativeValue rel) + { + try + { + using (var stream = GetLinkContentAsStream(rel)) + { + using (StreamReader sr = new StreamReader(stream)) + { + return sr.ReadToEnd(); + } + } + } + catch + { + return string.Empty; + } + } + + public Stream GetLinkContentAsStream(RelativeValue rel = RelativeValue.Unknown) + { + if (Rel != rel && rel != RelativeValue.Unknown) + return null; + // Uri MaxLength is 65519 (https://msdn.microsoft.com/en-us/library/z6c2z492.aspx) + // if using data URI scheme, very long URI may happen. + var safeUriString = Href.Length > 65519 ? Href.Substring(0, 65519) : Href; + + try + { + var uri = new Uri(safeUriString, UriKind.RelativeOrAbsolute); + + if (!uri.IsAbsoluteUri) + uri = new Uri(OwnerDocument.BaseUri, uri); + + // should work with http: and file: protocol urls + var httpRequest = WebRequest.Create(uri); + using (var webResponse = httpRequest.GetResponse()) + { + using (var stream = webResponse.GetResponseStream()) + { + if (stream.CanSeek) + stream.Position = 0; + MemoryStream returnedStream = new MemoryStream(); + stream.CopyTo(returnedStream); + returnedStream.Position = 0; + return returnedStream; + } + } + } + catch (Exception ex) + { + Trace.TraceError("Error loading Link content: '{0}', error: {1} ", Href, ex.Message); + return null; + } + + } + } +} diff --git a/Source/NonSvgElementAttribute.cs b/Source/NonSvgElementAttribute.cs new file mode 100644 index 000000000..4a7d6bf1c --- /dev/null +++ b/Source/NonSvgElementAttribute.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Svg +{ + /// + /// Specifies the SVG name of an . + /// + [AttributeUsage(AttributeTargets.Class)] + public sealed class NonSvgElementAttribute : Attribute + { + /// + /// Gets the name of the SVG element. + /// + public string ElementName { get; private set; } + + /// + /// Initializes a new instance of the class with the specified element name; + /// + /// The name of the non SVG element. + public NonSvgElementAttribute(string elementName) + { + this.ElementName = elementName; + } + } +} diff --git a/Source/SvgDocument.cs b/Source/SvgDocument.cs index f0be9afb0..2a88ce97a 100644 --- a/Source/SvgDocument.cs +++ b/Source/SvgDocument.cs @@ -48,7 +48,7 @@ private static int GetSystemDpi() isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); #else var platform = Environment.OSVersion.Platform; - isWindows = platform == PlatformID.Win32NT; + isWindows = platform == PlatformID.Win32NT; #endif if (isWindows) @@ -194,15 +194,15 @@ public virtual TSvgElement GetElementById(string id) where TSvgElem /// Boolean whether the system is capable of using GDI+ public static bool SystemIsGdiPlusCapable() { - try + try { EnsureSystemIsGdiPlusCapable(); } - catch(SvgGdiPlusCannotBeLoadedException) + catch (SvgGdiPlusCannotBeLoadedException) { return false; } - catch(Exception) + catch (Exception) { //If somehow another type of exception is raised by the ensure function we will let it bubble up, since that might indicate other issues/problems throw; @@ -230,7 +230,7 @@ public static void EnsureSystemIsGdiPlusCapable() throw new SvgGdiPlusCannotBeLoadedException(e); } //If the Matrix creation is causing another type of exception we should just raise that one - throw; + throw; } } @@ -260,32 +260,35 @@ private static bool ExceptionCaughtIsGdiPlusRelated(Exception e) /// Opens the document at the specified path and loads the SVG contents. /// /// A containing the path of the file to open. + /// base uri for linked file /// An with the contents loaded. /// The document at the specified cannot be found. - public static SvgDocument Open(string path) + public static SvgDocument Open(string path, Uri baseUri = null) { - return Open(path, null); + return Open(path, null, baseUri == null ? new Uri(System.IO.Path.GetFullPath(path)) : baseUri); } /// /// Opens the document at the specified path and loads the SVG contents. /// /// A containing the path of the file to open. + /// base uri for linked file /// An with the contents loaded. /// The document at the specified cannot be found. - public static T Open(string path) where T : SvgDocument, new() + public static T Open(string path, Uri baseUri = null) where T : SvgDocument, new() { - return Open(path, null); + return Open(path, null, baseUri); } /// /// Opens the document at the specified path and loads the SVG contents. /// /// A containing the path of the file to open. + /// base uri for linked file /// A dictionary of custom entity definitions to be used when resolving XML entities within the document. /// An with the contents loaded. /// The document at the specified cannot be found. - public static T Open(string path, Dictionary entities) where T : SvgDocument, new() + public static T Open(string path, Dictionary entities, Uri baseUri = null) where T : SvgDocument, new() { if (string.IsNullOrEmpty(path)) { @@ -299,8 +302,11 @@ public static SvgDocument Open(string path) using (var stream = File.OpenRead(path)) { - var doc = Open(stream, entities); - doc.BaseUri = new Uri(System.IO.Path.GetFullPath(path)); + var doc = Open(stream, entities, baseUri == null ? new Uri(System.IO.Path.GetFullPath(path)) : baseUri); + if (baseUri == null) + doc.BaseUri = new Uri(System.IO.Path.GetFullPath(path)); + else + doc.BaseUri = baseUri; return doc; } } @@ -309,9 +315,10 @@ public static SvgDocument Open(string path) /// Attempts to open an SVG document from the specified . /// /// The containing the SVG document to open. - public static T Open(Stream stream) where T : SvgDocument, new() + /// base uri for linked file + public static T Open(Stream stream, Uri baseUri = null) where T : SvgDocument, new() { - return Open(stream, null); + return Open(stream, null, baseUri); } @@ -342,8 +349,9 @@ public static SvgDocument Open(string path) /// /// The containing the SVG document to open. /// Custom entity definitions. + /// base uri for linked file /// The parameter cannot be null. - public static T Open(Stream stream, Dictionary entities) where T : SvgDocument, new() + public static T Open(Stream stream, Dictionary entities, Uri baseUri = null) where T : SvgDocument, new() { if (stream == null) { @@ -356,10 +364,10 @@ public static SvgDocument Open(string path) XmlResolver = new SvgDtdResolver(), WhitespaceHandling = WhitespaceHandling.None }; - return Open(reader); + return Open(reader, baseUri); } - private static T Open(XmlReader reader) where T : SvgDocument, new() + private static T Open(XmlReader reader, Uri baseUri = null) where T : SvgDocument, new() { if (!SkipGdiPlusCapabilityCheck) { @@ -392,6 +400,8 @@ public static SvgDocument Open(string path) else { svgDocument = elementFactory.CreateDocument(reader); + if (baseUri != null) + svgDocument.BaseUri = baseUri; element = svgDocument; } @@ -435,6 +445,16 @@ public static SvgDocument Open(string path) { styles.Add(unknown); } + else if (element is SvgLink) + { + var stylecontent = ((SvgLink)element).GetLinkContentAsText(SvgLink.RelativeValue.Stylesheet); + if (!string.IsNullOrEmpty(stylecontent)) + { + var elem = new SvgUnknownElement("style"); + elem.Content = stylecontent; + styles.Add(elem); + } + } break; case XmlNodeType.CDATA: case XmlNodeType.Text: @@ -491,8 +511,9 @@ public static SvgDocument Open(string path) /// Opens an SVG document from the specified . /// /// The containing the SVG document XML. + /// base uri for linked file /// The parameter cannot be null. - public static SvgDocument Open(XmlDocument document) + public static SvgDocument Open(XmlDocument document, Uri baseUri = null) { if (document == null) { @@ -500,7 +521,7 @@ public static SvgDocument Open(XmlDocument document) } var reader = new SvgNodeReader(document.DocumentElement, null); - return Open(reader); + return Open(reader, baseUri); } public static Bitmap OpenAsBitmap(string path) diff --git a/Source/SvgElementFactory.cs b/Source/SvgElementFactory.cs index 0adee1825..541622781 100644 --- a/Source/SvgElementFactory.cs +++ b/Source/SvgElementFactory.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Globalization; using System.Xml; @@ -15,6 +15,7 @@ namespace Svg internal class SvgElementFactory { private List availableElements; + private List availableNonSvgElements; private Parser cssParser = new Parser(); /// @@ -38,6 +39,31 @@ where t.GetCustomAttributes(typeof(SvgElementAttribute), true).Length > 0 } } + + + /// + /// Gets a list of available types that can be used when creating an . + /// + public List AvailableNonSvgElements + { + get + { + if (availableNonSvgElements == null) + { + var nonSvgTypes = from t in typeof(SvgDocument).Assembly.GetExportedTypes() + where t.GetCustomAttributes(typeof(NonSvgElementAttribute), true).Length > 0 + && t.IsSubclassOf(typeof(NonSvgElement)) + select new ElementInfo { ElementName = ((NonSvgElementAttribute)t.GetCustomAttributes(typeof(NonSvgElementAttribute), true)[0]).ElementName, ElementType = t }; + + availableNonSvgElements = nonSvgTypes.ToList(); + } + + return availableNonSvgElements; + } + } + + + /// /// Creates an from the current node in the specified . /// @@ -111,8 +137,21 @@ public SvgElement CreateElement(XmlReader reader, SvgDocument document) else { // All non svg element (html, ...) - createdElement = new NonSvgElement(elementName); - SetAttributes(createdElement, reader, document); + ElementInfo validType; + if (AvailableNonSvgElements.ToDictionary(e => e.ElementName, e => e).TryGetValue(elementName, out validType)) + { + createdElement = (NonSvgElement)Activator.CreateInstance(validType.ElementType); + } + else + { + createdElement = new NonSvgElement(elementName); + } + + if (createdElement != null) + { + SetAttributes(createdElement, reader, document); + } + } //Trace.TraceInformation("End CreateElement");