diff --git a/src/Textamina.Markdig.Tests/Specs/MediaSpecs.md b/src/Textamina.Markdig.Tests/Specs/MediaSpecs.md
index e4930019..6b8537bb 100644
--- a/src/Textamina.Markdig.Tests/Specs/MediaSpecs.md
+++ b/src/Textamina.Markdig.Tests/Specs/MediaSpecs.md
@@ -11,6 +11,6 @@ Allows to embed audio/video links to popular website:

.
-
-
+
+
````````````````````````````````
\ No newline at end of file
diff --git a/src/Textamina.Markdig.Tests/Specs/Specs.cs b/src/Textamina.Markdig.Tests/Specs/Specs.cs
index a0b90cd9..ab609c31 100644
--- a/src/Textamina.Markdig.Tests/Specs/Specs.cs
+++ b/src/Textamina.Markdig.Tests/Specs/Specs.cs
@@ -18428,11 +18428,11 @@ namespace Textamina.Markdig.Tests
// 
//
// Should be rendered as:
- //
- //
+ //
+ //
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 1, "Extensions Media links");
- TestParser.TestSpec("\n\n", "\n", "medias");
+ TestParser.TestSpec("\n\n", "\n", "medias");
}
}
}
diff --git a/src/Textamina.Markdig/Extensions/Medias/MediaExtension.cs b/src/Textamina.Markdig/Extensions/Medias/MediaExtension.cs
index 625454e5..5be42dcc 100644
--- a/src/Textamina.Markdig/Extensions/Medias/MediaExtension.cs
+++ b/src/Textamina.Markdig/Extensions/Medias/MediaExtension.cs
@@ -1,105 +1,27 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.
-
using System;
-using System.Collections.Generic;
using Textamina.Markdig.Renderers;
+using Textamina.Markdig.Renderers.Html;
using Textamina.Markdig.Renderers.Html.Inlines;
using Textamina.Markdig.Syntax.Inlines;
namespace Textamina.Markdig.Extensions.Medias
{
- public class MediaOptions
- {
- public MediaOptions()
- {
- Width = 500;
- Height = 281;
- ExtensionToMimeType = new Dictionary(StringComparer.OrdinalIgnoreCase)
- {
- {".3gp", "video/3gpp"},
- {".3g2", "video/3gpp2"},
- {".avi", "video/x-msvideo"},
- {".uvh", "video/vnd.dece.hd"},
- {".uvm", "video/vnd.dece.mobile"},
- {".uvu", "video/vnd.uvvu.mp4"},
- {".uvp", "video/vnd.dece.pd"},
- {".uvs", "video/vnd.dece.sd"},
- {".uvv", "video/vnd.dece.video"},
- {".fvt", "video/vnd.fvt"},
- {".f4v", "video/x-f4v"},
- {".flv", "video/x-flv"},
- {".fli", "video/x-fli"},
- {".h261", "video/h261"},
- {".h263", "video/h263"},
- {".h264", "video/h264"},
- {".jpm", "video/jpm"},
- {".jpgv", "video/jpeg"},
- {".m4v", "video/x-m4v"},
- {".asf", "video/x-ms-asf"},
- {".pyv", "video/vnd.ms-playready.media.pyv"},
- {".wm", "video/x-ms-wm"},
- {".wmx", "video/x-ms-wmx"},
- {".wmv", "video/x-ms-wmv"},
- {".wvx", "video/x-ms-wvx"},
- {".mj2", "video/mj2"},
- {".mxu", "video/vnd.mpegurl"},
- {".mpeg", "video/mpeg"},
- {".mp4", "video/mp4"},
- {".ogv", "video/ogg"},
- {".webm", "video/webm"},
- {".qt", "video/quicktime"},
- {".movie", "video/x-sgi-movie"},
- {".viv", "video/vnd.vivo"},
-
- {".adp", "audio/adpcm"},
- {".aac", "audio/x-aac"},
- {".aif", "audio/x-aiff"},
- {".uva", "audio/vnd.dece.audio"},
- {".eol", "audio/vnd.digital-winds"},
- {".dra", "audio/vnd.dra"},
- {".dts", "audio/vnd.dts"},
- {".dtshd", "audio/vnd.dts.hd"},
- {".rip", "audio/vnd.rip"},
- {".lvp", "audio/vnd.lucent.voice"},
- {".m3u", "audio/x-mpegurl"},
- {".pya", "audio/vnd.ms-playready.media.pya"},
- {".wma", "audio/x-ms-wma"},
- {".wax", "audio/x-ms-wax"},
- {".mid", "audio/midi"},
- {".mpga", "audio/mpeg"},
- {".mp4a", "audio/mp4"},
- {".ecelp4800", "audio/vnd.nuera.ecelp4800"},
- {".ecelp7470", "audio/vnd.nuera.ecelp7470"},
- {".ecelp9600", "audio/vnd.nuera.ecelp9600"},
- {".oga", "audio/ogg"},
- {".weba", "audio/webm"},
- {".ram", "audio/x-pn-realaudio"},
- {".rmp", "audio/x-pn-realaudio-plugin"},
- {".au", "audio/basic"},
- {".wav", "audio/x-wav"},
- };
- }
-
- public int Width { get; set; }
-
- public int Height { get; set; }
-
- public Dictionary ExtensionToMimeType { get; }
- }
-
-
-
///
/// Extension for extending image Markdown links in case a video or an audio file is linked and output proper link.
///
///
public class MediaExtension : IMarkdownExtension
{
- public MediaExtension(MediaOptions options = null)
+ public MediaExtension() : this(new MediaOptions())
{
- Options = options;
+ }
+
+ public MediaExtension(MediaOptions options)
+ {
+ Options = options ?? new MediaOptions();
}
public MediaOptions Options { get; }
@@ -125,22 +47,25 @@ namespace Textamina.Markdig.Extensions.Medias
Uri uri;
if (Uri.TryCreate(linkInline.Url, UriKind.RelativeOrAbsolute, out uri))
{
- // TODO: change code for extensions with Options
+ var htmlAttributes = new HtmlAttributes();
+ var fromAttributes = linkInline.TryGetAttributes();
+ if (fromAttributes != null)
+ {
+ fromAttributes.CopyTo(htmlAttributes, false, false);
+ }
+
+ // TODO: this code is not pluggable, so for now, we handle only the following web providers:
+ // - youtube
+ // - vimeo
var path = uri.GetComponents(UriComponents.Path, UriFormat.Unescaped);
- var isMp3 = path.EndsWith(".mp3", StringComparison.OrdinalIgnoreCase);
- var isOgg = path.EndsWith(".ogg", StringComparison.OrdinalIgnoreCase);
- var isMp4 = path.EndsWith(".mp4", StringComparison.OrdinalIgnoreCase);
- var isWebM = path.EndsWith(".webm", StringComparison.OrdinalIgnoreCase);
-
+ string iFrameUrl = null;
if (uri.Host.StartsWith("www.youtube.com", StringComparison.OrdinalIgnoreCase))
{
var query = SplitQuery(uri);
if (query.Length > 0 && query[0].StartsWith("v="))
{
- renderer.Write(
- $"");
- return true;
+ iFrameUrl = $"https://www.youtube.com/embed/{query[0].Substring(2)}";
}
}
else if (uri.Host.StartsWith("vimeo.com", StringComparison.OrdinalIgnoreCase))
@@ -148,22 +73,46 @@ namespace Textamina.Markdig.Extensions.Medias
var items = path.Split('/');
if (items.Length > 0)
{
- renderer.Write(
- $"");
- return true;
+ iFrameUrl = $"https://player.vimeo.com/video/{items[items.Length - 1]}";
}
}
- else if (isMp3 || isOgg)
+
+ if (iFrameUrl != null)
{
- var audioType = isMp3 ? "mp3" : "ogg";
- renderer.Write($"");
+ renderer.Write($"");
return true;
}
- else if (isMp4 || isWebM)
+ else
{
- var videoType = isMp4 ? "mp4" : "webm";
- renderer.Write($"");
- return true;
+ // Otherwise try to detect if we have an audio/video from the file extension
+ string mimeType;
+ var lastDot = path.LastIndexOf('.');
+ if (lastDot >= 0 &&
+ Options.ExtensionToMimeType.TryGetValue(path.Substring(lastDot), out mimeType))
+ {
+ var isAudio = mimeType.StartsWith("audio");
+ var tagType = isAudio ? "audio" : "video";
+
+ renderer.Write($"<{tagType}");
+ htmlAttributes.AddPropertyIfNotExist("width", Options.Width);
+ if (!isAudio)
+ {
+ htmlAttributes.AddPropertyIfNotExist("height", Options.Height);
+ }
+ htmlAttributes.AddPropertyIfNotExist("controls", null);
+ renderer.WriteAttributes(htmlAttributes);
+
+ renderer.Write($">{tagType}>");
+ renderer.Write("");
+
+ return true;
+ }
}
}
}
diff --git a/src/Textamina.Markdig/Extensions/Medias/MediaOptions.cs b/src/Textamina.Markdig/Extensions/Medias/MediaOptions.cs
new file mode 100644
index 00000000..dce9b0be
--- /dev/null
+++ b/src/Textamina.Markdig/Extensions/Medias/MediaOptions.cs
@@ -0,0 +1,90 @@
+// Copyright (c) Alexandre Mutel. All rights reserved.
+// This file is licensed under the BSD-Clause 2 license.
+// See the license.txt file in the project root for more information.
+using System;
+using System.Collections.Generic;
+
+namespace Textamina.Markdig.Extensions.Medias
+{
+ ///
+ /// Options for the .
+ ///
+ public class MediaOptions
+ {
+ public MediaOptions()
+ {
+ Width = "500";
+ Height = "281";
+ ExtensionToMimeType = new Dictionary(StringComparer.OrdinalIgnoreCase)
+ {
+ {".3gp", "video/3gpp"},
+ {".3g2", "video/3gpp2"},
+ {".avi", "video/x-msvideo"},
+ {".uvh", "video/vnd.dece.hd"},
+ {".uvm", "video/vnd.dece.mobile"},
+ {".uvu", "video/vnd.uvvu.mp4"},
+ {".uvp", "video/vnd.dece.pd"},
+ {".uvs", "video/vnd.dece.sd"},
+ {".uvv", "video/vnd.dece.video"},
+ {".fvt", "video/vnd.fvt"},
+ {".f4v", "video/x-f4v"},
+ {".flv", "video/x-flv"},
+ {".fli", "video/x-fli"},
+ {".h261", "video/h261"},
+ {".h263", "video/h263"},
+ {".h264", "video/h264"},
+ {".jpm", "video/jpm"},
+ {".jpgv", "video/jpeg"},
+ {".m4v", "video/x-m4v"},
+ {".asf", "video/x-ms-asf"},
+ {".pyv", "video/vnd.ms-playready.media.pyv"},
+ {".wm", "video/x-ms-wm"},
+ {".wmx", "video/x-ms-wmx"},
+ {".wmv", "video/x-ms-wmv"},
+ {".wvx", "video/x-ms-wvx"},
+ {".mj2", "video/mj2"},
+ {".mxu", "video/vnd.mpegurl"},
+ {".mpeg", "video/mpeg"},
+ {".mp4", "video/mp4"},
+ {".ogv", "video/ogg"},
+ {".webm", "video/webm"},
+ {".qt", "video/quicktime"},
+ {".movie", "video/x-sgi-movie"},
+ {".viv", "video/vnd.vivo"},
+
+ {".adp", "audio/adpcm"},
+ {".aac", "audio/x-aac"},
+ {".aif", "audio/x-aiff"},
+ {".uva", "audio/vnd.dece.audio"},
+ {".eol", "audio/vnd.digital-winds"},
+ {".dra", "audio/vnd.dra"},
+ {".dts", "audio/vnd.dts"},
+ {".dtshd", "audio/vnd.dts.hd"},
+ {".rip", "audio/vnd.rip"},
+ {".lvp", "audio/vnd.lucent.voice"},
+ {".m3u", "audio/x-mpegurl"},
+ {".pya", "audio/vnd.ms-playready.media.pya"},
+ {".wma", "audio/x-ms-wma"},
+ {".wax", "audio/x-ms-wax"},
+ {".mid", "audio/midi"},
+ {".mpga", "audio/mpeg"},
+ {".mp4a", "audio/mp4"},
+ {".ecelp4800", "audio/vnd.nuera.ecelp4800"},
+ {".ecelp7470", "audio/vnd.nuera.ecelp7470"},
+ {".ecelp9600", "audio/vnd.nuera.ecelp9600"},
+ {".oga", "audio/ogg"},
+ {".weba", "audio/webm"},
+ {".ram", "audio/x-pn-realaudio"},
+ {".rmp", "audio/x-pn-realaudio-plugin"},
+ {".au", "audio/basic"},
+ {".wav", "audio/x-wav"},
+ };
+ }
+
+ public string Width { get; set; }
+
+ public string Height { get; set; }
+
+ public Dictionary ExtensionToMimeType { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Textamina.Markdig/Renderers/Html/HtmlAttributes.cs b/src/Textamina.Markdig/Renderers/Html/HtmlAttributes.cs
index eaf4777f..fd9f7bf7 100644
--- a/src/Textamina.Markdig/Renderers/Html/HtmlAttributes.cs
+++ b/src/Textamina.Markdig/Renderers/Html/HtmlAttributes.cs
@@ -3,6 +3,7 @@
// See the license.txt file in the project root for more information.
using System;
using System.Collections.Generic;
+using System.Globalization;
using Textamina.Markdig.Syntax;
namespace Textamina.Markdig.Renderers.Html
@@ -64,18 +65,55 @@ namespace Textamina.Markdig.Renderers.Html
}
///
- /// Copies the values from this instance to the specified instance.
+ /// Adds the specified property only if it does not already exist.
+ ///
+ /// The name.
+ /// The value.
+ public void AddPropertyIfNotExist(string name, object value)
+ {
+ if (name == null) throw new ArgumentNullException(nameof(name));
+ if (Properties == null)
+ {
+ Properties = new List> {new KeyValuePair(name, value == null ? null : Convert.ToString(value, CultureInfo.InvariantCulture))};
+ }
+ else
+ {
+ // Check that there is not already a property with the same key
+ bool copyProp = true;
+ for (int i = 0; i < Properties.Count; i++)
+ {
+ var againstProp = Properties[i];
+ if (againstProp.Key == name)
+ {
+ copyProp = false;
+ break;
+ }
+ }
+ if (copyProp)
+ {
+ Properties.Add(new KeyValuePair(name, value == null ? null : Convert.ToString(value, CultureInfo.InvariantCulture)));
+ }
+ }
+ }
+
+ ///
+ /// Copies/merge the values from this instance to the specified instance.
///
/// The HTML attributes.
+ /// If set to true it will merge properties to the target htmlAttributes. Default is false
+ /// If set to true it will try to share Classes and Properties if destination don't have them, otherwise it will make a copy. Default is true
///
- public void CopyTo(HtmlAttributes htmlAttributes)
+ public void CopyTo(HtmlAttributes htmlAttributes, bool mergeIdAndProperties = false, bool shared = true)
{
if (htmlAttributes == null) throw new ArgumentNullException(nameof(htmlAttributes));
// Add html htmlAttributes to the object
- htmlAttributes.Id = Id;
+ if (!mergeIdAndProperties || Id != null)
+ {
+ htmlAttributes.Id = Id;
+ }
if (htmlAttributes.Classes == null)
{
- htmlAttributes.Classes = Classes;
+ htmlAttributes.Classes = shared ? Classes : new List(Classes);
}
else if (Classes != null)
{
@@ -84,11 +122,21 @@ namespace Textamina.Markdig.Renderers.Html
if (htmlAttributes.Properties == null)
{
- htmlAttributes.Properties = Properties;
+ htmlAttributes.Properties = shared ? Properties : new List>(Properties);
}
else if (Properties != null)
{
- htmlAttributes.Properties.AddRange(Properties);
+ if (mergeIdAndProperties)
+ {
+ foreach (var prop in Properties)
+ {
+ htmlAttributes.AddPropertyIfNotExist(prop.Key, prop.Value);
+ }
+ }
+ else
+ {
+ htmlAttributes.Properties.AddRange(Properties);
+ }
}
}
}
diff --git a/src/Textamina.Markdig/Renderers/HtmlRenderer.cs b/src/Textamina.Markdig/Renderers/HtmlRenderer.cs
index e25aac42..ab0fdf49 100644
--- a/src/Textamina.Markdig/Renderers/HtmlRenderer.cs
+++ b/src/Textamina.Markdig/Renderers/HtmlRenderer.cs
@@ -200,40 +200,51 @@ namespace Textamina.Markdig.Renderers
public HtmlRenderer WriteAttributes(MarkdownObject obj)
{
if (obj == null) throw new ArgumentNullException(nameof(obj));
- var attributes = obj.TryGetAttributes();
- if (attributes != null)
+ return WriteAttributes(obj.TryGetAttributes());
+ }
+
+ ///
+ /// Writes the specified .
+ ///
+ /// The attributes to render.
+ /// This instance
+ public HtmlRenderer WriteAttributes(HtmlAttributes attributes)
+ {
+ if (attributes == null)
{
- if (attributes.Id != null)
- {
- Write(" id=\"").WriteEscape(attributes.Id).Write("\"");
- }
+ return this;
+ }
- if (attributes.Classes != null && attributes.Classes.Count > 0)
+ if (attributes.Id != null)
+ {
+ Write(" id=\"").WriteEscape(attributes.Id).Write("\"");
+ }
+
+ if (attributes.Classes != null && attributes.Classes.Count > 0)
+ {
+ Write(" class=\"");
+ for (int i = 0; i < attributes.Classes.Count; i++)
{
- Write(" class=\"");
- for (int i = 0; i < attributes.Classes.Count; i++)
+ var cssClass = attributes.Classes[i];
+ if (i > 0)
{
- var cssClass = attributes.Classes[i];
- if (i > 0)
- {
- Write(" ");
- }
- WriteEscape(cssClass);
+ Write(" ");
}
- Write("\"");
+ WriteEscape(cssClass);
}
+ Write("\"");
+ }
- if (attributes.Properties != null && attributes.Properties.Count > 0)
+ if (attributes.Properties != null && attributes.Properties.Count > 0)
+ {
+ foreach (var property in attributes.Properties)
{
- foreach (var property in attributes.Properties)
+ Write(" ").Write(property.Key);
+ if (property.Value != null)
{
- Write(" ").Write(property.Key);
- if (property.Value != null)
- {
- Write("=").Write("\"");
- WriteEscape(property.Value);
- Write("\"");
- }
+ Write("=").Write("\"");
+ WriteEscape(property.Value);
+ Write("\"");
}
}
}