Страницы

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

Показаны сообщения с ярлыком office. Показать все сообщения
Показаны сообщения с ярлыком office. Показать все сообщения

пятница, 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; } Суть в том, что на вход я подаю эталонную ширину ячейки, а затем сравниваю ее с текущей. Если ширина болье => ячейка объеденена... Если у кого-то есть мысли лучше, то пишите.

пятница, 10 января 2020 г.

Подскажите корректную архитектуру для программы, которая работает с бд

#c_sharp #sql #net #office #ms_access


Писал программу, которой на вход подаётся бд access  и выполняются различные преобразования
,и столкнулся с тем, что если поменяется что то во входной структуре бд, то придётся
жестко рефакторить код... Дело в том, что я работаю через интероп, в некоторых местах
делаю запросы на обновления, которые закодированы в коде, в другом месте, где требуется
мощь высокоуровневых языков я открываю recordset и произвожу обновления записей. Можно
грузить все в память и работать через datatable, но я побоялся больших баз и отказался
от этой затеи... Подскажите, лучше всего уйти от того, что есть к коду, который легко
редактировать если структура поменяется, сохраняя высокую скорость обработки. 
    


Ответы

Ответ 1



Создать в БД таблицу с конфигом базы. И сходя из этого формировать SQL-запросы. Например, создаём таблицу info описанием таблиц. CREATE TABLE info ( ID int identity (1, 1) primary key ,NameTable varchar(50) ,NameColumn varchar(50) ) При запуске приложения читаем эту таблицу, и формируем запросы для других таблиц. Например загружаем таблицу в DataTable c именем infoTable: string command = "SELECT [ID] "; foreach (DataRow row in infoTable.Rows) { command = command + ",[" + row[2].ToString() + "]"; } command = command + " FROM [" + row[1].ToString() + "]"; В итоге в command получается запрос типа: SELECT [ID], [NameColumn1], [NameColumn2] FROM [NameTable] И так далее... Меняется таблица в БД, меняем описание, запросы формируются иначе.

Ответ 2



Еще как вариант изменить программу таким образом чтобы она только выполняла внешние скрипты (в виде файликов 001_update_table1.sql), которые вы будите при необходимости редактировать. В таком случае: Вам не будет необходимости все время пересобирать и переписывать реализацию программы. Можно реализовать поддержку различные версии БД. Правки в одном месте (только в скриптах), не нужно выискивать переменные в DataSet'ах иди захардкоженых переменных.

Ответ 3



А может быть на входе подавать не только бд, а и пакет изменений. Структуру которая будет описывать такой список изменений можно уже разработать в зависимости от поставленных задач. Таким образом при изменении бд вам не нужно будет редактировать код, а только файл с необходимыми изменениями.

четверг, 2 января 2020 г.

Массовая смена автора документа

#linux #bash #office #open_office #libre_office


Подскажите как быть)

Предыстория: 

У друга на работе уволился админ и на компе, буха, юрика
и самого директора, изменил пользователя на Х%Й Булыжников или Пися
Камушкин - не суть)
Но обнаружили это через неделю или две, когда открыли документы doc,
odt,xsl  и прочие офисные, нажали файл - Свойства и там увидели сие
чудо) а файлы они эти куда-то шлют и там через эту процедуру, даты
сверяют в случае чего,)


Как можно массово такие офисные файлы перевладеть (сменить или убрать автора)?)

Руками долго (создать новый док, перекинуть все в него и сохранить - их
там сотни документов) естественно, теперь Пользователь нормальный, но
документы некоторые остались с пасхалкой)))

Подскажите, как под linux  или windows  можно автоматически поменять.

Думаю что скриптом можно, но не пойму как работать с api или самой libreOffice  из
консоли

Спасибо...

p.s. как выяснилось - админа очень обидели... и потом поняли что не
правы, но было поздно)

Конвертировать в другой формат, в надежде смены автора так же безсмысленно

libreoffice -env:UserInstallation=file:///home/firefedot/.config/libreoffice-alt
--headless --convert-to odt *.doc


В итоге остался тот же автор создания документа и те же авторы изменений.
    


Ответы

Ответ 1



