script/utils/export_chat_zoom_space.py
changeset 1558 761ba7426984
equal deleted inserted replaced
1557:7c67caaafdeb 1558:761ba7426984
       
     1 #!/usr/bin/env python
       
     2 # coding=utf-8
       
     3 
       
     4 import argparse
       
     5 import bisect
       
     6 import datetime
       
     7 import json
       
     8 import os.path
       
     9 import re
       
    10 import sys
       
    11 import uuid  # @UnresolvedImport
       
    12 
       
    13 import requests
       
    14 
       
    15 import dateutil.tz
       
    16 from dateutil.parser import parse as parse_date_raw
       
    17 from dateutil.tz import tzutc
       
    18 from iri_tweet.utils import get_logger, set_logging, set_logging_options
       
    19 from lxml import etree
       
    20 
       
    21 
       
    22 LDT_CONTENT_REST_API_PATH = "api/ldt/1.0/contents/"
       
    23 LDT_PROJECT_REST_API_PATH = "api/ldt/1.0/projects/"
       
    24 
       
    25 def parse_date(datestr, default_tz, default=None):
       
    26     res = parse_date_raw(datestr, default=default)
       
    27     if res.tzinfo is None:
       
    28         res = res.replace(tzinfo=default_tz)
       
    29     return res
       
    30 
       
    31 
       
    32 def re_fn(expr, item):
       
    33     reg = re.compile(expr, re.I)
       
    34     res = reg.search(item)
       
    35     if res:
       
    36         get_logger().debug("re_fn : " + repr(expr) + "~" + repr(item)) #@UndefinedVariable
       
    37     return res is not None
       
    38 
       
    39 def parse_polemics_1(tw_text, extended_mode):
       
    40     """
       
    41     parse polemics in text and return a list of polemic code. None if not polemic found
       
    42     """
       
    43     polemics = {}
       
    44     for m in re.finditer(r"(\+\+|\-\-|\?\?|\=\=)",tw_text):
       
    45         pol_link = {
       
    46             '++' : 'OK',
       
    47             '--' : 'KO',
       
    48             '??' : 'Q',
       
    49             '==' : 'REF'}[m.group(1)]
       
    50         polemics[pol_link] = pol_link
       
    51 
       
    52     if extended_mode:
       
    53         if "?" in tw_text:
       
    54             polemics["Q"] = "Q"
       
    55 
       
    56     if len(polemics) > 0:
       
    57         return polemics.keys()
       
    58     else:
       
    59         return None
       
    60 
       
    61 def parse_polemics_2(tw_text, extended_mode):
       
    62     """
       
    63     parse polemics in text and return a list of polemic code. None if not polemic found
       
    64     """
       
    65     polemics = {}
       
    66     for m in re.finditer(r"(\+\+|\!\!|\?\?|\=\=)",tw_text):
       
    67         pol_link = {
       
    68             '++' : 'OK',
       
    69             '!!' : 'KO',
       
    70             '??' : 'Q',
       
    71             '==' : 'REF'}[m.group(1)]
       
    72         polemics[pol_link] = pol_link
       
    73 
       
    74     if extended_mode:
       
    75         if "?" in tw_text:
       
    76             polemics["Q"] = "Q"
       
    77 
       
    78 
       
    79     if len(polemics) > 0:
       
    80         return polemics.keys()
       
    81     else:
       
    82         return None
       
    83 
       
    84 def parse_polemics_3(tw_text, extended_mode):
       
    85     """
       
    86     parse polemics in text and return a list of polemic code. None if not polemic found
       
    87     """
       
    88     polemics = {}
       
    89     for m in re.finditer(r"(\+\+|\?\?|\*\*|\=\=)",tw_text):
       
    90         pol_link = {
       
    91             '++' : 'OK',
       
    92             '??' : 'KO',
       
    93             '**' : 'REF',
       
    94             '==' : 'Q'}[m.group(1)]
       
    95         polemics[pol_link] = pol_link
       
    96 
       
    97     if len(polemics) > 0:
       
    98         return polemics.keys()
       
    99     else:
       
   100         return None
       
   101 
       
   102 
       
   103 protocol_version_map = {
       
   104     "1" : parse_polemics_1,
       
   105     "2" : parse_polemics_2,
       
   106     "3" : parse_polemics_3
       
   107 }
       
   108 
       
   109 def get_options():
       
   110 
       
   111     parser = argparse.ArgumentParser(description="All date should be given using iso8601 format. If no timezone is used, the date is considered as UTC")
       
   112 
       
   113     parser.add_argument("-f", "--file", dest="filename",
       
   114                       help="write export to file", metavar="FILE", default="project.ldt")
       
   115     parser.add_argument("-d", "--chat-database", dest="database",
       
   116                       help="Input chat file", metavar="CHAT_DATABASE")
       
   117     parser.add_argument("-a", "--annotation-protocol", dest="protocol_version",
       
   118                       help="annotation protocol version", metavar="PROTOCOL_VERSION",
       
   119                       default="2")
       
   120     parser.add_argument("-s", "--start-date", dest="start_date",
       
   121                       help="start date", metavar="START_DATE", default=None)
       
   122     parser.add_argument("-e", "--end-date", dest="end_date",
       
   123                       help="end date", metavar="END_DATE", default=None)
       
   124     parser.add_argument("-I", "--content-file", dest="content_file",
       
   125                       help="Content file", metavar="CONTENT_FILE")
       
   126     parser.add_argument("-c", "--content", dest="content",
       
   127                       help="Content url", metavar="CONTENT")
       
   128     parser.add_argument("-V", "--video-url", dest="video",
       
   129                       help="video url", metavar="VIDEO")
       
   130     parser.add_argument("-i", "--content-id", dest="content_id",
       
   131                       help="Content id", metavar="CONTENT_ID")
       
   132     parser.add_argument("-x", "--exclude", dest="exclude",
       
   133                       help="file containing the id to exclude", metavar="EXCLUDE")
       
   134     parser.add_argument("-C", "--color", dest="color",
       
   135                       help="Color code", metavar="COLOR", default="16763904")
       
   136     parser.add_argument("-H", "--hashtag", dest="hashtag",
       
   137                       help="Hashtag", metavar="HASHTAG", default=[], action="append")
       
   138     parser.add_argument("-D", "--duration", dest="duration", type=int,
       
   139                       help="Duration", metavar="DURATION", default=None)
       
   140     parser.add_argument("-n", "--name", dest="name",
       
   141                       help="Cutting name", metavar="NAME", default="Chats")
       
   142     parser.add_argument("-R", "--replace", dest="replace", action="store_true",
       
   143                       help="Replace tweet ensemble", default=False)
       
   144     parser.add_argument("-m", "--merge", dest="merge", action="store_true",
       
   145                       help="merge tweet ensemble, choose the first ensemble", default=False)
       
   146     parser.add_argument("-L", "--list-conf", dest="listconf",
       
   147                       help="list of file to process", metavar="LIST_CONF", default=None)
       
   148     parser.add_argument("-E", "--extended", dest="extended_mode", action="store_true",
       
   149                       help="Trigger polemic extended mode", default=False)
       
   150     parser.add_argument("-b", "--base-url", dest="base_url",
       
   151                       help="base URL of the platform", metavar="BASE_URL", default="http://ldt.iri.centrepompidou.fr/ldtplatform/")
       
   152     parser.add_argument("-p", "--project", dest="project_id",
       
   153                       help="Project id", metavar="PROJECT_ID", default=None)
       
   154     parser.add_argument("-P", "--post-param", dest="post_param",
       
   155                       help="Post param", metavar="POST_PARAM", default=None)
       
   156     parser.add_argument("--user-whitelist", dest="user_whitelist", action="store",
       
   157                       help="A list of user screen name", metavar="USER_WHITELIST",default=None)
       
   158     parser.add_argument("--cut", dest="cuts", action="append",
       
   159                       help="A cut with the forma <ts in ms>::<duration>", metavar="CUT", default=[])
       
   160     parser.add_argument("-Z","--tz", dest="timezone",
       
   161                       help="The timezone of the timestamps", metavar="TZ", default="UTC")
       
   162 
       
   163 
       
   164     set_logging_options(parser)
       
   165 
       
   166     return (parser.parse_args(), parser)
       
   167 
       
   168 
       
   169 def find_delta(deltas, ts):
       
   170     i = bisect.bisect_right(deltas, (ts+1,0))
       
   171     if i:
       
   172         return deltas[i-1]
       
   173     return (0,0)
       
   174 
       
   175 
       
   176 def parse_duration(s):
       
   177     try:
       
   178         return int(s)
       
   179     except ValueError:
       
   180         parts = s.split(":")
       
   181         if len(parts) < 2:
       
   182             raise ValueError("Bad duration format")
       
   183         time_params = {
       
   184             'hours': int(parts[0]),
       
   185             'minutes': int(parts[1]),
       
   186             'seconds': int(parts[2]) if len(parts)>2 else 0
       
   187         }
       
   188         return int(round(datetime.timedelta(**time_params).total_seconds()*1000))
       
   189 
       
   190 # CHAT_REGEXP = re.compile(r"^(?P<created_at>\d{2}:\d{2}:\d{2})\t\sFrom\s{2}(?P<user>.+?)\s:\s(?P<text>.*)$", re.DOTALL)
       
   191 CHAT_REGEXP = re.compile(r"^(?P<created_at>\d{2}:\d{2}:\d{2})\sFrom\s(?P<user_from>.+) To Everyone:$", re.MULTILINE)
       
   192 CHAT_LINE_REGEXP = re.compile(r"^\t(?P<text>.*)$", re.MULTILINE)
       
   193 CHAT_DM_REGEXP = re.compile(r"\(Direct Message\)", re.IGNORECASE)
       
   194 
       
   195 def parse_chat_line(chat_id, chat_line):
       
   196     if (m := CHAT_REGEXP.match(chat_line)) is not None:
       
   197         res = {k: v.replace('\r','\n') if k == 'text' else v for k,v in m.groupdict().items()}
       
   198         res['id'] = chat_id
       
   199         if user_str := res.get('user_from'):
       
   200             res['user'] = user_str
       
   201         res['tags'] = re.findall('#(\w+)',res['text'])
       
   202         return res
       
   203     else:
       
   204         return {}
       
   205 
       
   206 def finalize_chat_msg(chat_msg):
       
   207     res = dict(chat_msg)
       
   208     res['text'] = res['text'].replace('\r','\n')
       
   209     res['text'] = res['text'].removesuffix('\n')
       
   210     res['tags'] = re.findall('#(\w+)',res['text'])
       
   211     return res
       
   212 
       
   213 def read_chat_file(chat_file_path):
       
   214     current_msg = None
       
   215     chat_content = []
       
   216     chat_id = 0
       
   217     with open(chat_file_path, "r") as chat_file:
       
   218         for chat_line in chat_file:
       
   219             if ((m := CHAT_REGEXP.match(chat_line)) and not CHAT_DM_REGEXP.search(chat_line)):
       
   220                 #current_msg = {k: v.replace('\r','\n') if k == 'text' else v for k,v in m.groupdict().items()}
       
   221                 if current_msg:
       
   222                     chat_content.append(finalize_chat_msg(current_msg))
       
   223                 current_msg = dict(m.groupdict())
       
   224                 chat_id += 1
       
   225                 current_msg['id'] = "%04d" % (chat_id)
       
   226                 if user_str := current_msg.get('user_from'):
       
   227                     current_msg['user'] = user_str
       
   228                 current_msg['tags'] = []
       
   229                 current_msg['text'] = ""
       
   230             elif (m := CHAT_LINE_REGEXP.match(chat_line)):
       
   231                 if current_msg:
       
   232                     current_msg['text'] += m.group('text') + "\n"
       
   233     if current_msg:
       
   234         chat_content.append(finalize_chat_msg(current_msg))
       
   235 
       
   236     return chat_content
       
   237 
       
   238 
       
   239 if __name__ == "__main__" :
       
   240 
       
   241     (options, parser) = get_options()
       
   242 
       
   243     set_logging(options)
       
   244 
       
   245     get_logger().debug("OPTIONS : " + repr(options)) #@UndefinedVariable
       
   246 
       
   247 
       
   248     deltas = [(0,0)]
       
   249     total_delta = 0
       
   250     if options.cuts:
       
   251         cuts_raw = sorted([tuple([parse_duration(s) for s in c.split("::")]) for c in options.cuts])
       
   252         for c, d in cuts_raw:
       
   253             deltas.append((c+total_delta, -1))
       
   254             total_delta += d
       
   255             deltas.append((c+total_delta, total_delta))
       
   256 
       
   257     if len(sys.argv) == 1 or options.database is None:
       
   258         parser.print_help()
       
   259         sys.exit(1)
       
   260 
       
   261     user_whitelist_file = options.user_whitelist
       
   262     user_whitelist = None
       
   263 
       
   264     if options.listconf:
       
   265 
       
   266         parameters = []
       
   267         confdoc = etree.parse(options.listconf)
       
   268         for node in confdoc.xpath("/zoom_export/file"):
       
   269             params = {}
       
   270             for snode in node:
       
   271                 if snode.tag == "path":
       
   272                     params['content_file'] = snode.text
       
   273                     params['content_file_write'] = snode.text
       
   274                 elif snode.tag == "project_id":
       
   275                     params['content_file'] = options.base_url + LDT_PROJECT_REST_API_PATH + snode.text + "/?format=json"
       
   276                     params['content_file_write'] = options.base_url + LDT_PROJECT_REST_API_PATH + snode.text + "/?format=json"
       
   277                     params['project_id'] = snode.text
       
   278                 elif snode.tag == "start_date":
       
   279                     params['start_date'] = snode.text
       
   280                 elif snode.tag == "end_date":
       
   281                     params['end_date'] = snode.text
       
   282                 elif snode.tag == "duration":
       
   283                     params['duration'] = int(snode.text)
       
   284                 elif snode.tag == "hashtags":
       
   285                     params['hashtags'] = [snode.text]
       
   286             if options.hashtag or 'hashtags' not in params :
       
   287                 params['hashtags'] = options.hashtag
       
   288             parameters.append(params)
       
   289     else:
       
   290         if options.project_id:
       
   291             content_file = options.base_url + LDT_PROJECT_REST_API_PATH + options.project_id + "/?format=json"
       
   292         else:
       
   293             content_file = options.content_file
       
   294         parameters = [{
       
   295             'start_date': options.start_date,
       
   296             'end_date' : options.end_date,
       
   297             'duration' : options.duration,
       
   298             'content_file' : content_file,
       
   299             'content_file_write' : content_file,
       
   300             'hashtags' : options.hashtag,
       
   301             'project_id' : options.project_id
       
   302         }]
       
   303     post_param = {}
       
   304     if options.post_param:
       
   305         post_param = json.loads(options.post_param)
       
   306 
       
   307     item_tz = dateutil.tz.UTC
       
   308     if options.timezone:
       
   309         item_tz = dateutil.tz.gettz(options.timezone)
       
   310         get_logger().debug("TIMEZONE " + options.timezone + " PARSED :: " + repr(item_tz))
       
   311 
       
   312     if item_tz is None:
       
   313         get_logger().error("Timezone '%s' not recognized.", options.timezone)
       
   314         print("Error: Timezone '%s' not recognized." % options.timezone)
       
   315         parser.print_help()
       
   316         sys.exit(1)
       
   317 
       
   318     display_content_node = None
       
   319     for params in parameters:
       
   320 
       
   321         get_logger().debug("PARAMETERS " + repr(params)) #@UndefinedVariable
       
   322 
       
   323         start_date_str = params.get("start_date",None)
       
   324         end_date_str = params.get("end_date", None)
       
   325         duration = params.get("duration", None)
       
   326         content_file = params.get("content_file", None)
       
   327         content_file_write = params.get("content_file_write", None)
       
   328         hashtags = list(set(params.get('hashtags', [])))
       
   329 
       
   330         if user_whitelist_file:
       
   331             with open(user_whitelist_file, 'r+') as f:
       
   332                 user_whitelist = list(set([s.strip() for s in f]))
       
   333 
       
   334         start_date = None
       
   335         if start_date_str:
       
   336             start_date = parse_date(start_date_str, item_tz)
       
   337 
       
   338         root = None
       
   339         ensemble_parent = None
       
   340         project = None
       
   341 
       
   342         #to do : analyse situation ldt or iri ? filename set or not ?
       
   343 
       
   344         if content_file and content_file.find("http") == 0:
       
   345 
       
   346             get_logger().debug("url : " + content_file) #@UndefinedVariable
       
   347 
       
   348             r = requests.get(content_file, params=post_param)
       
   349             get_logger().debug("url response " + repr(r) + " content " + repr(r.text)) #@UndefinedVariable
       
   350             project = r.json()
       
   351             text_match = re.match(r"\<\?\s*xml.*?\?\>(.*)", project['ldt'], re.I|re.S)
       
   352             root = etree.fromstring(text_match.group(1) if text_match else project['ldt'])
       
   353 
       
   354         elif content_file and os.path.exists(content_file):
       
   355 
       
   356             doc = etree.parse(content_file)
       
   357             root = doc.getroot()
       
   358             for child in root:
       
   359                 if child.tag == "project":
       
   360                     project = child
       
   361                     break
       
   362             if project is None:
       
   363                 root = None
       
   364 
       
   365         content_id = None
       
   366 
       
   367         if root is None:
       
   368 
       
   369             root = etree.Element("iri")
       
   370 
       
   371             project = etree.SubElement(root, "project", {"abstract":"Polemics Chat","title":"Polemic Chat", "user":"IRI Web", "id":str(uuid.uuid4())})
       
   372 
       
   373             medias = etree.SubElement(root, "medias")
       
   374             media = etree.SubElement(medias, "media", {"pict":"", "src":options.content, "video":options.video, "id":options.content_id, "extra":""})
       
   375 
       
   376             annotations = etree.SubElement(root, "annotations")
       
   377             content = etree.SubElement(annotations, "content", {"id":options.content_id})
       
   378             ensemble_parent = content
       
   379 
       
   380             content_id = options.content_id
       
   381 
       
   382 
       
   383         if ensemble_parent is None:
       
   384             file_type = None
       
   385             for node in root:
       
   386                 if node.tag == "project":
       
   387                     file_type = "ldt"
       
   388                     break
       
   389                 elif node.tag == "head":
       
   390                     file_type = "iri"
       
   391                     break
       
   392 
       
   393             if file_type == "ldt":
       
   394                 media_nodes = root.xpath("//media")
       
   395                 media = None
       
   396                 if len(media_nodes) > 0:
       
   397                     media = media_nodes[0]
       
   398                 annotations_node = root.find("annotations")
       
   399                 if annotations_node is None:
       
   400                     annotations_node = etree.SubElement(root, "annotations")
       
   401                 content_node = annotations_node.find("content")
       
   402                 if content_node is None and media is not None:
       
   403                     content_node = etree.SubElement(annotations_node,"content", id=media.get("id"))
       
   404                 ensemble_parent = content_node
       
   405                 content_id = content_node.get("id")
       
   406                 display_nodes = root.xpath("//displays/display/content[@id='%s']" % content_id)
       
   407                 if len(display_nodes) == 0:
       
   408                     get_logger().info("No display node found. Will not update display")
       
   409                     display_content_node = None
       
   410                 else:
       
   411                     display_content_node = display_nodes[0]
       
   412 
       
   413             elif file_type == "iri":
       
   414                 body_node = root.find("body")
       
   415                 if body_node is None:
       
   416                     body_node = etree.SubElement(root, "body")
       
   417                 ensembles_node = body_node.find("ensembles")
       
   418                 if ensembles_node is None:
       
   419                     ensembles_node = etree.SubElement(body_node, "ensembles")
       
   420                 ensemble_parent = ensembles_node
       
   421                 content_id = root.xpath("head/meta[@name='id']/@content")[0]
       
   422                 display_content_node = None
       
   423 
       
   424 
       
   425         if ensemble_parent is None:
       
   426             get_logger().error("Can not process file") #@UndefinedVariable
       
   427             sys.exit()
       
   428 
       
   429         if options.replace:
       
   430             for ens in ensemble_parent.iterchildren(tag="ensemble"):
       
   431                 ens_id = ens.get("id","")
       
   432                 if ens_id.startswith("chat_"):
       
   433                     ensemble_parent.remove(ens)
       
   434                     # remove in display nodes
       
   435                     if display_content_node is not None:
       
   436                         for cut_display in display_content_node.iterchildren():
       
   437                             if cut_display.get('idens','') == ens_id:
       
   438                                 display_content_node.remove(cut_display)
       
   439 
       
   440         ensemble = None
       
   441         elements = None
       
   442         decoupage = None
       
   443 
       
   444         if options.merge:
       
   445             for ens in ensemble_parent.findall("ensemble"):
       
   446                 if ens.get('id',"").startswith("chat_"):
       
   447                     ensemble = ens
       
   448                     break
       
   449             if ensemble is not None:
       
   450                 elements = ensemble.find(".//elements")
       
   451                 decoupage = ensemble.find("decoupage")
       
   452 
       
   453         if ensemble is None or elements is None:
       
   454             ensemble = etree.SubElement(ensemble_parent, "ensemble", {"id":"chat_" + str(uuid.uuid4()), "title":"Ensemble Chat", "author":"IRI Web", "abstract":"Ensemble Chat"})
       
   455             decoupage = etree.SubElement(ensemble, "decoupage", {"id": str(uuid.uuid4()), "author": "IRI Web"})
       
   456 
       
   457             etree.SubElement(decoupage, "title").text = options.name
       
   458             etree.SubElement(decoupage, "abstract").text = options.name
       
   459 
       
   460             elements = etree.SubElement(decoupage, "elements")
       
   461 
       
   462         ensemble_id = ensemble.get('id', '')
       
   463         decoupage_id = decoupage.get('id', '') if decoupage is not None else None
       
   464 
       
   465         end_date = None
       
   466         if end_date_str:
       
   467             end_date = parse_date(end_date_str, item_tz)
       
   468         elif start_date and duration:
       
   469             end_date = start_date + datetime.timedelta(seconds=duration)
       
   470         elif start_date and options.base_url:
       
   471             # get duration from api
       
   472             content_url = options.base_url + LDT_CONTENT_REST_API_PATH + content_id + "/?format=json"
       
   473             r = requests.get(content_url)
       
   474             duration = int(r.json()['duration'])
       
   475             get_logger().debug("get duration " + content_url) #@UndefinedVariable
       
   476             get_logger().debug("get duration " + repr(duration)) #@UndefinedVariable
       
   477 
       
   478             end_date = start_date + datetime.timedelta(seconds=int(duration/1000))
       
   479 
       
   480         if end_date and deltas:
       
   481             end_date = end_date + datetime.timedelta(milliseconds=deltas[-1][1])
       
   482 
       
   483         for cht in read_chat_file(options.database.strip()):
       
   484 
       
   485             cht_ts_dt = cht['created_at']
       
   486             default_date = start_date or datetime.now()
       
   487             cht_ts = parse_date(cht_ts_dt, item_tz, default_date.replace(tzinfo=item_tz))
       
   488             if start_date is None:
       
   489                 start_date = cht_ts
       
   490             if cht_ts < start_date or cht_ts > end_date:
       
   491                 continue
       
   492             cht_ts_rel = cht_ts-start_date
       
   493             cht_ts_rel_milli = int(round(cht_ts_rel.total_seconds() * 1000))
       
   494             if deltas:
       
   495                 d = find_delta(deltas, cht_ts_rel_milli)
       
   496                 if d[1] < 0:
       
   497                     continue
       
   498                 else :
       
   499                     cht_ts_rel_milli -= d[1]
       
   500 
       
   501             username = cht['user'] or "anon."
       
   502 
       
   503             element = etree.SubElement(elements, "element" , {"id": "%s-%s" % (uuid.uuid4(),cht['id']), "color":options.color, "author":username, "date":cht_ts.strftime("%Y/%m/%d"), "begin": str(cht_ts_rel_milli), "dur":"0", "src":"zoom"})
       
   504             etree.SubElement(element, "title").text = username + ": " + cht['text'][:255]
       
   505             etree.SubElement(element, "abstract").text = cht['text']
       
   506 
       
   507             tags_node = etree.SubElement(element, "tags")
       
   508 
       
   509             for tag in cht['tags']:
       
   510                 etree.SubElement(tags_node,"tag").text = tag
       
   511 
       
   512             meta_element = etree.SubElement(element, 'meta')
       
   513 
       
   514             etree.SubElement(meta_element, "polemic_version").text = options.protocol_version
       
   515             parse_polemics = protocol_version_map.get(options.protocol_version, parse_polemics_2)
       
   516             polemics_list = parse_polemics(cht['text'], options.extended_mode)
       
   517             if polemics_list:
       
   518                 polemics_element = etree.Element('polemics')
       
   519                 for pol in polemics_list:
       
   520                     etree.SubElement(polemics_element, 'polemic').text = pol
       
   521                 meta_element.append(polemics_element)
       
   522 
       
   523             etree.SubElement(meta_element, "source", attrib={"url":"http://zoom.io", "mimetype":"text/plain"}).text = etree.CDATA(json.dumps({'chat': cht['text']}))
       
   524 
       
   525         # sort by tc in
       
   526         if options.merge :
       
   527             # remove all elements and put them in a array
       
   528             # sort them with tc
       
   529             #put them back
       
   530             elements[:] = sorted(elements,key=lambda n: int(n.get('begin')))
       
   531 
       
   532         #add to display node
       
   533         if display_content_node is not None:
       
   534             display_dec = None
       
   535             for dec in display_content_node.iterchildren(tag="decoupage"):
       
   536                 if dec.get('idens','') == ensemble_id and dec.get('id', '') == decoupage_id:
       
   537                     display_dec = dec
       
   538                     break
       
   539             if display_dec is None and ensemble_id and decoupage_id:
       
   540                 etree.SubElement(display_content_node, "decoupage", attrib={'idens': ensemble_id, 'id': decoupage_id, 'tagsSelect':''})
       
   541 
       
   542         output_data = etree.tostring(root, encoding="utf-8", method="xml", pretty_print=False, xml_declaration=True).decode('utf-8')
       
   543 
       
   544         if content_file_write and content_file_write.find("http") == 0:
       
   545 
       
   546             project["ldt"] = output_data
       
   547             project['owner'] = project['owner'].replace('%7E','~')
       
   548             project['contents'] = [c_url.replace('%7E','~') for c_url in project['contents']]
       
   549 
       
   550             post_param = {}
       
   551             if options.post_param:
       
   552                 post_param = json.loads(options.post_param)
       
   553 
       
   554             get_logger().debug("write http " + content_file_write) #@UndefinedVariable
       
   555             get_logger().debug("write http " + repr(post_param)) #@UndefinedVariable
       
   556             get_logger().debug("write http " + repr(project)) #@UndefinedVariable
       
   557             r = requests.put(content_file_write, data=json.dumps(project), headers={'content-type':'application/json'}, params=post_param)
       
   558             get_logger().debug("write http " + repr(r) + " content " + r.text) #@UndefinedVariable
       
   559             if r.status_code != requests.codes.ok:  # pylint: disable=E1101
       
   560                 r.raise_for_status()
       
   561         else:
       
   562             if content_file_write and os.path.exists(content_file_write):
       
   563                 dest_file_name = content_file_write
       
   564             else:
       
   565                 dest_file_name = options.filename
       
   566 
       
   567             get_logger().debug("WRITE : " + dest_file_name) #@UndefinedVariable
       
   568             output = open(dest_file_name, "w")
       
   569             output.write(output_data)
       
   570             output.flush()
       
   571             output.close()