| /* -*- Mode: ObjC; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ |
| /* |
| * This file is part of the Collabora Office project. |
| * |
| * This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| * |
| * This file incorporates work covered by the following license notice: |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed |
| * with this work for additional information regarding copyright |
| * ownership. The ASF licenses this file to you under the Apache |
| * License, Version 2.0 (the "License"); you may not use this file |
| * except in compliance with the License. You may obtain a copy of |
| * the License at http://www.apache.org/licenses/LICENSE-2.0 . |
| */ |
| |
| #include <sal/config.h> |
| |
| #include "DataFlavorMapping.hxx" |
| #include "HtmlFmtFlt.hxx" |
| #include <com/sun/star/datatransfer/UnsupportedFlavorException.hpp> |
| #include <com/sun/star/datatransfer/XMimeContentType.hpp> |
| #include <com/sun/star/datatransfer/MimeContentTypeFactory.hpp> |
| #include <com/sun/star/lang/IllegalArgumentException.hpp> |
| #include <com/sun/star/uno/Sequence.hxx> |
| #include <comphelper/processfactory.hxx> |
| |
| #include <rtl/ustring.hxx> |
| #include <sal/log.hxx> |
| #include <osl/endian.h> |
| |
| #include <cassert> |
| #include <cstring> |
| |
| #include <premac.h> |
| #include <UIKit/UIKit.h> |
| #include <MobileCoreServices/MobileCoreServices.h> |
| #include <postmac.h> |
| |
| using namespace css::datatransfer; |
| using namespace css::uno; |
| using namespace css::lang; |
| using namespace cppu; |
| |
| namespace |
| { |
| /* Determine whether or not a DataFlavor is valid. |
| */ |
| bool isValidFlavor(const DataFlavor& aFlavor) |
| { |
| size_t len = aFlavor.MimeType.getLength(); |
| Type dtype = aFlavor.DataType; |
| return ((len > 0) |
| && ((dtype == cppu::UnoType<Sequence<sal_Int8>>::get()) |
| || (dtype == cppu::UnoType<OUString>::get()))); |
| } |
| |
| OUString NSStringToOUString(const NSString* cfString) |
| { |
| assert(cfString && "Invalid parameter"); |
| |
| const char* utf8Str = [cfString UTF8String]; |
| unsigned int len = rtl_str_getLength(utf8Str); |
| |
| return OUString(utf8Str, len, RTL_TEXTENCODING_UTF8); |
| } |
| |
| NSString* OUStringToNSString(const OUString& ustring) |
| { |
| OString utf8Str = OUStringToOString(ustring, RTL_TEXTENCODING_UTF8); |
| return [NSString stringWithCString:utf8Str.getStr() encoding:NSUTF8StringEncoding]; |
| } |
| |
| NSString* PBTYPE_UTF8PLAINTEXT = (__bridge NSString*)kUTTypeUTF8PlainText; |
| NSString* PBTYPE_RTF = (__bridge NSString*)kUTTypeRTF; |
| NSString* PBTYPE_PNG = (__bridge NSString*)kUTTypePNG; |
| NSString* PBTYPE_JPEG = (__bridge NSString*)kUTTypeJPEG; |
| NSString* PBTYPE_HTML = (__bridge NSString*)kUTTypeHTML; |
| NSString* PBTYPE_PDF = (__bridge NSString*)kUTTypePDF; |
| |
| const char* FLAVOR_SESX |
| = "application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\""; |
| const char* FLAVOR_SLSDX = "application/" |
| "x-openoffice-linksrcdescriptor-xml;windows_formatname=\"Star Link " |
| "Source Descriptor (XML)\""; |
| const char* FLAVOR_LSX |
| = "application/x-openoffice-link-source-xml;windows_formatname=\"Star Link Source (XML)\""; |
| const char* FLAVOR_EOX |
| = "application/x-openoffice-embedded-obj-xml;windows_formatname=\"Star Embedded Object (XML)\""; |
| const char* FLAVOR_SVXB |
| = "application/x-openoffice-svbx;windows_formatname=\"SVXB (StarView Bitmap/Animation)\""; |
| const char* FLAVOR_GDIMF |
| = "application/x-openoffice-gdimetafile;windows_formatname=\"GDIMetaFile\""; |
| const char* FLAVOR_SODX = "application/x-openoffice-objectdescriptor-xml;windows_formatname=\"Star " |
| "Object Descriptor (XML)\""; |
| struct FlavorMap |
| { |
| NSString* SystemFlavor; |
| const char* OOoFlavor; |
| const char* HumanPresentableName; |
| bool DataTypeOUString; // sequence<byte> otherwise |
| }; |
| |
| // The SystemFlavor member is nil for the cases where there is no predefined pasteboard type UTI and |
| // we use the internal MIME type (media type) also on the pasteboard. That is OK, there dos not seem |
| // to be a requirement that the types are well-formed UTIs even on iOS. For an introduction to UTIs, |
| // see for instance |
| // https://alastairs-place.net/blog/2012/06/06/utis-are-better-than-you-think-and-heres-why/ |
| // |
| // In those cases the MIME type might actually have parameters appended, separated by semicolons. |
| // At least the FLAVOR_SODX one must have at least a typename="%PRODUCTNAME %PRODUCTVERSION |
| // Spreadsheet" parameter (with macros expanded and translated) for LO to recognise it. See |
| // lcl_TestFormat() in sc/source/ui/view/cellsh.cxx. |
| |
| static const FlavorMap flavorMap[] |
| = { { PBTYPE_UTF8PLAINTEXT, "text/plain;charset=utf-16", "Unicode Text (UTF-16)", true }, |
| { PBTYPE_RTF, "text/rtf", "Rich Text Format", false }, |
| { PBTYPE_PNG, "image/png", "Portable Network Graphics", false }, |
| { PBTYPE_JPEG, "image/jpeg", "JPEG", false }, |
| { PBTYPE_HTML, "text/html", "Plain HTML", false }, |
| { PBTYPE_PDF, "application/pdf", "PDF File", false }, |
| { nil, FLAVOR_SESX, "Star Embed Source (XML)", false }, |
| { nil, FLAVOR_SLSDX, "Star Link Source Descriptor (XML)", false }, |
| { nil, FLAVOR_LSX, "Star Link Source (XML)", false }, |
| { nil, FLAVOR_EOX, "Star Embedded Object (XML)", false }, |
| { nil, FLAVOR_SVXB, "SVXB (StarView Bitmap/Animation", false }, |
| { nil, FLAVOR_GDIMF, "GDIMetaFile", false }, |
| { nil, FLAVOR_SODX, "Star Object Descriptor (XML)", false } }; |
| |
| #define SIZE_FLAVOR_MAP (sizeof(flavorMap) / sizeof(FlavorMap)) |
| |
| inline bool isByteSequenceType(const Type& theType) |
| { |
| return (theType == cppu::UnoType<Sequence<sal_Int8>>::get()); |
| } |
| |
| inline bool isOUStringType(const Type& theType) |
| { |
| return (theType == cppu::UnoType<OUString>::get()); |
| } |
| |
| } // unnamed namespace |
| |
| /* A base class for other data provider. |
| */ |
| class DataProviderBaseImpl : public DataProvider |
| { |
| public: |
| DataProviderBaseImpl(const Any& data); |
| DataProviderBaseImpl(id data); |
| virtual ~DataProviderBaseImpl() override; |
| |
| protected: |
| Any mData; |
| //NSData* mSystemData; |
| id mSystemData; |
| }; |
| |
| DataProviderBaseImpl::DataProviderBaseImpl(const Any& data) |
| : mData(data) |
| , mSystemData(nil) |
| { |
| } |
| |
| DataProviderBaseImpl::DataProviderBaseImpl(id data) |
| : mSystemData(data) |
| { |
| [mSystemData retain]; |
| } |
| |
| DataProviderBaseImpl::~DataProviderBaseImpl() |
| { |
| if (mSystemData) |
| { |
| [mSystemData release]; |
| } |
| } |
| |
| class Utf8DataProvider : public DataProviderBaseImpl |
| { |
| public: |
| Utf8DataProvider(const Any& data); |
| Utf8DataProvider(NSData* data); |
| |
| NSData* getSystemData() override; |
| Any getOOoData() override; |
| }; |
| |
| Utf8DataProvider::Utf8DataProvider(const Any& data) |
| : DataProviderBaseImpl(data) |
| { |
| } |
| |
| Utf8DataProvider::Utf8DataProvider(NSData* data) |
| : DataProviderBaseImpl(data) |
| { |
| } |
| |
| NSData* Utf8DataProvider::getSystemData() |
| { |
| OUString ustr; |
| mData >>= ustr; |
| |
| OString strUtf8; |
| ustr.convertToString(&strUtf8, RTL_TEXTENCODING_UTF8, OUSTRING_TO_OSTRING_CVTFLAGS); |
| |
| return [NSData dataWithBytes:strUtf8.getStr() length:strUtf8.getLength()]; |
| } |
| |
| Any Utf8DataProvider::getOOoData() |
| { |
| Any oOOData; |
| |
| if (mSystemData) |
| { |
| oOOData <<= OUString(static_cast<const char*>([mSystemData bytes]), [mSystemData length], |
| RTL_TEXTENCODING_UTF8); |
| } |
| else |
| { |
| oOOData = mData; |
| } |
| |
| return oOOData; |
| } |
| |
| class ByteSequenceDataProvider : public DataProviderBaseImpl |
| { |
| public: |
| ByteSequenceDataProvider(const Any& data); |
| ByteSequenceDataProvider(NSData* data); |
| |
| NSData* getSystemData() override; |
| Any getOOoData() override; |
| }; |
| |
| ByteSequenceDataProvider::ByteSequenceDataProvider(const Any& data) |
| : DataProviderBaseImpl(data) |
| { |
| } |
| |
| ByteSequenceDataProvider::ByteSequenceDataProvider(NSData* data) |
| : DataProviderBaseImpl(data) |
| { |
| } |
| |
| NSData* ByteSequenceDataProvider::getSystemData() |
| { |
| Sequence<sal_Int8> rawData; |
| mData >>= rawData; |
| |
| return [NSData dataWithBytes:rawData.getArray() length:rawData.getLength()]; |
| } |
| |
| Any ByteSequenceDataProvider::getOOoData() |
| { |
| Any oOOData; |
| |
| if (mSystemData) |
| { |
| unsigned int flavorDataLength = [mSystemData length]; |
| Sequence<sal_Int8> byteSequence; |
| byteSequence.realloc(flavorDataLength); |
| memcpy(byteSequence.getArray(), [mSystemData bytes], flavorDataLength); |
| oOOData <<= byteSequence; |
| } |
| else |
| { |
| oOOData = mData; |
| } |
| |
| return oOOData; |
| } |
| |
| class HTMLFormatDataProvider : public DataProviderBaseImpl |
| { |
| public: |
| HTMLFormatDataProvider(NSData* data); |
| |
| NSData* getSystemData() override; |
| Any getOOoData() override; |
| }; |
| |
| HTMLFormatDataProvider::HTMLFormatDataProvider(NSData* data) |
| : DataProviderBaseImpl(data) |
| { |
| } |
| |
| NSData* HTMLFormatDataProvider::getSystemData() |
| { |
| Sequence<sal_Int8> textHtmlData; |
| mData >>= textHtmlData; |
| |
| Sequence<sal_Int8> htmlFormatData = TextHtmlToHTMLFormat(textHtmlData); |
| |
| return [NSData dataWithBytes:htmlFormatData.getArray() length:htmlFormatData.getLength()]; |
| } |
| |
| Any HTMLFormatDataProvider::getOOoData() |
| { |
| Any oOOData; |
| |
| if (mSystemData) |
| { |
| unsigned int flavorDataLength = [mSystemData length]; |
| Sequence<sal_Int8> unkHtmlData; |
| |
| unkHtmlData.realloc(flavorDataLength); |
| memcpy(unkHtmlData.getArray(), [mSystemData bytes], flavorDataLength); |
| |
| Sequence<sal_Int8>* pPlainHtml = &unkHtmlData; |
| Sequence<sal_Int8> plainHtml; |
| |
| if (isHTMLFormat(unkHtmlData)) |
| { |
| plainHtml = HTMLFormatToTextHtml(unkHtmlData); |
| pPlainHtml = &plainHtml; |
| } |
| |
| oOOData <<= *pPlainHtml; |
| } |
| else |
| { |
| oOOData = mData; |
| } |
| |
| return oOOData; |
| } |
| |
| DataFlavorMapper::DataFlavorMapper() |
| { |
| Reference<XComponentContext> xContext = comphelper::getProcessComponentContext(); |
| mrXMimeCntFactory = MimeContentTypeFactory::create(xContext); |
| } |
| |
| DataFlavorMapper::~DataFlavorMapper() |
| { |
| // release potential NSStrings |
| for (OfficeOnlyTypes::iterator it = maOfficeOnlyTypes.begin(); it != maOfficeOnlyTypes.end(); |
| ++it) |
| { |
| [it->second release]; |
| it->second = nil; |
| } |
| } |
| |
| DataFlavor DataFlavorMapper::systemToOpenOfficeFlavor(const NSString* systemDataFlavor) const |
| { |
| DataFlavor oOOFlavor; |
| |
| for (size_t i = 0; i < SIZE_FLAVOR_MAP; i++) |
| { |
| if ((flavorMap[i].SystemFlavor == nil |
| && ([systemDataFlavor |
| isEqualToString:[NSString stringWithUTF8String:flavorMap[i].OOoFlavor]] |
| || |
| [systemDataFlavor hasPrefix:[[NSString stringWithUTF8String:flavorMap[i].OOoFlavor] |
| stringByAppendingString:@";"]])) |
| || (flavorMap[i].SystemFlavor != nil && |
| [systemDataFlavor |
| isEqualToString:const_cast<NSString*>(flavorMap[i].SystemFlavor)])) |
| { |
| if (flavorMap[i].SystemFlavor == nil) |
| oOOFlavor.MimeType = NSStringToOUString(systemDataFlavor); |
| else |
| oOOFlavor.MimeType = OUString::createFromAscii(flavorMap[i].OOoFlavor); |
| oOOFlavor.HumanPresentableName |
| = OUString::createFromAscii(flavorMap[i].HumanPresentableName); |
| oOOFlavor.DataType = flavorMap[i].DataTypeOUString |
| ? cppu::UnoType<OUString>::get() |
| : cppu::UnoType<Sequence<sal_Int8>>::get(); |
| return oOOFlavor; |
| } |
| } // for |
| |
| // look if this might be an internal type; if it comes in here it must have |
| // been through openOfficeToSystemFlavor before, so it should then be in the map |
| OUString aTryFlavor(NSStringToOUString(systemDataFlavor)); |
| if (maOfficeOnlyTypes.find(aTryFlavor) != maOfficeOnlyTypes.end()) |
| { |
| oOOFlavor.MimeType = aTryFlavor; |
| oOOFlavor.HumanPresentableName.clear(); |
| oOOFlavor.DataType = cppu::UnoType<Sequence<sal_Int8>>::get(); |
| } |
| |
| return oOOFlavor; |
| } |
| |
| NSString* DataFlavorMapper::openOfficeToSystemFlavor(const DataFlavor& oOOFlavor, |
| bool& rbInternal) const |
| { |
| NSString* sysFlavor = nullptr; |
| rbInternal = false; |
| |
| for (size_t i = 0; i < SIZE_FLAVOR_MAP; ++i) |
| { |
| if (oOOFlavor.MimeType.startsWith(OUString::createFromAscii(flavorMap[i].OOoFlavor))) |
| { |
| if (flavorMap[i].SystemFlavor != nil) |
| sysFlavor = flavorMap[i].SystemFlavor; |
| else |
| sysFlavor = OUStringToNSString(oOOFlavor.MimeType); |
| |
| // Flavor set, then break |
| if (sysFlavor != nullptr) |
| break; |
| } |
| } |
| |
| if (!sysFlavor) |
| { |
| // For some reason, if we allow text/html, we get an OSL_ENSURE failure in xmloff that |
| // apparently is a symptom of something being seriously wrong: |
| // xmloff/source/transform/OOo2Oasis.cxx:1925: duplicate doc handler |
| // Because is then followed a bit later by an assertion failure: |
| // Assertion failed: (!m_pFirst && !m_pLast && "There are still indices registered"), function ~SwContentIndexReg, file [...]/sw/source/core/bastyp/index.cxx, line 226 |
| |
| if (oOOFlavor.MimeType == "text/html") |
| return nil; |
| |
| rbInternal = true; |
| OfficeOnlyTypes::const_iterator it = maOfficeOnlyTypes.find(oOOFlavor.MimeType); |
| |
| if (it == maOfficeOnlyTypes.end()) |
| { |
| // tdf#161461 stop crashing by retaining NSString |
| // OUStringToNSString() returns an autoreleased NSString so it |
| // needs to be retained for the life of maOfficeOnlyTypes. |
| sysFlavor = maOfficeOnlyTypes[oOOFlavor.MimeType] = |
| [OUStringToNSString(oOOFlavor.MimeType) retain]; |
| } |
| else |
| { |
| sysFlavor = it->second; |
| } |
| } |
| |
| return sysFlavor; |
| } |
| |
| NSString* DataFlavorMapper::openOfficeImageToSystemFlavor() |
| { |
| if ([[UIPasteboard generalPasteboard] containsPasteboardTypes:@[ PBTYPE_PNG ]]) |
| return PBTYPE_PNG; |
| else if ([[UIPasteboard generalPasteboard] containsPasteboardTypes:@[ PBTYPE_JPEG ]]) |
| return PBTYPE_JPEG; |
| else if ([[UIPasteboard generalPasteboard] containsPasteboardTypes:@[ PBTYPE_PDF ]]) |
| return PBTYPE_PDF; |
| return @""; |
| } |
| |
| DataProviderPtr_t |
| DataFlavorMapper::getDataProvider(const NSString* systemFlavor, |
| Reference<XTransferable> const& rTransferable) const |
| { |
| DataProviderPtr_t dp; |
| |
| try |
| { |
| DataFlavor oOOFlavor = systemToOpenOfficeFlavor(systemFlavor); |
| |
| Any data = rTransferable->getTransferData(oOOFlavor); |
| |
| if (isByteSequenceType(data.getValueType())) |
| { |
| dp = DataProviderPtr_t(new ByteSequenceDataProvider(data)); |
| } |
| else // Must be OUString type |
| { |
| SAL_WARN_IF(!isOUStringType(data.getValueType()), "vcl", "must be OUString type"); |
| dp = DataProviderPtr_t(new Utf8DataProvider(data)); |
| } |
| } |
| catch (const UnsupportedFlavorException& e) |
| { |
| SAL_WARN("vcl.ios.clipboard", |
| "DataFlavorMapper::getDataProvider(): Exception: " << e.Message); |
| // Somebody violates the contract of the clipboard |
| // interface @see XTransferable |
| } |
| |
| return dp; |
| } |
| |
| DataProviderPtr_t DataFlavorMapper::getDataProvider(const NSString* systemFlavor, |
| NSData* systemData) |
| { |
| DataProviderPtr_t dp; |
| |
| if (systemData == nil) |
| return dp; |
| |
| if ([systemFlavor caseInsensitiveCompare:PBTYPE_UTF8PLAINTEXT] == NSOrderedSame) |
| { |
| dp = DataProviderPtr_t(new Utf8DataProvider(systemData)); |
| } |
| else if ([systemFlavor caseInsensitiveCompare:PBTYPE_HTML] == NSOrderedSame) |
| { |
| dp = DataProviderPtr_t(new HTMLFormatDataProvider(systemData)); |
| } |
| else |
| { |
| dp = DataProviderPtr_t(new ByteSequenceDataProvider(systemData)); |
| } |
| |
| return dp; |
| } |
| |
| bool DataFlavorMapper::isValidMimeContentType(const OUString& contentType) const |
| { |
| bool result = true; |
| |
| try |
| { |
| Reference<XMimeContentType> xCntType(mrXMimeCntFactory->createMimeContentType(contentType)); |
| } |
| catch (const IllegalArgumentException& e) |
| { |
| SAL_WARN("vcl.ios.clipboard", |
| "DataFlavorMapper::isValidMimeContentType(): Exception: " << e.Message); |
| result = false; |
| } |
| |
| return result; |
| } |
| |
| NSArray* DataFlavorMapper::flavorSequenceToTypesArray( |
| const css::uno::Sequence<css::datatransfer::DataFlavor>& flavors) const |
| { |
| const sal_uInt32 nFlavors = flavors.getLength(); |
| NSMutableArray* array = [[NSMutableArray alloc] initWithCapacity:1]; |
| |
| bool bNeedDummyInternalFlavor(false); |
| |
| for (sal_uInt32 i = 0; i < nFlavors; i++) |
| { |
| if (flavors[i].MimeType.startsWith("image/bmp")) |
| { |
| [array addObject:PBTYPE_PNG]; |
| } |
| else |
| { |
| const NSString* str = openOfficeToSystemFlavor(flavors[i], bNeedDummyInternalFlavor); |
| |
| if (str != nil) |
| { |
| [str retain]; |
| [array addObject:str]; |
| } |
| } |
| } |
| |
| return [array autorelease]; |
| } |
| |
| css::uno::Sequence<css::datatransfer::DataFlavor> |
| DataFlavorMapper::typesArrayToFlavorSequence(NSArray* types) const |
| { |
| int nFormats = [types count]; |
| Sequence<DataFlavor> flavors; |
| |
| for (int i = 0; i < nFormats; i++) |
| { |
| NSString* sysFormat = [types objectAtIndex:i]; |
| DataFlavor oOOFlavor = systemToOpenOfficeFlavor(sysFormat); |
| |
| if (isValidFlavor(oOOFlavor)) |
| { |
| flavors.realloc(flavors.getLength() + 1); |
| flavors.getArray()[flavors.getLength() - 1] = oOOFlavor; |
| SAL_INFO("vcl.ios.clipboard", |
| "Mapped " << [sysFormat UTF8String] << " to " << oOOFlavor.MimeType); |
| } |
| else |
| { |
| SAL_INFO("vcl.ios.clipboard", |
| "Was not able to map " << [sysFormat UTF8String] << " to an internal flavour"); |
| } |
| } |
| |
| return flavors; |
| } |
| |
| /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |