├── CabLib.dll ├── .editorconfig ├── .vscode ├── settings.json ├── launch.json └── tasks.json ├── renovate.json ├── Models ├── Altinn3 │ ├── prefill │ │ ├── PrefillInheritance.cs │ │ └── prefill.cs │ ├── layoutSettings │ │ └── LayoutSettingsInheritance.cs │ ├── Readme.cs │ ├── PolicyUpdates.cs │ ├── TextResource.cs │ ├── Altinn3AppData.cs │ ├── layout │ │ └── LayoutInheritance.cs │ └── policy │ │ └── policy.cs ├── Altinn2 │ ├── InfoPath │ │ └── XSNFileContent.cs │ ├── Translation.cs │ ├── TranslationText.cs │ ├── TextResource.cs │ ├── Altinn2AppData.cs │ ├── FormFieldPrefill.cs │ ├── FormMetadata.cs │ └── ServiceEditionVersion.cs └── FieldPermission.cs ├── Enums ├── ComponentType.cs ├── RepeatingGroupType.cs ├── PageControlType.cs ├── LanguageType.cs └── TextType.cs ├── GlobalSuppressions.cs ├── .github └── workflows │ └── build-validation.yml ├── LICENSE ├── Helpers ├── ComponentPropsFirstContractResolver.cs ├── InfoPathXmlParser.cs ├── ModelConverter.cs ├── XExtensions.cs ├── Utils.cs ├── MergeLanguageResults.cs ├── PrefillConverter.cs ├── TulPackageParser.cs └── Page2Layout.cs ├── README.md ├── Altinn2Convert.sln ├── Program.cs ├── Altinn2Convert.csproj ├── Services ├── GenerateAltinn3ClassesFromJsonSchema.cs ├── BatchService.cs └── ConvertService.cs ├── .gitignore └── Altinn3.ruleset /CabLib.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altinn/altinn2-convert/main/CabLib.dll -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # SA1305: Field names should not use Hungarian notation 4 | dotnet_diagnostic.SA1305.severity = none 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "search.useIgnoreFiles": false, 3 | "search.exclude": { 4 | "bin": true, 5 | "obj": true 6 | } 7 | } -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "local>Altinn/renovate-config" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /Models/Altinn3/prefill/PrefillInheritance.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Altinn2Convert.Models.Altinn3.prefill 4 | { 5 | public partial class Test 6 | { 7 | [JsonProperty("$schema", Order = int.MinValue)] 8 | public string schema { get; } = "https://altinncdn.no/schemas/json/prefill/prefill.schema.v1.json"; 9 | } 10 | } -------------------------------------------------------------------------------- /Models/Altinn3/layoutSettings/LayoutSettingsInheritance.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Altinn2Convert.Models.Altinn3.layoutSettings 4 | { 5 | public partial class Test 6 | { 7 | [JsonProperty("$schema", Order = int.MinValue)] 8 | public string schema { get; } = "https://altinncdn.no/schemas/json/layout/layoutSettings.schema.v1.json"; 9 | } 10 | } -------------------------------------------------------------------------------- /Models/Altinn3/Readme.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Altinn2Convert.Models.Altinn3 4 | { 5 | public class Readme 6 | { 7 | public string Org { get; set; } 8 | 9 | public string App { get; set; } 10 | 11 | public List Authenticationlevels { get; set; } 12 | 13 | public List RoleCodes { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /Models/Altinn3/PolicyUpdates.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Altinn2Convert.Models.Altinn3 4 | { 5 | public class PolicyUpdates 6 | { 7 | public string Org { get; set; } 8 | 9 | public string App { get; set; } 10 | 11 | public List Authenticationlevels { get; set; } 12 | 13 | public List RoleCodes { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /Enums/ComponentType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Altinn2Convert.Enums 6 | { 7 | /// 8 | /// Component types 9 | /// 10 | public enum ComponentType 11 | { 12 | Input, 13 | RadioButtons, 14 | Dropdown, 15 | NavigationButtons, 16 | Button, 17 | Paragraph, 18 | TextArea, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Models/Altinn2/InfoPath/XSNFileContent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Xml; 4 | using System.Xml.Linq; 5 | 6 | namespace Altinn2Convert.Models.Altinn2.InfoPath 7 | { 8 | public class XSNFileContent 9 | { 10 | public string XSDDocument { get; set; } 11 | 12 | public XmlDocument Manifest { get; set; } 13 | 14 | public Dictionary Pages { get; set; } 15 | 16 | // public byte[] PrimaryXsd { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /Enums/RepeatingGroupType.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn2Convert.Enums 2 | { 3 | /// 4 | /// Classifies the type of repeating group that contains the controls 5 | /// 6 | public enum RepeatingGroupType 7 | { 8 | /// 9 | /// The repeating control is a section, repeating section or an optional section 10 | /// 11 | Section = 1, 12 | 13 | /// 14 | /// The repeating control is a horizontal or vertical repeating table 15 | /// 16 | Table = 2 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:Field names should not use Hungarian notation", Justification = "Stupid rule", Scope = "member", Target = "~M:Altinn2Convert.Helpers.InfoPathParser.TranslateDropdownNodes(System.Collections.Generic.Dictionary{System.String,Altinn2Convert.Models.InfoPath.FormText},System.Xml.XmlNodeList,System.Xml.XmlNamespaceManager)")] 7 | -------------------------------------------------------------------------------- /Enums/PageControlType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Altinn2Convert.Enums 6 | { 7 | /// 8 | /// Page control type enum 9 | /// 10 | public enum PageControlType 11 | { 12 | DropDown = 1, 13 | ListBox = 2, 14 | Button = 3, 15 | ExpressionBox = 4, 16 | Hyperlink = 5, 17 | TableHeader = 6, 18 | Hint = 7, 19 | ValidationText1 = 8, 20 | ValidationText2 = 9, 21 | ActionButton = 10, 22 | ResourceText1 = 11, 23 | ResourceText2 = 12, 24 | ExpressionBoxCopy = 13, 25 | PictureButton = 14 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Models/Altinn2/Translation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Xml.Serialization; 4 | 5 | namespace Altinn2Convert.Models.Altinn2 6 | { 7 | /// 8 | /// Representation of Translation XML document 9 | /// 10 | public class Translation 11 | { 12 | /// 13 | /// The language of the translation 14 | /// 15 | [XmlAttribute("language")] 16 | public string Language { get; set; } 17 | 18 | /// 19 | /// Data areas 20 | /// 21 | [XmlArray] 22 | [XmlArrayItem(ElementName = "DataArea")] 23 | public List DataAreas { get; set; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/workflows/build-validation.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | pull_request: 5 | branches: [ main ] 6 | paths: 7 | - '**.cs' 8 | - '**.csproj' 9 | 10 | env: 11 | DOTNET_VERSION: '6.0.x' # The .NET SDK version to use 12 | 13 | jobs: 14 | build: 15 | 16 | name: build-${{matrix.os}} 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | matrix: 20 | os: [ubuntu-latest, windows-latest, macOS-latest] 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Setup .NET 6 25 | uses: actions/setup-dotnet@v3 26 | with: 27 | dotnet-version: ${{ env.DOTNET_VERSION }} 28 | 29 | - name: Install dependencies 30 | run: dotnet restore 31 | 32 | - name: Build 33 | run: dotnet build --configuration Release --no-restore 34 | -------------------------------------------------------------------------------- /Models/Altinn2/TranslationText.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Xml.Serialization; 5 | 6 | namespace Altinn2Convert.Models.Altinn2 7 | { 8 | /// 9 | /// Representation of Translation Text object 10 | /// 11 | public class TranslationText 12 | { 13 | /// 14 | /// Text type 15 | /// 16 | [XmlAttribute("textType")] 17 | public string TextType { get; set; } 18 | 19 | /// 20 | /// Text code 21 | /// 22 | [XmlAttribute("textCode")] 23 | public string TextCode { get; set; } 24 | 25 | /// 26 | /// The text value 27 | /// 28 | [XmlText] 29 | public string Value { get; set; } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Altinn 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Enums/LanguageType.cs: -------------------------------------------------------------------------------- 1 | namespace Altinn2Convert.Enums 2 | { 3 | /// 4 | /// Enumeration Providing the Supported Languages 5 | /// 6 | public enum LanguageType : int 7 | { 8 | /// 9 | /// Default language 10 | /// 11 | Default = 0, 12 | 13 | /// 14 | /// There has been no changes to this section 15 | /// of the parameters since the edition was created 16 | /// 17 | English = 1033, 18 | 19 | /// 20 | /// There has been changes to this section of parameters since the edition was 21 | /// created, but the section has required parameters that are not yet filled out. 22 | /// 23 | NorwegianNO = 1044, 24 | 25 | /// 26 | /// All required parameters in this section has been filled out 27 | /// 28 | NorwegianNN = 2068, 29 | 30 | /// 31 | /// All required parameters in this section has been filled out 32 | /// 33 | Sami = 1083, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Models/FieldPermission.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Altinn2Convert.Models 6 | { 7 | /// 8 | /// Encapsulates a business entity representing a FieldPermission. 9 | /// 10 | public class FieldPermission 11 | { 12 | /// 13 | /// Gets or sets XPath for the field. 14 | /// 15 | public string XPath { get; set; } 16 | 17 | /// 18 | /// Gets or sets Permission for the field. 19 | /// 20 | public string Permission { get; set; } 21 | 22 | /// 23 | /// Gets or sets Operation for the field. 24 | /// 25 | public string Operation { get; set; } 26 | 27 | /// 28 | /// Gets or sets Permission on Operation for the field. 29 | /// 30 | public int PermissionOnOperation { get; set; } 31 | } 32 | 33 | /// 34 | /// List of FieldPermissionBEs. 35 | /// 36 | public class FieldPermissionList : List 37 | { 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Helpers/ComponentPropsFirstContractResolver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Newtonsoft.Json; 5 | using Newtonsoft.Json.Serialization; 6 | 7 | namespace Altinn2Convert.Helpers 8 | { 9 | /// Custom contract resolver that places the common component props first in the json output 10 | public class ComponentPropsFirstContractResolver : DefaultContractResolver 11 | { 12 | /// 13 | /// Creates properties for the given . 14 | /// 15 | /// The type to create properties for. 16 | /// /// The member serialization mode for the type. 17 | /// Properties for the given . 18 | protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) 19 | { 20 | return base.CreateProperties(type, memberSerialization).OrderBy(p => p.DeclaringType != typeof(Altinn2Convert.Models.Altinn3.layout.Component)).ToList(); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET 6 Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/net6.0/Altinn2Convert.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "integratedTerminal", 18 | "stopAtEntry": true 19 | }, 20 | { 21 | "name": ".NET 6 Attach", 22 | "type": "coreclr", 23 | "request": "attach", 24 | "processId": "${command:pickProcess}" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /Models/Altinn3/TextResource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using Newtonsoft.Json; 5 | 6 | namespace Altinn2Convert.Models.Altinn3 7 | { 8 | /// 9 | /// Representation of a text resource file structure 10 | /// 11 | public class TextResource 12 | { 13 | /// 14 | /// Language of the text resource 15 | /// 16 | [JsonProperty("language")] 17 | public string Language { get; set; } 18 | 19 | /// 20 | /// Collection of text resource items 21 | /// 22 | [JsonProperty("resources")] 23 | public List Resources { get; set; } 24 | } 25 | 26 | /// 27 | /// Represents a text resource item 28 | /// 29 | public class TextResourceItem 30 | { 31 | /// 32 | /// The text resource ID 33 | /// 34 | [JsonProperty("id")] 35 | public string Id { get; set; } 36 | 37 | /// 38 | /// The text resource value 39 | /// 40 | [JsonProperty("value")] 41 | public string Value { get; set; } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Models/Altinn2/TextResource.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Text.Json; 5 | using System.Text.Json.Serialization; 6 | 7 | namespace Altinn2Convert.Models.Altinn2 8 | { 9 | /// 10 | /// Representation of a text resource file structure 11 | /// 12 | public class TextResource 13 | { 14 | /// 15 | /// Language of the text resource 16 | /// 17 | [JsonPropertyName("language")] 18 | public string Language { get; set; } 19 | 20 | /// 21 | /// Collection of text resource items 22 | /// 23 | [JsonPropertyName("resources")] 24 | public List Resources { get; set; } 25 | } 26 | 27 | /// 28 | /// Represents a text resource item 29 | /// 30 | public class TextResourceItem 31 | { 32 | /// 33 | /// The text resource ID 34 | /// 35 | [JsonPropertyName("id")] 36 | public string Id { get; set; } 37 | 38 | /// 39 | /// The text resource value 40 | /// 41 | [JsonPropertyName("value")] 42 | public string Value { get; set; } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Models/Altinn2/Altinn2AppData.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Xml; 3 | using System.Xml.Linq; 4 | 5 | using Altinn2Convert.Models.Altinn2.InfoPath; 6 | 7 | namespace Altinn2Convert.Models.Altinn2 8 | { 9 | public class Altinn2AppData 10 | { 11 | public string Org { get; set; } 12 | 13 | public string App { get; set; } 14 | 15 | public string ModelName { get; set; } 16 | 17 | public ServiceEditionVersion ServiceEditionVersion { get; set; } 18 | 19 | public List Languages { get; } = new List(); 20 | 21 | public List FormMetadata { get; set; } 22 | 23 | public XmlDocument AttachmentTypes { get; set; } 24 | 25 | public XDocument AutorizationRules { get; set; } 26 | 27 | public FormFieldPrefill.FormFieldPrefill FormFieldPrefill { get; set; } 28 | 29 | public XmlDocument FormTrack { get; set; } 30 | 31 | public Dictionary TranslationsParsed { get; set; } = new (); 32 | 33 | public Dictionary TranslationsXml { get; set; } = new (); 34 | 35 | public Dictionary XSNFiles { get; set; } = new (); 36 | 37 | public XDocument Manifest { get; set; } 38 | 39 | } 40 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/Altinn2Convert.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/Altinn2Convert.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/Altinn2Convert.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # altinn2-convert 2 | Command line tool for converting Altinn 2 reporting services into Altinn 3 apps. 3 | 4 | **Note:** This tool is currently a proof of concept (POC), and functionality is therefore limited. 5 | 6 | ## Getting started 7 | These instructions will get you a copy of the project up and running on your local machine. 8 | 9 | ### Installing 10 | Clone the [altinn2-convert repo](https://github.com/Altinn/altinn2-convert) and navigate to the folder. 11 | 12 | ``` 13 | git clone https://github.com/Altinn/altinn2-convert 14 | cd altinn2-convert 15 | ``` 16 | 17 | ### Setting up an empty app 18 | For easy app development, create an empty app in [Altinn Studio](https://altinn.studio) if this does not already exist, and clone this to your local machine. 19 | Files generated using this tool can then be saved directly in the app directory. 20 | 21 | ## Using the converter 22 | 23 | Run the application from your faviorite IDE (f.ex. VS Code), or from the command line using `dotnet run`. Modify the `mode` variable in `Program.cs` to run in different modes 24 | 25 | - "generate": Generate models for the Altinn3 json files based on json schema on altinncdn.no 26 | - "test": Convert a single schema from `TULPACKAGE.zip` in the root directory to `out/` in the project root 27 | - "run": Convert all pacages in "~/TUL" to a Altinn3 app in `~/TULtoAltinn3`. 28 | 29 | There might be other modes in the future when the project matures. 30 | 31 | -------------------------------------------------------------------------------- /Altinn2Convert.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30204.135 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Altinn2Convert", "Altinn2Convert.csproj", "{3F71806A-240F-4A1F-8BDD-A1AFD572771C}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{615066D9-65E6-4066-A2E4-D898C44EF387}" 9 | ProjectSection(SolutionItems) = preProject 10 | .editorconfig = .editorconfig 11 | EndProjectSection 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {3F71806A-240F-4A1F-8BDD-A1AFD572771C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {3F71806A-240F-4A1F-8BDD-A1AFD572771C}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {3F71806A-240F-4A1F-8BDD-A1AFD572771C}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {3F71806A-240F-4A1F-8BDD-A1AFD572771C}.Release|Any CPU.Build.0 = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | GlobalSection(ExtensibilityGlobals) = postSolution 28 | SolutionGuid = {D164C087-0281-42EE-BB93-F887EED01233} 29 | EndGlobalSection 30 | EndGlobal 31 | -------------------------------------------------------------------------------- /Helpers/InfoPathXmlParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Threading.Tasks; 5 | using System.Xml; 6 | using System.Xml.Linq; 7 | 8 | namespace Altinn2Convert.Helpers 9 | { 10 | public class InfoPathXmlParser 11 | { 12 | private string _rootPath; 13 | 14 | public void Extract(string tmpDir, string language, string xsnPath) 15 | { 16 | var e = new CabLib.Extract(); 17 | var extractedXsnPath = Path.Join(tmpDir, "form", language); 18 | 19 | e.ExtractFile(xsnPath, extractedXsnPath); 20 | e.CleanUp(); 21 | _rootPath = extractedXsnPath; 22 | } 23 | 24 | public XmlDocument GetManifest() 25 | { 26 | var x = new XmlDocument(); 27 | x.Load(Path.Join(_rootPath, "manifest.xsf")); 28 | return x; 29 | } 30 | 31 | public Dictionary GetPages(List pageIds) 32 | { 33 | var ret = new Dictionary(); 34 | foreach (var page in pageIds) 35 | { 36 | ret[page] = XDocument.Load(Path.Join(_rootPath, page)); 37 | } 38 | 39 | return ret; 40 | } 41 | 42 | public string GetXSDDocument() 43 | { 44 | var path = Path.Join(_rootPath, "myschema.xsd"); 45 | if (!File.Exists(path)) 46 | { 47 | return null; 48 | } 49 | 50 | var x = new XmlDocument(); 51 | x.Load(path); 52 | var schemaLocation = x.GetElementsByTagName("import", "http://www.w3.org/2001/XMLSchema").Item(0).Attributes.GetNamedItem("schemaLocation").Value; 53 | return File.ReadAllText(Path.Join(_rootPath, schemaLocation)); 54 | } 55 | 56 | } 57 | } -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.IO; 4 | using System.Threading.Tasks; 5 | using Altinn2Convert.Services; 6 | 7 | namespace Altinn2Convert 8 | { 9 | /// 10 | /// Program. 11 | /// 12 | public class Program 13 | { 14 | /// 15 | /// Main method. 16 | /// 17 | public static async Task Main() 18 | { 19 | CultureInfo.CurrentCulture = new CultureInfo("en_US"); 20 | // var mode = "generate"; 21 | // var mode = "test"; 22 | var mode = "run"; 23 | if (mode == "generate") 24 | { 25 | var generateClass = new GenerateAltinn3ClassesFromJsonSchema(); 26 | await generateClass.Generate(); 27 | } 28 | 29 | if (mode == "test") 30 | { 31 | var service = new ConvertService(); 32 | var targetDirectory = "out"; 33 | if (Directory.Exists(Path.Join(targetDirectory))) 34 | { 35 | Directory.Delete(Path.Join(targetDirectory), recursive: true); 36 | } 37 | 38 | var a2 = await service.ParseAltinn2File("TULPACKAGE.zip", targetDirectory); 39 | await service.DumpRawTulPackageAsJson(a2, targetDirectory); 40 | var a3 = await service.Convert(a2); 41 | // await service.DeduplicateTests(a3); 42 | // service.CopyAppTemplate(targetDirectory); 43 | // await service.UpdateAppTemplateFiles(targetDirectory, a3); 44 | await service.WriteAltinn3Files(a3, targetDirectory); 45 | } 46 | 47 | if (mode == "run") 48 | { 49 | var homeFolder = System.Environment.GetFolderPath(System.Environment.SpecialFolder.UserProfile); 50 | var tulFolder = Path.Join(homeFolder, "TUL"); 51 | var altinn3Folder = Path.Join(homeFolder, "TULtoAltinn3"); 52 | 53 | var bs = new BatchService(); 54 | await bs.ConvertAll(tulFolder, altinn3Folder); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Altinn2Convert.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net6.0 6 | warnings 7 | 8 | 9 | 10 | 9 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | all 25 | runtime; build; native; contentfiles; analyzers; buildtransitive 26 | 27 | 28 | 29 | 30 | 31 | 32 | Altinn3.ruleset 33 | 34 | 35 | 36 | 1701;1702; 37 | 38 | 39 | 40 | true 41 | $(NoWarn);1591 42 | Altinn 43 | Altinn 44 | 45 | 46 | 47 | 48 | CabLib.dll 49 | 50 | 51 | 52 | 53 | 54 | Always 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Models/Altinn2/FormFieldPrefill.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Xml.Serialization; 4 | 5 | namespace Altinn2Convert.Models.Altinn2.FormFieldPrefill 6 | { 7 | [XmlRoot(ElementName="FormRegisterPrefill")] 8 | public class FormFieldPrefill 9 | { 10 | [XmlElement(ElementName="Register")] 11 | public List Register { get; set; } 12 | 13 | [XmlElement(ElementName="PredefinedDefinition")] 14 | public PredefinedDefinition PredefinedDefinition { get; set; } 15 | 16 | [XmlElement(ElementName="OtherPrefillSettings")] 17 | public OtherPrefillSettings OtherPrefillSettings { get; set; } 18 | 19 | [XmlAttribute(AttributeName="formatVersion")] 20 | public string FormatVersion { get; set; } 21 | 22 | [XmlAttribute(AttributeName="frp", Namespace="http://www.w3.org/2000/xmlns/")] 23 | public string Frp { get; set; } 24 | } 25 | 26 | [XmlRoot(ElementName="field")] 27 | public class Field 28 | { 29 | [XmlAttribute(AttributeName="xPath")] 30 | public string XPath { get; set; } 31 | 32 | [XmlAttribute(AttributeName="page")] 33 | public string Page { get; set; } 34 | 35 | [XmlAttribute(AttributeName="contextType")] 36 | public string ContextType { get; set; } 37 | 38 | [XmlAttribute(AttributeName="registerField")] 39 | public string RegisterField { get; set; } 40 | } 41 | 42 | [XmlRoot(ElementName="Register")] 43 | public class Register 44 | { 45 | [XmlElement(ElementName="field")] 46 | public List Field { get; set; } 47 | 48 | [XmlAttribute(AttributeName="name")] 49 | public string Name { get; set; } 50 | } 51 | 52 | [XmlRoot(ElementName="PredefinedDefinition")] 53 | public class PredefinedDefinition 54 | { 55 | [XmlAttribute(AttributeName="name")] 56 | public string Name { get; set; } 57 | } 58 | 59 | [XmlRoot(ElementName="KeyValuePrefill")] 60 | public class KeyValuePrefill 61 | { 62 | [XmlAttribute(AttributeName="enabled")] 63 | public string Enabled { get; set; } 64 | } 65 | 66 | [XmlRoot(ElementName="OtherPrefillSettings")] 67 | public class OtherPrefillSettings 68 | { 69 | [XmlElement(ElementName="KeyValuePrefill")] 70 | public KeyValuePrefill KeyValuePrefill { get; set; } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Helpers/ModelConverter.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Xml; 8 | using System.Xml.Linq; 9 | using System.Xml.Schema; 10 | 11 | // using Altinn.Studio.Designer.Factories.ModelFactory; 12 | // using Altinn.Studio.Designer.ModelMetadatalModels; 13 | 14 | using Altinn2Convert.Models.Altinn2; 15 | using Altinn2Convert.Models.Altinn3; 16 | 17 | using Manatee.Json.Schema; 18 | using Newtonsoft.Json; 19 | 20 | namespace Altinn2Convert.Helpers 21 | { 22 | public static class ModelConverter 23 | { 24 | public static Dictionary Convert(Altinn2AppData a2, out string? modelName) 25 | { 26 | modelName = null; 27 | var ret = new Dictionary(); 28 | if (a2.XSNFiles.Count == 0) 29 | { 30 | return ret; 31 | } 32 | 33 | // Get xsd from first xsn file (all languages are equal) 34 | string xsd = a2.XSNFiles.First().Value.XSDDocument; 35 | if (xsd == null) 36 | { 37 | return ret; 38 | } 39 | 40 | // using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xsd))) 41 | // { 42 | // var reader = XmlReader.Create(stream); 43 | // XsdToJsonSchema xsdToJsonSchemaConverter = new XsdToJsonSchema(reader); 44 | 45 | // JsonSchema schemaJsonSchema = xsdToJsonSchemaConverter.AsJsonSchema(); 46 | 47 | // JsonSchemaToInstanceModelGenerator converter = new JsonSchemaToInstanceModelGenerator(a2.Org, a2.App, schemaJsonSchema); 48 | // ModelMetadata modelMetadata = converter.GetModelMetadata(); 49 | 50 | // modelName = modelMetadata.Elements["melding"].TypeName; 51 | // // generate c# model 52 | // JsonMetadataParser modelGenerator = new JsonMetadataParser(); 53 | // string classes = modelGenerator.CreateModelFromMetadata(modelMetadata); 54 | 55 | // HandleTexts(org, app, converter.GetTexts()); 56 | 57 | // add files to return 58 | ret.Add($"model.xsd", xsd); 59 | // ret.Add($"model.schema.json", new Manatee.Json.Serialization.JsonSerializer().Serialize(schemaJsonSchema).GetIndentedString(0)); 60 | // ret.Add($"model.metadata.json", JsonConvert.SerializeObject(modelMetadata, Newtonsoft.Json.Formatting.Indented)); 61 | // ret.Add($"model.cs", classes); 62 | // } 63 | 64 | return ret; 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /Helpers/XExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Xml.Linq; 4 | 5 | // Found on https://stackoverflow.com/a/23541182 6 | namespace Altinn2Convert.Helpers 7 | { 8 | public static class XExtensions 9 | { 10 | /// 11 | /// Get the absolute XPath to a given XElement, including the namespace. 12 | /// (e.g. "/a:people/b:person[6]/c:name[1]/d:last[1]"). 13 | /// 14 | public static string GetAbsoluteXPath(this XElement element) 15 | { 16 | if (element == null) 17 | { 18 | throw new ArgumentNullException("element"); 19 | } 20 | 21 | 22 | Func relativeXPath = e => 23 | { 24 | int index = e.IndexPosition(); 25 | 26 | var currentNamespace = e.Name.Namespace; 27 | 28 | string name; 29 | if (string.IsNullOrEmpty(currentNamespace.ToString())) 30 | { 31 | name = e.Name.LocalName; 32 | } 33 | else 34 | { 35 | // name = "*[local-name()='" + e.Name.LocalName + "']"; 36 | string namespacePrefix = e.GetPrefixOfNamespace(currentNamespace); 37 | name = namespacePrefix + ":" + e.Name.LocalName; 38 | } 39 | 40 | // If the element is the root or has no sibling elements, no index is required 41 | return ((index == -1) || (index == -2)) ? "/" + name : string.Format( 42 | "/{0}[{1}]", 43 | name, 44 | index.ToString()); 45 | }; 46 | 47 | var ancestors = from e in element.Ancestors() 48 | select relativeXPath(e); 49 | 50 | return string.Concat(ancestors.Reverse().ToArray()) + 51 | relativeXPath(element); 52 | } 53 | 54 | /// 55 | /// Get the index of the given XElement relative to its 56 | /// siblings with identical names. If the given element is 57 | /// the root, -1 is returned or -2 if element has no sibling elements. 58 | /// 59 | /// 60 | /// The element to get the index of. 61 | /// 62 | public static int IndexPosition(this XElement element) 63 | { 64 | if (element == null) 65 | { 66 | throw new ArgumentNullException("element"); 67 | } 68 | 69 | if (element.Parent == null) 70 | { 71 | // Element is root 72 | return -1; 73 | } 74 | 75 | if (element.Parent.Elements(element.Name).Count() == 1) 76 | { 77 | // Element has no sibling elements 78 | return -2; 79 | } 80 | 81 | int i = 1; // Indexes for nodes start at 1, not 0 82 | 83 | foreach (var sibling in element.Parent.Elements(element.Name)) 84 | { 85 | if (sibling == element) 86 | { 87 | return i; 88 | } 89 | 90 | i++; 91 | } 92 | 93 | throw new InvalidOperationException("element has been removed from its parent."); 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /Services/GenerateAltinn3ClassesFromJsonSchema.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Threading.Tasks; 6 | using NJsonSchema; 7 | using NJsonSchema.CodeGeneration.CSharp; 8 | 9 | namespace Altinn2Convert.Services 10 | { 11 | /// 12 | /// Http client for fetching schemas from CDN 13 | /// 14 | public class GenerateAltinn3ClassesFromJsonSchema 15 | { 16 | /// 17 | /// Http client for fetching schemas from CDN 18 | /// 19 | private HttpClient _httpClient; 20 | 21 | /// 22 | /// constructor 23 | /// 24 | public GenerateAltinn3ClassesFromJsonSchema() 25 | { 26 | _httpClient = new HttpClient(); 27 | } 28 | 29 | /// 30 | /// Settings for C# class generation 31 | /// 32 | private CSharpGeneratorSettings GetSettings(string folderName) 33 | { 34 | return new CSharpGeneratorSettings 35 | { 36 | Namespace = $"Altinn2Convert.Models.Altinn3.{folderName}", 37 | GenerateNullableReferenceTypes = true, 38 | GenerateDataAnnotations = true, 39 | GenerateOptionalPropertiesAsNullable = true 40 | }; 41 | } 42 | 43 | /// 44 | /// Download the json schemas from cdn 45 | /// 46 | private async Task GetJsonSchema() 47 | { 48 | var urls = new string[] 49 | { 50 | // "https://altinncdn.no/schemas/json/component/number-format.schema.v1.json", 51 | // "https://altinncdn.no/schemas/json/layout/layout-sets.schema.v1.json", 52 | "https://altinncdn.no/schemas/json/layout/layout.schema.v1.json", 53 | "https://altinncdn.no/schemas/json/layout/layoutSettings.schema.v1.json", 54 | "https://altinncdn.no/schemas/json/policy/policy.schema.v1.json", 55 | "https://altinncdn.no/schemas/json/prefill/prefill.schema.v1.json", 56 | "https://altinncdn.no/schemas/json/widget/widget.schema.v1.json" 57 | }; 58 | var tasks = urls.Select(url => JsonSchema.FromUrlAsync(url)); 59 | var result = await Task.WhenAll(tasks); 60 | return result; 61 | } 62 | 63 | /// 64 | /// Extracts Layout from InfoPath views, and writes the result to disk 65 | /// 66 | public async Task Generate() 67 | { 68 | var jsons = await GetJsonSchema(); 69 | foreach (var json in jsons) 70 | { 71 | var documentPathSplit = json.DocumentPath.Split('/'); 72 | var filename = documentPathSplit[documentPathSplit.Length - 1].Split('.').First(); 73 | // var folder = documentPathSplit[documentPathSplit.Length - 2]; 74 | var path = Path.Join("Models", "Altinn3", filename); 75 | if (!Directory.Exists(path)) 76 | { 77 | Directory.CreateDirectory(path); 78 | } 79 | 80 | var generator = new CSharpGenerator(null, GetSettings(filename)); 81 | var file = generator.GenerateFile(json, "test"); 82 | await File.WriteAllTextAsync(Path.Join(path, $"{filename}.cs"), file, System.Text.Encoding.UTF8); 83 | } 84 | } 85 | } 86 | 87 | } -------------------------------------------------------------------------------- /Models/Altinn3/Altinn3AppData.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text.RegularExpressions; 6 | using Altinn.Platform.Storage.Interface.Models; 7 | using Layout = Altinn2Convert.Models.Altinn3.layout.Test; 8 | using LayoutSettings = Altinn2Convert.Models.Altinn3.layoutSettings.Test; 9 | using Prefill = Altinn2Convert.Models.Altinn3.prefill.Test; 10 | 11 | namespace Altinn2Convert.Models.Altinn3 12 | { 13 | public class Altinn3AppData 14 | { 15 | /// Layouts of the app 16 | public Dictionary Layouts { get; set; } = new Dictionary(); 17 | 18 | /// Texts of the app 19 | public Dictionary Texts { get; set; } = new Dictionary(); 20 | 21 | public LayoutSettings LayoutSettings { get; set; } = new() { Pages = new() { Order = new(), ExcludeFromPdf = new(), Triggers = new() } }; 22 | 23 | public Prefill? Prefill { get; set; } 24 | 25 | /// map of filename in /App/models/[filename] and the string content of the file 26 | public Dictionary ModelFiles { get; set; } = new(); 27 | 28 | public string? ModelName { get; set; } 29 | 30 | public Application ApplicationMetadata { get; set; } = new(); 31 | 32 | public PolicyUpdates PolicyUpdates { get; set; } = new(); 33 | 34 | public Readme Readme { get; set; } = new(); 35 | 36 | #region helper functions 37 | 38 | public void AddLayout(string page, Models.Altinn3.layout.Layout layout) 39 | { 40 | Layouts[page] = new() { Data = new() { Layout = layout } }; 41 | LayoutSettings?.Pages?.Order?.Add(page); 42 | } 43 | 44 | public void AddText(string lang, string id, string value) 45 | { 46 | if (value == null) 47 | { 48 | return; 49 | } 50 | 51 | if (!Texts.ContainsKey(lang)) 52 | { 53 | Texts[lang] = new TextResource { Language = lang, Resources = new() }; 54 | } 55 | 56 | Texts[lang].Resources.Add(new TextResourceItem { Id = id, Value = StripUselessHtml(value) }); 57 | } 58 | 59 | /// Add texts 60 | /// Texts[lang][key] = text 61 | public void AddTexts(Dictionary> texts) 62 | { 63 | foreach (var (lang, keyText) in texts) 64 | { 65 | foreach (var (key, text) in keyText) 66 | { 67 | AddText(lang, key, text); 68 | } 69 | } 70 | } 71 | 72 | private Regex _htmlRegexWrappingDiv = new Regex(@"^
([^<]*)<\/div>$", RegexOptions.IgnoreCase); 73 | 74 | public string StripUselessHtml(string input) 75 | { 76 | // TODO: Find some better way to do (more of) this. 77 | var match = _htmlRegexWrappingDiv.Match(input); 78 | if (match.Success) 79 | { 80 | return match.Groups[1].Value; 81 | } 82 | 83 | return input; 84 | } 85 | 86 | public List GetOptionIds() 87 | { 88 | return Layouts.Values.SelectMany(layout => 89 | layout?.Data?.Layout?.Select(component => (component as Altinn3.layout.SelectionComponents)?.OptionsId) ?? new List()) 90 | .Where(c => c is not null).Select(c => c!) // Strip nulls and nullability 91 | .Distinct().ToList(); 92 | } 93 | 94 | #endregion 95 | } 96 | } -------------------------------------------------------------------------------- /Services/BatchService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Compression; 4 | using System.Text.RegularExpressions; 5 | using System.Threading.Tasks; 6 | using System.Xml.Linq; 7 | using System.Xml.XPath; 8 | 9 | namespace Altinn2Convert.Services 10 | { 11 | public class BatchService 12 | { 13 | public async Task ConvertAll(string sourceDirectory, string targetDirectory) 14 | { 15 | // Ensure targetDirectory exists 16 | Directory.CreateDirectory(targetDirectory); 17 | 18 | var zipFiles = new DirectoryInfo(sourceDirectory); 19 | foreach (var zipFileInfo in zipFiles.EnumerateFiles("*.zip")) 20 | { 21 | var zipFile = zipFileInfo.Name; 22 | var name = GetPackageName(Path.Join(sourceDirectory, zipFile)); 23 | Console.WriteLine($"Converting {zipFile}"); 24 | PrepareTargetDirectory(zipFile, targetDirectory, name); 25 | try 26 | { 27 | await DoConversion(zipFile, sourceDirectory, Path.Join(targetDirectory, name)); 28 | } 29 | catch (Exception e) 30 | { 31 | Console.ForegroundColor = ConsoleColor.Red; 32 | Console.Error.WriteLine(e.StackTrace); 33 | Console.Error.WriteLine(e.Message); 34 | Console.Error.WriteLine($"\nFailed to convert {zipFile}\n"); 35 | Console.ResetColor(); 36 | return; 37 | } 38 | } 39 | } 40 | 41 | public string GetPackageName(string zipFile) 42 | { 43 | using (var stream = new FileStream(zipFile, FileMode.Open, FileAccess.Read)) 44 | using (var archive = new ZipArchive(stream, ZipArchiveMode.Read, leaveOpen: false, entryNameEncoding: System.Text.Encoding.UTF8)) 45 | using (var manifestStream = archive.GetEntry("manifest.xml").Open()) 46 | { 47 | var manifest = XDocument.Load(manifestStream); 48 | // var ownerOrg = manifest.XPathSelectElement("/ServiceEditionVersion/DataAreas/DataArea[@type=\"Service\"]/Property[@name=\"ServiceOwnerCode\"]").Attribute("value").Value; 49 | var serviceName = manifest.XPathSelectElement("/ServiceEditionVersion/DataAreas/DataArea[@type=\"Service\"]/Property[@name=\"ServiceName\"]")?.Attribute("value")?.Value; 50 | return Regex.Replace(serviceName, "[^0-9a-zA-Z -]", ""); 51 | } 52 | } 53 | 54 | public void PrepareTargetDirectory(string zipFile, string targetDirectory, string name) 55 | { 56 | // ensure existing folders with the same name are cleared 57 | if (Directory.Exists(Path.Join(targetDirectory, name))) 58 | { 59 | Directory.Delete(Path.Join(targetDirectory, name), recursive: true); 60 | } 61 | 62 | Directory.CreateDirectory(Path.Join(targetDirectory, name)); 63 | } 64 | 65 | public async Task DoConversion(string zipFile, string sourceDirectory, string targetDirectory) 66 | { 67 | var service = new ConvertService(); 68 | var a2 = await service.ParseAltinn2File(Path.Join(sourceDirectory, zipFile), targetDirectory); 69 | await service.DumpRawTulPackageAsJson(a2, targetDirectory); 70 | var a3 = await service.Convert(a2); 71 | // await service.DeduplicateTests(a3); 72 | // service.CopyAppTemplate(targetDirectory); 73 | // await service.UpdateAppTemplateFiles(targetDirectory, a3); 74 | await service.WriteAltinn3Files(a3, targetDirectory); 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /Models/Altinn2/FormMetadata.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text.RegularExpressions; 6 | using System.Xml.Serialization; 7 | 8 | namespace Altinn2Convert.Models.Altinn2.FormMetadata 9 | { 10 | public class FormMetadata 11 | { 12 | public FormMetadata(string name, string caption, string transform, int sequence, string pageType) 13 | { 14 | Name = name; 15 | Caption = caption; 16 | Transform = transform; 17 | Sequence = sequence; 18 | PageType = pageType; 19 | } 20 | 21 | public string Name { get; internal set; } 22 | 23 | public string Caption { get; internal set; } 24 | 25 | private static readonly Regex _invalidChars = new Regex(@"[^a-zA-Z0-9-_.]"); 26 | 27 | private static readonly Regex _dash = new Regex(@"[-]+"); 28 | 29 | private static readonly Regex _dotDash = new Regex(@"[.][-]"); 30 | 31 | private string _sanitize(string input) 32 | { 33 | input = input 34 | .Replace("æ", "ae") 35 | .Replace("ø", "oe") 36 | .Replace("å", "aa") 37 | .Replace("Æ", "Ae") 38 | .Replace("Ø", "Oe") 39 | .Replace("Å", "Aa"); 40 | return _dotDash.Replace(_dash.Replace(_invalidChars.Replace(input, "-"), "-"), "."); 41 | } 42 | 43 | public string A3PageName 44 | { 45 | get { return SanitizedCaption; } 46 | } 47 | 48 | public string SanitizedCaption 49 | { 50 | get { return _sanitize(Caption); } 51 | } 52 | 53 | public string SanitizedName 54 | { 55 | get { return _sanitize(Name); } 56 | } 57 | 58 | public string Transform { get; internal set; } 59 | 60 | public int Sequence { get; internal set; } 61 | 62 | public string PageType { get; internal set; } 63 | } 64 | 65 | [XmlRoot(ElementName="FormPages")] 66 | public class FormPages 67 | { 68 | [XmlElement(ElementName="Page")] 69 | public List Page { get; set; } 70 | 71 | [XmlAttribute(AttributeName="formatVersion")] 72 | public string FormatVersion { get; set; } 73 | 74 | [XmlAttribute(AttributeName="fp", Namespace="http://www.w3.org/2000/xmlns/")] 75 | public string Fp { get; set; } 76 | 77 | public List GetFormMetadata() 78 | { 79 | return this.Page.Select(p => new FormMetadata( 80 | p.Name, 81 | p.Property.Find(m => m.Name == "Caption")?.TextCode, 82 | p.Property.Find(m => m.Name == "Transform")?.Value, 83 | int.Parse(p.Property.Find(m => m.Name == "Sequence")?.Value ?? "0"), 84 | p.Property.Find(m => m.Name == "PageType")?.Value)) 85 | .OrderBy(m => m.Sequence).ToList(); 86 | } 87 | } 88 | 89 | [XmlRoot(ElementName="Property")] 90 | public class Property 91 | { 92 | [XmlAttribute(AttributeName="name")] 93 | public string Name { get; set; } 94 | 95 | [XmlAttribute(AttributeName="textCode")] 96 | public string TextCode { get; set; } 97 | 98 | [XmlAttribute(AttributeName="value")] 99 | public string Value { get; set; } 100 | } 101 | 102 | [XmlRoot(ElementName="Page")] 103 | public class Page 104 | { 105 | [XmlElement(ElementName="Property")] 106 | public List Property { get; set; } 107 | 108 | [XmlAttribute(AttributeName="name")] 109 | public string Name { get; set; } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /Models/Altinn2/ServiceEditionVersion.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Xml.Serialization; 4 | 5 | namespace Altinn2Convert.Models.Altinn2 6 | { 7 | /// 8 | /// Service that handles extraction of texts. 9 | /// 10 | public class ServiceEditionVersion 11 | { 12 | /// 13 | /// Package info 14 | /// 15 | [XmlArray] 16 | [XmlArrayItem(ElementName = "Property")] 17 | public List PackageInfo { get; set; } 18 | 19 | /// 20 | /// Data areas 21 | /// 22 | [XmlArray] 23 | [XmlArrayItem(ElementName = "DataArea")] 24 | public List DataAreas { get; set; } 25 | 26 | /// 27 | /// Translations 28 | /// 29 | public Translations Translations { get; set; } 30 | } 31 | 32 | /// 33 | /// Data Area 34 | /// 35 | public class DataArea 36 | { 37 | /// 38 | /// Data area type 39 | /// 40 | [XmlAttribute("type")] 41 | public string Type { get; set; } 42 | 43 | /// 44 | /// Data area properties 45 | /// 46 | [XmlArray] 47 | [XmlArrayItem(ElementName = "Property")] 48 | public List Properties { get; set; } 49 | 50 | /// 51 | /// Logical form 52 | /// 53 | [XmlElement("LogicalForm")] 54 | public LogicalForm LogicalForm { get; set; } 55 | 56 | /// 57 | /// Translation texts 58 | /// 59 | [XmlArray] 60 | [XmlArrayItem(ElementName = "Text")] 61 | public List Texts { get; set; } 62 | 63 | /// 64 | /// Data area properties 65 | /// 66 | [XmlArray] 67 | [XmlArrayItem(ElementName = "File")] 68 | public List Files { get; set; } 69 | } 70 | 71 | /// 72 | /// Logical form 73 | /// 74 | public class LogicalForm 75 | { 76 | /// 77 | /// Logical form properties 78 | /// 79 | [XmlArray] 80 | [XmlArrayItem(ElementName = "Property")] 81 | public List Properties { get; set; } 82 | 83 | /// 84 | /// Data area properties 85 | /// 86 | [XmlArray] 87 | [XmlArrayItem(ElementName = "File")] 88 | public List Files { get; set; } 89 | 90 | /// 91 | /// Translation texts 92 | /// 93 | [XmlArray] 94 | [XmlArrayItem(ElementName = "Texts")] 95 | public List Texts { get; set; } 96 | } 97 | 98 | /// 99 | /// Property 100 | /// 101 | public class Property 102 | { 103 | /// 104 | /// Name 105 | /// 106 | [XmlAttribute("name")] 107 | public string Name { get; set; } 108 | 109 | /// 110 | /// Value 111 | /// 112 | [XmlAttribute("value")] 113 | public string Value { get; set; } 114 | } 115 | 116 | /// 117 | /// Translation 118 | /// 119 | public class Translations 120 | { 121 | /// 122 | /// Translation file list 123 | /// 124 | [XmlArray] 125 | [XmlArrayItem(ElementName = "File")] 126 | public List Files { get; set; } 127 | } 128 | 129 | /// 130 | /// Translation file 131 | /// 132 | public class ServiceFile 133 | { 134 | /// 135 | /// Language 136 | /// 137 | [XmlAttribute("language")] 138 | public string Language { get; set; } 139 | 140 | /// 141 | /// Version 142 | /// 143 | [XmlAttribute("version")] 144 | public string Version { get; set; } 145 | 146 | /// 147 | /// Name 148 | /// 149 | [XmlAttribute("name")] 150 | public string Name { get; set; } 151 | 152 | /// 153 | /// File type 154 | /// 155 | [XmlAttribute("fileType")] 156 | public string FileType { get; set; } 157 | } 158 | } -------------------------------------------------------------------------------- /Helpers/Utils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.IO.Compression; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Xml.Serialization; 8 | using Altinn2Convert.Models.Altinn2; 9 | 10 | namespace Altinn2Convert.Helpers 11 | { 12 | /// 13 | /// Collection of utility methods 14 | /// 15 | public class Utils 16 | { 17 | /// 18 | /// Method to nest up relative paths (XPaths). 19 | /// 20 | /// Example: 21 | /// input: "/root/group1/child1/../../group2/field1" 22 | /// output: "/root/group2/field1" 23 | /// 24 | /// 25 | /// Empty or NULL strings will be returned as-is 26 | /// 27 | /// 28 | /// The string with a relative path, e.g. "/message/delivery/form/entity/employeeTax/@my:container/../@my:section/../@my:attribute" 29 | /// The un-nested path, e.g. "/message/delivery/form/entity/employeeTax/@my:attribute" 30 | public static string UnbundlePath(string path) 31 | { 32 | if (string.IsNullOrEmpty(path)) 33 | { 34 | return path; 35 | } 36 | 37 | string[] parts = path.Split('/'); 38 | 39 | Stack pathElements = new Stack(); 40 | 41 | foreach (string item in parts) 42 | { 43 | if ("..".Equals(item) && pathElements.Count > 0 && !string.IsNullOrEmpty(pathElements.Peek())) 44 | { 45 | pathElements.Pop(); 46 | } 47 | else if ("..".Equals(item)) 48 | { 49 | // don't add unwanted items to the stack 50 | } 51 | else if (".".Equals(item)) 52 | { 53 | // don't add unwanted items to the stack 54 | } 55 | else 56 | { 57 | pathElements.Push(item); 58 | } 59 | } 60 | 61 | StringBuilder absolutePath = new StringBuilder(); 62 | pathElements.Reverse(); 63 | 64 | foreach (string item in pathElements.Reverse()) 65 | { 66 | absolutePath.Append(item + "/"); 67 | } 68 | 69 | if (absolutePath.Length == 0) 70 | { 71 | return ""; 72 | } 73 | 74 | absolutePath.Remove(absolutePath.Length - 1, 1); // remove the last "/" since we allways add an extra "/" at the end in the construction phase. 75 | 76 | return absolutePath.ToString(); 77 | } 78 | 79 | /// 80 | /// Set up InfoPath parser, extract 81 | /// 82 | /// Path to service zip file 83 | /// Path to store output files 84 | /// The command that was used 85 | /// The temporary directory where extracted files are stored 86 | public static ServiceEditionVersion RunSetup(string zipPath, string outputPath, string command, string tmpDir) 87 | { 88 | if (File.Exists(zipPath)) 89 | { 90 | ZipFile.ExtractToDirectory(zipPath, tmpDir); 91 | SetupOutputDir(outputPath, command); 92 | using var fileStream = File.Open(Path.Join(tmpDir, "manifest.xml"), FileMode.Open); 93 | XmlSerializer serializer = new XmlSerializer(typeof(ServiceEditionVersion)); 94 | ServiceEditionVersion serviceEditionVersion = (ServiceEditionVersion)serializer.Deserialize(fileStream); 95 | return serviceEditionVersion; 96 | } 97 | 98 | throw new Exception("Unable to extract service from provided zip file path. Please check that the path is correct."); 99 | } 100 | 101 | private static void SetupOutputDir(string outputPath, string command) 102 | { 103 | string fullPath = command switch 104 | { 105 | "texts" => Path.Join(outputPath, "config", "texts"), 106 | "layout" => Path.Join(outputPath, "ui", "layouts"), 107 | _ => outputPath 108 | }; 109 | 110 | if (!Directory.Exists(fullPath)) 111 | { 112 | Directory.CreateDirectory(fullPath); 113 | } 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Models/Altinn3/layout/LayoutInheritance.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | 3 | namespace Altinn2Convert.Models.Altinn3.layout 4 | { 5 | public partial class Test 6 | { 7 | [JsonProperty("$schema", Order = int.MinValue)] 8 | public string schema { get; } = "https://altinncdn.no/schemas/json/layout/layout.schema.v1.json"; 9 | } 10 | 11 | public partial class Component 12 | { 13 | public Component() 14 | { 15 | Type = (ComponentType)(-1); 16 | } 17 | } 18 | 19 | public partial class AddressComponent : Component 20 | { 21 | public AddressComponent() 22 | { 23 | Type = ComponentType.AddressComponent; 24 | } 25 | } 26 | 27 | public partial class AttachmentListComponent : Component 28 | { 29 | public AttachmentListComponent() 30 | { 31 | Type = ComponentType.AttachmentList; 32 | } 33 | } 34 | 35 | public partial class ButtonComponent : Component 36 | { 37 | public ButtonComponent() 38 | { 39 | Type = ComponentType.Button; 40 | } 41 | } 42 | 43 | public partial class CheckboxesComponent : Component 44 | { 45 | public CheckboxesComponent() 46 | { 47 | Type = ComponentType.Checkboxes; 48 | } 49 | } 50 | 51 | public partial class DatepickerComponent : Component 52 | { 53 | public DatepickerComponent() 54 | { 55 | Type = ComponentType.Datepicker; 56 | } 57 | } 58 | 59 | public partial class FileUploadComponent : Component 60 | { 61 | public FileUploadComponent() 62 | { 63 | Type = ComponentType.FileUpload; 64 | } 65 | } 66 | 67 | public partial class GroupComponent : Component 68 | { 69 | public GroupComponent() 70 | { 71 | Type = ComponentType.Group; 72 | } 73 | } 74 | 75 | public partial class HeaderComponent : Component 76 | { 77 | public HeaderComponent() 78 | { 79 | Type = ComponentType.Header; 80 | } 81 | } 82 | 83 | public partial class ImageComponent : Component 84 | { 85 | public ImageComponent() 86 | { 87 | Type = ComponentType.Image; 88 | } 89 | } 90 | 91 | public partial class InputComponent : Component 92 | { 93 | public InputComponent() 94 | { 95 | Type = ComponentType.Input; 96 | } 97 | } 98 | 99 | public partial class NavigationButtonsComponent : Component 100 | { 101 | public NavigationButtonsComponent() 102 | { 103 | Type = ComponentType.NavigationButtons; 104 | } 105 | } 106 | 107 | public partial class ParagraphComponent : Component 108 | { 109 | public ParagraphComponent() 110 | { 111 | Type = ComponentType.Paragraph; 112 | } 113 | } 114 | 115 | public partial class SelectionComponents : Component 116 | { 117 | } 118 | 119 | public partial class RadioButtonsComponent : SelectionComponents 120 | { 121 | public RadioButtonsComponent() 122 | { 123 | Type = ComponentType.RadioButtons; 124 | } 125 | } 126 | 127 | public partial class DropdownComponent : SelectionComponents 128 | { 129 | public DropdownComponent() 130 | { 131 | Type = ComponentType.Dropdown; 132 | } 133 | } 134 | 135 | public partial class SummaryComponent : Component 136 | { 137 | public SummaryComponent() 138 | { 139 | Type = ComponentType.Summary; 140 | } 141 | } 142 | 143 | public partial class TextAreaComponent : Component 144 | { 145 | public TextAreaComponent() 146 | { 147 | Type = ComponentType.TextArea; 148 | } 149 | } 150 | 151 | public partial class Src 152 | { 153 | public string this[string language] 154 | { 155 | get 156 | { 157 | switch (language) 158 | { 159 | case "nb": 160 | return this.Nb; 161 | case "nn": 162 | return this.Nn; 163 | case "en": 164 | return this.En; 165 | default: 166 | return AdditionalProperties[language] as string; 167 | } 168 | } 169 | 170 | set 171 | { 172 | switch (language) 173 | { 174 | case "nb": 175 | this.Nb = value; 176 | break; 177 | case "nn": 178 | this.Nn = value; 179 | break; 180 | case "en": 181 | this.En = value; 182 | break; 183 | default: 184 | AdditionalProperties[language] = value; 185 | break; 186 | } 187 | } 188 | } 189 | } 190 | } -------------------------------------------------------------------------------- /Models/Altinn3/policy/policy.cs: -------------------------------------------------------------------------------- 1 | //---------------------- 2 | // 3 | // Generated using the NJsonSchema v10.5.2.0 (Newtonsoft.Json v13.0.0.0) (http://NJsonSchema.org) 4 | // 5 | //---------------------- 6 | 7 | #nullable enable 8 | 9 | namespace Altinn2Convert.Models.Altinn3.policy 10 | { 11 | #pragma warning disable // Disable all warnings 12 | 13 | /// Policy for defining rules for who (subjects) can do actions on resources in an app. 14 | [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v13.0.0.0)")] 15 | public partial class Policy 16 | { 17 | /// Required array containing one or more rules. 18 | [Newtonsoft.Json.JsonProperty("Rules", Required = Newtonsoft.Json.Required.Always)] 19 | [System.ComponentModel.DataAnnotations.Required] 20 | [System.ComponentModel.DataAnnotations.MinLength(1)] 21 | public System.Collections.Generic.ICollection Rules { get; set; } = new System.Collections.ObjectModel.Collection(); 22 | 23 | 24 | } 25 | 26 | /// Rule describing who (subjects) can do actions on resources in the app. 27 | [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v13.0.0.0)")] 28 | public partial class Rule 29 | { 30 | /// The intended consequence of a satisfied rule (either "Permit" or "Deny"). 31 | [Newtonsoft.Json.JsonProperty("Effect", Required = Newtonsoft.Json.Required.Always)] 32 | [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)] 33 | [Newtonsoft.Json.JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))] 34 | public RuleEffect Effect { get; set; }= default!; 35 | 36 | /// Optional description of the rule. 37 | [Newtonsoft.Json.JsonProperty("Description", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 38 | public string? Description { get; set; }= default!; 39 | 40 | /// Array containing one or more subjects. 41 | [Newtonsoft.Json.JsonProperty("Subjects", Required = Newtonsoft.Json.Required.Always)] 42 | [System.ComponentModel.DataAnnotations.Required] 43 | public System.Collections.Generic.ICollection Subjects { get; set; } = new System.Collections.ObjectModel.Collection(); 44 | 45 | /// Array containing one or more resources. 46 | [Newtonsoft.Json.JsonProperty("Resources", Required = Newtonsoft.Json.Required.Always)] 47 | [System.ComponentModel.DataAnnotations.Required] 48 | public System.Collections.Generic.ICollection Resources { get; set; } = new System.Collections.ObjectModel.Collection(); 49 | 50 | /// Array containing one or more actions that can be performed on the resources. 51 | [Newtonsoft.Json.JsonProperty("Actions", Required = Newtonsoft.Json.Required.Always, ItemConverterType = typeof(Newtonsoft.Json.Converters.StringEnumConverter))] 52 | [System.ComponentModel.DataAnnotations.Required] 53 | public System.Collections.Generic.ICollection Actions { get; set; } = new System.Collections.ObjectModel.Collection(); 54 | 55 | 56 | } 57 | 58 | /// JSON Schema describing a simplified access control policy format for Altinn applications. 59 | [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v13.0.0.0)")] 60 | public partial class Test 61 | { 62 | [Newtonsoft.Json.JsonProperty("Policy", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 63 | public Policy? Policy { get; set; }= default!; 64 | 65 | private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); 66 | 67 | [Newtonsoft.Json.JsonExtensionData] 68 | public System.Collections.Generic.IDictionary AdditionalProperties 69 | { 70 | get { return _additionalProperties; } 71 | set { _additionalProperties = value; } 72 | } 73 | 74 | 75 | } 76 | 77 | [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v13.0.0.0)")] 78 | public enum RuleEffect 79 | { 80 | [System.Runtime.Serialization.EnumMember(Value = @"Permit")] 81 | Permit = 0, 82 | 83 | [System.Runtime.Serialization.EnumMember(Value = @"Deny")] 84 | Deny = 1, 85 | 86 | } 87 | 88 | /// An action that can be performed on the resources. 89 | [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v13.0.0.0)")] 90 | public enum Actions 91 | { 92 | [System.Runtime.Serialization.EnumMember(Value = @"instantiate")] 93 | Instantiate = 0, 94 | 95 | [System.Runtime.Serialization.EnumMember(Value = @"read")] 96 | Read = 1, 97 | 98 | [System.Runtime.Serialization.EnumMember(Value = @"write")] 99 | Write = 2, 100 | 101 | [System.Runtime.Serialization.EnumMember(Value = @"confirm")] 102 | Confirm = 3, 103 | 104 | [System.Runtime.Serialization.EnumMember(Value = @"complete")] 105 | Complete = 4, 106 | 107 | } 108 | } -------------------------------------------------------------------------------- /Helpers/MergeLanguageResults.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.RegularExpressions; 5 | 6 | using Altinn2Convert.Models.Altinn3.layout; 7 | 8 | namespace Altinn2Convert.Helpers 9 | { 10 | public static class MergeLanguageResults 11 | { 12 | /// Merge layout lists for multiple languages and extract texts 13 | public static MergeLangResult MergeLang(List languages, List layouts, string textKeyPrefix) 14 | { 15 | var ret = new MergeLangResult(); 16 | // All layouts are equal (except language texts) 17 | // Use the first layout list for everything not language related. 18 | var mainLayout = layouts[0].Components; 19 | 20 | for (var i = 0; i < mainLayout.Count; i++) 21 | { 22 | var mainComponent = mainLayout[i]; 23 | 24 | // Handle special components that might differ 25 | switch (mainComponent) 26 | { 27 | case ImageComponent mainImage: 28 | for (var l = 1; l < languages.Count; l++) 29 | { 30 | var languageImage = (ImageComponent)layouts[l].Components[i]; 31 | mainImage.Image.Src[languages[l]] = languageImage.Image.Src[languages[l]]; 32 | } 33 | 34 | break; 35 | case SelectionComponents mainSelection: 36 | if(mainSelection.Options?.Any() ?? false) 37 | { 38 | foreach (var option in mainSelection.Options) 39 | { 40 | var labelKey = $"{textKeyPrefix}-{mainComponent.Id}-{option.Value}"; 41 | for (var l = 0; l < languages.Count; l++) 42 | { 43 | var language = languages[l]; 44 | var languageRadio = (SelectionComponents)layouts[l].Components[i]; 45 | var languageOption = languageRadio?.Options?.First(o=>o.Value == option.Value); 46 | ret.SetText(labelKey, languageOption?.Label, language); 47 | } 48 | 49 | option.Label = labelKey; 50 | } 51 | } 52 | 53 | 54 | break; 55 | } 56 | 57 | // Temporary variables 58 | var textResourceBindings = new Dictionary(); 59 | var bindingsKeys = new List>(); 60 | 61 | // Add possible bindings from all languages 62 | for (var l = 0; l < languages.Count; l++) 63 | { 64 | var languageCompoment = layouts[l].Components[i]; 65 | 66 | foreach (var binding in languageCompoment?.TextResourceBindings?.Keys ?? new List()) 67 | { 68 | if (binding == "help") 69 | { 70 | // Help bindings already have unique keys 71 | textResourceBindings["help"] = languageCompoment?.TextResourceBindings?["help"]; 72 | } 73 | else if (!bindingsKeys.Any((el) => { return el.Item1 == binding; })) 74 | { 75 | var key = $"{textKeyPrefix}-{mainComponent.Id}-{binding}"; 76 | if (new Regex(@"[\d-]+").IsMatch(mainComponent.Id)) 77 | { 78 | key = $"{mainComponent.Id}-{binding}"; 79 | } 80 | 81 | bindingsKeys.Add(new Tuple(binding, key)); 82 | textResourceBindings[binding] = key; 83 | } 84 | } 85 | } 86 | 87 | // Add all text to the text resources 88 | for (var l = 0; l < languages.Count; l++) 89 | { 90 | var language = languages[l]; 91 | var languageLayoutResources = layouts[l].Components[i].TextResourceBindings; 92 | if (languageLayoutResources != null) 93 | { 94 | foreach (var (binding, key) in bindingsKeys) 95 | { 96 | var value = languageLayoutResources[binding]; 97 | ret.SetText(key, value, language); 98 | } 99 | } 100 | } 101 | 102 | // Add textResourceBindings to main component 103 | mainComponent.TextResourceBindings = textResourceBindings; 104 | ret.Layout.Add(mainComponent); 105 | } 106 | 107 | // Add texts with no compoment connection 108 | for (var l = 0; l < languages.Count; l++) 109 | { 110 | var language = languages[l]; 111 | layouts[l].UnusedTexts.Select((text, index) => 112 | { 113 | ret.SetText($"{textKeyPrefix}-unknown-{index}", text, language); 114 | return 1; 115 | }).ToList(); 116 | } 117 | 118 | return ret; 119 | } 120 | 121 | public class MergeLangResult 122 | { 123 | public Models.Altinn3.layout.Layout Layout { get; set; } = new (); 124 | 125 | /// Dictionary of texts for field in the current language: Texts[lang][key] = text ) 126 | public Dictionary> Texts { get; set; } = new (); 127 | 128 | public void SetText(string key, string value, string lang) 129 | { 130 | if (!Texts.ContainsKey(lang)) 131 | { 132 | Texts[lang] = new (); 133 | } 134 | 135 | Texts[lang][key] = value; 136 | } 137 | } 138 | } 139 | } -------------------------------------------------------------------------------- /Helpers/PrefillConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | using Altinn2Convert.Models.Altinn2.FormFieldPrefill; 4 | using Altinn2Convert.Models.Altinn3.prefill; 5 | using Prefill = Altinn2Convert.Models.Altinn3.prefill.Test; 6 | 7 | namespace Altinn2Convert.Helpers 8 | { 9 | public static class PrefillConverter 10 | { 11 | public static Prefill? Convert(FormFieldPrefill a2prefill) 12 | { 13 | var anyPrefill = false; 14 | var ret = new Prefill() 15 | { 16 | ER = new (), 17 | DSF = new (), 18 | UserProfile = new (), 19 | }; 20 | a2prefill?.Register?.FirstOrDefault(r => r.Name == "ER")?.Field?.ForEach(field => 21 | { 22 | var path = XpathToJsonPath(field.XPath); 23 | if (path.StartsWith("@my")) 24 | { 25 | return; 26 | } 27 | 28 | // ChosenOrgNr" registerField="BusinessAddress" /> 29 | // ChosenOrgNr" registerField="BusinessPostalCity" /> 30 | // ChosenOrgNr" registerField="BusinessPostalCode" /> 31 | // ChosenOrgNr" registerField="Name" /> 32 | // ChosenOrgNr" registerField="OrganizationNumber" /> 33 | // ChosenOrgNr" registerField="PostalAddress" /> 34 | // ChosenOrgNr" registerField="PostalPostalCity" /> 35 | // ChosenOrgNr" registerField="PostalPostalCode" /> 36 | switch (field.RegisterField) 37 | { 38 | // TODO: Find all prefill codes in altinn2 er 39 | case "OrganizationNumber": 40 | ret.ER.OrgNumber = path; 41 | anyPrefill = true; 42 | break; 43 | case "Name": 44 | ret.ER.Name = path; 45 | anyPrefill = true; 46 | break; 47 | case "PostalAddress": 48 | ret.ER.MailingAddress = path; 49 | anyPrefill = true; 50 | break; 51 | case "PostalPostalCode": 52 | ret.ER.MailingPostalCode = path; 53 | anyPrefill = true; 54 | break; 55 | case "PostalPostalCity": 56 | anyPrefill = true; 57 | break; 58 | } 59 | }); 60 | a2prefill?.Register?.FirstOrDefault(r => r.Name == "DSF")?.Field?.ForEach(field => 61 | { 62 | var path = XpathToJsonPath(field.XPath); 63 | if (path.StartsWith("@my")) 64 | { 65 | return; 66 | } 67 | 68 | // Person" registerField="Address" /> 69 | // Person" registerField="FirstName" /> 70 | // Person" registerField="LastName" /> 71 | // Person" registerField="MiddleName" /> 72 | // Person" registerField="Name" /> 73 | // Person" registerField="PlaceName" /> 74 | // Person" registerField="PostalCity" /> 75 | // Person" registerField="PostalCode" /> 76 | // Person" registerField="SocialSecurityNumber" /> 77 | // Person" registerField="StreetName" /> 78 | switch (field.RegisterField) 79 | { 80 | // TODO: Find all prefill codes in altinn2 er 81 | case "Address": 82 | // TODO: figure out what is right here 83 | // all of theese seems to match a single field in Altinn2 84 | ret.DSF.AddressStreetName = path; 85 | ret.DSF.AddressHouseNumber = path; 86 | ret.DSF.AddressHouseLetter = path; 87 | anyPrefill = true; 88 | break; 89 | case "FirstName": 90 | ret.DSF.FirstName = path; 91 | anyPrefill = true; 92 | break; 93 | case "LastName": 94 | ret.DSF.LastName = path; 95 | anyPrefill = true; 96 | break; 97 | case "MiddleName": 98 | ret.DSF.MiddleName = path; 99 | anyPrefill = true; 100 | break; 101 | case "Name": 102 | ret.DSF.Name = path; 103 | anyPrefill = true; 104 | break; 105 | case "PlaceName": 106 | ret.DSF.AddressCity = path; 107 | // ret.DSF.MailingPostalCity = path; 108 | anyPrefill = true; 109 | break; 110 | case "PostalCity": 111 | ret.DSF.AddressCity = path; 112 | // ret.DSF.MailingPostalCity = path; 113 | anyPrefill = true; 114 | break; 115 | case "PostalCode": 116 | ret.DSF.AddressPostalCode = path; 117 | // ret.DSF.MailingPostalCode = path; 118 | anyPrefill = true; 119 | break; 120 | case "SocialSecurityNumber": 121 | ret.DSF.SSN = path; 122 | anyPrefill = true; 123 | break; 124 | // TOOD: Complete list 125 | } 126 | }); 127 | a2prefill?.Register?.FirstOrDefault(r => r.Name == "DLS")?.Field?.ForEach(field => 128 | { 129 | var path = XpathToJsonPath(field.XPath); 130 | if (path.StartsWith("@my")) 131 | { 132 | return; 133 | } 134 | 135 | // Profile" registerField="ProfileEmail" /> 136 | // Profile" registerField="ProfileLastName" /> 137 | // Profile" registerField="ProfileMobile" /> 138 | // Profile" registerField="ProfileSSN" /> 139 | switch (field.RegisterField) 140 | { 141 | // TODO: Fix mapping 142 | case "ProfileEmail": 143 | ret.UserProfile.Email = path; 144 | anyPrefill = true; 145 | break; 146 | case "ProfileLastName": 147 | ret.UserProfile.PartyPersonLastName = path; 148 | anyPrefill = true; 149 | break; 150 | case "ProfileMobile": 151 | ret.UserProfile.PartyPersonMobileNumber = path; 152 | anyPrefill = true; 153 | break; 154 | case "ProfileSSN": 155 | ret.UserProfile.PartySSN = path; 156 | anyPrefill = true; 157 | break; 158 | } 159 | }); 160 | if(!anyPrefill) 161 | { 162 | return null; 163 | } 164 | 165 | return ret; 166 | } 167 | 168 | public static string XpathToJsonPath(string xpath) 169 | { 170 | int rootIndex = xpath.Substring(1).IndexOf("/"); 171 | return xpath.Substring(rootIndex + 2).Replace("/", ".") + ".value"; 172 | } 173 | } 174 | } -------------------------------------------------------------------------------- /Helpers/TulPackageParser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text.RegularExpressions; 6 | using System.Xml; 7 | using System.Xml.Linq; 8 | using System.Xml.Serialization; 9 | using System.Xml.XPath; 10 | 11 | using Altinn2Convert.Models.Altinn2; 12 | using FormFieldPrefill = Altinn2Convert.Models.Altinn2.FormFieldPrefill.FormFieldPrefill; 13 | using FormMetadata = Altinn2Convert.Models.Altinn2.FormMetadata.FormMetadata; 14 | using FormPages = Altinn2Convert.Models.Altinn2.FormMetadata.FormPages; 15 | 16 | namespace Altinn2Convert.Helpers 17 | { 18 | public class TulPackageParser 19 | { 20 | private string _rootPath; 21 | 22 | private ServiceEditionVersion _manifest; 23 | 24 | public XDocument Xmanifest { get; } 25 | 26 | public TulPackageParser(string rootPath) 27 | { 28 | _rootPath = rootPath; 29 | var manifest_file = Path.Join(rootPath, "manifest.xml"); 30 | Xmanifest = XDocument.Load(manifest_file); 31 | using var fileStream = File.Open(manifest_file, FileMode.Open); 32 | XmlSerializer serializer = new XmlSerializer(typeof(ServiceEditionVersion)); 33 | _manifest = (ServiceEditionVersion)serializer.Deserialize(fileStream); 34 | } 35 | 36 | public ServiceEditionVersion GetServiceEditionVersion() 37 | { 38 | return _manifest; 39 | } 40 | 41 | public List GetLanguages() 42 | { 43 | return _manifest.Translations.Files.Select(f => TULToISOLang(f.Language)).ToList(); 44 | } 45 | 46 | public XmlDocument GetTranslationXml(string language) 47 | { 48 | var lan = ISOToTULLang(language); 49 | var file = _manifest.Translations.Files.First(f => f.Language == lan); 50 | var x = new XmlDocument(); 51 | x.Load(Path.Join(_rootPath, file.Name)); 52 | return x; 53 | } 54 | 55 | public Translation GetTranslationParsed(string language) 56 | { 57 | var lan = ISOToTULLang(language); 58 | var file = _manifest.Translations.Files.First(f => f.Language == lan); 59 | var filePath = Path.Join(_rootPath, file.Name); 60 | using var fileStream = File.Open(filePath, FileMode.Open); 61 | XmlSerializer serializer = new XmlSerializer(typeof(Translation)); 62 | return (Translation)serializer.Deserialize(fileStream); 63 | } 64 | 65 | public List GetFormMetadata() 66 | { 67 | var path = _manifest.DataAreas.Find(d => d.Type == "Form")?.LogicalForm.Files.Find(f => f.FileType == "FormPageMetadata"); 68 | if (path == null) 69 | { 70 | return null; 71 | } 72 | 73 | // var x = new XmlDocument(); 74 | // x.Load(Path.Join(_rootPath, path.Name)); 75 | // return x; 76 | using var fileStream = File.Open(Path.Join(_rootPath, path.Name), FileMode.Open); 77 | var serializer = new XmlSerializer(typeof(FormPages)); 78 | return ((FormPages)serializer.Deserialize(fileStream)).GetFormMetadata(); 79 | } 80 | 81 | public XmlDocument GetFormTrack() 82 | { 83 | var path = _manifest.DataAreas.Find(d => d.Type == "Form")?.LogicalForm.Files.Find(f => f.FileType == "FormTrack")?.Name; 84 | if (path == null) 85 | { 86 | return null; 87 | } 88 | 89 | var x = new XmlDocument(); 90 | x.Load(Path.Join(_rootPath, path)); 91 | return x; 92 | } 93 | 94 | public FormFieldPrefill GetFormFieldPrefill() 95 | { 96 | var path = _manifest.DataAreas.Find(d => d.Type == "Form")?.LogicalForm.Files.Find(f => f.FileType == "FormFieldPrefill"); 97 | if (path == null) 98 | { 99 | return null; 100 | } 101 | 102 | using var fileStream = File.Open(Path.Join(_rootPath, path.Name), FileMode.Open); 103 | var serializer = new XmlSerializer(typeof(FormFieldPrefill)); 104 | return (FormFieldPrefill)serializer.Deserialize(fileStream); 105 | } 106 | 107 | public string GetXsnPath(string language) 108 | { 109 | var lan = ISOToTULLang(language); 110 | var xsnPath = _manifest.DataAreas 111 | .Find(d => d.Type == "Form") 112 | ?.LogicalForm 113 | .Files 114 | .Find(f => f.FileType == "FormTemplate" && f.Language == lan)?.Name; 115 | if (xsnPath == null) 116 | { 117 | return null; 118 | } 119 | 120 | return Path.Join(_rootPath, xsnPath); 121 | } 122 | 123 | public XmlDocument GetAttachmentTypes() 124 | { 125 | var path = _manifest?.DataAreas?.Find(d => d.Type == "AttachmentTypes")?.Files?.Find(f => f.FileType == "AttachmentTypes"); 126 | if (path == null) 127 | { 128 | return null; 129 | } 130 | 131 | var x = new XmlDocument(); 132 | x.Load(Path.Join(_rootPath, path.Name)); 133 | return x; 134 | } 135 | 136 | public XDocument GetAuthorizationRules() 137 | { 138 | var path = _manifest.DataAreas.Find(d => d.Type == "Security")?.Files.Find(f => f.FileType == "AuthorizationRules"); 139 | var x = XDocument.Load(Path.Join(_rootPath, path!.Name)); 140 | return x; 141 | } 142 | 143 | public XmlDocument GetWorkflowDefinition() 144 | { 145 | var path = _manifest.DataAreas.Find(d => d.Type == "Workflow")?.Files.Find(f => f.FileType == "WorkflowDefinition"); 146 | var x = new XmlDocument(); 147 | x.Load(Path.Join(_rootPath, path!.Name)); 148 | return x; 149 | } 150 | 151 | public string GetOrg() 152 | { 153 | return Xmanifest.XPathSelectElement("/ServiceEditionVersion/DataAreas/DataArea[@type=\"Service\"]/Property[@name=\"ServiceOwnerCode\"]")?.Attribute("value")?.Value; 154 | } 155 | 156 | public string GetApp() 157 | { 158 | return Xmanifest.XPathSelectElement("/ServiceEditionVersion/DataAreas/DataArea[@type=\"Service\"]/Property[@name=\"ServiceName\"]")?.Attribute("value")?.Value; 159 | } 160 | 161 | public static string TULToISOLang(string lang) 162 | { 163 | switch (lang) 164 | { 165 | case "1033": 166 | return "en"; 167 | case "1044": 168 | return "nb"; 169 | case "2068": 170 | return "nn"; 171 | case "1083": 172 | return "se"; 173 | } 174 | 175 | throw new ArgumentException("Unknown TUL language " + lang); 176 | } 177 | 178 | public static string ISOToTULLang(string language) 179 | { 180 | switch (language) 181 | { 182 | case "en": 183 | return "1033"; 184 | case "nb": 185 | return "1044"; 186 | case "nn": 187 | return "2068"; 188 | case "se": 189 | return "1083"; 190 | } 191 | 192 | throw new ArgumentException("Unknown TUL language " + language); 193 | } 194 | } 195 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | # tools/** 311 | # !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | 352 | /TULPACKAGE* 353 | /out/ -------------------------------------------------------------------------------- /Altinn3.ruleset: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | -------------------------------------------------------------------------------- /Enums/TextType.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Altinn2Convert.Enums 6 | { 7 | /// 8 | /// Text type enum 9 | /// 10 | public enum TextType : int 11 | { 12 | /// 13 | /// Not defined, to be used in TextQuery objects 14 | /// 15 | NotDefined = 0, 16 | 17 | /// 18 | /// There has been no changes to this section 19 | /// of the parameters since the edition was created 20 | /// 21 | Help = 01, 22 | 23 | /// 24 | /// There has been changes to this section of parameters since the edition was 25 | /// created, but the section has required parameters that are not yet filled out. 26 | /// 27 | WorkflowParameter = 02, 28 | 29 | /// 30 | /// All required parameters in this section has been filled out 31 | /// 32 | PageDisplayName = 03, 33 | 34 | /// 35 | /// The parameters in this section has not been changed since last migration 36 | /// 37 | ServiceName = 04, 38 | 39 | /// 40 | /// The parameters in this section has not been changed since last migration 41 | /// 42 | ServiceEditionName = 05, 43 | 44 | /// 45 | /// The parameters in this section has not been changed since last migration 46 | /// 47 | LogicalForm = 06, 48 | 49 | /// 50 | /// It is used for Service Metadata,Service Edition Metadata,Page Metadata,Workflow texts 51 | /// 52 | Parameter = 07, 53 | 54 | /// 55 | /// It is used for Service Metadata,Service Edition Metadata,Page Metadata,Workflow texts 56 | /// 57 | ExpressionBox = 09, 58 | 59 | /// 60 | /// It is used for Service Metadata,Service Edition Metadata,Page Metadata,Workflow texts 61 | /// 62 | DropdownListBox = 10, 63 | 64 | /// 65 | /// It is used for Service Metadata,Service Edition Metadata,Page Metadata,Workflow texts 66 | /// 67 | ListBox = 11, 68 | 69 | /// 70 | /// It is used for Service Metadata,Service Edition Metadata,Page Metadata,Workflow texts 71 | /// 72 | Button = 12, 73 | 74 | /// 75 | /// It is used for Service Metadata,Service Edition Metadata,Page Metadata,Workflow texts 76 | /// 77 | HyperLink = 13, 78 | 79 | /// 80 | /// It is used for Service Metadata,Service Edition Metadata,Page Metadata,Workflow texts 81 | /// 82 | TableHeader = 14, 83 | 84 | /// 85 | /// It is used for Service Metadata,Service Edition Metadata,Page Metadata,Workflow texts 86 | /// 87 | HintText = 15, 88 | 89 | /// 90 | /// It is used for Service Metadata,Service Edition Metadata,Page Metadata,Workflow texts 91 | /// 92 | ValidationText1 = 16, 93 | 94 | /// 95 | /// It is used for Service Metadata,Service Edition Metadata,Page Metadata,Workflow texts 96 | /// 97 | ValidationText2 = 17, 98 | 99 | /// 100 | /// It is used for Service Edition Metadata for ReceiptText 101 | /// 102 | ReceiptText = 18, 103 | 104 | /// 105 | /// It is used for Service Metadata,Service Edition Metadata,Page Metadata,Workflow texts 106 | /// 107 | FormName = 19, 108 | 109 | /// 110 | /// It is used for Service Edition Metadata for ReceiptText 111 | /// 112 | SenderName = 20, 113 | 114 | /// 115 | /// Information to be displayed in the Right Pane of the LookUp Service 116 | /// 117 | LookUpInfoRightPane = 21, 118 | 119 | /// 120 | /// Name of a State in the Collaboration Service State Model 121 | /// 122 | StateName = 22, 123 | 124 | /// 125 | /// Name of an event in the Collaboration Service State Model 126 | /// 127 | EventName = 23, 128 | 129 | /// 130 | /// Dialog Component Title 131 | /// 132 | Title = 24, 133 | 134 | /// 135 | /// Dialog Component Image Name 136 | /// 137 | ImageUrl = 25, 138 | 139 | /// 140 | /// Dialog Component Information Text 141 | /// 142 | Text = 26, 143 | 144 | /// 145 | /// Dialog Component Status Text 146 | /// 147 | StatusText = 27, 148 | 149 | /// 150 | /// Dialog Component Link Text 151 | /// 152 | LinkList = 28, 153 | 154 | /// 155 | /// Name of the Notice in Collaboration Services 156 | /// 157 | NoticeName = 29, 158 | 159 | /// 160 | /// Role Type Name 161 | /// 162 | RoleTypeName = 30, 163 | 164 | /// 165 | /// Role Type description 166 | /// 167 | RoleTypeDescription = 31, 168 | 169 | /// 170 | /// ER Description 171 | /// 172 | ERDiscription = 32, 173 | 174 | /// 175 | /// Image Alternative 176 | /// 177 | AlternateText = 33, 178 | 179 | /// 180 | /// ToolTip Text 181 | /// 182 | TooltipText = 34, 183 | 184 | /// 185 | /// TextType representing all the Dialog Page related TextTypes 186 | /// 187 | AllDialogPageTexts = 35, 188 | 189 | /// 190 | /// It is used for Service Edition Metadata for ReceiptText 191 | /// 192 | ResourceText1 = 36, 193 | 194 | /// 195 | /// It is used for Service Edition Metadata for ReceiptText 196 | /// 197 | ResourceText2 = 37, 198 | 199 | /// 200 | /// It is used for Service Edition Metadata for ReceiptText 201 | /// 202 | PictureButton = 38, 203 | 204 | /// 205 | /// Custom Help URL 206 | /// 207 | CustomHelpURL = 39, 208 | 209 | /// 210 | /// It is used for Mapping LEDE Text Attribute from a XML schema Doc 211 | /// 212 | XSD_CaptionText = 40, 213 | 214 | /// 215 | /// It is used for mapping HJELP Text Attribute from a XML schema Doc 216 | /// 217 | XSD_HelpText = 41, 218 | 219 | /// 220 | /// It is used for mapping FEIL Text Attribute from a XML schema Doc 221 | /// 222 | XSD_ErrorText = 42, 223 | 224 | /// 225 | /// It is used for mapping DSE Text Attribute from a XML schema Doc 226 | /// 227 | XSD_DSEText = 43, 228 | 229 | /// 230 | /// It is used for Service Metadata,Service Edition Metadata,Page Metadata,Workflow texts 231 | /// and Help text . 232 | /// 233 | All = 08, 234 | 235 | /// 236 | /// It is used for Service Edition Metadata for ReceiptText 237 | /// 238 | ReceiptEmailText = 44, 239 | 240 | /// 241 | /// It is used for Service Edition Metadata for ReceiptText 242 | /// 243 | ReceiptInformationText = 45, 244 | 245 | /// 246 | /// It is used for access consent description 247 | /// 248 | AccessConsentDescription = 46, 249 | 250 | /// 251 | /// It is used for access consent details 252 | /// 253 | AccessConsentDetails = 47, 254 | 255 | /// 256 | /// It is used for Service Edition Metadata for DelegationText 257 | /// 258 | DelegationDescriptionText = 48 259 | } 260 | } 261 | -------------------------------------------------------------------------------- /Services/ConvertService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Globalization; 4 | using System.IO; 5 | using System.IO.Compression; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Text.RegularExpressions; 9 | using System.Threading.Tasks; 10 | using System.Xml; 11 | using System.Xml.Linq; 12 | using System.Xml.XPath; 13 | 14 | using Altinn2Convert.Helpers; 15 | using Altinn2Convert.Models.Altinn2; 16 | using Altinn2Convert.Models.Altinn3; 17 | using Newtonsoft.Json; 18 | 19 | namespace Altinn2Convert.Services 20 | { 21 | public class ConvertService 22 | { 23 | private JsonSerializerSettings serializerOptions { get; set; } = new JsonSerializerSettings 24 | { 25 | NullValueHandling = NullValueHandling.Ignore, 26 | ContractResolver = new ComponentPropsFirstContractResolver(), 27 | }; 28 | 29 | public async Task ParseAltinn2File(string zipPath, string outDir) 30 | { 31 | outDir = Path.Join(outDir, "TULPACKAGE"); 32 | var a2 = new Altinn2AppData(); 33 | if (!File.Exists(zipPath)) 34 | { 35 | throw new Exception($"Altinn2 file '{zipPath}' does not exist"); 36 | } 37 | 38 | ZipFile.ExtractToDirectory(zipPath, outDir); 39 | var tulPackageParser = new TulPackageParser(outDir); 40 | a2.Manifest = tulPackageParser.Xmanifest; 41 | a2.Languages.AddRange(tulPackageParser.GetLanguages()); 42 | a2.ServiceEditionVersion = tulPackageParser.GetServiceEditionVersion(); 43 | a2.FormMetadata = tulPackageParser.GetFormMetadata(); 44 | a2.AttachmentTypes = tulPackageParser.GetAttachmentTypes(); 45 | a2.AutorizationRules = tulPackageParser.GetAuthorizationRules(); 46 | a2.FormFieldPrefill = tulPackageParser.GetFormFieldPrefill(); 47 | a2.FormTrack = tulPackageParser.GetFormTrack(); 48 | a2.Org = tulPackageParser.GetOrg(); 49 | a2.App = tulPackageParser.GetApp(); 50 | 51 | foreach (var language in a2.Languages) 52 | { 53 | // Handle translations 54 | a2.TranslationsParsed[language] = tulPackageParser.GetTranslationParsed(language); 55 | a2.TranslationsXml[language] = tulPackageParser.GetTranslationXml(language); 56 | 57 | // Parse xsn content 58 | var xsnPath = tulPackageParser.GetXsnPath(language); 59 | if (xsnPath == null) 60 | { 61 | continue; 62 | } 63 | 64 | var infoPath = new InfoPathXmlParser(); 65 | infoPath.Extract(outDir, language, xsnPath); 66 | 67 | a2.XSNFiles[language] = new Models.Altinn2.InfoPath.XSNFileContent 68 | { 69 | XSDDocument = infoPath.GetXSDDocument(), 70 | Manifest = infoPath.GetManifest(), 71 | Pages = infoPath.GetPages(a2.FormMetadata.Select(m => m.Transform).ToList()), 72 | }; 73 | } 74 | 75 | return a2; 76 | } 77 | 78 | public async Task DumpRawTulPackageAsJson(Altinn2AppData a2, string path) 79 | { 80 | var target = Path.Join(path, "altinn2.json"); 81 | await File.WriteAllTextAsync(target, JsonConvert.SerializeObject(a2, Newtonsoft.Json.Formatting.Indented, serializerOptions), Encoding.UTF8); 82 | } 83 | 84 | public async Task Convert(Altinn2AppData a2) 85 | { 86 | var a3 = new Altinn3AppData(); 87 | 88 | // Add extra texts 89 | a2.Languages.ForEach(language => 90 | { 91 | var t = a2.TranslationsXml[language]; 92 | var serviceName = t.SelectSingleNode("//Translation/DataAreas/DataArea[@type=\"Service\"]/Texts/Text[@textType=\"ServiceName\"]"); 93 | a3.AddText(language, "appName", serviceName?.InnerText); 94 | var serviceEditionName = t.SelectSingleNode("//Translation/DataAreas/DataArea[@type=\"ServiceEdition\"]/Texts/Text[@textType=\"ServiceEditionName\"]"); 95 | a3.AddText(language, "ServiceEditionName", serviceEditionName?.InnerText); 96 | var receiptText = t.SelectSingleNode("//Translation/DataAreas/DataArea[@type=\"ServiceEdition\"]/Texts/Text[@textType=\"ReceiptText\"]"); 97 | a3.AddText(language, "ReceiptText", receiptText?.InnerText); 98 | var receiptEmailText = t.SelectSingleNode("//Translation/DataAreas/DataArea[@type=\"ServiceEdition\"]/Texts/Text[@textType=\"ReceiptEmailText\"]"); 99 | a3.AddText(language, "ReceiptEmailText", receiptEmailText?.InnerText); 100 | var receiptInformationText = t.SelectSingleNode("//Translation/DataAreas/DataArea[@type=\"ServiceEdition\"]/Texts/Text[@textType=\"ReceiptInformationText\"]"); 101 | a3.AddText(language, "ReceiptInformationText", receiptInformationText?.InnerText); 102 | 103 | // Add translation for page name 104 | a2.FormMetadata?.ForEach(formMetadata => 105 | { 106 | var pageDisplayName = t.SelectSingleNode($"//Translation/DataAreas/DataArea[@type=\"Form\"]/LogicalForm/Texts/Text[@textType=\"PageDisplayName\"][@textCode=\"{formMetadata.Name}\"]"); 107 | a3.AddText(language, formMetadata.A3PageName, pageDisplayName?.InnerText); 108 | }); 109 | 110 | foreach (XmlElement helpText in t.SelectNodes($"//Translation/DataAreas/DataArea[@type=\"Form\"]/LogicalForm/Texts/Text[@textType=\"HelpText\"]")) 111 | { 112 | a3.AddText(language, helpText?.GetAttribute("textCode"), helpText?.InnerText); 113 | } 114 | }); 115 | 116 | // Add layouts and texts for layout components 117 | a2.FormMetadata?.OrderBy(f => f.Sequence).ToList().ForEach(formMetadata => 118 | { 119 | // Read layout only from 120 | var pages = a2.Languages.Select(language => a2.XSNFiles[language].Pages[formMetadata.Transform]).ToList(); 121 | 122 | var layoutLists = a2.Languages.Select(language => 123 | { 124 | var page2layout = new Page2Layout(a2.XSNFiles[language].Pages[formMetadata.Transform], language); 125 | page2layout.FillLayoutComponents(); 126 | return page2layout; 127 | }).ToList(); 128 | 129 | var mergedLang = MergeLanguageResults.MergeLang(a2.Languages, layoutLists, textKeyPrefix: formMetadata.SanitizedName); 130 | 131 | // Add Layout to List of layout files 132 | a3.AddLayout(formMetadata.A3PageName, mergedLang.Layout); 133 | 134 | // Add texts for this page 135 | a3.AddTexts(mergedLang.Texts); 136 | }); 137 | 138 | // Try to convert prefills 139 | a3.Prefill = PrefillConverter.Convert(a2.FormFieldPrefill); 140 | 141 | 142 | // Read xsd from xsn files and convert to altinn3 set of models 143 | a3.ModelFiles = ModelConverter.Convert(a2, out var modelName); 144 | a3.ModelName = modelName; 145 | 146 | // Create summary page 147 | var summaryLayout = new Models.Altinn3.layout.Layout(); 148 | a3.LayoutSettings?.Pages?.Order?.ToList().ForEach(pageName => 149 | { 150 | summaryLayout.Add(new Models.Altinn3.layout.HeaderComponent 151 | { 152 | Id = Regex.Replace(pageName.ToLower(), "[^0-9a-zA-Z-]", "") + "-summary", 153 | TextResourceBindings = new Dictionary 154 | { 155 | {"title", pageName} 156 | }, 157 | Size = Models.Altinn3.layout.HeaderComponentSize.H2, 158 | }); 159 | a3.Layouts[pageName]?.Data?.Layout?.ToList().ForEach(layout => 160 | { 161 | switch (layout.Type) 162 | { 163 | case Models.Altinn3.layout.ComponentType.Group: 164 | case Models.Altinn3.layout.ComponentType.Header: 165 | case Models.Altinn3.layout.ComponentType.InstantiationButton: 166 | case Models.Altinn3.layout.ComponentType.Image: 167 | case Models.Altinn3.layout.ComponentType.Paragraph: 168 | case Models.Altinn3.layout.ComponentType.NavigationButtons: 169 | case Models.Altinn3.layout.ComponentType.Button: 170 | case Models.Altinn3.layout.ComponentType.Summary: 171 | break;// Ignore these types in summary 172 | default: 173 | summaryLayout.Add(new Altinn2Convert.Models.Altinn3.layout.SummaryComponent 174 | { 175 | Id = Regex.Replace(pageName.ToLower(), "[^0-9a-zA-Z-]", "") + "-" + layout.Id + "-summary", 176 | ComponentRef = layout.Id, 177 | PageRef = pageName, 178 | }); 179 | break; 180 | } 181 | }); 182 | }); 183 | summaryLayout.Add(new Models.Altinn3.layout.ButtonComponent 184 | { 185 | Id = "submit", 186 | TextResourceBindings = new Dictionary(){{"title","submit"}} 187 | }); 188 | a3.AddText("nb", "submit", "Send inn"); 189 | a3.AddText("nn", "submit", "Send inn"); 190 | a3.AddText("en", "submit", "Submit"); 191 | a3.AddLayout("Summary", summaryLayout); 192 | a3.LayoutSettings?.Pages?.ExcludeFromPdf?.Add("Summary"); 193 | 194 | 195 | // Fill info into applicationMetadata 196 | a3.ApplicationMetadata.Id = $"{a2.Org.ToLower()}/{Regex.Replace(a2.App.ToLower(), "[^0-9a-zA-Z-]", "")}"; 197 | a3.ApplicationMetadata.Org = a2.Org.ToLower(); 198 | a3.ApplicationMetadata.Title ??= new(); 199 | a3.ApplicationMetadata.Title["nb"] = a2.App; 200 | a3.ApplicationMetadata.DataTypes ??= new(); 201 | a3.ApplicationMetadata.DataTypes.Add(new() 202 | { 203 | Id = "ref-data-as-pdf", 204 | AllowedContentTypes = new() 205 | { 206 | "application/pdf" 207 | }, 208 | MaxCount = 0, 209 | MinCount = 0, 210 | }); 211 | if (!string.IsNullOrWhiteSpace(a3.ModelName)) 212 | { 213 | a3.ApplicationMetadata.DataTypes.Add(new() 214 | { 215 | Id = "model", 216 | AllowedContentTypes = new() 217 | { 218 | "application/xml" 219 | }, 220 | AppLogic = new() 221 | { 222 | AutoCreate = true, 223 | ClassRef = $"Altinn.App.Models.{a3.ModelName}" 224 | }, 225 | TaskId = "Task_1", 226 | MaxCount = 1, 227 | MinCount = 1, 228 | }); 229 | } 230 | 231 | // Read policy 232 | XNamespace xacml = "urn:oasis:names:tc:xacml:2.0:policy:schema:os"; 233 | a3.PolicyUpdates.App = a3.ApplicationMetadata.Id.Split('/')[1]; 234 | a3.PolicyUpdates.Org = a3.ApplicationMetadata.Org; 235 | 236 | var authenticationLevel = 237 | from sm in a2.AutorizationRules.Descendants(xacml + "SubjectMatch") 238 | where sm.Element(xacml + "SubjectAttributeDesignator")?.Attribute("AttributeId")?.Value == "urn:oasis:names:tc:xacml:2.0:subject:urn:altinn:authenticationlevel" 239 | select sm.Element(xacml + "AttributeValue")?.Value; 240 | a3.PolicyUpdates.Authenticationlevels = authenticationLevel.Distinct().ToList(); 241 | 242 | var roleCodes = 243 | from sm in a2.AutorizationRules.Descendants(xacml + "SubjectMatch") 244 | where sm.Element(xacml + "SubjectAttributeDesignator")?.Attribute("AttributeId")?.Value == "urn:oasis:names:tc:xacml:2.0:subject:urn:altinn:rolecode" 245 | select sm.Element(xacml + "AttributeValue")?.Value; 246 | a3.PolicyUpdates.RoleCodes = roleCodes.Distinct().ToList(); 247 | 248 | // TODO: get from manifest.xml 249 | a3.ApplicationMetadata.PartyTypesAllowed = new() 250 | { 251 | BankruptcyEstate = true, 252 | Organisation = true, 253 | Person = true, 254 | SubUnit = true, 255 | }; 256 | 257 | a3.ApplicationMetadata.AutoDeleteOnProcessEnd = false; 258 | a3.ApplicationMetadata.Created = DateTime.ParseExact(a2.Manifest.XPathSelectElement("/ServiceEditionVersion/DataAreas/DataArea[@type=\"Service\"]/Property[@name=\"LastUpdated\"]")?.Attribute("value")?.Value, "dd.MM.yyyy", new CultureInfo("no-NB")); 259 | a3.ApplicationMetadata.CreatedBy = a2.Manifest.XPathSelectElement("/ServiceEditionVersion/PackageInfo/Property[@name=\"CreatedBy\"]")?.Attribute("value")?.Value; 260 | a3.ApplicationMetadata.LastChangedBy = "altinn2-convert"; 261 | // a3.ApplicationMetadata.LastChanged = DateTime.UtcNow; // This is not constant, and messes up git diffs 262 | 263 | 264 | // TODO: Add extra layout field for attachment types 265 | // a2.AttachmentTypes 266 | return a3; 267 | } 268 | 269 | public async Task DeduplicateTests(Altinn3AppData A3) 270 | { 271 | // TODO: Implement 272 | } 273 | 274 | public async Task UpdateAppTemplateFiles(string root, Altinn3AppData a3) 275 | { 276 | // Replace [ORG] and [APP] in policy.xml 277 | var path = Path.Join(root, "App", "config", "authorization", "policy.xml"); 278 | var policy = await File.ReadAllTextAsync(path); 279 | policy = policy.Replace("[ORG]", a3.PolicyUpdates.Org).Replace("[APP]", a3.PolicyUpdates.App); 280 | 281 | //Add comment with info about auth level. Maybe try to replace in correct spots later 282 | var comment = $"\n"; 283 | comment += $"\n"; 284 | policy = policy.Replace("", "\n" + comment); 285 | 286 | await File.WriteAllTextAsync(path, policy, Encoding.UTF8); 287 | 288 | // Add functionality for altinn2 code lists 289 | foreach (var optionId in a3.GetOptionIds()) 290 | { 291 | Console.WriteLine(optionId); 292 | 293 | } 294 | } 295 | 296 | public void CopyAppTemplate(string root) 297 | { 298 | CopyDirs("../altinn-studio/src/studio/AppTemplates/AspNet", root); 299 | } 300 | 301 | private void CopyDirs(string src, string dest) 302 | { 303 | Directory.CreateDirectory(dest); 304 | var srcDir = new DirectoryInfo(src); 305 | foreach (var file in srcDir.GetFiles()) 306 | { 307 | file.CopyTo(Path.Join(dest, file.Name)); 308 | } 309 | 310 | foreach (var dir in srcDir.GetDirectories()) 311 | { 312 | CopyDirs(Path.Join(src, dir.Name), Path.Join(dest, dir.Name)); 313 | } 314 | } 315 | 316 | public async Task WriteAltinn3Files(Altinn3AppData A3, string root) 317 | { 318 | var appPath = Path.Join(root, "App"); 319 | // Write settings 320 | var settingsFolder = Path.Join(appPath, "ui"); 321 | Directory.CreateDirectory(settingsFolder); 322 | string settingsContent = JsonConvert.SerializeObject(A3.LayoutSettings, Newtonsoft.Json.Formatting.Indented, serializerOptions); 323 | await File.WriteAllTextAsync(Path.Join(settingsFolder, "Settings.json"), settingsContent, Encoding.UTF8); 324 | 325 | // Write layouts 326 | var layoutsFolder = Path.Join(appPath, "ui", "layouts"); 327 | Directory.CreateDirectory(layoutsFolder); 328 | foreach (var page in A3.LayoutSettings.Pages.Order) 329 | { 330 | string content = JsonConvert.SerializeObject(A3.Layouts[page], Newtonsoft.Json.Formatting.Indented, serializerOptions); 331 | await File.WriteAllTextAsync(Path.Join(layoutsFolder, $"{page}.json"), content, Encoding.UTF8); 332 | } 333 | 334 | // Write texts 335 | var textsFolder = Path.Join(appPath, "config", "texts"); 336 | Directory.CreateDirectory(textsFolder); 337 | foreach (var text in A3.Texts.Values) 338 | { 339 | string content = JsonConvert.SerializeObject(text, Newtonsoft.Json.Formatting.Indented, serializerOptions); 340 | await File.WriteAllTextAsync(Path.Join(textsFolder, $"resource.{text.Language}.json"), content, Encoding.UTF8); 341 | } 342 | 343 | // Prepare models directory 344 | var models = Path.Join(appPath, "models"); 345 | Directory.CreateDirectory(models); 346 | 347 | // Write model files 348 | foreach (var (file, content) in A3.ModelFiles) 349 | { 350 | await File.WriteAllTextAsync(Path.Join(models, file), content, Encoding.UTF8); 351 | } 352 | 353 | // Write prefills 354 | if (A3.Prefill != null) 355 | { 356 | string prefillContent = JsonConvert.SerializeObject(A3.Prefill, Newtonsoft.Json.Formatting.Indented, serializerOptions); 357 | await File.WriteAllTextAsync(Path.Join(models, $"model.prefill.json"), prefillContent, Encoding.UTF8); 358 | } 359 | 360 | // Copy referenced images 361 | foreach (var language in A3.Texts.Keys) 362 | { 363 | var files = A3.Layouts.SelectMany( 364 | kv => kv.Value.Data.Layout 365 | .Where(l => l.Type == Models.Altinn3.layout.ComponentType.Image) 366 | .Select(l => ((Models.Altinn3.layout.ImageComponent)l)?.Image?.Src?[language]?.Replace("wwwroot/images/", ""))) 367 | .Where(url => !string.IsNullOrWhiteSpace(url)) 368 | .ToList(); 369 | if (files.Count > 0) 370 | { 371 | var imagesFolder = Path.Join(appPath, "wwwroot", "images"); 372 | Directory.CreateDirectory(imagesFolder); 373 | foreach (var file in files) 374 | { 375 | File.Copy(Path.Join(root, "TULPACKAGE", "form", language, file), Path.Join(imagesFolder, file), overwrite: true); 376 | } 377 | } 378 | } 379 | 380 | // write applicationmetadata.json 381 | var applicationMetadata = JsonConvert.SerializeObject(A3.ApplicationMetadata, Newtonsoft.Json.Formatting.Indented, serializerOptions); 382 | await File.WriteAllTextAsync(Path.Join(appPath, "config", "applicationmetadata.json"), applicationMetadata); 383 | 384 | //Write Readme.convertion.md 385 | var readme = new StringBuilder(); 386 | readme.Append($"# Conversion report for {A3.ApplicationMetadata.Title["nb"]}\n\n"); 387 | readme.Append($"Authentication levels: {string.Join(", ", A3.PolicyUpdates.Authenticationlevels)}\n"); 388 | readme.Append($"Role codes {string.Join(", ", A3.PolicyUpdates.RoleCodes)}\n"); 389 | await File.WriteAllTextAsync(Path.Join(appPath, "Readme.convertion.md"), readme.ToString()); 390 | } 391 | } 392 | } -------------------------------------------------------------------------------- /Helpers/Page2Layout.cs: -------------------------------------------------------------------------------- 1 | #nullable enable 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text.RegularExpressions; 7 | using System.Xml; 8 | using System.Xml.Linq; 9 | using System.Xml.XPath; 10 | 11 | using Altinn2Convert.Models.Altinn2; 12 | using Altinn2Convert.Models.Altinn3; 13 | using Altinn2Convert.Models.Altinn3.layout; 14 | 15 | namespace Altinn2Convert.Helpers 16 | { 17 | public class Page2Layout 18 | { 19 | #pragma warning disable SA1311 20 | private readonly static XNamespace xd = "http://schemas.microsoft.com/office/infopath/2003"; 21 | private readonly static XNamespace xsl = "http://www.w3.org/1999/XSL/Transform"; 22 | #pragma warning restore SA1311 23 | 24 | public List Components { get; } = new(); 25 | 26 | public Queue UnusedTexts { get; } = new(); 27 | 28 | public Dictionary HandeledRadioNames { get; } = new(); 29 | 30 | public XDocument Root { get; } 31 | 32 | public string Language { get; set; } 33 | 34 | public Page2Layout(XDocument root, string language) 35 | { 36 | Root = root; 37 | Language = language; 38 | } 39 | 40 | public class ConverterState 41 | { 42 | public ConverterState() 43 | { 44 | } 45 | 46 | public ConverterState(ConverterState state) 47 | { 48 | components = state.components; 49 | unusedTexts = state.unusedTexts; 50 | helpTextReference = state.helpTextReference; 51 | xPathPrefix = state.xPathPrefix; 52 | } 53 | 54 | public List components { get; set; } = new(); 55 | 56 | public Queue unusedTexts { get; set; } = new(); 57 | 58 | public string? helpTextReference { get; set; } 59 | 60 | public string xPathPrefix { get; set; } = ""; 61 | } 62 | 63 | public void GetLayoutComponentRecurs(XElement element, ConverterState state) 64 | { 65 | if (element.Attribute(xd + "binding")?.Value?.Contains("@my:") == true) 66 | { 67 | return; 68 | } 69 | 70 | // Try to find relevant elements that we extract components from. 71 | if (HandleImg(element, state)) 72 | { 73 | } 74 | else if (HandleSelect(element, state)) 75 | { 76 | } 77 | else if (HandleRadio(element, state)) 78 | { 79 | } 80 | else if (HandleInputText(element, state)) 81 | { 82 | } 83 | else if (HandlePlainText(element, state)) 84 | { 85 | } 86 | else if (HandleHelpButton(element, state)) 87 | { 88 | } 89 | else if (HandleTemplate(element, state)) 90 | { 91 | } 92 | else if (HandleTable(element, state)) 93 | { 94 | } 95 | else if (HandleTableRow(element, state)) 96 | { 97 | } 98 | else 99 | { 100 | // If no match, we just recurse over all child elements making a group when we find a 128 | { 129 | { "next", "next" }, 130 | { "back", "back" } 131 | } 132 | }); 133 | } 134 | 135 | public Dictionary GetTextResouceBindings(ConverterState state, int keepCount = 0) 136 | { 137 | // Try to find a preceding ExpressionBox to get relevant text 138 | var textResourceBindings = new Dictionary(); 139 | if (state.unusedTexts.TryDequeue(out string? title)) 140 | { 141 | textResourceBindings["title"] = title; 142 | } 143 | 144 | // Join all other texts from the same row into the description 145 | if (state.unusedTexts.Count > keepCount) 146 | { 147 | var description = new List(); 148 | while (state.unusedTexts.Count > keepCount) 149 | { 150 | description.Add(state.unusedTexts.Dequeue()); 151 | } 152 | 153 | textResourceBindings["description"] = string.Join('\n', description); 154 | } 155 | 156 | if (state.helpTextReference != null) 157 | { 158 | textResourceBindings["help"] = state.helpTextReference; 159 | state.helpTextReference = null; 160 | } 161 | 162 | return textResourceBindings; 163 | } 164 | 165 | #region xmlToComponents 166 | 167 | public bool HandleTableRow(XElement node, ConverterState state) 168 | { 169 | if (node.Name == "tr") 170 | { 171 | // Make a new queue of unused texts 172 | var trState = new ConverterState(state) 173 | { 174 | unusedTexts = new Queue() 175 | }; 176 | foreach (var n in node.Elements()) 177 | { 178 | GetLayoutComponentRecurs(n, state); 179 | } 180 | 181 | while (trState.unusedTexts.Count > 0) 182 | { 183 | // Add unused texts to the parent element 184 | state.unusedTexts.Enqueue(trState.unusedTexts.Dequeue()); 185 | } 186 | 187 | return true; 188 | } 189 | 190 | return false; 191 | } 192 | 193 | public bool HandleTable(XElement element, ConverterState state) 194 | { 195 | if (element.Name == "table") 196 | { 197 | var tableContent = new List(); 198 | var tableUnusedTexts = new Queue(); 199 | var tableState = new ConverterState(state) 200 | { 201 | components = tableContent, 202 | unusedTexts = tableUnusedTexts, 203 | }; 204 | foreach (var n in element.Elements()) 205 | { 206 | GetLayoutComponentRecurs(n, tableState); 207 | } 208 | 209 | // // Create a Group if table contains more than 2 fields (and the first isn't a group) 210 | // if (tableContent.Count > 2 && tableContent[0]?.Type != ComponentType.Group) 211 | // { 212 | // state.components.Add(new GroupComponent() 213 | // { 214 | // Id = XElementToId(element), 215 | // Type = Models.Altinn3.layout.ComponentType.Group, 216 | // Children = tableContent.Select(c => c.Id).ToList(), 217 | // MaxCount = 1, 218 | // }); 219 | // } 220 | 221 | // A table with a single text element is a header 222 | if (tableContent.Count == 0 && tableUnusedTexts.Count == 1) 223 | { 224 | var title = tableUnusedTexts.Dequeue(); 225 | state.components.Add(new HeaderComponent 226 | { 227 | Id = XElementToId(element, title), 228 | TextResourceBindings = new Dictionary 229 | { 230 | { "title", title } 231 | }, 232 | Size = HeaderComponentSize.H2 233 | }); 234 | } 235 | 236 | while (tableUnusedTexts.Count > 0) 237 | { 238 | // Add unused texts to the parent element 239 | state.unusedTexts.Enqueue(tableUnusedTexts.Dequeue()); 240 | } 241 | 242 | state.components.AddRange(tableContent); 243 | return true; 244 | } 245 | 246 | return false; 247 | } 248 | 249 | public bool HandleTemplate(XElement element, ConverterState state) 250 | { 251 | if (element.Name == xsl + "apply-templates") 252 | { 253 | var mode = element.Attribute("mode")?.Value; 254 | var select = element.Attribute("select")?.Value; 255 | var template = Root.Descendants(xsl + "template").FirstOrDefault(el => el.Attribute("mode")?.Value == mode); 256 | if (template != null) 257 | { 258 | var child = template.Element("div"); 259 | // Repeating group 260 | if (child != null && child.Attribute(xd + "xctname")?.Value == "RepeatingSection") 261 | { 262 | var templateState = new ConverterState(state) 263 | { 264 | xPathPrefix = "", 265 | components = new(), 266 | }; 267 | GetLayoutComponentRecurs(template, templateState); 268 | state.components.Add(new GroupComponent() 269 | { 270 | Id = XElementToId(element), 271 | Children = templateState.components.Select(c => c.Id).ToList(), 272 | DataModelBindings = new Dictionary 273 | { 274 | { "group", addXpathPrefix(state.xPathPrefix, select) } 275 | }, 276 | }); 277 | state.components.AddRange(templateState.components); 278 | } 279 | else 280 | { 281 | // Non repeating group 282 | var templateState = new ConverterState(state) 283 | { 284 | xPathPrefix = addXpathPrefix(state.xPathPrefix, select), 285 | }; 286 | GetLayoutComponentRecurs(template, templateState); 287 | } 288 | } 289 | 290 | return true; 291 | } 292 | 293 | return false; 294 | } 295 | 296 | public bool HandleHelpButton(XElement element, ConverterState state) 297 | { 298 | if ( 299 | element.Name == "button" && 300 | element.Attribute(xd + "xctname")?.Value == "PictureButton" && 301 | element.Attribute(xd + "CtrlId") != null) 302 | { 303 | state.helpTextReference = element.Attribute(xd + "CtrlId").Value; 304 | return true; 305 | } 306 | 307 | return false; 308 | } 309 | 310 | public bool HandlePlainText(XElement element, ConverterState state) 311 | { 312 | if (element.Name == "a") 313 | { 314 | // Convert to markdown link 315 | state.unusedTexts.Enqueue($"[" + string.Concat(element.DescendantNodes().Where(n => n.NodeType == XmlNodeType.Text)) + "](" + element.Attribute("href")?.Value + ")"); 316 | return true; 317 | } 318 | 319 | if ( 320 | element.Name == "span" && 321 | element.Attribute(xd + "xctname")?.Value == "ExpressionBox") 322 | { 323 | var binding = element.Attribute(xd + "binding"); 324 | if (binding != null) 325 | { 326 | state.unusedTexts.Enqueue(StripQuotes(binding.Value)); 327 | return true; 328 | } 329 | 330 | var valueOf = string.Join(" ", element.Descendants(xsl + "value-of").Select(node => node.Attribute("select")?.Value).Where(v => v != null)); 331 | if (!string.IsNullOrWhiteSpace(valueOf)) 332 | { 333 | state.unusedTexts.Enqueue(StripQuotes(valueOf)); 334 | return true; 335 | } 336 | } 337 | 338 | return false; 339 | } 340 | 341 | public bool HandleSelect(XElement element, ConverterState state) 342 | { 343 | if (element.Name == "select") 344 | { 345 | var textResourceBindings = GetTextResouceBindings(state); 346 | var component = new DropdownComponent() 347 | { 348 | Id = XElementToId(element, textResourceBindings), 349 | DataModelBindings = new Dictionary() 350 | { 351 | { "simpleBinding", xPathToJsonPath(state.xPathPrefix, element.Attribute(xd + "binding").Value) } 352 | }, 353 | TextResourceBindings = textResourceBindings, 354 | }; 355 | 356 | var xslForEach = element.Descendants(xsl + "for-each"); 357 | if (xslForEach.Any()) 358 | { 359 | var selectAttr = xslForEach.FirstOrDefault()?.Attribute("select")?.Value; 360 | if (selectAttr != null) 361 | { 362 | var match = Regex.Match(selectAttr, @".*""(.*)"".*"); 363 | if (match.Success) 364 | { 365 | component.OptionsId = match.Groups[1].Value; 366 | } 367 | } 368 | } 369 | else 370 | { 371 | component.Options = element.Descendants("option").Select(option => 372 | { 373 | var label = string.Join(" ", option.Nodes().Where(node => node.NodeType == XmlNodeType.Text)); 374 | if (label != null) 375 | { 376 | return new Options 377 | { 378 | Label = label, 379 | Value = option.Attribute("value")?.Value ?? "", 380 | }; 381 | } 382 | 383 | return null!; // Nulls are filtered on the next line. 384 | }).Where(op => op != null).Select(op => op!).ToList(); 385 | } 386 | 387 | state.components.Add(component); 388 | return true; 389 | } 390 | 391 | return false; 392 | } 393 | 394 | /// 395 | /// Radio buttons are complicated, because there are no parent element I can stop the 396 | /// depth first search and switch to parsing only radio button. 397 | /// thus I need to find the previous 398 | /// 399 | public bool HandleRadio(XElement element, ConverterState state) 400 | { 401 | if ( 402 | element.Name == "input" && 403 | element.Attribute("type")?.Value == "radio" && 404 | element.Attribute("name") != null) 405 | { 406 | var name = element.Attribute("name")!.Value; 407 | // Get or initialize component 408 | RadioButtonsComponent radio; 409 | if (HandeledRadioNames.TryGetValue(name, out radio)) 410 | { 411 | } 412 | else 413 | { 414 | var textResourceBindings = GetTextResouceBindings(state, keepCount: 0); 415 | radio = new RadioButtonsComponent() 416 | { 417 | Id = XElementToId(element, textResourceBindings), 418 | Options = new List(), 419 | TextResourceBindings = textResourceBindings, 420 | DataModelBindings = new Dictionary() 421 | { 422 | { "simpleBinding", xPathToJsonPath(state.xPathPrefix, element.Attribute(xd + "binding").Value) } 423 | } 424 | }; 425 | HandeledRadioNames[name] = radio; 426 | state.components.Add(radio); 427 | } 428 | 429 | // Find the text label 430 | string? label = null; 431 | element.Ancestors("td").FirstOrDefault()?.NodesAfterSelf()?.OfType()?.FirstOrDefault()?.Descendants(xsl + "value-of").ToList().ForEach((elm) => 432 | { 433 | label = StripQuotes(elm.Attribute("select")?.Value); 434 | }); 435 | if (label == null) 436 | { 437 | element.Ancestors("td").FirstOrDefault()?.NodesBeforeSelf()?.OfType()?.FirstOrDefault()?.Descendants(xsl + "value-of").ToList().ForEach((elm) => 438 | { 439 | label = StripQuotes(elm.Attribute("select")?.Value); 440 | }); 441 | } 442 | 443 | // Add this option 444 | radio.Options?.Add(new() 445 | { 446 | Label = label ?? element.Attribute(xd + "onValue")?.Value ?? "UKJENT", 447 | Value = element.Attribute(xd + "onValue")?.Value ?? "", 448 | }); 449 | 450 | return true; 451 | } 452 | 453 | return false; 454 | } 455 | 456 | public bool HandleInputText(XElement element, ConverterState state) 457 | { 458 | if ( 459 | element.Name != "span" || 460 | element.Attribute(xd + "xctname")?.Value != "PlainText" || 461 | element.Attribute(xd + "binding") == null 462 | ) 463 | { 464 | return false; 465 | } 466 | 467 | var textResourceBindings = GetTextResouceBindings(state); 468 | var dataModelBindings = new Dictionary() 469 | { 470 | { "simpleBinding", xPathToJsonPath(state.xPathPrefix, element.Attribute(xd + "binding")?.Value) }, 471 | }; 472 | var readOnly = element.Attribute(xd + "disableEditing")?.Value == "yes"; 473 | 474 | var style = element.Attribute("style")?.Value; 475 | var regex = new Regex(@"HEIGHT: (\d+)px;"); 476 | var height = regex.Match(style ?? ""); 477 | 478 | if ((height?.Success ?? false) && int.Parse(height.Groups[1].Value) > 30) 479 | { 480 | state.components.Add(new TextAreaComponent() 481 | { 482 | Id = XElementToId(element, textResourceBindings), 483 | TextResourceBindings = textResourceBindings, 484 | DataModelBindings = dataModelBindings, 485 | ReadOnly = readOnly, 486 | }); 487 | } 488 | else 489 | { 490 | state.components.Add(new InputComponent() 491 | { 492 | Id = XElementToId(element, textResourceBindings), 493 | TextResourceBindings = textResourceBindings, 494 | DataModelBindings = dataModelBindings, 495 | ReadOnly = readOnly, 496 | }); 497 | } 498 | 499 | return true; 500 | } 501 | 502 | public bool HandleImg(XElement element, ConverterState state) 503 | { 504 | if (element.Name != "img") 505 | { 506 | return false; 507 | } 508 | 509 | var src = element.Attribute("src")?.Value; 510 | if (src != null && !src.StartsWith("res://")) 511 | { 512 | var imageSrc = new Src(); 513 | imageSrc[this.Language] = $"wwwroot/images/{src}"; 514 | state.components.Add(new ImageComponent() 515 | { 516 | Id = XElementToId(element), 517 | Image = new() 518 | { 519 | Src = imageSrc, 520 | Align = ImageAlign.Center, 521 | Width = "100%", 522 | } 523 | }); 524 | } 525 | 526 | return true; 527 | } 528 | 529 | #endregion 530 | 531 | public static string XElementToId(XElement element, Dictionary textResourceBindings) 532 | { 533 | return XElementToId(element, textResourceBindings.TryGetValue("title", out var value) ? value : null); 534 | } 535 | 536 | public static string XElementToId(XElement element, string? titleText = null) 537 | { 538 | if (titleText is not null) 539 | { 540 | var r = new Regex(@"^([\d\.]+) (.*)"); 541 | var match = r.Match(titleText); 542 | if (match.Success) 543 | { 544 | return match.Groups[1].Value.Replace('.', '-'); 545 | } 546 | } 547 | 548 | var id = element.GetAbsoluteXPath() 549 | .Replace("/xsl:stylesheet/xsl:template[1]/html/body/", "") 550 | .Replace("/xsl:stylesheet/xsl:template", "") 551 | .Replace("xsl:", "") 552 | .Replace('/', '-') 553 | .Replace("[", "") 554 | .Replace("]", "") 555 | .Replace(':', '-'); 556 | if (id.StartsWith('-')) 557 | { 558 | return id.Substring(1); 559 | } 560 | 561 | return id; 562 | } 563 | 564 | public static string StripQuotes(string? value) 565 | { 566 | return Regex.Replace(value, @"^""(.*)""$", "$1"); 567 | } 568 | 569 | public static string addXpathPrefix(string? xPathPrefix, string? value) 570 | { 571 | if (string.IsNullOrWhiteSpace(xPathPrefix)) 572 | { 573 | return value ?? ""; 574 | } 575 | 576 | if (string.IsNullOrWhiteSpace(value)) 577 | { 578 | return xPathPrefix ?? ""; 579 | } 580 | 581 | return xPathPrefix + "/" + value; 582 | } 583 | 584 | public static string xPathToJsonPath(string? xPathPrefix, string? value) 585 | { 586 | return Utils.UnbundlePath(addXpathPrefix(xPathPrefix, value))?.Replace('/', '.') + ".value"; 587 | } 588 | } 589 | } -------------------------------------------------------------------------------- /Models/Altinn3/prefill/prefill.cs: -------------------------------------------------------------------------------- 1 | //---------------------- 2 | // 3 | // Generated using the NJsonSchema v10.5.2.0 (Newtonsoft.Json v13.0.0.0) (http://NJsonSchema.org) 4 | // 5 | //---------------------- 6 | 7 | #nullable enable 8 | 9 | namespace Altinn2Convert.Models.Altinn3.prefill 10 | { 11 | #pragma warning disable // Disable all warnings 12 | 13 | /// Data from the Altinn user profile. 14 | [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v13.0.0.0)")] 15 | public partial class UserProfile 16 | { 17 | /// ID of the user. 18 | [Newtonsoft.Json.JsonProperty("UserId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 19 | public string? UserId { get; set; }= default!; 20 | 21 | /// The username. 22 | [Newtonsoft.Json.JsonProperty("UserName", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 23 | public string? UserName { get; set; }= default!; 24 | 25 | /// The users phone number. 26 | [Newtonsoft.Json.JsonProperty("PhoneNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 27 | public string? PhoneNumber { get; set; }= default!; 28 | 29 | /// The users email. 30 | [Newtonsoft.Json.JsonProperty("Email", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 31 | public string? Email { get; set; }= default!; 32 | 33 | /// The users party id. 34 | [Newtonsoft.Json.JsonProperty("PartyId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 35 | public string? PartyId { get; set; }= default!; 36 | 37 | /// The user type (default = 0, user = 1, org = 2). 38 | [Newtonsoft.Json.JsonProperty("UserType", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 39 | public string? UserType { get; set; }= default!; 40 | 41 | /// The user's language preference in Altinn 42 | [Newtonsoft.Json.JsonProperty("ProfileSettingPreference.Language", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 43 | public string? ProfileSettingPreferenceLanguage { get; set; }= default!; 44 | 45 | /// The user's preselected party. 46 | [Newtonsoft.Json.JsonProperty("ProfileSettingPreference.PreSelectedPartyId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 47 | public string? ProfileSettingPreferencePreSelectedPartyId { get; set; }= default!; 48 | 49 | /// Boolean indicating whether the users want to be asked for the party on every form submission. 50 | [Newtonsoft.Json.JsonProperty("ProfileSettingsPreference.DoNotPromptForParty", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 51 | public string? ProfileSettingsPreferenceDoNotPromptForParty { get; set; }= default!; 52 | 53 | /// The if of the users party. 54 | [Newtonsoft.Json.JsonProperty("Party.PartyId", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 55 | public string? PartyPartyId { get; set; }= default!; 56 | 57 | /// The type of the users party. (Person = 1, Organization = 2, SelfIdentified = 3, SubUnit = 4, BankruptcyEstate = 5) 58 | [Newtonsoft.Json.JsonProperty("Party.PartyTypeName", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 59 | public string? PartyPartyTypeName { get; set; }= default!; 60 | 61 | /// The party organization number. 62 | [Newtonsoft.Json.JsonProperty("Party.OrgNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 63 | public string? PartyOrgNumber { get; set; }= default!; 64 | 65 | /// The party social security number. 66 | [Newtonsoft.Json.JsonProperty("Party.SSN", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 67 | public string? PartySSN { get; set; }= default!; 68 | 69 | /// The party unit type. 70 | [Newtonsoft.Json.JsonProperty("Party.UnitType", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 71 | public string? PartyUnitType { get; set; }= default!; 72 | 73 | /// The party name. 74 | [Newtonsoft.Json.JsonProperty("Party.Name", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 75 | public string? PartyName { get; set; }= default!; 76 | 77 | /// Boolean value indicating if the party is deleted. 78 | [Newtonsoft.Json.JsonProperty("Party.isDeleted", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 79 | public string? PartyIsDeleted { get; set; }= default!; 80 | 81 | /// Boolean value indicating whether if the reportee in the list is only there for showing the hierarchy (a parent unit with no access). 82 | [Newtonsoft.Json.JsonProperty("Party.OnlyHierarchyElementWithNoAccess", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 83 | public string? PartyOnlyHierarchyElementWithNoAccess { get; set; }= default!; 84 | 85 | /// The social security number. 86 | [Newtonsoft.Json.JsonProperty("Party.Person.SSN", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 87 | public string? PartyPersonSSN { get; set; }= default!; 88 | 89 | /// The name persons full name. 90 | [Newtonsoft.Json.JsonProperty("Party.Person.Name", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 91 | public string? PartyPersonName { get; set; }= default!; 92 | 93 | /// The persons first name. 94 | [Newtonsoft.Json.JsonProperty("Party.Person.FirstName", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 95 | public string? PartyPersonFirstName { get; set; }= default!; 96 | 97 | /// The persons middle name. 98 | [Newtonsoft.Json.JsonProperty("Party.Person.MiddleName", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 99 | public string? PartyPersonMiddleName { get; set; }= default!; 100 | 101 | /// The persons last name. 102 | [Newtonsoft.Json.JsonProperty("Party.Person.LastName", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 103 | public string? PartyPersonLastName { get; set; }= default!; 104 | 105 | /// The telephone number. 106 | [Newtonsoft.Json.JsonProperty("Party.Person.TelephoneNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 107 | public string? PartyPersonTelephoneNumber { get; set; }= default!; 108 | 109 | /// The mobile number. 110 | [Newtonsoft.Json.JsonProperty("Party.Person.MobileNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 111 | public string? PartyPersonMobileNumber { get; set; }= default!; 112 | 113 | /// The mailing address. 114 | [Newtonsoft.Json.JsonProperty("Party.Person.MailingAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 115 | public string? PartyPersonMailingAddress { get; set; }= default!; 116 | 117 | /// The mailing address postal code. 118 | [Newtonsoft.Json.JsonProperty("Party.Person.MailingPostalCode", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 119 | public string? PartyPersonMailingPostalCode { get; set; }= default!; 120 | 121 | /// The mailing address postal city. 122 | [Newtonsoft.Json.JsonProperty("Party.Person.MailingPostalCity", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 123 | public string? PartyPersonMailingPostalCity { get; set; }= default!; 124 | 125 | /// The address municipal number. 126 | [Newtonsoft.Json.JsonProperty("Party.Person.AddressMunicipalNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 127 | public string? PartyPersonAddressMunicipalNumber { get; set; }= default!; 128 | 129 | /// The address municipal name. 130 | [Newtonsoft.Json.JsonProperty("Party.Person.AddressMunicipalName", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 131 | public string? PartyPersonAddressMunicipalName { get; set; }= default!; 132 | 133 | /// The address street name. 134 | [Newtonsoft.Json.JsonProperty("Party.Person.AddressStreetName", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 135 | public string? PartyPersonAddressStreetName { get; set; }= default!; 136 | 137 | /// The address house number. 138 | [Newtonsoft.Json.JsonProperty("Party.Person.AddressHouseNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 139 | public string? PartyPersonAddressHouseNumber { get; set; }= default!; 140 | 141 | /// The address house letter. 142 | [Newtonsoft.Json.JsonProperty("Party.Person.AddressHouseLetter", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 143 | public string? PartyPersonAddressHouseLetter { get; set; }= default!; 144 | 145 | /// The address postal code. 146 | [Newtonsoft.Json.JsonProperty("Party.Person.AddressPostalCode", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 147 | public string? PartyPersonAddressPostalCode { get; set; }= default!; 148 | 149 | /// The address city. 150 | [Newtonsoft.Json.JsonProperty("Party.Person.AddressCity", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 151 | public string? PartyPersonAddressCity { get; set; }= default!; 152 | 153 | /// The organization number 154 | [Newtonsoft.Json.JsonProperty("Party.Organization.OrgNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 155 | public string? PartyOrganizationOrgNumber { get; set; }= default!; 156 | 157 | /// The organization name. 158 | [Newtonsoft.Json.JsonProperty("Party.Organization.Name", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 159 | public string? PartyOrganizationName { get; set; }= default!; 160 | 161 | /// The unit type. 162 | [Newtonsoft.Json.JsonProperty("Party.Organization.UnitType", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 163 | public string? PartyOrganizationUnitType { get; set; }= default!; 164 | 165 | /// The phone number 166 | [Newtonsoft.Json.JsonProperty("Party.Organization.TelephoneNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 167 | public string? PartyOrganizationTelephoneNumber { get; set; }= default!; 168 | 169 | /// The mobile number. 170 | [Newtonsoft.Json.JsonProperty("Party.Organization.MobileNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 171 | public string? PartyOrganizationMobileNumber { get; set; }= default!; 172 | 173 | /// The fax number. 174 | [Newtonsoft.Json.JsonProperty("Party.Organization.FaxNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 175 | public string? PartyOrganizationFaxNumber { get; set; }= default!; 176 | 177 | /// The email address. 178 | [Newtonsoft.Json.JsonProperty("Party.Organization.EMailAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 179 | public string? PartyOrganizationEMailAddress { get; set; }= default!; 180 | 181 | /// The internet address. 182 | [Newtonsoft.Json.JsonProperty("Party.Organization.InternetAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 183 | public string? PartyOrganizationInternetAddress { get; set; }= default!; 184 | 185 | /// The mailing address. 186 | [Newtonsoft.Json.JsonProperty("Party.Organization.MailingAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 187 | public string? PartyOrganizationMailingAddress { get; set; }= default!; 188 | 189 | /// The mailing address. 190 | [Newtonsoft.Json.JsonProperty("Party.Organization.MailingPostalCode", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 191 | public string? PartyOrganizationMailingPostalCode { get; set; }= default!; 192 | 193 | /// The mailing address postal city. 194 | [Newtonsoft.Json.JsonProperty("Party.Organization.MailingPostalCity", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 195 | public string? PartyOrganizationMailingPostalCity { get; set; }= default!; 196 | 197 | /// The business address. 198 | [Newtonsoft.Json.JsonProperty("Party.Organization.BusinessAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 199 | public string? PartyOrganizationBusinessAddress { get; set; }= default!; 200 | 201 | /// The business postal code. 202 | [Newtonsoft.Json.JsonProperty("Party.Organization.BusinessPostalCode", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 203 | public string? PartyOrganizationBusinessPostalCode { get; set; }= default!; 204 | 205 | /// The business postal city. 206 | [Newtonsoft.Json.JsonProperty("Party.Organization.BusinessPostalCity", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 207 | public string? PartyOrganizationBusinessPostalCity { get; set; }= default!; 208 | 209 | private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); 210 | 211 | [Newtonsoft.Json.JsonExtensionData] 212 | public System.Collections.Generic.IDictionary AdditionalProperties 213 | { 214 | get { return _additionalProperties; } 215 | set { _additionalProperties = value; } 216 | } 217 | 218 | 219 | } 220 | 221 | /// Data from ER (Enhetsregisteret). 222 | [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v13.0.0.0)")] 223 | public partial class ER 224 | { 225 | /// The organization number. 226 | [Newtonsoft.Json.JsonProperty("OrgNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 227 | public string? OrgNumber { get; set; }= default!; 228 | 229 | /// The organization name. 230 | [Newtonsoft.Json.JsonProperty("Name", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 231 | public string? Name { get; set; }= default!; 232 | 233 | /// The unit type. 234 | [Newtonsoft.Json.JsonProperty("UnitType", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 235 | public string? UnitType { get; set; }= default!; 236 | 237 | /// The phone number. 238 | [Newtonsoft.Json.JsonProperty("TelephoneNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 239 | public string? TelephoneNumber { get; set; }= default!; 240 | 241 | /// The mobile number. 242 | [Newtonsoft.Json.JsonProperty("MobileNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 243 | public string? MobileNumber { get; set; }= default!; 244 | 245 | /// The fax number. 246 | [Newtonsoft.Json.JsonProperty("FaxNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 247 | public string? FaxNumber { get; set; }= default!; 248 | 249 | /// The email address. 250 | [Newtonsoft.Json.JsonProperty("EMailAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 251 | public string? EMailAddress { get; set; }= default!; 252 | 253 | /// The internet address. 254 | [Newtonsoft.Json.JsonProperty("InternetAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 255 | public string? InternetAddress { get; set; }= default!; 256 | 257 | /// The mailing address. 258 | [Newtonsoft.Json.JsonProperty("MailingAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 259 | public string? MailingAddress { get; set; }= default!; 260 | 261 | /// The mailing postal code. 262 | [Newtonsoft.Json.JsonProperty("MailingPostalCode", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 263 | public string? MailingPostalCode { get; set; }= default!; 264 | 265 | /// The mailing postal city. 266 | [Newtonsoft.Json.JsonProperty("MailingPostalCity", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 267 | public string? MailingPostalCity { get; set; }= default!; 268 | 269 | /// The business address 270 | [Newtonsoft.Json.JsonProperty("BusinessAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 271 | public string? BusinessAddress { get; set; }= default!; 272 | 273 | /// The business postal code. 274 | [Newtonsoft.Json.JsonProperty("BusinessPostalCode", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 275 | public string? BusinessPostalCode { get; set; }= default!; 276 | 277 | /// The business postal city. 278 | [Newtonsoft.Json.JsonProperty("BusinessPostalCity", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 279 | public string? BusinessPostalCity { get; set; }= default!; 280 | 281 | private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); 282 | 283 | [Newtonsoft.Json.JsonExtensionData] 284 | public System.Collections.Generic.IDictionary AdditionalProperties 285 | { 286 | get { return _additionalProperties; } 287 | set { _additionalProperties = value; } 288 | } 289 | 290 | 291 | } 292 | 293 | /// Data from DSF (Det Sentrale Folkeregisteret). 294 | [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v13.0.0.0)")] 295 | public partial class DSF 296 | { 297 | /// The persons social security number. 298 | [Newtonsoft.Json.JsonProperty("SSN", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 299 | public string? SSN { get; set; }= default!; 300 | 301 | /// The persons full name. 302 | [Newtonsoft.Json.JsonProperty("Name", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 303 | public string? Name { get; set; }= default!; 304 | 305 | /// The persons first name. 306 | [Newtonsoft.Json.JsonProperty("FirstName", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 307 | public string? FirstName { get; set; }= default!; 308 | 309 | /// The persons middle name. 310 | [Newtonsoft.Json.JsonProperty("MiddleName", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 311 | public string? MiddleName { get; set; }= default!; 312 | 313 | /// The persons last name. 314 | [Newtonsoft.Json.JsonProperty("LastName", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 315 | public string? LastName { get; set; }= default!; 316 | 317 | /// The persons telephone number. 318 | [Newtonsoft.Json.JsonProperty("TelephoneNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 319 | public string? TelephoneNumber { get; set; }= default!; 320 | 321 | /// The persons mobile number. 322 | [Newtonsoft.Json.JsonProperty("MobileNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 323 | public string? MobileNumber { get; set; }= default!; 324 | 325 | /// The persons mailing address. 326 | [Newtonsoft.Json.JsonProperty("MailingAddress", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 327 | public string? MailingAddress { get; set; }= default!; 328 | 329 | /// The persons mailing postal code. 330 | [Newtonsoft.Json.JsonProperty("MailingPostalCode", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 331 | public string? MailingPostalCode { get; set; }= default!; 332 | 333 | /// The persons full name. 334 | [Newtonsoft.Json.JsonProperty("MailingPostalCity", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 335 | public string? MailingPostalCity { get; set; }= default!; 336 | 337 | /// The persons address municipal number. 338 | [Newtonsoft.Json.JsonProperty("AddressMunicipalNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 339 | public string? AddressMunicipalNumber { get; set; }= default!; 340 | 341 | /// The persons address municipal name. 342 | [Newtonsoft.Json.JsonProperty("AddressMunicipalName", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 343 | public string? AddressMunicipalName { get; set; }= default!; 344 | 345 | /// The persons address street name. 346 | [Newtonsoft.Json.JsonProperty("AddressStreetName", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 347 | public string? AddressStreetName { get; set; }= default!; 348 | 349 | /// The persons address house number. 350 | [Newtonsoft.Json.JsonProperty("AddressHouseNumber", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 351 | public string? AddressHouseNumber { get; set; }= default!; 352 | 353 | /// The persons address house letter. 354 | [Newtonsoft.Json.JsonProperty("AddressHouseLetter", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 355 | public string? AddressHouseLetter { get; set; }= default!; 356 | 357 | /// The persons address postal code. 358 | [Newtonsoft.Json.JsonProperty("AddressPostalCode", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 359 | public string? AddressPostalCode { get; set; }= default!; 360 | 361 | /// The persons address city. 362 | [Newtonsoft.Json.JsonProperty("AddressCity", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 363 | public string? AddressCity { get; set; }= default!; 364 | 365 | private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); 366 | 367 | [Newtonsoft.Json.JsonExtensionData] 368 | public System.Collections.Generic.IDictionary AdditionalProperties 369 | { 370 | get { return _additionalProperties; } 371 | set { _additionalProperties = value; } 372 | } 373 | 374 | 375 | } 376 | 377 | /// Schema that describes the prefill configuration for Altinn applications. 378 | [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "10.5.2.0 (Newtonsoft.Json v13.0.0.0)")] 379 | public partial class Test 380 | { 381 | /// Flag to determine if existing values in the app data model can be overwritten by prefill data. 382 | [Newtonsoft.Json.JsonProperty("allowOverwrite", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 383 | public bool? AllowOverwrite { get; set; }= default!; 384 | 385 | [Newtonsoft.Json.JsonProperty("UserProfile", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 386 | public UserProfile? UserProfile { get; set; }= default!; 387 | 388 | [Newtonsoft.Json.JsonProperty("ER", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 389 | public ER? ER { get; set; }= default!; 390 | 391 | [Newtonsoft.Json.JsonProperty("DSF", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)] 392 | public DSF? DSF { get; set; }= default!; 393 | 394 | private System.Collections.Generic.IDictionary _additionalProperties = new System.Collections.Generic.Dictionary(); 395 | 396 | [Newtonsoft.Json.JsonExtensionData] 397 | public System.Collections.Generic.IDictionary AdditionalProperties 398 | { 399 | get { return _additionalProperties; } 400 | set { _additionalProperties = value; } 401 | } 402 | 403 | 404 | } 405 | } --------------------------------------------------------------------------------