Browse Source

Add DocumentPdfGeneratorAppService to generate PDF

pull/22430/head
liangshiwei 2 years ago
parent
commit
14c842363c
  1. 5
      Directory.Packages.props
  2. 42
      modules/docs/Volo.Docs.sln
  3. 6
      modules/docs/app/VoloDocs.Web/VoloDocsWebModule.cs
  4. 1
      modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo.Docs.Admin.Application.Contracts.csproj
  5. 4
      modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/DocsAdminApplicationContractsModule.cs
  6. 1
      modules/docs/src/Volo.Docs.Admin.Application/Volo.Docs.Admin.Application.csproj
  7. 2
      modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/DocsAdminApplicationModule.cs
  8. 1
      modules/docs/src/Volo.Docs.Admin.HttpApi/Volo.Docs.Admin.HttpApi.csproj
  9. 4
      modules/docs/src/Volo.Docs.Admin.HttpApi/Volo/Docs/Admin/DocsAdminHttpApiModule.cs
  10. 2
      modules/docs/src/Volo.Docs.Common.Application.Contracts/Volo/Docs/Common/DocsCommonPermissionDefinitionProvider.cs
  11. 2
      modules/docs/src/Volo.Docs.Common.Application.Contracts/Volo/Docs/Common/DocsCommonPermissions.cs
  12. 3
      modules/docs/src/Volo.Docs.Common.Application/Volo.Docs.Common.Application.csproj
  13. 2
      modules/docs/src/Volo.Docs.Common.Application/Volo/Docs/Common/Documents/DocsDocumentPdfContainer.cs
  14. 64
      modules/docs/src/Volo.Docs.Common.Application/Volo/Docs/Common/Documents/DocsDocumentPdfGeneratorOptions.cs
  15. 419
      modules/docs/src/Volo.Docs.Common.Application/Volo/Docs/Common/Documents/DocumentPdfGenerator.cs
  16. 84
      modules/docs/src/Volo.Docs.Common.Application/Volo/Docs/Common/Documents/DocumentPdfGeneratorAppService.cs
  17. 41
      modules/docs/src/Volo.Docs.Common.Application/Volo/Docs/Common/Documents/HtmlIdTagWorkerFactory.cs
  18. 11
      modules/docs/src/Volo.Docs.Common.Application/Volo/Docs/Common/Documents/IDocumentPdfGenerator.cs
  19. 19
      modules/docs/src/Volo.Docs.Common.HttpApi.Client/Volo.Docs.Common.HttpApi.Client.csproj
  20. 18
      modules/docs/src/Volo.Docs.Common.HttpApi/Volo.Docs.Common.HttpApi.csproj
  21. 35
      modules/docs/src/Volo.Docs.Common.HttpApi/Volo/Docs/DocsCommonHttpApiModule.cs
  22. 12
      modules/docs/src/Volo.Docs.Common.HttpApi/Volo/Docs/DocsControllerBase.cs
  23. 30
      modules/docs/src/Volo.Docs.Common.HttpApi/Volo/Docs/Documents/DocsDocumentPdfGeneratorController.cs
  24. 1
      modules/docs/src/Volo.Docs.Domain.Shared/Volo.Docs.Domain.Shared.csproj
  25. 23
      modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/DocumentParams.cs
  26. 2
      modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/Rendering/DocumentPartialTemplateContent.cs
  27. 10
      modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/Rendering/DocumentPartialTemplateWithValues.cs
  28. 20
      modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/Rendering/DocumentRenderParameters.cs
  29. 12
      modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/Rendering/IDocumentSectionRenderer.cs
  30. 302
      modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/Rendering/ScribanDocumentSectionRenderer.cs
  31. 3
      modules/docs/src/Volo.Docs.Web/DocsWebAutoMapperProfile.cs
  32. 11
      modules/docs/src/Volo.Docs.Web/HtmlConverting/DocumentPartialTemplateWithValuesDto.cs
  33. 8
      modules/docs/src/Volo.Docs.Web/HtmlConverting/DocumentRenderParameters.cs
  34. 17
      modules/docs/src/Volo.Docs.Web/HtmlConverting/IDocumentSectionRenderer.cs
  35. 14
      modules/docs/src/Volo.Docs.Web/HtmlConverting/IWebDocumentSectionRenderer.cs
  36. 316
      modules/docs/src/Volo.Docs.Web/HtmlConverting/ScribanDocumentSectionRenderer.cs
  37. 38
      modules/docs/src/Volo.Docs.Web/HtmlConverting/ScribanWebDocumentSectionRenderer.cs
  38. 43
      modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs
  39. 2
      modules/docs/src/Volo.Docs.Web/Volo.Docs.Web.csproj

5
Directory.Packages.props

@ -38,6 +38,9 @@
<PackageVersion Include="EphemeralMongo6.runtime.linux-x64" Version="1.1.3" /> <PackageVersion Include="EphemeralMongo6.runtime.linux-x64" Version="1.1.3" />
<PackageVersion Include="EphemeralMongo6.runtime.osx-x64" Version="1.1.3" /> <PackageVersion Include="EphemeralMongo6.runtime.osx-x64" Version="1.1.3" />
<PackageVersion Include="EphemeralMongo6.runtime.win-x64" Version="1.1.3" /> <PackageVersion Include="EphemeralMongo6.runtime.win-x64" Version="1.1.3" />
<PackageVersion Include="itext.bouncy-castle-adapter" Version="9.1.0" />
<PackageVersion Include="itext7" Version="9.1.0" />
<PackageVersion Include="itext7.pdfhtml" Version="6.1.0" />
<PackageVersion Include="MongoSandbox.Core" Version="1.0.1" /> <PackageVersion Include="MongoSandbox.Core" Version="1.0.1" />
<PackageVersion Include="MongoSandbox6.runtime.linux-x64" Version="1.0.1" /> <PackageVersion Include="MongoSandbox6.runtime.linux-x64" Version="1.0.1" />
<PackageVersion Include="MongoSandbox6.runtime.osx-x64" Version="1.0.1" /> <PackageVersion Include="MongoSandbox6.runtime.osx-x64" Version="1.0.1" />
@ -183,4 +186,4 @@
<PackageVersion Include="ConfigureAwait.Fody" Version="3.3.2" /> <PackageVersion Include="ConfigureAwait.Fody" Version="3.3.2" />
<PackageVersion Include="Fody" Version="6.8.2" /> <PackageVersion Include="Fody" Version="6.8.2" />
</ItemGroup> </ItemGroup>
</Project> </Project>

42
modules/docs/Volo.Docs.sln

