Version 24.2.2.2, tag libreoffice-24.2.2.2
[LibreOffice.git] / xmloff / source / core / namespacemap.cxx
blob1d844fe04cd90f1c2d85eb79f0ade8294724bf33
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <sal/config.h>
22 #include <rtl/ustring.hxx>
23 #include <rtl/ustrbuf.hxx>
24 #include <sal/log.hxx>
26 #include <xmloff/xmltoken.hxx>
27 #include <xmloff/namespacemap.hxx>
29 #include <xmloff/xmlnamespace.hxx>
30 #include <o3tl/string_view.hxx>
33 using namespace ::xmloff::token;
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.
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.
42 * In this implementation, key and index should always be the same number.
44 * All references to Indices are now deprecated and the corresponding
45 * 'Key' methods should be used instead
47 * Martin 13/06/01
50 const OUString sEmpty;
52 SvXMLNamespaceMap::SvXMLNamespaceMap()
53 : m_sXMLNS( GetXMLToken ( XML_XMLNS ) )
55 // approx worst-case size
56 m_aNameHash.reserve(20);
57 maKeyToNamespaceMap.reserve(20);
60 SvXMLNamespaceMap::SvXMLNamespaceMap( const SvXMLNamespaceMap& rMap )
61 : m_sXMLNS( GetXMLToken ( XML_XMLNS ) )
63 m_aNameHash = rMap.m_aNameHash;
64 maKeyToNamespaceMap = rMap.maKeyToNamespaceMap;
67 SvXMLNamespaceMap& SvXMLNamespaceMap::operator=( const SvXMLNamespaceMap& rMap )
69 m_aNameHash = rMap.m_aNameHash;
70 maKeyToNamespaceMap = rMap.maKeyToNamespaceMap;
71 return *this;
74 SvXMLNamespaceMap::~SvXMLNamespaceMap()
78 void SvXMLNamespaceMap::Clear()
80 m_aNameHash.clear();
81 m_aNameCache.clear();
82 maKeyToNamespaceMap.clear();
83 m_aQNameCache.clear();
87 bool SvXMLNamespaceMap::operator ==( const SvXMLNamespaceMap& rCmp ) const
89 return m_aNameHash == rCmp.m_aNameHash;
92 sal_uInt16 SvXMLNamespaceMap::Add_( const OUString& rPrefix, const OUString &rName, sal_uInt16 nKey )
94 if( XML_NAMESPACE_UNKNOWN == nKey )
96 // create a new unique key with UNKNOWN flag set
97 nKey = XML_NAMESPACE_UNKNOWN_FLAG;
100 auto aIter = maKeyToNamespaceMap.find ( nKey );
101 if( aIter == maKeyToNamespaceMap.end() )
102 break;
103 nKey++;
105 while ( true );
107 m_aNameHash.insert_or_assign( rPrefix, NameSpaceEntry{ rName, rPrefix, nKey} );
108 maKeyToNamespaceMap.insert_or_assign( nKey, KeyToNameSpaceMapEntry{ rName, rPrefix} );
109 return nKey;
112 sal_uInt16 SvXMLNamespaceMap::Add( const OUString& rPrefix, const OUString& rName,
113 sal_uInt16 nKey )
115 if( XML_NAMESPACE_UNKNOWN == nKey )
116 nKey = GetKeyByName( rName );
118 #ifdef NDEBUG
119 if( XML_NAMESPACE_NONE == nKey )
120 return USHRT_MAX;
121 #else
122 assert(XML_NAMESPACE_NONE != nKey);
123 #endif
125 if ( m_aNameHash.find ( rPrefix ) == m_aNameHash.end() )
126 nKey = Add_( rPrefix, rName, nKey );
128 return nKey;
131 sal_uInt16 SvXMLNamespaceMap::AddIfKnown( const OUString& rPrefix, const OUString& rName )
133 sal_uInt16 nKey = GetKeyByName( rName );
135 #ifdef NDEBUG
136 if( XML_NAMESPACE_NONE == nKey )
137 return XML_NAMESPACE_UNKNOWN;
138 #else
139 assert(nKey != XML_NAMESPACE_NONE);
140 #endif
142 if( XML_NAMESPACE_UNKNOWN != nKey )
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 );
149 return nKey;
153 sal_uInt16 SvXMLNamespaceMap::GetKeyByPrefix( const OUString& rPrefix ) const
155 NameSpaceHash::const_iterator aIter = m_aNameHash.find(rPrefix);
156 return (aIter != m_aNameHash.end()) ? (*aIter).second.m_nKey : USHRT_MAX;
159 sal_uInt16 SvXMLNamespaceMap::GetKeyByName( const OUString& rName ) const
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; });
165 if (aIter != m_aNameHash.cend())
166 nKey = (*aIter).second.m_nKey;
168 return nKey;
171 const OUString& SvXMLNamespaceMap::GetPrefixByKey( sal_uInt16 nKey ) const
173 auto aIter = maKeyToNamespaceMap.find (nKey);
174 return (aIter != maKeyToNamespaceMap.end()) ? (*aIter).second.sPrefix : sEmpty;
177 const OUString& SvXMLNamespaceMap::GetNameByKey( sal_uInt16 nKey ) const
179 auto aIter = maKeyToNamespaceMap.find (nKey);
180 return (aIter != maKeyToNamespaceMap.end()) ? (*aIter).second.sName : sEmpty;
183 OUString SvXMLNamespaceMap::GetAttrNameByKey( sal_uInt16 nKey ) const
185 auto aIter = maKeyToNamespaceMap.find ( nKey );
186 if (aIter == maKeyToNamespaceMap.end())
187 return OUString();
189 const OUString & prefix( (*aIter).second.sPrefix );
190 if (prefix.isEmpty()) // default namespace
191 return m_sXMLNS;
193 return m_sXMLNS + ":" + prefix;
196 OUString SvXMLNamespaceMap::GetQNameByKey( sal_uInt16 nKey,
197 const OUString& rLocalName,
198 bool bCache) const
200 // We always want to return at least the rLocalName...
202 switch ( nKey )
204 case XML_NAMESPACE_UNKNOWN:
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]];
208 case XML_NAMESPACE_NONE:
209 // ...if there isn't one, return the local name
210 return rLocalName;
211 case XML_NAMESPACE_XMLNS:
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;
220 case XML_NAMESPACE_XML:
222 // this namespace is reserved, and needs not to be declared
223 return GetXMLToken(XML_XML) + ":" + rLocalName;
225 default:
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
236 auto aIter = maKeyToNamespaceMap.find ( nKey );
237 if ( aIter != maKeyToNamespaceMap.end() )
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;
250 else
252 // ... if it isn't, this is a Bad Thing, assert and return the local name
253 assert(false);
254 return rLocalName;
261 sal_uInt16 SvXMLNamespaceMap::GetKeyByAttrValueQName(
262 const OUString& rAttrValue,
263 OUString *pLocalName) const
265 return GetKeyByQName(rAttrValue, nullptr, pLocalName, nullptr, QNameMode::AttrValue);
269 @param rQName either attribute name or qualified/namespaced attribute value
270 @param bCacheAttrName true: rQName is element or attribute name, cache it
271 false: rQName is attribute value, may contain extra ':', don't cache it
273 sal_uInt16 SvXMLNamespaceMap::GetKeyByQName(const OUString& rQName,
274 OUString *pPrefix,
275 OUString *pLocalName,
276 OUString *pNamespace,
277 QNameMode const eMode) const
279 sal_uInt16 nKey;
281 NameSpaceHash::const_iterator it;
282 if (eMode == QNameMode::AttrNameCached)
283 it = m_aNameCache.find ( rQName );
284 else
285 it = m_aNameCache.end();
286 if ( it != m_aNameCache.end() )
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 )
296 auto aMapIter = maKeyToNamespaceMap.find (nKey);
297 *pNamespace = aMapIter != maKeyToNamespaceMap.end() ? (*aMapIter).second.sName : OUString();
300 else
302 OUString sEntryPrefix, sEntryName;
304 sal_Int32 nColonPos = rQName.indexOf( ':' );
305 if( -1 == nColonPos )
307 // case: no ':' found -> default namespace
308 sEntryName = rQName;
310 else
312 // normal case: ':' found -> get prefix/suffix
313 sEntryPrefix = rQName.copy( 0, nColonPos );
314 sEntryName = rQName.copy( nColonPos + 1 );
317 if (eMode == QNameMode::AttrNameCached && sEntryName.indexOf(':') != -1)
319 SAL_INFO("xmloff", "invalid attribute name with multiple ':'");
320 assert(false);
321 return XML_NAMESPACE_UNKNOWN;
324 if( pPrefix )
325 *pPrefix = sEntryPrefix;
326 if( pLocalName )
327 *pLocalName = sEntryName;
329 NameSpaceHash::const_iterator aIter = m_aNameHash.find( sEntryPrefix );
330 if ( aIter != m_aNameHash.end() )
332 // found: retrieve namespace key
333 nKey = (*aIter).second.m_nKey;
334 if ( pNamespace )
335 *pNamespace = (*aIter).second.m_sName;
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
344 nKey = XML_NAMESPACE_UNKNOWN;
346 if (eMode == QNameMode::AttrNameCached)
348 m_aNameCache.insert_or_assign(rQName, NameSpaceEntry{std::move(sEntryName), std::move(sEntryPrefix), nKey});
352 return nKey;
355 sal_uInt16 SvXMLNamespaceMap::GetFirstKey() const
357 return maKeyToNamespaceMap.empty() ? USHRT_MAX : (*maKeyToNamespaceMap.begin()).first;
360 sal_uInt16 SvXMLNamespaceMap::GetNextKey( sal_uInt16 nLastKey ) const
362 auto aIter = maKeyToNamespaceMap.find ( nLastKey );
363 assert(aIter != maKeyToNamespaceMap.end());
364 return (++aIter == maKeyToNamespaceMap.end()) ? USHRT_MAX : (*aIter).first;
368 // All methods after this are deprecated...
370 sal_uInt16 SvXMLNamespaceMap::GetIndexByKey( sal_uInt16 nKey )
372 return nKey;
374 sal_uInt16 SvXMLNamespaceMap::GetFirstIndex() const
376 return maKeyToNamespaceMap.empty() ? USHRT_MAX : (*maKeyToNamespaceMap.begin()).first;
379 sal_uInt16 SvXMLNamespaceMap::GetNextIndex( sal_uInt16 nOldIdx ) const
381 auto aIter = maKeyToNamespaceMap.find ( nOldIdx );
382 assert(aIter != maKeyToNamespaceMap.end());
383 return (++aIter == maKeyToNamespaceMap.end()) ? USHRT_MAX : (*aIter).first;
386 void SvXMLNamespaceMap::AddAtIndex( const OUString& rPrefix,
387 const OUString& rName, sal_uInt16 nKey )
389 if( XML_NAMESPACE_UNKNOWN == nKey )
390 nKey = GetKeyByName( rName );
392 assert(XML_NAMESPACE_NONE != nKey);
393 if( XML_NAMESPACE_NONE != nKey && ! ( m_aNameHash.count ( rPrefix ) ) )
395 Add_( rPrefix, rName, nKey );
399 OUString SvXMLNamespaceMap::GetAttrNameByIndex( sal_uInt16 nIdx ) const
401 return GetAttrNameByKey( nIdx );
404 const OUString& SvXMLNamespaceMap::GetPrefixByIndex( sal_uInt16 nIdx ) const
406 auto aIter = maKeyToNamespaceMap.find (nIdx);
407 return (aIter != maKeyToNamespaceMap.end()) ? (*aIter).second.sPrefix : sEmpty;
410 const OUString& SvXMLNamespaceMap::GetNameByIndex( sal_uInt16 nIdx ) const
412 auto aIter = maKeyToNamespaceMap.find (nIdx);
413 return (aIter != maKeyToNamespaceMap.end()) ? (*aIter).second.sName : sEmpty;
416 sal_uInt16 SvXMLNamespaceMap::GetIndexByPrefix( const OUString& rPrefix ) const
418 NameSpaceHash::const_iterator aIter = m_aNameHash.find(rPrefix);
419 return (aIter != m_aNameHash.end()) ? (*aIter).second.m_nKey : USHRT_MAX;
421 sal_uInt16 SvXMLNamespaceMap::GetKeyByAttrName(
422 const OUString& rAttrName,
423 OUString *pLocalName) const
425 return GetKeyByQName(rAttrName, nullptr, pLocalName, nullptr, QNameMode::AttrNameCached);
428 sal_uInt16 SvXMLNamespaceMap::GetKeyByAttrName( const OUString& rAttrName,
429 OUString *pPrefix,
430 OUString *pLocalName,
431 OUString *pNamespace ) const
433 return GetKeyByQName(rAttrName, pPrefix, pLocalName, pNamespace, QNameMode::AttrNameCached);
436 bool SvXMLNamespaceMap::NormalizeURI( OUString& rName )
438 // try OASIS + W3 URI normalization
439 bool bSuccess = NormalizeOasisURN( rName );
440 if( ! bSuccess )
441 bSuccess = NormalizeW3URI( rName );
442 return bSuccess;
445 bool SvXMLNamespaceMap::NormalizeW3URI( OUString& rName )
447 // check if URI matches:
448 // http://www.w3.org/[0-9]*/[:letter:]*
449 // (year)/(WG name)
450 // For the following WG/standards names:
451 // - xforms
453 bool bSuccess = false;
454 const OUString& sURIPrefix = GetXMLToken( XML_URI_W3_PREFIX );
455 if( rName.startsWith( sURIPrefix ) )
457 const OUString& sURISuffix = GetXMLToken( XML_URI_XFORMS_SUFFIX );
458 sal_Int32 nCompareFrom = rName.getLength() - sURISuffix.getLength();
459 if( rName.subView( nCompareFrom ) == sURISuffix )
461 // found W3 prefix, and xforms suffix
462 rName = GetXMLToken( XML_N_XFORMS_1_0 );
463 bSuccess = true;
466 return bSuccess;
469 bool SvXMLNamespaceMap::NormalizeOasisURN( OUString& rName )
471 // #i38644#
472 // we exported the wrong namespace for smil, so we correct this here on load
473 // for older documents
474 if( IsXMLToken( rName, ::xmloff::token::XML_N_SVG ) )
476 rName = GetXMLToken( ::xmloff::token::XML_N_SVG_COMPAT );
477 return true;
479 else if( IsXMLToken( rName, ::xmloff::token::XML_N_FO ) )
481 rName = GetXMLToken( ::xmloff::token::XML_N_FO_COMPAT );
482 return true;
484 else if( IsXMLToken( rName, ::xmloff::token::XML_N_SMIL ) ||
485 IsXMLToken( rName, ::xmloff::token::XML_N_SMIL_OLD ) )
487 rName = GetXMLToken( ::xmloff::token::XML_N_SMIL_COMPAT );
488 return true;
492 // Check if URN matches
493 // :urn:oasis:names:tc:[^:]*:xmlns:[^:]*:1.[^:]*
494 // |---| |---| |-----|
495 // TC-Id Sub-Id Version
497 sal_Int32 nNameLen = rName.getLength();
498 // :urn:oasis:names:tc.*
499 const OUString& rOasisURN = GetXMLToken( XML_URN_OASIS_NAMES_TC );
500 if( !rName.startsWith( rOasisURN ) )
501 return false;
503 // :urn:oasis:names:tc:.*
504 sal_Int32 nPos = rOasisURN.getLength();
505 if( nPos >= nNameLen || rName[nPos] != ':' )
506 return false;
508 // :urn:oasis:names:tc:[^:]:.*
509 sal_Int32 nTCIdStart = nPos+1;
510 sal_Int32 nTCIdEnd = rName.indexOf( ':', nTCIdStart );
511 if( -1 == nTCIdEnd )
512 return false;
514 // :urn:oasis:names:tc:[^:]:xmlns.*
515 nPos = nTCIdEnd + 1;
516 std::u16string_view sTmp( rName.subView( nPos ) );
517 const OUString& rXMLNS = GetXMLToken( XML_XMLNS );
518 if( !o3tl::starts_with(sTmp, rXMLNS ) )
519 return false;
521 // :urn:oasis:names:tc:[^:]:xmlns:.*
522 nPos += rXMLNS.getLength();
523 if( nPos >= nNameLen || rName[nPos] != ':' )
524 return false;
526 // :urn:oasis:names:tc:[^:]:xmlns:[^:]*:.*
527 nPos = rName.indexOf( ':', nPos+1 );
528 if( -1 == nPos )
529 return false;
531 // :urn:oasis:names:tc:[^:]:xmlns:[^:]*:[^:][^:][^:][^:]*
532 sal_Int32 nVersionStart = nPos+1;
533 if( nVersionStart+2 >= nNameLen ||
534 -1 != rName.indexOf( ':', nVersionStart ) )
535 return false;
537 // :urn:oasis:names:tc:[^:]:xmlns:[^:]*:1\.[^:][^:]*
538 if( rName[nVersionStart] != '1' || rName[nVersionStart+1] != '.' )
539 return false;
541 // replace [tcid] with current TCID and version with current version.
543 rName = rName.subView( 0, nTCIdStart ) +
544 GetXMLToken( XML_OPENDOCUMENT ) +
545 rName.subView( nTCIdEnd, nVersionStart-nTCIdEnd ) +
546 GetXMLToken( XML_1_0 );
548 return true;
551 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */