Merge branch 'feature/backend-refactor' into feature/files-refactor

# Conflicts:
#	products/ASC.Files/Core/GlobalUsings.cs
#	products/ASC.Files/Server/GlobalUsings.cs
This commit is contained in:
pavelbannov 2022-03-05 11:50:42 +03:00
commit d035deba31
127 changed files with 3507 additions and 3814 deletions

View File

@ -29,34 +29,30 @@ dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_property = false:silent
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true:warning
dotnet_style_predefined_type_for_member_access = true:warning
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
dotnet_style_parentheses_in_other_operators = always_for_clarity:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
# Expression-level preferences
dotnet_style_coalesce_expression = true:warning
dotnet_style_collection_initializer = true:warning
dotnet_style_explicit_tuple_names = true:warning
dotnet_style_null_propagation = true:warning
dotnet_style_object_initializer = true:warning
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true:warning
dotnet_style_prefer_compound_assignment = true:warning
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning
dotnet_style_prefer_inferred_tuple_names = true:warning
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning
dotnet_style_prefer_simplified_boolean_expressions = true:warning
dotnet_style_prefer_simplified_interpolation = true:suggestion
# Field preferences
dotnet_style_readonly_field = true:warning
@ -64,6 +60,13 @@ dotnet_style_readonly_field = true:warning
# Parameter preferences
dotnet_code_quality_unused_parameters = all:warning
# Suppression preferences
dotnet_remove_unnecessary_suppression_exclusions = none
# New line preferences
dotnet_style_allow_multiple_blank_lines_experimental = false
dotnet_style_allow_statement_immediately_after_block_experimental = false
#### C# Coding Conventions ####
# var preferences
@ -72,44 +75,48 @@ csharp_style_var_for_built_in_types = true:warning
csharp_style_var_when_type_is_apparent = true:warning
# Expression-bodied members
csharp_style_expression_bodied_accessors = when_on_single_line:suggestion
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_indexers = when_on_single_line:suggestion
csharp_style_expression_bodied_lambdas = when_on_single_line:suggestion
csharp_style_expression_bodied_local_functions = when_on_single_line:suggestion
csharp_style_expression_bodied_methods = false:suggestion
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = when_on_single_line:suggestion
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
# Pattern matching preferences
csharp_style_pattern_matching_over_as_with_null_check = true:warning
csharp_style_pattern_matching_over_is_with_cast_check = true:warning
csharp_style_prefer_switch_expression = true:suggestion
csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
# Null-checking preferences
csharp_style_conditional_delegate_call = true:warning
csharp_style_conditional_delegate_call = true:suggestion
# Modifier preferences
csharp_prefer_static_local_function = true:suggestion
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:suggestion
# Code-block preferences
csharp_prefer_braces = true:silent
csharp_prefer_simple_using_statement = true:suggestion
csharp_prefer_braces = true:warning
csharp_prefer_simple_using_statement = false:silent
csharp_style_namespace_declarations = file_scoped:warning
# Expression-level preferences
csharp_prefer_simple_default_expression = true:warning
csharp_style_deconstructed_variable_declaration = true:warning
csharp_style_inlined_variable_declaration = true:warning
csharp_style_pattern_local_over_anonymous_function = true:warning
csharp_style_prefer_index_operator = true:warning
csharp_style_prefer_range_operator = true:warning
csharp_style_throw_expression = true:warning
csharp_prefer_simple_default_expression = false:silent
csharp_style_deconstructed_variable_declaration = true:suggestion
csharp_style_implicit_object_creation_when_type_is_apparent = false:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_pattern_local_over_anonymous_function = true:suggestion
csharp_style_prefer_index_operator = false:suggestion
csharp_style_prefer_null_check_over_type_check = true:suggestion
csharp_style_prefer_range_operator = false:suggestion
csharp_style_throw_expression = true:suggestion
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:suggestion
# 'using' directive preferences
csharp_using_directive_placement = outside_namespace:silent
csharp_using_directive_placement = outside_namespace:warning
# New line preferences
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false:silent
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false:silent
csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
#### C# Formatting Rules ####
@ -126,7 +133,7 @@ csharp_new_line_between_query_expression_clauses = true
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = false
csharp_indent_case_contents_when_block = true
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
@ -162,32 +169,32 @@ csharp_preserve_single_line_statements = true
# Naming rules
dotnet_naming_rule.private_or_internal_field_should_be_begin_with_underscore.severity = warning
dotnet_naming_rule.private_or_internal_field_should_be_begin_with_underscore.symbols = private_or_internal_field
dotnet_naming_rule.private_or_internal_field_should_be_begin_with_underscore.style = begin_with_underscore
dotnet_naming_rule.interface_should_be_begins_with_i.severity = warning
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
dotnet_naming_rule.types_should_be_pascal_case.severity = warning
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = warning
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case
# Symbol specifications
dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =
dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field
dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected
dotnet_naming_symbols.private_or_internal_field.required_modifiers =
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =
dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =
# Naming styles
dotnet_naming_style.pascal_case.required_prefix =
@ -200,5 +207,58 @@ dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case
# Default severity for analyzer diagnostics with category 'Style'
dotnet_analyzer_diagnostic.category-Style.severity = silent
dotnet_naming_style.begin_with_underscore.required_prefix = _
dotnet_naming_style.begin_with_underscore.required_suffix =
dotnet_naming_style.begin_with_underscore.word_separator =
dotnet_naming_style.begin_with_underscore.capitalization = camel_case
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_prefer_local_over_anonymous_function = true:suggestion
csharp_style_prefer_tuple_swap = true:suggestion
csharp_prefer_static_local_function = true:suggestion
csharp_style_prefer_switch_expression = true:suggestion
csharp_style_prefer_pattern_matching = true:silent
csharp_style_prefer_not_pattern = true:suggestion
csharp_style_prefer_extended_property_pattern = true:suggestion
dotnet_diagnostic.CA1001.severity = warning
dotnet_diagnostic.CA1805.severity = warning
dotnet_diagnostic.CA1841.severity = warning
[*.{cs,vb}]
dotnet_style_operator_placement_when_wrapping = beginning_of_line
tab_width = 4
indent_size = 4
end_of_line = crlf
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:silent
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_object_initializer = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
dotnet_style_prefer_conditional_expression_over_return = true:suggestion
dotnet_style_explicit_tuple_names = true:suggestion
dotnet_style_prefer_inferred_tuple_names = true:suggestion
dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_compound_assignment = true:suggestion
dotnet_style_prefer_simplified_interpolation = true:suggestion
dotnet_style_namespace_match_folder = true:suggestion
dotnet_style_readonly_field = true:warning
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
dotnet_style_allow_multiple_blank_lines_experimental = false:silent
dotnet_style_allow_statement_immediately_after_block_experimental = false:silent
dotnet_code_quality_unused_parameters = all:warning
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
dotnet_style_parentheses_in_other_operators = always_for_clarity:silent
dotnet_style_qualification_for_field = false:silent
dotnet_style_qualification_for_property = false:silent
dotnet_style_qualification_for_method = false:silent
dotnet_style_qualification_for_event = false:silent
dotnet_diagnostic.CA1715.severity = warning
dotnet_diagnostic.CA1716.severity = silent
dotnet_diagnostic.CA5397.severity = warning

View File

@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">

View File

@ -1,11 +1,9 @@
global using System;
global using System.Collections.Generic;
global using System.ComponentModel;
global using System.Globalization;
global using System.Linq;
global using System.ComponentModel;
global using System.Globalization;
global using System.Linq.Expressions;
global using System.Net;
global using System.Net.Http;
global using System.Reflection;
global using System.Runtime.Serialization;
global using System.Security;
@ -14,8 +12,7 @@ global using System.Security.Claims;
global using System.Text.Encodings.Web;
global using System.Text.Json;
global using System.Text.Json.Serialization;
global using System.Threading;
global using System.Threading.Tasks;
global using System.Web;
global using System.Xml.Linq;

View File

@ -12,6 +12,7 @@
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<WarningsAsErrors></WarningsAsErrors>
<NoWarn>$(NoWarn);NU1605</NoWarn>
<ImplicitUsings>enable</ImplicitUsings>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">

View File

@ -1,32 +1,26 @@
global using System;
global using System.Collections;
global using System.Collections;
global using System.Collections.Concurrent;
global using System.Collections.Generic;
global using System.Configuration;
global using System.Diagnostics;
global using System.Globalization;
global using System.IO;
global using System.Linq;
global using System.Net;
global using System.Net.Mail;
global using System.Reflection;
global using System.Runtime.Caching;
global using System.Runtime.Loader;
global using System.Runtime.Serialization;
global using System.Security.Cryptography;
global using System.Security.Principal;
global using System.ServiceModel;
global using System.Text;
global using System.Text.RegularExpressions;
global using System.Threading;
global using System.Threading.Tasks;
global using System.Web;
global using System.Xml.Linq;
global using System.Xml.XPath;
global using System.ServiceModel;
global using System.Runtime.Serialization;
global using ARSoft.Tools.Net;
global using ARSoft.Tools.Net.Dns;
global using ASC.Common;
global using ASC.Common.Caching;
global using ASC.Common.DependencyInjection;
@ -35,26 +29,26 @@ global using ASC.Common.Security;
global using ASC.Common.Security.Authorizing;
global using ASC.Common.Utils;
global using ASC.Security.Cryptography;
global using Autofac;
global using Autofac.Configuration;
global using AutoMapper;
global using Confluent.Kafka;
global using Confluent.Kafka.Admin;
global using Google.Protobuf;
global using JWT;
global using JWT.Algorithms;
global using JWT.Serializers;
global using log4net.Appender;
global using log4net.Config;
global using log4net.Core;
global using log4net.Util;
global using Microsoft.AspNetCore.Cryptography.KeyDerivation;
global using Microsoft.AspNetCore.Http;
global using Microsoft.AspNetCore.Http.Extensions;
@ -70,16 +64,16 @@ global using Microsoft.Extensions.Hosting;
global using Microsoft.Extensions.Logging;
global using Microsoft.Extensions.Options;
global using Microsoft.Net.Http.Headers;
global using Newtonsoft.Json;
global using Newtonsoft.Json.Serialization;
global using NLog;
global using NLog.Common;
global using NLog.Targets;
global using NVelocity;
global using NVelocity.App;
global using NVelocity.Runtime.Resource.Loader;
global using StackExchange.Redis.Extensions.Core.Abstractions;

View File

@ -11,6 +11,7 @@
<Copyright>(c) Ascensio System SIA. All rights reserved</Copyright>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugType>full</DebugType>

View File

@ -10,9 +10,9 @@ public class DbVoipCall
public Guid AnsweredBy { get; set; }
public DateTime DialDate { get; set; }
public int DialDuration { get; set; }
public string RecordSid { get; set; }
public string RecordUrl { get; set; }
public int RecordDuration { get; set; }
public string Sid { get; set; }
public string Uri { get; set; }
public int Duration { get; set; }
public decimal RecordPrice { get; set; }
public int ContactId { get; set; }
public decimal Price { get; set; }
@ -30,6 +30,7 @@ public static class DbVoipCallExtension
return modelBuilder;
}
public static void MySqlAddDbVoipCall(this ModelBuilder modelBuilder)
{
modelBuilder.Entity<DbVoipCall>(entity =>
@ -89,19 +90,19 @@ public static class DbVoipCallExtension
.HasColumnName("price")
.HasColumnType("decimal(10,4)");
entity.Property(e => e.RecordDuration).HasColumnName("record_duration");
entity.Property(e => e.Duration).HasColumnName("record_duration");
entity.Property(e => e.RecordPrice)
.HasColumnName("record_price")
.HasColumnType("decimal(10,4)");
entity.Property(e => e.RecordSid)
entity.Property(e => e.Sid)
.HasColumnName("record_sid")
.HasColumnType("varchar(50)")
.HasCharSet("utf8")
.UseCollation("utf8_general_ci");
entity.Property(e => e.RecordUrl)
entity.Property(e => e.Uri)
.HasColumnName("record_url")
.HasColumnType("text")
.HasCharSet("utf8")
@ -160,18 +161,18 @@ public static class DbVoipCallExtension
.HasColumnType("numeric(10,4)")
.HasDefaultValueSql("NULL");
entity.Property(e => e.RecordDuration).HasColumnName("record_duration");
entity.Property(e => e.Duration).HasColumnName("record_duration");
entity.Property(e => e.RecordPrice)
.HasColumnName("record_price")
.HasColumnType("numeric(10,4)");
entity.Property(e => e.RecordSid)
entity.Property(e => e.Sid)
.HasColumnName("record_sid")
.HasMaxLength(50)
.HasDefaultValueSql("NULL");
entity.Property(e => e.RecordUrl).HasColumnName("record_url");
entity.Property(e => e.Uri).HasColumnName("record_url");
entity.Property(e => e.Status).HasColumnName("status");

View File

@ -1,16 +1,11 @@
global using System;
global using System.Collections;
global using System.Collections;
global using System.Collections.Concurrent;
global using System.Collections.Generic;
global using System.Configuration;
global using System.Data.Common;
global using System.Diagnostics;
global using System.Globalization;
global using System.IO;
global using System.Linq;
global using System.Linq.Expressions;
global using System.Net;
global using System.Net.Http;
global using System.Reflection;
global using System.Resources;
global using System.Runtime.Caching;
@ -25,8 +20,6 @@ global using System.Text;
global using System.Text.Json;
global using System.Text.Json.Serialization;
global using System.Text.RegularExpressions;
global using System.Threading;
global using System.Threading.Tasks;
global using System.Web;
global using System.Xml;

View File

@ -5,6 +5,7 @@
<TargetFramework>net6.0</TargetFramework>
<OutputType>Library</OutputType>
<NoWarn>NU1701</NoWarn>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>

View File

@ -1,20 +1,14 @@
global using System;
global using System.Collections.Generic;
global using System.ComponentModel.DataAnnotations;
global using System.ComponentModel.DataAnnotations;
global using System.ComponentModel.DataAnnotations.Schema;
global using System.Configuration;
global using System.Data;
global using System.Data.Common;
global using System.Diagnostics;
global using System.IO;
global using System.Linq;
global using System.Reflection;
global using System.Security.Cryptography;
global using System.ServiceModel;
global using System.Text;
global using System.Text.RegularExpressions;
global using System.Threading;
global using System.Threading.Tasks;
global using System.Xml;
global using System.Xml.Linq;
global using System.Xml.XPath;

View File

@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">

View File

@ -1,6 +1,4 @@
global using System;
global using System.Collections.Generic;
global using System.Globalization;
global using System.Globalization;
global using System.Security.Cryptography;
global using System.Text;
@ -19,5 +17,4 @@ global using ASC.Web.Studio.Core.Notify;
global using Microsoft.AspNetCore.Http;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Options;
global using Microsoft.Extensions.Primitives;
global using Microsoft.Extensions.Primitives;

View File

@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">

View File

@ -1,19 +1,12 @@
global using System;
global using System.Collections.Concurrent;
global using System.Collections.Generic;
global using System.Collections.Concurrent;
global using System.Globalization;
global using System.IO;
global using System.Linq;
global using System.Net;
global using System.Net.Http;
global using System.Runtime.Serialization;
global using System.Security.Cryptography;
global using System.ServiceModel;
global using System.Text;
global using System.Text.Json;
global using System.Text.Json.Serialization;
global using System.Threading;
global using System.Threading.Tasks;
global using System.Web;
global using Amazon;

View File

@ -3,6 +3,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<NoWarn>NU1701</NoWarn>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">

View File

@ -1,12 +1,7 @@
global using System;
global using System.Collections.Generic;
global using System.Data;
global using System.Data;
global using System.Diagnostics;
global using System.Globalization;
global using System.IO;
global using System.Linq;
global using System.Net;
global using System.Net.Http;
global using System.Net.Http.Headers;
global using System.Reflection;
global using System.Runtime.Serialization;
@ -14,8 +9,6 @@ global using System.Security.Cryptography.Pkcs;
global using System.Security.Cryptography.X509Certificates;
global using System.Text;
global using System.Text.Json.Serialization;
global using System.Threading;
global using System.Threading.Tasks;
global using System.Web;
global using System.Xml.Linq;
global using System.Xml.XPath;

View File

@ -5,6 +5,7 @@
<ApplicationIcon />
<OutputType>Library</OutputType>
<StartupObject />
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">

View File

@ -1,26 +1,23 @@
global using System;
global using System.Collections.Generic;
global using System.Data;
global using System.Linq;
global using System.Data;
global using ASC.Common;
global using ASC.Common.Mapping;
global using ASC.Core;
global using ASC.Core.Common.EF;
global using ASC.Feed.Models;
global using ASC.Core.Common.EF.Model;
global using ASC.Core.Tenants;
global using ASC.Core.Users;
global using ASC.Common.Mapping;
global using ASC.Feed.Mapping;
global using ASC.Feed.Data;
global using ASC.Feed.Core;
global using AutoMapper;
global using ASC.Feed.Data;
global using ASC.Feed.Mapping;
global using ASC.Feed.Models;
global using Autofac;
global using AutoMapper;
global using Microsoft.EntityFrameworkCore;
global using Microsoft.EntityFrameworkCore.Migrations;
global using Microsoft.EntityFrameworkCore.Infrastructure;
global using Microsoft.EntityFrameworkCore.Migrations;
global using Newtonsoft.Json;

View File

@ -5,6 +5,7 @@
<ApplicationIcon />
<OutputType>Library</OutputType>
<StartupObject />
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">

View File

@ -1,11 +1,8 @@
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Net;
global using System.Net;
global using System.Net.Sockets;
global using System.Runtime.Serialization;
global using System.Web;
global using ASC.Common;
global using ASC.Common.Caching;
global using ASC.Common.Logging;
@ -15,11 +12,11 @@ global using ASC.Core.Common.EF;
global using ASC.Core.Common.EF.Context;
global using ASC.Core.Common.EF.Model;
global using ASC.Core.Common.Settings;
global using AutoMapper;
global using AutoMapper.QueryableExtensions;
global using Microsoft.AspNetCore.Http;
global using Microsoft.AspNetCore.Http.Extensions;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.Options;
global using AutoMapper;
global using AutoMapper.QueryableExtensions;

View File

@ -5,6 +5,7 @@
<ApplicationIcon />
<OutputType>Library</OutputType>
<StartupObject />
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">

View File

@ -1,33 +1,29 @@
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using System.Threading;
global using System.Web;
global using System.Web;
global using ASC.Common;
global using ASC.Common.Logging;
global using ASC.Common.Mapping;
global using ASC.Core;
global using ASC.Core.Common.EF;
global using ASC.Core.Common.EF.Model;
global using ASC.MessagingSystem.Mapping;
global using ASC.MessagingSystem.Core;
global using ASC.MessagingSystem.Core.Sender;
global using ASC.MessagingSystem.Data;
global using ASC.MessagingSystem.Mapping;
global using ASC.MessagingSystem.Models;
global using AutoMapper;
global using Microsoft.AspNetCore.Http;
global using Microsoft.EntityFrameworkCore;
global using Microsoft.EntityFrameworkCore.Migrations;
global using Microsoft.EntityFrameworkCore.Metadata;
global using Microsoft.EntityFrameworkCore.Infrastructure;
global using Microsoft.EntityFrameworkCore.Metadata;
global using Microsoft.EntityFrameworkCore.Migrations;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Options;
global using Microsoft.Extensions.Primitives;
global using AutoMapper;
global using Newtonsoft.Json;
global using UAParser;

View File

@ -6,6 +6,7 @@
<Product>ASC.Notify.Textile</Product>
<Copyright>(c) Ascensio System SIA. All rights reserved</Copyright>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugType>full</DebugType>

View File

@ -1,7 +1,4 @@
global using System;
global using System.IO;
global using System.Linq;
global using System.Reflection;
global using System.Reflection;
global using System.Text;
global using System.Text.RegularExpressions;
global using System.Web;

View File

@ -12,6 +12,7 @@
<Copyright>(c) Ascensio System SIA. All rights reserved</Copyright>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugType>full</DebugType>

View File

@ -1,19 +1,18 @@
namespace Textile
namespace Textile;
public class BlockModifier
{
public class BlockModifier
protected BlockModifier()
{
protected BlockModifier()
{
}
}
public virtual string ModifyLine(string line)
{
return line;
}
public virtual string ModifyLine(string line)
{
return line;
}
public virtual string Conclude(string line)
{
return line;
}
public virtual string Conclude(string line)
{
return line;
}
}

View File

@ -1,10 +1,9 @@
namespace Textile
namespace Textile;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class BlockModifierAttribute : Attribute
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class BlockModifierAttribute : Attribute
public BlockModifierAttribute()
{
public BlockModifierAttribute()
{
}
}
}

View File

@ -10,142 +10,141 @@
// You must not remove this notice, or any other, from this software.
#endregion
namespace Textile.Blocks
{
public static class BlockAttributesParser
{
public static StyleReader Styler { get; set; }
namespace Textile.Blocks;
/// <summary>
///
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
static public string ParseBlockAttributes(string input)
public static class BlockAttributesParser
{
public static StyleReader Styler { get; set; }
/// <summary>
///
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
static public string ParseBlockAttributes(string input)
{
return ParseBlockAttributes(input, "");
}
/// <summary>
///
/// </summary>
/// <param name="input"></param>
/// <param name="element"></param>
/// <returns></returns>
static public string ParseBlockAttributes(string input, string element)
{
var style = string.Empty;
var cssClass = string.Empty;
var lang = string.Empty;
var colspan = string.Empty;
var rowspan = string.Empty;
var id = string.Empty;
if (Styler != null)
{
return ParseBlockAttributes(input, "");
style = GetStyle(element, style);
}
/// <summary>
///
/// </summary>
/// <param name="input"></param>
/// <param name="element"></param>
/// <returns></returns>
static public string ParseBlockAttributes(string input, string element)
if (input.Length == 0)
return style.Length > 0 ? " style=\"" + style + "\"" : "";
Match m;
var matched = input;
if (element == "td")
{
var style = string.Empty;
var cssClass = string.Empty;
var lang = string.Empty;
var colspan = string.Empty;
var rowspan = string.Empty;
var id = string.Empty;
if (Styler != null)
{
style = GetStyle(element, style);
}
if (input.Length == 0)
return style.Length > 0 ? " style=\"" + style + "\"" : "";
Match m;
var matched = input;
if (element == "td")
{
// column span
m = Regex.Match(matched, @"\\(\d+)");
if (m.Success)
colspan = m.Groups[1].Value;
// row span
m = Regex.Match(matched, @"/(\d+)");
if (m.Success)
rowspan = m.Groups[1].Value;
// vertical align
m = Regex.Match(matched, @"(" + Globals.VerticalAlignPattern + @")");
if (m.Success)
style += "vertical-align:" + Globals.VerticalAlign[m.Captures[0].Value] + ";";
}
// First, match custom styles
m = Regex.Match(matched, @"\{([^}]*)\}");
// column span
m = Regex.Match(matched, @"\\(\d+)");
if (m.Success)
{
style += m.Groups[1].Value + ";";
matched = matched.Replace(m.ToString(), "");
}
// Then match the language
m = Regex.Match(matched, @"\[([^()]+)\]");
colspan = m.Groups[1].Value;
// row span
m = Regex.Match(matched, @"/(\d+)");
if (m.Success)
{
lang = m.Groups[1].Value;
matched = matched.Replace(m.ToString(), "");
}
rowspan = m.Groups[1].Value;
// vertical align
m = Regex.Match(matched, @"(" + Globals.VerticalAlignPattern + @")");
if (m.Success)
style += "vertical-align:" + Globals.VerticalAlign[m.Captures[0].Value] + ";";
}
// Match classes and IDs after that
m = Regex.Match(matched, @"\(([^()]+)\)");
// First, match custom styles
m = Regex.Match(matched, @"\{([^}]*)\}");
if (m.Success)
{
style += m.Groups[1].Value + ";";
matched = matched.Replace(m.ToString(), "");
}
// Then match the language
m = Regex.Match(matched, @"\[([^()]+)\]");
if (m.Success)
{
lang = m.Groups[1].Value;
matched = matched.Replace(m.ToString(), "");
}
// Match classes and IDs after that
m = Regex.Match(matched, @"\(([^()]+)\)");
if (m.Success)
{
cssClass = m.Groups[1].Value;
matched = matched.Replace(m.ToString(), "");
// Separate the public class and the ID
m = Regex.Match(cssClass, @"^(.*)#(.*)$");
if (m.Success)
{
cssClass = m.Groups[1].Value;
matched = matched.Replace(m.ToString(), "");
// Separate the public class and the ID
m = Regex.Match(cssClass, @"^(.*)#(.*)$");
if (m.Success)
{
cssClass = m.Groups[1].Value;
id = m.Groups[2].Value;
}
if (Styler != null && !string.IsNullOrEmpty(cssClass))
{
style = GetStyle("." + cssClass, style);
}
id = m.Groups[2].Value;
}
// Get the padding on the left
m = Regex.Match(matched, @"([(]+)");
if (m.Success)
if (Styler != null && !string.IsNullOrEmpty(cssClass))
{
style += "padding-left:" + m.Groups[1].Length + "em;";
matched = matched.Replace(m.ToString(), "");
style = GetStyle("." + cssClass, style);
}
// Get the padding on the right
m = Regex.Match(matched, @"([)]+)");
if (m.Success)
{
style += "padding-right:" + m.Groups[1].Length + "em;";
matched = matched.Replace(m.ToString(), "");
}
// Get the text alignment
m = Regex.Match(matched, "(" + Globals.HorizontalAlignPattern + ")");
if (m.Success)
style += "text-align:" + Globals.HorizontalAlign[m.Groups[1].Value] + ";";
return
(style.Length > 0 ? " style=\"" + style + "\"" : "") +
(cssClass.Length > 0 ? " class=\"" + cssClass + "\"" : "") +
(lang.Length > 0 ? " lang=\"" + lang + "\"" : "") +
(id.Length > 0 ? " id=\"" + id + "\"" : "") +
(colspan.Length > 0 ? " colspan=\"" + colspan + "\"" : "") +
(rowspan.Length > 0 ? " rowspan=\"" + rowspan + "\"" : "")
;
}
private static string GetStyle(string element, string style)
// Get the padding on the left
m = Regex.Match(matched, @"([(]+)");
if (m.Success)
{
var styled = Styler.GetStyle(element);
if (!string.IsNullOrEmpty(styled))
{
style += styled;
}
return style;
style += "padding-left:" + m.Groups[1].Length + "em;";
matched = matched.Replace(m.ToString(), "");
}
// Get the padding on the right
m = Regex.Match(matched, @"([)]+)");
if (m.Success)
{
style += "padding-right:" + m.Groups[1].Length + "em;";
matched = matched.Replace(m.ToString(), "");
}
// Get the text alignment
m = Regex.Match(matched, "(" + Globals.HorizontalAlignPattern + ")");
if (m.Success)
style += "text-align:" + Globals.HorizontalAlign[m.Groups[1].Value] + ";";
return
(style.Length > 0 ? " style=\"" + style + "\"" : "") +
(cssClass.Length > 0 ? " class=\"" + cssClass + "\"" : "") +
(lang.Length > 0 ? " lang=\"" + lang + "\"" : "") +
(id.Length > 0 ? " id=\"" + id + "\"" : "") +
(colspan.Length > 0 ? " colspan=\"" + colspan + "\"" : "") +
(rowspan.Length > 0 ? " rowspan=\"" + rowspan + "\"" : "")
;
}
private static string GetStyle(string element, string style)
{
var styled = Styler.GetStyle(element);
if (!string.IsNullOrEmpty(styled))
{
style += styled;
}
return style;
}
}

View File

@ -1,10 +1,9 @@
namespace Textile.Blocks
namespace Textile.Blocks;
public class BoldPhraseBlockModifier : PhraseBlockModifier
{
public class BoldPhraseBlockModifier : PhraseBlockModifier
public override string ModifyLine(string line)
{
public override string ModifyLine(string line)
{
return PhraseModifierFormat(line, @"\*\*", "b");
}
return PhraseModifierFormat(line, @"\*\*", "b");
}
}

View File

@ -1,17 +1,16 @@
namespace Textile.Blocks
{
public class CapitalsBlockModifier : BlockModifier
{
public override string ModifyLine(string line)
{
var me = new MatchEvaluator(CapitalsFormatMatchEvaluator);
line = Regex.Replace(line, @"(?<=^|\s|" + Globals.PunctuationPattern + @")(?<caps>[A-Z][A-Z0-9]+)(?=$|\s|" + Globals.PunctuationPattern + @")", me);
return line;
}
namespace Textile.Blocks;
private string CapitalsFormatMatchEvaluator(Match m)
{
return @"<span class=""caps"">" + m.Groups["caps"].Value + @"</span>";
}
public class CapitalsBlockModifier : BlockModifier
{
public override string ModifyLine(string line)
{
var me = new MatchEvaluator(CapitalsFormatMatchEvaluator);
line = Regex.Replace(line, @"(?<=^|\s|" + Globals.PunctuationPattern + @")(?<caps>[A-Z][A-Z0-9]+)(?=$|\s|" + Globals.PunctuationPattern + @")", me);
return line;
}
private string CapitalsFormatMatchEvaluator(Match m)
{
return @"<span class=""caps"">" + m.Groups["caps"].Value + @"</span>";
}
}

View File

@ -1,10 +1,9 @@
namespace Textile.Blocks
namespace Textile.Blocks;
public class CitePhraseBlockModifier : PhraseBlockModifier
{
public class CitePhraseBlockModifier : PhraseBlockModifier
public override string ModifyLine(string line)
{
public override string ModifyLine(string line)
{
return PhraseModifierFormat(line, @"\?\?", "cite");
}
return PhraseModifierFormat(line, @"\?\?", "cite");
}
}

View File

@ -10,47 +10,46 @@
// You must not remove this notice, or any other, from this software.
#endregion
namespace Textile.Blocks
namespace Textile.Blocks;
public class CodeBlockModifier : BlockModifier
{
public class CodeBlockModifier : BlockModifier
public override string ModifyLine(string line)
{
public override string ModifyLine(string line)
{
// Replace "@...@" zones with "<code>" tags.
var me = new MatchEvaluator(CodeFormatMatchEvaluator);
line = Regex.Replace(line,
@"(?<before>^|([\s\([{]))" + // before
"@" +
@"(\|(?<lang>\w+)\|)?" + // lang
"(?<code>[^@]+)" + // code
"@" +
@"(?<after>$|([\]}])|(?=" + Globals.PunctuationPattern + @"{1,2}|\s|$))", // after
me);
// Encode the contents of the "<code>" tags so that we don't
// generate formatting out of it.
line = NoTextileEncoder.EncodeNoTextileZones(line,
@"(?<=(^|\s)<code(" + Globals.HtmlAttributesPattern + @")>)",
@"(?=</code>)");
return line;
}
public override string Conclude(string line)
{
// Recode everything except "<" and ">";
line = NoTextileEncoder.DecodeNoTextileZones(line,
@"(?<=(^|\s)<code(" + Globals.HtmlAttributesPattern + @")>)",
@"(?=</code>)",
new string[] { "<", ">" });
return line;
}
public string CodeFormatMatchEvaluator(Match m)
{
var res = m.Groups["before"].Value + "<code";
if (m.Groups["lang"].Length > 0)
res += " language=\"" + m.Groups["lang"].Value + "\"";
res += ">" + m.Groups["code"].Value + "</code>" + m.Groups["after"].Value;
return res;
}
// Replace "@...@" zones with "<code>" tags.
var me = new MatchEvaluator(CodeFormatMatchEvaluator);
line = Regex.Replace(line,
@"(?<before>^|([\s\([{]))" + // before
"@" +
@"(\|(?<lang>\w+)\|)?" + // lang
"(?<code>[^@]+)" + // code
"@" +
@"(?<after>$|([\]}])|(?=" + Globals.PunctuationPattern + @"{1,2}|\s|$))", // after
me);
// Encode the contents of the "<code>" tags so that we don't
// generate formatting out of it.
line = NoTextileEncoder.EncodeNoTextileZones(line,
@"(?<=(^|\s)<code(" + Globals.HtmlAttributesPattern + @")>)",
@"(?=</code>)");
return line;
}
}
public override string Conclude(string line)
{
// Recode everything except "<" and ">";
line = NoTextileEncoder.DecodeNoTextileZones(line,
@"(?<=(^|\s)<code(" + Globals.HtmlAttributesPattern + @")>)",
@"(?=</code>)",
new string[] { "<", ">" });
return line;
}
public string CodeFormatMatchEvaluator(Match m)
{
var res = m.Groups["before"].Value + "<code";
if (m.Groups["lang"].Length > 0)
res += " language=\"" + m.Groups["lang"].Value + "\"";
res += ">" + m.Groups["code"].Value + "</code>" + m.Groups["after"].Value;
return res;
}
}

View File

@ -1,10 +1,9 @@
namespace Textile.Blocks
namespace Textile.Blocks;
public class DeletedPhraseBlockModifier : PhraseBlockModifier
{
public class DeletedPhraseBlockModifier : PhraseBlockModifier
public override string ModifyLine(string line)
{
public override string ModifyLine(string line)
{
return PhraseModifierFormat(line, @"\-", "del");
}
return PhraseModifierFormat(line, @"\-", "del");
}
}

View File

@ -1,10 +1,9 @@
namespace Textile.Blocks
namespace Textile.Blocks;
public class EmphasisPhraseBlockModifier : PhraseBlockModifier
{
public class EmphasisPhraseBlockModifier : PhraseBlockModifier
public override string ModifyLine(string line)
{
public override string ModifyLine(string line)
{
return PhraseModifierFormat(line, @"_", "em");
}
return PhraseModifierFormat(line, @"_", "em");
}
}

View File

@ -10,13 +10,12 @@
// You must not remove this notice, or any other, from this software.
#endregion
namespace Textile.Blocks
namespace Textile.Blocks;
public class FootNoteReferenceBlockModifier : BlockModifier
{
public class FootNoteReferenceBlockModifier : BlockModifier
public override string ModifyLine(string line)
{
public override string ModifyLine(string line)
{
return Regex.Replace(line, @"\b\[([0-9]+)\](\W)", "<sup><a href=\"#fn$1\">$1</a></sup>$2");
}
return Regex.Replace(line, @"\b\[([0-9]+)\](\W)", "<sup><a href=\"#fn$1\">$1</a></sup>$2");
}
}

View File

@ -10,79 +10,78 @@
// You must not remove this notice, or any other, from this software.
#endregion
namespace Textile.Blocks
namespace Textile.Blocks;
public class GlyphBlockModifier : BlockModifier
{
public class GlyphBlockModifier : BlockModifier
public override string ModifyLine(string line)
{
public override string ModifyLine(string line)
line = Regex.Replace(line, "\"\\z", "\" ");
// fix: hackish
string[,] glyphs = {
{ @"([^\s[{(>_*])?\'(?(1)|(\s|s\b|" + Globals.PunctuationPattern + @"))", "$1&#8217;$2" }, // single closing
{ @"\'", "&#8216;" }, // single opening
{ @"([^\s[{(>_*])?""(?(1)|(\s|" + Globals.PunctuationPattern + @"))", "$1&#8221;$2" }, // double closing
{ @"""", "&#8220;" }, // double opening
{ @"\b( )?\.{3}", "$1&#8230;" }, // ellipsis
{ @"\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])", "<acronym title=\"$2\">$1</acronym>" }, // 3+ uppercase acronym
{ @"(\s)?--(\s)?", "$1&#8212;$2" }, // em dash
{ @"\s-\s", " &#8211; " }, // en dash
{ @"(\d+)( )?x( )?(\d+)", "$1$2&#215;$3$4" }, // dimension sign
{ @"\b ?[([](TM|tm)[])]", "&#8482;" }, // trademark
{ @"\b ?[([](R|r)[])]", "&#174;" }, // registered
{ @"\b ?[([](C|c)[])]", "&#169;" } // copyright
};
var sb = new StringBuilder();
if (!Regex.IsMatch(line, "<.*>"))
{
line = Regex.Replace(line, "\"\\z", "\" ");
// fix: hackish
string[,] glyphs = {
{ @"([^\s[{(>_*])?\'(?(1)|(\s|s\b|" + Globals.PunctuationPattern + @"))", "$1&#8217;$2" }, // single closing
{ @"\'", "&#8216;" }, // single opening
{ @"([^\s[{(>_*])?""(?(1)|(\s|" + Globals.PunctuationPattern + @"))", "$1&#8221;$2" }, // double closing
{ @"""", "&#8220;" }, // double opening
{ @"\b( )?\.{3}", "$1&#8230;" }, // ellipsis
{ @"\b([A-Z][A-Z0-9]{2,})\b(?:[(]([^)]*)[)])", "<acronym title=\"$2\">$1</acronym>" }, // 3+ uppercase acronym
{ @"(\s)?--(\s)?", "$1&#8212;$2" }, // em dash
{ @"\s-\s", " &#8211; " }, // en dash
{ @"(\d+)( )?x( )?(\d+)", "$1$2&#215;$3$4" }, // dimension sign
{ @"\b ?[([](TM|tm)[])]", "&#8482;" }, // trademark
{ @"\b ?[([](R|r)[])]", "&#174;" }, // registered
{ @"\b ?[([](C|c)[])]", "&#169;" } // copyright
};
var sb = new StringBuilder();
if (!Regex.IsMatch(line, "<.*>"))
// If no HTML, do a simple search & replace.
for (var i = 0; i < glyphs.GetLength(0); ++i)
{
// If no HTML, do a simple search & replace.
for (var i = 0; i < glyphs.GetLength(0); ++i)
{
line = Regex.Replace(line, glyphs[i, 0], glyphs[i, 1]);
}
sb.Append(line);
line = Regex.Replace(line, glyphs[i, 0], glyphs[i, 1]);
}
else
{
var splits = Regex.Split(line, "(<.*?>)");
var offtags = "code|pre|notextile";
var codepre = false;
foreach (var split in splits)
{
var modifiedSplit = split;
if (modifiedSplit.Length == 0)
continue;
if (Regex.IsMatch(modifiedSplit, @"<(" + offtags + ")>"))
codepre = true;
if (Regex.IsMatch(modifiedSplit, @"<\/(" + offtags + ")>"))
codepre = false;
if (!Regex.IsMatch(modifiedSplit, "<.*>") && !codepre)
{
for (var i = 0; i < glyphs.GetLength(0); ++i)
{
modifiedSplit = Regex.Replace(modifiedSplit, glyphs[i, 0], glyphs[i, 1]);
}
}
// do htmlspecial if between <code>
if (codepre)
{
//TODO: htmlspecialchars(line)
//line = Regex.Replace(line, @"&lt;(\/?" + offtags + ")&gt;", "<$1>");
//line = line.Replace("&amp;#", "&#");
}
sb.Append(modifiedSplit);
}
}
return sb.ToString();
sb.Append(line);
}
else
{
var splits = Regex.Split(line, "(<.*?>)");
var offtags = "code|pre|notextile";
var codepre = false;
foreach (var split in splits)
{
var modifiedSplit = split;
if (modifiedSplit.Length == 0)
continue;
if (Regex.IsMatch(modifiedSplit, @"<(" + offtags + ")>"))
codepre = true;
if (Regex.IsMatch(modifiedSplit, @"<\/(" + offtags + ")>"))
codepre = false;
if (!Regex.IsMatch(modifiedSplit, "<.*>") && !codepre)
{
for (var i = 0; i < glyphs.GetLength(0); ++i)
{
modifiedSplit = Regex.Replace(modifiedSplit, glyphs[i, 0], glyphs[i, 1]);
}
}
// do htmlspecial if between <code>
if (codepre)
{
//TODO: htmlspecialchars(line)
//line = Regex.Replace(line, @"&lt;(\/?" + offtags + ")&gt;", "<$1>");
//line = line.Replace("&amp;#", "&#");
}
sb.Append(modifiedSplit);
}
}
return sb.ToString();
}
}

View File

@ -11,46 +11,45 @@
#endregion
namespace Textile.Blocks
namespace Textile.Blocks;
public class HyperLinkBlockModifier : BlockModifier
{
public class HyperLinkBlockModifier : BlockModifier
private readonly string _rel = string.Empty;
public override string ModifyLine(string line)
{
private readonly string m_rel = string.Empty;
line = Regex.Replace(line,
@"(?<pre>[\s[{(]|" + Globals.PunctuationPattern + @")?" + // $pre
"\"" + // start
Globals.BlockModifiersPattern + // attributes
"(?<text>[\\w\\W]+?)" + // text
@"\s?" +
@"(?:\((?<title>[^)]+)\)(?=""))?" + // title
"\":" +
@"""(?<url>\S+[^""]+)""" + // url
@"(?<slash>\/)?" + // slash
@"(?<post>[^\w\/;]*)" + // post
@"(?=\s|$)",
new MatchEvaluator(HyperLinksFormatMatchEvaluator));
return line;
}
public override string ModifyLine(string line)
{
line = Regex.Replace(line,
@"(?<pre>[\s[{(]|" + Globals.PunctuationPattern + @")?" + // $pre
"\"" + // start
Globals.BlockModifiersPattern + // attributes
"(?<text>[\\w\\W]+?)" + // text
@"\s?" +
@"(?:\((?<title>[^)]+)\)(?=""))?" + // title
"\":" +
@"""(?<url>\S+[^""]+)""" + // url
@"(?<slash>\/)?" + // slash
@"(?<post>[^\w\/;]*)" + // post
@"(?=\s|$)",
new MatchEvaluator(HyperLinksFormatMatchEvaluator));
return line;
}
private string HyperLinksFormatMatchEvaluator(Match m)
{
//TODO: check the URL
var atts = BlockAttributesParser.ParseBlockAttributes(m.Groups["atts"].Value, "a");
if (m.Groups["title"].Length > 0)
atts += " title=\"" + m.Groups["title"].Value + "\"";
var linkText = m.Groups["text"].Value.Trim(' ');
private string HyperLinksFormatMatchEvaluator(Match m)
{
//TODO: check the URL
var atts = BlockAttributesParser.ParseBlockAttributes(m.Groups["atts"].Value, "a");
if (m.Groups["title"].Length > 0)
atts += " title=\"" + m.Groups["title"].Value + "\"";
var linkText = m.Groups["text"].Value.Trim(' ');
var str = m.Groups["pre"].Value + "<a ";
if (!string.IsNullOrEmpty(m_rel))
str += "ref=\"" + m_rel + "\" ";
str += "href=\"" +
m.Groups["url"].Value + m.Groups["slash"].Value + "\"" +
atts +
">" + linkText + "</a>" + m.Groups["post"].Value;
return str;
}
var str = m.Groups["pre"].Value + "<a ";
if (!string.IsNullOrEmpty(_rel))
str += "ref=\"" + _rel + "\" ";
str += "href=\"" +
m.Groups["url"].Value + m.Groups["slash"].Value + "\"" +
atts +
">" + linkText + "</a>" + m.Groups["post"].Value;
return str;
}
}

View File

@ -10,60 +10,59 @@
// You must not remove this notice, or any other, from this software.
#endregion
namespace Textile.Blocks
namespace Textile.Blocks;
public class ImageBlockModifier : BlockModifier
{
public class ImageBlockModifier : BlockModifier
public override string ModifyLine(string line)
{
public override string ModifyLine(string line)
line = Regex.Replace(line,
@"\!" + // opening !
@"(?<algn>\<|\=|\>)?" + // optional alignment atts
Globals.BlockModifiersPattern + // optional style, public class atts
@"(?:\. )?" + // optional dot-space
@"(?<url>[^\s(!]+)" + // presume this is the src
@"\s?" + // optional space
@"(?:\((?<title>([^\)]+))\))?" +// optional title
@"\!" + // closing
@"(?::(?<href>(\S+)))?" + // optional href
@"(?=\s|\.|,|;|\)|\||$)", // lookahead: space or simple punctuation or end of string
new MatchEvaluator(ImageFormatMatchEvaluator)
);
return line;
}
string ImageFormatMatchEvaluator(Match m)
{
var atts = BlockAttributesParser.ParseBlockAttributes(m.Groups["atts"].Value, "img");
if (m.Groups["algn"].Length > 0)
atts += " align=\"" + Globals.ImageAlign[m.Groups["algn"].Value] + "\"";
if (m.Groups["title"].Length > 0)
{
line = Regex.Replace(line,
@"\!" + // opening !
@"(?<algn>\<|\=|\>)?" + // optional alignment atts
Globals.BlockModifiersPattern + // optional style, public class atts
@"(?:\. )?" + // optional dot-space
@"(?<url>[^\s(!]+)" + // presume this is the src
@"\s?" + // optional space
@"(?:\((?<title>([^\)]+))\))?" +// optional title
@"\!" + // closing
@"(?::(?<href>(\S+)))?" + // optional href
@"(?=\s|\.|,|;|\)|\||$)", // lookahead: space or simple punctuation or end of string
new MatchEvaluator(ImageFormatMatchEvaluator)
);
return line;
atts += " title=\"" + m.Groups["title"].Value + "\"";
atts += " alt=\"" + m.Groups["title"].Value + "\"";
}
else
{
atts += " alt=\"\"";
}
// Get Image Size?
var res = "<img src=\"" + m.Groups["url"].Value + "\"" + atts + " />";
if (m.Groups["href"].Length > 0)
{
var href = m.Groups["href"].Value;
var end = string.Empty;
var endMatch = Regex.Match(href, @"(.*)(?<end>\.|,|;|\))$");
if (m.Success && !string.IsNullOrEmpty(endMatch.Groups["end"].Value))
{
href = href[0..^1];
end = endMatch.Groups["end"].Value;
}
res = "<a href=\"" + Globals.EncodeHTMLLink(href) + "\">" + res + "</a>" + end;
}
string ImageFormatMatchEvaluator(Match m)
{
var atts = BlockAttributesParser.ParseBlockAttributes(m.Groups["atts"].Value, "img");
if (m.Groups["algn"].Length > 0)
atts += " align=\"" + Globals.ImageAlign[m.Groups["algn"].Value] + "\"";
if (m.Groups["title"].Length > 0)
{
atts += " title=\"" + m.Groups["title"].Value + "\"";
atts += " alt=\"" + m.Groups["title"].Value + "\"";
}
else
{
atts += " alt=\"\"";
}
// Get Image Size?
var res = "<img src=\"" + m.Groups["url"].Value + "\"" + atts + " />";
if (m.Groups["href"].Length > 0)
{
var href = m.Groups["href"].Value;
var end = string.Empty;
var endMatch = Regex.Match(href, @"(.*)(?<end>\.|,|;|\))$");
if (m.Success && !string.IsNullOrEmpty(endMatch.Groups["end"].Value))
{
href = href[0..^1];
end = endMatch.Groups["end"].Value;
}
res = "<a href=\"" + Globals.EncodeHTMLLink(href) + "\">" + res + "</a>" + end;
}
return res;
}
return res;
}
}

View File

@ -1,10 +1,9 @@
namespace Textile.Blocks
namespace Textile.Blocks;
public class InsertedPhraseBlockModifier : PhraseBlockModifier
{
public class InsertedPhraseBlockModifier : PhraseBlockModifier
public override string ModifyLine(string line)
{
public override string ModifyLine(string line)
{
return PhraseModifierFormat(line, @"\+", "ins");
}
return PhraseModifierFormat(line, @"\+", "ins");
}
}

View File

@ -1,10 +1,9 @@
namespace Textile.Blocks
namespace Textile.Blocks;
public class ItalicPhraseBlockModifier : PhraseBlockModifier
{
public class ItalicPhraseBlockModifier : PhraseBlockModifier
public override string ModifyLine(string line)
{
public override string ModifyLine(string line)
{
return PhraseModifierFormat(line, @"__", "i");
}
return PhraseModifierFormat(line, @"__", "i");
}
}

View File

@ -11,22 +11,21 @@
#endregion
namespace Textile.Blocks
{
public class NoTextileBlockModifier : BlockModifier
{
public override string ModifyLine(string line)
{
line = NoTextileEncoder.EncodeNoTextileZones(line, @"(?<=^|\s)<notextile>", @"</notextile>(?=(\s|$)?)");
line = NoTextileEncoder.EncodeNoTextileZones(line, @"==", @"==");
return line;
}
namespace Textile.Blocks;
public override string Conclude(string line)
{
line = NoTextileEncoder.DecodeNoTextileZones(line, @"(?<=^|\s)<notextile>", @"</notextile>(?=(\s|$)?)");
line = NoTextileEncoder.DecodeNoTextileZones(line, @"==", @"==");
return line;
}
public class NoTextileBlockModifier : BlockModifier
{
public override string ModifyLine(string line)
{
line = NoTextileEncoder.EncodeNoTextileZones(line, @"(?<=^|\s)<notextile>", @"</notextile>(?=(\s|$)?)");
line = NoTextileEncoder.EncodeNoTextileZones(line, @"==", @"==");
return line;
}
public override string Conclude(string line)
{
line = NoTextileEncoder.DecodeNoTextileZones(line, @"(?<=^|\s)<notextile>", @"</notextile>(?=(\s|$)?)");
line = NoTextileEncoder.DecodeNoTextileZones(line, @"==", @"==");
return line;
}
}

View File

@ -1,79 +1,78 @@
namespace Textile.Blocks
namespace Textile.Blocks;
public static class NoTextileEncoder
{
public static class NoTextileEncoder
private static readonly string[,] TextileModifiers = {
{ "\"", "&#34;" },
{ "%", "&#37;" },
{ "*", "&#42;" },
{ "+", "&#43;" },
{ "-", "&#45;" },
{ "<", "&lt;" }, // or "&#60;"
{ "=", "&#61;" },
{ ">", "&gt;" }, // or "&#62;"
{ "?", "&#63;" },
{ "^", "&#94;" },
{ "_", "&#95;" },
{ "~", "&#126;" },
{ "@", "&#64;" },
{ "'", "&#39;" },
{ "|", "&#124;" },
{ "!", "&#33;" },
{ "(", "&#40;" },
{ ")", "&#41;" },
{ ".", "&#46;" },
{ "x", "&#120;" }
};
public static string EncodeNoTextileZones(string tmp, string patternPrefix, string patternSuffix)
{
private static readonly string[,] TextileModifiers = {
{ "\"", "&#34;" },
{ "%", "&#37;" },
{ "*", "&#42;" },
{ "+", "&#43;" },
{ "-", "&#45;" },
{ "<", "&lt;" }, // or "&#60;"
{ "=", "&#61;" },
{ ">", "&gt;" }, // or "&#62;"
{ "?", "&#63;" },
{ "^", "&#94;" },
{ "_", "&#95;" },
{ "~", "&#126;" },
{ "@", "&#64;" },
{ "'", "&#39;" },
{ "|", "&#124;" },
{ "!", "&#33;" },
{ "(", "&#40;" },
{ ")", "&#41;" },
{ ".", "&#46;" },
{ "x", "&#120;" }
};
return EncodeNoTextileZones(tmp, patternPrefix, patternSuffix, null);
}
public static string EncodeNoTextileZones(string tmp, string patternPrefix, string patternSuffix)
public static string EncodeNoTextileZones(string tmp, string patternPrefix, string patternSuffix, string[] exceptions)
{
string evaluator(Match m)
{
return EncodeNoTextileZones(tmp, patternPrefix, patternSuffix, null);
}
public static string EncodeNoTextileZones(string tmp, string patternPrefix, string patternSuffix, string[] exceptions)
{
string evaluator(Match m)
var toEncode = m.Groups["notex"].Value;
if (toEncode.Length == 0)
{
var toEncode = m.Groups["notex"].Value;
if (toEncode.Length == 0)
{
return string.Empty;
}
for (var i = 0; i < TextileModifiers.GetLength(0); ++i)
{
if (exceptions == null || Array.IndexOf(exceptions, TextileModifiers[i, 0]) < 0)
{
toEncode = toEncode.Replace(TextileModifiers[i, 0], TextileModifiers[i, 1]);
}
}
return patternPrefix + toEncode + patternSuffix;
return string.Empty;
}
tmp = Regex.Replace(tmp, "("+ patternPrefix + "(?<notex>.+?)" + patternSuffix + ")*", new MatchEvaluator(evaluator));
return tmp;
}
public static string DecodeNoTextileZones(string tmp, string patternPrefix, string patternSuffix)
{
return DecodeNoTextileZones(tmp, patternPrefix, patternSuffix, null);
}
public static string DecodeNoTextileZones(string tmp, string patternPrefix, string patternSuffix, string[] exceptions)
{
string evaluator(Match m)
for (var i = 0; i < TextileModifiers.GetLength(0); ++i)
{
var toEncode = m.Groups["notex"].Value;
for (var i = 0; i < TextileModifiers.GetLength(0); ++i)
if (exceptions == null || Array.IndexOf(exceptions, TextileModifiers[i, 0]) < 0)
{
if (exceptions == null || Array.IndexOf(exceptions, TextileModifiers[i, 0]) < 0)
{
toEncode = toEncode.Replace(TextileModifiers[i, 1], TextileModifiers[i, 0]);
}
toEncode = toEncode.Replace(TextileModifiers[i, 0], TextileModifiers[i, 1]);
}
return toEncode;
}
tmp = Regex.Replace(tmp, "(" + patternPrefix + "(?<notex>.+?)" + patternSuffix + ")*", new MatchEvaluator(evaluator));
return tmp;
return patternPrefix + toEncode + patternSuffix;
}
tmp = Regex.Replace(tmp, "("+ patternPrefix + "(?<notex>.+?)" + patternSuffix + ")*", new MatchEvaluator(evaluator));
return tmp;
}
public static string DecodeNoTextileZones(string tmp, string patternPrefix, string patternSuffix)
{
return DecodeNoTextileZones(tmp, patternPrefix, patternSuffix, null);
}
public static string DecodeNoTextileZones(string tmp, string patternPrefix, string patternSuffix, string[] exceptions)
{
string evaluator(Match m)
{
var toEncode = m.Groups["notex"].Value;
for (var i = 0; i < TextileModifiers.GetLength(0); ++i)
{
if (exceptions == null || Array.IndexOf(exceptions, TextileModifiers[i, 0]) < 0)
{
toEncode = toEncode.Replace(TextileModifiers[i, 1], TextileModifiers[i, 0]);
}
}
return toEncode;
}
tmp = Regex.Replace(tmp, "(" + patternPrefix + "(?<notex>.+?)" + patternSuffix + ")*", new MatchEvaluator(evaluator));
return tmp;
}
}

View File

@ -10,82 +10,81 @@
// You must not remove this notice, or any other, from this software.
#endregion
namespace Textile.Blocks
namespace Textile.Blocks;
public abstract class PhraseBlockModifier : BlockModifier
{
public abstract class PhraseBlockModifier : BlockModifier
protected PhraseBlockModifier()
{
protected PhraseBlockModifier()
{
}
}
protected string PhraseModifierFormat(string input, string modifier, string tag)
protected string PhraseModifierFormat(string input, string modifier, string tag)
{
// All phrase modifiers are one character, or a double character. Sometimes,
// there's an additional escape character for the regex ('\').
var compressedModifier = modifier;
if (modifier.Length == 4)
{
// All phrase modifiers are one character, or a double character. Sometimes,
// there's an additional escape character for the regex ('\').
var compressedModifier = modifier;
if (modifier.Length == 4)
{
compressedModifier = modifier.Substring(0, 2);
}
else if (modifier.Length == 2)
{
if (modifier[0] != '\\')
compressedModifier = modifier[0].ToString();
//else: compressedModifier = modifier;
}
compressedModifier = modifier.Substring(0, 2);
}
else if (modifier.Length == 2)
{
if (modifier[0] != '\\')
compressedModifier = modifier[0].ToString();
//else: compressedModifier = modifier;
}
//else: compressedModifier = modifier;
// We try to remove the Textile tag used for the formatting from
// the punctuation pattern, so that we match the end of the formatted
// zone correctly.
var punctuationPattern = Globals.PunctuationPattern.Replace(compressedModifier, "");
// We try to remove the Textile tag used for the formatting from
// the punctuation pattern, so that we match the end of the formatted
// zone correctly.
var punctuationPattern = Globals.PunctuationPattern.Replace(compressedModifier, "");
// Now we can do the replacement.
var pmme = new PhraseModifierMatchEvaluator(tag);
var res = Regex.Replace(input,
@"(?<=\s|" + punctuationPattern + @"|[{\(\[]|^)" +
modifier +
Globals.BlockModifiersPattern +
@"(:(?<cite>(\S+)))?" +
@"(?<content>[^" + compressedModifier + "]*)" +
@"(?<end>" + punctuationPattern + @"*)" +
modifier +
@"(?=[\]\)}]|" + punctuationPattern + @"+|\s|$)",
new MatchEvaluator(pmme.MatchEvaluator)
);
return res;
// Now we can do the replacement.
var pmme = new PhraseModifierMatchEvaluator(tag);
var res = Regex.Replace(input,
@"(?<=\s|" + punctuationPattern + @"|[{\(\[]|^)" +
modifier +
Globals.BlockModifiersPattern +
@"(:(?<cite>(\S+)))?" +
@"(?<content>[^" + compressedModifier + "]*)" +
@"(?<end>" + punctuationPattern + @"*)" +
modifier +
@"(?=[\]\)}]|" + punctuationPattern + @"+|\s|$)",
new MatchEvaluator(pmme.MatchEvaluator)
);
return res;
}
private sealed class PhraseModifierMatchEvaluator
{
private readonly string _tag;
public PhraseModifierMatchEvaluator(string tag)
{
_tag = tag;
}
private sealed class PhraseModifierMatchEvaluator
public string MatchEvaluator(Match m)
{
readonly string m_tag;
public PhraseModifierMatchEvaluator(string tag)
if (m.Groups["content"].Length == 0)
{
m_tag = tag;
// It's possible that the "atts" match groups eats the contents
// when the user didn't want to give block attributes, but the content
// happens to match the syntax. For example: "*(blah)*".
if (m.Groups["atts"].Length == 0)
return m.ToString();
return "<" + _tag + ">" + m.Groups["atts"].Value + m.Groups["end"].Value + "</" + _tag + ">";
}
public string MatchEvaluator(Match m)
{
if (m.Groups["content"].Length == 0)
{
// It's possible that the "atts" match groups eats the contents
// when the user didn't want to give block attributes, but the content
// happens to match the syntax. For example: "*(blah)*".
if (m.Groups["atts"].Length == 0)
return m.ToString();
return "<" + m_tag + ">" + m.Groups["atts"].Value + m.Groups["end"].Value + "</" + m_tag + ">";
}
var atts = BlockAttributesParser.ParseBlockAttributes(m.Groups["atts"].Value, _tag);
if (m.Groups["cite"].Length > 0)
atts += " cite=\"" + m.Groups["cite"] + "\"";
var atts = BlockAttributesParser.ParseBlockAttributes(m.Groups["atts"].Value, m_tag);
if (m.Groups["cite"].Length > 0)
atts += " cite=\"" + m.Groups["cite"] + "\"";
var res = "<" + m_tag + atts + ">" +
m.Groups["content"].Value + m.Groups["end"].Value +
"</" + m_tag + ">";
return res;
}
var res = "<" + _tag + atts + ">" +
m.Groups["content"].Value + m.Groups["end"].Value +
"</" + _tag + ">";
return res;
}
}
}

View File

@ -10,28 +10,27 @@
// You must not remove this notice, or any other, from this software.
#endregion
namespace Textile.Blocks
{
public class PreBlockModifier : BlockModifier
{
public override string ModifyLine(string line)
{
// Encode the contents of the "<pre>" tags so that we don't
// generate formatting out of it.
line = NoTextileEncoder.EncodeNoTextileZones(line,
@"(?<=(^|\s)<pre(" + Globals.HtmlAttributesPattern + @")>)",
@"(?=</pre>)");
return line;
}
namespace Textile.Blocks;
public override string Conclude(string line)
{
// Recode everything.
line = NoTextileEncoder.DecodeNoTextileZones(line,
@"(?<=(^|\s)<pre(" + Globals.HtmlAttributesPattern + @")>)",
@"(?=</pre>)",
new string[] { "<", ">" });
return line;
}
public class PreBlockModifier : BlockModifier
{
public override string ModifyLine(string line)
{
// Encode the contents of the "<pre>" tags so that we don't
// generate formatting out of it.
line = NoTextileEncoder.EncodeNoTextileZones(line,
@"(?<=(^|\s)<pre(" + Globals.HtmlAttributesPattern + @")>)",
@"(?=</pre>)");
return line;
}
public override string Conclude(string line)
{
// Recode everything.
line = NoTextileEncoder.DecodeNoTextileZones(line,
@"(?<=(^|\s)<pre(" + Globals.HtmlAttributesPattern + @")>)",
@"(?=</pre>)",
new string[] { "<", ">" });
return line;
}
}

View File

@ -1,10 +1,9 @@
namespace Textile.Blocks
namespace Textile.Blocks;
public class SpanPhraseBlockModifier : PhraseBlockModifier
{
public class SpanPhraseBlockModifier : PhraseBlockModifier
public override string ModifyLine(string line)
{
public override string ModifyLine(string line)
{
return PhraseModifierFormat(line, @"%", "span");
}
return PhraseModifierFormat(line, @"%", "span");
}
}

View File

@ -1,10 +1,9 @@
namespace Textile.Blocks
namespace Textile.Blocks;
public class StrongPhraseBlockModifier : PhraseBlockModifier
{
public class StrongPhraseBlockModifier : PhraseBlockModifier
public override string ModifyLine(string line)
{
public override string ModifyLine(string line)
{
return PhraseModifierFormat(line, @"\*", "strong");
}
return PhraseModifierFormat(line, @"\*", "strong");
}
}
}

View File

@ -1,10 +1,9 @@
namespace Textile.Blocks
namespace Textile.Blocks;
public class SubScriptPhraseBlockModifier : PhraseBlockModifier
{
public class SubScriptPhraseBlockModifier : PhraseBlockModifier
public override string ModifyLine(string line)
{
public override string ModifyLine(string line)
{
return PhraseModifierFormat(line, @"~", "sub");
}
return PhraseModifierFormat(line, @"~", "sub");
}
}
}

View File

@ -1,10 +1,9 @@
namespace Textile.Blocks
namespace Textile.Blocks;
public class SuperScriptPhraseBlockModifier : PhraseBlockModifier
{
public class SuperScriptPhraseBlockModifier : PhraseBlockModifier
public override string ModifyLine(string line)
{
public override string ModifyLine(string line)
{
return PhraseModifierFormat(line, @"\^", "sup");
}
return PhraseModifierFormat(line, @"\^", "sup");
}
}

View File

@ -10,126 +10,125 @@
// You must not remove this notice, or any other, from this software.
#endregion
namespace Textile
namespace Textile;
/// <summary>
/// Base class for formatter states.
/// </summary>
/// A formatter state describes the current situation
/// of the text being currently processed. A state can
/// write HTML code when entered, exited, and can modify
/// each line of text it receives.
public abstract class FormatterState
{
/// <summary>
/// Base class for formatter states.
/// The formatter this state belongs to.
/// </summary>
/// A formatter state describes the current situation
/// of the text being currently processed. A state can
/// write HTML code when entered, exited, and can modify
/// each line of text it receives.
public abstract class FormatterState
public TextileFormatter Formatter { get; }
/// <summary>
/// Public constructor.
/// </summary>
/// <param name="f">The parent formatter.</param>
protected FormatterState(TextileFormatter formatter)
{
/// <summary>
/// The formatter this state belongs to.
/// </summary>
public TextileFormatter Formatter { get; }
Formatter = formatter;
}
/// <summary>
/// Public constructor.
/// </summary>
/// <param name="f">The parent formatter.</param>
protected FormatterState(TextileFormatter formatter)
/// <summary>
///
/// </summary>
/// <param name="input"></param>
/// <param name="m"></param>
/// <returns></returns>
public abstract string Consume(string input, Match m);
/// <summary>
/// Method called when the state is entered.
/// </summary>
public abstract void Enter();
/// <summary>
/// Method called when the state is exited.
/// </summary>
public abstract void Exit();
/// <summary>
/// Method called when a line of text should be written
/// to the web form.
/// </summary>
/// <param name="input">The line of text.</param>
public abstract void FormatLine(string input);
/// <summary>
/// Returns whether this state can last for more than one line.
/// </summary>
/// <returns>A boolean value stating whether this state is only for one line.</returns>
/// This method should return true only if this state is genuinely
/// multi-line. For example, a header text is only one line long. You can
/// have several consecutive lines of header texts, but they are not the same
/// header - just several headers one after the other.
/// Bulleted and numbered lists are good examples of multi-line states.
//abstract public bool IsOneLineOnly();
/// <summary>
///
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public abstract bool ShouldExit(string input);
/// <summary>
///
/// </summary>
/// <param name="actualTag"></param>
/// <param name="alignNfo"></param>
/// <param name="attNfo"></param>
/// <returns></returns>
public virtual bool ShouldNestState(FormatterState other)
{
return false;
}
/// <summary>
/// Returns whether block formatting (quick phrase modifiers, etc.) should be
/// applied to this line.
/// </summary>
/// <param name="input">The line of text</param>
/// <returns>Whether the line should be formatted for blocks</returns>
public virtual bool ShouldFormatBlocks(string input)
{
return true;
}
/// <summary>
/// Returns whether the current state accepts being superceded by another one
/// we would possibly find by parsing the input line of text.
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public virtual bool ShouldParseForNewFormatterState(string input)
{
return true;
}
/// <summary>
/// Gets the formatting state we should fallback to if we don't find anything
/// relevant in a line of text.
/// </summary>
public virtual Type FallbackFormattingState
{
get
{
Formatter = formatter;
}
/// <summary>
///
/// </summary>
/// <param name="input"></param>
/// <param name="m"></param>
/// <returns></returns>
public abstract string Consume(string input, Match m);
/// <summary>
/// Method called when the state is entered.
/// </summary>
public abstract void Enter();
/// <summary>
/// Method called when the state is exited.
/// </summary>
public abstract void Exit();
/// <summary>
/// Method called when a line of text should be written
/// to the web form.
/// </summary>
/// <param name="input">The line of text.</param>
public abstract void FormatLine(string input);
/// <summary>
/// Returns whether this state can last for more than one line.
/// </summary>
/// <returns>A boolean value stating whether this state is only for one line.</returns>
/// This method should return true only if this state is genuinely
/// multi-line. For example, a header text is only one line long. You can
/// have several consecutive lines of header texts, but they are not the same
/// header - just several headers one after the other.
/// Bulleted and numbered lists are good examples of multi-line states.
//abstract public bool IsOneLineOnly();
/// <summary>
///
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public abstract bool ShouldExit(string input);
/// <summary>
///
/// </summary>
/// <param name="actualTag"></param>
/// <param name="alignNfo"></param>
/// <param name="attNfo"></param>
/// <returns></returns>
public virtual bool ShouldNestState(FormatterState other)
{
return false;
}
/// <summary>
/// Returns whether block formatting (quick phrase modifiers, etc.) should be
/// applied to this line.
/// </summary>
/// <param name="input">The line of text</param>
/// <returns>Whether the line should be formatted for blocks</returns>
public virtual bool ShouldFormatBlocks(string input)
{
return true;
}
/// <summary>
/// Returns whether the current state accepts being superceded by another one
/// we would possibly find by parsing the input line of text.
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public virtual bool ShouldParseForNewFormatterState(string input)
{
return true;
}
/// <summary>
/// Gets the formatting state we should fallback to if we don't find anything
/// relevant in a line of text.
/// </summary>
public virtual Type FallbackFormattingState
{
get
{
return typeof(States.ParagraphFormatterState);
}
}
protected FormatterState CurrentFormatterState
{
get { return this.Formatter.CurrentState; }
}
protected void ChangeFormatterState(FormatterState formatterState)
{
this.Formatter.ChangeState(formatterState);
return typeof(States.ParagraphFormatterState);
}
}
}
protected FormatterState CurrentFormatterState
{
get { return this.Formatter.CurrentState; }
}
protected void ChangeFormatterState(FormatterState formatterState)
{
this.Formatter.ChangeState(formatterState);
}
}

View File

@ -1,21 +1,20 @@
namespace Textile
namespace Textile;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class FormatterStateAttribute : Attribute
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class FormatterStateAttribute : Attribute
public string Pattern { get; }
public FormatterStateAttribute(string pattern)
{
public string Pattern { get; }
Pattern = pattern;
}
public FormatterStateAttribute(string pattern)
{
Pattern = pattern;
}
public static FormatterStateAttribute Get(Type type)
{
var atts = type.GetCustomAttributes(typeof(FormatterStateAttribute), false);
if (atts.Length == 0)
return null;
return (FormatterStateAttribute)atts[0];
}
public static FormatterStateAttribute Get(Type type)
{
var atts = type.GetCustomAttributes(typeof(FormatterStateAttribute), false);
if (atts.Length == 0)
return null;
return (FormatterStateAttribute)atts[0];
}
}

View File

@ -1,6 +1,4 @@
global using System;
global using System.Collections.Generic;
global using System.Text;
global using System.Text;
global using System.Text.RegularExpressions;
global using Textile.Blocks;

View File

@ -11,78 +11,77 @@
#endregion
namespace Textile
namespace Textile;
/// <summary>
/// A utility class for global things used by the TextileFormatter.
/// </summary>
static class Globals
{
#region Global Regex Patterns
public const string HorizontalAlignPattern = @"(?:[()]*(\<(?!>)|(?<!<)\>|\<\>|=)[()]*)";
public const string VerticalAlignPattern = @"[\-^~]";
public const string CssClassPattern = @"(?:\([^)]+\))";
public const string LanguagePattern = @"(?:\[[^]]+\])";
public const string CssStylePattern = @"(?:\{[^}]+\})";
public const string ColumnSpanPattern = @"(?:\\\d+)";
public const string RowSpanPattern = @"(?:/\d+)";
public const string AlignPattern = "(?<align>" + HorizontalAlignPattern + "?" + VerticalAlignPattern + "?|" + VerticalAlignPattern + "?" + HorizontalAlignPattern + "?)";
public const string SpanPattern = @"(?<span>" + ColumnSpanPattern + "?" + RowSpanPattern + "?|" + RowSpanPattern + "?" + ColumnSpanPattern + "?)";
public const string BlockModifiersPattern = @"(?<atts>" + CssClassPattern + "?" + CssStylePattern + "?" + LanguagePattern + "?|" +
CssStylePattern + "?" + LanguagePattern + "?" + CssClassPattern + "?|" +
LanguagePattern + "?" + CssStylePattern + "?" + CssClassPattern + "?)";
public const string PunctuationPattern = @"[\!""#\$%&'()\*\+,\-\./:;<=>\?@\[\\\]\^_`{}~]";
public const string HtmlAttributesPattern = @"(\s+\w+=((""[^""]+"")|('[^']+')))*";
#endregion
/// <summary>
/// A utility class for global things used by the TextileFormatter.
/// Image alignment tags, mapped to their HTML meanings.
/// </summary>
static class Globals
public static Dictionary<string, string> ImageAlign { get; set; }
/// <summary>
/// Horizontal text alignment tags, mapped to their HTML meanings.
/// </summary>
public static Dictionary<string, string> HorizontalAlign { get; set; }
/// <summary>
/// Vertical text alignment tags, mapped to their HTML meanings.
/// </summary>
public static Dictionary<string, string> VerticalAlign { get; set;}
static Globals()
{
#region Global Regex Patterns
public const string HorizontalAlignPattern = @"(?:[()]*(\<(?!>)|(?<!<)\>|\<\>|=)[()]*)";
public const string VerticalAlignPattern = @"[\-^~]";
public const string CssClassPattern = @"(?:\([^)]+\))";
public const string LanguagePattern = @"(?:\[[^]]+\])";
public const string CssStylePattern = @"(?:\{[^}]+\})";
public const string ColumnSpanPattern = @"(?:\\\d+)";
public const string RowSpanPattern = @"(?:/\d+)";
public const string AlignPattern = "(?<align>" + HorizontalAlignPattern + "?" + VerticalAlignPattern + "?|" + VerticalAlignPattern + "?" + HorizontalAlignPattern + "?)";
public const string SpanPattern = @"(?<span>" + ColumnSpanPattern + "?" + RowSpanPattern + "?|" + RowSpanPattern + "?" + ColumnSpanPattern + "?)";
public const string BlockModifiersPattern = @"(?<atts>" + CssClassPattern + "?" + CssStylePattern + "?" + LanguagePattern + "?|" +
CssStylePattern + "?" + LanguagePattern + "?" + CssClassPattern + "?|" +
LanguagePattern + "?" + CssStylePattern + "?" + CssClassPattern + "?)";
public const string PunctuationPattern = @"[\!""#\$%&'()\*\+,\-\./:;<=>\?@\[\\\]\^_`{}~]";
public const string HtmlAttributesPattern = @"(\s+\w+=((""[^""]+"")|('[^']+')))*";
#endregion
/// <summary>
/// Image alignment tags, mapped to their HTML meanings.
/// </summary>
public static Dictionary<string, string> ImageAlign { get; set; }
/// <summary>
/// Horizontal text alignment tags, mapped to their HTML meanings.
/// </summary>
public static Dictionary<string, string> HorizontalAlign { get; set; }
/// <summary>
/// Vertical text alignment tags, mapped to their HTML meanings.
/// </summary>
public static Dictionary<string, string> VerticalAlign { get; set;}
static Globals()
ImageAlign = new Dictionary<string, string>
{
ImageAlign = new Dictionary<string, string>
{
["<"] = "left",
["="] = "center",
[">"] = "right"
};
["<"] = "left",
["="] = "center",
[">"] = "right"
};
HorizontalAlign = new Dictionary<string, string>
{
["<"] = "left",
["="] = "center",
[">"] = "right",
["<>"] = "justify"
};
VerticalAlign = new Dictionary<string, string>
{
["^"] = "top",
["-"] = "middle",
["~"] = "bottom"
};
}
public static string EncodeHTMLLink(string url)
HorizontalAlign = new Dictionary<string, string>
{
url = url.Replace("&amp;", "&#38;");
url = System.Text.RegularExpressions.Regex.Replace(url, "&(?=[^#])", "&#38;");
return url;
}
["<"] = "left",
["="] = "center",
[">"] = "right",
["<>"] = "justify"
};
VerticalAlign = new Dictionary<string, string>
{
["^"] = "top",
["-"] = "middle",
["~"] = "bottom"
};
}
public static string EncodeHTMLLink(string url)
{
url = url.Replace("&amp;", "&#38;");
url = System.Text.RegularExpressions.Regex.Replace(url, "&(?=[^#])", "&#38;");
return url;
}
}

View File

@ -10,44 +10,39 @@
// You must not remove this notice, or any other, from this software.
#endregion
#region Using Statements
#endregion
namespace Textile;
namespace Textile
/// <summary>
/// Interface through which the HTML formatted text
/// will be sent.
/// </summary>
/// Clients of the TextileFormatter class will have to provide
/// an outputter that implements this interface. Most of the
/// time, it'll be the WebForm itself.
public interface IOutputter
{
/// <summary>
/// Interface through which the HTML formatted text
/// will be sent.
/// Method called just before the formatted text
/// is sent to the outputter.
/// </summary>
/// Clients of the TextileFormatter class will have to provide
/// an outputter that implements this interface. Most of the
/// time, it'll be the WebForm itself.
public interface IOutputter
{
/// <summary>
/// Method called just before the formatted text
/// is sent to the outputter.
/// </summary>
void Begin();
void Begin();
/// <summary>
/// Metohd called whenever the TextileFormatter wants to
/// print some text.
/// </summary>
/// <param name="text">The formatted HTML text.</param>
void Write(string text);
/// <summary>
/// Metohd called whenever the TextileFormatter wants to
/// print some text. This should automatically print an
/// additionnal end of line character.
/// </summary>
/// <param name="line">The formatted HTML text.</param>
void WriteLine(string line);
/// <summary>
/// Metohd called whenever the TextileFormatter wants to
/// print some text.
/// </summary>
/// <param name="text">The formatted HTML text.</param>
void Write(string text);
/// <summary>
/// Metohd called whenever the TextileFormatter wants to
/// print some text. This should automatically print an
/// additionnal end of line character.
/// </summary>
/// <param name="line">The formatted HTML text.</param>
void WriteLine(string line);
/// <summary>
/// Method called at the end of the formatting.
/// </summary>
void End();
}
/// <summary>
/// Method called at the end of the formatting.
/// </summary>
void End();
}

View File

@ -10,42 +10,41 @@
// You must not remove this notice, or any other, from this software.
#endregion
namespace Textile.States
namespace Textile.States;
[FormatterState(SimpleBlockFormatterState.PatternBegin + @"bq" + SimpleBlockFormatterState.PatternEnd)]
public class BlockQuoteFormatterState : SimpleBlockFormatterState
{
[FormatterState(SimpleBlockFormatterState.PatternBegin + @"bq" + SimpleBlockFormatterState.PatternEnd)]
public class BlockQuoteFormatterState : SimpleBlockFormatterState
public BlockQuoteFormatterState(TextileFormatter f)
: base(f)
{
public BlockQuoteFormatterState(TextileFormatter f)
: base(f)
{
}
}
public override void Enter()
{
Formatter.Output.Write("<blockquote" + FormattedStylesAndAlignment("blockquote") + "><p>");
}
public override void Enter()
{
Formatter.Output.Write("<blockquote" + FormattedStylesAndAlignment("blockquote") + "><p>");
}
public override void Exit()
{
Formatter.Output.WriteLine("</p></blockquote>");
}
public override void Exit()
{
Formatter.Output.WriteLine("</p></blockquote>");
}
public override void FormatLine(string input)
{
Formatter.Output.Write(input);
}
public override void FormatLine(string input)
{
Formatter.Output.Write(input);
}
public override bool ShouldExit(string input)
{
if (Regex.IsMatch(input, @"^\s*$"))
return true;
Formatter.Output.WriteLine("<br />");
return false;
}
public override bool ShouldExit(string input)
{
if (Regex.IsMatch(input, @"^\s*$"))
return true;
Formatter.Output.WriteLine("<br />");
return false;
}
public override Type FallbackFormattingState
{
get { return null; }
}
public override Type FallbackFormattingState
{
get { return null; }
}
}

View File

@ -1,78 +1,77 @@
namespace Textile.States
namespace Textile.States;
[FormatterState(@"^\s*<code" + Globals.HtmlAttributesPattern + ">")]
public class CodeFormatterState : FormatterState
{
[FormatterState(@"^\s*<code" + Globals.HtmlAttributesPattern + ">")]
public class CodeFormatterState : FormatterState
private bool _shouldExitNextTime = false;
private bool _shouldFixHtmlEntities = false;
public CodeFormatterState(TextileFormatter f)
: base(f)
{
bool m_shouldExitNextTime = false;
bool m_shouldFixHtmlEntities = false;
public CodeFormatterState(TextileFormatter f)
: base(f)
{
}
public override string Consume(string input, Match m)
{
if (!Regex.IsMatch(input, "</code>"))
{
this.Formatter.ChangeState(this);
}
else
{
this.Formatter.ChangeState(new PassthroughFormatterState(this.Formatter));
}
return input;
}
public override bool ShouldNestState(FormatterState other)
{
return true;
}
public override void Enter()
{
m_shouldFixHtmlEntities = false;
}
public override void Exit()
{
}
public override void FormatLine(string input)
{
if (m_shouldFixHtmlEntities)
input = FixEntities(input);
Formatter.Output.WriteLine(input);
m_shouldFixHtmlEntities = true;
}
public override bool ShouldExit(string input)
{
if (m_shouldExitNextTime)
return true;
m_shouldExitNextTime = Regex.IsMatch(input, @"</code>");
m_shouldFixHtmlEntities = !m_shouldExitNextTime;
return false;
}
public override bool ShouldFormatBlocks(string input)
{
return false;
}
public override bool ShouldParseForNewFormatterState(string input)
{
return false;
}
private string FixEntities(string text)
{
// de-entify any remaining angle brackets or ampersands
text = text.Replace("&", "&amp;");
text = text.Replace(">", "&gt;");
text = text.Replace("<", "&lt;");
//Regex.Replace(text, @"\b&([#a-z0-9]+;)", "x%x%");
return text;
}
}
}
public override string Consume(string input, Match m)
{
if (!Regex.IsMatch(input, "</code>"))
{
this.Formatter.ChangeState(this);
}
else
{
this.Formatter.ChangeState(new PassthroughFormatterState(this.Formatter));
}
return input;
}
public override bool ShouldNestState(FormatterState other)
{
return true;
}
public override void Enter()
{
_shouldFixHtmlEntities = false;
}
public override void Exit()
{
}
public override void FormatLine(string input)
{
if (_shouldFixHtmlEntities)
input = FixEntities(input);
Formatter.Output.WriteLine(input);
_shouldFixHtmlEntities = true;
}
public override bool ShouldExit(string input)
{
if (_shouldExitNextTime)
return true;
_shouldExitNextTime = Regex.IsMatch(input, @"</code>");
_shouldFixHtmlEntities = !_shouldExitNextTime;
return false;
}
public override bool ShouldFormatBlocks(string input)
{
return false;
}
public override bool ShouldParseForNewFormatterState(string input)
{
return false;
}
private string FixEntities(string text)
{
// de-entify any remaining angle brackets or ampersands
text = text.Replace("&", "&amp;");
text = text.Replace(">", "&gt;");
text = text.Replace("<", "&lt;");
//Regex.Replace(text, @"\b&([#a-z0-9]+;)", "x%x%");
return text;
}
}

View File

@ -11,50 +11,49 @@
#endregion
namespace Textile.States
namespace Textile.States;
[FormatterState(SimpleBlockFormatterState.PatternBegin + @"fn[0-9]+" + SimpleBlockFormatterState.PatternEnd)]
public class FootNoteFormatterState : SimpleBlockFormatterState
{
[FormatterState(SimpleBlockFormatterState.PatternBegin + @"fn[0-9]+" + SimpleBlockFormatterState.PatternEnd)]
public class FootNoteFormatterState : SimpleBlockFormatterState
private int _noteID = 0;
public FootNoteFormatterState(TextileFormatter f)
: base(f)
{
int m_noteID = 0;
}
public FootNoteFormatterState(TextileFormatter f)
: base(f)
{
}
public override void Enter()
{
Formatter.Output.Write(
string.Format("<p id=\"fn{0}\"{1}><sup>{2}</sup> ",
_noteID,
FormattedStylesAndAlignment("p"),
_noteID));
}
public override void Enter()
{
Formatter.Output.Write(
string.Format("<p id=\"fn{0}\"{1}><sup>{2}</sup> ",
m_noteID,
FormattedStylesAndAlignment("p"),
m_noteID));
}
public override void Exit()
{
Formatter.Output.WriteLine("</p>");
}
public override void Exit()
{
Formatter.Output.WriteLine("</p>");
}
public override void FormatLine(string input)
{
Formatter.Output.Write(input);
}
public override void FormatLine(string input)
{
Formatter.Output.Write(input);
}
public override bool ShouldExit(string input)
{
return true;
}
protected override void OnContextAcquired()
{
var m = Regex.Match(Tag, @"^fn(?<id>[0-9]+)");
_noteID = int.Parse(m.Groups["id"].Value);
}
public override bool ShouldExit(string input)
{
return true;
}
protected override void OnContextAcquired()
{
var m = Regex.Match(Tag, @"^fn(?<id>[0-9]+)");
m_noteID = int.Parse(m.Groups["id"].Value);
}
public override bool ShouldNestState(FormatterState other)
{
return false;
}
public override bool ShouldNestState(FormatterState other)
{
return false;
}
}

View File

@ -11,95 +11,94 @@
#endregion
namespace Textile.States
namespace Textile.States;
[FormatterState(SimpleBlockFormatterState.PatternBegin + @"pad[0-9]+" + SimpleBlockFormatterState.PatternEnd)]
public class PaddingFormatterState : SimpleBlockFormatterState
{
[FormatterState(SimpleBlockFormatterState.PatternBegin + @"pad[0-9]+" + SimpleBlockFormatterState.PatternEnd)]
public class PaddingFormatterState : SimpleBlockFormatterState
public PaddingFormatterState(TextileFormatter formatter)
: base(formatter)
{
public PaddingFormatterState(TextileFormatter formatter)
: base(formatter)
}
public int HeaderLevel { get; private set; } = 0;
public override void Enter()
{
for (var i = 0; i < HeaderLevel; i++)
{
}
public int HeaderLevel { get; private set; } = 0;
public override void Enter()
{
for (var i = 0; i < HeaderLevel; i++)
{
Formatter.Output.Write($"<br {FormattedStylesAndAlignment("br")}/>");
}
}
public override void Exit()
{
}
protected override void OnContextAcquired()
{
var m = Regex.Match(Tag, @"^pad(?<lvl>[0-9]+)");
HeaderLevel = int.Parse(m.Groups["lvl"].Value);
}
public override void FormatLine(string input)
{
Formatter.Output.Write(input);
}
public override bool ShouldExit(string intput)
{
return true;
}
public override bool ShouldNestState(FormatterState other)
{
return false;
Formatter.Output.Write($"<br {FormattedStylesAndAlignment("br")}/>");
}
}
/// <summary>
/// Formatting state for headers and titles.
/// </summary>
[FormatterState(SimpleBlockFormatterState.PatternBegin + @"h[0-9]+" + SimpleBlockFormatterState.PatternEnd)]
public class HeaderFormatterState : SimpleBlockFormatterState
public override void Exit()
{
public int HeaderLevel { get; private set; } = 0;
}
public HeaderFormatterState(TextileFormatter f)
: base(f)
{
}
protected override void OnContextAcquired()
{
var m = Regex.Match(Tag, @"^pad(?<lvl>[0-9]+)");
HeaderLevel = int.Parse(m.Groups["lvl"].Value);
}
public override void Enter()
{
Formatter.Output.Write("<h" + HeaderLevel + FormattedStylesAndAlignment("h" + HeaderLevel) + ">");
}
public override void FormatLine(string input)
{
Formatter.Output.Write(input);
}
public override void Exit()
{
Formatter.Output.WriteLine("</h" + HeaderLevel + ">");
}
public override bool ShouldExit(string intput)
{
return true;
}
protected override void OnContextAcquired()
{
var m = Regex.Match(Tag, @"^h(?<lvl>[0-9]+)");
HeaderLevel = int.Parse(m.Groups["lvl"].Value);
}
public override void FormatLine(string input)
{
Formatter.Output.Write(input);
}
public override bool ShouldExit(string intput)
{
return true;
}
public override bool ShouldNestState(FormatterState other)
{
return false;
}
public override bool ShouldNestState(FormatterState other)
{
return false;
}
}
/// <summary>
/// Formatting state for headers and titles.
/// </summary>
[FormatterState(SimpleBlockFormatterState.PatternBegin + @"h[0-9]+" + SimpleBlockFormatterState.PatternEnd)]
public class HeaderFormatterState : SimpleBlockFormatterState
{
public int HeaderLevel { get; private set; } = 0;
public HeaderFormatterState(TextileFormatter f)
: base(f)
{
}
public override void Enter()
{
Formatter.Output.Write("<h" + HeaderLevel + FormattedStylesAndAlignment("h" + HeaderLevel) + ">");
}
public override void Exit()
{
Formatter.Output.WriteLine("</h" + HeaderLevel + ">");
}
protected override void OnContextAcquired()
{
var m = Regex.Match(Tag, @"^h(?<lvl>[0-9]+)");
HeaderLevel = int.Parse(m.Groups["lvl"].Value);
}
public override void FormatLine(string input)
{
Formatter.Output.Write(input);
}
public override bool ShouldExit(string intput)
{
return true;
}
public override bool ShouldNestState(FormatterState other)
{
return false;
}
}

View File

@ -21,15 +21,15 @@ namespace Textile.States
internal const string PatternBegin = @"^\s*(?<tag>";
internal const string PatternEnd = @")" + Globals.BlockModifiersPattern + @"(?:\s+)? (?<content>.*)$";
private bool m_firstItem = true;
private bool m_firstItemLine = true;
private string m_tag;
private string m_attsInfo;
private string m_alignInfo;
private bool _firstItem = true;
private bool _firstItemLine = true;
private string _tag;
private string _attsInfo;
private string _alignInfo;
protected int NestingDepth
{
get { return m_tag.Length; }
get { return _tag.Length; }
}
protected ListFormatterState(TextileFormatter formatter)
@ -39,9 +39,9 @@ namespace Textile.States
public override string Consume(string input, Match m)
{
m_tag = m.Groups["tag"].Value;
m_alignInfo = m.Groups["align"].Value;
m_attsInfo = m.Groups["atts"].Value;
_tag = m.Groups["tag"].Value;
_alignInfo = m.Groups["align"].Value;
_attsInfo = m.Groups["atts"].Value;
input = m.Groups["content"].Value;
this.Formatter.ChangeState(this);
@ -51,8 +51,8 @@ namespace Textile.States
public sealed override void Enter()
{
m_firstItem = true;
m_firstItemLine = true;
_firstItem = true;
_firstItemLine = true;
WriteIndent();
}
@ -64,19 +64,19 @@ namespace Textile.States
public sealed override void FormatLine(string input)
{
if (m_firstItemLine)
if (_firstItemLine)
{
if (!m_firstItem)
if (!_firstItem)
Formatter.Output.WriteLine("</li>");
Formatter.Output.Write("<li " + FormattedStylesAndAlignment("li") + ">");
m_firstItemLine = false;
_firstItemLine = false;
}
else
{
Formatter.Output.WriteLine("<br />");
}
Formatter.Output.Write(input);
m_firstItem = false;
_firstItem = false;
}
public sealed override bool ShouldNestState(FormatterState other)
@ -109,7 +109,7 @@ namespace Textile.States
// previously (no "**" or "##" tags), or if it's
// a new list item.
if (IsMatchForMe(input, NestingDepth, NestingDepth))
m_firstItemLine = true;
_firstItemLine = true;
return false;
}
@ -131,7 +131,7 @@ namespace Textile.States
protected string FormattedStylesAndAlignment(string element)
{
return Blocks.BlockAttributesParser.ParseBlockAttributes(m_alignInfo + m_attsInfo, element);
return Blocks.BlockAttributesParser.ParseBlockAttributes(_alignInfo + _attsInfo, element);
}
}
}

View File

@ -1,64 +1,63 @@
namespace Textile.States
namespace Textile.States;
[FormatterState(@"^\s*<notextile>\s*$")]
public class NoTextileFormatterState : FormatterState
{
[FormatterState(@"^\s*<notextile>\s*$")]
public class NoTextileFormatterState : FormatterState
private bool _shouldExitNextTime = false;
public NoTextileFormatterState(TextileFormatter f)
: base(f)
{
bool m_shouldExitNextTime = false;
}
public NoTextileFormatterState(TextileFormatter f)
: base(f)
{
}
public override string Consume(string input, Match m)
{
this.Formatter.ChangeState(this);
return string.Empty;
}
public override string Consume(string input, Match m)
{
this.Formatter.ChangeState(this);
return string.Empty;
}
public override bool ShouldNestState(FormatterState other)
{
return false;
}
public override bool ShouldNestState(FormatterState other)
{
return false;
}
public override void Enter()
{
}
public override void Enter()
{
}
public override void Exit()
{
}
public override void Exit()
{
}
public override void FormatLine(string input)
{
if (!_shouldExitNextTime)
Formatter.Output.WriteLine(input);
}
public override void FormatLine(string input)
{
if (!m_shouldExitNextTime)
Formatter.Output.WriteLine(input);
}
public override bool ShouldExit(string input)
{
if (_shouldExitNextTime)
return true;
_shouldExitNextTime = Regex.IsMatch(input, @"^\s*</notextile>\s*$");
return false;
}
public override bool ShouldExit(string input)
{
if (m_shouldExitNextTime)
return true;
m_shouldExitNextTime = Regex.IsMatch(input, @"^\s*</notextile>\s*$");
return false;
}
public override bool ShouldFormatBlocks(string input)
{
return false;
}
public override bool ShouldFormatBlocks(string input)
{
return false;
}
public override bool ShouldParseForNewFormatterState(string input)
{
return false;
}
public override bool ShouldParseForNewFormatterState(string input)
public override Type FallbackFormattingState
{
get
{
return false;
}
public override Type FallbackFormattingState
{
get
{
return null;
}
return null;
}
}
}
}

View File

@ -11,37 +11,36 @@
#endregion
namespace Textile.States
namespace Textile.States;
/// <summary>
/// Formatting state for a numbered list.
/// </summary>
[FormatterState(ListFormatterState.PatternBegin + @"#+" + ListFormatterState.PatternEnd)]
public class OrderedListFormatterState : ListFormatterState
{
/// <summary>
/// Formatting state for a numbered list.
/// </summary>
[FormatterState(ListFormatterState.PatternBegin + @"#+" + ListFormatterState.PatternEnd)]
public class OrderedListFormatterState : ListFormatterState
public OrderedListFormatterState(TextileFormatter formatter)
: base(formatter)
{
public OrderedListFormatterState(TextileFormatter formatter)
: base(formatter)
{
}
}
protected override void WriteIndent()
{
Formatter.Output.WriteLine("<ol" + FormattedStylesAndAlignment("ol") + ">");
}
protected override void WriteIndent()
{
Formatter.Output.WriteLine("<ol" + FormattedStylesAndAlignment("ol") + ">");
}
protected override void WriteOutdent()
{
Formatter.Output.WriteLine("</ol>");
}
protected override void WriteOutdent()
{
Formatter.Output.WriteLine("</ol>");
}
protected override bool IsMatchForMe(string input, int minNestingDepth, int maxNestingDepth)
{
return Regex.IsMatch(input, @"^\s*([\*#]{" + (minNestingDepth - 1) + @"," + (maxNestingDepth - 1) + @"})#" + Globals.BlockModifiersPattern + @"\s");
}
protected override bool IsMatchForMe(string input, int minNestingDepth, int maxNestingDepth)
{
return Regex.IsMatch(input, @"^\s*([\*#]{" + (minNestingDepth - 1) + @"," + (maxNestingDepth - 1) + @"})#" + Globals.BlockModifiersPattern + @"\s");
}
protected override bool IsMatchForOthers(string input, int minNestingDepth, int maxNestingDepth)
{
return Regex.IsMatch(input, @"^\s*([\*#]{" + (minNestingDepth - 1) + @"," + (maxNestingDepth - 1) + @"})\*" + Globals.BlockModifiersPattern + @"\s");
}
protected override bool IsMatchForOthers(string input, int minNestingDepth, int maxNestingDepth)
{
return Regex.IsMatch(input, @"^\s*([\*#]{" + (minNestingDepth - 1) + @"," + (maxNestingDepth - 1) + @"})\*" + Globals.BlockModifiersPattern + @"\s");
}
}

View File

@ -10,45 +10,44 @@
// You must not remove this notice, or any other, from this software.
#endregion
namespace Textile.States
namespace Textile.States;
/// <summary>
/// Formatting state for a standard text (i.e. just paragraphs).
/// </summary>
[FormatterState(SimpleBlockFormatterState.PatternBegin + @"p" + SimpleBlockFormatterState.PatternEnd)]
public class ParagraphFormatterState : SimpleBlockFormatterState
{
/// <summary>
/// Formatting state for a standard text (i.e. just paragraphs).
/// </summary>
[FormatterState(SimpleBlockFormatterState.PatternBegin + @"p" + SimpleBlockFormatterState.PatternEnd)]
public class ParagraphFormatterState : SimpleBlockFormatterState
public ParagraphFormatterState(TextileFormatter f)
: base(f)
{
public ParagraphFormatterState(TextileFormatter f)
: base(f)
{
}
}
public override void Enter()
{
Formatter.Output.Write("<p" + FormattedStylesAndAlignment("p") + ">");
}
public override void Enter()
{
Formatter.Output.Write("<p" + FormattedStylesAndAlignment("p") + ">");
}
public override void Exit()
{
Formatter.Output.WriteLine("</p>");
}
public override void Exit()
{
Formatter.Output.WriteLine("</p>");
}
public override void FormatLine(string input)
{
Formatter.Output.Write(input);
}
public override void FormatLine(string input)
{
Formatter.Output.Write(input);
}
public override bool ShouldExit(string input)
{
if (Regex.IsMatch(input, @"^\s*$"))
return true;
Formatter.Output.WriteLine("<br />");
return false;
}
public override bool ShouldExit(string input)
{
if (Regex.IsMatch(input, @"^\s*$"))
return true;
Formatter.Output.WriteLine("<br />");
return false;
}
public override bool ShouldNestState(FormatterState other)
{
return false;
}
public override bool ShouldNestState(FormatterState other)
{
return false;
}
}

View File

@ -1,40 +1,39 @@
namespace Textile.States
namespace Textile.States;
[FormatterState(@"^\s*<(h[0-9]|p|pre|blockquote)" + Globals.HtmlAttributesPattern + ">")]
public class PassthroughFormatterState : FormatterState
{
[FormatterState(@"^\s*<(h[0-9]|p|pre|blockquote)" + Globals.HtmlAttributesPattern + ">")]
public class PassthroughFormatterState : FormatterState
public PassthroughFormatterState(TextileFormatter f)
: base(f)
{
public PassthroughFormatterState(TextileFormatter f)
: base(f)
{
}
}
public override string Consume(string input, Match m)
{
this.Formatter.ChangeState(this);
return input;
}
public override string Consume(string input, Match m)
{
this.Formatter.ChangeState(this);
return input;
}
public override bool ShouldNestState(FormatterState other)
{
return false;
}
public override bool ShouldNestState(FormatterState other)
{
return false;
}
public override void Enter()
{
}
public override void Enter()
{
}
public override void Exit()
{
}
public override void Exit()
{
}
public override void FormatLine(string input)
{
Formatter.Output.WriteLine(input);
}
public override void FormatLine(string input)
{
Formatter.Output.WriteLine(input);
}
public override bool ShouldExit(string input)
{
return true;
}
public override bool ShouldExit(string input)
{
return true;
}
}

View File

@ -1,59 +1,58 @@
namespace Textile.States
namespace Textile.States;
[FormatterState(SimpleBlockFormatterState.PatternBegin + @"bc" + SimpleBlockFormatterState.PatternEnd)]
public class PreCodeFormatterState : SimpleBlockFormatterState
{
[FormatterState(SimpleBlockFormatterState.PatternBegin + @"bc" + SimpleBlockFormatterState.PatternEnd)]
public class PreCodeFormatterState : SimpleBlockFormatterState
public PreCodeFormatterState(TextileFormatter formatter)
: base(formatter)
{
public PreCodeFormatterState(TextileFormatter formatter)
: base(formatter)
{
}
}
public override void Enter()
{
Formatter.Output.Write("<pre><code>");
}
public override void Enter()
{
Formatter.Output.Write("<pre><code>");
}
public override void Exit()
{
Formatter.Output.WriteLine("</code></pre>");
}
public override void Exit()
{
Formatter.Output.WriteLine("</code></pre>");
}
public override void FormatLine(string input)
{
Formatter.Output.WriteLine(FixEntities(input));
}
public override void FormatLine(string input)
{
Formatter.Output.WriteLine(FixEntities(input));
}
public override bool ShouldExit(string input)
{
if (Regex.IsMatch(input, @"^\s*$"))
return true;
Formatter.Output.WriteLine("<br />");
return false;
}
public override bool ShouldExit(string input)
{
if (Regex.IsMatch(input, @"^\s*$"))
return true;
Formatter.Output.WriteLine("<br />");
return false;
}
public override bool ShouldFormatBlocks(string input)
{
return false;
}
public override bool ShouldFormatBlocks(string input)
{
return false;
}
public override bool ShouldNestState(FormatterState other)
{
return false;
}
public override bool ShouldNestState(FormatterState other)
{
return false;
}
public override bool ShouldParseForNewFormatterState(string input)
{
return false;
}
public override bool ShouldParseForNewFormatterState(string input)
{
return false;
}
private string FixEntities(string text)
{
// de-entify any remaining angle brackets or ampersands
text = text.Replace("&", "&amp;");
text = text.Replace(">", "&gt;");
text = text.Replace("<", "&lt;");
//Regex.Replace(text, @"\b&([#a-z0-9]+;)", "x%x%");
return text;
}
private string FixEntities(string text)
{
// de-entify any remaining angle brackets or ampersands
text = text.Replace("&", "&amp;");
text = text.Replace(">", "&gt;");
text = text.Replace("<", "&lt;");
//Regex.Replace(text, @"\b&([#a-z0-9]+;)", "x%x%");
return text;
}
}

View File

@ -1,75 +1,74 @@
namespace Textile.States
namespace Textile.States;
[FormatterState(@"^\s*<pre" + Globals.HtmlAttributesPattern + ">")]
public class PreFormatterState : FormatterState
{
[FormatterState(@"^\s*<pre" + Globals.HtmlAttributesPattern + ">")]
public class PreFormatterState : FormatterState
private bool _shouldExitNextTime = false;
private int _fakeNestingDepth = 0;
public PreFormatterState(TextileFormatter f)
: base(f)
{
bool m_shouldExitNextTime = false;
int m_fakeNestingDepth = 0;
public PreFormatterState(TextileFormatter f)
: base(f)
{
}
public override string Consume(string input, Match m)
{
if (!Regex.IsMatch(input, "</pre>"))
{
this.Formatter.ChangeState(this);
}
else
{
this.Formatter.ChangeState(new PassthroughFormatterState(this.Formatter));
}
return input;
}
public override bool ShouldNestState(FormatterState other)
{
return false;
}
public override void Enter()
{
}
public override void Exit()
{
}
public override void FormatLine(string input)
{
if (Regex.IsMatch(input, "<pre>"))
m_fakeNestingDepth++;
Formatter.Output.WriteLine(input);
}
public override bool ShouldExit(string input)
{
if (m_shouldExitNextTime)
return true;
if (Regex.IsMatch(input, @"</pre>"))
m_fakeNestingDepth--;
if (m_fakeNestingDepth <= 0)
m_shouldExitNextTime = true;
return false;
}
public override bool ShouldFormatBlocks(string input)
{
return false;
}
public override bool ShouldParseForNewFormatterState(string input)
{
// Only allow a child formatting state for <code> tag.
return Regex.IsMatch(input, @"^\s*<code");
}
public override Type FallbackFormattingState
{
get { return null; }
}
}
}
public override string Consume(string input, Match m)
{
if (!Regex.IsMatch(input, "</pre>"))
{
this.Formatter.ChangeState(this);
}
else
{
this.Formatter.ChangeState(new PassthroughFormatterState(this.Formatter));
}
return input;
}
public override bool ShouldNestState(FormatterState other)
{
return false;
}
public override void Enter()
{
}
public override void Exit()
{
}
public override void FormatLine(string input)
{
if (Regex.IsMatch(input, "<pre>"))
_fakeNestingDepth++;
Formatter.Output.WriteLine(input);
}
public override bool ShouldExit(string input)
{
if (_shouldExitNextTime)
return true;
if (Regex.IsMatch(input, @"</pre>"))
_fakeNestingDepth--;
if (_fakeNestingDepth <= 0)
_shouldExitNextTime = true;
return false;
}
public override bool ShouldFormatBlocks(string input)
{
return false;
}
public override bool ShouldParseForNewFormatterState(string input)
{
// Only allow a child formatting state for <code> tag.
return Regex.IsMatch(input, @"^\s*<code");
}
public override Type FallbackFormattingState
{
get { return null; }
}
}

View File

@ -1,64 +1,63 @@
namespace Textile.States
namespace Textile.States;
public abstract class SimpleBlockFormatterState : FormatterState
{
public abstract class SimpleBlockFormatterState : FormatterState
internal const string PatternBegin = @"^\s*(?<tag>";
internal const string PatternEnd = @")" + Globals.AlignPattern + Globals.BlockModifiersPattern + @"\.(?:\s+)?(?<content>.*)$";
public string Tag { get; private set; } = null;
public string AlignInfo { get; private set; } = null;
public string AttInfo { get; private set; } = null;
protected SimpleBlockFormatterState(TextileFormatter formatter)
: base(formatter)
{
internal const string PatternBegin = @"^\s*(?<tag>";
internal const string PatternEnd = @")" + Globals.AlignPattern + Globals.BlockModifiersPattern + @"\.(?:\s+)?(?<content>.*)$";
public string Tag { get; private set; } = null;
public string AlignInfo { get; private set; } = null;
public string AttInfo { get; private set; } = null;
protected SimpleBlockFormatterState(TextileFormatter formatter)
: base(formatter)
{
}
public override string Consume(string input, Match m)
{
Tag = m.Groups["tag"].Value;
AlignInfo = m.Groups["align"].Value;
AttInfo = m.Groups["atts"].Value;
input = m.Groups["content"].Value;
OnContextAcquired();
this.Formatter.ChangeState(this);
return input;
}
public override bool ShouldNestState(FormatterState other)
{
var blockFormatterState = (SimpleBlockFormatterState)other;
return blockFormatterState.Tag != Tag ||
blockFormatterState.AlignInfo != AlignInfo ||
blockFormatterState.AttInfo != AttInfo;
}
protected virtual void OnContextAcquired()
{
}
/// <summary>
///
/// </summary>
/// <returns></returns>
protected string FormattedAlignment()
{
return Blocks.BlockAttributesParser.ParseBlockAttributes(AlignInfo);
}
protected string FormattedStyles(string element)
{
return Blocks.BlockAttributesParser.ParseBlockAttributes(AttInfo, element);
}
protected string FormattedStylesAndAlignment(string element)
{
return Blocks.BlockAttributesParser.ParseBlockAttributes(AlignInfo + AttInfo, element);
}
}
}
public override string Consume(string input, Match m)
{
Tag = m.Groups["tag"].Value;
AlignInfo = m.Groups["align"].Value;
AttInfo = m.Groups["atts"].Value;
input = m.Groups["content"].Value;
OnContextAcquired();
this.Formatter.ChangeState(this);
return input;
}
public override bool ShouldNestState(FormatterState other)
{
var blockFormatterState = (SimpleBlockFormatterState)other;
return blockFormatterState.Tag != Tag ||
blockFormatterState.AlignInfo != AlignInfo ||
blockFormatterState.AttInfo != AttInfo;
}
protected virtual void OnContextAcquired()
{
}
/// <summary>
///
/// </summary>
/// <returns></returns>
protected string FormattedAlignment()
{
return Blocks.BlockAttributesParser.ParseBlockAttributes(AlignInfo);
}
protected string FormattedStyles(string element)
{
return Blocks.BlockAttributesParser.ParseBlockAttributes(AttInfo, element);
}
protected string FormattedStylesAndAlignment(string element)
{
return Blocks.BlockAttributesParser.ParseBlockAttributes(AlignInfo + AttInfo, element);
}
}

View File

@ -1,45 +1,44 @@
namespace Textile.States
namespace Textile.States;
public class TableCellParser
{
public class TableCellParser
readonly string _lineFragment;
public TableCellParser(string input)
{
readonly string m_lineFragment;
public TableCellParser(string input)
{
m_lineFragment = input;
}
public string GetLineFragmentFormatting()
{
var htmlTag = "td";
var m = Regex.Match(m_lineFragment,
@"^((?<head>_?)" +
Globals.SpanPattern +
Globals.AlignPattern +
Globals.BlockModifiersPattern +
@"(?<dot>\.)\s?)?" +
@"(?<content>.*)"
);
if (!m.Success)
throw new Exception("Couldn't parse table cell.");
if (m.Groups["head"].Value == "_")
htmlTag = "th";
//string opts = BlockAttributesParser.ParseBlockAttributes(m.Groups["span"].Value, "td") +
// BlockAttributesParser.ParseBlockAttributes(m.Groups["align"].Value, "td") +
// BlockAttributesParser.ParseBlockAttributes(m.Groups["atts"].Value, "td");
var opts = Blocks.BlockAttributesParser.ParseBlockAttributes(m.Groups["span"].Value + m.Groups["align"].Value + m.Groups["atts"].Value, "td");
var res = "<" + htmlTag + opts + ">";
// It may be possible the user actually intended to have a dot at the beginning of
// this cell's text, without any formatting (header tag or options).
if (string.IsNullOrEmpty(opts) && htmlTag == "td" && !string.IsNullOrEmpty(m.Groups["dot"].Value))
res += ".";
res += m.Groups["content"].Value;
res += "</" + htmlTag + ">";
return res;
}
_lineFragment = input;
}
}
public string GetLineFragmentFormatting()
{
var htmlTag = "td";
var m = Regex.Match(_lineFragment,
@"^((?<head>_?)" +
Globals.SpanPattern +
Globals.AlignPattern +
Globals.BlockModifiersPattern +
@"(?<dot>\.)\s?)?" +
@"(?<content>.*)"
);
if (!m.Success)
throw new Exception("Couldn't parse table cell.");
if (m.Groups["head"].Value == "_")
htmlTag = "th";
//string opts = BlockAttributesParser.ParseBlockAttributes(m.Groups["span"].Value, "td") +
// BlockAttributesParser.ParseBlockAttributes(m.Groups["align"].Value, "td") +
// BlockAttributesParser.ParseBlockAttributes(m.Groups["atts"].Value, "td");
var opts = Blocks.BlockAttributesParser.ParseBlockAttributes(m.Groups["span"].Value + m.Groups["align"].Value + m.Groups["atts"].Value, "td");
var res = "<" + htmlTag + opts + ">";
// It may be possible the user actually intended to have a dot at the beginning of
// this cell's text, without any formatting (header tag or options).
if (string.IsNullOrEmpty(opts) && htmlTag == "td" && !string.IsNullOrEmpty(m.Groups["dot"].Value))
res += ".";
res += m.Groups["content"].Value;
res += "</" + htmlTag + ">";
return res;
}
}

View File

@ -1,65 +1,64 @@
namespace Textile.States
namespace Textile.States;
[FormatterState(@"^\s*(?<tag>table)" +
Globals.SpanPattern +
Globals.AlignPattern +
Globals.BlockModifiersPattern +
@"\.\s*$")]
public class TableFormatterState : FormatterState
{
[FormatterState(@"^\s*(?<tag>table)" +
Globals.SpanPattern +
Globals.AlignPattern +
Globals.BlockModifiersPattern +
@"\.\s*$")]
public class TableFormatterState : FormatterState
private string _attsInfo;
private string _alignInfo;
public TableFormatterState(TextileFormatter f)
: base(f)
{
private string m_attsInfo;
private string m_alignInfo;
}
public TableFormatterState(TextileFormatter f)
: base(f)
{
}
public override string Consume(string input, Match m)
{
_alignInfo = m.Groups["align"].Value;
_attsInfo = m.Groups["atts"].Value;
public override string Consume(string input, Match m)
{
m_alignInfo = m.Groups["align"].Value;
m_attsInfo = m.Groups["atts"].Value;
//TODO: check the state (it could already be a table!)
this.Formatter.ChangeState(this);
//TODO: check the state (it could already be a table!)
this.Formatter.ChangeState(this);
return string.Empty;
}
return string.Empty;
}
public override bool ShouldNestState(FormatterState other)
{
return false;
}
public override bool ShouldNestState(FormatterState other)
{
return false;
}
public override void Enter()
{
Formatter.Output.WriteLine("<table" + FormattedStylesAndAlignment() + ">");
}
public override void Enter()
{
Formatter.Output.WriteLine("<table" + FormattedStylesAndAlignment() + ">");
}
public override void Exit()
{
Formatter.Output.WriteLine("</table>");
}
public override void Exit()
{
Formatter.Output.WriteLine("</table>");
}
public override void FormatLine(string input)
{
if (input.Length > 0)
throw new Exception("The TableFormatter state is not supposed to format any lines!");
}
public override void FormatLine(string input)
{
if (input.Length > 0)
throw new Exception("The TableFormatter state is not supposed to format any lines!");
}
public override bool ShouldExit(string input)
{
var m = Regex.Match(input,
@"^\s*" + Globals.AlignPattern + Globals.BlockModifiersPattern +
@"(\.\s?)?(?<tag>\|)" +
@"(?<content>.*)(?=\|)"
);
return !m.Success;
}
public override bool ShouldExit(string input)
{
var m = Regex.Match(input,
@"^\s*" + Globals.AlignPattern + Globals.BlockModifiersPattern +
@"(\.\s?)?(?<tag>\|)" +
@"(?<content>.*)(?=\|)"
);
return !m.Success;
}
protected string FormattedStylesAndAlignment()
{
return Blocks.BlockAttributesParser.ParseBlockAttributes(m_alignInfo + m_attsInfo);
}
protected string FormattedStylesAndAlignment()
{
return Blocks.BlockAttributesParser.ParseBlockAttributes(_alignInfo + _attsInfo);
}
}

View File

