149 lines
6.4 KiB
C#
149 lines
6.4 KiB
C#
using System.Net;
|
|
using System.Net.Http.Headers;
|
|
using System.Text.RegularExpressions;
|
|
|
|
namespace LMEMappeCatastaliApp.Services;
|
|
|
|
public sealed partial class FiducialPointService
|
|
{
|
|
private const string ServiceBaseUrl = "https://www1.agenziaentrate.gov.it/servizi/Monografie/";
|
|
private readonly HttpClient _httpClient = new() { Timeout = TimeSpan.FromSeconds(30) };
|
|
|
|
public FiducialPointService()
|
|
{
|
|
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("LMEMappeCatastali/1.0 (Android)");
|
|
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("text/html"));
|
|
}
|
|
|
|
public async Task<IReadOnlyList<FiducialAreaOption>> GetProvincesAsync(
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var html = await GetHtmlAsync("ricerca.php", cancellationToken);
|
|
return ParseOptions(html, "provincia");
|
|
}
|
|
|
|
public async Task<IReadOnlyList<FiducialAreaOption>> GetMunicipalitiesAsync(
|
|
string provinceCode,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var code = NormalizeCode(provinceCode);
|
|
var html = await GetHtmlAsync($"ricerca.php?level=1&prov={Uri.EscapeDataString(code)}", cancellationToken);
|
|
return ParseOptions(html, "co");
|
|
}
|
|
|
|
public async Task<IReadOnlyList<FiducialAreaOption>> GetSheetsAsync(
|
|
string provinceCode,
|
|
string municipalityCode,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var province = NormalizeCode(provinceCode);
|
|
var municipality = NormalizeCode(municipalityCode);
|
|
var path = $"ricerca.php?level=2&prov={Uri.EscapeDataString(province)}";
|
|
var html = await PostFormAsync(path, new Dictionary<string, string> { ["co"] = municipality }, cancellationToken);
|
|
return ParseOptions(html, "foglio");
|
|
}
|
|
|
|
public async Task<IReadOnlyList<FiducialPointEntry>> GetFiducialsAsync(
|
|
string provinceName,
|
|
string municipalityName,
|
|
string municipalityCode,
|
|
string sheet,
|
|
CancellationToken cancellationToken = default)
|
|
{
|
|
var code = NormalizeCode(municipalityCode);
|
|
var normalizedSheet = sheet.Trim();
|
|
var query = string.Join("&",
|
|
[
|
|
$"provincia={Uri.EscapeDataString(provinceName.Trim())}",
|
|
$"comune={Uri.EscapeDataString(municipalityName.Trim())}",
|
|
$"co={Uri.EscapeDataString(code)}"
|
|
]);
|
|
var html = await PostFormAsync(
|
|
$"risultato.php?{query}",
|
|
new Dictionary<string, string> { ["foglio"] = normalizedSheet },
|
|
cancellationToken);
|
|
|
|
return DownloadLinkRegex().Matches(html)
|
|
.Select(match =>
|
|
{
|
|
var relativeUrl = WebUtility.HtmlDecode(match.Groups["url"].Value);
|
|
var fileName = WebUtility.HtmlDecode(match.Groups["name"].Value);
|
|
return new FiducialPointEntry(
|
|
FormatIdentifier(fileName),
|
|
new Uri(new Uri(ServiceBaseUrl), relativeUrl).AbsoluteUri);
|
|
})
|
|
.DistinctBy(item => item.Identifier, StringComparer.OrdinalIgnoreCase)
|
|
.OrderBy(item => item.Identifier, StringComparer.OrdinalIgnoreCase)
|
|
.ToArray();
|
|
}
|
|
|
|
private async Task<string> GetHtmlAsync(string relativeUrl, CancellationToken cancellationToken)
|
|
{
|
|
using var response = await _httpClient.GetAsync(ServiceBaseUrl + relativeUrl, cancellationToken);
|
|
response.EnsureSuccessStatusCode();
|
|
return await response.Content.ReadAsStringAsync(cancellationToken);
|
|
}
|
|
|
|
private async Task<string> PostFormAsync(
|
|
string relativeUrl,
|
|
IReadOnlyDictionary<string, string> values,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
using var content = new FormUrlEncodedContent(values);
|
|
using var response = await _httpClient.PostAsync(ServiceBaseUrl + relativeUrl, content, cancellationToken);
|
|
response.EnsureSuccessStatusCode();
|
|
return await response.Content.ReadAsStringAsync(cancellationToken);
|
|
}
|
|
|
|
private static IReadOnlyList<FiducialAreaOption> ParseOptions(string html, string selectId)
|
|
{
|
|
var select = SelectRegex().Matches(html)
|
|
.LastOrDefault(match => string.Equals(match.Groups["id"].Value, selectId, StringComparison.OrdinalIgnoreCase));
|
|
if (select is null)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
return OptionRegex().Matches(select.Groups["body"].Value)
|
|
.Select(match => new FiducialAreaOption(
|
|
WebUtility.HtmlDecode(match.Groups["value"].Value).Trim(),
|
|
WebUtility.HtmlDecode(StripTagsRegex().Replace(match.Groups["label"].Value, string.Empty)).Trim()))
|
|
.Where(item => !string.IsNullOrWhiteSpace(item.Code) && !string.IsNullOrWhiteSpace(item.Name))
|
|
.DistinctBy(item => item.Code, StringComparer.OrdinalIgnoreCase)
|
|
.ToArray();
|
|
}
|
|
|
|
private static string NormalizeCode(string value)
|
|
{
|
|
var normalized = value.Trim().ToUpperInvariant();
|
|
if (normalized.Length is < 2 or > 4 || normalized.Any(character => !char.IsLetterOrDigit(character)))
|
|
{
|
|
throw new ArgumentException("Codice territoriale non valido.", nameof(value));
|
|
}
|
|
|
|
return normalized;
|
|
}
|
|
|
|
private static string FormatIdentifier(string fileName)
|
|
{
|
|
var parts = fileName.Split('-', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
|
return parts.Length == 3 ? $"PF{parts[2]}/{parts[1]}/{parts[0]}" : fileName;
|
|
}
|
|
|
|
[GeneratedRegex("<select\\b[^>]*(?:id|name)=[\\\"'](?<id>[^\\\"']+)[\\\"'][^>]*>(?<body>[\\s\\S]*?)</select>", RegexOptions.IgnoreCase, matchTimeoutMilliseconds: 2000)]
|
|
private static partial Regex SelectRegex();
|
|
|
|
[GeneratedRegex("<option\\b[^>]*value=[\\\"'](?<value>[^\\\"']*)[\\\"'][^>]*>(?<label>[\\s\\S]*?)</option>", RegexOptions.IgnoreCase, matchTimeoutMilliseconds: 2000)]
|
|
private static partial Regex OptionRegex();
|
|
|
|
[GeneratedRegex("href=[\\\"'](?<url>download\\.php\\?[^\\\"']*namefile=(?<name>[A-Z0-9-]+))[^\\\"']*[\\\"']", RegexOptions.IgnoreCase, matchTimeoutMilliseconds: 2000)]
|
|
private static partial Regex DownloadLinkRegex();
|
|
|
|
[GeneratedRegex("<[^>]+>", RegexOptions.None, 2000)]
|
|
private static partial Regex StripTagsRegex();
|
|
}
|
|
|
|
public sealed record FiducialAreaOption(string Code, string Name);
|
|
|
|
public sealed record FiducialPointEntry(string Identifier, string MonographUrl);
|