/***************************************************************************
 *   crengine-ng                                                           *
 *   Copyright (C) 2019,2020 Konstantin Potapov <pkbo@users.sourceforge.net>
 *   Copyright (C) 2020 poire-z <poire-z@users.noreply.github.com>         *
 *   Copyright (C) 2020 Aleksey Chernov <valexlin@gmail.com>               *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU General Public License           *
 *   as published by the Free Software Foundation; either version 2        *
 *   of the License, or (at your option) any later version.                *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the Free Software           *
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,            *
 *   MA 02110-1301, USA.                                                   *
 ***************************************************************************/

#include "docxfmt.h"

#include <ldomdocument.h>
#include <lvdocviewcallback.h>
#include <fb2def.h>
#include <lvstreamutils.h>
#include <crlog.h>

#include "lvopc.h"
#include "lvxml/lvhtmlparser.h"
#include "lvtinydom/ldomdocumentwriter.h"
#include "odxutil.h"

#define DOCX_TAG_NAME(itm) docx_el_##itm##_name
#define DOCX_TAG_ID(itm)   docx_el_##itm
#define DOCX_TAG_CHILD(itm) \
    { DOCX_TAG_ID(itm), DOCX_TAG_NAME(itm) }
#define DOCX_LAST_ITEM \
    { -1, NULL }

static const lChar32* const docx_DocumentContentType = U"application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml";
static const lChar32* const docx_NumberingContentType = U"application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml";
static const lChar32* const docx_StylesContentType = U"application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml";
static const lChar32* const docx_ImageRelationship = U"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image";
static const lChar32* const docx_HyperlinkRelationship = U"http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink";
static const lChar32* const docx_FootNotesRelationShip = U"http://schemas.openxmlformats.org/officeDocument/2006/relationships/footnotes";
static const lChar32* const docx_EndNotesRelationShip = U"http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes";

enum
{
#define DOCX_NUM_FMT(itm)
#define DOCX_TAG(itm) DOCX_TAG_ID(itm),
    docx_el_NULL = 0,
#include "docxdtd.inc"
    docx_el_MAX_ID
};

#define DOCX_NUM_FMT(itm)
#define DOCX_TAG(itm) static const lChar32* const DOCX_TAG_NAME(itm) = U## #itm;
#include "docxdtd.inc"

const struct item_def_t styles_elements[] = {
    DOCX_TAG_CHILD(styles),
    DOCX_TAG_CHILD(style),
    DOCX_TAG_CHILD(docDefaults),
    DOCX_LAST_ITEM
};

enum docx_multilevel_type
{
    docx_hybrid_multilevel,
    docx_multilevel,
    docx_singlelevel
};

#define DOCX_NUM_FMT(itm) docx_numFormat_##itm,
#define DOCX_TAG(itm)
enum docx_numFormat_type
{
#include "docxdtd.inc"
    docx_numFormat_MAX_ID
};

enum docx_LevelSuffix_type
{
    docx_level_suffix_tab,
    docx_level_suffix_space,
    docx_level_suffix_nothing
};

const struct item_def_t style_elements[] = {
    DOCX_TAG_CHILD(name),
    DOCX_TAG_CHILD(basedOn),
    DOCX_TAG_CHILD(pPr),
    DOCX_TAG_CHILD(rPr),
    DOCX_TAG_CHILD(tblPr),
    DOCX_TAG_CHILD(trPr),
    DOCX_TAG_CHILD(tcPr),
    DOCX_LAST_ITEM
};

const struct item_def_t docDefaults_elements[] = {
    DOCX_TAG_CHILD(pPr),
    DOCX_TAG_CHILD(pPrDefault),
    DOCX_TAG_CHILD(rPrDefault),
    DOCX_TAG_CHILD(rPr),
    DOCX_LAST_ITEM
};

const struct item_def_t rPr_elements[] = {
    DOCX_TAG_CHILD(b),
    DOCX_TAG_CHILD(i),
    DOCX_TAG_CHILD(color),
    DOCX_TAG_CHILD(jc),
    DOCX_TAG_CHILD(lang),
    DOCX_TAG_CHILD(rFonts),
    DOCX_TAG_CHILD(rStyle),
    DOCX_TAG_CHILD(u),
    DOCX_TAG_CHILD(vertAlign),
    DOCX_TAG_CHILD(sz),
    DOCX_TAG_CHILD(vanish),
    DOCX_TAG_CHILD(strike),
    DOCX_LAST_ITEM
};

const struct item_def_t numPr_elements[] = {
    DOCX_TAG_CHILD(ilvl),
    DOCX_TAG_CHILD(numId),
    DOCX_LAST_ITEM
};

const struct item_def_t pPr_elements[] = {
    DOCX_TAG_CHILD(pageBreakBefore),
    DOCX_TAG_CHILD(keepNext),
    DOCX_TAG_CHILD(pStyle),
    DOCX_TAG_CHILD(jc),
    DOCX_TAG_CHILD(spacing),
    DOCX_TAG_CHILD(numPr),
    DOCX_TAG_CHILD(textAlignment),
    DOCX_TAG_CHILD(ind),
    DOCX_TAG_CHILD(suppressAutoHyphens),
    //    DOCX_TAG_CHILD(rPr), don't care about Paragraph merker formatting
    DOCX_TAG_CHILD(outlineLvl),
    DOCX_LAST_ITEM
};

const struct item_def_t p_elements[] = {
    DOCX_TAG_CHILD(r),
    DOCX_TAG_CHILD(pPr),
    DOCX_TAG_CHILD(hyperlink),
    DOCX_TAG_CHILD(bookmarkStart),
    DOCX_LAST_ITEM
};

const struct item_def_t r_elements[] = {
    DOCX_TAG_CHILD(br),
    DOCX_TAG_CHILD(t),
    DOCX_TAG_CHILD(tab),
    DOCX_TAG_CHILD(drawing),
    DOCX_TAG_CHILD(rPr),
    DOCX_TAG_CHILD(footnoteReference),
    DOCX_TAG_CHILD(footnoteRef),
    DOCX_TAG_CHILD(endnoteReference),
    DOCX_TAG_CHILD(endnoteRef),
    DOCX_TAG_CHILD(fldChar),
    DOCX_TAG_CHILD(instrText),
    DOCX_LAST_ITEM
};

const struct item_def_t hyperlink_elements[] = {
    DOCX_TAG_CHILD(r),
    DOCX_LAST_ITEM
};

const struct item_def_t drawing_elements[] = {
    DOCX_TAG_CHILD(blipFill),
    DOCX_TAG_CHILD(blip),
    DOCX_TAG_CHILD(graphic),
    DOCX_TAG_CHILD(graphicData),
    DOCX_TAG_CHILD(inline),
    DOCX_TAG_CHILD(anchor),
    DOCX_TAG_CHILD(pic),
    DOCX_LAST_ITEM
};

const struct item_def_t tbl_elements[] = {
    DOCX_TAG_CHILD(bookmarkStart),
    DOCX_TAG_CHILD(tblPr),
    DOCX_TAG_CHILD(tblGrid),
    DOCX_TAG_CHILD(tcPr),
    DOCX_TAG_CHILD(gridCol),
    DOCX_TAG_CHILD(gridSpan),
    DOCX_TAG_CHILD(tr),
    DOCX_TAG_CHILD(tc),
    DOCX_TAG_CHILD(p),
    DOCX_TAG_CHILD(vMerge),
    DOCX_LAST_ITEM
};

const struct item_def_t lvl_elements[] = {
    DOCX_TAG_CHILD(isLgl),
    DOCX_TAG_CHILD(lvlJc),
    DOCX_TAG_CHILD(lvlRestart),
    DOCX_TAG_CHILD(lvlText),
    DOCX_TAG_CHILD(numFmt),
    DOCX_TAG_CHILD(pPr),
    DOCX_TAG_CHILD(pStyle),
    DOCX_TAG_CHILD(rPr),
    DOCX_TAG_CHILD(start),
    DOCX_TAG_CHILD(suff),
    DOCX_LAST_ITEM
};

const struct item_def_t numbering_elements[] = {
    DOCX_TAG_CHILD(numbering),
    DOCX_TAG_CHILD(abstractNum),
    DOCX_TAG_CHILD(num),
    DOCX_LAST_ITEM
};

const struct item_def_t abstractNum_elements[] = {
    DOCX_TAG_CHILD(lvl),
    DOCX_LAST_ITEM
};

const struct item_def_t num_elements[] = {
    DOCX_TAG_CHILD(abstractNumId),
    DOCX_TAG_CHILD(lvlOverride),
    DOCX_LAST_ITEM
};

const struct item_def_t document_elements[] = {
    DOCX_TAG_CHILD(document),
    DOCX_TAG_CHILD(body),
    DOCX_TAG_CHILD(p),
    DOCX_TAG_CHILD(tbl),
    DOCX_LAST_ITEM
};

const struct item_def_t footnotes_elements[] = {
    DOCX_TAG_CHILD(footnotes),
    DOCX_TAG_CHILD(footnote),
    DOCX_TAG_CHILD(endnotes),
    DOCX_TAG_CHILD(endnote),
    DOCX_TAG_CHILD(p),
    DOCX_LAST_ITEM
};

const struct item_def_t no_elements[] = {
    DOCX_LAST_ITEM
};

const struct item_def_t jc_attr_values[] = {
    { css_ta_left, U"left" },
    { css_ta_right, U"right" },
    { css_ta_center, U"center" },
    { css_ta_justify, U"both" },
    DOCX_LAST_ITEM
};

const struct item_def_t vertAlign_attr_values[] = {
    { css_va_baseline, U"baseline" },
    { css_va_super, U"superscript" },
    { css_va_sub, U"subscript" },
    DOCX_LAST_ITEM
};

const struct item_def_t textAlignment_attr_values[] = {
    { css_va_inherit, U"auto" },
    { css_va_baseline, U"baseline" },
    { css_va_bottom, U"bottom" },
    { css_va_middle, U"center" },
    { css_va_top, U"top" },
    DOCX_LAST_ITEM
};

const struct item_def_t lineRule_attr_values[] = {
    { odx_lineRule_atLeast, U"atLeast" },
    { odx_lineRule_auto, U"auto" },
    { odx_lineRule_exact, U"exact" },
    DOCX_LAST_ITEM
};

const struct item_def_t styleType_attr_values[] = {
    { odx_paragraph_style, U"paragraph" },
    { odx_character_style, U"character" },
    { odx_numbering_style, U"numbering" },
    { odx_table_style, U"table" },
    DOCX_LAST_ITEM
};

const struct item_def_t lvlSuff_attr_values[] = {
    { docx_level_suffix_tab, U"tab" },
    { docx_level_suffix_space, U"space" },
    { docx_level_suffix_nothing, U"nothing" },
    DOCX_LAST_ITEM
};

#define DOCX_TAG(itm)
#define DOCX_NUM_FMT(itm) { docx_numFormat_##itm, U## #itm },
const struct item_def_t numFmt_attr_values[] = {
#include "docxdtd.inc"
    DOCX_LAST_ITEM
};

bool DetectDocXFormat(LVStreamRef stream) {
    LVContainerRef m_arc = LVOpenArchieve(stream);
    if (m_arc.isNull())
        return false; // not a ZIP archive

    OpcPackage package(m_arc);

    return package.partExist(package.getContentPartName(docx_DocumentContentType));
}

class docxImportContext;

class docxNumLevel: public LVRefCounter
{
private:
    bool m_isLgl;
    css_text_align_t m_lvlJc;
    css_length_t m_ilvl;
    css_length_t m_lvlRestart;
    lString32 m_lvlText;
    bool m_lvlTextNull;
    docx_numFormat_type m_lvlNumFormat;
    odx_pPr m_pPr;
    odx_rPr m_rPr;
    lString32 m_pStyle;
    css_length_t m_lvlStart;
    docx_LevelSuffix_type m_suffix;
public:
    docxNumLevel();
    virtual ~docxNumLevel() { }
    void reset();
    ///properties
    inline bool isLgl() const {
        return m_isLgl;
    }
    inline void setLgl(bool value) {
        m_isLgl = value;
    }

    inline css_text_align_t getLevelAlign() const {
        return m_lvlJc;
    }
    inline void setLevelAlign(css_text_align_t value) {
        m_lvlJc = value;
    }
    inline css_length_t getLevel() const {
        return m_ilvl;
    }
    inline void setLevel(const css_length_t& value) {
        m_ilvl = value;
    }
    inline css_length_t getLevelRestart() const {
        return m_lvlRestart;
    }
    inline void setLevelRestart(const css_length_t& value) {
        m_lvlRestart = value;
    }
    inline lString32 getLevelText() const {
        return m_lvlText;
    }
    inline void setLevelText(const lString32 value) {
        m_lvlText = value;
    }
    inline bool getLevelTextNull() const {
        return m_lvlTextNull;
    }
    inline void setLevelTextNull(const bool value) {
        m_lvlTextNull = value;
    }
    inline docx_numFormat_type getNumberFormat() const {
        return m_lvlNumFormat;
    }
    inline void setNumberFormat(const docx_numFormat_type value) {
        m_lvlNumFormat = value;
    }
    inline lString32 getReferencedStyleId() const {
        return m_pStyle;
    }
    inline void setReferencedStyleId(const lString32 value) {
        m_pStyle = value;
    }
    inline css_length_t getLevelStart() const {
        return m_lvlStart;
    }
    inline void setLevelStart(const css_length_t& value) {
        m_lvlStart = value;
    }
    inline docx_LevelSuffix_type getLevelSuffix() const {
        return m_suffix;
    }
    inline void setLevelSuffix(const docx_LevelSuffix_type value) {
        m_suffix = value;
    }
    inline odx_rPr* get_rPr() {
        return &m_rPr;
    }
    inline odx_pPr* get_pPr() {
        return &m_pPr;
    }
    css_list_style_type_t getListType() const;
};

typedef LVFastRef<docxNumLevel> docxNumLevelRef;

class docxAbstractNum: public LVRefCounter
{
private:
    docx_multilevel_type m_multilevel;
    css_length_t m_abstractNumId;
    LVHashTable<lUInt32, docxNumLevelRef> m_levels;
public:
    docxAbstractNum();
    docxNumLevel* getLevel(int level);
    void addLevel(docxNumLevelRef docxLevel);
    void setId(int id) {
        m_abstractNumId.value = id;
        m_abstractNumId.type = css_val_in;
    }
    int getId() {
        return m_abstractNumId.value;
    }
    virtual ~docxAbstractNum() { }
    void reset();
};

typedef LVFastRef<docxAbstractNum> docxAbstractNumRef;

class docxNum: public LVRefCounter
{
private:
    css_length_t m_id;
    css_length_t m_abstractNumId;
    LVHashTable<lUInt32, docxNumLevelRef> m_overrides;
public:
    docxNum()
            : m_id(css_val_unspecified, 0)
            , m_abstractNumId(css_val_unspecified, 0)
            , m_overrides(10) {
    }
    const docxAbstractNumRef getBase(docxImportContext& context) const;
    void setId(int id) {
        m_id.value = id;
        m_id.type = css_val_in;
    }
    int getId() const {
        return m_id.value;
    }
    void setBaseId(int id) {
        m_abstractNumId.value = id;
        m_abstractNumId.type = css_val_in;
    }
    int getBaseId() const {
        return m_abstractNumId.value;
    }
    void overrideLevel(docxNumLevelRef docxLevel);
    docxNumLevel* getDocxLevel(docxImportContext& context, int level);
    bool isValid() const;
    void reset();
};

typedef LVFastRef<docxNum> docxNumRef;

class docxImportContext: public odx_ImportContext
{
private:
    LVHashTable<lUInt32, docxAbstractNumRef> m_abstractNumbers;
    LVHashTable<lUInt32, docxNumRef> m_Numbers;
    LVArray<css_list_style_type_t> m_ListLevels;
    OpcPartRef m_docPart;
    OpcPartRef m_relatedPart;
    OpcPackage* m_package;
public:
    docxImportContext(OpcPackage* package, ldomDocument* doc);
    virtual ~docxImportContext();
    void addNum(docxNumRef num);
    void addAbstractNum(docxAbstractNumRef abstractNum);
    docxNumRef getNum(lUInt32 id) {
        return m_Numbers.get(id);
    }
    docxAbstractNumRef getAbstractNum(lUInt32 id) {
        return m_abstractNumbers.get(id);
    }
    lString32 getImageTarget(lString32 id) {
        return getRelationTarget(docx_ImageRelationship, id);
    }
    lString32 getLinkTarget(lString32 id) {
        return getRelationTarget(docx_HyperlinkRelationship, id);
    }
    lString32 getRelationTarget(const lChar32* const relationType, lString32 id) {
        if (!m_relatedPart.isNull())
            return m_relatedPart->getRelatedPartName(relationType, id);
        return m_docPart->getRelatedPartName(relationType, id);
    }
    LVStreamRef openContentPart(const lChar32* const contentType);
    LVStreamRef openRelatedPart(const lChar32* const relationshipType);
    void closeRelatedPart();
    void openList(int level, int numid, ldomDocumentWriter* writer);
    void closeList(int level, ldomDocumentWriter* writer);
    inline int getListLevel() {
        return m_ListLevels.length();
    }
    inline bool isInList() {
        return m_ListLevels.length() != 0;
    }
    lString32 m_footNoteId;
    int m_footNoteCount;
    int m_endNoteCount;
    bool m_inField;
    ldomNode* m_linkNode;
    odx_Style* m_pStyle;
private:
    lString32 getListStyle(css_list_style_type_t listType);
};

class docx_ElementHandler: public xml_ElementHandler
{
protected:
    docxImportContext* m_importContext;
protected:
    static bool parse_OnOff_attribute(const lChar32* attrValue);
    void generateLink(const lChar32* target, const lChar32* type, const lChar32* text);
    docx_ElementHandler(docXMLreader* reader, ldomDocumentWriter* writer, docxImportContext* context,
                        int element, const struct item_def_t* children)
            : xml_ElementHandler(reader, writer, element, children)
            , m_importContext(context) {
    }
    virtual ~docx_ElementHandler() { }
};

class docx_rPrHandler: public docx_ElementHandler
{
private:
    odx_rPr* m_rPr;
public:
    docx_rPrHandler(docXMLreader* reader, ldomDocumentWriter* writer, docxImportContext* context)
            : docx_ElementHandler(reader, writer, context, docx_el_rPr, rPr_elements)
            , m_rPr(NULL) {
    }
    ldomNode* handleTagOpen(int tagId);
    void handleAttribute(const lChar32* attrname, const lChar32* attrvalue);
    void start(odx_rPr* rPr);
    void reset();
};

class docx_drawingHandler: public docx_ElementHandler
{
private:
    int m_level;
public:
    docx_drawingHandler(docXMLreader* reader, ldomDocumentWriter* writer, docxImportContext* context)
            : docx_ElementHandler(reader, writer, context, docx_el_drawing, drawing_elements)
            , m_level(0) {
    }
    ldomNode* handleTagOpen(int tagId);
    void handleAttribute(const lChar32* attrname, const lChar32* attrvalue);
    void handleTagClose(const lChar32* nsname, const lChar32* tagname);
    void reset() {
        m_level = 1;
    }
};

class docx_pHandler;
class docx_rHandler: public docx_ElementHandler
{
private:
    odx_rPr m_rPr;
    docx_pHandler* m_pHandler;
    docx_rPrHandler m_rPrHandler;
    lString32 m_footnoteId;
    lString32 m_instruction;
    docx_drawingHandler m_drawingHandler;
    bool m_content;
private:
    void handleInstruction(lString32& instruction, lString32 parameters);
public:
    docx_rHandler(docXMLreader* reader, ldomDocumentWriter* writer, docxImportContext* context, docx_pHandler* pHandler)
            : docx_ElementHandler(reader, writer, context, docx_el_r, r_elements)
            , m_pHandler(pHandler)
            , m_rPrHandler(reader, writer, context)
            , m_drawingHandler(reader, writer, context)
            , m_content(false) {
    }
    ldomNode* handleTagOpen(int tagId);
    void handleAttribute(const lChar32* attrname, const lChar32* attrvalue);
    void handleText(const lChar32* text, int len, lUInt32 flags);
    void handleTagClose(const lChar32* nsname, const lChar32* tagname);
    void reset();
};

class docx_pPrHandler: public docx_ElementHandler
{
private:
    odx_pPr* m_pPr;
public:
    docx_pPrHandler(docXMLreader* reader, ldomDocumentWriter* writer, docxImportContext* context)
            : docx_ElementHandler(reader, writer, context, docx_el_pPr, pPr_elements)
            , m_pPr(NULL) {
    }
    ldomNode* handleTagOpen(int tagId);
    void handleAttribute(const lChar32* attrname, const lChar32* attrvalue);
    void handleTagClose(const lChar32* nsname, const lChar32* tagname);
    void start(odx_pPr* pPr);
    void reset();
};

class docx_hyperlinkHandler: public docx_ElementHandler
{
    docx_rHandler m_rHandler;
    lString32 m_target;
    int m_runCount;
public:
    docx_hyperlinkHandler(docXMLreader* reader, ldomDocumentWriter* writer, docxImportContext* context, docx_pHandler* pHandler)
            : docx_ElementHandler(reader, writer, context, docx_el_hyperlink, hyperlink_elements)
            , m_rHandler(reader, writer, context, pHandler)
            , m_runCount(0) {
    }
    ldomNode* handleTagOpen(int tagId);
    void handleAttribute(const lChar32* attrname, const lChar32* attrvalue);
    void handleTagClose(const lChar32* nsname, const lChar32* tagname);
    void reset() {
        m_target.clear();
        m_rHandler.reset();
        m_runCount = 0;
    }
};

class docx_documentHandler;

class docx_pHandler: public docx_ElementHandler, public odx_styleTagsHandler
{
private:
    docx_pPrHandler m_pPrHandler;
    odx_pPr m_pPr;
    docx_rHandler m_rHandler;
    odx_titleHandler* m_titleHandler;
    docx_hyperlinkHandler m_hyperlinkHandler;
    int m_runCount;
    bool m_inTitle;
public:
    docx_pHandler(docXMLreader* reader, ldomDocumentWriter* writer, docxImportContext* context, odx_titleHandler* p_documentHandler)
            : docx_ElementHandler(reader, writer, context, docx_el_p, p_elements)
            , m_pPrHandler(reader, writer, context)
            , m_rHandler(reader, writer, context, this)
            , m_titleHandler(p_documentHandler)
            , m_hyperlinkHandler(reader, writer, context, this)
            , m_runCount(0)
            , m_inTitle(false) {
    }
    ldomNode* handleTagOpen(int tagId);
    void handleAttribute(const lChar32* attrname, const lChar32* attrvalue);
    void handleTagClose(const lChar32* nsname, const lChar32* tagname);
    void reset();
};

struct docx_row_span_info
{
    ldomNode* column;
    int rows;
    docx_row_span_info()
            : column(NULL)
            , rows(1) { }
    docx_row_span_info(ldomNode* column)
            : column(column)
            , rows(1) { }
};

class docx_tblHandler: public docx_ElementHandler
{
private:
    LVArray<int> m_levels;
    LVArray<docx_row_span_info> m_rowSpaninfo;
    int m_rowCount;
    odx_titleHandler m_titleHandler;
    docx_pHandler m_pHandler;
    xml_SkipElementHandler m_skipHandler;
    xml_ElementHandler* m_pHandler_;
    int m_colSpan;
    int m_column;
    int m_columnCount;
    enum vMergeState_tyep
    {
        VMERGE_NONE,
        VMERGE_RESET,
        VMERGE_CONTINUE
    };
    int m_vMergeState;
    void endRowSpan(int column);
public:
    docx_tblHandler(docXMLreader* reader, ldomDocumentWriter* writer, docxImportContext* context, odx_titleHandler* titleHandler)
            : docx_ElementHandler(reader, writer, context, docx_el_tbl, tbl_elements)
            , m_rowCount(0)
            , m_titleHandler(writer, titleHandler->useClassForTitle())
            , m_pHandler(reader, writer, context, &m_titleHandler)
            , m_skipHandler(reader, writer, docx_el_p)
            , m_pHandler_(NULL)
            , m_colSpan(1)
            , m_column(0)
            , m_columnCount(0)
            , m_vMergeState(VMERGE_NONE) {
    }
    ldomNode* handleTagOpen(int tagId);
    void handleAttribute(const lChar32* attrname, const lChar32* attrvalue);
    void handleTagClose(const lChar32* nsname, const lChar32* tagname);
    void reset();
};

class docx_footnotesHandler: public docx_ElementHandler
{
private:
    bool m_normal;
    int m_pCount;
    docx_pHandler paragraphHandler;
private:
    bool isEndNote() {
        return m_element == docx_el_endnotes;
    }
public:
    docx_footnotesHandler(docXMLreader* reader, ldomDocumentWriter* writer, docxImportContext* context, int element)
            : docx_ElementHandler(reader, writer, context, element, footnotes_elements)
            , m_normal()
            , m_pCount()
            , paragraphHandler(reader, writer, context, NULL) {
    }
    ldomNode* handleTagOpen(int tagId);
    void handleAttribute(const lChar32* attrname, const lChar32* attrvalue);
    void handleTagClose(const lChar32* nsname, const lChar32* tagname);
};

class docx_documentHandler: public docx_ElementHandler
{
private:
    docx_pHandler paragraphHandler;
    docx_tblHandler m_tableHandler;
protected:
    odx_titleHandler* m_titleHandler;
public:
    docx_documentHandler(docXMLreader* reader, ldomDocumentWriter* writer, docxImportContext* context, odx_titleHandler* titleHandler)
            : docx_ElementHandler(reader, writer, context, docx_el_document, document_elements)
            , paragraphHandler(reader, writer, context, titleHandler)
            , m_tableHandler(reader, writer, context, titleHandler)
            , m_titleHandler(titleHandler) {
    }
    ldomNode* handleTagOpen(int tagId);
    void handleAttribute(const lChar32* nsname, const lChar32* attrname, const lChar32* attrvalue);
    void handleTagClose(const lChar32* nsname, const lChar32* tagname);
};

class docx_styleHandler: public docx_ElementHandler
{
private:
    docx_pPrHandler m_pPrHandler;
    docx_rPrHandler m_rPrHandler;
    odx_StyleRef m_styleRef;
    odx_Style* m_style;
public:
    /// constructor
    docx_styleHandler(docXMLreader* reader, ldomDocumentWriter* writer, docxImportContext* context)
            : docx_ElementHandler(reader, writer, context, docx_el_style, style_elements)
            , m_pPrHandler(reader, writer, context)
            , m_rPrHandler(reader, writer, context) {
    }
    ldomNode* handleTagOpen(int tagId);
    void handleAttribute(const lChar32* attrname, const lChar32* attrvalue);
    void handleTagClose(const lChar32* nsname, const lChar32* tagname);
    void start();
};

class docx_stylesHandler: public docx_ElementHandler
{
private:
    docx_styleHandler m_styleHandler;
    docx_pPrHandler m_pPrHandler;
    docx_rPrHandler m_rPrHandler;
public:
    /// constructor
    docx_stylesHandler(docXMLreader* reader, ldomDocumentWriter* writer, docxImportContext* context)
            : docx_ElementHandler(reader, writer, context, docx_el_styles, styles_elements)
            , m_styleHandler(reader, writer, context)
            , m_pPrHandler(reader, writer, context)
            , m_rPrHandler(reader, writer, context) {
    }
    /// destructor
    ldomNode* handleTagOpen(int tagId);
    void handleTagClose(const lChar32* nsname, const lChar32* tagname);
    void reset();
};

class docx_lvlHandler: public docx_ElementHandler
{
private:
    docxNumLevel* m_lvl;
    docx_pPrHandler m_pPrHandler;
    docx_rPrHandler m_rPrHandler;
public:
    /// constructor
    docx_lvlHandler(docXMLreader* reader, ldomDocumentWriter* writer, docxImportContext* context)
            : docx_ElementHandler(reader, writer, context, docx_el_lvl, lvl_elements)
            , m_pPrHandler(reader, writer, context)
            , m_rPrHandler(reader, writer, context) {
    }
    void start(docxNumLevel* level) {
        m_lvl = level;
        docx_ElementHandler::start();
    }
    void handleAttribute(const lChar32* attrname, const lChar32* attrvalue);
    ldomNode* handleTagOpen(int tagId);
    void reset();
};