Спасибо @Qwertiy за подсказку. В общем пока что вариант такой нашел. Сначала приводим все нужные файлы к типу odt, таким образом libreoffice -env:UserInstallation=file:///home/firefedot/.config/libreoffice-alt --headless --convert-to odt *.doc Затем можно скриптом, а можно и руками, делаем так. Распаковывем наш файл ODT в папку unzip file.odt -d folder После этого ищем и заменяем нужного/ненужного автора grep NONAMES meta.xml --color # Или если не знаем имени, то grep creator meta.xml --color И мы увидим нужное нам, оно будет подсвечено) Заменяем все что нужно sed -i 's/NONAMES/MyName/g' meta.xml Затем, мы приводим завершающие действия. Запаковываем все обратно, в два этапа, для правильной структуры нового файла 1: cd folder zip -0 -X file.odt mimetype 2: zip -r file.odt * -x mimetype После этого файл открывается без ошибки и если открыть меню Файл-Свойства, мы увидим того автора,которого внесли. Осталось проверить как это все работает именами файлов содержащие пробелы. p.s. если @Qwertiy возражает, то могу все это передать ему в ответ.

Ответ 2



Odt-файл - это zip-архив. Надо его распаковать как zip-архив, найти, в каком месте там находится имя пользователя, поменять и упаковать обратно. Аналогично с docx (но не doc). PS: Делал так с vsdx.

понедельник, 23 декабря 2019 г.

Возможно ли заставить работать приложение, на ПК, где нет необходимой версии офиса?

#c_sharp #net #visual_studio #com #office


Возможно ли заставить работать приложение, на ПК, где нет необходимой версии офиса?

Разрабатывал приложение, где стоял 2016 офис, потом кинул приложение на ПК, где 2007
офис и возникли проблемы при запуске...

На машине стоял 2007 офис и я попробовал кинуть библиотеку(Вроде, от 2016) и получил
такую ошибку:


  
    System.IO.FileLoadException: Could not load file or assembly 'office, Version=14.0.0.0,
