3

为了从日志记录脚本收集输出,我想使用onepy将信息添加到 OneNote 2013 笔记本。不幸的是,update_page_content()onepy提供的方法对我不起作用。为了更深入地理解这个问题,我切换到 C#,那里有许多 OneNote API 的在线示例,经过一些麻烦,我设法让以下简约 C# 示例工作:

using System;
using OneNote = Microsoft.Office.Interop.OneNote;

class Program
{
    static void Main(string[] args)
    {
        OneNote.Application onenoteApp = new OneNote.Application();
        string xml = "<one:Page xmlns:one=\"http://schemas.microsoft.com/office/onenote/2013/onenote\" ...> ... </one:Page>";
        onenoteApp.UpdatePageContent(xml, DateTime.MinValue);
    }
}

该字符串xml是通过修改 XML 文档获得的,该文档是使用该GetPageContent方法从 OneNote 中检索的,如我之前链接的问题中所述。这个问题的具体内容xml无关紧要,唯一重要的是,上述程序多次运行都没有问题,并且对现有 OneNote 页面的更改总是成功执行。

现在转到 Python,我尝试在不进行实质性更改的情况下翻译我的简约程序。我的结果如下所示:

import win32com
import pytz
import datetime

onenote_app = win32com.client.Dispatch('OneNote.Application.15')
xml = "<one:Page xmlns:one=\"http://schemas.microsoft.com/office/onenote/2013/onenote\" ...> ... </one:Page>"
date = pytz.utc.localize(datetime.datetime.fromordinal(1))
onenote_app.UpdatePageContent(xml, date)

