blob: 6621ad6d809e5430024931b29bcab455d7b115a0 [file]
import 'dart:convert' show ChunkedConversionSink;
import 'package:meta/meta.dart';
import '../../xml/exceptions/tag_exception.dart';
import '../../xml/navigation/parent.dart';
import '../../xml/nodes/attribute.dart';
import '../../xml/nodes/cdata.dart';
import '../../xml/nodes/comment.dart';
import '../../xml/nodes/declaration.dart';
import '../../xml/nodes/doctype.dart';
import '../../xml/nodes/element.dart';
import '../../xml/nodes/node.dart';
import '../../xml/nodes/processing.dart';
import '../../xml/nodes/text.dart';
import '../../xml/utils/name.dart';
import '../event.dart';
import '../events/cdata.dart';
import '../events/comment.dart';
import '../events/declaration.dart';
import '../events/doctype.dart';
import '../events/end_element.dart';
import '../events/processing.dart';
import '../events/start_element.dart';
import '../events/text.dart';
import '../utils/conversion_sink.dart';
import '../utils/event_attribute.dart';
import '../utils/list_converter.dart';
import '../visitor.dart';
extension XmlNodeDecoderExtension on Stream<List<XmlEvent>> {
/// Converts a sequence of [XmlEvent] objects to [XmlNode] objects.
Stream<List<XmlNode>> toXmlNodes() => transform(const XmlNodeDecoder());
}
/// A converter that decodes a sequence of [XmlEvent] objects to a forest of
/// [XmlNode] objects.
class XmlNodeDecoder extends XmlListConverter<XmlEvent, XmlNode> {
const XmlNodeDecoder();
@override
ChunkedConversionSink<List<XmlEvent>> startChunkedConversion(
Sink<List<XmlNode>> sink) =>
_XmlNodeDecoderSink(sink);
// Internal helper to efficiently convert an [Iterable] of [XmlEvent] to a
// list of [XmlNodes].
@internal
List<XmlNode> convertIterable(Iterable<XmlEvent> events) {
final result = <XmlNode>[];
final sink =
_XmlNodeDecoderSink(ConversionSink<List<XmlNode>>(result.addAll));
events.forEach(sink.visit);
return result;
}
}
class _XmlNodeDecoderSink extends ChunkedConversionSink<List<XmlEvent>>
with XmlEventVisitor {
_XmlNodeDecoderSink(this.sink);
final Sink<List<XmlNode>> sink;
XmlElement? parent;
@override
void add(List<XmlEvent> chunk) => chunk.forEach(visit);
@override
void visitCDATAEvent(XmlCDATAEvent event) =>
commit(XmlCDATA(event.text), event);
@override
void visitCommentEvent(XmlCommentEvent event) =>
commit(XmlComment(event.text), event);
@override
void visitDeclarationEvent(XmlDeclarationEvent event) =>
commit(XmlDeclaration(convertAttributes(event.attributes)), event);
@override
void visitDoctypeEvent(XmlDoctypeEvent event) => commit(
XmlDoctype(event.name, event.externalId, event.internalSubset), event);
@override
void visitEndElementEvent(XmlEndElementEvent event) {
if (parent == null) {
throw XmlTagException.unexpectedClosingTag(event.name);
}
final element = parent!;
XmlTagException.checkClosingTag(element.name.qualified, event.name);
element.isSelfClosing = element.children.isNotEmpty;
parent = element.parentElement;
if (parent == null) {
commit(element, event.parent);
}
}
@override
void visitProcessingEvent(XmlProcessingEvent event) =>
commit(XmlProcessing(event.target, event.text), event);
@override
void visitStartElementEvent(XmlStartElementEvent event) {
final element = XmlElement(
XmlName.fromString(event.name),
convertAttributes(event.attributes),
);
if (event.isSelfClosing) {
commit(element, event);
} else {
if (parent != null) {
parent!.children.add(element);
}
parent = element;
}
}
@override
void visitTextEvent(XmlTextEvent event) => commit(XmlText(event.text), event);
@override
void close() {
if (parent != null) {
throw XmlTagException.missingClosingTag(parent!.name.qualified);
}
sink.close();
}
void commit(XmlNode node, XmlEvent? event) {
if (parent == null) {
// If we have information about a parent event, create hidden
// [XmlElement] nodes to make sure namespace resolution works
// as expected.
for (var outerElement = node, outerEvent = event?.parent;
outerEvent != null;
outerEvent = outerEvent.parent) {
outerElement = XmlElement(
XmlName.fromString(outerEvent.name),
convertAttributes(outerEvent.attributes),
[outerElement],
outerEvent.isSelfClosing,
);
}
sink.add(<XmlNode>[node]);
} else {
parent!.children.add(node);
}
}
Iterable<XmlAttribute> convertAttributes(
Iterable<XmlEventAttribute> attributes) =>
attributes.map((attribute) => XmlAttribute(
XmlName.fromString(attribute.name),
attribute.value,
attribute.attributeType));
}