Culture=neutral,
    PublicKeyToken=71e9bce111e9429c' or one of its dependencies. The
    located assembly's manifest definition does not match the assembly
    reference. (Exception from HRESULT: 0x80131040) File name: 'office,
    Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c'
    
    
  
  
  WRN: Assembly binding logging is turned OFF. To enable assembly bind
  failure logging, set the registry value
  [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1. Note: There
  is some performance penalty associated with assembly bind failure
  logging. To turn this feature off, remove the registry value
  [HKLM\Software\Microsoft\Fusion!EnableLog].


От офиса я использую только Access, например выполняю запросы, а где-то открываю
RecordSet и делаю обновление по одной записи...
    


Ответы

Ответ 1



У Microsoft для таких случаев есть свободно распространяемые компоненты. Для Access 2016 - можно взять тут. Похожие компоненты есть и для некоторых других приложений из пакета MS Office. Эти компоненты не позволяют создавать файлы, но позволяют просмотреть и напечатать, а если Excel- или Access-файл представляет из себя приложение, то и использовать функции такого приложения. По факту эти компоненты содержат все необходимое кроме интерфейса для создания и редактирования, который вы можете реализовать в свое программе. Таким образом вы можете не требовать от пользователя наличие установленного офиса, а включить в свой инсталятор такой компонент и устанавливать его вместе с приложением, либо оставить выбор за пользователем, ставить себе полный пакет офис или установить только компоненты для просмотра документов, о чем собственно и написано в описании компонента, например Microsoft Access 2016 Runtime: Microsoft Access 2016 provides a rich platform for developing database management solutions with easy-to-use customization tools. If no end-user customization is required (including report modifications), you can choose to distribute those Access 2016 solutions so that they run without requiring a full installation of Access 2016. To do so, you must package and distribute your application with the Access 2016 Runtime.

суббота, 14 декабря 2019 г.

Зависшие процессы Office interop

#c_sharp #office #office_interop


Предположим, в приложении необходимо использовать Office interop.(без вариантов)
Всем известно, что он создает приложения office без интерфейса и через них выполняет
работу.

Ну как быть, если пользователь некорректно завершил работу с программой? Например,
убил через диспетчер задач. Получается, что в процессах остаются висеть приложения офиса.

Можно ли это предотвратить? Если нет, то можно ли понять при запуске(повторном) приложения,
что в процессах висит мусор, который остался от прошлого запуска и грохнуть эти процессы,
не затронув реальные приложения офиса(например, юзер запустил Excel.)
    


Ответы

Ответ 1



Как вариант, средствами Win32 можно объединить приложение и запущенные из него процессы в задание (Job) тогда процессы Interop будут завершаться вместе с родительским приложением. Для этого нужно будет создать задание с помощью CreateJobObject, а затем добавлять созданные процессы в задание с помощью AssignProcessToJobObject Есть похожее обсуждение на английском языке: «Kill child process when parent process is killed». Из ответа @Ron можно взять код класса-обертки ChildProcessTracker: /// /// Allows processes to be automatically killed if this parent process unexpectedly quits. /// This feature requires Windows 8 or greater. On Windows 7, nothing is done. /// References: /// https://stackoverflow.com/a/4657392/386091 /// https://stackoverflow.com/a/9164742/386091 public static class ChildProcessTracker { /// /// Add the process to be tracked. If our current process is killed, the child processes /// that we are tracking will be automatically killed, too. If the child process terminates /// first, that's fine, too. /// public static void AddProcess(int processHandle) { if (s_jobHandle != IntPtr.Zero) { bool success = AssignProcessToJobObject(s_jobHandle, new IntPtr(processHandle)); if (!success) throw new Win32Exception(); } } static ChildProcessTracker() { // This feature requires Windows 8 or later. To support Windows 7 requires // registry settings to be added if you are using Visual Studio plus an // app.manifest change. // https://stackoverflow.com/a/4232259/386091 // https://stackoverflow.com/a/9507862/386091 if (Environment.OSVersion.Version < new Version(6, 2)) return; // The job name is optional (and can be null) but it helps with diagnostics. // If it's not null, it has to be unique. Use SysInternals' Handle command-line // utility: handle -a ChildProcessTracker string jobName = "ChildProcessTracker" + Process.GetCurrentProcess().Id; s_jobHandle = CreateJobObject(IntPtr.Zero, jobName); var info = new JOBOBJECT_BASIC_LIMIT_INFORMATION { LimitFlags = JOBOBJECTLIMIT.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE }; // This is the key flag. When our process is killed, Windows will automatically // close the job handle, and when that happens, we want the child processes to // be killed, too. var extendedInfo = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION(); extendedInfo.BasicLimitInformation = info; int length = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); IntPtr extendedInfoPtr = Marshal.AllocHGlobal(length); try { Marshal.StructureToPtr(extendedInfo, extendedInfoPtr, false); if (!SetInformationJobObject(s_jobHandle, JobObjectInfoType.ExtendedLimitInformation, extendedInfoPtr, (uint)length)) { throw new Win32Exception(); } } finally { Marshal.FreeHGlobal(extendedInfoPtr); } } [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] static extern IntPtr CreateJobObject(IntPtr lpJobAttributes, string name); [DllImport("kernel32.dll")] static extern bool SetInformationJobObject(IntPtr job, JobObjectInfoType infoType, IntPtr lpJobObjectInfo, uint cbJobObjectInfoLength); [DllImport("kernel32.dll", SetLastError = true)] static extern bool AssignProcessToJobObject(IntPtr job, IntPtr process); // Windows will automatically close any open job handles when our process terminates. // This can be verified by using SysInternals' Handle utility. When the job handle // is closed, the child processes will be killed. private static readonly IntPtr s_jobHandle; } public enum JobObjectInfoType { AssociateCompletionPortInformation = 7, BasicLimitInformation = 2, BasicUIRestrictions = 4, EndOfJobTimeInformation = 6, ExtendedLimitInformation = 9, SecurityLimitInformation = 5, GroupInformation = 11 } [StructLayout(LayoutKind.Sequential)] public struct JOBOBJECT_BASIC_LIMIT_INFORMATION { public Int64 PerProcessUserTimeLimit; public Int64 PerJobUserTimeLimit; public JOBOBJECTLIMIT LimitFlags; public UIntPtr MinimumWorkingSetSize; public UIntPtr MaximumWorkingSetSize; public UInt32 ActiveProcessLimit; public Int64 Affinity; public UInt32 PriorityClass; public UInt32 SchedulingClass; } [Flags] public enum JOBOBJECTLIMIT : uint { JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000 } [StructLayout(LayoutKind.Sequential)] public struct IO_COUNTERS { public UInt64 ReadOperationCount; public UInt64 WriteOperationCount; public UInt64 OtherOperationCount; public UInt64 ReadTransferCount; public UInt64 WriteTransferCount; public UInt64 OtherTransferCount; } [StructLayout(LayoutKind.Sequential)] public struct JOBOBJECT_EXTENDED_LIMIT_INFORMATION { public JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation; public IO_COUNTERS IoInfo; public UIntPtr ProcessMemoryLimit; public UIntPtr JobMemoryLimit; public UIntPtr PeakProcessMemoryUsed; public UIntPtr PeakJobMemoryUsed; } , и регистрировать с помощью него процессы Excel как дочерние: [DllImport("user32.dll")] static extern int GetWindowThreadProcessId(int hWnd, out int lpdwProcessId); var app = new Microsoft.Office.Interop.Excel.Application(); int processId; GetWindowThreadProcessId(app.Hwnd, out processId); ChildProcessTracker.AddProcess(processId); , и тогда ОС будет убивать их вместе с родительским процессом. Рекоммендую ознакомиться с документацией и почитать комментарии в обсуждении по ссылке, могут возникнуть нюансы с поддержкой разных версий ОС и Visual Studio, при запуске с ограниченными правами, при запуске родительского приложения из других процессов. Если нет, то можно ли понять при запуске(повторном) приложения, что в процессах висит мусор, который остался от прошлого запуска и грохнуть эти процессы, не затронув реальные приложения офиса(например, юзер запустил Excel.) Сомневаюсь в существовании надежного метода. Процесс Excel запущенный старым процессом ничем не отличается от процесса запущенного текущим/другими приложениями/пользователем. Можно, как предложил @VadimTagil, написать код, который будет сохранять запущенные процессы в постоянное хранилище (диск/БД/реестр) и убивать их по определенной логике. Еще один вариант, вынести формирование Excel в отдельные процессы-задания, не зависящие от основного приложения. Вообще, если в нормальном ходе работы приложения мусор не остается, то проблемы будут возникать лишь в исключительных случаях (убитый извне процесс), обработку которых можно будет оставить пользователю. Убедитесь, что для всех объектов вызывается Marshal.ReleaseComObject и что все процессы завершаются и, возможно, без задания можно будет обойтись.

Ответ 2



Я полагаю, суть вопроса в методике отделения "мусора" от нормальных процессов Excel. Умея это, уже можно что-то смастерить: удалять мусорные процессы при запуске основной программы, или периодически - фоновым сервисом, когда основная программа закрыта. Наиболее правильное решение: добавлять Id всех создаваемых Interop-процессов в БД, при корректном их завершении - удалять. Соответственно, при некорректном завершении приложения в БД останутся Id мертвых процессов, которые можно на следующем запуске прибить (предварительно убедившись, что это все еще существующие процессы Excel, так как они могли быть прибиты чем-то другим и те же Id уже переиспользованы системой для другой программы). Но, если хочется метод попроще, можно считать "мусором" любой процесс Excel, не имеющий видимого главного окна: using System.Diagnostics; using System.Runtime.InteropServices; static class Program { [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] static extern bool IsWindowVisible(IntPtr hWnd); public static bool IsProcessDead(Process pr) { IntPtr hwnd = pr.MainWindowHandle; if (hwnd == IntPtr.Zero) return true; return !IsWindowVisible(hwnd); } void ClearProcesses() { Process[] prs=Process.GetProcessesByName("excel"); foreach (Process proc in prs) { if(IsProcessDead(proc))proc.Kill(); } } } Если на целевой машине нет других программ, использующих невидимые эксели для своих целей, это можно считать нормальным допущением.

пятница, 13 декабря 2019 г.

Развивается ли СУБД Access? [закрыт]

#sql #access #office


        
             
                
                    
                        
                            Closed. This question is opinion-based. It is not currently
accepting answers.
                            
                        
                    
                
                            
                                
                
                        
                            
                        
                    
                        
                            Want to improve this question? Update the question so
it can be answered with facts and citations by editing this post.
                        
                        Closed 3 года назад.
                                                                                
           
                
        
Например, Microsoft при новых версиях MS SQL Server говорит, что стоимость запросов
уменьшается, производительность повышается и все такое(т.е все круто), а про новые
Access ничего подобного не встречал.

Собственно вопрос, а идет ли какое-либо развитие в плане производительности в СУБД
Access или нет?

Мне довелось использовать  2007,2013,2016 и как стопорились они на запросах, которые
проглатывает на ура MS SQL так и стопорятся.

Мне кажется, что в Access до сих пор не задействует потенциал многоядерных процессоров.

Даже допустимый размер БД с 2гб не повышают.
    


Ответы

Ответ 1



Access - это SOHO - решение - для дома и МАЛОГО офиса, оно не предназначено для поддержки огромных баз. А раз так, то и вкладывать в него усилия R&D нет смысла.

Ответ 2



Думаю что вы правы и как СУБД MS Access не развивается уже лет 10. Но и собственно зачем его развивать? Движение идёт в сторону мобильных, где в качестве БД есть MS SQL Compact. Другое направление развития -- это веб-приложения, но там по части БД довольно широкий выбор. Вот документ, в котором Jet 4.0 объявляется устаревшим: https://msdn.microsoft.com/en-us/library/ms810810.aspx

четверг, 10 января 2019 г.

Массовая смена автора документа

Подскажите как быть)
Предыстория:
У друга на работе уволился админ и на компе, буха, юрика и самого директора, изменил пользователя на Х%Й Булыжников или Пися Камушкин - не суть) Но обнаружили это через неделю или две, когда открыли документы doc, odt,xsl и прочие офисные, нажали файл - Свойства и там увидели сие чудо) а файлы они эти куда-то шлют и там через эту процедуру, даты сверяют в случае чего,)
Как можно массово такие офисные файлы перевладеть (сменить или убрать автора)?)
Руками долго (создать новый док, перекинуть все в него и сохранить - их там сотни документов) естественно, теперь Пользователь нормальный, но документы некоторые остались с пасхалкой)))
Подскажите, как под linux или windows можно автоматически поменять.
Думаю что скриптом можно, но не пойму как работать с api или самой libreOffice из консоли
Спасибо...
p.s. как выяснилось - админа очень обидели... и потом поняли что не правы, но было поздно)
Конвертировать в другой формат, в надежде смены автора так же безсмысленно
libreoffice -env:UserInstallation=file:///home/firefedot/.config/libreoffice-alt --headless --convert-to odt *.doc
В итоге остался тот же автор создания документа и те же авторы изменений.


