diff --git a/modules/importer/conf/module.xml b/modules/importer/conf/module.xml index 992495418..db64b2017 100644 --- a/modules/importer/conf/module.xml +++ b/modules/importer/conf/module.xml @@ -7,5 +7,6 @@ + diff --git a/modules/importer/importer.admin.controller.php b/modules/importer/importer.admin.controller.php index 8b65ce085..f2267357f 100644 --- a/modules/importer/importer.admin.controller.php +++ b/modules/importer/importer.admin.controller.php @@ -17,6 +17,8 @@ var $oCommentModel = null; var $oTrackbackController = null; + var $oXmlParser = null; + /** * @brief 초기화 **/ @@ -380,6 +382,9 @@ return true; } + /** + * @brief module.xml 형식의 데이터 import + **/ function procImporterAdminModuleImport() { set_time_limit(0); @@ -441,6 +446,7 @@ // 아이템 종료시 DB 입력 } else if( $str == '') { + $obj->member_srl = 0; if($this->importDocument($obj)) $inserted_count ++; if($inserted_count >= 50) { $manual_break = true; @@ -584,7 +590,7 @@ } /** - * @brief 댓글 정리 + * @brief 첨부파일 정리 **/ function importAttaches($fp) { $attaches = array(); @@ -710,7 +716,7 @@ } // 게시글 등록 - $obj->member_srl = 0; + //$obj->member_srl = 0; $obj->password_is_hashed = true; $output = $this->oDocumentController->insertDocument($obj, true); @@ -788,8 +794,14 @@ foreach($trackbacks as $key => $val) { $val->module_srl = $obj->module_srl; $val->document_srl = $obj->document_srl; - $trackback_output = $this->oTrackbackController->insertTrackback($val, true); + $val->trackback_srl = getNextSequence(); + $val->list_order = $val->trackback_srl*-1; + $trackback_output = executeQuery('trackback.insertTrackback', $val); + //$trackback_output = $this->oTrackbackController->insertTrackback($val, true); } + $oTrackbackModel = &getModel('trackback'); + $trackback_count = $oTrackbackModel->getTrackbackCount($obj->document_srl); + $this->oDocumentController->updateTrackbackCount($obj->document_srl, $trackback_count); } return true; @@ -868,5 +880,293 @@ fclose($f); return $temp_filename; } + + /** + * @brief TTXML import + **/ + function procImporterAdminTTXMLImport() { + set_time_limit(0); + + $xml_file = Context::get('xml_file'); + $target_module = Context::get('target_module'); + $user_id = Context::get('user_id'); + if(!$user_id) return new Object(-1,'msg_invalid_request'); + + $oMemberModel = &getModel('member'); + $member_info = $oMemberModel->getMemberInfoByUserID($user_id); + + $total_count = 0; + $success_count = 0; + + // xml_file 경로가 없으면 에러~ + if(!$xml_file) return new Object(-1, 'msg_no_xml_file'); + + // local 파일 지정인데 파일이 없으면 역시 에러~ + if(!eregi('^http:',$xml_file) && (!eregi("\.xml$", $xml_file) || !file_exists($xml_file)) ) return new Object(-1,'msg_no_xml_file'); + + // 필요한 객체 미리 생성 + $this->oDocumentController = &getController('document'); + $this->oDocumentModel = &getModel('document'); + $this->oCommentController = &getController('comment'); + $this->oCommentModel = &getModel('comment'); + $this->oTrackbackController = &getController('trackback'); + + // 타켓 모듈의 유무 체크 + if(!$target_module) return new Object(-1,'msg_invalid_request'); + $module_srl = $target_module; + + // 타겟 모듈의 카테고리 정보 구함 + $category_list = $this->oDocumentModel->getCategoryList($module_srl); + if(count($category_list)) { + foreach($category_list as $key => $val) $category_titles[$val->title] = $val->category_srl; + } else { + $category_list = $category_titles = array(); + } + + // 이제부터 데이터를 가져오면서 처리 + $fp = $this->getFilePoint($xml_file); + if(!$fp) return new Object(-1,'msg_no_xml_file'); + + $manual_break = false; + $obj = null; + $attaches = array(); + $document_srl = null; + + $inserted_count = 0; + + $this->oXmlParser = new XmlParser(); + + // 본문 데이터부터 처리 시작 + $i=0; + $started = $category_started = $post_started = false; + $category_buff = ''; + while(!feof($fp)) { + $str = fgets($fp, 1024); + + if(substr($str,0,7)=='') $str = substr($str,7); + if(substr($str,0,6)=='') $str = substr($str,6); + + if(substr($str,0,10)=='' || substr($str,0,6) == '') { + $category_started = true; + $category_buff .= $str; + } + + // ') { + $str = substr($str,7); + + // 게시글 처리 + $post_buff .= ''; + $xml_doc = $this->oXmlParser->parse($post_buff); + $post = $xml_doc->post; + + $obj = null; + $obj->module_srl = $module_srl; + $obj->document_srl = getNextSequence(); + $obj->title = $post->title->body; + $obj->content = str_replace('[##_ATTACH_PATH_##]/','',$post->content->body); + $obj->password = md5($post->password->body); + $obj->allow_comment = $post->acceptcomment->body==1?'Y':'N'; + $obj->allow_trackback= $post->accepttrackback->body==1?'Y':'N'; + $obj->regdate = date("YmdHis",$post->created->body); + $obj->last_update = date("YmdHis",$post->modified->body); + + $category = trim($post->category->body); + if($category) { + $tmp_category = explode('/',$category); + if(count($tmp_category)>1) $category = $tmp_category[1]; + $obj->category_srl = (int)$category_titles[$category]; + if(!$obj->category_srl) { + $tmp = array_values($category_titles); + $obj->category_srl = (int)$tmp[0]; + } + } + $obj->user_id = $member_info->user_id; + $obj->nick_name = $member_info->nick_name; + $obj->user_name = $member_info->user_name; + $obj->member_srl = $member_info->member_srl; + + // 댓글 + $obj->comments = $this->importTTComment($post->comment); + + // 꼬리표 + $tags = $post->tag; + if(!is_array($tags)) $tags = array($tags); + if(count($tags)) { + $tag_list = array(); + foreach($tags as $key => $val) { + $tag = trim($val->body); + if(!$tag) continue; + $tag_list[] = $tag; + } + if(count($tag_list)) $obj->tags = implode(',',$tag_list); + } + + // 엮인글 + if($post->trackback) { + $trackbacks = $post->trackback; + if(!is_array($trackbacks)) $trackbacks = array($trackbacks); + if(count($trackbacks)) { + foreach($trackbacks as $key => $val) { + $tobj = null; + $tobj->url = $val->url->body; + $tobj->title = $val->title->body; + $tobj->blog_name = $val->site->body; + $tobj->excerpt = $val->excerpt->body; + $tobj->regdate = date("YmdHis",$val->received->body); + $tobj->ipaddress = $val->ip->body; + $obj->trackbacks[] = $tobj; + } + } + } + + // 첨부파일 + $obj->attaches = $attaches; + + $total_count ++; + if($this->importDocument($obj)) $success_count ++; + + // 새로운 게시글을 위한 처리 + $post_started = false; + $obj = null; + $post_buff = ''; + $attaches = array(); + } + + // oXmlParser->parse(''.$category_buff.''); + if($xml_doc->category) { + $this->insertTTCategory($xml_doc, 0, $module_srl, $category_titles); + + // 입력완료 후 카테고리 xml파일 재생성 + $this->oDocumentController->makeCategoryXmlFile($module_srl); + $xml_doc = null; + } + + $category_buff = null; + } + + $post_started = true; + } + + // 게시글 버퍼링중일때 처리 + if($post_started) { + // 첨부파일의 경우 별도로 버퍼링을 하지 않고 직접 파일에 기록해야 함 + if(substr($str,0,12)=='importTTAttaches($fp, $str); + else $post_buff .= $str; + } + } + + fclose($fp); + + $this->add('is_finished','1'); + $this->setMessage(sprintf(Context::getLang('msg_import_finished'), $success_count, $total_count)); + } + + /** + * @brief TTXML에 맞게 category정보 입력 + **/ + function insertTTCategory($xml_doc, $parent_srl =0, $module_srl, &$category_titles) { + // category element가 없으면 pass + if(!$xml_doc->category) return; + $categories = $xml_doc->category; + + // 하나만 있을 경우 배열 처리 + if(!is_array($categories)) $categories = array($categories); + + // ttxml에서는 priority순서로 오는게 아니라서 정렬 + foreach($categories as $obj) { + $title = trim($obj->name->body); + $priority = $obj->priority->body; + $root = $obj->root->body; + if($root==1) continue; + $category_list[$priority]->title = $title; + $category_list[$priority]->category = $obj->category; + } + + // 데이터 입력 + foreach($category_list as $obj) { + if(!$category_titles[$obj->title]) { + $args = null; + $args->title = $obj->title; + $args->parent_srl = $parent_srl; + $args->module_srl = $module_srl; + $output = $this->oDocumentController->insertCategory($args); + if($output->toBool()) $category_titles[$args->title] = $output->get('category_srl'); + $output = null; + } + + $this->insertTTCategory($obj, $category_titles[$obj->title], $module_srl, $category_titles); + + } + } + + /** + * @brief TTXML 첨부파일 정리 + **/ + function importTTAttaches($fp, $buff) { + if(substr(trim($buff), -13) != '') { + while(!feof($fp)) { + $buff .= fgets($fp, 1024); + if(substr(trim($buff), -13) == '') break; + } + } + + $xml_doc = $this->oXmlParser->parse($buff); + $buff = ''; + + $obj = null; + $obj->filename = $xml_doc->attachment->name->body; + $obj->file = $this->getTmpFilename('file'); + $obj->download_count = $xml_doc->attachment->downloads->body; + + $f = fopen($obj->file, "w"); + fwrite($f, base64_decode($xml_doc->attachment->content->body)); + fclose($f); + + return $obj; + } + + /** + * @brief TTXML 댓글 정리 + **/ + function importTTComment($comment) { + if(!$comment) return; + if(!is_array($comment)) $comment = array($comment); + + $comments = array(); + + $sequence = 0; + foreach($comment as $key => $val) { + $obj = null; + $obj->sequence = $sequence; + + if($val->commenter->attrs->id) $obj->parent = $sequence-1; + + $obj->is_secret = $val->secret->body==1?'Y':'N'; + $obj->content = nl2br($val->content->body); + $obj->password = $val->password->body; + $obj->nick_name = $val->commenter->name->body; + $obj->homepage = $val->commenter->homepage->body; + $obj->regdate = date("YmdHis",$val->written->body); + $obj->ipaddress = $val->commenter->ip->body; + $comments[] = $obj; + + $sequence++; + } + return $comments; + } + } ?> diff --git a/modules/importer/importer.admin.view.php b/modules/importer/importer.admin.view.php index 1f4ea23e7..2bd362f10 100644 --- a/modules/importer/importer.admin.view.php +++ b/modules/importer/importer.admin.view.php @@ -26,8 +26,14 @@ case 'member' : $template_filename = "member"; break; + case 'ttxml' : + $oModuleModel = &getModel('module'); + $mid_list = $oModuleModel->getMidList(); + Context::set('mid_list', $mid_list); + + $template_filename = "ttxml"; + break; case 'module' : - // 전체 모듈 목록 구함 $oModuleModel = &getModel('module'); $mid_list = $oModuleModel->getMidList(); Context::set('mid_list', $mid_list); diff --git a/modules/importer/lang/en.lang.php b/modules/importer/lang/en.lang.php index a69be6568..57f82d677 100644 --- a/modules/importer/lang/en.lang.php +++ b/modules/importer/lang/en.lang.php @@ -14,6 +14,7 @@ $lang->source_type = 'Previous target'; $lang->type_member = 'Member data'; $lang->type_message = 'Message data'; + $lang->type_ttxml = 'TTXML'; $lang->type_module = 'Articles data'; $lang->type_syncmember = 'Synchronize member data'; $lang->target_module = 'Target module'; @@ -46,6 +47,8 @@ // blah blah.. $lang->about_type_member = 'If you are transfering the member information, select this option'; $lang->about_type_message = 'If you are transfering the message information, select this option'; + $lang->about_type_ttxml = '데이터 이전 대상이 TTXML(textcube계열)일 경우 선택해주세요'; + $lang->about_ttxml_user_id = 'TTXML이전시에 글쓴이로 지정할 사용자 아이디를 입력해주세요. (이미 가입된 아이디여야 합니다)'; $lang->about_type_module = 'If you are transfering the board or articles information, select this option'; $lang->about_type_syncmember = 'If you are trying to synchronize the member information after transfering member and article information, select this option'; $lang->about_importer = "You can transfer Zeroboard4, Zeroboard5 Beta or other program's data into ZeroboardXE's data.\nIn order to tranfer, you have to use XML Exporter to convert the data you want into XML File then upload it."; diff --git a/modules/importer/lang/es.lang.php b/modules/importer/lang/es.lang.php index ce5ea4c11..79d151731 100644 --- a/modules/importer/lang/es.lang.php +++ b/modules/importer/lang/es.lang.php @@ -14,6 +14,7 @@ $lang->source_type = 'Objetivo a transferir'; $lang->type_member = 'Información del usuario'; $lang->type_message = '쪽지(메세지) 정보'; + $lang->type_ttxml = 'TTXML'; $lang->type_module = 'Información del documento.'; $lang->type_syncmember = 'Sincronizar la información del usuario'; $lang->target_module = 'Objetivo del módulo'; @@ -46,6 +47,8 @@ // bla bla... $lang->about_type_member = 'Seleccione esta opción si estas transferiendo la información del usuario.'; $lang->about_type_message = '데이터 이전 대상이 쪽지(메세지)일 경우 선택해주세요'; + $lang->about_type_ttxml = '데이터 이전 대상이 TTXML(textcube계열)일 경우 선택해주세요'; + $lang->about_ttxml_user_id = 'TTXML이전시에 글쓴이로 지정할 사용자 아이디를 입력해주세요. (이미 가입된 아이디여야 합니다)'; $lang->about_type_module = 'Seleccione esta opción si estas transfeririendo información del documento de los tableros'; $lang->about_type_syncmember = 'Seleccione esta opción cuando tenga que sincronizar la información del usuario luego de haber transferido la información del usuario y del artículo.'; $lang->about_importer = "Es posible trasferir los datos de Zeroboard4, zb5beta o de otros programas a ZeroBoardXE.\nPara la transferencia debe utilizar Exportador XML para transformar los datos en archivo XML, y luego subir ese archivo."; diff --git a/modules/importer/lang/jp.lang.php b/modules/importer/lang/jp.lang.php index 95cc1dcb2..ca5e50281 100644 --- a/modules/importer/lang/jp.lang.php +++ b/modules/importer/lang/jp.lang.php @@ -14,6 +14,7 @@ $lang->source_type = 'データ変換の対象'; $lang->type_member = '会員情報'; $lang->type_message = '쪽지(메세지) 정보'; + $lang->type_ttxml = 'TTXML'; $lang->type_module = '書き込みデータ情報'; $lang->type_syncmember = '会員情報同期化'; $lang->target_module = '対象モジュール'; @@ -46,6 +47,8 @@ // Bla, Blah.. $lang->about_type_member = 'データ変換の対象が会員情報の場合は選択してください。'; $lang->about_type_message = '데이터 이전 대상이 쪽지(메세지)일 경우 선택해주세요'; + $lang->about_type_ttxml = '데이터 이전 대상이 TTXML(textcube계열)일 경우 선택해주세요'; + $lang->about_ttxml_user_id = 'TTXML이전시에 글쓴이로 지정할 사용자 아이디를 입력해주세요. (이미 가입된 아이디여야 합니다)'; $lang->about_type_module = 'データ変換の対象が書き込みデータである場合は選択してください。'; $lang->about_type_syncmember = '会員情報と書き込みデータなどの変換を行った後、会員情報を同期化する必要がある場合は、選択してください。'; $lang->about_importer = "ゼロボード4、zb5betaまたは他のプログラムの書き込みデータをゼロボードXEのデータに変換することができます。\n変換するためには、XML Exporterを利用して変換したい書き込みデータをXMLファイルで作成してアップロードしてください。"; diff --git a/modules/importer/lang/ko.lang.php b/modules/importer/lang/ko.lang.php index 300ab35bb..95e9e5224 100644 --- a/modules/importer/lang/ko.lang.php +++ b/modules/importer/lang/ko.lang.php @@ -14,6 +14,7 @@ $lang->source_type = '이전 대상'; $lang->type_member = '회원 정보'; $lang->type_message = '쪽지(메세지) 정보'; + $lang->type_ttxml = 'TTXML'; $lang->type_module = '게시물 정보'; $lang->type_syncmember = '회원정보 동기화'; $lang->target_module = '대상 모듈'; @@ -46,9 +47,10 @@ // 주절 주절.. $lang->about_type_member = '데이터 이전 대상이 회원정보일 경우 선택해주세요'; $lang->about_type_message = '데이터 이전 대상이 쪽지(메세지)일 경우 선택해주세요'; + $lang->about_type_ttxml = '데이터 이전 대상이 TTXML(textcube계열)일 경우 선택해주세요'; + $lang->about_ttxml_user_id = 'TTXML이전시에 글쓴이로 지정할 사용자 아이디를 입력해주세요. (이미 가입된 아이디여야 합니다)'; $lang->about_type_module = '데이터 이전 대상이 게시판등의 게시물 정보일 경우 선택해주세요'; $lang->about_type_syncmember = '회원정보와 게시물정보등을 이전후 회원정보 동기화 해야 할때 선택해주세요'; $lang->about_importer = "제로보드4, zb5beta 또는 다른 프로그램의 데이터를 제로보드XE 데이터로 이전할 수 있습니다.\n이전을 위해서는 XML Exporter를 이용해서 원하는 데이터를 XML파일로 생성후 업로드해주셔야 합니다."; - $lang->about_target_path = "첨부파일을 받기 위해 제로보드4가 설치된 위치를 입력해주세요.\n같은 서버에 있을 경우 /home/아이디/public_html/bbs 등과 같이 제로보드4의 위치를 입력하시고\n다른 서버일 경우 http://도메인/bbs 처럼 제로보드가 설치된 곳의 url을 입력해주세요"; ?> diff --git a/modules/importer/lang/ru.lang.php b/modules/importer/lang/ru.lang.php index a5b63b5b0..a5f5d3f71 100644 --- a/modules/importer/lang/ru.lang.php +++ b/modules/importer/lang/ru.lang.php @@ -14,6 +14,7 @@ $lang->source_type = 'Предыдущее назначение'; $lang->type_member = 'Данные пользователей'; $lang->type_message = '쪽지(메세지) 정보'; + $lang->type_ttxml = 'TTXML'; $lang->type_module = 'Данные статей'; $lang->type_syncmember = 'Синхронизировать данные пользователей'; $lang->target_module = 'Модуль назначения'; @@ -46,6 +47,8 @@ // blah blah.. чепуха) $lang->about_type_member = 'Если Вы импортируете информацию пользователей, выберите эту опцию'; $lang->about_type_message = '데이터 이전 대상이 쪽지(메세지)일 경우 선택해주세요'; + $lang->about_type_ttxml = '데이터 이전 대상이 TTXML(textcube계열)일 경우 선택해주세요'; + $lang->about_ttxml_user_id = 'TTXML이전시에 글쓴이로 지정할 사용자 아이디를 입력해주세요. (이미 가입된 아이디여야 합니다)'; $lang->about_type_module = 'Если Вы импортируете информацию форума или статей, выберите эту опцию'; $lang->about_type_syncmember = 'Если Вы пытаетесь синхронизировать информацию пользователей после импорта информации пользователей и статей, выберите эту опцию'; $lang->about_importer = "Вы можете импортировать данные Zeroboard4, Zeroboard5 Beta или других программ в ZeroboardXE.\nЧтобы импортировать, Вам следует использовать XML Экспортер (XML Exporter), чтобы конвертировать нужные данные в XML Файл и затем загрузить его."; diff --git a/modules/importer/lang/zh-CN.lang.php b/modules/importer/lang/zh-CN.lang.php index 1e289d054..ca381956f 100644 --- a/modules/importer/lang/zh-CN.lang.php +++ b/modules/importer/lang/zh-CN.lang.php @@ -14,6 +14,7 @@ $lang->source_type = '导入对象'; $lang->type_member = '会员信息'; $lang->type_message = '短信息(MemoBox)'; + $lang->type_ttxml = 'TTXML'; $lang->type_module = '版面信息'; $lang->type_syncmember = '同步会员信息'; $lang->target_module = '模块对象'; @@ -46,6 +47,8 @@ // 说明 $lang->about_type_member = '数据导入对象为会员信息时请选择此项。'; $lang->about_type_message = '数据导入对象为短信息(MemoBox)时请选择此项。'; + $lang->about_type_ttxml = '데이터 이전 대상이 TTXML(textcube계열)일 경우 선택해주세요'; + $lang->about_ttxml_user_id = 'TTXML이전시에 글쓴이로 지정할 사용자 아이디를 입력해주세요. (이미 가입된 아이디여야 합니다)'; $lang->about_type_module = '数据导入对象为版面主题时请选择此项。'; $lang->about_type_syncmember = '导入会员信息和文章信息后需要同步会员信息时请选择此项。'; $lang->about_importer = "不仅可以导入Zeroboard 4,Zb5beta的数据,也可以把其他程序数据导入到Zeroboard XE当中。\n导入数据时请利用 XML Exporter生成XML文件后再上传。"; diff --git a/modules/importer/tpl/index.html b/modules/importer/tpl/index.html index defadbfff..c92a0a3ec 100644 --- a/modules/importer/tpl/index.html +++ b/modules/importer/tpl/index.html @@ -27,6 +27,10 @@ + + + + diff --git a/modules/importer/tpl/js/importer_admin.js b/modules/importer/tpl/js/importer_admin.js index c80f31cb1..4df22c5b5 100644 --- a/modules/importer/tpl/js/importer_admin.js +++ b/modules/importer/tpl/js/importer_admin.js @@ -181,3 +181,65 @@ function completeImportModule(ret_obj, response_tags) { } } +/* TTXML 데이터 import */ +function doImportTTXML(fo_obj) { + var target_module = fo_obj.target_module.options[fo_obj.target_module.selectedIndex].value; + if(!target_module) return false; + + var xml_file = fo_obj.xml_file.value; + if(!xml_file) return false; + + var params = new Array(); + params['xml_file'] = xml_file; + params['target_module'] = target_module; + params['total_count'] = fo_obj.total_count.value; + params['success_count'] = fo_obj.success_count.value; + params['readed_line'] = fo_obj.readed_line.value; + params['user_id'] = fo_obj.user_id.value; + + var response_tags = new Array("error","message", "total_count", "success_count", "readed_line", "is_finished"); + + exec_xml('importer','procImporterAdminTTXMLImport', params, completeImportTTXML, response_tags); + + return false; +} + +function completeImportTTXML(ret_obj, response_tags) { + var total_count = ret_obj['total_count']; + var success_count = ret_obj['success_count']; + var readed_line = ret_obj['readed_line']; + var is_finished = ret_obj['is_finished']; + + if(is_finished == '1') { + var fo_obj = xGetElementById("fo_import"); + fo_obj.target_module.disabled = false; + fo_obj.xml_file.disabled = false; + fo_obj.total_count.value = 0; + fo_obj.success_count.value = 0; + fo_obj.readed_line.value = 0; + + xGetElementById("status").style.display = "none"; + xGetElementById("status_button_prev").style.display = "block"; + xGetElementById("status_button").style.display = "none"; + + + xInnerHtml("status", ret_obj['message']); + + alert(ret_obj['message']); + } else { + var fo_obj = xGetElementById("fo_import"); + fo_obj.target_module.disabled = true; + fo_obj.xml_file.disabled = true; + fo_obj.total_count.value = total_count; + fo_obj.success_count.value = success_count; + fo_obj.readed_line.value = readed_line; + + xGetElementById("status").style.display = "block"; + xGetElementById("status_button_prev").style.display = "none"; + xGetElementById("status_button").style.display = "block"; + + xInnerHtml("status", ret_obj['message']); + + doImportTTXML(fo_obj); + } +} diff --git a/modules/importer/tpl/ttxml.html b/modules/importer/tpl/ttxml.html new file mode 100644 index 000000000..8ee123b82 --- /dev/null +++ b/modules/importer/tpl/ttxml.html @@ -0,0 +1,60 @@ + + + +

{$lang->importer} {$lang->cmd_management}

+ + +
{nl2br($lang->about_importer)}
+ + +
+
+ + + + + + + + + +
{$lang->import_step_title[1]} - {$lang->import_step_desc[12]}
+ +
+ + + + + +
+ {$lang->user_id} : +

{$lang->about_ttxml_user_id}

+
+ + + + + + + + + +
{$lang->import_step_title[2]} - {$lang->import_step_desc[2]}
+ +

ex1) ../module.xml

+

ex2) http://...../module.xml

+
+
+ +
+ + + +
+
+