我试图非常小心地为这两个变量使用相同的值。当然,这两个字符串的内容xml是相同的(复制粘贴)。此外,根据 VS2015 调试器,两者都DateTime.MinValuedate同一日期。但是,当我执行 python 程序时,我收到了这个非常无益的错误。

    135         def UpdatePageContent(self, bstrPageChangesXmlIn=defaultNamedNotOptArg, dateExpectedLastModified=(1899, 12, 30, 0, 0, 0, 5, 364, 0), xsSchema=2, force=False):
    136         return self._oleobj_.InvokeTypes(1610743816, LCID, 1, (24, 0), ((8, 1), (7, 49), (3, 49), (11, 49)),bstrPageChangesXmlIn
--> 137             , dateExpectedLastModified, xsSchema, force)
    138 
    139     _prop_map_get_ = {

com_error: (-2147352567, 'Exception occurred.', (0, None, None, None, 0, -2147213296), None)

据我了解,C# 和 Python 实际上都使用同一个库来执行它们的调用(都称为Microsoft OneNote 15.0 Object Library)。因此,原则上这两个程序都应该可以正常工作。如果我在这一点上没有记错的话,我会假设 Python在调用库时做了一些不同的事情。我如何进一步追踪这里的实际问题是什么?有没有办法使用 Visual Studio 2015 的内置 Python 支持来更好地理解 C# 和 Python 代码之间的区别?

4

2 回答 2

2

正如 jayongg 在他的回答中已经指出的那样,我的问题是作为参数传递给UpdatePageContent()调用的日期。但是,他建议将零作为最后修改日期并禁用文档中详述的日期检查并不像看起来那么简单。在我的回答中,我将详细说明我遇到的所有陷阱。

这个问题显示了第一个问题。例如,如果尝试将datetime对象或普通整数传递给UpdatePageContent(),则会引发如下错误。

    135         def UpdatePageContent(self, bstrPageChangesXmlIn=defaultNamedNotOptArg, dateExpectedLastModified=(1899, 12, 30, 0, 0, 0, 5, 364, 0), xsSchema=2, force=False):
    136         return self._oleobj_.InvokeTypes(1610743816, LCID, 1, (24, 0), ((8, 1), (7, 49), (3, 49), (11, 49)),bstrPageChangesXmlIn
--> 137             , dateExpectedLastModified, xsSchema, force)
    138 
    139     _prop_map_get_ = {

ValueError: astimezone() cannot be applied to a naive datetime

特别是,将 date 参数留空并使用默认值不会按预期工作:

    135         def UpdatePageContent(self, bstrPageChangesXmlIn=defaultNamedNotOptArg, dateExpectedLastModified=(1899, 12, 30, 0, 0, 0, 5, 364, 0), xsSchema=2, force=False):
    136         return self._oleobj_.InvokeTypes(1610743816, LCID, 1, (24, 0), ((8, 1), (7, 49), (3, 49), (11, 49)),bstrPageChangesXmlIn
--> 137             , dateExpectedLastModified, xsSchema, force)
    138 
    139     _prop_map_get_ = {

TypeError: must be a pywintypes time object (got tuple)

显然,python API 调用的一些内部结构搞砸了。如果遵循onepy 项目页面上的说明并使用makepy预生成 API 包装器,则可以检查负责这些调用的源文件,但至少对我来说这不是很有指导意义。我怀疑该InvokeTypes方法试图将传递的日期转换为 API 可以理解的格式,但 Python 包装器本身并未实现必要的要求。

幸运的是,有一个非常简单的解决方法。诀窍是让传递的datetime对象时区感知。为此,必须首先对其进行本地化。例如,这可以使用pytz模块来完成。

import datetime
import pytz

date = pytz.utc.localize(datetime.datetime.fromordinal(1))

有了这些知识,API 调用就可以工作了,但是我的问题中的 COM 错误被提出了。这就是 jayongg 的答案所在:我必须将零作为上次修改日期传递(或者我需要知道上次修改发生的日期,这也应该有效)。现在棘手的问题是:什么是零?

在我的 C# 代码中,这个日期由 给出DateTime.MinValue,根据 Visual Studio 2015,它等于 0001-01-01 00:00:00。Python 中的相同日期是datetime.datetime.fromordinal(1),但它仍然不能使调用工作。我不得不怀疑 Visual Studio 提供的信息是错误的,或者在两者之间发生了一些魔法,可能是类型转换 VSDate -> Int -> APIDate。

那么我如何找出正确的零日期呢?原来答案已经存在,只需要知道去哪里找。如果在 Python API 包装器中进行检查,则会为相关参数提供一个默认值:

dateExpectedLastModified=(1899, 12, 30, 0, 0, 0, 5, 364, 0)

可以使用以下代码段获得相同的结果。

>> date = pytz.utc.localize(datetime.datetime(year=1899, month=12, day=30))
>> print(tuple(date.timetuple()))
(1899, 12, 30, 0, 0, 0, 5, 364, 0)

瞧,将变量 date 传递给调用就可以了。

>> onenote_app.UpdatePageContent(xml, date)
>>

完全有道理,对吧?我的意思是,除了 1899 年 12 月 30 日,你还会通过哪个日期?其实还是有点道理的。这一天是都柏林朱利安日期零点的前一天。根据德语Wikipedia 文章,Excel 将这一天用作日期的零点,因此 OneNote 这样做听起来似乎是合理的。

为什么会有一日之差?显然,1900 年被误认为闰年,但事实并非如此。因此 1899-12-31 被转移到 1899-12-30,这正是 API 想要的。叹。为什么关于如何存储日期有这么多不同的协议?哦,顺便提一下。Office for Mac 使用 1904 作为零点。嗯,当然咯。

于 2016-01-24T18:25:13.363 回答
0

您的错误代码(-2147213296)是 0x80042010,即:

hrLastModifiedDateDidNotMatch

0x80042010

上次修改日期不匹配。

https://msdn.microsoft.com/en-us/library/office/ff966472(v=office.14).aspx

您可以尝试传递最后修改日期 0。来自:https ://msdn.microsoft.com/en-us/library/office/gg649853(v= office.14).aspx:dateExpectedLastModified—(可选)日期和您认为要更新的页面上次修改的时间。如果您为此参数传递一个非零值,则仅当您传递的值与页面上次修改的实际日期和时间相匹配时,OneNote 才会继续更新。传递此参数的值有助于防止意外覆盖用户自上次修改页面以来所做的编辑。

于 2016-01-20T22:37:45.520 回答