1. time:时间
时间戳(timestamp): time.time() 延迟线程的运行: time.sleep(secs) (指定时间戳下的)当前时区时间: time.localtime([secs]) (指定时间戳下的)格林威治时间: time.gmtime([secs]) (指定时间元组下的)格式化时间: time.strftime(fmt[,tupletime])
%y 两位数的年份表示(00-99) %Y 四位数的年份表示(000-9999) %m 月份(01-12) %d 月内中的一天(0-31) %H 24小时制小时数(0-23) %I 12小时制小时数(01-12) %M 分钟数(00=59) %S 秒(00-59) %a 本地简化星期名称 %A 本地完整星期名称 %b 本地简化的月份名称 %B 本地完整的月份名称 %c 本地相应的日期表示和时间表示 %j 年内的一天(001-366) %p 本地A.M.或P.M.的等价符 %U 一年中的星期数(00-53)星期天为星期的开始 %w 星期(0-6),星期天为星期的开始 %W 一年中的星期数(00-53)星期一为星期的开始 %x 本地相应的日期表示 %X 本地相应的时间表示 %Z 当前时区的名称 %% %号本身
2. calendar:日历
判断闰年: calendar.isleap(year) 查看某年某月日历: calendar.month(year, mouth) 查看某年某月起始星期与当月天数: calendar.monthrange(year, month) 查看某年某月某日是星期几: calendar.weekday(year, month, day)
3. datetime:可以运算的时间
当前时间: datetime.datetime.now() 昨天: datetime.datetime.now() + datetime.timedelta(days=-1) 修改时间: datatime_obj.replace([...]) 格式化时间戳:datetime.date.fromtimestamp(timestamp)
4. sys:系统
命令行参数List,第一个元素是程序本身路径:sys.argv 退出程序,正常退出时exit(0): sys.exit(n) 获取Python解释程序的版本信息: sys.version 最大int值: sys.maxsize | sys.maxint 环境变量: sys.path 操作系统平台名称: sys.platform
5. os:操作系统
os.getcwd() 获取当前工作目录,即当前python脚本工作的目录路径 os.chdir("dirname") 改变当前脚本工作目录;相当于shell下cd os.curdir 返回当前目录: ('.') os.pardir 获取当前目录的父目录字符串名:('..') os.makedirs('dirname1/dirname2') 可生成多层递归目录 os.removedirs('dirname1') 若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推 os.mkdir('dirname') 生成单级目录;相当于shell中mkdir dirname os.rmdir('dirname') 删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir dirname os.listdir('dirname') 列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印 os.remove() 删除一个文件 os.rename("oldname","newname") 重命名文件/目录 os.stat('path/filename') 获取文件/目录信息 os.sep 输出操作系统特定的路径分隔符,win下为"\\",Linux下为"/" os.linesep 输出当前平台使用的行终止符,win下为"\t\n",Linux下为"\n" os.pathsep 输出用于分割文件路径的字符串 win下为;,Linux下为: os.name 输出字符串指示当前使用平台。win->'nt'; Linux->'posix' os.system("bash command") 运行shell命令,直接显示 os.environ 获取系统环境变量 os.path.abspath(path) 返回path规范化的绝对路径 os.path.split(path) 将path分割成目录和文件名二元组返回 os.path.dirname(path) 返回path的目录。其实就是os.path.split(path)的第一个元素 os.path.basename(path) 返回path最后的文件名。如何path以/或\结尾,那么就会返回空值。即os.path.split(path)的第二个元素 os.path.exists(path) 如果path存在,返回True;如果path不存在,返回False os.path.isabs(path) 如果path是绝对路径,返回True os.path.isfile(path) 如果path是一个存在的文件,返回True。否则返回False os.path.isdir(path) 如果path是一个存在的目录,则返回True。否则返回False os.path.join(path1[, path2[, ...]]) 将多个路径组合后返回,第一个绝对路径之前的参数将被忽略 os.path.getatime(path) 返回path所指向的文件或者目录的最后存取时间 os.path.getmtime(path) 返回path所指向的文件或者目录的最后修改时间 os.path.getsize(path) 返回path的大小
# normcase函数 # 在Linux和Mac平台上,该函数会原样返回path,在windows平台上会将路径中所有字符转换为小写,并将所有斜杠转换为反斜杠。 >>> os.path.normcase('c:/windows\\system32\\') 'c:\\windows\\system32\\' # normpath函数 # 规范化路径,如..和/ >>> os.path.normpath('c://windows\\System32\\../Temp/') 'c:\\windows\\Temp' >>> a='/Users/jieli/test1/\\\a1/\\\\aa.py/../..' >>> print(os.path.normpath(a)) /Users/jieli/test1
#os路径处理 #方式一:推荐使用 import os #具体应用 import os,sys possible_topdir = os.path.normpath(os.path.join( os.path.abspath(__file__), os.pardir, #上一级 os.pardir, os.pardir )) sys.path.insert(0,possible_topdir) #方式二:不推荐使用 os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
6. random:随机数
import random print(random.random())#(0,1)----float 大于0且小于1之间的小数 print(random.randint(1,3)) #[1,3] 大于等于1且小于等于3之间的整数 print(random.randrange(1,3)) #[1,3) 大于等于1且小于3之间的整数 print(random.choice([1,'23',[4,5]]))#1或者23或者[4,5] print(random.sample([1,'23',[4,5]],2))#列表元素任意2个组合 print(random.uniform(1,3))#大于1小于3的小数,如1.927109612082716 item=[1,3,5,7,9] random.shuffle(item) #打乱item的顺序,相当于"洗牌" print(item) import random def make_code(n): res='' for i in range(n): s1=chr(random.randint(65,90)) s2=str(random.randint(0,9)) res+=random.choice([s1,s2]) return res print(make_code(9))
7. json:序列化
# json: {} 与 [] 嵌套的数据 # 注:json中的字符串必须全部用""来标识 ''' 序列化:对象 => 字符串 序列化成字符串:json.dumps(json_obj) 序列化字符串到文件中:json.dump(json_obj, write_file) # 注:字符形式操作 反序列化成对象:json.loads(json_str) 从文件读流中反序列化成对象:json.load(read_file)
8. pickle:序列化
序列化:对象 => 字符串 序列化成字符串:pickle.dumps(obj) 序列化字符串到文件中:pickle.dump(obj, write_bytes_file) # 注:字节形式操作 反序列化成对象:pickle.loads(bytes_str) 从文件读流中反序列化成对象:pickle.load(read_bytes_file)
9. logging 模块
9.1 什么是logging模块
logging模块是python提供的用于记录日志的模块
9.2 为什么需要logging
我们完全可以自己打开文件然后,日志写进去,但是这些操作重复且没有任何技术含量,所以python帮我们进行了封装,有了logging后我们在记录日志时 只需要简单的调用接口即可,非常方便!
9.3 日志级别
在开始记录日志前还需要明确,日志的级别
随着时间的推移,日志记录会非常多,成千上万行,如何快速找到需要的日志记录这就成了问题
解决的方案就是 给日志划分级别
- logging模块将日志分为了五个级别,从高到低分别是:
- info 常规信息
- debug 调试信息
- warning 警告信息
- error 错误信息
- cretical 严重错误
本质上他们使用数字来表示级别的,从高到低分别是10,20,30,40,50
9.4 logging模块的使用
#1.导入模块 import logging #2.输出日志 logging.info("info") logging.debug("debug") logging.warning("warning") logging.error("error") logging.critical("critical") #输出 WARNING:root:warning #输出 ERROR:root:error #输出 CRITICAL:root:critical
我们发现info 和 debug都没有输出,这是因为它们的级别不够。
在默认情况下,logging的最低显示级别为warning,对应的数值为30。
日志被打印到了控制台。
日志输出格式为:级别 日志生成器名称 日志消息。
如何修改这写默认的行为呢?,这就需要我们自己来进行配置。
9.5 自定义配置
import logging logging.basicConfig() """可用参数 filename:用指定的文件名创建FiledHandler(后边会具体讲解handler的概念),这样日志会被存储在指定的文件中。 filemode:文件打开方式,在指定了filename时使用这个参数,默认值为“a”还可指定为“w”。 format:指定handler使用的日志显示格式。 datefmt:指定日期时间格式。 level:设置rootlogger(后边会讲解具体概念)的日志级别 """ #案例: logging.basicConfig( filename="aaa.log", filemode="at", datefmt="%Y-%m-%d %H:%M:%S %p", format="%(asctime)s - %(name)s - %(levelname)s - %(module)s: %(message)s", level=10 )
9.6 格式化全部可用名称
%(name)s:Logger的名字,并非用户名,详细查看 %(levelno)s:数字形式的日志级别 %(levelname)s:文本形式的日志级别 %(pathname)s:调用日志输出函数的模块的完整路径名,可能没有 %(filename)s:调用日志输出函数的模块的文件名 %(module)s:调用日志输出函数的模块名 %(funcName)s:调用日志输出函数的函数名 %(lineno)d:调用日志输出函数的语句所在的代码行 %(created)f:当前时间,用UNIX标准的表示时间的浮 点数表示 %(relativeCreated)d:输出日志信息时的,自Logger创建以 来的毫秒数 %(asctime)s:字符串形式的当前时间。默认格式是 “2003-07-08 16:49:45,896”。逗号后面的是毫秒 %(thread)d:线程ID。可能没有 %(threadName)s:线程名。可能没有 %(process)d:进程ID。可能没有 %(message)s:用户输出的消息
至此我们已经可以自己来配置一 写基础信息了,但是当我们想要将同一个日志输出到不同位置时,这些基础配置就无法实现了, 例如 有一个登录注册的功能 需要记录日志,同时生成两份 一份给程序员看,一份给老板看,作为程序员应该查看较为详细的日志,二老板则应该简单一些,因为他不需要关心程序的细节。要实现这样的需要我们需要系统的了解loggin模块。
logging模块的四个核心角色
- Logger 日志生成器 产生日志
- Filter 日志过滤器 过滤日志
- Handler 日志处理器 对日志进行格式化,并输出到指定位置(控制台或文件)
- Formater 处理日志的格式
9.7 一条日志完整的生命周期:
1.由logger 产生日志 -> 2.交给过滤器判断是否被过滤 -> 3.将日志消息分发给绑定的所有处理器 -> 4处理器按照绑定的格式化对象输出日志
其中 第一步 会先检查日志级别 如果低于设置的级别则不执行
第二步 使用场景不多 需要使用面向对象的技术点 后续用到再讲
第三步 也会检查日志级别,如果得到的日志低于自身的日志级别则不输出
生成器的级别应低于句柄否则给句柄设置级别是没有意义的。例如 handler设置为20 生成器设置为30。30以下的日志压根不会产生。第四步 如果不指定格式则按照默认格式。logging各角色的使用(了解)。
# 生成器 logger1 = logging.getLogger("日志对象1") # 文件句柄 handler1 = logging.FileHandler("log1.log",encoding="utf-8") handler2 = logging.FileHandler("log2.log",encoding="utf-8") # 控制台句柄 handler3 = logging.StreamHandler() # 格式化对象 fmt1 = logging.Formatter( fmt="%(asctime)s - %(name)s - %(levelname)s: %(message)s", datefmt="%m-%d %H:%M:%S %p") fmt2 = logging.Formatter( fmt="%(asctime)s - %(levelname)s : %(message)s", datefmt="%Y/%m/%d %H:%M:%S") # 绑定格式化对象与文件句柄 handler1.setFormatter(fmt1) handler2.setFormatter(fmt2) handler3.setFormatter(fmt1) # 绑定生成器与文件句柄 logger1.addHandler(handler1) logger1.addHandler(handler2) logger1.addHandler(handler3) # 设置日志级别 logger1.setLevel(10) #生成器日志级别 handler1.setLevel(20) #句柄日志级别 # 测试 logger1.debug("debug msessage") logger1.info("info msessage") logger1.warning("warning msessage") logger1.critical("critical msessage")
到此我们已经可以实现上述的需求了,但是这并不是我们最终的实现方式,因为每次都要编写这样的代码是非常痛苦的
9.8 logging的继承(了解)
可以将一个日志指定为另一个日志的子日志 或子孙日志
当存在继承关系时 子孙级日志收到日志时会将该日志向上传递
指定继承关系:
import logging log1 = logging.getLogger("mother") log2 = logging.getLogger("mother.son") log3 = logging.getLogger("mother.son.grandson") # handler fh = logging.FileHandler(filename="cc.log",encoding="utf-8") # formatter fm = logging.Formatter("%(asctime)s - %(name)s -%(filename)s - %(message)s") # 绑定 log1.addHandler(fh) log2.addHandler(fh) log3.addHandler(fh) # 绑定格式 fh.setFormatter(fm) # 测试 # log1.error("测试") # log2.error("测试") log3.error("测试") # 取消传递 log3.propagate = False # 再次测试 log3.error("测试")
9.9 通过字典配置日志模块(重点)
每次都要编写代码来配置非常麻烦 ,我们可以写一个完整的配置保存起来,以便后续直接使用 LOGGING_DIC模板
import logging.config logging.config.dictConfig(LOGGING_DIC) logging.getLogger("aa").debug("测试") standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \ '[%(levelname)s][%(message)s]' #其中name为getlogger指定的名字 simple_format = '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s' id_simple_format = '[%(levelname)s][%(asctime)s] %(message)s' logfile_path = "配置文件路径" LOGGING_DIC = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'standard': { 'format': standard_format }, 'simple': { 'format': simple_format }, }, 'filters': {}, 'handlers': { #打印到终端的日志 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', # 打印到屏幕 'formatter': 'simple' }, #打印到文件的日志,收集info及以上的日志 'default': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', # 保存到文件 'formatter': 'standard', 'filename': logfile_path, # 日志文件 'maxBytes': 1024*1024*5, # 日志大小 5M 'backupCount': 5, #日志文件最大个数 'encoding': 'utf-8', # 日志文件的编码 }, }, 'loggers': { #logging.getLogger(__name__)拿到的logger配置 'aa': { 'handlers': ['default', 'console'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕 'level': 'DEBUG', 'propagate': True, # 向上(更高level的logger)传递 }, }, }
补充:getLogger参数就是对应字典中loggers的key , 如果没有匹配的key 则返回系统默认的生成器,我们可以在字典中通过空的key来将一个生成器设置为默认的。
'loggers': { # 把key设置为空 '': { 'handlers': ['default', 'console'], # 这里把上面定义的两个handler都加上,即log数据既写入文件又打印到屏幕 'level': 'DEBUG', 'propagate': True, # 向上(更高level的logger)传递 }, },
往后在使用时可以这调用模块提供的函数,来输出日志。
logging.info("测试信息!")
另外我们在第一次使用日志时并没有指定生成器,但也可以使用,这是因为系统有默认的生成器名称就叫root。
最后来完成之前的需求:
有一个登录注册的功能 需要记录日志,同时生成两份 一份给程序员看,一份给老板看,作为程序员应该查看较为详细的日志,二老板则应该简单一些,因为他不需要关心程序的细节
# 程序员看的格式 standard_format = '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]' \ '[%(levelname)s][%(message)s]' #其中name为getlogger指定的名字 logfile_path1 = "coder.log" # 老板看的格式 simple_format = '[%(levelname)s][%(asctime)s]%(message)s' logfile_path2 = "boss.log" LOGGING_DIC = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'standard': { 'format': standard_format }, 'simple': { 'format': simple_format }, }, 'filters': {}, 'handlers': { #打印到终端的日志 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', # 打印到屏幕 'formatter': 'simple' }, #打印到文件的日志,收集info及以上的日志 'std': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', # 保存到文件 'formatter': 'standard', 'filename': logfile_path1, # 日志文件 'maxBytes': 1024*1024*5, # 日志大小 5M 'backupCount': 5, #日志文件最大个数 'encoding': 'utf-8', # 日志文件的编码 }, 'boss': { 'level': 'DEBUG', 'class': 'logging.handlers.RotatingFileHandler', # 保存到文件 'formatter': 'simple', 'filename': logfile_path2, # 日志文件 'maxBytes': 1024 * 1024 * 5, # 日志大小 5M 'backupCount': 5, # 日志文件最大个数 'encoding': 'utf-8', # 日志文件的编码 } }, 'loggers': { #logging.getLogger(__name__)拿到的logger配置 'aa': { 'handlers': ['std', 'console',"boss"], # 这里把上面定义的handler都加上,即log数据会同时输出到三个位置 'level': 'INFO', 'propagate': True, # 向上(更高level的logger)传递 }, }, }
10. random:随机数
(0, 1) 小数:random.random() [1, 10] 整数:random.randint(1, 10) [1, 10) 整数:random.randrange(1, 10) (1, 10) 小数:random.uniform(1, 10) 单例集合随机选择1个:random.choice(item) 单例集合随机选择n个:random.sample(item, n) 洗牌单列集合:random.shuffle(item)
# 产生指定位数的验证码 import random def random_code(count): code = '' for i in range(count): num = random.randint(1, 3) if num == 1: tag = str(random.randint(0, 9)) elif num == 2: tag = chr(random.randint(65, 90)) else: tag = chr(random.randint(97, 122)) code += tag return code print(random_code(6))
11. shutil:可以操作权限的处理文件模块
# 基于路径的文件复制: shutil.copyfile('source_file', 'target_file') # 基于流的文件复制: with open('source_file', 'rb') as r, open('target_file', 'wb') as w: shutil.copyfileobj(r, w) # 递归删除目标目录 shutil.rmtree('target_folder') # 文件移动 shutil.remove('old_file', 'new_file') # 文件夹压缩 shutil.make_archive('file_name', 'format', 'archive_path') # 文件夹解压 shutil.unpack_archive('unpack_file', 'unpack_name', 'format')
12. shevle:可以用字典存取数据到文件的序列化模块
# 将序列化文件操作dump与load进行封装 s_dic = shelve.open("target_file", writeback=True) # 注:writeback允许序列化的可变类型,可以直接修改值 # 序列化::存 s_dic['key1'] = 'value1' s_dic['key2'] = 'value2' # 反序列化:取 print(s_dic['key1']) # 文件这样的释放 s_dic.close()
12.1 三流:标准输入输出错误流
import sys sys.stdout.write('msg') sys.stderr.write('msg') msg = sys.stdin.readline() # print默认是对sys.stdout.write('msg') + sys.stdout.write('\n')的封装 # 格式化结束符print:print('msg', end='')
13. hashlib模块:加密
import hashlib # 基本使用 cipher = hashlib.md5('需要加密的数据的二进制形式'.encode('utf-8')) print(cipher.hexdigest()) # 加密结果码 # 加盐 cipher = hashlib.md5() cipher.update('前盐'.encode('utf-8')) cipher.update('需要加密的数据'.encode('utf-8')) cipher.update('后盐'.encode('utf-8')) print(cipher.hexdigest()) # 加密结果码 # 其他算法 cipher = hashlib.sha3_256(b'') print(cipher.hexdigest()) cipher = hashlib.sha3_512(b'') print(cipher.hexdigest())
14. hmac模块:加密
# 必须加盐 cipher = hmac.new('盐'.encode('utf-8')) cipher.update('数据'.encode('utf-8')) print(cipher.hexdigest())
15. configparser模块:操作配置文件
# my.ini [section1] option1_1 = value1_1 option1_2 = value1_2 [section2] option2_1 = value2_1 option2_2 = value2_2 import configparser parser = configparser.ConfigParser() # 读 parser.read('my.ini', encoding='utf-8') # 所有section print(parser.sections()) # 某section下所有option print(parser.options('section_name')) # 某section下某option对应的值 print(parser.get('section_name', 'option_name')) # 写 parser.set('section_name', 'option_name', 'value') parser.write(open('my.ini', 'w'))
16. subprocess模块:操作shell命令
import subprocess order = subprocess.Popen('终端命令', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) suc_res = order.stdout.read().decode('系统默认编码') err_res = order.stderr.read().decode('系统默认编码') order = subprocess.run('终端命令', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) suc_res = order.stdout.decode('系统默认编码') err_res = order.stderr.decode('系统默认编码')
17. xlrd模块:excel读
年终报表 教学部 市场部 咨询部 总计 Jan-19 10 15 5 30 Feb-19 10 15 5 30 Mar-19 10 15 5 30 Apr-19 10 15 5 30 May-19 10 15 5 30 Jun-19 10 15 5 30 Jul-19 10 15 5 30 Aug-19 10 15 5 30 Sep-19 10 15 5 30 Oct-19 10 15 5 30 Nov-19 10 15 5 30 Dec-19 10 15 5 30 import xlrd # 读取文件 work_book = xlrd.open_workbook("机密数据.xlsx") # 获取所有所有表格名称 print(work_book.sheet_names()) # 选取一个表 sheet = work_book.sheet_by_index(1) # 表格名称 print(sheet.name) # 行数 print(sheet.nrows) # 列数 print(sheet.ncols) # 某行全部 print(sheet.row(6)) # 某列全部 print(sheet.col(6)) # 某行列区间 print(sheet.row_slice(6, start_colx=0, end_colx=4)) # 某列行区间 print(sheet.col_slice(3, start_colx=3, end_colx=6)) # 某行类型 | 值 print(sheet.row_types(6), sheet.row_values(6)) # 单元格 print(sheet.cell(6,0).value) # 取值 print(sheet.cell(6,0).ctype) # 取类型 print(sheet.cell_value(6,0)) # 直接取值 print(sheet.row(6)[0]) # 时间格式转换 print(xlrd.xldate_as_datetime(sheet.cell(6, 0).value, 0))
18. xlwt模块:excel写
import xlwt # 创建工作簿 work = xlwt.Workbook() # 创建一个表 sheet = work.add_sheet("员工信息数据") # 创建一个字体对象 font = xlwt.Font() font.name = "Times New Roman" # 字体名称 font.bold = True # 加粗 font.italic = True # 斜体 font.underline = True # 下划线 # 创建一个样式对象 style = xlwt.XFStyle() style.font = font keys = ['Owen', 'Zero', 'Egon', 'Liuxx', 'Yhh'] # 写入标题 for k in keys: sheet.write(0, keys.index(k), k, style) # 写入数据 sheet.write(1, 0, 'cool', style) # 保存至文件 work.save("test.xls")
19. xml模块
<?xml version="1.0"?> <data> <country name="Liechtenstein"> <rank updated="yes">2</rank> <year>2008</year> <gdppc>141100</gdppc> <neighbor name="Austria" direction="E"/> <neighbor name="Switzerland" direction="W"/> </country> <country name="Singapore"> <rank updated="yes">5</rank> <year>2011</year> <gdppc>59900</gdppc> <neighbor name="Malaysia" direction="N"/> </country> <country name="Panama"> <rank updated="yes">69</rank> <year>2011</year> <gdppc>13600</gdppc> <neighbor name="Costa Rica" direction="W"/> <neighbor name="Colombia" direction="E"/> </country> </data> import xml.etree.ElementTree as ET # 读文件 tree = ET.parse("xmltest.xml") # 根节点 root_ele = tree.getroot() # 遍历下一级 for ele in root_ele: print(ele) # 全文搜索指定名的子标签 ele.iter("标签名") # 非全文查找满足条件的第一个子标签 ele.find("标签名") # 非全文查找满足条件的所有子标签 ele.findall("标签名") # 标签名 ele.tag # 标签内容 ele.text # 标签属性 ele.attrib # 修改 ele.tag = "新标签名" ele.text = "新文本" ele.set("属性名", "新属性值") # 删除 sup_ele.remove(sub_ele) # 添加 my_ele=ET.Element('myEle') my_ele.text = 'new_ele' my_ele.attrib = {'name': 'my_ele'} root.append(my_ele) # 重新写入硬盘 tree.write("xmltest.xml")