问题
Using ruamel.yaml, the output of roundtrip parsing of the YAML
a: {b: }
is
a: {b: !!null ''}
Is there any way to preserve the empty message, or overwrite the None
representer to output an empty message as above?
回答1:
This is not trivial and I am not sure if the solution presented below doesn't have adverse side-effects.
The cause for the non-triviality has to do with multiple things:
You are using the less readable flow style
a: {b: }
instead of block style:
a: b:
The latter round-trips without a change
- The empty value for key
b
loads asNone
, which I have not been able to subclass in ruamel.yaml, and hence style information cannot be attached to that value, and you have to rely on the "default" emitter (which in your case doesn't do what you want). This
a: {b:}
is entirely different from your
a: {b: }
and currently the emitter goes for safe and inserts tag information into the stream.
With that background information, you can force the style of the representation for None
to the empty string and based on that hack the emitter:
import sys
import ruamel.yaml
yaml_str = """\
a: {b: }
"""
class MyEmitter(ruamel.yaml.emitter.Emitter):
def choose_scalar_style(self):
# override selection for 'null'
if self.analysis is None:
self.analysis = self.analyze_scalar(self.event.value)
if self.event.style == '"' or self.canonical:
return '"'
if (not self.event.style or self.event.style == '?') and \
(self.event.implicit[0] or not self.event.implicit[2]):
if (not (self.simple_key_context and
(self.analysis.empty or self.analysis.multiline)) and
(self.flow_level and self.analysis.allow_flow_plain or
(not self.flow_level and self.analysis.allow_block_plain))):
return ''
if (self.event.style == '') and self.event.tag == 'tag:yaml.org,2002:null' and \
(self.event.implicit[0] or not self.event.implicit[2]):
if self.flow_level and not self.analysis.allow_flow_plain:
return ''
self.analysis.allow_block = True
if self.event.style and self.event.style in '|>':
if (not self.flow_level and not self.simple_key_context and
self.analysis.allow_block):
return self.event.style
if not self.event.style and self.analysis.allow_double_quoted:
if "'" in self.event.value or '\n' in self.event.value:
return '"'
if not self.event.style or self.event.style == '\'':
if (self.analysis.allow_single_quoted and
not (self.simple_key_context and self.analysis.multiline)):
return '\''
return '"'
def process_scalar(self):
# if style '' and tag is 'null' insert empty space
if self.analysis is None:
self.analysis = self.analyze_scalar(self.event.value)
if self.style is None:
self.style = self.choose_scalar_style()
split = (not self.simple_key_context)
if self.sequence_context and not self.flow_level:
self.write_indent()
if self.style == '"':
self.write_double_quoted(self.analysis.scalar, split)
elif self.style == '\'':
self.write_single_quoted(self.analysis.scalar, split)
elif self.style == '>':
self.write_folded(self.analysis.scalar)
elif self.style == '|':
self.write_literal(self.analysis.scalar)
elif self.event.tag == 'tag:yaml.org,2002:null':
self.stream.write(u' ') # not sure if this doesn't break other things
else:
self.write_plain(self.analysis.scalar, split)
self.analysis = None
self.style = None
if self.event.comment:
self.write_post_comment(self.event)
class MyRepresenter(ruamel.yaml.representer.RoundTripRepresenter):
def represent_none(self, data):
if len(self.represented_objects) == 0 and not self.serializer.use_explicit_start:
# this will be open ended (although it is not yet)
return self.represent_scalar(u'tag:yaml.org,2002:null', u'null')
return self.represent_scalar(u'tag:yaml.org,2002:null', u'', style='')
MyRepresenter.add_representer(type(None),
MyRepresenter.represent_none)
yaml = ruamel.yaml.YAML()
yaml.Emitter = MyEmitter
yaml.Representer = MyRepresenter
data = yaml.load(yaml_str)
yaml.dump(data, sys.stdout)
which gives:
a: {b: }
来源:https://stackoverflow.com/questions/49163514/preserve-empty-message-in-ruamel-yaml-roundtrip-parsing