Skip to content

Commit db713ca

Browse files
authored
Merge pull request #4264 from eXistSolutions/feat/insert-final-newline
[enhancement] add serialization option that inserts final newline
2 parents a333955 + aa05276 commit db713ca

File tree

8 files changed

+237
-51
lines changed

8 files changed

+237
-51
lines changed

exist-core/src/main/java/org/exist/storage/serializers/EXistOutputKeys.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,14 @@ public class EXistOutputKeys {
104104
* Set to "yes" to enable xdm-serialization rules, false otherwise.
105105
*/
106106
public final static String XDM_SERIALIZATION = "xdm-serialization";
107+
108+
/**
109+
* Enforce newline at the end of JSON and XML documents.
110+
*
111+
* It is common for editor software to enforce a newline at the end of non-
112+
* binary resources. This setting will do the same for serialization out of
113+
* exist-db and will lead to less meaningless changes in git and tools
114+
* like diff will be able to provide more meaningful information as well.
115+
*/
116+
public final static String INSERT_FINAL_NEWLINE = "insert-final-newline";
107117
}

exist-core/src/main/java/org/exist/util/serializer/IndentingXMLWriter.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,14 @@ public void processingInstruction(final String target, final String data) throws
140140
afterTag = true;
141141
}
142142

143+
@Override
144+
public void endDocument() throws TransformerException {
145+
super.endDocument();
146+
if ("yes".equals(outputProperties.getProperty(EXistOutputKeys.INSERT_FINAL_NEWLINE, "no"))) {
147+
super.characters("\n");
148+
}
149+
}
150+
143151
@Override
144152
public void endDocumentType() throws TransformerException {
145153
super.endDocumentType();

exist-core/src/main/java/org/exist/util/serializer/json/JSONSerializer.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ public void serialize(Sequence sequence, Writer writer) throws SAXException {
7070
generator.disable(JsonGenerator.Feature.STRICT_DUPLICATE_DETECTION);
7171
}
7272
serializeSequence(sequence, generator);
73+
if ("yes".equals(outputProperties.getProperty(EXistOutputKeys.INSERT_FINAL_NEWLINE, "no"))) {
74+
generator.writeRaw('\n');
75+
}
7376
generator.close();
7477
} catch (IOException | XPathException e) {
7578
throw new SAXException(e.getMessage(), e);

exist-core/src/main/java/org/exist/xquery/util/SerializerUtils.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,8 @@ public enum ExistParameterConvention implements ParameterConvention<QName> {
163163
JSON_IGNORE_WHITE_SPACE_TEXT_NODES("json-ignore-whitespace-text-nodes", Type.BOOLEAN, Cardinality.ZERO_OR_ONE, BooleanValue.TRUE),
164164
HIGHLIGHT_MATCHES("highlight-matches", Type.STRING, Cardinality.ZERO_OR_ONE, new StringValue("none")),
165165
JSONP("jsonp", Type.STRING, Cardinality.ZERO_OR_ONE, Sequence.EMPTY_SEQUENCE),
166-
ADD_EXIST_ID("add-exist-id", Type.STRING, Cardinality.ZERO_OR_ONE, new StringValue("none"));
166+
ADD_EXIST_ID("add-exist-id", Type.STRING, Cardinality.ZERO_OR_ONE, new StringValue("none")),
167+
ADD_NEWLINE_AT_EOF(EXistOutputKeys.INSERT_FINAL_NEWLINE, Type.BOOLEAN, Cardinality.ZERO_OR_ONE, BooleanValue.FALSE);
167168

168169

169170
private final QName parameterName;

exist-core/src/test/xquery/xquery3/serialize.xql

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -846,3 +846,51 @@ function ser:serialize-xml-134() {
846846
}
847847
return serialize((1 to 4)!text{.}, $params)
848848
};
849+
850+
declare
851+
%test:assertTrue
852+
function ser:exist-insert-final-newline-false() {
853+
let $doc := <root>
854+
<nested />
855+
</root>
856+
let $serialized := fn:serialize($doc,
857+
map {xs:QName("exist:insert-final-newline"): false()})
858+
return fn:ends-with($serialized, ">")
859+
};
860+
861+
declare
862+
%test:assertTrue
863+
function ser:exist-insert-final-newline-true() {
864+
let $doc := <root>
865+
<nested />
866+
</root>
867+
let $serialized := fn:serialize($doc,
868+
map {xs:QName("exist:insert-final-newline"): true()})
869+
return fn:ends-with($serialized, "&#x0A;")
870+
};
871+
872+
declare
873+
%test:assertTrue
874+
function ser:exist-insert-final-newline-false-json() {
875+
let $doc := map { "a": 1 }
876+
let $serialized := fn:serialize($doc,
877+
map {
878+
"method": "json",
879+
"exist:insert-final-newline": false()
880+
}
881+
)
882+
return fn:ends-with($serialized, "}")
883+
};
884+
885+
declare
886+
%test:assertTrue
887+
function ser:exist-insert-final-newline-true-json() {
888+
let $doc := map { "a": 1 }
889+
let $serialized := fn:serialize($doc,
890+
map {
891+
"method": "json",
892+
"exist:insert-final-newline": true()
893+
}
894+
)
895+
return fn:ends-with($serialized, "&#x0A;")
896+
};

extensions/modules/file/src/main/java/org/exist/xquery/modules/file/Sync.java

Lines changed: 6 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,8 @@ private Map<String, Sequence> getOptions(final Sequence parameter) throws XPathE
178178
}
179179
options.put(EXCLUDES_OPT, seq);
180180

181-
checkOption(optionsMap, PRUNE_OPT, options, Type.BOOLEAN);
182-
checkOption(optionsMap, AFTER_OPT, options, Type.DATE_TIME, Type.DATE_TIME_STAMP);
181+
checkOption(optionsMap, PRUNE_OPT, Type.BOOLEAN, options);
182+
checkOption(optionsMap, AFTER_OPT, Type.DATE_TIME, options);
183183
} else if (parameter.itemAt(0).getType() == Type.DATE_TIME) {
184184
options.put(AFTER_OPT, parameter);
185185
} else {
@@ -192,43 +192,25 @@ private Map<String, Sequence> getOptions(final Sequence parameter) throws XPathE
192192
private void checkOption(
193193
final AbstractMapType optionsMap,
194194
final String name,
195-
final Map<String, Sequence> options,
196-
final int... expectedTypes
195+
final int expectedType,
196+
final Map<String, Sequence> options
197197
) throws XPathException {
198198
final Sequence p = optionsMap.get(new StringValue(this, name));
199199

200200
if (p.isEmpty()) {
201201
return; // nothing to do, continue
202202
}
203203

204-
if (p.hasMany() || !isExpectedType(p.getItemType(), expectedTypes)) {
204+
if (p.hasMany() || !Type.subTypeOf(p.getItemType(),expectedType)) {
205205
throw new XPathException(this, ErrorCodes.XPTY0004,
206206
"Invalid value type for option \"" + name + "\", expected " +
207-
formatTypes(expectedTypes) + " got " +
207+
Type.getTypeName(expectedType) + " got " +
208208
Type.getTypeName(p.itemAt(0).getType()));
209209
}
210210

211211
options.put(name, p);
212212
}
213213

214-
private boolean isExpectedType(final int actualType, final int... expectedTypes) {
215-
for(int type : expectedTypes) {
216-
if(type == actualType) {
217-
return true;
218-
}
219-
}
220-
return false;
221-
}
222-
223-
private String formatTypes(final int... expectedTypes) {
224-
StringBuilder buff = new StringBuilder(Type.getTypeName(expectedTypes[0]));
225-
for(int i = 1; i < expectedTypes.length; i++) {
226-
buff.append(", ");
227-
buff.append(Type.getTypeName(expectedTypes[i]));
228-
}
229-
return buff.toString();
230-
}
231-
232214
private Sequence startSync(
233215
final String target,
234216
final String collectionPath,

extensions/modules/file/src/test/xquery/modules/file/serialize.xqm

Lines changed: 112 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -23,40 +23,126 @@ xquery version "3.1";
2323

2424
module namespace serialize="http://exist-db.org/testsuite/modules/file/serialize";
2525

26-
import module namespace test="http://exist-db.org/xquery/xqsuite" at "resource:org/exist/xquery/lib/xqsuite/xqsuite.xql";
2726
import module namespace file="http://exist-db.org/xquery/file";
27+
import module namespace helper="http://exist-db.org/xquery/test/util/helper" at "resource:util/helper.xqm";
28+
import module namespace fixtures="http://exist-db.org/xquery/test/util/fixtures" at "resource:util/fixtures.xqm";
29+
30+
31+
declare namespace test="http://exist-db.org/xquery/xqsuite";
32+
33+
34+
declare variable $serialize:suite := "serialize";
35+
declare variable $serialize:text := <node>data</node>;
36+
declare variable $serialize:xml := <root>
37+
<unary attribute="value" />
38+
<nested>
39+
text
40+
</nested>
41+
</root>;
2842

2943

3044
declare
31-
%test:pending("need to mechanism to setup a temporary file to work with")
32-
%test:assertEquals("datadata", "true", "true", "true")
45+
%test:tearDown
46+
function serialize:tear-down() as empty-sequence() {
47+
helper:clear-suite-fs($serialize:suite)
48+
};
49+
50+
declare
51+
%test:assertEquals("datamoredata")
3352
function serialize:append() {
53+
let $directory := helper:get-test-directory($serialize:suite)
54+
let $_ := file:mkdirs($directory)
55+
56+
let $path := $directory || "/append-test.txt"
3457

35-
let $node-set := text {"data"}
36-
let $path := system:get-exist-home() || "/test.txt"
37-
let $parameters := ()
38-
let $append := true()
39-
let $remove := file:delete($path)
40-
let $ser1 := file:serialize($node-set, $path, (), false())
41-
let $ser2 := file:serialize($node-set, $path, (), true())
42-
let $read := file:read($path)
43-
let $remove := file:delete($path)
44-
return ($read, $ser1, $ser2, $remove)
58+
let $_ := file:serialize-binary(xs:base64Binary("data"), $path)
59+
let $_ := file:serialize-binary(xs:base64Binary("moredata"), $path, true())
60+
61+
return file:read-binary($path) => xs:string()
4562
};
4663

4764
declare
48-
%test:pending("need to mechanism to setup a temporary file to work with")
49-
%test:assertEquals("data", "true", "true", "true")
65+
%test:assertEquals("moredata")
5066
function serialize:overwrite() {
67+
let $directory := helper:get-test-directory($serialize:suite)
68+
let $_ := file:mkdirs($directory)
69+
70+
let $path := $directory || "/overwrite-test.txt"
71+
let $_ := file:serialize-binary(xs:base64Binary("data"), $path)
72+
let $_ := file:serialize-binary(xs:base64Binary("moredata"), $path, false())
73+
74+
return file:read-binary($path) => xs:string()
75+
};
76+
77+
declare
78+
%test:assertEquals("<node>data</node>")
79+
function serialize:serialize3() {
80+
let $directory := helper:get-test-directory($serialize:suite)
81+
let $_ := file:mkdirs($directory)
82+
83+
let $path := $directory || "/serialize-3-test.txt"
84+
let $_ := file:serialize($serialize:text, $path, ())
85+
let $_ := file:serialize($serialize:text, $path, ())
86+
87+
return file:read($path)
88+
};
89+
90+
declare
91+
%test:assertTrue
92+
function serialize:xml-defaults() {
93+
let $directory := helper:get-test-directory($serialize:suite)
94+
let $_ := file:mkdirs($directory)
95+
96+
let $path := $directory || "/xml-defaults-test.xml"
97+
let $_ := file:serialize($serialize:xml, $path, ())
98+
99+
return file:read($path) eq
100+
"<root>" || $fixtures:NL ||
101+
" <unary attribute=""value""/>" || $fixtures:NL ||
102+
" <nested>" || $fixtures:NL ||
103+
" text" || $fixtures:NL ||
104+
" </nested>" || $fixtures:NL ||
105+
"</root>"
106+
};
107+
108+
declare
109+
%test:assertTrue
110+
function serialize:xml-final-newline() {
111+
let $directory := helper:get-test-directory($serialize:suite)
112+
let $_ := file:mkdirs($directory)
113+
114+
let $path := $directory || "/xml-final-newline.xml"
115+
let $_ := file:serialize($serialize:xml, $path,
116+
<output:serialization-parameters
117+
xmlns:output="http://www.w3.org/2010/xslt-xquery-serialization">
118+
<exist:insert-final-newline value="yes" />
119+
</output:serialization-parameters>)
120+
121+
return file:read($path) eq
122+
"<root>" || $fixtures:NL ||
123+
" <unary attribute=""value""/>" || $fixtures:NL ||
124+
" <nested>" || $fixtures:NL ||
125+
" text" || $fixtures:NL ||
126+
" </nested>" || $fixtures:NL ||
127+
"</root>" || $fixtures:NL
128+
};
129+
130+
declare
131+
%test:assertTrue
132+
function serialize:xml-minified() {
133+
let $directory := helper:get-test-directory($serialize:suite)
134+
let $_ := file:mkdirs($directory)
135+
136+
let $path := $directory || "/xml-minified.xml"
137+
let $_ := file:serialize($serialize:xml, $path,
138+
<output:serialization-parameters
139+
xmlns:output="http://www.w3.org/2010/xslt-xquery-serialization">
140+
<output:indent value="no"/>
141+
</output:serialization-parameters>)
142+
143+
return file:read($path) eq
144+
"<root><unary attribute=""value""/><nested>" || $fixtures:NL ||
145+
" text" || $fixtures:NL ||
146+
" </nested></root>"
147+
};
51148

52-
let $node-set := text {"data"}
53-
let $path := system:get-exist-home() || "/test.txt"
54-
let $parameters := ()
55-
let $append := true()
56-
let $remove := file:delete($path)
57-
let $ser1 := file:serialize($node-set, $path, (), false())
58-
let $ser2 := file:serialize($node-set, $path, (), false())
59-
let $read := file:read($path)
60-
let $remove := file:delete($path)
61-
return ($read, $ser1, $ser2, $remove)
62-
};

extensions/modules/file/src/test/xquery/modules/file/sync-serialize.xqm

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,3 +199,51 @@ function syse:unindented-no-declaration() {
199199
)
200200
)
201201
};
202+
203+
declare
204+
%test:assertEquals("true", "true")
205+
function syse:insert-final-newline-yes() {
206+
let $directory := helper:get-test-directory($syse:suite)
207+
let $sync := file:sync(
208+
$fixtures:collection,
209+
$directory,
210+
map{ "exist:insert-final-newline": true() }
211+
)
212+
213+
return (
214+
helper:assert-file-contents(
215+
$fixtures:XML_DECLARATION || $fixtures:NL ||
216+
$fixtures:SIMPLE_XML_INDENTED || $fixtures:NL,
217+
($directory, $syse:simple-file-name)
218+
),
219+
helper:assert-file-contents(
220+
$fixtures:XML_DECLARATION || $fixtures:NL ||
221+
$fixtures:COMPLEX_XML_INDENTED || $fixtures:NL,
222+
($directory, $syse:complex-file-name)
223+
)
224+
)
225+
};
226+
227+
declare
228+
%test:assertEquals("true", "true")
229+
function syse:insert-final-newline-no() {
230+
let $directory := helper:get-test-directory($syse:suite)
231+
let $sync := file:sync(
232+
$fixtures:collection,
233+
$directory,
234+
map{ "exist:insert-final-newline": false() }
235+
)
236+
237+
return (
238+
helper:assert-file-contents(
239+
$fixtures:XML_DECLARATION || $fixtures:NL ||
240+
$fixtures:SIMPLE_XML_INDENTED,
241+
($directory, $syse:simple-file-name)
242+
),
243+
helper:assert-file-contents(
244+
$fixtures:XML_DECLARATION || $fixtures:NL ||
245+
$fixtures:COMPLEX_XML_INDENTED,
246+
($directory, $syse:complex-file-name)
247+
)
248+
)
249+
};

0 commit comments

Comments
 (0)