class docx_numHandler: public docx_ElementHandler
{
    docx_lvlHandler m_lvlHandler;
    docxNumRef m_numRef;
    docxNumLevelRef m_levelRef;
public:
    docx_numHandler(docXMLreader* reader, ldomDocumentWriter* writer, docxImportContext* context)
            : docx_ElementHandler(reader, writer, context, docx_el_num, num_elements)
            , m_lvlHandler(reader, writer, context) {
    }
    void handleAttribute(const lChar32* attrname, const lChar32* attrvalue);
    ldomNode* handleTagOpen(int tagId);
    void handleTagClose(const lChar32* nsname, const lChar32* tagname);
    void start();
};

class docx_abstractNumHandler: public docx_ElementHandler
{
    docx_lvlHandler m_lvlHandler;
    docxNumLevelRef m_levelRef;
    docxAbstractNumRef m_abstractNumRef;
public:
    docx_abstractNumHandler(docXMLreader* reader, ldomDocumentWriter* writer, docxImportContext* context)
            : docx_ElementHandler(reader, writer, context, docx_el_abstractNum, abstractNum_elements)
            , m_lvlHandler(reader, writer, context) {
    }
    void handleAttribute(const lChar32* attrname, const lChar32* attrvalue);
    ldomNode* handleTagOpen(int tagId);
    void handleTagClose(const lChar32* nsname, const lChar32* tagname);
    void start();
};

class docx_numberingHandler: public docx_ElementHandler
{
private:
    docx_numHandler m_numHandler;
    docx_abstractNumHandler m_abstractNumHandler;
public:
    /// constructor
    docx_numberingHandler(docXMLreader* reader, ldomDocumentWriter* writer, docxImportContext* context)
            : docx_ElementHandler(reader, writer, context, docx_el_numbering, numbering_elements)
            , m_numHandler(reader, writer, context)
            , m_abstractNumHandler(reader, writer, context) {
    }
    ldomNode* handleTagOpen(int tagId);
    void handleTagClose(const lChar32* nsname, const lChar32* tagname);
};

docxNumLevel::docxNumLevel()
        : m_isLgl(false)
        , m_lvlJc(css_ta_inherit)
        , m_ilvl(css_val_unspecified, 0)
        , m_lvlRestart(css_val_unspecified, 0)
        , m_lvlTextNull(false)
        , m_lvlNumFormat(docx_numFormat_ordinal)
        , m_lvlStart(css_val_unspecified, 0)
        , m_suffix(docx_level_suffix_space) {
}

void docxNumLevel::reset() {
    m_isLgl = false;
    m_lvlJc = css_ta_inherit;
    m_ilvl.type = css_val_unspecified;
    m_lvlRestart.type = css_val_unspecified;
    m_lvlText.clear();
    m_lvlTextNull = false;
    m_lvlNumFormat = docx_numFormat_ordinal;
    m_pPr.reset();
    m_rPr.reset();
    m_pStyle.clear();
    m_lvlStart.type = css_val_unspecified;
    m_suffix = docx_level_suffix_space;
}

css_list_style_type_t docxNumLevel::getListType() const {
    if (m_isLgl)
        return css_lst_decimal;
    switch (m_lvlNumFormat) {
        case docx_numFormat_lowerLetter:
            return css_lst_lower_alpha;
        case docx_numFormat_lowerRoman:
            return css_lst_lower_roman;
        case docx_numFormat_upperLetter:
            return css_lst_upper_alpha;
        case docx_numFormat_upperRoman:
            return css_lst_upper_roman;
        case docx_numFormat_bullet:
            if (getLevelText() == U"\xf0a7")
                return css_lst_square;
            return css_lst_disc;
        case docx_numFormat_decimal:
            return css_lst_decimal;
        default:
            return css_lst_none;
    }
}

bool docx_ElementHandler::parse_OnOff_attribute(const lChar32* attrValue) {
    if (!lStr_cmp(attrValue, "1") || !lStr_cmp(attrValue, "on") || !lStr_cmp(attrValue, "true"))
        return true;
    return false;
}

void docx_ElementHandler::generateLink(const lChar32* target, const lChar32* type, const lChar32* text) {
    m_writer->OnTagOpen(U"", U"a");
    m_writer->OnAttribute(U"", U"href", target);
    if (type)
        m_writer->OnAttribute(U"", U"type", type);
    // Add classic role=doc-noteref attribute to allow popup/in-page footnotes
    m_writer->OnAttribute(U"", U"role", U"doc-noteref");
    m_writer->OnTagBody();
#ifndef ODX_CRENGINE_IN_PAGE_FOOTNOTES
    if (!lStr_cmp(type, "note")) {
        // For footnotes (but not endnotes), wrap in <sup> (to get the
        // same effect as the following in docx.css:
        //   a[type="note"] { vertical-align: super; font-size: 70%; }
        m_writer->OnTagOpen(U"", U"sup");
        m_writer->OnTagBody();
    }
#endif
    lString32 t(text);
    m_writer->OnText(t.c_str(), t.length(), 0);
#ifndef ODX_CRENGINE_IN_PAGE_FOOTNOTES
    if (!lStr_cmp(type, "note")) {
        m_writer->OnTagClose(U"", U"sup");
    }
#endif
    m_writer->OnTagClose(U"", U"a");
}

ldomNode* docx_rPrHandler::handleTagOpen(int tagId) {
    m_state = tagId;
    switch (tagId) {
        case docx_el_b:
            m_rPr->setBold(true);
            break;
        case docx_el_i:
            m_rPr->setItalic(true);
            break;
        case docx_el_u:
            m_rPr->setUnderline(true);
            break;
        case docx_el_vanish:
            m_rPr->setHidden(true);
            break;
        case docx_el_strike:
            m_rPr->setStrikeThrough(true);
            break;
        default:
            break;
    }
    return NULL;
}

void docx_rPrHandler::handleAttribute(const lChar32* attrname, const lChar32* attrvalue) {
    int attr_value;
    switch (m_state) {
        case docx_el_lang:
            if (!lStr_cmp(attrname, "val")) {
                if (m_rPr == m_importContext->get_rPrDefault()) {
                    m_importContext->setLanguage(attrvalue);
                }
            }
            break;
        case docx_el_color:
            // todo
            break;
        case docx_el_b:
            if (!lStr_cmp(attrname, "val"))
                m_rPr->setBold(parse_OnOff_attribute(attrvalue));
            break;
        case docx_el_i:
            if (!lStr_cmp(attrname, "val"))
                m_rPr->setItalic(parse_OnOff_attribute(attrvalue));
            break;
        case docx_el_u:
            if (!lStr_cmp(attrname, "val"))
                m_rPr->setUnderline(lStr_cmp(attrvalue, "none") != 0);
            break;
        case docx_el_jc:
            if (!lStr_cmp(attrname, "val")) {
                attr_value = parse_name(jc_attr_values, attrvalue);
                if (attr_value != -1)
                    m_rPr->setTextAlign((css_text_align_t)attr_value);
            }
            break;
        case docx_el_rFonts:
            //todo
            break;
        case docx_el_rStyle:
            m_rPr->setStyleId(m_importContext, attrvalue);
            break;
        case docx_el_strike:
            if (!lStr_cmp(attrname, "val"))
                m_rPr->setStrikeThrough(parse_OnOff_attribute(attrvalue));
            break;
        case docx_el_vertAlign:
            if (!lStr_cmp(attrname, "val")) {
                attr_value = parse_name(vertAlign_attr_values, attrvalue);
                if (attr_value != -1)
                    m_rPr->setVertAlign((css_vertical_align_t)attr_value);
            }
            break;
        case docx_el_sz:
            //todo
            break;
        case docx_el_vanish:
            if (!lStr_cmp(attrname, "val"))
                m_rPr->setHidden(parse_OnOff_attribute(attrvalue));
            break;
        default:
            break;
    }
}

void docx_rPrHandler::reset() {
    m_state = m_element;
    if (m_rPr)
        m_rPr->reset();
}

void docx_rPrHandler::start(odx_rPr* const rPr) {
    m_rPr = rPr;
    docx_ElementHandler::start();
}

void docx_rHandler::handleInstruction(lString32& instruction, lString32 parameters) {
    if (instruction == cs32("REF") || instruction == cs32("NOTEREF") || instruction == cs32("PAGEREF")) {
        lString32 argument, switches;
        if (parameters.split2(cs32(" "), argument, switches) && !argument.empty()) {
            m_importContext->m_linkNode = m_writer->OnTagOpen(U"", U"a");
            lString32 target = U"#";
            target << argument;
            m_writer->OnAttribute(U"", U"href", target.c_str());
            m_writer->OnTagBody();
        }
    }
}

ldomNode* docx_rHandler::handleTagOpen(int tagId) {
    switch (tagId) {
        case docx_el_br:
        case docx_el_t:
        case docx_el_tab:
            if (!m_content) {
                if (m_importContext->m_pStyle)
                    m_rPr.combineWith(m_importContext->m_pStyle->get_rPr(m_importContext));
                m_rPr.combineWith(m_importContext->get_rPrDefault());
                m_pHandler->closeStyleTags(&m_rPr, m_writer);
                m_pHandler->openStyleTags(&m_rPr, m_writer);
                m_content = true;
            }
            m_state = tagId;
            break;
        case docx_el_rPr:
            m_rPrHandler.start(&m_rPr);
            break;
        case docx_el_footnoteRef:
        case docx_el_endnoteRef:
            m_state = tagId;
            break;
        case docx_el_drawing:
            m_drawingHandler.start();
            break;
        case docx_el_footnoteReference:
        case docx_el_endnoteReference:
            m_footnoteId.clear();
        default:
            m_state = tagId;
            break;
    }
    return NULL;
}

