diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2e0571e..e419630 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -58,6 +58,7 @@ format changes.
### Fixed
+- **`Format.formatDocument` keeps every directive in document order** (#188; trendvidia/protowire#558). It used to drop `@proto`, and wrote every named directive before every dataset, so `@dataset t.v1.T (id)` / `(1)` followed by `@entry x { a = 1 }` came out with the `@entry` first. That order matters: an anonymous `@proto` binds the typed directive after it. `@type` is still written first, since it binds no `@proto`. The other directives follow in source order, each after its comments, with no blank line between them; a dataset with no type is written `@dataset (id)`. A document whose only directive is `@type` formats exactly as before. The output is byte for byte protowire-go's `FormatDocument` (protowire-go#262).
- **`Convert.xmlToProto` keeps the length of a `uint8` or `int8` array** (#179). `` imported as one `uint32` with `(sbe.encoding) = "uint8"`, so a 16-byte field was read as 1 byte and every later field in the block was misplaced. It now imports as `bytes` with `(sbe.length) = 16`, the same 16 bytes on the wire.
- **The SBE decoder rejects a string field that is not UTF-8** (#162; `docs/HARDENING.md` § UTF-8). Both tiers turned the bytes of an `(sbe.length)` `string` into a `String` with `new String(bytes, UTF_8)`, which puts U+FFFD in place of bad bytes and says nothing, so `41 42 FF FE` decoded as `"AB\uFFFD\uFFFD"`. `SbeWireCodec` and `View.stringValue` now decode strictly and throw `IllegalArgumentException` (`sbe: invalid UTF-8 in string field `). A `bytes` field takes any bytes, as before. `check-decode-android` decodes `adversarial.v1.SbeText`, so the lite tier runs the spec's `sbe/utf8-*` rows.
diff --git a/pxf-runtime/src/main/java/org/protowire/pxf/Format.java b/pxf-runtime/src/main/java/org/protowire/pxf/Format.java
index 7cc2dd0..3d25693 100644
--- a/pxf-runtime/src/main/java/org/protowire/pxf/Format.java
+++ b/pxf-runtime/src/main/java/org/protowire/pxf/Format.java
@@ -6,7 +6,9 @@
import java.math.MathContext;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.Base64;
+import java.util.Comparator;
import java.util.List;
import java.util.function.Predicate;
@@ -14,25 +16,84 @@
public final class Format {
private Format() {}
+ /**
+ * Pretty-prints a document, preserving comments.
+ *
+ *
It keeps every directive, with its dataset rows, in document order
+ * (trendvidia/protowire#558, #188). {@code @type} is written first: it
+ * binds no anonymous {@code @proto}, so moving it changes nothing. The
+ * other directives follow in source order, each after its comments: a
+ * named directive's body and a {@code @proto} body as written, and a
+ * dataset as its header and then one row per line, an absent cell left
+ * empty. A document whose only directive is {@code @type} formats as it
+ * always has. The output is byte for byte protowire-go's
+ * {@code FormatDocument}.
+ */
public static String formatDocument(Ast.Document doc) {
StringBuilder sb = new StringBuilder();
+ List