Kaydet (Commit) 3829949e authored tarafından Miklos Vajna's avatar Miklos Vajna

EPUB export: handle nested spans

ODT export uses nested spans to represent a combination of named character
style + automatic style (instead of having a parent for the automatic style),
but librevenge doesn't allow nested spans, so handle this explicitly.

The alternative would have been to remember the attribute list as member
data, but the underlying SvXMLAttributeList is reused after
startElement() returns, so it by the time characters() is invoked, it
won't have the data we need anymore. (Would be a trade-off between doing
the attributes -> property list conversion exactly once or depending the
number of characters() invocation anyway.)

Change-Id: I1dd2f060c421c126340db471a257093b30431d17
Reviewed-on: https://gerrit.libreoffice.org/42046Reviewed-by: 's avatarMiklos Vajna <vmiklos@collabora.co.uk>
Tested-by: 's avatarJenkins <ci@libreoffice.org>
üst 41a85500
...@@ -21,9 +21,10 @@ $(eval $(call gb_CppunitTest_use_libraries,writerperfect_epubexport, \ ...@@ -21,9 +21,10 @@ $(eval $(call gb_CppunitTest_use_libraries,writerperfect_epubexport, \
cppuhelper \ cppuhelper \
sal \ sal \
test \ test \
tl \
unotest \ unotest \
utl \ utl \
tl \ wpftwriter \
$(gb_UWINAPI) \ $(gb_UWINAPI) \
)) ))
......
...@@ -44,6 +44,8 @@ public: ...@@ -44,6 +44,8 @@ public:
void setUp() override; void setUp() override;
void tearDown() override; void tearDown() override;
void registerNamespaces(xmlXPathContextPtr &pXmlXpathCtx) override; void registerNamespaces(xmlXPathContextPtr &pXmlXpathCtx) override;
/// Asserts that rCssDoc has a key named rKey and one of its rules is rValue.
void assertCss(const std::map< OString, std::vector<OString> > &rCssDoc, const OString &rKey, const OString &rValue);
void createDoc(const OUString &rFile, const uno::Sequence<beans::PropertyValue> &rFilterData); void createDoc(const OUString &rFile, const uno::Sequence<beans::PropertyValue> &rFilterData);
/// Returns an XML representation of the stream named rName in the exported package. /// Returns an XML representation of the stream named rName in the exported package.
xmlDocPtr parseExport(const OUString &rName); xmlDocPtr parseExport(const OUString &rName);
...@@ -59,6 +61,7 @@ public: ...@@ -59,6 +61,7 @@ public:
void testParaNamedstyle(); void testParaNamedstyle();
void testCharNamedstyle(); void testCharNamedstyle();
void testNamedStyleInheritance(); void testNamedStyleInheritance();
void testNestedSpan();
CPPUNIT_TEST_SUITE(EPUBExportTest); CPPUNIT_TEST_SUITE(EPUBExportTest);
CPPUNIT_TEST(testOutlineLevel); CPPUNIT_TEST(testOutlineLevel);
...@@ -71,6 +74,7 @@ public: ...@@ -71,6 +74,7 @@ public:
CPPUNIT_TEST(testParaNamedstyle); CPPUNIT_TEST(testParaNamedstyle);
CPPUNIT_TEST(testCharNamedstyle); CPPUNIT_TEST(testCharNamedstyle);
CPPUNIT_TEST(testNamedStyleInheritance); CPPUNIT_TEST(testNamedStyleInheritance);
CPPUNIT_TEST(testNestedSpan);
CPPUNIT_TEST_SUITE_END(); CPPUNIT_TEST_SUITE_END();
}; };
...@@ -103,6 +107,16 @@ void EPUBExportTest::registerNamespaces(xmlXPathContextPtr &pXmlXpathCtx) ...@@ -103,6 +107,16 @@ void EPUBExportTest::registerNamespaces(xmlXPathContextPtr &pXmlXpathCtx)
xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("xhtml"), BAD_CAST("http://www.w3.org/1999/xhtml")); xmlXPathRegisterNs(pXmlXpathCtx, BAD_CAST("xhtml"), BAD_CAST("http://www.w3.org/1999/xhtml"));
} }
void EPUBExportTest::assertCss(const std::map< OString, std::vector<OString> > &rCssDoc, const OString &rKey, const OString &rValue)
{
auto it = rCssDoc.find(rKey);
CPPUNIT_ASSERT(it != rCssDoc.end());
const std::vector<OString> &rRule = it->second;
CPPUNIT_ASSERT_MESSAGE(OString("In '" + rKey + "', rule '" + rValue + "' is not found.").getStr(),
std::find(rRule.begin(), rRule.end(), rValue) != rRule.end());
}
void EPUBExportTest::createDoc(const OUString &rFile, const uno::Sequence<beans::PropertyValue> &rFilterData) void EPUBExportTest::createDoc(const OUString &rFile, const uno::Sequence<beans::PropertyValue> &rFilterData)
{ {
// Import the bugdoc and export as EPUB. // Import the bugdoc and export as EPUB.
...@@ -275,16 +289,32 @@ void EPUBExportTest::testNamedStyleInheritance() ...@@ -275,16 +289,32 @@ void EPUBExportTest::testNamedStyleInheritance()
// Find the CSS rule for the blue text. // Find the CSS rule for the blue text.
mpXmlDoc = parseExport("OEBPS/sections/section0001.xhtml"); mpXmlDoc = parseExport("OEBPS/sections/section0001.xhtml");
OUString aBlue = getXPath(mpXmlDoc, "//xhtml:p[2]/xhtml:span[2]", "class"); OString aBlue = getXPath(mpXmlDoc, "//xhtml:p[2]/xhtml:span[2]", "class").toUtf8();
std::map< OString, std::vector<OString> > aTree; std::map< OString, std::vector<OString> > aCssDoc;
parseCssExport("OEBPS/styles/stylesheet.css", aTree); parseCssExport("OEBPS/styles/stylesheet.css", aCssDoc);
CPPUNIT_ASSERT(aTree.find(aBlue.toUtf8()) != aTree.end()); assertCss(aCssDoc, aBlue, " color: #0000ff;");
const std::vector<OString> &rRule = aTree[aBlue.toUtf8()];
CPPUNIT_ASSERT(std::find(rRule.begin(), rRule.end(), " color: #0000ff;") != rRule.end());
// This failed, the span only had the properties from its style, but not // This failed, the span only had the properties from its style, but not
// from the style's parent(s). // from the style's parent(s).
CPPUNIT_ASSERT(std::find(rRule.begin(), rRule.end(), " font-family: 'Liberation Mono';") != rRule.end()); assertCss(aCssDoc, aBlue, " font-family: 'Liberation Mono';");
}
void EPUBExportTest::testNestedSpan()
{
createDoc("nested-span.fodt", {});
// Check textural content of nested span.
mpXmlDoc = parseExport("OEBPS/sections/section0001.xhtml");
// This crashed, span had no content.
assertXPathContent(mpXmlDoc, "//xhtml:p/xhtml:span[2]", "red");
// Check formatting of nested span.
OString aRed = getXPath(mpXmlDoc, "//xhtml:p/xhtml:span[2]", "class").toUtf8();
std::map< OString, std::vector<OString> > aCssDoc;
parseCssExport("OEBPS/styles/stylesheet.css", aCssDoc);
// This failed, direct formatting on top of named style was lost.
assertCss(aCssDoc, aRed, " color: #ff0000;");
assertCss(aCssDoc, aRed, " font-family: 'Liberation Mono';");
} }
CPPUNIT_TEST_SUITE_REGISTRATION(EPUBExportTest); CPPUNIT_TEST_SUITE_REGISTRATION(EPUBExportTest);
......
<?xml version="1.0" encoding="UTF-8"?>
<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" office:version="1.2" office:mimetype="application/vnd.oasis.opendocument.text">
<office:font-face-decls>
<style:font-face style:name="Liberation Mono" svg:font-family="&apos;Liberation Mono&apos;" style:font-family-generic="modern" style:font-pitch="fixed"/>
</office:font-face-decls>
<office:styles>
<style:style style:name="Source_20_Text" style:display-name="Source Text" style:family="text">
<style:text-properties style:font-name="Liberation Mono" fo:font-family="&apos;Liberation Mono&apos;" style:font-family-generic="modern" style:font-pitch="fixed"/>
</style:style>
</office:styles>
<office:automatic-styles>
<style:style style:name="T1" style:family="text">
<style:text-properties fo:color="#ff0000"/>
</style:style>
</office:automatic-styles>
<office:body>
<office:text>
<text:p>before<text:span text:style-name="Source_20_Text"><text:span text:style-name="T1">red</text:span></text:span></text:p>
</office:text>
</office:body>
</office:document>
...@@ -74,56 +74,63 @@ namespace exp ...@@ -74,56 +74,63 @@ namespace exp
class XMLSpanContext : public XMLImportContext class XMLSpanContext : public XMLImportContext
{ {
public: public:
XMLSpanContext(XMLImport &rImport); XMLSpanContext(XMLImport &rImport, const librevenge::RVNGPropertyList *pPropertyList);
XMLImportContext *CreateChildContext(const OUString &rName, const css::uno::Reference<css::xml::sax::XAttributeList> &xAttribs) override; XMLImportContext *CreateChildContext(const OUString &rName, const css::uno::Reference<css::xml::sax::XAttributeList> &xAttribs) override;
void SAL_CALL startElement(const OUString &rName, const css::uno::Reference<css::xml::sax::XAttributeList> &xAttribs) override; void SAL_CALL startElement(const OUString &rName, const css::uno::Reference<css::xml::sax::XAttributeList> &xAttribs) override;
void SAL_CALL endElement(const OUString &rName) override;
void SAL_CALL characters(const OUString &rChars) override; void SAL_CALL characters(const OUString &rChars) override;
private:
librevenge::RVNGPropertyList m_aPropertyList;
}; };
XMLSpanContext::XMLSpanContext(XMLImport &rImport) XMLSpanContext::XMLSpanContext(XMLImport &rImport, const librevenge::RVNGPropertyList *pPropertyList)
: XMLImportContext(rImport) : XMLImportContext(rImport)
{ {
if (!pPropertyList)
return;
// Inherit properties from parent span.
librevenge::RVNGPropertyList::Iter itProp(*pPropertyList);
for (itProp.rewind(); itProp.next();)
m_aPropertyList.insert(itProp.key(), itProp()->clone());
} }
XMLImportContext *XMLSpanContext::CreateChildContext(const OUString &rName, const css::uno::Reference<css::xml::sax::XAttributeList> &/*xAttribs*/) XMLImportContext *XMLSpanContext::CreateChildContext(const OUString &rName, const css::uno::Reference<css::xml::sax::XAttributeList> &/*xAttribs*/)
{ {
if (rName == "draw:frame") if (rName == "draw:frame")
return new XMLTextFrameContext(mrImport); return new XMLTextFrameContext(mrImport);
if (rName == "text:span")
return new XMLSpanContext(mrImport, &m_aPropertyList);
return nullptr; return nullptr;
} }
void XMLSpanContext::startElement(const OUString &/*rName*/, const css::uno::Reference<css::xml::sax::XAttributeList> &xAttribs) void XMLSpanContext::startElement(const OUString &/*rName*/, const css::uno::Reference<css::xml::sax::XAttributeList> &xAttribs)
{ {
librevenge::RVNGPropertyList aPropertyList;
for (sal_Int16 i = 0; i < xAttribs->getLength(); ++i) for (sal_Int16 i = 0; i < xAttribs->getLength(); ++i)
{ {
const OUString &rAttributeName = xAttribs->getNameByIndex(i); const OUString &rAttributeName = xAttribs->getNameByIndex(i);
const OUString &rAttributeValue = xAttribs->getValueByIndex(i); const OUString &rAttributeValue = xAttribs->getValueByIndex(i);
if (rAttributeName == "text:style-name") if (rAttributeName == "text:style-name")
FillStyles(rAttributeValue, mrImport.GetAutomaticTextStyles(), mrImport.GetTextStyles(), aPropertyList); FillStyles(rAttributeValue, mrImport.GetAutomaticTextStyles(), mrImport.GetTextStyles(), m_aPropertyList);
else else
{ {
OString sName = OUStringToOString(rAttributeName, RTL_TEXTENCODING_UTF8); OString sName = OUStringToOString(rAttributeName, RTL_TEXTENCODING_UTF8);
OString sValue = OUStringToOString(rAttributeValue, RTL_TEXTENCODING_UTF8); OString sValue = OUStringToOString(rAttributeValue, RTL_TEXTENCODING_UTF8);
aPropertyList.insert(sName.getStr(), sValue.getStr()); m_aPropertyList.insert(sName.getStr(), sValue.getStr());
} }
} }
mrImport.GetGenerator().openSpan(aPropertyList);
}
void XMLSpanContext::endElement(const OUString &/*rName*/)
{
mrImport.GetGenerator().closeSpan();
} }
void XMLSpanContext::characters(const OUString &rChars) void XMLSpanContext::characters(const OUString &rChars)
{ {
mrImport.GetGenerator().openSpan(m_aPropertyList);
OString sCharU8 = OUStringToOString(rChars, RTL_TEXTENCODING_UTF8); OString sCharU8 = OUStringToOString(rChars, RTL_TEXTENCODING_UTF8);
mrImport.GetGenerator().insertText(librevenge::RVNGString(sCharU8.getStr())); mrImport.GetGenerator().insertText(librevenge::RVNGString(sCharU8.getStr()));
mrImport.GetGenerator().closeSpan();
} }
/// Handler for <text:a>. /// Handler for <text:a>.
...@@ -178,7 +185,7 @@ XMLParaContext::XMLParaContext(XMLImport &rImport) ...@@ -178,7 +185,7 @@ XMLParaContext::XMLParaContext(XMLImport &rImport)
XMLImportContext *XMLParaContext::CreateChildContext(const OUString &rName, const css::uno::Reference<css::xml::sax::XAttributeList> &/*xAttribs*/) XMLImportContext *XMLParaContext::CreateChildContext(const OUString &rName, const css::uno::Reference<css::xml::sax::XAttributeList> &/*xAttribs*/)
{ {
if (rName == "text:span") if (rName == "text:span")
return new XMLSpanContext(mrImport); return new XMLSpanContext(mrImport, nullptr);
if (rName == "text:a") if (rName == "text:a")
return new XMLHyperlinkContext(mrImport); return new XMLHyperlinkContext(mrImport);
return nullptr; return nullptr;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment