Skip to content

Commit 1ff9058

Browse files
committed
#13 Add WikiImageLink node type.
Rename: FILE_LINK --> IMAGE_LINK
1 parent 9c3048a commit 1ff9058

File tree

5 files changed

+268
-31
lines changed

5 files changed

+268
-31
lines changed

CFG.txt

+8-7
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,15 @@ EXPANDABLE_URL -> EXPANDABLE EXPANDABLE_URL
8181
/ URL
8282
/ empty
8383

84-
FILE_LINK -> "[[" FILE_LINK_TARGET FILE_LINK_OPTION* FILE_LINK_CAPTION? "]]"
85-
>| "]]" # FILE_LINK_OPTION and FILE_LINK_CAPTION can have \n inside.
86-
FILE_LINK_TARGET -> "[\s_]*(File|Image|<...>)[\s_]*:" EXPANDABLE_TEXT
84+
# Syntax for inserting an image or thumbnail
85+
# c.f. https://www.mediawiki.org/wiki/Help:Images#Rendering_a_single_image
86+
IMAGE_LINK -> "[[" IMAGE_LINK_TARGET IMAGE_LINK_ARGUMENT* "]]"
87+
>| "]]" # IMAGE_LINK_OPTION and IMAGE_LINK_CAPTION can have \n inside.
88+
IMAGE_LINK_TARGET -> "[\s_]*(File|Image|<...>)[\s_]*:" EXPANDABLE_TEXT
8789
+| "|" # <...> is customizable File namespace aliases.
88-
FILE_LINK_OPTION -> "|" EXPANDABLE_TEXT
89-
+| "|"
90-
FILE_LINK_CAPTION -> "|" EXPANDABLE_TEXT # FILE_LINK_CAPTION is actually the last FILE_LINK_OPTION
91-
+| "]]"
90+
IMAGE_LINK_ARGUMENT -> "|" WIKITEXT "=" WIKITEXT
91+
/ "|" WIKITEXT # IMAGE_LINK_CAPTION is actually the last IMAGE_LINK_ARGUMENT
92+
+| "|" / "]]"
9293

9394
# Known issue: Current implementation will parse [[http://abc]] as WIKI_LINK,
9495
# while actually it should be trated as "[" EXTERNAL_LINK "]"

MwParserFromScratch/MwParserUtility.cs

+32-17
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,9 @@ namespace MwParserFromScratch
1212
/// </summary>
1313
public static class MwParserUtility
1414
{
15-
/// <summary>
16-
/// Normalizes a template argument name.
17-
/// </summary>
18-
/// <param name="argumentName">The argument name to be normalized.</param>
19-
/// <returns>The normalized argument name, with leading and trailing whitespace removed,
20-
/// or <c>null</c> if <see cref="argumentName"/> is <c>null</c>.</returns>
15+
16+
/// <inheritdoc cref="NormalizeTemplateArgumentName(string)"/>
17+
/// <param name="argumentName">The argument name to be normalized. The node will be converted into its string representation.</param>
2118
public static string NormalizeTemplateArgumentName(Node argumentName)
2219
{
2320
if (argumentName == null) return null;
@@ -29,23 +26,41 @@ public static string NormalizeTemplateArgumentName(Node argumentName)
2926
/// </summary>
3027
/// <param name="argumentName">The argument name to be normalized.</param>
3128
/// <returns>The normalized argument name, with leading and trailing whitespace removed,
32-
/// or <c>null</c> if <see cref="argumentName"/> is <c>null</c>.</returns>
29+
/// or <c>null</c> if <paramref name="argumentName"/> is <c>null</c>.</returns>
3330
public static string NormalizeTemplateArgumentName(string argumentName)
3431
{
3532
if (string.IsNullOrEmpty(argumentName)) return argumentName;
3633
return argumentName.Trim();
3734
}
3835

36+
/// <inheritdoc cref="NormalizeImageLinkArgumentName(string)"/>
37+
/// <param name="argumentName">The argument name to be normalized. The node will be converted into its string representation.</param>
38+
public static string NormalizeImageLinkArgumentName(Node argumentName)
39+
{
40+
if (argumentName == null) return null;
41+
return NormalizeImageLinkArgumentName(argumentName.ToString());
42+
}
43+
3944
/// <summary>
40-
/// Normalizes a page title expression. This is a simple version; it simply treats the part before
41-
/// the first colon mark as namesapce name. For a more complete version of title normalization,
42-
/// including title validation and namespace / interwiki prefix check,
43-
/// see WikiLink class in WikiClientLibrary package.
45+
/// Normalizes the argument name used in Image link syntax.
4446
/// </summary>
45-
/// <param name="title">The title to be normalized.</param>
46-
/// <returns>The normalized argument name, with leading and trailing whitespace removed,
47-
/// underscore replaced with space, starting with an upper-case letter.
48-
/// Or <c>null</c> if <see cref="title"/> is <c>null</c>.</returns>
47+
/// <param name="argumentName">The argument name to be normalized.</param>
48+
/// <returns>The normalized argument name, with leading and trailing whitespace removed, and first letter converted into lowercase
49+
/// or <c>null</c> if <paramref name="argumentName"/> is <c>null</c>.</returns>
50+
public static string NormalizeImageLinkArgumentName(string argumentName)
51+
{
52+
if (string.IsNullOrEmpty(argumentName)) return argumentName;
53+
argumentName = argumentName.Trim();
54+
if (argumentName.Length > 0 && char.IsUpper(argumentName, 0))
55+
{
56+
return char.ToLowerInvariant(argumentName[0]) + argumentName.Substring(1);
57+
}
58+
return argumentName;
59+
}
60+
61+
62+
/// <inheritdoc cref="NormalizeTitle(string)"/>
63+
/// <param name="title">The title to be normalized. The node will be converted into its string representation.</param>
4964
public static string NormalizeTitle(Node title)
5065
{
5166
if (title == null) return null;
@@ -54,14 +69,14 @@ public static string NormalizeTitle(Node title)
5469

5570
/// <summary>
5671
/// Normalizes a page title expression. This is a simple version; it simply treats the part before
57-
/// the first colon mark as namesapce name. For a more complete version of title normalization,
72+
/// the first colon mark as namespace name. For a more complete version of title normalization,
5873
/// including title validation and namespace / interwiki prefix check,
5974
/// see WikiLink class in WikiClientLibrary package.
6075
/// </summary>
6176
/// <param name="title">The title to be normalized.</param>
6277
/// <returns>The normalized argument name, with leading and trailing whitespace removed,
6378
/// underscore replaced with space, starting with an upper-case letter.
64-
/// Or <c>null</c> if <see cref="title"/> is <c>null</c>.</returns>
79+
/// Or <c>null</c> if <paramref name="title"/> is <c>null</c>.</returns>
6580
public static string NormalizeTitle(string title)
6681
{
6782
if (string.IsNullOrEmpty(title)) return title;

MwParserFromScratch/Nodes/Inline.cs

+129-1
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,134 @@ internal override void ToPlainTextCore(StringBuilder builder, NodePlainTextForma
127127
}
128128
}
129129

130+
/// <summary>
131+
/// <c>[[File:Image.png|arg1|arg2|alt text]]</c>
132+
/// </summary>
133+
public class WikiImageLink : InlineNode
134+
{
135+
private Run _Target;
136+
137+
public WikiImageLink() : this(null)
138+
{
139+
}
140+
141+
public WikiImageLink(Run target)
142+
{
143+
Target = target;
144+
Arguments = new WikiImageLinkArgumentCollection(this);
145+
}
146+
147+
/// <summary>
148+
/// Title of the image.
149+
/// </summary>
150+
public Run Target
151+
{
152+
get { return _Target; }
153+
set { Attach(ref _Target, value); }
154+
}
155+
156+
/// <summary>
157+
/// Image rendering arguments.
158+
/// </summary>
159+
public WikiImageLinkArgumentCollection Arguments { get; }
160+
161+
/// <inheritdoc />
162+
public override IEnumerable<Node> EnumChildren()
163+
{
164+
if (_Target != null) yield return _Target;
165+
foreach (var argument in Arguments) yield return argument;
166+
}
167+
168+
/// <inheritdoc />
169+
protected override Node CloneCore()
170+
{
171+
return new WikiImageLink(Target) { Arguments = { Arguments } };
172+
}
173+
174+
/// <inheritdoc />
175+
internal override void ToPlainTextCore(StringBuilder builder, NodePlainTextFormatter formatter)
176+
{
177+
var alt = Arguments.Alt;
178+
if (alt != null) formatter(alt, builder);
179+
var caption = Arguments.Caption;
180+
// delimit alt text and caption with a space.
181+
if (alt != null && caption != null)
182+
builder.Append(' ');
183+
if (caption != null) formatter(caption, builder);
184+
}
185+
}
186+
187+
/// <summary>
188+
/// An argument field of <see cref="WikiImageLink"/>.
189+
/// </summary>
190+
public class WikiImageLinkArgument : Node
191+
{
192+
private Wikitext _Name;
193+
private Wikitext _Value;
194+
195+
public WikiImageLinkArgument() : this(null, null)
196+
{
197+
}
198+
199+
public WikiImageLinkArgument(Wikitext name, Wikitext value)
200+
{
201+
Name = name;
202+
Value = value;
203+
}
204+
205+
/// <summary>
206+
/// Name of the argument.
207+
/// </summary>
208+
/// <value>Name of the argument, or <c>null</c> if the argument is anonymous.</value>
209+
public Wikitext Name
210+
{
211+
get { return _Name; }
212+
set { Attach(ref _Name, value); }
213+
}
214+
215+
/// <summary>
216+
/// Value of the argument.
217+
/// </summary>
218+
/// <value>Value of the argument. If the value is empty, it should be an empty <see cref="Wikitext"/> instance.</value>
219+
public Wikitext Value
220+
{
221+
get { return _Value; }
222+
set { Attach(ref _Value, value); }
223+
}
224+
225+
/// <summary>
226+
/// Enumerates the children of this node.
227+
/// </summary>
228+
[EditorBrowsable(EditorBrowsableState.Never)]
229+
public override IEnumerable<Node> EnumChildren()
230+
{
231+
if (_Name != null) yield return _Name;
232+
if (_Value != null) yield return _Value;
233+
}
234+
235+
protected override Node CloneCore()
236+
{
237+
var n = new TemplateArgument { Name = Name, Value = Value };
238+
return n;
239+
}
240+
241+
public override string ToString()
242+
{
243+
if (Name == null) return Value.ToString();
244+
return Name + "=" + Value;
245+
}
246+
247+
/// <summary>
248+
/// Infrastructure. This function will always throw a <seealso cref="NotSupportedException"/>.
249+
/// </summary>
250+
/// <param name="builder"></param>
251+
/// <param name="formatter"></param>
252+
internal override void ToPlainTextCore(StringBuilder builder, NodePlainTextFormatter formatter)
253+
{
254+
throw new NotSupportedException();
255+
}
256+
}
257+
130258
public class ExternalLink : InlineNode
131259
{
132260
private Run _Target;
@@ -400,7 +528,7 @@ internal override void ToPlainTextCore(StringBuilder builder, NodePlainTextForma
400528
}
401529

402530
/// <summary>
403-
/// {{{name|defalut}}}
531+
/// {{{name|default}}}
404532
/// </summary>
405533
public class ArgumentReference : InlineNode
406534
{

MwParserFromScratch/Nodes/TemplateArgumentCollection.cs

+5-6
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,15 @@ private IEnumerable<KeyValuePair<string, TemplateArgument>> EnumNameArgumentPair
5555
/// The name of argument that will be tested. Can either be a name or 1-based index.
5656
/// Leading and trailing white spaces will be ignored.
5757
/// </param>
58-
/// <exception cref="ArgumentNullException"><see cref="name"/> is <c>null</c>.</exception>
58+
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c>.</exception>
5959
/// <returns>A matching <see cref="TemplateArgument"/> with the specified name, or <c>null</c> if no matching template is found.</returns>
6060
public TemplateArgument this[string name]
6161
{
6262
get
6363
{
6464
if (name == null) throw new ArgumentNullException(nameof(name));
6565
name = name.Trim();
66-
// We want to choose the last matching arguments, if there're multiple choices.
66+
// We want to choose the last matching arguments, if there are multiple choices.
6767
return EnumNameArgumentPairs(true).FirstOrDefault(p => p.Key == name).Value;
6868
}
6969
}
@@ -72,12 +72,11 @@ public TemplateArgument this[string name]
7272
/// Gets an argument with the specified positional argument index.
7373
/// </summary>
7474
/// <param name="name">
75-
/// The index of argument that will be tested. Note that this index will not nessarily greater
75+
/// The index of argument that will be tested. Note that this index will not necessarily greater
7676
/// or equal than 1, because there might exist template argument with the name such as "-1", which
7777
/// can still be matched using this accessor.
7878
/// </param>
7979
/// <returns>A matching <see cref="TemplateArgument"/> with the specified name, or <c>null</c> if no matching template is found.</returns>
80-
/// <exception cref="ArgumentNullException"><see cref="name"/> is <c>null</c>.</exception>
8180
public TemplateArgument this[int name] => this[name.ToString()];
8281

8382
/// <summary>
@@ -87,7 +86,7 @@ public TemplateArgument this[string name]
8786
/// The name of argument that will be tested. Can either be a name or 1-based index.
8887
/// Leading and trailing white spaces will be ignored.
8988
/// </param>
90-
/// <exception cref="ArgumentNullException"><see cref="name"/> is <c>null</c>.</exception>
89+
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c>.</exception>
9190
public bool Contains(string name)
9291
{
9392
if (name == null) throw new ArgumentNullException(nameof(name));
@@ -99,7 +98,7 @@ public bool Contains(string name)
9998
/// Determines whether an argument with the specified positional argument index exists.
10099
/// </summary>
101100
/// <param name="name">
102-
/// The index of argument that will be tested. Note that this index will not nessarily greater
101+
/// The index of argument that will be tested. Note that this index will not necessarily greater
103102
/// or equal than 1, because there might exist template argument with the name such as "-1", which
104103
/// can still be matched using this accessor.
105104
/// </param>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
6+
namespace MwParserFromScratch.Nodes
7+
{
8+
/// <summary>
9+
/// Represents a collection of <see cref="WikiImageLinkArgument"/>
10+
/// that can be accessed via argument names.
11+
/// </summary>
12+
public class WikiImageLinkArgumentCollection : NodeCollection<WikiImageLinkArgument>
13+
{
14+
/// <inheritdoc />
15+
internal WikiImageLinkArgumentCollection(Node owner) : base(owner)
16+
{
17+
}
18+
19+
private IEnumerable<KeyValuePair<string, WikiImageLinkArgument>> EnumNameArgumentPairs(bool reverse)
20+
{
21+
return (reverse ? Reverse() : this).Select(arg =>
22+
new KeyValuePair<string, WikiImageLinkArgument>(MwParserUtility.NormalizeImageLinkArgumentName(arg.Name), arg));
23+
}
24+
25+
/// <summary>
26+
/// Enumerates the normalized name-<see cref="WikiImageLinkArgument"/> pairs in the collection.
27+
/// </summary>
28+
/// <remarks>If there are arguments with duplicate names, they will nonetheless be included in the sequence.</remarks>
29+
public IEnumerable<KeyValuePair<string, WikiImageLinkArgument>> EnumNameArgumentPairs()
30+
{
31+
return EnumNameArgumentPairs(false);
32+
}
33+
34+
/// <summary>
35+
/// Gets an named argument (<c>name=value</c>) with the specified name.
36+
/// </summary>
37+
/// <param name="name">
38+
/// The name of argument that will be tested. Leading and trailing white spaces will be ignored. First letter will be normalized.
39+
/// </param>
40+
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c>.</exception>
41+
/// <returns>A matching <see cref="WikiImageLinkArgument"/> with the specified name, or <c>null</c> if no matching template is found.</returns>
42+
public WikiImageLinkArgument this[string name]
43+
{
44+
get
45+
{
46+
if (name == null) throw new ArgumentNullException(nameof(name));
47+
name = MwParserUtility.NormalizeImageLinkArgumentName(name);
48+
// We want to choose the last matching arguments, if there are multiple choices.
49+
return EnumNameArgumentPairs(true).FirstOrDefault(p => p.Key == name).Value;
50+
}
51+
}
52+
53+
/// <summary>
54+
/// Gets the value of <c>link=</c> option, if available.
55+
/// </summary>
56+
public Wikitext Link => this["link"]?.Value;
57+
58+
/// <summary>
59+
/// Gets the value of <c>alt=</c> option, if available.
60+
/// </summary>
61+
public Wikitext Alt => this["alt"]?.Value;
62+
63+
/// <summary>
64+
/// Gets the value of <c>page=</c> option, if available.
65+
/// </summary>
66+
public Wikitext Page => this["page"]?.Value;
67+
68+
/// <summary>
69+
/// Gets the value of <c>class=</c> option, if available.
70+
/// </summary>
71+
public Wikitext ClassName => this["class"]?.Value;
72+
73+
/// <summary>
74+
/// Gets the value of <c>lang=</c> option, if available.
75+
/// </summary>
76+
public Wikitext Lang => this["lang"]?.Value;
77+
78+
/// <summary>
79+
/// Gets the image caption, if available.
80+
/// </summary>
81+
/// <remarks>The caption of the image is the last unnamed argument that is also after the last named argument.</remarks>
82+
public Wikitext Caption
83+
{
84+
get
85+
{
86+
return EnumNameArgumentPairs(true)
87+
.TakeWhile(p => p.Key == null)
88+
.FirstOrDefault()
89+
.Value?.Value;
90+
}
91+
}
92+
93+
}
94+
}

0 commit comments

Comments
 (0)