mirror of
https://github.com/xoofx/markdig.git
synced 2026-10-08 14:48:32 +00:00
Add better helpers for HtmlAttributes. Use this for MediaExtension
This commit is contained in:
@@ -11,6 +11,6 @@ Allows to embed audio/video links to popular website:
|
||||
|
||||

|
||||
.
|
||||
<p><iframe width="420" height="315" src="https://www.youtube.com/embed/mswPy5bt3TQ" frameborder="0" allowfullscreen></iframe></p>
|
||||
<p><iframe width="500" height="281" src="https://player.vimeo.com/video/8607834" frameborder="0" allowfullscreen></iframe></p>
|
||||
<p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" frameborder="0" allowfullscreen></iframe></p>
|
||||
<p><iframe src="https://player.vimeo.com/video/8607834" width="500" height="281" frameborder="0" allowfullscreen></iframe></p>
|
||||
````````````````````````````````
|
||||
@@ -18428,11 +18428,11 @@ namespace Textamina.Markdig.Tests
|
||||
// 
|
||||
//
|
||||
// Should be rendered as:
|
||||
// <p><iframe width="420" height="315" src="https://www.youtube.com/embed/mswPy5bt3TQ" frameborder="0" allowfullscreen></iframe></p>
|
||||
// <p><iframe width="500" height="281" src="https://player.vimeo.com/video/8607834" frameborder="0" allowfullscreen></iframe></p>
|
||||
// <p><iframe src="https://www.youtube.com/embed/mswPy5bt3TQ" width="500" height="281" frameborder="0" allowfullscreen></iframe></p>
|
||||
// <p><iframe src="https://player.vimeo.com/video/8607834" width="500" height="281" frameborder="0" allowfullscreen></iframe></p>
|
||||
|
||||
Console.WriteLine("Example {0}" + Environment.NewLine + "Section: {0}" + Environment.NewLine, 1, "Extensions Media links");
|
||||
TestParser.TestSpec("\n\n", "<p><iframe width=\"420\" height=\"315\" src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" frameborder=\"0\" allowfullscreen></iframe></p>\n<p><iframe width=\"500\" height=\"281\" src=\"https://player.vimeo.com/video/8607834\" frameborder=\"0\" allowfullscreen></iframe></p>", "medias");
|
||||
TestParser.TestSpec("\n\n", "<p><iframe src=\"https://www.youtube.com/embed/mswPy5bt3TQ\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen></iframe></p>\n<p><iframe src=\"https://player.vimeo.com/video/8607834\" width=\"500\" height=\"281\" frameborder=\"0\" allowfullscreen></iframe></p>", "medias");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<string, string>(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<string, string> ExtensionToMimeType { get; }
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Extension for extending image Markdown links in case a video or an audio file is linked and output proper link.
|
||||
/// </summary>
|
||||
/// <seealso cref="Textamina.Markdig.IMarkdownExtension" />
|
||||
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(
|
||||
$"<iframe width=\"{Options.Width}\" height=\"{Options.Height}\" src=\"https://www.youtube.com/embed/{query[0].Substring(2)}\" frameborder=\"0\" allowfullscreen></iframe>");
|
||||
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(
|
||||
$"<iframe width=\"{Options.Width}\" height=\"{Options.Height}\" src=\"https://player.vimeo.com/video/{items[items.Length - 1]}\" frameborder=\"0\" allowfullscreen></iframe>");
|
||||
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($"<audio width=\"{Options.Width}\" controls class=\"audioplayer\"><source type=\"audio/{audioType}\" src=\"{linkInline.Url}\"></source></audio>");
|
||||
renderer.Write($"<iframe src=\"{iFrameUrl}\"");
|
||||
htmlAttributes.AddPropertyIfNotExist("width", Options.Width);
|
||||
htmlAttributes.AddPropertyIfNotExist("height", Options.Height);
|
||||
htmlAttributes.AddPropertyIfNotExist("frameborder", "0");
|
||||
htmlAttributes.AddPropertyIfNotExist("allowfullscreen", null);
|
||||
renderer.WriteAttributes(htmlAttributes);
|
||||
renderer.Write("></iframe>");
|
||||
return true;
|
||||
}
|
||||
else if (isMp4 || isWebM)
|
||||
else
|
||||
{
|
||||
var videoType = isMp4 ? "mp4" : "webm";
|
||||
renderer.Write($"<video width=\"{Options.Width}\" height=\"{Options.Height}\" controls class=\"videoplayer\"><source type=\"video/{videoType}\" src=\"{linkInline.Url}\"></source></video>");
|
||||
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($"><source type=\"{mimeType}\" src=\"{linkInline.Url}\"></source></{tagType}>");
|
||||
renderer.Write("</iframe>");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
90
src/Textamina.Markdig/Extensions/Medias/MediaOptions.cs
Normal file
90
src/Textamina.Markdig/Extensions/Medias/MediaOptions.cs
Normal file
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// Options for the <see cref="MediaExtension"/>.
|
||||
/// </summary>
|
||||
public class MediaOptions
|
||||
{
|
||||
public MediaOptions()
|
||||
{
|
||||
Width = "500";
|
||||
Height = "281";
|
||||
ExtensionToMimeType = new Dictionary<string, string>(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<string, string> ExtensionToMimeType { get; }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies the values from this instance to the specified <see cref="HtmlAttributes"/> instance.
|
||||
/// Adds the specified property only if it does not already exist.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
public void AddPropertyIfNotExist(string name, object value)
|
||||
{
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
if (Properties == null)
|
||||
{
|
||||
Properties = new List<KeyValuePair<string, string>> {new KeyValuePair<string, string>(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<string, string>(name, value == null ? null : Convert.ToString(value, CultureInfo.InvariantCulture)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies/merge the values from this instance to the specified <see cref="HtmlAttributes"/> instance.
|
||||
/// </summary>
|
||||
/// <param name="htmlAttributes">The HTML attributes.</param>
|
||||
/// <param name="mergeIdAndProperties">If set to <c>true</c> it will merge properties to the target htmlAttributes. Default is <c>false</c></param>
|
||||
/// <param name="shared">If set to <c>true</c> it will try to share Classes and Properties if destination don't have them, otherwise it will make a copy. Default is <c>true</c></param>
|
||||
/// <exception cref="System.ArgumentNullException"></exception>
|
||||
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<string>(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<KeyValuePair<string, string>>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Writes the specified <see cref="HtmlAttributes"/>.
|
||||
/// </summary>
|
||||
/// <param name="attributes">The attributes to render.</param>
|
||||
/// <returns>This instance</returns>
|
||||
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("\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user