/// <summary> /// Processes an xml reader into an xml writer. /// </summary> /// <param name="include">Specifies if reader is from an included file.</param> /// <param name="reader">Reader for the source document.</param> /// <param name="writer">Writer where postprocessed data is written.</param> /// <param name="offset">Original offset for the line numbers being processed.</param> private void PreprocessReader(bool include, XmlReader reader, XmlWriter writer, int offset) { Stack stack = new Stack(5); IfContext context = new IfContext(true, true, IfState.Unknown); // start by assuming we want to keep the nodes in the source code // process the reader into the writer while (reader.Read()) { // update information here in case an error occurs before the next read this.UpdateInformation(reader, offset); SourceLineNumberCollection sourceLineNumbers = this.GetCurrentSourceLineNumbers(); // check for changes in conditional processing if (XmlNodeType.ProcessingInstruction == reader.NodeType) { bool ignore = false; string name = null; switch (reader.LocalName) { case "if": stack.Push(context); if (context.IsTrue) { context = new IfContext(context.IsTrue & context.Active, this.EvaluateExpression(reader.Value), IfState.If); } else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true { context = new IfContext(); } ignore = true; break; case "ifdef": stack.Push(context); name = reader.Value.Trim(); if (context.IsTrue) { context = new IfContext(context.IsTrue & context.Active, (null != this.core.GetVariableValue(sourceLineNumbers, name, true)), IfState.If); } else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true { context = new IfContext(); } ignore = true; OnIfDef(new IfDefEventArgs(sourceLineNumbers, true, context.IsTrue, name)); break; case "ifndef": stack.Push(context); name = reader.Value.Trim(); if (context.IsTrue) { context = new IfContext(context.IsTrue & context.Active, (null == this.core.GetVariableValue(sourceLineNumbers, name, true)), IfState.If); } else // Use a default IfContext object so we don't try to evaluate the expression if the context isn't true { context = new IfContext(); } ignore = true; OnIfDef(new IfDefEventArgs(sourceLineNumbers, false, !context.IsTrue, name)); break; case "elseif": if (0 == stack.Count) { throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.GetCurrentSourceLineNumbers(), "if", "elseif")); } if (IfState.If != context.IfState && IfState.ElseIf != context.IfState) { throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.GetCurrentSourceLineNumbers(), "if", "elseif")); } context.IfState = IfState.ElseIf; // we're now in an elseif if (!context.WasEverTrue) // if we've never evaluated the if context to true, then we can try this test { context.IsTrue = this.EvaluateExpression(reader.Value); } else if (context.IsTrue) { context.IsTrue = false; } ignore = true; break; case "else": if (0 == stack.Count) { throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.GetCurrentSourceLineNumbers(), "if", "else")); } if (IfState.If != context.IfState && IfState.ElseIf != context.IfState) { throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.GetCurrentSourceLineNumbers(), "if", "else")); } context.IfState = IfState.Else; // we're now in an else context.IsTrue = !context.WasEverTrue; // if we were never true, we can be true now ignore = true; break; case "endif": if (0 == stack.Count) { throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.GetCurrentSourceLineNumbers(), "if", "endif")); } context = (IfContext)stack.Pop(); ignore = true; break; } if (ignore) // ignore this node since we just handled it above { continue; } } if (!context.Active || !context.IsTrue) // if our context is not true then skip the rest of the processing and just read the next thing { continue; } switch (reader.NodeType) { case XmlNodeType.ProcessingInstruction: switch (reader.LocalName) { case "define": this.PreprocessDefine(reader.Value); break; case "error": this.PreprocessError(reader.Value); break; case "warning": this.PreprocessWarning(reader.Value); break; case "undef": this.PreprocessUndef(reader.Value); break; case "include": this.UpdateInformation(reader, offset); this.PreprocessInclude(reader.Value, writer); break; case "foreach": this.PreprocessForeach(reader, writer, offset); break; case "endforeach": // endforeach is handled in PreprocessForeach, so seeing it here is an error throw new WixException(WixErrors.UnmatchedPreprocessorInstruction(this.GetCurrentSourceLineNumbers(), "foreach", "endforeach")); case "pragma": this.PreprocessPragma(reader.Value, writer); break; default: // unknown processing instructions are currently ignored break; } break; case XmlNodeType.Element: bool empty = reader.IsEmptyElement; if (0 < this.includeNextStack.Count && (bool)this.includeNextStack.Peek()) { if ("Include" != reader.LocalName) { this.core.OnMessage(WixErrors.InvalidDocumentElement(this.GetCurrentSourceLineNumbers(), reader.Name, "include", "Include")); } this.includeNextStack.Pop(); this.includeNextStack.Push(false); break; } // output any necessary preprocessor processing instructions then write the start of the element this.WriteProcessingInstruction(reader, writer, offset); writer.WriteStartElement(reader.Name); while (reader.MoveToNextAttribute()) { string value = this.core.PreprocessString(this.GetCurrentSourceLineNumbers(), reader.Value); writer.WriteAttributeString(reader.Name, value); } if (empty) { writer.WriteEndElement(); } break; case XmlNodeType.EndElement: if (0 < reader.Depth || !include) { writer.WriteEndElement(); } break; case XmlNodeType.Text: string postprocessedText = this.core.PreprocessString(this.GetCurrentSourceLineNumbers(), reader.Value); writer.WriteString(postprocessedText); break; case XmlNodeType.CDATA: string postprocessedValue = this.core.PreprocessString(this.GetCurrentSourceLineNumbers(), reader.Value); writer.WriteCData(postprocessedValue); break; default: break; } } if (0 != stack.Count) { throw new WixException(WixErrors.NonterminatedPreprocessorInstruction(this.GetCurrentSourceLineNumbers(), "if", "endif")); } }
/// <summary> /// Processes an xml reader into an xml writer. /// </summary> /// <param name="include">Specifies if reader is from an included file.</param> /// <param name="reader">Reader for the source document.</param> /// <param name="writer">Writer where postprocessed data is written.</param> /// <param name="offset">Original offset for the line numbers being processed.</param> private void PreprocessReader(bool include, XmlReader reader, XmlWriter writer, int offset) { Stack stack = new Stack(5); IfContext context = new IfContext(true, true, IfState.Unknown); // start by assuming we want to keep the nodes in the source code // process the reader into the writer while (reader.Read()) { // update information here in case an error occurs before the next read this.UpdateInformation(reader, offset); // check for changes in conditional processing if (XmlNodeType.ProcessingInstruction == reader.NodeType) { bool ignore = false; string name = null; int index = 0; switch (reader.LocalName) { case "if": stack.Push(context); context = new IfContext(context.IsTrue & context.Active, this.EvaluateExpression(reader.Value), IfState.If); ignore = true; break; case "ifdef": stack.Push(context); name = reader.Value.Trim(); index = name.IndexOf('.'); if (0 > index || (!Preprocessor.HasStandardPrefix(name) && null == this.FindExtension(name.Substring(0, index)))) { name = String.Concat("var.", name); } context = new IfContext(context.IsTrue & context.Active, this.IsVariableDefined(name), IfState.If); ignore = true; break; case "ifndef": stack.Push(context); name = reader.Value.Trim(); index = name.IndexOf('.'); if (0 > index || (!Preprocessor.HasStandardPrefix(name) && null == this.FindExtension(name.Substring(0, index)))) { name = String.Concat("var.", name); } context = new IfContext(context.IsTrue & context.Active, !this.IsVariableDefined(name), IfState.If); ignore = true; break; case "elseif": if (0 == stack.Count) { throw new WixPreprocessorException(this.GetCurrentSourceLineNumbers(), "Missing <?if?> for <?elseif?>"); } if (IfState.If != context.IfState && IfState.ElseIf != context.IfState) { throw new WixPreprocessorException(this.GetCurrentSourceLineNumbers(), "Specified <?elseif?> without matching <?if?>"); } context.IfState = IfState.ElseIf; // we're now in an elseif if (!context.WasEverTrue) // if we've never evaluated the if context to true, then we can try this test { context.IsTrue = this.EvaluateExpression(reader.Value); } else if (context.IsTrue) { context.IsTrue = false; } ignore = true; break; case "else": if (0 == stack.Count) { throw new WixPreprocessorException(this.GetCurrentSourceLineNumbers(), "Missing <?if?> for <?else?>"); } if (IfState.If != context.IfState && IfState.ElseIf != context.IfState) { throw new WixPreprocessorException(this.GetCurrentSourceLineNumbers(), "Specified <?else?> without matching <?if?>"); } context.IfState = IfState.Else; // we're now in an else context.IsTrue = !context.WasEverTrue; // if we were never true, we can be true now ignore = true; break; case "endif": if (0 == stack.Count) { throw new WixPreprocessorException(this.GetCurrentSourceLineNumbers(), "Missing <?if?> for <?endif?>"); } context = (IfContext)stack.Pop(); ignore = true; break; } if (ignore) // ignore this node since we just handled it above { continue; } } if (!context.Active || !context.IsTrue) // if our context is not true then skip the rest of the processing and just read the next thing { continue; } switch (reader.NodeType) { case XmlNodeType.ProcessingInstruction: switch (reader.LocalName) { case "define": this.PreprocessDefine(reader.Value); break; case "undef": this.PreprocessUndef(reader.Value); break; case "include": this.UpdateInformation(reader, offset); this.PreprocessInclude(reader.Value, writer); break; case "foreach": this.PreprocessForeach(reader, writer, offset); break; case "endforeach": // endforeach is handled in PreprocessForeach, so seeing it here is an error throw new WixPreprocessorException(this.GetCurrentSourceLineNumbers(), "Cannot have a <?endforeach?> processing instruction without a matching <?foreach?>."); default: // Console.WriteLine("processing instruction: {0}, {1}", reader.Name, reader.Value); break; } break; case XmlNodeType.Element: bool empty = reader.IsEmptyElement; if (0 < this.includeNextStack.Count && (bool)this.includeNextStack.Peek()) { if (reader.Name != "Include") { throw new WixPreprocessorException(this.GetCurrentSourceLineNumbers(), "Included files must have <Include /> document element."); } this.includeNextStack.Pop(); this.includeNextStack.Push(false); break; } // output any necessary preprocessor processing instructions then write the start of the element this.WriteProcessingInstruction(reader, writer, offset); writer.WriteStartElement(reader.Name); while (reader.MoveToNextAttribute()) { string value = this.PreprocessVariables(reader.Value); writer.WriteAttributeString(reader.Name, value); } if (empty) { writer.WriteEndElement(); } break; case XmlNodeType.EndElement: if (0 < reader.Depth || !include) { writer.WriteEndElement(); } break; case XmlNodeType.Text: case XmlNodeType.CDATA: string postprocessedValue = this.PreprocessVariables(reader.Value); writer.WriteCData(postprocessedValue); break; default: //Console.WriteLine("processing instruction: {0}, {1}", reader.Name, reader.Value); break; } } if (0 != stack.Count) { throw new WixPreprocessorException(this.GetCurrentSourceLineNumbers(), "Missing <?endif?> for <?if?>"); } }