#include "HGOfdImpl.hpp" #include "../base/HGInc.h" #include "../base/HGUtility.h" #include "HGString.h" #define A4page_page_PhysicalBox_Width 210.000000 #define A4page_page_PhysicalBox_Height 297.000000 HGOfdReaderImpl::HGOfdReaderImpl() { m_zip = NULL; } HGOfdReaderImpl::~HGOfdReaderImpl() { } HGResult HGOfdReaderImpl::Open(const HGChar* fileName) { if (NULL != m_zip) { return HGBASE_ERR_FAIL; } int error = 0; m_zip = zip_open(StdStringToUtf8(fileName).c_str(), 0, &error); if (NULL == m_zip) { return HGBASE_ERR_FAIL; } std::string content; if (!ReadXml("Doc_0/Document.xml", content)) { zip_close(m_zip); m_zip = NULL; return HGBASE_ERR_FAIL; } tinyxml2::XMLDocument xmlDoc; if (tinyxml2::XML_SUCCESS == xmlDoc.Parse(content.c_str())) { tinyxml2::XMLElement* root = xmlDoc.RootElement(); if (NULL != root) { tinyxml2::XMLElement* pages = root->FirstChildElement("ofd:Pages"); if (NULL != pages) { tinyxml2::XMLElement* page = pages->FirstChildElement("ofd:Page"); if (NULL != page) { const char* attr = page->Attribute("BaseLoc"); if (NULL != attr) m_contentNames.push_back(attr); tinyxml2::XMLElement* p = page->NextSiblingElement("ofd:Page"); while (NULL != p) { const char* attr = p->Attribute("BaseLoc"); if (NULL != attr) m_contentNames.push_back(attr); p = p->NextSiblingElement("ofd:Page"); } } } } } return HGBASE_ERR_OK; } HGResult HGOfdReaderImpl::Close() { if (NULL == m_zip) { return HGBASE_ERR_FAIL; } m_contentNames.clear(); zip_close(m_zip); m_zip = NULL; return HGBASE_ERR_OK; } HGResult HGOfdReaderImpl::GetPageCount(HGUInt* count) { if (NULL == m_zip) { return HGBASE_ERR_FAIL; } if (NULL == count) { return HGBASE_ERR_INVALIDARG; } *count = (HGUInt)m_contentNames.size(); return HGBASE_ERR_OK; } static bool GetRect(const char *text, double data[4]) { bool ret = false; if (NULL == text) { return ret; } char str[256]; strcpy(str, text); int i = 0; char* pStr = strtok(str, " "); if (NULL != pStr) { data[i] = atof(pStr); ++i; } while (i < 4) { pStr = strtok(NULL, " "); if (NULL == pStr) break; data[i] = atof(pStr); ++i; } return (4 == i); } HGResult HGOfdReaderImpl::GetPageInfo(HGUInt page, HGOfdPageInfo* info) { if (NULL == m_zip) { return HGBASE_ERR_FAIL; } if (page >= (HGUInt)m_contentNames.size() || NULL == info) { return HGBASE_ERR_INVALIDARG; } char name[128]; sprintf(name, "Doc_0/%s", m_contentNames[page].c_str()); std::string content; if (!ReadXml(name, content)) { return HGBASE_ERR_FAIL; } tinyxml2::XMLDocument xmlDoc; std::string resId; if (tinyxml2::XML_SUCCESS == xmlDoc.Parse(content.c_str())) { tinyxml2::XMLElement* root = xmlDoc.RootElement(); if (NULL != root) { tinyxml2::XMLElement* content = root->FirstChildElement("ofd:Content"); if (NULL != content) { tinyxml2::XMLElement* layer = content->FirstChildElement("ofd:Layer"); if (NULL != layer) { const char* attr = layer->Attribute("Type"); #if defined(HG_CMP_MSC) if (NULL == attr || 0 != _stricmp("Background", attr)) #else if (NULL == attr || 0 != strcasecmp("Background", attr)) #endif { tinyxml2::XMLElement* p = layer->NextSiblingElement("ofd:Layer"); while (NULL != p) { const char* attr = p->Attribute("Type"); #if defined(HG_CMP_MSC) if (NULL != attr && 0 == _stricmp("Background", attr)) #else if (NULL != attr && 0 == strcasecmp("Background", attr)) #endif { break; } p = p->NextSiblingElement("ofd:Layer"); } layer = p; } if (NULL != layer) { tinyxml2::XMLElement* imgObject = layer->FirstChildElement("ofd:ImageObject"); if (NULL != imgObject) { resId = imgObject->Attribute("ResourceID"); } } } } } } if (resId.empty()) { return HGBASE_ERR_FAIL; } if (!ReadXml("Doc_0/DocumentRes.xml", content)) { return HGBASE_ERR_FAIL; } std::string imgName; if (tinyxml2::XML_SUCCESS == xmlDoc.Parse(content.c_str())) { tinyxml2::XMLElement* root = xmlDoc.RootElement(); if (NULL != root) { tinyxml2::XMLElement* multiMedias = root->FirstChildElement("ofd:MultiMedias"); if (NULL != multiMedias) { tinyxml2::XMLElement* multiMedia = multiMedias->FirstChildElement("ofd:MultiMedia"); if (NULL != multiMedia) { const char* attr = multiMedia->Attribute("ID"); #if defined(HG_CMP_MSC) if (NULL == attr || 0 != _stricmp(resId.c_str(), attr)) #else if (NULL == attr || 0 != strcasecmp(resId.c_str(), attr)) #endif { tinyxml2::XMLElement* p = multiMedia->NextSiblingElement("ofd:MultiMedia"); while (NULL != p) { const char* attr = p->Attribute("ID"); #if defined(HG_CMP_MSC) if (NULL != attr && 0 == _stricmp(resId.c_str(), attr)) #else if (NULL != attr && 0 == strcasecmp(resId.c_str(), attr)) #endif { break; } p = p->NextSiblingElement("ofd:MultiMedia"); } multiMedia = p; } if (NULL != multiMedia) { tinyxml2::XMLElement* mediaFile = multiMedia->FirstChildElement("ofd:MediaFile"); if (NULL != mediaFile) { imgName = mediaFile->GetText(); } } } } } } if (imgName.empty()) { return HGBASE_ERR_FAIL; } char img_name[128]; sprintf(img_name, "Doc_0/Res/%s", imgName.c_str()); HGJpegLoadInfo jpegInfo; if (!ReadJpeg(img_name, &jpegInfo, 0, 0, 0, 0, NULL)) { return HGBASE_ERR_FAIL; } info->width = jpegInfo.width; info->height = jpegInfo.height; info->bpp = jpegInfo.numComponents * 8; return HGBASE_ERR_OK; } HGResult HGOfdReaderImpl::LoadImage(HGUInt page, HGFloat xScale, HGFloat yScale, HGUInt imgType, HGUInt imgOrigin, HGImage* image) { if (NULL == m_zip) { return HGBASE_ERR_FAIL; } if (HGBASE_IMGORIGIN_TOP != imgOrigin && HGBASE_IMGORIGIN_BOTTOM != imgOrigin) { return HGBASE_ERR_INVALIDARG; } if (page >= (HGUInt)m_contentNames.size() || NULL == image) { return HGBASE_ERR_INVALIDARG; } char name[128]; sprintf(name, "Doc_0/%s", m_contentNames[page].c_str()); std::string content; if (!ReadXml(name, content)) { return HGBASE_ERR_FAIL; } tinyxml2::XMLDocument xmlDoc; std::string resId; if (tinyxml2::XML_SUCCESS == xmlDoc.Parse(content.c_str())) { tinyxml2::XMLElement* root = xmlDoc.RootElement(); if (NULL != root) { tinyxml2::XMLElement* content = root->FirstChildElement("ofd:Content"); if (NULL != content) { tinyxml2::XMLElement* layer = content->FirstChildElement("ofd:Layer"); if (NULL != layer) { const char* attr = layer->Attribute("Type"); #if defined(HG_CMP_MSC) if (NULL == attr || 0 != _stricmp("Background", attr)) #else if (NULL == attr || 0 != strcasecmp("Background", attr)) #endif { tinyxml2::XMLElement* p = layer->NextSiblingElement("ofd:Layer"); while (NULL != p) { const char* attr = p->Attribute("Type"); #if defined(HG_CMP_MSC) if (NULL != attr && 0 == _stricmp("Background", attr)) #else if (NULL != attr && 0 == strcasecmp("Background", attr)) #endif { break; } p = p->NextSiblingElement("ofd:Layer"); } layer = p; } if (NULL != layer) { tinyxml2::XMLElement* imgObject = layer->FirstChildElement("ofd:ImageObject"); if (NULL != imgObject) { resId = imgObject->Attribute("ResourceID"); } } } } } } if (resId.empty()) { return HGBASE_ERR_FAIL; } if (!ReadXml("Doc_0/DocumentRes.xml", content)) { return HGBASE_ERR_FAIL; } std::string imgName; if (tinyxml2::XML_SUCCESS == xmlDoc.Parse(content.c_str())) { tinyxml2::XMLElement* root = xmlDoc.RootElement(); if (NULL != root) { tinyxml2::XMLElement* multiMedias = root->FirstChildElement("ofd:MultiMedias"); if (NULL != multiMedias) { tinyxml2::XMLElement* multiMedia = multiMedias->FirstChildElement("ofd:MultiMedia"); if (NULL != multiMedia) { const char* attr = multiMedia->Attribute("ID"); #if defined(HG_CMP_MSC) if (NULL == attr || 0 != _stricmp(resId.c_str(), attr)) #else if (NULL == attr || 0 != strcasecmp(resId.c_str(), attr)) #endif { tinyxml2::XMLElement* p = multiMedia->NextSiblingElement("ofd:MultiMedia"); while (NULL != p) { const char* attr = p->Attribute("ID"); #if defined(HG_CMP_MSC) if (NULL != attr && 0 == _stricmp(resId.c_str(), attr)) #else if (NULL != attr && 0 == strcasecmp(resId.c_str(), attr)) #endif { break; } p = p->NextSiblingElement("ofd:MultiMedia"); } multiMedia = p; } if (NULL != multiMedia) { tinyxml2::XMLElement* mediaFile = multiMedia->FirstChildElement("ofd:MediaFile"); if (NULL != mediaFile) { imgName = mediaFile->GetText(); } } } } } } if (imgName.empty()) { return HGBASE_ERR_FAIL; } char img_name[128]; sprintf(img_name, "Doc_0/Res/%s", imgName.c_str()); if (!ReadJpeg(img_name, NULL, xScale, yScale, imgType, imgOrigin, image)) { return HGBASE_ERR_FAIL; } return HGBASE_ERR_OK; } bool HGOfdReaderImpl::ReadXml(const char* name, std::string& content) { struct zip_stat st; zip_stat_init(&st); zip_stat(m_zip, name, ZIP_FL_NOCASE, &st); zip_int64_t size = st.size; if (0 == size) { return false; } zip_file* file = zip_fopen(m_zip, name, ZIP_FL_NOCASE); if (NULL == file) { return false; } char* s = (char*)malloc((size_t)size + 1); if (NULL == s) { zip_fclose(file); return false; } zip_int64_t did_read = zip_fread(file, s, size); if (did_read != size) { free(s); zip_fclose(file); return false; } s[size] = 0; content = s; free(s); zip_fclose(file); return true; } bool HGOfdReaderImpl::ReadJpeg(const char* name, HGJpegLoadInfo* info, HGFloat xScale, HGFloat yScale, HGUInt imgType, HGUInt imgOrigin, HGImage* image) { struct zip_stat st; zip_stat_init(&st); zip_stat(m_zip, name, ZIP_FL_NOCASE, &st); zip_int64_t size = st.size; if (0 == size) { return false; } zip_file* file = zip_fopen(m_zip, name, ZIP_FL_NOCASE); if (NULL == file) { return false; } unsigned char* content = (unsigned char*)malloc((size_t)size); if (NULL == content) { zip_fclose(file); return false; } zip_int64_t did_read = zip_fread(file, content, size); if (did_read != size) { free(content); zip_fclose(file); return false; } HGBuffer buffer = NULL; HGBase_CreateBufferWithData(content, (size_t)size, &buffer); HGResult ret = HGImgFmt_LoadJpegImageFromBuffer(buffer, info, imgType, imgOrigin, image); HGBase_DestroyBuffer(buffer); free(content); zip_fclose(file); return (HGBASE_ERR_OK == ret); } HGOfdImageWriterImpl::HGOfdImageWriterImpl() { m_zip = NULL; m_curImgIndex = 0; } HGOfdImageWriterImpl::~HGOfdImageWriterImpl() { } HGResult HGOfdImageWriterImpl::Open(const HGChar* fileName) { if (NULL != m_zip) { return HGBASE_ERR_FAIL; } int error = 0; m_zip = zip_open(StdStringToUtf8(fileName).c_str(), ZIP_CREATE | ZIP_TRUNCATE, &error); if (NULL == m_zip) { return HGBASE_ERR_FAIL; } zip_add_dir(m_zip, "Doc_0"); zip_add_dir(m_zip, "Doc_0/Pages"); zip_add_dir(m_zip, "Doc_0/Res"); if (!AddOfdXml() || !AddPublicResXml()) { zip_close(m_zip); m_zip = NULL; return HGBASE_ERR_FAIL; } return HGBASE_ERR_OK; } HGResult HGOfdImageWriterImpl::Close() { if (NULL == m_zip) { return HGBASE_ERR_FAIL; } AddDocXml(); AddDocResXml(); zip_close(m_zip); m_zip = NULL; // 清理临时文件 std::list::const_iterator iter; for (iter = m_tmpFiles.begin(); iter != m_tmpFiles.end(); ++iter) { HGBase_DeleteFile(iter->c_str()); } m_tmpFiles.clear(); return HGBASE_ERR_OK; } HGResult HGOfdImageWriterImpl::SaveJpegImage(HGImage image, const HGJpegSaveInfo* info) { HGChar name[128]; sprintf(name, "Doc_0/Res/image_%u.jpg", m_curImgIndex); if (!AddJpegImageFile(image, info, name)) { return HGBASE_ERR_FAIL; } HGUInt xDpi, yDpi; HGBase_GetImageDpi(image, &xDpi, &yDpi); if (NULL != info) { if (HGIMGFMT_JPEGDENUNIT_INCH == info->densityUnit) { xDpi = info->xDensity; yDpi = info->yDensity; } else if (HGIMGFMT_JPEGDENUNIT_CENTIMETER == info->densityUnit) { xDpi = (uint32_t)((double)info->xDensity / 0.393700787 + 0.5); yDpi = (uint32_t)((double)info->yDensity / 0.393700787 + 0.5); } } HGImageInfo imgInfo; HGBase_GetImageInfo(image, &imgInfo); HGFloat physicalWidth = 25.4f * (HGFloat)imgInfo.width / (HGFloat)xDpi; HGFloat physicalHeight = 25.4f * (HGFloat)imgInfo.height / (HGFloat)yDpi; AddContentXmlFile(m_curImgIndex, physicalWidth, physicalHeight); ++m_curImgIndex; return HGBASE_ERR_OK; } bool HGOfdImageWriterImpl::AddOfdXml() { tinyxml2::XMLDocument xmlDoc; HGChar uuid[128]; HGBase_GetUuid(uuid, 128); time_t tm = time(NULL); struct tm *local_tm = localtime(&tm); char local_tm_str[256]; strftime(local_tm_str, 256, "%c", local_tm); tinyxml2::XMLElement *root = xmlDoc.NewElement("ofd:OFD"); root->SetAttribute("xmlns:ofd", "http://www.ofdspec.org/2016"); root->SetAttribute("DocType", "OFD"); root->SetAttribute("Version", "1.0"); xmlDoc.InsertEndChild(root); tinyxml2::XMLElement* docBody = xmlDoc.NewElement("ofd:DocBody"); root->InsertEndChild(docBody); tinyxml2::XMLElement* docRoot = xmlDoc.NewElement("ofd:DocRoot"); docRoot->SetText("Doc_0/Document.xml"); docBody->InsertEndChild(docRoot); tinyxml2::XMLElement* docInfo = xmlDoc.NewElement("ofd:DocInfo"); docBody->InsertEndChild(docInfo); tinyxml2::XMLElement* docId = xmlDoc.NewElement("ofd:DocID"); docId->SetText(uuid); docInfo->InsertEndChild(docId); tinyxml2::XMLElement* creationDate = xmlDoc.NewElement("ofd:CreationDate"); creationDate->SetText(local_tm_str); docInfo->InsertEndChild(creationDate); tinyxml2::XMLElement* modDate = xmlDoc.NewElement("ofd:ModDate"); modDate->SetText(local_tm_str); docInfo->InsertEndChild(modDate); tinyxml2::XMLElement* creator = xmlDoc.NewElement("ofd:Creator"); creator->SetText("ofd"); docInfo->InsertEndChild(creator); tinyxml2::XMLElement* createVersion = xmlDoc.NewElement("ofd:CreatorVersion"); createVersion->SetText("1.0.0"); docInfo->InsertEndChild(createVersion); return AddXmlFile(xmlDoc, "OFD.xml"); } bool HGOfdImageWriterImpl::AddDocXml() { tinyxml2::XMLDocument xmlDoc; tinyxml2::XMLElement* root = xmlDoc.NewElement("ofd:Document"); root->SetAttribute("xmlns:ofd", "http://www.ofdspec.org/2016"); xmlDoc.InsertEndChild(root); tinyxml2::XMLElement* commonData = xmlDoc.NewElement("ofd:CommonData"); root->InsertEndChild(commonData); tinyxml2::XMLElement* maxUnitID = xmlDoc.NewElement("ofd:MaxUnitID"); HGChar maxId[24]; sprintf(maxId, "%u", m_curImgIndex * 10 + 2); maxUnitID->SetText(maxId); commonData->InsertEndChild(maxUnitID); tinyxml2::XMLElement* pageArea = xmlDoc.NewElement("ofd:PageArea"); commonData->InsertEndChild(pageArea); tinyxml2::XMLElement* publicRes = xmlDoc.NewElement("ofd:PublicRes"); publicRes->SetText("PublicRes.xml"); commonData->InsertEndChild(publicRes); tinyxml2::XMLElement* documentRes = xmlDoc.NewElement("ofd:DocumentRes"); documentRes->SetText("DocumentRes.xml"); commonData->InsertEndChild(documentRes); tinyxml2::XMLElement* physicalBox = xmlDoc.NewElement("ofd:PhysicalBox"); char physicalBoxText[512]; sprintf(physicalBoxText, "0.000000 0.000000 %f %f", A4page_page_PhysicalBox_Width, A4page_page_PhysicalBox_Height); physicalBox->SetText(physicalBoxText); pageArea->InsertEndChild(physicalBox); tinyxml2::XMLElement* pages = xmlDoc.NewElement("ofd:Pages"); root->InsertEndChild(pages); for (HGUInt i = 0; i < m_curImgIndex; ++i) { tinyxml2::XMLElement* page = xmlDoc.NewElement("ofd:Page"); HGChar id[24]; sprintf(id, "%u", i * 10 + 1); page->SetAttribute("ID", id); HGChar loc[128]; sprintf(loc, "Pages/Page_%u/Content.xml", i); page->SetAttribute("BaseLoc", loc); pages->InsertEndChild(page); } return AddXmlFile(xmlDoc, "Doc_0/Document.xml"); } bool HGOfdImageWriterImpl::AddDocResXml() { tinyxml2::XMLDocument xmlDoc; tinyxml2::XMLElement* root = xmlDoc.NewElement("ofd:Res"); root->SetAttribute("xmlns:ofd", "http://www.ofdspec.org/2016"); root->SetAttribute("BaseLoc", "Res"); xmlDoc.InsertEndChild(root); tinyxml2::XMLElement* multiMedias = xmlDoc.NewElement("ofd:MultiMedias"); root->InsertEndChild(multiMedias); for (HGUInt i = 0; i < m_curImgIndex; ++i) { tinyxml2::XMLElement* multiMedia = xmlDoc.NewElement("ofd:MultiMedia"); multiMedia->SetAttribute("Type", "Image"); HGChar id[24]; sprintf(id, "%u", i * 10 + 2); multiMedia->SetAttribute("ID", id); multiMedias->InsertEndChild(multiMedia); tinyxml2::XMLElement* mediaFile = xmlDoc.NewElement("ofd:MediaFile"); HGChar loc[128]; sprintf(loc, "image_%u.jpg", i); mediaFile->SetText(loc); multiMedia->InsertEndChild(mediaFile); } return AddXmlFile(xmlDoc, "Doc_0/DocumentRes.xml"); } bool HGOfdImageWriterImpl::AddPublicResXml() { tinyxml2::XMLDocument xmlDoc; tinyxml2::XMLElement* root = xmlDoc.NewElement("ofd:Res"); root->SetAttribute("xmlns:ofd", "http://www.ofdspec.org/2016"); root->SetAttribute("BaseLoc", "Res"); xmlDoc.InsertEndChild(root); tinyxml2::XMLElement* fonts = xmlDoc.NewElement("ofd:Fonts"); root->InsertEndChild(fonts); return AddXmlFile(xmlDoc, "Doc_0/PublicRes.xml"); } bool HGOfdImageWriterImpl::AddXmlFile(tinyxml2::XMLDocument& xmlDoc, const HGChar* name) { HGChar tmpName[256]; HGBase_GetTmpFileName(NULL, tmpName, 256); if (tinyxml2::XML_SUCCESS != xmlDoc.SaveFile(tmpName)) { return false; } zip_source_t* s = zip_source_file(m_zip, tmpName, 0, 0); if (NULL == s) { HGBase_DeleteFile(tmpName); return false; } zip_int64_t ret = zip_file_add(m_zip, name, s, ZIP_FL_ENC_UTF_8 | ZIP_FL_OVERWRITE); if (ret < 0) { zip_source_free(s); HGBase_DeleteFile(tmpName); return false; } m_tmpFiles.push_back(tmpName); return true; } bool HGOfdImageWriterImpl::AddJpegImageFile(HGImage image, const HGJpegSaveInfo* info, const HGChar* name) { HGChar tmpName[256]; HGBase_GetTmpFileName(NULL, tmpName, 256); if (HGBASE_ERR_OK != HGImgFmt_SaveJpegImage(image, info, tmpName)) { return false; } zip_source_t* s = zip_source_file(m_zip, tmpName, 0, 0); if (NULL == s) { HGBase_DeleteFile(tmpName); return false; } zip_int64_t ret = zip_file_add(m_zip, name, s, ZIP_FL_OVERWRITE); if (ret < 0) { zip_source_free(s); HGBase_DeleteFile(tmpName); return false; } m_tmpFiles.push_back(tmpName); return true; } bool HGOfdImageWriterImpl::AddContentXmlFile(HGUInt index, HGFloat physicalWidth, HGFloat physicalHeight) { HGChar dir[128]; sprintf(dir, "Doc_0/Pages/Page_%u", index); zip_add_dir(m_zip, dir); tinyxml2::XMLDocument xmlDoc; tinyxml2::XMLElement* root = xmlDoc.NewElement("ofd:Page"); root->SetAttribute("xmlns:ofd", "http://www.ofdspec.org/2016"); xmlDoc.InsertEndChild(root); tinyxml2::XMLElement* area = xmlDoc.NewElement("ofd:Area"); root->InsertEndChild(area); tinyxml2::XMLElement* physicalBox = xmlDoc.NewElement("ofd:PhysicalBox"); char physicalBoxText[512]; sprintf(physicalBoxText, "0.000000 0.000000 %f %f", physicalWidth, physicalHeight); physicalBox->SetText(physicalBoxText); area->InsertEndChild(physicalBox); tinyxml2::XMLElement* content = xmlDoc.NewElement("ofd:Content"); root->InsertEndChild(content); tinyxml2::XMLElement* layer = xmlDoc.NewElement("ofd:Layer"); HGChar layerId[24]; sprintf(layerId, "%u", index * 10 + 3); layer->SetAttribute("ID", layerId); layer->SetAttribute("Type", "Background"); content->InsertEndChild(layer); tinyxml2::XMLElement* imgObject = xmlDoc.NewElement("ofd:ImageObject"); HGChar imgObjectId[24]; sprintf(imgObjectId, "%u", index * 10 + 4); imgObject->SetAttribute("ID", imgObjectId); char boundaryText[512]; sprintf(boundaryText, "0.000000 0.000000 %f %f", physicalWidth, physicalHeight); imgObject->SetAttribute("Boundary", boundaryText); HGChar imgObjectResId[24]; sprintf(imgObjectResId, "%u", index * 10 + 2); imgObject->SetAttribute("ResourceID", imgObjectResId); char ctmText[512]; sprintf(ctmText, "%f 0 0 %f 0 0", physicalWidth, physicalHeight); imgObject->SetAttribute("CTM", ctmText); layer->InsertEndChild(imgObject); HGChar name[256]; sprintf(name, "%s/Content.xml", dir); return AddXmlFile(xmlDoc, name); }