Пример #1
0
        public void ctor_NullSpecs_NullNodeDefs()
        {
            //arrange
            var actual = new XmlNodePath(null as string);

            //act

            //assert
            actual.NodeDefs.Should().BeNull();
        }
Пример #2
0
        public void ctor_EmptySpecs_NoNodeDefs()
        {
            //arrange
            var actual = new XmlNodePath(string.Empty);

            //act

            //assert
            actual.NodeDefs.Count.Should().Be(0);
        }
Пример #3
0
        public void TryRemoveAt_is_successful(string expectedAfter, string nodePath, string removedFragment, string before)
        {
            var beforeDoc          = ParseDoc(before);
            var expectedAfterDoc   = ParseDoc(expectedAfter);
            var path               = XmlNodePath.Parse(nodePath);
            var removedCompareNode = ParseDoc(string.Format("<r>{0}</r>", removedFragment)).Root.Nodes().Single();

            removedCompareNode.Remove();
            Assert.That(beforeDoc.TryRemove(path, removedCompareNode), Is.True);
            Assert.That(beforeDoc.DeepEquals(expectedAfterDoc), Is.True);
        }
Пример #4
0
        public void TryInsertAt_is_successful(string before, string nodePath, string insertedFragment, string expectedAfter)
        {
            var beforeDoc        = ParseDoc(before);
            var expectedAfterDoc = ParseDoc(expectedAfter);
            var path             = XmlNodePath.Parse(nodePath);
            var inserted         = ParseDoc(string.Format("<r>{0}</r>", insertedFragment)).Root.Nodes().Single();

            inserted.Remove();
            Assert.That(beforeDoc.TryInsert(path, inserted), Is.True);
            Assert.That(beforeDoc.DeepEquals(expectedAfterDoc), Is.True);
        }
        /// <summary>
        /// Creates an instance of an XML dispenser for a single target.
        /// </summary>
        /// <param name="writer"></param>
        /// <param name="targetNo">1 based target number.</param>
        /// <param name="settings"></param>
        /// <param name="outputIsAsync"></param>
        internal XmlDispenserForTarget(TextWriter writer, int targetNo, string settings, bool outputIsAsync) : base(writer, targetNo)
            //Note that writer is passed to base class, even though it is not used there (all relevant methods are overridden, so null could've been passed instead).
            //This is because base.Dispose (called by Dispose in this class) disposes the writer (it is unclear whether XmlWriter.Dispose disposes underlying writer).
        {
            var settingDict = settings?.SplitPairsToListOfTuples()?.ToDictionary(t => t.Item1, t => t.Item2);

            // Discrete settings to unpack:
            //    CollectionNode   - "xpath" defining the collection of clusters/records (may be null/empty, in which case output will contain XML fragment where each root constitutes record or cluster).
            //    ClusterNode      - "xpath" defining clusters node within collection node (null/empty means record nodes are directly inside collection node).
            //    RecordNode       - "xpath" defining record node within cluster node (or collection node if cluster node is empty). RecordNode is mandatory, if absent "__record__" is assumed.
            //    AttributeFields  - a semicolon-separated list of field names (item keys) to be projected into XML as attributes of the record node (and not inner nodes).
            //    IndentChars      - string to use when indenting, e.g. "\t" or "  "; allows "pretty-print" XML output; when absent, no indenting takes place.
            //    NewLineChars     - allows "pretty-print" XML output
            // "xpath" is always relative (no need for ./), each of the nodes is separated by /.
            _collNodePath  = new XmlNodePath(settingDict.GetStringSetting("CollectionNode"));
            _clstrNodePath = new XmlNodePath(settingDict.GetStringSetting("ClusterNode"));
            _recNodePath   = new XmlNodePath(settingDict.GetStringSetting("RecordNode"));
            if (_recNodePath.IsEmpty)
            {
                _recNodePath = new XmlNodePath("__record__");                     //RecordNode is mandatory; if absent in config, __record__ is assumed
            }
            var attrFlds = settingDict.GetStringSetting("AttributeFields")?.ToListOfStrings(';');

            _attributeFields = attrFlds == null ? new HashSet <string>() : new HashSet <string>(attrFlds);
            var indentChars  = settingDict.GetStringSetting("IndentChars");
            var newLineChars = settingDict.GetStringSetting("NewLineChars");

            _observeClusters = !_clstrNodePath.IsEmpty;
            _clusterDepth    = _observeClusters ? _clstrNodePath.NodeDefs.Count : 0;

            var writerSettings = new XmlWriterSettings()
            {
                ConformanceLevel = _collNodePath.IsEmpty ? ConformanceLevel.Fragment : ConformanceLevel.Document
            };

            if (indentChars != null)
            {
                writerSettings.Indent      = true;
                writerSettings.IndentChars = indentChars;
            }
            if (newLineChars != null)
            {
                writerSettings.NewLineChars = newLineChars;
            }

            writerSettings.Async = outputIsAsync;

            _xmlWriter = XmlWriter.Create(writer, writerSettings);

            _currClstrNo = 0; //will stay at 0 (undetermined) unless ClusterNode defined
            _atStart     = true;
        }
        /// <summary>
        /// Asynchronously write a series of starting nodes based on the path given
        /// </summary>
        /// <param name="nodesToWrite"></param>
        /// <returns></returns>
        private async Task WriteStartNodesAsync(XmlNodePath nodesToWrite)
        {
            //note that XmlWriter remembers all nodes that were started, so there is no need to remember them
            Debug.Assert(!nodesToWrite.IsEmpty);
            _ = nodesToWrite.NodeDefs.Skip(1).Any() ? new XmlNodePath(nodesToWrite.NodeDefs.Skip(1)) //the remaining nodes to write during recursive calls
                                                 : null;                                             //end of recursion

            await WriteStartNodeAsync(nodesToWrite.NodeDefs[0]);                                     //head

            if (nodesToWrite.NodeDefs.Skip(1).Any())                                                 //tail
            {                                                                                        //tail exists; inner node(s) are written using recursion
                await WriteStartNodesAsync(new XmlNodePath(nodesToWrite.NodeDefs.Skip(1)));
            }
        }
