#BILTNA Wish 4: Spell check for schedules

Why doesn’t Revit check spelling in schedules? No good reason that I know of, so here’s an investigation into how it might be done with the API. There’s also some discussion of JSON deserialization and communicating with the Bing Spell Check web API.

spellcheck

public void scheduleSpellCheck()
{
    Document doc = this.ActiveUIDocument.Document;
    ViewSchedule viewSchedule = doc.ActiveView as ViewSchedule;
    ViewSheet viewSheet = doc.ActiveView as ViewSheet;
    if (viewSchedule == null && viewSheet == null)
    {
        TaskDialog.Show("Error", "Active a schedule or sheet before running this command");
        return;
    }
    SectionType sectionType = SectionType.Body;
    List<string> scheduleText = new List<string>();
    
    List schedules = new List();
    if (viewSheet != null)
    {
        schedules = new FilteredElementCollector(doc).OfClass(typeof(ScheduleSheetInstance)).Cast()
            .Where(q => q.OwnerViewId == viewSheet.Id)
            .Select(q => doc.GetElement(q.ScheduleId) as ViewSchedule)
            .Where(q => !q.IsTitleblockRevisionSchedule).ToList();
    }
    else
    {
        schedules.Add(viewSchedule);
    }
    
    foreach (ViewSchedule vs in schedules)
    {
        scheduleText.Add(vs.Name);
        for (int i = 0; i < getRowColumnCount(vs, sectionType, true); i++)
        {
            for (int j = 0; j < getRowColumnCount(vs, sectionType, false); j++)
            {
                try
                {
                    scheduleText.Add(vs.GetCellText(sectionType, i, j));
                }
                catch (Exception ex)
                {
                    
                }
            }
        }
        SpellCheck(string.Join(" - ", scheduleText));
    }
}

 static async void SpellCheck(string text)
{
    HttpClient client = new HttpClient();
    client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", key);

    // The following headers are optional, but it is recommended they be treated as required.
    // These headers help the service return more accurate results.
    //client.DefaultRequestHeaders.Add("X-Search-Location", ClientLocation);
    //client.DefaultRequestHeaders.Add("X-MSEdge-ClientID", ClientId);
    //client.DefaultRequestHeaders.Add("X-MSEdge-ClientIP", ClientIp);

    HttpResponseMessage response = new HttpResponseMessage();
    string uri = host + path + params_;

    List<KeyValuePair<string, string>> values = new List<KeyValuePair<string, string>>();
    values.Add(new KeyValuePair<string, string>("text", text));

    using (FormUrlEncodedContent content = new FormUrlEncodedContent(values))
    {
        content.Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded");
        response = await client.PostAsync(uri, content);
    }

    string client_id;
    IEnumerable<string> header_values = new List<string>();
    if (response.Headers.TryGetValues("X-MSEdge-ClientID", out header_values))
    {
        client_id = header_values.First();
        Console.WriteLine("Client ID: " + client_id);
    }

    string results = await response.Content.ReadAsStringAsync(); 
    TaskDialog.Show("JSON", JsonPrettyPrint(results));
    FlaggedTokenList flaggedTokenList = new JavaScriptSerializer().Deserialize(results);
    List<string> output = new List<string>();
    foreach (FlaggedToken flaggedToken in flaggedTokenList.flaggedTokens)
    {
        List<string> suggestions = flaggedToken.suggestions.Select(q => q.suggestion.ToString()).ToList();
        if (suggestions.Count == 0)
            continue;
        output.Add(flaggedToken.token + " : " + string.Join(",", suggestions));
    }
    TaskDialog.Show("output", string.Join(Environment.NewLine, output));
}

static string host = "https://api.cognitive.microsoft.com";
static string path = "/bing/v7.0/spellcheck?";

// For a list of available markets, go to:
// https://docs.microsoft.com/rest/api/cognitiveservices/bing-autosuggest-api-v7-reference#market-codes
static string params_ = "mkt=en-US&mode=proof";

// NOTE: Replace this example key with a valid subscription key.
static string key = "d46d0d618a7343dabe8a4355e9d30187";
         
class FlaggedTokenList
{
    public List flaggedTokens { get; set; }
}

class FlaggedToken
{
    public string token { get; set; }
    public List suggestions { get; set; }
}    

class Suggestion
{
    public string suggestion { get; set; }
    public double score { get; set; }
}

        
private int getRowColumnCount(ViewSchedule view, SectionType sectionType, bool countRows)
{
    int ctr = 1;
    // loop over the columns of the schedule
    while (true)
    {
        try // GetCellText will throw an ArgumentException is the cell does not exist
        {
            if (countRows)
                view.GetCellText(sectionType, ctr, 1);
            else
                view.GetCellText(sectionType, 1, ctr);
            ctr++;
        }
        catch (Autodesk.Revit.Exceptions.ArgumentException)
        {
            return ctr;
        }
    }
}    

static string JsonPrettyPrint(string json)
{
    if (string.IsNullOrEmpty(json))
    {
        return string.Empty;
    }

    json = json.Replace(Environment.NewLine, "").Replace("\t", "");

    StringBuilder sb = new StringBuilder();
    bool quote = false;
    bool ignore = false;
    char last = ' ';
    int offset = 0;
    int indentLength = 3;

    foreach (char ch in json)
    {
        switch (ch)
        {
            case '"':
                if (!ignore) quote = !quote;
                break;
            case '\\':
                if (quote && last != '\\') ignore = true;
                break;
        }

        if (quote)
        {
            sb.Append(ch);
            if (last == '\\' && ignore) ignore = false;
        }
        else
        {
            switch (ch)
            {
                case '{':
                case '[':
                    sb.Append(ch);
                    sb.Append(Environment.NewLine);
                    sb.Append(new string(' ', ++offset * indentLength));
                    break;
                case '}':
                case ']':
                    sb.Append(Environment.NewLine);
                    sb.Append(new string(' ', --offset * indentLength));
                    sb.Append(ch);
                    break;
                case ',':
                    sb.Append(ch);
                    sb.Append(Environment.NewLine);
                    sb.Append(new string(' ', offset * indentLength));
                    break;
                case ':':
                    sb.Append(ch);
                    sb.Append(' ');
                    break;
                default:
                    if (quote || ch != ' ') sb.Append(ch);
                    break;
            }
        }
        last = ch;
    }

    return sb.ToString().Trim();
}

5 thoughts on “#BILTNA Wish 4: Spell check for schedules

  1. I ran into an error. and are missing from the code. I fix that and a dozen new errors show up including a missing .

    I fix that and make sure I have the BING lines:
    using System.Net.Http;
    using System.Net.Http.Headers;
    Which make new errors.

  2. I played around with this as well as spell checking tags (much more complicated, given you have to get parameters in families) a couple years ago.
    I’m glad a real developer is looking at it. I was waaaaay above my abilities.

Leave a comment