@ -63,11 +63,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Docs.MongoDB.Tests", "
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Docs.Installer", "src\Volo.Docs.Installer\Volo.Docs.Installer.csproj", "{50B9AC1D-C03E-47AA-9ED8-E7986BCFABA1}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Docs.Installer", "src\Volo.Docs.Installer\Volo.Docs.Installer.csproj", "{50B9AC1D-C03E-47AA-9ED8-E7986BCFABA1}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{AA7E58C3-E223-455F-81BD-4D2E2E152624}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Docs.Common.Application", "src\Volo.Docs.Common.Application\Volo.Docs.Common.Application.csproj", "{914EDEC4-EFCE-46D5-B6E0-A928B1907307}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Docs.Common.Application.Contracts", "src\Volo.Docs.Common.Application.Contracts\Volo.Docs.Common.Application.Contracts.csproj", "{D75944C2-FAB0-41A8-AB2A-1A2F97849302}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Docs.Common.Application.Contracts", "src\Volo.Docs.Common.Application.Contracts\Volo.Docs.Common.Application.Contracts.csproj", "{8CA19527-6870-4617-B6D1-EA8CAF224DF4}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Docs.Common.Application", "src\Volo.Docs.Common.Application\Volo.Docs.Common.Application.csproj", "{A96F4615-8405-493D-AA06-727D6F29B5F5}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "common", "common", "{F778B2C3-2E6D-4CF3-ACA7-10E21E6A7961}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Docs.Common.HttpApi", "src\Volo.Docs.Common.HttpApi\Volo.Docs.Common.HttpApi.csproj", "{052C076D-56FF-4DFA-838B-48BD30A1DB0A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Docs.Common.HttpApi.Client", "src\Volo.Docs.Common.HttpApi.Client\Volo.Docs.Common.HttpApi.Client.csproj", "{349C36A0-552D-44A3-AC74-03A4828D43AB}"
EndProject EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -171,14 +175,22 @@ Global
{50B9AC1D-C03E-47AA-9ED8-E7986BCFABA1}.Debug|Any CPU.Build.0 = Debug|Any CPU {50B9AC1D-C03E-47AA-9ED8-E7986BCFABA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{50B9AC1D-C03E-47AA-9ED8-E7986BCFABA1}.Release|Any CPU.ActiveCfg = Release|Any CPU {50B9AC1D-C03E-47AA-9ED8-E7986BCFABA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{50B9AC1D-C03E-47AA-9ED8-E7986BCFABA1}.Release|Any CPU.Build.0 = Release|Any CPU {50B9AC1D-C03E-47AA-9ED8-E7986BCFABA1}.Release|Any CPU.Build.0 = Release|Any CPU
{D75944C2-FAB0-41A8-AB2A-1A2F97849302}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {914EDEC4-EFCE-46D5-B6E0-A928B1907307}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D75944C2-FAB0-41A8-AB2A-1A2F97849302}.Debug|Any CPU.Build.0 = Debug|Any CPU {914EDEC4-EFCE-46D5-B6E0-A928B1907307}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D75944C2-FAB0-41A8-AB2A-1A2F97849302}.Release|Any CPU.ActiveCfg = Release|Any CPU {914EDEC4-EFCE-46D5-B6E0-A928B1907307}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D75944C2-FAB0-41A8-AB2A-1A2F97849302}.Release|Any CPU.Build.0 = Release|Any CPU {914EDEC4-EFCE-46D5-B6E0-A928B1907307}.Release|Any CPU.Build.0 = Release|Any CPU
{A96F4615-8405-493D-AA06-727D6F29B5F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8CA19527-6870-4617-B6D1-EA8CAF224DF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A96F4615-8405-493D-AA06-727D6F29B5F5}.Debug|Any CPU.Build.0 = Debug|Any CPU {8CA19527-6870-4617-B6D1-EA8CAF224DF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A96F4615-8405-493D-AA06-727D6F29B5F5}.Release|Any CPU.ActiveCfg = Release|Any CPU {8CA19527-6870-4617-B6D1-EA8CAF224DF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A96F4615-8405-493D-AA06-727D6F29B5F5}.Release|Any CPU.Build.0 = Release|Any CPU {8CA19527-6870-4617-B6D1-EA8CAF224DF4}.Release|Any CPU.Build.0 = Release|Any CPU
{052C076D-56FF-4DFA-838B-48BD30A1DB0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{052C076D-56FF-4DFA-838B-48BD30A1DB0A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{052C076D-56FF-4DFA-838B-48BD30A1DB0A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{052C076D-56FF-4DFA-838B-48BD30A1DB0A}.Release|Any CPU.Build.0 = Release|Any CPU
{349C36A0-552D-44A3-AC74-03A4828D43AB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{349C36A0-552D-44A3-AC74-03A4828D43AB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{349C36A0-552D-44A3-AC74-03A4828D43AB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{349C36A0-552D-44A3-AC74-03A4828D43AB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -211,9 +223,11 @@ Global
{DBE846CD-1BED-4F2C-ABF2-94F6240BCB9B} = {A982A58E-1E92-4764-9F56-39E7AABB8556} {DBE846CD-1BED-4F2C-ABF2-94F6240BCB9B} = {A982A58E-1E92-4764-9F56-39E7AABB8556}
{C5E2A2A3-D54D-4C2E-97BA-EA50A49ED7AD} = {59D430A9-AC61-4457-8338-5DA0705ABB5D} {C5E2A2A3-D54D-4C2E-97BA-EA50A49ED7AD} = {59D430A9-AC61-4457-8338-5DA0705ABB5D}
{50B9AC1D-C03E-47AA-9ED8-E7986BCFABA1} = {A982A58E-1E92-4764-9F56-39E7AABB8556} {50B9AC1D-C03E-47AA-9ED8-E7986BCFABA1} = {A982A58E-1E92-4764-9F56-39E7AABB8556}
{AA7E58C3-E223-455F-81BD-4D2E2E152624} = {42416152-5BAB-4706-93A6-57A19E71FE14} {F778B2C3-2E6D-4CF3-ACA7-10E21E6A7961} = {42416152-5BAB-4706-93A6-57A19E71FE14}
{D75944C2-FAB0-41A8-AB2A-1A2F97849302} = {AA7E58C3-E223-455F-81BD-4D2E2E152624} {914EDEC4-EFCE-46D5-B6E0-A928B1907307} = {F778B2C3-2E6D-4CF3-ACA7-10E21E6A7961}
{A96F4615-8405-493D-AA06-727D6F29B5F5} = {AA7E58C3-E223-455F-81BD-4D2E2E152624} {8CA19527-6870-4617-B6D1-EA8CAF224DF4} = {F778B2C3-2E6D-4CF3-ACA7-10E21E6A7961}
{052C076D-56FF-4DFA-838B-48BD30A1DB0A} = {F778B2C3-2E6D-4CF3-ACA7-10E21E6A7961}
{349C36A0-552D-44A3-AC74-03A4828D43AB} = {F778B2C3-2E6D-4CF3-ACA7-10E21E6A7961}
EndGlobalSection EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {13691265-2547-4FFF-B757-E8FACB05679D} SolutionGuid = {13691265-2547-4FFF-B757-E8FACB05679D}

6
modules/docs/app/VoloDocs.Web/VoloDocsWebModule.cs

@ -36,6 +36,7 @@ using Volo.Abp.PermissionManagement.HttpApi;
using Volo.Abp.Validation.Localization; using Volo.Abp.Validation.Localization;
using Volo.Docs.Documents.FullSearch.Elastic; using Volo.Docs.Documents.FullSearch.Elastic;
using Volo.Abp.Caching.StackExchangeRedis; using Volo.Abp.Caching.StackExchangeRedis;
using Volo.Docs.Common.Documents;
namespace VoloDocs.Web namespace VoloDocs.Web
{ {
@ -170,6 +171,11 @@ namespace VoloDocs.Web
options.EnableGoogleProgrammableSearchEngine = true; options.EnableGoogleProgrammableSearchEngine = true;
options.GoogleSearchEngineId = "77c7266532da1427f"; options.GoogleSearchEngineId = "77c7266532da1427f";
}); });
Configure<DocsDocumentPdfGeneratorOptions>(options =>
{
options.BaseUrl = configuration["App:selfUrl"];
});
} }
public override void OnApplicationInitialization(ApplicationInitializationContext context) public override void OnApplicationInitialization(ApplicationInitializationContext context)

1
modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo.Docs.Admin.Application.Contracts.csproj

@ -18,6 +18,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Volo.Docs.Domain.Shared\Volo.Docs.Domain.Shared.csproj" /> <ProjectReference Include="..\Volo.Docs.Domain.Shared\Volo.Docs.Domain.Shared.csproj" />
<ProjectReference Include="..\Volo.Docs.Common.Application.Contracts\Volo.Docs.Common.Application.Contracts.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Ddd.Application.Contracts\Volo.Abp.Ddd.Application.Contracts.csproj" /> <ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Ddd.Application.Contracts\Volo.Abp.Ddd.Application.Contracts.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Authorization.Abstractions\Volo.Abp.Authorization.Abstractions.csproj" /> <ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Authorization.Abstractions\Volo.Abp.Authorization.Abstractions.csproj" />
</ItemGroup> </ItemGroup>

4
modules/docs/src/Volo.Docs.Admin.Application.Contracts/Volo/Docs/Admin/DocsAdminApplicationContractsModule.cs

@ -4,6 +4,7 @@ using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Localization; using Volo.Abp.Localization;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem; using Volo.Abp.VirtualFileSystem;
using Volo.Docs.Common;
using Volo.Docs.Localization; using Volo.Docs.Localization;
namespace Volo.Docs.Admin namespace Volo.Docs.Admin
@ -11,7 +12,8 @@ namespace Volo.Docs.Admin
[DependsOn( [DependsOn(
typeof(DocsDomainSharedModule), typeof(DocsDomainSharedModule),
typeof(AbpDddApplicationContractsModule), typeof(AbpDddApplicationContractsModule),
typeof(AbpAuthorizationAbstractionsModule) typeof(AbpAuthorizationAbstractionsModule),
typeof(DocsCommonApplicationContractsModule)
)] )]
public class DocsAdminApplicationContractsModule : AbpModule public class DocsAdminApplicationContractsModule : AbpModule
{ {

1
modules/docs/src/Volo.Docs.Admin.Application/Volo.Docs.Admin.Application.csproj

@ -12,6 +12,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Volo.Docs.Admin.Application.Contracts\Volo.Docs.Admin.Application.Contracts.csproj" /> <ProjectReference Include="..\Volo.Docs.Admin.Application.Contracts\Volo.Docs.Admin.Application.Contracts.csproj" />
<ProjectReference Include="..\Volo.Docs.Common.Application\Volo.Docs.Common.Application.csproj" />
<ProjectReference Include="..\Volo.Docs.Domain\Volo.Docs.Domain.csproj" /> <ProjectReference Include="..\Volo.Docs.Domain\Volo.Docs.Domain.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AutoMapper\Volo.Abp.AutoMapper.csproj" /> <ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AutoMapper\Volo.Abp.AutoMapper.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Caching\Volo.Abp.Caching.csproj" /> <ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Caching\Volo.Abp.Caching.csproj" />

2
modules/docs/src/Volo.Docs.Admin.Application/Volo/Docs/Admin/DocsAdminApplicationModule.cs

@ -3,12 +3,14 @@ using Volo.Abp.Application;
using Volo.Abp.AutoMapper; using Volo.Abp.AutoMapper;
using Volo.Abp.Caching; using Volo.Abp.Caching;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Docs.Common;
namespace Volo.Docs.Admin namespace Volo.Docs.Admin
{ {
[DependsOn( [DependsOn(
typeof(DocsDomainModule), typeof(DocsDomainModule),
typeof(DocsAdminApplicationContractsModule), typeof(DocsAdminApplicationContractsModule),
typeof(DocsCommonApplicationModule),
typeof(AbpCachingModule), typeof(AbpCachingModule),
typeof(AbpAutoMapperModule), typeof(AbpAutoMapperModule),
typeof(AbpDddApplicationModule) typeof(AbpDddApplicationModule)

1
modules/docs/src/Volo.Docs.Admin.HttpApi/Volo.Docs.Admin.HttpApi.csproj

@ -12,6 +12,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Volo.Docs.Admin.Application.Contracts\Volo.Docs.Admin.Application.Contracts.csproj" /> <ProjectReference Include="..\Volo.Docs.Admin.Application.Contracts\Volo.Docs.Admin.Application.Contracts.csproj" />
<ProjectReference Include="..\Volo.Docs.Common.HttpApi\Volo.Docs.Common.HttpApi.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc\Volo.Abp.AspNetCore.Mvc.csproj" /> <ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc\Volo.Abp.AspNetCore.Mvc.csproj" />
</ItemGroup> </ItemGroup>

4
modules/docs/src/Volo.Docs.Admin.HttpApi/Volo/Docs/Admin/DocsAdminHttpApiModule.cs

@ -4,12 +4,14 @@ using Volo.Abp.Localization;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Docs.Localization; using Volo.Docs.Localization;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Docs.Common;
namespace Volo.Docs.Admin namespace Volo.Docs.Admin
{ {
[DependsOn( [DependsOn(
typeof(DocsAdminApplicationContractsModule), typeof(DocsAdminApplicationContractsModule),
typeof(AbpAspNetCoreMvcModule) typeof(AbpAspNetCoreMvcModule),
typeof(DocsCommonHttpApiModule)
)] )]
public class DocsAdminHttpApiModule : AbpModule public class DocsAdminHttpApiModule : AbpModule
{ {

2
modules/docs/src/Volo.Docs.Common.Application.Contracts/Volo/Docs/Common/DocsCommonPermissionDefinitionProvider.cs

@ -10,7 +10,7 @@ namespace Volo.Docs.Common
{ {
var group = context.AddGroup(DocsCommonPermissions.GroupName, L("Permission:DocumentManagement.Common")); var group = context.AddGroup(DocsCommonPermissions.GroupName, L("Permission:DocumentManagement.Common"));
group.AddPermission(DocsCommonPermissions.Documents.PdfCreation, L("Permission:PdfCreation")); group.AddPermission(DocsCommonPermissions.Documents.PdfGeneration, L("Permission:PdfGeneration"));
} }
private static LocalizableString L(string name) private static LocalizableString L(string name)

2
modules/docs/src/Volo.Docs.Common.Application.Contracts/Volo/Docs/Common/DocsCommonPermissions.cs

@ -8,7 +8,7 @@ namespace Volo.Docs.Common
public static class Documents public static class Documents
{ {
public const string PdfCreation = GroupName + ".PdfCreation"; public const string PdfGeneration = GroupName + ".PdfGeneration";
} }
public static string[] GetAll() public static string[] GetAll()

3
modules/docs/src/Volo.Docs.Common.Application/Volo.Docs.Common.Application.csproj

@ -17,6 +17,9 @@
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Caching\Volo.Abp.Caching.csproj" /> <ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Caching\Volo.Abp.Caching.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Ddd.Application\Volo.Abp.Ddd.Application.csproj" /> <ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Ddd.Application\Volo.Abp.Ddd.Application.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.BlobStoring\Volo.Abp.BlobStoring.csproj" /> <ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.BlobStoring\Volo.Abp.BlobStoring.csproj" />
<PackageReference Include="itext.bouncy-castle-adapter" />
<PackageReference Include="itext7" />
<PackageReference Include="itext7.pdfhtml" />
<PackageReference Include="Markdig.Signed" /> <PackageReference Include="Markdig.Signed" />
<PackageReference Include="Scriban" /> <PackageReference Include="Scriban" />
</ItemGroup> </ItemGroup>

2
modules/docs/src/Volo.Docs.Common.Application/Volo/Docs/Common/DocsDocumentPdfContainer.cs → modules/docs/src/Volo.Docs.Common.Application/Volo/Docs/Common/Documents/DocsDocumentPdfContainer.cs

@ -1,6 +1,6 @@
using Volo.Abp.BlobStoring; using Volo.Abp.BlobStoring;
namespace Volo.Docs.Common; namespace Volo.Docs.Common.Documents;
[BlobContainerName("docs-document-pdf")] [BlobContainerName("docs-document-pdf")]
public class DocsDocumentPdfContainer public class DocsDocumentPdfContainer

64
modules/docs/src/Volo.Docs.Common.Application/Volo/Docs/Common/Documents/DocsDocumentPdfGeneratorOptions.cs

@ -0,0 +1,64 @@
namespace Volo.Docs.Common.Documents;
public class DocsDocumentPdfGeneratorOptions
{
public string HtmlLayout { get; set; }
public string HtmlStyles { get; set; }
public string BaseUrl { get; set; }
public DocsDocumentPdfGeneratorOptions()
{
HtmlLayout = @"
<!DOCTYPE html>
<head>
<meta charset='utf-8' />
<style>
{{style-placeholder}}
</style>
</head>
<body>
{{content-placeholder}}
</body>
</html>";
HtmlStyles = @"
body { margin: 20px; line-height: 1.6; font-family: Arial, sans-serif;}
a { text-decoration: none; }
.page {
page-break-after: always;
margin-bottom: 30px;
padding: 15px;
}
img {
max-width:100%;
height: auto;
display: block;
}
code, pre {
background: #f8fafc;
white-space: pre-wrap;
word-wrap: break-word;
}
pre code { padding: 0; border: none; }
code {
padding: 2px 6px;
border-radius: 4px;
border: 1px solid #e2e8f0;
font-family: Consolas, monospace;
}
pre {
position: relative;
padding: 16px;
padding-top: calc(16px * 2.5);
border-radius: 8px;
border: 1px solid #e2e8f0;
}
blockquote {
border-left: 4px solid #e2e8f0;
margin: 20px 0;
padding: 10px 20px;
background: #f8fafc;
}";
}
}

419
modules/docs/src/Volo.Docs.Common.Application/Volo/Docs/Common/Documents/DocumentPdfGenerator.cs

@ -0,0 +1,419 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Web;
using iText.Html2pdf;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Action;
using Markdig;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using Volo.Abp;
using Volo.Abp.Content;
using Volo.Docs.Documents;
using Volo.Docs.Documents.Rendering;
using Volo.Docs.Projects;
using Volo.Extensions;
using ITextDocument = iText.Layout.Document;
namespace Volo.Docs.Common.Documents;
public class DocumentPdfGenerator : IDocumentPdfGenerator
{
protected IDocumentSourceFactory DocumentStoreFactory { get; }
protected IDocumentRepository DocumentRepository { get; }
protected IDocumentSectionRenderer DocumentSectionRenderer { get; }
protected IOptions<DocsDocumentPdfGeneratorOptions> Options { get; }
protected ILogger<DocumentPdfGenerator> Logger { get; set; }
protected IDocumentSource DocumentSource { get; set; }
protected DocumentParams DocumentParams { get; set; }
protected List<PdfDocumentNode> AllPdfDocumentNodes { get; set; } = new();
protected MarkdownPipeline MarkdownPipeline { get; } = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
public DocumentPdfGenerator(
IDocumentSourceFactory documentStoreFactory,
IDocumentRepository documentRepository,
IOptions<DocsDocumentPdfGeneratorOptions> options,
IDocumentSectionRenderer documentSectionRenderer)
{
DocumentStoreFactory = documentStoreFactory;
DocumentRepository = documentRepository;
Options = options;
DocumentSectionRenderer = documentSectionRenderer;
Logger = NullLogger<DocumentPdfGenerator>.Instance;
}
public virtual async Task<IRemoteStreamContent> GenerateAsync(Project project, string version, string languageCode)
{
DocumentSource = DocumentStoreFactory.Create(project.DocumentStoreType);
DocumentParams = await GetDocumentParamsAsync(project, version, languageCode);
var navigation = await GetNavigationAsync(project, version, languageCode);
await SetAllPdfDocumentNodesAsync(navigation.Items, project, version, languageCode);
var htmlContent = await ConvertDocumentsToHtmlAsync(AllPdfDocumentNodes);
var pdfStream = await GeneratePdfAsync(htmlContent);
return new RemoteStreamContent(pdfStream, CalculateDocumentPdfFileName(project.ShortName, version, languageCode), "application/pdf");
}
protected virtual async Task<string> ConvertDocumentsToHtmlAsync(List<PdfDocumentNode> pdfDocumentNodes)
{
var contentBuilder = new StringBuilder();
foreach (var pdfDocumentNode in pdfDocumentNodes)
{
if (pdfDocumentNode.Document != null)
{
var renderedDocument = await RenderDocumentAsync(pdfDocumentNode);
renderedDocument = NormalizeHtmlContent(pdfDocumentNode, Markdown.ToHtml(renderedDocument, MarkdownPipeline));
contentBuilder.AppendLine(renderedDocument);
}
if (pdfDocumentNode.HasChildren)
{
contentBuilder.AppendLine(await ConvertDocumentsToHtmlAsync(pdfDocumentNode.Children));
}
}
return contentBuilder.ToString();
}
protected virtual Task<MemoryStream> GeneratePdfAsync(string htmlContent)
{
var pdfStream = new MemoryStream();
var pdfWrite = new PdfWriter(pdfStream);
var pdfDocument = new PdfDocument(pdfWrite);
var textDocument = new ITextDocument(pdfDocument);
pdfWrite.SetCloseStream(false);
var htmlBuilder = new StringBuilder();
htmlBuilder.Append(Options.Value.HtmlLayout);
htmlBuilder.Replace("{{style-placeholder}}", Options.Value.HtmlStyles);
htmlBuilder.Replace("{{content-placeholder}}", htmlContent);
var converter = new ConverterProperties();
var tagWorkerFactory = new HtmlIdTagWorkerFactory(pdfDocument);
converter.SetTagWorkerFactory(tagWorkerFactory);
HtmlConverter.ConvertToDocument(htmlBuilder.ToString(), pdfDocument, converter);
tagWorkerFactory.AddNamedDestinations();
var pdfOutlines = pdfDocument.GetOutlines(false);
BuildPdfOutlines(pdfOutlines, AllPdfDocumentNodes);
textDocument.Close();
pdfStream.Position = 0;
return Task.FromResult(pdfStream);
}
private void BuildPdfOutlines(PdfOutline parentOutline, List<PdfDocumentNode> pdfDocumentNodes)
{
foreach (var pdfDocumentNode in pdfDocumentNodes)
{
if (pdfDocumentNode.IgnoreOnOutline)
{
continue;
}
var outline = parentOutline.AddOutline(pdfDocumentNode.Title);
if (!pdfDocumentNode.Id.IsNullOrWhiteSpace())
{
outline.AddAction(PdfAction.CreateGoTo(pdfDocumentNode.Id));
}
if (pdfDocumentNode.HasChildren)
{
BuildPdfOutlines(outline, pdfDocumentNode.Children);
}
}
}
protected virtual async Task<string> RenderDocumentAsync(PdfDocumentNode pdfDocumentNode)
{
var renderedDocument = await DocumentSectionRenderer.RenderAsync(pdfDocumentNode.Document.Content, pdfDocumentNode.RenderParameters);
if (pdfDocumentNode.RenderParameters != null)
{
renderedDocument = SetDocumentTitle(renderedDocument, pdfDocumentNode.Title);
}
return renderedDocument;
}
protected virtual async Task SetAllPdfDocumentNodesAsync(
List<NavigationNode> navigations,
Project project,
string version,
string languageCode,
PdfDocumentNode parentPdfDocumentNode = null)
{
var parameterCombinationsDocuments = new List<PdfDocumentNode>();
foreach (var navigation in navigations)
{
if (navigation.IgnoreOnDownload)
{
continue;
}
try
{
var pdfDocumentNode = new PdfDocumentNode
{
Title = navigation.Text,
IgnoreOnOutline = navigation.Path == "index.md"
};
if (!navigation.Path.IsNullOrWhiteSpace())
{
var path = NormalizeNavigationPath(navigation.Path);
var document = await GetDocumentAsync(project, path, version, languageCode);
var parameters = await DocumentSectionRenderer.GetAvailableParametersAsync(document.Content);
var parameterCombinations = GenerateAllParameterCombinations(parameters.Keys.ToList(), parameters);
var firstParameterCombination = parameterCombinations.FirstOrDefault();
pdfDocumentNode.Document = document;
pdfDocumentNode.Title = GetDocumentTitle(navigation.Text, document.Content, firstParameterCombination);
pdfDocumentNode.Id = GetDocumentId(navigation.Path, firstParameterCombination);
pdfDocumentNode.RenderParameters = firstParameterCombination;
if(parameters.Count <= 1)
{
if (parentPdfDocumentNode == null)
{
AllPdfDocumentNodes.AddRange(parameterCombinationsDocuments);
}
else
{
parentPdfDocumentNode.Children.AddRange(parameterCombinationsDocuments);
}
}
else
{
for (var i = 1; i < parameterCombinations.Count; i++)
{
var parameterCombination = parameterCombinations[i];
parameterCombinationsDocuments.Add(new PdfDocumentNode
{
Document = document,
Title = GetDocumentTitle(navigation.Text, document.Content, parameterCombination),
Id = GetDocumentId(navigation.Path, parameterCombination),
RenderParameters = parameterCombination
});
}
}
}
if (parentPdfDocumentNode == null)
{
AllPdfDocumentNodes.Add(pdfDocumentNode);
}
else
{
parentPdfDocumentNode.Children.Add(pdfDocumentNode);
}
if (navigation.HasChildItems)
{
await SetAllPdfDocumentNodesAsync(navigation.Items, project, version, languageCode, pdfDocumentNode);
}
}
catch (Exception e)
{
Logger.LogWarning(e, $"Cannot get document for the path '{navigation.Path}' in the project {project.Name}.");
}
}
}
protected virtual string CalculateDocumentPdfFileName(string projectName, string version, string languageCode)
{
return $"{projectName.ToLower()}-{languageCode.ToLower()}.pdf";
}
private string NormalizeNavigationPath(string path)
{
return !path.EndsWith(".md") ? Path.Combine(path, "index.md") : path;
}
private async Task<NavigationNode> GetNavigationAsync(
Project project,
string version,
string languageCode)
{
var navigationDocument = await GetDocumentAsync(project, project.NavigationDocumentName, version, languageCode);
if (!DocsJsonSerializerHelper.TryDeserialize<NavigationNode>(navigationDocument.Content, out var navigation))
{
throw new UserFriendlyException($"Cannot validate navigation file '{project.NavigationDocumentName}' for the project {project.Name}.");
}
navigation.Items.Insert(0, new NavigationNode
{
Text = "Index",
Path = "index.md"
});
return navigation;
}
private async Task<DocumentParams> GetDocumentParamsAsync(
Project project,
string version,
string languageCode)
{
if (project.ParametersDocumentName.IsNullOrWhiteSpace())
{
return new DocumentParams();
}
try
{
var documentParamsDocument = await GetDocumentAsync(project, project.ParametersDocumentName, version, languageCode);
if (!DocsJsonSerializerHelper.TryDeserialize<DocumentParams>(documentParamsDocument.Content, out var documentParams))
{
throw new UserFriendlyException($"Cannot validate document params file '{project.ParametersDocumentName}' for the project {project.Name}.");
}
return documentParams;
}
catch (Exception e)
{
Logger.LogError(e, $"Cannot get document params for the project {project.Name}.", e);
return new DocumentParams();
}
}
private async Task<Document> GetDocumentAsync(
Project project,
string documentName,
string version,
string languageCode)
{
version = string.IsNullOrWhiteSpace(version) ? project.LatestVersionBranchName : version;
var document = await DocumentRepository.FindAsync(project.Id, documentName, version, languageCode);
if (document != null)
{
return document;
}
document = await DocumentSource.GetDocumentAsync(project, documentName, languageCode, version);
return document;
}
protected virtual List<DocumentRenderParameters> GenerateAllParameterCombinations(List<string> parameterKeys, Dictionary<string, List<string>> parameters)
{
var parameterCombinations = new List<DocumentRenderParameters>();
if (parameters.Count <= 0)
{
return parameterCombinations;
}
GenerateCombinations(0, new DocumentRenderParameters());
return parameterCombinations;
void GenerateCombinations(int keyIndex, DocumentRenderParameters currentCombination)
{
if (keyIndex == parameterKeys.Count)
{
parameterCombinations.Add(new DocumentRenderParameters(currentCombination));
return;
}
var currentKey = parameterKeys[keyIndex];
foreach (var value in parameters[currentKey])
{
currentCombination[currentKey] = value;
GenerateCombinations(keyIndex + 1, currentCombination);
}
}
}
private string SetDocumentTitle(string content, string title)
{
var titleLine = content.Split('\n').FirstOrDefault(x => x.TrimStart().StartsWith("#"));
if (titleLine == null)
{
return content;
}
var newTitle = $"# {title}";
return content.Replace(titleLine, newTitle);
}
private string GetDocumentTitle(string title, string content, DocumentRenderParameters parameters)
{
if (parameters == null || parameters.Count <= 0)
{
return title;
}
var titleLine = content.Split('\n').FirstOrDefault(x => x.TrimStart().StartsWith("#"));
if (titleLine == null)
{
return title;
}
var paramValues = parameters.Select(x => $"{DocumentParams.Parameters.FirstOrDefault(p => p.Name == x.Key)?.DisplayName ?? x.Key}: {x.Value}").ToList();
return titleLine.TrimStart('#').Trim() + $" ({string.Join(", ", paramValues)})";
}
private string GetDocumentId(string path, DocumentRenderParameters parameters)
{
var id = path.Replace(".md",string.Empty).Replace("/","-").Replace(" ", "-").ToLower();
if (parameters != null)
{
id = $"{id}{parameters.Select(x => $"{x.Key}_{x.Value}").JoinAsString("-")}";
}
return id;
}
private string NormalizeHtmlContent(PdfDocumentNode pdfDocumentNode, string htmlContent)
{
htmlContent = $"<div class='page' id='{pdfDocumentNode.Id}'>{htmlContent}</div>";
htmlContent = Regex.Replace(htmlContent, @"(<img\s+[^>]*)src=""([^""]*)""([^>]*>)", delegate (Match match)
{
if (IsExternalLink(match.Groups[2].Value))
{
return match.Value;
}
var rootUrl = IsExternalLink(pdfDocumentNode.Document.RawRootUrl)
? pdfDocumentNode.Document.RawRootUrl.EnsureEndsWith('/')
: Options.Value.BaseUrl.EnsureEndsWith('/') + pdfDocumentNode.Document.RawRootUrl.TrimStart('/').EnsureEndsWith('/');
var newImageSource = rootUrl +
(pdfDocumentNode.Document.LocalDirectory.IsNullOrEmpty() ? "" : pdfDocumentNode.Document.LocalDirectory.TrimStart('/').EnsureEndsWith('/')) +
match.Groups[2].Value.TrimStart('/');
return match.Groups[1] + " src=\"" + HttpUtility.HtmlEncode(newImageSource) + "\" " + match.Groups[3];
}, RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Multiline);
return htmlContent;
}
private bool IsExternalLink(string link)
{
return link.StartsWith("http://", StringComparison.OrdinalIgnoreCase) ||
link.StartsWith("https://", StringComparison.OrdinalIgnoreCase);
}
protected class PdfDocumentNode
{
public Document Document { get; set; }
public string Title { get; set; }
public string Id { get; set; }
public List<PdfDocumentNode> Children { get; set; } = [];
public DocumentRenderParameters RenderParameters { get; set; }
public bool HasChildren => Children.Any();
public bool IgnoreOnOutline { get; set; }
}
}

84
modules/docs/src/Volo.Docs.Common.Application/Volo/Docs/Common/Documents/DocumentPdfGeneratorAppService.cs

@ -1,88 +1,28 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp; using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
using Volo.Abp.BlobStoring;
using Volo.Abp.Content; using Volo.Abp.Content;
using Volo.Docs.Documents;
using Volo.Docs.Projects; using Volo.Docs.Projects;
using Volo.Extensions;
namespace Volo.Docs.Common.Documents; namespace Volo.Docs.Common.Documents;
[Authorize(DocsCommonPermissions.Documents.PdfGeneration)]
public class DocumentPdfGeneratorAppService : ApplicationService, IDocumentPdfGeneratorAppService public class DocumentPdfGeneratorAppService : ApplicationService, IDocumentPdfGeneratorAppService
{ {
protected IBlobContainer<DocsDocumentPdfContainer> BlobContainer { get; } private readonly IDocumentPdfGenerator _documentPdfGenerator;
protected IProjectRepository ProjectRepository { get; } private readonly IProjectRepository _projectRepository;
protected IDocumentSourceFactory DocumentStoreFactory { get; }
protected IDocumentRepository DocumentRepository { get; }
public DocumentPdfGeneratorAppService( public DocumentPdfGeneratorAppService(
IBlobContainer<DocsDocumentPdfContainer> blobContainer, IDocumentPdfGenerator documentPdfGenerator,
IProjectRepository projectRepository, IProjectRepository projectRepository)
IDocumentSourceFactory documentStoreFactory,
IDocumentRepository documentRepository)
{ {
BlobContainer = blobContainer; _documentPdfGenerator = documentPdfGenerator;
ProjectRepository = projectRepository; _projectRepository = projectRepository;
DocumentStoreFactory = documentStoreFactory;
DocumentRepository = documentRepository;
} }
public virtual async Task<IRemoteStreamContent> GeneratePdfAsync(DocumentPdfGeneratorInput input)
{
var project = await ProjectRepository.GetAsync(input.ProjectId);
var fileName = CalculateDocumentPdfFileName(project.Name, input.Version, input.LanguageCode);
var stream = await BlobContainer.GetOrNullAsync(fileName);
if(stream != null)
{
return new RemoteStreamContent(stream, fileName , "application/pdf");
}
var documentStore = DocumentStoreFactory.Create(project.DocumentStoreType); public async Task<IRemoteStreamContent> GeneratePdfAsync(DocumentPdfGeneratorInput input)
var navigation = await GetNavigationAsync(documentStore, project, input.LanguageCode, input.Version);
}
protected virtual string CalculateDocumentPdfFileName(string projectName, string version, string languageCode)
{
return $"{projectName.ToLower()}-{version.ToLower()}--{languageCode.ToLower()}";
}
private async Task<NavigationNode> GetNavigationAsync(
IDocumentSource documentStore,
Project project,
string languageCode,
string version)
{ {
var navigationDocument = await GetDocumentAsync(documentStore, project, project.NavigationDocumentName, languageCode, version); var project = await _projectRepository.GetAsync(input.ProjectId);
return await _documentPdfGenerator.GenerateAsync(project, input.Version, input.LanguageCode);
if (!DocsJsonSerializerHelper.TryDeserialize<NavigationNode>(navigationDocument.Content, out var navigation))
{
throw new UserFriendlyException($"Cannot validate navigation file '{project.NavigationDocumentName}' for the project {project.Name}.");
}
return navigation;
}
private async Task<Document> GetDocumentAsync(
IDocumentSource documentStore,
Project project,
string documentName,
string languageCode,
string version)
{
version = string.IsNullOrWhiteSpace(version) ? project.LatestVersionBranchName : version;
var document = await DocumentRepository.FindAsync(project.Id, documentName, version, languageCode);
if (document != null)
{
return document;
}
document = await documentStore.GetDocumentAsync(project, documentName, languageCode, version);
return document;
} }
} }

41
modules/docs/src/Volo.Docs.Common.Application/Volo/Docs/Common/Documents/HtmlIdTagWorkerFactory.cs

@ -0,0 +1,41 @@
using System.Collections.Generic;
using iText.Html2pdf.Attach;
using iText.Html2pdf.Attach.Impl;
using iText.Kernel.Pdf;
using iText.Kernel.Pdf.Navigation;
using iText.StyledXmlParser.Node;
namespace Volo.Docs.Common.Documents;
public class HtmlIdTagWorkerFactory: DefaultTagWorkerFactory
{
private readonly PdfDocument _pdfDocument;
private readonly Dictionary<string, int> _pageDestinations = new();
public HtmlIdTagWorkerFactory(PdfDocument pdfDocument)
{
_pdfDocument = pdfDocument;
}
public override ITagWorker GetCustomTagWorker(IElementNode tag, ProcessorContext context)
{
var tagClass = tag.GetAttribute("class");
if (tag.Name().Equals("div") && tagClass =="page")
{
var id = tag.GetAttribute("id");
_pageDestinations[id] = _pdfDocument.GetNumberOfPages() + 1;
}
return base.GetCustomTagWorker(tag, context);
}
public void AddNamedDestinations()
{
foreach (var pageDestination in _pageDestinations)
{
var page = _pdfDocument.GetPage(pageDestination.Value);
var destination = PdfExplicitDestination.CreateFit(page);
_pdfDocument.AddNamedDestination(pageDestination.Key, destination.GetPdfObject());
}
}
}

11
modules/docs/src/Volo.Docs.Common.Application/Volo/Docs/Common/Documents/IDocumentPdfGenerator.cs

@ -0,0 +1,11 @@
using System.Threading.Tasks;
using Volo.Abp.Content;
using Volo.Abp.DependencyInjection;
using Volo.Docs.Projects;
namespace Volo.Docs.Common.Documents;
public interface IDocumentPdfGenerator : ITransientDependency
{
Task<IRemoteStreamContent> GenerateAsync(Project project, string version, string languageCode);
}

19
modules/docs/src/Volo.Docs.Common.HttpApi.Client/Volo.Docs.Common.HttpApi.Client.csproj

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\configureawait.props" />
<Import Project="..\..\..\..\common.props" />
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0;net9.0</TargetFrameworks>
<AssemblyName>Volo.Docs.Common.HttpApi.Client</AssemblyName>
<PackageId>Volo.Docs.Common.HttpApi.Client</PackageId>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Docs.Common.Application.Contracts\Volo.Docs.Common.Application.Contracts.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Http.Client\Volo.Abp.Http.Client.csproj" />
</ItemGroup>
</Project>

18
modules/docs/src/Volo.Docs.Common.HttpApi/Volo.Docs.Common.HttpApi.csproj

@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\..\configureawait.props" />
<Import Project="..\..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<AssemblyName>Volo.Docs.Common.HttpApi</AssemblyName>
<PackageId>Volo.Docs.Common.HttpApi</PackageId>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Docs.Common.Application.Contracts\Volo.Docs.Common.Application.Contracts.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore.Mvc\Volo.Abp.AspNetCore.Mvc.csproj" />
</ItemGroup>
</Project>

35
modules/docs/src/Volo.Docs.Common.HttpApi/Volo/Docs/DocsCommonHttpApiModule.cs

@ -0,0 +1,35 @@
using Localization.Resources.AbpUi;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Localization;
using Volo.Abp.Modularity;
using Volo.Docs.Common;
using Volo.Docs.Localization;
namespace Volo.Docs
{
[DependsOn(
typeof(DocsCommonApplicationContractsModule),
typeof(AbpAspNetCoreMvcModule)
)]
public class DocsCommonHttpApiModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
PreConfigure<IMvcBuilder>(mvcBuilder =>
{
mvcBuilder.AddApplicationPartIfNotExists(typeof(DocsCommonHttpApiModule).Assembly);
});
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpLocalizationOptions>(options =>
{
options.Resources
.Get<DocsResource>()
.AddBaseTypes(typeof(AbpUiResource));
});
}
}
}

12
modules/docs/src/Volo.Docs.Common.HttpApi/Volo/Docs/DocsControllerBase.cs

@ -0,0 +1,12 @@
using Volo.Abp.AspNetCore.Mvc;
using Volo.Docs.Localization;
namespace Volo.Docs;
public abstract class DocsControllerBase : AbpControllerBase
{
protected DocsControllerBase()
{
LocalizationResource = typeof(DocsResource);
}
}

30
modules/docs/src/Volo.Docs.Common.HttpApi/Volo/Docs/Documents/DocsDocumentPdfGeneratorController.cs

@ -0,0 +1,30 @@
using System.Threading.Tasks;
using Asp.Versioning;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp;
using Volo.Abp.Content;
using Volo.Docs.Common;
using Volo.Docs.Common.Documents;
namespace Volo.Docs.Documents;
[RemoteService(Name = DocsCommonRemoteServiceConsts.RemoteServiceName)]
[Area(DocsCommonRemoteServiceConsts.ModuleName)]
[ControllerName("Document")]
[Route("api/docs/documents")]
public class DocsDocumentPdfGeneratorController : DocsControllerBase, IDocumentPdfGeneratorAppService
{
protected IDocumentPdfGeneratorAppService DocumentPdfGeneratorAppService { get; }
public DocsDocumentPdfGeneratorController(IDocumentPdfGeneratorAppService documentPdfGeneratorAppService)
{
DocumentPdfGeneratorAppService = documentPdfGeneratorAppService;
}
[HttpGet]
[Route("pdf")]
public Task<IRemoteStreamContent> GeneratePdfAsync(DocumentPdfGeneratorInput input)
{
return DocumentPdfGeneratorAppService.GeneratePdfAsync(input);
}
}

1
modules/docs/src/Volo.Docs.Domain.Shared/Volo.Docs.Domain.Shared.csproj

@ -11,6 +11,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Scriban" />
<PackageReference Include="NuGet.Versioning" /> <PackageReference Include="NuGet.Versioning" />
</ItemGroup> </ItemGroup>

23
modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/DocumentParams.cs

@ -0,0 +1,23 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Volo.Docs.Documents;
public class DocumentParams
{
[JsonPropertyName("Parameters")]
public List<DocumentParameter> Parameters { get; set; } = new();
public class DocumentParameter
{
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("displayName")]
public string DisplayName { get; set; }
[JsonPropertyName("values")]
public List<Dictionary<string, string>> Values { get; set; }
}
}

2
modules/docs/src/Volo.Docs.Web/HtmlConverting/DocumentPartialTemplateContent.cs → modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/Rendering/DocumentPartialTemplateContent.cs

@ -1,4 +1,4 @@
namespace Volo.Docs.HtmlConverting namespace Volo.Docs.Documents.Rendering
{ {
public class DocumentPartialTemplateContent public class DocumentPartialTemplateContent
{ {

10
modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/Rendering/DocumentPartialTemplateWithValues.cs

@ -0,0 +1,10 @@
using System.Collections.Generic;
namespace Volo.Docs.Documents.Rendering;
public class DocumentPartialTemplateWithValues
{
public string Path { get; set; }
public Dictionary<string, string> Parameters { get; set; }
}

20
modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/Rendering/DocumentRenderParameters.cs

@ -0,0 +1,20 @@
using System.Collections.Generic;
namespace Volo.Docs.Documents.Rendering
{
public class DocumentRenderParameters : Dictionary<string,string>
{
public DocumentRenderParameters()
{
}
public DocumentRenderParameters(DocumentRenderParameters renderParameters)
{
foreach (var parameter in renderParameters)
{
Add(parameter.Key, parameter.Value);
}
}
}
}

12
modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/Rendering/IDocumentSectionRenderer.cs

@ -0,0 +1,12 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Volo.Docs.Documents.Rendering;
public interface IDocumentSectionRenderer : ITransientDependency
{
Task<string> RenderAsync(string document, DocumentRenderParameters parameters = null, List<DocumentPartialTemplateContent> partialTemplates = null);
Task<Dictionary<string, List<string>>> GetAvailableParametersAsync(string document);
}

302
modules/docs/src/Volo.Docs.Domain.Shared/Volo/Docs/Documents/Rendering/ScribanDocumentSectionRenderer.cs

@ -0,0 +1,302 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Scriban;
using Volo.Abp;
using Volo.Extensions;
namespace Volo.Docs.Documents.Rendering;
public class ScribanDocumentSectionRenderer : IDocumentSectionRenderer
{
protected readonly static List<DocsJsonSection> DocsJsonSections =
[
new("````json", "````"),
new("```json", "```")
];
protected const string DocsParam = "//[doc-params]";
protected const string DocsTemplates = "//[doc-template]";
protected const string DocsNav = "//[doc-nav]";
public ILogger<ScribanDocumentSectionRenderer> Logger { get; set; }
public ScribanDocumentSectionRenderer()
{
Logger = NullLogger<ScribanDocumentSectionRenderer>.Instance;
}
public async Task<string> RenderAsync(
string document,
DocumentRenderParameters parameters = null,
List<DocumentPartialTemplateContent> partialTemplates = null)
{
if (partialTemplates != null && partialTemplates.Any())
{
document = SetPartialTemplates(document, partialTemplates);
}
var scribanTemplate = Template.Parse(document);
if (parameters == null)
{
return await scribanTemplate.RenderAsync();
}
var result = await scribanTemplate.RenderAsync(parameters);
return RemoveOptionsJson(result, DocsParam, DocsNav);
}
public Task<Dictionary<string, List<string>>> GetAvailableParametersAsync(string document)
{
return GetSectionAsync<Dictionary<string, List<string>>>(document, DocsParam);
}
protected virtual async Task<T> GetSectionAsync<T>(string document, string sectionName) where T : new()
{
try
{
if (!HasJsonSection(document) || !document.Contains(sectionName))
{
return new T();
}
var (jsonBeginningIndex, jsonEndingIndex, insideJsonSection, _) =
GetJsonBeginEndIndexesAndPureJson(document, sectionName);
if (jsonBeginningIndex < 0 || jsonEndingIndex <= 0 || string.IsNullOrWhiteSpace(insideJsonSection))
{
return new T();
}
var pureJson = insideJsonSection.Replace(sectionName, "").Trim();
if (!DocsJsonSerializerHelper.TryDeserialize<T>(pureJson, out var section))
{
throw new UserFriendlyException($"ERROR-20200327: Cannot validate JSON content for `{sectionName}`!");
}
return await Task.FromResult(section);
}
catch (Exception)
{
Logger.LogWarning("Unable to parse parameters of document.");
return new T();
}
}
protected static string RemoveOptionsJson(string document, params string[] sectionNames)
{
foreach (var sectionName in sectionNames)
{
var orgDocument = document;
try
{
if (!HasJsonSection(document) || !document.Contains(sectionName))
{
continue;
}
var (jsonBeginningIndex, jsonEndingIndex, insideJsonSection, jsonSection) =
GetJsonBeginEndIndexesAndPureJson(document, sectionName);
if (jsonBeginningIndex < 0 || jsonEndingIndex <= 0 || string.IsNullOrWhiteSpace(insideJsonSection))
{
continue;
}
document = document.Remove(
jsonBeginningIndex - jsonSection.Opener.Length,
(jsonEndingIndex + jsonSection.Closer.Length) - (jsonBeginningIndex - jsonSection.Opener.Length)
);
}
catch (Exception)
{
document = orgDocument;
}
}
return document;
}
protected static bool HasJsonSection(string document)
{
return DocsJsonSections.Any(section => document.Contains(section.Opener) && document.Contains(section.Closer));
}
protected static (int, int, string, DocsJsonSection) GetJsonBeginEndIndexesAndPureJson(string document, string sectionName)
{
foreach (var section in DocsJsonSections)
{
var (jsonBeginningIndex, jsonEndingIndex, insideJsonSection) =
section.GetJsonBeginEndIndexesAndPureJson(document, sectionName);
if (jsonBeginningIndex >= 0 && jsonEndingIndex > 0 && !string.IsNullOrWhiteSpace(insideJsonSection))
{
return (jsonBeginningIndex, jsonEndingIndex, insideJsonSection, section);
}
}
return (-1, -1, "", null);
}
protected static string SetPartialTemplates(string document, IReadOnlyCollection<DocumentPartialTemplateContent> templates)
{
foreach (var section in DocsJsonSections)
{
document = section.SetPartialTemplates(document, templates);
}
return document;
}
protected class DocsJsonSection
{
public string Opener { get; }
public string Closer { get; }
public DocsJsonSection(string opener, string closer)
{
Opener = opener;
Closer = closer;
}
public (int, int, string) GetJsonBeginEndIndexesAndPureJson(string document, string sectionName)
{
var searchedIndex = 0;
while (searchedIndex < document.Length)
{
var jsonBeginningIndex = document.Substring(searchedIndex).IndexOf(Opener, StringComparison.Ordinal);
if (jsonBeginningIndex < 0)
{
return (-1, -1, "");
}
jsonBeginningIndex += Opener.Length + searchedIndex;
var jsonEndingIndex = document.Substring(jsonBeginningIndex).IndexOf(Closer, StringComparison.Ordinal);
if (jsonEndingIndex < 0)
{
return (-1, -1, "");
}
jsonEndingIndex += jsonBeginningIndex;
var insideJsonSection = document.Substring(jsonBeginningIndex, jsonEndingIndex - jsonBeginningIndex);
if (insideJsonSection.IndexOf(sectionName, StringComparison.Ordinal) < 0)
{
searchedIndex = jsonEndingIndex + Closer.Length;
continue;
}
return (jsonBeginningIndex, jsonEndingIndex, insideJsonSection);
}
return (-1, -1, "");
}
public async Task<List<DocumentPartialTemplateWithValues>> GetPartialTemplatesInDocumentAsync(string documentContent)
{
var templates = new List<DocumentPartialTemplateWithValues>();
while (documentContent.Contains(Opener))
{
var afterJsonOpener = documentContent.Substring(
documentContent.IndexOf(Opener, StringComparison.Ordinal) + Opener.Length
);
var betweenJsonOpenerAndCloser = afterJsonOpener.Substring(0,
afterJsonOpener.IndexOf(Closer, StringComparison.Ordinal)
);
documentContent = afterJsonOpener.Substring(
afterJsonOpener.IndexOf(Closer, StringComparison.Ordinal) + Closer.Length
);
if (!betweenJsonOpenerAndCloser.Contains(DocsTemplates))
{
continue;
}
var json = betweenJsonOpenerAndCloser.Substring(
betweenJsonOpenerAndCloser.IndexOf(DocsTemplates, StringComparison.Ordinal) +
DocsTemplates.Length);
if (!DocsJsonSerializerHelper.TryDeserialize<DocumentPartialTemplateWithValues>(json,
out var template))
{
throw new UserFriendlyException(
$"ERROR-20200327: Cannot validate JSON content for `AvailableParameters`!");
}
templates.Add(template);
}
return await Task.FromResult(templates);
}
public string SetPartialTemplates(string document, IReadOnlyCollection<DocumentPartialTemplateContent> templates)
{
var newDocument = new StringBuilder();
while (document.Contains(Opener))
{
var beforeJson = document.Substring(0,
document.IndexOf(Opener, StringComparison.Ordinal) + Opener.Length
);
var afterJsonOpener = document.Substring(
document.IndexOf(Opener, StringComparison.Ordinal) + Opener.Length
);
var betweenJsonOpenerAndCloser = afterJsonOpener.Substring(0,
afterJsonOpener.IndexOf(Closer, StringComparison.Ordinal)
);
if (!betweenJsonOpenerAndCloser.Contains(DocsTemplates))
{
document = afterJsonOpener.Substring(
afterJsonOpener.IndexOf(Closer, StringComparison.Ordinal) + Closer.Length
);
newDocument.Append(beforeJson + betweenJsonOpenerAndCloser + Closer);
continue;
}
var json = betweenJsonOpenerAndCloser.Substring(
betweenJsonOpenerAndCloser.IndexOf(DocsTemplates, StringComparison.Ordinal) +
DocsTemplates.Length
);
if (DocsJsonSerializerHelper.TryDeserialize<DocumentPartialTemplateWithValues>(json,
out var documentPartialTemplateWithValuesDto))
{
var template =
templates.FirstOrDefault(t => t.Path == documentPartialTemplateWithValuesDto.Path);
var beforeTemplate = document.Substring(0,
document.IndexOf(Opener, StringComparison.Ordinal)
);
newDocument.Append(beforeTemplate + template?.Content + Closer);
document = afterJsonOpener.Substring(
afterJsonOpener.IndexOf(Closer, StringComparison.Ordinal) + Closer.Length
);
}
}
newDocument.Append(document);
return newDocument.ToString();
}
}
}

3
modules/docs/src/Volo.Docs.Web/DocsWebAutoMapperProfile.cs

@ -1,6 +1,4 @@
using AutoMapper; using AutoMapper;
using Volo.Abp.AutoMapper;
using Volo.Docs.Documents;
namespace Volo.Docs namespace Volo.Docs
{ {
@ -8,7 +6,6 @@ namespace Volo.Docs
{ {
public DocsWebAutoMapperProfile() public DocsWebAutoMapperProfile()
{ {
} }
} }
} }

11
modules/docs/src/Volo.Docs.Web/HtmlConverting/DocumentPartialTemplateWithValuesDto.cs

@ -1,11 +0,0 @@
using System.Collections.Generic;
namespace Volo.Docs.HtmlConverting
{
public class DocumentPartialTemplateWithValuesDto
{
public string Path { get; set; }
public Dictionary<string, string> Parameters { get; set; }
}
}

8
modules/docs/src/Volo.Docs.Web/HtmlConverting/DocumentRenderParameters.cs

@ -1,8 +0,0 @@
using System.Collections.Generic;
namespace Volo.Docs.HtmlConverting
{
public class DocumentRenderParameters : Dictionary<string,string>
{
}
}

17
modules/docs/src/Volo.Docs.Web/HtmlConverting/IDocumentSectionRenderer.cs

@ -1,17 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
namespace Volo.Docs.HtmlConverting
{
public interface IDocumentSectionRenderer: ITransientDependency
{
Task<string> RenderAsync(string doucment, DocumentRenderParameters parameters = null, List<DocumentPartialTemplateContent> partialTemplates = null);
Task<Dictionary<string, List<string>>> GetAvailableParametersAsync(string document);
Task<List<DocumentPartialTemplateWithValuesDto>> GetPartialTemplatesInDocumentAsync(string documentContent);
Task<DocumentNavigationsDto> GetDocumentNavigationsAsync(string documentContent);
}
}

14
modules/docs/src/Volo.Docs.Web/HtmlConverting/IWebDocumentSectionRenderer.cs

@ -0,0 +1,14 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Docs.Documents.Rendering;
namespace Volo.Docs.HtmlConverting
{
public interface IWebDocumentSectionRenderer: IDocumentSectionRenderer
{
Task<List<DocumentPartialTemplateWithValues>> GetPartialTemplatesInDocumentAsync(string documentContent);
Task<DocumentNavigationsDto> GetDocumentNavigationsAsync(string documentContent);
}
}

316
modules/docs/src/Volo.Docs.Web/HtmlConverting/ScribanDocumentSectionRenderer.cs

@ -1,316 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Scriban;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp;
using Volo.Extensions;
namespace Volo.Docs.HtmlConverting
{
public class ScribanDocumentSectionRenderer : IDocumentSectionRenderer
{
private readonly static List<DocsJsonSection> DocsJsonSections = new List<DocsJsonSection>
{
new DocsJsonSection("````json", "````"),
new DocsJsonSection("```json", "```")
};
private const string DocsParam = "//[doc-params]";
private const string DocsTemplates = "//[doc-template]";
private const string DocsNav = "//[doc-nav]";
public ILogger<ScribanDocumentSectionRenderer> Logger { get; set; }
public ScribanDocumentSectionRenderer()
{
Logger = NullLogger<ScribanDocumentSectionRenderer>.Instance;
}
public async Task<string> RenderAsync(string document, DocumentRenderParameters parameters = null, List<DocumentPartialTemplateContent> partialTemplates = null)
{
if (partialTemplates != null && partialTemplates.Any())
{
document = SetPartialTemplates(document, partialTemplates);
}
var scribanTemplate = Template.Parse(document);
if (parameters == null)
{
return await scribanTemplate.RenderAsync();
}
var result = await scribanTemplate.RenderAsync(parameters);
return RemoveOptionsJson(result, DocsParam, DocsNav);
}
public Task<Dictionary<string, List<string>>> GetAvailableParametersAsync(string document)
{
return GetSectionAsync<Dictionary<string, List<string>>>(document, DocsParam);
}
public Task<DocumentNavigationsDto> GetDocumentNavigationsAsync(string documentContent)
{
return GetSectionAsync<DocumentNavigationsDto>(documentContent, DocsNav);
}
protected virtual async Task<T> GetSectionAsync<T>(string document, string sectionName) where T : new()
{
try
{
if (!HasJsonSection(document) || !document.Contains(sectionName))
{
return new T();
}
var (jsonBeginningIndex, jsonEndingIndex, insideJsonSection, _) = GetJsonBeginEndIndexesAndPureJson(document, sectionName);
if (jsonBeginningIndex < 0 || jsonEndingIndex <= 0 || string.IsNullOrWhiteSpace(insideJsonSection))
{
return new T();
}
var pureJson = insideJsonSection.Replace(sectionName, "").Trim();
if (!DocsJsonSerializerHelper.TryDeserialize<T>(pureJson, out var section))
{
throw new UserFriendlyException($"ERROR-20200327: Cannot validate JSON content for `{sectionName}`!");
}
return await Task.FromResult(section);
}
catch (Exception)
{
Logger.LogWarning("Unable to parse parameters of document.");
return new T();
}
}
private static string RemoveOptionsJson(string document, params string[] sectionNames)
{
foreach (var sectionName in sectionNames)
{
var orgDocument = document;
try
{
if (!HasJsonSection(document) || !document.Contains(sectionName))
{
continue;
}
var (jsonBeginningIndex, jsonEndingIndex, insideJsonSection, jsonSection) = GetJsonBeginEndIndexesAndPureJson(document, sectionName);
if (jsonBeginningIndex < 0 || jsonEndingIndex <= 0 || string.IsNullOrWhiteSpace(insideJsonSection))
{
continue;
}
document = document.Remove(
jsonBeginningIndex - jsonSection.Opener.Length,
(jsonEndingIndex + jsonSection.Closer.Length) - (jsonBeginningIndex - jsonSection.Opener.Length)
);
}
catch (Exception)
{
document = orgDocument;
}
}
return document;
}
private static bool HasJsonSection(string document)
{
return DocsJsonSections.Any(section => document.Contains(section.Opener) && document.Contains(section.Closer));
}
private static (int, int, string, DocsJsonSection) GetJsonBeginEndIndexesAndPureJson(string document, string sectionName)
{
foreach (var section in DocsJsonSections)
{
var (jsonBeginningIndex, jsonEndingIndex, insideJsonSection) = section.GetJsonBeginEndIndexesAndPureJson(document, sectionName);
if (jsonBeginningIndex >= 0 && jsonEndingIndex > 0 && !string.IsNullOrWhiteSpace(insideJsonSection))
{
return (jsonBeginningIndex, jsonEndingIndex, insideJsonSection, section);
}
}
return (-1, -1, "", null);
}
public async Task<List<DocumentPartialTemplateWithValuesDto>> GetPartialTemplatesInDocumentAsync(string documentContent)
{
var templates = new List<DocumentPartialTemplateWithValuesDto>();
foreach (var section in DocsJsonSections)
{
templates.AddRange(await section.GetPartialTemplatesInDocumentAsync(documentContent));
}
return templates;
}
private static string SetPartialTemplates(string document, IReadOnlyCollection<DocumentPartialTemplateContent> templates)
{
foreach (var section in DocsJsonSections)
{
document = section.SetPartialTemplates(document, templates);
}
return document;
}
private class DocsJsonSection
{
public string Opener { get; }
public string Closer { get; }
public DocsJsonSection(string opener, string closer)
{
Opener = opener;
Closer = closer;
}
public (int, int, string) GetJsonBeginEndIndexesAndPureJson(string document, string sectionName)
{
var searchedIndex = 0;
while (searchedIndex < document.Length)
{
var jsonBeginningIndex = document.Substring(searchedIndex).IndexOf(Opener, StringComparison.Ordinal);
if (jsonBeginningIndex < 0)
{
return (-1, -1, "");
}
jsonBeginningIndex += Opener.Length + searchedIndex;
var jsonEndingIndex = document.Substring(jsonBeginningIndex).IndexOf(Closer, StringComparison.Ordinal);
if (jsonEndingIndex < 0)
{
return (-1, -1, "");
}
jsonEndingIndex += jsonBeginningIndex;
var insideJsonSection = document[jsonBeginningIndex..jsonEndingIndex];
if (insideJsonSection.IndexOf(sectionName, StringComparison.Ordinal) < 0)
{
searchedIndex = jsonEndingIndex + Closer.Length;
continue;
}
return (jsonBeginningIndex, jsonEndingIndex, insideJsonSection);
}
return (-1, -1, "");
}
public async Task<List<DocumentPartialTemplateWithValuesDto>> GetPartialTemplatesInDocumentAsync(
string documentContent)
{
var templates = new List<DocumentPartialTemplateWithValuesDto>();
while (documentContent.Contains(Opener))
{
var afterJsonOpener = documentContent.Substring(
documentContent.IndexOf(Opener, StringComparison.Ordinal) + Opener.Length
);
var betweenJsonOpenerAndCloser = afterJsonOpener.Substring(0,
afterJsonOpener.IndexOf(Closer, StringComparison.Ordinal)
);
documentContent = afterJsonOpener.Substring(
afterJsonOpener.IndexOf(Closer, StringComparison.Ordinal) + Closer.Length
);
if (!betweenJsonOpenerAndCloser.Contains(DocsTemplates))
{
continue;
}
var json = betweenJsonOpenerAndCloser.Substring(
betweenJsonOpenerAndCloser.IndexOf(DocsTemplates, StringComparison.Ordinal) +
DocsTemplates.Length);
if (!DocsJsonSerializerHelper.TryDeserialize<DocumentPartialTemplateWithValuesDto>(json,
out var template))
{
throw new UserFriendlyException(
$"ERROR-20200327: Cannot validate JSON content for `AvailableParameters`!");
}
templates.Add(template);
}
return await Task.FromResult(templates);
}
public string SetPartialTemplates(string document,
IReadOnlyCollection<DocumentPartialTemplateContent> templates)
{
var newDocument = new StringBuilder();
while (document.Contains(Opener))
{
var beforeJson = document.Substring(0,
document.IndexOf(Opener, StringComparison.Ordinal) + Opener.Length
);
var afterJsonOpener = document.Substring(
document.IndexOf(Opener, StringComparison.Ordinal) + Opener.Length
);
var betweenJsonOpenerAndCloser = afterJsonOpener.Substring(0,
afterJsonOpener.IndexOf(Closer, StringComparison.Ordinal)
);
if (!betweenJsonOpenerAndCloser.Contains(DocsTemplates))
{
document = afterJsonOpener.Substring(
afterJsonOpener.IndexOf(Closer, StringComparison.Ordinal) + Closer.Length
);
newDocument.Append(beforeJson + betweenJsonOpenerAndCloser + Closer);
continue;
}
var json = betweenJsonOpenerAndCloser.Substring(
betweenJsonOpenerAndCloser.IndexOf(DocsTemplates, StringComparison.Ordinal) +
DocsTemplates.Length
);
if (DocsJsonSerializerHelper.TryDeserialize<DocumentPartialTemplateWithValuesDto>(json,
out var documentPartialTemplateWithValuesDto))
{
var template =
templates.FirstOrDefault(t => t.Path == documentPartialTemplateWithValuesDto.Path);
var beforeTemplate = document.Substring(0,
document.IndexOf(Opener, StringComparison.Ordinal)
);
newDocument.Append(beforeTemplate + template?.Content + Closer);
document = afterJsonOpener.Substring(
afterJsonOpener.IndexOf(Closer, StringComparison.Ordinal) + Closer.Length
);
}
}
newDocument.Append(document);
return newDocument.ToString();
}
}
}
}

38
modules/docs/src/Volo.Docs.Web/HtmlConverting/ScribanWebDocumentSectionRenderer.cs

@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Scriban;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Volo.Abp;
using Volo.Abp.ObjectMapping;
using Volo.Docs.Documents.Rendering;
using Volo.Extensions;
namespace Volo.Docs.HtmlConverting
{
public class ScribanWebDocumentSectionRenderer : ScribanDocumentSectionRenderer, IWebDocumentSectionRenderer
{
private IObjectMapper ObjectMapper { get; set; }
public Task<DocumentNavigationsDto> GetDocumentNavigationsAsync(string documentContent)
{
return GetSectionAsync<DocumentNavigationsDto>(documentContent, DocsNav);
}
public async Task<List<DocumentPartialTemplateWithValues>> GetPartialTemplatesInDocumentAsync(
string documentContent)
{
var templates = new List<DocumentPartialTemplateWithValues>();
foreach (var section in DocsJsonSections)
{
templates.AddRange(await section.GetPartialTemplatesInDocumentAsync(documentContent));
}
return templates;
}
}
}

43
modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml.cs

@ -19,6 +19,7 @@ using Volo.Abp.Data;
using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities;
using Volo.Abp.EventBus.Local; using Volo.Abp.EventBus.Local;
using Volo.Docs.Documents; using Volo.Docs.Documents;
using Volo.Docs.Documents.Rendering;
using Volo.Docs.HtmlConverting; using Volo.Docs.HtmlConverting;
using Volo.Docs.Models; using Volo.Docs.Models;
using Volo.Docs.Projects; using Volo.Docs.Projects;
@ -96,7 +97,7 @@ namespace Volo.Docs.Pages.Documents.Project
private readonly IDocumentAppService _documentAppService; private readonly IDocumentAppService _documentAppService;
private readonly IDocumentToHtmlConverterFactory _documentToHtmlConverterFactory; private readonly IDocumentToHtmlConverterFactory _documentToHtmlConverterFactory;
private readonly IProjectAppService _projectAppService; private readonly IProjectAppService _projectAppService;
private readonly IDocumentSectionRenderer _documentSectionRenderer; private readonly IWebDocumentSectionRenderer _webDocumentSectionRenderer;
private readonly DocsUiOptions _uiOptions; private readonly DocsUiOptions _uiOptions;
protected IDocsLinkGenerator DocsLinkGenerator => LazyServiceProvider.LazyGetRequiredService<IDocsLinkGenerator>(); protected IDocsLinkGenerator DocsLinkGenerator => LazyServiceProvider.LazyGetRequiredService<IDocsLinkGenerator>();
@ -108,17 +109,16 @@ namespace Volo.Docs.Pages.Documents.Project
IDocumentToHtmlConverterFactory documentToHtmlConverterFactory, IDocumentToHtmlConverterFactory documentToHtmlConverterFactory,
IProjectAppService projectAppService, IProjectAppService projectAppService,
IOptions<DocsUiOptions> options, IOptions<DocsUiOptions> options,
IDocumentSectionRenderer documentSectionRenderer) IWebDocumentSectionRenderer webDocumentSectionRenderer)
{ {
ObjectMapperContext = typeof(DocsWebModule); ObjectMapperContext = typeof(DocsWebModule);
_documentAppService = documentAppService; _documentAppService = documentAppService;
_documentToHtmlConverterFactory = documentToHtmlConverterFactory; _documentToHtmlConverterFactory = documentToHtmlConverterFactory;
_projectAppService = projectAppService; _projectAppService = projectAppService;
_documentSectionRenderer = documentSectionRenderer; _webDocumentSectionRenderer = webDocumentSectionRenderer;
_uiOptions = options.Value; _uiOptions = options.Value;
LocalizationResourceType = typeof(DocsResource); LocalizationResourceType = typeof(DocsResource);
} }
@ -561,11 +561,11 @@ namespace Volo.Docs.Pages.Documents.Project
var partialTemplates = await GetDocumentPartialTemplatesAsync(); var partialTemplates = await GetDocumentPartialTemplatesAsync();
DocumentNavigationsDto = await _documentSectionRenderer.GetDocumentNavigationsAsync(Document.Content); DocumentNavigationsDto = await _webDocumentSectionRenderer.GetDocumentNavigationsAsync(Document.Content);
try try
{ {
Document.Content = await _documentSectionRenderer.RenderAsync(Document.Content, UserPreferences, partialTemplates); Document.Content = await _webDocumentSectionRenderer.RenderAsync(Document.Content, UserPreferences, partialTemplates);
} }
catch (Exception e) catch (Exception e)
{ {
@ -619,7 +619,7 @@ namespace Volo.Docs.Pages.Documents.Project
private async Task<List<DocumentPartialTemplateContent>> GetDocumentPartialTemplatesAsync() private async Task<List<DocumentPartialTemplateContent>> GetDocumentPartialTemplatesAsync()
{ {
var partialTemplatesInDocument = await _documentSectionRenderer.GetPartialTemplatesInDocumentAsync(Document.Content); var partialTemplatesInDocument = await _webDocumentSectionRenderer.GetPartialTemplatesInDocumentAsync(Document.Content);
if (!partialTemplatesInDocument?.Any(t => t.Parameters != null) ?? true) if (!partialTemplatesInDocument?.Any(t => t.Parameters != null) ?? true)
{ {
@ -772,7 +772,9 @@ namespace Volo.Docs.Pages.Documents.Project
return; return;
} }
var availableParameters = await _documentSectionRenderer.GetAvailableParametersAsync(Document.Content); var availableParameters = await _webDocumentSectionRenderer.GetAvailableParametersAsync(Document.Content);
var parameterCombinations = new List<Dictionary<string, string>>();
GenerateCombinations(0, availableParameters.Keys.ToList(),availableParameters,new Dictionary<string, string>(), parameterCombinations);
DocumentPreferences = new DocumentParametersDto DocumentPreferences = new DocumentParametersDto
{ {
@ -813,11 +815,32 @@ namespace Volo.Docs.Pages.Documents.Project
{ {
if (!DocumentPreferences?.Parameters?.Any() ?? true) if (!DocumentPreferences?.Parameters?.Any() ?? true)
{ {
return; return;
} }
AlternativeOptionLinkQueries = CollectAlternativeOptionLinksRecursively(); AlternativeOptionLinkQueries = CollectAlternativeOptionLinksRecursively();
} }
void GenerateCombinations(
int keyIndex,
List<string> parameterKeys ,
Dictionary<string,List<string>> parameters,
Dictionary<string, string> currentCombination,
List<Dictionary<string, string>> parameterCombinations )
{
if (keyIndex == parameterKeys.Count)
{
parameterCombinations.Add(new Dictionary<string, string>(currentCombination));
return;
}
var currentKey = parameterKeys[keyIndex];
foreach (var value in parameters[currentKey])
{
currentCombination[currentKey] = value;
GenerateCombinations(keyIndex + 1,parameterKeys, parameters, currentCombination,parameterCombinations);
}
}
private List<string> CollectAlternativeOptionLinksRecursively(int index = 0) private List<string> CollectAlternativeOptionLinksRecursively(int index = 0)
{ {

2
modules/docs/src/Volo.Docs.Web/Volo.Docs.Web.csproj

@ -23,7 +23,7 @@
<ProjectReference Include="..\Volo.Docs.Application.Contracts\Volo.Docs.Application.Contracts.csproj" /> <ProjectReference Include="..\Volo.Docs.Application.Contracts\Volo.Docs.Application.Contracts.csproj" />
<PackageReference Include="Markdig.Signed" /> <PackageReference Include="Markdig.Signed" />
<PackageReference Include="Scriban" /> <PackageReference Include="Scriban" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" /> <PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" />
</ItemGroup> </ItemGroup>

Loading…
Cancel
Save