Пример #7
0
        public void ctor_SimpleSpecs_CorrectData()
        {
            //arrange
            var actual = new XmlNodePath("TheOnlyNode");

            //act
            var nodeDefs = actual.NodeDefs;

            //assert
            nodeDefs.Count.Should().Be(1);
            var nodeDef = nodeDefs[0];

            nodeDef.Name.Should().Be("TheOnlyNode");
            nodeDef.GetAttributes().Count.Should().Be(0);
        }
Пример #8
0
        public void IsEmpty_EmptyNode_ReturnsTrue()
        {
            //arrange
            var actual  = new XmlNodePath(null as string);
            var actual2 = new XmlNodePath(null as List <XmlNodeDef>);
            var actual3 = new XmlNodePath(string.Empty);

            //act

            //assert
            actual.NodeDefs.Should().BeNull();
            actual.IsEmpty.Should().BeTrue();
            actual2.NodeDefs.Should().BeNull();
            actual2.IsEmpty.Should().BeTrue();
            actual3.NodeDefs.Count.Should().Be(0); //empty string will result in empty list
            actual3.IsEmpty.Should().BeTrue();
        }
        /// <summary>
        /// Asynchronously send items of the current record to XML output.
        /// </summary>
        /// <param name="xrecNodePath"></param>
        /// <param name="line"></param>
        /// <returns></returns>
        private async Task WriteXrecordAsync(XmlNodePath xrecNodePath, ExternalLine line)
        {
            Debug.Assert(line.GetType() == typeof(Xrecord));

            var head = xrecNodePath.NodeDefs[0];
            var tail = xrecNodePath.NodeDefs.Skip(1).Any() ? new XmlNodePath(xrecNodePath.NodeDefs.Skip(1)) //XmlNodePath representing the remaining node patterns
                                                        : null;                                             //end of recursion

            await _xmlWriter.WriteStartElementAsync(null, head.Name, null);                                 //start element

            var attrsToWrite = head.GetAttributes();

            if (tail == null) //leaf level (end of recursion)
            {
                //Don't write records right away as some items may need to be written as attributes (and not inner nodes)
                // Instead, collect all attributes and inner nodes to write.
                //Items to be written as attributes are those that start with @ or are listed in AttributeFields part of XmlJsonOutputSettings.
                var innerNodesToWrite = new List <Tuple <string, object> >();
                foreach (var item in line.Items)
                {
                    if (item.Item1[0] == '@')
                    { //attribute
                        attrsToWrite.Add(Tuple.Create(item.Item1.Substring(1), item.Item2));
                    }
                    else if (_attributeFields.Contains(item.Item1))
                    { //attribute
                        attrsToWrite.Add(item);
                    }
                    else
                    { //inner node
                        innerNodesToWrite.Add(item);
                    }
                }
                attrsToWrite.ForEach(async a => await _xmlWriter.WriteAttributeStringAsync(null, a.Item1, null, a.Item2?.ToString()));    //attributes (note that ToString() for string is just a reference to itself, so no perfromance penalty)
                innerNodesToWrite.ForEach(async n => await _xmlWriter.WriteElementStringAsync(null, n.Item1, null, n.Item2?.ToString())); //inner nodes
            }
            else
            {                                                                                                                          //we're not at the leaf level yet, write the attributes and recurse
                attrsToWrite.ForEach(async a => await _xmlWriter.WriteAttributeStringAsync(null, a.Item1, null, a.Item2?.ToString())); //attributes
                await WriteXrecordAsync(tail, line);
            }
            await _xmlWriter.WriteEndElementAsync(); //end element (note that it will skip full end tag in case of no inner nodes (WriteFullEndElementAsync can be used to avoid this)
        }
