@@ -883,6 +883,65 @@ function correct_byteorder(data, ::Ucs4Datatype, byteorder::Byteorder)
883883 return data
884884end
885885
886+ """
887+ TaggedMapping{D} <: AbstractDict
888+ TaggedSequence{V} <: AbstractVector
889+ TaggedScalar <: AbstractString
890+
891+ A YAML node that carries an unrecognized (extension) `tag`, produced by [`ASDF.load_file`](@ref)
892+ when called with `extensions = true`. Each behaves exactly like its wrapped `value` — a mapping
893+ indexes and iterates as a dict, a sequence as a vector, a scalar as a string — while retaining
894+ the original `tag` so the node round-trips unchanged through [`ASDF.write_file`](@ref).
895+ """
896+ struct TaggedMapping{D<: AbstractDict } <: AbstractDict{Any, Any}
897+ tag:: String
898+ value:: D
899+ end
900+ # `keys`, `values`, `haskey`, `get`, etc. fall back to these via the `AbstractDict` interface.
901+ Base. getindex (m:: TaggedMapping , k) = getindex (m. value, k)
902+ Base. length (m:: TaggedMapping ) = length (m. value)
903+ Base. iterate (m:: TaggedMapping , state... ) = iterate (m. value, state... )
904+
905+ @doc (@doc TaggedMapping)
906+ struct TaggedSequence{V<: AbstractVector } <: AbstractVector{Any}
907+ tag:: String
908+ value:: V
909+ end
910+ Base. size (s:: TaggedSequence ) = size (s. value)
911+ Base. getindex (s:: TaggedSequence , i:: Int ) = getindex (s. value, i)
912+ Base. IndexStyle (:: Type{<:TaggedSequence} ) = IndexLinear ()
913+
914+ @doc (@doc TaggedMapping)
915+ struct TaggedScalar <: AbstractString
916+ tag:: String
917+ value:: String
918+ end
919+ Base. ncodeunits (s:: TaggedScalar ) = ncodeunits (s. value)
920+ Base. codeunit (s:: TaggedScalar ) = codeunit (s. value)
921+ Base. codeunit (s:: TaggedScalar , i:: Integer ) = codeunit (s. value, i)
922+ Base. isvalid (s:: TaggedScalar , i:: Integer ) = isvalid (s. value, i)
923+ Base. iterate (s:: TaggedScalar , i:: Integer = 1 ) = iterate (s. value, i)
924+
925+ # Serialize a tagged node by emitting its verbatim tag (`!<...>`) right before the wrapped
926+ # value, mirroring how `NDArrayWrapper` writes `!core/ndarray-1.0.0`. The YAML writer breaks the
927+ # line after the key only for a *non-empty* block collection, so the tag goes on its own
928+ # indented line there; for scalars and for empty collections (printed inline as `{}`/`[]`) the
929+ # writer leaves us on the key's line, so the tag stays inline. Both forms parse back to a tagged
930+ # node.
931+ function YAML. _print (io:: IO , val:: Union{TaggedMapping, TaggedSequence} , level:: Int = 0 , ignore_level:: Bool = false )
932+ if isempty (val. value)
933+ print (io, " !<" , val. tag, " > " )
934+ YAML. _print (io, val. value, level, ignore_level)
935+ else
936+ print (io, YAML. _indent (" !<" * val. tag * " >\n " , level, ignore_level))
937+ YAML. _print (io, val. value, level)
938+ end
939+ end
940+ function YAML. _print (io:: IO , val:: TaggedScalar , level:: Int = 0 , ignore_level:: Bool = false )
941+ print (io, " !<" , val. tag, " > " )
942+ YAML. _print (io, val. value, level, ignore_level)
943+ end
944+
886945function YAML. _print (io:: IO , val:: NDArray , level:: Int = 0 , ignore_level:: Bool = false )
887946 # TODO : Get compression from underlying header block?
888947 YAML. _print (io, NDArrayWrapper (val[]; compression = C_None), level, ignore_level)
@@ -1164,7 +1223,7 @@ Reads an ASDF file from disk.
11641223| Parameter | Description |
11651224| :------------------ | :--------------------------------------------------------------------------------------------------------------- |
11661225| `filename` | Path to the `.asdf` file |
1167- | `extensions` | When `true`, unknown YAML tags are parsed leniently (as maps, sequences, or scalars) instead of raising an error |
1226+ | `extensions` | When `true`, unknown YAML tags are parsed leniently (as maps, sequences, or scalars) instead of raising an error, and a warning naming each unrecognized tag is emitted (once per distinct tag). The tag is retained (see [`ASDF.TaggedMapping`](@ref)) so the node round-trips unchanged on write. |
11681227| `validate_checksum` | When `true`, each block's MD5 checksum is verified against the stored value |
11691228
11701229Block data is located lazily. Block headers are scanned after the YAML is parsed, and array data (`ndarray`) is read only when [`Base.getindex(ndarray::NDArray)`](@ref) is called, i.e., `ndarray[]`.
@@ -1181,14 +1240,21 @@ function load_file(filename::AbstractString; extensions = false, validate_checks
11811240 asdf_constructors[" tag:stsci.edu:asdf/core/extension_metadata-1.0.0" ] = ordered_map_constructor
11821241
11831242 if extensions
1184- # Use fallbacks for now
1243+ # Use fallbacks for now. Track which unrecognized tags we have already
1244+ # warned about so each distinct tag is reported at most once per load.
1245+ warned_tags = Set {Any} ()
11851246 asdf_constructors[nothing ] = (constructor, node) -> begin
1247+ if node. tag ∉ warned_tags
1248+ push! (warned_tags, node. tag)
1249+ @warn " Unrecognized tag encountered while loading; falling back to a generic representation" tag = node. tag
1250+ end
1251+ # Wrap in a `Tagged*` type so the unrecognized tag is retained and round-trips on write.
11861252 if node isa YAML. MappingNode
1187- return YAML. construct_mapping (constructor, node)
1253+ return TaggedMapping (node . tag, YAML. construct_mapping (OrderedDict{Any, Any}, constructor, node) )
11881254 elseif node isa YAML. SequenceNode
1189- return YAML. construct_sequence (constructor, node)
1255+ return TaggedSequence (node . tag, YAML. construct_sequence (constructor, node) )
11901256 else
1191- return YAML. construct_scalar (constructor, node)
1257+ return TaggedScalar (node . tag, YAML. construct_scalar (constructor, node) )
11921258 end
11931259 end
11941260 end
@@ -1444,7 +1510,11 @@ function write_file(filename::AbstractString, document::AbstractDict)
14441510 # - [ ] maybe make the document not a `Dict` but the stuff with the `metadata` that the writer returns?
14451511 # - [ ] preserve insertion order? https://github.com/JuliaAstro/ASDF.jl/tree/ordered
14461512 library = ASDFLibrary (software_name, software_author, software_homepage, software_version)
1447- full_document = merge (document, OrderedDict {Any, Any} (" asdf_library" => library))
1513+ # Build the output tree as an `OrderedDict` regardless of the input dict's concrete type, so
1514+ # insertion order is preserved (for a `TaggedMapping` document, `merge` would otherwise fall
1515+ # back to an unordered `Dict` and drop the order). The provenance entry is stamped last.
1516+ full_document = OrderedDict {Any, Any} (document)
1517+ full_document[" asdf_library" ] = library
14481518
14491519 # Write YAML part of file
14501520 io = open (filename, " w" )
0 commit comments