Страницы

Поиск по вопросам

пятница, 14 февраля 2020 г.

Обход объединенных ячеек в Word

#c_sharp #net #ms_word #office #office_interop


В общем, в продолжение своей предыдущей темы по парсингу таблиц Word'a с помощью
interop я столкнулся с объединенными ячейками.

Как я могу узнать на сколько ячеек произошло объединение, что бы я мог размножить
F3 на N строчек?



Капался в дебаггере в поисках нужного свойства, но что-то не нашел... Да и в интернете
поголовно пишут, что такого свойства нет

UPD 1

На одном из ресурсов подкинули вот такую идею(Код на VBA, но легко переписывается на C#):

Sub ParseInExcel()
    Dim doc As Document, tbl As Table, eObj, eWbk, eWst, eRng, eCell
    Dim i As Long, j As Long
    Set doc = ThisDocument
    Set tbl = doc.Tables(1)
    tbl.Range.Copy
    Set eObj = CreateObject("Excel.Application")
    Set eWbk = eObj.Workbooks.Add
    Set eWst = eWbk.Sheets(1)
    eWst.Paste
    Set eRng = eWst.Cells(1).CurrentRegion
    For i = 1 To eRng.Rows.Count
        For j = 1 To eRng.Columns.Count
            Set eCell = eRng.Cells(i, j)
            If eCell.MergeCells Then
                Debug.Print eCell.MergeArea.Cells(1).Value & " ";
            Else
                Debug.Print eCell.Value & " ";
            End If
        Next
        Debug.Print
    Next
    eWbk.Saved = True
    eWbk.Close
    Set eWbk = Nothing
    eObj.Quit
    Set eObj = Nothing
End Sub


Смысл в том, что из Word все копипастится в Excel, а там уже появляются свойства,
которые показывают объединение ячеек-MergeRange. Проблема в том, что будет занят буфер
обмена(самое проблемное на мой взгляд, так как не смогу работать за ПК в момент работы
программы иначе покалечу буфер), а так же в том, что подключается второй всадник апокалипсиса-Excel.

Если Excel знает, как представить таблицу из Word и создать свойства, которые показывают,
что некая зона объединена, то значит должен иметься какой-то однозначный алгоритм для
вычисления этого средствами Word'a.

Даже при сохранении в html-формате пишется кол-во объединенных колонок и строк.

UPD 2

По объединенным строкам я нашел решение, но по колонкам нет. Накидал свое решение,
но не думаю, что оно универсально. 

Может у кого-то есть еще идеи?

P.S Interop не принципиален, но в приоритете, так как много кода на нем нарисовал.

Если есть решение например для *.DOCX, то я могу сменить формат.
    


Ответы

Ответ 1



Получим xml с docx это не сложно вот Давайте рассмотрим такую таблицу 1 4 < 5 2 - - ^ Где < и ^ - направление обьединения ячеек. Тогда получим следующее (оставляю только значащие теги) (1) (4) (5) (2) - - Текст хранится в тегах текст. Вычислить обьеденённый столбец этим способом легче - по тегу gridSpan. Обьеденённые строки прийдется перелистывать vMerge тег. Вот пример как получить доступ к тегам внутри docx using System; using System.IO; using System.Xml; public class Demo { public static void docx2process(Stream file) { int isTable = 0; /*Для парсинга xml*/ int col = 0; int row = 0; int colSpan = 1; /*Парсинг архива*/ int ready = 0; while (ready++ < 10){ /*защита от повисания, обход архива*/ byte[] head = new byte[30]; file.Read(head, 0, 30); if (head[0] != 'P') break; //zip-header int i = (head[27] + head[29]) * 256 + head[28]; // extra len long paked = BitConverter.ToInt32(head,18); byte[] nam = new byte[255]; file.Read(nam, 0, head[26]); if (i != 0) file.Seek(i, SeekOrigin.Current); String aname = System.Text.Encoding.ASCII.GetString(nam, 0, head[26]); if (aname == "word/document.xml"){ long lastpos = file.Position; using (System.IO.Compression.DeflateStream deflate = new System.IO.Compression.DeflateStream(file, System.IO.Compression.CompressionMode.Decompress)){ System.Xml.XmlReader rd = System.Xml.XmlReader.Create(deflate); // Тут парсинг документа while (rd.Read()){ if (rd.NodeType == XmlNodeType.Element) switch (rd.Name){ case "w:gridSpan": colSpan = int.Parse(rd.GetAttribute("w:val"));break; case "w:vMerge":; /*Тут нужно допилить обьединение строк*/ break; case "w:tbl": row=0; isTable = rd.Depth ; break; case "w:tr": col=0; row++; break; case "w:tc": col+= colSpan; colSpan=1; break; case "w:t":/*Теги с текстом*/ if (rd.Read() && rd.NodeType == XmlNodeType.Text) /*Тут нужно написать свой вывод куда-либо*/ if (isTable>0) Console.WriteLine(string.Format("c={0}.{3} r={1} t={2}",col,row,rd.Value, colSpan)); else Console.WriteLine(rd.Value); break; case "w:p": /*Параграф*/ if (isTable == rd.Depth) isTable = 0; break; } } return; } file.Position = lastpos + paked; }else file.Seek(paked, SeekOrigin.Current); }; } public static void docx2process(string filename) { using (Stream f = File.OpenRead(filename)) docx2process(f); } public static void Main(string[] args) { docx2process("1.docx"); } } этот пример почти решает поставленую задачу. Обьедененные столбцы - видно. Обьеденённые строки - ещё нужно доработать.

Ответ 2



Изучение вот этого простого кода Sub test() Dim oneTable As Table Dim oneCell As Cell Dim i As Integer Set oneTable = ThisDocument.Tables(1) For Each oneCell In oneTable.Range.Cells i = i + 1 Debug.Print i, oneCell.RowIndex, oneCell.ColumnIndex Next End Sub на тестовых табличках с объединением ячеек даст достаточно информации для решения проблемы.

Ответ 3



Даже при сохранении в html-формате пишется кол-во объединенных колонок и строк. Так может и воспользоваться этой деталью? Перегнать doc в html и распарсить атрибут colspan/rowspan using System; using System.Collections.Generic; using System.Data; using System.IO; using System.Text; using Microsoft.Office.Interop.Word; using System.Runtime.InteropServices; //Add Reference: Microsoft HTML Object Library namespace ConsoleApplication1 { class Program { public static List FindMergedColumns(string html) { List res = new List(); mshtml.HTMLDocument doc = null; mshtml.IHTMLDocument2 d2 = null; mshtml.IHTMLDocument3 d = null; try { doc = new mshtml.HTMLDocument(); d2 = (mshtml.IHTMLDocument2)doc; d2.write(html); d2.close(); d = (mshtml.IHTMLDocument3)doc; var coll = d.getElementsByTagName("table"); object val; int numtable = 1; int row = 1, column = 1; int span; foreach (mshtml.IHTMLElement2 table in coll) { var rows = table.getElementsByTagName("tr"); foreach (mshtml.IHTMLElement2 tr in rows) { var cells = tr.getElementsByTagName("td"); foreach (mshtml.IHTMLElement td in cells) { val = td.getAttribute("colspan"); if(val == null)val = 0; span = Convert.ToInt32(val); if (span > 1) { res.Add(String.Format("Table {0}, Row {1}, Column {2}: {3} columns merged",numtable,row,column,span)); } column++; } row++; column = 1; } numtable++; row = 1; column = 1; } doc.close(); } finally { //освобождение ресурсов if (doc != null) Marshal.ReleaseComObject(doc); if (d2 != null) Marshal.ReleaseComObject(d2); if (d != null) Marshal.ReleaseComObject(d); } return res; } public static void Main(string[] argv) { var word = new Microsoft.Office.Interop.Word.Application(); object miss = System.Reflection.Missing.Value; object path = "c:\\test\\test.doc"; object readOnly = true; var docs = word.Documents.Open(ref path, ref miss, ref readOnly, ref miss, ref miss, ref miss, ref miss, ref miss, ref miss, ref miss, ref miss, ref miss, ref miss, ref miss, ref miss, ref miss); string tmp = Path.GetTempPath() + "file.htm"; //конвертируем doc в html docs.SaveAs(FileName: tmp, FileFormat: WdSaveFormat.wdFormatHTML); ((_Document)docs).Close(); ((_Application)word).Quit(); //парсим HTML string html = File.ReadAllText(tmp); var res = FindMergedColumns(html); File.Delete(tmp); foreach (var line in res) Console.WriteLine(line); Console.ReadKey(); } } }

Ответ 4



Обратите внимание на строки: WordTableAnalyser wta = new WordTableAnalyser(table,1.0); MyWordCell c = wta.getCell(8, 1); MessageBox.Show(c.rows.ToString() + ";" + c.columns.ToString()); Исходная таблица Приложение на основе Windows Forms using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using Microsoft.Office.Interop.Word; using System.Reflection; namespace WorkWithWord { using Word = Microsoft.Office.Interop.Word; public class MyWordCell { public double x = 0; public double x2 = 0; public double w = 0; public int columns = 1; public int rows = 1; public int ini_row=-1; public int ini_column=-1; public Word.Cell wc; public MyWordCell(Word.Cell cell) { wc = cell; } } public class DoubleToleranceComparer : IComparer,IEqualityComparer { double tolerance=0; public DoubleToleranceComparer(double tolerance) { this.tolerance = tolerance; } #region Члены IComparer public int Compare(double x, double y) { double delta = x - y; return Math.Abs(delta) <= tolerance ? 0 : Math.Sign(delta); } #endregion #region Члены IEqualityComparer public bool Equals(double x, double y) { throw new NotImplementedException(); } public int GetHashCode(double obj) { throw new NotImplementedException(); } #endregion } public class WordTableAnalyser { public static Object missingObj = System.Reflection.Missing.Value; public static Object trueObj = true; public static Object falseObj = false; Word.Table wtable = null; double cc = 0;//количество необъединенных столбцов double rc = 0;//количество необъединенных строк double WT = 0;//ширина таблицы double HT = 0;//высота таблицы DoubleToleranceComparer dt_comparer;//сравниватель double c допуском SortedList split_xs;//встречающиеся координаты границ ячеек таблицы по x List curRow = new List(); List> myTable = new List>(); public MyWordCell getCell(int ini_row,int ini_column) { MyWordCell c = null; for (int ci = 0; ci < myTable[ini_row-1].Count; ++ci) { if (myTable[ini_row - 1][ci].ini_column == ini_column) { c = myTable[ini_row - 1][ci]; break; } } return c; } public WordTableAnalyser(Word.Table table,double tolerance) { wtable = table; cc = table.Columns.Count; rc = table.Rows.Count; dt_comparer = new DoubleToleranceComparer(tolerance); split_xs = new SortedList(dt_comparer); split_xs.Add(0.0, 0.0); for (int i = 1; i <= cc; ++i) { try { Word.Cell cell=table.Cell(1,i); MyWordCell myCell = new MyWordCell(cell); myCell.x = WT; myCell.w = cell.PreferredWidth; myCell.x2 = myCell.x + myCell.w; myCell.ini_row = 1; myCell.ini_column = i; curRow.Add(myCell); WT += cell.PreferredWidth;//Width не всегда определена //cell. split_xs.Add(WT, WT); } catch(Exception ex) { } } myTable.Add(curRow); for(int j=2;j<=rc;++j) { List prevRow = curRow; curRow = new List(); List myTableRow = new List(); int prevColInd = 0; double curX=0; for (int i = 1; i <= cc; ++i) { try { Word.Cell cell=table.Cell(j,i); MyWordCell myCell = new MyWordCell(cell); myCell.x = curX; myCell.w = cell.PreferredWidth; myCell.x2 = myCell.x + myCell.w; myCell.ini_row = j; myCell.ini_column = i; curRow.Add(myCell); //while(myTable.Count 1) c.columns += delta_ind - 1; } } } } public partial class Form1 : Form { public static Object missingObj = System.Reflection.Missing.Value; public static Object trueObj = true; public static Object falseObj = false; public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { } private void buttonTest_Click(object sender, EventArgs e) { Word._Application application; Word._Document document=null; //создаем обьект приложения word application = new Word.Application(); // создаем путь к файлу Object templatePathObj = @"D:\Work\Life\StackOverflow\Разбиение объединённых ячеек в таблице.docm"; ; // если вылетим не этом этапе, приложение останется открытым try { document = application.Documents.Add(ref templatePathObj, ref missingObj, ref missingObj, ref missingObj); Word.Table table = document.Tables[1];//в файле примера одна единственная таблица int rcount = table.Rows.Count; int ccount = table.Columns.Count; WordTableAnalyser wta = new WordTableAnalyser(table,1.0); MyWordCell c = wta.getCell(8, 1); MessageBox.Show(c.rows.ToString() + ";" + c.columns.ToString()); application.Visible = true; } catch (Exception error) { if(document!=null)document.Close(ref falseObj, ref missingObj, ref missingObj); application.Quit(ref missingObj, ref missingObj, ref missingObj); document = null; application = null; MessageBox.Show(error.Message, "", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } } } }

Ответ 5



Поиски привели меня на сайт, где я нашел такое решение для определения объеденных строчек: int GetRowSpan(Cell cell) { cell.Select(); return (int)_wordApp.Selection.Information[WdInformation.wdEndOfRangeRowNumber] - (int)_wordApp.Selection.Information[WdInformation.wdStartOfRangeRowNumber] + 1; } К сожалению, ничего подобного я не нашел для объеденных колонок... Написал свое решение, но не думаю, что оно универсальное: int GetColumnSpan(WordTable table, Cell cell) { var columnIndex = cell.ColumnIndex; var etalonSize = table.Cell(1, columnIndex).Width; if (cell.Width - etalonSize < 0.5) { return 1; } var curWidth = cell.Width; var mergCount = 1; while (curWidth - etalonSize > 0.5) { curWidth -= etalonSize; mergCount++; } return mergCount; } Суть в том, что на вход я подаю эталонную ширину ячейки, а затем сравниваю ее с текущей. Если ширина болье => ячейка объеденена... Если у кого-то есть мысли лучше, то пишите.

Комментариев нет:

Отправить комментарий