Exemplo n.º 1
0
static void
ChangeCompetitionId(FlarmDevice &flarm, OperationEnvironment &env)
{
  while (true) {
    TCHAR old_id[64];
    if (flarm.GetCompetitionId(old_id, 64, env))
      _tprintf(_T("Old competition id: \"%s\"\n"), old_id);

    fprintf(stdout, "Please enter the new competition id:\n");
    fprintf(stdout, "> ");

    char id[64];
    if (fgets(id, 64, stdin) == NULL || strlen(id) == 0) {
      fprintf(stdout, "Invalid input\n");
      continue;
    }

    StripRight(id);
    fprintf(stdout, "Setting competition id to \"%s\" ...\n", id);

    const UTF8ToWideConverter value(id);
    if (flarm.SetCompetitionId(value, env))
      fprintf(stdout, "competition id set to \"%s\"\n", id);
    else
      fprintf(stdout, "Operation failed!\n");

    return;
  }
}
Exemplo n.º 2
0
static void
ChangeRegistration(FlarmDevice &flarm, OperationEnvironment &env)
{
  while (true) {
    TCHAR old_registration[64];
    if (flarm.GetPlaneRegistration(old_registration, 64, env))
      _tprintf(_T("Old plane registratio: \"%s\"\n"), old_registration);

    fprintf(stdout, "Please enter the new plane registration:\n");
    fprintf(stdout, "> ");

    char registration[64];
    if (fgets(registration, 64, stdin) == NULL || strlen(registration) == 0) {
      fprintf(stdout, "Invalid input\n");
      continue;
    }

    StripRight(registration);
    fprintf(stdout, "Setting plane registration to \"%s\" ...\n", registration);

    const UTF8ToWideConverter value(registration);
    if (flarm.SetPlaneRegistration(value, env))
      fprintf(stdout, "Plane registration set to \"%s\"\n", registration);
    else
      fprintf(stdout, "Operation failed!\n");

    return;
  }
}
Exemplo n.º 3
0
/*
Filtern einer Zeile:
  - Umwandeln von nicht-IGC-Zeichen in Leerzeichen
  - Entfernen von Leer- und Sonderzeichen am Ende
*/
char *igc_filter(char *st) {
  for (char *p = st; *p != 0; ++p)
    if (!IsAllowedIGCChar(*p))
      *p = ' ';
  StripRight(st);
  return st;
}
Exemplo n.º 4
0
static void
ChangeCoPilot(FlarmDevice &flarm, OperationEnvironment &env)
{
  while (true) {
    TCHAR old_copilot_name[64];
    if (flarm.GetCoPilot(old_copilot_name, 64, env))
      _tprintf(_T("Old copilot name: \"%s\"\n"), old_copilot_name);

    fprintf(stdout, "Please enter the new copilot name:\n");
    fprintf(stdout, "> ");

    char copilot_name[64];
    if (fgets(copilot_name, 64, stdin) == NULL || strlen(copilot_name) == 0) {
      fprintf(stdout, "Invalid input\n");
      continue;
    }

    StripRight(copilot_name);
    fprintf(stdout, "Setting copilot name to \"%s\" ...\n", copilot_name);

    const UTF8ToWideConverter value(copilot_name);
    if (flarm.SetCoPilot(value, env))
      fprintf(stdout, "CoPilot name set to \"%s\"\n", copilot_name);
    else
      fprintf(stdout, "Operation failed!\n");

    return;
  }
}
Exemplo n.º 5
0
static void
ChangePlaneType(FlarmDevice &flarm, OperationEnvironment &env)
{
  while (true) {
    TCHAR old_plane_type[64];
    if (flarm.GetPlaneType(old_plane_type, 64, env))
      _tprintf(_T("Old plane type: \"%s\"\n"), old_plane_type);

    fprintf(stdout, "Please enter the new plane type:\n");
    fprintf(stdout, "> ");

    char plane_type[64];
    if (fgets(plane_type, 64, stdin) == NULL || strlen(plane_type) == 0) {
      fprintf(stdout, "Invalid input\n");
      continue;
    }

    StripRight(plane_type);
    fprintf(stdout, "Setting plane type to \"%s\" ...\n", plane_type);

    const UTF8ToWideConverter value(plane_type);
    if (flarm.SetPlaneType(value, env))
      fprintf(stdout, "Plane type set to \"%s\"\n", plane_type);
    else
      fprintf(stdout, "Operation failed!\n");

    return;
  }
}
Exemplo n.º 6
0
static const char *
ParseLine(char *line)
{
  char *separator = strchr(line, '=');
  if (separator == nullptr)
    /* malformed line */
    return nullptr;

  char *p = StripRight(line, separator);
  if (p == line)
    /* empty name */
    return nullptr;

  *p = 0;

  char *value = const_cast<char *>(StripLeft(separator + 1));
  StripRight(value);
  return value;
}
Exemplo n.º 7
0
int main(int argc, char **argv)
{
  NarrowString<1024> usage;
  usage = "DRIVER\n\n"
          "Where DRIVER is one of:";
  {
    const DeviceRegister *driver;
    for (unsigned i = 0; (driver = GetDriverByIndex(i)) != nullptr; ++i) {
      WideToUTF8Converter driver_name(driver->name);
      usage.AppendFormat("\n\t%s", (const char *)driver_name);
    }
  }

  Args args(argc, argv, usage);
  tstring driver_name = args.ExpectNextT();
  args.ExpectEnd();

  driver = FindDriverByName(driver_name.c_str());
  if (driver == nullptr) {
    _ftprintf(stderr, _T("No such driver: %s\n"), driver_name.c_str());
    return 1;
  }

  DeviceConfig config;
  config.Clear();

  NullPort port;
  Device *device = driver->CreateOnPort != nullptr
    ? driver->CreateOnPort(config, port)
    : nullptr;

  NMEAParser parser;

  NMEAInfo data;
  data.Reset();

  char buffer[1024];
  while (fgets(buffer, sizeof(buffer), stdin) != nullptr) {
    StripRight(buffer);

    if (device == nullptr || !device->ParseNMEA(buffer, data))
      parser.ParseLine(buffer, data);
  }

  Dump(data);

  return EXIT_SUCCESS;
}
Exemplo n.º 8
0
void Plane::GenerateIndices(GLuint* indices, unsigned int width, unsigned int height){
	unsigned int i, j;

	//Generate initial index
	i = 0;
	indices[i++] = 0;

	//And the rest
	for (j = 0; j < height; ++j){
		if (j % 2 == 0){
			StripRight(indices, width, j, i);	
		} else {
			StripLeft(indices, width, j, i);
		}
	}
}
Exemplo n.º 9
0
/**
 * Decodes the FlarmNet.org file and puts the wanted
 * characters into the res pointer
 * @param file File handle
 * @param charCount Number of character to decode
 * @param res Pointer to be written in
 */
