Skip to content

Commit

Permalink
Fix numeric literal formatting
Browse files Browse the repository at this point in the history
Prior to this commit, in some situations, formatting changes the datatype of numeric literals (xsd:double and xsd:decimal). The change happens if the lexical form is ambiguous because it does not include a fractional part.

This commit checks for this condition and uses the quoted form in these cases.
  • Loading branch information
fkleedorfer committed Oct 30, 2024
1 parent 0739515 commit aa55f2d
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 2 deletions.
12 changes: 10 additions & 2 deletions src/main/java/de/atextor/turtle/formatter/TurtleFormatter.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ public class TurtleFormatter implements Function<Model, String>, BiConsumer<Mode

private static final Logger LOG = LoggerFactory.getLogger( TurtleFormatter.class );

private static final Pattern XSD_DECIMAL_UNQUOTED_REGEX = Pattern.compile("[+-]?\\d*\\.\\d+");

private static final Pattern XSD_DOUBLE_UNQUOTED_REGEX = Pattern.compile("(([+-]?\\d+\\.\\d+)|([+-]?\\.\\d+)|([+-]?\\d+))[eE][+-]?\\d+");

/**
* String escape sequences as described in <a href="https://www.w3.org/TR/turtle/#sec-escapes">Escape Sequences</a>.
* <p>
Expand Down Expand Up @@ -626,7 +630,8 @@ private State writeLiteral( final Literal literal, final State state ) {
if ( datatypeUri.equals( XSD.xdouble.getURI() ) ) {
if ( style.enableDoubleFormatting ) {
return state.write( style.doubleFormat.format( literal.getDouble() ) );
} else {
} else if (XSD_DOUBLE_UNQUOTED_REGEX.matcher(literal.getLexicalForm()).matches()) {
// only use unquoted form if it will be parsed as an xsd:double
return state.write( literal.getLexicalForm() );
}
}
Expand All @@ -637,7 +642,10 @@ private State writeLiteral( final Literal literal, final State state ) {
return state.write( quoteAndEscape( literal ) );
}
if ( datatypeUri.equals( XSD.decimal.getURI() ) ) {
return state.write( literal.getLexicalForm() );
if (XSD_DECIMAL_UNQUOTED_REGEX.matcher(literal.getLexicalForm()).matches()) {
// only use unquoted form if it will be parsed as an xsd:decimal
return state.write(literal.getLexicalForm());
}
}
if ( datatypeUri.equals( XSD.integer.getURI() ) ) {
return state.write( literal.getLexicalForm() );
Expand Down
73 changes: 73 additions & 0 deletions src/test/java/de/atextor/turtle/formatter/TurtleFormatterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1084,6 +1084,79 @@ public void testIntegerLiteralWithLeadingZeros(){
assertThat(result.trim()).isEqualTo(expected);
}

@Test
public void testDoubleLiteralWithoutFractions(){
String content = """
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix : <http://example.com/ns#> .
:thing :value "40"^^xsd:double.
""";
String expected = """
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix : <http://example.com/ns#> .
:thing :value "40"^^xsd:double .""";
final FormattingStyle style = FormattingStyle.DEFAULT;
final TurtleFormatter formatter = new TurtleFormatter(style);
final String result = formatter.applyToContent(content);
assertThat(result.trim()).isEqualTo(expected);
}

@Test
public void testDoubleLiteralWithFractions(){
String content = """
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix : <http://example.com/ns#> .
:thing :value "4.001E2"^^xsd:double.
""";
String expected = """
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix : <http://example.com/ns#> .
:thing :value 4.001E2 .""";
final FormattingStyle style = FormattingStyle.DEFAULT;
final TurtleFormatter formatter = new TurtleFormatter(style);
final String result = formatter.applyToContent(content);
assertThat(result.trim()).isEqualTo(expected);
}

@Test
public void testDecimalLiteralWithoutFractions(){
String content = """
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix : <http://example.com/ns#> .
:thing :value "40"^^xsd:decimal.
""";
String expected = """
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix : <http://example.com/ns#> .
:thing :value "40"^^xsd:decimal .""";
final FormattingStyle style = FormattingStyle.DEFAULT;
final TurtleFormatter formatter = new TurtleFormatter(style);
final String result = formatter.applyToContent(content);
assertThat(result.trim()).isEqualTo(expected);
}

@Test
public void testDecimalLiteralWithFractions(){
String content = """
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix : <http://example.com/ns#> .
:thing :value "40.0001"^^xsd:decimal.
""";
String expected = """
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
@prefix : <http://example.com/ns#> .
:thing :value 40.0001 .""";
final FormattingStyle style = FormattingStyle.DEFAULT;
final TurtleFormatter formatter = new TurtleFormatter(style);
final String result = formatter.applyToContent(content);
assertThat(result.trim()).isEqualTo(expected);
}


private Model modelFromString( final String content ) {
final Model model = ModelFactory.createDefaultModel();
final InputStream stream = new ByteArrayInputStream( content.getBytes( StandardCharsets.UTF_8 ) );
Expand Down

0 comments on commit aa55f2d

Please sign in to comment.