LibreOffice Module xmloff (master) 1
namespacemap.cxx
Go to the documentation of this file.
1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 *
9 * This file incorporates work covered by the following license notice:
10 *
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18 */
19
20#include <sal/config.h>
21
22#include <rtl/ustring.hxx>
23#include <rtl/ustrbuf.hxx>
24#include <sal/log.hxx>
25
26#include <xmloff/xmltoken.hxx>
28
30#include <o3tl/string_view.hxx>
31
32
33using namespace ::xmloff::token;
34
35/* The basic idea of this class is that we have two ways to search our
36 * data, by prefix and by key. We use an unordered_map for fast prefix
37 * searching and an STL map for fast key searching.
38 *
39 * The references to an 'Index' refer to an earlier implementation of the
40 * name space map and remain to support code which uses these interfaces.
41 *
42 * In this implementation, key and index should always be the same number.
43 *
44 * All references to Indices are now deprecated and the corresponding
45 * 'Key' methods should be used instead
46 *
47 * Martin 13/06/01
48 */
49
50const OUString sEmpty;
51
53: m_sXMLNS( GetXMLToken ( XML_XMLNS ) )
54{
55 // approx worst-case size
56 m_aNameHash.reserve(20);
57 maKeyToNamespaceMap.reserve(20);
58}
59
61: m_sXMLNS( GetXMLToken ( XML_XMLNS ) )
62{
65}
66
68{
71 return *this;
72}
73
75{
76}
77
79{
80 m_aNameHash.clear();
81 m_aNameCache.clear();
82 maKeyToNamespaceMap.clear();
83 m_aQNameCache.clear();
84}
85
86
88{
89 return m_aNameHash == rCmp.m_aNameHash;
90}
91
92sal_uInt16 SvXMLNamespaceMap::Add_( const OUString& rPrefix, const OUString &rName, sal_uInt16 nKey )
93{
94 if( XML_NAMESPACE_UNKNOWN == nKey )
95 {
96 // create a new unique key with UNKNOWN flag set
98 do
99 {
100 auto aIter = maKeyToNamespaceMap.find ( nKey );
101 if( aIter == maKeyToNamespaceMap.end() )
102 break;
103 nKey++;
104 }
105 while ( true );
106 }
107 m_aNameHash.insert_or_assign( rPrefix, NameSpaceEntry{ rName, rPrefix, nKey} );
108 maKeyToNamespaceMap.insert_or_assign( nKey, KeyToNameSpaceMapEntry{ rName, rPrefix} );
109 return nKey;
110}
111
112sal_uInt16 SvXMLNamespaceMap::Add( const OUString& rPrefix, const OUString& rName,
113 sal_uInt16 nKey )
114{
115 if( XML_NAMESPACE_UNKNOWN == nKey )
116 nKey = GetKeyByName( rName );
117
118#ifdef NDEBUG
119 if( XML_NAMESPACE_NONE == nKey )
120 return USHRT_MAX;
121#else
122 assert(XML_NAMESPACE_NONE != nKey);
123#endif
124
125 if ( m_aNameHash.find ( rPrefix ) == m_aNameHash.end() )
126 nKey = Add_( rPrefix, rName, nKey );
127
128 return nKey;
129}
130
131sal_uInt16 SvXMLNamespaceMap::AddIfKnown( const OUString& rPrefix, const OUString& rName )
132{
133 sal_uInt16 nKey = GetKeyByName( rName );
134
135#ifdef NDEBUG
136 if( XML_NAMESPACE_NONE == nKey )
138#else
139 assert(nKey != XML_NAMESPACE_NONE);
140#endif
141
142 if( XML_NAMESPACE_UNKNOWN != nKey )
143 {
144 NameSpaceHash::const_iterator aIter = m_aNameHash.find( rPrefix );
145 if( aIter == m_aNameHash.end() || (*aIter).second.m_sName != rName )
146 nKey = Add_( rPrefix, rName, nKey );
147 }
148
149 return nKey;
150}
151
152
153sal_uInt16 SvXMLNamespaceMap::GetKeyByPrefix( const OUString& rPrefix ) const
154{
155 NameSpaceHash::const_iterator aIter = m_aNameHash.find(rPrefix);
156 return (aIter != m_aNameHash.end()) ? (*aIter).second.m_nKey : USHRT_MAX;
157}
158
159sal_uInt16 SvXMLNamespaceMap::GetKeyByName( const OUString& rName ) const
160{
161 sal_uInt16 nKey = XML_NAMESPACE_UNKNOWN;
162 auto aIter = std::find_if(m_aNameHash.cbegin(), m_aNameHash.cend(),
163 [&rName](const NameSpaceHash::value_type& rEntry) { return rEntry.second.m_sName == rName; });
164
165 if (aIter != m_aNameHash.cend())
166 nKey = (*aIter).second.m_nKey;
167
168 return nKey;
169}
170
171const OUString& SvXMLNamespaceMap::GetPrefixByKey( sal_uInt16 nKey ) const
172{
173 auto aIter = maKeyToNamespaceMap.find (nKey);
174 return (aIter != maKeyToNamespaceMap.end()) ? (*aIter).second.sPrefix : sEmpty;
175}
176
177const OUString& SvXMLNamespaceMap::GetNameByKey( sal_uInt16 nKey ) const
178{
179 auto aIter = maKeyToNamespaceMap.find (nKey);
180 return (aIter != maKeyToNamespaceMap.end()) ? (*aIter).second.sName : sEmpty;
181}
182
183OUString SvXMLNamespaceMap::GetAttrNameByKey( sal_uInt16 nKey ) const
184{
185 auto aIter = maKeyToNamespaceMap.find ( nKey );
186 if (aIter == maKeyToNamespaceMap.end())
187 return OUString();
188
189 const OUString & prefix( (*aIter).second.sPrefix );
190 if (prefix.isEmpty()) // default namespace
191 return m_sXMLNS;
192
193 return m_sXMLNS + ":" + prefix;
194}
195
196OUString SvXMLNamespaceMap::GetQNameByKey( sal_uInt16 nKey,
197 const OUString& rLocalName,
198 bool bCache) const
199{
200 // We always want to return at least the rLocalName...
201
202 switch ( nKey )
203 {
205 // ...if it's a completely unknown namespace, assert and return the local name
206 SAL_WARN("xmloff.core", "unknown namespace, probable missing xmlns: declaration");
207 [[fallthrough]];
209 // ...if there isn't one, return the local name
210 return rLocalName;
212 {
213 // ...if it's in the xmlns namespace, make the prefix
214 // don't bother caching this, it rarely happens
215 if (!rLocalName.isEmpty()) // not default namespace
216 return m_sXMLNS + ":" + rLocalName;
217 else
218 return m_sXMLNS;
219 }
221 {
222 // this namespace is reserved, and needs not to be declared
223 return GetXMLToken(XML_XML) + ":" + rLocalName;
224 }
225 default:
226 {
227 QNameCache::const_iterator aQCacheIter;
228 if (bCache)
229 aQCacheIter = m_aQNameCache.find ( QNamePair ( nKey, rLocalName ) );
230 else
231 aQCacheIter = m_aQNameCache.end();
232 if ( aQCacheIter != m_aQNameCache.end() )
233 return (*aQCacheIter).second;
234 else
235 {
236 auto aIter = maKeyToNamespaceMap.find ( nKey );
237 if ( aIter != maKeyToNamespaceMap.end() )
238 {
239 // ...if it's in our map, make the prefix
240 const OUString & prefix( (*aIter).second.sPrefix );
241 OUString sQName;
242 if (!prefix.isEmpty()) // not default namespace
243 sQName = prefix + ":" + rLocalName;
244 else
245 sQName = rLocalName;
246 if (bCache)
247 m_aQNameCache.emplace(QNamePair(nKey, rLocalName), sQName);
248 return sQName;
249 }
250 else
251 {
252 // ... if it isn't, this is a Bad Thing, assert and return the local name
253 assert(false);
254 return rLocalName;
255 }
256 }
257 }
258 }
259}
260
262 const OUString& rAttrValue,
263 OUString *pLocalName) const
264{
265 return GetKeyByQName(rAttrValue, nullptr, pLocalName, nullptr, QNameMode::AttrValue);
266}
267
273sal_uInt16 SvXMLNamespaceMap::GetKeyByQName(const OUString& rQName,
274 OUString *pPrefix,
275 OUString *pLocalName,
276 OUString *pNamespace,
277 QNameMode const eMode) const
278{
279 sal_uInt16 nKey;
280
281 NameSpaceHash::const_iterator it;
283 it = m_aNameCache.find ( rQName );
284 else
285 it = m_aNameCache.end();
286 if ( it != m_aNameCache.end() )
287 {
288 const NameSpaceEntry &rEntry = (*it).second;
289 if ( pPrefix )
290 *pPrefix = rEntry.m_sPrefix;
291 if ( pLocalName )
292 *pLocalName = rEntry.m_sName;
293 nKey = rEntry.m_nKey;
294 if ( pNamespace )
295 {
296 auto aMapIter = maKeyToNamespaceMap.find (nKey);
297 *pNamespace = aMapIter != maKeyToNamespaceMap.end() ? (*aMapIter).second.sName : OUString();
298 }
299 }
300 else
301 {
302 OUString sEntryPrefix, sEntryName;
303
304 sal_Int32 nColonPos = rQName.indexOf( ':' );
305 if( -1 == nColonPos )
306 {
307 // case: no ':' found -> default namespace
308 sEntryName = rQName;
309 }
310 else
311 {
312 // normal case: ':' found -> get prefix/suffix
313 sEntryPrefix = rQName.copy( 0, nColonPos );
314 sEntryName = rQName.copy( nColonPos + 1 );
315 }
316
317 if (eMode == QNameMode::AttrNameCached && sEntryName.indexOf(':') != -1)
318 {
319 SAL_INFO("xmloff", "invalid attribute name with multiple ':'");
320 assert(false);
322 }
323
324 if( pPrefix )
325 *pPrefix = sEntryPrefix;
326 if( pLocalName )
327 *pLocalName = sEntryName;
328
329 NameSpaceHash::const_iterator aIter = m_aNameHash.find( sEntryPrefix );
330 if ( aIter != m_aNameHash.end() )
331 {
332 // found: retrieve namespace key
333 nKey = (*aIter).second.m_nKey;
334 if ( pNamespace )
335 *pNamespace = (*aIter).second.m_sName;
336 }
337 else if ( sEntryPrefix == m_sXMLNS )
338 // not found, but xmlns prefix: return xmlns 'namespace'
339 nKey = XML_NAMESPACE_XMLNS;
340 else if( nColonPos == -1 )
341 // not found, and no namespace: 'namespace' none
342 nKey = XML_NAMESPACE_NONE;
343 else
345
347 {
348 m_aNameCache.insert_or_assign(rQName, NameSpaceEntry{std::move(sEntryName), std::move(sEntryPrefix), nKey});
349 }
350 }
351
352 return nKey;
353}
354
356{
357 return maKeyToNamespaceMap.empty() ? USHRT_MAX : (*maKeyToNamespaceMap.begin()).first;
358}
359
360sal_uInt16 SvXMLNamespaceMap::GetNextKey( sal_uInt16 nLastKey ) const
361{
362 auto aIter = maKeyToNamespaceMap.find ( nLastKey );
363 return (++aIter == maKeyToNamespaceMap.end()) ? USHRT_MAX : (*aIter).first;
364}
365
366
367// All methods after this are deprecated...
368
369sal_uInt16 SvXMLNamespaceMap::GetIndexByKey( sal_uInt16 nKey )
370{
371 return nKey;
372}
374{
375 return maKeyToNamespaceMap.empty() ? USHRT_MAX : (*maKeyToNamespaceMap.begin()).first;
376}
377
378sal_uInt16 SvXMLNamespaceMap::GetNextIndex( sal_uInt16 nOldIdx ) const
379{
380 auto aIter = maKeyToNamespaceMap.find ( nOldIdx );
381 return (++aIter == maKeyToNamespaceMap.end()) ? USHRT_MAX : (*aIter).first;
382}
383
384void SvXMLNamespaceMap::AddAtIndex( const OUString& rPrefix,
385 const OUString& rName, sal_uInt16 nKey )
386{
387 if( XML_NAMESPACE_UNKNOWN == nKey )
388 nKey = GetKeyByName( rName );
389
390 assert(XML_NAMESPACE_NONE != nKey);
391 if( XML_NAMESPACE_NONE != nKey && ! ( m_aNameHash.count ( rPrefix ) ) )
392 {
393 Add_( rPrefix, rName, nKey );
394 }
395}
396
397OUString SvXMLNamespaceMap::GetAttrNameByIndex( sal_uInt16 nIdx ) const
398{
399 return GetAttrNameByKey( nIdx );
400}
401
402const OUString& SvXMLNamespaceMap::GetPrefixByIndex( sal_uInt16 nIdx ) const
403{
404 auto aIter = maKeyToNamespaceMap.find (nIdx);
405 return (aIter != maKeyToNamespaceMap.end()) ? (*aIter).second.sPrefix : sEmpty;
406}
407
408const OUString& SvXMLNamespaceMap::GetNameByIndex( sal_uInt16 nIdx ) const
409{
410 auto aIter = maKeyToNamespaceMap.find (nIdx);
411 return (aIter != maKeyToNamespaceMap.end()) ? (*aIter).second.sName : sEmpty;
412}
413
414sal_uInt16 SvXMLNamespaceMap::GetIndexByPrefix( const OUString& rPrefix ) const
415{
416 NameSpaceHash::const_iterator aIter = m_aNameHash.find(rPrefix);
417 return (aIter != m_aNameHash.end()) ? (*aIter).second.m_nKey : USHRT_MAX;
418}
420 const OUString& rAttrName,
421 OUString *pLocalName) const
422{
423 return GetKeyByQName(rAttrName, nullptr, pLocalName, nullptr, QNameMode::AttrNameCached);
424}
425
426sal_uInt16 SvXMLNamespaceMap::GetKeyByAttrName( const OUString& rAttrName,
427 OUString *pPrefix,
428 OUString *pLocalName,
429 OUString *pNamespace ) const
430{
431 return GetKeyByQName(rAttrName, pPrefix, pLocalName, pNamespace, QNameMode::AttrNameCached);
432}
433
434bool SvXMLNamespaceMap::NormalizeURI( OUString& rName )
435{
436 // try OASIS + W3 URI normalization
437 bool bSuccess = NormalizeOasisURN( rName );
438 if( ! bSuccess )
439 bSuccess = NormalizeW3URI( rName );
440 return bSuccess;
441}
442
444{
445 // check if URI matches:
446 // http://www.w3.org/[0-9]*/[:letter:]*
447 // (year)/(WG name)
448 // For the following WG/standards names:
449 // - xforms
450
451 bool bSuccess = false;
452 const OUString& sURIPrefix = GetXMLToken( XML_URI_W3_PREFIX );
453 if( rName.startsWith( sURIPrefix ) )
454 {
455 const OUString& sURISuffix = GetXMLToken( XML_URI_XFORMS_SUFFIX );
456 sal_Int32 nCompareFrom = rName.getLength() - sURISuffix.getLength();
457 if( rName.subView( nCompareFrom ) == sURISuffix )
458 {
459 // found W3 prefix, and xforms suffix
460 rName = GetXMLToken( XML_N_XFORMS_1_0 );
461 bSuccess = true;
462 }
463 }
464 return bSuccess;
465}
466
468{
469 // #i38644#
470 // we exported the wrong namespace for smil, so we correct this here on load
471 // for older documents
472 if( IsXMLToken( rName, ::xmloff::token::XML_N_SVG ) )
473 {
474 rName = GetXMLToken( ::xmloff::token::XML_N_SVG_COMPAT );
475 return true;
476 }
477 else if( IsXMLToken( rName, ::xmloff::token::XML_N_FO ) )
478 {
479 rName = GetXMLToken( ::xmloff::token::XML_N_FO_COMPAT );
480 return true;
481 }
482 else if( IsXMLToken( rName, ::xmloff::token::XML_N_SMIL ) ||
483 IsXMLToken( rName, ::xmloff::token::XML_N_SMIL_OLD ) )
484 {
485 rName = GetXMLToken( ::xmloff::token::XML_N_SMIL_COMPAT );
486 return true;
487 }
488
489
490 // Check if URN matches
491 // :urn:oasis:names:tc:[^:]*:xmlns:[^:]*:1.[^:]*
492 // |---| |---| |-----|
493 // TC-Id Sub-Id Version
494
495 sal_Int32 nNameLen = rName.getLength();
496 // :urn:oasis:names:tc.*
497 const OUString& rOasisURN = GetXMLToken( XML_URN_OASIS_NAMES_TC );
498 if( !rName.startsWith( rOasisURN ) )
499 return false;
500
501 // :urn:oasis:names:tc:.*
502 sal_Int32 nPos = rOasisURN.getLength();
503 if( nPos >= nNameLen || rName[nPos] != ':' )
504 return false;
505
506 // :urn:oasis:names:tc:[^:]:.*
507 sal_Int32 nTCIdStart = nPos+1;
508 sal_Int32 nTCIdEnd = rName.indexOf( ':', nTCIdStart );
509 if( -1 == nTCIdEnd )
510 return false;
511
512 // :urn:oasis:names:tc:[^:]:xmlns.*
513 nPos = nTCIdEnd + 1;
514 std::u16string_view sTmp( rName.subView( nPos ) );
515 const OUString& rXMLNS = GetXMLToken( XML_XMLNS );
516 if( !o3tl::starts_with(sTmp, rXMLNS ) )
517 return false;
518
519 // :urn:oasis:names:tc:[^:]:xmlns:.*
520 nPos += rXMLNS.getLength();
521 if( nPos >= nNameLen || rName[nPos] != ':' )
522 return false;
523
524 // :urn:oasis:names:tc:[^:]:xmlns:[^:]*:.*
525 nPos = rName.indexOf( ':', nPos+1 );
526 if( -1 == nPos )
527 return false;
528
529 // :urn:oasis:names:tc:[^:]:xmlns:[^:]*:[^:][^:][^:][^:]*
530 sal_Int32 nVersionStart = nPos+1;
531 if( nVersionStart+2 >= nNameLen ||
532 -1 != rName.indexOf( ':', nVersionStart ) )
533 return false;
534
535 // :urn:oasis:names:tc:[^:]:xmlns:[^:]*:1\.[^:][^:]*
536 if( rName[nVersionStart] != '1' || rName[nVersionStart+1] != '.' )
537 return false;
538
539 // replace [tcid] with current TCID and version with current version.
540
541 rName = rName.subView( 0, nTCIdStart ) +
542 GetXMLToken( XML_OPENDOCUMENT ) +
543 rName.subView( nTCIdEnd, nVersionStart-nTCIdEnd ) +
544 GetXMLToken( XML_1_0 );
545
546 return true;
547}
548
549/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
OUString m_sName
OUString m_sPrefix
sal_uInt16 m_nKey
OUString GetQNameByKey(sal_uInt16 nKey, const OUString &rLocalName, bool bCache=true) const
QNameCache m_aQNameCache
const OUString & GetNameByKey(sal_uInt16 nKey) const
sal_uInt16 GetFirstKey() const
void AddAtIndex(const OUString &rPrefix, const OUString &rName, sal_uInt16 nKey)
const OUString & GetPrefixByIndex(sal_uInt16 nIdx) const
SvXMLNamespaceMap & operator=(const SvXMLNamespaceMap &rCmp)
sal_uInt16 GetKeyByPrefix(const OUString &rPrefix) const
sal_uInt16 GetKeyByAttrName(const OUString &rAttrName, OUString *pPrefix, OUString *pLocalName, OUString *pNamespace) const
static bool NormalizeOasisURN(OUString &rName)
static bool NormalizeW3URI(OUString &rName)
OUString GetAttrNameByKey(sal_uInt16 nKey) const
KeyToNameSpaceMap maKeyToNamespaceMap
sal_uInt16 GetKeyByAttrValueQName(const OUString &rAttrName, OUString *pLocalName) const
sal_uInt16 AddIfKnown(const OUString &rPrefix, const OUString &rName)
NameSpaceHash m_aNameCache
sal_uInt16 GetNextKey(sal_uInt16 nOldKey) const
sal_uInt16 GetIndexByPrefix(const OUString &rPrefix) const
sal_uInt16 GetNextIndex(sal_uInt16 nOldIdx) const
bool operator==(const SvXMLNamespaceMap &rCmp) const
SAL_DLLPRIVATE sal_uInt16 Add_(const OUString &rPrefix, const OUString &rName, sal_uInt16 nKey)
sal_uInt16 GetFirstIndex() const
sal_uInt16 Add(const OUString &rPrefix, const OUString &rName, sal_uInt16 nKey=XML_NAMESPACE_UNKNOWN)
OUString GetAttrNameByIndex(sal_uInt16 nIdx) const
sal_uInt16 GetKeyByQName(const OUString &rQName, OUString *pPrefix, OUString *pLocalName, OUString *pNamespace, QNameMode eMode) const
const OUString & GetNameByIndex(sal_uInt16 nIdx) const
static bool NormalizeURI(OUString &rName)
static sal_uInt16 GetIndexByKey(sal_uInt16 nKey)
sal_uInt16 GetKeyByName(const OUString &rName) const
NameSpaceHash m_aNameHash
const OUString & GetPrefixByKey(sal_uInt16 nKey) const
Mode eMode
sal_uInt16 nPos
#define SAL_WARN(area, stream)
#define SAL_INFO(area, stream)
constexpr OUStringLiteral first
const OUString sEmpty
const sal_uInt16 XML_NAMESPACE_UNKNOWN_FLAG
::std::pair< sal_uInt16, OUString > QNamePair
const sal_uInt16 XML_NAMESPACE_NONE
const sal_uInt16 XML_NAMESPACE_UNKNOWN
const sal_uInt16 XML_NAMESPACE_XMLNS
constexpr bool starts_with(std::basic_string_view< charT, traits > sv, std::basic_string_view< charT, traits > x) noexcept
Handling of tokens in XML:
bool IsXMLToken(std::u16string_view rString, enum XMLTokenEnum eToken)
compare eToken to the string
Definition: xmltoken.cxx:3597
const OUString & GetXMLToken(enum XMLTokenEnum eToken)
return the OUString representation for eToken
Definition: xmltoken.cxx:3541
constexpr sal_uInt16 XML_NAMESPACE_XML