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>
27 #include <xmloff/namespacemap.hxx>
28 
29 #include <xmloff/xmlnamespace.hxx>
30 #include <o3tl/string_view.hxx>
31 
32 
33 using 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 
50 const OUString sEmpty;
51 
53 : sXMLNS( GetXMLToken ( XML_XMLNS ) )
54 {
55  // approx worst-case size
56  aNameHash.reserve(20);
57  aNameMap.reserve(20);
58 }
59 
61 : sXMLNS( GetXMLToken ( XML_XMLNS ) )
62 {
63  aNameHash = rMap.aNameHash;
64  aNameMap = rMap.aNameMap;
65 }
66 
68 {
69  aNameHash = rMap.aNameHash;
70  aNameMap = rMap.aNameMap;
71  return *this;
72 }
73 
75 {
76 }
77 
79 {
80  aNameHash.clear();
81  aNameCache.clear();
82  aNameMap.clear();
83  aQNameCache.clear();
84 }
85 
86 
88 {
89  return aNameHash == rCmp.aNameHash;
90 }
91 
92 sal_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  NameSpaceMap::const_iterator aIter = aNameMap.find ( nKey );
101  if( aIter == aNameMap.end() )
102  break;
103  nKey++;
104  }
105  while ( true );
106  }
108  pEntry->sName = rName;
109  pEntry->nKey = nKey;
110  pEntry->sPrefix = rPrefix;
111  aNameHash[ rPrefix ] = pEntry;
112  aNameMap [ nKey ] = pEntry;
113  return nKey;
114 }
115 
116 sal_uInt16 SvXMLNamespaceMap::Add( const OUString& rPrefix, const OUString& rName,
117  sal_uInt16 nKey )
118 {
119  if( XML_NAMESPACE_UNKNOWN == nKey )
120  nKey = GetKeyByName( rName );
121 
122 #ifdef NDEBUG
123  if( XML_NAMESPACE_NONE == nKey )
124  return USHRT_MAX;
125 #else
126  assert(XML_NAMESPACE_NONE != nKey);
127 #endif
128 
129  if ( aNameHash.find ( rPrefix ) == aNameHash.end() )
130  nKey = Add_( rPrefix, rName, nKey );
131 
132  return nKey;
133 }
134 
135 sal_uInt16 SvXMLNamespaceMap::AddIfKnown( const OUString& rPrefix, const OUString& rName )
136 {
137  sal_uInt16 nKey = GetKeyByName( rName );
138 
139 #ifdef NDEBUG
140  if( XML_NAMESPACE_NONE == nKey )
141  return XML_NAMESPACE_UNKNOWN;
142 #else
143  assert(nKey != XML_NAMESPACE_NONE);
144 #endif
145 
146  if( XML_NAMESPACE_UNKNOWN != nKey )
147  {
148  NameSpaceHash::const_iterator aIter = aNameHash.find( rPrefix );
149  if( aIter == aNameHash.end() || (*aIter).second->sName != rName )
150  nKey = Add_( rPrefix, rName, nKey );
151  }
152 
153  return nKey;
154 }
155 
156 
157 sal_uInt16 SvXMLNamespaceMap::GetKeyByPrefix( const OUString& rPrefix ) const
158 {
159  NameSpaceHash::const_iterator aIter = aNameHash.find(rPrefix);
160  return (aIter != aNameHash.end()) ? (*aIter).second->nKey : USHRT_MAX;
161 }
162 
163 sal_uInt16 SvXMLNamespaceMap::GetKeyByName( const OUString& rName ) const
164 {
165  sal_uInt16 nKey = XML_NAMESPACE_UNKNOWN;
166  auto aIter = std::find_if(aNameHash.cbegin(), aNameHash.cend(),
167  [&rName](const NameSpaceHash::value_type& rEntry) { return rEntry.second->sName == rName; });
168 
169  if (aIter != aNameHash.cend())
170  nKey = (*aIter).second->nKey;
171 
172  return nKey;
173 }
174 
175 const OUString& SvXMLNamespaceMap::GetPrefixByKey( sal_uInt16 nKey ) const
176 {
177  NameSpaceMap::const_iterator aIter = aNameMap.find (nKey);
178  return (aIter != aNameMap.end()) ? (*aIter).second->sPrefix : sEmpty;
179 }
180 
181 const OUString& SvXMLNamespaceMap::GetNameByKey( sal_uInt16 nKey ) const
182 {
183  NameSpaceMap::const_iterator aIter = aNameMap.find (nKey);
184  return (aIter != aNameMap.end()) ? (*aIter).second->sName : sEmpty;
185 }
186 
187 OUString SvXMLNamespaceMap::GetAttrNameByKey( sal_uInt16 nKey ) const
188 {
189  NameSpaceMap::const_iterator aIter = aNameMap.find ( nKey );
190  if (aIter == aNameMap.end())
191  return OUString();
192 
193  const OUString & prefix( (*aIter).second->sPrefix );
194  if (prefix.isEmpty()) // default namespace
195  return sXMLNS;
196 
197  return sXMLNS + ":" + prefix;
198 }
199 
200 OUString SvXMLNamespaceMap::GetQNameByKey( sal_uInt16 nKey,
201  const OUString& rLocalName,
202  bool bCache) const
203 {
204  // We always want to return at least the rLocalName...
205 
206  switch ( nKey )
207  {
209  // ...if it's a completely unknown namespace, assert and return the local name
210  SAL_WARN("xmloff.core", "unknown namespace, probable missing xmlns: declaration");
211  [[fallthrough]];
212  case XML_NAMESPACE_NONE:
213  // ...if there isn't one, return the local name
214  return rLocalName;
215  case XML_NAMESPACE_XMLNS:
216  {
217  // ...if it's in the xmlns namespace, make the prefix
218  // don't bother caching this, it rarely happens
219  OUStringBuffer sQName;
220  sQName.append ( sXMLNS );
221  if (!rLocalName.isEmpty()) // not default namespace
222  {
223  sQName.append ( ':' );
224  sQName.append ( rLocalName );
225  }
226  return sQName.makeStringAndClear();
227  }
228  case XML_NAMESPACE_XML:
229  {
230  // this namespace is reserved, and needs not to be declared
231  return GetXMLToken(XML_XML) + ":" + rLocalName;
232  }
233  default:
234  {
235  QNameCache::const_iterator aQCacheIter;
236  if (bCache)
237  aQCacheIter = aQNameCache.find ( QNamePair ( nKey, rLocalName ) );
238  else
239  aQCacheIter = aQNameCache.end();
240  if ( aQCacheIter != aQNameCache.end() )
241  return (*aQCacheIter).second;
242  else
243  {
244  NameSpaceMap::const_iterator aIter = aNameMap.find ( nKey );
245  if ( aIter != aNameMap.end() )
246  {
247  // ...if it's in our map, make the prefix
248  const OUString & prefix( (*aIter).second->sPrefix );
249  OUStringBuffer sQName(prefix.getLength() + 1 + rLocalName.getLength());
250  if (!prefix.isEmpty()) // not default namespace
251  {
252  sQName.append( prefix );
253  sQName.append( ':' );
254  }
255  sQName.append ( rLocalName );
256  if (bCache)
257  {
258  OUString sString(sQName.makeStringAndClear());
259  aQNameCache.emplace(QNamePair(nKey, rLocalName), sString);
260  return sString;
261  }
262  else
263  return sQName.makeStringAndClear();
264  }
265  else
266  {
267  // ... if it isn't, this is a Bad Thing, assert and return the local name
268  assert(false);
269  return rLocalName;
270  }
271  }
272  }
273  }
274 }
275 
277  const OUString& rAttrValue,
278  OUString *pLocalName) const
279 {
280  return GetKeyByQName(rAttrValue, nullptr, pLocalName, nullptr, QNameMode::AttrValue);
281 }
282 
288 sal_uInt16 SvXMLNamespaceMap::GetKeyByQName(const OUString& rQName,
289  OUString *pPrefix,
290  OUString *pLocalName,
291  OUString *pNamespace,
292  QNameMode const eMode) const
293 {
294  sal_uInt16 nKey;
295 
296  NameSpaceHash::const_iterator it;
297  if (eMode == QNameMode::AttrNameCached)
298  it = aNameCache.find ( rQName );
299  else
300  it = aNameCache.end();
301  if ( it != aNameCache.end() )
302  {
303  const NameSpaceEntry &rEntry = *((*it).second);
304  if ( pPrefix )
305  *pPrefix = rEntry.sPrefix;
306  if ( pLocalName )
307  *pLocalName = rEntry.sName;
308  nKey = rEntry.nKey;
309  if ( pNamespace )
310  {
311  NameSpaceMap::const_iterator aMapIter = aNameMap.find (nKey);
312  *pNamespace = aMapIter != aNameMap.end() ? (*aMapIter).second->sName : OUString();
313  }
314  }
315  else
316  {
317  OUString sEntryPrefix, sEntryName;
318 
319  sal_Int32 nColonPos = rQName.indexOf( ':' );
320  if( -1 == nColonPos )
321  {
322  // case: no ':' found -> default namespace
323  sEntryName = rQName;
324  }
325  else
326  {
327  // normal case: ':' found -> get prefix/suffix
328  sEntryPrefix = rQName.copy( 0, nColonPos );
329  sEntryName = rQName.copy( nColonPos + 1 );
330  }
331 
332  if (eMode == QNameMode::AttrNameCached && sEntryName.indexOf(':') != -1)
333  {
334  SAL_INFO("xmloff", "invalid attribute name with multiple ':'");
335  assert(false);
336  return XML_NAMESPACE_UNKNOWN;
337  }
338 
339  if( pPrefix )
340  *pPrefix = sEntryPrefix;
341  if( pLocalName )
342  *pLocalName = sEntryName;
343 
344  NameSpaceHash::const_iterator aIter = aNameHash.find( sEntryPrefix );
345  if ( aIter != aNameHash.end() )
346  {
347  // found: retrieve namespace key
348  nKey = (*aIter).second->nKey;
349  if ( pNamespace )
350  *pNamespace = (*aIter).second->sName;
351  }
352  else if ( sEntryPrefix == sXMLNS )
353  // not found, but xmlns prefix: return xmlns 'namespace'
354  nKey = XML_NAMESPACE_XMLNS;
355  else if( nColonPos == -1 )
356  // not found, and no namespace: 'namespace' none
357  nKey = XML_NAMESPACE_NONE;
358  else
359  nKey = XML_NAMESPACE_UNKNOWN;
360 
361  if (eMode == QNameMode::AttrNameCached)
362  {
364  xEntry->sPrefix = std::move(sEntryPrefix);
365  xEntry->sName = std::move(sEntryName);
366  xEntry->nKey = nKey;
367  aNameCache.emplace(rQName, std::move(xEntry));
368  }
369  }
370 
371  return nKey;
372 }
373 
375 {
376  return aNameMap.empty() ? USHRT_MAX : (*aNameMap.begin()).second->nKey;
377 }
378 
379 sal_uInt16 SvXMLNamespaceMap::GetNextKey( sal_uInt16 nLastKey ) const
380 {
381  NameSpaceMap::const_iterator aIter = aNameMap.find ( nLastKey );
382  return (++aIter == aNameMap.end()) ? USHRT_MAX : (*aIter).second->nKey;
383 }
384 
385 
386 // All methods after this are deprecated...
387 
388 sal_uInt16 SvXMLNamespaceMap::GetIndexByKey( sal_uInt16 nKey )
389 {
390  return nKey;
391 }
393 {
394  return aNameMap.empty() ? USHRT_MAX : (*aNameMap.begin()).second->nKey;
395 }
396 
397 sal_uInt16 SvXMLNamespaceMap::GetNextIndex( sal_uInt16 nOldIdx ) const
398 {
399  NameSpaceMap::const_iterator aIter = aNameMap.find ( nOldIdx );
400  return (++aIter == aNameMap.end()) ? USHRT_MAX : (*aIter).second->nKey;
401 }
402 
403 void SvXMLNamespaceMap::AddAtIndex( const OUString& rPrefix,
404  const OUString& rName, sal_uInt16 nKey )
405 {
406  if( XML_NAMESPACE_UNKNOWN == nKey )
407  nKey = GetKeyByName( rName );
408 
409  assert(XML_NAMESPACE_NONE != nKey);
410  if( XML_NAMESPACE_NONE != nKey && ! ( aNameHash.count ( rPrefix ) ) )
411  {
412  Add_( rPrefix, rName, nKey );
413  }
414 }
415 
416 OUString SvXMLNamespaceMap::GetAttrNameByIndex( sal_uInt16 nIdx ) const
417 {
418  return GetAttrNameByKey( nIdx );
419 }
420 
421 const OUString& SvXMLNamespaceMap::GetPrefixByIndex( sal_uInt16 nIdx ) const
422 {
423  NameSpaceMap::const_iterator aIter = aNameMap.find (nIdx);
424  return (aIter != aNameMap.end()) ? (*aIter).second->sPrefix : sEmpty;
425 }
426 
427 const OUString& SvXMLNamespaceMap::GetNameByIndex( sal_uInt16 nIdx ) const
428 {
429  NameSpaceMap::const_iterator aIter = aNameMap.find (nIdx);
430  return (aIter != aNameMap.end()) ? (*aIter).second->sName : sEmpty;
431 }
432 
433 sal_uInt16 SvXMLNamespaceMap::GetIndexByPrefix( const OUString& rPrefix ) const
434 {
435  NameSpaceHash::const_iterator aIter = aNameHash.find(rPrefix);
436  return (aIter != aNameHash.end()) ? (*aIter).second->nKey : USHRT_MAX;
437 }
439  const OUString& rAttrName,
440  OUString *pLocalName) const
441 {
442  return GetKeyByQName(rAttrName, nullptr, pLocalName, nullptr, QNameMode::AttrNameCached);
443 }
444 
445 sal_uInt16 SvXMLNamespaceMap::GetKeyByAttrName( const OUString& rAttrName,
446  OUString *pPrefix,
447  OUString *pLocalName,
448  OUString *pNamespace ) const
449 {
450  return GetKeyByQName(rAttrName, pPrefix, pLocalName, pNamespace, QNameMode::AttrNameCached);
451 }
452 
453 bool SvXMLNamespaceMap::NormalizeURI( OUString& rName )
454 {
455  // try OASIS + W3 URI normalization
456  bool bSuccess = NormalizeOasisURN( rName );
457  if( ! bSuccess )
458  bSuccess = NormalizeW3URI( rName );
459  return bSuccess;
460 }
461 
462 bool SvXMLNamespaceMap::NormalizeW3URI( OUString& rName )
463 {
464  // check if URI matches:
465  // http://www.w3.org/[0-9]*/[:letter:]*
466  // (year)/(WG name)
467  // For the following WG/standards names:
468  // - xforms
469 
470  bool bSuccess = false;
471  const OUString& sURIPrefix = GetXMLToken( XML_URI_W3_PREFIX );
472  if( rName.startsWith( sURIPrefix ) )
473  {
474  const OUString& sURISuffix = GetXMLToken( XML_URI_XFORMS_SUFFIX );
475  sal_Int32 nCompareFrom = rName.getLength() - sURISuffix.getLength();
476  if( rName.subView( nCompareFrom ) == sURISuffix )
477  {
478  // found W3 prefix, and xforms suffix
479  rName = GetXMLToken( XML_N_XFORMS_1_0 );
480  bSuccess = true;
481  }
482  }
483  return bSuccess;
484 }
485 
487 {
488  // #i38644#
489  // we exported the wrong namespace for smil, so we correct this here on load
490  // for older documents
491  if( IsXMLToken( rName, ::xmloff::token::XML_N_SVG ) )
492  {
494  return true;
495  }
496  else if( IsXMLToken( rName, ::xmloff::token::XML_N_FO ) )
497  {
499  return true;
500  }
501  else if( IsXMLToken( rName, ::xmloff::token::XML_N_SMIL ) ||
503  {
505  return true;
506  }
507 
508 
509  // Check if URN matches
510  // :urn:oasis:names:tc:[^:]*:xmlns:[^:]*:1.[^:]*
511  // |---| |---| |-----|
512  // TC-Id Sub-Id Version
513 
514  sal_Int32 nNameLen = rName.getLength();
515  // :urn:oasis:names:tc.*
516  const OUString& rOasisURN = GetXMLToken( XML_URN_OASIS_NAMES_TC );
517  if( !rName.startsWith( rOasisURN ) )
518  return false;
519 
520  // :urn:oasis:names:tc:.*
521  sal_Int32 nPos = rOasisURN.getLength();
522  if( nPos >= nNameLen || rName[nPos] != ':' )
523  return false;
524 
525  // :urn:oasis:names:tc:[^:]:.*
526  sal_Int32 nTCIdStart = nPos+1;
527  sal_Int32 nTCIdEnd = rName.indexOf( ':', nTCIdStart );
528  if( -1 == nTCIdEnd )
529  return false;
530 
531  // :urn:oasis:names:tc:[^:]:xmlns.*
532  nPos = nTCIdEnd + 1;
533  std::u16string_view sTmp( rName.subView( nPos ) );
534  const OUString& rXMLNS = GetXMLToken( XML_XMLNS );
535  if( !o3tl::starts_with(sTmp, rXMLNS ) )
536  return false;
537 
538  // :urn:oasis:names:tc:[^:]:xmlns:.*
539  nPos += rXMLNS.getLength();
540  if( nPos >= nNameLen || rName[nPos] != ':' )
541  return false;
542 
543  // :urn:oasis:names:tc:[^:]:xmlns:[^:]*:.*
544  nPos = rName.indexOf( ':', nPos+1 );
545  if( -1 == nPos )
546  return false;
547 
548  // :urn:oasis:names:tc:[^:]:xmlns:[^:]*:[^:][^:][^:][^:]*
549  sal_Int32 nVersionStart = nPos+1;
550  if( nVersionStart+2 >= nNameLen ||
551  -1 != rName.indexOf( ':', nVersionStart ) )
552  return false;
553 
554  // :urn:oasis:names:tc:[^:]:xmlns:[^:]*:1\.[^:][^:]*
555  if( rName[nVersionStart] != '1' || rName[nVersionStart+1] != '.' )
556  return false;
557 
558  // replace [tcid] with current TCID and version with current version.
559 
560  rName = rName.subView( 0, nTCIdStart ) +
561  GetXMLToken( XML_OPENDOCUMENT ) +
562  rName.subView( nTCIdEnd, nVersionStart-nTCIdEnd ) +
563  GetXMLToken( XML_1_0 );
564 
565  return true;
566 }
567 
568 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
sal_uInt16 GetKeyByPrefix(const OUString &rPrefix) const
SAL_DLLPRIVATE sal_uInt16 Add_(const OUString &rPrefix, const OUString &rName, sal_uInt16 nKey)
static sal_uInt16 GetIndexByKey(sal_uInt16 nKey)
NameSpaceHash aNameCache
sal_uInt16 GetFirstKey() const
OUString GetAttrNameByKey(sal_uInt16 nKey) const
bool IsXMLToken(std::u16string_view rString, enum XMLTokenEnum eToken)
compare eToken to the string
Definition: xmltoken.cxx:3567
sal_uInt16 GetKeyByAttrName(const OUString &rAttrName, OUString *pPrefix, OUString *pLocalName, OUString *pNamespace) const
constexpr bool starts_with(std::basic_string_view< charT, traits > sv, std::basic_string_view< charT, traits > x) noexcept
const sal_uInt16 XML_NAMESPACE_UNKNOWN
const sal_uInt16 XML_NAMESPACE_XMLNS
bool operator==(const SvXMLNamespaceMap &rCmp) const
const OUString sEmpty
OUString sPrefix
sal_uInt16 GetKeyByAttrValueQName(const OUString &rAttrName, OUString *pLocalName) const
constexpr sal_uInt16 XML_NAMESPACE_XML
SvXMLNamespaceMap & operator=(const SvXMLNamespaceMap &rCmp)
sal_uInt16 GetIndexByPrefix(const OUString &rPrefix) const
static bool NormalizeURI(OUString &rName)
static bool NormalizeW3URI(OUString &rName)
sal_uInt16 nKey
const sal_uInt16 XML_NAMESPACE_UNKNOWN_FLAG
OUString GetAttrNameByIndex(sal_uInt16 nIdx) const
QNameCache aQNameCache
sal_uInt16 GetNextKey(sal_uInt16 nOldKey) const
const sal_uInt16 XML_NAMESPACE_NONE
OUString GetQNameByKey(sal_uInt16 nKey, const OUString &rLocalName, bool bCache=true) const
sal_uInt16 AddIfKnown(const OUString &rPrefix, const OUString &rName)
const OUString & GetNameByKey(sal_uInt16 nKey) const
NameSpaceHash aNameHash
static bool NormalizeOasisURN(OUString &rName)
const OUString & GetXMLToken(enum XMLTokenEnum eToken)
return the OUString representation for eToken
Definition: xmltoken.cxx:3511
Handling of tokens in XML:
#define SAL_INFO(area, stream)
sal_uInt16 Add(const OUString &rPrefix, const OUString &rName, sal_uInt16 nKey=XML_NAMESPACE_UNKNOWN)
sal_uInt16 GetKeyByName(const OUString &rName) const
#define SAL_WARN(area, stream)
void AddAtIndex(const OUString &rPrefix, const OUString &rName, sal_uInt16 nKey)
const OUString & GetPrefixByIndex(sal_uInt16 nIdx) const
const OUString & GetNameByIndex(sal_uInt16 nIdx) const
::std::pair< sal_uInt16, OUString > QNamePair
const OUString & GetPrefixByKey(sal_uInt16 nKey) const
sal_uInt16 GetFirstIndex() const
sal_uInt16 nPos
sal_uInt16 GetKeyByQName(const OUString &rQName, OUString *pPrefix, OUString *pLocalName, OUString *pNamespace, QNameMode eMode) const
NameSpaceMap aNameMap
sal_uInt16 GetNextIndex(sal_uInt16 nOldIdx) const