Add Pro fiducial monograph lookup
This commit is contained in:
1 parent
342935d99d
commit
7b642afe2d
6 files changed
+417
-3
No files matched your search
@@ -0,0 +1,148 @@
|
||||
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);
|
||||
Reference in new issue
Block a user