/// <summary>Parse an XML configuration file matching this type.</summary> /// <param name="reader">An <see cref="XmlReader"/> which does the actual XML reading.</param> /// <param name="errors">[out] Returns a dictionary into which parse errors will be placed, where /// the key is the name of the argument the user provided, and the value is an error message /// describing that argument.</param> /// <returns>True if no errors were encountered, false otherwise.</returns> public bool ReadXml(XmlReader reader, out Dictionary<string, string> errors) { ConfigurationParseContext parseContext = new ConfigurationParseContext(); if (this.ParseXmlConfigFileRaw(reader, parseContext)) { // Check for all required List<PropertyInfo> missing = _required.FindAll(x => !parseContext.FlagsSetInConfigurationFile.Contains(x) && ((_fields[x].Type & FieldTypes.CliOnly) == FieldTypes.None)); // it's only missing if it's required *and* it's not CLI only foreach (PropertyInfo f in missing) { parseContext.AddConfigurationError(f.Name, "missing required element"); } } errors = parseContext.GenerateErrorDictionary(); return errors.Count == 0; }
private bool ParseResponseFile(ConfigurationParseContext parseContext, string responseFileName) { string commandText; try { commandText = File.ReadAllText(responseFileName); } catch (IOException ex) { parseContext.AddCommandLineError(string.Empty, "While loading command line response file " + responseFileName + ": " + ex.Message); return false; } return this.ParseArgsImpl(null, ArgumentSplitter.CommandLineToArgvW(commandText), false, parseContext); }
private bool ParseArgsImpl(string defaultConfig, IList<string> args, bool allowConfigFileLoad, ConfigurationParseContext parseContext) { PropertyInfo lastArg = null; bool status = true; if (defaultConfig != null) { if (!ParseXmlConfigFileSafe(parseContext, defaultConfig)) { status = false; } } bool lastArgWasUnknown = false; for (int idx = 0; idx < args.Count;) { string arg = args[idx]; if (arg.StartsWith("@", StringComparison.Ordinal) && allowConfigFileLoad) { string configFileName = arg.Substring(1); if (configFileName.EndsWith(".rsp", StringComparison.OrdinalIgnoreCase)) { status = status && ParseResponseFile(parseContext, configFileName); } else { status = status && ParseXmlConfigFileSafe(parseContext, configFileName); } idx++; lastArg = null; lastArgWasUnknown = false; } else if (arg.Equals("/?", StringComparison.Ordinal) || arg.Equals("-?", StringComparison.Ordinal) || arg.Equals("--?", StringComparison.Ordinal)) { this.PrintUsage(); if (allowConfigFileLoad) { Console.Out.WriteLine("Invoke with @file.rsp (with .rsp extension) to load console response file."); Console.Out.WriteLine("Invoke with @file.xml (with anything but .rsp extension) to load XML config."); } parseContext.RemoveAllErrors(); return false; } else if (arg.StartsWith("/", StringComparison.Ordinal) || arg.StartsWith("-", StringComparison.Ordinal)) { // parse arg value string name = arg.Substring(1); if (_names.TryGetValue(name, out lastArg)) { } else if (_shortNames.TryGetValue(name, out lastArg)) { name = _fields[lastArg].Name; } else if (name.EndsWith("-", StringComparison.Ordinal)) { name = name.Substring(0, name.Length - 1); if (this.TryFindArgumentByName(name, out lastArg)) { lastArgWasUnknown = false; Type normalizedType = GetNormalizedType(lastArg.PropertyType); if (normalizedType == typeof(bool) && HasNoMinusAttribute(lastArg)) { parseContext.AddCommandLineError(name, "unknown argument (the negated version of this flag is explicitly disallowed)"); status = false; lastArg = null; idx++; } else if (normalizedType != typeof(bool)) { parseContext.AddCommandLineError(name, "boolean switch used with non-boolean argument"); status = false; lastArg = null; idx++; } } else { parseContext.AddCommandLineError(name, "unknown argument"); status = false; lastArgWasUnknown = true; idx++; } } else { // error parseContext.AddCommandLineError(name, "unknown argument"); status = false; lastArgWasUnknown = true; idx++; } if (lastArg != null && parseContext.FlagsSetOnCommandLine.Contains(lastArg) && (!IsArray(lastArg.PropertyType))) { parseContext.AddCommandLineError(name, "specified multiple times"); status = false; } if (lastArg != null && GetNormalizedType(lastArg.PropertyType) != typeof(bool)) { lastArgWasUnknown = false; idx++; } } else if (_defaultField != null) { lastArg = _defaultField; } else { if (!lastArgWasUnknown) { // if the last arg was a typo, this probably isn't a separate error parseContext.AddCommandLineError(string.Empty, "default argument given but there is no default argument"); status = false; } idx++; // try to keep parsing } if (lastArg != null) { lastArgWasUnknown = false; bool parseOK = true; // We've seen the arg and set lastArg, so parse a value if (parseContext.FlagsSetOnCommandLine.Contains(lastArg) && (!IsArray(lastArg.PropertyType))) { parseContext.AddCommandLineError(_fields[lastArg].Name, "specified multiple times"); parseOK = false; status = false; } object value; string parse_error; int endIdx; if (!this.ParseArg(lastArg.PropertyType, args, idx, out endIdx, out parse_error, out value)) { parseContext.AddCommandLineError(_fields[lastArg].Name, parse_error); parseOK = false; status = false; } if (parseOK) { if (IsArray(lastArg.PropertyType)) { // parse array List<object> vals; // add it to the list of arrays if (parseContext.MultipleUseArgumentValues.TryGetValue(lastArg, out vals)) { // update the list of arrays parseContext.MultipleUseArgumentValues[lastArg].Add(value); } else { vals = new List<object>(); vals.Add(value); parseContext.MultipleUseArgumentValues.Add(lastArg, vals); parseContext.FlagsSetOnCommandLine.Add(lastArg); } } else { try { lastArg.SetValue(this, value, null); // Remove any errors about this field from the config XML parseContext.RemoveConfigurationError(_fields[lastArg].Name); parseContext.FlagsSetOnCommandLine.Add(lastArg); } catch (TargetInvocationException ex) { if (ex.InnerException is ArgumentException) { parseContext.AddCommandLineError(_fields[lastArg].Name, ex.InnerException.Message); status = false; } else { throw; } } } } idx = endIdx; lastArg = null; } } List<PropertyInfo> missing = _required.FindAll(x => !parseContext.FlagsSetOnCommandLine.Contains(x) && !parseContext.FlagsSetInConfigurationFile.Contains(x)); if (missing.Count > 0) { foreach (PropertyInfo f in missing) { string n = f.Name; // get the name of the arg, if it differs from the property name foreach (KeyValuePair<string, PropertyInfo> v in _names) { if (v.Value.Name == f.Name) { n = v.Key; } } parseContext.AddCommandLineError(n, "missing required argument"); } status = false; } return status; }
/// <summary>Parses the argument list.</summary> /// <exception cref="ArgumentNullException">Thrown when one or more required arguments are null.</exception> /// <exception cref="TargetInvocationException">Thrown when a target invocation error condition /// occurs.</exception> /// <param name="defaultConfig">The default configuration file (this value can be null)</param> /// <param name="args">The array of arguments, similar to <c>argv</c>.</param> /// <param name="allowConfigFileLoad">If <c>true</c>, allows the parser to load data from XML /// configuration files and console response files; otherwise disallows these actions.</param> /// <param name="errors">[out] Returns a dictionary into which parse errors will be placed, where /// the key is the name of the argument the user provided, and the value is an error message /// describing that argument.</param> /// <returns> /// <c>true</c> if a program should continue running after this method returns, or <c>false</c> /// if the program should terminate. (For example, returns <c>true</c> when the parse is /// successful, <c>false</c> if an erroneous argument is provided, and <c>false</c> if the user /// requested usage with /?) /// </returns> public bool ParseArgs(string defaultConfig, IList<string> args, bool allowConfigFileLoad, out Dictionary<string, string> errors) { if (args == null) { throw new ArgumentNullException("args"); } ConfigurationParseContext parseContext = new ConfigurationParseContext(); bool status = ParseArgsImpl(defaultConfig, args, allowConfigFileLoad, parseContext); // Set arrays to their parsed values foreach (KeyValuePair<PropertyInfo, List<object>> arrayArgumentValue in parseContext.MultipleUseArgumentValues) { List<object> o = arrayArgumentValue.Value; Array a = Array.CreateInstance(arrayArgumentValue.Key.PropertyType.GetElementType(), o.Count); for (int i = 0; i < o.Count; i++) { a.SetValue(o[i], i); } try { arrayArgumentValue.Key.SetValue(this, a, null); } catch (TargetInvocationException ex) { if (ex.InnerException is ArgumentException) { parseContext.AddCommandLineError(arrayArgumentValue.Key.Name, ex.InnerException.Message); status = false; } else { throw; } } } errors = parseContext.GenerateErrorDictionary(); if (errors.Count != 0) { status = false; } // Validate is only run if we parsed OK if (status) { return this.Validate(errors); } return status; }
/// <summary> /// Parse an XML configuration file matching this type. Does NOT check for missing required /// arguments (that is done by ReadXml(XmlReader, out string)) or call Validate() method. It /// *only* throws setter errors. /// </summary> /// <exception cref="ArgumentNullException">Thrown when one or more required arguments are null.</exception> /// <exception cref="TargetInvocationException">Thrown when a target invocation error condition /// occurs.</exception> /// <param name="reader">An <see cref="XmlReader"/> which does the actual XML reading.</param> /// <param name="parseContext">Context for the configuration parse currently running.</param> /// <returns>True if no errors were encountered, false otherwise.</returns> private bool ParseXmlConfigFileRaw(XmlReader reader, ConfigurationParseContext parseContext) { List<PropertyInfo> mySetArgs = new List<PropertyInfo>(); bool status = true; StringBuilder general_errors = new StringBuilder(); if (reader == null) { throw new ArgumentNullException("reader"); } XmlDocument document = new XmlDocument(); try { document.Load(reader); } catch (XmlException ex) { parseContext.AddConfigurationError(string.Empty, ex.Message); return false; } catch (UnauthorizedAccessException ex) { parseContext.AddConfigurationError(string.Empty, ex.Message); return false; } XmlElement root = null; foreach (XmlNode n in document.ChildNodes) { XmlElement asElement = n as XmlElement; if (asElement != null) { if (n.Name == this.GetClassName()) { root = asElement; } else { general_errors.AppendLine("unknown element " + n.Name); status = false; } } } if (root == null) { general_errors.AppendLine("missing root element " + this.GetClassName()); status = false; } else { // TODO: should we check version here? XmlElement el; foreach (XmlNode node in root.ChildNodes) { el = node as XmlElement; if (el != null) { string name = el.Name; PropertyInfo field; if (_names.TryGetValue(name, out field)) { } else if (_shortNames.TryGetValue(name, out field)) { } else { parseContext.AddConfigurationError(name, "unrecognized element"); status = false; continue; } if ((_fields[field].Type & FieldTypes.CliOnly) != FieldTypes.None) { parseContext.AddConfigurationError(name, "parameter can only be specified at the command line"); status = false; } if (mySetArgs.Contains(field) && !IsArray(field.PropertyType)) { parseContext.AddConfigurationError(name, "specified multiple times"); status = false; } object value; string e; if (!this.ReadType(field.PropertyType, el, out value, out e)) { parseContext.AddConfigurationError(field.Name, e); status = false; } // don't set it if it was set by the CLI, for non-arrays if (IsArray(field.PropertyType)) { List<object> l; if (parseContext.MultipleUseArgumentValues.TryGetValue(field, out l)) { l.Add(value); } else { parseContext.MultipleUseArgumentValues.Add(field, new List<object>(new object[] { value })); } } else { if (!parseContext.FlagsSetOnCommandLine.Contains(field)) { try { field.SetValue(this, value, null); // is this cast OK? } catch (TargetInvocationException ex) { if (ex.InnerException is ArgumentException) { parseContext.AddConfigurationError(field.Name, ex.InnerException.Message); status = false; } else { throw; } } } mySetArgs.Add(field); } } } } // now set any arrays foreach (PropertyInfo key in parseContext.MultipleUseArgumentValues.Keys) { Array v = Array.CreateInstance(key.PropertyType.GetElementType(), parseContext.MultipleUseArgumentValues[key].Count); int i = 0; foreach (object o in parseContext.MultipleUseArgumentValues[key]) { v.SetValue(o, i); i++; } try { key.SetValue(this, v, null); } catch (TargetInvocationException ex) { if (ex.InnerException is ArgumentException) { parseContext.AddConfigurationError(key.Name, ex.InnerException.Message); status = false; } else { throw; } } mySetArgs.Add(key); } foreach (PropertyInfo arg in mySetArgs) { parseContext.FlagsSetInConfigurationFile.Add(arg); } if (general_errors.Length > 0) { parseContext.AddConfigurationError(string.Empty, general_errors.ToString()); } return status; }
private bool ParseXmlConfigFileSafe(ConfigurationParseContext parseContext, string configFileName) { try { using (XmlTextReader r = new XmlTextReader(configFileName) { DtdProcessing = DtdProcessing.Ignore }) { if (!this.ParseXmlConfigFileRaw(r, parseContext)) { return false; } } } catch (IOException ex) { parseContext.AddCommandLineError(string.Empty, "While loading XML configuration file " + configFileName + ": " + ex.Message); return false; } catch (InvalidOperationException ex) { parseContext.AddCommandLineError(string.Empty, "While loading XML configuration file " + configFileName + ": " + ex.Message); return false; } catch (UriFormatException ex) { parseContext.AddCommandLineError(string.Empty, "While loading XML configuration file " + configFileName + ": " + ex.Message); return false; } return true; }