AnkiIO 1.0.2
Build, validate, import, and export Anki-compatible decks from .NET
Loading...
Searching...
No Matches
AnkiNoteType.cs
1using System.Collections.ObjectModel;
2
3namespace AnkiIO;
4
45public sealed class AnkiNoteType
46{
47 internal const string DefaultCss = ".card { font-family: Arial; font-size: 20px; text-align: center; color: black; background: white; }";
48
49 private readonly List<AnkiField> fields = [];
50 private readonly List<AnkiCardTemplate> templates = [];
51 private readonly ReadOnlyCollection<AnkiField> fieldsView;
52 private readonly ReadOnlyCollection<AnkiCardTemplate> templatesView;
53 private string css = DefaultCss;
54 private bool isFrozen;
55
71 public AnkiNoteType(string name, AnkiNoteTypeKind kind = AnkiNoteTypeKind.Standard, long? id = null)
72 {
73 ArgumentException.ThrowIfNullOrWhiteSpace(name);
74 Name = name;
75 Kind = kind;
76 Id = id ?? AnkiId.New();
77 fieldsView = fields.AsReadOnly();
78 templatesView = templates.AsReadOnly();
79 }
80
88 public long Id { get; }
89
92 public string Name { get; }
93
96 public AnkiNoteTypeKind Kind { get; }
97
107 public string Css
108 {
109 get => css;
110 set
111 {
112 ArgumentNullException.ThrowIfNull(value);
113 EnsureMutable();
114 css = value;
115 }
116 }
117
127 public bool IsFrozen => isFrozen;
128
135 public IReadOnlyList<AnkiField> Fields => fieldsView;
136
143 public IReadOnlyList<AnkiCardTemplate> Templates => templatesView;
144
155 public AnkiNoteType AddField(string name)
156 {
157 EnsureMutable();
158 ArgumentException.ThrowIfNullOrWhiteSpace(name);
159 if (fields.Any(field => string.Equals(field.Name, name, StringComparison.OrdinalIgnoreCase)))
160 {
161 throw new ArgumentException($"Field '{name}' already exists.", nameof(name));
162 }
163
164 fields.Add(new AnkiField(name));
165 return this;
166 }
167
183 {
184 EnsureMutable();
185 ArgumentNullException.ThrowIfNull(field);
186 ArgumentException.ThrowIfNullOrWhiteSpace(field.Name);
187 ArgumentException.ThrowIfNullOrWhiteSpace(field.Font);
188 ArgumentOutOfRangeException.ThrowIfLessThan(field.FontSize, 1);
189 if (fields.Any(existing => string.Equals(existing.Name, field.Name, StringComparison.OrdinalIgnoreCase)))
190 {
191 throw new ArgumentException($"Field '{field.Name}' already exists.", nameof(field));
192 }
193
194 fields.Add(field);
195 return this;
196 }
197
210 public AnkiNoteType AddTemplate(string name, string questionFormat, string answerFormat)
211 {
212 EnsureMutable();
213 ArgumentException.ThrowIfNullOrWhiteSpace(name);
214 ArgumentNullException.ThrowIfNull(questionFormat);
215 ArgumentNullException.ThrowIfNull(answerFormat);
216 if (templates.Any(template => string.Equals(template.Name, name, StringComparison.OrdinalIgnoreCase)))
217 {
218 throw new ArgumentException($"Template '{name}' already exists.", nameof(name));
219 }
220
221 templates.Add(new AnkiCardTemplate(name, questionFormat, answerFormat));
222 return this;
223 }
224
243 {
244 EnsureMutable();
245 ArgumentNullException.ThrowIfNull(template);
246 ArgumentException.ThrowIfNullOrWhiteSpace(template.Name);
247 ArgumentNullException.ThrowIfNull(template.QuestionFormat);
248 ArgumentNullException.ThrowIfNull(template.AnswerFormat);
249 if (templates.Any(existing => string.Equals(existing.Name, template.Name, StringComparison.OrdinalIgnoreCase)))
250 {
251 throw new ArgumentException($"Template '{template.Name}' already exists.", nameof(template));
252 }
253
254 templates.Add(template);
255 return this;
256 }
257
258 internal bool HasEquivalentDefinition(AnkiNoteType other) =>
259 string.Equals(Name, other.Name, StringComparison.Ordinal)
260 && Kind == other.Kind
261 && string.Equals(Css, other.Css, StringComparison.Ordinal)
262 && fields.SequenceEqual(other.fields)
263 && templates.SequenceEqual(other.templates);
264
265 internal void Freeze() => isFrozen = true;
266
267 private void EnsureMutable()
268 {
269 if (isFrozen)
270 {
271 throw new InvalidOperationException($"Note type '{Name}' is frozen because it is already used by a note.");
272 }
273 }
274}
Defines one study direction by mapping note fields to a card front and back.
Configures one named input field in an AnkiNoteType.
Definition AnkiField.cs:45
int FontSize
Gets the note-editor font-size preference in pixels, not the rendered card size.
Definition AnkiField.cs:64
string Font
Gets the note-editor font-family preference, not the rendered card font.
Definition AnkiField.cs:60
string Name
Gets the exact field name used by note values and template references.
Definition AnkiField.cs:48
Creates positive 64-bit identifiers for new objects or repeatable external imports.
Definition AnkiId.cs:15
static long New()
Creates a positive, process-unique identifier for a new deck, note type, note, or card.
Definition AnkiId.cs:31
Defines the reusable schema and rendering rules shared by a family of Anki notes.
AnkiNoteTypeKind Kind
Gets the immutable strategy used to turn one note into cards.
AnkiNoteType AddConfiguredTemplate(AnkiCardTemplate template)
Adds a configured card template at the next card ordinal.
AnkiNoteType AddTemplate(string name, string questionFormat, string answerFormat)
Adds a card template at the next ordinal.
bool IsFrozen
Gets whether fields, templates, and CSS can no longer be changed.
AnkiNoteType(string name, AnkiNoteTypeKind kind=AnkiNoteTypeKind.Standard, long? id=null)
Initializes an unfrozen note type with no fields or templates and default card CSS.
IReadOnlyList< AnkiCardTemplate > Templates
Gets templates in zero-based card-ordinal order.
string Css
Gets or sets CSS shared by every card rendered from this note type.
long Id
Gets the persisted identity by which notes and package metadata refer to this definition.
AnkiNoteType AddField(string name)
Adds a uniquely named field at the end of the field order.
IReadOnlyList< AnkiField > Fields
Gets fields in positional storage and Anki-editor order.
AnkiNoteType AddConfiguredField(AnkiField field)
Adds a configured field at the end of the field order.
string Name
Gets the user-visible note-type name.
AnkiNoteTypeKind
Specifies how an AnkiNoteType turns one note into study cards.