0

常に追加される XML ファイルがあります。XML からデータを繰り返し読み込む必要がありますが、パスごとに、前回の実行で処理したデータを取得したくありません。

ファイルの長さは処理時にわかっているので、ファイルの長さ (末尾の /Contacts タグを除く) を使用して、最後に中断した場所を特定できると考えています。これを知っている場合、ファイル内の特定のバイト位置から始まるすべての Contact タグを取得する最良の方法は何ですか?

<?xml version="1.0"?>
<Contacts>
    <Contact>
      <Name>Todd</Name>
      <Email>todd@blah.com</Email>
  </Contact>
    <Contact>
      <Name>Sarah</Name>
      <Email>sarah@blah.com</Email>
  </Contact>
</Contacts>

このコード ブロックは、すべての連絡先を取得します。最初のコンタクト後 (バイト 116) にのみデータを取得するように制限したいと思います。

var xdoc = XDocument.Load(PATH_TO_FILE);
var contact = xdoc.Descendants("Contact").Select(x => (string)x).ToArray();
4

4 に答える 4

2

それでも特定のオフセットから読み取り、高レベルを維持したい場合。これは、終了タグのみを持つドキュメントのコンテンツを他のルート要素に配置する XmlTailReader です。

class XmlTailReader : XmlReader
{
    private readonly XmlReader _reader;
    private readonly XmlReader _fakeReader;
    private int _level;
    enum Fake { Start, Align, None, End };
    private Fake _fake;

    public XmlTailReader(XmlReader reader, string rootTag = "root")
    {
        _reader = reader;
        _fake = Fake.Start;

        var doc = new XmlDocument();
        var root = doc.CreateElement(rootTag);
        doc.AppendChild(root);
        // make sure that we'll get Element/EndElement
        root.AppendChild(doc.CreateComment("dummy")); 
        _fakeReader = new XmlNodeReader(root);
    }

    private XmlReader Proxy
    {
        get
        {
            switch(_fake)
            {
            case Fake.Start:
            case Fake.Align:
            case Fake.End:
                return _fakeReader;
            default:
                return _reader;
            }
        }
    }

    public override bool Read()
    {
        switch(_fake)
        {
        case Fake.Start:
            if (!_fakeReader.Read()) return false;
            if (NodeType == XmlNodeType.Element)
            {
                ++_level;
                _fake = Fake.Align;
            }
            return true;
        case Fake.Align:
            _fake = Fake.None;
            while(true) // align to first Element
            {
                if (!_reader.Read()) return false;
                if (NodeType == XmlNodeType.Element)
                {
                    ++_level;
                    break;
                }
            }
            return true;
        case Fake.None:
            try
            {
                if (!_reader.Read()) return false;
            }
            catch (XmlException e)
            {
                // if (!e.Message.StartsWith("Unexpected end tag.")) throw;
                // reading of extra-closing tag cause "Unexpected end tag"
                // so use this as event for transition too
                _fake = Fake.End;
                if (!_fakeReader.Read()) return false;
                return true;
            }
            switch(NodeType)
            {
            case XmlNodeType.Element:
                ++_level;
                break;
            case XmlNodeType.EndElement:
                if (--_level == 0)
                {
                    _fake = Fake.End;
                    if (!_fakeReader.Read()) return false;
                }
                break;
            }
            return true;
        default:
            return Proxy.Read();
        }
    }

    public override string Value
    {
        get { return Proxy.Value; }
    }

    public override XmlNodeType NodeType
    {
        get { return Proxy.NodeType; }
    }
    // rest use Proxy property for forwarding
}

void Main()
{
    var xml = "<?xml version=\"1.0\"?>" + @"
<Contacts>
    <Contact>
      <Name>Todd</Name>
      <Email>todd@blah.com</Email>
  </Contact>
    <Contact>
      <Name>Sarah</Name>
      <Email>sarah@blah.com</Email>
  </Contact>
    <Contact>
      <Name>Peter</Name>
      <Email>peter@blah.com</Email>
  </Contact>
</Contacts>";
    const string tag = "</Contact>";
    var xml2 = xml.Substring(xml.IndexOf(tag) + tag.Length);
    using(var sr = new StringReader(xml2))
    using(var xr = XmlReader.Create(sr, new XmlReaderSettings { ConformanceLevel = ConformanceLevel.Fragment, } ))
    using(var xr2 = new XmlTailReader(xr, "xxx"))
    {
        var xdoc = XDocument.Load(xr2);
        xdoc.Descendants("Contact").Dump();
    }
}

このような読み取りでは、ConformanceLevel は Fragment である必要があることに注意してください。

于 2012-11-29T14:33:44.393 に答える
1

インデックス位置で保存/取得する方法を見つけました。これも同様に機能します。

int position = 1;
var contacts = xdoc
    .Descendants("Contact")
    .Select((x, index) => new { Contact = x, Index = index })
    .Where(x => x.Index >= position)
    .Select(x => x.Contact);
于 2012-11-28T15:04:17.667 に答える
1

XML の読み取りの一貫性を崩したくない場合。XDocument次のような最初の要素で構築することは避けられません。

class XmlSkipReader : XmlReader
{
    private readonly XmlReader _reader;
    private readonly int _skip;
    private int _level, _skipped;
    public XmlSkipReader(XmlReader reader, int skip)
    {
        _reader = reader;
        _skip = skip;
    }

    public override bool Read()
    {
        if (_skipped == _skip) return _reader.Read();
        if (_level < 1)
        {
            if(!_reader.Read()) return false;
            switch(_reader.NodeType)
            {
                case XmlNodeType.Element: ++_level; break;
            }
            return true;
        }
        if(!_reader.Read()) return false;
        switch(_reader.NodeType)
        {
            case XmlNodeType.Element:
                ++_level;
                break;
            default: return true;
        }

        for(; _skipped < _skip; ++_skipped)
        {
            while(_level > 1)
            {
                if(!_reader.Read()) return false;
                switch(_reader.NodeType)
                {
                    case XmlNodeType.Element:
                        ++_level;
                        break;
                    case XmlNodeType.EndElement:
                        --_level;
                        break;
                }
            }
        }
        return _reader.Read();
    }
    // rest is just proxy to _reader
}

void Main()
{
    var xml = "<?xml version=\"1.0\"?>" + @"
<Contacts>
    <Contact>
      <Name>Todd</Name>
      <Email>todd@blah.com</Email>
  </Contact>
    <Contact>
      <Name>Sarah</Name>
      <Email>sarah@blah.com</Email>
  </Contact>
</Contacts>";
    using(var sr = new StringReader(xml))
    using(var xr = XmlReader.Create(sr))
    using(var xr2 = new XmlSkipReader(xr, 1))
    {
        var xdoc = XDocument.Load(xr2);
        xdoc.Descendants("Contact").Dump();
    }
}
于 2012-11-29T11:51:49.950 に答える
1

カスタム位置で Ducument の開始要素をエミュレートするトリッキーなストリームを作成できます。非常に大まかなコードですが、機能しています

void Main()
{
 var xml =
    @"<Contacts><Contact><Name>Todd</Name><Email>todd@blah.com</Email></Contact><Contact>
      <Name>Sarah1</Name>
      <Email>sarah@blah.com</Email>
  </Contact>
  <Contact>
      <Name>Sarah2</Name>
      <Email>sarah@blah.com</Email>
  </Contact>
</Contacts>";

    var ms = new MemoryStream(Encoding.UTF8.GetBytes(xml));
    ms.Position = 74;
    var reader = XmlReader.Create(new CustomReader("<Contacts>",ms));

    var xdoc = XDocument.Load(reader);
    var contact = xdoc.Descendants("Contact").Select(x => x).ToArray();

    contact.Dump();
}

public class CustomReader : Stream
{
    private readonly string _element;
    private readonly Stream _stream;
    private int _offset;

    public CustomReader(string element, Stream stream)
    {
        _element = element;
        _stream = stream;
        _offset = -element.Length;
    }

    public override bool CanRead
    {
        get { return true; }
    }

    public override bool CanSeek
    {
        get { return false; }
    }

    public override bool CanWrite
    {
        get { return false; }
    }

    public override void Close()
    {
        _stream.Close();
        base.Close();
    }

    public override void Flush()
    {
        throw new NotImplementedException();
    }

    public override long Length
    {
        get { throw new NotImplementedException(); }
    }

    public override long Position
    {
        get { throw new NotImplementedException(); }
        set { throw new NotImplementedException(); }
    }

    public override int Read(byte[] buffer, int offset, int count)
    {
        if (count == 0) return 0;

        if (_offset < 0)
        {
            var buf = Encoding.UTF8.GetBytes(_element);
            Buffer.BlockCopy(buf, 0, buffer, offset, buf.Length);
            _offset = 0;
            return buf.Length;
        }

        return _stream.Read(buffer, offset, count);
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        throw new NotImplementedException();
    }

    public override void SetLength(long value)
    {
        throw new NotImplementedException();
    }

    public override void Write(byte[] buffer, int offset, int count)
    {
        throw new NotImplementedException();
    }
}
于 2012-11-28T15:55:40.470 に答える