static void
LoadString(const char *bytes, size_t length, TCHAR *res, size_t res_size)
{
  const char *const end = bytes + length * 2;

#ifndef _UNICODE
  const char *const limit = res + res_size - 2;
#endif

  TCHAR *p = res;

  char tmp[3];
  tmp[2] = 0;

  while (bytes < end) {
    tmp[0] = *bytes++;
    tmp[1] = *bytes++;

    /* FLARMNet files are ISO-Latin-1, which is kind of short-sighted */

    const unsigned char ch = (unsigned char)strtoul(tmp, NULL, 16);
#ifdef _UNICODE
    /* Latin-1 can be converted to WIN32 wchar_t by casting */
    *p++ = ch;
#else
    /* convert to UTF-8 on all other platforms */

    if (p >= limit)
      break;

    p = Latin1ToUTF8(ch, p);
#endif
  }

  *p = 0;

#ifndef _UNICODE
  assert(ValidateUTF8(res));
#endif

  // Trim the string of any additional spaces
  StripRight(res);
}
Exemplo n.º 10
0
inline void
TeamCodeWidget::OnCodeClicked()
{
  TCHAR newTeammateCode[10];

  CopyTruncateString(newTeammateCode, ARRAY_SIZE(newTeammateCode),
                     CommonInterface::GetComputerSettings().team_code.team_code.GetCode());

  if (!TextEntryDialog(newTeammateCode, 7))
    return;

  StripRight(newTeammateCode);

  TeamCodeSettings &settings =
    CommonInterface::SetComputerSettings().team_code;
  settings.team_code.Update(newTeammateCode);
  if (settings.team_code.IsDefined())
    settings.team_flarm_id.Clear();
}
Exemplo n.º 11
0
void
LoadFlarmNameFile(TLineReader &reader, FlarmNameDatabase &db)
{
  TCHAR *line;
  while ((line = reader.ReadLine()) != NULL) {
    TCHAR *endptr;
    FlarmId id = FlarmId::Parse(line, &endptr);
    if (!id.IsDefined())
      /* ignore malformed records */
      continue;

    if (endptr > line && endptr[0] == _T('=') && endptr[1] != _T('\0')) {
      TCHAR *Name = endptr + 1;
      StripRight(Name);
      if (!db.Set(id, Name))
        break; // cant add anymore items !
    }
  }
}
Exemplo n.º 12
0
void
LogLastError(const TCHAR *fmt, ...)
{
  const DWORD error = GetLastError();

  TCHAR buffer[1024];
  va_list ap;
  va_start(ap, fmt);
  _vsntprintf(buffer, ARRAY_SIZE(buffer), fmt, ap);
  va_end(ap);

  TCHAR msg[256];
  FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM |
                FORMAT_MESSAGE_IGNORE_INSERTS,
                nullptr, error, 0, msg, ARRAY_SIZE(msg), nullptr);
  StripRight(msg);

  LogFormat(_T("%s: %s"), buffer, msg);
}
Exemplo n.º 13
0
void
KnobTextEntry(TCHAR *text, size_t width,
              const TCHAR *caption)
{
  if (width == 0)
    width = MAX_TEXTENTRY;

  KnobTextEntryWidget widget(text, width);
  WidgetDialog dialog(UIGlobals::GetDialogLook());
  dialog.CreateFull(UIGlobals::GetMainWindow(), caption, &widget);
  dialog.AddButton(_("Close"), mrOK);
  widget.CreateButtons(dialog);

  if (dialog.ShowModal() == mrOK) {
    StripRight(widget.GetValue());
    CopyTruncateString(text, width, widget.GetValue());
  }

  dialog.StealWidget();
}
Exemplo n.º 14
0
static void
ChangeBaudRate(FlarmDevice &flarm, OperationEnvironment &env)
{
  while (true) {
    unsigned baud_id;

    if (flarm.GetBaudRate(baud_id, env))
      printf("Old baud rate setting: \"%d\"\n", baud_id);

    fprintf(stdout, "Please enter the baud rate setting (2000-25500):\n");
    fprintf(stdout, "> ");

    char buffer[64];
    if (fgets(buffer, 64, stdin) == NULL || strlen(buffer) == 0) {
      fprintf(stdout, "Invalid input\n");
      continue;
    }

    StripRight(buffer);

    char *end_ptr;
    baud_id = strtoul(buffer, &end_ptr, 10);
    if (end_ptr == buffer) {
      fprintf(stdout, "Invalid input\n");
      continue;
    }

    fprintf(stdout, "Setting baud rate to \"%d\" ...\n", baud_id);

    if (flarm.SetBaudRate(baud_id, env))
      fprintf(stdout, "BaudRate set to \"%d\"\n", baud_id);
    else
      fprintf(stdout, "Operation failed!\n");

    return;
  }
}
Exemplo n.º 15
0
static void
ChangeRange(FlarmDevice &flarm, OperationEnvironment &env)
{
  while (true) {
    unsigned num_range;

    if (flarm.GetRange(num_range, env))
      printf("Old range setting: \"%d\"\n", num_range);

    fprintf(stdout, "Please enter the range setting (2000-25500):\n");
    fprintf(stdout, "> ");

    char range[64];
    if (fgets(range, 64, stdin) == NULL || strlen(range) == 0) {
      fprintf(stdout, "Invalid input\n");
      continue;
    }

    StripRight(range);

    char *end_ptr;
    num_range = strtoul(range, &end_ptr, 10);
    if (range == end_ptr) {
      fprintf(stdout, "Invalid input\n");
      continue;
    }

    fprintf(stdout, "Setting range to \"%d\" ...\n", num_range);

    if (flarm.SetRange(num_range, env))
      fprintf(stdout, "Range set to \"%d\"\n", num_range);
    else
      fprintf(stdout, "Operation failed!\n");

    return;
  }
}
Exemplo n.º 16
0
void
ParseInputFile(InputConfig &config, TLineReader &reader)
{
  // TODO code - Safer sizes, strings etc - use C++ (can scanf restrict length?)

  // Multiple modes (so large string)
  EventBuilder current;
  current.clear();

  int line = 0;

  // Read from the file
  TCHAR *buffer;
  while ((buffer = reader.ReadLine()) != NULL) {
    StripRight(buffer);
    line++;

    const TCHAR *key, *value;

    // experimental: if the first line is "#CLEAR" then the whole default config is cleared
    //               and can be overwritten by file
    if (line == 1 && StringIsEqual(buffer, _T("#CLEAR"))) {
      config.SetDefaults();
    } else if (buffer[0] == _T('\0')) {
      // Check valid line? If not valid, assume next record (primative, but works ok!)
      // General checks before continue...
      current.commit(config, line);

      // Clear all data.
      current.clear();

    } else if (StringIsEmpty(buffer) || buffer[0] == _T('#')) {
      // Do nothing - we probably just have a comment line
      // NOTE: Do NOT display buffer to user as it may contain an invalid stirng !

    } else if (parse_assignment(buffer, key, value)) {
      if (StringIsEqual(key, _T("mode"))) {
        current.mode = value;
      } else if (StringIsEqual(key, _T("type"))) {
        current.type = value;
      } else if (StringIsEqual(key, _T("data"))) {
        current.data = value;
      } else if (StringIsEqual(key, _T("event"))) {
        if (_tcslen(value) < 256) {
          TCHAR d_event[256] = _T("");
          TCHAR d_misc[256] = _T("");
          int ef;

          #if defined(__BORLANDC__)
          memset(d_event, 0, sizeof(d_event));
          memset(d_misc, 0, sizeof(d_event));
          if (StringFind(value, ' ') == nullptr) {
            _tcscpy(d_event, value);
          } else {
          #endif

          ef = _stscanf(value, _T("%[^ ] %[A-Za-z0-9_ \\/().,]"), d_event,
              d_misc);

          #if defined(__BORLANDC__)
          }
          #endif

          if ((ef == 1) || (ef == 2)) {

            // TODO code: Consider reusing existing identical events

            pt2Event event = InputEvents::findEvent(d_event);
            if (event) {
              TCHAR *allocated = UnescapeBackslash(d_misc);
              current.event_id = config.AppendEvent(event, allocated,
                                                    current.event_id);

              /* not freeing the string, because
                 InputConfig::AppendEvent() stores the string point
                 without duplicating it; strictly speaking, this is a
                 memory leak, but the input file is only loaded once
                 at startup, so this is acceptable; in return, we
                 don't have to duplicate the hard-coded defaults,
                 which saves some memory */
              //free(allocated);

            } else {
              LogFormat(_T("Invalid event type: %s at %i"), d_event, line);
            }
          } else {
            LogFormat("Invalid event type at %i", line);
          }
        }
      } else if (StringIsEqual(key, _T("label"))) {
        current.label = value;
      } else if (StringIsEqual(key, _T("location"))) {
        current.location = ParseUnsigned(value);

      } else {
        LogFormat(_T("Invalid key/value pair %s=%s at %i"), key, value, line);
      }
    } else  {
      LogFormat("Invalid line at %i", line);
    }

  }

  current.commit(config, line);
}
Exemplo n.º 17
0
ButtonLabel::Expanded
ButtonLabel::Expand(const TCHAR *text, TCHAR *buffer, size_t size)
{
  Expanded expanded;
  const TCHAR *dollar;

  if ((text == nullptr) || (*text == _T('\0')) || (*text == _T(' '))) {
    expanded.visible = false;
    return expanded;
  } else if ((dollar = StringFind(text, '$')) == nullptr) {
    /* no macro, we can just translate the text */
    expanded.visible = true;
    expanded.enabled = true;
    const TCHAR *nl;
    if (((nl = StringFind(text, '\n')) != nullptr) &&
        LacksAlphaASCII(nl + 1)) {
      /* Quick hack for skipping the translation for second line of a two line
         label with only digits and punctuation in the second line, e.g.
         for menu labels like "Config\n2/3" */

      /* copy the text up to the '\n' to a new buffer and translate it */
      TCHAR translatable[256];
      std::copy(text, nl, translatable);
      translatable[nl - text] = _T('\0');

      const TCHAR *translated = StringIsEmpty(translatable)
        ? _T("") : gettext(translatable);

      /* concatenate the translated text and the part starting with '\n' */
      _tcscpy(buffer, translated);
      _tcscat(buffer, nl);

      expanded.text = buffer;
    } else
      expanded.text = gettext(text);
    return expanded;
  } else {
    const TCHAR *macros = dollar;
    /* backtrack until the first non-whitespace character, because we
       don't want to translate whitespace between the text and the
       macro */
    macros = StripRight(text, macros);

    TCHAR s[100];
    expanded.enabled = !ExpandMacros(text, s, ARRAY_SIZE(s));
    if (s[0] == _T('\0') || s[0] == _T(' ')) {
      expanded.visible = false;
      return expanded;
    }

    /* copy the text (without trailing whitespace) to a new buffer and
       translate it */
    TCHAR translatable[256];
    std::copy(text, macros, translatable);
    translatable[macros - text] = _T('\0');

    const TCHAR *translated = StringIsEmpty(translatable)
      ? _T("") : gettext(translatable);

    /* concatenate the translated text and the macro output */
    _tcscpy(buffer, translated);
    _tcscat(buffer, s + (macros - text));

    expanded.visible = true;
    expanded.text = buffer;
    return expanded;
  }
}
Exemplo n.º 18
0
void FString::StripRight (const FString &charset)
{
	return StripRight (charset.Chars);
}
Exemplo n.º 19
0
ButtonLabel::Expanded
ButtonLabel::Expand(const TCHAR *text, TCHAR *buffer, size_t size)
{
  Expanded expanded;
  const TCHAR *dollar;

  if (text == nullptr || *text == _T('\0') || *text == _T(' ')) {
    expanded.visible = false;
    return expanded;
  } else if ((dollar = StringFind(text, '$')) == nullptr) {
    /* no macro, we can just translate the text */
    expanded.visible = true;
    expanded.enabled = true;
    const TCHAR *nl = StringFind(text, '\n');
    if (nl != nullptr && LacksAlphaASCII(nl + 1)) {
      /* Quick hack for skipping the translation for second line of a two line
         label with only digits and punctuation in the second line, e.g.
         for menu labels like "Config\n2/3" */

      /* copy the text up to the '\n' to a new buffer and translate it */
      TCHAR translatable[256];
      const TCHAR *translated = GetTextN(text, nl, translatable,
                                         ARRAY_SIZE(translatable));
      if (translated == nullptr) {
        /* buffer too small: keep it untranslated */
        expanded.text = text;
        return expanded;
      }

      /* concatenate the translated text and the part starting with '\n' */
      expanded.text = BuildString(buffer, size, translated, nl);
    } else
      expanded.text = gettext(text);
    return expanded;
  } else {
    const TCHAR *macros = dollar;
    /* backtrack until the first non-whitespace character, because we
       don't want to translate whitespace between the text and the
       macro */
    macros = StripRight(text, macros);

    TCHAR s[100];
    expanded.enabled = !ExpandMacros(text, s, ARRAY_SIZE(s));
    if (s[0] == _T('\0') || s[0] == _T(' ')) {
      expanded.visible = false;
      return expanded;
    }

    /* copy the text (without trailing whitespace) to a new buffer and
       translate it */
    TCHAR translatable[256];
    const TCHAR *translated = GetTextN(text, macros, translatable,
                                       ARRAY_SIZE(translatable));
    if (translated == nullptr) {
      /* buffer too small: fail */
      // TODO: find a more clever fallback
      expanded.visible = false;
      return expanded;
    }

    /* concatenate the translated text and the macro output */
    expanded.visible = true;
    expanded.text = BuildString(buffer, size, translated, s + (macros - text));
    return expanded;
  }
}
Exemplo n.º 20
0
/**
 * Recursively parse an XML element.
 */
