Dado este código:
// Decode the text string string test = "Version 21.1.0 - 2021 Edition (22nd March 2021)"; string[] textitems = test.Split(' '); // The text should split down like this: // [0] Version // [1] 21.1.0 // [2] - // [3] 2021 // [4] Edition // [5] (22nd // [6] March // [7] 2021)
He creado una enum
para usar:
enum UpdateInfo { Version = 1, Edition = 3, Day = 5, Month = 6, Year = 7 }
La información que me interesa es:
Version
y la Edition
son sencillas:
writer.WriteAttributeString("Version", textitems[(int)UpdateInfo.Version]); writer.WriteAttributeString("Edition", textitems[(int)UpdateInfo.Edition]);
Pero la Date
no lo es. Descubrí que no puedo analizar (por ejemplo):
(22nd March 2021)
Quiero la fecha corta, así que se me ocurrió el siguiente código después de investigar:
// Rebuild date as short date // Day - strip off "(" and "st", "nd", "rd" or "th" string day = string.Empty; for (int i = 0; i < textitems[(int)UpdateInfo.Day].Length; i++) { if (Char.IsDigit(textitems[(int)UpdateInfo.Day][i])) day += textitems[(int)UpdateInfo.Day][i]; } // Rebuilt long date string datetest = day + " " + textitems[(int)UpdateInfo.Month] + " " + textitems[(int)UpdateInfo.Year]; // Remove trailing ")" datetest = datetest.Trim(')'); // Now we can parse the long date string DateTime date = DateTime.ParseExact(datetest, "d MMMM yyyy", CultureInfo.InstalledUICulture, DateTimeStyles.None); if (date != null) writer.WriteAttributeString("Date", date.ToShortDateString());
¿Hay una forma más sencilla de lograr el mismo resultado sin inflar el código?
Nota:
<p class="rvps2"> <img alt="New Version Icon" style="vertical-align: middle; padding : 1px; margin : 0px 5px;" src="lib/IMG_NewVersion.png"> <span class="rvts16">Version 21.1.0 - 2021 Edition</span> <span class="rvts15"> (22nd March 2021)</span> </p>
Entonces, en realidad tengo un HtmlNode
(el elemento p
).
Yo no dividiría por espacios, hay demasiados. Dividiría por "-"
y luego usaría expresiones regulares para extraer la parte de la fecha. Entonces es fácil con TryParseExact
y dd'nd' MMMM yyyy
:
string[] textitems = test.Split('-'); string version = textitems[0].Trim(); string edition = textitems[1].Substring(0, textitems[1].IndexOf("(")).Trim(); string dateStr = Regex.Match(textitems[1], @"\(([^)]*)\)").Groups[1].Value; string[] formats = { "d'st' MMMM yyyy", "d'nd' MMMM yyyy" }; bool validDate = DateTime.TryParseExact(dateStr, formats, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTime date );
He agregado también d'st' MMMM yyyy
ya que puedo imaginar que este sería su próximo número. Otra opción era incluir los corchetes en el formato: "'('d'nd' MMMM yyyy')'"
.
Es posible que desee agregar algún código para validar la entrada primero, lo he omitido.
Para esto, ni siquiera me molestaría en dividir el texto, puede hacerlo con una expresión regular y coincidencias con nombre.
string test = "Version 21.1.0 - 2021 Edition (22nd August 2021)"; var regex = new Regex(@"Version (?'version'[\d.]+) - (?'edition'\d+) Edition \((?'date'[^)]+)", RegexOptions.None); var matches = regex.Matches(test); var version = matches[0].Groups["version"].Value; var edition = matches[0].Groups["edition"].Value; var dateString = matches[0].Groups["date"].Value; // remove date ordinal before parsing dateString = Regex.Replace(dateString, @"^(\d+)(st|nd|rd|th)", "$1"); var date = DateTime.ParseExact(dateString, "dd MMMM yyyy", CultureInfo.CurrentCulture); date.ToShortDateString().Dump();
Normalmente usaría TryParseExact
y manejaría cualquier excepción de análisis correctamente.
Puede obtener una explicación de la expresión regular principal aquí: https://regex101.com/r/Nzpa5h/1
He encontrado una solución que combina ambos enfoques. Dado que los datos originales son en realidad un HtmlNode
(como se indica en la parte inferior de la pregunta) y ya están divididos en dos elementos de span
, decidí hacerlo de esta manera:
// The paragraph element should only have two "span" elements var listSpan = itemParagraph.Descendants("span"); if(listSpan != null) { if(listSpan.Count() == 2) { // The first "span" element should contain: Version 21.1.0 - 2021 Edition var regex = new Regex(@"Version (?'version'[\d.]+) - (?'edition'\d+) Edition", RegexOptions.None); var matches = regex.Matches(listSpan.ElementAt(0).InnerText.Trim()); writer.WriteStartElement("Update"); writer.WriteAttributeString("Version", matches[0].Groups["version"].Value); writer.WriteAttributeString("Edition", matches[0].Groups["edition"].Value); // The second "span" element should contain: eg. (22nd March 2021) string dateString = listSpan.ElementAt(1).InnerText.Trim(' ', '(', ')'); string[] formats = { "d'st' MMMM yyyy", "d'nd' MMMM yyyy", "d'rd' MMMM yyyy", "d'th' MMMM yyyy" }; if (DateTime.TryParseExact(dateString, formats, CultureInfo.CurrentUICulture, DateTimeStyles.None, out DateTime dateRevision)) { writer.WriteAttributeString("Date", dateRevision.ToShortDateString()); } } }
Admito que no entiendo muy bien cómo funciona realmente este fragmento de código:
var regex = new Regex(@"Version (?'version'[\d.]+) - (?'edition'\d+) Edition", RegexOptions.None); var matches = regex.Matches(listSpan.ElementAt(0).InnerText.Trim());
El código anterior se modifica a partir de una de las respuestas proporcionadas. Pero funciona. :)
Decidí construir la fecha utilizando el enfoque de respuesta aceptado, ya que entiendo lo que está haciendo, en lugar de la sugerencia de expresiones regulares.
@phuzi, ¿tal vez podría agregar algunas explicaciones o sugerencias para desarrollar su respuesta con respecto a la sintaxis de expresiones regulares?