Line data Source code
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 :
21 : #include "RDFaImportHelper.hxx"
22 :
23 : #include <xmloff/xmlimp.hxx>
24 : #include <xmloff/nmspmap.hxx>
25 :
26 : #include <comphelper/sequenceasvector.hxx>
27 :
28 : #include <com/sun/star/rdf/URI.hpp>
29 : #include <com/sun/star/rdf/XDocumentMetadataAccess.hpp>
30 : #include <com/sun/star/rdf/XDocumentRepository.hpp>
31 :
32 : #include <rtl/ustring.hxx>
33 :
34 : #include <boost/bind.hpp>
35 : #include <boost/iterator_adaptors.hpp>
36 : #ifndef BOOST_ITERATOR_ADAPTOR_DWA053000_HPP_ // from iterator_adaptors.hpp
37 : // N.B.: the check for the header guard _of a specific version of boost_
38 : // is here so this may work on different versions of boost,
39 : // which sadly put the goods in different header files
40 : #include <boost/iterator/transform_iterator.hpp>
41 : #endif
42 :
43 : #include <map>
44 : #include <iterator>
45 : #include <functional>
46 : #include <algorithm>
47 :
48 :
49 : using namespace ::com::sun::star;
50 :
51 : namespace xmloff {
52 :
53 : /** a bit of context for parsing RDFa attributes */
54 : class SAL_DLLPRIVATE RDFaReader
55 : {
56 : const SvXMLImport & m_rImport;
57 :
58 140 : const SvXMLImport & GetImport() const { return m_rImport; }
59 :
60 : //FIXME: this is an ugly hack to workaround buggy SvXMLImport::GetAbsolute
61 79 : OUString GetAbsoluteReference(OUString const & i_rURI) const
62 : {
63 79 : if (i_rURI.isEmpty() || i_rURI[0] == '#')
64 : {
65 1 : return GetImport().GetBaseURL() + i_rURI;
66 : }
67 : else
68 : {
69 78 : return GetImport().GetAbsoluteReference(i_rURI);
70 : }
71 : }
72 :
73 : public:
74 38 : RDFaReader(SvXMLImport const & i_rImport)
75 38 : : m_rImport(i_rImport)
76 38 : { }
77 :
78 : // returns URI or blank node!
79 : OUString ReadCURIE(OUString const & i_rCURIE) const;
80 :
81 : std::vector< OUString >
82 : ReadCURIEs(OUString const & i_rCURIEs) const;
83 :
84 : OUString
85 : ReadURIOrSafeCURIE( OUString const & i_rURIOrSafeCURIE) const;
86 : };
87 :
88 : /** helper to insert RDFa statements into the RDF repository */
89 6 : class SAL_DLLPRIVATE RDFaInserter
90 : {
91 : const uno::Reference<uno::XComponentContext> m_xContext;
92 : uno::Reference< rdf::XDocumentRepository > m_xRepository;
93 :
94 : typedef ::std::map< OUString, uno::Reference< rdf::XBlankNode > >
95 : BlankNodeMap_t;
96 :
97 : BlankNodeMap_t m_BlankNodeMap;
98 :
99 : public:
100 6 : RDFaInserter(uno::Reference<uno::XComponentContext> const & i_xContext,
101 : uno::Reference< rdf::XDocumentRepository > const & i_xRepository)
102 : : m_xContext(i_xContext)
103 6 : , m_xRepository(i_xRepository)
104 6 : {}
105 :
106 : uno::Reference< rdf::XBlankNode >
107 : LookupBlankNode(OUString const & i_rNodeId );
108 :
109 : uno::Reference< rdf::XURI >
110 : MakeURI( OUString const & i_rURI) const;
111 :
112 : uno::Reference< rdf::XResource>
113 : MakeResource( OUString const & i_rResource);
114 :
115 : void InsertRDFaEntry(struct RDFaEntry const & i_rEntry);
116 : };
117 :
118 : /** store parsed RDFa attributes */
119 35 : struct SAL_DLLPRIVATE ParsedRDFaAttributes
120 : {
121 : OUString m_About;
122 : ::std::vector< OUString > m_Properties;
123 : OUString m_Content;
124 : OUString m_Datatype;
125 :
126 35 : ParsedRDFaAttributes(
127 : OUString const & i_rAbout,
128 : ::std::vector< OUString > const & i_rProperties,
129 : OUString const & i_rContent,
130 : OUString const & i_rDatatype)
131 : : m_About(i_rAbout)
132 : , m_Properties(i_rProperties)
133 : , m_Content(i_rContent)
134 35 : , m_Datatype(i_rDatatype)
135 35 : { }
136 : };
137 :
138 : /** store metadatable object and its RDFa attributes */
139 169 : struct SAL_DLLPRIVATE RDFaEntry
140 : {
141 : uno::Reference<rdf::XMetadatable> m_xObject;
142 : ::boost::shared_ptr<ParsedRDFaAttributes> m_pRDFaAttributes;
143 :
144 35 : RDFaEntry(uno::Reference<rdf::XMetadatable> const & i_xObject,
145 : ::boost::shared_ptr<ParsedRDFaAttributes> const& i_pRDFaAttributes)
146 : : m_xObject(i_xObject)
147 35 : , m_pRDFaAttributes(i_pRDFaAttributes)
148 35 : { }
149 : };
150 :
151 : ////////////////////////////////////////////////////////////////////////////
152 :
153 :
154 344 : static inline bool isWS(const sal_Unicode i_Char)
155 : {
156 344 : return ('\t' == i_Char) || ('\n' == i_Char) || ('\r' == i_Char)
157 688 : || (' ' == i_Char);
158 : }
159 :
160 42 : static OUString splitAtWS(OUString & io_rString)
161 : {
162 42 : const sal_Int32 len( io_rString.getLength() );
163 42 : sal_Int32 idxstt(0);
164 95 : while ((idxstt < len) && ( isWS(io_rString[idxstt])))
165 11 : ++idxstt; // skip leading ws
166 42 : sal_Int32 idxend(idxstt);
167 369 : while ((idxend < len) && (!isWS(io_rString[idxend])))
168 285 : ++idxend; // the CURIE
169 42 : const OUString ret(io_rString.copy(idxstt, idxend - idxstt));
170 42 : io_rString = io_rString.copy(idxend); // rest
171 42 : return ret;
172 : }
173 :
174 : OUString
175 62 : RDFaReader::ReadCURIE(OUString const & i_rCURIE) const
176 : {
177 : // the RDFa spec says that a prefix is required (it may be empty: ":foo")
178 62 : const sal_Int32 idx( i_rCURIE.indexOf(':') );
179 62 : if (idx >= 0)
180 : {
181 61 : OUString Prefix;
182 122 : OUString LocalName;
183 122 : OUString Namespace;
184 61 : sal_uInt16 nKey( GetImport().GetNamespaceMap()._GetKeyByAttrName(
185 61 : i_rCURIE, &Prefix, &LocalName, &Namespace) );
186 61 : if ( Prefix == "_" )
187 : {
188 : // eeek, it's a bnode!
189 : // "_" is not a valid URI scheme => we can identify bnodes
190 8 : return i_rCURIE;
191 : }
192 : else
193 : {
194 : SAL_WARN_IF(XML_NAMESPACE_NONE == nKey, "xmloff.core", "no namespace?");
195 53 : if ((XML_NAMESPACE_UNKNOWN != nKey) &&
196 : (XML_NAMESPACE_XMLNS != nKey))
197 : {
198 : // N.B.: empty LocalName is valid!
199 51 : const OUString URI(Namespace + LocalName);
200 51 : return GetAbsoluteReference(URI);
201 : }
202 : else
203 : {
204 : SAL_INFO("xmloff.core", "ReadCURIE: invalid CURIE: invalid prefix" );
205 2 : return OUString();
206 : }
207 61 : }
208 : }
209 : SAL_INFO("xmloff.core", "ReadCURIE: invalid CURIE: no prefix" );
210 1 : return OUString();
211 : }
212 :
213 : ::std::vector< OUString >
214 35 : RDFaReader::ReadCURIEs(OUString const & i_rCURIEs) const
215 : {
216 35 : std::vector< OUString > vec;
217 70 : OUString CURIEs(i_rCURIEs);
218 42 : do {
219 42 : OUString curie( splitAtWS(CURIEs) );
220 42 : if (!curie.isEmpty())
221 : {
222 41 : const OUString uri(ReadCURIE(curie));
223 41 : if (!uri.isEmpty())
224 : {
225 41 : vec.push_back(uri);
226 41 : }
227 42 : }
228 : }
229 42 : while (!CURIEs.isEmpty());
230 35 : if (vec.empty())
231 : {
232 : SAL_INFO("xmloff.core", "ReadCURIEs: invalid CURIEs" );
233 : }
234 70 : return vec;
235 : }
236 :
237 : OUString
238 38 : RDFaReader::ReadURIOrSafeCURIE(OUString const & i_rURIOrSafeCURIE) const
239 : {
240 38 : const sal_Int32 len(i_rURIOrSafeCURIE.getLength());
241 38 : if (len && (i_rURIOrSafeCURIE[0] == '['))
242 : {
243 9 : if ((len >= 2) && (i_rURIOrSafeCURIE[len - 1] == ']'))
244 : {
245 9 : return ReadCURIE(i_rURIOrSafeCURIE.copy(1, len - 2));
246 : }
247 : else
248 : {
249 : SAL_INFO("xmloff.core", "ReadURIOrSafeCURIE: invalid SafeCURIE" );
250 0 : return OUString();
251 : }
252 : }
253 : else
254 : {
255 29 : if (i_rURIOrSafeCURIE.matchAsciiL("_:", 2)) // blank node
256 : {
257 : SAL_INFO("xmloff.core", "ReadURIOrSafeCURIE: invalid URI: scheme is _" );
258 1 : return OUString();
259 : }
260 : else
261 : {
262 28 : return GetAbsoluteReference(i_rURIOrSafeCURIE);
263 : }
264 : }
265 : }
266 :
267 : ////////////////////////////////////////////////////////////////////////////
268 :
269 : uno::Reference< rdf::XBlankNode >
270 6 : RDFaInserter::LookupBlankNode(OUString const & i_rNodeId )
271 : {
272 6 : uno::Reference< rdf::XBlankNode > & rEntry( m_BlankNodeMap[ i_rNodeId ] );
273 6 : if (!rEntry.is())
274 : {
275 4 : rEntry = m_xRepository->createBlankNode();
276 : }
277 6 : return rEntry;
278 : }
279 :
280 : uno::Reference< rdf::XURI >
281 121 : RDFaInserter::MakeURI( OUString const & i_rURI) const
282 : {
283 121 : if (i_rURI.matchAsciiL("_:", 2)) // blank node
284 : {
285 : SAL_INFO("xmloff.core", "MakeURI: cannot create URI for blank node");
286 2 : return 0;
287 : }
288 : else
289 : {
290 : try
291 : {
292 119 : return rdf::URI::create( m_xContext, i_rURI );
293 : }
294 0 : catch (uno::Exception &)
295 : {
296 : SAL_WARN("xmloff.core", "MakeURI: cannot create URI");
297 0 : return 0;
298 : }
299 : }
300 : }
301 :
302 : uno::Reference< rdf::XResource>
303 35 : RDFaInserter::MakeResource( OUString const & i_rResource)
304 : {
305 35 : if (i_rResource.matchAsciiL("_:", 2)) // blank node
306 : {
307 : // we cannot use the blank node label as-is: it must be distinct
308 : // from labels in other graphs, so create fresh ones per XML stream
309 : // N.B.: content.xml and styles.xml are distinct graphs
310 6 : OUString name( i_rResource.copy(2) );
311 12 : const uno::Reference< rdf::XBlankNode > xBNode( LookupBlankNode(name) );
312 : SAL_WARN_IF(!xBNode.is(), "xmloff.core", "no blank node?");
313 12 : return uno::Reference<rdf::XResource>( xBNode, uno::UNO_QUERY);
314 : }
315 : else
316 : {
317 : return uno::Reference<rdf::XResource>( MakeURI( i_rResource ),
318 29 : uno::UNO_QUERY);
319 : }
320 : }
321 :
322 : /** i wrote this because c++ implementations cannot agree on which variant
323 : of boost::bind and std::mem_fun_ref applied to Reference::is compiles */
324 : class ref_is_null :
325 : public ::std::unary_function<sal_Bool, const uno::Reference<rdf::XURI> & >
326 : {
327 : public:
328 41 : sal_Bool operator() (const uno::Reference<rdf::XURI> & i_rRef)
329 : {
330 41 : return !i_rRef.is();
331 : }
332 : };
333 :
334 35 : void RDFaInserter::InsertRDFaEntry(
335 : struct RDFaEntry const & i_rEntry)
336 : {
337 : SAL_WARN_IF(!i_rEntry.m_xObject.is(), "xmloff.core", "InsertRDFaEntry: invalid arg: null object");
338 36 : if (!i_rEntry.m_xObject.is()) return;
339 :
340 : const uno::Reference< rdf::XResource > xSubject(
341 35 : MakeResource( i_rEntry.m_pRDFaAttributes->m_About ) );
342 35 : if (!xSubject.is())
343 : {
344 0 : return; // invalid
345 : }
346 :
347 69 : ::comphelper::SequenceAsVector< uno::Reference< rdf::XURI > > predicates;
348 :
349 35 : predicates.reserve(i_rEntry.m_pRDFaAttributes->m_Properties.size());
350 :
351 : ::std::remove_copy_if(
352 : ::boost::make_transform_iterator(
353 35 : i_rEntry.m_pRDFaAttributes->m_Properties.begin(),
354 : ::boost::bind(&RDFaInserter::MakeURI, this, _1)),
355 : // argh, this must be the same type :(
356 : ::boost::make_transform_iterator(
357 35 : i_rEntry.m_pRDFaAttributes->m_Properties.end(),
358 : ::boost::bind(&RDFaInserter::MakeURI, this, _1)),
359 : ::std::back_inserter(predicates),
360 105 : ref_is_null() );
361 : // compiles only on wntmsci12
362 : // ::boost::bind( ::std::logical_not<sal_Bool>(), ::boost::bind<sal_Bool>(&uno::Reference<rdf::XURI>::is, _1)));
363 : // compiles on unxsoli4, wntsci12, but not unxlngi6
364 : // ::boost::bind( ::std::logical_not<sal_Bool>(), ::boost::bind<sal_Bool, com::sun::star::uno::Reference<rdf::XURI> >(&uno::Reference<rdf::XURI>::is, _1)));
365 : // compiles on unxsoli4, unxlngi6, but not wntsci12
366 : // ::std::not1( ::std::mem_fun_ref(&uno::Reference<rdf::XURI>::is)) );
367 :
368 35 : if (!predicates.size())
369 : {
370 1 : return; // invalid
371 : }
372 :
373 68 : uno::Reference<rdf::XURI> xDatatype;
374 34 : if (!i_rEntry.m_pRDFaAttributes->m_Datatype.isEmpty())
375 : {
376 11 : xDatatype = MakeURI( i_rEntry.m_pRDFaAttributes->m_Datatype );
377 : }
378 :
379 : try
380 : {
381 : // N.B.: this will call xMeta->ensureMetadataReference, which is why
382 : // this must be done _after_ importing the whole XML file,
383 : // to prevent collision between generated ids and ids in the file
384 34 : m_xRepository->setStatementRDFa(xSubject, predicates.getAsConstList(),
385 : i_rEntry.m_xObject,
386 34 : i_rEntry.m_pRDFaAttributes->m_Content, xDatatype);
387 : }
388 0 : catch (uno::Exception &)
389 : {
390 : SAL_WARN("xmloff.core", "InsertRDFaEntry: setStatementRDFa failed?");
391 34 : }
392 : }
393 :
394 : ////////////////////////////////////////////////////////////////////////////
395 :
396 6 : RDFaImportHelper::RDFaImportHelper(const SvXMLImport & i_rImport)
397 6 : : m_rImport(i_rImport)
398 : {
399 6 : }
400 :
401 6 : RDFaImportHelper::~RDFaImportHelper()
402 : {
403 6 : }
404 :
405 : ::boost::shared_ptr<ParsedRDFaAttributes>
406 40 : RDFaImportHelper::ParseRDFa(
407 : OUString const & i_rAbout,
408 : OUString const & i_rProperty,
409 : OUString const & i_rContent,
410 : OUString const & i_rDatatype)
411 : {
412 40 : if (i_rProperty.isEmpty())
413 : {
414 : SAL_INFO("xmloff.core", "AddRDFa: invalid input: xhtml:property empty");
415 2 : return ::boost::shared_ptr<ParsedRDFaAttributes>();
416 : }
417 : // must parse CURIEs here: need namespace declaration context
418 38 : RDFaReader reader(GetImport());
419 38 : const OUString about( reader.ReadURIOrSafeCURIE(i_rAbout) );
420 38 : if (about.isEmpty()) {
421 3 : return ::boost::shared_ptr<ParsedRDFaAttributes>();
422 : }
423 : const ::std::vector< OUString > properties(
424 70 : reader.ReadCURIEs(i_rProperty) );
425 35 : if (!properties.size()) {
426 0 : return ::boost::shared_ptr<ParsedRDFaAttributes>();
427 : }
428 35 : const OUString datatype( !i_rDatatype.isEmpty()
429 : ? reader.ReadCURIE(i_rDatatype)
430 70 : : OUString() );
431 : return ::boost::shared_ptr<ParsedRDFaAttributes>(
432 73 : new ParsedRDFaAttributes(about, properties, i_rContent, datatype));
433 : }
434 :
435 : void
436 35 : RDFaImportHelper::AddRDFa(
437 : uno::Reference<rdf::XMetadatable> const & i_xObject,
438 : ::boost::shared_ptr<ParsedRDFaAttributes> & i_pRDFaAttributes)
439 : {
440 35 : if (!i_xObject.is())
441 : {
442 : SAL_WARN("xmloff.core", "AddRDFa: invalid arg: null textcontent");
443 0 : return;
444 : }
445 35 : if (!i_pRDFaAttributes.get())
446 : {
447 : SAL_WARN("xmloff.core", "AddRDFa: invalid arg: null RDFa attributes");
448 0 : return;
449 : }
450 35 : m_RDFaEntries.push_back(RDFaEntry(i_xObject, i_pRDFaAttributes));
451 : }
452 :
453 : void
454 36 : RDFaImportHelper::ParseAndAddRDFa(
455 : uno::Reference<rdf::XMetadatable> const & i_xObject,
456 : OUString const & i_rAbout,
457 : OUString const & i_rProperty,
458 : OUString const & i_rContent,
459 : OUString const & i_rDatatype)
460 : {
461 : ::boost::shared_ptr<ParsedRDFaAttributes> pAttributes(
462 36 : ParseRDFa(i_rAbout, i_rProperty, i_rContent, i_rDatatype) );
463 36 : if (pAttributes.get())
464 : {
465 31 : AddRDFa(i_xObject, pAttributes);
466 36 : }
467 36 : }
468 :
469 6 : void RDFaImportHelper::InsertRDFa(
470 : uno::Reference< rdf::XRepositorySupplier> const & i_xModel)
471 : {
472 : SAL_WARN_IF(!i_xModel.is(), "xmloff.core", "InsertRDFa: invalid arg: model null");
473 6 : if (!i_xModel.is()) return;
474 : const uno::Reference< rdf::XDocumentRepository > xRepository(
475 6 : i_xModel->getRDFRepository(), uno::UNO_QUERY);
476 : SAL_WARN_IF(!xRepository.is(), "xmloff.core", "InsertRDFa: no DocumentRepository?");
477 6 : if (!xRepository.is()) return;
478 12 : RDFaInserter inserter(GetImport().GetComponentContext(), xRepository);
479 : ::std::for_each(m_RDFaEntries.begin(), m_RDFaEntries.end(),
480 12 : ::boost::bind(&RDFaInserter::InsertRDFaEntry, &inserter, _1));
481 : }
482 :
483 276 : } // namespace xmloff
484 :
485 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|