1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 <sal/log.hxx>
25 #include <xmloff/xmltoken.hxx>
26 #include <xmloff/namespacemap.hxx>
28 #include <xmloff/xmlnamespace.hxx>
29 #include <o3tl/string_view.hxx>
32 using namespace ::xmloff::token
;
34 /* The basic idea of this class is that we have two ways to search our
35 * data, by prefix and by key. We use an unordered_map for fast prefix
36 * searching and an STL map for fast key searching.
38 * The references to an 'Index' refer to an earlier implementation of the
39 * name space map and remain to support code which uses these interfaces.
41 * In this implementation, key and index should always be the same number.
43 * All references to Indices are now deprecated and the corresponding
44 * 'Key' methods should be used instead
49 const OUString sEmpty
;
51 SvXMLNamespaceMap::SvXMLNamespaceMap()
52 : m_sXMLNS( GetXMLToken ( XML_XMLNS
) )
54 // approx worst-case size
55 m_aNameHash
.reserve(20);
56 maKeyToNamespaceMap
.reserve(20);
59 SvXMLNamespaceMap::SvXMLNamespaceMap( const SvXMLNamespaceMap
& rMap
)
60 : m_sXMLNS( GetXMLToken ( XML_XMLNS
) )
62 m_aNameHash
= rMap
.m_aNameHash
;
63 maKeyToNamespaceMap
= rMap
.maKeyToNamespaceMap
;
66 SvXMLNamespaceMap
& SvXMLNamespaceMap::operator=( const SvXMLNamespaceMap
& rMap
)
68 m_aNameHash
= rMap
.m_aNameHash
;
69 maKeyToNamespaceMap
= rMap
.maKeyToNamespaceMap
;
73 SvXMLNamespaceMap::~SvXMLNamespaceMap()
77 void SvXMLNamespaceMap::Clear()
81 maKeyToNamespaceMap
.clear();
82 m_aQNameCache
.clear();
86 bool SvXMLNamespaceMap::operator ==( const SvXMLNamespaceMap
& rCmp
) const
88 return m_aNameHash
== rCmp
.m_aNameHash
;
91 sal_uInt16
SvXMLNamespaceMap::Add_( const OUString
& rPrefix
, const OUString
&rName
, sal_uInt16 nKey
)
93 if( XML_NAMESPACE_UNKNOWN
== nKey
)
95 // create a new unique key with UNKNOWN flag set
96 nKey
= XML_NAMESPACE_UNKNOWN_FLAG
;
99 auto aIter
= maKeyToNamespaceMap
.find ( nKey
);
100 if( aIter
== maKeyToNamespaceMap
.end() )
106 m_aNameHash
.insert_or_assign( rPrefix
, NameSpaceEntry
{ rName
, rPrefix
, nKey
} );
107 maKeyToNamespaceMap
.insert_or_assign( nKey
, KeyToNameSpaceMapEntry
{ rName
, rPrefix
} );
111 sal_uInt16
SvXMLNamespaceMap::Add( const OUString
& rPrefix
, const OUString
& rName
,
114 if( XML_NAMESPACE_UNKNOWN
== nKey
)
115 nKey
= GetKeyByName( rName
);
118 if( XML_NAMESPACE_NONE
== nKey
)
121 assert(XML_NAMESPACE_NONE
!= nKey
);
124 if ( m_aNameHash
.find ( rPrefix
) == m_aNameHash
.end() )
125 nKey
= Add_( rPrefix
, rName
, nKey
);
130 sal_uInt16
SvXMLNamespaceMap::AddIfKnown( const OUString
& rPrefix
, const OUString
& rName
)
132 sal_uInt16 nKey
= GetKeyByName( rName
);
135 if( XML_NAMESPACE_NONE
== nKey
)
136 return XML_NAMESPACE_UNKNOWN
;
138 assert(nKey
!= XML_NAMESPACE_NONE
);
141 if( XML_NAMESPACE_UNKNOWN
!= nKey
)
143 NameSpaceHash::const_iterator aIter
= m_aNameHash
.find( rPrefix
);
144 if( aIter
== m_aNameHash
.end() || (*aIter
).second
.m_sName
!= rName
)
145 nKey
= Add_( rPrefix
, rName
, nKey
);
152 sal_uInt16
SvXMLNamespaceMap::GetKeyByPrefix( const OUString
& rPrefix
) const
154 NameSpaceHash::const_iterator aIter
= m_aNameHash
.find(rPrefix
);
155 return (aIter
!= m_aNameHash
.end()) ? (*aIter
).second
.m_nKey
: USHRT_MAX
;
158 sal_uInt16
SvXMLNamespaceMap::GetKeyByName( const OUString
& rName
) const
160 sal_uInt16 nKey
= XML_NAMESPACE_UNKNOWN
;
161 auto aIter
= std::find_if(m_aNameHash
.cbegin(), m_aNameHash
.cend(),
162 [&rName
](const NameSpaceHash::value_type
& rEntry
) { return rEntry
.second
.m_sName
== rName
; });
164 if (aIter
!= m_aNameHash
.cend())
165 nKey
= (*aIter
).second
.m_nKey
;
170 const OUString
& SvXMLNamespaceMap::GetPrefixByKey( sal_uInt16 nKey
) const
172 auto aIter
= maKeyToNamespaceMap
.find (nKey
);
173 return (aIter
!= maKeyToNamespaceMap
.end()) ? (*aIter
).second
.sPrefix
: sEmpty
;
176 const OUString
& SvXMLNamespaceMap::GetNameByKey( sal_uInt16 nKey
) const
178 auto aIter
= maKeyToNamespaceMap
.find (nKey
);
179 return (aIter
!= maKeyToNamespaceMap
.end()) ? (*aIter
).second
.sName
: sEmpty
;
182 OUString
SvXMLNamespaceMap::GetAttrNameByKey( sal_uInt16 nKey
) const
184 auto aIter
= maKeyToNamespaceMap
.find ( nKey
);
185 if (aIter
== maKeyToNamespaceMap
.end())
188 const OUString
& prefix( (*aIter
).second
.sPrefix
);
189 if (prefix
.isEmpty()) // default namespace
192 return m_sXMLNS
+ ":" + prefix
;
195 OUString
SvXMLNamespaceMap::GetQNameByKey( sal_uInt16 nKey
,
196 const OUString
& rLocalName
,
199 // We always want to return at least the rLocalName...
203 case XML_NAMESPACE_UNKNOWN
:
204 // ...if it's a completely unknown namespace, assert and return the local name
205 SAL_WARN("xmloff.core", "unknown namespace, probable missing xmlns: declaration");
207 case XML_NAMESPACE_NONE
:
208 // ...if there isn't one, return the local name
210 case XML_NAMESPACE_XMLNS
:
212 // ...if it's in the xmlns namespace, make the prefix
213 // don't bother caching this, it rarely happens
214 if (!rLocalName
.isEmpty()) // not default namespace
215 return m_sXMLNS
+ ":" + rLocalName
;
219 case XML_NAMESPACE_XML
:
221 // this namespace is reserved, and needs not to be declared
222 return GetXMLToken(XML_XML
) + ":" + rLocalName
;
226 QNameCache::const_iterator aQCacheIter
;
228 aQCacheIter
= m_aQNameCache
.find ( QNamePair ( nKey
, rLocalName
) );
230 aQCacheIter
= m_aQNameCache
.end();
231 if ( aQCacheIter
!= m_aQNameCache
.end() )
232 return (*aQCacheIter
).second
;
235 auto aIter
= maKeyToNamespaceMap
.find ( nKey
);
236 if ( aIter
!= maKeyToNamespaceMap
.end() )
238 // ...if it's in our map, make the prefix
239 const OUString
& prefix( (*aIter
).second
.sPrefix
);
241 if (!prefix
.isEmpty()) // not default namespace
242 sQName
= prefix
+ ":" + rLocalName
;
246 m_aQNameCache
.emplace(QNamePair(nKey
, rLocalName
), sQName
);
251 // ... if it isn't, this is a Bad Thing, assert and return the local name
260 sal_uInt16
SvXMLNamespaceMap::GetKeyByAttrValueQName(
261 const OUString
& rAttrValue
,
262 OUString
*pLocalName
) const
264 return GetKeyByQName(rAttrValue
, nullptr, pLocalName
, nullptr, QNameMode::AttrValue
);
268 @param rQName either attribute name or qualified/namespaced attribute value
269 @param bCacheAttrName true: rQName is element or attribute name, cache it
270 false: rQName is attribute value, may contain extra ':', don't cache it
272 sal_uInt16
SvXMLNamespaceMap::GetKeyByQName(const OUString
& rQName
,
274 OUString
*pLocalName
,
275 OUString
*pNamespace
,
276 QNameMode
const eMode
) const
280 NameSpaceHash::const_iterator it
;
281 if (eMode
== QNameMode::AttrNameCached
)
282 it
= m_aNameCache
.find ( rQName
);
284 it
= m_aNameCache
.end();
285 if ( it
!= m_aNameCache
.end() )
287 const NameSpaceEntry
&rEntry
= (*it
).second
;
289 *pPrefix
= rEntry
.m_sPrefix
;
291 *pLocalName
= rEntry
.m_sName
;
292 nKey
= rEntry
.m_nKey
;
295 auto aMapIter
= maKeyToNamespaceMap
.find (nKey
);
296 *pNamespace
= aMapIter
!= maKeyToNamespaceMap
.end() ? (*aMapIter
).second
.sName
: OUString();
301 OUString sEntryPrefix
, sEntryName
;
303 sal_Int32 nColonPos
= rQName
.indexOf( ':' );
304 if( -1 == nColonPos
)
306 // case: no ':' found -> default namespace
311 // normal case: ':' found -> get prefix/suffix
312 sEntryPrefix
= rQName
.copy( 0, nColonPos
);
313 sEntryName
= rQName
.copy( nColonPos
+ 1 );
316 if (eMode
== QNameMode::AttrNameCached
&& sEntryName
.indexOf(':') != -1)
318 SAL_INFO("xmloff", "invalid attribute name with multiple ':'");
320 return XML_NAMESPACE_UNKNOWN
;
324 *pPrefix
= sEntryPrefix
;
326 *pLocalName
= sEntryName
;
328 NameSpaceHash::const_iterator aIter
= m_aNameHash
.find( sEntryPrefix
);
329 if ( aIter
!= m_aNameHash
.end() )
331 // found: retrieve namespace key
332 nKey
= (*aIter
).second
.m_nKey
;
334 *pNamespace
= (*aIter
).second
.m_sName
;
336 else if ( sEntryPrefix
== m_sXMLNS
)
337 // not found, but xmlns prefix: return xmlns 'namespace'
338 nKey
= XML_NAMESPACE_XMLNS
;
339 else if( nColonPos
== -1 )
340 // not found, and no namespace: 'namespace' none
341 nKey
= XML_NAMESPACE_NONE
;
343 nKey
= XML_NAMESPACE_UNKNOWN
;
345 if (eMode
== QNameMode::AttrNameCached
)
347 m_aNameCache
.insert_or_assign(rQName
, NameSpaceEntry
{std::move(sEntryName
), std::move(sEntryPrefix
), nKey
});
354 sal_uInt16
SvXMLNamespaceMap::GetFirstKey() const
356 return maKeyToNamespaceMap
.empty() ? USHRT_MAX
: (*maKeyToNamespaceMap
.begin()).first
;
359 sal_uInt16
SvXMLNamespaceMap::GetNextKey( sal_uInt16 nLastKey
) const
361 auto aIter
= maKeyToNamespaceMap
.find ( nLastKey
);
362 assert(aIter
!= maKeyToNamespaceMap
.end());
363 return (++aIter
== maKeyToNamespaceMap
.end()) ? USHRT_MAX
: (*aIter
).first
;
367 // All methods after this are deprecated...
369 sal_uInt16
SvXMLNamespaceMap::GetIndexByKey( sal_uInt16 nKey
)
373 sal_uInt16
SvXMLNamespaceMap::GetFirstIndex() const
375 return maKeyToNamespaceMap
.empty() ? USHRT_MAX
: (*maKeyToNamespaceMap
.begin()).first
;
378 sal_uInt16
SvXMLNamespaceMap::GetNextIndex( sal_uInt16 nOldIdx
) const
380 auto aIter
= maKeyToNamespaceMap
.find ( nOldIdx
);
381 assert(aIter
!= maKeyToNamespaceMap
.end());
382 return (++aIter
== maKeyToNamespaceMap
.end()) ? USHRT_MAX
: (*aIter
).first
;
385 void SvXMLNamespaceMap::AddAtIndex( const OUString
& rPrefix
,
386 const OUString
& rName
, sal_uInt16 nKey
)
388 if( XML_NAMESPACE_UNKNOWN
== nKey
)
389 nKey
= GetKeyByName( rName
);
391 assert(XML_NAMESPACE_NONE
!= nKey
);
392 if( XML_NAMESPACE_NONE
!= nKey
&& ! ( m_aNameHash
.count ( rPrefix
) ) )
394 Add_( rPrefix
, rName
, nKey
);
398 OUString
SvXMLNamespaceMap::GetAttrNameByIndex( sal_uInt16 nIdx
) const
400 return GetAttrNameByKey( nIdx
);
403 const OUString
& SvXMLNamespaceMap::GetPrefixByIndex( sal_uInt16 nIdx
) const
405 auto aIter
= maKeyToNamespaceMap
.find (nIdx
);
406 return (aIter
!= maKeyToNamespaceMap
.end()) ? (*aIter
).second
.sPrefix
: sEmpty
;
409 const OUString
& SvXMLNamespaceMap::GetNameByIndex( sal_uInt16 nIdx
) const
411 auto aIter
= maKeyToNamespaceMap
.find (nIdx
);
412 return (aIter
!= maKeyToNamespaceMap
.end()) ? (*aIter
).second
.sName
: sEmpty
;
415 sal_uInt16
SvXMLNamespaceMap::GetIndexByPrefix( const OUString
& rPrefix
) const
417 NameSpaceHash::const_iterator aIter
= m_aNameHash
.find(rPrefix
);
418 return (aIter
!= m_aNameHash
.end()) ? (*aIter
).second
.m_nKey
: USHRT_MAX
;
420 sal_uInt16
SvXMLNamespaceMap::GetKeyByAttrName(
421 const OUString
& rAttrName
,
422 OUString
*pLocalName
) const
424 return GetKeyByQName(rAttrName
, nullptr, pLocalName
, nullptr, QNameMode::AttrNameCached
);
427 sal_uInt16
SvXMLNamespaceMap::GetKeyByAttrName( const OUString
& rAttrName
,
429 OUString
*pLocalName
,
430 OUString
*pNamespace
) const
432 return GetKeyByQName(rAttrName
, pPrefix
, pLocalName
, pNamespace
, QNameMode::AttrNameCached
);
435 bool SvXMLNamespaceMap::NormalizeURI( OUString
& rName
)
437 // try OASIS + W3 URI normalization
438 bool bSuccess
= NormalizeOasisURN( rName
);
440 bSuccess
= NormalizeW3URI( rName
);
444 bool SvXMLNamespaceMap::NormalizeW3URI( OUString
& rName
)
446 // check if URI matches:
447 // http://www.w3.org/[0-9]*/[:letter:]*
449 // For the following WG/standards names:
452 bool bSuccess
= false;
453 const OUString
& sURIPrefix
= GetXMLToken( XML_URI_W3_PREFIX
);
454 if( rName
.startsWith( sURIPrefix
) )
456 const OUString
& sURISuffix
= GetXMLToken( XML_URI_XFORMS_SUFFIX
);
457 sal_Int32 nCompareFrom
= rName
.getLength() - sURISuffix
.getLength();
458 if( rName
.subView( nCompareFrom
) == sURISuffix
)
460 // found W3 prefix, and xforms suffix
461 rName
= GetXMLToken( XML_N_XFORMS_1_0
);
468 bool SvXMLNamespaceMap::NormalizeOasisURN( OUString
& rName
)
471 // we exported the wrong namespace for smil, so we correct this here on load
472 // for older documents
473 if( IsXMLToken( rName
, ::xmloff::token::XML_N_SVG
) )
475 rName
= GetXMLToken( ::xmloff::token::XML_N_SVG_COMPAT
);
478 else if( IsXMLToken( rName
, ::xmloff::token::XML_N_FO
) )
480 rName
= GetXMLToken( ::xmloff::token::XML_N_FO_COMPAT
);
483 else if( IsXMLToken( rName
, ::xmloff::token::XML_N_SMIL
) ||
484 IsXMLToken( rName
, ::xmloff::token::XML_N_SMIL_OLD
) )
486 rName
= GetXMLToken( ::xmloff::token::XML_N_SMIL_COMPAT
);
491 // Check if URN matches
492 // :urn:oasis:names:tc:[^:]*:xmlns:[^:]*:1.[^:]*
493 // |---| |---| |-----|
494 // TC-Id Sub-Id Version
496 sal_Int32 nNameLen
= rName
.getLength();
497 // :urn:oasis:names:tc.*
498 const OUString
& rOasisURN
= GetXMLToken( XML_URN_OASIS_NAMES_TC
);
499 if( !rName
.startsWith( rOasisURN
) )
502 // :urn:oasis:names:tc:.*
503 sal_Int32 nPos
= rOasisURN
.getLength();
504 if( nPos
>= nNameLen
|| rName
[nPos
] != ':' )
507 // :urn:oasis:names:tc:[^:]:.*
508 sal_Int32 nTCIdStart
= nPos
+1;
509 sal_Int32 nTCIdEnd
= rName
.indexOf( ':', nTCIdStart
);
513 // :urn:oasis:names:tc:[^:]:xmlns.*
515 std::u16string_view
sTmp( rName
.subView( nPos
) );
516 const OUString
& rXMLNS
= GetXMLToken( XML_XMLNS
);
517 if( !o3tl::starts_with(sTmp
, rXMLNS
) )
520 // :urn:oasis:names:tc:[^:]:xmlns:.*
521 nPos
+= rXMLNS
.getLength();
522 if( nPos
>= nNameLen
|| rName
[nPos
] != ':' )
525 // :urn:oasis:names:tc:[^:]:xmlns:[^:]*:.*
526 nPos
= rName
.indexOf( ':', nPos
+1 );
530 // :urn:oasis:names:tc:[^:]:xmlns:[^:]*:[^:][^:][^:][^:]*
531 sal_Int32 nVersionStart
= nPos
+1;
532 if( nVersionStart
+2 >= nNameLen
||
533 -1 != rName
.indexOf( ':', nVersionStart
) )
536 // :urn:oasis:names:tc:[^:]:xmlns:[^:]*:1\.[^:][^:]*
537 if( rName
[nVersionStart
] != '1' || rName
[nVersionStart
+1] != '.' )
540 // replace [tcid] with current TCID and version with current version.
542 rName
= rName
.subView( 0, nTCIdStart
) +
543 GetXMLToken( XML_OPENDOCUMENT
) +
544 rName
.subView( nTCIdEnd
, nVersionStart
-nTCIdEnd
) +
545 GetXMLToken( XML_1_0
);
550 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */