Skip to content

Instantly share code, notes, and snippets.

@dnfield
Last active August 9, 2018 17:38
Show Gist options
  • Save dnfield/e176f1d604b21a67265896390c4ca6ee to your computer and use it in GitHub Desktop.
Save dnfield/e176f1d604b21a67265896390c4ca6ee to your computer and use it in GitHub Desktop.
/// A push based XmlReader interface, intended to be similar to .NET's XmlReader.
class XmlTextReader {
/// Creates a new reader for `input`.
///
/// Setting `ignoreWhitespace` to false will cause text nodes
XmlTextReader(String input, {this.ignoreWhitespace = true}) {
_result = Success(input, 0, null);
_depth = 0;
_eof = false;
}
/// If true, will ignore `XmlNodeType.TEXT` and when it is composed of whitespace.
bool ignoreWhitespace;
/// Parsing context.
Result _result;
/// The [XmlNodeType] of the current position of the reader.
XmlNodeType get nodeType => _nodeType;
XmlNodeType _nodeType;
/// The [XmlName] of the current node of the reader.
XmlName get name => _name;
XmlName _name;
/// The value of the current node of the reader.
///
/// The value will have
String get value => _value;
String _value;
/// Will return true if `nodeType == XmlNodeType.ELEMENT` and the element is self closing,
/// e.g. `<element />`.
bool get isEmptyElement => _isEmptyElement;
bool _isEmptyElement;
/// The zero based depth of the reader.
int get depth => _depth;
int _depth;
/// True if the reader is positioned at the end of the buffer.
bool get eof => _eof;
bool _eof;
/// The `List<XmlAttribute>` of the current element (if `nodeType == XmlNodeType.ELEMENT`).
UnmodifiableListView<XmlAttribute> get attributes =>
UnmodifiableListView<XmlAttribute>(_attributes);
List<XmlAttribute> _attributes;
/// Advances the reader to the next readable position. Returns true if more data remains, false if not.
bool read() {
if (_result.position > _result.buffer.length) {
_eof = true;
return false;
}
_result = _parseEvent(_result);
return (_result.isSuccess);
}
Result _parseEvent(Result context) {
_attributes = <XmlAttribute>[];
_isEmptyElement = null;
_value = null;
_nodeType = null;
Result result = _characterData.parseOn(context);
if (result.isSuccess) {
_nodeType = XmlNodeType.TEXT;
_value = result.value.trim();
if (_value == '') {
return _parseEvent(result);
}
return result;
}
result = _elementStart.parseOn(context);
if (result.isSuccess) {
_nodeType = XmlNodeType.ELEMENT;
_name = result.value[1];
_depth++;
_attributes = List<XmlAttribute>.from(result.value[2]);
if (result.value[4] == XmlToken.closeEndElement) {
_depth--;
_isEmptyElement = true;
} else {
_isEmptyElement = false;
}
return result;
}
result = _elementEnd.parseOn(context);
if (result.isSuccess) {
_nodeType = XmlNodeType.ELEMENT;
_name = result.value[1];
_depth--;
return result;
}
result = _comment.parseOn(context);
if (result.isSuccess) {
_nodeType = XmlNodeType.COMMENT;
_value = result.value[1];
return result;
}
// Parse CDATA as character data:
result = _cdata.parseOn(context);
if (result.isSuccess) {
_nodeType = XmlNodeType.CDATA;
_value = result.value[1];
return result;
}
// Parse processing instruction:
result = _processing.parseOn(context);
if (result.isSuccess) {
_nodeType = XmlNodeType.PROCESSING;
_value = result.value[2];
return result;
}
// Parse docytpes:
result = _doctype.parseOn(context);
if (result.isSuccess) {
_nodeType = XmlNodeType.DOCUMENT_TYPE;
_value = result.value[2];
return result;
}
if (context.position == context.buffer.length) {
_eof = true;
return context.failure('EOF');
}
// Skip to the next character when there is a problem:
return context.success(null, context.position + 1);
}
@override
String toString() => eof
? 'XmlTextReader{EOF}'
: 'XmlTextReader{$depth $nodeType $name $value $attributes}';
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment