|
//------------------------------------------------------------------------------
// <copyright file="RecordBuilder.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// <owner current="true" primary="true">Microsoft</owner>
//------------------------------------------------------------------------------
namespace System.Xml.Xsl.XsltOld {
using Res = System.Xml.Utils.Res;
using System;
using System.Diagnostics;
using System.Text;
using System.Xml;
using System.Xml.XPath;
using System.Collections;
internal sealed class RecordBuilder {
private int outputState;
private RecordBuilder next;
RecordOutput output;
// Atomization:
private XmlNameTable nameTable;
private OutKeywords atoms;
// Namespace manager for output
private OutputScopeManager scopeManager;
// Main node + Fields Collection
private BuilderInfo mainNode = new BuilderInfo();
private ArrayList attributeList = new ArrayList();
private int attributeCount;
private ArrayList namespaceList = new ArrayList();
private int namespaceCount;
private BuilderInfo dummy = new BuilderInfo();
// Current position in the list
private BuilderInfo currentInfo;
// Builder state
private bool popScope;
private int recordState;
private int recordDepth;
private const int NoRecord = 0; // No part of a new record was generated (old record was cleared out)
private const int SomeRecord = 1; // Record was generated partially (can be eventually record)
private const int HaveRecord = 2; // Record was fully generated
private const char s_Minus = '-';
private const string s_Space = " ";
private const string s_SpaceMinus = " -";
private const char s_Question = '?';
private const char s_Greater = '>';
private const string s_SpaceGreater = " >";
private const string PrefixFormat = "xp_{0}";
internal RecordBuilder(RecordOutput output, XmlNameTable nameTable) {
Debug.Assert(output != null);
this.output = output;
this.nameTable = nameTable != null ? nameTable : new NameTable();
this.atoms = new OutKeywords(this.nameTable);
this.scopeManager = new OutputScopeManager(this.nameTable, this.atoms);
}
//
// Internal properties
//
internal int OutputState {
get { return this.outputState; }
set { this.outputState = value; }
}
internal RecordBuilder Next {
get { return this.next; }
set { this.next = value; }
}
internal RecordOutput Output {
get { return this.output; }
}
internal BuilderInfo MainNode {
get { return this.mainNode; }
}
internal ArrayList AttributeList {
get { return this.attributeList; }
}
internal int AttributeCount {
get { return this.attributeCount; }
}
internal OutputScopeManager Manager {
get { return this.scopeManager; }
}
private void ValueAppend(string s, bool disableOutputEscaping) {
this.currentInfo.ValueAppend(s, disableOutputEscaping);
}
private bool CanOutput(int state) {
Debug.Assert(this.recordState != HaveRecord);
// If we have no record cached or the next event doesn't start new record, we are OK
if (this.recordState == NoRecord || (state & StateMachine.BeginRecord) == 0) {
return true;
}
else {
this.recordState = HaveRecord;
FinalizeRecord();
SetEmptyFlag(state);
return this.output.RecordDone(this) == Processor.OutputResult.Continue;
}
}
internal Processor.OutputResult BeginEvent(int state, XPathNodeType nodeType, string prefix, string name, string nspace, bool empty, Object htmlProps, bool search) {
if (! CanOutput(state)) {
return Processor.OutputResult.Overflow;
}
Debug.Assert(this.recordState == NoRecord || (state & StateMachine.BeginRecord) == 0);
AdjustDepth(state);
ResetRecord(state);
PopElementScope();
prefix = (prefix != null) ? this.nameTable.Add(prefix) : this.atoms.Empty;
name = (name != null) ? this.nameTable.Add(name) : this.atoms.Empty;
nspace = (nspace != null) ? this.nameTable.Add(nspace) : this.atoms.Empty;
switch (nodeType) {
case XPathNodeType.Element:
this.mainNode.htmlProps = htmlProps as HtmlElementProps;
this.mainNode.search = search;
BeginElement(prefix, name, nspace, empty);
break;
case XPathNodeType.Attribute:
BeginAttribute(prefix, name, nspace, htmlProps, search);
break;
case XPathNodeType.Namespace:
BeginNamespace(name, nspace);
break;
case XPathNodeType.Text:
break;
case XPathNodeType.ProcessingInstruction:
if (BeginProcessingInstruction(prefix, name, nspace) == false) {
return Processor.OutputResult.Error;
}
break;
case XPathNodeType.Comment:
BeginComment();
break;
case XPathNodeType.Root:
break;
case XPathNodeType.Whitespace:
case XPathNodeType.SignificantWhitespace:
case XPathNodeType.All:
break;
}
return CheckRecordBegin(state);
}
internal Processor.OutputResult TextEvent(int state, string text, bool disableOutputEscaping) {
if (! CanOutput(state)) {
return Processor.OutputResult.Overflow;
}
Debug.Assert(this.recordState == NoRecord || (state & StateMachine.BeginRecord) == 0);
AdjustDepth(state);
ResetRecord(state);
PopElementScope();
if ((state & StateMachine.BeginRecord) != 0) {
this.currentInfo.Depth = this.recordDepth;
this.currentInfo.NodeType = XmlNodeType.Text;
}
ValueAppend(text, disableOutputEscaping);
return CheckRecordBegin(state);
}
internal Processor.OutputResult EndEvent(int state, XPathNodeType nodeType) {
if (! CanOutput(state)) {
return Processor.OutputResult.Overflow;
}
AdjustDepth(state);
PopElementScope();
this.popScope = (state & StateMachine.PopScope) != 0;
if ((state & StateMachine.EmptyTag) != 0 && this.mainNode.IsEmptyTag == true) {
return Processor.OutputResult.Continue;
}
ResetRecord(state);
if ((state & StateMachine.BeginRecord) != 0) {
if(nodeType == XPathNodeType.Element) {
EndElement();
}
}
return CheckRecordEnd(state);
}
internal void Reset() {
if (this.recordState == HaveRecord) {
this.recordState = NoRecord;
}
}
internal void TheEnd() {
if (this.recordState == SomeRecord) {
this.recordState = HaveRecord;
FinalizeRecord();
this.output.RecordDone(this);
}
this.output.TheEnd();
}
//
// Utility implementation methods
//
private int FindAttribute(string name, string nspace, ref string prefix) {
Debug.Assert(this.attributeCount <= this.attributeList.Count);
for (int attrib = 0; attrib < this.attributeCount; attrib ++) {
Debug.Assert(this.attributeList[attrib] != null && this.attributeList[attrib] is BuilderInfo);
BuilderInfo attribute = (BuilderInfo) this.attributeList[attrib];
if (Ref.Equal(attribute.LocalName, name)) {
if (Ref.Equal(attribute.NamespaceURI, nspace)) {
return attrib;
}
if (Ref.Equal(attribute.Prefix, prefix)) {
// prefix conflict. Should be renamed.
prefix = string.Empty;
}
}
}
return -1;
}
private void BeginElement(string prefix, string name, string nspace, bool empty) {
Debug.Assert(this.attributeCount == 0);
this.currentInfo.NodeType = XmlNodeType.Element;
this.currentInfo.Prefix = prefix;
this.currentInfo.LocalName = name;
this.currentInfo.NamespaceURI = nspace;
this.currentInfo.Depth = this.recordDepth;
this.currentInfo.IsEmptyTag = empty;
this.scopeManager.PushScope(name, nspace, prefix);
}
private void EndElement() {
Debug.Assert(this.attributeCount == 0);
OutputScope elementScope = this.scopeManager.CurrentElementScope;
this.currentInfo.NodeType = XmlNodeType.EndElement;
this.currentInfo.Prefix = elementScope.Prefix;
this.currentInfo.LocalName = elementScope.Name;
this.currentInfo.NamespaceURI = elementScope.Namespace;
this.currentInfo.Depth = this.recordDepth;
}
private int NewAttribute() {
if (this.attributeCount >= this.attributeList.Count) {
Debug.Assert(this.attributeCount == this.attributeList.Count);
this.attributeList.Add(new BuilderInfo());
}
return this.attributeCount ++;
}
private void BeginAttribute(string prefix, string name, string nspace, Object htmlAttrProps, bool search) {
int attrib = FindAttribute(name, nspace, ref prefix);
if (attrib == -1) {
attrib = NewAttribute();
}
Debug.Assert(this.attributeList[attrib] != null && this.attributeList[attrib] is BuilderInfo);
BuilderInfo attribute = (BuilderInfo) this.attributeList[attrib];
attribute.Initialize(prefix, name, nspace);
attribute.Depth = this.recordDepth;
attribute.NodeType = XmlNodeType.Attribute;
attribute.htmlAttrProps = htmlAttrProps as HtmlAttributeProps;
attribute.search = search;
this.currentInfo = attribute;
}
private void BeginNamespace(string name, string nspace) {
bool thisScope = false;
if (Ref.Equal(name, this.atoms.Empty)) {
if (Ref.Equal(nspace, this.scopeManager.DefaultNamespace)) {
// Main Node is OK
}
else if (Ref.Equal(this.mainNode.NamespaceURI, this.atoms.Empty)) {
// http://www.w3.org/1999/11/REC-xslt-19991116-errata/ E25
// Should throw an error but ingnoring it in Everett.
// Would be a breaking change
}
else {
DeclareNamespace(nspace, name);
}
}
else {
string nspaceDeclared = this.scopeManager.ResolveNamespace(name, out thisScope);
if (nspaceDeclared != null) {
if (! Ref.Equal(nspace, nspaceDeclared)) {
if(!thisScope) {
DeclareNamespace(nspace, name);
}
}
}
else {
DeclareNamespace(nspace, name);
}
}
this.currentInfo = dummy;
currentInfo.NodeType = XmlNodeType.Attribute;
}
private bool BeginProcessingInstruction(string prefix, string name, string nspace) {
this.currentInfo.NodeType = XmlNodeType.ProcessingInstruction;
this.currentInfo.Prefix = prefix;
this.currentInfo.LocalName = name;
this.currentInfo.NamespaceURI = nspace;
this.currentInfo.Depth = this.recordDepth;
return true;
}
private void BeginComment() {
this.currentInfo.NodeType = XmlNodeType.Comment;
this.currentInfo.Depth = this.recordDepth;
}
private void AdjustDepth(int state) {
switch (state & StateMachine.DepthMask) {
case StateMachine.DepthUp:
this.recordDepth ++;
break;
case StateMachine.DepthDown:
this.recordDepth --;
break;
default:
break;
}
}
private void ResetRecord(int state) {
Debug.Assert(this.recordState == NoRecord || this.recordState == SomeRecord);
if ((state & StateMachine.BeginRecord) != 0) {
this.attributeCount = 0;
this.namespaceCount = 0;
this.currentInfo = this.mainNode;
this.currentInfo.Initialize(this.atoms.Empty, this.atoms.Empty, this.atoms.Empty);
this.currentInfo.NodeType = XmlNodeType.None;
this.currentInfo.IsEmptyTag = false;
this.currentInfo.htmlProps = null;
this.currentInfo.htmlAttrProps = null;
}
}
private void PopElementScope() {
if (this.popScope) {
this.scopeManager.PopScope();
this.popScope = false;
}
}
private Processor.OutputResult CheckRecordBegin(int state) {
Debug.Assert(this.recordState == NoRecord || this.recordState == SomeRecord);
if ((state & StateMachine.EndRecord) != 0) {
this.recordState = HaveRecord;
FinalizeRecord();
SetEmptyFlag(state);
return this.output.RecordDone(this);
}
else {
this.recordState = SomeRecord;
return Processor.OutputResult.Continue;
}
}
private Processor.OutputResult CheckRecordEnd(int state) {
Debug.Assert(this.recordState == NoRecord || this.recordState == SomeRecord);
if ((state & StateMachine.EndRecord) != 0) {
this.recordState = HaveRecord;
FinalizeRecord();
SetEmptyFlag(state);
return this.output.RecordDone(this);
}
else {
// For end event, if there is no end token, don't force token
return Processor.OutputResult.Continue;
}
}
private void SetEmptyFlag(int state) {
Debug.Assert(this.mainNode != null);
if ((state & StateMachine.BeginChild) != 0) {
this.mainNode.IsEmptyTag = false;
}
}
private void AnalyzeSpaceLang() {
Debug.Assert(this.mainNode.NodeType == XmlNodeType.Element);
for (int attr = 0; attr < this.attributeCount; attr ++) {
Debug.Assert(this.attributeList[attr] is BuilderInfo);
BuilderInfo info = (BuilderInfo) this.attributeList[attr];
if (Ref.Equal(info.Prefix, this.atoms.Xml)) {
OutputScope scope = this.scopeManager.CurrentElementScope;
if (Ref.Equal(info.LocalName, this.atoms.Lang)) {
scope.Lang = info.Value;
}
else if (Ref.Equal(info.LocalName, this.atoms.Space)) {
scope.Space = TranslateXmlSpace(info.Value);
}
}
}
}
private void FixupElement() {
Debug.Assert(this.mainNode.NodeType == XmlNodeType.Element);
if (Ref.Equal(this.mainNode.NamespaceURI, this.atoms.Empty)) {
this.mainNode.Prefix = this.atoms.Empty;
}
if (Ref.Equal(this.mainNode.Prefix, this.atoms.Empty)) {
if (Ref.Equal(this.mainNode.NamespaceURI, this.scopeManager.DefaultNamespace)) {
// Main Node is OK
}
else {
DeclareNamespace(this.mainNode.NamespaceURI, this.mainNode.Prefix);
}
}
else {
bool thisScope = false;
string nspace = this.scopeManager.ResolveNamespace(this.mainNode.Prefix, out thisScope);
if (nspace != null) {
if (! Ref.Equal(this.mainNode.NamespaceURI, nspace)) {
if (thisScope) { // Prefix conflict
this.mainNode.Prefix = GetPrefixForNamespace(this.mainNode.NamespaceURI);
}
else {
DeclareNamespace(this.mainNode.NamespaceURI, this.mainNode.Prefix);
}
}
}
else {
DeclareNamespace(this.mainNode.NamespaceURI, this.mainNode.Prefix);
}
}
OutputScope elementScope = this.scopeManager.CurrentElementScope;
elementScope.Prefix = this.mainNode.Prefix;
}
private void FixupAttributes(int attributeCount) {
for (int attr = 0; attr < attributeCount; attr ++) {
Debug.Assert(this.attributeList[attr] is BuilderInfo);
BuilderInfo info = (BuilderInfo) this.attributeList[attr];
if (Ref.Equal(info.NamespaceURI, this.atoms.Empty)) {
info.Prefix = this.atoms.Empty;
}
else {
if (Ref.Equal(info.Prefix, this.atoms.Empty)) {
info.Prefix = GetPrefixForNamespace(info.NamespaceURI);
}
else {
bool thisScope = false;
string nspace = this.scopeManager.ResolveNamespace(info.Prefix, out thisScope);
if (nspace != null) {
if (! Ref.Equal(info.NamespaceURI, nspace)) {
if(thisScope) { // prefix conflict
info.Prefix = GetPrefixForNamespace(info.NamespaceURI);
}
else {
DeclareNamespace(info.NamespaceURI, info.Prefix);
}
}
}
else {
DeclareNamespace(info.NamespaceURI, info.Prefix);
}
}
}
}
}
private void AppendNamespaces() {
for (int i = this.namespaceCount - 1; i >= 0; i --) {
BuilderInfo attribute = (BuilderInfo) this.attributeList[NewAttribute()];
attribute.Initialize((BuilderInfo)this.namespaceList[i]);
}
}
private void AnalyzeComment() {
Debug.Assert(this.mainNode.NodeType == XmlNodeType.Comment);
Debug.Assert((object) this.currentInfo == (object) this.mainNode);
StringBuilder newComment = null;
string comment = this.mainNode.Value;
bool minus = false;
int index = 0, begin = 0;
for (; index < comment.Length; index ++) {
switch (comment[index]) {
case s_Minus:
if (minus) {
if (newComment == null)
newComment = new StringBuilder(comment, begin, index, 2 * comment.Length);
else
newComment.Append(comment, begin, index - begin);
newComment.Append(s_SpaceMinus);
begin = index + 1;
}
minus = true;
break;
default:
minus = false;
break;
}
}
if (newComment != null) {
if (begin < comment.Length)
newComment.Append(comment, begin, comment.Length - begin);
if (minus)
newComment.Append(s_Space);
this.mainNode.Value = newComment.ToString();
}
else if (minus) {
this.mainNode.ValueAppend(s_Space, false);
}
}
private void AnalyzeProcessingInstruction() {
Debug.Assert(this.mainNode.NodeType == XmlNodeType.ProcessingInstruction || this.mainNode.NodeType == XmlNodeType.XmlDeclaration);
//Debug.Assert((object) this.currentInfo == (object) this.mainNode);
StringBuilder newPI = null;
string pi = this.mainNode.Value;
bool question = false;
int index = 0, begin = 0;
for (; index < pi.Length; index ++) {
switch (pi[index]) {
case s_Question:
question = true;
break;
case s_Greater:
if (question) {
if (newPI == null) {
newPI = new StringBuilder(pi, begin, index, 2 * pi.Length);
}
else {
newPI.Append(pi, begin, index - begin);
}
newPI.Append(s_SpaceGreater);
begin = index + 1;
}
question = false;
break;
default:
question = false;
break;
}
}
if (newPI != null) {
if (begin < pi.Length) {
newPI.Append(pi, begin, pi.Length - begin);
}
this.mainNode.Value = newPI.ToString();
}
}
private void FinalizeRecord() {
switch (this.mainNode.NodeType) {
case XmlNodeType.Element:
// Save count since FixupElement can add attribute...
int attributeCount = this.attributeCount;
FixupElement();
FixupAttributes(attributeCount);
AnalyzeSpaceLang();
AppendNamespaces();
break;
case XmlNodeType.Comment:
AnalyzeComment();
break;
case XmlNodeType.ProcessingInstruction:
AnalyzeProcessingInstruction();
break;
}
}
private int NewNamespace() {
if (this.namespaceCount >= this.namespaceList.Count) {
Debug.Assert(this.namespaceCount == this.namespaceList.Count);
this.namespaceList.Add(new BuilderInfo());
}
return this.namespaceCount ++;
}
private void DeclareNamespace(string nspace, string prefix) {
int index = NewNamespace();
Debug.Assert(this.namespaceList[index] != null && this.namespaceList[index] is BuilderInfo);
BuilderInfo ns = (BuilderInfo) this.namespaceList[index];
if (prefix == this.atoms.Empty) {
ns.Initialize(this.atoms.Empty, this.atoms.Xmlns, this.atoms.XmlnsNamespace);
}
else {
ns.Initialize(this.atoms.Xmlns, prefix, this.atoms.XmlnsNamespace);
}
ns.Depth = this.recordDepth;
ns.NodeType = XmlNodeType.Attribute;
ns.Value = nspace;
this.scopeManager.PushNamespace(prefix, nspace);
}
private string DeclareNewNamespace(string nspace) {
string prefix = this.scopeManager.GeneratePrefix(PrefixFormat);
DeclareNamespace(nspace, prefix);
return prefix;
}
internal string GetPrefixForNamespace(string nspace) {
string prefix = null;
if (this.scopeManager.FindPrefix(nspace, out prefix)) {
Debug.Assert(prefix != null && prefix.Length > 0);
return prefix;
}
else {
return DeclareNewNamespace(nspace);
}
}
private static XmlSpace TranslateXmlSpace(string space) {
if (space == "default") {
return XmlSpace.Default;
}
else if (space == "preserve") {
return XmlSpace.Preserve;
}
else {
return XmlSpace.None;
}
}
}
}
|