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/sequenceasvector.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 0 : const SvXMLImport & GetImport() const { return m_rImport; }
57 :
58 : //FIXME: this is an ugly hack to workaround buggy SvXMLImport::GetAbsolute
59 0 : OUString GetAbsoluteReference(OUString const & i_rURI) const
60 : {
61 0 : if (i_rURI.isEmpty() || i_rURI[0] == '#')
62 : {
63 0 : return GetImport().GetBaseURL() + i_rURI;
64 : }
65 : else
66 : {
67 0 : return GetImport().GetAbsoluteReference(i_rURI);
68 : }
69 : }
70 :
71 : public:
72 0 : RDFaReader(SvXMLImport const & i_rImport)
73 0 : : m_rImport(i_rImport)
74 0 : { }
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 0 : 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 0 : RDFaInserter(uno::Reference<uno::XComponentContext> const & i_xContext,
99 : uno::Reference< rdf::XDocumentRepository > const & i_xRepository)
100 : : m_xContext(i_xContext)
101 0 : , m_xRepository(i_xRepository)
102 0 : {}
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 0 : struct ParsedRDFaAttributes
118 : {
119 : OUString m_About;
120 : ::std::vector< OUString > m_Properties;
121 : OUString m_Content;
122 : OUString m_Datatype;
123 :
124 0 : 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 0 : , m_Datatype(i_rDatatype)
133 0 : { }
134 : };
135 :
136 : /** store metadatable object and its RDFa attributes */
137 0 : struct RDFaEntry
138 : {
139 : uno::Reference<rdf::XMetadatable> m_xObject;
140 : ::boost::shared_ptr<ParsedRDFaAttributes> m_pRDFaAttributes;
141 :
142 0 : RDFaEntry(uno::Reference<rdf::XMetadatable> const & i_xObject,
143 : ::boost::shared_ptr<ParsedRDFaAttributes> const& i_pRDFaAttributes)
144 : : m_xObject(i_xObject)
145 0 : , m_pRDFaAttributes(i_pRDFaAttributes)
146 0 : { }
147 : };
148 :
149 0 : static inline bool isWS(const sal_Unicode i_Char)
150 : {
151 0 : return ('\t' == i_Char) || ('\n' == i_Char) || ('\r' == i_Char)
152 0 : || (' ' == i_Char);
153 : }
154 :
155 0 : static OUString splitAtWS(OUString & io_rString)
156 : {
157 0 : const sal_Int32 len( io_rString.getLength() );
158 0 : sal_Int32 idxstt(0);
159 0 : while ((idxstt < len) && ( isWS(io_rString[idxstt])))
160 0 : ++idxstt; // skip leading ws
161 0 : sal_Int32 idxend(idxstt);
162 0 : while ((idxend < len) && (!isWS(io_rString[idxend])))
163 0 : ++idxend; // the CURIE
164 0 : const OUString ret(io_rString.copy(idxstt, idxend - idxstt));
165 0 : io_rString = io_rString.copy(idxend); // rest
166 0 : return ret;
167 : }
168 :
169 : OUString
170 0 : RDFaReader::ReadCURIE(OUString const & i_rCURIE) const
171 : {
172 : // the RDFa spec says that a prefix is required (it may be empty: ":foo")
173 0 : const sal_Int32 idx( i_rCURIE.indexOf(':') );
174 0 : if (idx >= 0)
175 : {
176 0 : OUString Prefix;
177 0 : OUString LocalName;
178 0 : OUString Namespace;
179 0 : sal_uInt16 nKey( GetImport().GetNamespaceMap()._GetKeyByAttrName(
180 0 : i_rCURIE, &Prefix, &LocalName, &Namespace) );
181 0 : if ( Prefix == "_" )
182 : {
183 : // eeek, it's a bnode!
184 : // "_" is not a valid URI scheme => we can identify bnodes
185 0 : return i_rCURIE;
186 : }
187 : else
188 : {
189 : SAL_WARN_IF(XML_NAMESPACE_NONE == nKey, "xmloff.core", "no namespace?");
190 0 : if ((XML_NAMESPACE_UNKNOWN != nKey) &&
191 : (XML_NAMESPACE_XMLNS != nKey))
192 : {
193 : // N.B.: empty LocalName is valid!
194 0 : const OUString URI(Namespace + LocalName);
195 0 : return GetAbsoluteReference(URI);
196 : }
197 : else
198 : {
199 : SAL_INFO("xmloff.core", "ReadCURIE: invalid CURIE: invalid prefix" );
200 0 : return OUString();
201 : }
202 0 : }
203 : }
204 : SAL_INFO("xmloff.core", "ReadCURIE: invalid CURIE: no prefix" );
205 0 : return OUString();
206 : }
207 :
208 : ::std::vector< OUString >
209 0 : RDFaReader::ReadCURIEs(OUString const & i_rCURIEs) const
210 : {
211 0 : std::vector< OUString > vec;
212 0 : OUString CURIEs(i_rCURIEs);
213 0 : do {
214 0 : OUString curie( splitAtWS(CURIEs) );
215 0 : if (!curie.isEmpty())
216 : {
217 0 : const OUString uri(ReadCURIE(curie));
218 0 : if (!uri.isEmpty())
219 : {
220 0 : vec.push_back(uri);
221 0 : }
222 0 : }
223 : }
224 0 : while (!CURIEs.isEmpty());
225 0 : if (vec.empty())
226 : {
227 : SAL_INFO("xmloff.core", "ReadCURIEs: invalid CURIEs" );
228 : }
229 0 : return vec;
230 : }
231 :
232 : OUString
233 0 : RDFaReader::ReadURIOrSafeCURIE(OUString const & i_rURIOrSafeCURIE) const
234 : {
235 0 : const sal_Int32 len(i_rURIOrSafeCURIE.getLength());
236 0 : if (len && (i_rURIOrSafeCURIE[0] == '['))
237 : {
238 0 : if ((len >= 2) && (i_rURIOrSafeCURIE[len - 1] == ']'))
239 : {
240 0 : 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 0 : if (i_rURIOrSafeCURIE.matchAsciiL("_:", 2)) // blank node
251 : {
252 : SAL_INFO("xmloff.core", "ReadURIOrSafeCURIE: invalid URI: scheme is _" );
253 0 : return OUString();
254 : }
255 : else
256 : {
257 0 : return GetAbsoluteReference(i_rURIOrSafeCURIE);
258 : }
259 : }
260 : }
261 :
262 : uno::Reference< rdf::XBlankNode >
263 0 : RDFaInserter::LookupBlankNode(OUString const & i_rNodeId )
264 : {
265 0 : uno::Reference< rdf::XBlankNode > & rEntry( m_BlankNodeMap[ i_rNodeId ] );
266 0 : if (!rEntry.is())
267 : {
268 0 : rEntry = m_xRepository->createBlankNode();
269 : }
270 0 : return rEntry;
271 : }
272 :
273 : uno::Reference< rdf::XURI >
274 0 : RDFaInserter::MakeURI( OUString const & i_rURI) const
275 : {
276 0 : if (i_rURI.matchAsciiL("_:", 2)) // blank node
277 : {
278 : SAL_INFO("xmloff.core", "MakeURI: cannot create URI for blank node");
279 0 : return 0;
280 : }
281 : else
282 : {
283 : try
284 : {
285 0 : 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 0 : RDFaInserter::MakeResource( OUString const & i_rResource)
297 : {
298 0 : if (i_rResource.matchAsciiL("_:", 2)) // 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 0 : OUString name( i_rResource.copy(2) );
304 0 : const uno::Reference< rdf::XBlankNode > xBNode( LookupBlankNode(name) );
305 : SAL_WARN_IF(!xBNode.is(), "xmloff.core", "no blank node?");
306 0 : return uno::Reference<rdf::XResource>( xBNode, uno::UNO_QUERY);
307 : }
308 : else
309 : {
310 : return uno::Reference<rdf::XResource>( MakeURI( i_rResource ),
311 0 : 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 0 : bool operator() (const uno::Reference<rdf::XURI> & i_rRef)
322 : {
323 0 : return !i_rRef.is();
324 : }
325 : };
326 :
327 0 : 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 0 : if (!i_rEntry.m_xObject.is()) return;
332 :
333 : const uno::Reference< rdf::XResource > xSubject(
334 0 : MakeResource( i_rEntry.m_pRDFaAttributes->m_About ) );
335 0 : if (!xSubject.is())
336 : {
337 0 : return; // invalid
338 : }
339 :
340 0 : ::comphelper::SequenceAsVector< uno::Reference< rdf::XURI > > predicates;
341 :
342 0 : predicates.reserve(i_rEntry.m_pRDFaAttributes->m_Properties.size());
343 :
344 : ::std::remove_copy_if(
345 : ::boost::make_transform_iterator(
346 0 : i_rEntry.m_pRDFaAttributes->m_Properties.begin(),
347 : ::boost::bind(&RDFaInserter::MakeURI, this, _1)),
348 : // argh, this must be the same type :(
349 : ::boost::make_transform_iterator(
350 0 : i_rEntry.m_pRDFaAttributes->m_Properties.end(),
351 : ::boost::bind(&RDFaInserter::MakeURI, this, _1)),
352 : ::std::back_inserter(predicates),
353 0 : 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 0 : if (!predicates.size())
362 : {
363 0 : return; // invalid
364 : }
365 :
366 0 : uno::Reference<rdf::XURI> xDatatype;
367 0 : if (!i_rEntry.m_pRDFaAttributes->m_Datatype.isEmpty())
368 : {
369 0 : xDatatype = MakeURI( i_rEntry.m_pRDFaAttributes->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 0 : m_xRepository->setStatementRDFa(xSubject, predicates.getAsConstList(),
378 : i_rEntry.m_xObject,
379 0 : i_rEntry.m_pRDFaAttributes->m_Content, xDatatype);
380 : }
381 0 : catch (uno::Exception &)
382 : {
383 : SAL_WARN("xmloff.core", "InsertRDFaEntry: setStatementRDFa failed?");
384 0 : }
385 : }
386 :
387 0 : RDFaImportHelper::RDFaImportHelper(const SvXMLImport & i_rImport)
388 0 : : m_rImport(i_rImport)
389 : {
390 0 : }
391 :
392 0 : RDFaImportHelper::~RDFaImportHelper()
393 : {
394 0 : }
395 :
396 : ::boost::shared_ptr<ParsedRDFaAttributes>
397 0 : RDFaImportHelper::ParseRDFa(
398 : OUString const & i_rAbout,
399 : OUString const & i_rProperty,
400 : OUString const & i_rContent,
401 : OUString const & i_rDatatype)
402 : {
403 0 : if (i_rProperty.isEmpty())
404 : {
405 : SAL_INFO("xmloff.core", "AddRDFa: invalid input: xhtml:property empty");
406 0 : return ::boost::shared_ptr<ParsedRDFaAttributes>();
407 : }
408 : // must parse CURIEs here: need namespace declaration context
409 0 : RDFaReader reader(GetImport());
410 0 : const OUString about( reader.ReadURIOrSafeCURIE(i_rAbout) );
411 0 : if (about.isEmpty()) {
412 0 : return ::boost::shared_ptr<ParsedRDFaAttributes>();
413 : }
414 : const ::std::vector< OUString > properties(
415 0 : reader.ReadCURIEs(i_rProperty) );
416 0 : if (!properties.size()) {
417 0 : return ::boost::shared_ptr<ParsedRDFaAttributes>();
418 : }
419 0 : const OUString datatype( !i_rDatatype.isEmpty()
420 : ? reader.ReadCURIE(i_rDatatype)
421 0 : : OUString() );
422 : return ::boost::shared_ptr<ParsedRDFaAttributes>(
423 0 : new ParsedRDFaAttributes(about, properties, i_rContent, datatype));
424 : }
425 :
426 : void
427 0 : RDFaImportHelper::AddRDFa(
428 : uno::Reference<rdf::XMetadatable> const & i_xObject,
429 : ::boost::shared_ptr<ParsedRDFaAttributes> & i_pRDFaAttributes)
430 : {
431 0 : if (!i_xObject.is())
432 : {
433 : SAL_WARN("xmloff.core", "AddRDFa: invalid arg: null textcontent");
434 0 : return;
435 : }
436 0 : if (!i_pRDFaAttributes.get())
437 : {
438 : SAL_WARN("xmloff.core", "AddRDFa: invalid arg: null RDFa attributes");
439 0 : return;
440 : }
441 0 : m_RDFaEntries.push_back(RDFaEntry(i_xObject, i_pRDFaAttributes));
442 : }
443 :
444 : void
445 0 : 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 : ::boost::shared_ptr<ParsedRDFaAttributes> pAttributes(
453 0 : ParseRDFa(i_rAbout, i_rProperty, i_rContent, i_rDatatype) );
454 0 : if (pAttributes.get())
455 : {
456 0 : AddRDFa(i_xObject, pAttributes);
457 0 : }
458 0 : }
459 :
460 0 : 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 0 : if (!i_xModel.is()) return;
465 : const uno::Reference< rdf::XDocumentRepository > xRepository(
466 0 : i_xModel->getRDFRepository(), uno::UNO_QUERY);
467 : SAL_WARN_IF(!xRepository.is(), "xmloff.core", "InsertRDFa: no DocumentRepository?");
468 0 : if (!xRepository.is()) return;
469 0 : RDFaInserter inserter(GetImport().GetComponentContext(), xRepository);
470 : ::std::for_each(m_RDFaEntries.begin(), m_RDFaEntries.end(),
471 0 : ::boost::bind(&RDFaInserter::InsertRDFaEntry, &inserter, _1));
472 : }
473 :
474 0 : } // namespace xmloff
475 :
476 : /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|