Пример #10
0
        public void ctor_ComplexSpecs_CorrectData()
        {
            //arrange
            var actual = new XmlNodePath("./FirstNode[@id=1]//SecondNode");

            //act
            var nodeDefs = actual.NodeDefs;

            //assert
            nodeDefs.Count.Should().Be(2);
            var nodeDef = nodeDefs[0];

            nodeDef.Name.Should().Be("FirstNode");
            var attrs = nodeDef.GetAttributes();

            attrs.Count.Should().Be(1);
            attrs[0].Item1.Should().Be("id");
            attrs[0].Item2.Should().Be("1");
            nodeDef = nodeDefs[1];
            nodeDef.Name.Should().Be("SecondNode");
            nodeDef.GetAttributes().Count.Should().Be(0);
        }
Пример #11
0
        private int _clstrBaseDepth, _recBaseDepth; //level limit when advancing to cluster/record node (i.e. level below collection/cluster node respectively)

        /// <summary>
        /// Create an instance of the record supplier from XML
        /// </summary>
        /// <param name="reader">Underlying text reader.</param>
        /// <param name="sourceNo"></param>
        /// <param name="settings"></param>
        /// <param name="intakeIsAsync"></param>
        internal XmlFeederForSource(TextReader reader, int sourceNo, string settings, bool intakeIsAsync) : base(reader, sourceNo)
            //Note that reader is passed to base class, even though it is not used there (all relevant methods are overridden, so null could've been passed instead).
            //This is because base.Dispose (called by Dispose in this class) disposes the reader (it is unclear whether XmlReader.Dispose disposes underlying reader).
        {
            _xmlReader = XmlReader.Create(reader, new XmlReaderSettings()
            {
                ConformanceLevel             = ConformanceLevel.Fragment,
                IgnoreWhitespace             = true,
                IgnoreComments               = true,
                IgnoreProcessingInstructions = true,
                Async = intakeIsAsync
            });

            var settingDict = settings?.SplitPairsToListOfTuples()?.ToDictionary(t => t.Item1, t => t.Item2);

            // Discrete settings to unpack:
            //    CollectionNode      - "xpath" to the collection of clusters/records (may be null/empty, in which case input stream contains XML fragment where each root constitutes record or cluster)
            //    ClusterNode         - "xpath" to cluster node within collection node (null/empty means single record clusters)
            //    RecordNode          - "xpath" to record node within cluster node (or collection node if cluster node is empty)
            //    IncludeExplicitText - true to include explicit text in RecordNode; false (default) to ignore it
            //    IncludeAttributes   - true to include attributes (prefixed by @); truePlain to include attributes w/o prefix; false (default) to ignore them
            // "xpath" is always relative (no need for ./), each of the nodes (separated by /) can contain attribute
            _collNodePath        = new XmlNodePath(settingDict.GetStringSetting("CollectionNode"));
            _clstrNodePath       = new XmlNodePath(settingDict.GetStringSetting("ClusterNode"));
            _xrecNodePath        = new XmlNodePath(settingDict.GetStringSetting("RecordNode"));
            _includeExplicitText = settingDict.GetStringSetting("IncludeExplicitText")?.ToLower() == "true";
            var inclAttrsSetting = settingDict.GetStringSetting("IncludeAttributes")?.ToLower();

            _includeAttributes        = inclAttrsSetting.SafeSubstring(0, 4) == "true";
            _addPrefixToAttrKeys      = inclAttrsSetting != "trueplain";
            _observeClusters          = !_clstrNodePath.IsEmpty;
            _addClusterDataToTraceBin = settingDict.GetStringSetting("AddClusterDataToTraceBin")?.ToLower() == "true";

            _currClstrCnt = 0; //will stay at 0 (undetermined) unless ClusterNode defined
            _readerState  = ReaderState.BeforeCollection;

            _clstrBaseDepth = _recBaseDepth = -1;
        }
Пример #12
0
        /// <summary>
        /// Asynchronously read _xmlReader until at given location.
        /// </summary>
        /// <param name="nodePath">Location path relative to current position.</param>
        /// <param name="baseDepth">Level (Depth) passed at the initial call to limit the scope of search for inner elements.</param>
        /// <param name="depth">Level (Depth) of the initial call this method (intended to only be passed during recursive calls).</param>
        /// <param name="initPath">Location path from the initial call this method (intended to only be passed during recursive calls - needed in case of reset).</param>
        /// <param name="firstInCluster">Indicator that the record is first in cluster (complicated/unreliable - to be refactored).</param>
        /// <param name="addAttrsToTraceBin">Indicator to add collected attribute data to trace bin; applicable only if AddClusterDataToTraceBin (which in turn is only applicable if _observeClusters).</param>
        /// <returns>Task with true if succeeded or false if location not found.</returns>
        private async Task <bool> AdvanceToLocationAsync(XmlNodePath nodePath, int baseDepth, int depth = -9, XmlNodePath initPath = null, bool firstInCluster = false, bool addAttrsToTraceBin = false)
        {
            Debug.Assert(!nodePath.IsEmpty);

            if (initPath == null)
            {
                initPath = nodePath;
            }

            var head = nodePath.NodeDefs[0];                                                        //XmlNodeDef representing the first xpath fragment (before the first /), i.e. current node pattern
            var tail = nodePath.NodeDefs.Skip(1).Any() ? new XmlNodePath(nodePath.NodeDefs.Skip(1)) //XmlNodePath representing the xpath fragments after the first /, i.e. the remaining node patterns
                                                    : null;                                         //end of recursion

            do
            {
            }   //advance till next element (ignore e.g. embedded text)
            while (await _xmlReader.ReadAsync() && !_xmlReader.IsStartElement() && _xmlReader.Depth > baseDepth);
            if (_xmlReader.EOF)
            {
                return(false);
            }

            //Distinction between initial level and base level :
            // initDepth - level at the beginning of initial (external) call (then passed with each recursive iteration)
            // baseDepth - level limit when advancing to next element (e.g. if advancing to cluster, it is the collection level)
            //Always: initDepth >= baseDepth (i.e. each call is made within level limit)

            int initDepth = depth == -9 ? _xmlReader.Depth : depth; //-9 means initial call from outside (as opposed to recursive call)

            if (MatchFound(head))
            {
                if (addAttrsToTraceBin)
                {
                    _traceBinKeyPrefix = CreateKey(_traceBinKeyPrefix);
                    GetAttributes(_traceBinKeyPrefix, false).ToList().ForEach(t => _traceBin.Add(t.Item1, t.Item2));
                }
                initDepth++;
                //are we there yet?
                if (tail == null || await AdvanceToLocationAsync(tail, baseDepth, initDepth, tail, false, addAttrsToTraceBin))
                {
                    return(true);                                                                                                       //success!
                }
            }

            //no match here, attempt to go back to the initial level and start over
            do
            {
                if (_xmlReader.Depth < initDepth)
                {
                    return(false);                           //beyond the initial level
                }
                if (_xmlReader.Depth == initDepth)
                { //at initial level, try again (or fail search if first in cluster)
                    if (_xmlReader.NodeType == XmlNodeType.EndElement && !firstInCluster)
                    {
                        return(false);
                    }
                    //TODO: refactor (remove reliance on firstInCluster) - too complicated.
                    return(await AdvanceToLocationAsync(initPath, baseDepth));
                }
            } while (await _xmlReader.ReadAsync());

            //end of stream, no xpath found
            return(false);
        }