/// <summary> /// Convert a tablespec block into one of our internal object model representations /// </summary> public TableDefinition ParseTableSpec(Block tableSpecBlock, Stack <Config.DocumentHeader> headerStack, IssueLogger issues) { List <ValidationError> discoveredErrors = new List <ValidationError>(); List <ItemDefinition> items = new List <ItemDefinition>(); var tableShape = tableSpecBlock.Table; TableDecoder decoder = new TableDecoder { Type = TableBlockType.Unknown }; var headerText = headerStack.Peek()?.Title; // Try matching based on header if (headerText != null) { var matchingDecoder = FindDecoderFromHeaderText(headerStack); if (null != matchingDecoder) { decoder = matchingDecoder; } } // Try matching based on shape if (decoder.Type == TableBlockType.Unknown && null != tableSpecBlock.Table) { var matchingDecoder = FindDecoderFromShape(tableShape); if (null != matchingDecoder) { decoder = matchingDecoder; } } switch (decoder.Type) { case TableBlockType.ErrorCodes: items.AddRange(ParseErrorTable(tableShape, decoder)); break; case TableBlockType.PathParameters: items.AddRange(ParseParameterTable(tableShape, ParameterLocation.Path, decoder, issues.For($"{decoder.Type}Table"))); break; case TableBlockType.ResourcePropertyDescriptions: case TableBlockType.RequestObjectProperties: case TableBlockType.ResponseObjectProperties: items.AddRange(ParseParameterTable(tableShape, ParameterLocation.JsonObject, decoder, issues)); break; case TableBlockType.ResourceNavigationPropertyDescriptions: items.AddRange(ParseParameterTable(tableShape, ParameterLocation.JsonObject, decoder, issues, true)); break; case TableBlockType.HttpHeaders: items.AddRange(ParseParameterTable(tableShape, ParameterLocation.Header, decoder, issues)); break; case TableBlockType.QueryStringParameters: items.AddRange(ParseParameterTable(tableShape, ParameterLocation.QueryString, decoder, issues)); break; case TableBlockType.EnumerationValues: items.AddRange(ParseEnumerationTable(tableShape, decoder)); break; case TableBlockType.AuthScopes: items.AddRange(ParseAuthScopeTable(tableShape, decoder)); break; case TableBlockType.Unknown: var headers = tableShape.ColumnHeaders != null?tableShape.ColumnHeaders.ComponentsJoinedByString(",") : "null"; issues.Message($"Ignored unclassified table: headerText='{headerText}', tableHeaders='{headers}'"); break; default: var hdrs = tableShape.ColumnHeaders != null?tableShape.ColumnHeaders.ComponentsJoinedByString(",") : "null"; issues.Message($"Ignored table: classification='{decoder.Type}', headerText='{headerText}', tableHeaders='{hdrs}'"); break; } return(new TableDefinition(decoder.Type, items, headerText)); }
private static IEnumerable <AuthScopeDefinition> ParseAuthScopeTable(IMarkdownTable table, TableDecoder decoder) { var records = from r in table.RowValues select new AuthScopeDefinition { Scope = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["scope"]), Title = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["title"]), Description = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["description"]), Required = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["required"]).ToBoolean() }; return(records); }
private static IEnumerable <EnumerationDefinition> ParseEnumerationTable(IMarkdownTable table, TableDecoder decoder) { List <EnumerationDefinition> records = new List <EnumerationDefinition>(); foreach (var r in table.RowValues) { var usedColumns = new List <string>(); records.Add(new EnumerationDefinition { MemberName = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["memberName"], usedColumns), NumericValue = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["numericValue"], usedColumns).ToInt32(), Description = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["description"], usedColumns), TypeName = decoder.Stamp, IsFlags = decoder.IsFlags, }); } return(records); }
private static IEnumerable <ParameterDefinition> ParseParameterTable(IMarkdownTable table, ParameterLocation location, TableDecoder decoder, IssueLogger issues, bool navigationProperties = false) { var records = table.RowValues.Select(r => new ParameterDefinition { Name = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["name"]), Type = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["type"]).ParseParameterDataType(defaultValue: ParameterDataType.String), Description = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["description"]), Required = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["description"]).IsRequired(), Optional = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["description"]).IsOptional(), Location = location, IsNavigatable = navigationProperties, }).ToList(); var badRows = records.Count(r => string.IsNullOrEmpty(r.Name)); if (badRows > 0) { var tableHeaders = $"|{ string.Join("|", table.ColumnHeaders)}|"; if (badRows == records.Count) { issues.Warning(ValidationErrorCode.MarkdownParserError, $"Failed to parse any rows out of table with headers: {tableHeaders}"); return(Enumerable.Empty <ParameterDefinition>()); } issues.Warning(ValidationErrorCode.ParameterParserError, $"Failed to parse {badRows} row(s) in table with headers: {tableHeaders}"); records = records.Where(r => !string.IsNullOrEmpty(r.Name)).ToList(); } return(records); }
/// <summary> /// Convert a markdown table into ErrorDefinition objects /// </summary> /// <param name="table"></param> /// <returns></returns> private static IEnumerable <ErrorDefinition> ParseErrorTable(IMarkdownTable table, TableDecoder decoder) { var records = from r in table.RowValues select new ErrorDefinition { HttpStatusCode = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["httpStatusCode"]), HttpStatusMessage = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["httpStatusMessage"]), ErrorCode = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["errorCode"]), Description = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["description"]) }; return(records); }
private static IEnumerable <ParameterDefinition> ParseParameterTable(IMarkdownTable table, ParameterLocation location, TableDecoder decoder, IssueLogger issues, bool navigationProperties = false) { // tables sometimes have column spans to delineate different sections of the table. for instance: // // | Name | Type | Description // |------|--------|-------------- // | one | int | first number // | two | int | second number // | **fancy numbers** // | pi | double | third number // // our markdown parser captures this as a regular row with all the columns, except with for all the blanks. // we try to infer such rows by looking for a **bold** first cell, followed by nbsp in all the other cells. // see below. var records = table.RowValues. Where(r => !r[0].StartsWith("**") || r.Skip(1).Any(c => c != " ")). // see comment above Select(r => new ParameterDefinition { Name = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["name"]), Type = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["type"]).ParseParameterDataType(defaultValue: ParameterDataType.String), Description = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["description"]), Required = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["description"]).IsRequired(), Optional = r.ValueForColumn(table, decoder.ParseRule.ColumnNames["description"]).IsOptional(), Location = location, IsNavigatable = navigationProperties, }).ToList(); var badRows = records.Count(r => string.IsNullOrEmpty(r.Name)); if (badRows > 0) { var tableHeaders = $"|{ string.Join("|", table.ColumnHeaders)}|"; if (badRows == records.Count) { issues.Warning(ValidationErrorCode.MarkdownParserError, $"Failed to parse any rows out of table with headers: {tableHeaders}\nTable was parsed as {decoder.ParseAs}, which requires a name column called one of the following: {{ {String.Join(",", decoder.ParseRule.ColumnNames["name"])} }}"); return(Enumerable.Empty <ParameterDefinition>()); } issues.Warning(ValidationErrorCode.ParameterParserError, $"Failed to parse {badRows} row(s) in table with headers: {tableHeaders}"); records = records.Where(r => !string.IsNullOrEmpty(r.Name)).ToList(); } return(records); }