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