private static ProjectData SetTaskPredecessorsOrSuccessors(ProjectData project, TaskId id, string value, bool isSuccessors)
        {
            var linkField = isSuccessors ? TaskFields.SuccessorLinks : TaskFields.PredecessorLinks;

            value = value.Trim();

            var predecessorsBuilder = ImmutableArray.CreateBuilder <TaskId>();
            var remainingTaskLinks  = project.Get(linkField, id).ToList();

            if (value.Length > 0)
            {
                var parts = value.Split(',');

                foreach (var part in parts)
                {
                    var partText = part.Trim();

                    var taskText = partText;
                    var sign     = Math.Max(partText.IndexOf('+'), partText.IndexOf('-'));
                    var lag      = TimeSpan.Zero;

                    if (sign >= 0)
                    {
                        var lagText = partText.Substring(sign).Trim();
                        lag      = project.Information.TimeConversion.ParseDuration(lagText);
                        taskText = partText.Substring(0, sign).Trim();
                    }

                    var taskOrdinalText = taskText;
                    var linkType        = (TaskLinkType?)null;

                    if (taskText.EndsWith("FS", StringComparison.OrdinalIgnoreCase))
                    {
                        linkType = TaskLinkType.FinishToStart;
                    }
                    else if (taskText.EndsWith("SS", StringComparison.OrdinalIgnoreCase))
                    {
                        linkType = TaskLinkType.StartToStart;
                    }
                    else if (taskText.EndsWith("FF", StringComparison.OrdinalIgnoreCase))
                    {
                        linkType = TaskLinkType.FinishToFinish;
                    }
                    else if (taskText.EndsWith("SF", StringComparison.OrdinalIgnoreCase))
                    {
                        linkType = TaskLinkType.StartToFinish;
                    }

                    if (linkType != null)
                    {
                        taskOrdinalText = taskText.Substring(0, taskText.Length - 2).Trim();
                    }
                    else if (sign < 0)
                    {
                        linkType = TaskLinkType.FinishToStart;
                    }
                    else
                    {
                        throw new FormatException($"'{partText}' isn't a valid value");
                    }

                    if (!int.TryParse(taskOrdinalText, out var taskOrdinal))
                    {
                        throw new FormatException($"'{taskOrdinalText}' isn't a valid int");
                    }

                    var taskId = project.GetTask(taskOrdinal);
                    if (taskId.IsDefault)
                    {
                        throw new FormatException($"'{taskOrdinal}' isn't a valid task");
                    }

                    var predecessorId = isSuccessors ? id : taskId;
                    var successorId   = isSuccessors ? taskId : id;

                    var taskLink = TaskLink.Create(predecessorId, successorId, linkType.Value, lag);

                    var existingLink = project.GetTaskLink(predecessorId, successorId);
                    if (existingLink != null)
                    {
                        project = project.RemoveTaskLink(existingLink);
                    }
                    else if (project.TaskLinkCausesCycle(taskLink))
                    {
                        var predecessorOrdinal = project.Get(TaskFields.Ordinal, predecessorId);
                        var successorOrdinal   = project.Get(TaskFields.Ordinal, successorId);
                        throw new InvalidOperationException($"Cannot add a link from task {predecessorOrdinal} to task {successorOrdinal} as this would cause a cycle.");
                    }

                    project = project.AddTaskLink(taskLink);

                    remainingTaskLinks.Remove(taskLink);
                }
            }

            foreach (var taskLink in remainingTaskLinks)
            {
                project = project.RemoveTaskLink(taskLink);
            }

            return(project);
        }