Commit e478769b authored by Matus Uzak's avatar Matus Uzak
Browse files

docx: Automatic numbering support.

* Using the combination of text:continue-numbering and text:start-value, which is fine for
  simple docx files.  To support interferrence of numbered list at the same level with
  separate numbering the text:continue-list support is required.

* Stopped using the KoGenStyles::AllowDuplicates parameter when inserting a list style into
  the styles collection.

* Added support for the w:lvlOverride element.

TODO: Numbered lists inside tables.  In MSWord, the automatic numbering is applied in a
table column.  Each column has a separate numbered lists.  The filter is OK, waiting the
layout part to test.

BUG:285303
parent 4d7dbd30
......@@ -1426,6 +1426,7 @@ void Utils::ParagraphBulletProperties::clear()
m_followingChar = UNUSED;
m_bulletRelativeSize = UNUSED;
m_bulletSize = UNUSED;
m_startOverride = false;
}
void Utils::ParagraphBulletProperties::setAlign(const QString& align)
......@@ -1532,6 +1533,11 @@ void Utils::ParagraphBulletProperties::setTextStyle(const KoGenStyle& textStyle)
}
}
void Utils::ParagraphBulletProperties::setStartOverride(const bool startOverride)
{
m_startOverride = startOverride;
}
QString Utils::ParagraphBulletProperties::startValue() const
{
return m_startValue;
......@@ -1577,6 +1583,11 @@ QString Utils::ParagraphBulletProperties::followingChar() const
return m_followingChar;
}
bool Utils::ParagraphBulletProperties::startOverride() const
{
return m_startOverride;
}
void Utils::ParagraphBulletProperties::addInheritedValues(const ParagraphBulletProperties& properties)
{
// This function is intented for helping to inherit some values from other properties
......
......@@ -113,6 +113,8 @@ public:
void setTextStyle(const KoGenStyle& textStyle);
void setStartOverride(const bool startOverride);
QString startValue() const;
QString bulletRelativeSize() const;
......@@ -131,6 +133,8 @@ public:
QString followingChar() const;
bool startOverride() const;
void addInheritedValues(const ParagraphBulletProperties& properties);
int m_level;
......@@ -156,6 +160,13 @@ private:
QString m_bulletSize;
KoGenStyle m_textStyle;
// MSWord specific: Restart the numbering when this list style is
// used for the 1st time. Otherwise don't restart in case any of the
// styles inheriting from the same abstract numbering definition was
// already used. Let's ignore presence of this attribute in
// addInheritedValues.
bool m_startOverride;
};
//! Container autodeleter. Works for QList, QHash and QMap.
......
......@@ -196,17 +196,24 @@ KoFilter::ConversionStatus DocxImport::parseParts(KoOdfWriters *writers, MSOOXML
reportProgress(15);
// Main document context, to which we collect footnotes, endnotes, comments, numbering, tablestyles
// Main document context, to which we collect footnotes, endnotes,
// comments, numbering, tablestyles
DocxXmlDocumentReaderContext mainContext(*this, documentPath, documentFile, *relationships, &themes);
// 3. parse styles
{
// get styles path from document's relationships, not from content types; typically returns /word/styles.xml
// get styles path from document's relationships, not from content
// types; typically returns /word/styles.xml
//
// ECMA-376, 11.3.12 Style Definitions Part, p. 65
// An instance of this part type contains the definition for a set of styles used by this document.
// A package shall contain at most two Style Definitions parts. One instance of that part shall be
// the target of an implicit relationship from the Main Document (§11.3.10) part, and the other shall
// be the target of an implicit relationship in from the Glossary Document (§11.3.8) part.
//
// An instance of this part type contains the definition for a set of
// styles used by this document. A package shall contain at most two
// Style Definitions parts. One instance of that part shall be the
// target of an implicit relationship from the Main Document (§11.3.10)
// part, and the other shall be the target of an implicit relationship
// in from the Glossary Document (§11.3.8) part.
const QString stylesPathAndFile(relationships->targetForType(documentPath, documentFile,
QLatin1String(MSOOXML::Schemas::officeDocument::relationships) + "/styles"));
DocxXmlStylesReader stylesReader(writers);
......@@ -238,6 +245,7 @@ KoFilter::ConversionStatus DocxImport::parseParts(KoOdfWriters *writers, MSOOXML
numberingPathAndFile, &numberingReader, writers, errorMessage, &numberingContext) )
}
mainContext.m_bulletStyles = numberingContext.m_bulletStyles;
mainContext.m_abstractNumIDs = numberingContext.m_abstractNumIDs;
reportProgress(30);
......@@ -254,6 +262,7 @@ KoFilter::ConversionStatus DocxImport::parseParts(KoOdfWriters *writers, MSOOXML
context.m_tableStyles = mainContext.m_tableStyles;
context.m_bulletStyles = mainContext.m_bulletStyles;
context.m_namedDefaultStyles = mainContext.m_namedDefaultStyles;
context.m_abstractNumIDs = mainContext.m_abstractNumIDs;
RETURN_IF_ERROR( loadAndParseDocumentFromFileIfExists(
footnotePathAndFile, &footnoteReader, writers, errorMessage, &context) )
......@@ -271,6 +280,7 @@ KoFilter::ConversionStatus DocxImport::parseParts(KoOdfWriters *writers, MSOOXML
DocxXmlDocumentReaderContext context(*this, commentPath, commentFile, *relationships, &themes);
context.m_tableStyles = mainContext.m_tableStyles;
context.m_bulletStyles = mainContext.m_bulletStyles;
//TODO: m_abstractNumIDs and m_namedDefaultStyles might be needed
RETURN_IF_ERROR( loadAndParseDocumentFromFileIfExists(
commentPathAndFile, &commentReader, writers, errorMessage, &context) )
......@@ -290,6 +300,7 @@ KoFilter::ConversionStatus DocxImport::parseParts(KoOdfWriters *writers, MSOOXML
context.m_tableStyles = mainContext.m_tableStyles;
context.m_bulletStyles = mainContext.m_bulletStyles;
context.m_namedDefaultStyles = mainContext.m_namedDefaultStyles;
context.m_abstractNumIDs = mainContext.m_abstractNumIDs;
RETURN_IF_ERROR( loadAndParseDocumentFromFileIfExists(
endnotePathAndFile, &endnoteReader, writers, errorMessage, &context) )
......
......@@ -153,7 +153,8 @@ void DocxXmlDocumentReader::init()
m_createSectionToNext = false;
m_currentVMLProperties.insideGroup = false;
m_outputFrames = true;
m_previousNumIdUsed = "";
m_currentNumId = "";
m_prevListLevel = 0;
}
KoFilter::ConversionStatus DocxXmlDocumentReader::read(MSOOXML::MsooXmlReaderContext* context)
......@@ -1779,7 +1780,7 @@ KoFilter::ConversionStatus DocxXmlDocumentReader::read_sdtContent()
{
READ_PROLOGUE
// This is not properly supported at all atm. todo: fix
// FIXME: This is not properly supported at all atm.
while (!atEnd()) {
readNext();
......@@ -1917,7 +1918,6 @@ KoFilter::ConversionStatus DocxXmlDocumentReader::read_p()
READ_PROLOGUE
m_paragraphStyleNameWritten = false;
m_currentStyleName.clear();
m_currentListStyleName.clear();
m_listFound = false;
m_closeHyperlink = false;
......@@ -2024,17 +2024,55 @@ KoFilter::ConversionStatus DocxXmlDocumentReader::read_p()
// In ooxml it seems that nothing should be created if sectPr was present
if (!m_createSectionToNext) {
if (m_listFound) {
m_currentListStyle = KoGenStyle(KoGenStyle::ListAutoStyle);
if (m_moveToStylesXml) {
m_currentTextStyle.setAutoStyleInStylesDotXml(true);
m_currentBulletProperties = m_currentBulletList.at(m_currentListLevel);
// update automatic numbering info
if (m_currentBulletProperties.m_type == MSOOXML::Utils::ParagraphBulletProperties::NumberType) {
QString numId;
bool listOpen = false;
if (m_continueListNum.contains(m_currentNumId)) {
listOpen = true;
numId = m_currentNumId;
}
else if (!m_currentBulletProperties.startOverride()) {
// TODO: The most recent one is required to check
// against the correct prevListLevel.
//
// Check if any of the lists that inherit numbering
// from the abstract numbering definition was opened.
QStringList numIDs = m_context->m_abstractNumIDs.keys(m_context->m_abstractNumIDs[m_currentNumId]);
QStringList::const_iterator i;
for (i = numIDs.constBegin(); i != numIDs.constEnd(); ++i) {
if (m_continueListNum.contains(*i)) {
listOpen = true;
numId = *i;
break;
}
}
}
if (listOpen) {
if (m_currentListLevel <= m_continueListNum[numId].first) {
m_continueListNum[m_currentNumId].second = true;
} else {
m_continueListNum[m_currentNumId].second = false;
}
}
}
if (m_usedListStyles.value(m_previousNumIdUsed).isEmpty()) {
QString listStyleName;
if (!m_usedListStyles.contains(m_currentNumId)) {
m_currentListStyle = KoGenStyle(KoGenStyle::ListAutoStyle);
if (m_moveToStylesXml) {
m_currentListStyle.setAutoStyleInStylesDotXml(true);
}
MSOOXML::Utils::ParagraphBulletProperties* bulletProps = 0;
int index = 0;
while (index < m_currentBulletList.size()) {
m_currentBulletProperties = m_currentBulletList.at(index);
bulletProps = const_cast<MSOOXML::Utils::ParagraphBulletProperties*>(&m_currentBulletList.at(index));
if (m_currentBulletProperties.m_type == MSOOXML::Utils::ParagraphBulletProperties::PictureType) {
if (bulletProps->m_type == MSOOXML::Utils::ParagraphBulletProperties::PictureType) {
// The reason why we are inserting bullets only
// here, is that we have to check for the
// actual text size before we can determine how
......@@ -2061,26 +2099,53 @@ KoFilter::ConversionStatus DocxXmlDocumentReader::read_p()
}
m_currentListStyle.addChildElement("list-style-properties",
m_currentBulletProperties.convertToListProperties(*mainStyles, MSOOXML::Utils::DocxFilter));
bulletProps->convertToListProperties(*mainStyles, MSOOXML::Utils::DocxFilter));
++index;
}
m_currentListStyleName = mainStyles->insert(m_currentListStyle, QString(), KoGenStyles::AllowDuplicates);
m_usedListStyles[m_previousNumIdUsed] = m_currentListStyleName;
// Stopped using the KoGenStyles::AllowDuplicates
// parameter, because there was a lot of duplicity. We
// should properly inherit from list styles in
// styles.xml representing abstract numbering
// definitions in OOXML.
listStyleName = mainStyles->insert(m_currentListStyle, QString());
Q_ASSERT(!listStyleName.isEmpty());
m_usedListStyles[m_currentNumId] = listStyleName;
}
else {
m_currentListStyleName = m_usedListStyles.value(m_previousNumIdUsed);
listStyleName = m_usedListStyles.value(m_currentNumId);
Q_ASSERT(!listStyleName.isEmpty());
}
// Start a new list
body->startElement("text:list");
if (!m_currentListStyleName.isEmpty()) {
body->addAttribute("text:style-name", m_currentListStyleName);
body->addAttribute("text:style-name", listStyleName);
// continue numbering if applicable
if (m_currentBulletProperties.m_type == MSOOXML::Utils::ParagraphBulletProperties::NumberType) {
if (m_continueListNum.contains(m_currentNumId)) {
if (m_continueListNum[m_currentNumId].second) {
body->addAttribute("text:continue-numbering", "true");
}
}
}
body->startElement("text:list-item");
for (int i = 0; i < m_currentListLevel; ++i) {
body->startElement("text:list");
body->startElement("text:list-item");
}
// restart numbering if applicable
if (m_currentBulletProperties.m_type == MSOOXML::Utils::ParagraphBulletProperties::NumberType) {
if (!m_continueListNum.contains(m_currentNumId) ||
(m_continueListNum.contains(m_currentNumId) &&
!m_continueListNum[m_currentNumId].second))
{
body->addAttribute("text:start-value", m_currentBulletProperties.startValue());
}
m_continueListNum[m_currentNumId] = qMakePair(m_currentListLevel, false);
}
m_currentParagraphStyle.addAttribute("style:list-style-name", listStyleName);
}
body->startElement("text:p", false);
if (m_currentStyleName.isEmpty()) {
QString currentParagraphStyleName;
......@@ -2108,12 +2173,10 @@ KoFilter::ConversionStatus DocxXmlDocumentReader::read_p()
(void)textPBuf.releaseWriter();
body->endElement(); //text:p
if (m_listFound) {
for (int i = 0; i < m_currentListLevel; ++i) {
for (int i = 0; i <= m_currentListLevel; ++i) {
body->endElement(); //text:list-item
body->endElement(); //text:list
}
body->endElement(); //text:list-item
body->endElement(); //text:list
}
kDebug() << "/text:p";
}
......@@ -2650,10 +2713,6 @@ KoFilter::ConversionStatus DocxXmlDocumentReader::read_pPr()
}
}
if (m_listFound) {
m_currentParagraphStyle.addAttribute("style:list-style-name", m_currentListStyleName);
}
READ_EPILOGUE
}
......@@ -2901,14 +2960,17 @@ KoFilter::ConversionStatus DocxXmlDocumentReader::read_ilvl()
#define CURRENT_EL numId
//! numPr handler (Numbering Definition Instance Reference)
/*!
This element specifies that the current paragraph references a numbering definition instance in the current document.
The presence of this element specifies that the paragraph will inherit the properties specified by the numbering
definition in the num element (§2.9.16) at the level specified by the level specified in the lvl element (§2.9.7)
and shall have an associated number positioned before the beginning of the text flow in this paragraph. When
this element appears as part of the paragraph formatting for a paragraph style, then any numbering level
defined using the ilvl element shall be ignored, and the pStyle element (§2.9.25) on the associated abstract
numbering definition shall be used instead.
This element specifies that the current paragraph references a numbering
definition instance in the current document.
The presence of this element specifies that the paragraph will inherit the
properties specified by the numbering definition in the num element (§2.9.16)
at the level specified by the level specified in the lvl element (§2.9.7) and
shall have an associated number positioned before the beginning of the text
flow in this paragraph. When this element appears as part of the paragraph
formatting for a paragraph style, then any numbering level defined using the
ilvl element shall be ignored, and the pStyle element (§2.9.25) on the
associated abstract numbering definition shall be used instead.
Parent elements:
- [done] numPr (§17.3.1.19)
......@@ -2924,13 +2986,14 @@ KoFilter::ConversionStatus DocxXmlDocumentReader::read_numId()
TRY_READ_ATTR(val)
// In docx, this value defines a predetermined style from numbering xml,
// The styles from numbering have to be given some name, NumStyle has been chosen here
// The styles from numbering have to be given some name, NumStyle has been
// chosen here
if (!val.isEmpty()) {
if (val == "0") {
m_listFound = false; // spec says that this means deleted list
} else {
m_currentBulletList = m_context->m_bulletStyles[val];
m_previousNumIdUsed = val;
m_currentNumId = val;
}
}
......@@ -4824,6 +4887,9 @@ KoFilter::ConversionStatus DocxXmlDocumentReader::read_tbl()
{
READ_PROLOGUE
// save current state of lists processing
saveState();
KoTable table;
m_table = &table;
......@@ -4913,6 +4979,9 @@ KoFilter::ConversionStatus DocxXmlDocumentReader::read_tbl()
body = buffer.releaseWriter();
}
// restore previous state of lists processing
restoreState();
READ_EPILOGUE
}
......@@ -6481,6 +6550,29 @@ KoFilter::ConversionStatus DocxXmlDocumentReader::read_r_m()
READ_EPILOGUE
}
// ************************************************
// State
// ************************************************
void DocxXmlDocumentReader::saveState()
{
DocumentReaderState state(m_usedListStyles, m_continueListNum);
m_statesBkp.push(state);
m_usedListStyles.clear();
m_continueListNum.clear();
}
void DocxXmlDocumentReader::restoreState()
{
if (m_statesBkp.isEmpty()) {
kWarning() << "Error: DocumentReaderState stack is corrupt!";
return;
}
DocumentReaderState s = m_statesBkp.pop();
m_usedListStyles = s.usedListStyles;
m_continueListNum = s.continueListNum;
}
#define DRAWINGML_NS "a"
#define DRAWINGML_PIC_NS "pic" // DrawingML/Picture
......
......@@ -348,20 +348,40 @@ private:
MSOOXML::DrawingTableStyleConverterProperties::Roles m_activeRoles;
bool m_wasCaption; // bookkeeping to ensure next para is suppressed if a caption is encountered
bool m_closeHyperlink; // should read_r close hyperlink
bool m_listFound; // was there numPr element in ppr
QString m_currentListStyleName;
//! The list identifier of the previous list, used to restart numbering if the current ID is different
QString m_previousNumIdUsed;
//! Map of list styles encountered so far, we can used the same list style if we have used it before
// instead of creating a new one.
QString m_currentNumId;
//! Map of list styles encountered so far, reuse already created list style.
QMap<QString, QString> m_usedListStyles;
//TODO: Merge with m_continueListNumbering defined in MsooXmlCommenReaderDrawingMLMethods.h
QMap<QString, QPair<int, bool> > m_continueListNum;
QMap<QString, QString> m_headers;
QMap<QString, QString> m_footers;
// ************************************************
// State
// ************************************************
//TODO: Add all the other attributes on demand.
struct DocumentReaderState {
explicit DocumentReaderState(const QMap<QString, QString> &usedListStyles,
const QMap <QString, QPair<int, bool> > &continueListNum)
: usedListStyles(usedListStyles),
continueListNum(continueListNum) {}
DocumentReaderState() {}
QMap<QString, QString> usedListStyles;
QMap<QString, QPair<int, bool> > continueListNum;
};
void saveState();
void restoreState();
QStack<DocumentReaderState> m_statesBkp;
#include <MsooXmlCommonReaderMethods.h>
#include <MsooXmlCommonReaderDrawingMLMethods.h>
......@@ -398,6 +418,10 @@ public:
// explicitly declare a style. The object type (family) is the key.
QMap<QString, QString> m_namedDefaultStyles;
// The map contains abstractNumId of the abstract numbering definition that
// is inherited by a numbering definition instance identified by numId (key).
QMap<QString, QString> m_abstractNumIDs;
private:
};
......
......@@ -161,6 +161,13 @@ KoFilter::ConversionStatus DocxXmlNumberingReader::read_lvlOverride()
{
READ_PROLOGUE
const QXmlStreamAttributes attrs(attributes());
TRY_READ_ATTR(ilvl)
int level = 0;
STRING_TO_INT(ilvl, level, QString("w:lvlOverride"));
level++;
while (!atEnd()) {
readNext();
BREAK_IF_END_OF(CURRENT_EL)
......@@ -178,6 +185,22 @@ KoFilter::ConversionStatus DocxXmlNumberingReader::read_lvlOverride()
++index;
}
}
else if (name() == "startOverride") {
int index = 0;
while (index < m_currentBulletList.size()) {
if (m_currentBulletList.at(index).m_level == level)
{
const QXmlStreamAttributes attrs2(attributes());
QString val( attrs2.value(QUALIFIED_NAME(val)).toString() );
if (!val.isEmpty()) {
m_currentBulletList[index].setStartValue(val);
}
m_currentBulletList[index].setStartOverride(true);
break;
}
++index;
}
}
SKIP_UNKNOWN
}
}
......@@ -452,6 +475,7 @@ KoFilter::ConversionStatus DocxXmlNumberingReader::read_num()
if (name() == "abstractNumId") {
TRY_READ(abstractNumId)
m_currentBulletList = m_abstractListStyles[m_currentAbstractId];
m_context->m_abstractNumIDs[numId] = m_currentAbstractId;
}
// lvlOverride may modify the bulletlist which we get above
ELSE_TRY_READ_IF(lvlOverride)
......
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