13 import os.path |
13 import os.path |
14 import sys |
14 import sys |
15 import unittest |
15 import unittest |
16 import xml |
16 import xml |
17 import stat |
17 import stat |
|
18 import logging |
18 from cStringIO import StringIO |
19 from cStringIO import StringIO |
19 from xml.etree import ElementTree as etree |
20 from xml.etree import ElementTree as etree |
20 from lib import scan, main |
21 from lib import scan, main, XmlParser, StubXmlParser |
21 |
22 from optparse import OptionParser |
22 |
23 |
23 __version__ = '0.1' |
24 __version__ = '0.1' |
24 |
25 |
25 |
26 |
26 UNSAFE_CHARS = ("\n", "\t", ":", "?", ",", "=", ".", "\\", "/", "[", "]", "|", "<", ">", "+", ";", '"', "-") |
27 UNSAFE_CHARS = ("\n", "\t", ":", "?", ",", "=", ".", "\\", "/", "[", "]", "|", "<", ">", "+", ";", '"', "-") |
27 |
|
28 |
|
29 class XmlParser(object): |
|
30 """ |
|
31 Simple class that reads an XML and returns its id |
|
32 |
|
33 >>> xp = XmlParser() |
|
34 >>> xp.parse(StringIO("<root id='rootid'>some content</root>")) |
|
35 'rootid' |
|
36 """ |
|
37 def parse(self, xmlfile): |
|
38 try: |
|
39 root = etree.parse(xmlfile).getroot() |
|
40 except xml.parsers.expat.ExpatError, e: |
|
41 sys.stderr.write("ERROR: %s could not be parse: %s\n" % (xmlfile, str(e))) |
|
42 return "" |
|
43 if 'id' not in root.attrib: |
|
44 return "" |
|
45 return root.attrib['id'] |
|
46 |
28 |
47 |
29 |
48 class FileRenamer(object): |
30 class FileRenamer(object): |
49 """ |
31 """ |
50 Given an xml file this class returns a MODE compatable filename |
32 Given an xml file this class returns a MODE compatable filename |
51 |
33 |
52 >>> fr = FileRenamer(xmlparser=StubXmlParser()) |
34 >>> fr = FileRenamer(xmlparser=StubXmlParser()) |
53 >>> fr.rename(r"c:\\temp\\xml\\class_c_active_scheduler.xml") |
35 >>> fr.rename(r"c:\\temp\\xml\\class_c_active_scheduler.xml") |
54 'class_c_active_scheduler=GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F=1=en=.reference' |
36 'class_c_active_scheduler=GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F=1=en=.reference' |
55 """ |
37 """ |
56 def __init__(self, xmlparser=XmlParser()): |
38 def __init__(self, xmlparser=XmlParser(), publishing_target="mode"): |
57 self.parser = xmlparser |
39 self.parser = xmlparser |
|
40 self.publishing_target = publishing_target |
58 |
41 |
59 def _escape(self, filename): |
42 def _escape(self, filename): |
60 for char in UNSAFE_CHARS: |
43 for char in UNSAFE_CHARS: |
61 filename = filename.replace(char, "") |
44 filename = filename.replace(char, "") |
62 filename = filename.encode('unicode-escape', 'ignore') |
45 filename = filename.encode('unicode-escape', 'ignore') |
72 Test-Document=GUID-1234=1=en=.reference |
55 Test-Document=GUID-1234=1=en=.reference |
73 """ |
56 """ |
74 id = self.parser.parse(xmlfile) |
57 id = self.parser.parse(xmlfile) |
75 filename = os.path.basename(xmlfile) |
58 filename = os.path.basename(xmlfile) |
76 filename, ext = os.path.splitext(filename) |
59 filename, ext = os.path.splitext(filename) |
77 filename = self._escape(filename) |
60 if self.publishing_target == "mode": |
78 newfilename = "=".join((filename, id, '1', 'en', '')) |
61 filename = self._escape(filename) |
79 ext = ext if ext == ".ditamap" else ".reference" |
62 newfilename = "=".join((filename, id, '1', 'en', '')) |
|
63 ext = ext if ext == ".ditamap" else ".reference" |
|
64 elif self.publishing_target == "ditaot": |
|
65 newfilename = id |
|
66 ext = ext = ext if ext == ".ditamap" else ".xml" |
80 return newfilename + ext |
67 return newfilename + ext |
81 |
68 |
82 |
69 |
83 def rename(indir): |
70 def rename(indir, publishing_target): |
84 fr = FileRenamer() |
71 fr = FileRenamer(publishing_target=publishing_target) |
85 for filepath in scan(indir): |
72 for filepath in scan(indir): |
86 newfilename = os.path.join(os.path.dirname(filepath), fr.rename(filepath)) |
73 newfilename = os.path.join(os.path.dirname(filepath), fr.rename(filepath)) |
87 os.chmod(filepath, stat.S_IWRITE) |
74 try: |
88 #print "Renaming %s to %s" % (filepath, newfilename) |
75 os.chmod(filepath, stat.S_IWRITE) |
89 os.rename(filepath, newfilename) |
76 except Exception, e: |
|
77 logging.error('Unable to make file \"%s\" writable, error was: %s' % (filepath, e)) |
|
78 continue |
|
79 else: |
|
80 logging.debug("Renaming %s to %s" % (filepath, newfilename)) |
|
81 try: |
|
82 os.rename(filepath, newfilename) |
|
83 except Exception, e: |
|
84 logging.error('Unable to rename file \"%s\" to \"%s\", error was: %s' % (filepath, newfilename, e)) |
90 |
85 |
|
86 def main(): |
|
87 usage = "usage: %prog <Path to the XML content> <publishing_target>\n" |
|
88 parser = OptionParser(usage, version='%prog ' + __version__) |
|
89 parser.add_option("-p", dest="publishing_target", type="choice", choices=["mode", "ditaot"], default="mode", |
|
90 help="Publishing Target: mode|ditaot, [default: %default]") |
|
91 parser.add_option("-l", "--loglevel", type="int", default=30, help="Log Level (debug=10, info=20, warning=30, [error=40], critical=50)") |
|
92 (options, args) = parser.parse_args() |
|
93 if len(args) < 1: |
|
94 parser.print_help() |
|
95 parser.error("Please supply the path to the XML content") |
|
96 |
|
97 if options.loglevel: |
|
98 logging.basicConfig(level=options.loglevel) |
|
99 |
|
100 rename(args[0],options.publishing_target) |
91 |
101 |
92 if __name__ == '__main__': |
102 if __name__ == '__main__': |
93 sys.exit(main(rename, __version__)) |
103 sys.exit(main()) |
94 |
104 |
95 ###################################### |
105 ###################################### |
96 # Test code |
106 # Test code |
97 ###################################### |
107 ###################################### |
98 class StubXmlParser(object): |
|
99 def parse(self, path): |
|
100 return "GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F" |
|
101 |
|
102 |
|
103 class TestXmlParser(unittest.TestCase): |
|
104 def test_i_issue_a_warning_and_continue_if_a_file_is_invalid(self): |
|
105 xml = XmlParser() |
|
106 try: |
|
107 xml.parse(StringIO("<foo><bar</foo>")) |
|
108 except Exception: |
|
109 self.fail("I shouldn't have raised an exception") |
|
110 |
|
111 def test_i_issue_a_warning_and_continue_if_a_file_does_not_have_an_id(self): |
|
112 xml = XmlParser() |
|
113 try: |
|
114 id = xml.parse(StringIO(brokencxxclass)) |
|
115 except Exception: |
|
116 self.fail("I shouldn't have raised an exception") |
|
117 self.assertTrue(id == "") |
|
118 |
|
119 def test_i_return_a_files_id(self): |
|
120 xml = XmlParser() |
|
121 id = xml.parse(StringIO(cxxclass)) |
|
122 self.assertTrue(id == "class_c_active_scheduler") |
|
123 |
|
124 |
108 |
125 class TestFileRenamer(unittest.TestCase): |
109 class TestFileRenamer(unittest.TestCase): |
126 def test_i_can_return_a_files_new_name(self): |
110 def test_i_can_return_a_files_new_mode_name(self): |
127 fr = FileRenamer(xmlparser=StubXmlParser()) |
111 fr = FileRenamer(xmlparser=StubXmlParser(),publishing_target="mode") |
128 newfile = fr.rename("hello.xml") |
112 newfile = fr.rename("hello.xml") |
129 self.assertTrue(newfile == "hello=GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F=1=en=.reference") |
113 self.assertTrue(newfile == "hello=GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F=1=en=.reference") |
130 |
114 |
131 def test_i_can_return_a_ditamaps_new_name(self): |
115 def test_i_can_return_a_ditamaps_new_mode_name(self,publishing_target="mode"): |
132 fr = FileRenamer(xmlparser=StubXmlParser()) |
116 fr = FileRenamer(xmlparser=StubXmlParser()) |
133 newfile = fr.rename("hello.ditamap") |
117 newfile = fr.rename("hello.ditamap") |
134 self.assertTrue(newfile == "hello=GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F=1=en=.ditamap") |
118 self.assertTrue(newfile == "hello=GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F=1=en=.ditamap") |
135 |
119 |
136 |
120 |
140 self.assertTrue(newfile == "hello=GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F=1=en=.reference") |
124 self.assertTrue(newfile == "hello=GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F=1=en=.reference") |
141 |
125 |
142 def test_i_can_remove_incompatable_characters_from_a_filename(self): |
126 def test_i_can_remove_incompatable_characters_from_a_filename(self): |
143 fr = FileRenamer(xmlparser=StubXmlParser()) |
127 fr = FileRenamer(xmlparser=StubXmlParser()) |
144 newfile = fr.rename("hello:?,=..xml") |
128 newfile = fr.rename("hello:?,=..xml") |
145 self.assertTrue(newfile == "hello=GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F=1=en=.reference") |
129 self.assertTrue(newfile , "hello=GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F=1=en=.reference") |
146 |
130 |
147 |
131 def test_i_can_return_a_files_new_ditaot_name(self): |
148 brokencxxclass = """<?xml version='1.0' encoding='UTF-8' standalone='no'?> |
132 fr = FileRenamer(xmlparser=StubXmlParser(),publishing_target="ditaot") |
149 <!DOCTYPE cxxClass PUBLIC "-//NOKIA//DTD DITA C++ API Class Reference Type v0.1.0//EN" "dtd/cxxClass.dtd" > |
133 newfile = fr.rename("hello.xml") |
150 <cxxClass> |
134 self.assertEquals(newfile, "GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F.xml") |
151 <apiName>CActiveScheduler</apiName> |
|
152 <shortdesc/> |
|
153 </cxxClass> |
|
154 """ |
|
155 |
135 |
156 cxxclass = """<?xml version='1.0' encoding='UTF-8' standalone='no'?> |
136 def test_i_can_return_a_ditamaps_new_ditaot_name(self): |
157 <!DOCTYPE cxxClass PUBLIC "-//NOKIA//DTD DITA C++ API Class Reference Type v0.1.0//EN" "dtd/cxxClass.dtd" > |
137 fr = FileRenamer(xmlparser=StubXmlParser(),publishing_target="ditaot") |
158 <cxxClass id="class_c_active_scheduler"> |
138 newfile = fr.rename("hello.ditamap") |
159 <apiName>CActiveScheduler</apiName> |
139 self.assertEquals(newfile, "GUID-BED8A733-2ED7-31AD-A911-C1F4707C67F.ditamap") |
160 <shortdesc/> |
|
161 </cxxClass> |
|
162 """ |
|