Ответ

Спасибо @Qwertiy за подсказку.
В общем пока что вариант такой нашел. Сначала приводим все нужные файлы к типу odt, таким образом
libreoffice -env:UserInstallation=file:///home/firefedot/.config/libreoffice-alt --headless --convert-to odt *.doc
Затем можно скриптом, а можно и руками, делаем так. Распаковывем наш файл ODT в папку
unzip file.odt -d folder
После этого ищем и заменяем нужного/ненужного автора
grep NONAMES meta.xml --color # Или если не знаем имени, то grep creator meta.xml --color
И мы увидим нужное нам, оно будет подсвечено)
Заменяем все что нужно
sed -i 's/NONAMES/MyName/g' meta.xml
Затем, мы приводим завершающие действия. Запаковываем все обратно, в два этапа, для правильной структуры нового файла
1:
cd folder zip -0 -X file.odt mimetype
2:
zip -r file.odt * -x mimetype
После этого файл открывается без ошибки и если открыть меню Файл-Свойства, мы увидим того автора,которого внесли.
Осталось проверить как это все работает именами файлов содержащие пробелы.
p.s. если @Qwertiy возражает, то могу все это передать ему в ответ.

вторник, 20 ноября 2018 г.

Возможно ли заставить работать приложение, на ПК, где нет необходимой версии офиса?

Возможно ли заставить работать приложение, на ПК, где нет необходимой версии офиса?
Разрабатывал приложение, где стоял 2016 офис, потом кинул приложение на ПК, где 2007 офис и возникли проблемы при запуске...
На машине стоял 2007 офис и я попробовал кинуть библиотеку(Вроде, от 2016) и получил такую ошибку:
System.IO.FileLoadException: Could not load file or assembly 'office, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c' or one of its dependencies. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040) File name: 'office, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c' WRN: Assembly binding logging is turned OFF. To enable assembly bind failure logging, set the registry value [HKLM\Software\Microsoft\Fusion!EnableLog] (DWORD) to 1. Note: There is some performance penalty associated with assembly bind failure logging. To turn this feature off, remove the registry value [HKLM\Software\Microsoft\Fusion!EnableLog].
От офиса я использую только Access, например выполняю запросы, а где-то открываю RecordSet и делаю обновление по одной записи...


Ответ

У Microsoft для таких случаев есть свободно распространяемые компоненты. Для Access 2016 - можно взять тут. Похожие компоненты есть и для некоторых других приложений из пакета MS Office. Эти компоненты не позволяют создавать файлы, но позволяют просмотреть и напечатать, а если Excel- или Access-файл представляет из себя приложение, то и использовать функции такого приложения. По факту эти компоненты содержат все необходимое кроме интерфейса для создания и редактирования, который вы можете реализовать в свое программе.
Таким образом вы можете не требовать от пользователя наличие установленного офиса, а включить в свой инсталятор такой компонент и устанавливать его вместе с приложением, либо оставить выбор за пользователем, ставить себе полный пакет офис или установить только компоненты для просмотра документов, о чем собственно и написано в описании компонента, например Microsoft Access 2016 Runtime
Microsoft Access 2016 provides a rich platform for developing database management solutions with easy-to-use customization tools. If no end-user customization is required (including report modifications), you can choose to distribute those Access 2016 solutions so that they run without requiring a full installation of Access 2016. To do so, you must package and distribute your application with the Access 2016 Runtime.