[Qt]Microsoft Office XML Reference: Пример извлечения информации

Начиная с версии MS Office 2000 офисный пакет умеет выполнять преобразование документов в формат XML. И это очень удобно – ввиду того, что данный формат является кроссплатформенным и легко поддается обработке.
Для разработчиков компания предоставляет набор документации – XML Reference Schemas, (например для 2003 офиса вы можете скачать на сайте компании Microsoft, либо на нашем сайте). Назначение всех тегов достаточно подробно документированно, и не вижу необходимости детально останавливаться на этом. Но в тоже время, в небольшой статье, хотелось бы показать небольшой пример, как можно извлечь информацию MSOffice XML-документа и преобразовать ее в необходимый вам формат.
Предположим, есть ряд документов, которые содержат табличные данные с некой персональной информацией – например ФИО, год рождения, место проживания. Структура таблиц во всех документах одинакова. Вам необходимо эту информацию извлечь и свести в один документ – для простоты предположим, что это обычный файл CSV. Что для этого необходимо?
Во первых, давайте определимся с инструментами.
Для реализации задачи достаточно обычного SAX-парсера – т.е. однопроходное сканирования каждого документа вполне решает, но в данной задаче, давайте используем возможности DOM-модели. В качестве языка разработки будем использовать Qt, средой разработки соответственно используется Qt Creator 1.3.1, версия Qt 4.6.2, компилятор MinGW.
Во вторых, создадим алгоритм работы. Он будет выглядеть следующим образом:
1. Пользователь выбирает директорию, в которой находятся искомые файлы
2. Программа сканирует директорию по маске, и добавляет все найденные файлы в список.
3. После заполнения списка, пользователю становится доступна операция “Извлечь”
4. В каждом файле списка находится тег < w:body > – согласно документации этот тег содержит собственно тело документа. Родительским для него является корневой тег < w:wordDocument >, все остальные теги (за исключением тегов стилей, параметров и пр. – но в данной задаче мы их использовать не будем, потому не будем и рассматривать) по отношению к нему являются дочерними.
5. Следующим шагом мы должны найти тег таблицы – < w:tbl >. Здесь я бы хотел коротко (почему коротко – так как в документации это описано достаточно подробно)обратить ваше внимание, на то, как формируется структура элементов документа. Например если речь идет о обычном тексте, то мы будем видеть примерно такую структуру:

< w:p > - начало параграфа
< w:r > - начало записи
< w:t >Hello, World.< /w:t > - собственно запись
< /w:r >
< /w:p >

таким образом, что бы получить какой либо текст, мы вначале должны получить узел < w:p > (параграфа Paragraph), затем узел < w:r > (записи Run), а затем уже собственно текст.
Если же например параграф имеет выравнивание, то код тогда будет уже сложнее:

< w:p >
< w:pPr > - параметры параграфа
< w:tabs >
< w:tab w:val ="center" w:pos="1440"/ >
< w:tab w:val="left" w:pos="4320"/ >
< w:tab w:val="decimal" w:pos="7200"/ >
< /w:tabs >
< /w:pPr >
< w:r >
< w:tab/ >
< w:tab/ >
< w:t >Hello, World.< /w:t >
< /w:r >
< /w:p >

ну и так далее. Но так как, я не ставлю себе задачу пересказа документации то особо останавливаться на этом мы не будем. Просто надо иметь ввиду, что что бы получить данные из ячейки таблицы, мы так же должны идти по такому же пути. Но о этом далее.
5. После того, как мы получили узел таблицы, нам необходимо последовательно перебирать ее строки – это тег < w:tr >
6. Каждая строка, содержит в качестве дочерних узлы ячеек – < w:tc > – это собственно то, что нам и необходимо.
7. Теперь для каждой из ячеек мы должны получить дочерние, по отношению к ней параграфы, для параграфов – дочерние записи, а для записей – собственно текст. Как в русских сказках – “Смерть в игле, игла в яйце…”
полученный текст нам надо обработать и сохранить.
Вот в общем то и все. Как видно из описания, возможности DOM-модели тут избыточны, поэтому, при желании вы можете переписать задачу под однопроходный парсер – на значительных объемах файлов, вы наверняка получите существенный выигрыш.
Ну и теперь осталось все это воплотить в код.
Шаги с первого по третий – Выбор, сканирование директории и добавление файлов:

