Skip to content

Commit cca179c

Browse files
Handle missing prefix on "rdf:about" (#5)
* Handle the fact that sometimes "rdf:about" is lacking the prefix. * Fix for XML parsers that read empty namespace.
1 parent 937285f commit cca179c

3 files changed

Lines changed: 113 additions & 77 deletions

File tree

src/commonMain/kotlin/com/ashampoo/xmp/impl/Utils.kt

Lines changed: 71 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,19 @@ object Utils {
2525
*/
2626
const val UUID_LENGTH = 32 + UUID_SEGMENT_COUNT
2727

28+
const val HEX_RADIX = 16
29+
30+
private const val XML_NAME_LENGTH = 0x0100
31+
2832
/**
2933
* table of XML name start chars (<= 0xFF)
3034
*/
31-
private val xmlNameStartChars = BooleanArray(0x0100)
35+
private val xmlNameStartChars = BooleanArray(XML_NAME_LENGTH)
3236

3337
/**
3438
* table of XML name chars (<= 0xFF)
3539
*/
36-
private val xmlNameChars = BooleanArray(0x0100)
40+
private val xmlNameChars = BooleanArray(XML_NAME_LENGTH)
3741

3842
private val controlCharRegex = Regex("[\\p{Cntrl}]")
3943

@@ -48,18 +52,16 @@ object Utils {
4852
* normalization rules:
4953
*
5054
* * The primary subtag is lower case, the suggested practice of ISO 639.
51-
* * All 2 letter secondary subtags are upper case, the suggested
52-
* practice of ISO 3166.
55+
* * All 2 letter secondary subtags are upper case, the suggested practice of ISO 3166.
5356
* * All other subtags are lower case.
5457
*
55-
*
5658
* @param value raw value
5759
* @return Returns the normalized value.
5860
*/
5961
@kotlin.jvm.JvmStatic
6062
fun normalizeLangValue(value: String): String {
6163

62-
// don't normalize x-default
64+
/* Don't normalize x-default */
6365
if (XMPConst.X_DEFAULT == value)
6466
return value
6567

@@ -80,12 +82,14 @@ object Utils {
8082
/* Leave as is. */
8183
}
8284

83-
else ->
85+
else -> {
86+
8487
/* Convert second subtag to uppercase, all other to lowercase */
8588
if (subTag != 2)
8689
buffer.append(value[i].lowercaseChar())
8790
else
8891
buffer.append(value[i].uppercaseChar())
92+
}
8993
}
9094
}
9195

@@ -98,10 +102,11 @@ object Utils {
98102
* * [qualName="value"] - An element in an array of structs, chosen by a field value.
99103
* * [?qualName="value"] - An element in an array, chosen by a qualifier value.
100104
*
101-
* The value portion is a string quoted by ''' or '"'. The value may contain
102-
* any character including a doubled quoting character. The value may be
103-
* empty. *Note:* It is assumed that the expression is formal
104-
* correct
105+
* The value portion is a string quoted by ''' or '"'.
106+
* The value may contain any character including a doubled quoting character.
107+
* The value may be empty.
108+
*
109+
* *Note:* It is assumed that the expression is formal correct
105110
*
106111
* @param selector the selector
107112
* @return Returns an array where the first entry contains the name and the second the value.
@@ -149,12 +154,13 @@ object Utils {
149154
* Check some requirements for an UUID:
150155
*
151156
* * Length of the UUID is 32
152-
* * The Delimiter count is 4 and all the 4 delimiter are on their right position (8,13,18,23)
157+
* * The Delimiter count is 4 and all the 4 delimiter are on their right position (8, 13, 18, 23)
153158
*
154159
* @param uuid uuid to test
155160
* @return true - this is a well formed UUID, false - UUID has not the expected format
156161
*/
157162
@kotlin.jvm.JvmStatic
163+
@Suppress("MagicNumber")
158164
fun checkUUIDFormat(uuid: String?): Boolean {
159165

160166
var result = true
@@ -167,7 +173,9 @@ object Utils {
167173
while (delimPos < uuid.length) {
168174

169175
if (uuid[delimPos] == '-') {
176+
170177
delimCnt++
178+
171179
result = result && (delimPos == 8 || delimPos == 13 || delimPos == 18 || delimPos == 23)
172180
}
173181

@@ -178,10 +186,10 @@ object Utils {
178186
}
179187

180188
/**
181-
* Simple check for valid XMLNames. Within ASCII range<br></br>
182-
* ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6]<br></br>
183-
* are accepted, above all characters (which is not entirely
184-
* correct according to the XML Spec.
189+
* Simple check for valid XMLNames. Within ASCII range
190+
* ":" | [A-Z] | "_" | [a-z] | [#xC0-#xD6] | [#xD8-#xF6]
191+
* are accepted, above all characters
192+
* (which is not entirely correct according to the XML Spec).
185193
*
186194
* @param name an XML Name
187195
* @return Return `true` if the name is correct.
@@ -233,18 +241,21 @@ object Utils {
233241
* @return Returns the value ready for XML output.
234242
*/
235243
@kotlin.jvm.JvmStatic
244+
@Suppress("ComplexCondition", "kotlin:S3776")
236245
fun escapeXML(value: String, forAttribute: Boolean, escapeWhitespaces: Boolean): String {
237246

238247
// quick check if character are contained that need special treatment
239248
var needsEscaping = false
240249

241250
for (index in 0 until value.length) {
242251

243-
val c = value[index]
252+
val char = value[index]
253+
254+
val isControlChar = char == '\t' || char == '\n' || char == '\r'
244255

245256
if (
246-
c == '<' || c == '>' || c == '&' || escapeWhitespaces &&
247-
(c == '\t' || c == '\n' || c == '\r') || forAttribute && c == '"'
257+
char == '<' || char == '>' || char == '&' ||
258+
escapeWhitespaces && isControlChar || forAttribute && char == '"'
248259
) {
249260
needsEscaping = true
250261
break
@@ -260,7 +271,9 @@ object Utils {
260271
@Suppress("LoopWithTooManyJumpStatements")
261272
for (char in value) {
262273

263-
if (!(escapeWhitespaces && (char == '\t' || char == '\n' || char == '\r'))) {
274+
val isControlChar = char == '\t' || char == '\n' || char == '\r'
275+
276+
if (!(escapeWhitespaces && isControlChar)) {
264277

265278
when (char) {
266279

@@ -295,7 +308,7 @@ object Utils {
295308
// write control chars escaped,
296309
// if there are others than tab, LF and CR the xml will become invalid.
297310
buffer.append("&#x")
298-
buffer.append(char.code.toString(16).uppercase())
311+
buffer.append(char.code.toString(HEX_RADIX).uppercase())
299312
buffer.append(';')
300313
}
301314
}
@@ -318,47 +331,64 @@ object Utils {
318331
* All characters according to the XML Spec 1.1 are accepted:
319332
* http://www.w3.org/TR/xml11/#NT-NameStartChar
320333
*
321-
* @param ch a character
334+
* @param char a character
322335
* @return Returns true if the character is a valid first char of an XML name.
323336
*/
324-
private fun isNameStartChar(ch: Char): Boolean =
325-
ch.code <= 0xFF && xmlNameStartChars[ch.code] || ch.code >= 0x100 && ch.code <= 0x2FF ||
326-
ch.code >= 0x370 && ch.code <= 0x37D || ch.code >= 0x37F && ch.code <= 0x1FFF ||
327-
ch.code >= 0x200C && ch.code <= 0x200D || ch.code >= 0x2070 && ch.code <= 0x218F ||
328-
ch.code >= 0x2C00 && ch.code <= 0x2FEF || ch.code >= 0x3001 && ch.code <= 0xD7FF ||
329-
ch.code >= 0xF900 && ch.code <= 0xFDCF || ch.code >= 0xFDF0 && ch.code <= 0xFFFD ||
330-
ch.code >= 0x10000 && ch.code <= 0xEFFFF
337+
@Suppress("MagicNumber", "kotlin:S3776")
338+
private fun isNameStartChar(char: Char): Boolean =
339+
char.code <= 0xFF && xmlNameStartChars[char.code] ||
340+
char.code >= 0x100 && char.code <= 0x2FF ||
341+
char.code >= 0x370 && char.code <= 0x37D ||
342+
char.code >= 0x37F && char.code <= 0x1FFF ||
343+
char.code >= 0x200C && char.code <= 0x200D ||
344+
char.code >= 0x2070 && char.code <= 0x218F ||
345+
char.code >= 0x2C00 && char.code <= 0x2FEF ||
346+
char.code >= 0x3001 && char.code <= 0xD7FF ||
347+
char.code >= 0xF900 && char.code <= 0xFDCF ||
348+
char.code >= 0xFDF0 && char.code <= 0xFFFD ||
349+
char.code >= 0x10000 && char.code <= 0xEFFFF
331350

332351
/**
333352
* Simple check if a character is a valid XML name char
334353
* (every char except the first one), according to the XML Spec 1.1:
335354
* http://www.w3.org/TR/xml11/#NT-NameChar
336355
*
337-
* @param ch a character
356+
* @param char a character
338357
* @return Returns true if the character is a valid char of an XML name.
339358
*/
340-
private fun isNameChar(ch: Char): Boolean =
341-
ch.code <= 0xFF && xmlNameChars[ch.code] || isNameStartChar(ch) ||
342-
ch.code >= 0x300 && ch.code <= 0x36F || ch.code >= 0x203F && ch.code <= 0x2040
359+
@Suppress("MagicNumber")
360+
private fun isNameChar(char: Char): Boolean =
361+
char.code <= 0xFF && xmlNameChars[char.code] ||
362+
isNameStartChar(char) ||
363+
char.code >= 0x300 && char.code <= 0x36F ||
364+
char.code >= 0x203F && char.code <= 0x2040
343365

344366
/**
345367
* Initializes the char tables for the chars 0x00-0xFF for later use,
346368
* according to the XML 1.1 specification at http://www.w3.org/TR/xml11
347369
*/
370+
@Suppress("MagicNumber")
348371
private fun initCharTables() {
349372

350-
var ch = 0.toChar()
373+
var char = 0.toChar()
351374

352-
while (ch < xmlNameChars.size.toChar()) {
375+
while (char < xmlNameChars.size.toChar()) {
353376

354-
xmlNameStartChars[ch.code] = ch == ':' || 'A' <= ch && ch <= 'Z' || ch == '_' ||
355-
'a' <= ch && ch <= 'z' || 0xC0 <= ch.code && ch.code <= 0xD6 ||
356-
0xD8 <= ch.code && ch.code <= 0xF6 || 0xF8 <= ch.code && ch.code <= 0xFF
377+
xmlNameStartChars[char.code] = char == ':' ||
378+
'A' <= char && char <= 'Z' ||
379+
char == '_' ||
380+
'a' <= char && char <= 'z' ||
381+
0xC0 <= char.code && char.code <= 0xD6 ||
382+
0xD8 <= char.code && char.code <= 0xF6 ||
383+
0xF8 <= char.code && char.code <= 0xFF
357384

358-
xmlNameChars[ch.code] = xmlNameStartChars[ch.code] || ch == '-' || ch == '.' ||
359-
'0' <= ch && ch <= '9' || ch.code == 0xB7
385+
xmlNameChars[char.code] = xmlNameStartChars[char.code] ||
386+
char == '-' ||
387+
char == '.' ||
388+
'0' <= char && char <= '9' ||
389+
char.code == 0xB7
360390

361-
ch++
391+
char++
362392
}
363393
}
364394
}

src/commonMain/kotlin/com/ashampoo/xmp/impl/XMPRDFParser.kt

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,7 @@ internal object XMPRDFParser {
863863
XMPError.BADRDF
864864
)
865865

866-
// Fix a legacy DC namespace
866+
/* Fix a legacy DC namespace */
867867
if (XMPConst.NS_DC_DEPRECATED == namespace)
868868
namespace = XMPConst.NS_DC
869869

@@ -1107,20 +1107,26 @@ internal object XMPRDFParser {
11071107
*/
11081108
private fun getRDFTermKind(node: Node): Int {
11091109

1110-
var namespace = when (node) {
1110+
val namespace = when (node) {
11111111
is Element -> node.namespaceURI
11121112
is Attr -> node.namespaceURI
11131113
else -> throw XMPException("Unknown Node ${node.nodeType}", XMPError.BADXMP)
11141114
}
11151115

1116-
if (namespace == null &&
1116+
/*
1117+
* This code handles the fact that sometimes "rdf:about" and "rdf:ID"
1118+
* come without the prefix.
1119+
*
1120+
* Note that the check for the namespace must be for NULL or EMPTY, because depending
1121+
* on the used XML parser implementation the resulting namespace may be an empty string.
1122+
*/
1123+
@Suppress("ComplexCondition")
1124+
val mustBeRdfNamespace = namespace.isNullOrEmpty() &&
11171125
("about" == node.nodeName || "ID" == node.nodeName) &&
1118-
node is Attr && XMPConst.NS_RDF == node.ownerElement?.namespaceURI
1119-
) {
1120-
namespace = XMPConst.NS_RDF
1121-
}
1126+
node is Attr &&
1127+
XMPConst.NS_RDF == node.ownerElement?.namespaceURI
11221128

1123-
if (namespace == XMPConst.NS_RDF) {
1129+
if (mustBeRdfNamespace || namespace == XMPConst.NS_RDF) {
11241130

11251131
when (node.nodeName) {
11261132

@@ -1133,7 +1139,7 @@ internal object XMPRDFParser {
11331139
"rdf:Description" ->
11341140
return RDFTERM_DESCRIPTION
11351141

1136-
"rdf:about" ->
1142+
"rdf:about", "about" ->
11371143
return RDFTERM_ABOUT
11381144

11391145
"resource" ->
@@ -1142,7 +1148,7 @@ internal object XMPRDFParser {
11421148
"rdf:RDF" ->
11431149
return RDFTERM_RDF
11441150

1145-
"ID" ->
1151+
"rdf:ID", "ID" ->
11461152
return RDFTERM_ID
11471153

11481154
"nodeID" ->

src/commonTest/resources/com/ashampoo/xmp/sample_1.xmp

Lines changed: 26 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<x:xmpmeta xmlns:x='adobe:ns:meta/' x:xmptk='Image::ExifTool 12.27'>
33
<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>
44

5-
<rdf:Description rdf:about=''
5+
<rdf:Description about=''
66
xmlns:darktable='http://darktable.sf.net/'>
77
<darktable:auto_presets_applied>1</darktable:auto_presets_applied>
88
<darktable:history>
@@ -181,28 +181,28 @@
181181
</rdf:Description>
182182
</rdf:RDF>
183183
</x:xmpmeta>
184-
185-
186-
187-
188-
189-
190-
191-
192-
193-
194-
195-
196-
197-
198-
199-
200-
201-
202-
203-
204-
205-
206-
207-
208-
<?xpacket end='w'?>
184+
185+
186+
187+
188+
189+
190+
191+
192+
193+
194+
195+
196+
197+
198+
199+
200+
201+
202+
203+
204+
205+
206+
207+
208+
<?xpacket end='w'?>

0 commit comments

Comments
 (0)