Skip to content

Commit

Permalink
feat: add support for serializing xml cdata (#48)
Browse files Browse the repository at this point in the history
  • Loading branch information
tnc1997 authored Oct 7, 2023
1 parent d7ad368 commit 8a11d9e
Show file tree
Hide file tree
Showing 29 changed files with 1,501 additions and 164 deletions.
4 changes: 4 additions & 0 deletions xml_annotation/lib/src/annotations/xml_cdata.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
/// An annotation used to specify how a CDATA is serialized.
class XmlCDATA {
const XmlCDATA();
}
38 changes: 37 additions & 1 deletion xml_annotation/lib/src/extensions/xml_node_extensions.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
import 'package:xml/xml.dart';

extension XmlNodeExtensions on XmlNode {
/// Finds the CDATA of the recursive child elements in document order with the [name] and [namespace].
@Deprecated(
'Use findAllElements(name, namespace: namespace).map((e) => e.getCDATA()).whereType<String>() instead.',
)
Iterable<String> findAllElementsCDATA(String name, {String? namespace}) sync* {
for (final element in findAllElements(name, namespace: namespace)) {
final cdata = element.getCDATA();
if (cdata != null) yield cdata;
}
}

/// Finds the text of the recursive child elements in document order with the [name] and [namespace].
@Deprecated(
'Use findAllElements(name, namespace: namespace).map((e) => e.getText()).whereType<String>() instead.',
Expand All @@ -12,6 +23,17 @@ extension XmlNodeExtensions on XmlNode {
}
}

/// Finds the CDATA of the direct child elements in document order with the [name] and [namespace].
@Deprecated(
'Use findElements(name, namespace: namespace).map((e) => e.getCDATA()).whereType<String>() instead.',
)
Iterable<String> findElementsCDATA(String name, {String? namespace}) sync* {
for (final element in findElements(name, namespace: namespace)) {
final cdata = element.getCDATA();
if (cdata != null) yield cdata;
}
}

/// Finds the text of the direct child elements in document order with the [name] and [namespace].
@Deprecated(
'Use findElements(name, namespace: namespace).map((e) => e.getText()).whereType<String>() instead.',
Expand All @@ -23,6 +45,14 @@ extension XmlNodeExtensions on XmlNode {
}
}

/// Gets the CDATA of the first child element with the [name] and [namespace] or `null` if there are no `XmlCDATA` children.
@Deprecated(
'Use getElement(name, namespace: namespace).getCDATA() instead.',
)
String? getElementCDATA(String name, {String? namespace}) {
return getElement(name, namespace: namespace)?.getCDATA();
}

/// Gets the text of the first child element with the [name] and [namespace] or `null` if there are no `XmlText` children.
@Deprecated(
'Use getElement(name, namespace: namespace).getText() instead.',
Expand All @@ -31,6 +61,12 @@ extension XmlNodeExtensions on XmlNode {
return getElement(name, namespace: namespace)?.getText();
}

/// Gets the CDATA or `null` if there are no `XmlCDATA` children.
String? getCDATA() {
final cdatas = children.whereType<XmlCDATA>().map((e) => e.value);
return cdatas.isNotEmpty ? cdatas.join() : null;
}

/// Gets the _direct_ child elements in document order with the specified tag `name` and `namespace` or `null` if there are no matching elements.
Iterable<XmlElement>? getElements(String name, {String? namespace}) {
final elements = findElements(name, namespace: namespace);
Expand All @@ -39,7 +75,7 @@ extension XmlNodeExtensions on XmlNode {

/// Gets the text or `null` if there are no `XmlText` children.
String? getText() {
final texts = children.whereType<XmlText>().map((e) => e.text);
final texts = children.whereType<XmlText>().map((e) => e.value);
return texts.isNotEmpty ? texts.join() : null;
}
}
1 change: 1 addition & 0 deletions xml_annotation/lib/xml_annotation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
library xml_annotation;

export 'package:xml_annotation/src/annotations/xml_attribute.dart';
export 'package:xml_annotation/src/annotations/xml_cdata.dart';
export 'package:xml_annotation/src/annotations/xml_element.dart';
export 'package:xml_annotation/src/annotations/xml_enum.dart';
export 'package:xml_annotation/src/annotations/xml_root_element.dart';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import 'builder_generator.dart';

class XmlCDATABuilderGenerator extends BuilderGenerator {
/// If `false` (the default) then the type does not represent a nullable type.
final bool _isNullable;

const XmlCDATABuilderGenerator({bool isNullable = false})
: _isNullable = isNullable;

@override
String generateBuilder(String expression, {String builder = 'builder'}) {
final buffer = StringBuffer();

if (_isNullable) {
buffer.write('if ($expression != null) { ');
}

buffer.write('$builder.cdata($expression);');

if (_isNullable) {
buffer.write(' }');
}

return buffer.toString();
}
}

class NullableXmlCDATABuilderGenerator extends XmlCDATABuilderGenerator {
const NullableXmlCDATABuilderGenerator() : super(isNullable: true);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import 'constructor_generator.dart';

class XmlCDATAConstructorGenerator extends ConstructorGenerator {
/// If `false` (the default) then the type does not represent a nullable type.
final bool _isNullable;

const XmlCDATAConstructorGenerator({bool isNullable = false})
: _isNullable = isNullable;

@override
String generateConstructor(String expression) {
final buffer = StringBuffer();

if (_isNullable) {
buffer.write('$expression != null ? ');
}

buffer.write('XmlCDATA($expression)');

if (_isNullable) {
buffer.write(' : null');
}

return buffer.toString();
}
}

class NullableXmlCDATAConstructorGenerator
extends XmlCDATAConstructorGenerator {
const NullableXmlCDATAConstructorGenerator() : super(isNullable: true);
}
17 changes: 17 additions & 0 deletions xml_serializable/lib/src/extensions/dart_object_extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,23 @@ extension DartObjectExtensions on DartObject {
return null;
}

/// Returns an [XmlCDATA] corresponding to the value of the object being represented or `null` if this object is not of type [XmlCDATA].
XmlCDATA? toXmlCDATAValue() {
final type = this.type;

if (type is InterfaceType) {
final element = type.element2;

if (element is ClassElement &&
element.library.identifier.startsWith('package:xml_annotation') &&
element.name == 'XmlCDATA') {
return XmlCDATA();
}
}

return null;
}

/// Returns an [XmlElement] corresponding to the value of the object being represented or `null` if this object is not of type [XmlElement].
XmlElement? toXmlElementValue() {
final type = this.type;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ extension ElementAnnotationExtensions on ElementAnnotation {
element.enclosingElement3.name == 'XmlAttribute';
}

/// Returns `true` if this annotation marks the associated member as being serializable as an `XmlCDATA`.
bool get isXmlCDATA {
final element = this.element;

return element is ConstructorElement &&
element.library.identifier.startsWith('package:xml_annotation') &&
element.enclosingElement3.name == 'XmlCDATA';
}

/// Returns `true` if this annotation marks the associated member as being serializable as an `XmlElement`.
bool get isXmlElement {
final element = this.element;
Expand Down
7 changes: 7 additions & 0 deletions xml_serializable/lib/src/extensions/element_extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ extension ElementExtensions on Element {
DartObject? getXmlAttribute() =>
metadata.singleWhere((e) => e.isXmlAttribute).computeConstantValue();

/// Gets the annotation of the form `@XmlCDATA()`. Throws a [StateError] if this element does not have an annotation of the form `@XmlCDATA()`. Returns `null` if the value of the annotation could not be computed because of errors.
DartObject? getXmlCDATA() =>
metadata.singleWhere((e) => e.isXmlCDATA).computeConstantValue();

/// Gets the annotation of the form `@XmlElement()`. Throws a [StateError] if this element does not have an annotation of the form `@XmlElement()`. Returns `null` if the value of the annotation could not be computed because of errors.
DartObject? getXmlElement() =>
metadata.singleWhere((e) => e.isXmlElement).computeConstantValue();
Expand Down Expand Up @@ -35,6 +39,9 @@ extension ElementExtensions on Element {
/// Returns `true` if this element has an annotation of the form `@XmlAttribute()`.
bool get hasXmlAttribute => metadata.any((e) => e.isXmlAttribute);

/// Returns `true` if this element has an annotation of the form `@XmlCDATA()`.
bool get hasXmlCDATA => metadata.any((e) => e.isXmlCDATA);

/// Returns `true` if this element has an annotation of the form `@XmlElement()`.
bool get hasXmlElement => metadata.any((e) => e.isXmlElement);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:analyzer/dart/element/type.dart';
import '../builder_generators/builder_generator.dart';
import '../builder_generators/iterable_builder_generator.dart';
import '../builder_generators/xml_attribute_builder_generator.dart';
import '../builder_generators/xml_cdata_builder_generator.dart';
import '../builder_generators/xml_root_element_builder_generator.dart';
import '../builder_generators/xml_serializable_xml_element_builder_generator.dart';
import '../builder_generators/xml_text_builder_generator.dart';
Expand All @@ -28,6 +29,16 @@ BuilderGenerator builderGeneratorFactory(Element element) {
'`@XmlAttribute()` can only be used on fields.',
);
}
} else if (element.hasXmlCDATA) {
if (element is FieldElement) {
return XmlCDATABuilderGenerator(isNullable: element.type.isNullable);
} else {
throw ArgumentError.value(
element,
'element',
'`@XmlCDATA()` can only be used on fields.',
);
}
} else if (element.hasXmlElement) {
if (element is FieldElement) {
return xmlElementBuilderGeneratorFactory(element);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:analyzer/dart/element/type.dart';
import '../constructor_generators/constructor_generator.dart';
import '../constructor_generators/iterable_constructor_generator.dart';
import '../constructor_generators/xml_attribute_constructor_generator.dart';
import '../constructor_generators/xml_cdata_constructor_generator.dart';
import '../constructor_generators/xml_root_element_constructor_generator.dart';
import '../constructor_generators/xml_serializable_xml_element_constructor_generator.dart';
import '../constructor_generators/xml_text_constructor_generator.dart';
Expand All @@ -30,6 +31,16 @@ ConstructorGenerator constructorGeneratorFactory(Element element) {
'`@XmlAttribute()` can only be used on fields.',
);
}
} else if (element.hasXmlCDATA) {
if (element is FieldElement) {
return XmlCDATAConstructorGenerator(isNullable: element.type.isNullable);
} else {
throw ArgumentError.value(
element,
'element',
'`@XmlCDATA()` can only be used on fields.',
);
}
} else if (element.hasXmlElement) {
if (element is FieldElement) {
return xmlElementConstructorGeneratorFactory(element);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import '../extensions/element_extensions.dart';
import '../extensions/field_element_extensions.dart';
import '../getter_generators/getter_generator.dart';
import '../getter_generators/xml_attribute_getter_generator.dart';
import '../getter_generators/xml_cdata_getter_generator.dart';
import '../getter_generators/xml_element_getter_generator.dart';
import '../getter_generators/xml_element_iterable_getter_generator.dart';
import '../getter_generators/xml_text_getter_generator.dart';
Expand All @@ -26,6 +27,16 @@ GetterGenerator getterGeneratorFactory(Element element) {
'`@XmlAttribute()` can only be used on fields.',
);
}
} else if (element.hasXmlCDATA) {
if (element is FieldElement) {
return XmlCDATAGetterGenerator(isNullable: element.type.isNullable);
} else {
throw ArgumentError.value(
element,
'element',
'`@XmlCDATA()` can only be used on fields.',
);
}
} else if (element.hasXmlElement) {
if (element is FieldElement) {
return xmlElementGetterGeneratorFactory(element);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import 'getter_generator.dart';

class XmlCDATAGetterGenerator extends GetterGenerator {
/// If `false` (the default) then the type does not represent a nullable type.
final bool _isNullable;

const XmlCDATAGetterGenerator({bool isNullable = false})
: _isNullable = isNullable;

@override
String generateGetter(String expression) {
final buffer = StringBuffer(expression);

buffer.write('.getCDATA()');

if (!_isNullable) {
buffer.write('!');
}

return buffer.toString();
}
}

class NullableXmlCDATAGetterGenerator extends XmlCDATAGetterGenerator {
const NullableXmlCDATAGetterGenerator() : super(isNullable: true);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import 'package:source_gen/source_gen.dart';
import 'package:xml_annotation/xml_annotation.dart';

/// A [TypeChecker] for [XmlCDATA].
@Deprecated(
'Use element.isXmlCDATA instead of xmlCDATATypeChecker.hasAnnotationOf(element).',
)
const xmlCDATATypeChecker = TypeChecker.fromRuntime(XmlCDATA);
4 changes: 3 additions & 1 deletion xml_serializable/lib/src/xml_serializable_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ class XmlSerializableGenerator extends GeneratorForAnnotation<XmlSerializable> {

for (final element in element.allFields) {
if (element.hasXmlAttribute ||
element.hasXmlCDATA ||
element.hasXmlElement ||
element.hasXmlText) {
buffer.writeln(
Expand Down Expand Up @@ -154,6 +155,7 @@ class XmlSerializableGenerator extends GeneratorForAnnotation<XmlSerializable> {

for (final element in element.allFields) {
if (element.hasXmlAttribute ||
element.hasXmlCDATA ||
element.hasXmlElement ||
element.hasXmlText) {
buffer.writeln(
Expand Down Expand Up @@ -264,7 +266,7 @@ class XmlSerializableGenerator extends GeneratorForAnnotation<XmlSerializable> {
buffer.writeln('final children = <XmlNode>[];');

for (final element in element.allFields) {
if (element.hasXmlElement || element.hasXmlText) {
if (element.hasXmlCDATA || element.hasXmlElement || element.hasXmlText) {
buffer.writeln('final ${element.name} = instance.${element.name};');

buffer.writeln(
Expand Down
4 changes: 4 additions & 0 deletions xml_serializable/lib/xml_serializable.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ library xml_serializable;
export 'package:xml_serializable/src/builder_generators/builder_generator.dart';
export 'package:xml_serializable/src/builder_generators/iterable_builder_generator.dart';
export 'package:xml_serializable/src/builder_generators/xml_attribute_builder_generator.dart';
export 'package:xml_serializable/src/builder_generators/xml_cdata_builder_generator.dart';
export 'package:xml_serializable/src/builder_generators/xml_root_element_builder_generator.dart';
export 'package:xml_serializable/src/builder_generators/xml_serializable_xml_element_builder_generator.dart';
export 'package:xml_serializable/src/builder_generators/xml_text_builder_generator.dart';
export 'package:xml_serializable/src/builder_generators/xml_text_xml_element_builder_generator.dart';
export 'package:xml_serializable/src/constructor_generators/constructor_generator.dart';
export 'package:xml_serializable/src/constructor_generators/iterable_constructor_generator.dart';
export 'package:xml_serializable/src/constructor_generators/xml_attribute_constructor_generator.dart';
export 'package:xml_serializable/src/constructor_generators/xml_cdata_constructor_generator.dart';
export 'package:xml_serializable/src/constructor_generators/xml_root_element_constructor_generator.dart';
export 'package:xml_serializable/src/constructor_generators/xml_serializable_xml_element_constructor_generator.dart';
export 'package:xml_serializable/src/constructor_generators/xml_text_constructor_generator.dart';
Expand All @@ -28,6 +30,7 @@ export 'package:xml_serializable/src/generator_factories/getter_generator_factor
export 'package:xml_serializable/src/generator_factories/serializer_generator_factory.dart';
export 'package:xml_serializable/src/getter_generators/getter_generator.dart';
export 'package:xml_serializable/src/getter_generators/xml_attribute_getter_generator.dart';
export 'package:xml_serializable/src/getter_generators/xml_cdata_getter_generator.dart';
export 'package:xml_serializable/src/getter_generators/xml_element_getter_generator.dart';
export 'package:xml_serializable/src/getter_generators/xml_element_iterable_getter_generator.dart';
export 'package:xml_serializable/src/getter_generators/xml_text_getter_generator.dart';
Expand All @@ -45,6 +48,7 @@ export 'package:xml_serializable/src/serializer_generators/string_serializer_gen
export 'package:xml_serializable/src/serializer_generators/stringable_serializer_generator.dart';
export 'package:xml_serializable/src/serializer_generators/uri_serializer_generator.dart';
export 'package:xml_serializable/src/type_checkers/xml_attribute_type_checker.dart';
export 'package:xml_serializable/src/type_checkers/xml_cdata_type_checker.dart';
export 'package:xml_serializable/src/type_checkers/xml_element_type_checker.dart';
export 'package:xml_serializable/src/type_checkers/xml_root_element_type_checker.dart';
export 'package:xml_serializable/src/type_checkers/xml_serializable_type_checker.dart';
Expand Down
5 changes: 5 additions & 0 deletions xml_serializable/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,8 @@ dependency_overrides:
git:
ref: feat/29
url: https://github.com/tnc1997/dart-source-gen-test.git
xml_annotation:
git:
path: xml_annotation
ref: main
url: https://github.com/tnc1997/dart-xml-serializable.git
Loading

0 comments on commit 8a11d9e

Please sign in to comment.