diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..75d9084f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,276 @@ +# Remove the line below if you want to inherit .editorconfig settings from higher directories +root = true + +# All files +[*] +end_of_line = lf +insert_final_newline = true + +# C# files +[*.cs] + +#### Core EditorConfig Options #### + +# Indentation and spacing +indent_size = 4 +indent_style = space +tab_width = 4 + +# New line preferences +end_of_line = crlf +insert_final_newline = false + +#### .NET Code Actions #### + +# Type members +dotnet_hide_advanced_members = false +dotnet_member_insertion_location = with_other_members_of_the_same_kind +dotnet_property_generation_behavior = prefer_auto_properties + +# Symbol search +dotnet_search_reference_assemblies = true + +#### .NET Coding Conventions #### + +# Organize usings +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# this. and Me. preferences +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# Language keywords vs BCL types preferences +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = always_for_clarity +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# Expression-level preferences +dotnet_prefer_system_hash_code = true +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true +dotnet_style_prefer_collection_expression = when_types_loosely_match +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# Field preferences +dotnet_style_readonly_field = true + +# Parameter preferences +dotnet_code_quality_unused_parameters = non_public + +# Suppression preferences +dotnet_remove_unnecessary_suppression_exclusions = none + +# New line preferences +dotnet_style_allow_multiple_blank_lines_experimental = false +dotnet_style_allow_statement_immediately_after_block_experimental = false + +#### C# Coding Conventions #### + +# var preferences +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = false + +# Expression-bodied members +csharp_style_expression_bodied_accessors = true +csharp_style_expression_bodied_constructors = false +csharp_style_expression_bodied_indexers = true +csharp_style_expression_bodied_lambdas = when_on_single_line +csharp_style_expression_bodied_local_functions = when_on_single_line +csharp_style_expression_bodied_methods = false +csharp_style_expression_bodied_operators = false +csharp_style_expression_bodied_properties = true + +# Pattern matching preferences +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_extended_property_pattern = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Null-checking preferences +csharp_style_conditional_delegate_call = true + +# Modifier preferences +csharp_prefer_static_anonymous_function = true +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async +csharp_style_prefer_readonly_struct = true +csharp_style_prefer_readonly_struct_member = true + +# Code-block preferences +csharp_prefer_braces = true +csharp_prefer_simple_using_statement = false +csharp_prefer_system_threading_lock = true +csharp_style_namespace_declarations = file_scoped +csharp_style_prefer_method_group_conversion = false +csharp_style_prefer_primary_constructors = false +csharp_style_prefer_top_level_statements = false + +# Expression-level preferences +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_prefer_implicitly_typed_lambda_expression = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_local_over_anonymous_function = false +csharp_style_prefer_null_check_over_type_check = true +csharp_style_prefer_range_operator = true +csharp_style_prefer_tuple_swap = true +csharp_style_prefer_unbound_generic_type_in_nameof = true +csharp_style_prefer_utf8_string_literals = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# 'using' directive preferences +csharp_using_directive_placement = inside_namespace + +# New line preferences +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = false +csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = false +csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = false +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = false +csharp_style_allow_embedded_statements_on_same_line_experimental = false + +#### C# Formatting Rules #### + +# New line preferences +csharp_new_line_before_catch = false +csharp_new_line_before_else = false +csharp_new_line_before_finally = false +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = none +csharp_new_line_between_query_expression_clauses = true + +# Indentation preferences +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = false +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# Space preferences +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# Wrapping preferences +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = false + +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.private_or_internal_const_field_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.private_or_internal_const_field_should_be_pascal_case.symbols = private_or_internal_const_field +dotnet_naming_rule.private_or_internal_const_field_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.private_or_internal_field_should_be_underscore_camel_case.severity = suggestion +dotnet_naming_rule.private_or_internal_field_should_be_underscore_camel_case.symbols = private_or_internal_field +dotnet_naming_rule.private_or_internal_field_should_be_underscore_camel_case.style = underscore_camel_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field +dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected +dotnet_naming_symbols.private_or_internal_field.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, method, event +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +dotnet_naming_symbols.private_or_internal_const_field.applicable_kinds = field +dotnet_naming_symbols.private_or_internal_const_field.applicable_accessibilities = internal, private, protected_internal, private_protected +dotnet_naming_symbols.private_or_internal_const_field.required_modifiers = const + +# Naming styles + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.underscore_camel_case.required_prefix = _ +dotnet_naming_style.underscore_camel_case.required_suffix = +dotnet_naming_style.underscore_camel_case.word_separator = +dotnet_naming_style.underscore_camel_case.capitalization = camel_case + +# Public API analyzer + +dotnet_public_api_analyzer.require_api_files = true diff --git a/.gitattributes b/.gitattributes index 5fa1ed0e..e4cab669 100644 --- a/.gitattributes +++ b/.gitattributes @@ -10,7 +10,7 @@ # default for csharp files. # Note: This is only used by command line ############################################################################### -#*.cs diff=csharp +*.cs diff=csharp ############################################################################### # Set the merge driver for project and solution files @@ -22,27 +22,28 @@ # these files as binary and thus will always conflict and require user # intervention with every merge. To do so, just uncomment the entries below ############################################################################### -#*.sln merge=binary -#*.csproj merge=binary -#*.vbproj merge=binary -#*.vcxproj merge=binary -#*.vcproj merge=binary -#*.dbproj merge=binary -#*.fsproj merge=binary -#*.lsproj merge=binary -#*.wixproj merge=binary -#*.modelproj merge=binary -#*.sqlproj merge=binary -#*.wwaproj merge=binary +*.sln merge=binary +*.csproj merge=binary +*.vbproj merge=binary +*.vcxproj merge=binary +*.vcproj merge=binary +*.dbproj merge=binary +*.fsproj merge=binary +*.lsproj merge=binary +*.wixproj merge=binary +*.modelproj merge=binary +*.sqlproj merge=binary +*.wwaproj merge=binary ############################################################################### # behavior for image files # # image files are treated as binary by default. ############################################################################### -#*.jpg binary -#*.png binary -#*.gif binary +*.jpg binary +*.png binary +*.gif binary +*.ico binary ############################################################################### # diff behavior for common document formats @@ -51,16 +52,16 @@ # is only available from the command line. Turn it on by uncommenting the # entries below. ############################################################################### -#*.doc diff=astextplain -#*.DOC diff=astextplain -#*.docx diff=astextplain -#*.DOCX diff=astextplain -#*.dot diff=astextplain -#*.DOT diff=astextplain -#*.pdf diff=astextplain -#*.PDF diff=astextplain -#*.rtf diff=astextplain -#*.RTF diff=astextplain +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain ############################################################################### # exclude files except those with cs file extension from repository language detection diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..dd84ea78 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 00000000..bbcbbe7d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/actions/documentation/docfx-build/action.yml b/.github/actions/documentation/docfx-build/action.yml new file mode 100644 index 00000000..a24e6e75 --- /dev/null +++ b/.github/actions/documentation/docfx-build/action.yml @@ -0,0 +1,45 @@ +name: 'Generate docs with docfx' +author: 'Pete Sramek' +description: 'Generate documentation using docfx' +inputs: +# Required + artifact-name: + description: 'Name of the artifact to upload after generating documentation' + required: true + docfx-json-manifest: + description: 'Path to the docfx JSON manifest file' + required: true + output-directory: + description: 'Target directory for generated documentation' + required: true +# Optional + dotnet_sdk_version: + description: '.NET SDK version. Default: 10.x' + required: false + default: '10.x' + +runs: + using: composite + steps: + - name: Dotnet Setup + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ inputs.dotnet_sdk_version }} + - name: 'testing variables' + shell: bash + run: | + echo "artifact-name: ${{ inputs.artifact-name }}" + echo "docfx-json-manifest: ${{ inputs.docfx-json-manifest }}" + echo "output-directory: ${{ inputs.output-directory }}" + echo "dotnet-sdk-version: ${{ inputs.dotnet_sdk_version }}" + - name: 'Update docfx tool' + run: dotnet tool update -g docfx + shell: bash + - name: 'Generate documentation' + run: docfx build ${{ inputs.docfx-json-manifest }} + shell: bash + - name: Upload artifact + uses: actions/upload-artifact@v7 + with: + name: ${{ inputs.artifact-name }} + path: ${{ inputs.output-directory }} diff --git a/.github/actions/documentation/docfx-metadata/action.yml b/.github/actions/documentation/docfx-metadata/action.yml new file mode 100644 index 00000000..bbcd6383 --- /dev/null +++ b/.github/actions/documentation/docfx-metadata/action.yml @@ -0,0 +1,65 @@ +name: 'Generate metadata with docfx' +author: 'Pete Sramek' +description: 'Generate metadata using docfx' + +inputs: +# Required + artifact-name: + description: 'Name of the artifact to upload after generating metadata' + required: true + docfx-json-manifest: + description: 'Path to the docfx JSON manifest file' + required: true + temporary-directory: + description: 'Temporary directory for docfx metadata generation' + required: true + output-directory: + description: 'Target directory for generated documentation' + required: true +# Optional + dotnet_sdk_version: + description: '.NET SDK version. Default: 10.x' + required: false + default: '10.x' + +runs: + using: composite + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + - name: Dotnet Setup + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.dotnet-sdk-version }} + - name: 'Update docfx tool' + run: dotnet tool update -g docfx + shell: bash + - name: 'Delete temporary folder' + shell: bash + run: | + if [ -d "${{ inputs.temporary-directory }}" ]; then + rm -rf "${{ inputs.temporary-directory }}" + fi + - name: 'Delete temporary folder' + shell: bash + run: | + if [ -d "$${{ inputs.output-directory }}" ]; then + rm -rf "${{ inputs.output-directory }}" + fi + - name: 'Generate assembly metadata' + shell: bash + run: docfx metadata ${{ inputs.docfx-json-manifest }} + - name: 'List directory' + shell: bash + run: | + ls + - name: 'Copy metadata to output directory' + shell: bash + run: | + mkdir -p ${{ inputs.output-directory }} + cp -r ${{ inputs.temporary-directory }}/* ${{ inputs.output-directory }} + - name: 'Upload artifact' + uses: actions/upload-artifact@v7 + with: + name: ${{ inputs.artifact-name }} + path: ${{ inputs.output-directory }} diff --git a/.github/actions/git/push-changes/action.yml b/.github/actions/git/push-changes/action.yml new file mode 100644 index 00000000..31ddd3f2 --- /dev/null +++ b/.github/actions/git/push-changes/action.yml @@ -0,0 +1,99 @@ +name: 'Push Changes' +author: 'Pete Sramek' +description: 'Push changes to a specified branch in the repository.' +inputs: + # Required + commit-message: + description: 'The commit message to use when pushing changes.' + required: true +# Optional + dotnet_sdk_version: + description: '.NET SDK version. Default: 10.x' + required: false + default: '10.x' + artifact-name: + description: 'Name of the artifact to download before pushing changes. Default: ''''' + required: false + default: '' + working-directory: + description: 'The working directory where the changes will be pushed from. Default ''.''' + required: false + default: '.' + target-branch: + description: 'The branch to push changes to.. Default: ''''' + required: false + default: '' + +runs: + using: "composite" + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: Dotnet Setup + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ env.dotnet-sdk-version }} + + - name: Download a single artifact + if: ${{ inputs.artifact-name != '' }} + uses: actions/download-artifact@v8 + with: + name: ${{ inputs.artifact-name }} + path: ${{ inputs.working-directory }} + + - name: Stage changes in ${{ inputs.working-directory }} + shell: bash + run: | + git add . + working-directory: ${{ inputs.working-directory }} + + - if: runner.os == 'Windows' + shell: bash + run: | + git config --global core.autocrlf false + git config --global core.eol lf + + - if: runner.os != 'Windows' + shell: bash + run: | + git config --global core.autocrlf true + git config --global core.eol lf + + - name: Create or switch to ${{ inputs.target-branch }} + if: ${{ inputs.target-branch != '' }} + shell: bash + run: | + git fetch origin + git stash push --keep-index + if git show-ref --verify --quiet refs/heads/${{ inputs.target-branch }}; then + echo "Branch ${{ inputs.target-branch }} already exists, switching to it." + git checkout ${{ inputs.target-branch }} --force + git pull origin ${{ inputs.target-branch }} + else + echo "Branch ${{ inputs.target-branch }} does not exist, creating it." + git checkout -b ${{ inputs.target-branch }} origin/${{ inputs.target-branch }} --force || git checkout -b ${{ inputs.target-branch }} --force + git pull origin ${{ inputs.target-branch }} + fi + git stash apply + + - name: Validate changes + id: validate + shell: bash + run: | + set +e + git diff --quiet --exit-code --cached + echo has-changes="$?" >> $GITHUB_OUTPUT + set -e + working-directory: ${{ inputs.working-directory }} + + - name: Push changes to ${{ github.head_ref || github.ref }} + if: ${{ fromJSON(steps.validate.outputs.has-changes) == '1' }} + shell: bash + run: | + git config user.name "$(git log -n 1 --pretty=format:%an)" + git config user.email "$(git log -n 1 --pretty=format:%ae)" + git commit -m '${{ inputs.commit-message }}' + git pull --rebase origin ${{ github.head_ref || github.ref }} + git push + working-directory: ${{ inputs.working-directory }} diff --git a/.github/actions/github/branch-protection/lock/action.yml b/.github/actions/github/branch-protection/lock/action.yml new file mode 100644 index 00000000..d8f345a1 --- /dev/null +++ b/.github/actions/github/branch-protection/lock/action.yml @@ -0,0 +1,40 @@ +name: 'Lock branch' +author: 'Pete Sramek' +description: 'Apply branch protection to prevent direct pushes. Requires PRs with at least one approval.' +inputs: + branch: + description: 'Branch name to lock.' + required: true + token: + description: 'GitHub token with administration:write (repo admin) permission. Use a PAT; GITHUB_TOKEN cannot call the branch protection API.' + required: true + +runs: + using: composite + steps: + - name: 'Lock branch ${{ inputs.branch }}' + shell: bash + env: + GH_TOKEN: ${{ inputs.token }} + run: | + if ! gh api --method PUT /repos/${{ github.repository }}/branches/${{ inputs.branch }}/protection \ + --input - << 'EOF' + { + "required_status_checks": null, + "enforce_admins": false, + "required_pull_request_reviews": { + "dismiss_stale_reviews": true, + "require_code_owner_reviews": false, + "required_approving_review_count": 1 + }, + "restrictions": null, + "allow_force_pushes": false, + "allow_deletions": false, + "lock_branch": false + } + EOF + then + echo "::error::Failed to apply branch protection to '${{ inputs.branch }}'. Ensure the token has 'administration: write' permission and the branch exists." + exit 1 + fi + echo "🔒 Branch '${{ inputs.branch }}' is now protected." >> $GITHUB_STEP_SUMMARY diff --git a/.github/actions/github/branch-protection/unlock/action.yml b/.github/actions/github/branch-protection/unlock/action.yml new file mode 100644 index 00000000..9c41d395 --- /dev/null +++ b/.github/actions/github/branch-protection/unlock/action.yml @@ -0,0 +1,21 @@ +name: 'Unlock branch' +author: 'Pete Sramek' +description: 'Remove branch protection to allow a workflow to push directly. Always re-lock after the operation.' +inputs: + branch: + description: 'Branch name to unlock.' + required: true + token: + description: 'GitHub token with administration:write (repo admin) permission. Use a PAT; GITHUB_TOKEN cannot call the branch protection API.' + required: true + +runs: + using: composite + steps: + - name: 'Unlock branch ${{ inputs.branch }}' + shell: bash + env: + GH_TOKEN: ${{ inputs.token }} + run: | + gh api --method DELETE /repos/${{ github.repository }}/branches/${{ inputs.branch }}/protection || true + echo "🔓 Branch protection removed from '${{ inputs.branch }}'." >> $GITHUB_STEP_SUMMARY diff --git a/.github/actions/github/create-release/action.yml b/.github/actions/github/create-release/action.yml new file mode 100644 index 00000000..0e0015ce --- /dev/null +++ b/.github/actions/github/create-release/action.yml @@ -0,0 +1,40 @@ +name: 'Create GitHub release' +author: 'Pete Sramek' +description: 'Create GitHub release.' +inputs: + release-version: + description: 'Version to use for the release.' + required: true + is-preview: + description: 'Is this a preview release?' + required: true + notes-start-tag: + description: 'Tag to start generating release notes from. Default: ''''' + required: false + default: '' + +runs: + using: composite + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + - run: | + echo "release-version=${{ inputs.release-version }}" + echo "is-preview=${{ inputs.is-preview }}" + echo "preview-argument=${{ inputs.is-preview == 'true' && '--prerelease' || '' }}" + echo "notes-start-tag=${{ inputs.notes-start-tag }}" + echo "notes-start-tag-argument="${{ inputs.notes-start-tag != '' && '--notes-start-tag $(inputs.notes-start-tag)' || '' }}" + shell: bash + - name: 'Create git tag ${{ env.release-version }}' + shell: bash + run: | + git tag -a ${{ env.release-version }} -m "${{ env.release-version }}" + - name: 'Create GitHub release PolylineAlgorithm ${{ env.release-version }}' + shell: bash + env: + GH_TOKEN: ${{ github.token }} + release-version: ${{ inputs.release-version }} + preview-argument: "${{ inputs.is-preview == 'true' && '--prerelease' || '' }}" + notes-start-tag-argument: "${{ inputs.notes-start-tag != '' && '--notes-start-tag $(inputs.notes-start-tag)' || '' }}" + run: | + gh release create ${{ env.release-version }} --generate-notes --discussion-category "General" ${{ env.preview-argument }} ${{ env.notes-start-tag-argument }} diff --git a/.github/actions/github/write-file-to-summary/action.yml b/.github/actions/github/write-file-to-summary/action.yml new file mode 100644 index 00000000..c185ce4b --- /dev/null +++ b/.github/actions/github/write-file-to-summary/action.yml @@ -0,0 +1,25 @@ +name: 'Write file to step summary' +author: 'Pete Sramek' +description: 'Writes file contents to step summary.' +inputs: +# Required + file-glob-pattern: + description: 'Search pattern for files to write.' + required: true + +# Optional + working-directory: + description: 'Working directory to search for file.' + required: false + default: ${{ github.workspace }} + +runs: + using: composite + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: Writing ${{ inputs.file }} to step summary + shell: bash + run: cat ${{ inputs.file }} >> $GITHUB_STEP_SUMMARY + working-directory: ${{ inputs.working-directory }} \ No newline at end of file diff --git a/.github/actions/nuget/publish-package/action.yml b/.github/actions/nuget/publish-package/action.yml new file mode 100644 index 00000000..eec82f65 --- /dev/null +++ b/.github/actions/nuget/publish-package/action.yml @@ -0,0 +1,61 @@ +name: 'Publish packages to NuGet feed' +author: 'Pete Sramek' +description: 'Publishes packages in working directory to public NuGet feed' +inputs: +# Required + package-artifact-name: + description: 'Name of the artifact to download and publish' + required: true + nuget-feed-url: + description: 'NuGet feed URL' + required: true + nuget-feed-api-key: + description: 'NuGet feed API key' + required: true + nuget-feed-server: + description: 'NuGet feed server. Allowed values: ''NuGet'', ''AzureArtifacts''' + required: true +# Optional + dotnet-sdk-version: + description: '.NET SDK version. Default: 10.x' + required: false + default: '10.x' + working-directory: + description: 'Working directory to search for file.' + required: false + default: ${{ github.workspace }} + +runs: + using: composite + + steps: + + - if: ${{ inputs.nuget-feed-server != 'NuGet' && inputs.nuget-feed-server != 'AzureArtifacts' }} + name: 'Validate NuGet feed server type' + shell: bash + run: | + echo "Invalid NuGet feed server type. Allowed values are ''NuGet'' or ''AzureArtifacts''." + exit 1 + + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: Download package artifact + uses: actions/download-artifact@v8 + with: + name: ${{ inputs.package-artifact-name }} + + - if: ${{ inputs.nuget-feed-server == 'NuGet' }} + name: 'Publish package to NuGet' + shell: bash + run: | + dotnet nuget push **/*.nupkg --source ${{ inputs.nuget-feed-url }} --api-key ${{ inputs.nuget-feed-api-key }} + working-directory: ${{ env.working-directory }} + + - if: ${{ inputs.nuget-feed-server == 'AzureArtifacts' }} + name: 'Publish package to Azure Artifacts' + shell: bash + run: | + dotnet nuget add source ${{ inputs.nuget-feed-url }} --name nuget-feed --username username --password ${{ inputs.nuget-feed-api-key }} --store-password-in-clear-text + dotnet nuget push **/*.nupkg --source nuget-feed --api-key ${{ inputs.nuget-feed-api-key }} --skip-duplicate + working-directory: ${{ env.working-directory }} \ No newline at end of file diff --git a/.github/actions/source/compile/action.yml b/.github/actions/source/compile/action.yml new file mode 100644 index 00000000..0af331ee --- /dev/null +++ b/.github/actions/source/compile/action.yml @@ -0,0 +1,74 @@ +name: 'Compile source code' +author: 'Pete Sramek' +description: 'Compiles source code, uploads build artifacts.' +inputs: +# Required + assembly-version: + description: 'Assembly version.' + required: true + assembly-informational-version: + description: 'Assembly informational version.' + required: true + file-version: + description: 'Assembly file version.' + required: true + treat-warnins-as-error: + description: 'Treat warnings as errors.' + required: true + project-path: + description: 'Search pattern for source code.' + required: true +# Optional + dotnet_sdk_version: + description: '.NET SDK version. Default: ''10.x''' + required: false + default: '10.x' + build-configuration: + description: 'Build configuration. Default: ''Release''' + required: false + default: 'Release' + build-platform: + description: 'Build platform. Deafult: ''Any CPU''' + required: false + default: 'Any CPU' + upload-build-artifacts: + description: 'Upload build artifacts, Default: ''true''' + required: false + default: 'true' + build-artifacts-glob-pattern: + description: 'Search pattern for build artifacts. Default: **/bin/*, **/obj/*' + required: false + default: | + **/bin/* + **/obj/* + build-artifacts-name: + description: 'Upload build artifacts, Default: ''build''' + required: false + default: 'build' + +runs: + using: "composite" + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: 'Setup .NET ${{ inputs.dotnet_sdk_version }}' + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ inputs.dotnet_sdk_version }} + + - if: ${{ inputs.treat-warnings-as-error == 'true' }} + name: 'Validate warnings with .NET CLI' + shell: bash + run: dotnet format analyzers --severity warn --verify-no-changes + + - name: 'Build with .NET CLI' + shell: bash + run: dotnet build ${{ inputs.search-glob-pattern }} --configuration ${{ inputs.build-configuration }} /p:Platform="${{ inputs.build-platform }}" /p:Version=${{ inputs.assembly-version }} /p:AssemblyInformationalVersion=${{ inputs.assembly-informational-version }} /p:FileVersion=${{ inputs.file-version }} + + - name: 'Upload build artifacts' + if: ${{ inputs.upload-build-artifacts == 'true' }} + uses: actions/upload-artifact@v7 + with: + name: ${{ inputs.build-artifacts-name }} + path: ${{ inputs.build-artifacts-glob-pattern }} diff --git a/.github/actions/source/format/action.yml b/.github/actions/source/format/action.yml new file mode 100644 index 00000000..de2536c2 --- /dev/null +++ b/.github/actions/source/format/action.yml @@ -0,0 +1,73 @@ +name: 'Format source code' +author: 'Pete Sramek' +description: 'Formats source code using dotnet format tool. Pushes changes to the current branch.' +inputs: +# Required + project-path: + description: 'Path to the project or solution file.' + required: true +# Optional + dotnet_sdk_version: + description: '.NET SDK version. Default: ''10.x''' + required: false + default: '10.x' + format-whitespace: + description: 'Format whitespace. Default: ''true''' + required: false + default: 'true' + format-style: + description: 'Format style. Default: ''true''' + required: false + default: 'true' + format-analyzers: + description: 'Format analyzers. Default: ''false''' + required: false + default: 'false' + format-analyzers-diagnostics-parameter: + description: 'Format analyzers diagnostics parameter. Default: ''''' + required: false + default: '' + +runs: + using: "composite" + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: 'Setup .NET ${{ inputs.dotnet_sdk_version }}' + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ inputs.dotnet_sdk_version }} + + - name: Format whitespace + if: ${{ inputs.format-whitespace == 'true' }} + shell: bash + run: | + dotnet format whitespace + working-directory: ${{ github.workspace }} + + - name: Format style + if: ${{ inputs.format-style == 'true' }} + shell: bash + run: | + dotnet format style + working-directory: ${{ github.workspace }} + + - name: 'Set target branch' + if: ${{ inputs.format-analyzers == 'true' && inputs.format-analyzers-diagnostics-parameter != '' }} + id: set-diagnostics-parameter + shell: bash + run: | + echo "format-analyzers-diagnostics-parameter=--diagnostics ${{ inputs.format-analyzers-diagnostics-parameter }}" >> $GITHUB_OUTPUT + + - name: Format analyzers + if: ${{ inputs.format-analyzers == 'true' }} + shell: bash + run: | + dotnet format analyzers ${{ env.format-analyzers-diagnostics-parameter }} + working-directory: ${{ github.workspace }} + + - name: 'Push changes' + uses: './.github/actions/git/push-changes' + with: + commit-message: 'Formatted csharp files' diff --git a/.github/actions/testing/code-coverage/action.yml b/.github/actions/testing/code-coverage/action.yml new file mode 100644 index 00000000..8751a7b2 --- /dev/null +++ b/.github/actions/testing/code-coverage/action.yml @@ -0,0 +1,73 @@ +name: 'Test with .NET CLI' +author: 'Pete Sramek' +description: 'Run tests, collects code coverage, logs test results, uploads test artifacts' +inputs: +# Required + test-result-folder: + description: 'Folder where test results are stored' + required: true +# Optional + dotnet_sdk_version: + description: '.NET SDK version. Default: 10.x' + required: false + default: '10.x' + +outputs: + code-coverage-report-file: + description: 'Path to the generated code coverage report' + value: ${{ steps.variables.outputs.code-coverage-report-file }} + code-coverage-merge-file: + description: 'Path to the merged code coverage file' + value: ${{ steps.variables.outputs.code-coverage-merge-file }} + +runs: + using: composite + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: 'Setup .NET ${{ inputs.dotnet_sdk_version }}' + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ inputs.dotnet_sdk_version }} + + - if: ${{ endsWith(inputs.test-result-folder, '/') }} + name: 'Set TEST_RESULT_FOLDER environment variable' + shell: bash + run: echo "TEST_RESULT_FOLDER=${{ inputs.test-result-folder }}" >> $GITHUB_ENV + + - if: ${{ !endsWith(inputs.test-result-folder, '/') }} + name: 'Set TEST_RESULT_FOLDER environment variable' + shell: bash + run: echo "TEST_RESULT_FOLDER=${{ inputs.test-result-folder }}/" >> $GITHUB_ENV + + - name: 'Set CODE_COVERAGE_REPORT_FOLDER environment variable' + shell: bash + run: echo "CODE_COVERAGE_REPORT_FOLDER=${{ env.TEST_RESULT_FOLDER }}coverage-report/" >> $GITHUB_ENV + + - name: 'Set CODE_COVERAGE_MERGED_FILE environment variable' + shell: bash + run: echo "CODE_COVERAGE_MERGED_FILE=${{ env.CODE_COVERAGE_REPORT_FOLDER }}code-coverage.cobertura.xml" >> $GITHUB_ENV + + - name: 'Set code-coverage-report output' + id: variables + shell: bash + run: | + echo "code-coverage-report-file=${{ env.CODE_COVERAGE_REPORT_FOLDER }}Summary.md" >> $GITHUB_OUTPUT + echo "code-coverage-merge-file=${{ env.CODE_COVERAGE_MERGED_FILE }}" >> $GITHUB_OUTPUT + + - name: 'Update dotnet-coverage tool' + shell: bash + run: dotnet tool update --global dotnet-coverage + + - name: 'Merge code coverage reports' + shell: bash + run: dotnet-coverage merge --output ${{ env.CODE_COVERAGE_MERGED_FILE }} --output-format cobertura "${{ env.TEST_RESULT_FOLDER }}**/*.cobertura.xml" + + - name: 'Update dotnet-reportgenerator-globaltool tool' + shell: bash + run: dotnet tool update --global dotnet-reportgenerator-globaltool + + - name: 'Generate code coverage report' + shell: bash + run: reportgenerator -reports:${{ env.CODE_COVERAGE_MERGED_FILE }} -targetdir:${{ env.CODE_COVERAGE_REPORT_FOLDER }} -reporttypes:"MarkdownSummary" diff --git a/.github/actions/testing/test-report/action.yml b/.github/actions/testing/test-report/action.yml new file mode 100644 index 00000000..40e66385 --- /dev/null +++ b/.github/actions/testing/test-report/action.yml @@ -0,0 +1,60 @@ +name: 'Generate test report with Liquid Test Reports CLI' +author: 'Pete Sramek' +description: 'Run tests, collects code coverage, logs test results, uploads test artifacts' +inputs: +# Required + test-result-folder: + description: 'Folder where test results are stored' + required: true +# Optional + dotnet_sdk_version: + description: '.NET SDK version. Default: 10.x' + required: false + default: '10.x' + test-report-filename: + description: 'Filename of the test report. Default: test-report.md' + required: false + default: 'test-report.md' + +outputs: + test-report-file: + description: 'Path to the generated test report' + value: ${{ steps.variables.outputs.test-report-file }} + +runs: + using: composite + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: 'Setup .NET ${{ inputs.dotnet_sdk_version }}' + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ inputs.dotnet_sdk_version }} + + - name: Install LiquidTestReports.Cli + shell: bash + run: dotnet tool install --global LiquidTestReports.Cli --prerelease + + - if: ${{ endsWith(inputs.test-result-folder, '/') }} + name: 'Set TEST_RESULT_FOLDER environment variable' + shell: bash + run: echo "TEST_RESULT_FOLDER=${{ inputs.test-result-folder }}" >> $GITHUB_ENV + + - if: ${{ !endsWith(inputs.test-result-folder, '/') }} + name: 'Set TEST_RESULT_FOLDER environment variable' + shell: bash + run: echo "TEST_RESULT_FOLDER=${{ inputs.test-result-folder }}/" >> $GITHUB_ENV + + - name: 'Set TEST_REPORT_FILE environment variable' + shell: bash + run: echo "TEST_REPORT_FILE=${{ env.TEST_RESULT_FOLDER }}${{ inputs.test-report-filename }}" >> $GITHUB_ENV + + - name: 'Set test-report-file output' + id: variables + shell: bash + run: echo "test-report-file=${{ env.TEST_REPORT_FILE }}" >> $GITHUB_OUTPUT + + - name: Generate test report + shell: bash + run: liquid --inputs "Folder=${{ env.TEST_RESULT_FOLDER }};File=**/*.trx;Format=Trx" --output-file ${{ env.TEST_REPORT_FILE }} diff --git a/.github/actions/testing/test/action.yml b/.github/actions/testing/test/action.yml new file mode 100644 index 00000000..b4aff310 --- /dev/null +++ b/.github/actions/testing/test/action.yml @@ -0,0 +1,84 @@ +name: 'Test with .NET CLI' +author: 'Pete Sramek' +description: 'Run tests, collects code coverage, logs test results, uploads test artifacts' +inputs: +# Required + project-path: + description: 'Search pattern for test projects.' + required: true +# Optional + dotnet_sdk_version: + description: '.NET SDK version. Default: 10.x' + required: false + default: '10.x' + build-configuration: + description: 'Build configuration. Default: Release' + required: false + default: 'Release' + use-trf-logger: + description: 'Use TRX logger. Default: true' + required: false + default: 'true' + collect-code-coverage: + description: 'Collect code coverage. Default: true' + required: false + default: 'true' + code-coverage-output-format: + description: 'Code coverage output format. Default: cobertura' + required: false + default: 'cobertura' + code-coverage-settings-file: + description: 'Code coverage settings file. Default: ""' + required: false + default: '' + upload-test-artifacts: + description: 'Upload test artifacts, Default: true' + required: false + default: 'true' + test-results-directory: + description: 'Search pattern for test artifacts. Default: "test-results"' + required: false + default: 'test-results' + test-artifacts-name: + description: 'Test artifacts name, Default: "test-results"' + required: false + default: 'test-results' + +runs: + using: composite + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: 'Setup .NET ${{ inputs.dotnet_sdk_version }}' + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ inputs.dotnet_sdk_version }} + + - if: ${{ inputs.use-trf-logger == 'true' }} + name: 'Set TRX_LOGGER_ARGS environment variable' + shell: bash + run: echo "TRX_LOGGER_ARGS=--report-trx" >> $GITHUB_ENV + + - if: ${{ inputs.collect-code-coverage == 'true' }} + name: 'Set CODE_COVERAGE_ARGS environment variable' + shell: bash + run: | + echo "CODE_COVERAGE_ARGS=--coverage --coverage-output-format ${{ inputs.code-coverage-output-format}}" >> $GITHUB_ENV + + - if: ${{ inputs.collect-code-coverage == 'true' && inputs.code-coverage-settings-file != ''}} + name: 'Set CODE_COVERAGE_ARGS environment variable' + shell: bash + run: | + echo "CODE_COVERAGE_ARGS=${{ env.CODE_COVERAGE_ARGS }} --coverage-settings ${{ inputs.code-coverage-settings-file }}" >> $GITHUB_ENV + + - name: 'Test with .NET CLI' + shell: bash + run: dotnet test --project ${{ inputs.project-path }} --configuration ${{ inputs.build-configuration }} ${{ env.CODE_COVERAGE_ARGS }} ${{ env.TRX_LOGGER_ARGS }} --results-directory ${{ inputs.test-results-directory }} + + - name: 'Upload test results' + if: ${{ inputs.upload-test-artifacts == 'true' }} + uses: actions/upload-artifact@v7 + with: + name: '${{ inputs.test-artifacts-name }}' + path: '${{ inputs.test-results-directory }}*' diff --git a/.github/actions/versioning/extract-version/action.yml b/.github/actions/versioning/extract-version/action.yml new file mode 100644 index 00000000..ba604761 --- /dev/null +++ b/.github/actions/versioning/extract-version/action.yml @@ -0,0 +1,51 @@ +name: 'Extract version' +author: 'Pete Sramek' +description: 'Determines versions for the build.' +inputs: +# Required + branch-name: + description: 'Branch name.' + required: true +# Optional + default-version: + description: 'Default version to use if no match is found. Default: ''0.0''' + required: false + default: '0.0' + version-format: + description: 'Version format. Default: ''(\d+).(\d+)''' + required: false + default: '(\d+).(\d+)' + dotnet_sdk_version: + description: '.NET SDK version. Default: ''10.x''' + required: false + default: '10.x' +outputs: + version: + description: 'Version extracted from the branch name.' + value: ${{ steps.resolve-version.outputs.version }} + +runs: + using: "composite" + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: 'Setup .NET ${{ inputs.dotnet_sdk_version }}' + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ inputs.dotnet_sdk_version }} + + - name: 'Extract version from branch name' + id: regex-match + uses: actions-ecosystem/action-regex-match@v2 + with: + text: ${{ inputs.branch-name }} + regex: ${{ inputs.version-format }} + flags: 'g' + + - name: 'Resolve version' + id: resolve-version + shell: bash + run: | + VERSION="${{ steps.regex-match.outputs.match }}" + echo "version=${VERSION:-${{ inputs.default-version }}}" >> $GITHUB_OUTPUT diff --git a/.github/actions/versioning/format-version/action.yml b/.github/actions/versioning/format-version/action.yml new file mode 100644 index 00000000..70289ee0 --- /dev/null +++ b/.github/actions/versioning/format-version/action.yml @@ -0,0 +1,66 @@ +name: 'Format version' +author: 'Pete Sramek' +description: 'Formats versions.' +inputs: +# Required + version: + description: 'Version to use.' + required: true + patch: + description: 'Patch version to append to the formatted version.' + required: true + build-number: + description: 'Build number to append to the formatted version.' + required: true + sha: + description: 'Commit SHA to append to the formatted version.' + required: true + pre-release-tag: + description: 'Pre-release tag to append to the formatted version.' + required: true +# Optional + dotnet_sdk_version: + description: '.NET SDK version. Default: ''10.x''' + required: false + default: '10.x' + +outputs: + friendly-version: + description: 'Formatted friendly version.' + value: ${{ steps.format-version.outputs.friendly-version }} + assembly-version: + description: 'Formatted assembly version.' + value: ${{ steps.format-version.outputs.assembly-version }} + assembly-informational-version: + description: 'Formatted assembly informational version.' + value: ${{ steps.format-version.outputs.assembly-informational-version }} + file-version: + description: 'Formatted file version.' + value: ${{ steps.format-version.outputs.file-version }} + release-version: + description: 'Formatted release version.' + value: ${{ steps.format-version.outputs.release-version }} + +runs: + using: "composite" + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: 'Setup .NET ${{ inputs.dotnet_sdk_version }}' + uses: actions/setup-dotnet@v4 + with: + dotnet-version: ${{ inputs.dotnet_sdk_version }} + - name: 'Format version' + shell: bash + id: format-version + run: | + echo "friendly-version=${{ inputs.version }}" >> $GITHUB_OUTPUT + echo "assembly-version=${{ inputs.version }}.${{ inputs.patch }}.${{ inputs.build-number }}" >> $GITHUB_OUTPUT + echo "assembly-informational-version=${{ inputs.version }}.${{ inputs.patch }}+${{ inputs.sha }}" >> $GITHUB_OUTPUT + echo "file-version=${{ inputs.version }}.${{ inputs.patch }}.${{ inputs.build-number }}" >> $GITHUB_OUTPUT + if [ -n "${{ inputs.pre-release-tag }}" ]; then + echo "release-version=${{ inputs.version }}.${{ inputs.patch }}-${{ inputs.pre-release-tag }}.${{ inputs.build-number }}" >> $GITHUB_OUTPUT + else + echo "release-version=${{ inputs.version }}.${{ inputs.patch }}.${{ inputs.build-number }}" >> $GITHUB_OUTPUT + fi diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..02a54502 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +version: 2 +updates: + - package-ecosystem: "dotnet-sdk" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "nuget" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000..047d4afd --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,258 @@ +name: Build + +on: + workflow_dispatch: + push: + branches-ignore: + - 'preview/**' + - 'release/**' + paths: + - 'src/**' + +permissions: + actions: read + pages: write + id-token: write + contents: write + +concurrency: + group: build-${{ github.head_ref || github.ref }} + cancel-in-progress: false + +env: + dotnet-sdk-version: '10.x' + build-configuration: 'Release' + build-platform: 'Any CPU' + git-version: '6.0.x' + test-result-directory: 'test-results' + nuget-packages-directory: 'nuget-packages' + +jobs: + workflow-variables: + name: 'Set workflow variables' + runs-on: ubuntu-latest + + outputs: + is-release: ${{ startsWith(github.ref_name, 'release') }} + is-preview: ${{ startsWith(github.ref_name, 'preview') }} + + steps: + - name: 'Set workflow variables' + id: github + run: | + echo "is-release:${{ startsWith(github.ref_name, 'release') }}" + echo "is-preview:${{ startsWith(github.ref_name, 'preview') }}" + + versioning: + name: 'Extract version' + runs-on: ubuntu-latest + outputs: + friendly-version: ${{ steps.format-version.outputs.friendly-version }} + assembly-version: ${{ steps.format-version.outputs.assembly-version }} + assembly-informational-version: ${{ steps.format-version.outputs.assembly-informational-version }} + file-version: ${{ steps.format-version.outputs.file-version }} + release-version: ${{ steps.format-version.outputs.release-version }} + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + - name: 'Setup .NET ${{ env.dotnet-sdk-version }}' + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ env.dotnet-sdk-version }} + - name: 'Extract version from branch name' + id: extract-version + uses: './.github/actions/versioning/extract-version' + with: + branch-name: ${{ github.ref_name }} + - name: 'Create build number' + shell: bash + id: create-build-number + run: | + git fetch --unshallow --filter=tree:0 + build_number=$(git rev-list --count origin/${{ github.ref_name }} ^origin/main) + echo "build-number=$build_number" >> $GITHUB_OUTPUT + - name: 'Create pre-release tag' + shell: bash + id: create-pre-release-tag + env: + build-number: ${{ steps.create-build-number.outputs.build-number }} + run: | + if [[ '${{ needs.workflow-variables.outputs.is-release }}' == 'true' ]]; then + echo "pre-release-tag=" >> $GITHUB_OUTPUT + elif [[ '${{ needs.workflow-variables.outputs.is-preview }}' == 'true' ]]; then + pre_release_tag='preview' + echo "pre-release-tag=$pre_release_tag" >> $GITHUB_OUTPUT + else + pre_release_tag=$(echo ${{ github.ref_name }} | tr '/' '-' | tr '.' '-'| tr '_' '-') + echo "pre-release-tag=$pre_release_tag" >> $GITHUB_OUTPUT + fi + - name: 'Format version' + id: format-version + uses: ./.github/actions/versioning/format-version + with: + version: ${{ steps.extract-version.outputs.version }} + patch: ${{ github.run_number }} + build-number: ${{ steps.create-build-number.outputs.build-number }} + sha: ${{ github.sha }} + pre-release-tag: ${{ steps.create-pre-release-tag.outputs.pre-release-tag }} + + format: + name: 'Format source code' + runs-on: ubuntu-latest + + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: 'Format source code' + uses: ./.github/actions/source/format + with: + project-path: '**/PolylineAlgorithm.csproj' + + build: + name: 'Compile source code' + needs: [workflow-variables, versioning, format] + runs-on: ubuntu-latest + + env: + assembly-version: ${{ needs.versioning.outputs.assembly-version }} + assembly-informational-version: ${{ needs.versioning.outputs.assembly-informational-version }} + file-version: ${{ needs.versioning.outputs.file-version }} + + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: 'Compile source code' + uses: ./.github/actions/source/compile + with: + project-path: '**/PolylineAlgorithm.csproj' + assembly-version: ${{ env.assembly-version }} + assembly-informational-version: ${{ env.assembly-informational-version }} + file-version: ${{ env.file-version }} + treat-warnins-as-error: ${{ needs.workflow-variables.outputs.is-release }} + + test: + name: 'Run tests' + needs: [build] + runs-on: ubuntu-latest + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: 'Setup .NET' + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ env.dotnet-sdk-version }} + + - name: 'Run tests' + uses: ./.github/actions/testing/test + with: + project-path: './tests/PolylineAlgorithm.Tests/PolylineAlgorithm.Tests.csproj' + test-results-directory: '${{ runner.temp }}/${{ env.test-result-directory }}/' + code-coverage-settings-file: '${{ github.workspace}}/code-coverage-settings.xml' + + - name: 'Generate test report' + uses: ./.github/actions/testing/test-report + id: test-report + with: + test-result-folder: '${{ runner.temp }}/${{ env.test-result-directory }}/' + + - name: Write test report summary + run: cat ${{ steps.test-report.outputs.test-report-file }} >> $GITHUB_STEP_SUMMARY + + - name: 'Generate code coverage' + uses: ./.github/actions/testing/code-coverage + id: code-coverage-report + with: + test-result-folder: '${{ runner.temp }}/${{ env.test-result-directory }}/' + + - name: Write code coverage report summary + run: cat ${{ steps.code-coverage-report.outputs.code-coverage-report-file }} >> $GITHUB_STEP_SUMMARY + + pack: + name: 'Package binaries' + needs: [versioning, build] + runs-on: ubuntu-latest + env: + assembly-version: ${{ needs.versioning.outputs.assembly-version }} + assembly-informational-version: ${{ needs.versioning.outputs.assembly-informational-version }} + file-version: ${{ needs.versioning.outputs.file-version }} + release-version: ${{ needs.versioning.outputs.release-version }} + package-artifact-name: package + outputs: + package-artifact-name: ${{ env.package-artifact-name }} + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ env.dotnet-sdk-version }} + + - name: Download Build + uses: actions/download-artifact@v8 + with: + name: build + + - name: Pack with .NET + run: | + dotnet pack ${{ vars.SRC_DEFAULT_GLOB_PATTERN }} --configuration ${{ env.build-configuration }} /p:Platform="${{ env.build-platform }}" /p:PackageVersion=${{ env.release-version }} /p:Version=${{ env.assembly-version }} /p:AssemblyInformationalVersion=${{ env.assembly-informational-version }} /p:FileVersion=${{ env.file-version }} --output ${{ runner.temp }}/${{ env.nuget-packages-directory }} + + - name: Upload Package + uses: actions/upload-artifact@v7 + with: + name: ${{ env.package-artifact-name }} + path: | + ${{ runner.temp }}/${{ env.nuget-packages-directory }}/**/*.nupkg + ${{ runner.temp }}/${{ env.nuget-packages-directory }}/**/*.snupkg + + publish-package: + name: 'Publish development package' + needs: [pack] + env: + package-artifact-name: ${{ needs.pack.outputs.package-artifact-name }} + runs-on: ubuntu-latest + environment: 'Development' + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ env.dotnet-sdk-version }} + + - name: 'Publish package to Azure Artifact feed' + uses: ./.github/actions/nuget/publish-package + with: + package-artifact-name: ${{ env.package-artifact-name }} + nuget-feed-url: ${{ vars.NUGET_PACKAGE_FEED_URL }} + nuget-feed-api-key: ${{ secrets.NUGET_PACKAGE_FEED_API_KEY }} + nuget-feed-server: 'AzureArtifacts' + working-directory: ${{ runner.temp }}/${{ env.nuget-packages-directory }} + dotnet-sdk-version: ${{ env.dotnet-sdk-version }}' + + generate-assembly-metadata: + name: 'Generate assembly metadata' + needs: [versioning, build] + env: + friendly-version: ${{ needs.versioning.outputs.friendly-version }} + runs-on: ubuntu-latest + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + - name: 'Generate assembly metadata' + uses: ./.github/actions/documentation/docfx-metadata + with: + artifact-name: 'assembly-metadata' + docfx-json-manifest: './api-reference/assembly-metadata.json' + temporary-directory: './api-reference/temp' + output-directory: './api-reference/${{ env.friendly-version }}' + - name: 'Push assembly metadata artifact' + uses: ./.github/actions/git/push-changes + with: + artifact-name: 'assembly-metadata' + commit-message: 'Updated docs for version ${{ env.friendly-version }}' + working-directory: './api-reference/${{ env.friendly-version }}' diff --git a/.github/workflows/bump-version.yml b/.github/workflows/bump-version.yml new file mode 100644 index 00000000..6f79680c --- /dev/null +++ b/.github/workflows/bump-version.yml @@ -0,0 +1,156 @@ +name: 'Bump version' + +on: + workflow_dispatch: + inputs: + bump-type: + type: choice + options: + - 'minor' + - 'major' + description: 'Version bump type. Use ''minor'' to add features (keeps API files), ''major'' for breaking changes (resets API files).' + required: true + +permissions: + actions: read + contents: write + pull-requests: write + +concurrency: + group: bump-version + cancel-in-progress: false + +env: + dotnet-sdk-version: '10.x' + +jobs: + detect-version: + name: 'Detect current version and calculate next' + runs-on: ubuntu-latest + outputs: + current-version: ${{ steps.detect.outputs.current-version }} + next-version: ${{ steps.calculate.outputs.next-version }} + target-branch: ${{ steps.calculate.outputs.target-branch }} + steps: + - name: 'Checkout main' + uses: actions/checkout@v6 + with: + ref: main + fetch-depth: 0 + + - name: 'Detect highest release branch' + id: detect + run: | + git fetch origin + latest=$(git ls-remote --heads origin 'release/*' | grep -oP 'release/\K\d+\.\d+' | sort -V | tail -1) + if [[ -z "$latest" ]]; then + latest="0.0" + fi + echo "Detected current version: $latest" + echo "current-version=$latest" >> $GITHUB_OUTPUT + + - name: 'Calculate next version' + id: calculate + run: | + current="${{ steps.detect.outputs.current-version }}" + major="${current%%.*}" + minor="${current##*.}" + if [[ "${{ inputs.bump-type }}" == "major" ]]; then + next_major=$((major + 1)) + next_version="${next_major}.0" + else + next_minor=$((minor + 1)) + next_version="${major}.${next_minor}" + fi + echo "Next version: $next_version" + echo "next-version=$next_version" >> $GITHUB_OUTPUT + echo "target-branch=develop/${next_version}" >> $GITHUB_OUTPUT + + validate: + name: 'Validate bump' + needs: [detect-version] + runs-on: ubuntu-latest + steps: + - name: 'Checkout main' + uses: actions/checkout@v6 + with: + ref: main + fetch-depth: 1 + + - name: 'Validate workflow is running from main' + if: ${{ github.ref_name != 'main' }} + run: | + echo "This workflow must be run from the 'main' branch. Current branch: '${{ github.ref_name }}'." + exit 1 + + - name: 'Validate target branch does not exist' + run: | + set +e + git ls-remote --exit-code --heads origin "${{ needs.detect-version.outputs.target-branch }}" + if [[ $? -eq 0 ]]; then + echo "Target branch '${{ needs.detect-version.outputs.target-branch }}' already exists." + exit 1 + fi + set -e + + create-branch: + name: 'Create ${{ needs.detect-version.outputs.target-branch }}' + needs: [detect-version, validate] + runs-on: ubuntu-latest + env: + next-version: ${{ needs.detect-version.outputs.next-version }} + target-branch: ${{ needs.detect-version.outputs.target-branch }} + steps: + - name: 'Checkout main' + uses: actions/checkout@v6 + with: + ref: main + fetch-depth: 0 + + - name: 'Setup .NET ${{ env.dotnet-sdk-version }}' + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ env.dotnet-sdk-version }} + + - name: 'Configure git' + run: | + git config user.name "$(git log -n 1 --pretty=format:%an)" + git config user.email "$(git log -n 1 --pretty=format:%ae)" + + - name: 'Create develop branch from main' + run: | + git checkout -b ${{ env.target-branch }} + + - name: 'Reset PublicAPI files for major bump' + if: ${{ inputs.bump-type == 'major' }} + run: | + find . \( -name "PublicAPI.Shipped.txt" -o -name "PublicAPI.Unshipped.txt" \) | while read file; do + printf '\xef\xbb\xbf#nullable enable\n' > "$file" + echo "Reset: $file" + done + + - name: 'Commit API file reset' + if: ${{ inputs.bump-type == 'major' }} + run: | + git add . + git diff --staged --quiet || git commit -m "Reset PublicAPI files for major version ${{ env.next-version }}" + + - name: 'Push develop branch' + run: | + git push --set-upstream origin ${{ env.target-branch }} + + - name: 'Write summary' + run: | + echo "## ✅ Branch created: \`${{ env.target-branch }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "| | |" >> $GITHUB_STEP_SUMMARY + echo "|---|---|" >> $GITHUB_STEP_SUMMARY + echo "| **Bump type** | ${{ inputs.bump-type }} |" >> $GITHUB_STEP_SUMMARY + echo "| **Previous version** | ${{ needs.detect-version.outputs.current-version }} |" >> $GITHUB_STEP_SUMMARY + echo "| **New version** | ${{ env.next-version }} |" >> $GITHUB_STEP_SUMMARY + echo "| **Target branch** | \`${{ env.target-branch }}\` |" >> $GITHUB_STEP_SUMMARY + if [[ "${{ inputs.bump-type }}" == "major" ]]; then + echo "| **API files** | Reset (major bump) |" >> $GITHUB_STEP_SUMMARY + else + echo "| **API files** | Preserved (minor bump) |" >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 1b11d4ec..00000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,97 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL Advanced" - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - schedule: - - cron: '29 17 * * 3' - -jobs: - analyze: - name: Analyze (${{ matrix.language }}) - # Runner size impacts CodeQL analysis time. To learn more, please see: - # - https://gh.io/recommended-hardware-resources-for-running-codeql - # - https://gh.io/supported-runners-and-hardware-resources - # - https://gh.io/using-larger-runners (GitHub.com only) - # Consider using larger runners or machines with greater resources for possible analysis time improvements. - runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} - permissions: - # required for all workflows - security-events: write - - # required to fetch internal or private CodeQL packs - packages: read - - # only required for workflows in private repositories - actions: read - contents: read - - strategy: - fail-fast: false - matrix: - include: - - language: csharp - build-mode: none - # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' - # Use `c-cpp` to analyze code written in C, C++ or both - # Use 'java-kotlin' to analyze code written in Java, Kotlin or both - # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both - # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, - # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. - # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how - # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Setup .NET - uses: actions/setup-dotnet@v3 - with: - dotnet-version: 9.0.x - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - build-mode: ${{ matrix.build-mode }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality - - # If the analyze step fails for one of the languages you are analyzing with - # "We were unable to automatically build your code", modify the matrix above - # to set the build mode to "manual" for that language. Then modify this step - # to build your code. - # ℹ️ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - - if: matrix.build-mode == 'manual' - shell: bash - run: | - echo 'If you are using a "manual" build mode for one or more of the' \ - 'languages you are analyzing, replace this with the commands to build' \ - 'your code, for example:' - echo ' make bootstrap' - echo ' make release' - exit 1 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: "/language:${{matrix.language}}" diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml deleted file mode 100644 index 0bfc8aa7..00000000 --- a/.github/workflows/dotnet.yml +++ /dev/null @@ -1,28 +0,0 @@ -# This workflow will build a .NET project -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net - -name: .NET - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Setup .NET - uses: actions/setup-dotnet@v3 - with: - dotnet-version: 8.0.x - - name: Restore dependencies - run: dotnet restore - - name: Build - run: dotnet build --no-restore - - name: Test - run: dotnet test --no-build --verbosity normal diff --git a/.github/workflows/promote-branch.yml b/.github/workflows/promote-branch.yml new file mode 100644 index 00000000..8e566ec3 --- /dev/null +++ b/.github/workflows/promote-branch.yml @@ -0,0 +1,227 @@ +name: 'Promote branch' + +on: + workflow_dispatch: + inputs: + promotion-type: + type: choice + options: + - 'preview' + - 'release' + description: 'Promotion type.' + required: true + base-branch: + type: string + description: 'Base branch to create target from.' + default: 'main' + required: true + +permissions: + actions: read + id-token: write + contents: write + pull-requests: write + +concurrency: + group: 'promote-branch-${{ inputs.promotion-type }}-${{github.ref_name }}' + cancel-in-progress: false + +env: + dotnet-sdk-version: '10.x' + is-development-branch: ${{ startsWith(github.ref_name, 'develop') }} + is-maintenance-branch: ${{ startsWith(github.ref_name, 'support') }} + is-preview-branch: ${{ startsWith(github.ref_name, 'preview') }} + +jobs: + versioning: + name: 'Extract version' + runs-on: ubuntu-latest + outputs: + friendly-version: ${{ steps.extract-version.outputs.version }} + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + - name: 'Setup .NET ${{ env.dotnet-sdk-version }}' + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ env.dotnet-sdk-version }} + - name: 'Extract version from branch name' + id: extract-version + uses: './.github/actions/versioning/extract-version' + with: + branch-name: ${{ github.ref_name }} + workflow-variables: + name: 'Set workflow variables' + needs: [versioning] + runs-on: ubuntu-latest + outputs: + base-branch: ${{ steps.set-base-branch.outputs.base-branch }} + target-branch: ${{ steps.set-target-branch.outputs.target-branch }} + target-branch-exists: ${{ steps.check-target-branch-exists.outputs.target-branch-exists }} + pull-request-exists: ${{ steps.check-pull-request-exists.outputs.pull-request-exists }} + steps: + - name: 'Set target branch' + id: set-target-branch + run: | + if [[ "${{ inputs.promotion-type }}" == "preview" ]]; then + target_branch="preview/${{ needs.versioning.outputs.friendly-version }}" + elif [[ "${{ inputs.promotion-type }}" == "release" ]]; then + target_branch="release/${{ needs.versioning.outputs.friendly-version }}" + fi + + echo "Setting target branch $target_branch." + + echo "target-branch=$target_branch" >> $GITHUB_OUTPUT + - name: 'Set base branch' + id: set-base-branch + run: | + base_branch=${{ inputs.base-branch }} + + echo "Setting base branch $base_branch." + + echo "base-branch=$base_branch" >> $GITHUB_OUTPUT + - name: 'Check if base branch exists' + id: check-target-branch-exists + env: + target-branch: ${{ steps.set-target-branch.outputs.target-branch }} + run: | + set +e + + git ls-remote --exit-code --heads origin ${{ env.target-branch }} + + if [[ $? -eq 0 ]]; then + echo "Target branch ${{ env.target-branch }} does exist." + target_branch_exists="true" + else + echo "Target branch ${{ env.target-branch }} does not exist." + target_branch_exists="false" + fi + + echo "target-branch-exists=$target_branch_exists" >> $GITHUB_OUTPUT + + set -e + - name: 'Check if base branch exists' + id: check-base-branch-exists + env: + base-branch: ${{ steps.set-base-branch.outputs.base-branch }} + run: | + set +e + + git ls-remote --exit-code --heads origin ${{ env.base-branch }} + + if [[ $? -eq 0 ]]; then + echo "Base branch ${{ env.base-branch }} does exist." + base_branch_exists="true" + else + echo "Base branch ${{ env.base-branch }} does not exist." + base_branch_exists="false" + fi + + echo "base-branch-exists=$base_branch_exists" >> $GITHUB_OUTPUT + + set -e + - name: 'Check if pull request exists exists' + id: check-pull-request-exists + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository_owner }}/${{ github.event.repository.name }} + current-branch: ${{ github.ref_name }} + target-branch: ${{ steps.set-target-branch.outputs.target-branch }} + run: | + pull_request_count=$(gh pr list --head ${{ env.current-branch }} --base ${{ env.target-branch }} --state open --limit 1 --json id --jq '. | length') + + if [[ $pull_request_count -eq 0 ]]; then + echo "Pull request does not exist." + pull_request_exists="false" + else + echo "Pull request does exist." + pull_request_exists="true" + fi + + echo "pull-request-exists=$pull_request_exists" >> $GITHUB_OUTPUT + + validate-promotion: + name: 'Validate promotion' + needs: [ versioning, workflow-variables ] + runs-on: ubuntu-latest + env: + promotion-type: ${{ inputs.promotion-type }} + base-branch: ${{ needs.workflow-variables.outputs.base-branch }} + current-branch: ${{ github.ref_name }} + target-branch: ${{ needs.workflow-variables.outputs.target-branch }} + pull-request-exists: ${{ needs.workflow-variables.outputs.pull-request-exists }} + outputs: + target-branch: ${{ env.target-branch }} + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + - name: 'Check promotion type' + if: ${{ (env.promotion-type != 'release') && (env.promotion-type != 'preview') }} + run: | + echo "Invalid promotion type ${{ inputs.promotion-type }}" + exit 1 + - name: 'Validate source branch for preview promotion' + if: ${{ env.promotion-type == 'preview' && (env.is-development-branch == 'false') && (env.is-maintenance-branch == 'false') }} + run: | + echo "Preview promotion requires a 'develop/**' or 'support/**' source branch. Current branch: '${{ github.ref_name }}'" + exit 1 + - name: 'Validate source branch for release promotion' + if: ${{ env.promotion-type == 'release' && env.is-preview-branch == 'false' }} + run: | + echo "Release promotion requires a 'preview/**' source branch. Current branch: '${{ github.ref_name }}'" + exit 1 + - name: 'Validate default and current branch' + if: ${{ env.base-branch == env.current-branch }} + run: | + echo "Default and current branch cannot be the same." + echo "Default branch is '${{ env.base-branch }}'" + echo "Current branch is '${{ env.current-branch }}'" + exit 1 + + - name: 'Validate target and current branch' + if: ${{ env.target-branch == env.current-branch }} + run: | + echo "Default and target branch cannot be the same." + echo "Default branch is '${{ env.target-branch }}'" + echo "Current branch is '${{ env.current-branch }}'" + exit 1 + + - name: 'Validate pull request' + if: ${{ env.pull-request-exists == 'true' }} + run: | + echo "Pull request exists." + exit 1 + + promote-branch: + name: 'Promote branch ${{ github.ref_name }} to ${{ needs.workflow-variables.outputs.target-branch }}' + needs: [ workflow-variables, validate-promotion ] + runs-on: ubuntu-latest + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + - name: 'Setup .NET ${{ env.dotnet-sdk-version }}' + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ env.dotnet-sdk-version }} + - name: 'Create target branch' + if: ${{ needs.workflow-variables.outputs.target-branch-exists == 'false' }} + env: + base-branch: ${{ needs.workflow-variables.outputs.base-branch }} + target-branch: ${{ needs.workflow-variables.outputs.target-branch }} + run: | + git fetch origin + git push origin origin/${{ env.base-branch }}:refs/heads/${{ env.target-branch }} + - name: 'Lock target branch' + if: ${{ needs.workflow-variables.outputs.target-branch-exists == 'false' }} + uses: './.github/actions/github/branch-protection/lock' + with: + branch: ${{ needs.workflow-variables.outputs.target-branch }} + token: ${{ secrets.GH_ADMIN_TOKEN }} + - name: 'Create PR: "Promote ${{ env.current-branch }} to ${{ env.target-branch }}"' + if: ${{ needs.workflow-variables.outputs.pull-request-exists == 'false' }} + env: + GH_TOKEN: ${{ github.token }} + current-branch: ${{ github.ref_name }} + target-branch: ${{ needs.workflow-variables.outputs.target-branch }} + run: | + gh pr create --title "Promote ${{ env.current-branch }} to ${{ env.target-branch }}" --fill --base ${{ env.target-branch }} --head ${{ env.current-branch }} diff --git a/.github/workflows/publish-documentation.yml b/.github/workflows/publish-documentation.yml new file mode 100644 index 00000000..2a579943 --- /dev/null +++ b/.github/workflows/publish-documentation.yml @@ -0,0 +1,174 @@ +name: 'Publish documentation' + +on: + workflow_dispatch: + push: + branches: + - 'release/**' + paths: + - 'src/**' + - 'api-reference/**' + +permissions: + actions: read + pages: write + id-token: write + +concurrency: + group: publish-docs-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +env: + dotnet-sdk-version: '10.x' + +jobs: + validate-branch: + name: 'Validate branch' + runs-on: ubuntu-latest + steps: + - name: 'Ensure documentation is published from a release branch' + if: ${{ !startsWith(github.ref_name, 'release/') }} + run: | + echo "Documentation should only be published from 'release/**' branches." + echo "Current branch: '${{ github.ref_name }}'" + exit 1 + + workflow-variables: + name: 'Workflow variables' + needs: [validate-branch] + runs-on: ubuntu-latest + + outputs: + github-run-number: ${{ github.run_number }} + + steps: + - name: 'Workflow variables' + id: github + run: | + echo "github-run-number:${{ github.run_number }}" + + versioning: + name: 'Extract version' + needs: [workflow-variables] + runs-on: ubuntu-latest + outputs: + friendly-version: ${{ steps.versioning.outputs.version }} + + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + with: + fetch-depth: 0 # Ensure the full git history is available for versioning + - name: 'Setup .NET ${{ env.dotnet-sdk-version }}' + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ env.dotnet-sdk-version }} + - name: 'Extract version from branch name' + id: versioning + uses: './.github/actions/versioning/extract-version' + with: + branch-name: ${{ github.ref_name }} + + generate-docs: + name: 'Generate documentation' + needs: [workflow-variables, versioning] + runs-on: ubuntu-latest + env: + friendly-version: ${{ needs.versioning.outputs.friendly-version }} + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: 'Setup .NET ${{ env.dotnet-sdk-version }}' + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ env.dotnet-sdk-version }} + + - name: 'Install docfx' + shell: bash + run: dotnet tool update -g docfx + + - name: 'Regenerate API metadata for v${{ env.friendly-version }}' + shell: bash + run: | + rm -rf api-reference/${{ env.friendly-version }} + cd api-reference + docfx metadata assembly-metadata.json + mv temp ${{ env.friendly-version }} + + - name: 'Discover all versions' + id: discover-versions + shell: bash + run: | + versions=$(ls api-reference/ | grep -E '^\d+\.\d+$' | sort -V | paste -sd ',' -) + latest=$(ls api-reference/ | grep -E '^\d+\.\d+$' | sort -V | tail -1) + echo "versions=$versions" >> $GITHUB_OUTPUT + echo "latest=$latest" >> $GITHUB_OUTPUT + + - name: 'Update versions.json' + shell: bash + run: | + versions_array=$(echo "${{ steps.discover-versions.outputs.versions }}" | tr ',' '\n' | jq -R . | jq -s .) + jq --arg latest "${{ steps.discover-versions.outputs.latest }}" \ + --argjson versions "$versions_array" \ + '.latest = $latest | .versions = $versions' \ + api-reference/versions.json > /tmp/versions.json + mv /tmp/versions.json api-reference/versions.json + + - name: 'Update api-reference.json with version groups' + shell: bash + run: | + cp api-reference/api-reference.json /tmp/api-reference.json + for ver in $(echo "${{ steps.discover-versions.outputs.versions }}" | tr ',' ' '); do + jq --arg ver "$ver" --arg group "v${ver}" \ + '.build.content += [{"dest": "", "files": ["*.yml"], "group": $group, "src": $ver, "rootTocPath": "~/toc.html"}] | + .build.groups = (.build.groups // {}) | + .build.groups[$group] = {"dest": $ver}' \ + /tmp/api-reference.json > /tmp/api-reference-new.json + mv /tmp/api-reference-new.json /tmp/api-reference.json + done + mv /tmp/api-reference.json api-reference/api-reference.json + + - name: 'Update toc.yml with Reference dropdown' + shell: bash + run: | + latest="${{ steps.discover-versions.outputs.latest }}" + { + echo "### YamlMime:TableOfContent" + echo "- name: Guide" + echo " href: guide/" + echo "- name: Reference" + echo " dropdown: true" + echo " items:" + for ver in $(echo "${{ steps.discover-versions.outputs.versions }}" | tr ',' '\n' | sort -Vr); do + if [ "$ver" = "$latest" ]; then + echo " - name: v${ver} (latest)" + else + echo " - name: v${ver}" + fi + echo " href: ${ver}/PolylineAlgorithm.html" + done + } > api-reference/toc.yml + + - name: 'Build documentation' + shell: bash + run: docfx build api-reference/api-reference.json + + - name: 'Upload artifact' + uses: actions/upload-pages-artifact@v4 + with: + name: github-pages + path: './api-reference/_docs' + + publish-docs: + name: 'Publish documentation' + needs: [workflow-variables, versioning, generate-docs] + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + + - name: 'Deploy to GitHub Pages' + id: deployment + uses: actions/deploy-pages@v5 diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 00000000..8ea9622c --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,258 @@ +name: 'Pull Request' + +on: + pull_request: + types: + - opened + - synchronize + - reopened + branches: + - 'preview/**' + - 'release/**' + +permissions: + actions: read + pages: write + id-token: write + contents: write + +concurrency: + group: pull-request-${{ github.head_ref || github.ref }} + cancel-in-progress: true + +env: + dotnet-sdk-version: '10.x' + build-configuration: 'Release' + build-platform: 'Any CPU' + git-version: '6.0.x' + test-result-directory: 'test-results' + nuget-packages-directory: 'nuget-packages' + +jobs: + workflow-variables: + name: 'Set workflow variables' + runs-on: ubuntu-latest + + outputs: + is-release: ${{ startsWith(github.base_ref, 'release') }} + is-preview: ${{ startsWith(github.base_ref , 'preview') }} + + steps: + - name: 'Set workflow variables' + run: | + echo "is-release:${{ startsWith(github.base_ref, 'release') }}" + echo "is-preview:${{ startsWith(github.base_ref, 'preview') }}" + + versioning: + name: 'Extract version from branch' + runs-on: ubuntu-latest + needs: [workflow-variables] + outputs: + friendly-version: ${{ steps.format-version.outputs.friendly-version }} + assembly-version: ${{ steps.format-version.outputs.assembly-version }} + assembly-informational-version: ${{ steps.format-version.outputs.assembly-informational-version }} + file-version: ${{ steps.format-version.outputs.file-version }} + release-version: ${{ steps.format-version.outputs.release-version }} + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + - name: 'Setup .NET ${{ env.dotnet-sdk-version }}' + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ env.dotnet-sdk-version }} + - name: 'Extract version from branch name' + id: extract-version + uses: './.github/actions/versioning/extract-version' + with: + branch-name: ${{ github.base_ref }} + - name: 'Create build number' + shell: bash + id: create-build-number + run: | + git fetch --unshallow --filter=tree:0 + build_number=$(git rev-list --count origin/${{ github.base_ref }} ^origin/main) + echo "build-number=$build_number" >> $GITHUB_OUTPUT + - name: 'Create pre-release tag' + shell: bash + id: create-pre-release-tag + env: + build-number: ${{ steps.create-build-number.outputs.build-number }} + run: | + if [[ '${{ needs.workflow-variables.outputs.is-release }}' == 'true' ]]; then + echo "pre-release-tag=" >> $GITHUB_OUTPUT + elif [[ '${{ needs.workflow-variables.outputs.is-preview }}' == 'true' ]]; then + pre_release_tag='preview' + echo "pre-release-tag=$pre_release_tag" >> $GITHUB_OUTPUT + else + pre_release_tag=$(echo ${{ github.base_ref }} | tr '/' '-' | tr '.' '-'| tr '_' '-') + echo "pre-release-tag=$pre_release_tag" >> $GITHUB_OUTPUT + fi + - name: 'Format version' + id: format-version + uses: ./.github/actions/versioning/format-version + with: + version: ${{ steps.extract-version.outputs.version }} + patch: ${{ github.run_number }} + build-number: ${{ steps.create-build-number.outputs.build-number }} + sha: ${{ github.sha }} + pre-release-tag: ${{ steps.create-pre-release-tag.outputs.pre-release-tag }} + + build: + name: 'Compile source code' + needs: [workflow-variables, versioning] + runs-on: ubuntu-latest + + env: + assembly-version: ${{ needs.versioning.outputs.assembly-version }} + assembly-informational-version: ${{ needs.versioning.outputs.assembly-informational-version }} + file-version: ${{ needs.versioning.outputs.file-version }} + + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: 'Compile source code' + uses: ./.github/actions/source/compile + with: + project-path: '**/PolylineAlgorithm.csproj' + assembly-version: ${{ env.assembly-version }} + assembly-informational-version: ${{ env.assembly-informational-version }} + file-version: ${{ env.file-version }} + treat-warnins-as-error: ${{ needs.workflow-variables.outputs.is-release }} + + test: + name: 'Run tests' + needs: [build] + runs-on: ubuntu-latest + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: 'Setup .NET' + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ env.dotnet-sdk-version }} + + - name: 'Run tests' + uses: ./.github/actions/testing/test + with: + project-path: './tests/PolylineAlgorithm.Tests/PolylineAlgorithm.Tests.csproj' + test-results-directory: '${{ runner.temp }}/${{ env.test-result-directory }}/' + code-coverage-settings-file: '${{ github.workspace}}/code-coverage-settings.xml' + + - name: 'Generate test report' + uses: ./.github/actions/testing/test-report + id: test-report + with: + test-result-folder: '${{ runner.temp }}/${{ env.test-result-directory }}/' + + - name: Write test report summary + run: cat ${{ steps.test-report.outputs.test-report-file }} >> $GITHUB_STEP_SUMMARY + + - name: 'Generate code coverage' + uses: ./.github/actions/testing/code-coverage + id: code-coverage-report + with: + test-result-folder: '${{ runner.temp }}/${{ env.test-result-directory }}/' + + - name: Write code coverage report summary + run: cat ${{ steps.code-coverage-report.outputs.code-coverage-report-file }} >> $GITHUB_STEP_SUMMARY + + pack: + name: 'Package binaries' + needs: [versioning, build] + runs-on: ubuntu-latest + env: + assembly-version: ${{ needs.versioning.outputs.assembly-version }} + assembly-informational-version: ${{ needs.versioning.outputs.assembly-informational-version }} + file-version: ${{ needs.versioning.outputs.file-version }} + release-version: ${{ needs.versioning.outputs.release-version }} + package-artifact-name: package + outputs: + package-artifact-name: ${{ env.package-artifact-name }} + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ env.dotnet-sdk-version }} + + - name: Download Build + uses: actions/download-artifact@v8 + with: + name: build + + - name: Pack with .NET + run: | + dotnet pack ${{ vars.SRC_DEFAULT_GLOB_PATTERN }} --configuration ${{ env.build-configuration }} /p:Platform="${{ env.build-platform }}" /p:PackageVersion=${{ env.release-version }} /p:Version=${{ env.assembly-version }} /p:AssemblyInformationalVersion=${{ env.assembly-informational-version }} /p:FileVersion=${{ env.file-version }} --output ${{ runner.temp }}/${{ env.nuget-packages-directory }} + + - name: Upload Package + uses: actions/upload-artifact@v7 + with: + name: ${{ env.package-artifact-name }} + path: | + ${{ runner.temp }}/${{ env.nuget-packages-directory }}/**/*.nupkg + ${{ runner.temp }}/${{ env.nuget-packages-directory }}/**/*.snupkg + + publish-development-package: + name: 'Publish development package' + needs: [pack] + env: + package-artifact-name: ${{ needs.pack.outputs.package-artifact-name }} + runs-on: ubuntu-latest + environment: 'Development' + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ env.dotnet-sdk-version }} + + - name: 'Publish package to Azure Artifact feed' + uses: ./.github/actions/nuget/publish-package + with: + package-artifact-name: ${{ env.package-artifact-name }} + nuget-feed-url: ${{ vars.NUGET_PACKAGE_FEED_URL }} + nuget-feed-api-key: ${{ secrets.NUGET_PACKAGE_FEED_API_KEY }} + nuget-feed-server: 'AzureArtifacts' + working-directory: ${{ runner.temp }}/${{ env.nuget-packages-directory }} + dotnet-sdk-version: ${{ env.dotnet-sdk-version }}' + + benchmark: + if: ${{ github.env.is_release || vars.BENCHMARKDOTNET_RUN_OVERRIDE == 'true' }} + name: Benchmark with .NET CLI on ${{ matrix.os }} + needs: [build] + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + - name: Install .NET SDK + uses: actions/setup-dotnet@v5 + with: + dotnet-version: | + 8.x + 10.x + - name: Download Build + uses: actions/download-artifact@v8 + with: + name: build + - name: Benchmark + working-directory: ${{ vars.BENCHMARKDOTNET_WORKING_DIRECTORY }} + run: dotnet run --configuration ${{ env.build-configuration }} /p:Platform=${{ env.build-platform }} --framework ${{ vars.DEFAULT_BUILD_FRAMEWORK }} --runtimes ${{ vars.BENCHMARKDOTNET_RUNTIMES }} --filter ${{ vars.BENCHMARKDOTNET_FILTER }} --artifacts ${{ runner.temp }}/benchmarks/ --exporters GitHub --memory --iterationTime 100 --join + - name: Upload Benchmark Results + uses: actions/upload-artifact@v7 + with: + name: benchmark-${{ matrix.os }} + path: | + ${{ runner.temp }}/benchmarks/**/*-report-github.md + - name: Write Benchmark Summary + shell: bash + run: cat **/*-report-github.md > $GITHUB_STEP_SUMMARY + working-directory: ${{ runner.temp }}/benchmarks/ diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..b8113bf4 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,368 @@ +name: 'Release' + +on: + push: + branches: + - 'preview/**' + - 'release/**' + paths: + - 'src/**' + +permissions: + actions: read + pages: write + id-token: write + contents: write + +concurrency: + group: release-${{ github.head_ref || github.ref }} + cancel-in-progress: false + +env: + dotnet-sdk-version: '10.x' + build-configuration: 'Release' + build-platform: 'Any CPU' + git-version: '6.0.x' + test-result-directory: 'test-results' + nuget-packages-directory: 'nuget-packages' + +jobs: + workflow-variables: + name: 'Set workflow variables' + runs-on: ubuntu-latest + + outputs: + is-release: ${{ startsWith(github.ref_name, 'release') }} + is-preview: ${{ startsWith(github.ref_name, 'preview') }} + + steps: + - name: 'Set workflow variables' + id: github + run: | + echo "is-release:${{ startsWith(github.ref_name, 'release') }}" + echo "is-preview:${{ startsWith(github.ref_name, 'preview') }}" + + validate-release: + name: 'Validate release' + needs: [workflow-variables] + runs-on: ubuntu-latest + steps: + - name: 'Validate release branch' + if: ${{ needs.workflow-variables.outputs.is-release != 'true' && needs.workflow-variables.outputs.is-preview != 'true' }} + run: | + echo "This workflow can only be run on 'release/**' or 'preview/**' branches." + exit 1 + + versioning: + name: 'Extract version from branch' + runs-on: ubuntu-latest + needs: [workflow-variables, validate-release] + outputs: + friendly-version: ${{ steps.format-version.outputs.friendly-version }} + assembly-version: ${{ steps.format-version.outputs.assembly-version }} + assembly-informational-version: ${{ steps.format-version.outputs.assembly-informational-version }} + file-version: ${{ steps.format-version.outputs.file-version }} + release-version: ${{ steps.format-version.outputs.release-version }} + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + - name: 'Setup .NET ${{ env.dotnet-sdk-version }}' + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ env.dotnet-sdk-version }} + - name: 'Extract version from branch name' + id: extract-version + uses: './.github/actions/versioning/extract-version' + with: + branch-name: ${{ github.ref_name }} + - name: 'Create build number' + shell: bash + id: create-build-number + run: | + git fetch --unshallow --filter=tree:0 + build_number=$(git rev-list --count origin/${{ github.ref_name }} ^origin/main) + echo "build-number=$build_number" >> $GITHUB_OUTPUT + - name: 'Create pre-release tag' + shell: bash + id: create-pre-release-tag + env: + build-number: ${{ steps.create-build-number.outputs.build-number }} + run: | + if [[ '${{ needs.workflow-variables.outputs.is-release }}' == 'true' ]]; then + echo "pre-release-tag=" >> $GITHUB_OUTPUT + elif [[ '${{ needs.workflow-variables.outputs.is-preview }}' == 'true' ]]; then + pre_release_tag='preview' + echo "pre-release-tag=$pre_release_tag" >> $GITHUB_OUTPUT + else + pre_release_tag=$(echo ${{ github.ref_name }} | tr '/' '-' | tr '.' '-'| tr '_' '-') + echo "pre-release-tag=$pre_release_tag" >> $GITHUB_OUTPUT + fi + - name: 'Format version' + id: format-version + uses: ./.github/actions/versioning/format-version + with: + version: ${{ steps.extract-version.outputs.version }} + patch: ${{ github.run_number }} + build-number: ${{ steps.create-build-number.outputs.build-number }} + sha: ${{ github.sha }} + pre-release-tag: ${{ steps.create-pre-release-tag.outputs.pre-release-tag }} + + build: + name: 'Compile source code' + needs: [workflow-variables, versioning, validate-release] + runs-on: ubuntu-latest + + env: + assembly-version: ${{ needs.versioning.outputs.assembly-version }} + assembly-informational-version: ${{ needs.versioning.outputs.assembly-informational-version }} + file-version: ${{ needs.versioning.outputs.file-version }} + + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: 'Compile source code' + uses: ./.github/actions/source/compile + with: + project-path: '**/PolylineAlgorithm.csproj' + assembly-version: ${{ env.assembly-version }} + assembly-informational-version: ${{ env.assembly-informational-version }} + file-version: ${{ env.file-version }} + treat-warnins-as-error: ${{ needs.workflow-variables.outputs.is-release }} + + test: + name: 'Run tests' + needs: [build, validate-release] + runs-on: ubuntu-latest + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: 'Setup .NET' + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ env.dotnet-sdk-version }} + + - name: 'Run tests' + uses: ./.github/actions/testing/test + with: + project-path: './tests/PolylineAlgorithm.Tests/PolylineAlgorithm.Tests.csproj' + test-results-directory: '${{ runner.temp }}/${{ env.test-result-directory }}/' + code-coverage-settings-file: '${{ github.workspace}}/code-coverage-settings.xml' + + - name: 'Generate test report' + uses: ./.github/actions/testing/test-report + id: test-report + with: + test-result-folder: '${{ runner.temp }}/${{ env.test-result-directory }}/' + + - name: Write test report summary + run: cat ${{ steps.test-report.outputs.test-report-file }} >> $GITHUB_STEP_SUMMARY + + - name: 'Generate code coverage' + uses: ./.github/actions/testing/code-coverage + id: code-coverage-report + with: + test-result-folder: '${{ runner.temp }}/${{ env.test-result-directory }}/' + + - name: Write code coverage report summary + run: cat ${{ steps.code-coverage-report.outputs.code-coverage-report-file }} >> $GITHUB_STEP_SUMMARY + + pack: + name: 'Package binaries' + needs: [versioning, build, test, validate-release] + runs-on: ubuntu-latest + env: + assembly-version: ${{ needs.versioning.outputs.assembly-version }} + assembly-informational-version: ${{ needs.versioning.outputs.assembly-informational-version }} + file-version: ${{ needs.versioning.outputs.file-version }} + release-version: ${{ needs.versioning.outputs.release-version }} + package-artifact-name: package + outputs: + package-artifact-name: ${{ env.package-artifact-name }} + steps: + - name: 'Checkout ${{ github.base_ref }}' + uses: actions/checkout@v6 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ env.dotnet-sdk-version }} + + - name: Download Build + uses: actions/download-artifact@v8 + with: + name: build + + - name: Pack with .NET + run: | + dotnet pack ${{ vars.SRC_DEFAULT_GLOB_PATTERN }} --configuration ${{ env.build-configuration }} /p:Platform="${{ env.build-platform }}" /p:PackageVersion=${{ env.release-version }} /p:Version=${{ env.assembly-version }} /p:AssemblyInformationalVersion=${{ env.assembly-informational-version }} /p:FileVersion=${{ env.file-version }} --output ${{ runner.temp }}/${{ env.nuget-packages-directory }} + + - name: Upload Package + uses: actions/upload-artifact@v7 + with: + name: ${{ env.package-artifact-name }} + path: | + ${{ runner.temp }}/${{ env.nuget-packages-directory }}/**/*.nupkg + ${{ runner.temp }}/${{ env.nuget-packages-directory }}/**/*.snupkg + + publish-package: + name: 'Publish package' + needs: [pack, validate-release] + env: + package-artifact-name: ${{ needs.pack.outputs.package-artifact-name }} + runs-on: ubuntu-latest + environment: 'NuGet' + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: ${{ env.dotnet-sdk-version }} + + - name: 'Publish package to Azure Artifact feed' + uses: ./.github/actions/nuget/publish-package + with: + package-artifact-name: ${{ env.package-artifact-name }} + nuget-feed-url: ${{ vars.NUGET_PACKAGE_FEED_URL }} + nuget-feed-api-key: ${{ secrets.NUGET_PACKAGE_FEED_API_KEY }} + nuget-feed-server: 'NuGet' + working-directory: ${{ runner.temp }}/${{ env.nuget-packages-directory }} + dotnet-sdk-version: ${{ env.dotnet-sdk-version }}' + + release: + name: 'Create release' + needs: [workflow-variables, publish-package, validate-release, versioning] + runs-on: ubuntu-latest + env: + release-version: ${{ needs.versioning.outputs.release-version }} + is-preview: ${{ needs.workflow-variables.outputs.is-preview }} + notes-start-tag: ${{ needs.workflow-variables.outputs.notes-start-tag }} + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + + - name: 'Determine notes start tag' + id: determine-notes-start-tag + run: | + notes-start-tag=$(git describe --abbrev=0 --tags) + echo "notes-start-tag=$notes-start-tag" >> $GITHUB_OUTPUT + shell: bash + + - name: 'Create GitHub Release' + uses: ./.github/actions/github/create-release + with: + release-version: ${{ env.release-version }} + is-preview: ${{ env.is-preview }} + notes-start-tag: ${{ steps.determine-notes-start-tag.outputs.notes-start-tag }} + + merge-to-main: + name: 'Merge ${{ github.ref_name }} into main' + needs: [workflow-variables, release, versioning] + if: ${{ needs.workflow-variables.outputs.is-release == 'true' }} + runs-on: ubuntu-latest + env: + GH_TOKEN: ${{ github.token }} + current-branch: ${{ github.ref_name }} + current-version: ${{ needs.versioning.outputs.friendly-version }} + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: 'Detect if current branch is the latest release' + id: detect-latest + run: | + git fetch origin + latest_version=$(git ls-remote --heads origin 'release/*' | grep -oP 'release/\K\d+\.\d+' | sort -V | tail -1) + current_version=$(echo "${{ env.current-version }}" | grep -oP '^\d+\.\d+') + echo "Latest release branch version: $latest_version" + echo "Current version (normalized): $current_version" + if [[ "$latest_version" == "$current_version" ]]; then + echo "is-latest=true" >> $GITHUB_OUTPUT + else + echo "is-latest=false" >> $GITHUB_OUTPUT + fi + + - name: 'Check if PR to main already exists' + id: check-pr + if: ${{ steps.detect-latest.outputs.is-latest == 'true' }} + run: | + pr_count=$(gh pr list --head "${{ env.current-branch }}" --base main --state open --limit 1 --json id --jq '. | length') + if [[ $pr_count -gt 0 ]]; then + echo "pr-exists=true" >> $GITHUB_OUTPUT + else + echo "pr-exists=false" >> $GITHUB_OUTPUT + fi + + - name: 'Create PR: Merge ${{ env.current-branch }} into main' + if: ${{ steps.detect-latest.outputs.is-latest == 'true' && steps.check-pr.outputs.pr-exists == 'false' }} + run: | + gh pr create \ + --title "Merge ${{ env.current-branch }} into main" \ + --fill \ + --base main \ + --head "${{ env.current-branch }}" + + - name: 'Write merge summary' + run: | + if [[ "${{ steps.detect-latest.outputs.is-latest }}" == "true" ]]; then + echo "✅ PR created to merge **${{ env.current-branch }}** into **main**." >> $GITHUB_STEP_SUMMARY + else + echo "⏭️ Skipped merge to main: **${{ env.current-branch }}** is not the highest release branch." >> $GITHUB_STEP_SUMMARY + fi + + create-support-branch: + name: 'Create support branch for ${{ github.ref_name }}' + needs: [workflow-variables, release, versioning] + if: ${{ needs.workflow-variables.outputs.is-release == 'true' }} + runs-on: ubuntu-latest + env: + current-version: ${{ needs.versioning.outputs.friendly-version }} + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: 'Resolve support branch name' + id: resolve-support-branch + run: | + major_minor=$(echo "${{ env.current-version }}" | grep -oP '^\d+\.\d+') + echo "support-branch=support/$major_minor" >> $GITHUB_OUTPUT + + - name: 'Check if support branch already exists' + id: check-support-branch + run: | + git fetch origin + if git ls-remote --exit-code --heads origin "${{ steps.resolve-support-branch.outputs.support-branch }}" > /dev/null 2>&1; then + echo "support-branch-exists=true" >> $GITHUB_OUTPUT + else + echo "support-branch-exists=false" >> $GITHUB_OUTPUT + fi + + - name: 'Create support branch' + if: ${{ steps.check-support-branch.outputs.support-branch-exists == 'false' }} + run: | + git config user.name "$(git log -n 1 --pretty=format:%an)" + git config user.email "$(git log -n 1 --pretty=format:%ae)" + git checkout -b "${{ steps.resolve-support-branch.outputs.support-branch }}" + git push --set-upstream origin "${{ steps.resolve-support-branch.outputs.support-branch }}" + + - name: 'Lock support branch' + if: ${{ steps.check-support-branch.outputs.support-branch-exists == 'false' }} + uses: './.github/actions/github/branch-protection/lock' + with: + branch: ${{ steps.resolve-support-branch.outputs.support-branch }} + token: ${{ secrets.GH_ADMIN_TOKEN }} + + - name: 'Write support branch summary' + run: | + if [[ "${{ steps.check-support-branch.outputs.support-branch-exists }}" == "false" ]]; then + echo "✅ Created and locked support branch **${{ steps.resolve-support-branch.outputs.support-branch }}**." >> $GITHUB_STEP_SUMMARY + else + echo "⏭️ Support branch **${{ steps.resolve-support-branch.outputs.support-branch }}** already exists." >> $GITHUB_STEP_SUMMARY + fi diff --git a/.github/workflows/static.yml b/.github/workflows/static.yml deleted file mode 100644 index 1526b959..00000000 --- a/.github/workflows/static.yml +++ /dev/null @@ -1,44 +0,0 @@ -# Your GitHub workflow file under .github/workflows/ -# Trigger the action on push to main -on: - push: - branches: - - main - -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - actions: read - pages: write - id-token: write - -# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. -# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. -concurrency: - group: "pages" - cancel-in-progress: false - -jobs: - publish-docs: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Dotnet Setup - uses: actions/setup-dotnet@v3 - with: - dotnet-version: 8.x - - - run: dotnet tool update -g docfx - - run: docfx ./docs/docfx.json - - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - # Upload entire repository - path: './docs/_site' - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.gitignore b/.gitignore index 3c4efe20..2118d126 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -## Ignore Visual Studio temporary files, build results, and +## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files @@ -258,4 +258,13 @@ paket-files/ # Python Tools for Visual Studio (PTVS) __pycache__/ -*.pyc \ No newline at end of file +*.pyc + +# BenchmarkDotNet artifacts +**/BenchmarkDotNet.Artifacts/ + +# GitHub Copilot Testing folder +/.codetesting + +# DocFX build output +api-reference/_docs/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..cb8bb32a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,93 @@ +# Polyline Algorithm Agents Instructions + +## Purpose + +Instructions for automated agents (bots, CI, and code review tools) and contributors interacting with the Polyline Algorithm library. + +--- + +## General Guidelines + +- All contributions and automation **must adhere to code style** (`.editorconfig`, `dotnet format`). +- **Unit tests** are required for new features and bug fixes (`tests/` directory). +- **Benchmarks** must be updated for performance-impacting changes (`benchmarks/` directory). + +--- + +## Pull Requests + +Agents and contributors should: + +- **Attach benchmark results** for encoding/decoding performance changes +- Document **public API changes** in XML comments and verify updates at [API Reference](https://petesramek.github.io/polyline-algorithm-csharp/) +- Run format and static analysis tools before submitting (`dotnet format`, analyzers) +- Update **README.md** and `/samples` for public API changes + +--- + +## Error Handling and Logging + +- Throw **descriptive exceptions** for invalid input/edge cases +- Use internal logging helpers for operational status (`LogInfoExtensions`, `LogWarningExtensions`) + +--- + +## Encoding/Decoding Agents + +- Use abstraction interfaces (`IPolylineEncoder`, `IPolylineDecoder` if available) +- Prefer extension methods for collections and arrays +- Validate latitude/longitude ranges + +--- + +## Issue and PR Templates + +Agents should reference standardized templates from `.github`. Contributors must use them for new issues or PRs. + +--- + +## Extensibility + +- Add encoding schemes or coordinate types in **separate classes/files** +- Register via factory pattern if supporting multiple algorithms +- Do not mix logic between different polyline versions + +--- + +## Future-proofing + +- Support for precision or custom coordinate fields: update `PolylineEncodingOptions` with clear doc comments + +--- + +## Documentation + +- Keep XML doc comments up-to-date in source files +- API reference is auto-generated and hosted at + [https://petesramek.github.io/polyline-algorithm-csharp/](https://petesramek.github.io/polyline-algorithm-csharp/) +- After public API changes, verify docs render correctly on the website +- Add usage samples in XML comments and `/samples` directory + +--- + +## Agent File Format (for `.github/agents`) + +Each agent instruction file should specify: + +``` +# AGENT INSTRUCTIONS + +- Purpose and scope +- Required tools/commands +- Coding and testing requirements +- Logging/error handling expectations +- Documentation or samples to update +``` + +--- + +## Contact & Questions + +Questions or clarifications: open a GitHub issue and tag `@petesramek`. + +--- diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..a2492d27 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,50 @@ +# Contributing to PolylineAlgorithm + +Thank you for your interest in improving this library! + +## Developer Documentation + +In-depth developer guides are in the [`/docs`](./docs/README.md) folder: + +- [Local Development](./docs/local-development.md) — build, test, and format commands +- [Testing](./docs/testing.md) — how to write unit tests +- [Benchmarks](./docs/benchmarks.md) — how to write and run benchmarks +- [Composite Actions](./docs/composite-actions.md) — reusable CI actions catalogue +- [Workflows](./docs/workflows.md) — CI/CD pipeline overview +- [Branch Strategy](./docs/branch-strategy.md) — branch lifecycle and environments +- [Versioning](./docs/versioning.md) — branch naming and the version pipeline +- [API Documentation](./docs/api-documentation.md) — DocFX and the API reference site + +## Guidelines + +- **Follow code style:** Use `.editorconfig` and run `dotnet format`. +- **Add unit tests:** Place all tests in `/tests`, following naming conventions. +- **Benchmark updates:** Add or update `/benchmarks` for major changes. + +## Issue and PR Templates + +Please use the provided templates in `.github` for all new issues or pull requests. + +## API Documentation + +API reference is auto-generated from XML comments and published at +👉 [API Reference](https://petesramek.github.io/polyline-algorithm-csharp/) + +- All public classes, interfaces, and methods require XML doc comments. +- After merging, verify that documentation renders correctly. +- Add usage samples where applicable. + +## Submitting a Change + +1. Fork the repo and create a new branch. +2. Implement your changes, tests, and update doc comments. +3. Run `dotnet format`, and all tests/benchmarks. +4. Submit a pull request, using the provided template. + +## Contact + +For help or questions, open an issue and tag `@petesramek`. + +## License + +MIT License © Pete Sramek diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 00000000..96ebb124 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,25 @@ + + + + 14.0 + enable + enable + true + en + + + + latest + All + true + true + + + + + + + + + + diff --git a/DropoutCoder.PolylineAlgorithm.sln b/DropoutCoder.PolylineAlgorithm.sln deleted file mode 100644 index a7673087..00000000 --- a/DropoutCoder.PolylineAlgorithm.sln +++ /dev/null @@ -1,61 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.8.34330.188 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DropoutCoder.PolylineAlgorithm", "src\DropoutCoder.PolylineAlgorithm.csproj", "{882322A6-E758-4662-8D1C-7C555C8FC3F2}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{51C886AF-D610-48A4-9D73-2DEB38742801}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{576FEFFC-B624-40C3-A8AF-4E5233802EA0}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuget", "nuget", "{7F9807A1-01FF-40C3-9342-3F3F35D632DA}" - ProjectSection(SolutionItems) = preProject - nuget\DropoutCoder.PolylineAlgorithm.nuspec = nuget\DropoutCoder.PolylineAlgorithm.nuspec - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DropoutCoder.PolylineAlgorithm.Tests", "tests\DropoutCoder.PolylineAlgorithm.Tests.csproj", "{30324A08-AA42-425D-87DA-8F9C6AF60454}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{C13E31F9-B8EF-4915-A1C8-BC33F6431EB9}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{33C03F16-4313-4579-87E6-65892AF21D7D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DropoutCoder.PolylineAlgorithm.Benchmarks", "benchmarks\DropoutCoder.PolylineAlgorithm.Benchmarks\DropoutCoder.PolylineAlgorithm.Benchmarks.csproj", "{9C7CBAD5-415B-4589-86E1-01C849F9C56C}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks", "benchmarks\DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks\DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks.csproj", "{D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {882322A6-E758-4662-8D1C-7C555C8FC3F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {882322A6-E758-4662-8D1C-7C555C8FC3F2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {882322A6-E758-4662-8D1C-7C555C8FC3F2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {882322A6-E758-4662-8D1C-7C555C8FC3F2}.Release|Any CPU.Build.0 = Release|Any CPU - {30324A08-AA42-425D-87DA-8F9C6AF60454}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {30324A08-AA42-425D-87DA-8F9C6AF60454}.Debug|Any CPU.Build.0 = Debug|Any CPU - {30324A08-AA42-425D-87DA-8F9C6AF60454}.Release|Any CPU.ActiveCfg = Release|Any CPU - {30324A08-AA42-425D-87DA-8F9C6AF60454}.Release|Any CPU.Build.0 = Release|Any CPU - {9C7CBAD5-415B-4589-86E1-01C849F9C56C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9C7CBAD5-415B-4589-86E1-01C849F9C56C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9C7CBAD5-415B-4589-86E1-01C849F9C56C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9C7CBAD5-415B-4589-86E1-01C849F9C56C}.Release|Any CPU.Build.0 = Release|Any CPU - {D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {882322A6-E758-4662-8D1C-7C555C8FC3F2} = {51C886AF-D610-48A4-9D73-2DEB38742801} - {30324A08-AA42-425D-87DA-8F9C6AF60454} = {576FEFFC-B624-40C3-A8AF-4E5233802EA0} - {9C7CBAD5-415B-4589-86E1-01C849F9C56C} = {33C03F16-4313-4579-87E6-65892AF21D7D} - {D9F175EA-6F4C-4BFF-AB1D-5F45324B6C1B} = {33C03F16-4313-4579-87E6-65892AF21D7D} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {93A268DC-0947-4FBB-B495-DDAD4B013D82} - EndGlobalSection -EndGlobal diff --git a/LICENSE b/LICENSE index ee65c099..f38ec86e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 Petr Šrámek +Copyright (c) 2025 Pete Sramek Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/PolylineAlgorithm.slnx b/PolylineAlgorithm.slnx new file mode 100644 index 00000000..29fa9445 --- /dev/null +++ b/PolylineAlgorithm.slnx @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/README.md b/README.md index 2695d8c8..9dfb8a18 100644 --- a/README.md +++ b/README.md @@ -1,113 +1,188 @@ -# .NET Polyline Algorithm (.NET Standard 2.0) +# PolylineAlgorithm for .NET -Lightweight .NET Standard 2.0 library implementing Google Polyline Algorithm. Designed with respect to flexibility, but still with easy to use. +Lightweight .NET Standard 2.1 library implementing Google-compliant Encoded Polyline Algorithm with strong input validation, modern API patterns, and extensibility for custom coordinate types. -## Getting started -### Prerequisites +## Table of Contents -.NET Polyline Algorithm is avalable as nuget package Cloudikka.PolylineAlgorithm targeting .NET Standard 2.0. +- [Features](#features) +- [Installation](#installation) +- [Usage](#usage) +- [API Reference](#api-reference) +- [Benchmarks](#benchmarks) +- [FAQ](#faq) +- [Contributing](#contributing) +- [Support](#support) +- [License](#license) -Command line: +## Features -`Install-Package Cloudikka.PolylineAlgorithm` +- Fully compliant Google Encoded Polyline Algorithm for .NET Standard 2.1+ +- Extensible encoding and decoding APIs for custom coordinate and polyline types (`IPolylineEncoder`, `IPolylineDecoder`, `AbstractPolylineEncoder`, `AbstractPolylineDecoder`) +- Extension methods for encoding from `List` and arrays (`PolylineEncoderExtensions`) +- Robust input validation with descriptive exceptions for malformed/invalid data +- Advanced configuration via `PolylineEncodingOptions` (precision, buffer size, logging) +- Logging and diagnostic support for CI/CD and developer diagnostics via `Microsoft.Extensions.Logging` +- Low-level utilities for normalization, validation, encoding and decoding via static `PolylineEncoding` class +- Thorough unit tests and benchmarks for correctness and performance +- Auto-generated API documentation ([API Reference](https://petesramek.github.io/polyline-algorithm-csharp/)) +- Support for .NET Core, .NET 5+, Xamarin, Unity, Blazor, and other platforms supporting `netstandard2.1` -NuGet Package Manager: +## Installation -`Cloudikka.PolylineAlgorithm` +Using dotnet tool -#### Warning +```shell +dotnet add package PolylineAlgorithm +``` -Library is using ValueTuple Structure. ValueTuple struct is avalable in .NET Framework 4.7 and above. Incase your project is targeting lower version of .NET Framework you probably have to install System.ValueTuple NuGet package. (not tested yet) - -Command line: +or via NuGet -`Install-Package System.ValueTuple` +```powershell +Install-Package PolylineAlgorithm +``` -NuGet Package Manager: +## Usage -`System.ValueTuple` +The library provides abstract base classes to implement your own encoder and decoder for any coordinate and polyline type. -### Hot to use it +### Custom encoder and decoder -There are three ways how to use .NET Polyline Algorithm library based on your needs. For each is available Encode and Decode methods. +#### Encoding -#### Static methods +Custom encoder implementation. -Whenever you just need to encode or decode Google polyline you can use static methods defined in static PolylineAlgorithm class. +```csharp +using PolylineAlgorithm; +using PolylineAlgorithm.Abstraction; -##### Decoding +public sealed class MyPolylineEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> { + public MyPolylineEncoder() + : base() { } -```csharp - string polyline = "polyline"; - IEnumerable<(double, double)> coordinates = PolylineAlgorithm.Decode(polyline); -``` + public MyPolylineEncoder(PolylineEncodingOptions options) + : base(options) { } -##### Encoding + protected override double GetLatitude((double Latitude, double Longitude) coordinate) { + return coordinate.Latitude; + } -```csharp - IEnumerable<(double, double)> coordinates = new (double, double) [] { (35.635, 76.27182), (35.2435, 75.625), ... }; - string polyline = PolylineAlgorithm.Encode(coordinates); + protected override double GetLongitude((double Latitude, double Longitude) coordinate) { + return coordinate.Longitude; + } + + protected override string CreatePolyline(ReadOnlyMemory polyline) { + return polyline.ToString(); + } +} ``` +Custom encoder usage. -#### Default instance +```csharp +using PolylineAlgorithm.Extensions; -If you need to use dependency injection, you would like to have instance to deliver the work for you. In that case you can use default instance of PolylineEncoding class, which implements IPolylineEncoding<(double Latitude, double Longitude)> interface. +var coordinates = new List<(double Latitude, double Longitude)> +{ + (48.858370, 2.294481), + (51.500729, -0.124625) +}; -##### Decoding +var encoder = new MyPolylineEncoder(); +string encoded = encoder.Encode(coordinates); // extension method for List -```csharp - string polyline = "polyline"; - var encoding = new PolylineEncoding(); - IEnumerable<(double, double)> coordinates = encoding.Decode(polyline); +Console.WriteLine(encoded); ``` -##### Encoding +#### Decoding + +Custom decoder implementation. ```csharp - IEnumerable<(double, double)> coordinates = new (double, double) [] { (35.635, 76.27182), (35.2435, 75.625), ... }; - var encoding = new PolylineEncoding(); - string polyline = encoding.Encode(coordinates); -``` +using PolylineAlgorithm; +using PolylineAlgorithm.Abstraction; -#### Inherited base class +public sealed class MyPolylineDecoder : AbstractPolylineDecoder { + public MyPolylineDecoder() + : base() { } -There may be a scenario you need to pass and return different types to and from without a need to add another extra layer. In this case you can inherit PolylineEncodingBase class and override template methods CreateResult and GetCoordinates. - -##### Inheriting + public MyPolylineDecoder(PolylineEncodingOptions options) + : base(options) { } -```csharp - public class MyPolylineEncoding : PolylineEncodingBase { - - protected override Coordinate CreateResult(double latitude, double longitude) { - return new Coordinate(latitude, longitude); - } - - protected override (double Latitude, double Longitude) GetCoordinate(Coordinate source) { - return (source.Latitude, source.Longitude); - } - - } + protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) { + return (latitude, longitude); + } + + protected override ReadOnlyMemory GetReadOnlyMemory(in string polyline) { + return polyline.AsMemory(); + } +} ``` -##### Decoding +Custom decoder usage. ```csharp - string polyline = "polyline"; - var encoding = new MyPolylineEncoding(); - IEnumerable coordinates = encoding.Decode(polyline); +string encoded = "yseiHoc_MwacOjnwM"; + +var decoder = new MyPolylineDecoder(); +IEnumerable<(double Latitude, double Longitude)> decoded = decoder.Decode(encoded); ``` -##### Encoding +> **Note:** +> If you need low-level utilities for normalization, validation, encoding and decoding, use static methods from the `PolylineEncoding` class. -```csharp - IEnumerable coordinates = new Coordinate [] { new Coordinate(35.635, 76.27182), new Coordinate(35.2435, 75.625), ... }; - var encoding = new MyPolylineEncoding(); - string polyline = encoding.Encode(coordinates); -``` +## API Reference + +Full API docs and guides (auto-generated from source) are available at [API Reference](https://petesramek.github.io/polyline-algorithm-csharp/) + +## Benchmarks + +- See `/benchmarks` in the repo for performance evaluation. +- Contributors: Update benchmarks and document results for performance-impacting PRs. + +## FAQ + +**Q: What coordinate ranges are valid?** +A: Latitude must be -90..90; longitude -180..180. Out-of-range input throws `ArgumentOutOfRangeException`. + +**Q: Which .NET versions are supported?** +A: All platforms supporting `netstandard2.1` (including .NET Core and .NET 5+). + +**Q: What happens if I pass invalid or malformed input to the decoder?** +A: The decoder will throw descriptive exceptions (`InvalidPolylineException`) for malformed polyline strings. Check exception handling in your application. + +**Q: How do I customize encoding options (e.g., precision, buffer size, logging)?** +A: Use `PolylineEncodingOptionsBuilder` to set custom options and pass the built `PolylineEncodingOptions` to the encoder or decoder constructor. + +**Q: Is the library thread-safe?** +A: Yes, the main encoding and decoding APIs are stateless and thread-safe. If using mutable shared resources, manage synchronization in your code. + +**Q: Can the library be used in Unity, Xamarin, Blazor, or other .NET-compatible platforms?** +A: Yes! Any environment supporting `netstandard2.1` can use this library. + +**Q: Where can I report bugs or request features?** +A: Open a GitHub issue using the provided templates in the repository and tag @petesramek. + +**Q: Is there support for elevation, time stamps, or third coordinate values?** +A: Not currently, not planned to be added, but you can extend by implementing your own encoder/decoder using `PolylineEncoding` class methods. + +**Q: How do I contribute documentation improvements?** +A: Update XML doc comments in the codebase and submit a PR; all public APIs require XML documentation. To improve guides, update the relevant markdown file in the `/api-reference/guide` folder. + +**Q: Does the library support streaming or incremental decoding of polylines?** +A: Currently, only batch encode/decode is supported. For streaming scenarios, implement custom logic using `PolylineEncoding` utility functions. + +## Contributing + +- Follow code style and PR instructions in [AGENTS.md](./AGENTS.md). +- Ensure all features are covered by tests and XML doc comments. +- For questions or suggestions, open an issue and use the provided templates. + +## Support + +Have a question, bug, or feature request? [Open an issue!](https://github.com/petesramek/polyline-algorithm-csharp/issues) -### Documentation +--- -Documentation is can be found at https://dropoutcoder.github.io/polyline-algorithm-csharp/api/index.html. +## License -Happy coding! +MIT License © Pete Sramek diff --git a/api-reference/1.0/PolylineAlgorithm.Abstraction.AbstractPolylineDecoder-2.yml b/api-reference/1.0/PolylineAlgorithm.Abstraction.AbstractPolylineDecoder-2.yml new file mode 100644 index 00000000..6349f59c --- /dev/null +++ b/api-reference/1.0/PolylineAlgorithm.Abstraction.AbstractPolylineDecoder-2.yml @@ -0,0 +1,233 @@ +### YamlMime:ApiPage +title: Class AbstractPolylineDecoder +body: +- api1: Class AbstractPolylineDecoder + id: PolylineAlgorithm_Abstraction_AbstractPolylineDecoder_2 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L22 + metadata: + uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2 + commentId: T:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2 +- facts: + - name: Namespace + value: + text: PolylineAlgorithm.Abstraction + url: PolylineAlgorithm.Abstraction.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: Provides a base implementation for decoding encoded polyline strings into sequences of geographic coordinates. +- code: 'public abstract class AbstractPolylineDecoder : IPolylineDecoder' +- h4: Type Parameters +- parameters: + - name: TPolyline + description: The type that represents the encoded polyline input. + - name: TCoordinate + description: The type that represents a decoded geographic coordinate. +- h4: Inheritance +- inheritance: + - text: object + url: https://learn.microsoft.com/dotnet/api/system.object + - text: AbstractPolylineDecoder + url: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder-2.html +- h4: Implements +- list: + - text: IPolylineDecoder + url: PolylineAlgorithm.Abstraction.IPolylineDecoder-2.html +- h4: Inherited Members +- list: + - text: object.Equals(object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) + - text: object.Equals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object) + - text: object.GetHashCode() + url: https://learn.microsoft.com/dotnet/api/system.object.gethashcode + - text: object.GetType() + url: https://learn.microsoft.com/dotnet/api/system.object.gettype + - text: object.MemberwiseClone() + url: https://learn.microsoft.com/dotnet/api/system.object.memberwiseclone + - text: object.ReferenceEquals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.referenceequals + - text: object.ToString() + url: https://learn.microsoft.com/dotnet/api/system.object.tostring +- h2: Remarks +- markdown: >- + Derive from this class to implement a decoder for a specific polyline type. Override + + and to provide type-specific behavior. +- h2: Constructors +- api3: AbstractPolylineDecoder() + id: PolylineAlgorithm_Abstraction_AbstractPolylineDecoder_2__ctor + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L28 + metadata: + uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.#ctor + commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.#ctor +- markdown: Initializes a new instance of the class with default encoding options. +- code: protected AbstractPolylineDecoder() +- api3: AbstractPolylineDecoder(PolylineEncodingOptions) + id: PolylineAlgorithm_Abstraction_AbstractPolylineDecoder_2__ctor_PolylineAlgorithm_PolylineEncodingOptions_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L40 + metadata: + uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.#ctor(PolylineAlgorithm.PolylineEncodingOptions) + commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.#ctor(PolylineAlgorithm.PolylineEncodingOptions) +- markdown: Initializes a new instance of the class with the specified encoding options. +- code: protected AbstractPolylineDecoder(PolylineEncodingOptions options) +- h4: Parameters +- parameters: + - name: options + type: + - text: PolylineEncodingOptions + url: PolylineAlgorithm.PolylineEncodingOptions.html + description: The to use for encoding operations. +- h4: Exceptions +- parameters: + - type: + - text: ArgumentNullException + url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception + description: Thrown when options is null. +- h2: Properties +- api3: Options + id: PolylineAlgorithm_Abstraction_AbstractPolylineDecoder_2_Options + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L54 + metadata: + uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.Options + commentId: P:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.Options +- markdown: Gets the encoding options used by this polyline decoder. +- code: public PolylineEncodingOptions Options { get; } +- h4: Property Value +- parameters: + - type: + - text: PolylineEncodingOptions + url: PolylineAlgorithm.PolylineEncodingOptions.html +- h2: Methods +- api3: CreateCoordinate(double, double) + id: PolylineAlgorithm_Abstraction_AbstractPolylineDecoder_2_CreateCoordinate_System_Double_System_Double_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L202 + metadata: + uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.CreateCoordinate(System.Double,System.Double) + commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.CreateCoordinate(System.Double,System.Double) +- markdown: Creates a TCoordinate instance from the specified latitude and longitude values. +- code: protected abstract TCoordinate CreateCoordinate(double latitude, double longitude) +- h4: Parameters +- parameters: + - name: latitude + type: + - text: double + url: https://learn.microsoft.com/dotnet/api/system.double + description: The latitude component of the coordinate, in degrees. + - name: longitude + type: + - text: double + url: https://learn.microsoft.com/dotnet/api/system.double + description: The longitude component of the coordinate, in degrees. +- h4: Returns +- parameters: + - type: + - TCoordinate + description: A TCoordinate instance representing the specified geographic coordinate. +- api3: Decode(TPolyline, CancellationToken) + id: PolylineAlgorithm_Abstraction_AbstractPolylineDecoder_2_Decode__0_System_Threading_CancellationToken_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L81 + metadata: + uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.Decode(`0,System.Threading.CancellationToken) + commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.Decode(`0,System.Threading.CancellationToken) +- markdown: >- + Decodes an encoded TPolyline into a sequence of TCoordinate instances, + + with support for cancellation. +- code: public IEnumerable Decode(TPolyline polyline, CancellationToken cancellationToken = default) +- h4: Parameters +- parameters: + - name: polyline + type: + - TPolyline + description: The TPolyline instance containing the encoded polyline string to decode. + - name: cancellationToken + type: + - text: CancellationToken + url: https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken + description: A that can be used to cancel the decoding operation. + optional: true +- h4: Returns +- parameters: + - type: + - text: IEnumerable + url: https://learn.microsoft.com/dotnet/api/system.collections.generic.ienumerable-1 + - < + - TCoordinate + - '>' + description: An of TCoordinate representing the decoded latitude and longitude pairs. +- h4: Exceptions +- parameters: + - type: + - text: ArgumentNullException + url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception + description: Thrown when polyline is null. + - type: + - text: ArgumentException + url: https://learn.microsoft.com/dotnet/api/system.argumentexception + description: Thrown when polyline is empty. + - type: + - text: InvalidPolylineException + url: PolylineAlgorithm.InvalidPolylineException.html + description: Thrown when the polyline format is invalid or malformed at a specific position. + - type: + - text: OperationCanceledException + url: https://learn.microsoft.com/dotnet/api/system.operationcanceledexception + description: Thrown when cancellationToken is canceled during decoding. +- api3: GetReadOnlyMemory(in TPolyline) + id: PolylineAlgorithm_Abstraction_AbstractPolylineDecoder_2_GetReadOnlyMemory__0__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L187 + metadata: + uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.GetReadOnlyMemory(`0@) + commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.GetReadOnlyMemory(`0@) +- markdown: Extracts the underlying read-only memory region of characters from the specified polyline instance. +- code: protected abstract ReadOnlyMemory GetReadOnlyMemory(in TPolyline polyline) +- h4: Parameters +- parameters: + - name: polyline + type: + - TPolyline + description: The TPolyline instance from which to extract the character sequence. +- h4: Returns +- parameters: + - type: + - text: ReadOnlyMemory + url: https://learn.microsoft.com/dotnet/api/system.readonlymemory-1 + - < + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + - '>' + description: A of representing the encoded polyline characters. +- api3: ValidateFormat(ReadOnlyMemory, ILogger?) + id: PolylineAlgorithm_Abstraction_AbstractPolylineDecoder_2_ValidateFormat_System_ReadOnlyMemory_System_Char__Microsoft_Extensions_Logging_ILogger_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineDecoder.cs#L167 + metadata: + uid: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.ValidateFormat(System.ReadOnlyMemory{System.Char},Microsoft.Extensions.Logging.ILogger) + commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineDecoder`2.ValidateFormat(System.ReadOnlyMemory{System.Char},Microsoft.Extensions.Logging.ILogger) +- markdown: Validates the format of the polyline character sequence, ensuring all characters are within the allowed range. +- code: protected virtual void ValidateFormat(ReadOnlyMemory sequence, ILogger? logger) +- h4: Parameters +- parameters: + - name: sequence + type: + - text: ReadOnlyMemory + url: https://learn.microsoft.com/dotnet/api/system.readonlymemory-1 + - < + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + - '>' + description: The read-only memory region of characters representing the polyline to validate. + - name: logger + type: + - text: ILogger + url: https://learn.microsoft.com/dotnet/api/microsoft.extensions.logging.ilogger + - '?' + description: An optional used to log a warning when format validation fails. +- h4: Exceptions +- parameters: + - type: + - text: ArgumentException + url: https://learn.microsoft.com/dotnet/api/system.argumentexception + description: Thrown when the polyline contains characters outside the valid encoding range or has an invalid block structure. +languageId: csharp +metadata: + description: Provides a base implementation for decoding encoded polyline strings into sequences of geographic coordinates. diff --git a/api-reference/1.0/PolylineAlgorithm.Abstraction.AbstractPolylineEncoder-2.yml b/api-reference/1.0/PolylineAlgorithm.Abstraction.AbstractPolylineEncoder-2.yml new file mode 100644 index 00000000..ec0a65e9 --- /dev/null +++ b/api-reference/1.0/PolylineAlgorithm.Abstraction.AbstractPolylineEncoder-2.yml @@ -0,0 +1,219 @@ +### YamlMime:ApiPage +title: Class AbstractPolylineEncoder +body: +- api1: Class AbstractPolylineEncoder + id: PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L27 + metadata: + uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2 + commentId: T:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2 +- facts: + - name: Namespace + value: + text: PolylineAlgorithm.Abstraction + url: PolylineAlgorithm.Abstraction.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: Provides a base implementation for encoding sequences of geographic coordinates into encoded polyline strings. +- code: 'public abstract class AbstractPolylineEncoder : IPolylineEncoder' +- h4: Type Parameters +- parameters: + - name: TCoordinate + description: The type that represents a geographic coordinate to encode. + - name: TPolyline + description: The type that represents the encoded polyline output. +- h4: Inheritance +- inheritance: + - text: object + url: https://learn.microsoft.com/dotnet/api/system.object + - text: AbstractPolylineEncoder + url: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder-2.html +- h4: Implements +- list: + - text: IPolylineEncoder + url: PolylineAlgorithm.Abstraction.IPolylineEncoder-2.html +- h4: Inherited Members +- list: + - text: object.Equals(object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) + - text: object.Equals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object) + - text: object.GetHashCode() + url: https://learn.microsoft.com/dotnet/api/system.object.gethashcode + - text: object.GetType() + url: https://learn.microsoft.com/dotnet/api/system.object.gettype + - text: object.MemberwiseClone() + url: https://learn.microsoft.com/dotnet/api/system.object.memberwiseclone + - text: object.ReferenceEquals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.referenceequals + - text: object.ToString() + url: https://learn.microsoft.com/dotnet/api/system.object.tostring +- h4: Extension Methods +- list: + - text: PolylineEncoderExtensions.Encode(IPolylineEncoder, List) + url: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.html#PolylineAlgorithm_Extensions_PolylineEncoderExtensions_Encode__2_PolylineAlgorithm_Abstraction_IPolylineEncoder___0___1__System_Collections_Generic_List___0__ + - text: PolylineEncoderExtensions.Encode(IPolylineEncoder, TCoordinate[]) + url: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.html#PolylineAlgorithm_Extensions_PolylineEncoderExtensions_Encode__2_PolylineAlgorithm_Abstraction_IPolylineEncoder___0___1____0___ +- h2: Remarks +- markdown: >- + Derive from this class to implement an encoder for a specific coordinate and polyline type. Override + + , , and to provide type-specific behavior. +- h2: Constructors +- api3: AbstractPolylineEncoder() + id: PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2__ctor + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L33 + metadata: + uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.#ctor + commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.#ctor +- markdown: Initializes a new instance of the class with default encoding options. +- code: protected AbstractPolylineEncoder() +- api3: AbstractPolylineEncoder(PolylineEncodingOptions) + id: PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2__ctor_PolylineAlgorithm_PolylineEncodingOptions_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L43 + metadata: + uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.#ctor(PolylineAlgorithm.PolylineEncodingOptions) + commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.#ctor(PolylineAlgorithm.PolylineEncodingOptions) +- markdown: Initializes a new instance of the class with the specified encoding options. +- code: protected AbstractPolylineEncoder(PolylineEncodingOptions options) +- h4: Parameters +- parameters: + - name: options + type: + - text: PolylineEncodingOptions + url: PolylineAlgorithm.PolylineEncodingOptions.html + description: The to use for encoding operations. +- h4: Exceptions +- parameters: + - type: + - text: ArgumentNullException + url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception + description: Thrown when options is null +- h2: Properties +- api3: Options + id: PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2_Options + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L57 + metadata: + uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.Options + commentId: P:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.Options +- markdown: Gets the encoding options used by this polyline encoder. +- code: public PolylineEncodingOptions Options { get; } +- h4: Property Value +- parameters: + - type: + - text: PolylineEncodingOptions + url: PolylineAlgorithm.PolylineEncodingOptions.html +- h2: Methods +- api3: CreatePolyline(ReadOnlyMemory) + id: PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2_CreatePolyline_System_ReadOnlyMemory_System_Char__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L174 + metadata: + uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.CreatePolyline(System.ReadOnlyMemory{System.Char}) + commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.CreatePolyline(System.ReadOnlyMemory{System.Char}) +- markdown: Creates a polyline instance from the provided read-only sequence of characters. +- code: protected abstract TPolyline CreatePolyline(ReadOnlyMemory polyline) +- h4: Parameters +- parameters: + - name: polyline + type: + - text: ReadOnlyMemory + url: https://learn.microsoft.com/dotnet/api/system.readonlymemory-1 + - < + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + - '>' + description: A containing the encoded polyline characters. +- h4: Returns +- parameters: + - type: + - TPolyline + description: An instance of TPolyline representing the encoded polyline. +- api3: Encode(ReadOnlySpan, CancellationToken) + id: PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2_Encode_System_ReadOnlySpan__0__System_Threading_CancellationToken_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L80 + metadata: + uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.Encode(System.ReadOnlySpan{`0},System.Threading.CancellationToken) + commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.Encode(System.ReadOnlySpan{`0},System.Threading.CancellationToken) +- markdown: Encodes a collection of TCoordinate instances into an encoded TPolyline string. +- code: >- + [SuppressMessage("Design", "MA0051:Method is too long", Justification = "Method contains local methods. Actual method only 55 lines.")] + + public TPolyline Encode(ReadOnlySpan coordinates, CancellationToken cancellationToken = default) +- h4: Parameters +- parameters: + - name: coordinates + type: + - text: ReadOnlySpan + url: https://learn.microsoft.com/dotnet/api/system.readonlyspan-1 + - < + - TCoordinate + - '>' + description: The collection of TCoordinate objects to encode. + - name: cancellationToken + type: + - text: CancellationToken + url: https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken + description: A that can be used to cancel the encoding operation. + optional: true +- h4: Returns +- parameters: + - type: + - TPolyline + description: An instance of TPolyline representing the encoded coordinates. +- h4: Exceptions +- parameters: + - type: + - text: ArgumentNullException + url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception + description: Thrown when coordinates is null. + - type: + - text: ArgumentException + url: https://learn.microsoft.com/dotnet/api/system.argumentexception + description: Thrown when coordinates is an empty enumeration. + - type: + - text: InvalidOperationException + url: https://learn.microsoft.com/dotnet/api/system.invalidoperationexception + description: Thrown when the internal encoding buffer cannot accommodate the encoded value. +- api3: GetLatitude(TCoordinate) + id: PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2_GetLatitude__0_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L194 + metadata: + uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.GetLatitude(`0) + commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.GetLatitude(`0) +- markdown: Extracts the latitude value from the specified coordinate. +- code: protected abstract double GetLatitude(TCoordinate current) +- h4: Parameters +- parameters: + - name: current + type: + - TCoordinate + description: The coordinate from which to extract the latitude. +- h4: Returns +- parameters: + - type: + - text: double + url: https://learn.microsoft.com/dotnet/api/system.double + description: The latitude value as a . +- api3: GetLongitude(TCoordinate) + id: PolylineAlgorithm_Abstraction_AbstractPolylineEncoder_2_GetLongitude__0_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/AbstractPolylineEncoder.cs#L184 + metadata: + uid: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.GetLongitude(`0) + commentId: M:PolylineAlgorithm.Abstraction.AbstractPolylineEncoder`2.GetLongitude(`0) +- markdown: Extracts the longitude value from the specified coordinate. +- code: protected abstract double GetLongitude(TCoordinate current) +- h4: Parameters +- parameters: + - name: current + type: + - TCoordinate + description: The coordinate from which to extract the longitude. +- h4: Returns +- parameters: + - type: + - text: double + url: https://learn.microsoft.com/dotnet/api/system.double + description: The longitude value as a . +languageId: csharp +metadata: + description: Provides a base implementation for encoding sequences of geographic coordinates into encoded polyline strings. diff --git a/api-reference/1.0/PolylineAlgorithm.Abstraction.IPolylineDecoder-2.yml b/api-reference/1.0/PolylineAlgorithm.Abstraction.IPolylineDecoder-2.yml new file mode 100644 index 00000000..704f92b2 --- /dev/null +++ b/api-reference/1.0/PolylineAlgorithm.Abstraction.IPolylineDecoder-2.yml @@ -0,0 +1,90 @@ +### YamlMime:ApiPage +title: Interface IPolylineDecoder +body: +- api1: Interface IPolylineDecoder + id: PolylineAlgorithm_Abstraction_IPolylineDecoder_2 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/IPolylineDecoder.cs#L22 + metadata: + uid: PolylineAlgorithm.Abstraction.IPolylineDecoder`2 + commentId: T:PolylineAlgorithm.Abstraction.IPolylineDecoder`2 +- facts: + - name: Namespace + value: + text: PolylineAlgorithm.Abstraction + url: PolylineAlgorithm.Abstraction.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: Defines a contract for decoding an encoded polyline into a sequence of geographic coordinates. +- code: public interface IPolylineDecoder +- h4: Type Parameters +- parameters: + - name: TPolyline + description: >- + The type that represents the encoded polyline input. Common implementations use , + + but custom wrapper types are allowed to carry additional metadata. + - name: TValue + description: >- + The coordinate type returned by the decoder. Typical implementations return a struct or class that + + contains latitude and longitude (for example a LatLng type or a ValueTuple<double,double>). +- h2: Methods +- api3: Decode(TPolyline, CancellationToken) + id: PolylineAlgorithm_Abstraction_IPolylineDecoder_2_Decode__0_System_Threading_CancellationToken_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/IPolylineDecoder.cs#L48 + metadata: + uid: PolylineAlgorithm.Abstraction.IPolylineDecoder`2.Decode(`0,System.Threading.CancellationToken) + commentId: M:PolylineAlgorithm.Abstraction.IPolylineDecoder`2.Decode(`0,System.Threading.CancellationToken) +- markdown: >- + Decodes the specified encoded polyline into an ordered sequence of geographic coordinates. + + The sequence preserves the original vertex order encoded by the polyline. +- code: IEnumerable Decode(TPolyline polyline, CancellationToken cancellationToken = default) +- h4: Parameters +- parameters: + - name: polyline + type: + - TPolyline + description: >- + The TPolyline instance containing the encoded polyline to decode. + + Implementations SHOULD validate the input and may throw + + or for invalid formats. + - name: cancellationToken + type: + - text: CancellationToken + url: https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken + description: >- + A to observe while decoding. If cancellation is requested, + + implementations SHOULD stop work and throw an . + optional: true +- h4: Returns +- parameters: + - type: + - text: IEnumerable + url: https://learn.microsoft.com/dotnet/api/system.collections.generic.ienumerable-1 + - < + - TValue + - '>' + description: >- + An of TValue representing the decoded + + latitude/longitude pairs (or equivalent coordinates) in the same order they were encoded. +- h4: Remarks +- markdown: >- + Implementations commonly follow the Google Encoded Polyline Algorithm Format, but this interface + + does not mandate a specific encoding. Consumers should rely on a concrete decoder's documentation + + to understand the exact encoding supported. +- h4: Exceptions +- parameters: + - type: + - text: OperationCanceledException + url: https://learn.microsoft.com/dotnet/api/system.operationcanceledexception + description: Thrown when the provided cancellationToken requests cancellation. +languageId: csharp +metadata: + description: Defines a contract for decoding an encoded polyline into a sequence of geographic coordinates. diff --git a/api-reference/1.0/PolylineAlgorithm.Abstraction.IPolylineEncoder-2.yml b/api-reference/1.0/PolylineAlgorithm.Abstraction.IPolylineEncoder-2.yml new file mode 100644 index 00000000..979fe774 --- /dev/null +++ b/api-reference/1.0/PolylineAlgorithm.Abstraction.IPolylineEncoder-2.yml @@ -0,0 +1,142 @@ +### YamlMime:ApiPage +title: Interface IPolylineEncoder +body: +- api1: Interface IPolylineEncoder + id: PolylineAlgorithm_Abstraction_IPolylineEncoder_2 + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs#L36 + metadata: + uid: PolylineAlgorithm.Abstraction.IPolylineEncoder`2 + commentId: T:PolylineAlgorithm.Abstraction.IPolylineEncoder`2 +- facts: + - name: Namespace + value: + text: PolylineAlgorithm.Abstraction + url: PolylineAlgorithm.Abstraction.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: >- + Contract for encoding a sequence of geographic coordinates into an encoded polyline representation. + + Implementations interpret the generic TValue type and produce an encoded + + representation of those coordinates as TPolyline. +- code: public interface IPolylineEncoder +- h4: Type Parameters +- parameters: + - name: TValue + description: >- + The concrete coordinate representation used by the encoder (for example a struct or class containing + + Latitude and Longitude values). Implementations must document the expected shape, + + units (typically decimal degrees), and any required fields for TValue. + + Common shapes: + + - A struct or class with two double properties named Latitude and Longitude. + + - A tuple-like type (for example ValueTuple<double,double>) where the encoder documents + which element represents latitude and longitude. + - name: TPolyline + description: >- + The encoded polyline representation returned by the encoder (for example string, + + ReadOnlyMemory<char>, or a custom wrapper type). Concrete implementations should document + + the chosen representation and any memory / ownership expectations. +- h4: Extension Methods +- list: + - text: PolylineEncoderExtensions.Encode(IPolylineEncoder, List) + url: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.html#PolylineAlgorithm_Extensions_PolylineEncoderExtensions_Encode__2_PolylineAlgorithm_Abstraction_IPolylineEncoder___0___1__System_Collections_Generic_List___0__ + - text: PolylineEncoderExtensions.Encode(IPolylineEncoder, TValue[]) + url: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.html#PolylineAlgorithm_Extensions_PolylineEncoderExtensions_Encode__2_PolylineAlgorithm_Abstraction_IPolylineEncoder___0___1____0___ +- h2: Remarks +- markdown: >- + - This interface is intentionally minimal to allow different encoding strategies (Google encoded polyline, + precision/scale variants, or custom compressed formats) to be expressed behind a common contract. + - Implementations should document: + - Coordinate precision and rounding rules (for example 1e-5 for 5-decimal precision). + - Coordinate ordering and whether altitude or additional dimensions are supported. + - Thread-safety guarantees: whether instances are safe to reuse concurrently or must be instantiated per-call. + - Implementations are encouraged to be memory-efficient; the API accepts a + to avoid forced allocations when callers already have contiguous memory. +- h2: Methods +- api3: Encode(ReadOnlySpan, CancellationToken) + id: PolylineAlgorithm_Abstraction_IPolylineEncoder_2_Encode_System_ReadOnlySpan__0__System_Threading_CancellationToken_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Abstraction/IPolylineEncoder.cs#L76 + metadata: + uid: PolylineAlgorithm.Abstraction.IPolylineEncoder`2.Encode(System.ReadOnlySpan{`0},System.Threading.CancellationToken) + commentId: M:PolylineAlgorithm.Abstraction.IPolylineEncoder`2.Encode(System.ReadOnlySpan{`0},System.Threading.CancellationToken) +- markdown: >- + Encodes a sequence of geographic coordinates into an encoded polyline representation. + + The order of coordinates in coordinates is preserved in the encoded result. +- code: TPolyline Encode(ReadOnlySpan coordinates, CancellationToken cancellationToken = default) +- h4: Parameters +- parameters: + - name: coordinates + type: + - text: ReadOnlySpan + url: https://learn.microsoft.com/dotnet/api/system.readonlyspan-1 + - < + - TValue + - '>' + description: >- + The collection of TValue instances to encode into a polyline. + + The span may be empty; implementations should return an appropriate empty encoded representation + + (for example an empty string or an empty memory slice) rather than null. + - name: cancellationToken + type: + - text: CancellationToken + url: https://learn.microsoft.com/dotnet/api/system.threading.cancellationtoken + description: >- + A that can be used to cancel the encoding operation. + + Implementations should observe this token and throw + + when cancellation is requested. For fast, in-memory encoders cancellation may be best-effort. + optional: true +- h4: Returns +- parameters: + - type: + - TPolyline + description: >- + A TPolyline containing the encoded polyline that represents the input coordinates. + + The exact format and any delimiting/terminating characters are implementation-specific and must be + + documented by concrete encoder types. +- h4: Examples +- markdown: >- +
// Example pseudocode for typical usage with a string-based encoder:
+
+    var coords = new[] {
+        new Coordinate { Latitude = 47.6219, Longitude = -122.3503 },
+        new Coordinate { Latitude = 47.6220, Longitude = -122.3504 }
+    };
+
+    IPolylineEncoder<Coordinate,string> encoder = new GoogleEncodedPolylineEncoder();
+
+    string encoded = encoder.Encode(coords, CancellationToken.None);
+- h4: Remarks +- markdown: >- + - Implementations should validate input as appropriate and document any preconditions (for example + if coordinates must be within [-90,90] latitude and [-180,180] longitude). + - For large input sequences, implementations may provide streaming or incremental encoders; those + variants can still implement this interface by materializing the final encoded result. +- h4: Exceptions +- parameters: + - type: + - text: OperationCanceledException + url: https://learn.microsoft.com/dotnet/api/system.operationcanceledexception + description: Thrown if the operation is canceled via cancellationToken. +languageId: csharp +metadata: + description: >- + Contract for encoding a sequence of geographic coordinates into an encoded polyline representation. + + Implementations interpret the generic TValue type and produce an encoded + + representation of those coordinates as TPolyline. diff --git a/api-reference/1.0/PolylineAlgorithm.Abstraction.yml b/api-reference/1.0/PolylineAlgorithm.Abstraction.yml new file mode 100644 index 00000000..e9de48f7 --- /dev/null +++ b/api-reference/1.0/PolylineAlgorithm.Abstraction.yml @@ -0,0 +1,34 @@ +### YamlMime:ApiPage +title: Namespace PolylineAlgorithm.Abstraction +body: +- api1: Namespace PolylineAlgorithm.Abstraction + id: PolylineAlgorithm_Abstraction + metadata: + uid: PolylineAlgorithm.Abstraction + commentId: N:PolylineAlgorithm.Abstraction +- h3: Classes +- parameters: + - type: + text: AbstractPolylineDecoder + url: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder-2.html + description: Provides a base implementation for decoding encoded polyline strings into sequences of geographic coordinates. + - type: + text: AbstractPolylineEncoder + url: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder-2.html + description: Provides a base implementation for encoding sequences of geographic coordinates into encoded polyline strings. +- h3: Interfaces +- parameters: + - type: + text: IPolylineDecoder + url: PolylineAlgorithm.Abstraction.IPolylineDecoder-2.html + description: Defines a contract for decoding an encoded polyline into a sequence of geographic coordinates. + - type: + text: IPolylineEncoder + url: PolylineAlgorithm.Abstraction.IPolylineEncoder-2.html + description: >- + Contract for encoding a sequence of geographic coordinates into an encoded polyline representation. + + Implementations interpret the generic TValue type and produce an encoded + + representation of those coordinates as TPolyline. +languageId: csharp diff --git a/api-reference/1.0/PolylineAlgorithm.Extensions.PolylineDecoderExtensions.yml b/api-reference/1.0/PolylineAlgorithm.Extensions.PolylineDecoderExtensions.yml new file mode 100644 index 00000000..4b97f584 --- /dev/null +++ b/api-reference/1.0/PolylineAlgorithm.Extensions.PolylineDecoderExtensions.yml @@ -0,0 +1,195 @@ +### YamlMime:ApiPage +title: Class PolylineDecoderExtensions +body: +- api1: Class PolylineDecoderExtensions + id: PolylineAlgorithm_Extensions_PolylineDecoderExtensions + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs#L16 + metadata: + uid: PolylineAlgorithm.Extensions.PolylineDecoderExtensions + commentId: T:PolylineAlgorithm.Extensions.PolylineDecoderExtensions +- facts: + - name: Namespace + value: + text: PolylineAlgorithm.Extensions + url: PolylineAlgorithm.Extensions.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: Provides extension methods for the interface to facilitate decoding encoded polylines. +- code: public static class PolylineDecoderExtensions +- h4: Inheritance +- inheritance: + - text: object + url: https://learn.microsoft.com/dotnet/api/system.object + - text: PolylineDecoderExtensions + url: PolylineAlgorithm.Extensions.PolylineDecoderExtensions.html +- h4: Inherited Members +- list: + - text: object.Equals(object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) + - text: object.Equals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object) + - text: object.GetHashCode() + url: https://learn.microsoft.com/dotnet/api/system.object.gethashcode + - text: object.GetType() + url: https://learn.microsoft.com/dotnet/api/system.object.gettype + - text: object.MemberwiseClone() + url: https://learn.microsoft.com/dotnet/api/system.object.memberwiseclone + - text: object.ReferenceEquals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.referenceequals + - text: object.ToString() + url: https://learn.microsoft.com/dotnet/api/system.object.tostring +- h2: Methods +- api3: Decode(IPolylineDecoder, char[]) + id: PolylineAlgorithm_Extensions_PolylineDecoderExtensions_Decode__1_PolylineAlgorithm_Abstraction_IPolylineDecoder_System_String___0__System_Char___ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs#L33 + metadata: + uid: PolylineAlgorithm.Extensions.PolylineDecoderExtensions.Decode``1(PolylineAlgorithm.Abstraction.IPolylineDecoder{System.String,``0},System.Char[]) + commentId: M:PolylineAlgorithm.Extensions.PolylineDecoderExtensions.Decode``1(PolylineAlgorithm.Abstraction.IPolylineDecoder{System.String,``0},System.Char[]) +- markdown: Decodes an encoded polyline represented as a character array into a sequence of geographic coordinates. +- code: public static IEnumerable Decode(this IPolylineDecoder decoder, char[] polyline) +- h4: Parameters +- parameters: + - name: decoder + type: + - text: IPolylineDecoder + url: PolylineAlgorithm.Abstraction.IPolylineDecoder-2.html + - < + - text: string + url: https://learn.microsoft.com/dotnet/api/system.string + - ',' + - " " + - TValue + - '>' + description: The instance used to perform the decoding operation. + - name: polyline + type: + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + - '[' + - ']' + description: The encoded polyline as a character array to decode. The array is converted to a string internally. +- h4: Returns +- parameters: + - type: + - text: IEnumerable + url: https://learn.microsoft.com/dotnet/api/system.collections.generic.ienumerable-1 + - < + - TValue + - '>' + description: An of TValue containing the decoded coordinate pairs. +- h4: Type Parameters +- parameters: + - name: TValue + description: The coordinate type returned by the decoder. +- h4: Exceptions +- parameters: + - type: + - text: ArgumentNullException + url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception + description: Thrown when decoder or polyline is null. +- api3: Decode(IPolylineDecoder, ReadOnlyMemory) + id: PolylineAlgorithm_Extensions_PolylineDecoderExtensions_Decode__1_PolylineAlgorithm_Abstraction_IPolylineDecoder_System_String___0__System_ReadOnlyMemory_System_Char__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs#L61 + metadata: + uid: PolylineAlgorithm.Extensions.PolylineDecoderExtensions.Decode``1(PolylineAlgorithm.Abstraction.IPolylineDecoder{System.String,``0},System.ReadOnlyMemory{System.Char}) + commentId: M:PolylineAlgorithm.Extensions.PolylineDecoderExtensions.Decode``1(PolylineAlgorithm.Abstraction.IPolylineDecoder{System.String,``0},System.ReadOnlyMemory{System.Char}) +- markdown: Decodes an encoded polyline represented as a read-only memory of characters into a sequence of geographic coordinates. +- code: public static IEnumerable Decode(this IPolylineDecoder decoder, ReadOnlyMemory polyline) +- h4: Parameters +- parameters: + - name: decoder + type: + - text: IPolylineDecoder + url: PolylineAlgorithm.Abstraction.IPolylineDecoder-2.html + - < + - text: string + url: https://learn.microsoft.com/dotnet/api/system.string + - ',' + - " " + - TValue + - '>' + description: The instance used to perform the decoding operation. + - name: polyline + type: + - text: ReadOnlyMemory + url: https://learn.microsoft.com/dotnet/api/system.readonlymemory-1 + - < + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + - '>' + description: The encoded polyline as a read-only memory of characters to decode. The memory is converted to a string internally. +- h4: Returns +- parameters: + - type: + - text: IEnumerable + url: https://learn.microsoft.com/dotnet/api/system.collections.generic.ienumerable-1 + - < + - TValue + - '>' + description: An of TValue containing the decoded coordinate pairs. +- h4: Type Parameters +- parameters: + - name: TValue + description: The coordinate type returned by the decoder. +- h4: Exceptions +- parameters: + - type: + - text: ArgumentNullException + url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception + description: Thrown when decoder is null. +- api3: Decode(IPolylineDecoder, TValue>, string) + id: PolylineAlgorithm_Extensions_PolylineDecoderExtensions_Decode__1_PolylineAlgorithm_Abstraction_IPolylineDecoder_System_ReadOnlyMemory_System_Char____0__System_String_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Extensions/PolylineDecoderExtensions.cs#L86 + metadata: + uid: PolylineAlgorithm.Extensions.PolylineDecoderExtensions.Decode``1(PolylineAlgorithm.Abstraction.IPolylineDecoder{System.ReadOnlyMemory{System.Char},``0},System.String) + commentId: M:PolylineAlgorithm.Extensions.PolylineDecoderExtensions.Decode``1(PolylineAlgorithm.Abstraction.IPolylineDecoder{System.ReadOnlyMemory{System.Char},``0},System.String) +- markdown: >- + Decodes an encoded polyline string into a sequence of geographic coordinates, + + using a decoder that accepts of . +- code: public static IEnumerable Decode(this IPolylineDecoder, TValue> decoder, string polyline) +- h4: Parameters +- parameters: + - name: decoder + type: + - text: IPolylineDecoder + url: PolylineAlgorithm.Abstraction.IPolylineDecoder-2.html + - < + - text: ReadOnlyMemory + url: https://learn.microsoft.com/dotnet/api/system.readonlymemory-1 + - < + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + - '>' + - ',' + - " " + - TValue + - '>' + description: The instance used to perform the decoding operation. + - name: polyline + type: + - text: string + url: https://learn.microsoft.com/dotnet/api/system.string + description: The encoded polyline string to decode. The string is converted to internally. +- h4: Returns +- parameters: + - type: + - text: IEnumerable + url: https://learn.microsoft.com/dotnet/api/system.collections.generic.ienumerable-1 + - < + - TValue + - '>' + description: An of TValue containing the decoded coordinate pairs. +- h4: Type Parameters +- parameters: + - name: TValue + description: The coordinate type returned by the decoder. +- h4: Exceptions +- parameters: + - type: + - text: ArgumentNullException + url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception + description: Thrown when decoder or polyline is null. +languageId: csharp +metadata: + description: Provides extension methods for the interface to facilitate decoding encoded polylines. diff --git a/api-reference/1.0/PolylineAlgorithm.Extensions.PolylineEncoderExtensions.yml b/api-reference/1.0/PolylineAlgorithm.Extensions.PolylineEncoderExtensions.yml new file mode 100644 index 00000000..aeba23f9 --- /dev/null +++ b/api-reference/1.0/PolylineAlgorithm.Extensions.PolylineEncoderExtensions.yml @@ -0,0 +1,139 @@ +### YamlMime:ApiPage +title: Class PolylineEncoderExtensions +body: +- api1: Class PolylineEncoderExtensions + id: PolylineAlgorithm_Extensions_PolylineEncoderExtensions + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs#L19 + metadata: + uid: PolylineAlgorithm.Extensions.PolylineEncoderExtensions + commentId: T:PolylineAlgorithm.Extensions.PolylineEncoderExtensions +- facts: + - name: Namespace + value: + text: PolylineAlgorithm.Extensions + url: PolylineAlgorithm.Extensions.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: Provides extension methods for the interface to facilitate encoding geographic coordinates into polylines. +- code: public static class PolylineEncoderExtensions +- h4: Inheritance +- inheritance: + - text: object + url: https://learn.microsoft.com/dotnet/api/system.object + - text: PolylineEncoderExtensions + url: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.html +- h4: Inherited Members +- list: + - text: object.Equals(object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) + - text: object.Equals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object) + - text: object.GetHashCode() + url: https://learn.microsoft.com/dotnet/api/system.object.gethashcode + - text: object.GetType() + url: https://learn.microsoft.com/dotnet/api/system.object.gettype + - text: object.MemberwiseClone() + url: https://learn.microsoft.com/dotnet/api/system.object.memberwiseclone + - text: object.ReferenceEquals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.referenceequals + - text: object.ToString() + url: https://learn.microsoft.com/dotnet/api/system.object.tostring +- h2: Methods +- api3: Encode(IPolylineEncoder, List) + id: PolylineAlgorithm_Extensions_PolylineEncoderExtensions_Encode__2_PolylineAlgorithm_Abstraction_IPolylineEncoder___0___1__System_Collections_Generic_List___0__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs#L37 + metadata: + uid: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.Encode``2(PolylineAlgorithm.Abstraction.IPolylineEncoder{``0,``1},System.Collections.Generic.List{``0}) + commentId: M:PolylineAlgorithm.Extensions.PolylineEncoderExtensions.Encode``2(PolylineAlgorithm.Abstraction.IPolylineEncoder{``0,``1},System.Collections.Generic.List{``0}) +- markdown: Encodes a of TCoordinate instances into an encoded polyline. +- code: >- + [SuppressMessage("Design", "CA1002:Do not expose generic lists", Justification = "We need a list as we do need to marshal it as span.")] + + [SuppressMessage("Design", "MA0016:Prefer using collection abstraction instead of implementation", Justification = "We need a list as we do need to marshal it as span.")] + + public static TPolyline Encode(this IPolylineEncoder encoder, List coordinates) +- h4: Parameters +- parameters: + - name: encoder + type: + - text: IPolylineEncoder + url: PolylineAlgorithm.Abstraction.IPolylineEncoder-2.html + - < + - TCoordinate + - ',' + - " " + - TPolyline + - '>' + description: The instance used to perform the encoding operation. + - name: coordinates + type: + - text: List + url: https://learn.microsoft.com/dotnet/api/system.collections.generic.list-1 + - < + - TCoordinate + - '>' + description: The list of TCoordinate objects to encode. +- h4: Returns +- parameters: + - type: + - TPolyline + description: A TPolyline instance representing the encoded polyline for the provided coordinates. +- h4: Type Parameters +- parameters: + - name: TCoordinate + description: The type that represents a geographic coordinate to encode. + - name: TPolyline + description: The type that represents the encoded polyline output. +- h4: Exceptions +- parameters: + - type: + - text: ArgumentNullException + url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception + description: Thrown when encoder or coordinates is null. +- api3: Encode(IPolylineEncoder, TCoordinate[]) + id: PolylineAlgorithm_Extensions_PolylineEncoderExtensions_Encode__2_PolylineAlgorithm_Abstraction_IPolylineEncoder___0___1____0___ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Extensions/PolylineEncoderExtensions.cs#L73 + metadata: + uid: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.Encode``2(PolylineAlgorithm.Abstraction.IPolylineEncoder{``0,``1},``0[]) + commentId: M:PolylineAlgorithm.Extensions.PolylineEncoderExtensions.Encode``2(PolylineAlgorithm.Abstraction.IPolylineEncoder{``0,``1},``0[]) +- markdown: Encodes an array of TCoordinate instances into an encoded polyline. +- code: public static TPolyline Encode(this IPolylineEncoder encoder, TCoordinate[] coordinates) +- h4: Parameters +- parameters: + - name: encoder + type: + - text: IPolylineEncoder + url: PolylineAlgorithm.Abstraction.IPolylineEncoder-2.html + - < + - TCoordinate + - ',' + - " " + - TPolyline + - '>' + description: The instance used to perform the encoding operation. + - name: coordinates + type: + - TCoordinate + - '[' + - ']' + description: The array of TCoordinate objects to encode. +- h4: Returns +- parameters: + - type: + - TPolyline + description: A TPolyline instance representing the encoded polyline for the provided coordinates. +- h4: Type Parameters +- parameters: + - name: TCoordinate + description: The type that represents a geographic coordinate to encode. + - name: TPolyline + description: The type that represents the encoded polyline output. +- h4: Exceptions +- parameters: + - type: + - text: ArgumentNullException + url: https://learn.microsoft.com/dotnet/api/system.argumentnullexception + description: Thrown when encoder or coordinates is null. +languageId: csharp +metadata: + description: Provides extension methods for the interface to facilitate encoding geographic coordinates into polylines. diff --git a/api-reference/1.0/PolylineAlgorithm.Extensions.yml b/api-reference/1.0/PolylineAlgorithm.Extensions.yml new file mode 100644 index 00000000..c39da0ca --- /dev/null +++ b/api-reference/1.0/PolylineAlgorithm.Extensions.yml @@ -0,0 +1,19 @@ +### YamlMime:ApiPage +title: Namespace PolylineAlgorithm.Extensions +body: +- api1: Namespace PolylineAlgorithm.Extensions + id: PolylineAlgorithm_Extensions + metadata: + uid: PolylineAlgorithm.Extensions + commentId: N:PolylineAlgorithm.Extensions +- h3: Classes +- parameters: + - type: + text: PolylineDecoderExtensions + url: PolylineAlgorithm.Extensions.PolylineDecoderExtensions.html + description: Provides extension methods for the interface to facilitate decoding encoded polylines. + - type: + text: PolylineEncoderExtensions + url: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.html + description: Provides extension methods for the interface to facilitate encoding geographic coordinates into polylines. +languageId: csharp diff --git a/api-reference/1.0/PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.yml b/api-reference/1.0/PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.yml new file mode 100644 index 00000000..45962074 --- /dev/null +++ b/api-reference/1.0/PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.yml @@ -0,0 +1,313 @@ +### YamlMime:ApiPage +title: Class ExceptionGuard +body: +- api1: Class ExceptionGuard + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L29 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard + commentId: T:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard +- facts: + - name: Namespace + value: + text: PolylineAlgorithm.Internal.Diagnostics + url: PolylineAlgorithm.Internal.Diagnostics.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: Centralizes exception throwing for common validation and error scenarios used across the library. +- code: public static class ExceptionGuard +- h4: Inheritance +- inheritance: + - text: object + url: https://learn.microsoft.com/dotnet/api/system.object + - text: ExceptionGuard + url: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.html +- h4: Inherited Members +- list: + - text: object.Equals(object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) + - text: object.Equals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object) + - text: object.GetHashCode() + url: https://learn.microsoft.com/dotnet/api/system.object.gethashcode + - text: object.GetType() + url: https://learn.microsoft.com/dotnet/api/system.object.gettype + - text: object.MemberwiseClone() + url: https://learn.microsoft.com/dotnet/api/system.object.memberwiseclone + - text: object.ReferenceEquals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.referenceequals + - text: object.ToString() + url: https://learn.microsoft.com/dotnet/api/system.object.tostring +- h2: Remarks +- markdown: >- + Methods in this class are intentionally small and annotated so that they can act as single + + call sites for throwing exceptions (improving inlining and stack traces). Many members have + + attributes to avoid polluting callers' stack traces (__StackTraceHidden__ on supported targets) + + or to prevent inlining on older targets. +- h2: Methods +- api3: StackAllocLimitMustBeEqualOrGreaterThan(int, string) + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_StackAllocLimitMustBeEqualOrGreaterThan_System_Int32_System_String_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L97 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.StackAllocLimitMustBeEqualOrGreaterThan(System.Int32,System.String) + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.StackAllocLimitMustBeEqualOrGreaterThan(System.Int32,System.String) +- markdown: Throws an when a stack allocation limit is below the required minimum. +- code: >- + [DoesNotReturn] + + public static void StackAllocLimitMustBeEqualOrGreaterThan(int minValue, string paramName) +- h4: Parameters +- parameters: + - name: minValue + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: Minimum required stack allocation limit. + - name: paramName + type: + - text: string + url: https://learn.microsoft.com/dotnet/api/system.string + description: Name of the parameter representing the limit. +- api3: ThrowArgumentCannotBeEmptyEnumerationMessage(string) + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowArgumentCannotBeEmptyEnumerationMessage_System_String_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L111 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowArgumentCannotBeEmptyEnumerationMessage(System.String) + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowArgumentCannotBeEmptyEnumerationMessage(System.String) +- markdown: Throws an when an enumeration argument is empty but must contain at least one element. +- code: >- + [DoesNotReturn] + + public static void ThrowArgumentCannotBeEmptyEnumerationMessage(string paramName) +- h4: Parameters +- parameters: + - name: paramName + type: + - text: string + url: https://learn.microsoft.com/dotnet/api/system.string + description: Name of the parameter representing the enumeration. +- api3: ThrowArgumentNull(string) + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowArgumentNull_System_String_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L51 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowArgumentNull(System.String) + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowArgumentNull(System.String) +- markdown: Throws an for a null argument. +- code: >- + [DoesNotReturn] + + public static void ThrowArgumentNull(string paramName) +- h4: Parameters +- parameters: + - name: paramName + type: + - text: string + url: https://learn.microsoft.com/dotnet/api/system.string + description: Name of the parameter that was null. +- api3: ThrowBufferOverflow(string) + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowBufferOverflow_System_String_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L65 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowBufferOverflow(System.String) + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowBufferOverflow(System.String) +- markdown: Throws an with a provided message. +- code: >- + [DoesNotReturn] + + public static void ThrowBufferOverflow(string message) +- h4: Parameters +- parameters: + - name: message + type: + - text: string + url: https://learn.microsoft.com/dotnet/api/system.string + description: Message that describes the overflow condition. +- api3: ThrowCoordinateValueOutOfRange(double, double, double, string) + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowCoordinateValueOutOfRange_System_Double_System_Double_System_Double_System_String_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L82 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowCoordinateValueOutOfRange(System.Double,System.Double,System.Double,System.String) + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowCoordinateValueOutOfRange(System.Double,System.Double,System.Double,System.String) +- markdown: Throws an when a coordinate value is outside the allowed range. +- code: >- + [DoesNotReturn] + + public static void ThrowCoordinateValueOutOfRange(double value, double min, double max, string paramName) +- h4: Parameters +- parameters: + - name: value + type: + - text: double + url: https://learn.microsoft.com/dotnet/api/system.double + description: The coordinate value that was out of range. + - name: min + type: + - text: double + url: https://learn.microsoft.com/dotnet/api/system.double + description: Inclusive minimum allowed value. + - name: max + type: + - text: double + url: https://learn.microsoft.com/dotnet/api/system.double + description: Inclusive maximum allowed value. + - name: paramName + type: + - text: string + url: https://learn.microsoft.com/dotnet/api/system.string + description: Name of the parameter containing the coordinate. +- api3: ThrowCouldNotWriteEncodedValueToBuffer() + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowCouldNotWriteEncodedValueToBuffer + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L124 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowCouldNotWriteEncodedValueToBuffer + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowCouldNotWriteEncodedValueToBuffer +- markdown: Throws an when an encoded value could not be written to the destination buffer. +- code: >- + [DoesNotReturn] + + public static void ThrowCouldNotWriteEncodedValueToBuffer() +- api3: ThrowDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength(int, int, string) + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength_System_Int32_System_Int32_System_String_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L140 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength(System.Int32,System.Int32,System.String) + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength(System.Int32,System.Int32,System.String) +- markdown: Throws an when a destination array is not large enough to contain the polyline data. +- code: >- + [DoesNotReturn] + + public static void ThrowDestinationArrayLengthMustBeEqualOrGreaterThanPolylineLength(int destinationLength, int polylineLength, string paramName) +- h4: Parameters +- parameters: + - name: destinationLength + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: The length of the destination array. + - name: polylineLength + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: The required polyline length. + - name: paramName + type: + - text: string + url: https://learn.microsoft.com/dotnet/api/system.string + description: Name of the parameter representing the destination array. +- api3: ThrowInvalidPolylineBlockTerminator() + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowInvalidPolylineBlockTerminator + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L212 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowInvalidPolylineBlockTerminator + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowInvalidPolylineBlockTerminator +- markdown: Throws an when the polyline block terminator is invalid. +- code: >- + [DoesNotReturn] + + public static void ThrowInvalidPolylineBlockTerminator() +- api3: ThrowInvalidPolylineCharacter(char, int) + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowInvalidPolylineCharacter_System_Char_System_Int32_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L171 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowInvalidPolylineCharacter(System.Char,System.Int32) + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowInvalidPolylineCharacter(System.Char,System.Int32) +- markdown: Throws an when an unexpected character is encountered in the polyline. +- code: >- + [DoesNotReturn] + + public static void ThrowInvalidPolylineCharacter(char character, int position) +- h4: Parameters +- parameters: + - name: character + type: + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + description: The invalid character. + - name: position + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: Position in the polyline where the character was found. +- api3: ThrowInvalidPolylineFormat(long) + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowInvalidPolylineFormat_System_Int64_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L199 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowInvalidPolylineFormat(System.Int64) + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowInvalidPolylineFormat(System.Int64) +- markdown: Throws an when the polyline format is malformed. +- code: >- + [DoesNotReturn] + + public static void ThrowInvalidPolylineFormat(long position) +- h4: Parameters +- parameters: + - name: position + type: + - text: long + url: https://learn.microsoft.com/dotnet/api/system.int64 + description: Approximate position where the polyline became malformed. +- api3: ThrowInvalidPolylineLength(int, int) + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowInvalidPolylineLength_System_Int32_System_Int32_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L156 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowInvalidPolylineLength(System.Int32,System.Int32) + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowInvalidPolylineLength(System.Int32,System.Int32) +- markdown: Throws an when the polyline length is invalid. +- code: >- + [DoesNotReturn] + + public static void ThrowInvalidPolylineLength(int length, int min) +- h4: Parameters +- parameters: + - name: length + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: The invalid length. + - name: min + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: The minimum required length. +- api3: ThrowNotFiniteNumber(string) + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowNotFiniteNumber_System_String_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L37 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowNotFiniteNumber(System.String) + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowNotFiniteNumber(System.String) +- markdown: Throws an when a numeric argument is not a finite value. +- code: >- + [DoesNotReturn] + + public static void ThrowNotFiniteNumber(string paramName) +- h4: Parameters +- parameters: + - name: paramName + type: + - text: string + url: https://learn.microsoft.com/dotnet/api/system.string + description: Name of the parameter that had a non-finite value. +- api3: ThrowPolylineBlockTooLong(int) + id: PolylineAlgorithm_Internal_Diagnostics_ExceptionGuard_ThrowPolylineBlockTooLong_System_Int32_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/Internal/Diagnostics/ExceptionGuard.cs#L185 + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowPolylineBlockTooLong(System.Int32) + commentId: M:PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.ThrowPolylineBlockTooLong(System.Int32) +- markdown: Throws an when a polyline block is longer than allowed. +- code: >- + [DoesNotReturn] + + public static void ThrowPolylineBlockTooLong(int position) +- h4: Parameters +- parameters: + - name: position + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: Position in the polyline where the block exceeded the allowed length. +languageId: csharp +metadata: + description: Centralizes exception throwing for common validation and error scenarios used across the library. diff --git a/api-reference/1.0/PolylineAlgorithm.Internal.Diagnostics.yml b/api-reference/1.0/PolylineAlgorithm.Internal.Diagnostics.yml new file mode 100644 index 00000000..dabb499a --- /dev/null +++ b/api-reference/1.0/PolylineAlgorithm.Internal.Diagnostics.yml @@ -0,0 +1,15 @@ +### YamlMime:ApiPage +title: Namespace PolylineAlgorithm.Internal.Diagnostics +body: +- api1: Namespace PolylineAlgorithm.Internal.Diagnostics + id: PolylineAlgorithm_Internal_Diagnostics + metadata: + uid: PolylineAlgorithm.Internal.Diagnostics + commentId: N:PolylineAlgorithm.Internal.Diagnostics +- h3: Classes +- parameters: + - type: + text: ExceptionGuard + url: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.html + description: Centralizes exception throwing for common validation and error scenarios used across the library. +languageId: csharp diff --git a/api-reference/1.0/PolylineAlgorithm.InvalidPolylineException.yml b/api-reference/1.0/PolylineAlgorithm.InvalidPolylineException.yml new file mode 100644 index 00000000..b776c617 --- /dev/null +++ b/api-reference/1.0/PolylineAlgorithm.InvalidPolylineException.yml @@ -0,0 +1,102 @@ +### YamlMime:ApiPage +title: Class InvalidPolylineException +body: +- api1: Class InvalidPolylineException + id: PolylineAlgorithm_InvalidPolylineException + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/InvalidPolylineException.cs#L17 + metadata: + uid: PolylineAlgorithm.InvalidPolylineException + commentId: T:PolylineAlgorithm.InvalidPolylineException +- facts: + - name: Namespace + value: + text: PolylineAlgorithm + url: PolylineAlgorithm.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: Exception thrown when a polyline is determined to be malformed or invalid during processing. +- code: 'public sealed class InvalidPolylineException : Exception, ISerializable' +- h4: Inheritance +- inheritance: + - text: object + url: https://learn.microsoft.com/dotnet/api/system.object + - text: Exception + url: https://learn.microsoft.com/dotnet/api/system.exception + - text: InvalidPolylineException + url: PolylineAlgorithm.InvalidPolylineException.html +- h4: Implements +- list: + - text: ISerializable + url: https://learn.microsoft.com/dotnet/api/system.runtime.serialization.iserializable +- h4: Inherited Members +- list: + - text: Exception.GetBaseException() + url: https://learn.microsoft.com/dotnet/api/system.exception.getbaseexception + - text: Exception.GetObjectData(SerializationInfo, StreamingContext) + url: https://learn.microsoft.com/dotnet/api/system.exception.getobjectdata + - text: Exception.GetType() + url: https://learn.microsoft.com/dotnet/api/system.exception.gettype + - text: Exception.ToString() + url: https://learn.microsoft.com/dotnet/api/system.exception.tostring + - text: Exception.Data + url: https://learn.microsoft.com/dotnet/api/system.exception.data + - text: Exception.HelpLink + url: https://learn.microsoft.com/dotnet/api/system.exception.helplink + - text: Exception.HResult + url: https://learn.microsoft.com/dotnet/api/system.exception.hresult + - text: Exception.InnerException + url: https://learn.microsoft.com/dotnet/api/system.exception.innerexception + - text: Exception.Message + url: https://learn.microsoft.com/dotnet/api/system.exception.message + - text: Exception.Source + url: https://learn.microsoft.com/dotnet/api/system.exception.source + - text: Exception.StackTrace + url: https://learn.microsoft.com/dotnet/api/system.exception.stacktrace + - text: Exception.TargetSite + url: https://learn.microsoft.com/dotnet/api/system.exception.targetsite + - text: object.Equals(object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) + - text: object.Equals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object) + - text: object.GetHashCode() + url: https://learn.microsoft.com/dotnet/api/system.object.gethashcode + - text: object.GetType() + url: https://learn.microsoft.com/dotnet/api/system.object.gettype + - text: object.ReferenceEquals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.referenceequals + - text: object.ToString() + url: https://learn.microsoft.com/dotnet/api/system.object.tostring +- h2: Remarks +- markdown: This exception is used internally to indicate that a polyline string does not conform to the expected format or contains errors. +- h2: Constructors +- api3: InvalidPolylineException() + id: PolylineAlgorithm_InvalidPolylineException__ctor + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/InvalidPolylineException.cs#L22 + metadata: + uid: PolylineAlgorithm.InvalidPolylineException.#ctor + commentId: M:PolylineAlgorithm.InvalidPolylineException.#ctor +- markdown: Initializes a new instance of the class. +- code: public InvalidPolylineException() +- api3: InvalidPolylineException(string, Exception) + id: PolylineAlgorithm_InvalidPolylineException__ctor_System_String_System_Exception_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/InvalidPolylineException.cs#L43 + metadata: + uid: PolylineAlgorithm.InvalidPolylineException.#ctor(System.String,System.Exception) + commentId: M:PolylineAlgorithm.InvalidPolylineException.#ctor(System.String,System.Exception) +- markdown: Initializes a new instance of the class with a specified error message and a reference to the inner exception that is the cause of this exception. +- code: public InvalidPolylineException(string message, Exception innerException) +- h4: Parameters +- parameters: + - name: message + type: + - text: string + url: https://learn.microsoft.com/dotnet/api/system.string + description: The error message that explains the reason for the exception. + - name: innerException + type: + - text: Exception + url: https://learn.microsoft.com/dotnet/api/system.exception + description: The exception that is the cause of the current exception, or a null reference if no inner exception is specified. +languageId: csharp +metadata: + description: Exception thrown when a polyline is determined to be malformed or invalid during processing. diff --git a/api-reference/1.0/PolylineAlgorithm.PolylineEncoding.yml b/api-reference/1.0/PolylineAlgorithm.PolylineEncoding.yml new file mode 100644 index 00000000..127dbf53 --- /dev/null +++ b/api-reference/1.0/PolylineAlgorithm.PolylineEncoding.yml @@ -0,0 +1,529 @@ +### YamlMime:ApiPage +title: Class PolylineEncoding +body: +- api1: Class PolylineEncoding + id: PolylineAlgorithm_PolylineEncoding + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L23 + metadata: + uid: PolylineAlgorithm.PolylineEncoding + commentId: T:PolylineAlgorithm.PolylineEncoding +- facts: + - name: Namespace + value: + text: PolylineAlgorithm + url: PolylineAlgorithm.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: >- + Provides methods for encoding and decoding polyline data, as well as utilities for normalizing and de-normalizing + + geographic coordinate values. +- code: public static class PolylineEncoding +- h4: Inheritance +- inheritance: + - text: object + url: https://learn.microsoft.com/dotnet/api/system.object + - text: PolylineEncoding + url: PolylineAlgorithm.PolylineEncoding.html +- h4: Inherited Members +- list: + - text: object.Equals(object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) + - text: object.Equals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object) + - text: object.GetHashCode() + url: https://learn.microsoft.com/dotnet/api/system.object.gethashcode + - text: object.GetType() + url: https://learn.microsoft.com/dotnet/api/system.object.gettype + - text: object.MemberwiseClone() + url: https://learn.microsoft.com/dotnet/api/system.object.memberwiseclone + - text: object.ReferenceEquals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.referenceequals + - text: object.ToString() + url: https://learn.microsoft.com/dotnet/api/system.object.tostring +- h2: Remarks +- markdown: >- + The class includes functionality for working with encoded polyline + data, such as reading and writing encoded values, as well as methods for normalizing and de-normalizing geographic + coordinates. It also provides validation utilities to ensure values conform to expected ranges for latitude and + longitude. +- h2: Methods +- api3: Denormalize(int, uint) + id: PolylineAlgorithm_PolylineEncoding_Denormalize_System_Int32_System_UInt32_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L121 + metadata: + uid: PolylineAlgorithm.PolylineEncoding.Denormalize(System.Int32,System.UInt32) + commentId: M:PolylineAlgorithm.PolylineEncoding.Denormalize(System.Int32,System.UInt32) +- markdown: Converts a normalized integer coordinate value back to its floating-point representation based on the specified precision. +- code: public static double Denormalize(int value, uint precision = 5) +- h4: Parameters +- parameters: + - name: value + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: The integer value to denormalize. Typically produced by the method. + - name: precision + type: + - text: uint + url: https://learn.microsoft.com/dotnet/api/system.uint32 + description: The number of decimal places used during normalization. Default is 5, matching standard polyline encoding precision. + optional: true +- h4: Returns +- parameters: + - type: + - text: double + url: https://learn.microsoft.com/dotnet/api/system.double + description: The denormalized floating-point coordinate value. +- h4: Remarks +- markdown: >- +

+ + This method reverses the normalization performed by . It takes an integer value and converts it + + to a double by dividing it by 10 raised to the power of the specified precision. If precision is 0, + + the value is returned as a double without division. + +

+ +

+ + The calculation is performed inside a checked block to ensure that any arithmetic overflow is detected + + and an is thrown. + +

+ +

+ + For example, with a precision of 5: + + +

  • A value of 3778903 becomes 37.78903
  • A value of -12241230 becomes -122.4123
+ +

+ +

+ + If the input value is 0, the method returns 0.0 immediately. + +

+- h4: Exceptions +- parameters: + - type: + - text: OverflowException + url: https://learn.microsoft.com/dotnet/api/system.overflowexception + description: Thrown if the arithmetic operation overflows during conversion. +- api3: GetRequiredBufferSize(int) + id: PolylineAlgorithm_PolylineEncoding_GetRequiredBufferSize_System_Int32_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L297 + metadata: + uid: PolylineAlgorithm.PolylineEncoding.GetRequiredBufferSize(System.Int32) + commentId: M:PolylineAlgorithm.PolylineEncoding.GetRequiredBufferSize(System.Int32) +- markdown: Calculates the number of characters required to encode a delta value in polyline format. +- code: public static int GetRequiredBufferSize(int delta) +- h4: Parameters +- parameters: + - name: delta + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: >- + The integer delta value to calculate the encoded size for. This value typically represents the difference between + + consecutive coordinate values in polyline encoding. +- h4: Returns +- parameters: + - type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: The number of characters required to encode the specified delta value. The minimum return value is 1. +- h4: Remarks +- markdown: >- +

+ + This method determines how many characters will be needed to represent an integer delta value when encoded + + using the polyline encoding algorithm. It performs the same zigzag encoding transformation as + + but only calculates the required buffer size without actually writing any data. + +

+ +

+ + The calculation process: + + +

  1. Applies zigzag encoding: left-shifts the value by 1 bit, then inverts all bits if the original value was negative
  2. Counts how many 5-bit chunks are needed to represent the encoded value
  3. Each chunk requires one character, with a minimum of 1 character for any value
+ +

+ +

+ + This method is useful for pre-allocating buffers of the correct size before encoding polyline data, helping to avoid + + buffer overflow checks during the actual encoding process. + +

+ +

+ + The method uses a long internally to prevent overflow during the left-shift operation on large negative values. + +

+- h4: See Also +- list: + - - text: PolylineEncoding + url: PolylineAlgorithm.PolylineEncoding.html + - . + - text: TryWriteValue + url: PolylineAlgorithm.PolylineEncoding.html#PolylineAlgorithm_PolylineEncoding_TryWriteValue_System_Int32_System_Span_System_Char__System_Int32__ + - ( + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + - ',' + - " " + - text: Span + url: https://learn.microsoft.com/dotnet/api/system.span-1 + - < + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + - '>' + - ',' + - " " + - ref + - " " + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + - ) +- api3: Normalize(double, uint) + id: PolylineAlgorithm_PolylineEncoding_Normalize_System_Double_System_UInt32_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L61 + metadata: + uid: PolylineAlgorithm.PolylineEncoding.Normalize(System.Double,System.UInt32) + commentId: M:PolylineAlgorithm.PolylineEncoding.Normalize(System.Double,System.UInt32) +- markdown: Normalizes a geographic coordinate value to an integer representation based on the specified precision. +- code: public static int Normalize(double value, uint precision = 5) +- h4: Parameters +- parameters: + - name: value + type: + - text: double + url: https://learn.microsoft.com/dotnet/api/system.double + description: The numeric value to normalize. Must be a finite number (not NaN or infinity). + - name: precision + type: + - text: uint + url: https://learn.microsoft.com/dotnet/api/system.uint32 + description: >- + The number of decimal places of precision to preserve in the normalized value. + + The value is multiplied by 10^precision before rounding. + + Default is 5, which is standard for polyline encoding. + optional: true +- h4: Returns +- parameters: + - type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: An integer representing the normalized value. Returns 0 if the input value is 0.0. +- h4: Remarks +- markdown: >- +

+ + This method converts a floating-point coordinate value into a normalized integer by multiplying it by 10 raised + + to the power of the specified precision, then truncating the result to an integer. + +

+ +

+ + For example, with the default precision of 5: + + +

  • A value of 37.78903 becomes 3778903
  • A value of -122.4123 becomes -12241230
+ +

+ +

+ + The method validates that the input value is finite (not NaN or infinity) before performing normalization. + + If the precision is 0, the value is rounded without multiplication. + +

+- h4: Exceptions +- parameters: + - type: + - text: ArgumentOutOfRangeException + url: https://learn.microsoft.com/dotnet/api/system.argumentoutofrangeexception + description: Thrown when value is not a finite number (NaN or infinity). + - type: + - text: OverflowException + url: https://learn.microsoft.com/dotnet/api/system.overflowexception + description: Thrown when the normalized result exceeds the range of a 32-bit signed integer during the conversion from double to int. +- api3: TryReadValue(ref int, ReadOnlyMemory, ref int) + id: PolylineAlgorithm_PolylineEncoding_TryReadValue_System_Int32__System_ReadOnlyMemory_System_Char__System_Int32__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L168 + metadata: + uid: PolylineAlgorithm.PolylineEncoding.TryReadValue(System.Int32@,System.ReadOnlyMemory{System.Char},System.Int32@) + commentId: M:PolylineAlgorithm.PolylineEncoding.TryReadValue(System.Int32@,System.ReadOnlyMemory{System.Char},System.Int32@) +- markdown: Attempts to read an encoded integer value from a polyline buffer, updating the specified delta and position. +- code: public static bool TryReadValue(ref int delta, ReadOnlyMemory buffer, ref int position) +- h4: Parameters +- parameters: + - name: delta + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: Reference to the integer accumulator that will be updated with the decoded value. + - name: buffer + type: + - text: ReadOnlyMemory + url: https://learn.microsoft.com/dotnet/api/system.readonlymemory-1 + - < + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + - '>' + description: The buffer containing polyline-encoded characters. + - name: position + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: Reference to the current position in the buffer. This value is updated as characters are read. +- h4: Returns +- parameters: + - type: + - text: bool + url: https://learn.microsoft.com/dotnet/api/system.boolean + description: true if a value was successfully read and decoded; false if the buffer ended before a complete value was read. +- h4: Remarks +- markdown: >- +

+ + This method decodes a value from a polyline-encoded character buffer, starting at the given position. It reads + + characters sequentially, applying the polyline decoding algorithm, and updates the delta with + + the decoded value. The position is advanced as characters are processed. + +

+ +

+ + The decoding process continues until a character with a value less than the algorithm's space constant is encountered, + + which signals the end of the encoded value. If the buffer is exhausted before a complete value is read, the method returns false. + +

+ +

+ + The decoded value is added to delta using zigzag decoding, which handles both positive and negative values. + +

+- api3: TryWriteValue(int, Span, ref int) + id: PolylineAlgorithm_PolylineEncoding_TryWriteValue_System_Int32_System_Span_System_Char__System_Int32__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L236 + metadata: + uid: PolylineAlgorithm.PolylineEncoding.TryWriteValue(System.Int32,System.Span{System.Char},System.Int32@) + commentId: M:PolylineAlgorithm.PolylineEncoding.TryWriteValue(System.Int32,System.Span{System.Char},System.Int32@) +- markdown: Attempts to write an encoded integer value to a polyline buffer, updating the specified position. +- code: public static bool TryWriteValue(int delta, Span buffer, ref int position) +- h4: Parameters +- parameters: + - name: delta + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: >- + The integer value to encode and write to the buffer. This value typically represents the difference between consecutive + + coordinate values in polyline encoding. + - name: buffer + type: + - text: Span + url: https://learn.microsoft.com/dotnet/api/system.span-1 + - < + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + - '>' + description: The destination buffer where the encoded characters will be written. Must have sufficient capacity to hold the encoded value. + - name: position + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: >- + Reference to the current position in the buffer. This value is updated as characters are written to reflect the new position + + after encoding is complete. +- h4: Returns +- parameters: + - type: + - text: bool + url: https://learn.microsoft.com/dotnet/api/system.boolean + description: >- + true if the value was successfully encoded and written to the buffer; false if the buffer + + does not have sufficient remaining capacity to hold the encoded value. +- h4: Remarks +- markdown: >- +

+ + This method encodes an integer delta value into a polyline-encoded format and writes it to the provided character buffer, + + starting at the given position. It applies zigzag encoding followed by the polyline encoding algorithm to represent + + both positive and negative values efficiently. + +

+ +

+ + The encoding process first converts the value using zigzag encoding (left shift by 1, with bitwise inversion for negative values), + + then writes it as a sequence of characters. Each character encodes 5 bits of data, with continuation bits indicating whether + + more characters follow. The position is advanced as characters are written. + +

+ +

+ + Before writing, the method validates that sufficient space is available in the buffer by calling . + + If the buffer does not have enough remaining capacity, the method returns false without modifying the buffer or position. + +

+ +

+ + This method is the inverse of and can be used to encode coordinate deltas for polyline serialization. + +

+- api3: ValidateBlockLength(ReadOnlySpan) + id: PolylineAlgorithm_PolylineEncoding_ValidateBlockLength_System_ReadOnlySpan_System_Char__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L437 + metadata: + uid: PolylineAlgorithm.PolylineEncoding.ValidateBlockLength(System.ReadOnlySpan{System.Char}) + commentId: M:PolylineAlgorithm.PolylineEncoding.ValidateBlockLength(System.ReadOnlySpan{System.Char}) +- markdown: Validates the block structure of a polyline segment, ensuring each encoded value does not exceed 7 characters and the polyline ends correctly. +- code: public static void ValidateBlockLength(ReadOnlySpan polyline) +- h4: Parameters +- parameters: + - name: polyline + type: + - text: ReadOnlySpan + url: https://learn.microsoft.com/dotnet/api/system.readonlyspan-1 + - < + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + - '>' + description: A span representing the polyline segment to validate. +- h4: Remarks +- markdown: >- +

+ + Iterates through the polyline, counting the length of each block (a sequence of characters representing an encoded value). + + Throws an if any block exceeds 7 characters or if the polyline does not end with a valid block terminator. + +

+- h4: Exceptions +- parameters: + - type: + - text: ArgumentException + url: https://learn.microsoft.com/dotnet/api/system.argumentexception + description: Thrown when a block exceeds 7 characters or the polyline does not end with a valid block terminator. +- api3: ValidateCharRange(ReadOnlySpan) + id: PolylineAlgorithm_PolylineEncoding_ValidateCharRange_System_ReadOnlySpan_System_Char__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L391 + metadata: + uid: PolylineAlgorithm.PolylineEncoding.ValidateCharRange(System.ReadOnlySpan{System.Char}) + commentId: M:PolylineAlgorithm.PolylineEncoding.ValidateCharRange(System.ReadOnlySpan{System.Char}) +- markdown: Validates that all characters in the polyline segment are within the allowed ASCII range for polyline encoding. +- code: public static void ValidateCharRange(ReadOnlySpan polyline) +- h4: Parameters +- parameters: + - name: polyline + type: + - text: ReadOnlySpan + url: https://learn.microsoft.com/dotnet/api/system.readonlyspan-1 + - < + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + - '>' + description: A span representing the polyline segment to validate. +- h4: Remarks +- markdown: >- +

+ + Uses SIMD vectorization for efficient validation of large spans. Falls back to scalar checks for any block where an invalid character is detected. + +

+ +

+ + The valid range is from '?' (63) to '_' (95), inclusive. If an invalid character is found, an is thrown. + +

+- h4: Exceptions +- parameters: + - type: + - text: ArgumentException + url: https://learn.microsoft.com/dotnet/api/system.argumentexception + description: Thrown when an invalid character is found in the polyline segment. +- api3: ValidateFormat(ReadOnlySpan) + id: PolylineAlgorithm_PolylineEncoding_ValidateFormat_System_ReadOnlySpan_System_Char__ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncoding.cs#L369 + metadata: + uid: PolylineAlgorithm.PolylineEncoding.ValidateFormat(System.ReadOnlySpan{System.Char}) + commentId: M:PolylineAlgorithm.PolylineEncoding.ValidateFormat(System.ReadOnlySpan{System.Char}) +- markdown: Validates the format of a polyline segment, ensuring all characters are valid and block structure is correct. +- code: public static void ValidateFormat(ReadOnlySpan polyline) +- h4: Parameters +- parameters: + - name: polyline + type: + - text: ReadOnlySpan + url: https://learn.microsoft.com/dotnet/api/system.readonlyspan-1 + - < + - text: char + url: https://learn.microsoft.com/dotnet/api/system.char + - '>' + description: A span representing the polyline segment to validate. +- h4: Remarks +- markdown: >- +

+ + This method performs two levels of validation on the provided polyline segment: + +

+ +
  1. + Character Range Validation: Checks that every character in the polyline is within the valid ASCII range for polyline encoding ('?' [63] to '_' [95], inclusive). + Uses SIMD acceleration for efficient validation of large segments. +
  2. + Block Structure Validation: Ensures that each encoded value (block) does not exceed 7 characters and that the polyline ends with a valid block terminator. +
+

+ + If an invalid character or block structure is detected, an is thrown with details about the error. + +

+- h4: Exceptions +- parameters: + - type: + - text: ArgumentException + url: https://learn.microsoft.com/dotnet/api/system.argumentexception + description: Thrown when an invalid character is found or the block structure is invalid. +languageId: csharp +metadata: + description: >- + Provides methods for encoding and decoding polyline data, as well as utilities for normalizing and de-normalizing + + geographic coordinate values. diff --git a/api-reference/1.0/PolylineAlgorithm.PolylineEncodingOptions.yml b/api-reference/1.0/PolylineAlgorithm.PolylineEncodingOptions.yml new file mode 100644 index 00000000..2e3880ce --- /dev/null +++ b/api-reference/1.0/PolylineAlgorithm.PolylineEncodingOptions.yml @@ -0,0 +1,127 @@ +### YamlMime:ApiPage +title: Class PolylineEncodingOptions +body: +- api1: Class PolylineEncodingOptions + id: PolylineAlgorithm_PolylineEncodingOptions + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncodingOptions.cs#L29 + metadata: + uid: PolylineAlgorithm.PolylineEncodingOptions + commentId: T:PolylineAlgorithm.PolylineEncodingOptions +- facts: + - name: Namespace + value: + text: PolylineAlgorithm + url: PolylineAlgorithm.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: Provides configuration options for polyline encoding operations. +- code: public sealed class PolylineEncodingOptions +- h4: Inheritance +- inheritance: + - text: object + url: https://learn.microsoft.com/dotnet/api/system.object + - text: PolylineEncodingOptions + url: PolylineAlgorithm.PolylineEncodingOptions.html +- h4: Inherited Members +- list: + - text: object.Equals(object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) + - text: object.Equals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object) + - text: object.GetHashCode() + url: https://learn.microsoft.com/dotnet/api/system.object.gethashcode + - text: object.GetType() + url: https://learn.microsoft.com/dotnet/api/system.object.gettype + - text: object.ReferenceEquals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.referenceequals + - text: object.ToString() + url: https://learn.microsoft.com/dotnet/api/system.object.tostring +- h2: Remarks +- markdown: >- +

+ + This class allows you to configure various aspects of polyline encoding, including: + +

+ +
  • The level for coordinate encoding
  • The for memory allocation strategy
  • The for diagnostic logging
+ +

+ + All properties have internal setters and should be configured through a builder or factory pattern. + +

+- h2: Properties +- api3: LoggerFactory + id: PolylineAlgorithm_PolylineEncodingOptions_LoggerFactory + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncodingOptions.cs#L41 + metadata: + uid: PolylineAlgorithm.PolylineEncodingOptions.LoggerFactory + commentId: P:PolylineAlgorithm.PolylineEncodingOptions.LoggerFactory +- markdown: Gets the logger factory used for diagnostic logging during encoding operations. +- code: public ILoggerFactory LoggerFactory { get; } +- h4: Property Value +- parameters: + - type: + - text: ILoggerFactory + url: https://learn.microsoft.com/dotnet/api/microsoft.extensions.logging.iloggerfactory +- h4: Remarks +- markdown: >- + The default logger factory is , which does not log any messages. + + To enable logging, provide a custom implementation. +- api3: Precision + id: PolylineAlgorithm_PolylineEncodingOptions_Precision + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncodingOptions.cs#L60 + metadata: + uid: PolylineAlgorithm.PolylineEncodingOptions.Precision + commentId: P:PolylineAlgorithm.PolylineEncodingOptions.Precision +- markdown: Gets the precision level used for encoding coordinate values. +- code: public uint Precision { get; } +- h4: Property Value +- parameters: + - type: + - text: uint + url: https://learn.microsoft.com/dotnet/api/system.uint32 +- h4: Remarks +- markdown: >- +

+ + The precision determines the number of decimal places to which each coordinate value (latitude or longitude) + + is multiplied and truncated (not rounded) before encoding. For example, a precision of 5 means each coordinate is multiplied by 10^5 + + and truncated to an integer before encoding. + +

+ +

+ + This setting does not directly correspond to a physical distance or accuracy in meters, but rather controls + + the granularity of the encoded values. + +

+- api3: StackAllocLimit + id: PolylineAlgorithm_PolylineEncodingOptions_StackAllocLimit + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncodingOptions.cs#L73 + metadata: + uid: PolylineAlgorithm.PolylineEncodingOptions.StackAllocLimit + commentId: P:PolylineAlgorithm.PolylineEncodingOptions.StackAllocLimit +- markdown: Gets the maximum buffer size (in characters) that can be allocated on the stack for encoding operations. +- code: public int StackAllocLimit { get; } +- h4: Property Value +- parameters: + - type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 +- h4: Remarks +- markdown: >- + When the required buffer size for encoding exceeds this limit, memory will be allocated on the heap instead of the stack. + + This setting specifically applies to stack allocation of character arrays (stackalloc char[]) used during polyline encoding, + + balancing performance and stack safety. +languageId: csharp +metadata: + description: Provides configuration options for polyline encoding operations. diff --git a/api-reference/1.0/PolylineAlgorithm.PolylineEncodingOptionsBuilder.yml b/api-reference/1.0/PolylineAlgorithm.PolylineEncodingOptionsBuilder.yml new file mode 100644 index 00000000..92e6d942 --- /dev/null +++ b/api-reference/1.0/PolylineAlgorithm.PolylineEncodingOptionsBuilder.yml @@ -0,0 +1,141 @@ +### YamlMime:ApiPage +title: Class PolylineEncodingOptionsBuilder +body: +- api1: Class PolylineEncodingOptionsBuilder + id: PolylineAlgorithm_PolylineEncodingOptionsBuilder + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs#L15 + metadata: + uid: PolylineAlgorithm.PolylineEncodingOptionsBuilder + commentId: T:PolylineAlgorithm.PolylineEncodingOptionsBuilder +- facts: + - name: Namespace + value: + text: PolylineAlgorithm + url: PolylineAlgorithm.html + - name: Assembly + value: PolylineAlgorithm.dll +- markdown: Provides a builder for configuring options for polyline encoding operations. +- code: public sealed class PolylineEncodingOptionsBuilder +- h4: Inheritance +- inheritance: + - text: object + url: https://learn.microsoft.com/dotnet/api/system.object + - text: PolylineEncodingOptionsBuilder + url: PolylineAlgorithm.PolylineEncodingOptionsBuilder.html +- h4: Inherited Members +- list: + - text: object.Equals(object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object) + - text: object.Equals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.equals#system-object-equals(system-object-system-object) + - text: object.GetHashCode() + url: https://learn.microsoft.com/dotnet/api/system.object.gethashcode + - text: object.GetType() + url: https://learn.microsoft.com/dotnet/api/system.object.gettype + - text: object.ReferenceEquals(object, object) + url: https://learn.microsoft.com/dotnet/api/system.object.referenceequals + - text: object.ToString() + url: https://learn.microsoft.com/dotnet/api/system.object.tostring +- h2: Methods +- api3: Build() + id: PolylineAlgorithm_PolylineEncodingOptionsBuilder_Build + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs#L38 + metadata: + uid: PolylineAlgorithm.PolylineEncodingOptionsBuilder.Build + commentId: M:PolylineAlgorithm.PolylineEncodingOptionsBuilder.Build +- markdown: Builds a new instance using the configured options. +- code: public PolylineEncodingOptions Build() +- h4: Returns +- parameters: + - type: + - text: PolylineEncodingOptions + url: PolylineAlgorithm.PolylineEncodingOptions.html + description: A configured instance. +- api3: Create() + id: PolylineAlgorithm_PolylineEncodingOptionsBuilder_Create + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs#L28 + metadata: + uid: PolylineAlgorithm.PolylineEncodingOptionsBuilder.Create + commentId: M:PolylineAlgorithm.PolylineEncodingOptionsBuilder.Create +- markdown: Creates a new instance for the specified coordinate type. +- code: public static PolylineEncodingOptionsBuilder Create() +- h4: Returns +- parameters: + - type: + - text: PolylineEncodingOptionsBuilder + url: PolylineAlgorithm.PolylineEncodingOptionsBuilder.html + description: An instance for configuring polyline encoding options. +- api3: WithLoggerFactory(ILoggerFactory) + id: PolylineAlgorithm_PolylineEncodingOptionsBuilder_WithLoggerFactory_Microsoft_Extensions_Logging_ILoggerFactory_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs#L97 + metadata: + uid: PolylineAlgorithm.PolylineEncodingOptionsBuilder.WithLoggerFactory(Microsoft.Extensions.Logging.ILoggerFactory) + commentId: M:PolylineAlgorithm.PolylineEncodingOptionsBuilder.WithLoggerFactory(Microsoft.Extensions.Logging.ILoggerFactory) +- markdown: Configures the to be used for logging during polyline encoding operations. +- code: public PolylineEncodingOptionsBuilder WithLoggerFactory(ILoggerFactory loggerFactory) +- h4: Parameters +- parameters: + - name: loggerFactory + type: + - text: ILoggerFactory + url: https://learn.microsoft.com/dotnet/api/microsoft.extensions.logging.iloggerfactory + description: The instance to use for logging. If null, a will be used instead. +- h4: Returns +- parameters: + - type: + - text: PolylineEncodingOptionsBuilder + url: PolylineAlgorithm.PolylineEncodingOptionsBuilder.html + description: The current instance for method chaining. +- api3: WithPrecision(uint) + id: PolylineAlgorithm_PolylineEncodingOptionsBuilder_WithPrecision_System_UInt32_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs#L82 + metadata: + uid: PolylineAlgorithm.PolylineEncodingOptionsBuilder.WithPrecision(System.UInt32) + commentId: M:PolylineAlgorithm.PolylineEncodingOptionsBuilder.WithPrecision(System.UInt32) +- markdown: Sets the coordinate encoding precision. +- code: public PolylineEncodingOptionsBuilder WithPrecision(uint precision) +- h4: Parameters +- parameters: + - name: precision + type: + - text: uint + url: https://learn.microsoft.com/dotnet/api/system.uint32 + description: The number of decimal places to use for encoding coordinate values. Default is 5. +- h4: Returns +- parameters: + - type: + - text: PolylineEncodingOptionsBuilder + url: PolylineAlgorithm.PolylineEncodingOptionsBuilder.html + description: The current instance for method chaining. +- api3: WithStackAllocLimit(int) + id: PolylineAlgorithm_PolylineEncodingOptionsBuilder_WithStackAllocLimit_System_Int32_ + src: https://github.com/petesramek/polyline-algorithm-csharp/blob/develop/1.0/src/PolylineAlgorithm/PolylineEncodingOptionsBuilder.cs#L61 + metadata: + uid: PolylineAlgorithm.PolylineEncodingOptionsBuilder.WithStackAllocLimit(System.Int32) + commentId: M:PolylineAlgorithm.PolylineEncodingOptionsBuilder.WithStackAllocLimit(System.Int32) +- markdown: Configures the buffer size used for stack allocation during polyline encoding operations. +- code: public PolylineEncodingOptionsBuilder WithStackAllocLimit(int stackAllocLimit) +- h4: Parameters +- parameters: + - name: stackAllocLimit + type: + - text: int + url: https://learn.microsoft.com/dotnet/api/system.int32 + description: The maximum buffer size to use for stack allocation. Must be greater than or equal to 1. +- h4: Returns +- parameters: + - type: + - text: PolylineEncodingOptionsBuilder + url: PolylineAlgorithm.PolylineEncodingOptionsBuilder.html + description: The current instance for method chaining. +- h4: Remarks +- markdown: This method allows customization of the internal buffer size for encoding, which can impact performance and memory usage. +- h4: Exceptions +- parameters: + - type: + - text: ArgumentOutOfRangeException + url: https://learn.microsoft.com/dotnet/api/system.argumentoutofrangeexception + description: Thrown if stackAllocLimit is less than 1. +languageId: csharp +metadata: + description: Provides a builder for configuring options for polyline encoding operations. diff --git a/api-reference/1.0/PolylineAlgorithm.yml b/api-reference/1.0/PolylineAlgorithm.yml new file mode 100644 index 00000000..b60dc3c0 --- /dev/null +++ b/api-reference/1.0/PolylineAlgorithm.yml @@ -0,0 +1,38 @@ +### YamlMime:ApiPage +title: Namespace PolylineAlgorithm +body: +- api1: Namespace PolylineAlgorithm + id: PolylineAlgorithm + metadata: + uid: PolylineAlgorithm + commentId: N:PolylineAlgorithm +- h3: Namespaces +- parameters: + - type: + text: PolylineAlgorithm.Abstraction + url: PolylineAlgorithm.Abstraction.html + - type: + text: PolylineAlgorithm.Extensions + url: PolylineAlgorithm.Extensions.html +- h3: Classes +- parameters: + - type: + text: InvalidPolylineException + url: PolylineAlgorithm.InvalidPolylineException.html + description: Exception thrown when a polyline is determined to be malformed or invalid during processing. + - type: + text: PolylineEncoding + url: PolylineAlgorithm.PolylineEncoding.html + description: >- + Provides methods for encoding and decoding polyline data, as well as utilities for normalizing and de-normalizing + + geographic coordinate values. + - type: + text: PolylineEncodingOptions + url: PolylineAlgorithm.PolylineEncodingOptions.html + description: Provides configuration options for polyline encoding operations. + - type: + text: PolylineEncodingOptionsBuilder + url: PolylineAlgorithm.PolylineEncodingOptionsBuilder.html + description: Provides a builder for configuring options for polyline encoding operations. +languageId: csharp diff --git a/api-reference/1.0/toc.yml b/api-reference/1.0/toc.yml new file mode 100644 index 00000000..290b7c63 --- /dev/null +++ b/api-reference/1.0/toc.yml @@ -0,0 +1,40 @@ +### YamlMime:TableOfContent +- name: PolylineAlgorithm + href: PolylineAlgorithm.yml + items: + - name: Classes + - name: InvalidPolylineException + href: PolylineAlgorithm.InvalidPolylineException.yml + - name: PolylineEncoding + href: PolylineAlgorithm.PolylineEncoding.yml + - name: PolylineEncodingOptions + href: PolylineAlgorithm.PolylineEncodingOptions.yml + - name: PolylineEncodingOptionsBuilder + href: PolylineAlgorithm.PolylineEncodingOptionsBuilder.yml +- name: PolylineAlgorithm.Abstraction + href: PolylineAlgorithm.Abstraction.yml + items: + - name: Classes + - name: AbstractPolylineDecoder + href: PolylineAlgorithm.Abstraction.AbstractPolylineDecoder-2.yml + - name: AbstractPolylineEncoder + href: PolylineAlgorithm.Abstraction.AbstractPolylineEncoder-2.yml + - name: Interfaces + - name: IPolylineDecoder + href: PolylineAlgorithm.Abstraction.IPolylineDecoder-2.yml + - name: IPolylineEncoder + href: PolylineAlgorithm.Abstraction.IPolylineEncoder-2.yml +- name: PolylineAlgorithm.Extensions + href: PolylineAlgorithm.Extensions.yml + items: + - name: Classes + - name: PolylineDecoderExtensions + href: PolylineAlgorithm.Extensions.PolylineDecoderExtensions.yml + - name: PolylineEncoderExtensions + href: PolylineAlgorithm.Extensions.PolylineEncoderExtensions.yml +- name: PolylineAlgorithm.Internal.Diagnostics + href: PolylineAlgorithm.Internal.Diagnostics.yml + items: + - name: Classes + - name: ExceptionGuard + href: PolylineAlgorithm.Internal.Diagnostics.ExceptionGuard.yml diff --git a/api-reference/api-reference.json b/api-reference/api-reference.json new file mode 100644 index 00000000..c5797921 --- /dev/null +++ b/api-reference/api-reference.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/docfx/main/schemas/docfx.schema.json", + "build": { + "xref": [ + "https://learn.microsoft.com/en-us/dotnet/.xrefmap.json" + ], + "content": [ + { + "files": [ + "index.md", + "toc.yml", + "guide/*.{md,yml}" + ], + "exclude": [ + "_docs/**" + ] + } + ], + "resource": [ + { + "files": [ + "media/**", + "versions.json" + ] + } + ], + "output": "_docs", + "template": [ + "default", + "modern", + "docs-versioning" + ], + "maxParallelism": 1, + "globalMetadata": { + "_appName": "PolylineAlgorithm for .NET", + "_appTitle": "PolylineAlgorithm for .NET", + "_enableSearch": true, + "_enableNewTab": true, + "_disableContribution": false, + "pdf": false + } + } +} \ No newline at end of file diff --git a/api-reference/assembly-metadata.json b/api-reference/assembly-metadata.json new file mode 100644 index 00000000..0416ceed --- /dev/null +++ b/api-reference/assembly-metadata.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://raw.githubusercontent.com/dotnet/docfx/main/schemas/docfx.schema.json", + "metadata": [ + { + "src": [ + { + "src": "../src", + "files": [ + "**/*.csproj" + ] + } + ], + "dest": "temp", + "outputFormat": "apiPage" + } + ] +} \ No newline at end of file diff --git a/api-reference/docs-versioning/layout/_master.tmpl b/api-reference/docs-versioning/layout/_master.tmpl new file mode 100644 index 00000000..68043875 --- /dev/null +++ b/api-reference/docs-versioning/layout/_master.tmpl @@ -0,0 +1,163 @@ +{{!Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license.}} +{{!include(/^public/.*/)}} +{{!include(favicon.ico)}} +{{!include(logo.svg)}} + + + + + {{#redirect_url}} + + {{/redirect_url}} + {{^redirect_url}} + {{#title}}{{title}}{{/title}}{{^title}}{{>partials/title}}{{/title}} {{#_appTitle}}| {{_appTitle}} {{/_appTitle}} + + + {{#_description}}{{/_description}} + {{#description}}{{/description}} + + + + + + {{#_noindex}}{{/_noindex}} + + {{#_disableNewTab}}{{/_disableNewTab}} + {{#_disableTocFilter}}{{/_disableTocFilter}} + {{#docurl}}{{/docurl}} + + + + + + + + + + + + + + + + + + {{#_googleAnalyticsTagId}} + + + {{/_googleAnalyticsTagId}} + {{/redirect_url}} + + + {{^redirect_url}} + +
+ {{^_disableNavbar}} + + {{/_disableNavbar}} +
+ +
+ {{^_disableToc}} +
+
+
+
Table of Contents
+ +
+
+ +
+
+
+ {{/_disableToc}} + +
+
+ {{^_disableToc}} + + {{/_disableToc}} + + {{^_disableBreadcrumb}} + + {{/_disableBreadcrumb}} +
+ +
+ {{!body}} +
+ + {{^_disableContribution}} +
+ {{#sourceurl}} + {{__global.improveThisDoc}} + {{/sourceurl}} + {{^sourceurl}}{{#docurl}} + {{__global.improveThisDoc}} + {{/docurl}}{{/sourceurl}} +
+ {{/_disableContribution}} + + {{^_disableNextArticle}} + + {{/_disableNextArticle}} + +
+ + {{^_disableAffix}} +
+ +
+ {{/_disableAffix}} +
+ + {{#_enableSearch}} +
+ {{/_enableSearch}} + +
+
+
+ {{{_appFooter}}}{{^_appFooter}}Made with docfx{{/_appFooter}} +
+
+
+ + + {{/redirect_url}} + diff --git a/api-reference/docs-versioning/public/version-switcher.js b/api-reference/docs-versioning/public/version-switcher.js new file mode 100644 index 00000000..325a0531 --- /dev/null +++ b/api-reference/docs-versioning/public/version-switcher.js @@ -0,0 +1,86 @@ +(function () { + 'use strict'; + + function getVersionFromPath(pathname) { + var match = pathname.match(/\/(\d+\.\d+)\//); + return match ? match[1] : null; + } + + function getPathAfterVersion(pathname) { + var match = pathname.match(/\/\d+\.\d+\/(.*)/); + return match ? match[1] : ''; + } + + function getSiteRoot() { + var meta = document.querySelector('meta[name="docfx:rel"]'); + return meta ? meta.getAttribute('content') : './'; + } + + function initVersionPicker(versions, latest) { + var select = document.getElementById('version-picker'); + if (!select) return; + + var currentVersion = getVersionFromPath(window.location.pathname); + var relativePath = getPathAfterVersion(window.location.pathname); + + versions.forEach(function (v) { + var option = document.createElement('option'); + option.value = v; + option.textContent = v === latest ? 'v' + v + ' (latest)' : 'v' + v; + if (v === currentVersion) { + option.selected = true; + } + select.appendChild(option); + }); + + if (!currentVersion) { + var placeholder = document.createElement('option'); + placeholder.value = ''; + placeholder.textContent = 'Select version'; + placeholder.selected = true; + placeholder.disabled = true; + select.insertBefore(placeholder, select.firstChild); + } + + select.addEventListener('change', function () { + var targetVersion = select.value; + if (!targetVersion) return; + + var newPathname; + if (currentVersion && relativePath) { + newPathname = window.location.pathname.replace( + '/' + currentVersion + '/', + '/' + targetVersion + '/' + ); + } else { + newPathname = window.location.pathname.replace(/\/$/, '') + '/' + targetVersion + '/'; + } + + window.location.pathname = newPathname; + }); + } + + function loadVersions() { + var root = getSiteRoot(); + var url = root + 'versions.json'; + + fetch(url) + .then(function (r) { + if (!r.ok) throw new Error('versions.json not found'); + return r.json(); + }) + .then(function (data) { + initVersionPicker(data.versions, data.latest); + }) + .catch(function () { + var container = document.getElementById('version-picker-container'); + if (container) container.style.display = 'none'; + }); + } + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', loadVersions); + } else { + loadVersions(); + } +}()); diff --git a/api-reference/favicon.ico b/api-reference/favicon.ico new file mode 100644 index 00000000..eb57e8d7 Binary files /dev/null and b/api-reference/favicon.ico differ diff --git a/api-reference/guide/advanced-scenarios.md b/api-reference/guide/advanced-scenarios.md new file mode 100644 index 00000000..7414d002 --- /dev/null +++ b/api-reference/guide/advanced-scenarios.md @@ -0,0 +1,168 @@ +# Advanced Usage + +PolylineAlgorithm is designed for extensibility and integration with advanced .NET scenarios. +This guide covers custom types, integrations, and best practices for power users. + +--- + +## Custom Coordinate and Polyline Types + +You can encode and decode custom coordinate or polyline representations by extending the abstract base classes: + +- `AbstractPolylineEncoder` +- `AbstractPolylineDecoder` + +### Example: Custom Encoder + +```csharp +public sealed class MyPolylineEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> +{ + public MyPolylineEncoder() : base() { } + + public MyPolylineEncoder(PolylineEncodingOptions options) + : base(options) { } + + protected override double GetLatitude((double Latitude, double Longitude) coordinate) + => coordinate.Latitude; + + protected override double GetLongitude((double Latitude, double Longitude) coordinate) + => coordinate.Longitude; + + protected override string CreatePolyline(ReadOnlyMemory polyline) + => polyline.ToString(); +} +``` + +--- + +## Example: Custom Decoder + +```csharp +public sealed class MyPolylineDecoder : AbstractPolylineDecoder +{ + public MyPolylineDecoder() : base() { } + + public MyPolylineDecoder(PolylineEncodingOptions options) + : base(options) { } + + protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) + => (latitude, longitude); + + protected override ReadOnlyMemory GetReadOnlyMemory(ref string polyline) + => polyline.AsMemory(); +} +``` + +--- + +# Registering Custom Encoder and Decoder with `IServiceCollection` + +For ASP.NET Core or DI-enabled .NET applications, you can easily register your custom polyline encoder and decoder as services with `IServiceCollection` by defining an extension method. This enables constructor injection and central DI management. + +--- + +## Example: Register Custom Polyline Encoder/Decoder + +Suppose you have the following custom encoder and decoder (see [Advanced Usage](./advanced.md)): + +```csharp +public sealed class MyPolylineEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> +{ + public MyPolylineEncoder(PolylineEncodingOptions options = null) + : base(options) { } + + // ... override required members ... +} + +public sealed class MyPolylineDecoder : AbstractPolylineDecoder +{ + public MyPolylineDecoder(PolylineEncodingOptions options = null) + : base(options) { } + + // ... override required members ... +} +``` + +--- + +## IServiceCollection Extension Method + +```csharp +using Microsoft.Extensions.DependencyInjection; +using PolylineAlgorithm; + +public static class PolylineServiceCollectionExtensions +{ + public static IServiceCollection AddMyPolylineEncoderDecoder( + this IServiceCollection services, + PolylineEncodingOptions options = null) + { + // Register encoder and decoder as singletons (adjust lifetime as needed) + services.AddSingleton>( + _ => new MyPolylineEncoder(options)); + services.AddSingleton>( + _ => new MyPolylineDecoder(options)); + return services; + } +} +``` + +--- + +## Usage + +In your application startup (e.g., `Program.cs` or `Startup.cs`): + +```csharp +using PolylineAlgorithm; + +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddMyPolylineEncoderDecoder( + PolylineEncodingOptionsBuilder.Create() + .WithStackAllocLimit(1024) + .Build() +); + +// Now you can inject IPolylineEncoder<(double, double), string> and IPolylineDecoder +``` + +--- + +## Benefits + +- **Central DI management** for polyline components +- Plug-and-play integration with ASP.NET Core and modern .NET project styles +- Easily swap out or configure encoders/decoders for different environments + +--- + +> **Tip:** +> You can generalize the extension method for different encoder/decoder types or include multiple algorithms by adding extra parameters. + +--- + +## Integration Guidance + +- **Batch or incremental processing:** + For large datasets, control the stack allocation limit via `PolylineEncodingOptions.StackAllocLimit`. +- **Thread safety:** + Default encoders/decoders are stateless and thread-safe. If extending for mutable types, ensure synchronization. +- **Logging:** + Integrate with .NET's `ILoggerFactory` when diagnostics or audit trails are needed. + +--- + +## Best Practices + +- Always validate input data—leverage built-in validation or extend for custom rules. +- Document all public APIs using XML comments for seamless integration with the auto-generated docs. +- For non-standard coordinate systems or precision, clearly specify semantics in your custom encoder/decoder. + +--- + +## More Resources + +- [Configuration](./configuration.md) +- [FAQ](./faq.md) +- [API Reference](https://petesramek.github.io/polyline-algorithm-csharp/) diff --git a/api-reference/guide/configuration.md b/api-reference/guide/configuration.md new file mode 100644 index 00000000..86f432fa --- /dev/null +++ b/api-reference/guide/configuration.md @@ -0,0 +1,75 @@ +# Configuration + +PolylineAlgorithm offers flexible configuration for encoding and decoding polylines, allowing you to fine-tune performance, control validation, and integrate diagnostics and logging. + +--- + +## PolylineEncodingOptions + +Most configuration is handled via the `PolylineEncodingOptions` object, which you can build using the fluent `PolylineEncodingOptionsBuilder`. + +### Example: Customizing Stack Alloc Limit + +```csharp +using PolylineAlgorithm; + +var options = PolylineEncodingOptionsBuilder.Create() + .WithStackAllocLimit(1024) // Set stack allocation threshold (bytes) + .Build(); + +var encoder = new PolylineEncoder(options); +``` + +--- + +## Logging and Diagnostics + +PolylineAlgorithm supports internal logging for advanced scenarios and diagnostic purposes. + +- Use your preferred .NET logging framework (`ILoggerFactory`) +- Attach loggers for encoding/decoding diagnostics, especially in automated or agent-based environments + +--- + +## Validation + +Input validation is always enabled by default: + +- Latitude: must be between -90 and 90 +- Longitude: must be between -180 and 180 +- Invalid or malformed coordinates throw descriptive exceptions + +For custom validation (e.g., for custom coordinate types), extend the provided interfaces or abstract base classes. + +--- + +## Advanced Configuration Options + +When using `PolylineEncodingOptionsBuilder`, you may set: + +- **Stack alloc limit:** Configure the threshold below which buffers are stack-allocated vs. rented from `ArrayPool` +- **Logging hooks:** Integrate your logger for troubleshooting/instrumentation +- **(Future)** Custom precision, additional metadata (as needed) + +See the XML API documentation for all available builder methods. + +--- + +## Example: Full Custom Encoder with Options + +```csharp +var options = PolylineEncodingOptionsBuilder.Create() + .WithStackAllocLimit(512) + .WithLoggerFactory(myLoggerFactory) + .Build(); + +var encoder = new PolylineEncoder(options); +``` + +--- + +## Further Reading + +- [Getting Started Guide](./guide.md) +- [Advanced Usage](./advanced.md) +- [API Reference](https://petesramek.github.io/polyline-algorithm-csharp/) diff --git a/api-reference/guide/faq.md b/api-reference/guide/faq.md new file mode 100644 index 00000000..186f97aa --- /dev/null +++ b/api-reference/guide/faq.md @@ -0,0 +1,63 @@ +# FAQ + +Frequently Asked Questions for PolylineAlgorithm + +--- + +## General + +**Q: What coordinate ranges are valid?** +A: Latitude must be between -90 and 90; longitude must be between -180 and 180. Passing out-of-range values throws `ArgumentOutOfRangeException`. + +**Q: Which .NET versions are supported?** +A: Any platform supporting `netstandard2.1`, including .NET Core, .NET 5+, Xamarin, Unity, and Blazor. + +**Q: Can the library be used in Unity, Xamarin, Blazor, or other .NET-compatible platforms?** +A: Yes! Any environment that supports `netstandard2.1` can use this library. + +--- + +## Usage & Extensibility + +**Q: How do I add a new polyline algorithm or coordinate type?** +A: Implement your own encoder/decoder using `AbstractPolylineEncoder` and `AbstractPolylineDecoder`. Add unit tests and XML doc comments, then submit a PR. + +**Q: How do I customize encoding options (e.g., buffer size, logging)?** +A: Use `PolylineEncodingOptionsBuilder` to set options, and pass the result to the encoder or decoder constructor. + +**Q: Is the library thread-safe?** +A: Yes, main encoding/decoding APIs are stateless and thread-safe. If you extend using shared mutable resources, ensure proper synchronization. + +**Q: What happens if I pass invalid or malformed input to the decoder?** +A: The decoder throws descriptive exceptions for malformed polyline strings. Ensure proper exception handling in your application. + +**Q: Does the library support streaming or incremental decoding of polylines?** +A: Currently, only batch encode/decode is supported. For streaming scenarios, implement your own logic using `PolylineEncoding` utilities. + +--- + +## Features & Support + +**Q: Is there support for elevation, timestamps, or third coordinate values?** +A: Not currently, and not planned for the core library. You may implement your own encoder/decoder using `PolylineEncoding` methods for extended coordinate data. + +**Q: How do I contribute documentation improvements?** +A: Update XML doc comments in the codebase and submit a pull request. To improve guides, update relevant markdown files in `/api-reference/guide`. + +**Q: Where can I report bugs or request features?** +A: Open a GitHub issue using the provided templates and tag `@petesramek`. + +--- + +## Documentation & Community + +**Q: Where can I find detailed API documentation?** +A: [API Reference](https://petesramek.github.io/polyline-algorithm-csharp/) + +**Q: How do I contribute?** +A: Read [CONTRIBUTING.md](../CONTRIBUTING.md), follow coding style and testing guidelines, and use issue/PR templates. + +**Q: Need more help?** +A: Open an issue in the [GitHub repository](https://github.com/petesramek/polyline-algorithm-csharp/issues). + +--- diff --git a/api-reference/guide/getting-started.md b/api-reference/guide/getting-started.md new file mode 100644 index 00000000..54d558c4 --- /dev/null +++ b/api-reference/guide/getting-started.md @@ -0,0 +1,76 @@ +# Getting Started + +PolylineAlgorithm is a lightweight, Google-compliant polyline encoding/decoding library for .NET Standard 2.1 and above. +Follow these simple steps to get started, encode and decode polylines, and configure advanced features. + +--- + +## Installation + +Install via the .NET CLI: + +```shell +dotnet add package PolylineAlgorithm +``` + +Or via NuGet Package Manager: + +```powershell +Install-Package PolylineAlgorithm +``` + +--- + +## Basic Usage + +### Encoding Coordinates + +```csharp +using PolylineAlgorithm; + +var coordinates = new List +{ + new Coordinate(48.858370, 2.294481), // Eiffel Tower + new Coordinate(51.500729, -0.124625) // Big Ben +}; + +var encoder = new PolylineEncoder(); +Polyline encoded = encoder.Encode(coordinates); + +Console.WriteLine(encoded.ToString()); // Prints the encoded polyline string +``` + +### Decoding a Polyline + +```csharp +using PolylineAlgorithm; + +var decoder = new PolylineDecoder(); +Polyline polyline = Polyline.FromString("yseiHoc_MwacOjnwM"); // Sample encoded string + +IEnumerable decoded = decoder.Decode(polyline); + +// Show decoded coordinates +foreach(var coord in decoded) +{ + Console.WriteLine($"{coord.Latitude}, {coord.Longitude}"); +} +``` + +--- + +## Customizing and Advanced Features + +- Use `PolylineEncodingOptionsBuilder` to customize settings (buffer size, logging, etc.) +- Implement custom encoder/decoder types for advanced coordinate representations +- See [API reference](https://petesramek.github.io/polyline-algorithm-csharp/) for details + +--- + +## Need More Help? + +- [FAQ](./faq.md) +- [Examples](./examples.md) +- [Report an Issue](https://github.com/petesramek/polyline-algorithm-csharp/issues) + +--- diff --git a/api-reference/guide/introduction.md b/api-reference/guide/introduction.md new file mode 100644 index 00000000..a0874834 --- /dev/null +++ b/api-reference/guide/introduction.md @@ -0,0 +1,33 @@ +# Introduction + +Welcome to **PolylineAlgorithm for .NET**, a modern library offering Google-compliant polyline encoding and decoding with strong input validation, extensible API design, and robust performance. + +## What is PolylineAlgorithm? + +PolylineAlgorithm provides tools for encoding a sequence of geographic coordinates into a compact string format used by Google Maps and other mapping platforms, and for decoding those strings back into coordinates. Its simple API and thorough documentation make it suitable for .NET Core, .NET 5+, Xamarin, Unity, Blazor, and any framework supporting `netstandard2.1`. + +## Key Benefits + +- **Standards compliance**: Implements Google’s Encoded Polyline Algorithm as specified +- **Immutable, strongly-typed objects**: Guarantees reliability and thread safety +- **Extensible**: Custom coordinate/polyline support via abstract base classes and interfaces +- **Robust input validation**: Throws descriptive exceptions for out-of-range or malformed input +- **Configuration and logging**: Advanced options and integration with `ILoggerFactory` +- **Full API documentation**: [Auto-generated API Reference](https://petesramek.github.io/polyline-algorithm-csharp/) +- **Benchmarked and tested**: Includes unit and performance tests + +## Who Should Use This Library? + +- .NET developers needing polyline encoding/decoding in mapping, GIS, logistics, or spatial applications +- Teams requiring reliability, easy integration, and customizable algorithms +- Anyone seeking a lightweight, modern, and maintainable polyline solution + +## How To Get Started + +- See the [Getting Started Guide](./guide.md) for installation and basic usage +- Explore [Examples](./examples.md) and [FAQ](./faq.md) for real-world scenarios and solutions +- Read about advanced [configuration](./configuration.md) and [customization](./advanced.md) + +--- + +For technical details, see the [API Reference](https://petesramek.github.io/polyline-algorithm-csharp/). diff --git a/api-reference/guide/sample.md b/api-reference/guide/sample.md new file mode 100644 index 00000000..336bc254 --- /dev/null +++ b/api-reference/guide/sample.md @@ -0,0 +1,115 @@ +# Sample Console Application: Using NetTopologySuite with PolylineAlgorithm + +This sample demonstrates how to encode and decode polylines using custom implementations (`NetTopologyPolylineEncoder` and `NetTopologyPolylineDecoder`) based on NetTopologySuite's `Point` type. + +--- + +## Prerequisites + +- Install the following NuGet packages: + - `PolylineAlgorithm` + - `NetTopologySuite` + +```shell +dotnet add package PolylineAlgorithm +dotnet add package NetTopologySuite +``` + +--- + +## Program.cs + +```csharp +using System; +using System.Collections.Generic; +using NetTopologySuite.Geometries; +using PolylineAlgorithm.Abstraction; + +class Program +{ + static void Main() + { + // Create some sample points (latitude, longitude) + var points = new List + { + new Point(48.858370, 2.294481), // Eiffel Tower + new Point(51.500729, -0.124625) // Big Ben + }; + + // Instantiate the custom encoder + var encoder = new NetTopologyPolylineEncoder(); + + // Encode the list of points to a polyline string + string encodedPolyline = encoder.Encode(points); + + Console.WriteLine("Encoded polyline string:"); + Console.WriteLine(encodedPolyline); + + // Instantiate the custom decoder + var decoder = new NetTopologyPolylineDecoder(); + + // Decode back to NetTopologySuite Point objects + IEnumerable decodedPoints = decoder.Decode(encodedPolyline); + + Console.WriteLine("\nDecoded coordinates:"); + foreach (var point in decodedPoints) + { + Console.WriteLine($"Latitude: {point.X}, Longitude: {point.Y}"); + } + } +} + +public sealed class NetTopologyPolylineDecoder : AbstractPolylineDecoder { + protected override Point CreateCoordinate(double latitude, double longitude) { + return new Point(latitude, longitude); + } + + protected override ReadOnlyMemory GetReadOnlyMemory(ref string polyline) { + return polyline.AsMemory(); + } +} + +public sealed class NetTopologyPolylineEncoder : AbstractPolylineEncoder { + protected override string CreatePolyline(ReadOnlyMemory polyline) { + if (polyline.IsEmpty) { + return string.Empty; + } + + return polyline.ToString(); + } + + protected override double GetLatitude(Point current) { + // Validate parameter + + return current.X; + } + + protected override double GetLongitude(Point current) { + // Validate parameter + + return current.Y; + } +} +``` + +--- + +## Expected Output + +```text +Encoded polyline string: +{sample output will be generated at runtime} + +Decoded coordinates: +Latitude: 48.85837, Longitude: 2.294481 +Latitude: 51.500729, Longitude: -0.124625 +``` + +--- + +## Notes + +- You can further extend this pattern to use any coordinate or geometry type supported by NetTopologySuite. +- The sample demonstrates real usage of a custom `PolylineEncoder`/`PolylineDecoder` in a typical .NET application. + +--- diff --git a/api-reference/guide/toc.yml b/api-reference/guide/toc.yml new file mode 100644 index 00000000..985c0293 --- /dev/null +++ b/api-reference/guide/toc.yml @@ -0,0 +1,12 @@ +- name: Introduction + href: introduction.md +- name: Getting Started + href: getting-started.md +- name: Configuration + href: configuration.md +- name: Advanced Scenarios + href: advanced-scenarios.md +- name: Sample + href: sample.md +- name: FAQ + href: faq.md diff --git a/api-reference/index.md b/api-reference/index.md new file mode 100644 index 00000000..8c5b8d16 --- /dev/null +++ b/api-reference/index.md @@ -0,0 +1,18 @@ +# PolylineAlgorithm API Reference + +Welcome! This documentation provides guides, configuration options, examples, and FAQs for the PolylineAlgorithm library. +For detailed class and method docs, see the [auto-generated API documentation](https://petesramek.github.io/polyline-algorithm-csharp/). + +## Contents + +- [Quick Start Guide](./guide.md) +- [Configuration Options](./configuration.md) +- [Advanced Usage](./advanced.md) +- [Examples](./examples.md) +- [FAQ](./faq.md) + +## Links + +- [API Reference Site](https://petesramek.github.io/polyline-algorithm-csharp/) +- [Contributing Guidelines](../CONTRIBUTING.md) +- [Changelog](./changelog.md) (if provided) diff --git a/api-reference/media/polyline-algorithm-50x46.png b/api-reference/media/polyline-algorithm-50x46.png new file mode 100644 index 00000000..6e14d059 Binary files /dev/null and b/api-reference/media/polyline-algorithm-50x46.png differ diff --git a/api-reference/media/polyline-algorithm.png b/api-reference/media/polyline-algorithm.png new file mode 100644 index 00000000..d07cf781 Binary files /dev/null and b/api-reference/media/polyline-algorithm.png differ diff --git a/api-reference/toc.yml b/api-reference/toc.yml new file mode 100644 index 00000000..df40958f --- /dev/null +++ b/api-reference/toc.yml @@ -0,0 +1,3 @@ +### YamlMime:TableOfContent +- name: Guide + href: guide/ \ No newline at end of file diff --git a/api-reference/versions.json b/api-reference/versions.json new file mode 100644 index 00000000..6b9556d1 --- /dev/null +++ b/api-reference/versions.json @@ -0,0 +1,4 @@ +{ + "latest": null, + "versions": [] +} diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks.csproj b/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks.csproj deleted file mode 100644 index 2d5b8cc1..00000000 --- a/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - Exe - net8.0 - enable - enable - - - - - - - - - - - diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/PolylineEncodingBenchmark.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/PolylineEncodingBenchmark.cs deleted file mode 100644 index 798fd3ae..00000000 --- a/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/PolylineEncodingBenchmark.cs +++ /dev/null @@ -1,40 +0,0 @@ -namespace DropoutCoder.PolylineAlgorithm.Benchmarks -{ - using BenchmarkDotNet.Attributes; - using BenchmarkDotNet.Engines; - using DropoutCoder.PolylineAlgorithm.Encoding; - - [MemoryDiagnoser] - [MarkdownExporter] - public class PolylineEncodingBenchmark - { - private Consumer _consumer = new Consumer(); - - [Params(10_000, 100_000, 1_000_000, Priority = 2)] - public int N; - - public IEnumerable<(double, double)> Coordinates; - - public PolylineEncoding Encoding { get; private set; } - - public string Polyline; - - [GlobalSetup] - public void Setup() - { - Encoding = new PolylineEncoding(); - Coordinates = new[] { (42.88895, -100.30630), (44.91513, 19.22495), (20.40244, 7.97495), (-15.52130, -63.74380), (-78.95116, -72.18130), (38.63072, 88.13120), (60.81071, 151.41245), (-58.20769, -173.43130), (59.40939, 83.91245), (-58.20769, 61.41245), (-20.86278, -119.99380), (34.10374, -150.93130), (-71.15367, 31.88120), (-72.04138, -153.74380), (-49.99635, -107.33755), (76.12614, 135.94370), (70.05664, 41.72495), (63.43879, -77.80630), (13.68456, -90.46255), (-75.90519, -7.49380), (74.71112, -127.02505), (-66.61109, 17.81870), (-49.08384, 37.50620) }; - Polyline = "}vwdGjafcRsvjKi}pxUhsrtCngtcAjjgzEdqvtLrscbKj}nr@wetlUc`nq]}_kfCyrfaK~wluUl`u}|@wa{lUmmuap@va{lU~oihCu||bF`|era@wsnnIjny{DxamaScqxza@dklDf{}kb@mtpeCavfzGqhx`Wyzzkm@jm`d@dba~Pppkg@h}pxU|rtnHp|flA|~xaPuykyN}fhv[h}pxUx~p}Ymx`sZih~iB{edwB"; - } - - [Benchmark] - public void Decode() => Encoding - .Decode(Polyline) - .Consume(_consumer); - - [Benchmark] - public void Encode() => Encoding - .Encode(Coordinates) - .Consume(_consumer); - } -} diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/Program.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/Program.cs deleted file mode 100644 index d6e3655e..00000000 --- a/benchmarks/DropoutCoder.PolylineAlgorithm.Benchmarks/Program.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace DropoutCoder.PolylineAlgorithm.Benchmarks -{ - using BenchmarkDotNet.Running; - - internal class Program - { - static void Main(string[] args) - { - BenchmarkRunner - .Run(); - } - } -} diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Constants.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Constants.cs deleted file mode 100644 index 9854d042..00000000 --- a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Constants.cs +++ /dev/null @@ -1,82 +0,0 @@ -// -// Copyright (c) Petr Šrámek. All rights reserved. -// Licensed under the MIT License. See LICENSE file in the project root for full license information. -// - -namespace DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks -{ - /// - /// Defines global constant values - /// - internal static class Constants - { - #region Constants - - /// - /// Defines the coordinate precision - /// - public const double Precision = 1E5; - - /// - /// Defines the shift length - /// - public const int ShiftLength = 5; - - #endregion - - /// - /// Defines ASCII characters constant values - /// - internal static class ASCII - { - #region Constants - - /// - /// Defines the ASCII Question Mark - /// - public const int QuestionMark = 63; - - /// - /// Defines the ASCII Space - /// - public const int Space = 32; - - /// - /// Defines the ASCII Unit Separator - /// - public const int UnitSeparator = 31; - - #endregion - } - - /// - /// Defines coordinates constant values - /// - internal static class Coordinate - { - #region Constants - - /// - /// Defines the maximum value for latitude - /// - public const int MaxLatitude = 90; - - /// - /// Defines the maximum value for longitude - /// - public const int MaxLongitude = 180; - - /// - /// Defines the minimum value for latitude - /// - public const int MinLatitude = -MaxLatitude; - - /// - /// Defines the minimum value for longitude - /// - public const int MinLongitude = -MaxLongitude; - - #endregion - } - } -} diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DecodePerformanceBenchmark.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DecodePerformanceBenchmark.cs deleted file mode 100644 index 43289f5d..00000000 --- a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DecodePerformanceBenchmark.cs +++ /dev/null @@ -1,200 +0,0 @@ -namespace DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks -{ - using BenchmarkDotNet.Attributes; - using BenchmarkDotNet.Engines; - using System; - - [MemoryDiagnoser] - public class DecodePerformanceBenchmark - { - private Consumer _consumer = new Consumer(); - public static IEnumerable<(int, char[])> Polylines() - { - yield return (1, "mz}lHssngJj`gqSnx~lEcovfTnms{Zdy~qQj_deI".ToCharArray()); - yield return (2, "}vwdGjafcRsvjKi}pxUhsrtCngtcAjjgzEdqvtLrscbKj}nr@wetlUc`nq]}_kfCyrfaK~wluUl`u}|@wa{lUmmuap@va{lU~oihCu||bF`|era@wsnnIjny{DxamaScqxza@dklDf{}kb@mtpeCavfzGqhx`Wyzzkm@jm`d@dba~Pppkg@h}pxU|rtnHp|flA|~xaPuykyN}fhv[h}pxUx~p}Ymx`sZih~iB{edwB".ToCharArray()); - yield return (3, "}adrJh}}cVazlw@uykyNhaqeE`vfzG_~kY}~`eTsr{~Cwn~aOty_g@thapJvvoqKxt{sStfahDmtvmIfmiqBhjq|HujpgComs{Z}dhdKcidPymnvBqmquE~qrfI`x{lPf|ftGn~}d_@q}saAurjmu@bwr_DxrfaK~{rO~bidPwfduXwlioFlpum@twvfFpmi~VzxcsOqyejYhh|i@pbnr[twvfF_ueUujvbSa_d~ZkcnjZla~f[pmquEebxo[j}nr@xnn|H{gyiKbh{yH`oenn@y{mpIrbd~EmipgH}fuov@hjqtTp|flAttvkFrym_d@|eyCwn~aOfvdNmeawM??{yxdUcidPca{}D_atqGenzcAlra{@trgWhn{aZ??tluqOgu~sH".ToCharArray()); - } - - [Benchmark(Baseline = true)] - [ArgumentsSource(nameof(Polylines))] - public void Decode_V1((int, char[]) arg) => V1.Decode(arg.Item2).Consume(_consumer); - - [Benchmark] - [ArgumentsSource(nameof(Polylines))] - public void Decode_V1_Parallel((int, char[]) arg) => Parallel.For(0, 100, (i) => V1.Decode(arg.Item2).Consume(_consumer)); - - [Benchmark] - [ArgumentsSource(nameof(Polylines))] - public void Decode_V2((int, char[]) arg) => V2.Decode(arg.Item2).Consume(_consumer); - - [Benchmark] - [ArgumentsSource(nameof(Polylines))] - public void Decode_V2_Parallel((int, char[]) arg) => Parallel.For(0, 100, (i) => V2.Decode(arg.Item2).Consume(_consumer)); - - private class V1 - { - public static IEnumerable<(double Latitude, double Longitude)> Decode(char[] polyline) - { - if (polyline is null || polyline.Length == 0) - { - throw new ArgumentException(nameof(polyline)); - } - - int index = 0; - int latitude = 0; - int longitude = 0; - - var result = new List<(double Latitude, double Longitude)>(); - - while (index < polyline.Length) - { - if (!TryCalculateNext(ref polyline, ref index, ref latitude)) - { - throw new InvalidOperationException(); - } - - if (!TryCalculateNext(ref polyline, ref index, ref longitude)) - { - throw new InvalidOperationException(); - } - - var coordinate = (GetDoubleRepresentation(latitude), GetDoubleRepresentation(longitude)); - - if (!CoordinateValidator.IsValid(coordinate)) - { - throw new InvalidOperationException(); - } - - result.Add(coordinate); - } - - return result; - } - - private static bool TryCalculateNext(ref char[] polyline, ref int index, ref int value) - { - int chunk; - int sum = 0; - int shifter = 0; - - do - { - chunk = polyline[index++] - Constants.ASCII.QuestionMark; - sum |= (chunk & Constants.ASCII.UnitSeparator) << shifter; - shifter += Constants.ShiftLength; - } while (chunk >= Constants.ASCII.Space && index < polyline.Length); - - if (index >= polyline.Length && chunk >= Constants.ASCII.Space) - return false; - - value += (sum & 1) == 1 ? ~(sum >> 1) : sum >> 1; - - return true; - } - - private static double GetDoubleRepresentation(int value) - { - return Convert.ToDouble(value) / Constants.Precision; - } - - public static class CoordinateValidator - { - public static bool IsValid((double Latitude, double Longitude) coordinate) - { - return IsValidLatitude(coordinate.Latitude) && IsValidLongitude(coordinate.Longitude); - } - - public static bool IsValidLatitude(double latitude) - { - return latitude >= Constants.Coordinate.MinLatitude && latitude <= Constants.Coordinate.MaxLatitude; - } - - public static bool IsValidLongitude(double longitude) - { - return longitude >= Constants.Coordinate.MinLongitude && longitude <= Constants.Coordinate.MaxLongitude; - } - } - } - - private class V2 - { - public static IEnumerable<(double Latitude, double Longitude)> Decode(char[] polyline) - { - if (polyline is null || polyline.Length == 0) - { - throw new ArgumentException(nameof(polyline)); - } - - int offset = 0; - int latitude = 0; - int longitude = 0; - - while (offset < polyline.Length) - { - if (!TryCalculateNext(ref polyline, ref offset, ref latitude)) - { - throw new InvalidOperationException(); - } - - if (!TryCalculateNext(ref polyline, ref offset, ref longitude)) - { - throw new InvalidOperationException(); - } - - var coordinate = (GetDoubleRepresentation(latitude), GetDoubleRepresentation(longitude)); - - if (!CoordinateValidator.IsValid(coordinate)) - { - throw new InvalidOperationException(); - } - - yield return (latitude, longitude); - } - } - - private static bool TryCalculateNext(ref char[] polyline, ref int offset, ref int value) - { - int chunk; - int sum = 0; - int shifter = 0; - - do - { - chunk = polyline[offset++] - Constants.ASCII.QuestionMark; - sum |= (chunk & Constants.ASCII.UnitSeparator) << shifter; - shifter += Constants.ShiftLength; - } while (chunk >= Constants.ASCII.Space && offset < polyline.Length); - - if (offset >= polyline.Length && chunk >= Constants.ASCII.Space) - return false; - - value += (sum & 1) == 1 ? ~(sum >> 1) : sum >> 1; - - return true; - } - - private static double GetDoubleRepresentation(int value) - { - return value / Constants.Precision; - } - - public static class CoordinateValidator - { - public static bool IsValid((double Latitude, double Longitude) coordinate) - { - return IsValidLatitude(coordinate.Latitude) && IsValidLongitude(coordinate.Longitude); - } - - public static bool IsValidLatitude(double latitude) - { - return latitude >= Constants.Coordinate.MinLatitude && latitude <= Constants.Coordinate.MaxLatitude; - } - - public static bool IsValidLongitude(double longitude) - { - return longitude >= Constants.Coordinate.MinLongitude && longitude <= Constants.Coordinate.MaxLongitude; - } - } - } - } -} diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks.csproj b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks.csproj deleted file mode 100644 index d799ba3b..00000000 --- a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - Exe - net8.0 - enable - enable - - - - - - - - - - - - diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/EncodePerformanceBenchmark.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/EncodePerformanceBenchmark.cs deleted file mode 100644 index d7a75e02..00000000 --- a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/EncodePerformanceBenchmark.cs +++ /dev/null @@ -1,222 +0,0 @@ -namespace DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks -{ - using BenchmarkDotNet.Attributes; - using BenchmarkDotNet.Engines; - using Microsoft.Extensions.ObjectPool; - using System.Collections.Generic; - using System.Text; - - [MemoryDiagnoser] - public class EncodePerformanceBenchmark - { - private Consumer _consumer = new Consumer(); - - public IEnumerable<(int, IEnumerable<(double, double)>)> Coordinates() - { - yield return (1, new[] { (49.47383, 59.06250), (-58.37407, 25.31250), (52.99363, -120.93750), (-44.49024, -174.37500) }); - yield return (2, new[] { (42.88895, -100.30630), (44.91513, 19.22495), (20.40244, 7.97495), (-15.52130, -63.74380), (-78.95116, -72.18130), (38.63072, 88.13120), (60.81071, 151.41245), (-58.20769, -173.43130), (59.40939, 83.91245), (-58.20769, 61.41245), (-20.86278, -119.99380), (34.10374, -150.93130), (-71.15367, 31.88120), (-72.04138, -153.74380), (-49.99635, -107.33755), (76.12614, 135.94370), (70.05664, 41.72495), (63.43879, -77.80630), (13.68456, -90.46255), (-75.90519, -7.49380), (74.71112, -127.02505), (-66.61109, 17.81870), (-49.08384, 37.50620) }); - yield return (3, new[] { (60.81071, -121.40005), (70.05664, -38.43130), (37.52379, -84.83755), (41.85003, 26.25620), (68.04709, 110.63120), (61.48922, 50.16245), (-4.46018, -58.11880), (-32.16061, -3.27505), (-50.89185, -55.30630), (-28.52070, 90.94370), (35.26009, 93.75620), (54.83622, 128.91245), (1.16022, 37.50620), (-44.26398, -131.24380), (-33.34325, 154.22495), (-59.65879, 90.94370), (-62.38215, 0.94370), (72.32117, 40.31870), (64.66910, 2.34995), (-61.04971, -84.83755), (77.10238, -91.86880), (-72.88859, -129.83755), (-69.24987, -24.36880), (77.41254, 119.06870), (-70.69409, 83.91245), (78.85650, 75.47495), (26.83989, 140.16245), (-24.75069, -108.74380), (30.53968, -145.30630), (79.12503, 145.78745), (-34.51006, 133.13120), (-73.29753, -60.93130), (-74.08712, 23.44370), (-76.57404, 100.78745), (-76.57404, 100.78745), (39.72082, 103.59995), (70.99412, 148.59995), (82.27591, 138.75620), (78.29964, -3.27505), (78.29964, -3.27505), (-8.65039, 47.34995) }); - } - - [Benchmark(Baseline = true)] - [ArgumentsSource(nameof(Coordinates))] - public void Encode_V1((int, IEnumerable<(double, double)>) arg) => V1.Encode(arg.Item2).Consume(_consumer); - - [Benchmark] - [ArgumentsSource(nameof(Coordinates))] - public void Encode_V1_Parallel((int, IEnumerable<(double, double)>) arg) => Parallel.For(100, 200, (i) => V1.Encode(arg.Item2).Consume(_consumer)); - - - [Benchmark] - [ArgumentsSource(nameof(Coordinates))] - public void Encode_V2((int, IEnumerable<(double, double)>) arg) => V2.Encode(arg.Item2).Consume(_consumer); - - - [Benchmark] - [ArgumentsSource(nameof(Coordinates))] - public void Encode_V2_Parallel((int, IEnumerable<(double, double)>) arg) => Parallel.For(100, 200, (i) => V2.Encode(arg.Item2).Consume(_consumer)); - - private class V1 - { - public static string Encode(IEnumerable<(double Latitude, double Longitude)> coordinates) - { - if (coordinates is null || !coordinates.Any()) - { - throw new ArgumentException(nameof(coordinates)); - } - - EnsureCoordinates(coordinates); - - int lastLatitude = 0; - int lastLongitude = 0; - var sb = new StringBuilder(); - - foreach (var coordinate in coordinates) - { - int latitude = GetIntegerRepresentation(coordinate.Latitude); - int longitude = GetIntegerRepresentation(coordinate.Longitude); - - sb.Append(GetEncodedCharacters(latitude - lastLatitude).ToArray()); - sb.Append(GetEncodedCharacters(longitude - lastLongitude).ToArray()); - - lastLatitude = latitude; - lastLongitude = longitude; - } - - return sb.ToString(); - } - - private static void EnsureCoordinates(IEnumerable<(double Latitude, double Longitude)> coordinates) - { - var invalidCoordinates = coordinates - .Where(c => !CoordinateValidator.IsValid(c)); - - if (invalidCoordinates.Any()) - { - throw new AggregateException( - invalidCoordinates - .Select(c => - new ArgumentOutOfRangeException() - ) - ); - } - } - - private static IEnumerable GetEncodedCharacters(int value) - { - int shifted = value << 1; - if (value < 0) - shifted = ~shifted; - - int rem = shifted; - - while (rem >= Constants.ASCII.Space) - { - yield return (char)((Constants.ASCII.Space | rem & Constants.ASCII.UnitSeparator) + Constants.ASCII.QuestionMark); - - rem >>= Constants.ShiftLength; - } - - yield return (char)(rem + Constants.ASCII.QuestionMark); - } - - private static int GetIntegerRepresentation(double value) - { - return (int)Math.Round(value * Constants.Precision); - } - - public static class CoordinateValidator - { - public static bool IsValid((double Latitude, double Longitude) coordinate) - { - return IsValidLatitude(coordinate.Latitude) && IsValidLongitude(coordinate.Longitude); - } - - public static bool IsValidLatitude(double latitude) - { - return latitude >= Constants.Coordinate.MinLatitude && latitude <= Constants.Coordinate.MaxLatitude; - } - - public static bool IsValidLongitude(double longitude) - { - return longitude >= Constants.Coordinate.MinLongitude && longitude <= Constants.Coordinate.MaxLongitude; - } - } - } - - private class V2 - { - private static readonly ObjectPool _pool = new DefaultObjectPoolProvider().CreateStringBuilderPool(5, int.MaxValue); - - public static string Encode(IEnumerable<(double Latitude, double Longitude)> coordinates) - { - if (coordinates is null || !coordinates.Any()) - { - throw new ArgumentException(nameof(coordinates)); - } - - EnsureCoordinates(coordinates); - - int previousLatitude = 0; - int previousLongitude = 0; - - var sb = _pool.Get(); - - foreach (var coordinate in coordinates) - { - int latitude = GetIntegerRepresentation(coordinate.Latitude); - int longitude = GetIntegerRepresentation(coordinate.Longitude); - - sb.Append(GetEncodedCharacters(latitude - previousLatitude).ToArray()); - sb.Append(GetEncodedCharacters(longitude - previousLongitude).ToArray()); - - previousLatitude = latitude; - previousLongitude = longitude; - } - - var result = sb.ToString(); - - _pool.Return(sb); - - return result; - } - - private static void EnsureCoordinates(IEnumerable<(double Latitude, double Longitude)> coordinates) - { - var invalidCoordinates = coordinates - .Where(c => !CoordinateValidator.IsValid(c)); - - if (invalidCoordinates.Any()) - { - throw new AggregateException( - invalidCoordinates - .Select(c => - new ArgumentOutOfRangeException() - ) - ); - } - } - - private static IEnumerable GetEncodedCharacters(int value) - { - int shifted = value << 1; - if (value < 0) - shifted = ~shifted; - - int rem = shifted; - - while (rem >= Constants.ASCII.Space) - { - yield return (char)((Constants.ASCII.Space | rem & Constants.ASCII.UnitSeparator) + Constants.ASCII.QuestionMark); - - rem >>= Constants.ShiftLength; - } - - yield return (char)(rem + Constants.ASCII.QuestionMark); - } - - private static int GetIntegerRepresentation(double value) - { - return (int)Math.Round(value * Constants.Precision); - } - - public static class CoordinateValidator - { - public static bool IsValid((double Latitude, double Longitude) coordinate) - { - return IsValidLatitude(coordinate.Latitude) && IsValidLongitude(coordinate.Longitude); - } - - public static bool IsValidLatitude(double latitude) - { - return latitude >= Constants.Coordinate.MinLatitude && latitude <= Constants.Coordinate.MaxLatitude; - } - - public static bool IsValidLongitude(double longitude) - { - return longitude >= Constants.Coordinate.MinLongitude && longitude <= Constants.Coordinate.MaxLongitude; - } - } - } - } -} diff --git a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Program.cs b/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Program.cs deleted file mode 100644 index 1a591c1c..00000000 --- a/benchmarks/DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks/Program.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace DropoutCoder.PolylineAlgorithm.Implementation.Benchmarks -{ - using BenchmarkDotNet.Running; - - internal class Program - { - static void Main(string[] args) - { - BenchmarkRunner - .Run(); - BenchmarkRunner - .Run(); - } - } -} diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj new file mode 100644 index 00000000..99b5011f --- /dev/null +++ b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj @@ -0,0 +1,26 @@ + + + + Exe + net8.0;net9.0;net10.0 + + + + pdbonly + true + + + + false + + + + + + + + + + + + diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineDecoderBenchmark.cs b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineDecoderBenchmark.cs new file mode 100644 index 00000000..f28c8141 --- /dev/null +++ b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineDecoderBenchmark.cs @@ -0,0 +1,124 @@ +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm.Benchmarks; + +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Engines; +using PolylineAlgorithm.Abstraction; +using PolylineAlgorithm.Utility; + +/// +/// Benchmarks for . +/// +public class PolylineDecoderBenchmark { + private readonly Consumer _consumer = new(); + + [Params(1, 100, 1_000)] + public int CoordinatesCount { get; set; } + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + /// + /// Encoded polyline as string. + /// + public string String { get; private set; } + + /// + /// Encoded polyline as char array. + /// + public char[] CharArray { get; private set; } + + /// + /// Encoded polyline as read-only memory. + /// + public ReadOnlyMemory Memory { get; private set; } + +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + + /// + /// String polyline decoder instance. + /// + private readonly StringPolylineDecoder _stringDecoder = new(); + + /// + /// Char array polyline decoder instance. + /// + private readonly CharArrayPolylineDecoder _charArrayDecoder = new(); + + /// + /// String polyline decoder instance. + /// + private readonly MemoryCharPolylineDecoder _memoryCharDecoder = new(); + + /// + /// Sets up benchmark data. + /// + [GlobalSetup] + public void SetupData() { + String = RandomValueProvider.GetPolyline(CoordinatesCount); + CharArray = RandomValueProvider.GetPolyline(CoordinatesCount).ToCharArray(); + Memory = RandomValueProvider.GetPolyline(CoordinatesCount).AsMemory(); + } + + /// + /// Benchmark: decode from string. + /// + [Benchmark] + public void PolylineDecoder_Decode_String() { + _stringDecoder + .Decode(String) + .Consume(_consumer); + } + + /// + /// Benchmark: decode from char array. + /// + [Benchmark] + public void PolylineDecoder_Decode_CharArray() { + _charArrayDecoder + .Decode(CharArray) + .Consume(_consumer); + } + + /// + /// Benchmark: decode from memory. + /// + [Benchmark] + public void PolylineDecoder_Decode_Memory() { + _memoryCharDecoder + .Decode(Memory) + .Consume(_consumer); + } + + private sealed class StringPolylineDecoder : AbstractPolylineDecoder { + protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) { + return (latitude, longitude); + } + + protected override ReadOnlyMemory GetReadOnlyMemory(in string polyline) { + return polyline?.AsMemory() ?? Memory.Empty; + } + } + + private sealed class CharArrayPolylineDecoder : AbstractPolylineDecoder { + protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) { + return (latitude, longitude); + } + + protected override ReadOnlyMemory GetReadOnlyMemory(in char[] polyline) { + return polyline?.AsMemory() ?? Memory.Empty; + } + } + + private sealed class MemoryCharPolylineDecoder : AbstractPolylineDecoder, (double Latitude, double Longitude)> { + protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) { + return (latitude, longitude); + } + + protected override ReadOnlyMemory GetReadOnlyMemory(in ReadOnlyMemory polyline) { + return polyline; + } + } +} \ No newline at end of file diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncoderBenchmark.cs b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncoderBenchmark.cs new file mode 100644 index 00000000..e0b97c5c --- /dev/null +++ b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncoderBenchmark.cs @@ -0,0 +1,92 @@ +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm.Benchmarks; + +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Engines; +using PolylineAlgorithm.Abstraction; +using PolylineAlgorithm.Extensions; +using PolylineAlgorithm.Utility; +using System.Collections.Generic; + +/// +/// Benchmarks for . +/// +public class PolylineEncoderBenchmark { + private readonly Consumer _consumer = new(); + + /// + /// Number of coordinates for benchmarks. + /// + [Params(1, 100, 1_000)] + public int CoordinatesCount { get; set; } + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + /// + /// Coordinates as list. + /// + public List<(double Latitude, double Longitude)> List { get; private set; } + + /// + /// Coordinates as array. + /// + public (double Latitude, double Longitude)[] Array { get; private set; } + + /// + /// Coordinates as read-only memory. + /// + public ReadOnlyMemory<(double Latitude, double Longitude)> Memory { get; private set; } + +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + + /// + /// Polyline encoder instance. + /// + private readonly StringPolylineEncoder _encoder = new(); + + /// + /// Sets up benchmark data. + /// + [GlobalSetup] + public void SetupData() { + List = [.. RandomValueProvider.GetCoordinates(CoordinatesCount)]; + Array = [.. List]; + Memory = Array.AsMemory(); + } + + /// + /// Benchmark: encode coordinates from span. + /// + [Benchmark] + public void PolylineEncoder_Encode_Span() { + var polyline = _encoder.Encode(Memory.Span); + _consumer.Consume(polyline); + } + + /// + /// Benchmark: encode coordinates from array. + /// + [Benchmark] + public void PolylineEncoder_Encode_Array() { + var polyline = _encoder.Encode(Array); + _consumer.Consume(polyline); + } + + /// + /// Benchmark: encode coordinates from list. + /// + [Benchmark] + public void PolylineEncoder_Encode_List() { + var polyline = _encoder.Encode(List); + _consumer.Consume(polyline); + } + + private sealed class StringPolylineEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> { + protected override string CreatePolyline(ReadOnlyMemory polyline) => polyline.ToString(); + protected override double GetLatitude((double Latitude, double Longitude) current) => current.Latitude; + protected override double GetLongitude((double Latitude, double Longitude) current) => current.Longitude; + } +} diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncodingBenchmark.cs b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncodingBenchmark.cs new file mode 100644 index 00000000..861ad85f --- /dev/null +++ b/benchmarks/PolylineAlgorithm.Benchmarks/PolylineEncodingBenchmark.cs @@ -0,0 +1,38 @@ +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm.Benchmarks; + +using BenchmarkDotNet.Attributes; +using PolylineAlgorithm.Utility; + +/// +/// Benchmarks for the polyline encoding validation methods in . +/// +public class PolylineEncodingBenchmark { +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + private string polyline; +#pragma warning restore CS8618 + + /// + /// Number of coordinates for benchmarks. Set by BenchmarkDotNet. + /// + [Params(8, 64, 128, 1024, 4096, 20480, 102400)] + public int CoordinatesCount { get; set; } + + [GlobalSetup] + public void Setup() { + polyline = RandomValueProvider.GetPolyline(CoordinatesCount); + } + + [Benchmark(Baseline = true)] + public void ValidateCharRange() => PolylineEncoding.ValidateCharRange(polyline); + + [Benchmark] + public void ValidateBlockLength() => PolylineEncoding.ValidateBlockLength(polyline); + + [Benchmark] + public void ValidateFormat() => PolylineEncoding.ValidateFormat(polyline); +} \ No newline at end of file diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/Program.cs b/benchmarks/PolylineAlgorithm.Benchmarks/Program.cs new file mode 100644 index 00000000..92c38c10 --- /dev/null +++ b/benchmarks/PolylineAlgorithm.Benchmarks/Program.cs @@ -0,0 +1,23 @@ +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm.Benchmarks; + +using BenchmarkDotNet.Running; + +/// +/// Main entry point for benchmarks. +/// +internal static class Program { + /// + /// Runs the benchmarks. + /// + /// Command-line arguments. + static void Main(string[] args) { + BenchmarkSwitcher + .FromAssembly(typeof(Program).Assembly) + .Run(args); + } +} \ No newline at end of file diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/Properties/CodeCoverage.cs b/benchmarks/PolylineAlgorithm.Benchmarks/Properties/CodeCoverage.cs new file mode 100644 index 00000000..04094932 --- /dev/null +++ b/benchmarks/PolylineAlgorithm.Benchmarks/Properties/CodeCoverage.cs @@ -0,0 +1,8 @@ +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +using System.Diagnostics.CodeAnalysis; + +[assembly: ExcludeFromCodeCoverage] \ No newline at end of file diff --git a/benchmarks/PolylineAlgorithm.Benchmarks/Properties/GlobalSuppressions.cs b/benchmarks/PolylineAlgorithm.Benchmarks/Properties/GlobalSuppressions.cs new file mode 100644 index 00000000..10dc609e --- /dev/null +++ b/benchmarks/PolylineAlgorithm.Benchmarks/Properties/GlobalSuppressions.cs @@ -0,0 +1,15 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores", Justification = "Benchmarks.")] +[assembly: SuppressMessage("Performance", "CA1819:Properties should not return arrays", Justification = "Benchmarks.")] +[assembly: SuppressMessage("Design", "MA0016:Prefer using collection abstraction instead of implementation", Justification = "Benchmarks.")] +[assembly: SuppressMessage("Design", "CA1002:Do not expose generic lists", Justification = "Benchmarks.")] +[assembly: SuppressMessage("Naming", "CA1720:Identifier contains type name", Justification = "Benchmarks.")] +[assembly: SuppressMessage("Maintainability", "CA1515:Consider making public types internal", Justification = "Benchmarks.")] +[assembly: SuppressMessage("Security", "CA5394:Do not use insecure randomness", Justification = "Benchmarks.")] +[assembly: SuppressMessage("Performance", "CA1822:Mark members as static", Justification = "Benchmarks need instance methods.")] \ No newline at end of file diff --git a/code-coverage-settings.xml b/code-coverage-settings.xml new file mode 100644 index 00000000..c22f21e5 --- /dev/null +++ b/code-coverage-settings.xml @@ -0,0 +1,26 @@ + + + + + + + src/** + + + + + + + + ^System\.Diagnostics\.DebuggerHiddenAttribute$ + ^System\.Diagnostics\.DebuggerNonUserCodeAttribute$ + ^System\.CodeDom\.Compiler\.GeneratedCodeAttribute$ + ^System\.Diagnostics\.CodeAnalysis\.ExcludeFromCodeCoverageAttribute$ + + + + True + True + + + \ No newline at end of file diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index 4378419e..00000000 --- a/docs/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -############### -# folder # -############### -/**/DROP/ -/**/TEMP/ -/**/packages/ -/**/bin/ -/**/obj/ -_site diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..fadcbe7d --- /dev/null +++ b/docs/README.md @@ -0,0 +1,23 @@ +# Developer Documentation + +Welcome to the developer documentation for **PolylineAlgorithm**. This section covers everything you need to contribute code, tests, benchmarks, and automation to the project. + +## Contents + +| Document | Description | +|---|---| +| [Local Development](./local-development.md) | Build, test, and format the codebase locally | +| [Testing](./testing.md) | How to write and run unit tests | +| [Benchmarks](./benchmarks.md) | How to write and run performance benchmarks | +| [Composite Actions](./composite-actions.md) | Reusable GitHub Actions used across workflows | +| [Workflows](./workflows.md) | CI/CD pipelines and how they connect | +| [Branch Strategy](./branch-strategy.md) | Branch lifecycle and environment mapping | +| [Versioning](./versioning.md) | Branch naming convention and the version pipeline | +| [API Documentation](./api-documentation.md) | How DocFX generates and publishes the API reference site | +| [Extensibility](./extensibility.md) | How to add new encoding algorithms | + +## Quick Links + +- [Contributing Guidelines](../CONTRIBUTING.md) +- [API Reference Site](https://petesramek.github.io/polyline-algorithm-csharp/) +- [GitHub Issues](https://github.com/petesramek/polyline-algorithm-csharp/issues) diff --git a/docs/api-documentation.md b/docs/api-documentation.md new file mode 100644 index 00000000..d709fe74 --- /dev/null +++ b/docs/api-documentation.md @@ -0,0 +1,136 @@ +# API Documentation + +This document explains how API documentation is generated and published for PolylineAlgorithm. + +## Toolchain + +Documentation is built with [DocFX](https://dotnet.github.io/docfx/), a static site generator for .NET API references and Markdown guides. + +## Repository Layout + +``` +api-reference/ +├── api-reference.json # DocFX build manifest (site generation) +├── assembly-metadata.json # DocFX metadata manifest (API extraction only) +├── toc.yml # Top-level table of contents +├── index.md # Landing page +├── favicon.ico +├── media/ # Images and other static assets +├── guide/ # Markdown guide articles +│ ├── toc.yml +│ ├── introduction.md +│ ├── getting-started.md +│ ├── configuration.md +│ ├── advanced-scenarios.md +│ ├── sample.md +│ └── faq.md +├── 0.0/ # Auto-generated API metadata for version 0.0 +├── 1.0/ # Auto-generated API metadata for version 1.0 +│ └── *.yml # DocFX apiPage YAML files (one per type) +└── _docs/ # Build output (excluded from DocFX content, gitignored) +``` + +## Two DocFX Manifests + +### `assembly-metadata.json` — API Extraction + +Used by the `documentation/docfx-metadata` composite action during CI builds to extract XML documentation comments from source code and produce DocFX-compatible YAML files. + +```json +{ + "metadata": [{ + "src": [{ "src": "../src", "files": ["**/*.csproj"] }], + "dest": "temp", + "outputFormat": "apiPage" + }] +} +``` + +- **Input:** All `.csproj` files under `src/` +- **Output:** YAML files in `api-reference/temp/`, then copied to `api-reference//` +- **When it runs:** Automatically after every successful `build` workflow run. The resulting YAML files are committed to the repository under `api-reference//` (e.g., `api-reference/1.2/`). + +### `api-reference.json` — Site Build + +Used by the `documentation/docfx-build` composite action to build the full documentation site. + +```json +{ + "build": { + "content": [ + { "files": ["index.md", "toc.yml", "guide/*.{md,yml}"], "exclude": ["_docs/**"] }, + { "dest": "", "files": ["*.yml"], "group": "v1.0", "src": "1.0" }, + { "dest": "", "files": ["*.yml"], "group": "v1.1", "src": "1.1" } + ], + "output": "_docs", + "template": ["default", "modern"] + } +} +``` + +- **Input:** Markdown guide articles + versioned API YAML files +- **Output:** Static HTML site in `api-reference/_docs/` +- **When it runs:** During `release.yml` (automatic on every push to `preview/**` or `release/**`) and `publish-documentation.yml` (manual trigger). + +## Publishing Flow + +``` +src/ changed + │ + ▼ +[build.yml] → docfx metadata → commit YAML to api-reference// + │ + ▼ + [release.yml] or [publish-documentation.yml] + │ + ▼ + docfx build → api-reference/_docs/ + │ + ▼ + GitHub Pages → petesramek.github.io/polyline-algorithm-csharp +``` + +## Adding a New Version to the Site + +When bumping the version to `X.Y`: + +1. The `build` workflow automatically generates metadata YAML files into `api-reference/X.Y/` after the first build on the new branch. +2. Add a new entry to `api-reference.json` under `build.content`: + ```json + { "dest": "", "files": ["*.yml"], "group": "vX.Y", "src": "X.Y", "rootTocPath": "~/toc.html" } + ``` +3. Add a matching group definition: + ```json + "vX.Y": { "dest": "X.Y" } + ``` +4. Add the new version to `api-reference/toc.yml` so it appears in the navigation dropdown: + ```yaml + - name: vX.Y + href: X.Y/PolylineAlgorithm.html + ``` + +## Writing API Documentation + +All public types, interfaces, and members must have XML doc comments. DocFX picks these up automatically: + +```csharp +/// +/// Encodes a sequence of coordinates into a polyline string. +/// +/// The coordinates to encode. +/// An encoded polyline string. +public string Encode(IEnumerable<(double Latitude, double Longitude)> coordinates) { ... } +``` + +After merging a change, verify the rendered documentation at the [API Reference Site](https://petesramek.github.io/polyline-algorithm-csharp/). + +## Local Preview + +To preview the documentation locally: + +```bash +dotnet tool update -g docfx +docfx build ./api-reference/api-reference.json --serve +``` + +Then open `http://localhost:8080` in your browser. diff --git a/docs/api/.gitignore b/docs/api/.gitignore deleted file mode 100644 index da7c71b8..00000000 --- a/docs/api/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -############### -# temp file # -############### -*.yml diff --git a/docs/api/.manifest b/docs/api/.manifest deleted file mode 100644 index 794f97a0..00000000 --- a/docs/api/.manifest +++ /dev/null @@ -1,23 +0,0 @@ -{ - "DropoutCoder.PolylineAlgorithm": "DropoutCoder.PolylineAlgorithm.yml", - "DropoutCoder.PolylineAlgorithm.Encoding": "DropoutCoder.PolylineAlgorithm.Encoding.yml", - "DropoutCoder.PolylineAlgorithm.Encoding.IPolylineEncoding`1": "DropoutCoder.PolylineAlgorithm.Encoding.IPolylineEncoding-1.yml", - "DropoutCoder.PolylineAlgorithm.Encoding.IPolylineEncoding`1.Decode(System.String)": "DropoutCoder.PolylineAlgorithm.Encoding.IPolylineEncoding-1.yml", - "DropoutCoder.PolylineAlgorithm.Encoding.IPolylineEncoding`1.Encode(System.Collections.Generic.IEnumerable{`0})": "DropoutCoder.PolylineAlgorithm.Encoding.IPolylineEncoding-1.yml", - "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncoding": "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncoding.yml", - "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncoding.CreateResult(System.Double,System.Double)": "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncoding.yml", - "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncoding.GetCoordinate(System.ValueTuple{System.Double,System.Double})": "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncoding.yml", - "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncodingBase`1": "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncodingBase-1.yml", - "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncodingBase`1.CreateResult(System.Double,System.Double)": "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncodingBase-1.yml", - "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncodingBase`1.Decode(System.String)": "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncodingBase-1.yml", - "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncodingBase`1.Encode(System.Collections.Generic.IEnumerable{`0})": "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncodingBase-1.yml", - "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncodingBase`1.GetCoordinate(`0)": "DropoutCoder.PolylineAlgorithm.Encoding.PolylineEncodingBase-1.yml", - "DropoutCoder.PolylineAlgorithm.PolylineAlgorithm": "DropoutCoder.PolylineAlgorithm.PolylineAlgorithm.yml", - "DropoutCoder.PolylineAlgorithm.PolylineAlgorithm.Decode(System.Char[])": "DropoutCoder.PolylineAlgorithm.PolylineAlgorithm.yml", - "DropoutCoder.PolylineAlgorithm.PolylineAlgorithm.Encode(System.Collections.Generic.IEnumerable{System.ValueTuple{System.Double,System.Double}})": "DropoutCoder.PolylineAlgorithm.PolylineAlgorithm.yml", - "DropoutCoder.PolylineAlgorithm.Validation": "DropoutCoder.PolylineAlgorithm.Validation.yml", - "DropoutCoder.PolylineAlgorithm.Validation.CoordinateValidator": "DropoutCoder.PolylineAlgorithm.Validation.CoordinateValidator.yml", - "DropoutCoder.PolylineAlgorithm.Validation.CoordinateValidator.IsValid(System.ValueTuple{System.Double,System.Double})": "DropoutCoder.PolylineAlgorithm.Validation.CoordinateValidator.yml", - "DropoutCoder.PolylineAlgorithm.Validation.CoordinateValidator.IsValidLatitude(System.Double)": "DropoutCoder.PolylineAlgorithm.Validation.CoordinateValidator.yml", - "DropoutCoder.PolylineAlgorithm.Validation.CoordinateValidator.IsValidLongitude(System.Double)": "DropoutCoder.PolylineAlgorithm.Validation.CoordinateValidator.yml" -} \ No newline at end of file diff --git a/docs/api/index.md b/docs/api/index.md deleted file mode 100644 index 306e118e..00000000 --- a/docs/api/index.md +++ /dev/null @@ -1,4 +0,0 @@ -# This is the API docs homepage. -Refer to [Markdown](http://daringfireball.net/projects/markdown/) for how to write markdown files. -## Quick Start Notes: -1. Add images to the *images* folder if the file is referencing an image. diff --git a/docs/benchmarks.md b/docs/benchmarks.md new file mode 100644 index 00000000..208cc0e2 --- /dev/null +++ b/docs/benchmarks.md @@ -0,0 +1,132 @@ +# Benchmarks + +This guide explains the benchmark project structure and how to write and run performance benchmarks. + +## Project Structure + +All benchmarks live in the `benchmarks/` directory: + +``` +benchmarks/ +└── PolylineAlgorithm.Benchmarks/ + ├── PolylineEncoderBenchmark.cs # Benchmarks for AbstractPolylineEncoder + ├── PolylineDecoderBenchmark.cs # Benchmarks for PolylineDecoder + ├── PolylineEncodingBenchmark.cs # Benchmarks for PolylineEncoding helpers + ├── Program.cs # BenchmarkSwitcher entry point + └── PolylineAlgorithm.Benchmarks.csproj +``` + +The project targets `net8.0`, `net9.0`, and `net10.0` and references the main `PolylineAlgorithm` library along with the `PolylineAlgorithm.Utility` helper project. + +## Framework + +Benchmarks use [BenchmarkDotNet](https://benchmarkdotnet.org/). Key packages: + +| Package | Purpose | +|---|---| +| `BenchmarkDotNet` | Core benchmarking framework | + +## Writing a New Benchmark + +1. Create a new `.cs` file in `benchmarks/PolylineAlgorithm.Benchmarks/`. +2. Add the standard copyright header. +3. Annotate the class with `[MemoryDiagnoser]` if you want allocation tracking. +4. Use `[Params]` to parameterize input sizes. +5. Mark benchmark methods with `[Benchmark]`. Mark one with `[Benchmark(Baseline = true)]` when comparing variants. +6. Use `[GlobalSetup]` to prepare shared data once per parameter combination. + +Example: + +```csharp +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm.Benchmarks; + +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Engines; + +/// +/// Benchmarks for . +/// +[MemoryDiagnoser] +public class MyEncoderBenchmark { + private readonly Consumer _consumer = new(); + + [Params(1, 100, 1_000)] + public int CoordinatesCount { get; set; } + + private (double Latitude, double Longitude)[] _data = []; + + [GlobalSetup] + public void Setup() { + _data = [.. RandomValueProvider.GetCoordinates(CoordinatesCount)]; + } + + [Benchmark(Baseline = true)] + public void EncodeArray() => new MyEncoder().Encode(_data).Consume(_consumer); +} +``` + +## Running Benchmarks Locally + +Benchmarks **must** run in Release configuration to produce meaningful results: + +```bash +dotnet run \ + --project ./benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj \ + --configuration Release \ + --framework net10.0 \ + -- --filter '*' +``` + +### Useful CLI flags + +| Flag | Description | +|---|---| +| `--filter '*'` | Run all benchmarks | +| `--filter '*Encoder*'` | Run benchmarks whose name contains `Encoder` | +| `--runtimes net8.0 net9.0 net10.0` | Run on multiple runtimes | +| `--exporters GitHub` | Export results as GitHub Flavored Markdown | +| `--memory` | Enable memory diagnoser output | +| `--iterationTime 100` | Iteration time in milliseconds | +| `--join` | Merge results from multiple runs | +| `--artifacts ` | Output directory for results | + +### Example: multi-runtime run with GitHub export + +```bash +dotnet run \ + --project ./benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj \ + --configuration Release \ + --framework net10.0 \ + -- --runtimes net8.0 net9.0 net10.0 \ + --filter '*' \ + --exporters GitHub \ + --memory \ + --iterationTime 100 \ + --join \ + --artifacts /tmp/benchmarks +``` + +## Benchmarks in CI + +The `pull-request` workflow runs benchmarks on Ubuntu, Windows, and macOS when `vars.BENCHMARKDOTNET_RUN_OVERRIDE == 'true'` or when building a release branch. Results are uploaded as artifacts (`benchmark-`) and written to the workflow step summary as a Markdown table. + +Relevant workflow variables: + +| Variable | Description | +|---|---| +| `BENCHMARKDOTNET_WORKING_DIRECTORY` | Working directory for `dotnet run` | +| `BENCHMARKDOTNET_RUNTIMES` | Space-separated runtimes to bench | +| `BENCHMARKDOTNET_FILTER` | Filter expression passed to `--filter` | +| `DEFAULT_BUILD_FRAMEWORK` | Framework used with `--framework` | +| `BENCHMARKDOTNET_RUN_OVERRIDE` | Set to `true` to force benchmark run on PRs | + +## When to Add or Update Benchmarks + +- Add a new benchmark file when introducing a new encoding/decoding code path. +- Update an existing benchmark when changing the algorithmic implementation of an existing path. +- Attach benchmark results to pull requests that affect performance-sensitive code (see [CONTRIBUTING.md](../CONTRIBUTING.md)). diff --git a/docs/branch-strategy.md b/docs/branch-strategy.md new file mode 100644 index 00000000..fb13f779 --- /dev/null +++ b/docs/branch-strategy.md @@ -0,0 +1,113 @@ +# Branch Strategy + +This document describes the branch model, the purpose of each branch type, and how a change moves from a feature branch all the way to a stable release. + +## Branch Types + +| Pattern | Purpose | Protected | +|---|---|---| +| `main` | Latest stable source of truth | ✅ Yes | +| `develop/X.Y` | Active feature development sink for version X.Y | ✅ Yes (PR only) | +| `support/X.Y` | Maintenance / backport development sink for version X.Y | ✅ Yes (PR only) | +| `feature/-` | Individual feature work, merged into `develop/X.Y` via PR | ❌ No | +| `bugfix/-` | Bug fix work, merged into `support/X.Y` via PR | ❌ No | +| `preview/X.Y` | Pre-release stabilization | ✅ Yes (1 approval required) | +| `release/X.Y` | Release stabilization | ✅ Yes (1 approval required) | + +## Change Lifecycle + +``` +1. Feature work + └─ feature/123-my-feature + │ + │ PR → develop/X.Y + │ +2. Bug fix work + └─ bugfix/124-my-fix + │ + │ PR → support/X.Y + │ +3. Promote to preview + └─ promote-branch.yml (manual) → creates preview/X.Y + PR: develop/X.Y → preview/X.Y + │ + │ PR open → [pull-request.yml]: compile, test, pack, benchmark (optional) + │ PR merged → [release.yml]: compile, test, pack, publish-NuGet (pre-release), GitHub release, docs + │ +4. Promote to release + └─ promote-branch.yml (manual) → creates release/X.Y + PR: preview/X.Y → release/X.Y + │ + │ PR open → [pull-request.yml] + │ PR merged → [release.yml]: publish-NuGet (stable), GitHub release, docs, creates support/X.Y (first time only) + │ +5. Back-merge to main (automatic, highest version only) + └─ [release.yml] creates PR: release/X.Y → main (only when X.Y is the highest release branch) + │ + │ PR merged → main is updated to the latest stable source +``` + +## Rules Per Branch Type + +### `main` + +- Represents the latest stable release — always in sync with the highest released version. +- Direct pushes are not allowed (protected). +- Updated automatically via a PR created by `release.yml` whenever the highest `release/X.Y` branch publishes a stable release. +- Serves as the baseline for version bumps: new development versions are derived from the state of `main` at the point the previous release left off. +- The `build.yml` workflow does **not** trigger on `main` pushes (branch-ignore pattern excludes `preview/**` and `release/**`, and `main` does not match `src/**` changes by default in the context of the ignore rules — check the workflow for current specifics). + +### `develop/X.Y` + +- Naming convention: `develop/.` (e.g. `develop/1.2`). +- Protected: all changes are merged via pull request from `feature/**` branches. +- The `build.yml` CI pipeline runs on every push to `src/`. +- When ready for stabilization, use `promote-branch.yml` to create a `preview/X.Y` branch and open a PR. + +### `support/X.Y` + +- Naming convention: `support/.` (e.g. `support/1.0`). +- Auto-created when the first stable release from `release/X.Y` is published. +- Protected: all changes are merged via pull request from `bugfix/**` branches. +- Can be promoted to `preview/X.Y` for a patch release. + +### `feature/-` + +- Short-lived branch for individual feature work (e.g. `feature/123-async-decoder`). +- Merged into the appropriate `develop/X.Y` via pull request. +- Not protected — deleted after merging. + +### `bugfix/-` + +- Short-lived branch for bug fixes (e.g. `bugfix/124-decode-overflow`). +- Merged into the appropriate `support/X.Y` via pull request. +- Not protected — deleted after merging. + +### `preview/X.Y` + +- Created automatically by `promote-branch.yml`. +- Locked immediately: requires at least one PR approval before any merge. +- The `pull-request.yml` workflow runs on every PR targeting this branch. +- On merge, `release.yml` publishes a **pre-release** NuGet package. +- When all pre-release validation is done, promote to `release/X.Y`. + +### `release/X.Y` + +- Created automatically by `promote-branch.yml` from `preview/X.Y`. +- Locked immediately: requires at least one PR approval. +- On merge, `release.yml` publishes a **stable** NuGet package and a GitHub release. +- After the first stable release, a corresponding `support/X.Y` branch is auto-created. +- When `X.Y` is the highest release branch, `release.yml` automatically opens a PR to merge back into `main`, keeping `main` in sync with the latest stable state. + +## Version in Branch Names + +The `X.Y` in `preview/X.Y` and `release/X.Y` drives the version pipeline. See [Versioning](./versioning.md) for details. + +## Environments + +| GitHub Environment | Used by | NuGet feed | +|---|---|---| +| `Development` | `build.yml`, `pull-request.yml` | Azure Artifacts | +| `Production` | `release.yml` | NuGet.org | + +## Locking and Unlocking Branches + +`preview/**` and `release/**` branches are locked via the [`github/branch-protection/lock`](./composite-actions.md#githubbranch-protectionlock) composite action when created. `develop/X.Y` and `support/X.Y` branches must be manually configured as protected in repository settings (PR required, no direct pushes). The [`github/branch-protection/unlock`](./composite-actions.md#githubbranch-protectionunlock) action temporarily removes protection when a workflow needs to push directly (e.g., `bump-version.yml`). Branches are always re-locked immediately after. diff --git a/docs/composite-actions.md b/docs/composite-actions.md new file mode 100644 index 00000000..66243f45 --- /dev/null +++ b/docs/composite-actions.md @@ -0,0 +1,307 @@ +# Composite Actions + +All reusable GitHub Actions live under `.github/actions/`. They are referenced with `uses: './.github/actions/'` inside workflows. This document catalogs each action, its inputs, outputs, and typical use. + +## documentation/docfx-build + +**Path:** `.github/actions/documentation/docfx-build` +**Description:** Installs the `docfx` global tool, builds a DocFX site from a JSON manifest, and uploads the generated output as a workflow artifact. + +| Input | Required | Default | Description | +|---|---|---|---| +| `artifact-name` | ✅ | — | Name of the uploaded artifact | +| `docfx-json-manifest` | ✅ | — | Path to the `docfx.json` build manifest | +| `output-directory` | ✅ | — | Target directory where DocFX writes output | +| `dotnet_sdk_version` | ❌ | `10.x` | .NET SDK version to use | + +**Used by:** `publish-documentation` workflow. + +--- + +## documentation/docfx-metadata + +**Path:** `.github/actions/documentation/docfx-metadata` +**Description:** Runs `docfx metadata` to extract API metadata from source (`.yml` files), copies the result to an output directory, and uploads it as a workflow artifact. + +| Input | Required | Default | Description | +|---|---|---|---| +| `artifact-name` | ✅ | — | Name of the uploaded artifact | +| `docfx-json-manifest` | ✅ | — | Path to the metadata-only `docfx.json` manifest | +| `temporary-directory` | ✅ | — | Temp folder DocFX writes raw metadata into | +| `output-directory` | ✅ | — | Final output directory for the metadata | +| `dotnet_sdk_version` | ❌ | `10.x` | .NET SDK version to use | + +**Used by:** `build` workflow (to regenerate versioned assembly metadata). + +--- + +## git/push-changes + +**Path:** `.github/actions/git/push-changes` +**Description:** Optionally downloads an artifact, stages all changes in a working directory, and pushes them to the current (or a specified target) branch. Skips the commit if there are no staged changes. + +| Input | Required | Default | Description | +|---|---|---|---| +| `commit-message` | ✅ | — | Commit message | +| `artifact-name` | ❌ | `''` | Artifact to download before staging | +| `working-directory` | ❌ | `.` | Directory to stage and commit from | +| `target-branch` | ❌ | `''` | Branch to push to (creates it if absent) | +| `dotnet_sdk_version` | ❌ | `10.x` | .NET SDK version | + +**Used by:** `source/format`, `build` (assembly metadata), and other workflows that commit generated artefacts. + +--- + +## github/branch-protection/lock + +**Path:** `.github/actions/github/branch-protection/lock` +**Description:** Applies branch protection rules to a branch via the GitHub API: requires at least one PR approval, disables force pushes and deletions. + +| Input | Required | Description | +|---|---|---| +| `branch` | ✅ | Branch name to protect | + +**Requires:** `administration: write` permission on the workflow token. +**Used by:** `promote-branch` workflow (after creating a new `preview/**` or `release/**` branch). + +--- + +## github/branch-protection/unlock + +**Path:** `.github/actions/github/branch-protection/unlock` +**Description:** Removes all branch protection rules from a branch so a workflow can push directly to it. Always re-lock immediately after. + +| Input | Required | Description | +|---|---|---| +| `branch` | ✅ | Branch name to unprotect | + +**Requires:** `administration: write` permission on the workflow token. +**Used by:** Workflows that need to push commits directly to protected branches (e.g., `bump-version`). + +--- + +## github/create-release + +**Path:** `.github/actions/github/create-release` +**Description:** Creates a git tag and a GitHub release with auto-generated release notes. Supports pre-release flag and a notes-start-tag for scoping the changelog. + +| Input | Required | Default | Description | +|---|---|---|---| +| `release-version` | ✅ | — | SemVer string used for both the tag and the release name | +| `is-preview` | ✅ | — | `'true'` marks the release as pre-release | +| `notes-start-tag` | ❌ | `''` | Git tag from which to start auto-generated notes | + +**Used by:** `release` workflow. + +--- + +## github/write-file-to-summary + +**Path:** `.github/actions/github/write-file-to-summary` +**Description:** Appends the contents of a file (matched by glob) to the GitHub step summary (`GITHUB_STEP_SUMMARY`). + +| Input | Required | Default | Description | +|---|---|---|---| +| `file-glob-pattern` | ✅ | — | Glob pattern for the file(s) to append | +| `working-directory` | ❌ | `${{ github.workspace }}` | Directory to resolve the glob against | + +--- + +## nuget/publish-package + +**Path:** `.github/actions/nuget/publish-package` +**Description:** Downloads a NuGet package artifact and pushes it to either a public NuGet feed or an Azure Artifacts feed. Validates the `nuget-feed-server` value before proceeding. + +| Input | Required | Default | Description | +|---|---|---|---| +| `package-artifact-name` | ✅ | — | Name of the artifact containing `.nupkg` files | +| `nuget-feed-url` | ✅ | — | Feed endpoint URL | +| `nuget-feed-api-key` | ✅ | — | API key / PAT for the feed | +| `nuget-feed-server` | ✅ | — | `'NuGet'` or `'AzureArtifacts'` | +| `dotnet-sdk-version` | ❌ | `10.x` | .NET SDK version | +| `working-directory` | ❌ | `${{ github.workspace }}` | Directory containing `.nupkg` files | + +**Used by:** `build` (Development environment) and `release` (NuGet.org) workflows. + +--- + +## source/compile + +**Path:** `.github/actions/source/compile` +**Description:** Builds a project in Release configuration, injecting version properties, and uploads the binary output as a workflow artifact. + +| Input | Required | Default | Description | +|---|---|---|---| +| `assembly-version` | ✅ | — | `AssemblyVersion` MSBuild property | +| `assembly-informational-version` | ✅ | — | `AssemblyInformationalVersion` MSBuild property | +| `file-version` | ✅ | — | `FileVersion` MSBuild property | +| `treat-warnins-as-error` | ✅ | — | When `'true'`, runs `dotnet format analyzers --verify-no-changes` | +| `project-path` | ✅ | — | Glob pattern for project files | +| `dotnet_sdk_version` | ❌ | `10.x` | .NET SDK version | +| `build-configuration` | ❌ | `Release` | Build configuration | +| `build-platform` | ❌ | `Any CPU` | MSBuild platform | +| `upload-build-artifacts` | ❌ | `true` | Whether to upload binary output | +| `build-artifacts-name` | ❌ | `build` | Artifact name | + +**Used by:** `build` and `pull-request` workflows. + +--- + +## source/format + +**Path:** `.github/actions/source/format` +**Description:** Runs `dotnet format whitespace`, `dotnet format style`, and optionally `dotnet format analyzers` on the codebase, then pushes any changes back to the branch via `git/push-changes`. + +| Input | Required | Default | Description | +|---|---|---|---| +| `project-path` | ✅ | — | Path or glob for the project/solution | +| `dotnet_sdk_version` | ❌ | `10.x` | .NET SDK version | +| `format-whitespace` | ❌ | `true` | Run `dotnet format whitespace` | +| `format-style` | ❌ | `true` | Run `dotnet format style` | +| `format-analyzers` | ❌ | `false` | Run `dotnet format analyzers` | +| `format-analyzers-diagnostics-parameter` | ❌ | `''` | Extra `--diagnostics` argument | + +**Used by:** `build` workflow (`format` job). + +--- + +## testing/test + +**Path:** `.github/actions/testing/test` +**Description:** Runs `dotnet test` with optional TRX logging and code coverage collection, then uploads all test result files as a workflow artifact. + +| Input | Required | Default | Description | +|---|---|---|---| +| `project-path` | ✅ | — | Glob pattern for test project files | +| `test-results-directory` | ❌ | `test-results` | Directory where test outputs are written | +| `code-coverage-settings-file` | ❌ | `''` | Path to the coverage settings XML | +| `use-trf-logger` | ❌ | `true` | Enable TRX logger (`--report-trx`) | +| `collect-code-coverage` | ❌ | `true` | Enable code coverage (`--coverage`) | +| `code-coverage-output-format` | ❌ | `cobertura` | Coverage output format | +| `upload-test-artifacts` | ❌ | `true` | Upload collected test result files | +| `test-artifacts-name` | ❌ | `test-results` | Artifact name | +| `dotnet_sdk_version` | ❌ | `10.x` | .NET SDK version | + +**Used by:** `build` and `pull-request` workflows (`test` job). + +--- + +## testing/test-report + +**Path:** `.github/actions/testing/test-report` +**Description:** Installs `LiquidTestReports.Cli` and converts `.trx` files in the test result folder into a single Markdown report. + +| Input | Required | Default | Description | +|---|---|---|---| +| `test-result-folder` | ✅ | — | Folder containing `.trx` files | +| `test-report-filename` | ❌ | `test-report.md` | Output filename | +| `dotnet_sdk_version` | ❌ | `10.x` | .NET SDK version | + +| Output | Description | +|---|---| +| `test-report-file` | Full path to the generated Markdown report | + +**Used by:** `build` and `pull-request` workflows (report written to step summary). + +--- + +## testing/code-coverage + +**Path:** `.github/actions/testing/code-coverage` +**Description:** Merges multiple Cobertura coverage files using `dotnet-coverage`, then generates a Markdown summary report with `reportgenerator`. + +| Input | Required | Default | Description | +|---|---|---|---| +| `test-result-folder` | ✅ | — | Folder containing `*.cobertura.xml` files | +| `dotnet_sdk_version` | ❌ | `10.x` | .NET SDK version | + +| Output | Description | +|---|---| +| `code-coverage-report-file` | Path to the generated `Summary.md` | +| `code-coverage-merge-file` | Path to the merged Cobertura XML file | + +**Used by:** `build` and `pull-request` workflows (report written to step summary). + +--- + +## versioning/extract-version + +**Path:** `.github/actions/versioning/extract-version` +**Description:** Extracts a `MAJOR.MINOR` version from a branch name using a configurable regex (default `(\d+).(\d+)`). Falls back to a default version if no match is found. + +| Input | Required | Default | Description | +|---|---|---|---| +| `branch-name` | ✅ | — | Branch name to parse | +| `default-version` | ❌ | `0.0` | Fallback when no version is found | +| `version-format` | ❌ | `(\d+).(\d+)` | Regex to extract the version | +| `dotnet_sdk_version` | ❌ | `10.x` | .NET SDK version | + +| Output | Description | +|---|---| +| `version` | Extracted `MAJOR.MINOR` string | + +**Used by:** All versioning-aware workflows. + +--- + +## versioning/format-version + +**Path:** `.github/actions/versioning/format-version` +**Description:** Produces all version strings used for .NET assembly metadata and NuGet package versions from a base `MAJOR.MINOR` version plus context inputs. + +| Input | Required | Description | +|---|---|---| +| `version` | ✅ | Base `MAJOR.MINOR` version | +| `patch` | ✅ | GitHub run number (used as patch segment) | +| `build-number` | ✅ | Commit count ahead of `main` | +| `sha` | ✅ | Commit SHA (appended to informational version) | +| `pre-release-tag` | ✅ | Pre-release label (`preview`, branch slug, or empty for stable) | + +| Output | Description | +|---|---| +| `friendly-version` | `MAJOR.MINOR` (human-readable label) | +| `assembly-version` | `MAJOR.MINOR.patch.buildNumber` | +| `assembly-informational-version` | `MAJOR.MINOR.patch+sha` | +| `file-version` | Same as `assembly-version` | +| `release-version` | `MAJOR.MINOR.patch[-preTag.buildNumber]` (NuGet version) | + +See [Versioning](./versioning.md) for the full pipeline. + +--- + +## Creating a New Composite Action + +1. Create a new directory under `.github/actions///`. +2. Add an `action.yml` file with `runs.using: composite`. +3. Declare all `inputs` with `description` and `required`. +4. Declare all `outputs` (if any) with `value` expressions referencing step outputs. +5. Keep each action focused on a single responsibility. +6. Reference optional `.NET SDK` version via an `inputs.dotnet_sdk_version` input (default `10.x`) for consistency with existing actions. +7. Use `actions/checkout@v6` as the first step when the action needs file access. + +```yaml +name: 'My Action' +author: 'Pete Sramek' +description: 'Short description of what this action does.' +inputs: + my-input: + description: 'Description of the input.' + required: true + dotnet_sdk_version: + description: '.NET SDK version. Default: 10.x' + required: false + default: '10.x' +outputs: + my-output: + description: 'Description of the output.' + value: ${{ steps.my-step.outputs.my-output }} +runs: + using: composite + steps: + - name: 'Checkout ${{ github.head_ref || github.ref }}' + uses: actions/checkout@v6 + - name: 'My step' + id: my-step + shell: bash + run: echo "my-output=value" >> $GITHUB_OUTPUT +``` diff --git a/docs/docfx.json b/docs/docfx.json deleted file mode 100644 index 86263ff4..00000000 --- a/docs/docfx.json +++ /dev/null @@ -1,58 +0,0 @@ -{ - "metadata": [ - { - "src": [ - { - "files": [ - "**.csproj" - ], - "src": "../src/" - } - ], - "dest": "api", - "disableGitFeatures": false - } - ], - "build": { - "content": [ - { - "files": [ - "**.yml", - "index.md" - ], - "src": "api", - "dest": "." - } - ], - "resource": [ - { - "files": [ - "images/**" - ] - } - ], - "overwrite": [ - { - "files": [ - "apidoc/**.md" - ], - "exclude": [ - "obj/**", - "_site/**" - ] - } - ], - "dest": "_site", - "globalMetadataFiles": [], - "fileMetadataFiles": [], - "template": [ - "default", - "modern" - ], - "postProcessors": [], - "noLangKeyword": false, - "keepFileLink": false, - "cleanupCacheHistory": false, - "disableGitFeatures": false - } -} \ No newline at end of file diff --git a/docs/extensibility.md b/docs/extensibility.md new file mode 100644 index 00000000..6df0d90a --- /dev/null +++ b/docs/extensibility.md @@ -0,0 +1,128 @@ +# Extensibility + +This guide explains how to use PolylineAlgorithm with your own coordinate types and polyline representations. + +## Design Overview + +The library is built around two generic abstract base classes: + +| Class | Purpose | +|---|---| +| `AbstractPolylineEncoder` | Encodes a sequence of coordinates into an encoded polyline | +| `AbstractPolylineDecoder` | Decodes an encoded polyline into a sequence of coordinates | + +Both implement corresponding interfaces (`IPolylineEncoder` and `IPolylineDecoder`). + +Type parameters: + +| Parameter | Meaning | Examples | +|---|---|---| +| `TCoordinate` | Your coordinate type | `(double Lat, double Lon)`, a custom `GeoPoint` class | +| `TPolyline` | Your polyline representation | `string`, `char[]`, `ReadOnlyMemory`, a custom wrapper | + +## Adding a Custom Encoder + +Subclass `AbstractPolylineEncoder` and implement the three abstract methods: + +| Method | Signature | What to return | +|---|---|---| +| `GetLatitude` | `double GetLatitude(TCoordinate current)` | The latitude in decimal degrees | +| `GetLongitude` | `double GetLongitude(TCoordinate current)` | The longitude in decimal degrees | +| `CreatePolyline` | `TPolyline CreatePolyline(ReadOnlyMemory polyline)` | Your output type built from the encoded char buffer | + +Example — encode `(double Latitude, double Longitude)` tuples to `string`: + +```csharp +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm; + +using PolylineAlgorithm.Abstraction; + +/// +/// Encodes geographic coordinate tuples into a Google-encoded polyline string. +/// +public sealed class TuplePolylineEncoder : AbstractPolylineEncoder<(double Latitude, double Longitude), string> { + /// + protected override string CreatePolyline(ReadOnlyMemory polyline) + => polyline.ToString(); + + /// + protected override double GetLatitude((double Latitude, double Longitude) current) + => current.Latitude; + + /// + protected override double GetLongitude((double Latitude, double Longitude) current) + => current.Longitude; +} +``` + +To use custom encoding options (e.g. precision 6): + +```csharp +var options = new PolylineEncodingOptionsBuilder() + .WithPrecision(6) + .Build(); + +var encoder = new TuplePolylineEncoder(options); +``` + +## Adding a Custom Decoder + +Subclass `AbstractPolylineDecoder` and implement the two abstract methods: + +| Method | Signature | What to return | +|---|---|---| +| `GetReadOnlyMemory` | `ReadOnlyMemory GetReadOnlyMemory(in TPolyline polyline)` | A `ReadOnlyMemory` view over the encoded polyline | +| `CreateCoordinate` | `TCoordinate CreateCoordinate(double latitude, double longitude)` | An instance of your coordinate type | + +Example — decode a `string` polyline into `(double Latitude, double Longitude)` tuples: + +```csharp +// +// Copyright © Pete Sramek. All rights reserved. +// Licensed under the MIT License. See LICENSE file in the project root for full license information. +// + +namespace PolylineAlgorithm; + +using PolylineAlgorithm.Abstraction; + +/// +/// Decodes a Google-encoded polyline string into geographic coordinate tuples. +/// +public sealed class TuplePolylineDecoder : AbstractPolylineDecoder { + /// + protected override ReadOnlyMemory GetReadOnlyMemory(in string polyline) + => polyline.AsMemory(); + + /// + protected override (double Latitude, double Longitude) CreateCoordinate(double latitude, double longitude) + => (latitude, longitude); +} +``` + +## Encoding Options + +`PolylineEncodingOptions` controls shared behavior. Configure it via `PolylineEncodingOptionsBuilder`: + +```csharp +var options = new PolylineEncodingOptionsBuilder() + .WithPrecision(5) // decimal digits (default: 5) + .WithLoggerFactory(myLoggerFactory) // enables internal logging + .Build(); +``` + +Pass the options to the constructor of any encoder or decoder: + +```csharp +var encoder = new TuplePolylineEncoder(options); +var decoder = new TuplePolylineDecoder(options); +``` + +## Extension Methods + +The library provides extension methods for `IPolylineEncoder` and `IPolylineDecoder` to support common collection types (`IEnumerable`, arrays, `ReadOnlyMemory`). These are in `PolylineAlgorithm.Extensions`. Your custom implementations automatically benefit from these extension methods as long as you implement the interfaces. diff --git a/docs/favicon.ico b/docs/favicon.ico deleted file mode 100644 index 71570f61..00000000 Binary files a/docs/favicon.ico and /dev/null differ diff --git a/docs/fonts/glyphicons-halflings-regular.eot b/docs/fonts/glyphicons-halflings-regular.eot deleted file mode 100644 index b93a4953..00000000 Binary files a/docs/fonts/glyphicons-halflings-regular.eot and /dev/null differ diff --git a/docs/fonts/glyphicons-halflings-regular.svg b/docs/fonts/glyphicons-halflings-regular.svg deleted file mode 100644 index 94fb5490..00000000 --- a/docs/fonts/glyphicons-halflings-regular.svg +++ /dev/null @@ -1,288 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/docs/fonts/glyphicons-halflings-regular.ttf b/docs/fonts/glyphicons-halflings-regular.ttf deleted file mode 100644 index 1413fc60..00000000 Binary files a/docs/fonts/glyphicons-halflings-regular.ttf and /dev/null differ diff --git a/docs/fonts/glyphicons-halflings-regular.woff b/docs/fonts/glyphicons-halflings-regular.woff deleted file mode 100644 index 9e612858..00000000 Binary files a/docs/fonts/glyphicons-halflings-regular.woff and /dev/null differ diff --git a/docs/fonts/glyphicons-halflings-regular.woff2 b/docs/fonts/glyphicons-halflings-regular.woff2 deleted file mode 100644 index 64539b54..00000000 Binary files a/docs/fonts/glyphicons-halflings-regular.woff2 and /dev/null differ diff --git a/docs/local-development.md b/docs/local-development.md new file mode 100644 index 00000000..48fa5f76 --- /dev/null +++ b/docs/local-development.md @@ -0,0 +1,74 @@ +# Local Development + +This guide explains how to build, test, and format the PolylineAlgorithm codebase locally. + +## Prerequisites + +- [.NET 10 SDK](https://dotnet.microsoft.com/download) (or newer) +- A terminal / shell + +## Building + +Build the main library using the solution file: + +```bash +dotnet build PolylineAlgorithm.slnx +``` + +To build in Release configuration (required before running tests or benchmarks): + +```bash +dotnet build PolylineAlgorithm.slnx --configuration Release +``` + +## Running Tests + +Run all unit tests: + +```bash +dotnet test ./tests/PolylineAlgorithm.Tests/PolylineAlgorithm.Tests.csproj --configuration Release +``` + +> **Note:** Always use Release configuration when running tests. The Debug configuration contains a `Debug.Assert` in `AbstractPolylineEncoderTest` that will crash the test runner. + +To collect code coverage at the same time: + +```bash +dotnet test ./tests/PolylineAlgorithm.Tests/PolylineAlgorithm.Tests.csproj \ + --configuration Release \ + --coverage \ + --coverage-output-format cobertura \ + --coverage-settings ./code-coverage-settings.xml +``` + +## Running Benchmarks + +See [Benchmarks](./benchmarks.md) for full details. Quick run: + +```bash +dotnet run --project ./benchmarks/PolylineAlgorithm.Benchmarks/PolylineAlgorithm.Benchmarks.csproj \ + --configuration Release \ + --framework net10.0 \ + -- --filter '*' +``` + +## Formatting + +The project uses `dotnet format` for code style enforcement. Run all format steps before committing: + +```bash +# Fix whitespace +dotnet format whitespace + +# Fix code style +dotnet format style + +# Fix analyzer warnings (optional — run when you want to fix diagnostics) +dotnet format analyzers +``` + +The CI `format` job also runs `dotnet format` automatically on every push to non-release branches and pushes the formatted result back to the branch. + +## Editor Configuration + +Code style rules are stored in `.editorconfig` at the repository root. Any compliant IDE (Visual Studio, VS Code with C# Dev Kit, Rider) will pick these up automatically. diff --git a/docs/logo.svg b/docs/logo.svg deleted file mode 100644 index ccb2d7bc..00000000 --- a/docs/logo.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - -Created by Docfx - - - - - - - diff --git a/docs/manifest.json b/docs/manifest.json deleted file mode 100644 index 202b1ff4..00000000 --- a/docs/manifest.json +++ /dev/null @@ -1 +0,0 @@ -{"homepages":[],"source_base_path":"C:/Projects/GitHub/Cloudikka/polyline-algorithm-csharp/src/Cloudikka.PolylineAlgorithm","xrefmap":"xrefmap.yml","files":[{"type":"ManagedReference","source_relative_path":"../../docs/source/api/Cloudikka.PolylineAlgorithm.PolylineAlgorithm.yml","output":{".html":{"relative_path":"api/Cloudikka.PolylineAlgorithm.PolylineAlgorithm.html","hash":"DuxXQJFG0JnJK4XZfGoqSw=="}},"is_incremental":false},{"type":"ManagedReference","source_relative_path":"../../docs/source/api/Cloudikka.PolylineAlgorithm.Validation.CoordinateValidator.yml","output":{".html":{"relative_path":"api/Cloudikka.PolylineAlgorithm.Validation.CoordinateValidator.html","hash":"bHKr12B+LaPPoDIiM/GdEw=="}},"is_incremental":false},{"type":"ManagedReference","source_relative_path":"../../docs/source/api/Cloudikka.PolylineAlgorithm.Validation.yml","output":{".html":{"relative_path":"api/Cloudikka.PolylineAlgorithm.Validation.html","hash":"JRNjS8Iv+pO6MsunBCDXvA=="}},"is_incremental":false},{"type":"ManagedReference","source_relative_path":"../../docs/source/api/Cloudikka.PolylineAlgorithm.yml","output":{".html":{"relative_path":"api/Cloudikka.PolylineAlgorithm.html","hash":"m5VwU6ucCYySNK9o1V+ObQ=="}},"is_incremental":false},{"type":"Toc","source_relative_path":"../../docs/source/api/toc.yml","output":{".html":{"relative_path":"api/toc.html","hash":"MJBrHUiuNZAV/fYefVG24A=="}},"is_incremental":false},{"type":"Toc","source_relative_path":"../../docs/source/toc.yml","output":{".html":{"relative_path":"toc.html","hash":"ErjYhgldNN21zQh8L4Olrg=="}},"is_incremental":false},{"type":"Toc","source_relative_path":"../../docs/source/articles/toc.yml","output":{".html":{"relative_path":"articles/toc.html","hash":"dsfYf/xjrWq6mnqKHgnW9g=="}},"is_incremental":false},{"type":"Conceptual","source_relative_path":"../../docs/source/api/index.md","output":{".html":{"relative_path":"api/index.html","hash":"dPgHIsVe2ZXJr7/R6lmg7Q=="}},"is_incremental":false},{"type":"Conceptual","source_relative_path":"../../docs/source/index.md","output":{".html":{"relative_path":"index.html","hash":"OqddFoprNY6Yz18TAcxrrA=="}},"is_incremental":false},{"type":"Conceptual","source_relative_path":"../../docs/source/articles/intro.md","output":{".html":{"relative_path":"articles/intro.html","hash":"bhq6zzP77tZUevOHXJu4fw=="}},"is_incremental":false},{"type":"ManagedReference","source_relative_path":"../../docs/source/api/Cloudikka.PolylineAlgorithm.Encoding.IPolylineEncoding-1.yml","output":{".html":{"relative_path":"api/Cloudikka.PolylineAlgorithm.Encoding.IPolylineEncoding-1.html","hash":"TcaAT5pBvPQKZDEXksnw6w=="}},"is_incremental":false},{"type":"ManagedReference","source_relative_path":"../../docs/source/api/Cloudikka.PolylineAlgorithm.Encoding.PolylineEncoding.yml","output":{".html":{"relative_path":"api/Cloudikka.PolylineAlgorithm.Encoding.PolylineEncoding.html","hash":"AkMKRIs+xaAFilAKiDfIRA=="}},"is_incremental":false},{"type":"ManagedReference","source_relative_path":"../../docs/source/api/Cloudikka.PolylineAlgorithm.Encoding.PolylineEncodingBase-1.yml","output":{".html":{"relative_path":"api/Cloudikka.PolylineAlgorithm.Encoding.PolylineEncodingBase-1.html","hash":"xGg93o6lzLNcZTa046c4cQ=="}},"is_incremental":false},{"type":"ManagedReference","source_relative_path":"../../docs/source/api/Cloudikka.PolylineAlgorithm.Encoding.yml","output":{".html":{"relative_path":"api/Cloudikka.PolylineAlgorithm.Encoding.html","hash":"q9cIXLi1Eu31lwAwDxYBmA=="}},"is_incremental":false}]} \ No newline at end of file diff --git a/docs/search-stopwords.json b/docs/search-stopwords.json deleted file mode 100644 index 0bdcc2c0..00000000 --- a/docs/search-stopwords.json +++ /dev/null @@ -1,121 +0,0 @@ -[ - "a", - "able", - "about", - "across", - "after", - "all", - "almost", - "also", - "am", - "among", - "an", - "and", - "any", - "are", - "as", - "at", - "be", - "because", - "been", - "but", - "by", - "can", - "cannot", - "could", - "dear", - "did", - "do", - "does", - "either", - "else", - "ever", - "every", - "for", - "from", - "get", - "got", - "had", - "has", - "have", - "he", - "her", - "hers", - "him", - "his", - "how", - "however", - "i", - "if", - "in", - "into", - "is", - "it", - "its", - "just", - "least", - "let", - "like", - "likely", - "may", - "me", - "might", - "most", - "must", - "my", - "neither", - "no", - "nor", - "not", - "of", - "off", - "often", - "on", - "only", - "or", - "other", - "our", - "own", - "rather", - "said", - "say", - "says", - "she", - "should", - "since", - "so", - "some", - "than", - "that", - "the", - "their", - "them", - "then", - "there", - "these", - "they", - "this", - "tis", - "to", - "too", - "twas", - "us", - "wants", - "was", - "we", - "were", - "what", - "when", - "where", - "which", - "while", - "who", - "whom", - "why", - "will", - "with", - "would", - "yet", - "you", - "your" -] diff --git a/docs/styles/docfx.css b/docs/styles/docfx.css deleted file mode 100644 index 83800086..00000000 --- a/docs/styles/docfx.css +++ /dev/null @@ -1,882 +0,0 @@ -/* Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information. */ -html, -body { - font-family: 'Segoe UI', Tahoma, Helvetica, sans-serif; - height: 100%; -} -button, -a { - color: #337ab7; - cursor: pointer; -} -button:hover, -button:focus, -a:hover, -a:focus { - color: #23527c; - text-decoration: none; -} -a.disable, -a.disable:hover { - text-decoration: none; - cursor: default; - color: #000000; -} - -/* workaround for leave space for fixed navbar with # anchor url*/ - -h1:before, -h2:before, -h3:before, -h4:before { - content: ''; - display: block; - position: relative; - width: 0; - height: 100px; - margin-top: -100px; -} - -h1, h2, h3, h4, h5, h6, span.xref { - word-wrap: break-word; - word-break: break-all; -} - -h1 mark, -h2 mark, -h3 mark, -h4 mark, -h5 mark, -h6 mark { - padding: 0; -} - -.inheritance .level0:before, -.inheritance .level1:before, -.inheritance .level2:before, -.inheritance .level3:before, -.inheritance .level4:before, -.inheritance .level5:before { - content: '↳'; - margin-right: 5px; -} - -.inheritance .level0 { - margin-left: 0em; -} - -.inheritance .level1 { - margin-left: 1em; -} - -.inheritance .level2 { - margin-left: 2em; -} - -.inheritance .level3 { - margin-left: 3em; -} - -.inheritance .level4 { - margin-left: 4em; -} - -.inheritance .level5 { - margin-left: 5em; -} - -span.parametername, -span.paramref, -span.typeparamref { - font-style: italic; -} -span.languagekeyword{ - font-weight: bold; -} - -svg:hover path { - fill: #ffffff; -} - -.hljs { - display: inline; - background-color: inherit; - padding: 0; -} -/* additional spacing fixes */ -.btn + .btn { - margin-left: 10px; -} -.btn.pull-right { - margin-left: 10px; - margin-top: 5px; -} -.table { - margin-bottom: 10px; -} -table p { - margin-bottom: 0; -} -table a { - display: inline-block; -} -h1, -.h1, -h2, -.h2, -h3, -.h3 { - margin-top: 15px; - margin-bottom: 10px; - font-weight: 400; -} -h4, -.h4, -h5, -.h5, -h6, -.h6 { - margin-top: 10px; - margin-bottom: 5px; -} -.navbar { - margin-bottom: 0; -} -#wrapper { - min-height: 100%; - position: relative; -} -/* blends header footer and content together with gradient effect */ -.grad-top { - /* For Safari 5.1 to 6.0 */ - /* For Opera 11.1 to 12.0 */ - /* For Firefox 3.6 to 15 */ - background: linear-gradient(rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0)); - /* Standard syntax */ - height: 5px; -} -.grad-bottom { - /* For Safari 5.1 to 6.0 */ - /* For Opera 11.1 to 12.0 */ - /* For Firefox 3.6 to 15 */ - background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.05)); - /* Standard syntax */ - height: 5px; -} -.divider { - margin: 0 5px; - color: #cccccc; -} -hr { - border-color: #cccccc; -} -header { - position: fixed; - top: 0; - left: 0; - right: 0; - z-index: 1000; -} -header .navbar { - border-width: 0 0 1px; - border-radius: 0; -} -.navbar-brand { - font-size: inherit; - padding: 0; -} -.navbar-collapse { - margin: 0 -15px; -} -.subnav { - min-height: 40px; -} - -.inheritance h5, .inheritedMembers h5{ - padding-bottom: 5px; - border-bottom: 1px solid #ccc; -} - -article h1, article h2, article h3, article h4{ - margin-top: 25px; -} - -article h4{ - border-bottom: 1px solid #ccc; -} - -article span.small.pull-right{ - margin-top: 20px; -} - -article section { - margin-left: 1em; -} - -/*.expand-all { - padding: 10px 0; -}*/ -.breadcrumb { - margin: 0; - padding: 10px 0; - background-color: inherit; - white-space: nowrap; -} -.breadcrumb > li + li:before { - content: "\00a0/"; -} -#autocollapse.collapsed .navbar-header { - float: none; -} -#autocollapse.collapsed .navbar-toggle { - display: block; -} -#autocollapse.collapsed .navbar-collapse { - border-top: 1px solid transparent; - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); -} -#autocollapse.collapsed .navbar-collapse.collapse { - display: none !important; -} -#autocollapse.collapsed .navbar-nav { - float: none !important; - margin: 7.5px -15px; -} -#autocollapse.collapsed .navbar-nav > li { - float: none; -} -#autocollapse.collapsed .navbar-nav > li > a { - padding-top: 10px; - padding-bottom: 10px; -} -#autocollapse.collapsed .collapse.in, -#autocollapse.collapsed .collapsing { - display: block !important; -} -#autocollapse.collapsed .collapse.in .navbar-right, -#autocollapse.collapsed .collapsing .navbar-right { - float: none !important; -} -#autocollapse .form-group { - width: 100%; -} -#autocollapse .form-control { - width: 100%; -} -#autocollapse .navbar-header { - margin-left: 0; - margin-right: 0; -} -#autocollapse .navbar-brand { - margin-left: 0; -} -.collapse.in, -.collapsing { - text-align: center; -} -.collapsing .navbar-form { - margin: 0 auto; - max-width: 400px; - padding: 10px 15px; - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); -} -.collapsed .collapse.in .navbar-form { - margin: 0 auto; - max-width: 400px; - padding: 10px 15px; - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); -} -.navbar .navbar-nav { - display: inline-block; -} -.docs-search { - background: white; - vertical-align: middle; -} -.docs-search > .search-query { - font-size: 14px; - border: 0; - width: 120%; - color: #555; -} -.docs-search > .search-query:focus { - outline: 0; -} -.search-results-frame { - clear: both; - display: table; - width: 100%; -} -.search-results.ng-hide { - display: none; -} -.search-results-container { - padding-bottom: 1em; - border-top: 1px solid #111; - background: rgba(25, 25, 25, 0.5); -} -.search-results-container .search-results-group { - padding-top: 50px !important; - padding: 10px; -} -.search-results-group-heading { - font-family: "Open Sans"; - padding-left: 10px; - color: white; -} -.search-close { - position: absolute; - left: 50%; - margin-left: -100px; - color: white; - text-align: center; - padding: 5px; - background: #333; - border-top-right-radius: 5px; - border-top-left-radius: 5px; - width: 200px; - box-shadow: 0 0 10px #111; -} -#search { - display: none; -} - -/* Search results display*/ -#search-results { - max-width: 960px !important; - margin-top: 120px; - margin-bottom: 115px; - margin-left: auto; - margin-right: auto; - line-height: 1.8; - display: none; -} - -#search-results>.search-list { - text-align: center; - font-size: 2.5rem; - margin-bottom: 50px; -} - -#search-results p { - text-align: center; -} - -#search-results .sr-items { - font-size: 24px; -} - -.sr-item { - margin-bottom: 25px; -} - -.sr-item>.item-href { - font-size: 14px; - color: #093; -} - -.sr-item>.item-brief { - font-size: 13px; -} - -.pagination>li>a { - color: #47A7A0 -} - -.pagination>.active>a { - background-color: #47A7A0; - border-color: #47A7A0; -} - -.fixed_header { - position: fixed; - width: 100%; - padding-bottom: 10px; - padding-top: 10px; - margin: 0px; - top: 0; - z-index: 9999; - left: 0; -} - -.fixed_header+.toc{ - margin-top: 50px; - margin-left: 0; -} - -.sidenav, .fixed_header, .toc { - background-color: #f1f1f1; -} - -.sidetoc { - position: fixed; - width: 260px; - top: 150px; - bottom: 0; - overflow-x: hidden; - overflow-y: auto; - background-color: #f1f1f1; - border-left: 1px solid #e7e7e7; - border-right: 1px solid #e7e7e7; - z-index: 1; -} - -body .toc{ - background-color: #f1f1f1; - overflow-x: hidden; -} - -.sidetoggle.ng-hide { - display: block !important; -} -.sidetoc-expand > .caret { - margin-left: 0px; - margin-top: -2px; -} -.sidetoc-expand > .caret-side { - border-left: 4px solid; - border-top: 4px solid transparent; - border-bottom: 4px solid transparent; - margin-left: 4px; - margin-top: -4px; -} -.sidetoc-heading { - font-weight: 500; -} - -.toc { - margin: 0px 0 0 10px; - padding: 0 10px; -} -.expand-stub { - position: absolute; - left: -10px; -} -.toc .nav > li > a.sidetoc-expand { - position: absolute; - top: 0; - left: 0; -} -.toc .nav > li > a { - color: #666666; - display: inline; - padding: 0; -} -.toc .nav > li > .expand-stub + a { - margin-left: 5px; - display: block; -} -.toc .nav > li > a:hover, -.toc .nav > li > a:focus { - color: #000000; - background: none; - text-decoration: inherit; -} -.toc .nav > li.active > a { - color: #337ab7; -} -.toc .nav > li.active > a:hover, -.toc .nav > li.active > a:focus { - color: #23527c; -} - -.toc .nav > li> .expand-stub { - cursor: pointer; -} - -.toc .nav > li.active > .expand-stub::before, -.toc .nav > li.in > .expand-stub::before, -.toc .nav > li.in.active > .expand-stub::before, -.toc .nav > li.filtered > .expand-stub::before { - content: "-"; -} - -.toc .nav > li > .expand-stub::before, -.toc .nav > li.active > .expand-stub::before { - content: "+"; -} - -.toc .nav > li.filtered > ul, -.toc .nav > li.in > ul { - display: block; -} - -.toc .nav > li > ul { - display: none; -} - -.toc .level1 > li { - font-weight: bold; - margin-top: 10px; - position: relative; -} -.toc .level2 { - font-weight: normal; - margin-top: 5px; - margin-left: 15px; - font-size: 14px; -} -.toc .level3 { - font-size: 12px; -} -.toc-toggle { - display: none; - margin: 0 15px 0px 15px; -} -.sidefilter { - position: fixed; - top: 90px; - width: 260px; - background-color: #f1f1f1; - padding: 15px; - border-left: 1px solid #e7e7e7; - border-right: 1px solid #e7e7e7; - z-index: 1; -} -.toc-filter { - border-radius: 5px; - background: #fff; - color: #666666; - padding: 5px; - position: relative; - margin: 0 5px 0 5px; -} -.toc-filter > input { - border: 0; - color: #666666; - padding-left: 20px; - width: 100%; -} -.toc-filter > input:focus { - outline: 0; -} -.toc-filter > .filter-icon { - position: absolute; - top: 10px; - left: 5px; -} -.article { - margin-top: 120px; - margin-bottom: 115px; -} - -#_content>a{ - margin-top: 105px; -} - -.article.grid-right { - margin-left: 280px; -} - -.inheritance hr { - margin-top: 5px; - margin-bottom: 5px; -} -.article img { - max-width: 100%; -} -.sideaffix { - margin-top: 50px; - font-size: 12px; - max-height: 100%; - overflow: hidden; - top: 100px; - bottom: 10px; - position: fixed; -} -.affix { - position: relative; - height: 100%; -} -.sideaffix > div.contribution { - margin-bottom: 20px; -} -.sideaffix > div.contribution > ul > li > a.contribution-link { - padding: 6px 10px; - font-weight: bold; - font-size: 14px; -} -.sideaffix > div.contribution > ul > li > a.contribution-link:hover { - background-color: #ffffff; -} -.sideaffix ul.nav > li > a:focus { - background: none; -} -.affix h5 { - font-weight: bold; - text-transform: uppercase; - padding-left: 10px; - font-size: 12px; -} -.affix > ul.level1 { - overflow: hidden; - padding-bottom: 10px; - height: calc(100% - 100px); - margin-right: -20px; -} -.affix ul > li > a:before { - color: #cccccc; - position: absolute; -} -.affix ul > li > a:hover { - background: none; - color: #666666; -} -.affix ul > li.active > a, -.affix ul > li.active > a:before { - color: #337ab7; -} -.affix ul > li > a { - padding: 5px 12px; - color: #666666; -} -.affix > ul > li.active:last-child { - margin-bottom: 50px; -} -.affix > ul > li > a:before { - content: "|"; - font-size: 16px; - top: 1px; - left: 0; -} -.affix > ul > li.active > a, -.affix > ul > li.active > a:before { - color: #337ab7; - font-weight: bold; -} -.affix ul ul > li > a { - padding: 2px 15px; -} -.affix ul ul > li > a:before { - content: ">"; - font-size: 14px; - top: -1px; - left: 5px; -} -.affix ul > li > a:before, -.affix ul ul { - display: none; -} -.affix ul > li.active > ul, -.affix ul > li.active > a:before, -.affix ul > li > a:hover:before { - display: block; - white-space: nowrap; -} -.codewrapper { - position: relative; -} -.trydiv { - height: 0px; -} -.tryspan { - position: absolute; - top: 0px; - right: 0px; - border-style: solid; - border-radius: 0px 4px; - box-sizing: border-box; - border-width: 1px; - border-color: #cccccc; - text-align: center; - padding: 2px 8px; - background-color: white; - font-size: 12px; - cursor: pointer; - z-index: 100; - display: none; - color: #767676; -} -.tryspan:hover { - background-color: #3b8bd0; - color: white; - border-color: #3b8bd0; -} -.codewrapper:hover .tryspan { - display: block; -} -.sample-response .response-content{ - max-height: 200px; -} -footer { - position: absolute; - left: 0; - right: 0; - bottom: 0; - z-index: 1000; -} -.footer { - border-top: 1px solid #e7e7e7; - background-color: #f8f8f8; - padding: 15px 0; -} -@media (min-width: 768px) { - #sidetoggle.collapse { - display: block; - } - .topnav .navbar-nav { - float: none; - white-space: nowrap; - } - .topnav .navbar-nav > li { - float: none; - display: inline-block; - } -} -@media only screen and (max-width: 768px) { - #mobile-indicator { - display: block; - } - /* TOC display for responsive */ - .article { - margin-top: 30px !important; - } - header { - position: static; - } - .topnav { - text-align: center; - } - .sidenav { - padding: 15px 0; - margin-left: -15px; - margin-right: -15px; - } - .sidefilter { - position: static; - width: auto; - float: none; - border: none; - } - .sidetoc { - position: static; - width: auto; - float: none; - padding-bottom: 0px; - border: none; - } - .toc .level2 > li { - display: inline-block; - } - .toc .level2 > li:after { - margin-left: -3px; - margin-right: 5px; - content: ", "; - color: #666666; - } - .article.grid-right { - margin-left: 0; - } - .grad-top, - .grad-bottom { - display: none; - } - .toc-toggle { - display: block; - } - .sidetoggle.ng-hide { - display: none !important; - } - /*.expand-all { - display: none; - }*/ - .sideaffix { - display: none; - } - .mobile-hide { - display: none; - } - .breadcrumb { - white-space: inherit; - } - - /* workaround for #hashtag url is no longer needed*/ - h1:before, - h2:before, - h3:before, - h4:before { - content: ''; - display: none; - } -} - -/* For toc iframe */ -@media (max-width: 260px) { - .toc .level2 > li { - display: block; - } - - .toc .level2 > li:after { - display: none; - } -} - -/* For code snippet line highlight */ -pre > code .line-highlight { - background-color: #ffffcc; -} - -/* Alerts */ -.alert h5 { - text-transform: uppercase; - font-weight: bold; - margin-top: 0; -} - -.alert h5:before { - position:relative; - top:1px; - display:inline-block; - font-family:'Glyphicons Halflings'; - line-height:1; - -webkit-font-smoothing:antialiased; - -moz-osx-font-smoothing:grayscale; - margin-right: 5px; - font-weight: normal; -} - -.alert-info h5:before { - content:"\e086" -} - -.alert-warning h5:before { - content:"\e127" -} - -.alert-danger h5:before { - content:"\e107" -} - -/* For Embedded Video */ -div.embeddedvideo { - padding-top: 56.25%; - position: relative; - width: 100%; -} - -div.embeddedvideo iframe { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - width: 100%; - height: 100%; -} - -/* For printer */ -@media print{ - .article.grid-right { - margin-top: 0px; - margin-left: 0px; - } - .sideaffix { - display: none; - } - .mobile-hide { - display: none; - } - .footer { - display: none; - } -} \ No newline at end of file diff --git a/docs/styles/docfx.js b/docs/styles/docfx.js deleted file mode 100644 index 0f84531f..00000000 --- a/docs/styles/docfx.js +++ /dev/null @@ -1,700 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE file in the project root for full license information. -$(function () { - var active = 'active'; - var expanded = 'in'; - var collapsed = 'collapsed'; - var filtered = 'filtered'; - var show = 'show'; - var hide = 'hide'; - - // Styling for tables in conceptual documents using Bootstrap. - // See http://getbootstrap.com/css/#tables - (function () { - $('table').addClass('table table-bordered table-striped table-condensed'); - })(); - - // Styling for alerts. - (function () { - $('.NOTE, .TIP').addClass('alert alert-info'); - $('.WARNING').addClass('alert alert-warning'); - $('.IMPORTANT, .CAUTION').addClass('alert alert-danger'); - })(); - - // Enable highlight.js - (function () { - $('pre code').each(function(i, block) { - hljs.highlightBlock(block); - }); - })(); - - // Line highlight for code snippet - (function () { - $('pre code[highlight-lines]').each(function (i, block) { - if (block.innerHTML === "") return; - var lines = block.innerHTML.split('\n'); - - queryString = block.getAttribute('highlight-lines'); - if (!queryString) return; - - var ranges = queryString.split(','); - for (var j = 0, range; range = ranges[j++];) { - var found = range.match(/^(\d+)\-(\d+)?$/); - if (found) { - // consider region as `{startlinenumber}-{endlinenumber}`, in which {endlinenumber} is optional - var start = +found[1]; - var end = +found[2]; - if (isNaN(end) || end > lines.length) { - end = lines.length; - } - } else { - // consider region as a sigine line number - if (isNaN(range)) continue; - var start = +range; - var end = start; - } - if (start <= 0 || end <= 0 || start > end || start > lines.length) { - // skip current region if invalid - continue; - } - lines[start - 1] = '' + lines[start - 1]; - lines[end - 1] = lines[end - 1] + ''; - } - - block.innerHTML = lines.join('\n'); - }); - })(); - - //Adjust the position of search box in navbar - (function () { - autoCollapse(); - $(window).on('resize', autoCollapse); - $(document).on('click', '.navbar-collapse.in', function (e) { - if ($(e.target).is('a')) { - $(this).collapse('hide'); - } - }); - - function autoCollapse() { - var navbar = $('#autocollapse'); - if (navbar.height() === null) { - setTimeout(autoCollapse, 300); - } - navbar.removeClass(collapsed); - if (navbar.height() > 60) { - navbar.addClass(collapsed); - } - } - })(); - - // Support full-text-search - (function () { - var query; - var relHref = $("meta[property='docfx\\:rel']").attr("content") || ""; - var search = searchFactory(); - search(); - highlightKeywords(); - addSearchEvent(); - - // Search factory - function searchFactory() { - var worker = new Worker(relHref + 'styles/search-worker.js'); - if (!worker) return localSearch; - return window.Worker ? webWorkerSearch : localSearch; - - function localSearch() { - console.log("using local search"); - var lunrIndex = lunr(function () { - this.ref('href'); - this.field('title', { boost: 50 }); - this.field('keywords', { boost: 20 }); - }); - lunr.tokenizer.seperator = /[\s\-\.]+/; - var searchData = {}; - var searchDataRequest = new XMLHttpRequest(); - - var indexPath = relHref + "index.json"; - if (indexPath) { - searchDataRequest.open('GET', indexPath); - searchDataRequest.onload = function () { - searchData = JSON.parse(this.responseText); - for (var prop in searchData) { - lunrIndex.add(searchData[prop]); - } - } - searchDataRequest.send(); - } - - $("body").bind("queryReady", function () { - var hits = lunrIndex.search(query); - var results = []; - hits.forEach(function (hit) { - var item = searchData[hit.ref]; - results.push({ 'href': item.href, 'title': item.title, 'keywords': item.keywords }); - }); - handleSearchResults(results); - }); - } - - function webWorkerSearch() { - console.log("using Web Worker"); - var indexReady = $.Deferred(); - - worker.onmessage = function (oEvent) { - switch (oEvent.data.e) { - case 'index-ready': - indexReady.resolve(); - break; - case 'query-ready': - var hits = oEvent.data.d; - handleSearchResults(hits); - break; - } - } - - indexReady.promise().done(function () { - $("body").bind("queryReady", function () { - worker.postMessage({ q: query }); - }); - }); - } - }; - - // Highlight the searching keywords - function highlightKeywords() { - var q = url('?q'); - if (q !== null) { - var keywords = q.split("%20"); - keywords.forEach(function (keyword) { - if (keyword !== "") { - $('.data-searchable *').mark(keyword); - $('article *').mark(keyword); - } - }); - } - } - - function addSearchEvent() { - $('body').bind("searchEvent", function () { - $('#search-query').keypress(function (e) { - return e.which !== 13; - }); - - $('#search-query').keyup(function () { - query = $(this).val(); - if (query.length < 3) { - flipContents("show"); - } else { - flipContents("hide"); - $("body").trigger("queryReady"); - $('#search-results>.search-list').text('Search Results for "' + query + '"'); - } - }).off("keydown"); - }); - } - - function flipContents(action) { - if (action === "show") { - $('.hide-when-search').show(); - $('#search-results').hide(); - } else { - $('.hide-when-search').hide(); - $('#search-results').show(); - } - } - - function relativeUrlToAbsoluteUrl(currentUrl, relativeUrl) { - var currentItems = currentUrl.split(/\/+/); - var relativeItems = relativeUrl.split(/\/+/); - var depth = currentItems.length - 1; - var items = []; - for (var i = 0; i < relativeItems.length; i++) { - if (relativeItems[i] === '..') { - depth--; - } else if (relativeItems[i] !== '.') { - items.push(relativeItems[i]); - } - } - return currentItems.slice(0, depth).concat(items).join('/'); - } - - function extractContentBrief(content) { - var briefOffset = 512; - var words = query.split(/\s+/g); - var queryIndex = content.indexOf(words[0]); - var briefContent; - if (queryIndex > briefOffset) { - return "..." + content.slice(queryIndex - briefOffset, queryIndex + briefOffset) + "..."; - } else if (queryIndex <= briefOffset) { - return content.slice(0, queryIndex + briefOffset) + "..."; - } - } - - function handleSearchResults(hits) { - var numPerPage = 10; - $('#pagination').empty(); - $('#pagination').removeData("twbs-pagination"); - if (hits.length === 0) { - $('#search-results>.sr-items').html('

No results found

'); - } else { - $('#pagination').twbsPagination({ - totalPages: Math.ceil(hits.length / numPerPage), - visiblePages: 5, - onPageClick: function (event, page) { - var start = (page - 1) * numPerPage; - var curHits = hits.slice(start, start + numPerPage); - $('#search-results>.sr-items').empty().append( - curHits.map(function (hit) { - var currentUrl = window.location.href; - var itemRawHref = relativeUrlToAbsoluteUrl(currentUrl, relHref + hit.href); - var itemHref = relHref + hit.href + "?q=" + query; - var itemTitle = hit.title; - var itemBrief = extractContentBrief(hit.keywords); - - var itemNode = $('
').attr('class', 'sr-item'); - var itemTitleNode = $('
').attr('class', 'item-title').append($('').attr('href', itemHref).attr("target", "_blank").text(itemTitle)); - var itemHrefNode = $('
').attr('class', 'item-href').text(itemRawHref); - var itemBriefNode = $('
').attr('class', 'item-brief').text(itemBrief); - itemNode.append(itemTitleNode).append(itemHrefNode).append(itemBriefNode); - return itemNode; - }) - ); - query.split(/\s+/).forEach(function (word) { - if (word !== '') { - $('#search-results>.sr-items *').mark(word); - } - }); - } - }); - } - } - })(); - - // Update href in navbar - (function () { - var toc = $('#sidetoc'); - var breadcrumb = new Breadcrumb(); - loadNavbar(); - loadToc(); - function loadNavbar() { - var navbarPath = $("meta[property='docfx\\:navrel']").attr("content"); - var tocPath = $("meta[property='docfx\\:tocrel']").attr("content"); - if (tocPath) tocPath = tocPath.replace(/\\/g, '/'); - if (navbarPath) navbarPath = navbarPath.replace(/\\/g, '/'); - $.get(navbarPath, function (data) { - $(data).find("#toc>ul").appendTo("#navbar"); - if ($('#search-results').length !== 0) { - $('#search').show(); - $('body').trigger("searchEvent"); - } - var index = navbarPath.lastIndexOf('/'); - var navrel = ''; - if (index > -1) { - navrel = navbarPath.substr(0, index + 1); - } - $('#navbar>ul').addClass('navbar-nav'); - var currentAbsPath = getAbsolutePath(window.location.pathname); - // set active item - $('#navbar').find('a[href]').each(function (i, e) { - var href = $(e).attr("href"); - if (isRelativePath(href)) { - href = navrel + href; - $(e).attr("href", href); - - // TODO: currently only support one level navbar - var isActive = false; - var originalHref = e.name; - if (originalHref) { - originalHref = navrel + originalHref; - if (getDirectory(getAbsolutePath(originalHref)) === getDirectory(getAbsolutePath(tocPath))) { - isActive = true; - } - } else { - if (getAbsolutePath(href) === currentAbsPath) { - isActive = true; - } - } - if (isActive) { - $(e).parent().addClass(active); - if (!breadcrumb.isNavPartLoaded) { - breadcrumb.insert({ - href: e.href, - name: e.innerHTML - }, 0); - breadcrumb.isNavPartLoaded = true; - } - } else { - $(e).parent().removeClass(active) - } - } - }); - }); - } - - function loadToc() { - var tocPath = $("meta[property='docfx\\:tocrel']").attr("content"); - if (tocPath) tocPath = tocPath.replace(/\\/g, '/'); - $('#sidetoc').load(tocPath + " #sidetoggle > div", function () { - registerTocEvents(); - - var index = tocPath.lastIndexOf('/'); - var tocrel = ''; - if (index > -1) { - tocrel = tocPath.substr(0, index + 1); - } - var currentHref = getAbsolutePath(window.location.pathname); - $('#sidetoc').find('a[href]').each(function (i, e) { - var href = $(e).attr("href"); - if (isRelativePath(href)) { - href = tocrel + href; - $(e).attr("href", href); - } - - if (getAbsolutePath(e.href) === currentHref) { - $(e).parent().addClass(active); - var parent = $(e).parent().parents('li').children('a'); - if (!breadcrumb.isTocPartLoaded) { - for (var i = parent.length - 1; i >= 0; i--) { - breadcrumb.push({ - href: parent[i].href, - name: parent[i].innerHTML - }); - } - breadcrumb.push({ - href: e.href, - name: e.innerHTML - }); - breadcrumb.isTocPartLoaded = true; - } - if (parent.length > 0) { - parent.addClass(active); - } - // for active li, expand it - $(e).parents('ul.nav>li').addClass(expanded); - - // Scroll to active item - var top = 0; - $(e).parents('li').each(function (i, e) { - top += $(e).position().top; - }); - // 50 is the size of the filter box - $('.sidetoc').scrollTop(top - 50); - if ($('footer').is(':visible')) { - $(".sidetoc").css("bottom", "70px"); - } - } else { - $(e).parent().removeClass(active); - $(e).parents('li').children('a').removeClass(active); - } - }); - }); - } - - function registerTocEvents() { - $('.toc .nav > li > .expand-stub').click(function (e) { - $(e.target).parent().toggleClass(expanded); - }); - $('.toc .nav > li > .expand-stub + a:not([href])').click(function (e) { - $(e.target).parent().toggleClass(expanded); - }); - $('#toc_filter_input').on('input', function (e) { - var val = this.value; - if (val === '') { - // Clear 'filtered' class - $('#toc li').removeClass(filtered).removeClass(hide); - return; - } - - // Get leaf nodes - $('#toc li>a').filter(function (i, e) { - return $(e).siblings().length === 0 - }).each(function (i, anchor) { - var text = $(anchor).text(); - var parent = $(anchor).parent(); - var parentNodes = parent.parents('ul>li'); - for (var i = 0; i < parentNodes.length; i++) { - var parentText = $(parentNodes[i]).children('a').text(); - if (parentText) text = parentText + '.' + text; - }; - if (filterNavItem(text, val)) { - parent.addClass(show); - parent.removeClass(hide); - } else { - parent.addClass(hide); - parent.removeClass(show); - } - }); - $('#toc li>a').filter(function (i, e) { - return $(e).siblings().length > 0 - }).each(function (i, anchor) { - var parent = $(anchor).parent(); - if (parent.find('li.show').length > 0) { - parent.addClass(show); - parent.addClass(filtered); - parent.removeClass(hide); - } else { - parent.addClass(hide); - parent.removeClass(show); - parent.removeClass(filtered); - } - }) - - function filterNavItem(name, text) { - if (!text) return true; - if (name.toLowerCase().indexOf(text.toLowerCase()) > -1) return true; - return false; - } - }); - } - - function Breadcrumb() { - var breadcrumb = []; - var isNavPartLoaded = false; - var isTocPartLoaded = false; - this.push = pushBreadcrumb; - this.insert = insertBreadcrumb; - - function pushBreadcrumb(obj) { - breadcrumb.push(obj); - setupBreadCrumb(breadcrumb); - } - - function insertBreadcrumb(obj, index) { - breadcrumb.splice(index, 0, obj); - setupBreadCrumb(breadcrumb); - } - - function setupBreadCrumb() { - var html = formList(breadcrumb, 'breadcrumb'); - $('#breadcrumb').html(html); - } - } - - function getAbsolutePath(href) { - // Use anchor to normalize href - var anchor = $('')[0]; - // Ignore protocal, remove search and query - return anchor.host + anchor.pathname; - } - - function isRelativePath(href) { - return !isAbsolutePath(href); - } - - function isAbsolutePath(href) { - return (/^(?:[a-z]+:)?\/\//i).test(href); - } - - function getDirectory(href) { - if (!href) return ''; - var index = href.lastIndexOf('/'); - if (index == -1) return ''; - if (index > -1) { - return href.substr(0, index); - } - } - })(); - - //Setup Affix - (function () { - var hierarchy = getHierarchy(); - if (hierarchy.length > 0) { - var html = '
In This Article
' - html += formList(hierarchy, ['nav', 'bs-docs-sidenav']); - $("#affix").append(html); - if ($('footer').is(':visible')) { - $(".sideaffix").css("bottom", "70px"); - } - $('#affix').on('activate.bs.scrollspy', function (e) { - if (e.target) { - if ($(e.target).find('li.active').length > 0) { - return; - } - var top = $(e.target).position().top; - $(e.target).parents('li').each(function (i, e) { - top += $(e).position().top; - }); - var container = $('#affix > ul'); - var height = container.height(); - container.scrollTop(container.scrollTop() + top - height/2); - } - }) - } - - function getHierarchy() { - // supported headers are h1, h2, h3, and h4 - // The topest header is ignored - var selector = ".article article"; - var affixSelector = "#affix"; - var headers = ['h4', 'h3', 'h2', 'h1']; - var hierarchy = []; - var toppestIndex = -1; - var startIndex = -1; - // 1. get header hierarchy - for (var i = headers.length - 1; i >= 0; i--) { - var header = $(selector + " " + headers[i]); - var length = header.length; - - // If contains no header in current selector, find the next one - if (length === 0) continue; - - // If the toppest header contains only one item, e.g. title, ignore - if (length === 1 && hierarchy.length === 0 && toppestIndex < 0) { - toppestIndex = i; - continue; - } - - // Get second level children - var nextLevelSelector = i > 0 ? headers[i - 1] : null; - var prevSelector; - for (var j = length - 1; j >= 0; j--) { - var e = header[j]; - var id = e.id; - if (!id) continue; // For affix, id is a must-have - var item = { - name: htmlEncode($(e).text()), - href: "#" + id, - items: [] - }; - if (nextLevelSelector) { - var selector = '#' + id + "~" + nextLevelSelector; - var currentSelector = selector; - if (prevSelector) currentSelector += ":not(" + prevSelector + ")"; - $(header[j]).siblings(currentSelector).each(function (index, e) { - if (e.id) { - item.items.push({ - name: htmlEncode($(e).text()), // innerText decodes text while innerHTML not - href: "#" + e.id - - }) - } - }) - prevSelector = selector; - } - hierarchy.push(item); - } - break; - }; - hierarchy.reverse(); - return hierarchy; - } - - function htmlEncode(str) { - return String(str) - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/'/g, ''') - .replace(//g, '>'); - } - - function htmlDecode(value) { - return String(value) - .replace(/"/g, '"') - .replace(/'/g, "'") - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/&/g, '&'); - } - })(); - - function formList(item, classes) { - var level = 1; - var model = { - items: item - }; - var cls = [].concat(classes).join(" "); - return getList(model, cls); - - function getList(model, cls) { - if (!model || !model.items) return null; - var l = model.items.length; - if (l === 0) return null; - var html = '
    '; - level++; - for (var i = 0; i < l; i++) { - var item = model.items[i]; - var href = item.href; - var name = item.name; - if (!name) continue; - html += href ? '
  • ' + name + '' : '
  • ' + name; - html += getList(item, cls) || ''; - html += '
  • '; - } - html += '
'; - return html; - } - } - - // Show footer - (function () { - initFooter(); - $(window).on("scroll", showFooter); - - function initFooter() { - if (needFooter()) { - shiftUpBottomCss(); - $("footer").show(); - } else { - resetBottomCss(); - $("footer").hide(); - } - } - - function showFooter() { - if (needFooter()) { - shiftUpBottomCss(); - $("footer").fadeIn(); - } else { - resetBottomCss(); - $("footer").fadeOut(); - } - } - - function needFooter() { - var scrollHeight = $(document).height(); - var scrollPosition = $(window).height() + $(window).scrollTop(); - return (scrollHeight - scrollPosition) < 1; - } - - function resetBottomCss() { - $(".sidetoc").css("bottom", "0"); - $(".sideaffix").css("bottom", "10px"); - } - - function shiftUpBottomCss() { - $(".sidetoc").css("bottom", "70px"); - $(".sideaffix").css("bottom", "70px"); - } - })(); - - // For LOGO SVG - // Replace SVG with inline SVG - // http://stackoverflow.com/questions/11978995/how-to-change-color-of-svg-image-using-css-jquery-svg-image-replacement - jQuery('img.svg').each(function () { - var $img = jQuery(this); - var imgID = $img.attr('id'); - var imgClass = $img.attr('class'); - var imgURL = $img.attr('src'); - - jQuery.get(imgURL, function (data) { - // Get the SVG tag, ignore the rest - var $svg = jQuery(data).find('svg'); - - // Add replaced image's ID to the new SVG - if (typeof imgID !== 'undefined') { - $svg = $svg.attr('id', imgID); - } - // Add replaced image's classes to the new SVG - if (typeof imgClass !== 'undefined') { - $svg = $svg.attr('class', imgClass + ' replaced-svg'); - } - - // Remove any invalid XML tags as per http://validator.w3.org - $svg = $svg.removeAttr('xmlns:a'); - - // Replace image with new SVG - $img.replaceWith($svg); - - }, 'xml'); - }); -}) diff --git a/docs/styles/docfx.vendor.css b/docs/styles/docfx.vendor.css deleted file mode 100644 index b8c40352..00000000 --- a/docs/styles/docfx.vendor.css +++ /dev/null @@ -1,1466 +0,0 @@ -/*! - * Bootstrap v3.3.7 (http://getbootstrap.com) - * Copyright 2011-2016 Twitter, Inc. - * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) - */ -/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */ -.label,sub,sup{vertical-align:baseline} -hr,img{border:0} -body,figure{margin:0} -.btn-group>.btn-group,.btn-toolbar .btn,.btn-toolbar .btn-group,.btn-toolbar .input-group,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.dropdown-menu{float:left} -.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.pre-scrollable{max-height:340px} -html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%} -article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block} -audio,canvas,progress,video{display:inline-block;vertical-align:baseline} -audio:not([controls]){display:none;height:0} -[hidden],template{display:none} -a{background-color:transparent} -a:active,a:hover{outline:0} -b,optgroup,strong{font-weight:700} -dfn{font-style:italic} -h1{margin:.67em 0} -mark{color:#000;background:#ff0} -sub,sup{position:relative;font-size:75%;line-height:0} -sup{top:-.5em} -sub{bottom:-.25em} -img{vertical-align:middle} -svg:not(:root){overflow:hidden} -hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box} -pre,textarea{overflow:auto} -code,kbd,pre,samp{font-size:1em} -button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit} -.glyphicon,address{font-style:normal} -button{overflow:visible} -button,select{text-transform:none} -button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer} -button[disabled],html input[disabled]{cursor:default} -button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0} -input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0} -input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto} -input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none} -table{border-spacing:0;border-collapse:collapse} -td,th{padding:0} -/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ -@media print{blockquote,img,pre,tr{page-break-inside:avoid} -*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important} -a,a:visited{text-decoration:underline} -a[href]:after{content:" (" attr(href) ")"} -abbr[title]:after{content:" (" attr(title) ")"} -a[href^="javascript:"]:after,a[href^="#"]:after{content:""} -blockquote,pre{border:1px solid #999} -thead{display:table-header-group} -img{max-width:100%!important} -h2,h3,p{orphans:3;widows:3} -h2,h3{page-break-after:avoid} -.navbar{display:none} -.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important} -.label{border:1px solid #000} -.table{border-collapse:collapse!important} -.table td,.table th{background-color:#fff!important} -.table-bordered td,.table-bordered th{border:1px solid #ddd!important} -} -.dropdown-menu,.modal-content{-webkit-background-clip:padding-box} -.btn,.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-warning.active,.btn-warning:active,.btn.active,.btn:active,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover,.form-control,.navbar-toggle,.open>.dropdown-toggle.btn-danger,.open>.dropdown-toggle.btn-default,.open>.dropdown-toggle.btn-info,.open>.dropdown-toggle.btn-primary,.open>.dropdown-toggle.btn-warning{background-image:none} -.img-thumbnail,body{background-color:#fff} -@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')} -.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale} -.glyphicon-asterisk:before{content:"\002a"} -.glyphicon-plus:before{content:"\002b"} -.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"} -.glyphicon-minus:before{content:"\2212"} -.glyphicon-cloud:before{content:"\2601"} -.glyphicon-envelope:before{content:"\2709"} -.glyphicon-pencil:before{content:"\270f"} -.glyphicon-glass:before{content:"\e001"} -.glyphicon-music:before{content:"\e002"} -.glyphicon-search:before{content:"\e003"} -.glyphicon-heart:before{content:"\e005"} -.glyphicon-star:before{content:"\e006"} -.glyphicon-star-empty:before{content:"\e007"} -.glyphicon-user:before{content:"\e008"} -.glyphicon-film:before{content:"\e009"} -.glyphicon-th-large:before{content:"\e010"} -.glyphicon-th:before{content:"\e011"} -.glyphicon-th-list:before{content:"\e012"} -.glyphicon-ok:before{content:"\e013"} -.glyphicon-remove:before{content:"\e014"} -.glyphicon-zoom-in:before{content:"\e015"} -.glyphicon-zoom-out:before{content:"\e016"} -.glyphicon-off:before{content:"\e017"} -.glyphicon-signal:before{content:"\e018"} -.glyphicon-cog:before{content:"\e019"} -.glyphicon-trash:before{content:"\e020"} -.glyphicon-home:before{content:"\e021"} -.glyphicon-file:before{content:"\e022"} -.glyphicon-time:before{content:"\e023"} -.glyphicon-road:before{content:"\e024"} -.glyphicon-download-alt:before{content:"\e025"} -.glyphicon-download:before{content:"\e026"} -.glyphicon-upload:before{content:"\e027"} -.glyphicon-inbox:before{content:"\e028"} -.glyphicon-play-circle:before{content:"\e029"} -.glyphicon-repeat:before{content:"\e030"} -.glyphicon-refresh:before{content:"\e031"} -.glyphicon-list-alt:before{content:"\e032"} -.glyphicon-lock:before{content:"\e033"} -.glyphicon-flag:before{content:"\e034"} -.glyphicon-headphones:before{content:"\e035"} -.glyphicon-volume-off:before{content:"\e036"} -.glyphicon-volume-down:before{content:"\e037"} -.glyphicon-volume-up:before{content:"\e038"} -.glyphicon-qrcode:before{content:"\e039"} -.glyphicon-barcode:before{content:"\e040"} -.glyphicon-tag:before{content:"\e041"} -.glyphicon-tags:before{content:"\e042"} -.glyphicon-book:before{content:"\e043"} -.glyphicon-bookmark:before{content:"\e044"} -.glyphicon-print:before{content:"\e045"} -.glyphicon-camera:before{content:"\e046"} -.glyphicon-font:before{content:"\e047"} -.glyphicon-bold:before{content:"\e048"} -.glyphicon-italic:before{content:"\e049"} -.glyphicon-text-height:before{content:"\e050"} -.glyphicon-text-width:before{content:"\e051"} -.glyphicon-align-left:before{content:"\e052"} -.glyphicon-align-center:before{content:"\e053"} -.glyphicon-align-right:before{content:"\e054"} -.glyphicon-align-justify:before{content:"\e055"} -.glyphicon-list:before{content:"\e056"} -.glyphicon-indent-left:before{content:"\e057"} -.glyphicon-indent-right:before{content:"\e058"} -.glyphicon-facetime-video:before{content:"\e059"} -.glyphicon-picture:before{content:"\e060"} -.glyphicon-map-marker:before{content:"\e062"} -.glyphicon-adjust:before{content:"\e063"} -.glyphicon-tint:before{content:"\e064"} -.glyphicon-edit:before{content:"\e065"} -.glyphicon-share:before{content:"\e066"} -.glyphicon-check:before{content:"\e067"} -.glyphicon-move:before{content:"\e068"} -.glyphicon-step-backward:before{content:"\e069"} -.glyphicon-fast-backward:before{content:"\e070"} -.glyphicon-backward:before{content:"\e071"} -.glyphicon-play:before{content:"\e072"} -.glyphicon-pause:before{content:"\e073"} -.glyphicon-stop:before{content:"\e074"} -.glyphicon-forward:before{content:"\e075"} -.glyphicon-fast-forward:before{content:"\e076"} -.glyphicon-step-forward:before{content:"\e077"} -.glyphicon-eject:before{content:"\e078"} -.glyphicon-chevron-left:before{content:"\e079"} -.glyphicon-chevron-right:before{content:"\e080"} -.glyphicon-plus-sign:before{content:"\e081"} -.glyphicon-minus-sign:before{content:"\e082"} -.glyphicon-remove-sign:before{content:"\e083"} -.glyphicon-ok-sign:before{content:"\e084"} -.glyphicon-question-sign:before{content:"\e085"} -.glyphicon-info-sign:before{content:"\e086"} -.glyphicon-screenshot:before{content:"\e087"} -.glyphicon-remove-circle:before{content:"\e088"} -.glyphicon-ok-circle:before{content:"\e089"} -.glyphicon-ban-circle:before{content:"\e090"} -.glyphicon-arrow-left:before{content:"\e091"} -.glyphicon-arrow-right:before{content:"\e092"} -.glyphicon-arrow-up:before{content:"\e093"} -.glyphicon-arrow-down:before{content:"\e094"} -.glyphicon-share-alt:before{content:"\e095"} -.glyphicon-resize-full:before{content:"\e096"} -.glyphicon-resize-small:before{content:"\e097"} -.glyphicon-exclamation-sign:before{content:"\e101"} -.glyphicon-gift:before{content:"\e102"} -.glyphicon-leaf:before{content:"\e103"} -.glyphicon-fire:before{content:"\e104"} -.glyphicon-eye-open:before{content:"\e105"} -.glyphicon-eye-close:before{content:"\e106"} -.glyphicon-warning-sign:before{content:"\e107"} -.glyphicon-plane:before{content:"\e108"} -.glyphicon-calendar:before{content:"\e109"} -.glyphicon-random:before{content:"\e110"} -.glyphicon-comment:before{content:"\e111"} -.glyphicon-magnet:before{content:"\e112"} -.glyphicon-chevron-up:before{content:"\e113"} -.glyphicon-chevron-down:before{content:"\e114"} -.glyphicon-retweet:before{content:"\e115"} -.glyphicon-shopping-cart:before{content:"\e116"} -.glyphicon-folder-close:before{content:"\e117"} -.glyphicon-folder-open:before{content:"\e118"} -.glyphicon-resize-vertical:before{content:"\e119"} -.glyphicon-resize-horizontal:before{content:"\e120"} -.glyphicon-hdd:before{content:"\e121"} -.glyphicon-bullhorn:before{content:"\e122"} -.glyphicon-bell:before{content:"\e123"} -.glyphicon-certificate:before{content:"\e124"} -.glyphicon-thumbs-up:before{content:"\e125"} -.glyphicon-thumbs-down:before{content:"\e126"} -.glyphicon-hand-right:before{content:"\e127"} -.glyphicon-hand-left:before{content:"\e128"} -.glyphicon-hand-up:before{content:"\e129"} -.glyphicon-hand-down:before{content:"\e130"} -.glyphicon-circle-arrow-right:before{content:"\e131"} -.glyphicon-circle-arrow-left:before{content:"\e132"} -.glyphicon-circle-arrow-up:before{content:"\e133"} -.glyphicon-circle-arrow-down:before{content:"\e134"} -.glyphicon-globe:before{content:"\e135"} -.glyphicon-wrench:before{content:"\e136"} -.glyphicon-tasks:before{content:"\e137"} -.glyphicon-filter:before{content:"\e138"} -.glyphicon-briefcase:before{content:"\e139"} -.glyphicon-fullscreen:before{content:"\e140"} -.glyphicon-dashboard:before{content:"\e141"} -.glyphicon-paperclip:before{content:"\e142"} -.glyphicon-heart-empty:before{content:"\e143"} -.glyphicon-link:before{content:"\e144"} -.glyphicon-phone:before{content:"\e145"} -.glyphicon-pushpin:before{content:"\e146"} -.glyphicon-usd:before{content:"\e148"} -.glyphicon-gbp:before{content:"\e149"} -.glyphicon-sort:before{content:"\e150"} -.glyphicon-sort-by-alphabet:before{content:"\e151"} -.glyphicon-sort-by-alphabet-alt:before{content:"\e152"} -.glyphicon-sort-by-order:before{content:"\e153"} -.glyphicon-sort-by-order-alt:before{content:"\e154"} -.glyphicon-sort-by-attributes:before{content:"\e155"} -.glyphicon-sort-by-attributes-alt:before{content:"\e156"} -.glyphicon-unchecked:before{content:"\e157"} -.glyphicon-expand:before{content:"\e158"} -.glyphicon-collapse-down:before{content:"\e159"} -.glyphicon-collapse-up:before{content:"\e160"} -.glyphicon-log-in:before{content:"\e161"} -.glyphicon-flash:before{content:"\e162"} -.glyphicon-log-out:before{content:"\e163"} -.glyphicon-new-window:before{content:"\e164"} -.glyphicon-record:before{content:"\e165"} -.glyphicon-save:before{content:"\e166"} -.glyphicon-open:before{content:"\e167"} -.glyphicon-saved:before{content:"\e168"} -.glyphicon-import:before{content:"\e169"} -.glyphicon-export:before{content:"\e170"} -.glyphicon-send:before{content:"\e171"} -.glyphicon-floppy-disk:before{content:"\e172"} -.glyphicon-floppy-saved:before{content:"\e173"} -.glyphicon-floppy-remove:before{content:"\e174"} -.glyphicon-floppy-save:before{content:"\e175"} -.glyphicon-floppy-open:before{content:"\e176"} -.glyphicon-credit-card:before{content:"\e177"} -.glyphicon-transfer:before{content:"\e178"} -.glyphicon-cutlery:before{content:"\e179"} -.glyphicon-header:before{content:"\e180"} -.glyphicon-compressed:before{content:"\e181"} -.glyphicon-earphone:before{content:"\e182"} -.glyphicon-phone-alt:before{content:"\e183"} -.glyphicon-tower:before{content:"\e184"} -.glyphicon-stats:before{content:"\e185"} -.glyphicon-sd-video:before{content:"\e186"} -.glyphicon-hd-video:before{content:"\e187"} -.glyphicon-subtitles:before{content:"\e188"} -.glyphicon-sound-stereo:before{content:"\e189"} -.glyphicon-sound-dolby:before{content:"\e190"} -.glyphicon-sound-5-1:before{content:"\e191"} -.glyphicon-sound-6-1:before{content:"\e192"} -.glyphicon-sound-7-1:before{content:"\e193"} -.glyphicon-copyright-mark:before{content:"\e194"} -.glyphicon-registration-mark:before{content:"\e195"} -.glyphicon-cloud-download:before{content:"\e197"} -.glyphicon-cloud-upload:before{content:"\e198"} -.glyphicon-tree-conifer:before{content:"\e199"} -.glyphicon-tree-deciduous:before{content:"\e200"} -.glyphicon-cd:before{content:"\e201"} -.glyphicon-save-file:before{content:"\e202"} -.glyphicon-open-file:before{content:"\e203"} -.glyphicon-level-up:before{content:"\e204"} -.glyphicon-copy:before{content:"\e205"} -.glyphicon-paste:before{content:"\e206"} -.glyphicon-alert:before{content:"\e209"} -.glyphicon-equalizer:before{content:"\e210"} -.glyphicon-king:before{content:"\e211"} -.glyphicon-queen:before{content:"\e212"} -.glyphicon-pawn:before{content:"\e213"} -.glyphicon-bishop:before{content:"\e214"} -.glyphicon-knight:before{content:"\e215"} -.glyphicon-baby-formula:before{content:"\e216"} -.glyphicon-tent:before{content:"\26fa"} -.glyphicon-blackboard:before{content:"\e218"} -.glyphicon-bed:before{content:"\e219"} -.glyphicon-apple:before{content:"\f8ff"} -.glyphicon-erase:before{content:"\e221"} -.glyphicon-hourglass:before{content:"\231b"} -.glyphicon-lamp:before{content:"\e223"} -.glyphicon-duplicate:before{content:"\e224"} -.glyphicon-piggy-bank:before{content:"\e225"} -.glyphicon-scissors:before{content:"\e226"} -.glyphicon-bitcoin:before,.glyphicon-btc:before,.glyphicon-xbt:before{content:"\e227"} -.glyphicon-jpy:before,.glyphicon-yen:before{content:"\00a5"} -.glyphicon-rub:before,.glyphicon-ruble:before{content:"\20bd"} -.glyphicon-scale:before{content:"\e230"} -.glyphicon-ice-lolly:before{content:"\e231"} -.glyphicon-ice-lolly-tasted:before{content:"\e232"} -.glyphicon-education:before{content:"\e233"} -.glyphicon-option-horizontal:before{content:"\e234"} -.glyphicon-option-vertical:before{content:"\e235"} -.glyphicon-menu-hamburger:before{content:"\e236"} -.glyphicon-modal-window:before{content:"\e237"} -.glyphicon-oil:before{content:"\e238"} -.glyphicon-grain:before{content:"\e239"} -.glyphicon-sunglasses:before{content:"\e240"} -.glyphicon-text-size:before{content:"\e241"} -.glyphicon-text-color:before{content:"\e242"} -.glyphicon-text-background:before{content:"\e243"} -.glyphicon-object-align-top:before{content:"\e244"} -.glyphicon-object-align-bottom:before{content:"\e245"} -.glyphicon-object-align-horizontal:before{content:"\e246"} -.glyphicon-object-align-left:before{content:"\e247"} -.glyphicon-object-align-vertical:before{content:"\e248"} -.glyphicon-object-align-right:before{content:"\e249"} -.glyphicon-triangle-right:before{content:"\e250"} -.glyphicon-triangle-left:before{content:"\e251"} -.glyphicon-triangle-bottom:before{content:"\e252"} -.glyphicon-triangle-top:before{content:"\e253"} -.glyphicon-console:before{content:"\e254"} -.glyphicon-superscript:before{content:"\e255"} -.glyphicon-subscript:before{content:"\e256"} -.glyphicon-menu-left:before{content:"\e257"} -.glyphicon-menu-right:before{content:"\e258"} -.glyphicon-menu-down:before{content:"\e259"} -.glyphicon-menu-up:before{content:"\e260"} -*,:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box} -html{font-size:10px;-webkit-tap-highlight-color:transparent} -body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333} -button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit} -a{color:#337ab7;text-decoration:none} -a:focus,a:hover{color:#23527c;text-decoration:underline} -a:focus{outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px} -.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto} -.img-rounded{border-radius:6px} -.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out} -.img-circle{border-radius:50%} -hr{margin-top:20px;margin-bottom:20px;border-top:1px solid #eee} -.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0} -.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} -[role=button]{cursor:pointer} -.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit} -.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777} -.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px} -.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%} -.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px} -.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%} -.h1,h1{font-size:36px} -.h2,h2{font-size:30px} -.h3,h3{font-size:24px} -.h4,h4{font-size:18px} -.h5,h5{font-size:14px} -.h6,h6{font-size:12px} -p{margin:0 0 10px} -.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4} -dt,kbd kbd,label{font-weight:700} -address,blockquote .small,blockquote footer,blockquote small,dd,dt,pre{line-height:1.42857143} -@media (min-width:768px){.lead{font-size:21px} -} -.small,small{font-size:85%} -.mark,mark{padding:.2em;background-color:#fcf8e3} -.list-inline,.list-unstyled{padding-left:0;list-style:none} -.text-left{text-align:left} -.text-right{text-align:right} -.text-center{text-align:center} -.text-justify{text-align:justify} -.text-nowrap{white-space:nowrap} -.text-lowercase{text-transform:lowercase} -.text-uppercase{text-transform:uppercase} -.text-capitalize{text-transform:capitalize} -.text-muted{color:#777} -.text-primary{color:#337ab7} -a.text-primary:focus,a.text-primary:hover{color:#286090} -.text-success{color:#3c763d} -a.text-success:focus,a.text-success:hover{color:#2b542c} -.text-info{color:#31708f} -a.text-info:focus,a.text-info:hover{color:#245269} -.text-warning{color:#8a6d3b} -a.text-warning:focus,a.text-warning:hover{color:#66512c} -.text-danger{color:#a94442} -a.text-danger:focus,a.text-danger:hover{color:#843534} -.bg-primary{color:#fff;background-color:#337ab7} -a.bg-primary:focus,a.bg-primary:hover{background-color:#286090} -.bg-success{background-color:#dff0d8} -a.bg-success:focus,a.bg-success:hover{background-color:#c1e2b3} -.bg-info{background-color:#d9edf7} -a.bg-info:focus,a.bg-info:hover{background-color:#afd9ee} -.bg-warning{background-color:#fcf8e3} -a.bg-warning:focus,a.bg-warning:hover{background-color:#f7ecb5} -.bg-danger{background-color:#f2dede} -a.bg-danger:focus,a.bg-danger:hover{background-color:#e4b9b9} -pre code,table{background-color:transparent} -.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee} -dl,ol,ul{margin-top:0} -blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child,ol ol,ol ul,ul ol,ul ul{margin-bottom:0} -address,dl{margin-bottom:20px} -ol,ul{margin-bottom:10px} -.list-inline{margin-left:-5px} -.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px} -dd{margin-left:0} -@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap} -.dl-horizontal dd{margin-left:180px} -.container{width:750px} -} -abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777} -.initialism{font-size:90%;text-transform:uppercase} -blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee} -blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;color:#777} -legend,pre{display:block;color:#333} -blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'} -.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0} -code,kbd{padding:2px 4px;font-size:90%} -caption,th{text-align:left} -.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''} -.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'} -code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace} -code{color:#c7254e;background-color:#f9f2f4;border-radius:4px} -kbd{color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)} -kbd kbd{padding:0;font-size:100%;-webkit-box-shadow:none;box-shadow:none} -pre{padding:9.5px;margin:0 0 10px;font-size:13px;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px} -.container,.container-fluid{margin-right:auto;margin-left:auto} -pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;border-radius:0} -.container,.container-fluid{padding-right:15px;padding-left:15px} -.pre-scrollable{overflow-y:scroll} -@media (min-width:992px){.container{width:970px} -} -@media (min-width:1200px){.container{width:1170px} -} -.row{margin-right:-15px;margin-left:-15px} -.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px} -.col-xs-12{width:100%} -.col-xs-11{width:91.66666667%} -.col-xs-10{width:83.33333333%} -.col-xs-9{width:75%} -.col-xs-8{width:66.66666667%} -.col-xs-7{width:58.33333333%} -.col-xs-6{width:50%} -.col-xs-5{width:41.66666667%} -.col-xs-4{width:33.33333333%} -.col-xs-3{width:25%} -.col-xs-2{width:16.66666667%} -.col-xs-1{width:8.33333333%} -.col-xs-pull-12{right:100%} -.col-xs-pull-11{right:91.66666667%} -.col-xs-pull-10{right:83.33333333%} -.col-xs-pull-9{right:75%} -.col-xs-pull-8{right:66.66666667%} -.col-xs-pull-7{right:58.33333333%} -.col-xs-pull-6{right:50%} -.col-xs-pull-5{right:41.66666667%} -.col-xs-pull-4{right:33.33333333%} -.col-xs-pull-3{right:25%} -.col-xs-pull-2{right:16.66666667%} -.col-xs-pull-1{right:8.33333333%} -.col-xs-pull-0{right:auto} -.col-xs-push-12{left:100%} -.col-xs-push-11{left:91.66666667%} -.col-xs-push-10{left:83.33333333%} -.col-xs-push-9{left:75%} -.col-xs-push-8{left:66.66666667%} -.col-xs-push-7{left:58.33333333%} -.col-xs-push-6{left:50%} -.col-xs-push-5{left:41.66666667%} -.col-xs-push-4{left:33.33333333%} -.col-xs-push-3{left:25%} -.col-xs-push-2{left:16.66666667%} -.col-xs-push-1{left:8.33333333%} -.col-xs-push-0{left:auto} -.col-xs-offset-12{margin-left:100%} -.col-xs-offset-11{margin-left:91.66666667%} -.col-xs-offset-10{margin-left:83.33333333%} -.col-xs-offset-9{margin-left:75%} -.col-xs-offset-8{margin-left:66.66666667%} -.col-xs-offset-7{margin-left:58.33333333%} -.col-xs-offset-6{margin-left:50%} -.col-xs-offset-5{margin-left:41.66666667%} -.col-xs-offset-4{margin-left:33.33333333%} -.col-xs-offset-3{margin-left:25%} -.col-xs-offset-2{margin-left:16.66666667%} -.col-xs-offset-1{margin-left:8.33333333%} -.col-xs-offset-0{margin-left:0} -@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left} -.col-sm-12{width:100%} -.col-sm-11{width:91.66666667%} -.col-sm-10{width:83.33333333%} -.col-sm-9{width:75%} -.col-sm-8{width:66.66666667%} -.col-sm-7{width:58.33333333%} -.col-sm-6{width:50%} -.col-sm-5{width:41.66666667%} -.col-sm-4{width:33.33333333%} -.col-sm-3{width:25%} -.col-sm-2{width:16.66666667%} -.col-sm-1{width:8.33333333%} -.col-sm-pull-12{right:100%} -.col-sm-pull-11{right:91.66666667%} -.col-sm-pull-10{right:83.33333333%} -.col-sm-pull-9{right:75%} -.col-sm-pull-8{right:66.66666667%} -.col-sm-pull-7{right:58.33333333%} -.col-sm-pull-6{right:50%} -.col-sm-pull-5{right:41.66666667%} -.col-sm-pull-4{right:33.33333333%} -.col-sm-pull-3{right:25%} -.col-sm-pull-2{right:16.66666667%} -.col-sm-pull-1{right:8.33333333%} -.col-sm-pull-0{right:auto} -.col-sm-push-12{left:100%} -.col-sm-push-11{left:91.66666667%} -.col-sm-push-10{left:83.33333333%} -.col-sm-push-9{left:75%} -.col-sm-push-8{left:66.66666667%} -.col-sm-push-7{left:58.33333333%} -.col-sm-push-6{left:50%} -.col-sm-push-5{left:41.66666667%} -.col-sm-push-4{left:33.33333333%} -.col-sm-push-3{left:25%} -.col-sm-push-2{left:16.66666667%} -.col-sm-push-1{left:8.33333333%} -.col-sm-push-0{left:auto} -.col-sm-offset-12{margin-left:100%} -.col-sm-offset-11{margin-left:91.66666667%} -.col-sm-offset-10{margin-left:83.33333333%} -.col-sm-offset-9{margin-left:75%} -.col-sm-offset-8{margin-left:66.66666667%} -.col-sm-offset-7{margin-left:58.33333333%} -.col-sm-offset-6{margin-left:50%} -.col-sm-offset-5{margin-left:41.66666667%} -.col-sm-offset-4{margin-left:33.33333333%} -.col-sm-offset-3{margin-left:25%} -.col-sm-offset-2{margin-left:16.66666667%} -.col-sm-offset-1{margin-left:8.33333333%} -.col-sm-offset-0{margin-left:0} -} -@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left} -.col-md-12{width:100%} -.col-md-11{width:91.66666667%} -.col-md-10{width:83.33333333%} -.col-md-9{width:75%} -.col-md-8{width:66.66666667%} -.col-md-7{width:58.33333333%} -.col-md-6{width:50%} -.col-md-5{width:41.66666667%} -.col-md-4{width:33.33333333%} -.col-md-3{width:25%} -.col-md-2{width:16.66666667%} -.col-md-1{width:8.33333333%} -.col-md-pull-12{right:100%} -.col-md-pull-11{right:91.66666667%} -.col-md-pull-10{right:83.33333333%} -.col-md-pull-9{right:75%} -.col-md-pull-8{right:66.66666667%} -.col-md-pull-7{right:58.33333333%} -.col-md-pull-6{right:50%} -.col-md-pull-5{right:41.66666667%} -.col-md-pull-4{right:33.33333333%} -.col-md-pull-3{right:25%} -.col-md-pull-2{right:16.66666667%} -.col-md-pull-1{right:8.33333333%} -.col-md-pull-0{right:auto} -.col-md-push-12{left:100%} -.col-md-push-11{left:91.66666667%} -.col-md-push-10{left:83.33333333%} -.col-md-push-9{left:75%} -.col-md-push-8{left:66.66666667%} -.col-md-push-7{left:58.33333333%} -.col-md-push-6{left:50%} -.col-md-push-5{left:41.66666667%} -.col-md-push-4{left:33.33333333%} -.col-md-push-3{left:25%} -.col-md-push-2{left:16.66666667%} -.col-md-push-1{left:8.33333333%} -.col-md-push-0{left:auto} -.col-md-offset-12{margin-left:100%} -.col-md-offset-11{margin-left:91.66666667%} -.col-md-offset-10{margin-left:83.33333333%} -.col-md-offset-9{margin-left:75%} -.col-md-offset-8{margin-left:66.66666667%} -.col-md-offset-7{margin-left:58.33333333%} -.col-md-offset-6{margin-left:50%} -.col-md-offset-5{margin-left:41.66666667%} -.col-md-offset-4{margin-left:33.33333333%} -.col-md-offset-3{margin-left:25%} -.col-md-offset-2{margin-left:16.66666667%} -.col-md-offset-1{margin-left:8.33333333%} -.col-md-offset-0{margin-left:0} -} -@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left} -.col-lg-12{width:100%} -.col-lg-11{width:91.66666667%} -.col-lg-10{width:83.33333333%} -.col-lg-9{width:75%} -.col-lg-8{width:66.66666667%} -.col-lg-7{width:58.33333333%} -.col-lg-6{width:50%} -.col-lg-5{width:41.66666667%} -.col-lg-4{width:33.33333333%} -.col-lg-3{width:25%} -.col-lg-2{width:16.66666667%} -.col-lg-1{width:8.33333333%} -.col-lg-pull-12{right:100%} -.col-lg-pull-11{right:91.66666667%} -.col-lg-pull-10{right:83.33333333%} -.col-lg-pull-9{right:75%} -.col-lg-pull-8{right:66.66666667%} -.col-lg-pull-7{right:58.33333333%} -.col-lg-pull-6{right:50%} -.col-lg-pull-5{right:41.66666667%} -.col-lg-pull-4{right:33.33333333%} -.col-lg-pull-3{right:25%} -.col-lg-pull-2{right:16.66666667%} -.col-lg-pull-1{right:8.33333333%} -.col-lg-pull-0{right:auto} -.col-lg-push-12{left:100%} -.col-lg-push-11{left:91.66666667%} -.col-lg-push-10{left:83.33333333%} -.col-lg-push-9{left:75%} -.col-lg-push-8{left:66.66666667%} -.col-lg-push-7{left:58.33333333%} -.col-lg-push-6{left:50%} -.col-lg-push-5{left:41.66666667%} -.col-lg-push-4{left:33.33333333%} -.col-lg-push-3{left:25%} -.col-lg-push-2{left:16.66666667%} -.col-lg-push-1{left:8.33333333%} -.col-lg-push-0{left:auto} -.col-lg-offset-12{margin-left:100%} -.col-lg-offset-11{margin-left:91.66666667%} -.col-lg-offset-10{margin-left:83.33333333%} -.col-lg-offset-9{margin-left:75%} -.col-lg-offset-8{margin-left:66.66666667%} -.col-lg-offset-7{margin-left:58.33333333%} -.col-lg-offset-6{margin-left:50%} -.col-lg-offset-5{margin-left:41.66666667%} -.col-lg-offset-4{margin-left:33.33333333%} -.col-lg-offset-3{margin-left:25%} -.col-lg-offset-2{margin-left:16.66666667%} -.col-lg-offset-1{margin-left:8.33333333%} -.col-lg-offset-0{margin-left:0} -} -caption{padding-top:8px;padding-bottom:8px;color:#777} -.table{width:100%;max-width:100%;margin-bottom:20px} -.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd} -.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd} -.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0} -.table>tbody+tbody{border-top:2px solid #ddd} -.table .table{background-color:#fff} -.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px} -.table-bordered,.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd} -.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px} -.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9} -.table-hover>tbody>tr:hover,.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5} -table col[class*=col-]{position:static;display:table-column;float:none} -table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none} -.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8} -.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8} -.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6} -.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7} -.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3} -.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3} -.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc} -.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede} -.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc} -.table-responsive{min-height:.01%;overflow-x:auto} -@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd} -.table-responsive>.table{margin-bottom:0} -.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap} -.table-responsive>.table-bordered{border:0} -.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0} -.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0} -.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0} -} -fieldset,legend{padding:0;border:0} -fieldset{min-width:0;margin:0} -legend{width:100%;margin-bottom:20px;font-size:21px;line-height:inherit;border-bottom:1px solid #e5e5e5} -label{display:inline-block;max-width:100%;margin-bottom:5px} -input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-appearance:none} -input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px\9;line-height:normal} -.form-control,output{font-size:14px;line-height:1.42857143;color:#555;display:block} -input[type=file]{display:block} -input[type=range]{display:block;width:100%} -select[multiple],select[size]{height:auto} -input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px} -output{padding-top:7px} -.form-control{width:100%;height:34px;padding:6px 12px;background-color:#fff;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s} -.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)} -.form-control::-moz-placeholder{color:#999;opacity:1} -.form-control:-ms-input-placeholder{color:#999} -.form-control::-webkit-input-placeholder{color:#999} -.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .form-control-feedback,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d} -.form-control::-ms-expand{background-color:transparent;border:0} -.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{background-color:#eee;opacity:1} -.form-control[disabled],fieldset[disabled] .form-control{cursor:not-allowed} -textarea.form-control{height:auto} -@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date].form-control,input[type=time].form-control,input[type=datetime-local].form-control,input[type=month].form-control{line-height:34px} -.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px} -.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px} -} -.form-group{margin-bottom:15px} -.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px} -.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer} -.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px\9;margin-left:-20px} -.checkbox+.checkbox,.radio+.radio{margin-top:-5px} -.checkbox-inline,.radio-inline{position:relative;display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer} -.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px} -.checkbox-inline.disabled,.checkbox.disabled label,.radio-inline.disabled,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio label,fieldset[disabled] .radio-inline,fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed} -.form-control-static{min-height:34px;padding-top:7px;padding-bottom:7px;margin-bottom:0} -.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0} -.form-group-sm .form-control,.input-sm{padding:5px 10px;border-radius:3px;font-size:12px} -.input-sm{height:30px;line-height:1.5} -select.input-sm{height:30px;line-height:30px} -select[multiple].input-sm,textarea.input-sm{height:auto} -.form-group-sm .form-control{height:30px;line-height:1.5} -.form-group-lg .form-control,.input-lg{border-radius:6px;padding:10px 16px;font-size:18px} -.form-group-sm select.form-control{height:30px;line-height:30px} -.form-group-sm select[multiple].form-control,.form-group-sm textarea.form-control{height:auto} -.form-group-sm .form-control-static{height:30px;min-height:32px;padding:6px 10px;font-size:12px;line-height:1.5} -.input-lg{height:46px;line-height:1.3333333} -select.input-lg{height:46px;line-height:46px} -select[multiple].input-lg,textarea.input-lg{height:auto} -.form-group-lg .form-control{height:46px;line-height:1.3333333} -.form-group-lg select.form-control{height:46px;line-height:46px} -.form-group-lg select[multiple].form-control,.form-group-lg textarea.form-control{height:auto} -.form-group-lg .form-control-static{height:46px;min-height:38px;padding:11px 16px;font-size:18px;line-height:1.3333333} -.has-feedback{position:relative} -.has-feedback .form-control{padding-right:42.5px} -.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none} -.collapsing,.dropdown,.dropup{position:relative} -.form-group-lg .form-control+.form-control-feedback,.input-group-lg+.form-control-feedback,.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px} -.form-group-sm .form-control+.form-control-feedback,.input-group-sm+.form-control-feedback,.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px} -.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)} -.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168} -.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d} -.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .form-control-feedback,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b} -.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)} -.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b} -.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b} -.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .form-control-feedback,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442} -.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)} -.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483} -.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442} -.has-feedback label~.form-control-feedback{top:25px} -.has-feedback label.sr-only~.form-control-feedback{top:0} -.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373} -@media (min-width:768px){.form-inline .form-control-static,.form-inline .form-group{display:inline-block} -.form-inline .control-label,.form-inline .form-group{margin-bottom:0;vertical-align:middle} -.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle} -.form-inline .input-group{display:inline-table;vertical-align:middle} -.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto} -.form-inline .input-group>.form-control{width:100%} -.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle} -.form-inline .checkbox label,.form-inline .radio label{padding-left:0} -.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0} -.form-inline .has-feedback .form-control-feedback{top:0} -.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right} -} -.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0} -.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px} -.form-horizontal .form-group{margin-right:-15px;margin-left:-15px} -.form-horizontal .has-feedback .form-control-feedback{right:15px} -@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:11px;font-size:18px} -.form-horizontal .form-group-sm .control-label{padding-top:6px;font-size:12px} -} -.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border:1px solid transparent;border-radius:4px} -.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:-webkit-focus-ring-color auto 5px;outline-offset:-2px} -.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none} -.btn.active,.btn:active{outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)} -.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65} -a.btn.disabled,fieldset[disabled] a.btn{pointer-events:none} -.btn-default{color:#333;background-color:#fff;border-color:#ccc} -.btn-default.focus,.btn-default:focus{color:#333;background-color:#e6e6e6;border-color:#8c8c8c} -.btn-default.active,.btn-default:active,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad} -.btn-default.active.focus,.btn-default.active:focus,.btn-default.active:hover,.btn-default:active.focus,.btn-default:active:focus,.btn-default:active:hover,.open>.dropdown-toggle.btn-default.focus,.open>.dropdown-toggle.btn-default:focus,.open>.dropdown-toggle.btn-default:hover{color:#333;background-color:#d4d4d4;border-color:#8c8c8c} -.btn-default.disabled.focus,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled].focus,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc} -.btn-default .badge{color:#fff;background-color:#333} -.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4} -.btn-primary.focus,.btn-primary:focus{color:#fff;background-color:#286090;border-color:#122b40} -.btn-primary.active,.btn-primary:active,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74} -.btn-primary.active.focus,.btn-primary.active:focus,.btn-primary.active:hover,.btn-primary:active.focus,.btn-primary:active:focus,.btn-primary:active:hover,.open>.dropdown-toggle.btn-primary.focus,.open>.dropdown-toggle.btn-primary:focus,.open>.dropdown-toggle.btn-primary:hover{color:#fff;background-color:#204d74;border-color:#122b40} -.btn-primary.disabled.focus,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled].focus,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4} -.btn-primary .badge{color:#337ab7;background-color:#fff} -.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c} -.btn-success.focus,.btn-success:focus{color:#fff;background-color:#449d44;border-color:#255625} -.btn-success.active,.btn-success:active,.btn-success:hover,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439} -.btn-success.active.focus,.btn-success.active:focus,.btn-success.active:hover,.btn-success:active.focus,.btn-success:active:focus,.btn-success:active:hover,.open>.dropdown-toggle.btn-success.focus,.open>.dropdown-toggle.btn-success:focus,.open>.dropdown-toggle.btn-success:hover{color:#fff;background-color:#398439;border-color:#255625} -.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none} -.btn-success.disabled.focus,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled].focus,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c} -.btn-success .badge{color:#5cb85c;background-color:#fff} -.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da} -.btn-info.focus,.btn-info:focus{color:#fff;background-color:#31b0d5;border-color:#1b6d85} -.btn-info.active,.btn-info:active,.btn-info:hover,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc} -.btn-info.active.focus,.btn-info.active:focus,.btn-info.active:hover,.btn-info:active.focus,.btn-info:active:focus,.btn-info:active:hover,.open>.dropdown-toggle.btn-info.focus,.open>.dropdown-toggle.btn-info:focus,.open>.dropdown-toggle.btn-info:hover{color:#fff;background-color:#269abc;border-color:#1b6d85} -.btn-info.disabled.focus,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled].focus,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da} -.btn-info .badge{color:#5bc0de;background-color:#fff} -.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236} -.btn-warning.focus,.btn-warning:focus{color:#fff;background-color:#ec971f;border-color:#985f0d} -.btn-warning.active,.btn-warning:active,.btn-warning:hover,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512} -.btn-warning.active.focus,.btn-warning.active:focus,.btn-warning.active:hover,.btn-warning:active.focus,.btn-warning:active:focus,.btn-warning:active:hover,.open>.dropdown-toggle.btn-warning.focus,.open>.dropdown-toggle.btn-warning:focus,.open>.dropdown-toggle.btn-warning:hover{color:#fff;background-color:#d58512;border-color:#985f0d} -.btn-warning.disabled.focus,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled].focus,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236} -.btn-warning .badge{color:#f0ad4e;background-color:#fff} -.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a} -.btn-danger.focus,.btn-danger:focus{color:#fff;background-color:#c9302c;border-color:#761c19} -.btn-danger.active,.btn-danger:active,.btn-danger:hover,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925} -.btn-danger.active.focus,.btn-danger.active:focus,.btn-danger.active:hover,.btn-danger:active.focus,.btn-danger:active:focus,.btn-danger:active:hover,.open>.dropdown-toggle.btn-danger.focus,.open>.dropdown-toggle.btn-danger:focus,.open>.dropdown-toggle.btn-danger:hover{color:#fff;background-color:#ac2925;border-color:#761c19} -.btn-danger.disabled.focus,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled].focus,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a} -.btn-danger .badge{color:#d9534f;background-color:#fff} -.btn-link{font-weight:400;color:#337ab7;border-radius:0} -.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none} -.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent} -.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent} -.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none} -.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px} -.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px} -.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px} -.btn-block{display:block;width:100%} -.btn-block+.btn-block{margin-top:5px} -input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%} -.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear} -.fade.in{opacity:1} -.collapse{display:none} -.collapse.in{display:block} -tr.collapse.in{display:table-row} -tbody.collapse.in{display:table-row-group} -.collapsing{height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility} -.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px dashed;border-top:4px solid\9;border-right:4px solid transparent;border-left:4px solid transparent} -.dropdown-toggle:focus{outline:0} -.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)} -.dropdown-menu-right,.dropdown-menu.pull-right{right:0;left:auto} -.dropdown-header,.dropdown-menu>li>a{display:block;padding:3px 20px;line-height:1.42857143;white-space:nowrap} -.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle,.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0} -.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child,.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0} -.btn-group-vertical>.btn:not(:first-child):not(:last-child),.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn,.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0} -.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5} -.dropdown-menu>li>a{clear:both;font-weight:400;color:#333} -.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5} -.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0} -.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777} -.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)} -.open>.dropdown-menu{display:block} -.open>a{outline:0} -.dropdown-menu-left{right:auto;left:0} -.dropdown-header{font-size:12px;color:#777} -.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990} -.nav-justified>.dropdown .dropdown-menu,.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto} -.pull-right>.dropdown-menu{right:0;left:auto} -.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px dashed;border-bottom:4px solid\9} -.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px} -@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto} -.navbar-right .dropdown-menu-left{right:auto;left:0} -} -.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle} -.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left} -.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2} -.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px} -.btn-toolbar{margin-left:-5px} -.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px} -.btn .caret,.btn-group>.btn:first-child{margin-left:0} -.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0} -.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px} -.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px} -.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)} -.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none} -.btn-lg .caret{border-width:5px 5px 0} -.dropup .btn-lg .caret{border-width:0 5px 5px} -.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%} -.btn-group-vertical>.btn-group>.btn{float:none} -.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0} -.btn-group-vertical>.btn:first-child:not(:last-child){border-radius:4px 4px 0 0} -.btn-group-vertical>.btn:last-child:not(:first-child){border-radius:0 0 4px 4px} -.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0} -.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0} -.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0} -.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate} -.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%} -.btn-group-justified>.btn-group .btn{width:100%} -.btn-group-justified>.btn-group .dropdown-menu{left:auto} -[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none} -.input-group{position:relative;display:table;border-collapse:separate} -.input-group[class*=col-]{float:none;padding-right:0;padding-left:0} -.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0} -.input-group .form-control:focus{z-index:3} -.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px} -select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px} -select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto} -.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px} -select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px} -select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto} -.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell} -.nav>li,.nav>li>a{display:block;position:relative} -.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0} -.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle} -.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px} -.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px} -.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px} -.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0} -.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0} -.input-group-addon:first-child{border-right:0} -.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0} -.input-group-addon:last-child{border-left:0} -.input-group-btn{position:relative;font-size:0;white-space:nowrap} -.input-group-btn>.btn{position:relative} -.input-group-btn>.btn+.btn{margin-left:-1px} -.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2} -.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px} -.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{z-index:2;margin-left:-1px} -.nav{padding-left:0;margin-bottom:0;list-style:none} -.nav>li>a{padding:10px 15px} -.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee} -.nav>li.disabled>a{color:#777} -.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent} -.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7} -.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5} -.nav>li>a>img{max-width:none} -.nav-tabs{border-bottom:1px solid #ddd} -.nav-tabs>li{float:left;margin-bottom:-1px} -.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0} -.nav-tabs>li>a:hover{border-color:#eee #eee #ddd} -.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent} -.nav-tabs.nav-justified{width:100%;border-bottom:0} -.nav-tabs.nav-justified>li{float:none} -.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center;margin-right:0;border-radius:4px} -.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd} -@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%} -.nav-tabs.nav-justified>li>a{margin-bottom:0;border-bottom:1px solid #ddd;border-radius:4px 4px 0 0} -.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff} -} -.nav-pills>li{float:left} -.nav-justified>li,.nav-stacked>li{float:none} -.nav-pills>li>a{border-radius:4px} -.nav-pills>li+li{margin-left:2px} -.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7} -.nav-stacked>li+li{margin-top:2px;margin-left:0} -.nav-justified{width:100%} -.nav-justified>li>a{margin-bottom:5px;text-align:center} -.nav-tabs-justified{border-bottom:0} -.nav-tabs-justified>li>a{margin-right:0;border-radius:4px} -.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd} -@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%} -.nav-justified>li>a{margin-bottom:0} -.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0} -.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff} -} -.tab-content>.tab-pane{display:none} -.tab-content>.active{display:block} -.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0} -.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent} -.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)} -.navbar-collapse.in{overflow-y:auto} -@media (min-width:768px){.navbar{border-radius:4px} -.navbar-header{float:left} -.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none} -.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important} -.navbar-collapse.in{overflow-y:visible} -.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0} -} -.embed-responsive,.modal,.modal-open,.progress{overflow:hidden} -@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px} -} -.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px} -.navbar-static-top{z-index:1000;border-width:0 0 1px} -.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030} -.navbar-fixed-top{top:0;border-width:0 0 1px} -.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0} -.navbar-brand{float:left;height:50px;padding:15px;font-size:18px;line-height:20px} -.navbar-brand:focus,.navbar-brand:hover{text-decoration:none} -.navbar-brand>img{display:block} -@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0} -.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0} -.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px} -} -.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;border:1px solid transparent;border-radius:4px} -.navbar-toggle:focus{outline:0} -.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px} -.navbar-toggle .icon-bar+.icon-bar{margin-top:4px} -.navbar-nav{margin:7.5px -15px} -.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px} -@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none} -.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px} -.navbar-nav .open .dropdown-menu>li>a{line-height:20px} -.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none} -} -.progress-bar-striped,.progress-striped .progress-bar,.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} -@media (min-width:768px){.navbar-toggle{display:none} -.navbar-nav{float:left;margin:0} -.navbar-nav>li{float:left} -.navbar-nav>li>a{padding-top:15px;padding-bottom:15px} -} -.navbar-form{padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);margin:8px -15px} -@media (min-width:768px){.navbar-form .form-control-static,.navbar-form .form-group{display:inline-block} -.navbar-form .control-label,.navbar-form .form-group{margin-bottom:0;vertical-align:middle} -.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle} -.navbar-form .input-group{display:inline-table;vertical-align:middle} -.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto} -.navbar-form .input-group>.form-control{width:100%} -.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle} -.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0} -.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0} -.navbar-form .has-feedback .form-control-feedback{top:0} -.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none} -} -.breadcrumb>li,.pagination{display:inline-block} -.btn .badge,.btn .label{top:-1px;position:relative} -@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px} -.navbar-form .form-group:last-child{margin-bottom:0} -} -.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0} -.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-radius:4px 4px 0 0} -.navbar-btn{margin-top:8px;margin-bottom:8px} -.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px} -.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px} -.navbar-text{margin-top:15px;margin-bottom:15px} -@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px} -.navbar-left{float:left!important} -.navbar-right{float:right!important;margin-right:-15px} -.navbar-right~.navbar-right{margin-right:0} -} -.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7} -.navbar-default .navbar-brand{color:#777} -.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent} -.navbar-default .navbar-nav>li>a,.navbar-default .navbar-text{color:#777} -.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent} -.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7} -.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent} -.navbar-default .navbar-toggle{border-color:#ddd} -.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd} -.navbar-default .navbar-toggle .icon-bar{background-color:#888} -.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7} -.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7} -@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777} -.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent} -.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7} -.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent} -} -.navbar-default .navbar-link{color:#777} -.navbar-default .navbar-link:hover{color:#333} -.navbar-default .btn-link{color:#777} -.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333} -.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc} -.navbar-inverse{background-color:#222;border-color:#080808} -.navbar-inverse .navbar-brand{color:#9d9d9d} -.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent} -.navbar-inverse .navbar-nav>li>a,.navbar-inverse .navbar-text{color:#9d9d9d} -.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent} -.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808} -.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent} -.navbar-inverse .navbar-toggle{border-color:#333} -.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333} -.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff} -.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010} -.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808} -@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808} -.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808} -.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d} -.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent} -.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808} -.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent} -} -.navbar-inverse .navbar-link{color:#9d9d9d} -.navbar-inverse .navbar-link:hover{color:#fff} -.navbar-inverse .btn-link{color:#9d9d9d} -.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff} -.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444} -.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px} -.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"} -.breadcrumb>.active{color:#777} -.pagination{padding-left:0;margin:20px 0;border-radius:4px} -.pager li,.pagination>li{display:inline} -.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd} -.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px} -.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px} -.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{z-index:2;color:#23527c;background-color:#eee;border-color:#ddd} -.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:3;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7} -.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd} -.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px;line-height:1.3333333} -.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px} -.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px} -.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px;line-height:1.5} -.badge,.label{font-weight:700;line-height:1;white-space:nowrap;text-align:center} -.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px} -.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px} -.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none} -.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px} -.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee} -.pager .next>a,.pager .next>span{float:right} -.pager .previous>a,.pager .previous>span{float:left} -.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff} -a.badge:focus,a.badge:hover,a.label:focus,a.label:hover{color:#fff;cursor:pointer;text-decoration:none} -.label{display:inline;padding:.2em .6em .3em;font-size:75%;color:#fff;border-radius:.25em} -.label:empty{display:none} -.label-default{background-color:#777} -.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e} -.label-primary{background-color:#337ab7} -.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090} -.label-success{background-color:#5cb85c} -.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44} -.label-info{background-color:#5bc0de} -.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5} -.label-warning{background-color:#f0ad4e} -.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f} -.label-danger{background-color:#d9534f} -.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c} -.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;color:#fff;vertical-align:middle;background-color:#777;border-radius:10px} -.badge:empty{display:none} -.media-object,.thumbnail{display:block} -.btn-group-xs>.btn .badge,.btn-xs .badge{top:0;padding:1px 5px} -.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff} -.jumbotron,.jumbotron .h1,.jumbotron h1{color:inherit} -.list-group-item>.badge{float:right} -.list-group-item>.badge+.badge{margin-right:5px} -.nav-pills>li>a>.badge{margin-left:3px} -.jumbotron{padding-top:30px;padding-bottom:30px;margin-bottom:30px;background-color:#eee} -.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200} -.alert,.thumbnail{margin-bottom:20px} -.alert .alert-link,.close{font-weight:700} -.jumbotron>hr{border-top-color:#d5d5d5} -.container .jumbotron,.container-fluid .jumbotron{padding-right:15px;padding-left:15px;border-radius:6px} -.jumbotron .container{max-width:100%} -@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px} -.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px} -.jumbotron .h1,.jumbotron h1{font-size:63px} -} -.thumbnail{padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out} -.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto} -a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7} -.thumbnail .caption{padding:9px;color:#333} -.alert{padding:15px;border:1px solid transparent;border-radius:4px} -.alert h4{margin-top:0;color:inherit} -.alert>p,.alert>ul{margin-bottom:0} -.alert>p+p{margin-top:5px} -.alert-dismissable,.alert-dismissible{padding-right:35px} -.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit} -.modal,.modal-backdrop{top:0;right:0;bottom:0;left:0} -.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6} -.alert-success hr{border-top-color:#c9e2b3} -.alert-success .alert-link{color:#2b542c} -.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1} -.alert-info hr{border-top-color:#a6e1ec} -.alert-info .alert-link{color:#245269} -.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc} -.alert-warning hr{border-top-color:#f7e1b5} -.alert-warning .alert-link{color:#66512c} -.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1} -.alert-danger hr{border-top-color:#e4b9c0} -.alert-danger .alert-link{color:#843534} -@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0} -to{background-position:0 0} -} -@-o-keyframes progress-bar-stripes{from{background-position:40px 0} -to{background-position:0 0} -} -@keyframes progress-bar-stripes{from{background-position:40px 0} -to{background-position:0 0} -} -.progress{height:20px;margin-bottom:20px;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)} -.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease} -.progress-bar-striped,.progress-striped .progress-bar{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px} -.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite} -.progress-bar-success{background-color:#5cb85c} -.progress-striped .progress-bar-success{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} -.progress-striped .progress-bar-info,.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} -.progress-bar-info{background-color:#5bc0de} -.progress-striped .progress-bar-info{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} -.progress-bar-warning{background-color:#f0ad4e} -.progress-striped .progress-bar-warning{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} -.progress-bar-danger{background-color:#d9534f} -.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)} -.media{margin-top:15px} -.media:first-child{margin-top:0} -.media,.media-body{overflow:hidden;zoom:1} -.media-body{width:10000px} -.media-object.img-thumbnail{max-width:none} -.media-right,.media>.pull-right{padding-left:10px} -.media-left,.media>.pull-left{padding-right:10px} -.media-body,.media-left,.media-right{display:table-cell;vertical-align:top} -.media-middle{vertical-align:middle} -.media-bottom{vertical-align:bottom} -.media-heading{margin-top:0;margin-bottom:5px} -.media-list{padding-left:0;list-style:none} -.list-group{padding-left:0;margin-bottom:20px} -.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd} -.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px} -.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px} -a.list-group-item,button.list-group-item{color:#555} -a.list-group-item .list-group-item-heading,button.list-group-item .list-group-item-heading{color:#333} -a.list-group-item:focus,a.list-group-item:hover,button.list-group-item:focus,button.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5} -button.list-group-item{width:100%;text-align:left} -.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee} -.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit} -.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777} -.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7} -.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit} -.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef} -.list-group-item-success{color:#3c763d;background-color:#dff0d8} -a.list-group-item-success,button.list-group-item-success{color:#3c763d} -a.list-group-item-success .list-group-item-heading,button.list-group-item-success .list-group-item-heading{color:inherit} -a.list-group-item-success:focus,a.list-group-item-success:hover,button.list-group-item-success:focus,button.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6} -a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover,button.list-group-item-success.active,button.list-group-item-success.active:focus,button.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d} -.list-group-item-info{color:#31708f;background-color:#d9edf7} -a.list-group-item-info,button.list-group-item-info{color:#31708f} -a.list-group-item-info .list-group-item-heading,button.list-group-item-info .list-group-item-heading{color:inherit} -a.list-group-item-info:focus,a.list-group-item-info:hover,button.list-group-item-info:focus,button.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3} -a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover,button.list-group-item-info.active,button.list-group-item-info.active:focus,button.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f} -.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3} -a.list-group-item-warning,button.list-group-item-warning{color:#8a6d3b} -a.list-group-item-warning .list-group-item-heading,button.list-group-item-warning .list-group-item-heading{color:inherit} -a.list-group-item-warning:focus,a.list-group-item-warning:hover,button.list-group-item-warning:focus,button.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc} -a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover,button.list-group-item-warning.active,button.list-group-item-warning.active:focus,button.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b} -.list-group-item-danger{color:#a94442;background-color:#f2dede} -a.list-group-item-danger,button.list-group-item-danger{color:#a94442} -a.list-group-item-danger .list-group-item-heading,button.list-group-item-danger .list-group-item-heading{color:inherit} -a.list-group-item-danger:focus,a.list-group-item-danger:hover,button.list-group-item-danger:focus,button.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc} -a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover,button.list-group-item-danger.active,button.list-group-item-danger.active:focus,button.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442} -.panel-heading>.dropdown .dropdown-toggle,.panel-title,.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit} -.list-group-item-heading{margin-top:0;margin-bottom:5px} -.list-group-item-text{margin-bottom:0;line-height:1.3} -.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)} -.panel-title,.panel>.list-group,.panel>.panel-collapse>.list-group,.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0} -.panel-body{padding:15px} -.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px} -.panel-title{margin-top:0;font-size:16px} -.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px} -.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0} -.panel-group .panel-heading,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0} -.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px} -.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px} -.panel>.panel-heading+.panel-collapse>.list-group .list-group-item:first-child{border-top-left-radius:0;border-top-right-radius:0} -.list-group+.panel-footer,.panel-heading+.list-group .list-group-item:first-child{border-top-width:0} -.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px} -.panel>.table-responsive:first-child>.table:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px} -.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px} -.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px} -.panel>.table-responsive:last-child>.table:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px} -.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px} -.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px} -.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd} -.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0} -.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0} -.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0} -.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0} -.panel>.table-responsive{margin-bottom:0;border:0} -.panel-group{margin-bottom:20px} -.panel-group .panel{margin-bottom:0;border-radius:4px} -.panel-group .panel+.panel{margin-top:5px} -.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd} -.panel-group .panel-footer{border-top:0} -.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd} -.panel-default{border-color:#ddd} -.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd} -.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd} -.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333} -.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd} -.panel-primary{border-color:#337ab7} -.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7} -.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7} -.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff} -.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7} -.panel-success{border-color:#d6e9c6} -.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6} -.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6} -.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d} -.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6} -.panel-info{border-color:#bce8f1} -.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1} -.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1} -.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f} -.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1} -.panel-warning{border-color:#faebcc} -.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc} -.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc} -.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b} -.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc} -.panel-danger{border-color:#ebccd1} -.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1} -.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1} -.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442} -.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1} -.embed-responsive{position:relative;display:block;height:0;padding:0} -.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0} -.embed-responsive-16by9{padding-bottom:56.25%} -.embed-responsive-4by3{padding-bottom:75%} -.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)} -.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)} -.well-lg{padding:24px;border-radius:6px} -.well-sm{padding:9px;border-radius:3px} -.close{float:right;font-size:21px;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2} -.popover,.tooltip{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-style:normal;font-weight:400;line-height:1.42857143;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;word-wrap:normal;white-space:normal;line-break:auto;text-decoration:none} -.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5} -button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0} -.modal{position:fixed;z-index:1050;display:none;-webkit-overflow-scrolling:touch;outline:0} -.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)} -.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)} -.modal-open .modal{overflow-x:hidden;overflow-y:auto} -.modal-dialog{position:relative;width:auto;margin:10px} -.modal-content{position:relative;background-color:#fff;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)} -.modal-backdrop{position:fixed;z-index:1040;background-color:#000} -.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0} -.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5} -.modal-header{padding:15px;border-bottom:1px solid #e5e5e5} -.modal-header .close{margin-top:-2px} -.modal-title{margin:0;line-height:1.42857143} -.modal-body{position:relative;padding:15px} -.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5} -.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px} -.modal-footer .btn-group .btn+.btn{margin-left:-1px} -.modal-footer .btn-block+.btn-block{margin-left:0} -.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll} -@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto} -.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)} -.modal-sm{width:300px} -} -@media (min-width:992px){.modal-lg{width:900px} -} -.tooltip{position:absolute;z-index:1070;display:block;font-size:12px;text-align:left;text-align:start;filter:alpha(opacity=0);opacity:0} -.tooltip.in{filter:alpha(opacity=90);opacity:.9} -.tooltip.top{padding:5px 0;margin-top:-3px} -.tooltip.right{padding:0 5px;margin-left:3px} -.tooltip.bottom{padding:5px 0;margin-top:3px} -.tooltip.left{padding:0 5px;margin-left:-3px} -.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;background-color:#000;border-radius:4px} -.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid} -.tooltip.top .tooltip-arrow,.tooltip.top-left .tooltip-arrow,.tooltip.top-right .tooltip-arrow{bottom:0;border-width:5px 5px 0;border-top-color:#000} -.tooltip.top .tooltip-arrow{left:50%;margin-left:-5px} -.tooltip.top-left .tooltip-arrow{right:5px;margin-bottom:-5px} -.tooltip.top-right .tooltip-arrow{left:5px;margin-bottom:-5px} -.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000} -.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000} -.tooltip.bottom .tooltip-arrow,.tooltip.bottom-left .tooltip-arrow,.tooltip.bottom-right .tooltip-arrow{border-width:0 5px 5px;border-bottom-color:#000;top:0} -.tooltip.bottom .tooltip-arrow{left:50%;margin-left:-5px} -.tooltip.bottom-left .tooltip-arrow{right:5px;margin-top:-5px} -.tooltip.bottom-right .tooltip-arrow{left:5px;margin-top:-5px} -.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-size:14px;text-align:left;text-align:start;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)} -.carousel-caption,.carousel-control{color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)} -.popover.top{margin-top:-10px} -.popover.right{margin-left:10px} -.popover.bottom{margin-top:10px} -.popover.left{margin-left:-10px} -.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0} -.popover-content{padding:9px 14px} -.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid} -.carousel,.carousel-inner{position:relative} -.popover>.arrow{border-width:11px} -.popover>.arrow:after{content:"";border-width:10px} -.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0} -.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0} -.popover.left>.arrow:after,.popover.right>.arrow:after{bottom:-10px;content:" "} -.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0} -.popover.right>.arrow:after{left:1px;border-right-color:#fff;border-left-width:0} -.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)} -.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff} -.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)} -.popover.left>.arrow:after{right:1px;border-right-width:0;border-left-color:#fff} -.carousel-inner{width:100%;overflow:hidden} -.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left} -.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1} -@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000px;perspective:1000px} -.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)} -.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)} -.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)} -} -.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block} -.carousel-inner>.active{left:0} -.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%} -.carousel-inner>.next{left:100%} -.carousel-inner>.prev{left:-100%} -.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0} -.carousel-inner>.active.left{left:-100%} -.carousel-inner>.active.right{left:100%} -.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;background-color:rgba(0,0,0,0);filter:alpha(opacity=50);opacity:.5} -.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x} -.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x} -.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9} -.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block;margin-top:-10px} -.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px} -.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px} -.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;font-family:serif;line-height:1} -.carousel-control .icon-prev:before{content:'\2039'} -.carousel-control .icon-next:before{content:'\203a'} -.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none} -.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000\9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px} -.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff} -.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px} -.carousel-caption .btn,.text-hide{text-shadow:none} -@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-10px;font-size:30px} -.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-10px} -.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-10px} -.carousel-caption{right:20%;left:20%;padding-bottom:30px} -.carousel-indicators{bottom:20px} -} -.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.modal-header:after,.modal-header:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "} -.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.modal-header:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both} -.center-block{display:block;margin-right:auto;margin-left:auto} -.pull-right{float:right!important} -.pull-left{float:left!important} -.hide{display:none!important} -.show{display:block!important} -.hidden,.visible-lg,.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important} -.invisible{visibility:hidden} -.text-hide{font:0/0 a;color:transparent;background-color:transparent;border:0} -.affix{position:fixed} -@-ms-viewport{width:device-width} -@media (max-width:767px){.visible-xs{display:block!important} -table.visible-xs{display:table!important} -tr.visible-xs{display:table-row!important} -td.visible-xs,th.visible-xs{display:table-cell!important} -.visible-xs-block{display:block!important} -.visible-xs-inline{display:inline!important} -.visible-xs-inline-block{display:inline-block!important} -} -@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important} -table.visible-sm{display:table!important} -tr.visible-sm{display:table-row!important} -td.visible-sm,th.visible-sm{display:table-cell!important} -.visible-sm-block{display:block!important} -.visible-sm-inline{display:inline!important} -.visible-sm-inline-block{display:inline-block!important} -} -@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important} -table.visible-md{display:table!important} -tr.visible-md{display:table-row!important} -td.visible-md,th.visible-md{display:table-cell!important} -.visible-md-block{display:block!important} -.visible-md-inline{display:inline!important} -.visible-md-inline-block{display:inline-block!important} -} -@media (min-width:1200px){.visible-lg{display:block!important} -table.visible-lg{display:table!important} -tr.visible-lg{display:table-row!important} -td.visible-lg,th.visible-lg{display:table-cell!important} -.visible-lg-block{display:block!important} -.visible-lg-inline{display:inline!important} -.visible-lg-inline-block{display:inline-block!important} -.hidden-lg{display:none!important} -} -@media (max-width:767px){.hidden-xs{display:none!important} -} -@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important} -} -@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important} -} -.visible-print{display:none!important} -@media print{.visible-print{display:block!important} -table.visible-print{display:table!important} -tr.visible-print{display:table-row!important} -td.visible-print,th.visible-print{display:table-cell!important} -} -.visible-print-block{display:none!important} -@media print{.visible-print-block{display:block!important} -} -.visible-print-inline{display:none!important} -@media print{.visible-print-inline{display:inline!important} -} -.visible-print-inline-block{display:none!important} -@media print{.visible-print-inline-block{display:inline-block!important} -.hidden-print{display:none!important} -} -.hljs{display:block;background:#fff;padding:.5em;color:#333;overflow-x:auto;-webkit-text-size-adjust:none} -.bash .hljs-shebang,.hljs-comment,.java .hljs-javadoc,.javascript .hljs-javadoc,.rust .hljs-preprocessor{color:#969896} -.apache .hljs-sqbracket,.c .hljs-preprocessor,.coffeescript .hljs-regexp,.coffeescript .hljs-subst,.cpp .hljs-preprocessor,.hljs-string,.javascript .hljs-regexp,.json .hljs-attribute,.less .hljs-built_in,.makefile .hljs-variable,.markdown .hljs-blockquote,.markdown .hljs-emphasis,.markdown .hljs-link_label,.markdown .hljs-strong,.markdown .hljs-value,.nginx .hljs-number,.nginx .hljs-regexp,.objectivec .hljs-preprocessor .hljs-title,.perl .hljs-regexp,.php .hljs-regexp,.scss .hljs-built_in,.xml .hljs-value{color:#df5000} -.css .hljs-at_rule,.css .hljs-important,.go .hljs-typename,.haskell .hljs-type,.hljs-keyword,.http .hljs-request,.ini .hljs-setting,.java .hljs-javadoctag,.javascript .hljs-javadoctag,.javascript .hljs-tag,.less .hljs-at_rule,.less .hljs-tag,.nginx .hljs-title,.objectivec .hljs-preprocessor,.php .hljs-phpdoc,.scss .hljs-at_rule,.scss .hljs-important,.scss .hljs-tag,.sql .hljs-built_in,.stylus .hljs-at_rule,.swift .hljs-preprocessor{color:#a71d5d} -.apache .hljs-cbracket,.apache .hljs-common,.apache .hljs-keyword,.bash .hljs-built_in,.bash .hljs-literal,.c .hljs-built_in,.c .hljs-number,.coffeescript .hljs-built_in,.coffeescript .hljs-literal,.coffeescript .hljs-number,.cpp .hljs-built_in,.cpp .hljs-number,.cs .hljs-built_in,.cs .hljs-number,.css .hljs-attribute,.css .hljs-function,.css .hljs-hexcolor,.css .hljs-number,.go .hljs-built_in,.go .hljs-constant,.haskell .hljs-number,.http .hljs-attribute,.http .hljs-literal,.java .hljs-number,.javascript .hljs-built_in,.javascript .hljs-literal,.javascript .hljs-number,.json .hljs-number,.less .hljs-attribute,.less .hljs-function,.less .hljs-hexcolor,.less .hljs-number,.makefile .hljs-keyword,.markdown .hljs-link_reference,.nginx .hljs-built_in,.objectivec .hljs-built_in,.objectivec .hljs-literal,.objectivec .hljs-number,.php .hljs-literal,.php .hljs-number,.puppet .hljs-function,.python .hljs-number,.ruby .hljs-constant,.ruby .hljs-number,.ruby .hljs-prompt,.ruby .hljs-subst .hljs-keyword,.ruby .hljs-symbol,.rust .hljs-number,.scss .hljs-attribute,.scss .hljs-function,.scss .hljs-hexcolor,.scss .hljs-number,.scss .hljs-preprocessor,.sql .hljs-number,.stylus .hljs-attribute,.stylus .hljs-hexcolor,.stylus .hljs-number,.stylus .hljs-params,.swift .hljs-built_in,.swift .hljs-number{color:#0086b3} -.apache .hljs-tag,.cs .hljs-xmlDocTag,.css .hljs-tag,.stylus .hljs-tag,.xml .hljs-title{color:#63a35c} -.bash .hljs-variable,.cs .hljs-preprocessor,.cs .hljs-preprocessor .hljs-keyword,.css .hljs-attr_selector,.css .hljs-value,.ini .hljs-keyword,.ini .hljs-value,.javascript .hljs-tag .hljs-title,.makefile .hljs-constant,.nginx .hljs-variable,.scss .hljs-variable,.xml .hljs-tag{color:#333} -.bash .hljs-title,.c .hljs-title,.coffeescript .hljs-title,.cpp .hljs-title,.cs .hljs-title,.css .hljs-class,.css .hljs-id,.css .hljs-pseudo,.diff .hljs-chunk,.haskell .hljs-pragma,.haskell .hljs-title,.ini .hljs-title,.java .hljs-title,.javascript .hljs-title,.less .hljs-class,.less .hljs-id,.less .hljs-pseudo,.makefile .hljs-title,.objectivec .hljs-title,.perl .hljs-sub,.php .hljs-title,.puppet .hljs-title,.python .hljs-decorator,.python .hljs-title,.ruby .hljs-parent,.ruby .hljs-title,.rust .hljs-title,.scss .hljs-class,.scss .hljs-id,.scss .hljs-pseudo,.stylus .hljs-class,.stylus .hljs-id,.stylus .hljs-pseudo,.stylus .hljs-title,.swift .hljs-title,.xml .hljs-attribute{color:#795da3} -.coffeescript .hljs-attribute,.coffeescript .hljs-reserved{color:#1d3e81} -.diff .hljs-chunk{font-weight:700} -.diff .hljs-addition{color:#55a532;background-color:#eaffea} -.diff .hljs-deletion{color:#bd2c00;background-color:#ffecec} -.markdown .hljs-link_url{text-decoration:underline} \ No newline at end of file diff --git a/docs/styles/docfx.vendor.js b/docs/styles/docfx.vendor.js deleted file mode 100644 index 95c2d628..00000000 --- a/docs/styles/docfx.vendor.js +++ /dev/null @@ -1,45 +0,0 @@ -/*! jQuery v2.1.4 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */ -!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b="length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){ -return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,ba=/<([\w:]+)/,ca=/<|&#?\w+;/,da=/<(?:script|style|link)/i,ea=/checked\s*(?:[^=]|=\s*.checked.)/i,fa=/^$|\/(?:java|ecma)script/i,ga=/^true\/(.*)/,ha=/^\s*\s*$/g,ia={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ia.optgroup=ia.option,ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead,ia.th=ia.td;function ja(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function ka(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function la(a){var b=ga.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function ma(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function na(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function oa(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pa(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=oa(h),f=oa(a),d=0,e=f.length;e>d;d++)pa(f[d],g[d]);if(b)if(c)for(f=f||oa(a),g=g||oa(h),d=0,e=f.length;e>d;d++)na(f[d],g[d]);else na(a,h);return g=oa(h,"script"),g.length>0&&ma(g,!i&&oa(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(ca.test(e)){f=f||k.appendChild(b.createElement("div")),g=(ba.exec(e)||["",""])[1].toLowerCase(),h=ia[g]||ia._default,f.innerHTML=h[1]+e.replace(aa,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=oa(k.appendChild(e),"script"),i&&ma(f),c)){j=0;while(e=f[j++])fa.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(oa(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&ma(oa(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(oa(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!da.test(a)&&!ia[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(aa,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(oa(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(oa(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&ea.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(oa(c,"script"),ka),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,oa(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,la),j=0;g>j;j++)h=f[j],fa.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(ha,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qa,ra={};function sa(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function ta(a){var b=l,c=ra[a];return c||(c=sa(a,b),"none"!==c&&c||(qa=(qa||n("