The Labs \ Source Viewer \ SSCLI \ System.Xml \ DtdParserProxy

  1. //------------------------------------------------------------------------------
  2. // <copyright file="XmlTextReaderHelpers.cs" company="Microsoft">
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. // </copyright>
  14. //------------------------------------------------------------------------------
  15. using System;
  16. using System.IO;
  17. using System.Text;
  18. using System.Security;
  19. using System.Xml.Schema;
  20. using System.Collections;
  21. using System.Diagnostics;
  22. using System.Globalization;
  23. using System.Collections.Generic;
  24. namespace System.Xml
  25. {
  26.    
  27.     internal partial class XmlTextReaderImpl
  28.     {
  29.        
  30.         //
  31.         // ParsingState
  32.         //
  33.         // Parsing state (aka. scanner data) - holds parsing buffer and entity input data information
  34.         private struct ParsingState
  35.         {
  36.             // character buffer
  37.             internal char[] chars;
  38.             internal int charPos;
  39.             internal int charsUsed;
  40.             internal Encoding encoding;
  41.             internal bool appendMode;
  42.            
  43.             // input stream & byte buffer
  44.             internal Stream stream;
  45.             internal Decoder decoder;
  46.             internal byte[] bytes;
  47.             internal int bytePos;
  48.             internal int bytesUsed;
  49.            
  50.             // input text reader
  51.             internal TextReader textReader;
  52.            
  53.             // current line number & position
  54.             internal int lineNo;
  55.             internal int lineStartPos;
  56.            
  57.             // base uri of the current entity
  58.             internal string baseUriStr;
  59.             internal Uri baseUri;
  60.            
  61.             // eof flag of the entity
  62.             internal bool isEof;
  63.             internal bool isStreamEof;
  64.            
  65.             // entity type & id
  66.             internal SchemaEntity entity;
  67.             internal int entityId;
  68.            
  69.             // normalization
  70.             internal bool eolNormalized;
  71.            
  72.             // EndEntity reporting
  73.             internal bool entityResolvedManually;
  74.            
  75.             internal void Clear()
  76.             {
  77.                 chars = null;
  78.                 charPos = 0;
  79.                 charsUsed = 0;
  80.                 encoding = null;
  81.                 stream = null;
  82.                 decoder = null;
  83.                 bytes = null;
  84.                 bytePos = 0;
  85.                 bytesUsed = 0;
  86.                 textReader = null;
  87.                 lineNo = 1;
  88.                 lineStartPos = -1;
  89.                 baseUriStr = string.Empty;
  90.                 baseUri = null;
  91.                 isEof = false;
  92.                 isStreamEof = false;
  93.                 eolNormalized = true;
  94.                 entityResolvedManually = false;
  95.             }
  96.            
  97.             internal void Close(bool closeInput)
  98.             {
  99.                 if (closeInput) {
  100.                     if (stream != null) {
  101.                         stream.Close();
  102.                     }
  103.                     else if (textReader != null) {
  104.                         textReader.Close();
  105.                     }
  106.                 }
  107.             }
  108.            
  109.             internal int LineNo {
  110.                 get { return lineNo; }
  111.             }
  112.            
  113.             internal int LinePos {
  114.                 get { return charPos - lineStartPos; }
  115.             }
  116.         }
  117.        
  118.         //
  119.         // XmlContext
  120.         //
  121.         private class XmlContext
  122.         {
  123.             internal XmlSpace xmlSpace;
  124.             internal string xmlLang;
  125.             internal string defaultNamespace;
  126.             internal XmlContext previousContext;
  127.            
  128.             internal XmlContext()
  129.             {
  130.                 xmlSpace = XmlSpace.None;
  131.                 xmlLang = string.Empty;
  132.                 defaultNamespace = string.Empty;
  133.                 previousContext = null;
  134.             }
  135.            
  136.             internal XmlContext(XmlContext previousContext)
  137.             {
  138.                 this.xmlSpace = previousContext.xmlSpace;
  139.                 this.xmlLang = previousContext.xmlLang;
  140.                 this.defaultNamespace = previousContext.defaultNamespace;
  141.                 this.previousContext = previousContext;
  142.             }
  143.         }
  144.        
  145.         //
  146.         // NoNamespaceManager
  147.         //
  148.         private class NoNamespaceManager : XmlNamespaceManager
  149.         {
  150.             public NoNamespaceManager() : base()
  151.             {
  152.             }
  153.             public override string DefaultNamespace {
  154.                 get { return string.Empty; }
  155.             }
  156.             public override void PushScope()
  157.             {
  158.             }
  159.             public override bool PopScope()
  160.             {
  161.                 return false;
  162.             }
  163.             public override void AddNamespace(string prefix, string uri)
  164.             {
  165.             }
  166.             public override void RemoveNamespace(string prefix, string uri)
  167.             {
  168.             }
  169.             public override IEnumerator GetEnumerator()
  170.             {
  171.                 return null;
  172.             }
  173.             public override IDictionary<string, string> GetNamespacesInScope(XmlNamespaceScope scope)
  174.             {
  175.                 return null;
  176.             }
  177.             public override string LookupNamespace(string prefix)
  178.             {
  179.                 return string.Empty;
  180.             }
  181.             public override string LookupPrefix(string uri)
  182.             {
  183.                 return null;
  184.             }
  185.             public override bool HasNamespace(string prefix)
  186.             {
  187.                 return false;
  188.             }
  189.         }
  190.        
  191.         //
  192.         // DtdParserProxy: IDtdParserAdapter proxy for XmlTextReaderImpl
  193.         //
  194.         internal class DtdParserProxy : IDtdParserAdapter
  195.         {
  196.             // Fields
  197.             private XmlTextReaderImpl reader;
  198.             private DtdParser dtdParser;
  199.             private SchemaInfo schemaInfo;
  200.            
  201.             // Constructors
  202.             internal DtdParserProxy(XmlTextReaderImpl reader)
  203.             {
  204.                 this.reader = reader;
  205.                 this.dtdParser = new DtdParser(this);
  206.             }
  207.            
  208.             internal DtdParserProxy(XmlTextReaderImpl reader, SchemaInfo schemaInfo)
  209.             {
  210.                 this.reader = reader;
  211.                 this.schemaInfo = schemaInfo;
  212.             }
  213.            
  214.             internal DtdParserProxy(string baseUri, string docTypeName, string publicId, string systemId, string internalSubset, XmlTextReaderImpl reader)
  215.             {
  216.                 this.reader = reader;
  217.                 this.dtdParser = new DtdParser(baseUri, docTypeName, publicId, systemId, internalSubset, this);
  218.             }
  219.            
  220.            
  221.             // DtdParser proxies
  222.             internal void Parse(bool saveInternalSubset)
  223.             {
  224.                 if (dtdParser == null) {
  225.                     throw new InvalidOperationException();
  226.                 }
  227.                 dtdParser.Parse(saveInternalSubset);
  228.             }
  229.            
  230.             internal SchemaInfo DtdSchemaInfo {
  231.                 get { return dtdParser != null ? dtdParser.SchemaInfo : schemaInfo; }
  232.             }
  233.            
  234.             internal string InternalDtdSubset {
  235.                 get {
  236.                     if (dtdParser == null) {
  237.                         throw new InvalidOperationException();
  238.                     }
  239.                     return dtdParser.InternalSubset;
  240.                 }
  241.             }
  242.            
  243.             // IDtdParserAdapter proxies
  244.             XmlNameTable IDtdParserAdapter.NameTable {
  245.                 get { return reader.DtdParserProxy_NameTable; }
  246.             }
  247.            
  248.             XmlNamespaceManager IDtdParserAdapter.NamespaceManager {
  249.                 get { return reader.DtdParserProxy_NamespaceManager; }
  250.             }
  251.            
  252.             bool IDtdParserAdapter.DtdValidation {
  253.                 get { return reader.DtdParserProxy_DtdValidation; }
  254.             }
  255.            
  256.             bool IDtdParserAdapter.Normalization {
  257.                 get { return reader.DtdParserProxy_Normalization; }
  258.             }
  259.            
  260.             bool IDtdParserAdapter.Namespaces {
  261.                 get { return reader.DtdParserProxy_Namespaces; }
  262.             }
  263.            
  264.             bool IDtdParserAdapter.V1CompatibilityMode {
  265.                 get { return reader.DtdParserProxy_V1CompatibilityMode; }
  266.             }
  267.            
  268.             Uri IDtdParserAdapter.BaseUri {
  269.                 get { return reader.DtdParserProxy_BaseUri; }
  270.             }
  271.            
  272.             bool IDtdParserAdapter.IsEof {
  273.                 get { return reader.DtdParserProxy_IsEof; }
  274.             }
  275.            
  276.             char[] IDtdParserAdapter.ParsingBuffer {
  277.                 get { return reader.DtdParserProxy_ParsingBuffer; }
  278.             }
  279.            
  280.             int IDtdParserAdapter.ParsingBufferLength {
  281.                 get { return reader.DtdParserProxy_ParsingBufferLength; }
  282.             }
  283.            
  284.             int IDtdParserAdapter.CurrentPosition {
  285.                 get { return reader.DtdParserProxy_CurrentPosition; }
  286.                 set { reader.DtdParserProxy_CurrentPosition = value; }
  287.             }
  288.            
  289.             int IDtdParserAdapter.EntityStackLength {
  290.                 get { return reader.DtdParserProxy_EntityStackLength; }
  291.             }
  292.            
  293.             bool IDtdParserAdapter.IsEntityEolNormalized {
  294.                 get { return reader.DtdParserProxy_IsEntityEolNormalized; }
  295.             }
  296.            
  297.             ValidationEventHandler IDtdParserAdapter.EventHandler {
  298.                 get { return reader.DtdParserProxy_EventHandler; }
  299.                 set { reader.DtdParserProxy_EventHandler = value; }
  300.             }
  301.            
  302.             void IDtdParserAdapter.OnNewLine(int pos)
  303.             {
  304.                 reader.DtdParserProxy_OnNewLine(pos);
  305.             }
  306.            
  307.             int IDtdParserAdapter.LineNo {
  308.                 get { return reader.DtdParserProxy_LineNo; }
  309.             }
  310.            
  311.             int IDtdParserAdapter.LineStartPosition {
  312.                 get { return reader.DtdParserProxy_LineStartPosition; }
  313.             }
  314.            
  315.             int IDtdParserAdapter.ReadData()
  316.             {
  317.                 return reader.DtdParserProxy_ReadData();
  318.             }
  319.            
  320.             void IDtdParserAdapter.SendValidationEvent(XmlSeverityType severity, XmlSchemaException exception)
  321.             {
  322.                 reader.DtdParserProxy_SendValidationEvent(severity, exception);
  323.             }
  324.            
  325.             int IDtdParserAdapter.ParseNumericCharRef(BufferBuilder internalSubsetBuilder)
  326.             {
  327.                 return reader.DtdParserProxy_ParseNumericCharRef(internalSubsetBuilder);
  328.             }
  329.            
  330.             int IDtdParserAdapter.ParseNamedCharRef(bool expand, BufferBuilder internalSubsetBuilder)
  331.             {
  332.                 return reader.DtdParserProxy_ParseNamedCharRef(expand, internalSubsetBuilder);
  333.             }
  334.            
  335.             void IDtdParserAdapter.ParsePI(BufferBuilder sb)
  336.             {
  337.                 reader.DtdParserProxy_ParsePI(sb);
  338.             }
  339.            
  340.             void IDtdParserAdapter.ParseComment(BufferBuilder sb)
  341.             {
  342.                 reader.DtdParserProxy_ParseComment(sb);
  343.             }
  344.            
  345.             bool IDtdParserAdapter.PushEntity(SchemaEntity entity, int entityId)
  346.             {
  347.                 return reader.DtdParserProxy_PushEntity(entity, entityId);
  348.             }
  349.            
  350.             bool IDtdParserAdapter.PopEntity(out SchemaEntity oldEntity, out int newEntityId)
  351.             {
  352.                 return reader.DtdParserProxy_PopEntity(out oldEntity, out newEntityId);
  353.             }
  354.            
  355.             bool IDtdParserAdapter.PushExternalSubset(string systemId, string publicId)
  356.             {
  357.                 return reader.DtdParserProxy_PushExternalSubset(systemId, publicId);
  358.             }
  359.            
  360.             void IDtdParserAdapter.PushInternalDtd(string baseUri, string internalDtd)
  361.             {
  362.                 reader.DtdParserProxy_PushInternalDtd(baseUri, internalDtd);
  363.             }
  364.            
  365.             void IDtdParserAdapter.Throw(Exception e)
  366.             {
  367.                 reader.DtdParserProxy_Throw(e);
  368.             }
  369.            
  370.             void IDtdParserAdapter.OnSystemId(string systemId, LineInfo keywordLineInfo, LineInfo systemLiteralLineInfo)
  371.             {
  372.                 reader.DtdParserProxy_OnSystemId(systemId, keywordLineInfo, systemLiteralLineInfo);
  373.             }
  374.            
  375.             void IDtdParserAdapter.OnPublicId(string publicId, LineInfo keywordLineInfo, LineInfo publicLiteralLineInfo)
  376.             {
  377.                 reader.DtdParserProxy_OnPublicId(publicId, keywordLineInfo, publicLiteralLineInfo);
  378.             }
  379.         }
  380.        
  381.         //
  382.         // NodeData
  383.         //
  384.         private class NodeData : IComparable
  385.         {
  386.             // static instance with no data - is used when XmlTextReader is closed
  387.             static NodeData s_None;
  388.            
  389.             // NOTE: Do not use this property for reference comparison. It may not be unique.
  390.             static internal NodeData None {
  391.                 get {
  392.                     if (s_None == null) {
  393.                         // no locking; s_None is immutable so it's not a problem that it may get initialized more than once
  394.                         s_None = new NodeData();
  395.                     }
  396.                     return s_None;
  397.                 }
  398.             }
  399.            
  400.             // type
  401.             internal XmlNodeType type;
  402.            
  403.             // name
  404.             internal string localName;
  405.             internal string prefix;
  406.             internal string ns;
  407.             internal string nameWPrefix;
  408.            
  409.             // value:
  410.             // value == null -> the value is kept in the 'chars' buffer starting at valueStartPos and valueLength long
  411.             string value;
  412.             char[] chars;
  413.             int valueStartPos;
  414.             int valueLength;
  415.            
  416.             // main line info
  417.             internal LineInfo lineInfo;
  418.            
  419.             // second line info
  420.             internal LineInfo lineInfo2;
  421.            
  422.             // quote char for attributes
  423.             internal char quoteChar;
  424.            
  425.             // depth
  426.             internal int depth;
  427.            
  428.             // empty element / default attribute
  429.             bool isEmptyOrDefault;
  430.            
  431.             // entity id
  432.             internal int entityId;
  433.            
  434.             // helper members
  435.             internal bool xmlContextPushed;
  436.            
  437.             // attribute value chunks
  438.             internal NodeData nextAttrValueChunk;
  439.            
  440.             // type info
  441.             internal object schemaType;
  442.             internal object typedValue;
  443.            
  444.             internal NodeData()
  445.             {
  446.                 Clear(XmlNodeType.None);
  447.                 xmlContextPushed = false;
  448.             }
  449.            
  450.             internal int LineNo {
  451.                 get { return lineInfo.lineNo; }
  452.             }
  453.            
  454.             internal int LinePos {
  455.                 get { return lineInfo.linePos; }
  456.             }
  457.            
  458.             internal bool IsEmptyElement {
  459.                 get { return type == XmlNodeType.Element && isEmptyOrDefault; }
  460.                 set {
  461.                     Debug.Assert(type == XmlNodeType.Element);
  462.                     isEmptyOrDefault = value;
  463.                 }
  464.             }
  465.            
  466.             internal bool IsDefaultAttribute {
  467.                 get { return type == XmlNodeType.Attribute && isEmptyOrDefault; }
  468.                 set {
  469.                     Debug.Assert(type == XmlNodeType.Attribute);
  470.                     isEmptyOrDefault = value;
  471.                 }
  472.             }
  473.            
  474.             internal bool ValueBuffered {
  475.                 get { return value == null; }
  476.             }
  477.            
  478.             internal string StringValue {
  479.                 get {
  480.                     Debug.Assert(valueStartPos >= 0 || this.value != null, "Value not ready.");
  481.                    
  482.                     if (this.value == null) {
  483.                         this.value = new string(chars, valueStartPos, valueLength);
  484.                     }
  485.                     return this.value;
  486.                 }
  487.             }
  488.            
  489.             internal void TrimSpacesInValue()
  490.             {
  491.                 if (ValueBuffered) {
  492.                     XmlComplianceUtil.StripSpaces(chars, valueStartPos, ref valueLength);
  493.                 }
  494.                 else {
  495.                     value = XmlComplianceUtil.StripSpaces(value);
  496.                 }
  497.             }
  498.            
  499.             internal void Clear(XmlNodeType type)
  500.             {
  501.                 this.type = type;
  502.                 ClearName();
  503.                 value = string.Empty;
  504.                 valueStartPos = -1;
  505.                 nameWPrefix = string.Empty;
  506.                 schemaType = null;
  507.                 typedValue = null;
  508.             }
  509.            
  510.             internal void ClearName()
  511.             {
  512.                 localName = string.Empty;
  513.                 prefix = string.Empty;
  514.                 ns = string.Empty;
  515.                 nameWPrefix = string.Empty;
  516.             }
  517.            
  518.             internal void SetLineInfo(int lineNo, int linePos)
  519.             {
  520.                 lineInfo.Set(lineNo, linePos);
  521.             }
  522.            
  523.             internal void SetLineInfo2(int lineNo, int linePos)
  524.             {
  525.                 lineInfo2.Set(lineNo, linePos);
  526.             }
  527.            
  528.             internal void SetValueNode(XmlNodeType type, string value)
  529.             {
  530.                 Debug.Assert(value != null);
  531.                
  532.                 this.type = type;
  533.                 ClearName();
  534.                 this.value = value;
  535.                 this.valueStartPos = -1;
  536.             }
  537.            
  538.             internal void SetValueNode(XmlNodeType type, char[] chars, int startPos, int len)
  539.             {
  540.                 this.type = type;
  541.                 ClearName();
  542.                
  543.                 this.value = null;
  544.                 this.chars = chars;
  545.                 this.valueStartPos = startPos;
  546.                 this.valueLength = len;
  547.             }
  548.            
  549.             internal void SetNamedNode(XmlNodeType type, string localName)
  550.             {
  551.                 SetNamedNode(type, localName, string.Empty, localName);
  552.             }
  553.            
  554.             internal void SetNamedNode(XmlNodeType type, string localName, string prefix, string nameWPrefix)
  555.             {
  556.                 Debug.Assert(localName != null);
  557.                 Debug.Assert(localName.Length > 0);
  558.                
  559.                 this.type = type;
  560.                 this.localName = localName;
  561.                 this.prefix = prefix;
  562.                 this.nameWPrefix = nameWPrefix;
  563.                 this.ns = string.Empty;
  564.                 this.value = string.Empty;
  565.                 this.valueStartPos = -1;
  566.             }
  567.            
  568.             internal void SetValue(string value)
  569.             {
  570.                 this.valueStartPos = -1;
  571.                 this.value = value;
  572.             }
  573.            
  574.             internal void SetValue(char[] chars, int startPos, int len)
  575.             {
  576.                 this.value = null;
  577.                 this.chars = chars;
  578.                 this.valueStartPos = startPos;
  579.                 this.valueLength = len;
  580.             }
  581.            
  582.             internal void OnBufferInvalidated()
  583.             {
  584.                 if (this.value == null) {
  585.                     Debug.Assert(valueStartPos != -1);
  586.                     Debug.Assert(chars != null);
  587.                     this.value = new string(chars, valueStartPos, valueLength);
  588.                 }
  589.                 valueStartPos = -1;
  590.             }
  591.            
  592.             internal string GetAtomizedValue(XmlNameTable nameTable)
  593.             {
  594.                 if (this.value == null) {
  595.                     Debug.Assert(valueStartPos != -1);
  596.                     Debug.Assert(chars != null);
  597.                     return nameTable.Add(chars, valueStartPos, valueLength);
  598.                 }
  599.                 else {
  600.                     return nameTable.Add(this.value);
  601.                 }
  602.             }
  603.            
  604.             internal void CopyTo(BufferBuilder sb)
  605.             {
  606.                 CopyTo(0, sb);
  607.             }
  608.            
  609.             internal void CopyTo(int valueOffset, BufferBuilder sb)
  610.             {
  611.                 if (value == null) {
  612.                     Debug.Assert(valueStartPos != -1);
  613.                     Debug.Assert(chars != null);
  614.                     sb.Append(chars, valueStartPos + valueOffset, valueLength - valueOffset);
  615.                 }
  616.                 else {
  617.                     if (valueOffset <= 0) {
  618.                         sb.Append(value);
  619.                     }
  620.                     else {
  621.                         sb.Append(value, valueOffset, value.Length - valueOffset);
  622.                     }
  623.                 }
  624.             }
  625.            
  626.             internal int CopyTo(int valueOffset, char[] buffer, int offset, int length)
  627.             {
  628.                 if (value == null) {
  629.                     Debug.Assert(valueStartPos != -1);
  630.                     Debug.Assert(chars != null);
  631.                     int copyCount = valueLength - valueOffset;
  632.                     if (copyCount > length) {
  633.                         copyCount = length;
  634.                     }
  635.                     Buffer.BlockCopy(chars, (valueStartPos + valueOffset) * 2, buffer, offset * 2, copyCount * 2);
  636.                     return copyCount;
  637.                 }
  638.                 else {
  639.                     int copyCount = value.Length - valueOffset;
  640.                     if (copyCount > length) {
  641.                         copyCount = length;
  642.                     }
  643.                     value.CopyTo(valueOffset, buffer, offset, copyCount);
  644.                     return copyCount;
  645.                 }
  646.             }
  647.            
  648.             internal int CopyToBinary(IncrementalReadDecoder decoder, int valueOffset)
  649.             {
  650.                 if (value == null) {
  651.                     Debug.Assert(valueStartPos != -1);
  652.                     Debug.Assert(chars != null);
  653.                     return decoder.Decode(chars, valueStartPos + valueOffset, valueLength - valueOffset);
  654.                 }
  655.                 else {
  656.                     return decoder.Decode(value, valueOffset, value.Length - valueOffset);
  657.                 }
  658.             }
  659.            
  660.             internal void AdjustLineInfo(int valueOffset, bool isNormalized, ref LineInfo lineInfo)
  661.             {
  662.                 if (valueOffset == 0) {
  663.                     return;
  664.                 }
  665.                 if (valueStartPos != -1) {
  666.                     XmlTextReaderImpl.AdjustLineInfo(chars, valueStartPos, valueStartPos + valueOffset, isNormalized, ref lineInfo);
  667.                 }
  668.                 else {
  669.                     char[] chars = value.ToCharArray(0, valueOffset);
  670.                     XmlTextReaderImpl.AdjustLineInfo(chars, 0, chars.Length, isNormalized, ref lineInfo);
  671.                 }
  672.             }
  673.            
  674.             // This should be inlined by JIT compiler
  675.             internal string GetNameWPrefix(XmlNameTable nt)
  676.             {
  677.                 if (nameWPrefix != null) {
  678.                     return nameWPrefix;
  679.                 }
  680.                 else {
  681.                     return CreateNameWPrefix(nt);
  682.                 }
  683.             }
  684.            
  685.             internal string CreateNameWPrefix(XmlNameTable nt)
  686.             {
  687.                 Debug.Assert(nameWPrefix == null);
  688.                 if (prefix.Length == 0) {
  689.                     nameWPrefix = localName;
  690.                 }
  691.                 else {
  692.                     nameWPrefix = nt.Add(string.Concat(prefix, ":", localName));
  693.                 }
  694.                 return nameWPrefix;
  695.             }
  696.            
  697.             int IComparable.CompareTo(object obj)
  698.             {
  699.                 NodeData other = obj as NodeData;
  700.                 if (other != null) {
  701.                     if (Ref.Equal(localName, other.localName)) {
  702.                         if (Ref.Equal(ns, other.ns)) {
  703.                             return 0;
  704.                         }
  705.                         else {
  706.                             return string.CompareOrdinal(ns, other.ns);
  707.                         }
  708.                     }
  709.                     else {
  710.                         return string.CompareOrdinal(localName, other.localName);
  711.                     }
  712.                 }
  713.                 else {
  714.                     Debug.Assert(false, "We should never get to this point.");
  715.                     return GetHashCode().CompareTo(other.GetHashCode());
  716.                 }
  717.             }
  718.         }
  719.     }
  720. }

Developer Fusion