Skip to content

Commit

Permalink
feat: increase compatibility with Apple iCalendar
Browse files Browse the repository at this point in the history
  • Loading branch information
robert-virkus committed Sep 27, 2022
1 parent 13ebf09 commit 126a9f2
Show file tree
Hide file tree
Showing 3 changed files with 458 additions and 18 deletions.
30 changes: 18 additions & 12 deletions lib/src/components.dart
Original file line number Diff line number Diff line change
Expand Up @@ -157,12 +157,15 @@ abstract class VComponent {
/// Parses the component from the specified [text].
///
/// When succeeding, this returns a [VCalendar], [VEvent] or similar component as defined by the given [text].
/// The [text] can either contain `\r\n` (`CRLF`) or `\n` linebreaks, when both linebreak types are present in the [text], `CRLF` linebreaks are assumed.
///
/// The [text] can either contain `\r\n` (`CRLF`) or `\n` line-breaks, when both line-break types are present in the [text], `CRLF` line-breaks are assumed.
/// Folded lines are unfolded automatically.
/// When you have a custom line delimiter, use [parseLines] instead.
/// Define the [customParser] if you want to support specific properties. Note that unknown properties will be made available with [getProperty], e.g. `final value = component.getProperty('X-NAME')?.textValue;`
static VComponent parse(String text,
{Property? Function(String name, String definition)? customParser}) {
static VComponent parse(
String text, {
Property? Function(String name, String definition)? customParser,
}) {
final containsStandardCompliantLineBreaks = text.contains('\r\n');
final foldedLines = containsStandardCompliantLineBreaks
? text.split('\r\n')
Expand Down Expand Up @@ -239,10 +242,13 @@ abstract class VComponent {

/// Unfolds the given [input] lines
///
/// When [containsStandardCompliantLineBreaks] is not the default `true`, then extra care is taken
/// to re-include lines that have been split in error.
static List<String> unfold(List<String> input,
{bool containsStandardCompliantLineBreaks = true}) {
/// When [containsStandardCompliantLineBreaks] is not the default `true`,
/// some care is taken to re-include lines that may have been
/// split in error, however this is not perfect.
static List<String> unfold(
List<String> input, {
bool containsStandardCompliantLineBreaks = true,
}) {
final output = <String>[];
StringBuffer? buffer;
for (var i = 0; i < input.length; i++) {
Expand All @@ -256,7 +262,7 @@ abstract class VComponent {
continue;
} else if (!containsStandardCompliantLineBreaks &&
!current.contains(':')) {
// this can happen when the description or similiar fields also contain \n linebreaks
// this can happen when the description or similar fields also contain \n line-breaks
buffer
..write('\n')
..write(current.trimLeft());
Expand Down Expand Up @@ -1537,9 +1543,9 @@ class VTimezone extends VComponent {
void checkValidity() {
super.checkValidity();
checkMandatoryProperty(TextProperty.propertyNameTimezoneId);
if (children.length < 2) {
if (children.length == 0) {
throw FormatException(
'A valid VTIMEZONE requires at least one STANDARD and one DAYLIGHT sub-component');
'A valid VTIMEZONE requires at least one STANDARD or one DAYLIGHT sub-component');
}
var numberOfStandardChildren = 0, numberOfDaylightChildren = 0;
for (final phase in children) {
Expand All @@ -1549,9 +1555,9 @@ class VTimezone extends VComponent {
numberOfDaylightChildren++;
}
}
if (numberOfStandardChildren == 0 || numberOfDaylightChildren == 0) {
if (numberOfStandardChildren == 0 && numberOfDaylightChildren == 0) {
throw FormatException(
'A valid VTIMEZONE requires at least one STANDARD and one DAYLIGHT sub-component');
'A valid VTIMEZONE requires at least one STANDARD or one DAYLIGHT sub-component');
}
}

Expand Down
27 changes: 21 additions & 6 deletions lib/src/properties.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ import 'parameters.dart';
import 'types.dart';
import 'util.dart';

/// Defines an iCalendar property
class Property {
Property(this.definition, ValueType defaultValueType,
{dynamic Function(Property property, String textValue)? parser})
: name = _getName(definition),
/// Creates a new [Property]
Property(
this.definition,
ValueType defaultValueType, {
dynamic Function(Property property, String textValue)? parser,
}) : name = _getName(definition),
textValue = _getTextContent(definition),
parameters = _parseParameters(definition) {
if (parser != null) {
Expand All @@ -30,18 +34,28 @@ class Property {
/// Additional parameters for this property, e.g. `{'TZID' : TextValueType('America/New_York')}}
final Map<String, Parameter> parameters;

/// Parses this property.
///
/// The default implementation does not support parsing and
/// throws a [FormatException]
dynamic parse(String textValue) {
throw FormatException(
'Implement parse to allow custom value in property: $definition');
'Implement parse to allow custom value in property: $definition',
);
}

/// Retrieves the parameter with the given [type]
Parameter? operator [](ParameterType type) => parameters[type.name];

//// Sets the parameter [value] for the given [type]
operator []=(ParameterType type, Parameter value) =>
parameters[value.name] = value;

/// Sets the parameter [value]
void setParameter(Parameter value) => parameters[value.name] = value;

/// Sets the parameter when [value] is not null
/// and removes the parameter when [value] is null.
void setOrRemoveParameter(ParameterType type, Parameter? value) {
if (value == null) {
parameters.remove(type.name);
Expand All @@ -50,8 +64,9 @@ class Property {
}
}

T? getParameterValue<T>(ParameterType param) =>
parameters[param.name]?.value as T?;
/// Retrieves the parameter value for [type]
T? getParameterValue<T>(ParameterType type) =>
parameters[type.name]?.value as T?;

static String? _lastContent;
static List<int>? _lastRunes;
Expand Down
Loading

0 comments on commit 126a9f2

Please sign in to comment.