void docx_rHandler::handleAttribute(const lChar32* attrname, const lChar32* attrvalue) {
    if ((docx_el_footnoteReference == m_state || docx_el_endnoteReference == m_state) &&
        !lStr_cmp(attrname, "id")) {
        m_footnoteId = attrvalue;
    }
    if (docx_el_fldChar == m_state && !lStr_cmp(attrname, "fldCharType")) {
        if (!lStr_cmp(attrvalue, "begin")) {
            m_importContext->m_inField = true;
        } else if (!lStr_cmp(attrvalue, "end")) {
            if (m_importContext->m_linkNode) {
                m_writer->OnTagClose(U"", U"a");
                m_importContext->m_linkNode = NULL;
            }
            m_importContext->m_inField = false;
        }
    }
}

void docx_rHandler::handleText(const lChar32* text, int len, lUInt32 flags) {
    switch (m_state) {
        case docx_el_t:
            m_writer->OnText(text, len, flags);
            break;
        case docx_el_instrText:
            m_instruction = text;
            break;
        default:
            break;
    }
}

void docx_rHandler::handleTagClose(const lChar32* nsname, const lChar32* tagname) {
    lChar32 nobsp = 0x00A0;
    CR_UNUSED2(nsname, tagname);

    switch (m_state) {
        case docx_el_br:
            m_writer->OnTagOpenAndClose(U"", U"br");
            m_state = docx_el_r;
            break;
        case docx_el_r:
            stop();
            break;
        case docx_el_tab:
            m_writer->OnText(&nobsp, 1, 0);
            m_state = docx_el_r;
            break;
        case docx_el_footnoteReference:
            if (!m_footnoteId.empty()) {
                m_importContext->m_footNoteCount++;
                lString32 target = U"#n_";
                target << m_footnoteId;
                generateLink(target.c_str(), U"note", m_footnoteId.c_str());
            }
            m_state = docx_el_r;
            break;
        case docx_el_instrText:
            if (m_importContext->m_inField) {
                m_instruction.trim();
                if (!m_instruction.empty()) {
                    lString32 instruction, parameters;
                    if (m_instruction.split2(cs32(" "), instruction, parameters))
                        handleInstruction(instruction, parameters);
                }
            }
            m_state = docx_el_r;
            break;
        case docx_el_endnoteReference:
            if (!m_footnoteId.empty()) {
                m_importContext->m_endNoteCount++;
                lString32 target = U"#c_";
                target << m_footnoteId;
                generateLink(target.c_str(), U"comment", m_footnoteId.c_str());
            }
            m_state = docx_el_r;
            break;
        case docx_el_footnoteRef:
        case docx_el_endnoteRef:
            if (!m_importContext->m_footNoteId.empty()) {
                m_writer->OnTagOpen(U"", U"sup");
                m_writer->OnTagBody();
                m_writer->OnText(m_importContext->m_footNoteId.c_str(), m_importContext->m_footNoteId.length(), 0);
                m_writer->OnTagClose(U"", U"sup");
            }
        default:
            m_state = docx_el_r;
            break;
    }
}

void docx_rHandler::reset() {
    m_rPrHandler.reset();
    m_state = docx_el_r;
    m_content = false;
}

ldomNode* docx_pPrHandler::handleTagOpen(int tagId) {
    switch (tagId) {
        case docx_el_rPr:
            break;
        case docx_el_numPr:
            m_state = tagId;
            setChildrenInfo(numPr_elements);
            break;
        case docx_el_pageBreakBefore:
            m_state = tagId;
            m_pPr->setPageBreakBefore(true);
            break;
        case docx_el_keepNext:
            m_state = tagId;
            m_pPr->setKeepNext(true);
            break;
        case docx_el_mirrorIndents:
            m_state = tagId;
            m_pPr->setMirrorIndents(true);
            break;
        case docx_el_suppressAutoHyphens:
            m_pPr->setHyphenate(css_hyph_none);
            //fallthrough
        default:
            m_state = tagId;
    }
    return NULL;
}

void docx_pPrHandler::handleAttribute(const lChar32* attrname, const lChar32* attrvalue) {
    switch (m_state) {
        case docx_el_pStyle:
            if (!lStr_cmp(attrname, "val")) {
                m_pPr->setStyleId(m_importContext, attrvalue);
            }
            break;
        case docx_el_jc:
            if (!lStr_cmp(attrname, "val")) {
                int attr_value = parse_name(jc_attr_values, attrvalue);
                if (attr_value != -1)
                    m_pPr->setTextAlign((css_text_align_t)attr_value);
            }
            break;
        case docx_el_spacing:
            if (!lStr_cmp(attrname, "line")) {
                css_length_t val;
                parse_int(attrvalue, val);
                m_pPr->set(odx_p_line_spacing_prop, val);
            } else if (!lStr_cmp(attrname, "lineRule")) {
                int attr_value = parse_name(lineRule_attr_values, attrvalue);
                if (-1 != attr_value)
                    m_pPr->setLineRule((odx_lineRule_type)attr_value);
            } else if (!lStr_cmp(attrname, "afterAutospacing")) {
                m_pPr->set(odx_p_after_auto_spacing_prop, parse_OnOff_attribute(attrvalue));
            } else if (!lStr_cmp(attrname, "beforeAutospacing")) {
                m_pPr->set(odx_p_before_auto_spacing_prop, parse_OnOff_attribute(attrvalue));
            } else {
                //todo
            }
            break;
        case docx_el_textAlignment:
            if (!lStr_cmp(attrname, "val")) {
                int attr_value = parse_name(textAlignment_attr_values, attrvalue);
                if (attr_value != -1)
                    m_pPr->setVertAlign((css_vertical_align_t)attr_value);
            }
            break;
        case docx_el_ind:
            //todo
            break;
        case docx_el_ilvl:
            if (!lStr_cmp(attrname, "val")) {
                css_length_t val;
                parse_int(attrvalue, val);
                m_pPr->set(odx_p_ilvl_prop, val.value);
            }
            break;
        case docx_el_numId:
            if (!lStr_cmp(attrname, "val")) {
                css_length_t val;
                parse_int(attrvalue, val);
                m_pPr->set(odx_p_num_id_prop, val);
            }
            break;
        case docx_el_outlineLvl:
            if (!lStr_cmp(attrname, "val")) {
                css_length_t val;
                parse_int(attrvalue, val);
                m_pPr->set(odx_p_outline_level_prop, val);
            }
            break;
        case docx_el_pageBreakBefore:
            if (!lStr_cmp(attrname, "val"))
                m_pPr->setPageBreakBefore(parse_OnOff_attribute(attrvalue));
            break;
        case docx_el_keepNext:
            if (!lStr_cmp(attrname, "val"))
                m_pPr->setKeepNext(parse_OnOff_attribute(attrvalue));
            break;
        case docx_el_mirrorIndents:
            if (!lStr_cmp(attrname, "val"))
                m_pPr->setMirrorIndents(parse_OnOff_attribute(attrvalue));
            break;
        case docx_el_suppressAutoHyphens:
            if (!lStr_cmp(attrname, "val") && !parse_OnOff_attribute(attrvalue))
                m_pPr->setHyphenate(css_hyph_auto);
            break;
        default:
            break;
    }
}

void docx_pPrHandler::handleTagClose(const lChar32* nsname, const lChar32* tagname) {
    switch (m_state) {
        case docx_el_ilvl:
        case docx_el_numId:
            m_state = docx_el_numPr;
            break;
        case docx_el_numPr:
            setChildrenInfo(pPr_elements);
            //falltrrough
        default:
            docx_ElementHandler::handleTagClose(nsname, tagname);
            break;
    }
}

void docx_pPrHandler::reset() {
    if (m_pPr)
        m_pPr->reset();
}

void docx_pPrHandler::start(odx_pPr* pPr) {
    m_pPr = pPr;
    docx_ElementHandler::start();
}

ldomNode* docx_pHandler::handleTagOpen(int tagId) {
    switch (tagId) {
        case docx_el_r:
        case docx_el_hyperlink:
            if (0 == m_runCount) {
                m_pPr.combineWith(m_importContext->get_pPrDefault());
                css_length_t outlineLevel = m_pPr.getOutlineLvl();
                m_importContext->m_pStyle = m_pPr.getStyle(m_importContext);
                if (outlineLevel.type != css_val_unspecified) {
                    m_inTitle = true;
                }
                int numId = m_pPr.getNumberingId();
                if (numId != 0 && !m_inTitle) {
                    int level = m_pPr.getNumberingLevel() + 1;
                    if (level > m_importContext->getListLevel())
                        m_importContext->openList(level, numId, m_writer);
                    else if (level < m_importContext->getListLevel())
                        m_importContext->closeList(level, m_writer);
                    else
                        m_writer->OnTagClose(U"", U"li");
                    m_writer->OnTagOpen(U"", U"li");
                } else {
                    if (m_importContext->isInList())
                        m_importContext->closeList(0, m_writer);
                    if (m_inTitle)
                        m_titleHandler->onTitleStart(outlineLevel.value + 1);
                    else
                        m_writer->OnTagOpen(U"", U"p");
                }
                lString32 style = m_pPr.getCss();
                if (!style.empty())
                    m_writer->OnAttribute(U"", U"style", style.c_str());
                m_writer->OnTagBody();
            }
            if (docx_el_r == tagId)
                m_rHandler.start();
            else
                m_hyperlinkHandler.start();
            m_runCount++;
            break;
        case docx_el_bookmarkStart:
            m_state = tagId;
            break;
            break;
        case docx_el_pPr:
            m_pPrHandler.start(&m_pPr);
            break;
        default:
            m_state = tagId;
            break;
    }
    return NULL;
}

void docx_pHandler::handleAttribute(const lChar32* attrname, const lChar32* attrvalue) {
    if (docx_el_bookmarkStart == m_state && !lStr_cmp(attrname, "name")) {
        m_writer->OnTagOpen(U"", U"a");
        m_writer->OnAttribute(U"", U"id", attrvalue);
        m_writer->OnTagBody();
        m_writer->OnTagClose(U"", U"a");
    }
}

void docx_pHandler::handleTagClose(const lChar32* nsname, const lChar32* tagname) {
    CR_UNUSED2(nsname, tagname);

    switch (m_state) {
        case docx_el_p:
            closeStyleTags(m_writer);
            if (m_pPr.getNumberingId() == 0) {
                if (!m_inTitle) {
                    m_writer->OnTagClose(U"", U"p");
                }
            }
            stop();
            if (m_inTitle) {
                m_inTitle = false;
                m_titleHandler->onTitleEnd();
            }
            break;
        default:
            m_state = docx_el_p;
            break;
    }
}

void docx_pHandler::reset() {
    m_pPrHandler.reset();
    m_rHandler.reset();
    m_state = docx_el_p;
    m_runCount = 0;
}

ldomNode* docx_documentHandler::handleTagOpen(int tagId) {
    if (tagId != docx_el_p && m_importContext->isInList())
        m_importContext->closeList(0, m_writer);
    switch (tagId) {
        case docx_el_p:
            paragraphHandler.start();
            break;
        case docx_el_tbl:
            m_tableHandler.start();
            break;
        case docx_el_body:
            m_titleHandler->onBodyStart();
            m_writer->OnTagBody();
            //fallthrough
        default:
            m_state = tagId;
            break;
    }
    return NULL;
}

void docx_documentHandler::handleAttribute(const lChar32* nsname, const lChar32* attrname, const lChar32* attrvalue) {
    if (m_state == docx_el_document && !lStr_cmp(nsname, "xmlns"))
        CRLog::debug("namespace declaration %s:%s", LCSTR(attrname), LCSTR(attrvalue));
}

void docx_documentHandler::handleTagClose(const lChar32* nsname, const lChar32* tagname) {
    switch (m_state) {
        case docx_el_body:
            m_titleHandler->onBodyEnd();
            m_writer->OnTagClose(nsname, tagname);
            break;
        default:
            break;
    }
}

ldomNode* docx_styleHandler::handleTagOpen(int tagId) {
    switch (tagId) {
        case docx_el_pPr:
            m_pPrHandler.start(m_style->get_pPrPointer());
            break;
        case docx_el_rPr:
            m_rPrHandler.start(m_style->get_rPrPointer());
            break;
        case docx_el_tblPr:
        case docx_el_trPr:
        case docx_el_tcPr:
            m_state = tagId;
            break;
        default:
            m_state = tagId;
            break;
    }
    return NULL;
}

void docx_styleHandler::handleAttribute(const lChar32* attrname, const lChar32* attrvalue) {
    switch (m_state) {
        case docx_el_style:
            if (!lStr_cmp(attrname, "type")) {
                int attr_value = parse_name(styleType_attr_values, attrvalue);
                if (-1 != attr_value)
                    m_style->setStyleType((odx_style_type)attr_value);
            } else if (!lStr_cmp(attrname, "styleId")) {
                m_style->setId(attrvalue);
            }
            break;
        case docx_el_name:
            if (!lStr_cmp(attrname, "val"))
                m_style->setName(attrvalue);
            break;
        case docx_el_basedOn:
            if (!lStr_cmp(attrname, "val"))
                m_style->setBasedOn(attrvalue);
            break;
        case docx_el_pPr:
            break;
        case docx_el_rPr:
            break;
    }
}

void docx_styleHandler::handleTagClose(const lChar32* nsname, const lChar32* tagname) {
    CR_UNUSED2(nsname, tagname);

    switch (m_state) {
        case docx_el_style:
            if (m_style->isValid())
                m_importContext->addStyle(m_styleRef);
            stop();
            break;
        default:
            m_state = docx_el_style;
            break;
    }
}

void docx_styleHandler::start() {
    docx_ElementHandler::start();
    m_styleRef = odx_StyleRef(new odx_Style);
    m_style = m_styleRef.get();
    m_state = docx_el_style;
}

ldomNode* docx_stylesHandler::handleTagOpen(int tagId) {
    switch (tagId) {
        case docx_el_pPr:
            m_pPrHandler.start(m_importContext->get_pPrDefault());
            break;
        case docx_el_rPr:
            m_rPrHandler.start(m_importContext->get_rPrDefault());
            break;
        case docx_el_style:
            m_styleHandler.start();
            break;
        case docx_el_docDefaults:
            setChildrenInfo(docDefaults_elements);
            //falltrough
        default:
            m_state = tagId;
            break;
    }
    return NULL;
}

void docx_stylesHandler::handleTagClose(const lChar32* nsname, const lChar32* tagname) {
    switch (m_state) {
        case docx_el_rPrDefault:
        case docx_el_pPrDefault:
            m_state = docx_el_docDefaults;
            break;
        case docx_el_docDefaults:
            setChildrenInfo(styles_elements);
            //fallthrough
        case docx_el_style:
            m_state = docx_el_styles;
            break;
        case docx_el_styles:
            stop();
            break;
        default:
            CRLog::error("Unexpected tag(%s:%)", nsname, tagname);
            break;
    }
}

void docx_stylesHandler::reset() {
    m_styleHandler.reset();
    m_state = docx_el_NULL;
}

bool parseStyles(docxImportContext* importContext) {
    LVStreamRef m_stream = importContext->openContentPart(docx_StylesContentType);
    if (m_stream.isNull())
        return false;

    docXMLreader docReader(NULL);
    docx_stylesHandler stylesHandler(&docReader, NULL, importContext);
    docReader.setHandler(&stylesHandler);

    LVXMLParser parser(m_stream, &docReader);

    if (!parser.Parse())
        return false;
    return true;
}

bool parseNumbering(docxImportContext* importContext) {
    LVStreamRef m_stream = importContext->openContentPart(docx_NumberingContentType);
    if (m_stream.isNull())
        return false;

    docXMLreader docReader(NULL);
    docx_numberingHandler numberingHandler(&docReader, NULL, importContext);
    docReader.setHandler(&numberingHandler);

    LVXMLParser parser(m_stream, &docReader);

    if (!parser.Parse())
        return false;
    return true;
}

void parseFootnotes(ldomDocumentWriter& writer, docxImportContext& context, int element) {
    LVStreamRef m_stream;

    if (element == docx_el_footnotes)
        m_stream = context.openRelatedPart(docx_FootNotesRelationShip);
    else
        m_stream = context.openRelatedPart(docx_EndNotesRelationShip);

    if (!m_stream.isNull()) {
        docXMLreader docReader(&writer);
        docx_footnotesHandler footnotesHandler(&docReader, &writer, &context, element);
        docReader.setHandler(&footnotesHandler);

        LVXMLParser parser(m_stream, &docReader);

        if (parser.Parse())
#ifdef ODX_CRENGINE_IN_PAGE_FOOTNOTES
            writer.OnTagClose(U"", docx_el_body_name);
#else
            // We didn't add <body name=notes> to not trigger crengine auto-in-page-foonotes
            // mechanism, so we can tweak them with style tweaks. We used a simple <div> instead.
            writer.OnTagClose(U"", U"div");
#endif
    }
    context.closeRelatedPart();
}

bool ImportDocXDocument(LVStreamRef stream, ldomDocument* doc, LVDocViewCallback* progressCallback, CacheLoadingCallback* formatCallback) {
    LVContainerRef arc = LVOpenArchieve(stream);
    if (arc.isNull())
        return false; // not a ZIP archive

    doc->setContainer(arc);
    OpcPackage package(arc);

    docxImportContext importContext(&package, doc);

    package.readCoreProperties(doc->getProps());

    if (doc->openFromCache(formatCallback)) {
        if (progressCallback) {
            progressCallback->OnLoadFileEnd();
        }
        return true;
    }

    parseNumbering(&importContext);

    if (!parseStyles(&importContext))
        return false;

    LVStreamRef m_stream = importContext.openContentPart(docx_DocumentContentType);
    if (m_stream.isNull())
        return false;

    ldomDocumentWriter writer(doc);
    docXMLreader docReader(&writer);

    importContext.startDocument(writer);

#ifdef DOCX_FB2_DOM_STRUCTURE
    //Two options when dealing with titles: (FB2|HTML)
    odx_fb2TitleHandler titleHandler(&writer, DOCX_USE_CLASS_FOR_HEADING); //<section><title>..</title></section>
#else
    odx_titleHandler titleHandler(&writer); //<hx>..</hx>
#endif
    docx_documentHandler documentHandler(&docReader, &writer, &importContext, &titleHandler);
    docReader.setHandler(&documentHandler);

    LVXMLParser parser(m_stream, &docReader);

    if (!parser.Parse())
        return false;

    if (importContext.m_footNoteCount > 0) {
        parseFootnotes(writer, importContext, docx_el_footnotes);
    }
    if (importContext.m_endNoteCount > 0) {
        parseFootnotes(writer, importContext, docx_el_endnotes);
    }

    importContext.endDocument(writer);
    writer.OnStop();

    if (progressCallback) {
        progressCallback->OnLoadFileEnd();
        doc->compact();
        doc->dumpStatistics();
    }
    return true;
}

docxImportContext::docxImportContext(OpcPackage* package, ldomDocument* doc)
        : odx_ImportContext(doc)
        , m_abstractNumbers(16)
        , m_Numbers(16)
        , m_footNoteCount(0)
        , m_endNoteCount(0)
        , m_inField(false)
        , m_linkNode(NULL)
        , m_pStyle(NULL)
        , m_package(package) {
}

docxImportContext::~docxImportContext() {
}

void docxImportContext::addNum(docxNumRef num) {
    if (!num.isNull()) {
        m_Numbers.set(num->getId(), num);
    }
}

void docxImportContext::addAbstractNum(docxAbstractNumRef abstractNum) {
    if (!abstractNum.isNull()) {
        m_abstractNumbers.set(abstractNum->getId(), abstractNum);
    }
}

LVStreamRef docxImportContext::openContentPart(const lChar32* const contentType) {
    m_docPart = m_package->getContentPart(contentType);
    if (!m_docPart.isNull()) {
        return m_docPart->open();
    }
    return LVStreamRef();
}

LVStreamRef docxImportContext::openRelatedPart(const lChar32* const relationshipType) {
    if (!m_docPart.isNull()) {
        m_relatedPart = m_docPart->getRelatedPart(relationshipType);
        if (!m_relatedPart.isNull())
            return m_relatedPart->open();
    }
    return LVStreamRef();
}

void docxImportContext::closeRelatedPart() {
    if (!m_relatedPart.isNull()) {
        m_relatedPart.Clear();
    }
}

void docxImportContext::openList(int level, int numid, ldomDocumentWriter* writer) {
    const docxNumRef num = getNum(numid);

    for (int i = getListLevel(); i < level; i++) {
        const docxNumLevel* listLevel = NULL;
        css_list_style_type_t listType = css_lst_disc;
        if (!num.isNull())
            listLevel = num->getDocxLevel(const_cast<docxImportContext&>(*this), level - 1);
        if (listLevel)
            listType = listLevel->getListType();
        writer->OnTagOpen(U"", U"ol");
        m_ListLevels.add(listType);
        writer->OnAttribute(U"", U"style", getListStyleCss(listType).c_str());
        writer->OnTagBody();
        if (i != level - 1)
            writer->OnTagOpenNoAttr(U"", U"li");
    }
}

void docxImportContext::closeList(int level, ldomDocumentWriter* writer) {
    for (int i = getListLevel(); i > level; i--) {
        writer->OnTagClose(U"", U"li");
        writer->OnTagClose(U"", U"ol");
        m_ListLevels.remove(getListLevel() - 1);
    }
}

ldomNode* docx_lvlHandler::handleTagOpen(int tagId) {
    switch (tagId) {
        case docx_el_pPr:
            m_pPrHandler.start(m_lvl->get_pPr());
            break;
        case docx_el_rPr:
            m_rPrHandler.start(m_lvl->get_rPr());
            break;
        case docx_el_isLgl:
            m_lvl->setLgl(true);
            //fallthrough
        case docx_el_lvlJc:
        case docx_el_lvlRestart:
        case docx_el_lvlText:
        case docx_el_numFmt:
        case docx_el_pStyle:
        case docx_el_start:
        default:
            m_state = tagId;
            break;
    }
    return NULL;
}

void docx_lvlHandler::handleAttribute(const lChar32* attrname, const lChar32* attrvalue) {
    css_length_t result;

    if (!lStr_cmp(attrname, "val")) {
        int attr_value;

        switch (m_state) {
            case docx_el_pStyle:
                m_lvl->setReferencedStyleId(attrvalue);
                break;
            case docx_el_lvlJc:
                attr_value = parse_name(jc_attr_values, attrvalue);
                if (attr_value != -1)
                    m_lvl->setLevelAlign((css_text_align_t)attr_value);
                break;
            case docx_el_isLgl:
                m_lvl->setLgl(parse_OnOff_attribute(attrvalue));
                break;
            case docx_el_lvlRestart:
                parse_int(attrvalue, result);
                m_lvl->setLevelRestart(result);
                break;
            case docx_el_lvlText:
                m_lvl->setLevelText(attrvalue);
                break;
            case docx_el_numFmt:
                attr_value = parse_name(numFmt_attr_values, attrvalue);
                if (-1 != attr_value)
                    m_lvl->setNumberFormat((docx_numFormat_type)attr_value);
                break;
            case docx_el_start:
                parse_int(attrvalue, result);
                m_lvl->setLevelStart(result);
                break;
            case docx_el_suff:
                attr_value = parse_name(lvlSuff_attr_values, attrvalue);
                if (-1 != attr_value)
                    m_lvl->setLevelSuffix((docx_LevelSuffix_type)attr_value);
                break;
        }
    } else if (!lStr_cmp(attrname, "ilvl")) {
        // m_state should be docx_el_lvl
        parse_int(attrvalue, result);
        m_lvl->setLevel(result);
    } else if (!lStr_cmp(attrname, "null")) {
        // m_state should be docx_el_lvl
        m_lvl->setLevelTextNull(parse_OnOff_attribute(attrvalue));
    }
}

void docx_lvlHandler::reset() {
    m_rPrHandler.reset();
    m_pPrHandler.reset();
    if (m_lvl)
        m_lvl->reset();
}

ldomNode* docx_footnotesHandler::handleTagOpen(int tagId) {
    switch (tagId) {
        case docx_el_p:
            if (m_normal && !m_importContext->m_footNoteId.empty()) {
                if (m_pCount == 0) {
                    m_writer->OnTagOpen(U"", U"section");
                    lString32 id = isEndNote() ? U"c_" : U"n_";
                    id << m_importContext->m_footNoteId.c_str();
                    m_writer->OnAttribute(U"", U"id", id.c_str());
                    m_writer->OnAttribute(U"", U"role", isEndNote() ? U"doc-rearnote" : U"doc-footnote");
                    m_writer->OnTagBody();
                }
                paragraphHandler.start();
            } else {
                m_state = tagId;
            }
            m_pCount++;
            break;
        case docx_el_footnote:
        case docx_el_endnote:
            m_normal = true;
            m_importContext->m_footNoteId.clear();
            m_pCount = 0;
            m_state = tagId;
            break;
        case docx_el_footnotes:
        case docx_el_endnotes:
#ifdef ODX_CRENGINE_IN_PAGE_FOOTNOTES
            m_writer->OnTagOpen(U"", docx_el_body_name);
            if (isEndNote()) {
                m_writer->OnAttribute(U"", U"name", U"comments");
                m_writer->OnTagBody();
                m_writer->OnTagOpen(U"", U"subtitle");
                m_writer->OnTagBody();
                m_writer->OnText(U"* * *", 5, 0);
                m_writer->OnTagClose(U"", U"subtitle");
            } else {
                m_writer->OnAttribute(U"", U"name", U"notes");
                m_writer->OnTagBody();
            }
#else
            // We don't add <body name=notes> to not trigger crengine auto-in-page-foonotes
            // mechanism, so we can tweak them with style tweaks. We use a simple <div> instead.
            m_writer->OnTagOpen(U"", U"div");
            m_writer->OnAttribute(U"", U"style", U"page-break-before: always");
            m_writer->OnTagBody();
#endif
            //fallthrough
        default:
            m_state = tagId;
            break;
    }
    return NULL;
}

void docx_footnotesHandler::handleAttribute(const lChar32* attrname, const lChar32* attrvalue) {
    switch (m_state) {
        case docx_el_footnote:
        case docx_el_endnote:
            if (!lStr_cmp(attrname, "type")) {
                if (lStr_cmp(attrvalue, "normal"))
                    m_normal = false;
            } else if (!lStr_cmp(attrname, "id"))
                m_importContext->m_footNoteId.append(attrvalue);
            break;
        default:
            break;
    }
}

void docx_footnotesHandler::handleTagClose(const lChar32* nsname, const lChar32* tagname) {
    switch (m_state) {
        case docx_el_p:
            m_state = isEndNote() ? docx_el_endnote : docx_el_footnote;
            break;
        case docx_el_endnote:
        case docx_el_footnote:
            m_writer->OnTagClose(U"", U"section");
        default:
            docx_ElementHandler::handleTagClose(nsname, tagname);
            break;
    }
}

ldomNode* docx_hyperlinkHandler::handleTagOpen(int tagId) {
    switch (tagId) {
        case docx_el_r:
            if (!m_target.empty() && 0 == m_runCount) {
                m_writer->OnTagOpen(U"", U"a");
                m_writer->OnAttribute(U"", U"href", m_target.c_str());
                m_writer->OnTagBody();
            }
            m_runCount++;
            m_rHandler.start();
            break;
        default:
            m_state = tagId;
            break;
    }
    return NULL;
}

void docx_hyperlinkHandler::handleAttribute(const lChar32* attrname, const lChar32* attrvalue) {
    if (docx_el_hyperlink == m_state) {
        if (!lStr_cmp(attrname, "id")) {
            m_target = m_importContext->getLinkTarget(lString32(attrvalue));
        } else if (!lStr_cmp(attrname, "anchor") && m_target.empty()) {
            m_target = cs32("#") + lString32(attrvalue);
        }
    }
}

void docx_hyperlinkHandler::handleTagClose(const lChar32* nsname, const lChar32* tagname) {
    switch (m_state) {
        case docx_el_hyperlink:
            if (!m_target.empty()) {
                m_writer->OnTagClose(U"", U"a");
            }
        default:
            docx_ElementHandler::handleTagClose(nsname, tagname);
            break;
    }
}

ldomNode* docx_drawingHandler::handleTagOpen(int tagId) {
    m_level++;
    m_state = tagId;
    return NULL;
}

void docx_drawingHandler::handleAttribute(const lChar32* attrname, const lChar32* attrvalue) {
    if (m_state == docx_el_blip && !lStr_cmp(attrname, "embed")) {
        lString32 imgPath = m_importContext->getImageTarget(lString32(attrvalue));
        if (!imgPath.empty()) {
            m_writer->OnTagOpen(U"", U"img");
            m_writer->OnAttribute(U"", U"src", imgPath.c_str());
            m_writer->OnTagBody();
            m_writer->OnTagClose(U"", U"img", true);
        }
    }
}