static bool
XML::ParseXMLElement(XMLNode &node, Parser *pXML)
{
  bool is_declaration;
  const TCHAR *text = nullptr;
  XMLNode *pNew;
  enum Status status; // inside or outside a tag
  enum Attrib attrib = eAttribName;

  /* the name of the attribute that is currently being */
  tstring attribute_name;

  assert(pXML);

  // If this is the first call to the function
  if (pXML->nFirst) {
    // Assume we are outside of a tag definition
    pXML->nFirst = false;
    status = eOutsideTag;
  } else {
    // If this is not the first call then we should only be called when inside a tag.
    status = eInsideTag;
  }

  // Iterate through the tokens in the document
  while (true) {
    // Obtain the next token
    NextToken token = GetNextToken(pXML);
    if (gcc_unlikely(token.type == eTokenError))
      return false;

    // Check the current status
    switch (status) {
      // If we are outside of a tag definition
    case eOutsideTag:

      // Check what type of token we obtained
      switch (token.type) {
        // If we have found text or quoted text
      case eTokenText:
      case eTokenQuotedText:
      case eTokenEquals:
        if (text == nullptr)
          text = token.pStr;

        break;

        // If we found a start tag '<' and declarations '<?'
      case eTokenTagStart:
      case eTokenDeclaration:
        // Cache whether this new element is a declaration or not
        is_declaration = token.type == eTokenDeclaration;

        // If we have node text then add this to the element
        if (text != nullptr) {
          size_t length = StripRight(text, token.pStr - text);
          node.AddText(text, length);
          text = nullptr;
        }

        // Find the name of the tag
        token = GetNextToken(pXML);

        // Return an error if we couldn't obtain the next token or
        // it wasnt text
        if (token.type != eTokenText) {
          pXML->error = eXMLErrorMissingTagName;
          return false;
        }

        // If the name of the new element differs from the name of
        // the current element we need to add the new element to
        // the current one and recurse
        pNew = &node.AddChild(token.pStr, token.length,
                              is_declaration);

        while (true) {
          // Callself to process the new node.  If we return
          // FALSE this means we dont have any more
          // processing to do...

          if (!ParseXMLElement(*pNew, pXML)) {
            return false;
          } else {
            // If the call to recurse this function
            // evented in a end tag specified in XML then
            // we need to unwind the calls to this
            // function until we find the appropriate node
            // (the element name and end tag name must
            // match)
            if (pXML->cbEndTag) {
              // If we are back at the root node then we
              // have an unmatched end tag
              if (node.GetName() == nullptr) {
                pXML->error = eXMLErrorUnmatchedEndTag;
                return false;
              }

              // If the end tag matches the name of this
              // element then we only need to unwind
              // once more...

              if (CompareTagName(node.GetName(), pXML->lpEndTag)) {
                pXML->cbEndTag = 0;
              }

              return true;
            } else {
              // If we didn't have a new element to create
              break;
            }
          }
        }
        break;

        // If we found an end tag
      case eTokenTagEnd:

        // If we have node text then add this to the element
        if (text != nullptr) {
          size_t length = StripRight(text, token.pStr - text);
          TCHAR *text2 = FromXMLString(text, length);
          if (text2 == nullptr) {
            pXML->error = eXMLErrorUnexpectedToken;
            return false;
          }

          node.AddText(text2);
          free(text2);
          text = nullptr;
        }

        // Find the name of the end tag
        token = GetNextToken(pXML);

        // The end tag should be text
        if (token.type != eTokenText) {
          pXML->error = eXMLErrorMissingEndTagName;
          return false;
        }

        // After the end tag we should find a closing tag
        if (GetNextToken(pXML).type != eTokenCloseTag) {
          pXML->error = eXMLErrorMissingEndTagName;
          return false;
        }

        // We need to return to the previous caller.  If the name
        // of the tag cannot be found we need to keep returning to
        // caller until we find a match
        if (!CompareTagName(node.GetName(), token.pStr)) {
          pXML->lpEndTag = token.pStr;
          pXML->cbEndTag = token.length;
        }

        // Return to the caller
        return true;

        // Errors...
      case eTokenCloseTag: /* '>'         */
      case eTokenShortHandClose: /* '/>'        */
        pXML->error = eXMLErrorUnexpectedToken;
        return false;
      default:
        break;
      }
      break;

      // If we are inside a tag definition we need to search for attributes
    case eInsideTag:
      // Check what part of the attribute (name, equals, value) we
      // are looking for.
      switch (attrib) {
        // If we are looking for a new attribute
      case eAttribName:
        // Check what the current token type is
        switch (token.type) {
          // If the current type is text...
          // Eg.  'attribute'
        case eTokenText:
          // Cache the token then indicate that we are next to
          // look for the equals
          attribute_name.assign(token.pStr, token.length);
          attrib = eAttribEquals;
          break;

          // If we found a closing tag...
          // Eg.  '>'
        case eTokenCloseTag:
          // We are now outside the tag
          status = eOutsideTag;
          break;

          // If we found a short hand '/>' closing tag then we can
          // return to the caller
        case eTokenShortHandClose:
          return true;

          // Errors...
        case eTokenQuotedText: /* '"SomeText"'   */
        case eTokenTagStart: /* '<'            */
        case eTokenTagEnd: /* '</'           */
        case eTokenEquals: /* '='            */
        case eTokenDeclaration: /* '<?'           */
          pXML->error = eXMLErrorUnexpectedToken;
          return false;
        default:
          break;
        }
        break;

        // If we are looking for an equals
      case eAttribEquals:
        // Check what the current token type is
        switch (token.type) {
          // If the current type is text...
          // Eg.  'Attribute AnotherAttribute'
        case eTokenText:
          // Add the unvalued attribute to the list
          node.AddAttribute(std::move(attribute_name), _T(""), 0);
          // Cache the token then indicate.  We are next to
          // look for the equals attribute
          attribute_name.assign(token.pStr, token.length);
          break;

          // If we found a closing tag 'Attribute >' or a short hand
          // closing tag 'Attribute />'
        case eTokenShortHandClose:
        case eTokenCloseTag:
          assert(!attribute_name.empty());

          // If we are a declaration element '<?' then we need
          // to remove extra closing '?' if it exists
          if (node.IsDeclaration() && attribute_name.back() == _T('?')) {
            attribute_name.pop_back();
          }

          if (!attribute_name.empty())
            // Add the unvalued attribute to the list
            node.AddAttribute(std::move(attribute_name), _T(""), 0);

          // If this is the end of the tag then return to the caller
          if (token.type == eTokenShortHandClose)
            return true;

          // We are now outside the tag
          status = eOutsideTag;
          break;

          // If we found the equals token...
          // Eg.  'Attribute ='
        case eTokenEquals:
          // Indicate that we next need to search for the value
          // for the attribute
          attrib = eAttribValue;
          break;

          // Errors...
        case eTokenQuotedText: /* 'Attribute "InvalidAttr"'*/
        case eTokenTagStart: /* 'Attribute <'            */
        case eTokenTagEnd: /* 'Attribute </'           */
        case eTokenDeclaration: /* 'Attribute <?'           */
          pXML->error = eXMLErrorUnexpectedToken;
          return false;
        default:
          break;
        }
        break;

        // If we are looking for an attribute value
      case eAttribValue:
        // Check what the current token type is
        switch (token.type) {
          // If the current type is text or quoted text...
          // Eg.  'Attribute = "Value"' or 'Attribute = Value' or
          // 'Attribute = 'Value''.
        case eTokenText:
        case eTokenQuotedText:
          // If we are a declaration element '<?' then we need
          // to remove extra closing '?' if it exists
          if (node.IsDeclaration() && (token.pStr[token.length - 1]) == _T('?')) {
            token.length--;
          }

          // Add the valued attribute to the list
          if (token.type == eTokenQuotedText) {
            token.pStr++;
            token.length -= 2;
          }

          assert(!attribute_name.empty());

          {
            TCHAR *value = FromXMLString(token.pStr, token.length);
            if (value == nullptr) {
              pXML->error = eXMLErrorUnexpectedToken;
              return false;
            }

            node.AddAttribute(std::move(attribute_name),
                              value, _tcslen(value));
            free(value);
          }

          // Indicate we are searching for a new attribute
          attrib = eAttribName;
          break;

          // Errors...
        case eTokenTagStart: /* 'Attr = <'          */
        case eTokenTagEnd: /* 'Attr = </'         */
        case eTokenCloseTag: /* 'Attr = >'          */
        case eTokenShortHandClose: /* "Attr = />"         */
        case eTokenEquals: /* 'Attr = ='          */
        case eTokenDeclaration: /* 'Attr = <?'         */
          pXML->error = eXMLErrorUnexpectedToken;
          return false;
        default:
          break;
        }
      }
    }
  }
}