diff --git a/Portals/RealEstatesWatcher.AdsPortals.Base/RealEstateAdsPortalBase.cs b/Portals/RealEstatesWatcher.AdsPortals.Base/RealEstateAdsPortalBase.cs index 8e34523..6da235e 100644 --- a/Portals/RealEstatesWatcher.AdsPortals.Base/RealEstateAdsPortalBase.cs +++ b/Portals/RealEstatesWatcher.AdsPortals.Base/RealEstateAdsPortalBase.cs @@ -140,4 +140,72 @@ private async Task> GetAdsDirectlyAsync() throw new RealEstateAdsPortalException($"({Name}): Error getting latest ads: {ex.Message}", ex); } } + + /// + /// Parses a price value from a node's inner text by removing all non-numeric characters. + /// + /// The HTML node containing the price information + /// XPath to select the specific node containing the price + /// The parsed decimal price, or decimal.Zero if parsing fails + protected static decimal ParsePriceFromNode(HtmlNode node, string xpath) + { + var value = node.SelectSingleNode(xpath)?.InnerText; + if (value is null) + return decimal.Zero; + + value = RegexMatchers.AllNonNumberValues().Replace(value, string.Empty); + + return decimal.TryParse(value, out var price) + ? price + : decimal.Zero; + } + + /// + /// Parses layout information from text using regex pattern matching. + /// + /// The text to parse for layout information + /// The parsed Layout enum value, or Layout.NotSpecified if not found + protected static Layout ParseLayoutFromText(string text) + { + var result = RegexMatchers.Layout().Match(text); + if (!result.Success) + return Layout.NotSpecified; + + var layoutValue = result.Groups.Skip(1).First(group => group.Success).Value; + layoutValue = RegexMatchers.AllWhitespaceCharacters().Replace(layoutValue, string.Empty); + + return LayoutExtensions.ToLayout(layoutValue); + } + + /// + /// Parses floor area from text using regex pattern matching. + /// + /// The text to parse for floor area information + /// The parsed decimal floor area, or decimal.Zero if not found + protected static decimal ParseFloorAreaFromText(string text) + { + var result = RegexMatchers.FloorArea().Match(text); + if (!result.Success) + return decimal.Zero; + + var floorAreaValue = result.Groups.Skip(1).First(group => group.Success).Value; + + return decimal.TryParse(floorAreaValue, out var floorArea) + ? floorArea + : decimal.Zero; + } + + /// + /// Returns the price comment when the price is zero, otherwise returns null. + /// + /// The price value to check + /// The HTML node containing the price comment + /// XPath to select the specific node containing the price comment + /// The price comment text if price is zero, otherwise null + protected static string? GetPriceCommentWhenZero(decimal price, HtmlNode node, string xpath) + { + return price is decimal.Zero + ? node.SelectSingleNode(xpath)?.InnerText?.Trim() + : null; + } } \ No newline at end of file diff --git a/Portals/RealEstatesWatcher.AdsPortals.BazosCz/BazosCzAdsPortal.cs b/Portals/RealEstatesWatcher.AdsPortals.BazosCz/BazosCzAdsPortal.cs index f7366a0..3ff706e 100644 --- a/Portals/RealEstatesWatcher.AdsPortals.BazosCz/BazosCzAdsPortal.cs +++ b/Portals/RealEstatesWatcher.AdsPortals.BazosCz/BazosCzAdsPortal.cs @@ -77,60 +77,63 @@ private static Uri ParseWebUrl(HtmlNode node, string rootHost) private static decimal ParsePrice(HtmlNode node) { - var value = node.SelectSingleNode(@"./div[@class=""inzeratycena""]")?.InnerText; - if (value is null) - return decimal.Zero; - - value = RegexMatchers.AllNonNumberValues().Replace(value, string.Empty); - - return decimal.TryParse(value, out var price) - ? price - : decimal.Zero; + return ParsePriceFromNode(node, @"./div[@class=""inzeratycena""]"); } private static decimal ParseFloorArea(HtmlNode node) { var value = ParseTitle(node); - var result = RegexMatchers.FloorArea().Match(value); - if (!result.Success) + var floorArea = ParseFloorAreaFromText(value); + if (floorArea != decimal.Zero) { - value = ParseText(node); - result = RegexMatchers.FloorArea().Match(value); - if (!result.Success) - return decimal.Zero; + // Handle special number format with dots and commas + var result = RegexMatchers.FloorArea().Match(value); + if (result.Success) + { + var floorAreaValue = result.Groups.Skip(1).First(group => group.Success).Value; + floorAreaValue = floorAreaValue.Replace(".", ",") + .Replace(" ", string.Empty) + .Trim(','); + + return decimal.TryParse(floorAreaValue, NumberStyles.AllowDecimalPoint, new NumberFormatInfo{ NumberDecimalSeparator = ","}, out var parsedFloorArea) + ? parsedFloorArea + : decimal.Zero; + } + return floorArea; } - var floorAreaValue = result.Groups.Skip(1).First(group => group.Success).Value; - floorAreaValue = floorAreaValue.Replace(".", ",") - .Replace(" ", string.Empty) - .Trim(','); + value = ParseText(node); + var result2 = RegexMatchers.FloorArea().Match(value); + if (result2.Success) + { + var floorAreaValue = result2.Groups.Skip(1).First(group => group.Success).Value; + floorAreaValue = floorAreaValue.Replace(".", ",") + .Replace(" ", string.Empty) + .Trim(','); + + return decimal.TryParse(floorAreaValue, NumberStyles.AllowDecimalPoint, new NumberFormatInfo{ NumberDecimalSeparator = ","}, out var parsedFloorArea) + ? parsedFloorArea + : decimal.Zero; + } - return decimal.TryParse(floorAreaValue, NumberStyles.AllowDecimalPoint, new NumberFormatInfo{ NumberDecimalSeparator = ","}, out var floorArea) - ? floorArea - : decimal.Zero; + return decimal.Zero; } - private static string? ParsePriceComment(HtmlNode node) => ParsePrice(node) is decimal.Zero - ? node.SelectSingleNode("./div[@class=\"inzeratycena\"]")?.InnerText?.Trim() - : null; + private static string? ParsePriceComment(HtmlNode node) + { + return GetPriceCommentWhenZero(ParsePrice(node), node, "./div[@class=\"inzeratycena\"]"); + } private static Layout ParseLayout(HtmlNode node) { var value = ParseTitle(node); - var result = RegexMatchers.Layout().Match(value); - if (!result.Success) - { - value = ParseText(node); - result = RegexMatchers.Layout().Match(value); - if (!result.Success) - return Layout.NotSpecified; - } - - var layoutValue = result.Groups.Skip(1).First(group => group.Success).Value; - layoutValue = RegexMatchers.AllWhitespaceCharacters().Replace(layoutValue, string.Empty); + var layout = ParseLayoutFromText(value); + if (layout != Layout.NotSpecified) + return layout; - return LayoutExtensions.ToLayout(layoutValue); + value = ParseText(node); + return ParseLayoutFromText(value); } } \ No newline at end of file diff --git a/Portals/RealEstatesWatcher.AdsPortals.BezrealitkyCz/BezrealitkyCzAdsPortal.cs b/Portals/RealEstatesWatcher.AdsPortals.BezrealitkyCz/BezrealitkyCzAdsPortal.cs index 0749ac6..43a2089 100644 --- a/Portals/RealEstatesWatcher.AdsPortals.BezrealitkyCz/BezrealitkyCzAdsPortal.cs +++ b/Portals/RealEstatesWatcher.AdsPortals.BezrealitkyCz/BezrealitkyCzAdsPortal.cs @@ -50,15 +50,7 @@ private static string ParseTitle(HtmlNode node) private static decimal ParsePrice(HtmlNode node) { - var value = node.SelectSingleNode(".//span[contains(@class,'propertyPriceAmount')]")?.InnerText; - if (value is null) - return decimal.Zero; - - value = RegexMatchers.AllNonNumberValues().Replace(value, string.Empty); - - return decimal.TryParse(value, out var price) - ? price - : decimal.Zero; + return ParsePriceFromNode(node, ".//span[contains(@class,'propertyPriceAmount')]"); } private static decimal ParseAdditionalFees(HtmlNode node) @@ -84,15 +76,7 @@ private static Layout ParseLayout(HtmlNode node) return Layout.NotSpecified; var value = HttpUtility.HtmlDecode(values[0].InnerText); - - var result = RegexMatchers.Layout().Match(value); - if (!result.Success) - return Layout.NotSpecified; - - var layoutValue = result.Groups.Skip(1).First(group => group.Success).Value; - layoutValue = RegexMatchers.AllWhitespaceCharacters().Replace(layoutValue, string.Empty); - - return LayoutExtensions.ToLayout(layoutValue); + return ParseLayoutFromText(value); } private static decimal ParseFloorArea(HtmlNode node) @@ -102,16 +86,7 @@ private static decimal ParseFloorArea(HtmlNode node) return decimal.Zero; var value = HttpUtility.HtmlDecode(values[^1].InnerText); - - var result = RegexMatchers.FloorArea().Match(value); - if (!result.Success) - return decimal.Zero; - - var floorAreaValue = result.Groups.Skip(1).First(group => group.Success).Value; - - return decimal.TryParse(floorAreaValue, out var floorArea) - ? floorArea - : decimal.Zero; + return ParseFloorAreaFromText(value); } private static Uri? ParseImageUrl(HtmlNode node) diff --git a/Portals/RealEstatesWatcher.AdsPortals.BidliCz/BidliCzAdsPortal.cs b/Portals/RealEstatesWatcher.AdsPortals.BidliCz/BidliCzAdsPortal.cs index 3160035..7b57f1a 100644 --- a/Portals/RealEstatesWatcher.AdsPortals.BidliCz/BidliCzAdsPortal.cs +++ b/Portals/RealEstatesWatcher.AdsPortals.BidliCz/BidliCzAdsPortal.cs @@ -75,15 +75,7 @@ private static decimal ParsePrice(HtmlNode node) private static Layout ParseLayout(HtmlNode node) { var value = ParseTitle(node); - - var result = RegexMatchers.Layout().Match(value); - if (!result.Success) - return Layout.NotSpecified; - - var layoutValue = result.Groups.Skip(1).First(group => group.Success).Value; - layoutValue = RegexMatchers.AllWhitespaceCharacters().Replace(layoutValue, string.Empty); - - return LayoutExtensions.ToLayout(layoutValue); + return ParseLayoutFromText(value); } private static string ParseAddress(HtmlNode node) => node.SelectSingleNode(".//span[@class=\"adresa\"]").InnerText; @@ -98,16 +90,7 @@ private static Uri ParseWebUrl(HtmlNode node, string rootHost) private static decimal ParseFloorArea(HtmlNode node) { var value = ParseTitle(node); - - var result = RegexMatchers.FloorArea().Match(value); - if (!result.Success) - return decimal.Zero; - - var floorAreaValue = result.Groups.Skip(1).First(group => group.Success).Value; - - return decimal.TryParse(floorAreaValue, out var floorArea) - ? floorArea - : decimal.Zero; + return ParseFloorAreaFromText(value); } private static Uri? ParseImageUrl(HtmlNode node, string rootHost) diff --git a/Portals/RealEstatesWatcher.AdsPortals.BravisCz/BravisCzAdsPortal.cs b/Portals/RealEstatesWatcher.AdsPortals.BravisCz/BravisCzAdsPortal.cs index 8caf02b..c04f8ff 100644 --- a/Portals/RealEstatesWatcher.AdsPortals.BravisCz/BravisCzAdsPortal.cs +++ b/Portals/RealEstatesWatcher.AdsPortals.BravisCz/BravisCzAdsPortal.cs @@ -35,15 +35,7 @@ public class BravisCzAdsPortal(string watchedUrl, private static decimal ParsePrice(HtmlNode node) { - var value = node.SelectSingleNode(".//strong[@class='price']")?.FirstChild?.InnerText; - if (value == null) - return decimal.Zero; - - value = RegexMatchers.AllNonNumberValues().Replace(value, string.Empty); - - return decimal.TryParse(value, out var price) - ? price - : decimal.Zero; + return ParsePriceFromNode(node, ".//strong[@class='price']"); } private static decimal ParseAdditionalFees(HtmlNode node) @@ -74,15 +66,7 @@ private static decimal ParseAdditionalFees(HtmlNode node) private static Layout ParseLayout(HtmlNode node) { var value = node.SelectSingleNode(".//ul[@class='params']/li[contains(text(),\"Typ\")]").InnerText; - - var result = RegexMatchers.Layout().Match(value); - if (!result.Success) - return Layout.NotSpecified; - - var layoutValue = result.Groups.Skip(1).First(group => group.Success).Value; - layoutValue = RegexMatchers.AllWhitespaceCharacters().Replace(layoutValue, string.Empty); - - return LayoutExtensions.ToLayout(layoutValue); + return ParseLayoutFromText(value); } private static string ParseAddress(HtmlNode node) => node.SelectSingleNode(".//em[@class=\"location\"]").InnerText; diff --git a/Portals/RealEstatesWatcher.AdsPortals.CeskeRealityCz/CeskeRealityCzAdsPortal.cs b/Portals/RealEstatesWatcher.AdsPortals.CeskeRealityCz/CeskeRealityCzAdsPortal.cs index fa6d693..ba03ee9 100644 --- a/Portals/RealEstatesWatcher.AdsPortals.CeskeRealityCz/CeskeRealityCzAdsPortal.cs +++ b/Portals/RealEstatesWatcher.AdsPortals.CeskeRealityCz/CeskeRealityCzAdsPortal.cs @@ -38,38 +38,24 @@ public partial class CeskeRealityCzAdsPortal(string watchedUrl, private static decimal ParsePrice(HtmlNode node) { - var value = node.SelectSingleNode(".//h3[@class='i-estate__footer-price-value']")?.InnerText; - if (value is null) - return decimal.Zero; - - value = RegexMatchers.AllNonNumberValues().Replace(value, string.Empty); - - return decimal.TryParse(value, out var price) - ? price - : decimal.Zero; + return ParsePriceFromNode(node, ".//h3[@class='i-estate__footer-price-value']"); } - private static string? ParsePriceComment(HtmlNode node) => ParsePrice(node) is decimal.Zero - ? node.SelectSingleNode(".//h3[@class='i-estate__footer-price-value']")?.InnerText?.Trim() - : null; + private static string? ParsePriceComment(HtmlNode node) + { + return GetPriceCommentWhenZero(ParsePrice(node), node, ".//h3[@class='i-estate__footer-price-value']"); + } private static Layout ParseLayout(HtmlNode node) { var value = ParseTitle(node); - var result = RegexMatchers.Layout().Match(value); - if (!result.Success) - { - value = ParseText(node); - result = RegexMatchers.Layout().Match(value); - if (!result.Success) - return Layout.NotSpecified; - } + var layout = ParseLayoutFromText(value); + if (layout != Layout.NotSpecified) + return layout; - var layoutValue = result.Groups.Skip(1).First(group => group.Success).Value; - layoutValue = RegexMatchers.AllWhitespaceCharacters().Replace(layoutValue, string.Empty); - - return LayoutExtensions.ToLayout(layoutValue); + value = ParseText(node); + return ParseLayoutFromText(value); } private static string ParseAddress(HtmlNode node) @@ -95,16 +81,7 @@ private static Uri ParseWebUrl(HtmlNode node, string rootUri) private static decimal ParseFloorArea(HtmlNode node) { var value = ParseTitle(node); - - var result = RegexMatchers.FloorArea().Match(value); - if (!result.Success) - return decimal.Zero; - - var floorAreaValue = result.Groups.Skip(1).First(group => group.Success).Value; - - return decimal.TryParse(floorAreaValue, out var floorArea) - ? floorArea - : decimal.Zero; + return ParseFloorAreaFromText(value); } private static Uri? ParseImageUrl(HtmlNode node) diff --git a/Portals/RealEstatesWatcher.AdsPortals.MMRealityCz/MMRealityCzAdsPortal.cs b/Portals/RealEstatesWatcher.AdsPortals.MMRealityCz/MMRealityCzAdsPortal.cs index 2b77039..17be76c 100644 --- a/Portals/RealEstatesWatcher.AdsPortals.MMRealityCz/MMRealityCzAdsPortal.cs +++ b/Portals/RealEstatesWatcher.AdsPortals.MMRealityCz/MMRealityCzAdsPortal.cs @@ -38,15 +38,7 @@ public partial class MmRealityCzAdsPortal(string watchedUrl, private static decimal ParsePrice(HtmlNode node) { - var value = node.SelectSingleNode(".//div[@class='rds-content']//div[contains(@class, 'price')]")?.InnerText; - if (value is null) - return decimal.Zero; - - value = RegexMatchers.AllNonNumberValues().Replace(value, string.Empty); - - return decimal.TryParse(value, out var price) - ? price - : decimal.Zero; + return ParsePriceFromNode(node, ".//div[@class='rds-content']//div[contains(@class, 'price')]"); } private static string? ParsePriceComment(HtmlNode node) => node.SelectSingleNode(".//div[@class='rds-content']//div[contains(@class, 'price')]")?.InnerText; @@ -54,15 +46,7 @@ private static decimal ParsePrice(HtmlNode node) private static Layout ParseLayout(HtmlNode node) { var title = ParseTitle(node); - - var result = RegexMatchers.Layout().Match(title); - if (!result.Success) - return Layout.NotSpecified; - - var layoutValue = result.Groups.Skip(1).First(group => group.Success).Value; - layoutValue = RegexMatchers.AllWhitespaceCharacters().Replace(layoutValue, string.Empty); - - return LayoutExtensions.ToLayout(layoutValue); + return ParseLayoutFromText(title); } private static string ParseAddress(HtmlNode node) diff --git a/Portals/RealEstatesWatcher.AdsPortals.RealcityCz/RealcityCzAdsPortal.cs b/Portals/RealEstatesWatcher.AdsPortals.RealcityCz/RealcityCzAdsPortal.cs index 3802f2d..2ac7cf8 100644 --- a/Portals/RealEstatesWatcher.AdsPortals.RealcityCz/RealcityCzAdsPortal.cs +++ b/Portals/RealEstatesWatcher.AdsPortals.RealcityCz/RealcityCzAdsPortal.cs @@ -36,33 +36,18 @@ public class RealcityCzAdsPortal(string watchedUrl, private static decimal ParsePrice(HtmlNode node) { - var value = node.SelectSingleNode(".//div[@class=\"price\"]/span")?.InnerText; - if (value is null) - return decimal.Zero; - - value = RegexMatchers.AllNonNumberValues().Replace(value, string.Empty); - - return decimal.TryParse(value, out var price) - ? price - : decimal.Zero; + return ParsePriceFromNode(node, ".//div[@class=\"price\"]/span"); } - private static string? ParsePriceComment(HtmlNode node) => ParsePrice(node) is decimal.Zero - ? node.SelectSingleNode(".//div[@class=\"price\"]/span")?.InnerText?.Trim() - : null; + private static string? ParsePriceComment(HtmlNode node) + { + return GetPriceCommentWhenZero(ParsePrice(node), node, ".//div[@class=\"price\"]/span"); + } private static Layout ParseLayout(HtmlNode node) { var value = ParseTitle(node); - - var result = RegexMatchers.Layout().Match(value); - if (!result.Success) - return Layout.NotSpecified; - - var layoutValue = result.Groups.Skip(1).First(group => group.Success).Value; - layoutValue = RegexMatchers.AllWhitespaceCharacters().Replace(layoutValue, string.Empty); - - return LayoutExtensions.ToLayout(layoutValue); + return ParseLayoutFromText(value); } private static string ParseAddress(HtmlNode node) => HttpUtility.HtmlDecode(node.SelectSingleNode(".//div[@class=\"address\"]").InnerText).Trim(); diff --git a/Portals/RealEstatesWatcher.AdsPortals.RealityIdnesCz/RealityIdnesCzAdsPortal.cs b/Portals/RealEstatesWatcher.AdsPortals.RealityIdnesCz/RealityIdnesCzAdsPortal.cs index 4d77595..f8a7d57 100644 --- a/Portals/RealEstatesWatcher.AdsPortals.RealityIdnesCz/RealityIdnesCzAdsPortal.cs +++ b/Portals/RealEstatesWatcher.AdsPortals.RealityIdnesCz/RealityIdnesCzAdsPortal.cs @@ -43,15 +43,7 @@ private static string ParseTitle(HtmlNode node) private static decimal ParsePrice(HtmlNode node) { - var value = node.SelectSingleNode(".//p[@class=\"c-products__price\"]/strong")?.InnerText; - if (value is null) - return decimal.Zero; - - value = RegexMatchers.AllNonNumberValues().Replace(value, ""); - - return decimal.TryParse(value.Trim(), out var price) - ? price - : decimal.Zero; + return ParsePriceFromNode(node, ".//p[@class=\"c-products__price\"]/strong"); } private static string? ParsePriceComment(HtmlNode node) @@ -66,15 +58,7 @@ private static decimal ParsePrice(HtmlNode node) private static Layout ParseLayout(HtmlNode node) { var value = ParseTitle(node); - - var result = RegexMatchers.Layout().Match(value); - if (!result.Success) - return Layout.NotSpecified; - - var layoutValue = result.Groups.Skip(1).First(group => group.Success).Value; - layoutValue = RegexMatchers.AllWhitespaceCharacters().Replace(layoutValue, ""); - - return LayoutExtensions.ToLayout(layoutValue); + return ParseLayoutFromText(value); } private static string ParseAddress(HtmlNode node) => node.SelectSingleNode(".//p[@class=\"c-products__info\"]").InnerText.Trim(); diff --git a/Portals/RealEstatesWatcher.AdsPortals.RemaxCz/RemaxCzAdsProtal.cs b/Portals/RealEstatesWatcher.AdsPortals.RemaxCz/RemaxCzAdsProtal.cs index aaf5656..e5397b6 100644 --- a/Portals/RealEstatesWatcher.AdsPortals.RemaxCz/RemaxCzAdsProtal.cs +++ b/Portals/RealEstatesWatcher.AdsPortals.RemaxCz/RemaxCzAdsProtal.cs @@ -36,20 +36,13 @@ public RemaxCzAdsProtal(string watchedUrl, private static decimal ParsePrice(HtmlNode node) { - var value = node.SelectSingleNode(".//div[contains(@class,\"item-price\")]/strong")?.FirstChild?.InnerText; - if (value is null) - return decimal.Zero; - - value = RegexMatchers.AllNonNumberValues().Replace(value, string.Empty); - - return decimal.TryParse(value, out var price) - ? price - : decimal.Zero; + return ParsePriceFromNode(node, ".//div[contains(@class,\"item-price\")]/strong"); } - private static string? ParsePriceComment(HtmlNode node) => ParsePrice(node) is decimal.Zero - ? node.SelectSingleNode(".//div[contains(@class,'item-price')]/strong")?.InnerText?.Trim() - : null; + private static string? ParsePriceComment(HtmlNode node) + { + return GetPriceCommentWhenZero(ParsePrice(node), node, ".//div[contains(@class,'item-price')]/strong"); + } private static string ParseTitle(HtmlNode node) => node.SelectSingleNode(".//h2/strong").InnerText; @@ -86,29 +79,12 @@ private static Uri ParseWebUrl(HtmlNode node, string rootHost) private static decimal ParseFloorArea(HtmlNode node) { var value = ParseTitle(node); - - var result = RegexMatchers.FloorArea().Match(value); - if (!result.Success) - return decimal.Zero; - - var floorAreaValue = result.Groups.Skip(1).First(group => group.Success).Value; - - return decimal.TryParse(floorAreaValue, out var floorArea) - ? floorArea - : decimal.Zero; + return ParseFloorAreaFromText(value); } private static Layout ParseLayout(HtmlNode node) { var value = ParseTitle(node); - - var result = RegexMatchers.Layout().Match(value); - if (!result.Success) - return Layout.NotSpecified; - - var layoutValue = result.Groups.Skip(1).First(group => group.Success).Value; - layoutValue = RegexMatchers.AllWhitespaceCharacters().Replace(layoutValue, string.Empty); - - return LayoutExtensions.ToLayout(layoutValue); + return ParseLayoutFromText(value); } } \ No newline at end of file diff --git a/Portals/RealEstatesWatcher.AdsPortals.SrealityCz/SrealityCzAdsPortal.cs b/Portals/RealEstatesWatcher.AdsPortals.SrealityCz/SrealityCzAdsPortal.cs index cd67eec..15ad908 100644 --- a/Portals/RealEstatesWatcher.AdsPortals.SrealityCz/SrealityCzAdsPortal.cs +++ b/Portals/RealEstatesWatcher.AdsPortals.SrealityCz/SrealityCzAdsPortal.cs @@ -95,11 +95,7 @@ private static decimal ParsePrice(HtmlNode node) private static Layout ParseLayout(HtmlNode node) { - var result = RegexMatchers.Layout().Match(ParseTitle(node)); - - return result.Success - ? LayoutExtensions.ToLayout(result.Groups[1].Value) - : Layout.NotSpecified; + return ParseLayoutFromText(ParseTitle(node)); } private static decimal ParseFloorArea(HtmlNode node)