void docx_drawingHandler::handleTagClose(const lChar32* nsname, const lChar32* tagname) {
    CR_UNUSED2(nsname, tagname);

    if (m_level <= 1)
        stop();
    m_level--;
}

void docx_tblHandler::endRowSpan(int column) {
    docx_row_span_info rowSpan = m_rowSpaninfo[column];
    if (rowSpan.rows > 1) {
        CRLog::warn("Row span on column: %d, end: %d", column, rowSpan.rows);
        if (rowSpan.column) {
            rowSpan.column->setAttributeValue(LXML_NS_NONE,
                                              rowSpan.column->getDocument()->getAttrNameIndex(U"rowspan"),
                                              lString32::itoa(rowSpan.rows).c_str());
        } else {
            CRLog::error("No column node");
        }
    }
}

ldomNode* docx_tblHandler::handleTagOpen(int tagId) {
    bool elementHandled = false;
    switch (tagId) {
        case docx_el_p:
            m_pHandler_->start();
            elementHandled = true;
            break;
        case docx_el_tc:
            m_colSpan = 1;
            CRLog::warn("Column: %d", m_column);
            m_vMergeState = VMERGE_NONE;
            break;
        case docx_el_vMerge:
            m_vMergeState = VMERGE_CONTINUE;
            break;
        case docx_el_tr:
            m_column = 0;
            m_writer->OnTagOpenNoAttr(U"", U"tr");
            break;
        default:
            break;
    }
    if (!elementHandled) {
        m_state = tagId;
        m_levels.add(tagId);
    }
    return NULL;
}

void docx_tblHandler::handleAttribute(const lChar32* attrname, const lChar32* attrvalue) {
    if (m_state == docx_el_gridSpan && !lStr_cmp(attrname, "val")) {
        m_colSpan = lString32(attrvalue).atoi();
    } else if (m_state == docx_el_vMerge && !lStr_cmp(attrname, "val")) {
        if (!lStr_cmp(attrvalue, "restart"))
            m_vMergeState = VMERGE_RESET;
    }
}

void docx_tblHandler::handleTagClose(const lChar32* nsname, const lChar32* tagname) {
    CR_UNUSED2(nsname, tagname);

    if (!m_levels.empty()) {
        switch (m_state) {
            case docx_el_tblPr:
                m_writer->OnTagOpenNoAttr(U"", U"table");
                break;
            case docx_el_tr:
                m_writer->OnTagClose(U"", U"tr");
                m_rowCount++;
                break;
            case docx_el_tc:
                m_column++;
                if (m_pHandler_ == &m_pHandler)
                    m_writer->OnTagClose(U"", U"td");
                break;
            case docx_el_gridCol:
                m_columnCount++;
                break;
            case docx_el_tblGrid:
                if (m_columnCount)
                    m_rowSpaninfo.reserve(m_columnCount);
                break;
            case docx_el_tcPr:
                if (VMERGE_NONE == m_vMergeState || VMERGE_RESET == m_vMergeState) {
                    m_pHandler_ = &m_pHandler;
                    ldomNode* columnNode = m_writer->OnTagOpen(U"", U"td");
                    for (int i = 0; i < m_colSpan; i++) {
                        if (m_column + i >= m_columnCount)
                            break; // shouldn't happen
                        endRowSpan(m_column + i);
                    }
                    m_rowSpaninfo[m_column] = docx_row_span_info(columnNode);
                    if (m_colSpan > 1)
                        m_writer->OnAttribute(U"", U"colspan", lString32::itoa(m_colSpan).c_str());
                    m_writer->OnTagBody();
                } else if (VMERGE_CONTINUE == m_vMergeState) {
                    m_pHandler_ = &m_skipHandler;
                    m_rowSpaninfo[m_column].rows++;
                }
                m_column += m_colSpan - 1;
                break;
            default:
                break;
        }
        m_levels.erase(m_levels.length() - 1, 1);
        if (!m_levels.empty()) {
            m_state = m_levels[m_levels.length() - 1];
        } else {
            m_state = docx_el_tbl;
        }
    } else {
        for (int i = 0; i < m_columnCount; i++) {
            endRowSpan(i);
        }
        m_writer->OnTagClose(U"", U"table");
        stop();
    }
}

void docx_tblHandler::reset() {
    m_levels.clear();
    m_rowSpaninfo.clear();
    m_rowCount = 0;
    m_columnCount = 0;
}

ldomNode* docx_numberingHandler::handleTagOpen(int tagId) {
    switch (tagId) {
        case docx_el_abstractNum:
            m_abstractNumHandler.start();
            break;
        case docx_el_num:
            m_numHandler.start();
            break;
        default:
            m_state = tagId;
    }
    return NULL;
}

void docx_numberingHandler::handleTagClose(const lChar32* nsname, const lChar32* tagname) {
    switch (m_state) {
        case docx_el_num:
        case docx_el_abstractNum:
            m_state = docx_el_numbering;
            break;
        case docx_el_numbering:
            stop();
            break;
        default:
            CRLog::error("Unexpected tag(%s:%)", nsname, tagname);
            break;
    }
}

ldomNode* docx_abstractNumHandler::handleTagOpen(int tagId) {
    switch (tagId) {
        case docx_el_lvl:
            if (!m_levelRef.isNull())
                m_abstractNumRef->addLevel(m_levelRef);
            m_levelRef = docxNumLevelRef(new docxNumLevel);
            m_lvlHandler.start(m_levelRef.get());
            break;
        default:
            m_state = tagId;
    }
    return NULL;
}

void docx_abstractNumHandler::handleAttribute(const lChar32* attrname, const lChar32* attrvalue) {
    switch (m_state) {
        case docx_el_abstractNum:
            if (!lStr_cmp(attrname, "abstractNumId"))
                m_abstractNumRef->setId(lString32(attrvalue).atoi());
            break;
        default:
            break;
    }
}

void docx_abstractNumHandler::handleTagClose(const lChar32* nsname, const lChar32* tagname) {
    CR_UNUSED2(nsname, tagname);

    switch (m_state) {
        case docx_el_abstractNum:
            if (!m_levelRef.isNull())
                m_abstractNumRef->addLevel(m_levelRef);
            if (!m_abstractNumRef.isNull())
                m_importContext->addAbstractNum(m_abstractNumRef);
            stop();
            break;
        default:
            m_state = docx_el_abstractNum;
            break;
    }
}

void docx_abstractNumHandler::start() {
    m_abstractNumRef = docxAbstractNumRef(new docxAbstractNum);
    docx_ElementHandler::start();
}

void docx_numHandler::handleAttribute(const lChar32* attrname, const lChar32* attrvalue) {
    switch (m_state) {
        case docx_el_num:
            if (!lStr_cmp(attrname, "numId"))
                m_numRef->setId(lString32(attrvalue).atoi());
            break;
        case docx_el_abstractNumId:
            if (!lStr_cmp(attrname, "val"))
                m_numRef->setBaseId(lString32(attrvalue).atoi());
            break;
        default:
            break;
    }
}

ldomNode* docx_numHandler::handleTagOpen(int tagId) {
    switch (tagId) {
        case docx_el_lvl:
            if (!m_levelRef.isNull())
                m_numRef->overrideLevel(m_levelRef);
            m_levelRef = docxNumLevelRef(new docxNumLevel);
            m_lvlHandler.start(m_levelRef.get());
            break;
        default:
            m_state = tagId;
    }
    return NULL;
}

void docx_numHandler::handleTagClose(const lChar32* nsname, const lChar32* tagname) {
    CR_UNUSED2(nsname, tagname);

    switch (m_state) {
        case docx_el_num:
            if (!m_levelRef.isNull())
                m_numRef->overrideLevel(m_levelRef);
            if (m_numRef->isValid())
                m_importContext->addNum(m_numRef);
            stop();
            break;
        default:
            m_state = docx_el_num;
            break;
    }
}

void docx_numHandler::start() {
    m_numRef = docxNumRef(new docxNum);
    docx_ElementHandler::start();
}

const docxAbstractNumRef docxNum::getBase(docxImportContext& context) const {
    return context.getAbstractNum(getBaseId());
}

void docxNum::overrideLevel(docxNumLevelRef docxLevel) {
    if (!docxLevel.isNull())
        m_overrides.set(docxLevel->getLevel().value, docxLevel);
}

docxNumLevel* docxNum::getDocxLevel(docxImportContext& context, int level) {
    docxNumLevelRef levelRef = m_overrides.get(level);
    if (!levelRef.isNull())
        return levelRef.get();
    docxAbstractNumRef baseRef = getBase(context);
    if (!baseRef.isNull())
        return baseRef->getLevel(level);
    return NULL;
}

bool docxNum::isValid() const {
    return (m_id.type != css_val_unspecified && m_abstractNumId.type != css_val_unspecified);
}

void docxNum::reset() {
    m_id.type = css_val_unspecified;
    m_abstractNumId.type = css_val_unspecified;
    m_overrides.clear();
}

void docxAbstractNum::addLevel(docxNumLevelRef docxLevel) {
    m_levels.set(docxLevel->getLevel().value, docxLevel);
}

docxAbstractNum::docxAbstractNum()
        : m_multilevel(docx_singlelevel)
        , m_abstractNumId(css_val_unspecified, 0)
        , m_levels(10) {
}

docxNumLevel* docxAbstractNum::getLevel(int level) {
    return m_levels.get(level).get();
}

void docxAbstractNum::reset() {
    m_levels.clear();
}