void fmMain::on_acLoad_triggered()
{
ui->lwMain->clear(); //Чистим список файлов
ui->acUnion->setEnabled(false);//Кнопку обработки делаем недоступной
dir = "";//закрытая переменная класса, содержит выбранную директорию, чистим
dir = QFileDialog::getExistingDirectory(0,"","");
if(dir =="")return;
QDir TargetDir(dir);
QFileInfoList dirContent = TargetDir.entryInfoList(QStringList()
<< "*.xml",
QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
for(int i = 0; i < dirContent.size();i++)
ui->lwMain->addItem(dirContent.at(i).fileName());
if(ui->lwMain->count() > 0)//если есть хоть один файл - разрешаем обработку
ui->acUnion->setEnabled(true);

}
Далее – последовательно загружаем файлы и начинаем обработку:

void fmMain::on_acUnion_triggered()
{
QWidget::setCursor(QCursor(Qt::WaitCursor));
for(int i = 0;i < ui->lwMain->count();i++){
loadFile(dir+"/"+ui->lwMain->item(i)->text()); //загружаем каждый файл
}
QString fileName = QFileDialog::getSaveFileName(this,
tr("Сохранить"), "",
tr("File CSV (*.csv);;All Files (*)"));
QFile fileOut(fileName);
fileOut.open(QIODevice::WriteOnly);
QTextStream out(&fileOut);
for (int i = 0; i < list.size(); ++i)//закрытая переменная класса, список полученных строк
out << list.at(i) << endl;
QWidget::setCursor(QCursor(Qt::ArrowCursor));
QTextCodec *codec=QTextCodec::codecForName("utf8");
QTextCodec::setCodecForTr(codec);
QString message = tr("Операция выполнена. Обработанный файл находится по адресу ");
QMessageBox::about(this,tr("Операция выполнена"), message+""+fileName+"");
}

void fmMain::loadFile(QString filein){//собственно загрузка
QFile out(filein);
out.open(QIODevice::ReadOnly);
domRead.setContent(&out);
QDomElement root = domRead.documentElement();//получаем корневой узел
getElement(root);

}
void fmMain::getElement(const QDomNode &node){
QDomElement tmpnode = node.firstChildElement(“w:body”);
tmpnode = tmpnode.firstChildElement(“w:tbl”);//находим первый табличный узел

while(!tmpnode.isNull()){
getTable(tmpnode);
tmpnode = tmpnode.nextSiblingElement(“w:tbl”);
}
}
void fmMain::getTable(const QDomNode &node){
QDomElement tablerow = node.firstChildElement(“w:tr”);
while(!tablerow.isNull()){
getCell(tablerow);
tablerow = tablerow.nextSiblingElement(“w:tr”);
}

}
void fmMain::getCell(const QDomNode &node){
QDomElement tablecell = node.firstChildElement(“w:tc”);
QString result = “”; //строка вывода
while(!tablecell.isNull()){
parsingCell(tablecell,result);
tablecell = tablecell.nextSiblingElement(“w:tc”);
}
list << result;//строку в список
}
void fmMain::parsingCell(const QDomNode &node, QString &result){
QString tmp = “”;
int count = node.childNodes().count();
QDomElement cell,rows,parag = node.firstChildElement(“w:p”);
if(!parag.isNull())
rows = parag.firstChildElement(“w:r”);
if(!rows.isNull())
cell = rows.firstChildElement(“w:t”);
for(int i = 0; i < count-1; i++){
tmp += cell.text() +” “;
parag.nextSiblingElement(“w:p”);//здесь строго говоря надо организовывать
if(!parag.isNull())//перебор для каждого элемента – узлов может быть более одного
rows = parag.firstChildElement(“w:r”);
if(!rows.isNull())
cell = rows.firstChildElement(“w:t”);
}
result += tmp+”;”;
}
Вот и все. Хочу обратить внимание, что это только один из возможных способов решения данной задачи – он наиболее простой, но далеко не самый эффективный.

©Varkon Ltd 2010
При перепечатке материала ссылка на данный сайт обязательна.
ООО Варкон. Высококачественный сервис и поддержка.

Если статья была полезна вам – не забывайте поделиться ею со своими друзьями в социальных сетях. Если есть вопросы – задавайте в комментариях либо в наших социальных группах.