@ -1,73 +1,72 @@
namespace Textile.States
namespace Textile.States;
[FormatterState(@"^\s*(" + Globals.AlignPattern + Globals.BlockModifiersPattern + @"\.\s?)?" +
@"\|(?<content>.*)\|\s*$")]
public class TableRowFormatterState : FormatterState
{
[FormatterState(@"^\s*(" + Globals.AlignPattern + Globals.BlockModifiersPattern + @"\.\s?)?" +
@"\|(?<content>.*)\|\s*$")]
public class TableRowFormatterState : FormatterState
private string _attsInfo;
private string _alignInfo;
public TableRowFormatterState(TextileFormatter f)
: base(f)
{
private string m_attsInfo;
private string m_alignInfo;
public TableRowFormatterState(TextileFormatter f)
: base(f)
{
}
public override string Consume(string input, Match m)
{
m_alignInfo = m.Groups["align"].Value;
m_attsInfo = m.Groups["atts"].Value;
input = "|" + m.Groups["content"].Value + "|";
if (!(this.Formatter.CurrentState is TableFormatterState))
{
var s = new TableFormatterState(this.Formatter);
this.Formatter.ChangeState(s);
}
this.Formatter.ChangeState(this);
return input;
}
public override bool ShouldNestState(FormatterState other)
{
return false;
}
public override void Enter()
{
Formatter.Output.WriteLine("<tr" + FormattedStylesAndAlignment() + ">");
}
public override void Exit()
{
Formatter.Output.WriteLine("</tr>");
}
public override void FormatLine(string input)
{
// can get: Align & Classes
var sb = new StringBuilder();
var cellsInput = input.Split('|');
for (var i = 1; i < cellsInput.Length - 1; i++)
{
var cellInput = cellsInput[i];
var tcp = new TableCellParser(cellInput);
sb.Append(tcp.GetLineFragmentFormatting());
}
Formatter.Output.WriteLine(sb.ToString());
}
public override bool ShouldExit(string input)
{
return true;
}
protected string FormattedStylesAndAlignment()
{
return Blocks.BlockAttributesParser.ParseBlockAttributes(m_alignInfo + m_attsInfo);
}
}
}
public override string Consume(string input, Match m)
{
_alignInfo = m.Groups["align"].Value;
_attsInfo = m.Groups["atts"].Value;
input = "|" + m.Groups["content"].Value + "|";
if (!(this.Formatter.CurrentState is TableFormatterState))
{
var s = new TableFormatterState(this.Formatter);
this.Formatter.ChangeState(s);
}
this.Formatter.ChangeState(this);
return input;
}
public override bool ShouldNestState(FormatterState other)
{
return false;
}
public override void Enter()
{
Formatter.Output.WriteLine("<tr" + FormattedStylesAndAlignment() + ">");
}
public override void Exit()
{
Formatter.Output.WriteLine("</tr>");
}
public override void FormatLine(string input)
{
// can get: Align & Classes
var sb = new StringBuilder();
var cellsInput = input.Split('|');
for (var i = 1; i < cellsInput.Length - 1; i++)
{
var cellInput = cellsInput[i];
var tcp = new TableCellParser(cellInput);
sb.Append(tcp.GetLineFragmentFormatting());
}
Formatter.Output.WriteLine(sb.ToString());
}
public override bool ShouldExit(string input)
{
return true;
}
protected string FormattedStylesAndAlignment()
{
return Blocks.BlockAttributesParser.ParseBlockAttributes(_alignInfo + _attsInfo);
}
}

View File

@ -10,37 +10,36 @@
// You must not remove this notice, or any other, from this software.
#endregion
namespace Textile.States
namespace Textile.States;
/// <summary>
/// Formatting state for a bulleted list.
/// </summary>
[FormatterState(ListFormatterState.PatternBegin + @"\*+" + ListFormatterState.PatternEnd)]
public class UnorderedListFormatterState : ListFormatterState
{
/// <summary>
/// Formatting state for a bulleted list.
/// </summary>
[FormatterState(ListFormatterState.PatternBegin + @"\*+" + ListFormatterState.PatternEnd)]
public class UnorderedListFormatterState : ListFormatterState
public UnorderedListFormatterState(TextileFormatter formatter)
: base(formatter)
{
public UnorderedListFormatterState(TextileFormatter formatter)
: base(formatter)
{
}
protected override void WriteIndent()
{
Formatter.Output.WriteLine("<ul" + FormattedStylesAndAlignment("ul") + ">");
}
protected override void WriteOutdent()
{
Formatter.Output.WriteLine("</ul>");
}
protected override bool IsMatchForMe(string input, int minNestingDepth, int maxNestingDepth)
{
return Regex.IsMatch(input, @"^\s*[\*]{" + minNestingDepth + @"," + maxNestingDepth + @"}" + Globals.BlockModifiersPattern + @"\s");
}
protected override bool IsMatchForOthers(string input, int minNestingDepth, int maxNestingDepth)
{
return Regex.IsMatch(input, @"^\s*[#]{" + minNestingDepth + @"," + maxNestingDepth + @"}" + Globals.BlockModifiersPattern + @"\s");
}
}
}
protected override void WriteIndent()
{
Formatter.Output.WriteLine("<ul" + FormattedStylesAndAlignment("ul") + ">");
}
protected override void WriteOutdent()
{
Formatter.Output.WriteLine("</ul>");
}
protected override bool IsMatchForMe(string input, int minNestingDepth, int maxNestingDepth)
{
return Regex.IsMatch(input, @"^\s*[\*]{" + minNestingDepth + @"," + maxNestingDepth + @"}" + Globals.BlockModifiersPattern + @"\s");
}
protected override bool IsMatchForOthers(string input, int minNestingDepth, int maxNestingDepth)
{
return Regex.IsMatch(input, @"^\s*[#]{" + minNestingDepth + @"," + maxNestingDepth + @"}" + Globals.BlockModifiersPattern + @"\s");
}
}

View File

@ -11,42 +11,41 @@
#endregion
namespace Textile
namespace Textile;
public class StringBuilderTextileFormatter : IOutputter
{
public class StringBuilderTextileFormatter : IOutputter
private StringBuilder _stringBuilder = null;
public StringBuilderTextileFormatter()
{
StringBuilder m_stringBuilder = null;
public StringBuilderTextileFormatter()
{
}
public string GetFormattedText()
{
return m_stringBuilder.ToString();
}
#region IOutputter Members
public void Begin()
{
m_stringBuilder = new StringBuilder();
}
public void End()
{
}
public void Write(string text)
{
m_stringBuilder.Append(text);
}
public void WriteLine(string line)
{
m_stringBuilder.AppendLine(line);
}
#endregion
}
public string GetFormattedText()
{
return _stringBuilder.ToString();
}
#region IOutputter Members
public void Begin()
{
_stringBuilder = new StringBuilder();
}
public void End()
{
}
public void Write(string text)
{
_stringBuilder.Append(text);
}
public void WriteLine(string line)
{
_stringBuilder.AppendLine(line);
}
#endregion
}

View File

@ -1,28 +1,26 @@
namespace Textile
namespace Textile;
public class StyleReader
{
public class StyleReader
private readonly Regex _styleParser = new Regex(@"(?<selector>[^\{]+)(?<style>[^\}]+)");
private readonly Regex _minimizer = new Regex(@";\s+");
private readonly System.Collections.Specialized.StringDictionary _tagStyler = new System.Collections.Specialized.StringDictionary();
public StyleReader(string styles)
{
private readonly Regex _styleParser = new Regex(@"(?<selector>[^\{]+)(?<style>[^\}]+)");
private readonly Regex _minimizer = new Regex(@";\s+");
private readonly System.Collections.Specialized.StringDictionary _tagStyler = new System.Collections.Specialized.StringDictionary();
public StyleReader(string styles)
//Read it
var matches = _styleParser.Matches(styles.Replace(System.Environment.NewLine, ""));
foreach (Match match in matches)
{
//Read it
var matches = _styleParser.Matches(styles.Replace(System.Environment.NewLine, ""));
foreach (Match match in matches)
if (match.Success)
{
if (match.Success)
{
_tagStyler.Add(match.Groups["selector"].Value.Trim('{', '}', ' '), _minimizer.Replace(match.Groups["style"].Value.Trim('{', '}', ' '), ";"));
}
_tagStyler.Add(match.Groups["selector"].Value.Trim('{', '}', ' '), _minimizer.Replace(match.Groups["style"].Value.Trim('{', '}', ' '), ";"));
}
}
}
public string GetStyle(string tag)
{
return _tagStyler[tag];
}
public string GetStyle(string tag)
{
return _tagStyler[tag];
}
}

View File

@ -12,160 +12,159 @@
namespace Textile
namespace Textile;
/// <summary>
/// Class for formatting Textile input into HTML.
/// </summary>
/// This class takes raw Textile text and sends the
/// formatted, ready to display HTML string to the
/// outputter defined in the constructor of the
/// class.
public partial class TextileFormatter
{
/// <summary>
/// Class for formatting Textile input into HTML.
/// </summary>
/// This class takes raw Textile text and sends the
/// formatted, ready to display HTML string to the
/// outputter defined in the constructor of the
/// class.
public partial class TextileFormatter
static TextileFormatter()
{
static TextileFormatter()
{
RegisterFormatterState(typeof(HeaderFormatterState));
RegisterFormatterState(typeof(PaddingFormatterState));
RegisterFormatterState(typeof(BlockQuoteFormatterState));
RegisterFormatterState(typeof(ParagraphFormatterState));
RegisterFormatterState(typeof(FootNoteFormatterState));
RegisterFormatterState(typeof(OrderedListFormatterState));
RegisterFormatterState(typeof(UnorderedListFormatterState));
RegisterFormatterState(typeof(TableFormatterState));
RegisterFormatterState(typeof(TableRowFormatterState));
RegisterFormatterState(typeof(CodeFormatterState));
RegisterFormatterState(typeof(PreFormatterState));
RegisterFormatterState(typeof(PreCodeFormatterState));
RegisterFormatterState(typeof(NoTextileFormatterState));
RegisterFormatterState(typeof(HeaderFormatterState));
RegisterFormatterState(typeof(PaddingFormatterState));
RegisterFormatterState(typeof(BlockQuoteFormatterState));
RegisterFormatterState(typeof(ParagraphFormatterState));
RegisterFormatterState(typeof(FootNoteFormatterState));
RegisterFormatterState(typeof(OrderedListFormatterState));
RegisterFormatterState(typeof(UnorderedListFormatterState));
RegisterFormatterState(typeof(TableFormatterState));
RegisterFormatterState(typeof(TableRowFormatterState));
RegisterFormatterState(typeof(CodeFormatterState));
RegisterFormatterState(typeof(PreFormatterState));
RegisterFormatterState(typeof(PreCodeFormatterState));
RegisterFormatterState(typeof(NoTextileFormatterState));
RegisterBlockModifier(new NoTextileBlockModifier());
RegisterBlockModifier(new CodeBlockModifier());
RegisterBlockModifier(new PreBlockModifier());
RegisterBlockModifier(new HyperLinkBlockModifier());
RegisterBlockModifier(new ImageBlockModifier());
RegisterBlockModifier(new GlyphBlockModifier());
RegisterBlockModifier(new EmphasisPhraseBlockModifier());
RegisterBlockModifier(new StrongPhraseBlockModifier());
RegisterBlockModifier(new ItalicPhraseBlockModifier());
RegisterBlockModifier(new BoldPhraseBlockModifier());
RegisterBlockModifier(new CitePhraseBlockModifier());
RegisterBlockModifier(new DeletedPhraseBlockModifier());
RegisterBlockModifier(new InsertedPhraseBlockModifier());
RegisterBlockModifier(new SuperScriptPhraseBlockModifier());
RegisterBlockModifier(new SubScriptPhraseBlockModifier());
RegisterBlockModifier(new SpanPhraseBlockModifier());
RegisterBlockModifier(new FootNoteReferenceBlockModifier());
RegisterBlockModifier(new NoTextileBlockModifier());
RegisterBlockModifier(new CodeBlockModifier());
RegisterBlockModifier(new PreBlockModifier());
RegisterBlockModifier(new HyperLinkBlockModifier());
RegisterBlockModifier(new ImageBlockModifier());
RegisterBlockModifier(new GlyphBlockModifier());
RegisterBlockModifier(new EmphasisPhraseBlockModifier());
RegisterBlockModifier(new StrongPhraseBlockModifier());
RegisterBlockModifier(new ItalicPhraseBlockModifier());
RegisterBlockModifier(new BoldPhraseBlockModifier());
RegisterBlockModifier(new CitePhraseBlockModifier());
RegisterBlockModifier(new DeletedPhraseBlockModifier());
RegisterBlockModifier(new InsertedPhraseBlockModifier());
RegisterBlockModifier(new SuperScriptPhraseBlockModifier());
RegisterBlockModifier(new SubScriptPhraseBlockModifier());
RegisterBlockModifier(new SpanPhraseBlockModifier());
RegisterBlockModifier(new FootNoteReferenceBlockModifier());
//TODO: capitals block modifier
}
/// <summary>
/// Public constructor, where the formatter is hooked up
/// to an outputter.
/// </summary>
/// <param name="output">The outputter to be used.</param>
public TextileFormatter(IOutputter output)
{
Output = output;
}
#region Properties for Output
/// <summary>
/// The ouputter to which the formatted text
/// is sent to.
/// </summary>
public IOutputter Output { get; } = null;
/// <summary>
/// The offset for the header tags.
/// </summary>
/// When the formatted text is inserted into another page
/// there might be a need to offset all headers (h1 becomes
/// h4, for instance). The header offset allows this.
public int HeaderOffset { get; set; } = 0;
#endregion
#region Properties for Conversion
public bool FormatImages
{
get { return IsBlockModifierEnabled(typeof(ImageBlockModifier)); }
set { SwitchBlockModifier(typeof(ImageBlockModifier), value); }
}
public bool FormatLinks
{
get { return IsBlockModifierEnabled(typeof(HyperLinkBlockModifier)); }
set { SwitchBlockModifier(typeof(HyperLinkBlockModifier), value); }
}
public bool FormatLists
{
get { return IsBlockModifierEnabled(typeof(OrderedListFormatterState)); }
set
{
SwitchBlockModifier(typeof(OrderedListFormatterState), value);
SwitchBlockModifier(typeof(UnorderedListFormatterState), value);
}
}
public bool FormatFootNotes
{
get { return IsBlockModifierEnabled(typeof(FootNoteReferenceBlockModifier)); }
set
{
SwitchBlockModifier(typeof(FootNoteReferenceBlockModifier), value);
SwitchFormatterState(typeof(FootNoteFormatterState), value);
}
}
public bool FormatTables
{
get { return IsFormatterStateEnabled(typeof(TableFormatterState)); }
set
{
SwitchFormatterState(typeof(TableFormatterState), value);
SwitchFormatterState(typeof(TableRowFormatterState), value);
}
}
/// <summary>
/// Attribute to add to all links.
/// </summary>
public string Rel { get; set; } = string.Empty;
#endregion
#region Utility Methods
/// <summary>
/// Utility method for quickly formatting a text without having
/// to create a TextileFormatter with an IOutputter.
/// </summary>
/// <param name="input">The string to format</param>
/// <returns>The formatted version of the string</returns>
public static string FormatString(string input)
{
var s = new StringBuilderTextileFormatter();
var f = new TextileFormatter(s);
f.Format(input);
return s.GetFormattedText();
}
/// <summary>
/// Utility method for formatting a text with a given outputter.
/// </summary>
/// <param name="input">The string to format</param>
/// <param name="outputter">The IOutputter to use</param>
public static void FormatString(string input, IOutputter outputter)
{
var f = new TextileFormatter(outputter);
f.Format(input);
}
#endregion
//TODO: capitals block modifier
}
/// <summary>
/// Public constructor, where the formatter is hooked up
/// to an outputter.
/// </summary>
/// <param name="output">The outputter to be used.</param>
public TextileFormatter(IOutputter output)
{
Output = output;
}
#region Properties for Output
/// <summary>
/// The ouputter to which the formatted text
/// is sent to.
/// </summary>
public IOutputter Output { get; } = null;
/// <summary>
/// The offset for the header tags.
/// </summary>
/// When the formatted text is inserted into another page
/// there might be a need to offset all headers (h1 becomes
/// h4, for instance). The header offset allows this.
public int HeaderOffset { get; set; } = 0;
#endregion
#region Properties for Conversion
public bool FormatImages
{
get { return IsBlockModifierEnabled(typeof(ImageBlockModifier)); }
set { SwitchBlockModifier(typeof(ImageBlockModifier), value); }
}
public bool FormatLinks
{
get { return IsBlockModifierEnabled(typeof(HyperLinkBlockModifier)); }
set { SwitchBlockModifier(typeof(HyperLinkBlockModifier), value); }
}
public bool FormatLists
{
get { return IsBlockModifierEnabled(typeof(OrderedListFormatterState)); }
set
{
SwitchBlockModifier(typeof(OrderedListFormatterState), value);
SwitchBlockModifier(typeof(UnorderedListFormatterState), value);
}
}
public bool FormatFootNotes
{
get { return IsBlockModifierEnabled(typeof(FootNoteReferenceBlockModifier)); }
set
{
SwitchBlockModifier(typeof(FootNoteReferenceBlockModifier), value);
SwitchFormatterState(typeof(FootNoteFormatterState), value);
}
}
public bool FormatTables
{
get { return IsFormatterStateEnabled(typeof(TableFormatterState)); }
set
{
SwitchFormatterState(typeof(TableFormatterState), value);
SwitchFormatterState(typeof(TableRowFormatterState), value);
}
}
/// <summary>
/// Attribute to add to all links.
/// </summary>
public string Rel { get; set; } = string.Empty;
#endregion
#region Utility Methods
/// <summary>
/// Utility method for quickly formatting a text without having
/// to create a TextileFormatter with an IOutputter.
/// </summary>
/// <param name="input">The string to format</param>
/// <returns>The formatted version of the string</returns>
public static string FormatString(string input)
{
var s = new StringBuilderTextileFormatter();
var f = new TextileFormatter(s);
f.Format(input);
return s.GetFormattedText();
}
/// <summary>
/// Utility method for formatting a text with a given outputter.
/// </summary>
/// <param name="input">The string to format</param>
/// <param name="outputter">The IOutputter to use</param>
public static void FormatString(string input, IOutputter outputter)
{
var f = new TextileFormatter(outputter);
f.Format(input);
}
#endregion
}

View File

@ -11,40 +11,39 @@
#endregion
namespace Textile
namespace Textile;
public partial class TextileFormatter
{
public partial class TextileFormatter
#region Block Modifiers Registration
private static readonly List<BlockModifier> _blockModifiers = new List<BlockModifier>();
private static readonly List<Type> _blockModifiersTypes = new List<Type>();
public static void RegisterBlockModifier(BlockModifier blockModifer)
{
#region Block Modifiers Registration
private static readonly List<BlockModifier> s_blockModifiers = new List<BlockModifier>();
private static readonly List<Type> s_blockModifiersTypes = new List<Type>();
public static void RegisterBlockModifier(BlockModifier blockModifer)
{
s_blockModifiers.Add(blockModifer);
s_blockModifiersTypes.Add(blockModifer.GetType());
}
#endregion
#region Block Modifiers Management
private readonly List<Type> m_disabledBlockModifiers = new List<Type>();
public bool IsBlockModifierEnabled(Type type)
{
return !m_disabledBlockModifiers.Contains(type);
}
public void SwitchBlockModifier(Type type, bool onOff)
{
if (onOff)
m_disabledBlockModifiers.Remove(type);
else if (!m_disabledBlockModifiers.Contains(type))
m_disabledBlockModifiers.Add(type);
}
#endregion
_blockModifiers.Add(blockModifer);
_blockModifiersTypes.Add(blockModifer.GetType());
}
#endregion
#region Block Modifiers Management
private readonly List<Type> _disabledBlockModifiers = new List<Type>();
public bool IsBlockModifierEnabled(Type type)
{
return !_disabledBlockModifiers.Contains(type);
}
public void SwitchBlockModifier(Type type, bool onOff)
{
if (onOff)
_disabledBlockModifiers.Remove(type);
else if (!_disabledBlockModifiers.Contains(type))
_disabledBlockModifiers.Add(type);
}
#endregion
}

View File

@ -11,108 +11,107 @@
#endregion
namespace Textile
namespace Textile;
public partial class TextileFormatter
{
public partial class TextileFormatter
private readonly Regex _velocityArguments =
new Regex("nostyle(?<arg>.*?)/nostyle", RegexOptions.IgnoreCase | RegexOptions.Singleline);
private string ArgMatchReplace(Match match)
{
private readonly Regex VelocityArguments =
new Regex("nostyle(?<arg>.*?)/nostyle", RegexOptions.IgnoreCase | RegexOptions.Singleline);
return match.Result("${arg}");
}
private string ArgMatchReplace(Match match)
#region Formatting Methods
/// <summary>
/// Formats the given text.
/// </summary>
/// <param name="input">The text to format.</param>
public void Format(string input)
{
Output.Begin();
// Clean the text...
var str = PrepareInputForFormatting(input);
// ...and format each line.
foreach (var line in str.Split('\n'))
{
return match.Result("${arg}");
}
var tmp = line;
#region Formatting Methods
/// <summary>
/// Formats the given text.
/// </summary>
/// <param name="input">The text to format.</param>
public void Format(string input)
{
Output.Begin();
// Clean the text...
var str = PrepareInputForFormatting(input);
// ...and format each line.
foreach (var line in str.Split('\n'))
{
var tmp = line;
// Let's see if the current state(s) is(are) finished...
while (CurrentState != null && CurrentState.ShouldExit(tmp))
PopState();
if (!Regex.IsMatch(tmp, @"^\s*$"))
{
// Figure out the new state for this text line, if possible.
if (CurrentState == null || CurrentState.ShouldParseForNewFormatterState(tmp))
{
tmp = HandleFormattingState(tmp);
}
// else, the current state doesn't want to be superceded by
// a new one. We'll leave him be.
// Modify the line with our block modifiers.
if (CurrentState == null || CurrentState.ShouldFormatBlocks(tmp))
{
foreach (var blockModifier in s_blockModifiers)
{
//TODO: if not disabled...
tmp = blockModifier.ModifyLine(tmp);
}
for (var i = s_blockModifiers.Count - 1; i >= 0; i--)
{
var blockModifier = s_blockModifiers[i];
tmp = blockModifier.Conclude(tmp);
}
}
tmp = VelocityArguments.Replace(tmp, ArgMatchReplace);
// Format the current line.
CurrentState.FormatLine(tmp);
}
}
// We're done. There might be a few states still on
// the stack (for example if the text ends with a nested
// list), so we must pop them all so that they have
// their "Exit" method called correctly.
while (m_stackOfStates.Count > 0)
// Let's see if the current state(s) is(are) finished...
while (CurrentState != null && CurrentState.ShouldExit(tmp))
PopState();
Output.End();
if (!Regex.IsMatch(tmp, @"^\s*$"))
{
// Figure out the new state for this text line, if possible.
if (CurrentState == null || CurrentState.ShouldParseForNewFormatterState(tmp))
{
tmp = HandleFormattingState(tmp);
}
// else, the current state doesn't want to be superceded by
// a new one. We'll leave him be.
// Modify the line with our block modifiers.
if (CurrentState == null || CurrentState.ShouldFormatBlocks(tmp))
{
foreach (var blockModifier in _blockModifiers)
{
//TODO: if not disabled...
tmp = blockModifier.ModifyLine(tmp);
}
for (var i = _blockModifiers.Count - 1; i >= 0; i--)
{
var blockModifier = _blockModifiers[i];
tmp = blockModifier.Conclude(tmp);
}
}
tmp = _velocityArguments.Replace(tmp, ArgMatchReplace);
// Format the current line.
CurrentState.FormatLine(tmp);
}
}
// We're done. There might be a few states still on
// the stack (for example if the text ends with a nested
// list), so we must pop them all so that they have
// their "Exit" method called correctly.
while (_stackOfStates.Count > 0)
PopState();
#endregion
#region Preparation Methods
/// <summary>
/// Cleans up a text before formatting.
/// </summary>
/// <param name="input">The text to clean up.</param>
/// <returns>The clean text.</returns>
/// This method cleans stuff like line endings, so that
/// we don't have to bother with it while formatting.
private string PrepareInputForFormatting(string input)
{
input = CleanWhiteSpace(input);
return input;
}
private string CleanWhiteSpace(string text)
{
text = text.Replace("\r\n", "\n");
text = text.Replace("\t", "");
text = Regex.Replace(text, @"\n{3,}", "\n\n");
text = Regex.Replace(text, @"\n *\n", "\n\n");
text = Regex.Replace(text, "\"$", "\" ");
return text;
}
#endregion
Output.End();
}
}
#endregion
#region Preparation Methods
/// <summary>
/// Cleans up a text before formatting.
/// </summary>
/// <param name="input">The text to clean up.</param>
/// <returns>The clean text.</returns>
/// This method cleans stuff like line endings, so that
/// we don't have to bother with it while formatting.
private string PrepareInputForFormatting(string input)
{
input = CleanWhiteSpace(input);
return input;
}
private string CleanWhiteSpace(string text)
{
text = text.Replace("\r\n", "\n");
text = text.Replace("\t", "");
text = Regex.Replace(text, @"\n{3,}", "\n\n");
text = Regex.Replace(text, @"\n *\n", "\n\n");
text = Regex.Replace(text, "\"$", "\" ");
return text;
}
#endregion
}

View File

@ -10,144 +10,142 @@
// You must not remove this notice, or any other, from this software.
#endregion
namespace Textile
namespace Textile;
public partial class TextileFormatter
{
public partial class TextileFormatter
#region State Registration
private static readonly List<Type> _registeredStates = new List<Type>();
private static readonly List<FormatterStateAttribute> _registeredStatesAttributes = new List<FormatterStateAttribute>();
public static void RegisterFormatterState(Type formatterStateType)
{
#region State Registration
if (!formatterStateType.IsSubclassOf(typeof(FormatterState)))
throw new ArgumentException("The formatter state must be a sub-public class of FormatterStateBase.");
private static readonly List<Type> s_registeredStates = new List<Type>();
private static readonly List<FormatterStateAttribute> s_registeredStatesAttributes = new List<FormatterStateAttribute>();
if (formatterStateType.GetConstructor(new Type[] { typeof(TextileFormatter) }) == null)
throw new ArgumentException("The formatter state must have a constructor that takes a TextileFormatter reference.");
public static void RegisterFormatterState(Type formatterStateType)
{
if (!formatterStateType.IsSubclassOf(typeof(FormatterState)))
throw new ArgumentException("The formatter state must be a sub-public class of FormatterStateBase.");
var att = FormatterStateAttribute.Get(formatterStateType);
if (att == null)
throw new ArgumentException("The formatter state must have the FormatterStateAttribute.");
if (formatterStateType.GetConstructor(new Type[] { typeof(TextileFormatter) }) == null)
throw new ArgumentException("The formatter state must have a constructor that takes a TextileFormatter reference.");
var att = FormatterStateAttribute.Get(formatterStateType);
if (att == null)
throw new ArgumentException("The formatter state must have the FormatterStateAttribute.");
s_registeredStates.Add(formatterStateType);
s_registeredStatesAttributes.Add(att);
}
#endregion
#region State Management
private readonly List<Type> m_disabledFormatterStates = new List<Type>();
private readonly Stack<FormatterState> m_stackOfStates = new Stack<FormatterState>();
private bool IsFormatterStateEnabled(Type type)
{
return !m_disabledFormatterStates.Contains(type);
}
private void SwitchFormatterState(Type type, bool onOff)
{
if (onOff)
m_disabledFormatterStates.Remove(type);
else if (!m_disabledFormatterStates.Contains(type))
m_disabledFormatterStates.Add(type);
}
/// <summary>
/// Pushes a new state on the stack.
/// </summary>
/// <param name="s">The state to push.</param>
/// The state will be entered automatically.
private void PushState(FormatterState s)
{
m_stackOfStates.Push(s);
s.Enter();
}
/// <summary>
/// Removes the last state from the stack.
/// </summary>
/// The state will be exited automatically.
private void PopState()
{
m_stackOfStates.Peek().Exit();
m_stackOfStates.Pop();
}
/// <summary>
/// The current state, if any.
/// </summary>
internal FormatterState CurrentState
{
get
{
if (m_stackOfStates.Count > 0)
return m_stackOfStates.Peek();
else
return null;
}
}
internal void ChangeState(FormatterState formatterState)
{
if (CurrentState != null && CurrentState.GetType() == formatterState.GetType())
{
if (!CurrentState.ShouldNestState(formatterState))
return;
}
PushState(formatterState);
}
#endregion
#region State Handling
/// <summary>
/// Parses the string and updates the state accordingly.
/// </summary>
/// <param name="input">The text to process.</param>
/// <returns>The text, ready for formatting.</returns>
/// This method modifies the text because it removes some
/// syntax stuff. Maybe the states themselves should handle
/// their own syntax and remove it?
private string HandleFormattingState(string input)
{
for (var i = 0; i < s_registeredStates.Count; i++)
{
var type = s_registeredStates[i];
if (IsFormatterStateEnabled(type))
{
var att = s_registeredStatesAttributes[i];
var m = Regex.Match(input, att.Pattern);
if (m.Success)
{
var formatterState = (FormatterState)Activator.CreateInstance(type, this);
return formatterState.Consume(input, m);
}
}
}
// Default, when no block is specified, we ask the current state, or
// use the paragraph state.
if (CurrentState != null)
{
if (CurrentState.FallbackFormattingState != null)
{
var formatterState = (FormatterState)Activator.CreateInstance(CurrentState.FallbackFormattingState, this);
ChangeState(formatterState);
}
// else, the current state doesn't want to be superceded by
// a new one. We'll leave him be.
}
else
{
ChangeState(new States.ParagraphFormatterState(this));
}
return input;
}
#endregion
_registeredStates.Add(formatterStateType);
_registeredStatesAttributes.Add(att);
}
#endregion
#region State Management
private readonly List<Type> _disabledFormatterStates = new List<Type>();
private readonly Stack<FormatterState> _stackOfStates = new Stack<FormatterState>();
private bool IsFormatterStateEnabled(Type type)
{
return !_disabledFormatterStates.Contains(type);
}
private void SwitchFormatterState(Type type, bool onOff)
{
if (onOff)
_disabledFormatterStates.Remove(type);
else if (!_disabledFormatterStates.Contains(type))
_disabledFormatterStates.Add(type);
}
/// <summary>
/// Pushes a new state on the stack.
/// </summary>
/// <param name="s">The state to push.</param>
/// The state will be entered automatically.
private void PushState(FormatterState s)
{
_stackOfStates.Push(s);
s.Enter();
}
/// <summary>
/// Removes the last state from the stack.
/// </summary>
/// The state will be exited automatically.
private void PopState()
{
_stackOfStates.Peek().Exit();
_stackOfStates.Pop();
}
/// <summary>
/// The current state, if any.
/// </summary>
internal FormatterState CurrentState
{
get
{
if (_stackOfStates.Count > 0)
return _stackOfStates.Peek();
else
return null;
}
}
internal void ChangeState(FormatterState formatterState)
{
if (CurrentState != null && CurrentState.GetType() == formatterState.GetType())
{
if (!CurrentState.ShouldNestState(formatterState))
return;
}
PushState(formatterState);
}
#endregion
#region State Handling
/// <summary>
/// Parses the string and updates the state accordingly.
/// </summary>
/// <param name="input">The text to process.</param>
/// <returns>The text, ready for formatting.</returns>
/// This method modifies the text because it removes some
/// syntax stuff. Maybe the states themselves should handle
/// their own syntax and remove it?
private string HandleFormattingState(string input)
{
for (var i = 0; i < _registeredStates.Count; i++)
{
var type = _registeredStates[i];
if (IsFormatterStateEnabled(type))
{
var att = _registeredStatesAttributes[i];
var m = Regex.Match(input, att.Pattern);
if (m.Success)
{
var formatterState = (FormatterState)Activator.CreateInstance(type, this);
return formatterState.Consume(input, m);
}
}
}
// Default, when no block is specified, we ask the current state, or
// use the paragraph state.
if (CurrentState != null)
{
if (CurrentState.FallbackFormattingState != null)
{
var formatterState = (FormatterState)Activator.CreateInstance(CurrentState.FallbackFormattingState, this);
ChangeState(formatterState);
}
// else, the current state doesn't want to be superceded by
// a new one. We'll leave him be.
}
else
{
ChangeState(new States.ParagraphFormatterState(this));
}
return input;
}
#endregion
}

View File

@ -5,6 +5,7 @@
<ApplicationIcon />
<OutputType>Library</OutputType>
<StartupObject />
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">

View File

@ -24,39 +24,37 @@
*/
namespace ASC.VoipService.Dao
namespace ASC.VoipService.Dao;
public class AbstractDao
{
public class AbstractDao
private readonly string _dbid = "default";
private readonly Lazy<VoipDbContext> _lazyVoipDbContext;
protected VoipDbContext VoipDbContext { get => _lazyVoipDbContext.Value; }
protected int TenantID
{
private readonly string dbid = "default";
get;
private set;
}
private Lazy<VoipDbContext> LazyVoipDbContext { get; }
protected VoipDbContext VoipDbContext { get => LazyVoipDbContext.Value; }
protected AbstractDao(DbContextManager<VoipDbContext> dbOptions, TenantManager tenantManager)
{
_lazyVoipDbContext = new Lazy<VoipDbContext>(() => dbOptions.Get(_dbid));
TenantID = tenantManager.GetCurrentTenant().Id;
}
protected AbstractDao(DbContextManager<VoipDbContext> dbOptions, TenantManager tenantManager)
{
LazyVoipDbContext = new Lazy<VoipDbContext>(() => dbOptions.Get(dbid));
TenantID = tenantManager.GetCurrentTenant().Id;
}
protected int TenantID
{
get;
private set;
}
protected string GetTenantColumnName(string table)
{
const string tenant = "tenant_id";
if (!table.Contains(' ')) return tenant;
return table.Substring(table.IndexOf(" ", StringComparison.Ordinal)).Trim() + "." + tenant;
}
protected string GetTenantColumnName(string table)
{
const string tenant = "tenant_id";
if (!table.Contains(' ')) return tenant;
return table.Substring(table.IndexOf(" ", StringComparison.Ordinal)).Trim() + "." + tenant;
}
protected static Guid ToGuid(object guid)
{
var str = guid as string;
return !string.IsNullOrEmpty(str) ? new Guid(str) : Guid.Empty;
}
protected static Guid ToGuid(object guid)
{
var str = guid as string;
return !string.IsNullOrEmpty(str) ? new Guid(str) : Guid.Empty;
}
}

View File

@ -1,100 +0,0 @@
/*
*
* (c) Copyright Ascensio System Limited 2010-2018
*
* This program is freeware. You can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that
* Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights.
*
* THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR
* FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales@onlyoffice.com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software. If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute.
* Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks.
*
*/
namespace ASC.VoipService.Dao
{
[Singletone]
public class VoipDaoCache
{
internal ICache Cache { get; }
private ICacheNotify<CachedVoipItem> Notify { get; }
public VoipDaoCache(ICacheNotify<CachedVoipItem> notify, ICache cache)
{
Cache = cache;
Notify = notify;
Notify.Subscribe((c) => Cache.Remove(CachedVoipDao.GetCacheKey(c.Tenant)), Common.Caching.CacheNotifyAction.Any);
}
public void ResetCache(int tenant)
{
Notify.Publish(new CachedVoipItem { Tenant = tenant }, Common.Caching.CacheNotifyAction.Any);
}
}
[Scope]
public class CachedVoipDao : VoipDao
{
private readonly ICache cache;
private static readonly TimeSpan timeout = TimeSpan.FromDays(1);
private VoipDaoCache VoipDaoCache { get; }
public CachedVoipDao(
TenantManager tenantManager,
DbContextManager<VoipDbContext> dbOptions,
AuthContext authContext,
TenantUtil tenantUtil,
SecurityContext securityContext,
BaseCommonLinkUtility baseCommonLinkUtility,
ConsumerFactory consumerFactory,
VoipDaoCache voipDaoCache)
: base(tenantManager, dbOptions, authContext, tenantUtil, securityContext, baseCommonLinkUtility, consumerFactory)
{
cache = voipDaoCache.Cache;
VoipDaoCache = voipDaoCache;
}
public override VoipPhone SaveOrUpdateNumber(VoipPhone phone)
{
var result = base.SaveOrUpdateNumber(phone);
VoipDaoCache.ResetCache(TenantID);
return result;
}
public override void DeleteNumber(string phoneId = "")
{
base.DeleteNumber(phoneId);
VoipDaoCache.ResetCache(TenantID);
}
public override IEnumerable<VoipPhone> GetNumbers(params string[] ids)
{
var numbers = cache.Get<List<VoipPhone>>(GetCacheKey(TenantID));
if (numbers == null)
{
numbers = new List<VoipPhone>(base.GetAllNumbers());
cache.Insert(GetCacheKey(TenantID), numbers, DateTime.UtcNow.Add(timeout));
}
return ids.Length > 0 ? numbers.Where(r => ids.Contains(r.Id) || ids.Contains(r.Number)).ToList() : numbers;
}
public static string GetCacheKey(int tenant)
{
return "voip" + tenant.ToString(CultureInfo.InvariantCulture);
}
}
}

View File

@ -23,92 +23,79 @@
*
*/
namespace ASC.VoipService.Dao
namespace ASC.VoipService.Dao;
public class VoipCallFilter
{
public class VoipCallFilter
public string Type { get; set; }
public DateTime? FromDate { get; set; }
public DateTime? ToDate { get; set; }
public Guid? Agent { get; set; }
public int? Client { get; set; }
public int? ContactID { get; set; }
public string Id { get; set; }
public string ParentId { get; set; }
public string SortBy { get; set; }
public bool SortOrder { get; set; }
public string SearchText { get; set; }
public long Offset { get; set; }
public long Max { get; set; }
public int? TypeStatus
{
public string Type { get; set; }
public DateTime? FromDate { get; set; }
public DateTime? ToDate { get; set; }
public Guid? Agent { get; set; }
public int? Client { get; set; }
public int? ContactID { get; set; }
public string Id { get; set; }
public string ParentId { get; set; }
public string SortBy { get; set; }
public bool SortOrder { get; set; }
public string SearchText { get; set; }
public long Offset { get; set; }
public long Max { get; set; }
public int? TypeStatus
get
{
get
{
if (string.IsNullOrWhiteSpace(Type)) return null;
if (TypeStatuses.TryGetValue(Type, out var status)) return status;
if (string.IsNullOrWhiteSpace(Type)) return null;
if (TypeStatuses.TryGetValue(Type, out var status)) return status;
return null;
}
return null;
}
}
public string SortByColumn
public string SortByColumn
{
get
{
get
{
if (string.IsNullOrWhiteSpace(SortBy)) return null;
return SortColumns.ContainsKey(SortBy) ? SortColumns[SortBy] : null;
}
if (string.IsNullOrWhiteSpace(SortBy)) return null;
return SortColumns.ContainsKey(SortBy) ? SortColumns[SortBy] : null;
}
}
private static Dictionary<string, int> TypeStatuses
private static Dictionary<string, int> TypeStatuses
{
get
{
get
{
return new Dictionary<string, int>
return new Dictionary<string, int>
{
{
{
"answered", (int)VoipCallStatus.Answered
},
{
"missed", (int)VoipCallStatus.Missed
},
{
"outgoing", (int)VoipCallStatus.Outcoming
}
};
}
}
private static Dictionary<string, string> SortColumns
{
get
{
return new Dictionary<string, string>
"answered", (int)VoipCallStatus.Answered
},
{
{
"date", "dial_date"
},
{
"duration", "dial_duration"
},
{
"price", "price"
},
};
}
"missed", (int)VoipCallStatus.Missed
},
{
"outgoing", (int)VoipCallStatus.Outcoming
}
};
}
}
private static Dictionary<string, string> SortColumns
{
get
{
return new Dictionary<string, string>
{
{
"date", "dial_date"
},
{
"duration", "dial_duration"
},
{
"price", "price"
},
};
}
}
}

View File

@ -23,342 +23,303 @@
*
*/
namespace ASC.VoipService.Dao
namespace ASC.VoipService.Dao;
[Scope(Additional = typeof(EventTypeConverterExtension))]
public class VoipDao : AbstractDao
{
[Scope(typeof(CachedVoipDao))]
public class VoipDao : AbstractDao
private readonly AuthContext _authContext;
private readonly TenantUtil _tenantUtil;
private readonly SecurityContext _securityContext;
private readonly BaseCommonLinkUtility _baseCommonLinkUtility;
private readonly ConsumerFactory _consumerFactory;
private readonly IMapper _mapper;
public VoipDao(
TenantManager tenantManager,
DbContextManager<VoipDbContext> dbOptions,
AuthContext authContext,
TenantUtil tenantUtil,
SecurityContext securityContext,
BaseCommonLinkUtility baseCommonLinkUtility,
ConsumerFactory consumerFactory,
IMapper mapper)
: base(dbOptions, tenantManager)
{
public VoipDao(
TenantManager tenantManager,
DbContextManager<VoipDbContext> dbOptions,
AuthContext authContext,
TenantUtil tenantUtil,
SecurityContext securityContext,
BaseCommonLinkUtility baseCommonLinkUtility,
ConsumerFactory consumerFactory)
: base(dbOptions, tenantManager)
{
AuthContext = authContext;
TenantUtil = tenantUtil;
SecurityContext = securityContext;
BaseCommonLinkUtility = baseCommonLinkUtility;
ConsumerFactory = consumerFactory;
}
public virtual VoipPhone SaveOrUpdateNumber(VoipPhone phone)
{
if (!string.IsNullOrEmpty(phone.Number))
{
phone.Number = phone.Number.TrimStart('+');
}
var voipNumber = new VoipNumber
{
Id = phone.Id,
Number = phone.Number,
Alias = phone.Alias,
Settings = phone.Settings.ToString(),
TenantId = TenantID
};
VoipDbContext.VoipNumbers.Add(voipNumber);
VoipDbContext.SaveChanges();
return phone;
}
public virtual void DeleteNumber(string phoneId = "")
{
var number = VoipDbContext.VoipNumbers.Where(r => r.Id == phoneId && r.TenantId == TenantID).FirstOrDefault();
VoipDbContext.VoipNumbers.Remove(number);
VoipDbContext.SaveChanges();
}
public virtual IEnumerable<VoipPhone> GetAllNumbers()
{
return VoipDbContext.VoipNumbers
.Where(r => r.TenantId == TenantID)
.ToList()
.ConvertAll(ToPhone);
}
public virtual IEnumerable<VoipPhone> GetNumbers(params string[] ids)
{
var numbers = VoipDbContext.VoipNumbers.Where(r => r.TenantId == TenantID);
if (ids.Length > 0)
{
numbers = numbers.Where(r => ids.Any(a => a == r.Number || a == r.Id));
}
return numbers.ToList().ConvertAll(ToPhone);
}
public VoipPhone GetNumber(string id)
{
return GetNumbers(id.TrimStart('+')).FirstOrDefault();
}
public virtual VoipPhone GetCurrentNumber()
{
return GetNumbers().FirstOrDefault(r => r.Caller != null);
}
public VoipCall SaveOrUpdateCall(VoipCall call)
{
var voipCall = new DbVoipCall
{
TenantId = TenantID,
Id = call.Id,
NumberFrom = call.From,
NumberTo = call.To,
ContactId = call.ContactId
};
if (!string.IsNullOrEmpty(call.ParentID))
{
voipCall.ParentCallId = call.ParentID;
}
if (call.Status.HasValue)
{
voipCall.Status = (int)call.Status.Value;
}
if (!call.AnsweredBy.Equals(Guid.Empty))
{
voipCall.AnsweredBy = call.AnsweredBy;
}
if (call.DialDate == DateTime.MinValue)
{
call.DialDate = DateTime.UtcNow;
}
voipCall.DialDate = TenantUtil.DateTimeToUtc(call.DialDate);
if (call.DialDuration > 0)
{
voipCall.DialDuration = call.DialDuration;
}
if (call.Price > decimal.Zero)
{
voipCall.Price = call.Price;
}
if (call.VoipRecord != null)
{
if (!string.IsNullOrEmpty(call.VoipRecord.Id))
{
voipCall.RecordSid = call.VoipRecord.Id;
}
if (!string.IsNullOrEmpty(call.VoipRecord.Uri))
{
voipCall.RecordUrl = call.VoipRecord.Uri;
}
if (call.VoipRecord.Duration != 0)
{
voipCall.RecordDuration = call.VoipRecord.Duration;
}
if (call.VoipRecord.Price != default)
{
voipCall.RecordPrice = call.VoipRecord.Price;
}
}
VoipDbContext.VoipCalls.Add(voipCall);
VoipDbContext.SaveChanges();
return call;
}
public IEnumerable<VoipCall> GetCalls(VoipCallFilter filter)
{
var query = GetCallsQuery(filter);
if (filter.SortByColumn != null)
{
query.OrderBy(filter.SortByColumn, filter.SortOrder);
}
query = query.Skip((int)filter.Offset);
query = query.Take((int)filter.Max * 3);
var calls = query.ToList().ConvertAll(ToCall);
calls = calls.GroupJoin(calls, call => call.Id, h => h.ParentID, (call, h) =>
{
call.ChildCalls.AddRange(h);
return call;
}).Where(r => string.IsNullOrEmpty(r.ParentID)).ToList();
return calls;
}
public VoipCall GetCall(string id)
{
return GetCalls(new VoipCallFilter { Id = id }).FirstOrDefault();
}
public int GetCallsCount(VoipCallFilter filter)
{
return GetCallsQuery(filter).Where(r => r.DbVoipCall.ParentCallId == "").Count();
}
public IEnumerable<VoipCall> GetMissedCalls(Guid agent, long count = 0, DateTime? from = null)
{
var query = GetCallsQuery(new VoipCallFilter { Agent = agent, SortBy = "date", SortOrder = true, Type = "missed" });
if (from.HasValue)
{
query = query.Where(r => r.DbVoipCall.DialDate >= TenantUtil.DateTimeFromUtc(from.Value));
}
if (count != 0)
{
query = query.Take((int)count);
}
var a = query.Select(ca => new
{
dbVoipCall = ca,
tmpDate = VoipDbContext.VoipCalls
.Where(tmp => tmp.TenantId == ca.DbVoipCall.TenantId)
.Where(tmp => tmp.NumberFrom == ca.DbVoipCall.NumberFrom || tmp.NumberTo == ca.DbVoipCall.NumberFrom)
.Where(tmp => tmp.Status <= (int)VoipCallStatus.Missed)
.Max(tmp => tmp.DialDate)
}).Where(r => r.dbVoipCall.DbVoipCall.DialDate >= r.tmpDate || r.tmpDate == default);
return a.ToList().ConvertAll(r => ToCall(r.dbVoipCall));
}
private IQueryable<CallContact> GetCallsQuery(VoipCallFilter filter)
{
var q = VoipDbContext.VoipCalls
.Where(r => r.TenantId == TenantID);
if (!string.IsNullOrEmpty(filter.Id))
{
q = q.Where(r => r.Id == filter.Id || r.ParentCallId == filter.Id);
}
if (filter.ContactID.HasValue)
{
q = q.Where(r => r.ContactId == filter.ContactID.Value);
}
if (!string.IsNullOrWhiteSpace(filter.SearchText))
{
q = q.Where(r => r.Id.StartsWith(filter.SearchText));
}
if (filter.TypeStatus.HasValue)
{
q = q.Where(r => r.Status == filter.TypeStatus.Value);
}
if (filter.FromDate.HasValue)
{
q = q.Where(r => r.DialDate >= filter.FromDate.Value);
}
if (filter.ToDate.HasValue)
{
q = q.Where(r => r.DialDate <= filter.ToDate.Value);
}
if (filter.Agent.HasValue)
{
q = q.Where(r => r.AnsweredBy == filter.Agent.Value);
}
return q
.GroupBy(r => r.Id, r => r)
.Join(
VoipDbContext.CrmContact.DefaultIfEmpty(),
r => r.FirstOrDefault().ContactId,
c => c.Id,
(call, contact) => new CallContact { DbVoipCall = call.FirstOrDefault(), CrmContact = contact })
;
}
class CallContact
{
public DbVoipCall DbVoipCall { get; set; }
public CrmContact CrmContact { get; set; }
}
#region Converters
private VoipPhone ToPhone(VoipNumber r)
{
return GetProvider().GetPhone(r);
}
private VoipCall ToCall(CallContact dbVoipCall)
{
var call = new VoipCall
{
Id = dbVoipCall.DbVoipCall.Id,
ParentID = dbVoipCall.DbVoipCall.ParentCallId,
From = dbVoipCall.DbVoipCall.NumberFrom,
To = dbVoipCall.DbVoipCall.NumberTo,
AnsweredBy = dbVoipCall.DbVoipCall.AnsweredBy,
DialDate = TenantUtil.DateTimeFromUtc(dbVoipCall.DbVoipCall.DialDate),
DialDuration = dbVoipCall.DbVoipCall.DialDuration,
Price = dbVoipCall.DbVoipCall.Price,
Status = (VoipCallStatus)dbVoipCall.DbVoipCall.Status,
VoipRecord = new VoipRecord
{
Id = dbVoipCall.DbVoipCall.RecordSid,
Uri = dbVoipCall.DbVoipCall.RecordUrl,
Duration = dbVoipCall.DbVoipCall.RecordDuration,
Price = dbVoipCall.DbVoipCall.RecordPrice
},
ContactId = dbVoipCall.CrmContact.Id,
ContactIsCompany = dbVoipCall.CrmContact.IsCompany,
};
if (call.ContactId != 0)
{
call.ContactTitle = call.ContactIsCompany
? dbVoipCall.CrmContact.CompanyName
: dbVoipCall.CrmContact.FirstName == null || dbVoipCall.CrmContact.LastName == null ? null : $"{dbVoipCall.CrmContact.FirstName} {dbVoipCall.CrmContact.LastName}";
}
return call;
}
public Consumer Consumer
{
get { return ConsumerFactory.GetByKey("twilio"); }
}
public TwilioProvider GetProvider()
{
return new TwilioProvider(Consumer["twilioAccountSid"], Consumer["twilioAuthToken"], AuthContext, TenantUtil, SecurityContext, BaseCommonLinkUtility);
}
public bool ConfigSettingsExist
{
get
{
return !string.IsNullOrEmpty(Consumer["twilioAccountSid"]) &&
!string.IsNullOrEmpty(Consumer["twilioAuthToken"]);
}
}
private AuthContext AuthContext { get; }
private TenantUtil TenantUtil { get; }
private SecurityContext SecurityContext { get; }
private BaseCommonLinkUtility BaseCommonLinkUtility { get; }
private ConsumerFactory ConsumerFactory { get; }
#endregion
_authContext = authContext;
_tenantUtil = tenantUtil;
_securityContext = securityContext;
_baseCommonLinkUtility = baseCommonLinkUtility;
_consumerFactory = consumerFactory;
_mapper = mapper;
}
public virtual VoipPhone SaveOrUpdateNumber(VoipPhone phone)
{
if (!string.IsNullOrEmpty(phone.Number))
{
phone.Number = phone.Number.TrimStart('+');
}
var voipNumber = new VoipNumber
{
Id = phone.Id,
Number = phone.Number,
Alias = phone.Alias,
Settings = phone.Settings.ToString(),
TenantId = TenantID
};
VoipDbContext.VoipNumbers.Add(voipNumber);
VoipDbContext.SaveChanges();
return phone;
}
public virtual void DeleteNumber(string phoneId = "")
{
var number = VoipDbContext.VoipNumbers.Where(r => r.Id == phoneId && r.TenantId == TenantID).FirstOrDefault();
VoipDbContext.VoipNumbers.Remove(number);
VoipDbContext.SaveChanges();
}
public virtual IEnumerable<VoipPhone> GetAllNumbers()
{
return VoipDbContext.VoipNumbers
.Where(r => r.TenantId == TenantID)
.ToList()
.ConvertAll(ToPhone);
}
public virtual IEnumerable<VoipPhone> GetNumbers(params string[] ids)
{
var numbers = VoipDbContext.VoipNumbers.Where(r => r.TenantId == TenantID);
if (ids.Length > 0)
{
numbers = numbers.Where(r => ids.Any(a => a == r.Number || a == r.Id));
}
return numbers.ToList().ConvertAll(ToPhone);
}
public VoipPhone GetNumber(string id)
{
return GetNumbers(id.TrimStart('+')).FirstOrDefault();
}
public virtual VoipPhone GetCurrentNumber()
{
return GetNumbers().FirstOrDefault(r => r.Caller != null);
}
public VoipCall SaveOrUpdateCall(VoipCall call)
{
var voipCall = new DbVoipCall
{
TenantId = TenantID,
Id = call.Id,
NumberFrom = call.NumberFrom,
NumberTo = call.NumberTo,
ContactId = call.ContactId
};
if (!string.IsNullOrEmpty(call.ParentCallId))
{
voipCall.ParentCallId = call.ParentCallId;
}
if (call.Status.HasValue)
{
voipCall.Status = (int)call.Status.Value;
}
if (!call.AnsweredBy.Equals(Guid.Empty))
{
voipCall.AnsweredBy = call.AnsweredBy;
}
if (call.DialDate == DateTime.MinValue)
{
call.DialDate = DateTime.UtcNow;
}
voipCall.DialDate = _tenantUtil.DateTimeToUtc(call.DialDate);
if (call.DialDuration > 0)
{
voipCall.DialDuration = call.DialDuration;
}
if (call.Price > decimal.Zero)
{
voipCall.Price = call.Price;
}
if (call.VoipRecord != null)
{
if (!string.IsNullOrEmpty(call.VoipRecord.Sid))
{
voipCall.Sid = call.VoipRecord.Sid;
}
if (!string.IsNullOrEmpty(call.VoipRecord.Uri))
{
voipCall.Uri = call.VoipRecord.Uri;
}
if (call.VoipRecord.Duration != 0)
{
voipCall.Duration = call.VoipRecord.Duration;
}
if (call.VoipRecord.Price != default)
{
voipCall.RecordPrice = call.VoipRecord.Price;
}
}
VoipDbContext.VoipCalls.Add(voipCall);
VoipDbContext.SaveChanges();
return call;
}
public IEnumerable<VoipCall> GetCalls(VoipCallFilter filter)
{
var query = GetCallsQuery(filter);
if (filter.SortByColumn != null)
{
query.OrderBy(filter.SortByColumn, filter.SortOrder);
}
query = query.Skip((int)filter.Offset);
query = query.Take((int)filter.Max * 3);
var calls = _mapper.Map<List<CallContact>, IEnumerable<VoipCall>>(query.ToList());
calls = calls.GroupJoin(calls, call => call.Id, h => h.ParentCallId, (call, h) =>
{
call.ChildCalls.AddRange(h);
return call;
}).Where(r => string.IsNullOrEmpty(r.ParentCallId)).ToList();
return calls;
}
public VoipCall GetCall(string id)
{
return GetCalls(new VoipCallFilter { Id = id }).FirstOrDefault();
}
public int GetCallsCount(VoipCallFilter filter)
{
return GetCallsQuery(filter).Where(r => r.DbVoipCall.ParentCallId == "").Count();
}
public IEnumerable<VoipCall> GetMissedCalls(Guid agent, long count = 0, DateTime? from = null)
{
var query = GetCallsQuery(new VoipCallFilter { Agent = agent, SortBy = "date", SortOrder = true, Type = "missed" });
if (from.HasValue)
{
query = query.Where(r => r.DbVoipCall.DialDate >= _tenantUtil.DateTimeFromUtc(from.Value));
}
if (count != 0)
{
query = query.Take((int)count);
}
query = query.Select(ca => new
{
dbVoipCall = ca,
tmpDate = VoipDbContext.VoipCalls
.Where(tmp => tmp.TenantId == ca.DbVoipCall.TenantId)
.Where(tmp => tmp.NumberFrom == ca.DbVoipCall.NumberFrom || tmp.NumberTo == ca.DbVoipCall.NumberFrom)
.Where(tmp => tmp.Status <= (int)VoipCallStatus.Missed)
.Max(tmp => tmp.DialDate)
}).Where(r => r.dbVoipCall.DbVoipCall.DialDate >= r.tmpDate || r.tmpDate == default)
.Select(q=> q.dbVoipCall);
return _mapper.Map<List<CallContact>, IEnumerable<VoipCall>>(query.ToList());
}
private IQueryable<CallContact> GetCallsQuery(VoipCallFilter filter)
{
var q = VoipDbContext.VoipCalls
.Where(r => r.TenantId == TenantID);
if (!string.IsNullOrEmpty(filter.Id))
{
q = q.Where(r => r.Id == filter.Id || r.ParentCallId == filter.Id);
}
if (filter.ContactID.HasValue)
{
q = q.Where(r => r.ContactId == filter.ContactID.Value);
}
if (!string.IsNullOrWhiteSpace(filter.SearchText))
{
q = q.Where(r => r.Id.StartsWith(filter.SearchText));
}
if (filter.TypeStatus.HasValue)
{
q = q.Where(r => r.Status == filter.TypeStatus.Value);
}
if (filter.FromDate.HasValue)
{
q = q.Where(r => r.DialDate >= filter.FromDate.Value);
}
if (filter.ToDate.HasValue)
{
q = q.Where(r => r.DialDate <= filter.ToDate.Value);
}
if (filter.Agent.HasValue)
{
q = q.Where(r => r.AnsweredBy == filter.Agent.Value);
}
return from voipCalls in q
join crmContact in VoipDbContext.CrmContact on voipCalls.ContactId equals crmContact.Id into grouping
from g in grouping.DefaultIfEmpty()
select new CallContact { DbVoipCall = voipCalls, CrmContact = g };
}
private VoipPhone ToPhone(VoipNumber r)
{
return GetProvider().GetPhone(r);
}
public Consumer Consumer
{
get { return _consumerFactory.GetByKey("twilio"); }
}
public TwilioProvider GetProvider()
{
return new TwilioProvider(Consumer["twilioAccountSid"], Consumer["twilioAuthToken"], _authContext, _tenantUtil, _securityContext, _baseCommonLinkUtility);
}
public bool ConfigSettingsExist
{
get
{
return !string.IsNullOrEmpty(Consumer["twilioAccountSid"]) &&
!string.IsNullOrEmpty(Consumer["twilioAuthToken"]);
}
}
}
public class CallContact
{
public DbVoipCall DbVoipCall { get; set; }
public CrmContact CrmContact { get; set; }
}

View File

@ -1,14 +1,9 @@
global using System;
global using System.Collections.Generic;
global using System.Globalization;
global using System.Linq;
global using System.Reflection;
global using System.Reflection;
global using System.Text;
global using System.Threading;
global using System.Web;
global using ASC.Common;
global using ASC.Common.Caching;
global using ASC.Common.Mapping;
global using ASC.Core;
global using ASC.Core.Common;
global using ASC.Core.Common.Configuration;
@ -16,19 +11,22 @@ global using ASC.Core.Common.EF;
global using ASC.Core.Common.EF.Context;
global using ASC.Core.Common.EF.Model;
global using ASC.Core.Tenants;
global using ASC.VoipService.Dao;
global using ASC.VoipService.Mappings;
global using ASC.VoipService.Twilio;
global using AutoMapper;
global using Newtonsoft.Json;
global using Newtonsoft.Json.Linq;
global using Newtonsoft.Json.Serialization;
global using Twilio.Clients;
global using Twilio.Exceptions;
global using Twilio.Http;
global using Twilio.Jwt;
global using Twilio.Jwt.Client;
global using Twilio.Rest.Api.V2010.Account;
global using Twilio.Rest.Api.V2010.Account.AvailablePhoneNumberCountry;
global using Twilio.Rest.Api.V2010.Account.Queue;
global using Twilio.TwiML;
global using Twilio.Types;
global using Twilio.Types;

View File

@ -23,32 +23,31 @@
*
*/
namespace ASC.VoipService
namespace ASC.VoipService;
public interface IVoipProvider
{
public interface IVoipProvider
{
IEnumerable<VoipPhone> GetExistingPhoneNumbers();
IEnumerable<VoipPhone> GetExistingPhoneNumbers();
IEnumerable<VoipPhone> GetAvailablePhoneNumbers(PhoneNumberType phoneNumberType, string isoCountryCode);
IEnumerable<VoipPhone> GetAvailablePhoneNumbers(PhoneNumberType phoneNumberType, string isoCountryCode);
VoipPhone BuyNumber(string phoneNumber);
VoipPhone BuyNumber(string phoneNumber);
VoipPhone DeleteNumber(VoipPhone phone);
VoipPhone DeleteNumber(VoipPhone phone);
VoipPhone GetPhone(VoipNumber r);
VoipPhone GetPhone(VoipNumber r);
VoipPhone GetPhone(string id);
VoipPhone GetPhone(string id);
VoipCall GetCall(string callId);
VoipCall GetCall(string callId);
string GetToken(Agent agent, int seconds = 60 * 60 * 24);
string GetToken(Agent agent, int seconds = 60 * 60 * 24);
void UpdateSettings(VoipPhone phone);
void UpdateSettings(VoipPhone phone);
VoipRecord GetRecord(string callId, string recordId);
VoipRecord GetRecord(string callId, string recordId);
void CreateQueue(VoipPhone newPhone);
void CreateQueue(VoipPhone newPhone);
void DisablePhone(VoipPhone phone);
}
void DisablePhone(VoipPhone phone);
}

View File

@ -0,0 +1,34 @@
namespace ASC.VoipService.Mappings;
[Scope]
public class CallTypeConverter : ITypeConverter<CallContact, VoipCall>
{
public VoipCall Convert(CallContact source, VoipCall destination, ResolutionContext context)
{
var result = context.Mapper.Map<DbVoipCall, VoipCall>(source.DbVoipCall);
result.VoipRecord = context.Mapper.Map<DbVoipCall, VoipRecord>(source.DbVoipCall);
if (source.CrmContact != null)
{
result.ContactId = source.CrmContact.Id;
result.ContactIsCompany = source.CrmContact.IsCompany;
result.ContactTitle = result.ContactIsCompany
? source.CrmContact.CompanyName
: source.CrmContact.FirstName == null || source.CrmContact.LastName == null ? null : $"{source.CrmContact.FirstName} {source.CrmContact.LastName}";
}
else
{
result.ContactId = 0;
}
return result;
}
}
public class EventTypeConverterExtension
{
public static void Register(DIHelper services)
{
services.TryAdd<CallTypeConverter>();
}
}

View File

@ -23,103 +23,103 @@
*
*/
namespace ASC.VoipService.Twilio
using HttpMethod = Twilio.Http.HttpMethod;
namespace ASC.VoipService.Twilio;
public class TwilioPhone : VoipPhone
{
public class TwilioPhone : VoipPhone
private readonly TwilioRestClient _twilio;
public TwilioPhone(
TwilioRestClient twilio,
AuthContext authContext,
TenantUtil tenantUtil,
SecurityContext securityContext,
BaseCommonLinkUtility baseCommonLinkUtility) :
base(authContext, tenantUtil, securityContext, baseCommonLinkUtility)
{
private readonly TwilioRestClient twilio;
public TwilioPhone(
TwilioRestClient twilio,
AuthContext authContext,
TenantUtil tenantUtil,
SecurityContext securityContext,
BaseCommonLinkUtility baseCommonLinkUtility) :
base(authContext, tenantUtil, securityContext, baseCommonLinkUtility)
{
this.twilio = twilio;
Settings = new TwilioVoipSettings(authContext, tenantUtil, securityContext, baseCommonLinkUtility);
}
#region Calls
public override VoipCall Call(string to, string contactId = null)
{
var number = to.Split('#');
var call = CallResource.Create(new CreateCallOptions(new PhoneNumber("+" + number[0].TrimStart('+')), new PhoneNumber("+" + Number.TrimStart('+')))
{
SendDigits = number.Length > 1 ? number[1] + "#" : string.Empty,
Record = Settings.Caller.Record,
Url = new System.Uri(Settings.Connect(contactId: contactId))
}, twilio);
return new VoipCall { Id = call.Sid, From = call.From, To = call.To };
}
public override VoipCall LocalCall(string to)
{
return Call(Number + "#" + to);
}
public override VoipCall RedirectCall(string callId, string to)
{
var call = CallResource.Update(callId, url: new System.Uri(Settings.Redirect(to)), method: HttpMethod.Post, client: twilio);
return new VoipCall { Id = call.Sid, To = to };
}
public override VoipCall HoldUp(string callId)
{
return RedirectCall(callId, "hold");
}
#endregion
#region Queue
public Queue CreateQueue(string name, int size, string waitUrl, int waitTime)
{
var queues = QueueResource.Read(new ReadQueueOptions(), twilio);
var queue = queues.FirstOrDefault(r => r.FriendlyName == name);
if (queue == null)
{
queue = QueueResource.Create(name, client: twilio);
}
return new Queue(queue.Sid, name, size, waitUrl, waitTime);
}
public string GetQueue(string name)
{
var queues = QueueResource.Read(new ReadQueueOptions(), twilio);
return queues.First(r => r.FriendlyName == name).Sid;
}
public IEnumerable<string> QueueCalls(string id)
{
var calls = MemberResource.Read(id, client: twilio);
return calls.Select(r => r.CallSid);
}
private void AnswerQueueCall(string queueId, string callId, bool reject = false)
{
var calls = QueueCalls(queueId);
if (calls.Contains(callId))
{
MemberResource.Update(queueId, callId, new System.Uri(Settings.Dequeue(reject)), HttpMethod.Post,
client: twilio);
}
}
public override void AnswerQueueCall(string callId)
{
AnswerQueueCall(Settings.Queue.Id, callId);
}
public override void RejectQueueCall(string callId)
{
AnswerQueueCall(Settings.Queue.Id, callId, true);
}
#endregion
_twilio = twilio;
Settings = new TwilioVoipSettings(authContext, tenantUtil, securityContext, baseCommonLinkUtility);
}
#region Calls
public override VoipCall Call(string to, string contactId = null)
{
var number = to.Split('#');
var call = CallResource.Create(new CreateCallOptions(new PhoneNumber("+" + number[0].TrimStart('+')), new PhoneNumber("+" + Number.TrimStart('+')))
{
SendDigits = number.Length > 1 ? number[1] + "#" : string.Empty,
Record = Settings.Caller.Record,
Url = new System.Uri(Settings.Connect(contactId: contactId))
}, _twilio);
return new VoipCall { Id = call.Sid, NumberFrom = call.From, NumberTo = call.To };
}
public override VoipCall LocalCall(string to)
{
return Call(Number + "#" + to);
}
public override VoipCall RedirectCall(string callId, string to)
{
var call = CallResource.Update(callId, url: new System.Uri(Settings.Redirect(to)), method: HttpMethod.Post, client: _twilio);
return new VoipCall { Id = call.Sid, NumberTo = to };
}
public override VoipCall HoldUp(string callId)
{
return RedirectCall(callId, "hold");
}
#endregion
#region Queue
public Queue CreateQueue(string name, int size, string waitUrl, int waitTime)
{
var queues = QueueResource.Read(new ReadQueueOptions(), _twilio);
var queue = queues.FirstOrDefault(r => r.FriendlyName == name);
if (queue == null)
{
queue = QueueResource.Create(name, client: _twilio);
}
return new Queue(queue.Sid, name, size, waitUrl, waitTime);
}
public string GetQueue(string name)
{
var queues = QueueResource.Read(new ReadQueueOptions(), _twilio);
return queues.First(r => r.FriendlyName == name).Sid;
}
public IEnumerable<string> QueueCalls(string id)
{
var calls = MemberResource.Read(id, client: _twilio);
return calls.Select(r => r.CallSid);
}
private void AnswerQueueCall(string queueId, string callId, bool reject = false)
{
var calls = QueueCalls(queueId);
if (calls.Contains(callId))
{
MemberResource.Update(queueId, callId, new System.Uri(Settings.Dequeue(reject)), HttpMethod.Post,
client: _twilio);
}
}
public override void AnswerQueueCall(string callId)
{
AnswerQueueCall(Settings.Queue.Id, callId);
}
public override void RejectQueueCall(string callId)
{
AnswerQueueCall(Settings.Queue.Id, callId, true);
}
#endregion
}

View File

@ -27,208 +27,206 @@
using RecordingResource = Twilio.Rest.Api.V2010.Account.Call.RecordingResource;
namespace ASC.VoipService.Twilio
namespace ASC.VoipService.Twilio;
public class TwilioProvider : IVoipProvider
{
public class TwilioProvider : IVoipProvider
private readonly string _accountSid;
private readonly string _authToken;
private readonly TwilioRestClient _client;
private readonly AuthContext _authContext;
private readonly TenantUtil _tenantUtil;
private readonly SecurityContext _securityContext;
private readonly BaseCommonLinkUtility _baseCommonLinkUtility;
public TwilioProvider(string accountSid, string authToken, AuthContext authContext, TenantUtil tenantUtil, SecurityContext securityContext, BaseCommonLinkUtility baseCommonLinkUtility)
{
private readonly string accountSid;
private readonly string authToken;
private readonly TwilioRestClient client;
if (string.IsNullOrEmpty(accountSid)) throw new ArgumentNullException(nameof(accountSid));
if (string.IsNullOrEmpty(authToken)) throw new ArgumentNullException(nameof(authToken));
private AuthContext AuthContext { get; }
private TenantUtil TenantUtil { get; }
private SecurityContext SecurityContext { get; }
private BaseCommonLinkUtility BaseCommonLinkUtility { get; }
_authToken = authToken;
_authContext = authContext;
_tenantUtil = tenantUtil;
_securityContext = securityContext;
_baseCommonLinkUtility = baseCommonLinkUtility;
_accountSid = accountSid;
public TwilioProvider(string accountSid, string authToken, AuthContext authContext, TenantUtil tenantUtil, SecurityContext securityContext, BaseCommonLinkUtility baseCommonLinkUtility)
_client = new TwilioRestClient(accountSid, authToken);
}
#region Call
public VoipRecord GetRecord(string callId, string recordSid)
{
var result = new VoipRecord { Sid = recordSid };
var count = 6;
while (count > 0)
{
if (string.IsNullOrEmpty(accountSid)) throw new ArgumentNullException(nameof(accountSid));
if (string.IsNullOrEmpty(authToken)) throw new ArgumentNullException(nameof(authToken));
this.authToken = authToken;
AuthContext = authContext;
TenantUtil = tenantUtil;
SecurityContext = securityContext;
BaseCommonLinkUtility = baseCommonLinkUtility;
this.accountSid = accountSid;
client = new TwilioRestClient(accountSid, authToken);
}
#region Call
public VoipRecord GetRecord(string callId, string recordSid)
{
var result = new VoipRecord { Id = recordSid };
var count = 6;
while (count > 0)
try
{
try
{
var record = RecordingResource.Fetch(callId, recordSid, client: client);
var record = RecordingResource.Fetch(callId, recordSid, client: _client);
if (!record.Price.HasValue)
{
count--;
Thread.Sleep(10000);
continue;
}
result.Price = (-1) * record.Price.Value;
result.Duration = Convert.ToInt32(record.Duration);
if (record.Uri != null)
{
result.Uri = record.Uri;
}
break;
}
catch (ApiException)
if (!record.Price.HasValue)
{
count--;
Thread.Sleep(10000);
continue;
}
}
return result;
}
result.Price = (-1) * record.Price.Value;
public void CreateQueue(VoipPhone newPhone)
{
newPhone.Settings.Queue = ((TwilioPhone)newPhone).CreateQueue(newPhone.Number, 5, string.Empty, 5);
}
#endregion
#region Numbers
public VoipPhone BuyNumber(string phoneNumber)
{
var newNumber = IncomingPhoneNumberResource.Create(
new CreateIncomingPhoneNumberOptions
result.Duration = Convert.ToInt32(record.Duration);
if (record.Uri != null)
{
PathAccountSid = accountSid,
PhoneNumber = new PhoneNumber(phoneNumber)
}, client);
return new TwilioPhone(client, AuthContext, TenantUtil, SecurityContext, BaseCommonLinkUtility) { Id = newNumber.Sid, Number = phoneNumber.Substring(1) };
}
public VoipPhone DeleteNumber(VoipPhone phone)
{
IncomingPhoneNumberResource.Delete(phone.Id, client: client);
return phone;
}
public IEnumerable<VoipPhone> GetExistingPhoneNumbers()
{
var result = IncomingPhoneNumberResource.Read(client: client);
return result.Select(r => new TwilioPhone(client, AuthContext, TenantUtil, SecurityContext, BaseCommonLinkUtility) { Id = r.Sid, Number = r.PhoneNumber.ToString() });
}
public IEnumerable<VoipPhone> GetAvailablePhoneNumbers(PhoneNumberType phoneNumberType, string isoCountryCode)
{
return phoneNumberType switch
{
PhoneNumberType.Local => LocalResource.Read(isoCountryCode, voiceEnabled: true, client: client).Select(r => new TwilioPhone(client, AuthContext, TenantUtil, SecurityContext, BaseCommonLinkUtility) { Number = r.PhoneNumber.ToString() }),
PhoneNumberType.TollFree => TollFreeResource.Read(isoCountryCode, voiceEnabled: true, client: client).Select(r => new TwilioPhone(client, AuthContext, TenantUtil, SecurityContext, BaseCommonLinkUtility) { Number = r.PhoneNumber.ToString() }),
_ => new List<VoipPhone>(),
};
}
public VoipPhone GetPhone(string phoneSid)
{
var phone = IncomingPhoneNumberResource.Fetch(phoneSid, client: client);
var result = new TwilioPhone(client, AuthContext, TenantUtil, SecurityContext, BaseCommonLinkUtility)
{
Id = phone.Sid,
Number = phone.PhoneNumber.ToString(),
Settings = new TwilioVoipSettings(AuthContext, TenantUtil, SecurityContext, BaseCommonLinkUtility)
};
if (phone.VoiceUrl == null)
{
result.Settings.VoiceUrl = result.Settings.Connect(false);
}
return result;
}
public VoipPhone GetPhone(VoipNumber data)
{
return new TwilioPhone(client, AuthContext, TenantUtil, SecurityContext, BaseCommonLinkUtility)
{
Id = data.Id,
Number = data.Number,
Alias = data.Alias,
Settings = new TwilioVoipSettings(data.Settings, AuthContext)
};
}
public VoipCall GetCall(string callId)
{
var result = new VoipCall { Id = callId };
var count = 6;
while (count > 0)
{
try
{
var call = CallResource.Fetch(result.Id, client: client);
if (!call.Price.HasValue || string.IsNullOrEmpty(call.Duration))
{
count--;
Thread.Sleep(10000);
continue;
}
result.Price = (-1) * call.Price.Value;
result.DialDuration = Convert.ToInt32(call.Duration);
break;
result.Uri = record.Uri;
}
catch (ApiException)
break;
}
catch (ApiException)
{
count--;
Thread.Sleep(10000);
}
}
return result;
}
public void CreateQueue(VoipPhone newPhone)
{
newPhone.Settings.Queue = ((TwilioPhone)newPhone).CreateQueue(newPhone.Number, 5, string.Empty, 5);
}
#endregion
#region Numbers
public VoipPhone BuyNumber(string phoneNumber)
{
var newNumber = IncomingPhoneNumberResource.Create(
new CreateIncomingPhoneNumberOptions
{
PathAccountSid = _accountSid,
PhoneNumber = new PhoneNumber(phoneNumber)
}, _client);
return new TwilioPhone(_client, _authContext, _tenantUtil, _securityContext, _baseCommonLinkUtility) { Id = newNumber.Sid, Number = phoneNumber.Substring(1) };
}
public VoipPhone DeleteNumber(VoipPhone phone)
{
IncomingPhoneNumberResource.Delete(phone.Id, client: _client);
return phone;
}
public IEnumerable<VoipPhone> GetExistingPhoneNumbers()
{
var result = IncomingPhoneNumberResource.Read(client: _client);
return result.Select(r => new TwilioPhone(_client, _authContext, _tenantUtil, _securityContext, _baseCommonLinkUtility) { Id = r.Sid, Number = r.PhoneNumber.ToString() });
}
public IEnumerable<VoipPhone> GetAvailablePhoneNumbers(PhoneNumberType phoneNumberType, string isoCountryCode)
{
return phoneNumberType switch
{
PhoneNumberType.Local => LocalResource.Read(isoCountryCode, voiceEnabled: true, client: _client).Select(r => new TwilioPhone(_client, _authContext, _tenantUtil, _securityContext, _baseCommonLinkUtility) { Number = r.PhoneNumber.ToString() }),
PhoneNumberType.TollFree => TollFreeResource.Read(isoCountryCode, voiceEnabled: true, client: _client).Select(r => new TwilioPhone(_client, _authContext, _tenantUtil, _securityContext, _baseCommonLinkUtility) { Number = r.PhoneNumber.ToString() }),
_ => new List<VoipPhone>(),
};
}
public VoipPhone GetPhone(string phoneSid)
{
var phone = IncomingPhoneNumberResource.Fetch(phoneSid, client: _client);
var result = new TwilioPhone(_client, _authContext, _tenantUtil, _securityContext, _baseCommonLinkUtility)
{
Id = phone.Sid,
Number = phone.PhoneNumber.ToString(),
Settings = new TwilioVoipSettings(_authContext, _tenantUtil, _securityContext, _baseCommonLinkUtility)
};
if (phone.VoiceUrl == null)
{
result.Settings.VoiceUrl = result.Settings.Connect(false);
}
return result;
}
public VoipPhone GetPhone(VoipNumber data)
{
return new TwilioPhone(_client, _authContext, _tenantUtil, _securityContext, _baseCommonLinkUtility)
{
Id = data.Id,
Number = data.Number,
Alias = data.Alias,
Settings = new TwilioVoipSettings(data.Settings, _authContext)
};
}
public VoipCall GetCall(string callId)
{
var result = new VoipCall { Id = callId };
var count = 6;
while (count > 0)
{
try
{
var call = CallResource.Fetch(result.Id, client: _client);
if (!call.Price.HasValue || string.IsNullOrEmpty(call.Duration))
{
count--;
Thread.Sleep(10000);
continue;
}
}
return result;
result.Price = (-1) * call.Price.Value;
result.DialDuration = Convert.ToInt32(call.Duration);
break;
}
catch (ApiException)
{
count--;
Thread.Sleep(10000);
}
}
public string GetToken(Agent agent, int seconds = 60 * 60 * 24)
{
var scopes = new HashSet<IScope>
return result;
}
public string GetToken(Agent agent, int seconds = 60 * 60 * 24)
{
var scopes = new HashSet<IScope>
{
new IncomingClientScope(agent.ClientID)
};
var capability = new ClientCapability(accountSid, authToken, scopes: scopes);
var capability = new ClientCapability(_accountSid, _authToken, scopes: scopes);
return capability.ToJwt();
}
public void UpdateSettings(VoipPhone phone)
{
IncomingPhoneNumberResource.Update(phone.Id, voiceUrl: new Uri(phone.Settings.Connect(false)), client: client);
}
public void DisablePhone(VoipPhone phone)
{
IncomingPhoneNumberResource.Update(phone.Id, voiceUrl: new Uri("https://demo.twilio.com/welcome/voice/"), client: client);
}
#endregion
return capability.ToJwt();
}
public enum PhoneNumberType
public void UpdateSettings(VoipPhone phone)
{
Local,
/* Mobile,*/
TollFree
IncomingPhoneNumberResource.Update(phone.Id, voiceUrl: new Uri(phone.Settings.Connect(false)), client: _client);
}
}
public void DisablePhone(VoipPhone phone)
{
IncomingPhoneNumberResource.Update(phone.Id, voiceUrl: new Uri("https://demo.twilio.com/welcome/voice/"), client: _client);
}
#endregion
}
public enum PhoneNumberType
{
Local,
/* Mobile,*/
TollFree
}

View File

@ -23,189 +23,187 @@
*
*/
namespace ASC.VoipService.Twilio
namespace ASC.VoipService.Twilio;
public class TwilioResponseHelper
{
public class TwilioResponseHelper
private readonly VoipSettings _settings;
private readonly string _baseUrl;
private readonly AuthContext _authContext;
private readonly TenantUtil _tenantUtil;
private readonly SecurityContext _securityContext;
public TwilioResponseHelper(
VoipSettings settings,
string baseUrl,
AuthContext authContext,
TenantUtil tenantUtil,
SecurityContext securityContext)
{
private readonly VoipSettings settings;
private readonly string baseUrl;
_settings = settings;
_authContext = authContext;
_tenantUtil = tenantUtil;
_securityContext = securityContext;
_baseUrl = baseUrl.TrimEnd('/') + "/twilio/";
}
private AuthContext AuthContext { get; }
private TenantUtil TenantUtil { get; }
private SecurityContext SecurityContext { get; }
public VoiceResponse Inbound(Tuple<Agent, bool> agentTuple)
{
var agent = agentTuple?.Item1;
var anyOnline = agentTuple != null && agentTuple.Item2;
var response = new VoiceResponse();
public TwilioResponseHelper(
VoipSettings settings,
string baseUrl,
AuthContext authContext,
TenantUtil tenantUtil,
SecurityContext securityContext)
if (_settings.WorkingHours != null && _settings.WorkingHours.Enabled)
{
this.settings = settings;
AuthContext = authContext;
TenantUtil = tenantUtil;
SecurityContext = securityContext;
this.baseUrl = baseUrl.TrimEnd('/') + "/twilio/";
var now = _tenantUtil.DateTimeFromUtc(DateTime.UtcNow);
if (!(_settings.WorkingHours.From <= now.TimeOfDay && _settings.WorkingHours.To >= now.TimeOfDay))
{
return AddVoiceMail(response);
}
}
public VoiceResponse Inbound(Tuple<Agent, bool> agentTuple)
if (anyOnline)
{
var agent = agentTuple?.Item1;
var anyOnline = agentTuple != null && agentTuple.Item2;
var response = new VoiceResponse();
if (settings.WorkingHours != null && settings.WorkingHours.Enabled)
if (!string.IsNullOrEmpty(_settings.GreetingAudio))
{
var now = TenantUtil.DateTimeFromUtc(DateTime.UtcNow);
if (!(settings.WorkingHours.From <= now.TimeOfDay && settings.WorkingHours.To >= now.TimeOfDay))
{
return AddVoiceMail(response);
}
response.Play(Uri.EscapeDataString(_settings.GreetingAudio));
}
if (anyOnline)
{
if (!string.IsNullOrEmpty(settings.GreetingAudio))
{
response.Play(Uri.EscapeDataString(settings.GreetingAudio));
}
response.Enqueue(settings.Queue.Name, GetEcho("Enqueue", agent != null), "POST",
GetEcho("Wait", agent != null), "POST");
}
return AddVoiceMail(response);
response.Enqueue(_settings.Queue.Name, GetEcho("Enqueue", agent != null), "POST",
GetEcho("Wait", agent != null), "POST");
}
public VoiceResponse Outbound()
return AddVoiceMail(response);
}
public VoiceResponse Outbound()
{
return !_settings.Caller.AllowOutgoingCalls
? new VoiceResponse()
: AddToResponse(new VoiceResponse(), _settings.Caller);
}
public VoiceResponse Dial()
{
return new VoiceResponse();
}
public VoiceResponse Queue()
{
return new VoiceResponse();
}
public VoiceResponse Enqueue(string queueResult)
{
return queueResult == "leave" ? AddVoiceMail(new VoiceResponse()) : new VoiceResponse();
}
public VoiceResponse Dequeue()
{
return AddToResponse(new VoiceResponse(), _settings.Caller);
}
public VoiceResponse Leave()
{
return AddVoiceMail(new VoiceResponse());
}
public VoiceResponse Wait(string queueTime, string queueSize)
{
var response = new VoiceResponse();
var queue = _settings.Queue;
if (Convert.ToInt32(queueTime) > queue.WaitTime || Convert.ToInt32(queueSize) > queue.Size) return response.Leave();
if (!string.IsNullOrEmpty(queue.WaitUrl))
{
return !settings.Caller.AllowOutgoingCalls
? new VoiceResponse()
: AddToResponse(new VoiceResponse(), settings.Caller);
var gather = new Gather(method: "POST", action: GetEcho("gatherQueue"));
gather.Play(Uri.EscapeDataString(queue.WaitUrl));
response.Gather(gather);
}
public VoiceResponse Dial()
else
{
return new VoiceResponse();
response.Pause(queue.WaitTime);
}
public VoiceResponse Queue()
return response;
}
public VoiceResponse GatherQueue(string digits, List<Agent> availableOperators)
{
var response = new VoiceResponse();
if (digits == "#") return AddVoiceMail(response);
var oper = _settings.Operators.Find(r => r.PostFix == digits && availableOperators.Contains(r)) ??
_settings.Operators.FirstOrDefault(r => availableOperators.Contains(r));
return oper != null ? AddToResponse(response, oper) : response;
}
public VoiceResponse Redirect(string to)
{
if (to == "hold")
{
return new VoiceResponse();
return new VoiceResponse().Play(Uri.EscapeDataString(_settings.HoldAudio), 0);
}
public VoiceResponse Enqueue(string queueResult)
if (Guid.TryParse(to, out var newCallerId))
{
return queueResult == "leave" ? AddVoiceMail(new VoiceResponse()) : new VoiceResponse();
_securityContext.AuthenticateMeWithoutCookie(newCallerId);
}
public VoiceResponse Dequeue()
return new VoiceResponse().Enqueue(_settings.Queue.Name, GetEcho("enqueue"), "POST",
GetEcho("wait") + "&RedirectTo=" + to, "POST");
}
public VoiceResponse VoiceMail()
{
return new VoiceResponse();
}
private VoiceResponse AddToResponse(VoiceResponse response, Agent agent)
{
var dial = new Dial(method: "POST", action: GetEcho("dial"), timeout: agent.TimeOut, record: agent.Record ? "record-from-answer" : "do-not-record");
switch (agent.Answer)
{
return AddToResponse(new VoiceResponse(), settings.Caller);
case AnswerType.Number:
response.Dial(dial.Number(agent.PhoneNumber, method: "POST", url: GetEcho("client")));
break;
case AnswerType.Client:
response.Dial(dial.Client(agent.ClientID, "POST", GetEcho("client")));
break;
case AnswerType.Sip:
response.Dial(dial.Sip(agent.ClientID, method: "POST", url: GetEcho("client")));
break;
}
public VoiceResponse Leave()
return response;
}
private VoiceResponse AddVoiceMail(VoiceResponse response)
{
return string.IsNullOrEmpty(_settings.VoiceMail)
? response.Say("")
: response.Play(Uri.EscapeDataString(_settings.VoiceMail)).Record(method: "POST", action: GetEcho("voiceMail"), maxLength: 30);
}
public string GetEcho(string action, bool user = true)
{
var result = _baseUrl.TrimEnd('/');
if (!string.IsNullOrEmpty(action))
{
return AddVoiceMail(new VoiceResponse());
result += "/" + action.TrimStart('/');
}
public VoiceResponse Wait(string queueTime, string queueSize)
if (user)
{
var response = new VoiceResponse();
var queue = settings.Queue;
if (Convert.ToInt32(queueTime) > queue.WaitTime || Convert.ToInt32(queueSize) > queue.Size) return response.Leave();
if (!string.IsNullOrEmpty(queue.WaitUrl))
{
var gather = new Gather(method: "POST", action: GetEcho("gatherQueue"));
gather.Play(Uri.EscapeDataString(queue.WaitUrl));
response.Gather(gather);
}
else
{
response.Pause(queue.WaitTime);
}
return response;
result += "?CallerId=" + _authContext.CurrentAccount.ID;
}
public VoiceResponse GatherQueue(string digits, List<Agent> availableOperators)
{
var response = new VoiceResponse();
if (digits == "#") return AddVoiceMail(response);
var oper = settings.Operators.Find(r => r.PostFix == digits && availableOperators.Contains(r)) ??
settings.Operators.FirstOrDefault(r => availableOperators.Contains(r));
return oper != null ? AddToResponse(response, oper) : response;
}
public VoiceResponse Redirect(string to)
{
if (to == "hold")
{
return new VoiceResponse().Play(Uri.EscapeDataString(settings.HoldAudio), 0);
}
if (Guid.TryParse(to, out var newCallerId))
{
SecurityContext.AuthenticateMeWithoutCookie(newCallerId);
}
return new VoiceResponse().Enqueue(settings.Queue.Name, GetEcho("enqueue"), "POST",
GetEcho("wait") + "&RedirectTo=" + to, "POST");
}
public VoiceResponse VoiceMail()
{
return new VoiceResponse();
}
private VoiceResponse AddToResponse(VoiceResponse response, Agent agent)
{
var dial = new Dial(method: "POST", action: GetEcho("dial"), timeout: agent.TimeOut, record: agent.Record ? "record-from-answer" : "do-not-record");
switch (agent.Answer)
{
case AnswerType.Number:
response.Dial(dial.Number(agent.PhoneNumber, method: "POST", url: GetEcho("client")));
break;
case AnswerType.Client:
response.Dial(dial.Client(agent.ClientID, "POST", GetEcho("client")));
break;
case AnswerType.Sip:
response.Dial(dial.Sip(agent.ClientID, method: "POST", url: GetEcho("client")));
break;
}
return response;
}
private VoiceResponse AddVoiceMail(VoiceResponse response)
{
return string.IsNullOrEmpty(settings.VoiceMail)
? response.Say("")
: response.Play(Uri.EscapeDataString(settings.VoiceMail)).Record(method: "POST", action: GetEcho("voiceMail"), maxLength: 30);
}
public string GetEcho(string action, bool user = true)
{
var result = baseUrl.TrimEnd('/');
if (!string.IsNullOrEmpty(action))
{
result += "/" + action.TrimStart('/');
}
if (user)
{
result += "?CallerId=" + AuthContext.CurrentAccount.ID;
}
return result;
}
return result;
}
}

View File

@ -26,58 +26,57 @@
using Uri = System.Uri;
namespace ASC.VoipService.Twilio
namespace ASC.VoipService.Twilio;
public class TwilioVoipSettings : VoipSettings
{
public class TwilioVoipSettings : VoipSettings
public TwilioVoipSettings(
AuthContext authContext,
TenantUtil tenantUtil,
SecurityContext securityContext,
BaseCommonLinkUtility baseCommonLinkUtility) :
base(authContext, tenantUtil, securityContext, baseCommonLinkUtility)
{ }
public TwilioVoipSettings(
Uri voiceUrl,
AuthContext authContext,
TenantUtil tenantUtil,
SecurityContext securityContext,
BaseCommonLinkUtility baseCommonLinkUtility) :
this(authContext, tenantUtil, securityContext, baseCommonLinkUtility)
{
public TwilioVoipSettings(
AuthContext authContext,
TenantUtil tenantUtil,
SecurityContext securityContext,
BaseCommonLinkUtility baseCommonLinkUtility) :
base(authContext, tenantUtil, securityContext, baseCommonLinkUtility)
{ }
if (string.IsNullOrEmpty(voiceUrl.Query)) return;
public TwilioVoipSettings(
Uri voiceUrl,
AuthContext authContext,
TenantUtil tenantUtil,
SecurityContext securityContext,
BaseCommonLinkUtility baseCommonLinkUtility) :
this(authContext, tenantUtil, securityContext, baseCommonLinkUtility)
JsonSettings = Encoding.UTF8.GetString(Convert.FromBase64String(HttpUtility.UrlDecode(HttpUtility.ParseQueryString(voiceUrl.Query)["settings"])));
}
public TwilioVoipSettings(string settings, AuthContext authContext) : base(settings, authContext)
{
}
public override string Connect(bool user = true, string contactId = null)
{
var result = GetEcho("", user);
if (!string.IsNullOrEmpty(contactId))
{
if (string.IsNullOrEmpty(voiceUrl.Query)) return;
JsonSettings = Encoding.UTF8.GetString(Convert.FromBase64String(HttpUtility.UrlDecode(HttpUtility.ParseQueryString(voiceUrl.Query)["settings"])));
result += "&ContactId=" + contactId;
}
return result;
}
public TwilioVoipSettings(string settings, AuthContext authContext) : base(settings, authContext)
{
}
public override string Redirect(string to)
{
return GetEcho("redirect") + "&RedirectTo=" + to;
}
public override string Connect(bool user = true, string contactId = null)
{
var result = GetEcho("", user);
if (!string.IsNullOrEmpty(contactId))
{
result += "&ContactId=" + contactId;
}
return result;
}
public override string Dequeue(bool reject)
{
return GetEcho("dequeue") + "&Reject=" + reject;
}
public override string Redirect(string to)
{
return GetEcho("redirect") + "&RedirectTo=" + to;
}
public override string Dequeue(bool reject)
{
return GetEcho("dequeue") + "&Reject=" + reject;
}
private string GetEcho(string method, bool user = true)
{
return new TwilioResponseHelper(this, BaseCommonLinkUtility.GetFullAbsolutePath(""), AuthContext, TenantUtil, SecurityContext).GetEcho(method, user);
}
private string GetEcho(string method, bool user = true)
{
return new TwilioResponseHelper(this, BaseCommonLinkUtility.GetFullAbsolutePath(""), AuthContext, TenantUtil, SecurityContext).GetEcho(method, user);
}
}

View File

@ -23,54 +23,46 @@
*
*/
namespace ASC.VoipService
namespace ASC.VoipService;
public class VoipCall : IMapFrom<CallContact>
{
public class VoipCall
public string Id { get; set; }
public string ParentCallId { get; set; }
public string NumberFrom { get; set; }
public string NumberTo { get; set; }
public Guid AnsweredBy { get; set; }
public DateTime DialDate { get; set; }
public int DialDuration { get; set; }
public VoipCallStatus? Status { get; set; }
public decimal Price { get; set; }
public int ContactId { get; set; }
public bool ContactIsCompany { get; set; }
public string ContactTitle { get; set; }
public DateTime Date { get; set; }
public DateTime EndDialDate { get; set; }
public VoipRecord VoipRecord { get; set; }
public List<VoipCall> ChildCalls { get; set; }
public VoipCall()
{
public string Id { get; set; }
public string ParentID { get; set; }
public string From { get; set; }
public string To { get; set; }
public Guid AnsweredBy { get; set; }
public DateTime DialDate { get; set; }
public int DialDuration { get; set; }
public VoipCallStatus? Status { get; set; }
public decimal Price { get; set; }
public int ContactId { get; set; }
public bool ContactIsCompany { get; set; }
public string ContactTitle { get; set; }
public DateTime Date { get; set; }
public DateTime EndDialDate { get; set; }
public VoipRecord VoipRecord { get; set; }
public List<VoipCall> ChildCalls { get; set; }
public VoipCall()
{
ChildCalls = new List<VoipCall>();
VoipRecord = new VoipRecord();
}
ChildCalls = new List<VoipCall>();
VoipRecord = new VoipRecord();
}
public enum VoipCallStatus
public void Mapping(Profile profile)
{
Incoming,
Outcoming,
Answered,
Missed
profile.CreateMap<DbVoipCall, VoipCall>();
profile.CreateMap<CallContact, VoipCall>()
.ConvertUsing<CallTypeConverter>();
}
}
public enum VoipCallStatus
{
Incoming,
Outcoming,
Answered,
Missed
}

View File

@ -23,142 +23,128 @@
*
*/
namespace ASC.VoipService
namespace ASC.VoipService;
public class Agent
{
public class Agent
public Guid Id { get; set; }
public AnswerType Answer { get; set; }
public string ClientID { get { return PhoneNumber + PostFix; } }
public bool Record { get; set; }
public int TimeOut { get; set; }
public AgentStatus Status { get; set; }
public bool AllowOutgoingCalls { get; set; }
public string PostFix { get; set; }
public string PhoneNumber { get; set; }
public string RedirectToNumber { get; set; }
public Agent()
{
public Guid Id { get; set; }
public AnswerType Answer { get; set; }
public string ClientID { get { return PhoneNumber + PostFix; } }
public bool Record { get; set; }
public int TimeOut { get; set; }
public AgentStatus Status { get; set; }
public bool AllowOutgoingCalls { get; set; }
public string PostFix { get; set; }
public string PhoneNumber { get; set; }
public string RedirectToNumber { get; set; }
public Agent()
{
Status = AgentStatus.Offline;
TimeOut = 30;
AllowOutgoingCalls = true;
Record = true;
}
public Agent(Guid id, AnswerType answer, VoipPhone phone, string postFix)
: this()
{
Id = id;
Answer = answer;
PhoneNumber = phone.Number;
AllowOutgoingCalls = phone.Settings.AllowOutgoingCalls;
Record = phone.Settings.Record;
PostFix = postFix;
}
Status = AgentStatus.Offline;
TimeOut = 30;
AllowOutgoingCalls = true;
Record = true;
}
public class Queue
public Agent(Guid id, AnswerType answer, VoipPhone phone, string postFix)
: this()
{
public string Id { get; set; }
public string Name { get; set; }
public int Size { get; set; }
public string WaitUrl { get; set; }
public int WaitTime { get; set; }
public Queue() { }
public Queue(string id, string name, int size, string waitUrl, int waitTime)
{
Id = id;
Name = name;
WaitUrl = waitUrl;
WaitTime = waitTime;
Size = size;
}
Id = id;
Answer = answer;
PhoneNumber = phone.Number;
AllowOutgoingCalls = phone.Settings.AllowOutgoingCalls;
Record = phone.Settings.Record;
PostFix = postFix;
}
public sealed class WorkingHours
{
public bool Enabled { get; set; }
public TimeSpan? From { get; set; }
public TimeSpan? To { get; set; }
public WorkingHours() { }
public WorkingHours(TimeSpan from, TimeSpan to)
{
From = from;
To = to;
}
private bool Equals(WorkingHours other)
{
return Enabled.Equals(other.Enabled) && From.Equals(other.From) && To.Equals(other.To);
}
public override bool Equals(object obj)
{
if (obj is null) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((WorkingHours)obj);
}
public override int GetHashCode()
{
return HashCode.Combine(Enabled, From, To);
}
}
public class VoipUpload
{
public string Name { get; set; }
public string Path { get; set; }
public AudioType AudioType { get; set; }
public bool IsDefault { get; set; }
}
#region Enum
public enum AnswerType
{
Number,
Sip,
Client
}
public enum GreetingMessageVoice
{
Man,
Woman,
Alice
}
public enum AgentStatus
{
Online,
Paused,
Offline
}
public enum AudioType
{
Greeting,
HoldUp,
VoiceMail,
Queue
}
#endregion
}
public class Queue
{
public string Id { get; set; }
public string Name { get; set; }
public int Size { get; set; }
public string WaitUrl { get; set; }
public int WaitTime { get; set; }
public Queue() { }
public Queue(string id, string name, int size, string waitUrl, int waitTime)
{
Id = id;
Name = name;
WaitUrl = waitUrl;
WaitTime = waitTime;
Size = size;
}
}
public sealed class WorkingHours
{
public bool Enabled { get; set; }
public TimeSpan? From { get; set; }
public TimeSpan? To { get; set; }
public WorkingHours() { }
public WorkingHours(TimeSpan from, TimeSpan to)
{
From = from;
To = to;
}
private bool Equals(WorkingHours other)
{
return Enabled.Equals(other.Enabled) && From.Equals(other.From) && To.Equals(other.To);
}
public override bool Equals(object obj)
{
if (obj is null) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((WorkingHours)obj);
}
public override int GetHashCode()
{
return HashCode.Combine(Enabled, From, To);
}
}
public class VoipUpload
{
public string Name { get; set; }
public string Path { get; set; }
public AudioType AudioType { get; set; }
public bool IsDefault { get; set; }
}
public enum AnswerType
{
Number,
Sip,
Client
}
public enum GreetingMessageVoice
{
Man,
Woman,
Alice
}
public enum AgentStatus
{
Online,
Paused,
Offline
}
public enum AudioType
{
Greeting,
HoldUp,
VoiceMail,
Queue
}

View File

@ -23,63 +23,67 @@
*
*/
namespace ASC.VoipService
using AutoMapper;
namespace ASC.VoipService;
public class VoipPhone
{
public class VoipPhone
public string Id { get; set; }
public string Number { get; set; }
public string Alias { get; set; }
public VoipSettings Settings { get; set; }
public Agent Caller
{
public string Id { get; set; }
public string Number { get; set; }
public string Alias { get; set; }
public VoipSettings Settings { get; set; }
public Agent Caller
{
get { return Settings.Caller; }
}
public VoipPhone(AuthContext authContext, TenantUtil tenantUtil, SecurityContext securityContext, BaseCommonLinkUtility baseCommonLinkUtility)
{
Settings = new VoipSettings(authContext, tenantUtil, securityContext, baseCommonLinkUtility);
}
public virtual VoipCall Call(string to, string contactId = null)
{
throw new NotImplementedException();
}
public virtual VoipCall LocalCall(string to)
{
throw new NotImplementedException();
}
public virtual VoipCall RedirectCall(string callId, string to)
{
throw new NotImplementedException();
}
public virtual VoipCall HoldUp(string callId)
{
throw new NotImplementedException();
}
public virtual void AnswerQueueCall(string callId)
{
throw new NotImplementedException();
}
public virtual void RejectQueueCall(string callId)
{
throw new NotImplementedException();
}
get { return Settings.Caller; }
}
public class VoipRecord
public VoipPhone(AuthContext authContext, TenantUtil tenantUtil, SecurityContext securityContext, BaseCommonLinkUtility baseCommonLinkUtility)
{
public string Id { get; set; }
Settings = new VoipSettings(authContext, tenantUtil, securityContext, baseCommonLinkUtility);
}
public string Uri { get; set; }
public virtual VoipCall Call(string to, string contactId = null)
{
throw new NotImplementedException();
}
public int Duration { get; set; }
public virtual VoipCall LocalCall(string to)
{
throw new NotImplementedException();
}
public decimal Price { get; set; }
public virtual VoipCall RedirectCall(string callId, string to)
{
throw new NotImplementedException();
}
public virtual VoipCall HoldUp(string callId)
{
throw new NotImplementedException();
}
public virtual void AnswerQueueCall(string callId)
{
throw new NotImplementedException();
}
public virtual void RejectQueueCall(string callId)
{
throw new NotImplementedException();
}
}
public class VoipRecord : IMapFrom<DbVoipCall>
{
public string Sid { get; set; }
public string Uri { get; set; }
public int Duration { get; set; }
public decimal Price { get; set; }
public void Mapping(Profile profile)
{
profile.CreateMap<DbVoipCall, VoipRecord>()
.ForMember(dest => dest.Price, opt => opt.MapFrom(src => src.RecordPrice));
}
}

View File

@ -23,165 +23,152 @@
*
*/
namespace ASC.VoipService
namespace ASC.VoipService;
public class VoipSettings
{
public class VoipSettings
public string VoiceUrl { get; set; }
public string Name { get; set; }
public List<Agent> Operators { get; set; }
public Queue Queue { get; set; }
public Agent Caller { get { return Operators.FirstOrDefault(r => r.Id == AuthContext.CurrentAccount.ID); } }
public WorkingHours WorkingHours { get; set; }
public string VoiceMail { get; set; }
public string GreetingAudio { get; set; }
public string HoldAudio { get; set; }
public bool AllowOutgoingCalls { get; set; }
public bool Pause { get; set; }
public bool Record { get; set; }
internal string JsonSettings
{
public string VoiceUrl { get; set; }
public string Name { get; set; }
public List<Agent> Operators { get; set; }
public Queue Queue { get; set; }
public Agent Caller { get { return Operators.FirstOrDefault(r => r.Id == AuthContext.CurrentAccount.ID); } }
public WorkingHours WorkingHours { get; set; }
public string VoiceMail { get; set; }
public string GreetingAudio { get; set; }
public string HoldAudio { get; set; }
public bool AllowOutgoingCalls { get; set; }
public bool Pause { get; set; }
public bool Record { get; set; }
internal string JsonSettings
get
{
get
return JsonConvert.SerializeObject(
new
{
Operators,
GreetingAudio,
Name,
Queue,
WorkingHours,
VoiceMail,
HoldAudio,
AllowOutgoingCalls,
Pause,
Record
},
new JsonSerializerSettings { ContractResolver = CustomSerializeContractResolver.Instance });
}
set
{
try
{
return JsonConvert.SerializeObject(
new
{
Operators,
GreetingAudio,
Name,
Queue,
WorkingHours,
VoiceMail,
HoldAudio,
AllowOutgoingCalls,
Pause,
Record
},
new JsonSerializerSettings { ContractResolver = CustomSerializeContractResolver.Instance });
var settings = JsonConvert.DeserializeObject<VoipSettings>(value, new JsonSerializerSettings { ContractResolver = CustomSerializeContractResolver.Instance });
Operators = settings.Operators ?? new List<Agent>();
Name = settings.Name;
Queue = settings.Queue;
WorkingHours = settings.WorkingHours;
GreetingAudio = settings.GreetingAudio;
VoiceMail = settings.VoiceMail;
HoldAudio = settings.HoldAudio;
AllowOutgoingCalls = settings.AllowOutgoingCalls;
Pause = settings.Pause;
Record = settings.Record;
}
set
catch (Exception)
{
try
{
var settings = JsonConvert.DeserializeObject<VoipSettings>(value, new JsonSerializerSettings { ContractResolver = CustomSerializeContractResolver.Instance });
Operators = settings.Operators ?? new List<Agent>();
Name = settings.Name;
Queue = settings.Queue;
WorkingHours = settings.WorkingHours;
GreetingAudio = settings.GreetingAudio;
VoiceMail = settings.VoiceMail;
HoldAudio = settings.HoldAudio;
AllowOutgoingCalls = settings.AllowOutgoingCalls;
Pause = settings.Pause;
Record = settings.Record;
}
catch (Exception)
{
}
}
}
protected AuthContext AuthContext { get; }
protected TenantUtil TenantUtil { get; }
protected SecurityContext SecurityContext { get; }
protected BaseCommonLinkUtility BaseCommonLinkUtility { get; }
public VoipSettings(AuthContext authContext, TenantUtil tenantUtil, SecurityContext securityContext, BaseCommonLinkUtility baseCommonLinkUtility)
{
Operators = new List<Agent>();
AuthContext = authContext;
TenantUtil = tenantUtil;
SecurityContext = securityContext;
BaseCommonLinkUtility = baseCommonLinkUtility;
}
public VoipSettings(string settings, AuthContext authContext)
{
JsonSettings = settings;
AuthContext = authContext;
}
public virtual string Connect(bool user = true, string contactId = null)
{
throw new NotImplementedException();
}
public virtual string Redirect(string to)
{
throw new NotImplementedException();
}
public virtual string Dequeue(bool reject)
{
throw new NotImplementedException();
}
public override string ToString()
{
return JsonSettings;
}
public VoipSettings GetSettings(string settings)
{
return new VoipSettings(AuthContext, TenantUtil, SecurityContext, BaseCommonLinkUtility) { JsonSettings = settings };
}
}
class CustomSerializeContractResolver : CamelCasePropertyNamesContractResolver
protected AuthContext AuthContext { get; }
protected TenantUtil TenantUtil { get; }
protected SecurityContext SecurityContext { get; }
protected BaseCommonLinkUtility BaseCommonLinkUtility { get; }
public VoipSettings(AuthContext authContext, TenantUtil tenantUtil, SecurityContext securityContext, BaseCommonLinkUtility baseCommonLinkUtility)
{
public static readonly CustomSerializeContractResolver Instance = new CustomSerializeContractResolver();
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.PropertyName == "voiceMail")
{
property.Converter = new VoiceMailConverter();
}
return property;
}
Operators = new List<Agent>();
AuthContext = authContext;
TenantUtil = tenantUtil;
SecurityContext = securityContext;
BaseCommonLinkUtility = baseCommonLinkUtility;
}
class VoiceMailConverter : JsonConverter
public VoipSettings(string settings, AuthContext authContext)
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
JsonSettings = settings;
AuthContext = authContext;
}
public virtual string Connect(bool user = true, string contactId = null)
{
throw new NotImplementedException();
}
public virtual string Redirect(string to)
{
throw new NotImplementedException();
}
public virtual string Dequeue(bool reject)
{
throw new NotImplementedException();
}
public override string ToString()
{
return JsonSettings;
}
public VoipSettings GetSettings(string settings)
{
return new VoipSettings(AuthContext, TenantUtil, SecurityContext, BaseCommonLinkUtility) { JsonSettings = settings };
}
}
class CustomSerializeContractResolver : CamelCasePropertyNamesContractResolver
{
public static readonly CustomSerializeContractResolver Instance = new CustomSerializeContractResolver();
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var property = base.CreateProperty(member, memberSerialization);
if (property.PropertyName == "voiceMail")
{
serializer.Serialize(writer, value);
property.Converter = new VoiceMailConverter();
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
return property;
}
}
class VoiceMailConverter : JsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
serializer.Serialize(writer, value);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.ValueType != null && reader.ValueType.Name == "String")
{
if (reader.ValueType != null && reader.ValueType.Name == "String")
{
return reader.Value;
}
var jObject = JObject.Load(reader);
var url = jObject.Value<string>("url");
return !string.IsNullOrEmpty(url) ? url : "";
return reader.Value;
}
public override bool CanConvert(Type objectType)
{
return true;
}
var jObject = JObject.Load(reader);
var url = jObject.Value<string>("url");
return !string.IsNullOrEmpty(url) ? url : "";
}
public override bool CanConvert(Type objectType)
{
return true;
}
}

View File

@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">

View File

@ -1,8 +1,4 @@
global using System;
global using System.Collections.Generic;
global using System.Linq;
global using ASC.Common;
global using ASC.Common;
global using ASC.Common.Caching;
global using ASC.Common.Logging;
global using ASC.Core;

View File

@ -2,6 +2,7 @@
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">

View File

@ -1,37 +1,31 @@
global using System;
global using System.Collections.Generic;
global using System.Globalization;
global using System.IO;
global using System.Linq;
global using System.Text;
global using System.Globalization;
global using System.Reflection;
global using System.Text;
global using ASC.AuditTrail.Attributes;
global using ASC.AuditTrail.Mappers;
global using ASC.AuditTrail.Models;
global using ASC.AuditTrail.Models.Mappings;
global using ASC.Common;
global using ASC.Common.Logging;
global using ASC.Common.Mapping;
global using ASC.Core.Common.EF;
global using ASC.Core.Users;
global using ASC.MessagingSystem.Core;
global using ASC.MessagingSystem.Data;
global using ASC.MessagingSystem.Models;
global using ASC.Web.Studio.Utility;
global using ASC.AuditTrail.Attributes;
global using ASC.Web.Core.Files;
global using ASC.Web.Files.Classes;
global using ASC.Web.Files.Utils;
global using ASC.AuditTrail.Models;
global using ASC.AuditTrail.Models.Mappings;
global using ASC.Common.Mapping;
global using ASC.Web.Studio.Utility;
global using Autofac;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.Options;
global using Newtonsoft.Json;
global using AutoMapper;
global using CsvHelper;
global using CsvHelper.Configuration;
global using CsvHelper.Configuration;
global using Microsoft.Extensions.Options;
global using Newtonsoft.Json;

View File

@ -5,6 +5,7 @@
<ApplicationIcon />
<OutputType>Exe</OutputType>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">

View File

@ -1,32 +1,22 @@
global using System;
global using System.IO;
global using System.Collections.Generic;
global using System.Linq;
global using System.Linq.Expressions;
global using System.Threading;
global using System.Threading.Tasks;
global using System.Linq.Expressions;
global using ASC.Api.Core;
global using ASC.ClearEvents.Services;
global using ASC.Common;
global using ASC.Common.Utils;
global using ASC.Common.Caching;
global using ASC.Common.DependencyInjection;
global using ASC.Common.Logging;
global using ASC.Common.Utils;
global using ASC.Core.Common.EF;
global using ASC.Core.Tenants;
global using ASC.Core.Tenants;
global using ASC.MessagingSystem.Data;
global using ASC.MessagingSystem.Models;
global using Autofac;
global using Autofac.Extensions.DependencyInjection;
global using Microsoft.EntityFrameworkCore;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.AspNetCore.Builder;
global using Microsoft.Extensions.Hosting.WindowsServices;
global using Microsoft.Extensions.Hosting;
global using Microsoft.Extensions.Options;
global using StackExchange.Redis.Extensions.Core.Configuration;

View File

@ -6,7 +6,8 @@
<OutputType>Exe</OutputType>
<RazorCompileOnBuild>false</RazorCompileOnBuild>
<GenerateMvcApplicationPartsAssemblyAttributes>false</GenerateMvcApplicationPartsAssemblyAttributes>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">

View File

@ -1,10 +1,4 @@
global using System;
global using System.Collections.Generic;
global using System.IO;
global using System.Linq;
global using System.Runtime.InteropServices;
global using System.Threading;
global using System.Threading.Tasks;
global using System.Runtime.InteropServices;
global using ASC.Api.Collections;
global using ASC.Api.Core;
@ -18,7 +12,6 @@ global using ASC.Core.Billing;
global using ASC.Data.Backup;
global using ASC.Data.Backup.ApiModels;
global using ASC.Data.Backup.Contracts;
global using ASC.Data.Backup.Controllers;
global using ASC.Data.Backup.Services;
global using ASC.Data.Backup.Storage;
global using ASC.Files.Core;
@ -29,12 +22,7 @@ global using ASC.Web.Studio.Utility;
global using Autofac;
global using Autofac.Extensions.DependencyInjection;
global using Microsoft.AspNetCore.Builder;
global using Microsoft.AspNetCore.Hosting;
global using Microsoft.AspNetCore.Mvc;
global using Microsoft.Extensions.Configuration;
global using Microsoft.Extensions.DependencyInjection;
global using Microsoft.Extensions.Hosting;
global using Microsoft.Extensions.Options;
global using static ASC.Data.Backup.BackupAjaxHandler;

View File

@ -10,6 +10,7 @@
<Product>ASC.ElasticSearch</Product>
<Copyright>(c) Ascensio System SIA. All rights reserved</Copyright>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugType>full</DebugType>

Some files were not shown because too many files have changed in this diff Show More